Skip to content

Commit d1fd588

Browse files
committed
Add method to fetch constraints from a vector data provider
Implemented for unique and not null constraints for postgres provider
1 parent 5e3bef7 commit d1fd588

File tree

7 files changed

+119
-1
lines changed

7 files changed

+119
-1
lines changed

python/core/qgsvectordataprovider.sip

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ class QgsVectorDataProvider : QgsDataProvider
5555
/** Bitmask of all provider's editing capabilities */
5656
static const int EditingCapabilities;
5757

58+
/**
59+
* Constraints which may be present on a field.
60+
* @note added in QGIS 3.0
61+
*/
62+
enum Constraint
63+
{
64+
ConstraintNotNull, //!< Field may not be null
65+
ConstraintUnique, //!< Field must have a unique value
66+
};
67+
typedef QFlags<QgsVectorDataProvider::Constraint> Constraints;
68+
5869
/**
5970
* Constructor of the vector provider
6071
* @param uri uniform resource locator (URI) for a dataset
@@ -230,6 +241,13 @@ class QgsVectorDataProvider : QgsDataProvider
230241
*/
231242
virtual QVariant defaultValue( int fieldId ) const;
232243

244+
/**
245+
* Returns any constraints which are present at the provider for a specified
246+
* field index.
247+
* @note added in QGIS 3.0
248+
*/
249+
virtual QgsVectorDataProvider::Constraints fieldConstraints( int fieldIndex ) const;
250+
233251
/**
234252
* Changes geometries of existing features
235253
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
@@ -434,3 +452,4 @@ class QgsVectorDataProvider : QgsDataProvider
434452
};
435453

436454
QFlags<QgsVectorDataProvider::Capability> operator|(QgsVectorDataProvider::Capability f1, QFlags<QgsVectorDataProvider::Capability> f2);
455+
QFlags<QgsVectorDataProvider::Constraint> operator|(QgsVectorDataProvider::Constraint f1, QFlags<QgsVectorDataProvider::Constraint> f2);

src/core/qgsvectordataprovider.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ QVariant QgsVectorDataProvider::defaultValue( int fieldId ) const
9898
return QVariant();
9999
}
100100

101+
QgsVectorDataProvider::Constraints QgsVectorDataProvider::fieldConstraints( int fieldIndex ) const
102+
{
103+
Q_UNUSED( fieldIndex );
104+
return 0;
105+
}
106+
101107
bool QgsVectorDataProvider::changeGeometryValues( const QgsGeometryMap &geometry_map )
102108
{
103109
Q_UNUSED( geometry_map );

src/core/qgsvectordataprovider.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,17 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
106106
ChangeAttributeValues | ChangeGeometries | AddAttributes | DeleteAttributes |
107107
RenameAttributes;
108108

109+
/**
110+
* Constraints which may be present on a field.
111+
* @note added in QGIS 3.0
112+
*/
113+
enum Constraint
114+
{
115+
ConstraintNotNull = 1, //!< Field may not be null
116+
ConstraintUnique = 1 << 1, //!< Field must have a unique value
117+
};
118+
Q_DECLARE_FLAGS( Constraints, Constraint )
119+
109120
/**
110121
* Constructor of the vector provider
111122
* @param uri uniform resource locator (URI) for a dataset
@@ -281,6 +292,13 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
281292
*/
282293
virtual QVariant defaultValue( int fieldId ) const;
283294

