query.diff

modified patch (some additions/cleanups; eg. allow subset strings) - Jürgen Fischer, 2010-04-19 01:09 PM

Download (51 KB)

View differences:

src/providers/postgres/qgspostgresprovider.h (working copy)
333 333

  
334 334
    QString whereClause( int featureId ) const;
335 335

  
336
    bool hasSufficientPermsAndCapabilities();
337

  
336 338
    const QgsField &field( int index ) const;
337 339

  
338 340
    /** Double quote a PostgreSQL identifier for placement in a SQL string.
......
383 385
     * Flag indicating if the layer data source is a valid PostgreSQL layer
384 386
     */
385 387
    bool valid;
388

  
386 389
    /**
390
     * provider references query (instead of a table)
391
     */
392
    bool isQuery;
393

  
394
    /**
387 395
     * Name of the table with no schema
388 396
     */
389 397
    QString mTableName;
390 398
    /**
391
     * Name of the table with schema included
399
     * Name of the table or subquery
392 400
     */
393
    QString mSchemaTableName;
401
    QString mQuery;
394 402
    /**
395 403
     * Name of the schema
396 404
     */
......
403 411
     * SQL statement used to limit the features retrieved
404 412
     */
405 413
    QString sqlWhereClause;
414

  
406 415
    /**
407 416
     * Primary key column for fetching features. If there is no primary key
408 417
     * the oid is used to fetch features.
......
546 555

  
547 556
    // A function that determines if the given schema.table.column
548 557
    // contains unqiue entries
549
    bool uniqueData( QString schemaName, QString tableName, QString colName );
558
    bool uniqueData( QString query, QString colName );
550 559

  
551 560
    // Function that populates the given cols structure.
552 561
    void findColumns( tableCols& cols );
src/providers/postgres/qgspostgresprovider.cpp (working copy)
82 82
  mTableName = mUri.table();
83 83
  geometryColumn = mUri.geometryColumn();
84 84
  sqlWhereClause = mUri.sql();
85

  
86
  if ( mSchemaName.isEmpty() &&
87
       mTableName.startsWith( "(select", Qt::CaseInsensitive ) &&
88
       mTableName.endsWith( ")" ) )
89
  {
90
    isQuery = true;
91
    mQuery = mTableName;
92
    mTableName = "";
93
  }
94
  else
95
  {
96
    isQuery = false;
97
    mQuery = mUri.quotedTablename();
98
  }
99

  
85 100
  primaryKey = mUri.keyColumn();
86 101
  mUseEstimatedMetadata = mUri.useEstimatedMetadata();
87 102

  
88
  // Keep a schema qualified table name for convenience later on.
89
  mSchemaTableName = mUri.quotedTablename();
90

  
91
  QgsDebugMsg( "Table name is " + mTableName );
92
  QgsDebugMsg( "SQL is " + sqlWhereClause );
93 103
  QgsDebugMsg( "Connection info is " + mUri.connectionInfo() );
94

  
95 104
  QgsDebugMsg( "Geometry column is: " + geometryColumn );
96 105
  QgsDebugMsg( "Schema is: " + mSchemaName );
97 106
  QgsDebugMsg( "Table name is: " + mTableName );
107
  QgsDebugMsg( "Query is: " + mQuery );
108
  QgsDebugMsg( "Where clause is: " + sqlWhereClause );
98 109

  
99 110
  connectionRW = NULL;
100 111
  connectionRO = Conn::connectDb( mUri.connectionInfo(), true );
......
104 115
    return;
105 116
  }
106 117

  
107
  QgsDebugMsg( "Checking for permissions on the relation" );
108

  
109
  // Check that we can read from the table (i.e., we have
110
  // select permission).
111
  QString sql = QString( "select * from %1 limit 1" ).arg( mSchemaTableName );
112
  Result testAccess = connectionRO->PQexec( sql );
113
  if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
118
  if ( !hasSufficientPermsAndCapabilities() ) // check permissions and set capabilities
114 119
  {
115
    showMessageBox( tr( "Unable to access relation" ),
116
                    tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
117
                    .arg( mSchemaTableName )
118
                    .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
119
                    .arg( sql ) );
120 120
    valid = false;
121 121
    disconnectDb();
122 122
    return;
123 123
  }
124 124

  
125
  if ( connectionRO->pgVersion() >= 80400 )
126
  {
127
    sql = QString( "SELECT "
128
                   "has_table_privilege(%1,'DELETE'),"
129
                   "has_any_column_privilege(%1,'UPDATE'),"
130
                   "has_column_privilege(%1,%2,'UPDATE'),"
131
                   "has_table_privilege(%1,'INSERT'),"
132
                   "current_schema()" )
133
          .arg( quotedValue( mSchemaTableName ) ).arg( quotedValue( geometryColumn ) );
134
  }
135
  else
136
  {
137
    sql = QString( "SELECT "
138
                   "has_table_privilege(%1,'DELETE'),"
139
                   "has_table_privilege(%1,'UPDATE'),"
140
                   "has_table_privilege(%1,'UPDATE'),"
141
                   "has_table_privilege(%1,'INSERT'),"
142
                   "current_schema()" )
143
          .arg( quotedValue( mSchemaTableName ) );
144
  }
145

  
146
  testAccess = connectionRO->PQexec( sql );
147
  if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
148
  {
149
    showMessageBox( tr( "Unable to access relation" ),
150
                    tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
151
                    .arg( mSchemaTableName )
152
                    .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
153
                    .arg( sql ) );
154
    valid = false;
155
    disconnectDb();
156
    return;
157
  }
158

  
159
  // postgres has fast access to features at id (thanks to primary key / unique index)
160
  // the latter flag is here just for compatibility
161
  enabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
162

  
163
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 0 ) ) == "t" )
164
  {
165
    // DELETE
166
    enabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
167
  }
168

  
169
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 1 ) ) == "t" )
170
  {
171
    // UPDATE
172
    enabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
173
  }
174

  
175
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 2 ) ) == "t" )
176
  {
177
    // UPDATE
178
    enabledCapabilities |= QgsVectorDataProvider::ChangeGeometries;
179
  }
180

  
181
  if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 3 ) ) == "t" )
182
  {
183
    // INSERT
184
    enabledCapabilities |= QgsVectorDataProvider::AddFeatures;
185
  }
186

  
187
  mCurrentSchema = QString::fromUtf8( PQgetvalue( testAccess, 0, 4 ) );
188
  if ( mCurrentSchema == mSchemaName )
189
  {
190
    mUri.clearSchema();
191
  }
192

  
193
  if ( mSchemaName == "" )
194
    mSchemaName = mCurrentSchema;
195

  
196
  sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE "
197
                 "pg_class.relnamespace=pg_namespace.oid AND "
198
                 "pg_get_userbyid(relowner)=current_user AND "
199
                 "relname=%1 AND nspname=%2" )
200
        .arg( quotedValue( mTableName ) )
201
        .arg( quotedValue( mSchemaName ) );
202
  testAccess = connectionRO->PQexec( sql );
203
  if ( PQresultStatus( testAccess ) == PGRES_TUPLES_OK && PQntuples( testAccess ) == 1 )
204
  {
205
    enabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes;
206
  }
207

  
208 125
  if ( !getGeometryDetails() ) // gets srid and geometry type
209 126
  {
210 127
    // the table is not a geometry table
......
485 402
      query += "," + fieldExpression( fld );
486 403
    }
487 404

  
488
    query += " from " + mSchemaTableName;
405
    query += " from " + mQuery;
489 406

  
490 407
    if ( !whereClause.isEmpty() )
491 408
      query += QString( " where %1" ).arg( whereClause );
......
675 592
    QString fetch = QString( "fetch forward %1 from %2" ).arg( mFeatureQueueSize ).arg( cursorName );
676 593
    if ( connectionRO->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously
677 594
    {
678
      QgsDebugMsg( "PQsendQuery failed (1)" );
595
      QgsDebugMsg( "PQsendQuery failed" );
679 596
    }
680 597

  
681 598
    Result queryResult;
......
876 793

  
877 794
void QgsPostgresProvider::loadFields()
878 795
{
879
  QgsDebugMsg( "Loading fields for table " + mTableName );
796
  if ( !isQuery )
797
  {
798
    QgsDebugMsg( "Loading fields for table " + mTableName );
880 799

  
881
  // Get the relation oid for use in later queries
882
  QString sql = QString( "SELECT regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
883
  Result tresult = connectionRO->PQexec( sql );
884
  QString tableoid = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
800
    // Get the relation oid for use in later queries
801
    QString sql = QString( "SELECT regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
802
    Result tresult = connectionRO->PQexec( sql );
803
    QString tableoid = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
885 804

  
886
  // Get the table description
887
  sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=0" ).arg( tableoid );
888
  tresult = connectionRO->PQexec( sql );
889
  if ( PQntuples( tresult ) > 0 )
890
    mDataComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
805
    // Get the table description
806
    sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=0" ).arg( tableoid );
807
    tresult = connectionRO->PQexec( sql );
808
    if ( PQntuples( tresult ) > 0 )
809
      mDataComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
810
  }
891 811

  
892 812
  // Populate the field vector for this layer. The field vector contains
893 813
  // field name, type, length, and precision (if numeric)
894
  sql = QString( "select * from %1 limit 0" ).arg( mSchemaTableName );
814
  QString sql = QString( "select * from %1 limit 0" ).arg( mQuery );
895 815

  
896 816
  Result result = connectionRO->PQexec( sql );
897 817

  
......
909 829
    QString typOid = QString().setNum( fldtyp );
910 830
    int fieldModifier = PQfmod( result, i );
911 831
    QString fieldComment( "" );
832
    int tableoid = PQftable( result, i );
912 833

  
913 834
    sql = QString( "SELECT typname,typtype,typelem,typlen FROM pg_type WHERE oid=%1" ).arg( typOid );
914 835
    // just oid; needs more work to support array type
......
921 842
    QString fieldElem = QString::fromUtf8( PQgetvalue( oidResult, 0, 2 ) );
922 843
    int fieldSize = QString::fromUtf8( PQgetvalue( oidResult, 0, 3 ) ).toInt();
923 844

  
924
    sql = QString( "SELECT attnum FROM pg_attribute WHERE attrelid=%1 AND attname=%2" )
925
          .arg( tableoid ).arg( quotedValue( fieldName ) );
845
    if ( tableoid >= 0 )
846
    {
847
      sql = QString( "SELECT attnum FROM pg_attribute WHERE attrelid=%1 AND attname=%2" )
848
            .arg( tableoid ).arg( quotedValue( fieldName ) );
926 849

  
927
    Result tresult = connectionRO->PQexec( sql );
928
    QString attnum = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
850
      Result tresult = connectionRO->PQexec( sql );
851
      QString attnum = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
929 852

  
930
    sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=%2" )
931
          .arg( tableoid ).arg( attnum );
853
      sql = QString( "SELECT description FROM pg_description WHERE objoid=%1 AND objsubid=%2" )
854
            .arg( tableoid ).arg( attnum );
932 855

  
933
    tresult = connectionRO->PQexec( sql );
934
    if ( PQntuples( tresult ) > 0 )
935
      fieldComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
856
      tresult = connectionRO->PQexec( sql );
857
      if ( PQntuples( tresult ) > 0 )
858
        fieldComment = QString::fromUtf8( PQgetvalue( tresult, 0, 0 ) );
859
    }
936 860

  
937 861
    QVariant::Type fieldType;
938 862

  
......
1007 931
  }
1008 932
}
1009 933

  
934
bool QgsPostgresProvider::hasSufficientPermsAndCapabilities()
935
{
936
  QgsDebugMsg( "Checking for permissions on the relation" );
937

  
938
  Result testAccess;
939
  if ( !isQuery )
940
  {
941
    // Check that we can read from the table (i.e., we have
942
    // select permission).
943
    QString sql = QString( "select * from %1 limit 1" ).arg( mQuery );
944
    Result testAccess = connectionRO->PQexec( sql );
945
    if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
946
    {
947
      showMessageBox( tr( "Unable to access relation" ),
948
                      tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
949
                      .arg( mQuery )
950
                      .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
951
                      .arg( sql ) );
952
      return false;
953
    }
954

  
955
    if ( connectionRO->pgVersion() >= 80400 )
956
    {
957
      sql = QString( "SELECT "
958
                     "has_table_privilege(%1,'DELETE'),"
959
                     "has_any_column_privilege(%1,'UPDATE'),"
960
                     "has_column_privilege(%1,%2,'UPDATE'),"
961
                     "has_table_privilege(%1,'INSERT'),"
962
                     "current_schema()" )
963
            .arg( quotedValue( mQuery ) ).arg( quotedValue( geometryColumn ) );
964
    }
965
    else
966
    {
967
      sql = QString( "SELECT "
968
                     "has_table_privilege(%1,'DELETE'),"
969
                     "has_table_privilege(%1,'UPDATE'),"
970
                     "has_table_privilege(%1,'UPDATE'),"
971
                     "has_table_privilege(%1,'INSERT'),"
972
                     "current_schema()" )
973
            .arg( quotedValue( mQuery ) );
974
    }
975

  
976
    testAccess = connectionRO->PQexec( sql );
977
    if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
978
    {
979
      showMessageBox( tr( "Unable to access relation" ),
980
                      tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" )
981
                      .arg( mQuery )
982
                      .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
983
                      .arg( sql ) );
984
      return false;
985
    }
986

  
987
    // postgres has fast access to features at id (thanks to primary key / unique index)
988
    // the latter flag is here just for compatibility
989
    enabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
990

  
991
    if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 0 ) ) == "t" )
992
    {
993
      // DELETE
994
      enabledCapabilities |= QgsVectorDataProvider::DeleteFeatures;
995
    }
996

  
997
    if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 1 ) ) == "t" )
998
    {
999
      // UPDATE
1000
      enabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues;
1001
    }
1002

  
1003
    if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 2 ) ) == "t" )
1004
    {
1005
      // UPDATE
1006
      enabledCapabilities |= QgsVectorDataProvider::ChangeGeometries;
1007
    }
1008

  
1009
    if ( QString::fromUtf8( PQgetvalue( testAccess, 0, 3 ) ) == "t" )
1010
    {
1011
      // INSERT
1012
      enabledCapabilities |= QgsVectorDataProvider::AddFeatures;
1013
    }
1014

  
1015
    mCurrentSchema = QString::fromUtf8( PQgetvalue( testAccess, 0, 4 ) );
1016
    if ( mCurrentSchema == mSchemaName )
1017
    {
1018
      mUri.clearSchema();
1019
    }
1020

  
1021
    if ( mSchemaName == "" )
1022
      mSchemaName = mCurrentSchema;
1023

  
1024
    sql = QString( "SELECT 1 FROM pg_class,pg_namespace WHERE "
1025
                   "pg_class.relnamespace=pg_namespace.oid AND "
1026
                   "pg_get_userbyid(relowner)=current_user AND "
1027
                   "relname=%1 AND nspname=%2" )
1028
          .arg( quotedValue( mTableName ) )
1029
          .arg( quotedValue( mSchemaName ) );
1030
    testAccess = connectionRO->PQexec( sql );
1031
    if ( PQresultStatus( testAccess ) == PGRES_TUPLES_OK && PQntuples( testAccess ) == 1 )
1032
    {
1033
      enabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes;
1034
    }
1035
  }
1036
  else
1037
  {
1038
    // Check if the sql is a select query
1039
    if ( !mQuery.startsWith( "(select", Qt::CaseInsensitive ) &&
1040
         !mQuery.endsWith( ")" ) )
1041
    {
1042
      QgsDebugMsg( "The custom query is not a select query." );
1043
      //TODO show a message by showMessageBox()
1044
      return false;
1045
    }
1046

  
1047
    // get a new alias for the subquery
1048
    int index = 0;
1049
    QString alias;
1050
    QRegExp regex;
1051
    do
1052
    {
1053
      alias = QString( "subQuery_%1" ).arg( QString::number( index++ ) );
1054
      QString pattern = QString( "(\\\"?)%1\\1" ).arg( QRegExp::escape( alias ) );
1055
      regex.setPattern( pattern );
1056
      regex.setCaseSensitivity( Qt::CaseInsensitive );
1057
    }
1058
    while ( mQuery.contains( regex ) );
1059

  
1060
    // convert the custom query into a subquery
1061
    mQuery = QString( "%1 as %2" )
1062
             .arg( mQuery )
1063
             .arg( quotedIdentifier( alias ) );
1064

  
1065
    QString sql = QString( "select * from %1 limit 1" ).arg( mQuery );
1066

  
1067
    testAccess = connectionRO->PQexec( sql );
1068
    if ( PQresultStatus( testAccess ) != PGRES_TUPLES_OK )
1069
    {
1070
      showMessageBox( tr( "Unable execute the query" ),
1071
                      tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" )
1072
                      .arg( QString::fromUtf8( PQresultErrorMessage( testAccess ) ) )
1073
                      .arg( sql ) );
1074
      return false;
1075
    }
1076

  
1077
    enabledCapabilities = QgsVectorDataProvider::SelectAtId | QgsVectorDataProvider::SelectGeometryAtId;
1078
  }
1079

  
1080
  return true;
1081
}
1082

  
1010 1083
QString QgsPostgresProvider::getPrimaryKey()
1011 1084
{
1012 1085
  // If we find a database primary key we will set this to true.  If it is a column which is serving
......
1017 1090
  // can be used as a key into the table. Primary keys are always
1018 1091
  // unique indices, so we catch them as well.
1019 1092

  
1020
  QString sql = QString( "select indkey from pg_index where indisunique and indrelid=regclass(%1)::oid and indpred is null" )
1021
                .arg( quotedValue( mSchemaTableName ) );
1093
  QString sql;
1094
  if ( !isQuery )
1095
  {
1096
    sql = QString( "select indkey from pg_index where indisunique and indrelid=regclass(%1)::oid and indpred is null" )
1097
          .arg( quotedValue( mQuery ) );
1022 1098

  
1023
  QgsDebugMsg( "Getting unique index using '" + sql + "'" );
1099
    QgsDebugMsg( "Getting unique index using '" + sql + "'" );
1024 1100

  
1025
  Result pk = connectionRO->PQexec( sql );
1101
    Result pk = connectionRO->PQexec( sql );
1026 1102

  
1027
  QgsDebugMsg( "Got " + QString::number( PQntuples( pk ) ) + " rows." );
1103
    QgsDebugMsg( "Got " + QString::number( PQntuples( pk ) ) + " rows." );
1028 1104

  
1029
  QStringList log;
1105
    QStringList log;
1030 1106

  
1031
  // if we got no tuples we ain't got no unique index :)
1032
  if ( PQntuples( pk ) == 0 )
1033
  {
1034
    QgsDebugMsg( "Relation has no unique index -- investigating alternatives" );
1107
    // if we got no tuples we ain't got no unique index :)
1108
    if ( PQntuples( pk ) == 0 )
1109
    {
1110
      QgsDebugMsg( "Relation has no unique index -- investigating alternatives" );
1035 1111

  
1036
    // Two options here. If the relation is a table, see if there is
1037
    // an oid column that can be used instead.
1038
    // If the relation is a view try to find a suitable column to use as
1039
    // the primary key.
1112
      // Two options here. If the relation is a table, see if there is
1113
      // an oid column that can be used instead.
1114
      // If the relation is a view try to find a suitable column to use as
1115
      // the primary key.
1040 1116

  
1041
    sql = QString( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" )
1042
          .arg( quotedValue( mSchemaTableName ) );
1043
    Result tableType = connectionRO->PQexec( sql );
1044
    QString type = QString::fromUtf8( PQgetvalue( tableType, 0, 0 ) );
1117
      sql = QString( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" )
1118
            .arg( quotedValue( mQuery ) );
1119
      Result tableType = connectionRO->PQexec( sql );
1120
      QString type = QString::fromUtf8( PQgetvalue( tableType, 0, 0 ) );
1045 1121

  
1046
    if ( type == "r" ) // the relation is a table
1047
    {
1048
      QgsDebugMsg( "Relation is a table. Checking to see if it has an oid column." );
1122
      if ( type == "r" ) // the relation is a table
1123
      {
1124
        QgsDebugMsg( "Relation is a table. Checking to see if it has an oid column." );
1049 1125

  
1050
      primaryKey = "";
1126
        primaryKey = "";
1051 1127

  
1052
      // If there is an oid on the table, use that instead,
1053
      // otherwise give up
1054
      sql = QString( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" )
1055
            .arg( quotedValue( mSchemaTableName ) );
1128
        // If there is an oid on the table, use that instead,
1129
        // otherwise give up
1130
        sql = QString( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" )
1131
              .arg( quotedValue( mQuery ) );
1056 1132

  
1057
      Result oidCheck = connectionRO->PQexec( sql );
1133
        Result oidCheck = connectionRO->PQexec( sql );
1058 1134

  
1059
      if ( PQntuples( oidCheck ) != 0 )
1060
      {
1061
        // Could warn the user here that performance will suffer if
1062
        // oid isn't indexed (and that they may want to add a
1063
        // primary key to the table)
1064
        primaryKey = "oid";
1065
        primaryKeyType = "int4";
1066
        mIsDbPrimaryKey = true;
1067
      }
1068
      else
1069
      {
1070
        sql = QString( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" )
1071
              .arg( quotedValue( mSchemaTableName ) );
1072

  
1073
        Result ctidCheck = connectionRO->PQexec( sql );
1074

  
1075
        if ( PQntuples( ctidCheck ) == 1 )
1135
        if ( PQntuples( oidCheck ) != 0 )
1076 1136
        {
1077
          sql = QString( "SELECT max(substring(ctid::text from E'\\\\((\\\\d+),\\\\d+\\\\)')::integer) from %1" )
1078
                .arg( mSchemaTableName );
1137
          // Could warn the user here that performance will suffer if
1138
          // oid isn't indexed (and that they may want to add a
1139
          // primary key to the table)
1140
          primaryKey = "oid";
1141
          primaryKeyType = "int4";
1142
          mIsDbPrimaryKey = true;
1143
        }
1144
        else
1145
        {
1146
          sql = QString( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" )
1147
                .arg( quotedValue( mQuery ) );
1079 1148

  
1080 1149
          Result ctidCheck = connectionRO->PQexec( sql );
1150

  
1081 1151
          if ( PQntuples( ctidCheck ) == 1 )
1082 1152
          {
1083
            int id = QString( PQgetvalue( ctidCheck, 0, 0 ) ).toInt();
1153
            sql = QString( "SELECT max(substring(ctid::text from E'\\\\((\\\\d+),\\\\d+\\\\)')::integer) from %1" )
1154
                  .arg( mQuery );
1084 1155

  
1085
            if ( id < 0x10000 )
1156
            Result ctidCheck = connectionRO->PQexec( sql );
1157
            if ( PQntuples( ctidCheck ) == 1 )
1086 1158
            {
1087
              // fallback to ctid
1088
              primaryKey = "ctid";
1089
              primaryKeyType = "tid";
1090
              mIsDbPrimaryKey = true;
1159
              int id = QString( PQgetvalue( ctidCheck, 0, 0 ) ).toInt();
1160

  
1161
              if ( id < 0x10000 )
1162
              {
1163
                // fallback to ctid
1164
                primaryKey = "ctid";
1165
                primaryKeyType = "tid";
1166
                mIsDbPrimaryKey = true;
1167
              }
1091 1168
            }
1092 1169
          }
1093 1170
        }
1094
      }
1095 1171

  
1096
      if ( primaryKey.isEmpty() )
1097
      {
1098
        showMessageBox( tr( "No suitable key column in table" ),
1099
                        tr( "The table has no column suitable for use as a key.\n\n"
1100
                            "Quantum GIS requires that the table either has a column of type\n"
1101
                            "int4 with a unique constraint on it (which includes the\n"
1102
                            "primary key), has a PostgreSQL oid column or has a ctid\n"
1103
                            "column with a 16bit block number.\n" ) );
1104
      }
1105
      else
1106
      {
1107
        mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
1108
        if ( mPrimaryKeyDefault.isNull() )
1172
        if ( primaryKey.isEmpty() )
1109 1173
        {
1110
          mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
1111
                               .arg( quotedIdentifier( primaryKey ) )
1112
                               .arg( quotedIdentifier( mSchemaName ) )
1113
                               .arg( quotedIdentifier( mTableName ) );
1174
          showMessageBox( tr( "No suitable key column in table" ),
1175
                          tr( "The table has no column suitable for use as a key.\n\n"
1176
                              "Quantum GIS requires that the table either has a column of type\n"
1177
                              "int4 with a unique constraint on it (which includes the\n"
1178
                              "primary key), has a PostgreSQL oid column or has a ctid\n"
1179
                              "column with a 16bit block number.\n" ) );
1114 1180
        }
1181
        else
1182
        {
1183
          mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
1184
          if ( mPrimaryKeyDefault.isNull() )
1185
          {
1186
            mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
1187
                                 .arg( quotedIdentifier( primaryKey ) )
1188
                                 .arg( quotedIdentifier( mSchemaName ) )
1189
                                 .arg( quotedIdentifier( mTableName ) );
1190
          }
1191
        }
1115 1192
      }
1116
    }
1117
    else if ( type == "v" ) // the relation is a view
1118
    {
1119
      if ( !primaryKey.isEmpty() )
1193
      else if ( type == "v" ) // the relation is a view
1120 1194
      {
1121
        // check last used candidate
1122
        sql = QString( "select pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attname=%1 and attrelid=regclass(%2)" )
1123
              .arg( quotedValue( primaryKey ) ).arg( quotedValue( mSchemaTableName ) );
1195
        if ( !primaryKey.isEmpty() )
1196
        {
1197
          // check last used candidate
1198
          sql = QString( "select pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attname=%1 and attrelid=regclass(%2)" )
1199
                .arg( quotedValue( primaryKey ) ).arg( quotedValue( mQuery ) );
1124 1200

  
1125
        QgsDebugMsg( "checking candidate: " + sql );
1201
          QgsDebugMsg( "checking candidate: " + sql );
1126 1202

  
1127
        Result result = connectionRO->PQexec( sql );
1203
          Result result = connectionRO->PQexec( sql );
1128 1204

  
1129
        QString type;
1130
        if ( PQresultStatus( result ) == PGRES_TUPLES_OK &&
1131
             PQntuples( result ) == 1 )
1132
        {
1133
          type = PQgetvalue( result, 0, 0 );
1205
          QString type;
1206
          if ( PQresultStatus( result ) == PGRES_TUPLES_OK &&
1207
               PQntuples( result ) == 1 )
1208
          {
1209
            type = PQgetvalue( result, 0, 0 );
1210
          }
1211

  
1212
          // mPrimaryKeyDefault stays null and is retrieved later on demand
1213

  
1214
          if (( type != "int4" && type != "oid" ) ||
1215
              !uniqueData( mQuery, primaryKey ) )
1216
          {
1217
            primaryKey = "";
1218
          }
1134 1219
        }
1135 1220

  
1136
        // mPrimaryKeyDefault stays null and is retrieved later on demand
1137

  
1138
        if (( type != "int4" && type != "oid" ) ||
1139
            !uniqueData( mSchemaName, mTableName, primaryKey ) )
1221
        if ( primaryKey.isEmpty() )
1140 1222
        {
1141
          primaryKey = "";
1223
          parseView();
1142 1224
        }
1143 1225
      }
1144

  
1145
      if ( primaryKey.isEmpty() )
1146
      {
1147
        parseView();
1148
      }
1226
      else
1227
        QgsDebugMsg( "Unexpected relation type of '" + type + "'." );
1149 1228
    }
1150
    else
1151
      QgsDebugMsg( "Unexpected relation type of '" + type + "'." );
1152
  }
1153
  else // have some unique indices on the table. Now choose one...
1154
  {
1155
    // choose which (if more than one) unique index to use
1156
    std::vector<std::pair<QString, QString> > suitableKeyColumns;
1157
    for ( int i = 0; i < PQntuples( pk ); ++i )
1229
    else // have some unique indices on the table. Now choose one...
1158 1230
    {
1159
      QString col = QString::fromUtf8( PQgetvalue( pk, i, 0 ) );
1160
      QStringList columns = col.split( " ", QString::SkipEmptyParts );
1161
      if ( columns.count() == 1 )
1231
      // choose which (if more than one) unique index to use
1232
      std::vector<std::pair<QString, QString> > suitableKeyColumns;
1233
      for ( int i = 0; i < PQntuples( pk ); ++i )
1162 1234
      {
1163
        // Get the column name and data type
1164
        sql = QString( "select attname,pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attnum=%1 and attrelid=regclass(%2)" )
1165
              .arg( col ).arg( quotedValue( mSchemaTableName ) );
1166
        Result types = connectionRO->PQexec( sql );
1167

  
1168
        if ( PQntuples( types ) > 0 )
1235
        QString col = QString::fromUtf8( PQgetvalue( pk, i, 0 ) );
1236
        QStringList columns = col.split( " ", QString::SkipEmptyParts );
1237
        if ( columns.count() == 1 )
1169 1238
        {
1170
          QString columnName = QString::fromUtf8( PQgetvalue( types, 0, 0 ) );
1171
          QString columnType = QString::fromUtf8( PQgetvalue( types, 0, 1 ) );
1239
          // Get the column name and data type
1240
          sql = QString( "select attname,pg_type.typname from pg_attribute,pg_type where atttypid=pg_type.oid and attnum=%1 and attrelid=regclass(%2)" )
1241
                .arg( col ).arg( quotedValue( mQuery ) );
1242
          Result types = connectionRO->PQexec( sql );
1172 1243

  
1173
          if ( columnType != "int4" )
1174
            log.append( tr( "The unique index on column '%1' is unsuitable because Quantum GIS does not currently "
1175
                            "support non-int4 type columns as a key into the table.\n" ).arg( columnName ) );
1244
          if ( PQntuples( types ) > 0 )
1245
          {
1246
            QString columnName = QString::fromUtf8( PQgetvalue( types, 0, 0 ) );
1247
            QString columnType = QString::fromUtf8( PQgetvalue( types, 0, 1 ) );
1248

  
1249
            if ( columnType != "int4" )
1250
              log.append( tr( "The unique index on column '%1' is unsuitable because Quantum GIS does not currently "
1251
                              "support non-int4 type columns as a key into the table.\n" ).arg( columnName ) );
1252
            else
1253
            {
1254
              mIsDbPrimaryKey = true;
1255
              suitableKeyColumns.push_back( std::make_pair( columnName, columnType ) );
1256
            }
1257
          }
1176 1258
          else
1177 1259
          {
1178
            mIsDbPrimaryKey = true;
1179
            suitableKeyColumns.push_back( std::make_pair( columnName, columnType ) );
1260
            //QgsDebugMsg( QString("name and type of %3. column of %1.%2 not found").arg(mSchemaName).arg(mTables).arg(col) );
1180 1261
          }
1181 1262
        }
1182 1263
        else
1183 1264
        {
1184
          //QgsDebugMsg( QString("name and type of %3. column of %1.%2 not found").arg(mSchemaName).arg(mTables).arg(col) );
1265
          sql = QString( "select attname from pg_attribute, pg_type where atttypid=pg_type.oid and attnum in (%1) and attrelid=regclass(%2)::oid" )
1266
                .arg( col.replace( " ", "," ) )
1267
                .arg( quotedValue( mQuery ) );
1268

  
1269
          Result types = connectionRO->PQexec( sql );
1270
          QString colNames;
1271
          int numCols = PQntuples( types );
1272
          for ( int j = 0; j < numCols; ++j )
1273
          {
1274
            if ( j == numCols - 1 )
1275
              colNames += tr( "and " );
1276
            colNames += quotedValue( QString::fromUtf8( PQgetvalue( types, j, 0 ) ) );
1277
            if ( j < numCols - 2 )
1278
              colNames += ",";
1279
          }
1280

  
1281
          log.append( tr( "The unique index based on columns %1 is unsuitable because Quantum GIS does not currently "
1282
                          "support multiple columns as a key into the table.\n" ).arg( colNames ) );
1185 1283
        }
1186 1284
      }
1285

  
1286
      // suitableKeyColumns now contains the name of columns (and their
1287
      // data type) that
1288
      // are suitable for use as a key into the table. If there is
1289
      // more than one we need to choose one. For the moment, just
1290
      // choose the first in the list.
1291

  
1292
      if ( suitableKeyColumns.size() > 0 )
1293
      {
1294
        primaryKey = suitableKeyColumns[0].first;
1295
        primaryKeyType = suitableKeyColumns[0].second;
1296
      }
1187 1297
      else
1188 1298
      {
1189
        sql = QString( "select attname from pg_attribute, pg_type where atttypid=pg_type.oid and attnum in (%1) and attrelid=regclass(%2)::oid" )
1190
              .arg( col.replace( " ", "," ) )
1191
              .arg( quotedValue( mSchemaTableName ) );
1299
        // If there is an oid on the table, use that instead,
1300
        // otherwise give up
1301
        sql = QString( "select attname from pg_attribute where attname='oid' and attrelid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
1302
        Result oidCheck = connectionRO->PQexec( sql );
1192 1303

  
1193
        Result types = connectionRO->PQexec( sql );
1194
        QString colNames;
1195
        int numCols = PQntuples( types );
1196
        for ( int j = 0; j < numCols; ++j )
1304
        if ( PQntuples( oidCheck ) != 0 )
1197 1305
        {
1198
          if ( j == numCols - 1 )
1199
            colNames += tr( "and " );
1200
          colNames += quotedValue( QString::fromUtf8( PQgetvalue( types, j, 0 ) ) );
1201
          if ( j < numCols - 2 )
1202
            colNames += ",";
1306
          primaryKey = "oid";
1307
          primaryKeyType = "int4";
1203 1308
        }
1204

  
1205
        log.append( tr( "The unique index based on columns %1 is unsuitable because Quantum GIS does not currently "
1206
                        "support multiple columns as a key into the table.\n" ).arg( colNames ) );
1309
        else
1310
        {
1311
          log.prepend( "There were no columns in the table that were suitable "
1312
                       "as a qgis key into the table (either a column with a "
1313
                       "unique index and type int4 or a PostgreSQL oid column.\n" );
1314
        }
1207 1315
      }
1208
    }
1209 1316

  
1210
    // suitableKeyColumns now contains the name of columns (and their
1211
    // data type) that
1212
    // are suitable for use as a key into the table. If there is
1213
    // more than one we need to choose one. For the moment, just
1214
    // choose the first in the list.
1215

  
1216
    if ( suitableKeyColumns.size() > 0 )
1217
    {
1218
      primaryKey = suitableKeyColumns[0].first;
1219
      primaryKeyType = suitableKeyColumns[0].second;
1220
    }
1221
    else
1222
    {
1223
      // If there is an oid on the table, use that instead,
1224
      // otherwise give up
1225
      sql = QString( "select attname from pg_attribute where attname='oid' and attrelid=regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
1226
      Result oidCheck = connectionRO->PQexec( sql );
1227

  
1228
      if ( PQntuples( oidCheck ) != 0 )
1317
      // Either primaryKey has been set by the above code, or it
1318
      // hasn't. If not, present some info to the user to give them some
1319
      // idea of why not.
1320
      if ( primaryKey.isEmpty() )
1229 1321
      {
1230
        primaryKey = "oid";
1231
        primaryKeyType = "int4";
1322
        // Give some info to the user about why things didn't work out.
1323
        valid = false;
1324
        showMessageBox( tr( "Unable to find a key column" ), log );
1232 1325
      }
1233 1326
      else
1234 1327
      {
1235
        log.prepend( "There were no columns in the table that were suitable "
1236
                     "as a qgis key into the table (either a column with a "
1237
                     "unique index and type int4 or a PostgreSQL oid column.\n" );
1328
        mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
1329
        if ( mPrimaryKeyDefault.isNull() )
1330
        {
1331
          mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
1332
                               .arg( quotedIdentifier( primaryKey ) )
1333
                               .arg( quotedIdentifier( mSchemaName ) )
1334
                               .arg( quotedIdentifier( mTableName ) );
1335
        }
1238 1336
      }
1239 1337
    }
1240

  
1241
    // Either primaryKey has been set by the above code, or it
1242
    // hasn't. If not, present some info to the user to give them some
1243
    // idea of why not.
1244
    if ( primaryKey.isEmpty() )
1245
    {
1246
      // Give some info to the user about why things didn't work out.
1247
      valid = false;
1248
      showMessageBox( tr( "Unable to find a key column" ), log );
1249
    }
1250
    else
1251
    {
1252
      mPrimaryKeyDefault = defaultValue( primaryKey ).toString();
1253
      if ( mPrimaryKeyDefault.isNull() )
1254
      {
1255
        mPrimaryKeyDefault = QString( "max(%1)+1 from %2.%3" )
1256
                             .arg( quotedIdentifier( primaryKey ) )
1257
                             .arg( quotedIdentifier( mSchemaName ) )
1258
                             .arg( quotedIdentifier( mTableName ) );
1259
      }
1260
    }
1261 1338
  }
1262 1339

  
1263 1340
  if ( !primaryKey.isNull() )
......
1436 1513
          .arg( quotedValue( i->second.column ) );
1437 1514
    Result result = connectionRO->PQexec( sql );
1438 1515

  
1439
    if ( PQntuples( result ) > 0 && uniqueData( mSchemaName, mTableName, i->first ) )
1516
    if ( PQntuples( result ) > 0 && uniqueData( mQuery, i->first ) )
1440 1517
    {
1441 1518
      // Got one. Use it.
1442 1519
      key = i->first;
......
1451 1528
    // exists). This is legacy support and could be removed in
1452 1529
    // future.
1453 1530
    i = suitable.find( "oid" );
1454
    if ( i != suitable.end() && uniqueData( mSchemaName, mTableName, i->first ) )
1531
    if ( i != suitable.end() && uniqueData( mQuery, i->first ) )
1455 1532
    {
1456 1533
      key = i->first;
1457 1534

  
......
1466 1543
      tableCols::const_iterator i = suitable.begin();
1467 1544
      for ( ; i != suitable.end(); ++i )
1468 1545
      {
1469
        if ( uniqueData( mSchemaName, mTableName, i->first ) )
1546
        if ( uniqueData( mQuery, i->first ) )
1470 1547
        {
1471 1548
          key = i->first;
1472 1549

  
......
1503 1580
  return key;
1504 1581
}
1505 1582

  
1506
bool QgsPostgresProvider::uniqueData( QString schemaName,
1507
                                      QString tableName, QString colName )
1583
bool QgsPostgresProvider::uniqueData( QString query, QString colName )
1508 1584
{
1509 1585
  // Check to see if the given column contains unique data
1510 1586

  
1511 1587
  bool isUnique = false;
1512 1588

  
1513
  QString sql = QString( "select count(distinct %1)=count(%1) from %2.%3" )
1589
  QString sql = QString( "select count(distinct %1)=count(%1) from %2" )
1514 1590
                .arg( quotedIdentifier( colName ) )
1515
                .arg( quotedIdentifier( schemaName ) )
1516
                .arg( quotedIdentifier( tableName ) );
1591
                .arg( mQuery );
1517 1592

  
1518 1593
  if ( !sqlWhereClause.isEmpty() )
1519 1594
  {
......
1780 1855
  {
1781 1856
    // get the field name
1782 1857
    const QgsField &fld = field( index );
1783
    QString sql;
1784
    if ( sqlWhereClause.isEmpty() )
1858
    QString sql = QString( "select min(%1) from %2" )
1859
                  .arg( quotedIdentifier( fld.name() ) )
1860
                  .arg( mQuery );
1861

  
1862
    if ( !sqlWhereClause.isEmpty() )
1785 1863
    {
1786
      sql = QString( "select min(%1) from %2" )
1787
            .arg( quotedIdentifier( fld.name() ) )
1788
            .arg( mSchemaTableName );
1864
      sql += QString( " where %1" ).arg( sqlWhereClause );
1789 1865
    }
1790
    else
1791
    {
1792
      sql = QString( "select min(%1) from %2 where %3" )
1793
            .arg( quotedIdentifier( fld.name() ) )
1794
            .arg( mSchemaTableName )
1795
            .arg( sqlWhereClause );
1796
    }
1866

  
1797 1867
    Result rmin = connectionRO->PQexec( sql );
1798 1868
    return convertValue( fld.type(), QString::fromUtf8( PQgetvalue( rmin, 0, 0 ) ) );
1799 1869
  }
......
1812 1882
  {
1813 1883
    // get the field name
1814 1884
    const QgsField &fld = field( index );
1815
    QString sql;
1816
    if ( sqlWhereClause.isEmpty() )
1885
    QString sql = QString( "select distinct %1 from %2" )
1886
                  .arg( quotedIdentifier( fld.name() ) )
1887
                  .arg( mQuery );
1888

  
1889
    if ( !sqlWhereClause.isEmpty() )
1817 1890
    {
1818
      sql = QString( "select distinct %1 from %2 order by %1" )
1819
            .arg( quotedIdentifier( fld.name() ) )
1820
            .arg( mSchemaTableName );
1891
      sql += QString( " where %1" ).arg( sqlWhereClause );
1821 1892
    }
1822
    else
1823
    {
1824
      sql = QString( "select distinct %1 from %2 where %3 order by %1" )
1825
            .arg( quotedIdentifier( fld.name() ) )
1826
            .arg( mSchemaTableName )
1827
            .arg( sqlWhereClause );
1828
    }
1829 1893

  
1894
    sql +=  QString( " order by %1" )
1895
            .arg( quotedIdentifier( fld.name() ) );
1896

  
1830 1897
    if ( limit >= 0 )
1831 1898
    {
1832 1899
      sql += QString( " LIMIT %1" ).arg( limit );
......
1891 1958
bool QgsPostgresProvider::parseEnumRange( QStringList& enumValues, const QString& attributeName ) const
1892 1959
{
1893 1960
  enumValues.clear();
1894
  QString enumRangeSql = QString( "SELECT enum_range(%1) from %2 limit 1" ).arg( quotedIdentifier( attributeName ) ).arg( mSchemaTableName );
1961
  QString enumRangeSql = QString( "SELECT enum_range(%1) from %2 limit 1" )
1962
                         .arg( quotedIdentifier( attributeName ) )
1963
                         .arg( mQuery );
1895 1964
  Result enumRangeRes = connectionRO->PQexec( enumRangeSql );
1896 1965
  if ( PQresultStatus( enumRangeRes ) == PGRES_TUPLES_OK && PQntuples( enumRangeRes ) > 0 )
1897 1966
  {
......
1979 2048
  {
1980 2049
    // get the field name
1981 2050
    const QgsField &fld = field( index );
1982
    QString sql;
1983
    if ( sqlWhereClause.isEmpty() )
2051
    QString sql = QString( "select max(%1) from %2" )
2052
                  .arg( quotedIdentifier( fld.name() ) )
2053
                  .arg( mQuery );
2054

  
2055
    if ( !sqlWhereClause.isEmpty() )
1984 2056
    {
1985
      sql = QString( "select max(%1) from %2" )
1986
            .arg( quotedIdentifier( fld.name() ) )
1987
            .arg( mSchemaTableName );
2057
      sql += QString( " where %1" ).arg( sqlWhereClause );
1988 2058
    }
1989
    else
1990
    {
1991
      sql = QString( "select max(%1) from %2 where %3" )
1992
            .arg( quotedIdentifier( fld.name() ) )
1993
            .arg( mSchemaTableName )
1994
            .arg( sqlWhereClause );
1995
    }
2059

  
1996 2060
    Result rmax = connectionRO->PQexec( sql );
1997 2061
    return convertValue( fld.type(), QString::fromUtf8( PQgetvalue( rmax, 0, 0 ) ) );
1998 2062
  }
......
2151 2215
  if ( flist.size() == 0 )
2152 2216
    return true;
2153 2217

  
2218
  if ( isQuery )
2219
    return false;
2220

  
2154 2221
  if ( !connectRW() )
2155 2222
    return false;
2156 2223

  
......
2162 2229

  
2163 2230
    // Prepare the INSERT statement
2164 2231
    QString insert = QString( "INSERT INTO %1(%2" )
2165
                     .arg( mSchemaTableName )
2232
                     .arg( mQuery )
2166 2233
                     .arg( quotedIdentifier( geometryColumn ) ),
2167 2234
                     values = QString( ") VALUES (GeomFromWKB($1%1,%2)" )
2168 2235
                              .arg( connectionRW->useWkbHex() ? "" : "::bytea" )
......
2316 2383
{
2317 2384
  bool returnvalue = true;
2318 2385

  
2386
  if ( isQuery )
2387
    return false;
2388

  
2319 2389
  if ( !connectRW() )
2320 2390
    return false;
2321 2391

  
......
2326 2396
    for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it )
2327 2397
    {
2328 2398
      QString sql = QString( "DELETE FROM %1 WHERE %2" )
2329
                    .arg( mSchemaTableName ).arg( whereClause( *it ) );
2399
                    .arg( mQuery ).arg( whereClause( *it ) );
2330 2400
      QgsDebugMsg( "delete sql: " + sql );
2331 2401

  
2332 2402
      //send DELETE statement and do error handling
......
2354 2424
{
2355 2425
  bool returnvalue = true;
2356 2426

  
2427
  if ( isQuery )
2428
    return false;
2429

  
2357 2430
  if ( !connectRW() )
2358 2431
    return false;
2359 2432

  
......
2374 2447
      }
2375 2448

  
2376 2449
      QString sql = QString( "ALTER TABLE %1 ADD COLUMN %2 %3" )
2377
                    .arg( mSchemaTableName )
2450
                    .arg( mQuery )
2378 2451
                    .arg( quotedIdentifier( iter->name() ) )
2379 2452
                    .arg( type );
2380 2453
      QgsDebugMsg( sql );
......
2388 2461
      if ( !iter->comment().isEmpty() )
2389 2462
      {
2390 2463
        sql = QString( "COMMENT ON COLUMN %1.%2 IS %3" )
2391
              .arg( mSchemaTableName )
2464
              .arg( mQuery )
2392 2465
              .arg( quotedIdentifier( iter->name() ) )
2393 2466
              .arg( quotedValue( iter->comment() ) );
2394 2467
        result = connectionRW->PQexec( sql );
......
2415 2488
{
2416 2489
  bool returnvalue = true;
2417 2490

  
2491
  if ( !isQuery )
2492
    return false;
2493

  
2418 2494
  if ( !connectRW() )
2419 2495
    return false;
2420 2496

  
......
2430 2506

  
2431 2507
      QString column = field_it->name();
2432 2508
      QString sql = QString( "ALTER TABLE %1 DROP COLUMN %2" )
2433
                    .arg( mSchemaTableName )
2509
                    .arg( mQuery )
2434 2510
                    .arg( quotedIdentifier( column ) );
2435 2511

  
2436 2512
      //send sql statement and do error handling
......
2460 2536
{
2461 2537
  bool returnvalue = true;
2462 2538

  
2539
  if ( !isQuery )
2540
    return false;
2541

  
2463 2542
  if ( !connectRW() )
2464 2543
    return false;
2465 2544

  
......
2476 2555
      if ( fid < 0 )
2477 2556
        continue;
2478 2557

  
2479
      QString sql = QString( "UPDATE %1 SET " ).arg( mSchemaTableName );
2558
      QString sql = QString( "UPDATE %1 SET " ).arg( mQuery );
2480 2559
      bool first = true;
2481 2560

  
2482 2561
      const QgsAttributeMap& attrs = iter.value();
......
2541 2620
{
2542 2621
  QgsDebugMsg( "entering." );
2543 2622

  
2623
  if ( !isQuery )
2624
    return false;
2625

  
2544 2626
  if ( !connectRW() )
2545 2627
    return false;
2546 2628

  
......
2552 2634
    connectionRW->PQexecNR( "BEGIN" );
2553 2635

  
2554 2636
    QString update = QString( "UPDATE %1 SET %2=GeomFromWKB($1%3,%4) WHERE %5=$2" )
2555
                     .arg( mSchemaTableName )
2637
                     .arg( mQuery )
2556 2638
                     .arg( quotedIdentifier( geometryColumn ) )
2557 2639
                     .arg( connectionRW->useWkbHex() ? "" : "::bytea" )
2558 2640
                     .arg( srid )
......
2636 2718

  
2637 2719
  sqlWhereClause = theSQL;
2638 2720

  
2639
  if ( !mIsDbPrimaryKey && !uniqueData( mSchemaName, mTableName, primaryKey ) )
2721
  if ( !mIsDbPrimaryKey && !uniqueData( mQuery, primaryKey ) )
2640 2722
  {
2641 2723
    sqlWhereClause = prevWhere;
2642 2724
    return false;
......
2663 2745
  // a thread the task of getting the full count.
2664 2746
  QString sql;
2665 2747

  
2666
  if ( mUseEstimatedMetadata )
2748
  if ( !isQuery && mUseEstimatedMetadata )
2667 2749
  {
2668
    sql = QString( "select reltuples::int from pg_catalog.pg_class where oid=regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
2750
    sql = QString( "select reltuples::int from pg_catalog.pg_class where oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
2669 2751
  }
2670 2752
  else
2671 2753
  {
2672
    sql = QString( "select count(*) from %1" ).arg( mSchemaTableName );
2754
    sql = QString( "select count(*) from %1" ).arg( mQuery );
2673 2755

  
2674
    if ( sqlWhereClause.length() > 0 )
2756
    if ( !sqlWhereClause.isEmpty() )
2675 2757
    {
2676 2758
      sql += " where " + sqlWhereClause;
2677 2759
    }
......
2696 2778
  QString ext;
2697 2779

  
2698 2780
  // get the extents
2699
  if ( mUseEstimatedMetadata || sqlWhereClause.isEmpty() )
2781
  if ( !isQuery && ( mUseEstimatedMetadata || sqlWhereClause.isEmpty() ) )
2700 2782
  {
2701 2783
    // do stats exists?
2702 2784
    sql = QString( "SELECT COUNT(*) FROM pg_stats WHERE schemaname=%1 AND tablename=%2 AND attname=%3" )
......
2727 2809
  {
2728 2810
    sql = QString( "select extent(%1) from %2" )
2729 2811
          .arg( quotedIdentifier( geometryColumn ) )
2730
          .arg( mSchemaTableName );
2812
          .arg( mQuery );
2731 2813

  
2732 2814
    if ( !sqlWhereClause.isEmpty() )
2733
      sql += QString( "where %1" ).arg( sqlWhereClause );
2815
      sql += QString( " where %1" ).arg( sqlWhereClause );
2734 2816

  
2735 2817
    result = connectionRO->PQexec( sql );
2736 2818
    if ( PQresultStatus( result ) != PGRES_TUPLES_OK )
......
2823 2905
  // version 7.4, binary cursors return data in XDR whereas previous versions
2824 2906
  // return data in the endian of the server
2825 2907

  
2826
  QString firstOid = QString( "select regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) );
2827
  Result oidResult = connectionRO->PQexec( firstOid );
2828
  // get the int value from a "normal" select
2829
  QString oidValue = QString::fromUtf8( PQgetvalue( oidResult, 0, 0 ) );
2908
  QString oidValue;
2909
  QString query;
2830 2910

  
2911
  if ( isQuery )
2912
  {
2913
    QString sql = QString( "select * from %1 limit 0" ).arg( mQuery );
2914
    Result res = connectionRO->PQexec( sql );
2915

  
2916
    // loop through the returned fields to get a valid oid
2917
    int i;
2918
    for ( i = 0; i < PQnfields( res ); i++ )
2919
    {
2920
      int tableoid = PQftable( res, i );
2921
      if ( tableoid >= 0 )
2922
      {
2923
        oidValue = QString::number( tableoid );
2924
        break;
2925
      }
2926
    }
2927

  
2928
    if ( i < PQnfields( res ) )
2929
    {
2930
      // get the table name
2931
      res = connectionRO->PQexec( QString( "SELECT relname FROM pg_class WHERE oid=%1" ).arg( oidValue ) );
2932
      query = QString::fromUtf8( PQgetvalue( res, 0, 0 ) );
2933
    }
2934
    else
2935
    {
2936
      QgsDebugMsg( "no oid found" );
2937
      return false;
2938
    }
2939
  }
2940
  else
2941
  {
2942
    QString firstOid = QString( "select regclass(%1)::oid" ).arg( quotedValue( mQuery ) );
2943
    Result oidResult = connectionRO->PQexec( firstOid );
2944
    // get the int value from a "normal" select
2945
    oidValue = QString::fromUtf8( PQgetvalue( oidResult, 0, 0 ) );
2946
  }
2947

  
2831 2948
  QgsDebugMsg( "Creating binary cursor" );
2832 2949

  
2833 2950
  // get the same value using a binary cursor
2834
  connectionRO->openCursor( "oidcursor", QString( "select regclass(%1)::oid" ).arg( quotedValue( mSchemaTableName ) ) );
2951
  connectionRO->openCursor( "oidcursor", QString( "select regclass(%1)::oid" ).arg( quotedValue( query ) ) );
2835 2952

  
2836 2953
  QgsDebugMsg( "Fetching a record and attempting to get check endian-ness" );
2837 2954

  
......
2861 2978
  valid = false;
2862 2979
  QStringList log;
2863 2980

  
2864
  QString sql = QString( "select type,srid from geometry_columns"
2865
                         " where f_table_name=%1 and f_geometry_column=%2 and f_table_schema=%3" )
2866
                .arg( quotedValue( mTableName ) )
2867
                .arg( quotedValue( geometryColumn ) )
2868
                .arg( quotedValue( mSchemaName ) );
2981
  Result result;
2982
  QString sql;
2869 2983

  
2870
  QgsDebugMsg( "Getting geometry column: " + sql );
2984
  if ( !isQuery )
2985
  {
2986
    sql = QString( "select type,srid from geometry_columns"
2987
                   " where f_table_name=%1 and f_geometry_column=%2 and f_table_schema=%3" )
2988
          .arg( quotedValue( mTableName ) )
2989
          .arg( quotedValue( geometryColumn ) )
2990
          .arg( quotedValue( mSchemaName ) );
2871 2991

  
2872
  Result result = connectionRO->PQexec( sql );
2992
    QgsDebugMsg( "Getting geometry column: " + sql );
2873 2993

  
2874
  QgsDebugMsg( "geometry column query returned " + QString::number( PQntuples( result ) ) );
2994
    Result result = connectionRO->PQexec( sql );
2875 2995

  
2876
  if ( PQntuples( result ) > 0 )
2877
  {
2878
    fType = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
2879
    srid = QString::fromUtf8( PQgetvalue( result, 0, 1 ) );
2996
    QgsDebugMsg( "geometry column query returned " + QString::number( PQntuples( result ) ) );
2997

  
2998
    if ( PQntuples( result ) > 0 )
2999
    {
3000
      fType = QString::fromUtf8( PQgetvalue( result, 0, 0 ) );
3001
      srid = QString::fromUtf8( PQgetvalue( result, 0, 1 ) );
3002
    }
2880 3003
  }
2881
  else
3004

  
3005
  if ( srid.isEmpty() || fType.isEmpty() )
2882 3006
  {
2883 3007
    // Didn't find what we need in the geometry_columns table, so
2884 3008
    // get stuff from the relevant column instead. This may (will?)
2885 3009
    // fail if there is no data in the relevant table.
2886
    sql = QString( "select srid(%1),geometrytype(%1) from %2" )
3010
    sql = QString( "select srid(%1), geometrytype(%1) from %2" )
2887 3011
          .arg( quotedIdentifier( geometryColumn ) )
2888
          .arg( mSchemaTableName );
3012
          .arg( mQuery );
2889 3013

  
2890 3014
    //it is possible that the where clause restricts the feature type
2891 3015
    if ( !sqlWhereClause.isEmpty() )
......
2921 3045
      {
2922 3046
        sql += QString( "(select %1 from %2 where %1 is not null limit %3) as t" )
2923 3047
               .arg( quotedIdentifier( geometryColumn ) )
2924
               .arg( mSchemaTableName )
3048
               .arg( mQuery )
2925 3049
               .arg( sGeomTypeSelectLimit );
2926 3050
      }
2927 3051
      else
2928 3052
      {
2929
        sql += mSchemaTableName;
3053
        sql += mQuery;
2930 3054
      }
2931 3055

  
2932
      if ( mUri.sql() != "" )
2933
        sql += " where " + mUri.sql();
3056
      if ( !sqlWhereClause.isEmpty() )
3057
        sql += " where " + sqlWhereClause;
2934 3058

  
2935 3059
      result = connectionRO->PQexec( sql );
2936 3060

  
......
2967 3091
    {
2968 3092
      showMessageBox( tr( "Unknown geometry type" ),
2969 3093
                      tr( "Column %1 in %2 has a geometry type of %3, which Quantum GIS does not currently support." )
2970
                      .arg( geometryColumn ).arg( mSchemaTableName ).arg( fType ) );
3094
                      .arg( geometryColumn ).arg( mQuery ).arg( fType ) );
2971 3095
      valid = false;
2972 3096
    }
2973 3097
  }
......
2975 3099
  {
2976 3100
    log.prepend( tr( "Quantum GIS was unable to determine the type and srid of column %1 in %2. The database communication log was:\n%3" )
2977 3101
                 .arg( geometryColumn )
2978
                 .arg( mSchemaTableName )
3102
                 .arg( mQuery )
2979 3103
                 .arg( QString::fromUtf8( PQresultErrorMessage( result ) ) ) );
2980 3104
    showMessageBox( tr( "Unable to get feature type and srid" ), log );
2981 3105
  }
......
3040 3164
      QgsDebugMsgLevel( err, 3 );
3041 3165
    }
3042 3166
  }
3167
  else
3168
  {
3169
    QgsDebugMsgLevel( QString( "Query failed: %1" ).arg( query ), 3 );
3170
  }
3043 3171
#endif
3044 3172

  
3045 3173
  return res;
......
3081 3209
      QString err = QString( "Query: %1 returned %2 [%3]" )
3082 3210
                    .arg( query )
3083 3211
                    .arg( errorStatus )
3084
                    .arg( PQresultErrorMessage( res ) );
3212
                    .arg( QString::fromUtf8( PQresultErrorMessage( res ) ) );
3085 3213
      QgsDebugMsgLevel( err, 3 );
3086 3214
#endif
3087 3215
      if ( openCursors )
3088 3216
      {
3089 3217
        PQexecNR( "ROLLBACK" );
3090
        QgsDebugMsg( QString( "Re-starting read-only transaction after errornous statement - state of %1 cursors lost" ).arg( openCursors ) );
3218

  
3219
        QgsPostgresProvider::showMessageBox(
3220
          tr( "Query failed" ),
3221
          tr( "%1 cursor states lost.\nSQL: %2\nResult: %3 (%4)" )
3222
          .arg( openCursors )
3223
          .arg( query )
3224
          .arg( errorStatus )
3225
          .arg( QString::fromUtf8( PQresultErrorMessage( res ) ) ) );
3226
        openCursors = 0;
3227

  
3091 3228
        PQexecNR( "BEGIN READ ONLY" );
3092 3229
      }
3093 3230
    }