QgsPostgresProvider源码分析(2)之QgsPo

2022-06-23  本文已影响0人  NullUser

2.QgsPostgresProvider类

class QgsPostgresProvider final: public QgsVectorDataProvider
{
};

部分成员变量:

(1) QgsPostgresProvider()

QgsPostgresProvider::QgsPostgresProvider( QString const &uri, const ProviderOptions &options,
    QgsDataProvider::ReadFlags flags )
  : QgsVectorDataProvider( uri, options, flags )
  , mShared( new QgsPostgresSharedData )
{
......
  // 1) 
  mUri = QgsDataSourceUri( uri );
  // populate members from the uri structure
  mSchemaName = mUri.schema();
  mTableName = mUri.table();
  mGeometryColumn = mUri.geometryColumn();
  mBoundingBoxColumn = mUri.param( "bbox" );
......
  // 2)
  if ( mSchemaName.isEmpty() && mTableName.startsWith( '(' ) && mTableName.endsWith( ')' ) )
  {
    mIsQuery = true;
    setQuery( mTableName );
    mTableName.clear();
  }
  else
  {
    mIsQuery = false;

    setQuery( ( !mSchemaName.isEmpty() ? quotedIdentifier( mSchemaName ) + '.' : QString() )
              + ( !mTableName.isEmpty() ? quotedIdentifier( mTableName ) : QString() ) );
  }
......
  if ( !hasSufficientPermsAndCapabilities() ) // check permissions and set capabilities
  {
    disconnectDb();
    return;
  }
……
  // 3)
  if ( !hasSufficientPermsAndCapabilities() ) // check permissions and set capabilities
  {
    disconnectDb();
    return;
  }
  // 4)
  if ( !getGeometryDetails() ) // gets srid, geometry and data type
  {
    // the table is not a geometry table
    QgsMessageLog::logMessage( tr( "Invalid PostgreSQL layer" ), tr( "PostGIS" ) );
    disconnectDb();
    return;
  }
……
}

构造函数

  1. 首先从uri里提取出表名、几何列等基本信息放到成员变量里。
  2. 然后提取schema,表名等,通过setQuery()设置查询源,如:schema为sde,表名为testTable,则查询源为"sde"."testTable",该值由成员变量mQuery保存,后续的sql语句将直接使用该变量值。
  3. 通过hasSufficientPermsAndCapabilities()判断数据库增删改查等权限并将权限赋值给mEnabledCapabilities。
  4. 从数据库查询srid,geometry等基本信息。

(2) featureSource() override

QgsAbstractFeatureSource *QgsPostgresProvider::featureSource() const
{
  return new QgsPostgresFeatureSource( this );
}

featureSource()返回new的QgsPostgresFeatureSource对象,QgsPostgresFeatureSource类保存了要素的一些源信息,如:几何列、属性字段、srid等,在QgsPostgresFeatureSource构造的时候,会将QgsPostgresProvider的一些基本信息赋值给QgsPostgresFeatureSource。

(3) storageType() override

QString QgsPostgresProvider::storageType() const
{
  return QStringLiteral( "PostgreSQL database with PostGIS extension" );
}

storageType()返回储存类型,其是一个字符串描述。

(4) crs() override

QgsCoordinateReferenceSystem QgsPostgresProvider::crs() const
{
  QgsCoordinateReferenceSystem srs;
  int srid = mRequestedSrid.isEmpty() ? mDetectedSrid.toInt() : mRequestedSrid.toInt();
  return sridToCrs( srid, connectionRO() );
}

crs()获取坐标参考系,QgsPostgresProvider内部有两个变量,mRequestedSrid:通过uri传入的srid;mDetectedSrid:从数据库或数据源检测出的srid。优先使用mRequestedSrid获取具体的坐标参考系对象。

(5) getFeatures() override

QgsFeatureIterator QgsPostgresProvider::getFeatures( const QgsFeatureRequest &request ) const
{
……
  QgsPostgresFeatureSource *featureSrc = static_cast<QgsPostgresFeatureSource *>( featureSource() );
  return QgsFeatureIterator( new QgsPostgresFeatureIterator( featureSrc, true, request ) );
}

