Skip to content

Commit

Permalink
add test for detecting composite key relations (#31092)
Browse files Browse the repository at this point in the history
  • Loading branch information
3nids committed Aug 6, 2019
1 parent e4faaf4 commit 0698eed
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 46 deletions.
88 changes: 51 additions & 37 deletions tests/src/python/test_qgsrelationeditwidget.py
Expand Up @@ -58,27 +58,29 @@ def setUpClass(cls):
if 'QGIS_PGTEST_DB' in os.environ:
cls.dbconn = os.environ['QGIS_PGTEST_DB']
# Create test layer
cls.vl_b = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'books', 'postgres')
cls.vl_a = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'authors', 'postgres')
cls.vl_link = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'books_authors', 'postgres')
cls.vl_books = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books" sql=', 'books', 'postgres')
cls.vl_authors = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."authors" sql=', 'authors', 'postgres')
cls.vl_editors = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'fk_book,fk_author\' table="qgis_test"."editors" sql=', 'editors', 'postgres')
cls.vl_link_books_authors = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' table="qgis_test"."books_authors" sql=', 'books_authors', 'postgres')

QgsProject.instance().addMapLayer(cls.vl_b)
QgsProject.instance().addMapLayer(cls.vl_a)
QgsProject.instance().addMapLayer(cls.vl_link)
QgsProject.instance().addMapLayer(cls.vl_books)
QgsProject.instance().addMapLayer(cls.vl_authors)
QgsProject.instance().addMapLayer(cls.vl_editors)
QgsProject.instance().addMapLayer(cls.vl_link_books_authors)

cls.relMgr = QgsProject.instance().relationManager()

cls.rel_a = QgsRelation()
cls.rel_a.setReferencingLayer(cls.vl_link.id())
cls.rel_a.setReferencedLayer(cls.vl_a.id())
cls.rel_a.setReferencingLayer(cls.vl_link_books_authors.id())
cls.rel_a.setReferencedLayer(cls.vl_authors.id())
cls.rel_a.addFieldPair('fk_author', 'pk')
cls.rel_a.setId('rel_a')
assert(cls.rel_a.isValid())
cls.relMgr.addRelation(cls.rel_a)

cls.rel_b = QgsRelation()
cls.rel_b.setReferencingLayer(cls.vl_link.id())
cls.rel_b.setReferencedLayer(cls.vl_b.id())
cls.rel_b.setReferencingLayer(cls.vl_link_books_authors.id())
cls.rel_b.setReferencedLayer(cls.vl_books.id())
cls.rel_b.addFieldPair('fk_book', 'pk')
cls.rel_b.setId('rel_b')
assert(cls.rel_b.isValid())
Expand All @@ -87,9 +89,10 @@ def setUpClass(cls):
# Our mock QgsVectorLayerTools, that allow injecting data where user input is expected
cls.vltools = VlTools()

assert(cls.vl_a.isValid())
assert(cls.vl_b.isValid())
assert(cls.vl_link.isValid())
assert(cls.vl_authors.isValid())
assert(cls.vl_books.isValid())
assert(cls.vl_editors.isValid())
assert(cls.vl_link_books_authors.isValid())

def setUp(self):
self.startTransaction()
Expand All @@ -102,13 +105,13 @@ def test_delete_feature(self):
"""
Check if a feature can be deleted properly
"""
self.createWrapper(self.vl_a, '"name"=\'Erich Gamma\'')
self.createWrapper(self.vl_authors, '"name"=\'Erich Gamma\'')

self.assertEqual(self.table_view.model().rowCount(), 1)

self.assertEqual(1, len([f for f in self.vl_b.getFeatures()]))
self.assertEqual(1, len([f for f in self.vl_books.getFeatures()]))

fid = next(self.vl_b.getFeatures(QgsFeatureRequest().setFilterExpression('"name"=\'Design Patterns. Elements of Reusable Object-Oriented Software\''))).id()
fid = next(self.vl_books.getFeatures(QgsFeatureRequest().setFilterExpression('"name"=\'Design Patterns. Elements of Reusable Object-Oriented Software\''))).id()

self.widget.featureSelectionManager().select([fid])

Expand All @@ -126,50 +129,50 @@ def clickOk():
btn.click()

# This is the important check that the feature is deleted
self.assertEqual(0, len([f for f in self.vl_b.getFeatures()]))
self.assertEqual(0, len([f for f in self.vl_books.getFeatures()]))