295+
/**
296+
* Returns any constraints which are present at the provider for a specified
297+
* field index.
298+
* @note added in QGIS 3.0
299+
*/
300+
virtual Constraints fieldConstraints( int fieldIndex ) const;
301+
284302
/**
285303
* Changes geometries of existing features
286304
* @param geometry_map A QgsGeometryMap whose index contains the feature IDs
@@ -520,6 +538,7 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider
520538
};
521539

522540
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorDataProvider::Capabilities )
541+
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorDataProvider::Constraints )
523542

524543

525544
#endif

src/providers/postgres/qgspostgresprovider.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,7 @@ bool QgsPostgresProvider::loadFields()
718718

719719
QMap<int, QMap<int, QString> > fmtFieldTypeMap, descrMap, defValMap;
720720
QMap<int, QMap<int, int> > attTypeIdMap;
721+
QMap<int, QMap<int, bool> > notNullMap, uniqueMap;
721722
if ( result.PQnfields() > 0 )
722723
{
723724
// Collect table oids
@@ -742,9 +743,13 @@ bool QgsPostgresProvider::loadFields()
742743
QString tableoidsFilter = '(' + tableoidsList.join( QStringLiteral( "," ) ) + ')';
743744

744745
// Collect formatted field types
745-
sql = "SELECT attrelid, attnum, pg_catalog.format_type(atttypid,atttypmod), pg_catalog.col_description(attrelid,attnum), pg_catalog.pg_get_expr(adbin,adrelid), atttypid"
746+
sql = "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"
746747
" FROM pg_attribute"
747748
" LEFT OUTER JOIN pg_attrdef ON attrelid=adrelid AND attnum=adnum"
749+
750+
// find unique constraints if present. Text cast required to handle int2vector comparison. Distinct required as multiple unique constraints may exist
751+
" LEFT OUTER JOIN ( SELECT DISTINCT indrelid, indkey, indisunique FROM pg_index WHERE indisunique ) uniq ON attrelid=indrelid AND attnum::text=indkey::text "
752+
748753
" WHERE attrelid IN " + tableoidsFilter;
749754
QgsPostgresResult fmtFieldTypeResult( connectionRO()->PQexec( sql ) );
750755
for ( int i = 0; i < fmtFieldTypeResult.PQntuples(); ++i )
@@ -755,10 +760,14 @@ bool QgsPostgresProvider::loadFields()
755760
QString descr = fmtFieldTypeResult.PQgetvalue( i, 3 );
756761
QString defVal = fmtFieldTypeResult.PQgetvalue( i, 4 );
757762
int attType = fmtFieldTypeResult.PQgetvalue( i, 5 ).toInt();
763+
bool attNotNull = fmtFieldTypeResult.PQgetvalue( i, 6 ).toInt();
764+
bool uniqueConstraint = fmtFieldTypeResult.PQgetvalue( i, 7 ).toInt();
758765
fmtFieldTypeMap[attrelid][attnum] = formatType;
759766
descrMap[attrelid][attnum] = descr;
760767
defValMap[attrelid][attnum] = defVal;
761768
attTypeIdMap[attrelid][attnum] = attType;
769+
notNullMap[attrelid][attnum] = attNotNull;
770+
uniqueMap[attrelid][attnum] = uniqueConstraint;
762771
}
763772
}
764773
}
@@ -988,6 +997,14 @@ bool QgsPostgresProvider::loadFields()
988997

989998
mAttrPalIndexName.insert( i, fieldName );
990999
mDefaultValues.insert( mAttributeFields.size(), defValMap[tableoid][attnum] );
1000+
1001+
Constraints constraints = 0;
1002+
if ( notNullMap[tableoid][attnum] )
1003+
constraints |= ConstraintNotNull;
1004+
if ( uniqueMap[tableoid][attnum] )
1005+
constraints |= ConstraintUnique;
1006+
mFieldConstraints.insert( mAttributeFields.size(), constraints );
1007+
9911008
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment, fieldSubType ) );
9921009
}
9931010

@@ -1719,6 +1736,11 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const
17191736
return defVal;
17201737
}
17211738

1739+
QgsVectorDataProvider::Constraints QgsPostgresProvider::fieldConstraints( int fieldIndex ) const
1740+
{
1741+
return mFieldConstraints.value( fieldIndex, 0 );
1742+
}
1743+
17221744
QString QgsPostgresProvider::paramValue( const QString& fieldValue, const QString &defaultValue ) const
17231745
{
17241746
if ( fieldValue.isNull() )

src/providers/postgres/qgspostgresprovider.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
161161
QgsAttributeList attributeIndexes() const override;
162162
QgsAttributeList pkAttributeIndexes() const override { return mPrimaryKeyAttrs; }
163163
QVariant defaultValue( int fieldId ) const override;
164+
Constraints fieldConstraints( int fieldIndex ) const override;
164165

165166
/** Adds a list of features
166167
@return true in case of success and false in case of failure*/
@@ -493,6 +494,7 @@ class QgsPostgresProvider : public QgsVectorDataProvider
493494
void setTransaction( QgsTransaction* transaction ) override;
494495