getFeatures()返回feature迭代器,通过迭代器遍历feature。QgsFeatureIterator有一个QgsAbstractFeatureIterator类型的指针成员变量mIter,QgsFeatureIteratord的接口最终是调用了mIter的函数。而具体的函数实现由QgsPostgresFeatureIterator实现。

(6) wkbType() override

QgsWkbTypes::Type QgsPostgresProvider::wkbType() const
{
  return mRequestedGeomType != QgsWkbTypes::Unknown ? mRequestedGeomType : mDetectedGeomType;
}

wkbType()返回feature的类型,类型声明在QgsWkbTypes::Type中。

(7) layerMetadata() override

QgsLayerMetadata QgsPostgresProvider::layerMetadata() const
{
  return mLayerMetadata;
}

layerMetadata()返回QgsLayerMetadata类型的成员变量mLayerMetadata。QgsLayerMetadata类为地图图层储存了一些结构化的元数据。

(8) featureCount() override

long long QgsPostgresProvider::featureCount() const
{
  long long featuresCounted = mShared->featuresCounted();
  if ( featuresCounted >= 0 )
    return featuresCounted;
……
  // 2)
  if ( !mIsQuery && mUseEstimatedMetadata )
  {
    if ( relkind() == Relkind::View && connectionRO()->pgVersion() >= 90000 )
    {
      sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT 1 FROM %1%2" ).arg( mQuery, filterWhereClause() );  
      ……
    }
    else
    {
      sql = QStringLiteral( "SELECT reltuples::bigint FROM pg_catalog.pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
      QgsPostgresResult result( connectionRO()->PQexec( sql ) );
      num = result.PQgetvalue( 0, 0 ).toLongLong();
    }
  }
  else
  {
    sql = QStringLiteral( "SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() );
    QgsPostgresResult result( connectionRO()->PQexec( sql ) );
    num = result.PQgetvalue( 0, 0 ).toLongLong();
  }

  mShared->setFeaturesCounted( num );
}

featureCount()返回要素数量。

  1. 从mShared中获取要素数量,mShared为std::shared_ptr<QgsPostgresSharedData> 类型。
  2. 如果mShared获取不到要素数量,则从数据库中查询要素数量,并赋值给mShared。

(9) empty() override

bool QgsPostgresProvider::empty() const
{
  QString sql = QStringLiteral( "SELECT EXISTS (SELECT * FROM %1%2 LIMIT 1)" ).arg( mQuery, filterWhereClause() );
  QgsPostgresResult res( connectionRO()->PQexec( sql ) );
  if ( res.PQresultStatus() != PGRES_TUPLES_OK )
  {
    pushError( res.PQresultErrorMessage() );
    return false;
  }

  return res.PQgetvalue( 0, 0 ) != QLatin1String( "t" );
}

empty()从数据库查询是否至少有一个要素。

(10) extent() override

QgsRectangle QgsPostgresProvider::extent() const
{
  if ( !isValid() || mGeometryColumn.isNull() )
    return QgsRectangle();

  if ( mSpatialColType == SctGeography )
    return QgsRectangle( -180.0, -90.0, 180.0, 90.0 );

  if ( mLayerExtent.isEmpty() )
  {
    ……
    // get the extents
    if ( !mIsQuery && mUseEstimatedMetadata )
    {
      ……
            sql = QStringLiteral( "SELECT %1(%2,%3,%4)" )
                  .arg( connectionRO()->majorVersion() < 2 ? "estimated_extent" :
                        ( connectionRO()->majorVersion() == 2 && connectionRO()->minorVersion() < 1 ? "st_estimated_extent" : "st_estimatedextent" ),
                        quotedValue( mSchemaName ),
                        quotedValue( mTableName ),
                        quotedValue( mGeometryColumn ) );  
      ……
     }
    if ( ext.isEmpty() )
    {
      sql = QStringLiteral( "SELECT %1(%2%3) FROM %4%5" )
            .arg( connectionRO()->majorVersion() < 2 ? "extent" : "st_extent",
                  quotedIdentifier( mBoundingBoxColumn ),
                  mSpatialColType == SctPcPatch ? "::geometry" : "",
                  mQuery,
                  filterWhereClause() );
    ……
    }
    
}

extent()返回范围,在成员变量mLayerExtent为空时,则从数据库查找范围。

(11) fields() override

QgsFields QgsPostgresProvider::fields() const
{
  return mAttributeFields;
}

fields()返回属性字段,该数据由QgsFields类型的成员变量mAttributeFields储存。QgsFields类可以添加QgsField对象,每个对象即代表一个属性字段,QgsField可以设置属性名setName()、类型setType()、类型名setTypeName()、长度setLength()、精度setPrecision()、注释setComment()。

(12) addFeatures() override

bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags )
{
……
    // Optimization: if we have a single primary key column whose default value
    // is a sequence, and that none of the features have a value set for that
    // column, then we can completely omit inserting it.
    bool skipSinglePKField = false;
    bool overrideIdentity = false;
    // 1)
    if ( ( mPrimaryKeyType == PktInt || mPrimaryKeyType == PktInt64 || mPrimaryKeyType == PktFidMap || mPrimaryKeyType == PktUint64 ) )
    {
    ……
        for ( int i = 0; i < flist.size(); i++ )
        {
          QgsAttributes attrs2 = flist[i].attributes();
          QVariant v2 = attrs2.value( idx, QVariant( QVariant::Int ) );
          // a PK field with a sequence val is auto populate by QGIS with this default
          // we are only interested in non default values
          if ( !v2.isNull() && v2.toString() != defaultValue )
          {
            foundNonEmptyPK = true;
            break;
          }
        } //for
        skipSinglePKField = !foundNonEmptyPK;
    ……
    }
    // 2)
    QgsAttributes attributevec = flist[0].attributes();
    // look for unique attribute values to place in statement instead of passing as parameter
    for ( int idx = 0; idx < attributevec.count(); ++idx )
    {……}
    insert += QStringLiteral( ") %1VALUES (%2)" ).arg( overrideIdentity ? "OVERRIDING SYSTEM VALUE " : "" ).arg( values );
    ……
    // 3)
    for ( QgsFeatureList::iterator features = flist.begin(); features != flist.end(); ++features )
    {……}
    ……

}

addFeatures()添加要素。

  1. 从flist中判断每个要素的主键属性,如果有值且非默认值,则认为有主键值,skipSinglePKField置false,否则置true。skipSinglePKField:当只有单主键时,如果主键是序列,那么插入要素时可以考虑忽略主键。
  2. 遍历第一个要素的所有属性,组装完Insert语句的字段。
  3. 遍历要素,获取每个要素的属性值,组装Values。
  4. 执行INSERT语句,将要素入库。

(13) deleteFeatures() override

bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds &ids )
{}

deleteFeature()从数据库删除要素。

(14) addAttributes() override

bool QgsPostgresProvider::addAttributes( const QList<QgsField> &attributes )
{
    ……
    QString sql = QStringLiteral( "ALTER TABLE %1 " ).arg( mQuery );
    for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
    {
      ……
      sql.append( QStringLiteral( "%1ADD COLUMN %2 %3" ).arg( delim, quotedIdentifier( iter->name() ), type ) );
      ……
    }
    ……
    for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
    {
      if ( !iter->comment().isEmpty() )
      {
        sql = QStringLiteral( "COMMENT ON COLUMN %1.%2 IS %3" )
              .arg( mQuery,
                    quotedIdentifier( iter->name() ),
                    quotedValue( iter->comment() ) );
      ……
      }
    }
    ……
    loadFields();
}

addAttributes()添加属性,通过ALERT TABLE语句完成,为表添加新的列。并在添加完成后调用私有函数loadFields()加载新的属性。
deleteAttribute()、renameAttributes()同样通过改表完成。

(15) changeAttributeValues() override、changeGeometryValues() override、changeFeatures() override

bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_map )
{}
bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
{}
bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_map,
    const QgsGeometryMap &geometry_map )
{}