# This is actually more checking that the database on delete action is properly set on the relation
self.assertEqual(0, len([f for f in self.vl_link.getFeatures()]))
self.assertEqual(0, len([f for f in self.vl_link_books_authors.getFeatures()]))

self.assertEqual(self.table_view.model().rowCount(), 0)

def test_list(self):
"""
Simple check if several related items are shown
"""
wrapper = self.createWrapper(self.vl_b) # NOQA
wrapper = self.createWrapper(self.vl_books) # NOQA

self.assertEqual(self.table_view.model().rowCount(), 4)

def test_add_feature(self):
"""
Check if a new related feature is added
"""
self.createWrapper(self.vl_a, '"name"=\'Douglas Adams\'')
self.createWrapper(self.vl_authors, '"name"=\'Douglas Adams\'')

self.assertEqual(self.table_view.model().rowCount(), 0)

self.vltools.setValues([None, 'The Hitchhiker\'s Guide to the Galaxy'])
self.vltools.setValues([None, 'The Hitchhiker\'s Guide to the Galaxy', 'Sputnik Editions', 1961])
btn = self.widget.findChild(QToolButton, 'mAddFeatureButton')
btn.click()

# Book entry has been created
self.assertEqual(2, len([f for f in self.vl_b.getFeatures()]))
self.assertEqual(2, len([f for f in self.vl_books.getFeatures()]))

# Link entry has been created
self.assertEqual(5, len([f for f in self.vl_link.getFeatures()]))
self.assertEqual(5, len([f for f in self.vl_link_books_authors.getFeatures()]))

self.assertEqual(self.table_view.model().rowCount(), 1)

def test_link_feature(self):
"""
Check if an existing feature can be linked
"""
wrapper = self.createWrapper(self.vl_a, '"name"=\'Douglas Adams\'') # NOQA
wrapper = self.createWrapper(self.vl_authors, '"name"=\'Douglas Adams\'') # NOQA