495496
QHash<int, QString> mDefaultValues;
497+
QHash<int, QgsVectorDataProvider::Constraints > mFieldConstraints;
496498
};
497499

498500

tests/src/python/test_provider_postgres.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
QgsFeatureRequest,
2727
QgsFeature,
2828
QgsTransactionGroup,
29+
QgsVectorDataProvider,
2930
NULL
3031
)
3132
from qgis.gui import QgsEditorWidgetRegistry
@@ -437,6 +438,34 @@ def testDoubleArray(self):
437438
self.assertTrue(isinstance(f.attributes()[value_idx], list))
438439
self.assertEqual(f.attributes()[value_idx], [1.1, 2, -5.12345])
439440

441+
def testNotNullConstraint(self):
442+
vl = QgsVectorLayer('%s table="qgis_test"."constraints" sql=' % (self.dbconn), "constraints", "postgres")
443+
self.assertTrue(vl.isValid())
444+
self.assertEqual(len(vl.fields()), 4)
445+
446+
# test some bad field indexes
447+
self.assertEqual(vl.dataProvider().fieldConstraints(-1), QgsVectorDataProvider.Constraints())
448+
self.assertEqual(vl.dataProvider().fieldConstraints(1001), QgsVectorDataProvider.Constraints())
449+
450+
self.assertTrue(vl.dataProvider().fieldConstraints(0) & QgsVectorDataProvider.ConstraintNotNull)
451+
self.assertFalse(vl.dataProvider().fieldConstraints(1) & QgsVectorDataProvider.ConstraintNotNull)
452+
self.assertTrue(vl.dataProvider().fieldConstraints(2) & QgsVectorDataProvider.ConstraintNotNull)
453+
self.assertFalse(vl.dataProvider().fieldConstraints(3) & QgsVectorDataProvider.ConstraintNotNull)
454+
455+
def testUniqueConstraint(self):
456+
vl = QgsVectorLayer('%s table="qgis_test"."constraints" sql=' % (self.dbconn), "constraints", "postgres")
457+
self.assertTrue(vl.isValid())
458+
self.assertEqual(len(vl.fields()), 4)
459+
460+
# test some bad field indexes
461+
self.assertEqual(vl.dataProvider().fieldConstraints(-1), QgsVectorDataProvider.Constraints())
462+
self.assertEqual(vl.dataProvider().fieldConstraints(1001), QgsVectorDataProvider.Constraints())
463+
464+
self.assertTrue(vl.dataProvider().fieldConstraints(0) & QgsVectorDataProvider.ConstraintUnique)
465+
self.assertTrue(vl.dataProvider().fieldConstraints(1) & QgsVectorDataProvider.ConstraintUnique)
466+
self.assertTrue(vl.dataProvider().fieldConstraints(2) & QgsVectorDataProvider.ConstraintUnique)
467+
self.assertFalse(vl.dataProvider().fieldConstraints(3) & QgsVectorDataProvider.ConstraintUnique)
468+
440469
# See http://hub.qgis.org/issues/15188
441470
def testNumericPrecision(self):
442471
uri = 'point?field=f1:int'

tests/testdata/provider/testdata_pg.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,3 +458,24 @@ CREATE TABLE qgis_test.widget_styles(
458458

459459
INSERT INTO qgis_editor_widget_styles VALUES
460460
('qgis_test', 'widget_styles', 'fld1', 'FooEdit', '<config><option key="param1" value="value1"/><option key="param2" value="2"/></config>');
461+
462+
463+
-----------------------------
464+
-- Table for constraint tests
465+
--
466+
467+
DROP TABLE IF EXISTS qgis_test.constraints;
468+
CREATE TABLE qgis_test.constraints
469+
(
470+
gid serial NOT NULL PRIMARY KEY, -- implicit unique key
471+
val int, -- unique constraint
472+
name text NOT NULL, -- unique index
473+
description text,
474+
CONSTRAINT constraint_val UNIQUE (val),
475+
CONSTRAINT constraint_val2 UNIQUE (val) -- create double unique constraint for test
476+
);
477+
478+
CREATE UNIQUE INDEX constraints_uniq
479+
ON qgis_test.constraints
480+
USING btree
481+
(name COLLATE pg_catalog."default"); -- unique index

0 commit comments

Comments
 (0)