changeAttributeValues()、changeGeometryValues()、changeFeatures()均通过UPDATE语句修改数据表。

(16) capabilities() override

QgsVectorDataProvider::Capabilities QgsPostgresProvider::capabilities() const
{
  return mEnabledCapabilities;
}

capabilities()返回Provider的能力,由QgsVectorDataProvider::Capability枚举组合。

(17) hasSpatialIndex() override

QgsFeatureSource::SpatialIndexPresence QgsPostgresProvider::hasSpatialIndex() const
{
  QgsPostgresProviderConnection conn( mUri.uri(), QVariantMap() );
  try
  {
    return conn.spatialIndexExists( mUri.schema(), mUri.table(), mUri.geometryColumn() ) ? SpatialIndexPresent : SpatialIndexNotPresent;
  }
  catch ( QgsProviderConnectionException & )
  {
    return SpatialIndexUnknown;
  }
}
bool QgsPostgresProviderConnection::spatialIndexExists( const QString &schema, const QString &name, const QString &geometryColumn ) const
{
  checkCapability( Capability::SpatialIndexExists );

  const QList<QVariantList> res = executeSql( QStringLiteral( R"""(SELECT COUNT(*)
                                                              FROM pg_class t, pg_class i, pg_namespace ns, pg_index ix, pg_attribute a
                                                              WHERE
                                                                  t.oid=ix.indrelid
                                                                  AND t.relnamespace=ns.oid
                                                                  AND i.oid=ix.indexrelid
                                                                  AND a.attrelid=t.oid
                                                                  AND a.attnum=ANY(ix.indkey)
                                                                  AND t.relkind IN ('r', 'm')
                                                                  AND ns.nspname=%1
                                                                  AND t.relname=%2
                                                                  AND a.attname=%3;
                                                              )""" ).arg(
                                    QgsPostgresConn::quotedValue( schema ),
                                    QgsPostgresConn::quotedValue( name ),
                                    QgsPostgresConn::quotedValue( geometryColumn ) ) );
  return !res.isEmpty() && !res.at( 0 ).isEmpty() && res.at( 0 ).at( 0 ).toBool();
}