f = QgsFeature(self.vl_b.fields())
f.setAttributes([self.vl_b.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy'])
self.vl_b.addFeature(f)
f = QgsFeature(self.vl_books.fields())
f.setAttributes([self.vl_books.dataProvider().defaultValueClause(0), 'The Hitchhiker\'s Guide to the Galaxy', 'Sputnik Editions', 1961])
self.vl_books.addFeature(f)

def choose_linked_feature():
dlg = QApplication.activeModalWidget()
Expand All @@ -187,7 +190,7 @@ def choose_linked_feature():
btn.click()
# magically the above code selects the feature here...

link_feature = next(self.vl_link.getFeatures(QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format(f[0]))))
link_feature = next(self.vl_link_books_authors.getFeatures(QgsFeatureRequest().setFilterExpression('"fk_book"={}'.format(f[0]))))
self.assertIsNotNone(link_feature[0])

self.assertEqual(self.table_view.model().rowCount(), 1)
Expand All @@ -196,12 +199,12 @@ def test_unlink_feature(self):
"""
Check if a linked feature can be unlinked
"""
wrapper = self.createWrapper(self.vl_b) # NOQA
wrapper = self.createWrapper(self.vl_books) # NOQA

# All authors are listed
self.assertEqual(self.table_view.model().rowCount(), 4)

it = self.vl_a.getFeatures(
it = self.vl_authors.getFeatures(
QgsFeatureRequest().setFilterExpression('"name" IN (\'Richard Helm\', \'Ralph Johnson\')'))

self.widget.featureSelectionManager().select([f.id() for f in it])
Expand All @@ -212,15 +215,15 @@ def test_unlink_feature(self):
btn.click()

# This is actually more checking that the database on delete action is properly set on the relation
self.assertEqual(2, len([f for f in self.vl_link.getFeatures()]))
self.assertEqual(2, len([f for f in self.vl_link_books_authors.getFeatures()]))

self.assertEqual(2, self.table_view.model().rowCount())

def test_discover_relations(self):
"""
Test the automatic discovery of relations
"""
relations = self.relMgr.discoverRelations([], [self.vl_a, self.vl_b, self.vl_link])
relations = self.relMgr.discoverRelations([], [self.vl_authors, self.vl_books, self.vl_link_books_authors])
relations = {r.name(): r for r in relations}
self.assertEqual({'books_authors_fk_book_fkey', 'books_authors_fk_author_fkey'}, set(relations.keys()))

Expand All @@ -238,16 +241,27 @@ def test_discover_relations(self):
self.assertEqual([1], ba2a.referencingFields())
self.assertEqual([0], ba2a.referencedFields())

self.assertEqual([], self.relMgr.discoverRelations([self.rel_a, self.rel_b], [self.vl_a, self.vl_b, self.vl_link]))
self.assertEqual(1, len(self.relMgr.discoverRelations([], [self.vl_a, self.vl_link])))
self.assertEqual([], self.relMgr.discoverRelations([self.rel_a, self.rel_b], [self.vl_authors, self.vl_books, self.vl_link_books_authors]))
self.assertEqual(1, len(self.relMgr.discoverRelations([], [self.vl_authors, self.vl_link_books_authors])))

# composite keys relation
relations = self.relMgr.discoverRelations([], [self.vl_books, self.vl_editors])
self.assertEqual(len(relations), 1)
relation = relations[0]
self.assertEqual('books_fk_editor_fkey', relation.name())
self.assertTrue(relation.isValid())
self.assertEqual('books', relation.referencingLayer().name())
self.assertEqual('editors', relation.referencedLayer().name())
self.assertEqual([2, 3], relation.referencingFields())
self.assertEqual([0, 1], relation.referencedFields())

def startTransaction(self):
"""
Start a new transaction and set all layers into transaction mode.
:return: None
"""
lyrs = [self.vl_a, self.vl_b, self.vl_link]
lyrs = [self.vl_authors, self.vl_books, self.vl_link_books_authors]

self.transaction = QgsTransaction.create(lyrs)
self.transaction.begin()
Expand All @@ -262,7 +276,7 @@ def rollbackTransaction(self):
:return: None
"""
lyrs = [self.vl_a, self.vl_b, self.vl_link]
lyrs = [self.vl_authors, self.vl_books, self.vl_link_books_authors]
for l in lyrs:
l.commitChanges()
self.transaction.rollback()
Expand All @@ -279,7 +293,7 @@ def createWrapper(self, layer, filter=None):
:return: The created wrapper
"""
if layer == self.vl_b:
if layer == self.vl_books:
relation = self.rel_b
nmrel = self.rel_a
else:
Expand Down
41 changes: 32 additions & 9 deletions tests/testdata/provider/testdata_pg_reltests.sql
@@ -1,32 +1,47 @@

-- Table: qgis_test.authors

DROP TABLE IF EXISTS qgis_test.books_authors;
DROP TABLE IF EXISTS qgis_test.authors;
DROP TABLE IF EXISTS qgis_test.books;
DROP TABLE IF EXISTS qgis_test.authors;
DROP TABLE IF EXISTS qgis_test.editors;


-- Table: qgis_test.authors

CREATE TABLE qgis_test.authors
(
pk serial NOT NULL,
name character varying(255),
name text,
CONSTRAINT authors_pkey PRIMARY KEY (pk),
CONSTRAINT authors_name_key UNIQUE (name)
);

-- Table: qgis_test.books
-- Table: qgis_test.editors

CREATE TABLE qgis_test.editors
(
name text NOT NULL,
year smallint NOT NULL,
CONSTRAINT editors_pkey PRIMARY KEY (name, year)
);


-- Table: qgis_test.books

CREATE TABLE qgis_test.books
(
pk serial NOT NULL,
name character varying(255),
fk_editor_name text NOT NULL,
fk_editor_year integer NOT NULL,
CONSTRAINT books_pkey PRIMARY KEY (pk),
CONSTRAINT books_name_key UNIQUE (name)
CONSTRAINT books_name_key UNIQUE (name),
CONSTRAINT books_fk_editor_fkey FOREIGN KEY (fk_editor_name, fk_editor_year)
REFERENCES qgis_test.editors (name, year) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE
);

-- Table: qgis_test.books_authors


CREATE TABLE qgis_test.books_authors
(
fk_book integer NOT NULL,
Expand All @@ -50,9 +65,17 @@ INSERT INTO qgis_test.authors(name)
('Ken Follett'),
('Gabriel García Márquez');

INSERT INTO qgis_test.books(name)
INSERT INTO qgis_test.editors(name, year)
VALUES
('Sputnik Editions', 1961),
('Apollo Editions', 1969);


INSERT INTO qgis_test.books(name, fk_editor_name, fk_editor_year)
VALUES
('Design Patterns. Elements of Reusable Object-Oriented Software');
('Design Patterns. Elements of Reusable Object-Oriented Software',
'Sputnik Editions',
1961);

INSERT INTO qgis_test.books_authors(fk_book, fk_author)
VALUES
Expand Down

0 comments on commit 0698eed

Please sign in to comment.