hasSpatialIndex()从数据库查询是否有空间索引。

(18) hasMetadata() override

bool QgsPostgresProvider::hasMetadata() const
{
  bool hasMetadata = true;
  QgsPostgresProvider::Relkind kind = relkind();

  if ( kind == Relkind::View || kind == Relkind::MaterializedView )
  {
    hasMetadata = false;
  }

  return hasMetadata;
}

hasMetadata判断数据源是否有元数据,在relkind()函数内通过SQL语句(SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid)获取类型

(19) vectorLayerTypeFlags() override

Qgis::VectorLayerTypeFlags QgsPostgresProvider::vectorLayerTypeFlags() const
{
  // 1)
  Qgis::VectorLayerTypeFlags flags;
  if ( mValid && mIsQuery )
  {
    flags.setFlag( Qgis::VectorLayerTypeFlag::SqlQuery );
  }
  return flags;
}

vectorLayerTypeFlags()返回矢量图层的类型标志,SqlQuery代表从Sql查询出矢量数据?

(20) crsToSrid()、sridToCrs()

static QMutex sMutex;
static QMap<int, QgsCoordinateReferenceSystem> sCrsCache;
int QgsPostgresProvider::crsToSrid( const QgsCoordinateReferenceSystem &crs, QgsPostgresConn *conn )
{
  QMutexLocker locker( &sMutex );
……
}
QgsCoordinateReferenceSystem QgsPostgresProvider::sridToCrs( int srid, QgsPostgresConn *conn )
{
  QMutexLocker locker( &sMutex );
……
}

crsToSrid()和sridToCrs()提供srid和QgsCoordinateReferenceSystem坐标对象的相互转换,在转换时首先从sCrsCache坐标缓存中查找,如果没有再访问数据库的spatial_ref_sys表获取数据,并将新数据存入缓存。两个函数均用一个互斥量加锁,确保了sCrsCache缓存的线程安全。

(21) setQuery()

void QgsPostgresProvider::setQuery( const QString &query )
{
  mQuery = query;

  mKind = Relkind::NotSet;
}

setQuery()设置查询的源,大多数情况下为表名,如:'sde'.'testTable',设置新源后,对应的relkind也应该重置。

(22) hasSufficientPermsAndCapabilities()

bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
{
  ……
  if ( !mIsQuery )
  {
    // 1)
    // Check that we can read from the table (i.e., we have select permission).
    QString sql = QStringLiteral( "SELECT * FROM %1 LIMIT 1" ).arg( mQuery );
    ……
    // 2)
      if ( connectionRO()->pgVersion() >= 80400 )
      {
        sql = QString( "SELECT "
                       "has_table_privilege(%1,'DELETE'),"
                       "has_any_column_privilege(%1,'UPDATE'),"
                       "%2"
                       "has_table_privilege(%1,'INSERT'),"
                       "current_schema()" )
              .arg( quotedValue( mQuery ),
                    mGeometryColumn.isNull()
                    ? QStringLiteral( "'f'," )
                    : QStringLiteral( "has_column_privilege(%1,%2,'UPDATE')," )
                    .arg( quotedValue( mQuery ),
                          quotedValue( mGeometryColumn ) )
                  );
      }
      else
      {
        sql = QString( "SELECT "
                       "has_table_privilege(%1,'DELETE'),"
                       "has_table_privilege(%1,'UPDATE'),"
                       "has_table_privilege(%1,'UPDATE'),"
                       "has_table_privilege(%1,'INSERT'),"
                       "current_schema()" )
              .arg( quotedValue( mQuery ) );
      }
     ……
      //3)
      sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE "
                     "pg_class.relnamespace=pg_namespace.oid AND "
                     "%3 AND "
                     "relname=%1 AND nspname=%2" )
            .arg( quotedValue( mTableName ),
                  quotedValue( mSchemaName ),
                  connectionRO()->pgVersion() < 80100 ? "pg_get_userbyid(relowner)=current_user" : "pg_has_role(relowner,'MEMBER')" );
      testAccess = connectionRO()->PQexec( sql );
      if ( testAccess.PQresultStatus() == PGRES_TUPLES_OK && testAccess.PQntuples() == 1 )
      {
        mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes | QgsVectorDataProvider::RenameAttributes;
      }
  ……
  //4)
  // supports layer metadata
  mEnabledCapabilities |= QgsVectorDataProvider::ReadLayerMetadata;
}

hasSufficientPermsAndCapabilities()获取provider的权限和能力,并赋值给mEnabledCapabilities成员变量,由QgsVectorDataProvider::Capability枚举组合。

  1. 先尝试能否查询出数据,如果不能直接返回。
  2. 获取对表的CRUD权限。
  3. 判断属性的操作权限。
  4. 按需自行增加权限。

(23) loadFields()

bool QgsPostgresProvider::loadFields()
{    
    ......
    // 1)
    // Get the table description
    sql = QStringLiteral( "SELECT description FROM pg_description WHERE objoid=regclass(%1)::oid AND objsubid=0" ).arg( quotedValue( mQuery ) );
    ......
  // 2)
  // Populate the field vector for this layer. The field vector contains
  // field name, type, length, and precision (if numeric)
  sql = QStringLiteral( "SELECT * FROM %1 LIMIT 0" ).arg( mQuery );
   ......
  if ( result.PQnfields() > 0 )
  {
    // 3)
    // Collect attribiute oids
    QSet<Oid> attroids;
    for ( int i = 0; i < result.PQnfields(); i++ )
    {
      Oid attroid = result.PQftype( i );
      attroids.insert( attroid );
    }
    // 4)
    // Collect table oids
    QSet<Oid> tableoids;
    for ( int i = 0; i < result.PQnfields(); i++ )
    {
      Oid tableoid = result.PQftable( i );
      if ( tableoid > 0 )
      {
        tableoids.insert( tableoid );
      }
    }
    ......
      // 5)
      // Collect formatted field types
      sql = QStringLiteral(
              "SELECT attrelid, attnum, pg_catalog.format_type(atttypid,atttypmod), pg_catalog.col_description(attrelid,attnum), pg_catalog.pg_get_expr(adbin,adrelid), atttypid, attnotnull::int, indisunique::int%1%2"
              " FROM pg_attribute"
              " LEFT OUTER JOIN pg_attrdef ON attrelid=adrelid AND attnum=adnum"
              // find unique constraints if present. Text cast required to handle int2vector comparison. Distinct required as multiple unique constraints may exist
              " LEFT OUTER JOIN ( SELECT DISTINCT indrelid, indkey, indisunique FROM pg_index WHERE indisunique ) uniq ON attrelid=indrelid AND attnum::text=indkey::text "
              " WHERE attrelid IN %3"
            ).arg( connectionRO()->pgVersion() >= 100000 ? QStringLiteral( ", attidentity" ) : QString(),
                   connectionRO()->pgVersion() >= 120000 ? QStringLiteral( ", attgenerated" ) : QString(),
                   tableoidsFilter );
        ......
        QString attGenerated = connectionRO()->pgVersion() >= 120000 ? fmtFieldTypeResult.PQgetvalue( i, 9 ) : "";
        fmtFieldTypeMap[attrelid][attnum] = formatType;
        descrMap[attrelid][attnum] = descr;
        defValMap[attrelid][attnum] = defVal;
        attTypeIdMap[attrelid][attnum] = attType;
        notNullMap[attrelid][attnum] = attNotNull;
        uniqueMap[attrelid][attnum] = uniqueConstraint;
        identityMap[attrelid][attnum] = attIdentity.isEmpty() ? " " : attIdentity;
        generatedMap[attrelid][attnum] = attGenerated.isEmpty() ? QString() : defVal;
      ......
      // 6)
      attroidsFilter = QStringLiteral( "WHERE oid in (%1)" ).arg( attroidsList.join( ',' ) );
 // Collect type info
  sql = QStringLiteral( "SELECT oid,typname,typtype,typelem,typlen FROM pg_type %1" ).arg( attroidsFilter );
  ......
  // 7)
  for ( int i = 0; i < result.PQnfields(); i++ )
  {
    QString fieldName = result.PQfname( i );
    if ( fieldName == mGeometryColumn )
      continue;
      ......
  }
  }
}

loadFields()加载属性字段,该函数内用到了许多PG连接库的接口,接口含义可参考PGSQL编程libpg官方文档

  1. 获取描述并保存在mDataComment变量。
  2. 从表中获取每个字段,LIMIT 0是因为只用获取字段属性。
  3. PQftype()获取该字段的类型oid并加入attroids。attroids和tableoids使用QSet,确保了数据唯一性。
  4. PQftable()获取字段所在表的oid并加入tableoids。
  5. 获取字段基本信息,并分别存入Map。
  1. 将attroids的值拼成过滤条件,然后从pg_type表中查询出字段类型的具体数据。
  2. 遍历最开始查询的每个字段,为每个字段匹配数据(字段名、类型、类型名、长度、精度、注释),并创建QgsField对象,将对象追加到mAttributeFields中。

(24) convertField()

bool QgsPostgresProvider::convertField( QgsField &field, const QMap<QString, QVariant> *options )
{
  switch ( field.type() )
  {
  }

  field.setTypeName( fieldType );
}

convertField()将数据域进行适当转换,主要是判断QgsField的type(),因为type()返回的是Qt的数据类型QVariant::LongLong、QVariant::String等,然后设置typeName为容易识别的类型。

(25) searchLayers()

QList<QgsVectorLayer *> QgsPostgresProvider::searchLayers( const QList<QgsVectorLayer *> &layers, const QString &connectionInfo, const QString &schema, const QString &tableName )
{
  QList<QgsVectorLayer *> result;
  const auto constLayers = layers;
  for ( QgsVectorLayer *layer : constLayers )
  {
    const QgsPostgresProvider *pgProvider = qobject_cast<QgsPostgresProvider *>( layer->dataProvider() );
    if ( pgProvider &&
         pgProvider->mUri.connectionInfo( false ) == connectionInfo && pgProvider->mSchemaName == schema && pgProvider->mTableName == tableName )
    {
      result.append( layer );
    }
  }
  return result;
}

searchLayers()从给定的图层列表中搜寻图层,通过获取已给图层的provider,并从provider获取connectionInfo、schema和table判断与形参是否一致。

上一篇 下一篇

猜你喜欢

热点阅读