From 02eb5a9dd760da7f040fbf83ea3ce180feb98acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Wed, 29 Oct 2025 20:15:45 -0500 Subject: [PATCH 1/7] PostGIS: Make sure BAGs OF are identified, even if they don't have a mapping=ARRAY meta attr --- modelbaker/dbconnector/pg_connector.py | 27 ++++++- tests/test_domain_class_relations.py | 34 ++++++++- tests/test_projectgen.py | 98 +++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 3 deletions(-) diff --git a/modelbaker/dbconnector/pg_connector.py b/modelbaker/dbconnector/pg_connector.py index b22207a..94440aa 100644 --- a/modelbaker/dbconnector/pg_connector.py +++ b/modelbaker/dbconnector/pg_connector.py @@ -804,7 +804,8 @@ def get_bags_of_info(self): cur.execute( sql.SQL( """SELECT cprop.tablename as current_layer_name, cprop.columnname as attribute, cprop.setting as target_layer_name, - meta_attrs_cardinality_min.attr_value as cardinality_min, meta_attrs_cardinality_max.attr_value as cardinality_max + meta_attrs_cardinality_min.attr_value as cardinality_min, meta_attrs_cardinality_max.attr_value as cardinality_max, + meta_attrs_array.attr_value as mapping_type FROM {schema}.t_ili2db_column_prop as cprop LEFT JOIN {schema}.t_ili2db_classname as cname ON cname.sqlname = cprop.tablename @@ -815,6 +816,30 @@ def get_bags_of_info(self): LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_max ON meta_attrs_cardinality_max.ilielement ILIKE cname.iliname||'.'||cprop.columnname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' AND meta_attrs_array.attr_value = 'ARRAY' + + UNION ALL + + -- Select BAGs OF with no mapping + SELECT cprop.setting as current_layer_name + , cprop.columnname as attribute + , cprop.tablename as target_layer_name + , meta_attrs_cardinality_min.attr_value as cardinality_min + , meta_attrs_cardinality_max.attr_value as cardinality_max + , '' as mapping_type + FROM {schema}.t_ili2db_column_prop as cprop + -- Get only structures (therefore, no LEFT JOIN here) + JOIN {schema}.t_ili2db_table_prop as tprop + ON tprop.tag = 'ch.ehi.ili2db.tableKind' and tprop.setting = 'STRUCTURE' and tprop.tablename = cprop.tablename + -- Get target table + LEFT JOIN {schema}.t_ili2db_attrname as aname + ON aname.target = cprop.setting and colowner = cprop.tablename and cprop.columnname = aname.sqlname + -- Get cardinalities + LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_max + ON meta_attrs_cardinality_max.ilielement = aname.iliname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' + LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_min + ON meta_attrs_cardinality_min.ilielement = aname.iliname AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' + -- Only FKs, only :M relations (therefore, BAGs OF) + WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' and meta_attrs_cardinality_max.attr_value not in ('0','1') """ ).format( schema=sql.Identifier(self.schema), diff --git a/tests/test_domain_class_relations.py b/tests/test_domain_class_relations.py index 5478b18..8432e2c 100644 --- a/tests/test_domain_class_relations.py +++ b/tests/test_domain_class_relations.py @@ -2501,6 +2501,38 @@ def test_domain_structure_relations_KbS_LV95_V1_3_postgis(self): "t_id", "dispname", ], + [ + "belasteter_standort_geo_lage_polygon", + "belasteter_standort_parzellenverweis", + "0..*", + "parzellenidentifikation", + "t_id", + "dispname", + ], + [ + "belasteter_standort_geo_lage_polygon", + "belasteter_standort_egrid", + "0..*", + "egrid_", + "t_id", + "dispname", + ], + [ + "belasteter_standort_geo_lage_punkt", + "belasteter_standort_parzellenverweis", + "0..*", + "parzellenidentifikation", + "t_id", + "dispname", + ], + [ + "belasteter_standort_geo_lage_punkt", + "belasteter_standort_egrid", + "0..*", + "egrid_", + "t_id", + "dispname", + ], ] count = 0 @@ -2521,7 +2553,7 @@ def test_domain_structure_relations_KbS_LV95_V1_3_postgis(self): value_field, ] in expected_bags_of_enum - assert count == 4 + assert count == len(expected_bags_of_enum) def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_postgis(self): # Schema Import diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index 26c362c..f4cf2cc 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -2103,6 +2103,102 @@ def test_bagof_cardinalities_postgis(self): ["catarrays_None", "catbag_1", "1..*", "refitemitem", "t_id", "dispname"], ["catarrays_None", "catlist_0", "0..*", "refitemitem", "t_id", "dispname"], ["catarrays_None", "catlist_1", "1..*", "refitemitem", "t_id", "dispname"], + [ + "catarrays_None", + "catarrays_catlistnoarray_1", + "1..*", + "refitem", + "t_id", + "dispname", + ], + [ + "catarrays_None", + "catarrays_catlistnoarray_0", + "0..*", + "refitem", + "t_id", + "dispname", + ], + [ + "catarrays_None", + "catarrays_catbagnoarray_0", + "0..*", + "refitem", + "t_id", + "dispname", + ], + [ + "catarrays_None", + "catarrays_catbagnoarray_1", + "1..*", + "refitem", + "t_id", + "dispname", + ], + [ + "enumarrays_None", + "enumarrays_enumlistnoarray_1", + "1..*", + "ei_typ", + "t_id", + "dispname", + ], + [ + "enumarrays_None", + "enumarrays_enumlistnoarray_0", + "0..*", + "ei_typ", + "t_id", + "dispname", + ], + [ + "enumarrays_None", + "enumarrays_enumbagnoarray_1", + "1..*", + "ei_typ", + "t_id", + "dispname", + ], + [ + "enumarrays_None", + "enumarrays_enumbagnoarray_0", + "0..*", + "ei_typ", + "t_id", + "dispname", + ], + [ + "textarrays_None", + "textarrays_textbagnoarray_1", + "1..*", + "structtext", + "t_id", + "dispname", + ], + [ + "textarrays_None", + "textarrays_textbagnoarray_0", + "0..*", + "structtext", + "t_id", + "dispname", + ], + [ + "textarrays_None", + "textarrays_textlistnoarray_0", + "0..*", + "structtext", + "t_id", + "dispname", + ], + [ + "textarrays_None", + "textarrays_textlistnoarray_1", + "1..*", + "structtext", + "t_id", + "dispname", + ], ] count = 0 @@ -2123,7 +2219,7 @@ def test_bagof_cardinalities_postgis(self): value_field, ] in expected_bags_of_enum - assert count == 8 + assert count == len(expected_bags_of_enum) # Test widget type and constraints count = 0 From 2040f6062d648e4f86b641c02ccad490600d4ef8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Wed, 29 Oct 2025 20:17:12 -0500 Subject: [PATCH 2/7] [fix] Avoid supressing a CatalogueRef if it's used by an attribute with a BAG OF with no mapping meta attr --- modelbaker/generator/generator.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/modelbaker/generator/generator.py b/modelbaker/generator/generator.py index 2391bc8..0ddfcc8 100644 --- a/modelbaker/generator/generator.py +++ b/modelbaker/generator/generator.py @@ -688,10 +688,24 @@ def suppress_catalogue_reference_layers(available_layers, relations, bags_of_enu # Remove reference layer if they are not BAG OF for item in catalogue_items: is_bag_of = False + + # First get the ref pointing to the item + ref_to_item_iliname, ref_to_item = "", "" + for relation in relations: + if relation.referenced_layer.ili_name == item["ili_name"]: + for ref in catalogue_refs: + if relation.referencing_layer.ili_name == ref["ili_name"]: + # We've found the corresponding ref structure + ref_to_item_iliname = ref["ili_name"] + ref_to_item = ref["name"] + break + + # Check if there is a BAG OF pointing to the item or to the ref for bag_of_layer_k, bag_of_layer_v in bags_of_enum.items(): for bag_of_attr_k, bag_of_data in bag_of_layer_v.items(): if ( - item["name"] == bag_of_data[2].name + item["name"] == bag_of_data[2].name # mapping=ARRAY + or ref_to_item == bag_of_data[2].name # no mapping ): # BAG OF's target_layer_name is_bag_of = True @@ -701,15 +715,7 @@ def suppress_catalogue_reference_layers(available_layers, relations, bags_of_enu # The ref has no BAG OF pointing to the item, therefore, # we'll suppress the ref cause users won't need it to add data. - - # First get the ref pointing to the item - for relation in relations: - if relation.referenced_layer.ili_name == item["ili_name"]: - for ref in catalogue_refs: - if relation.referencing_layer.ili_name == ref["ili_name"]: - # We've found the corresponding ref structure - layers_to_remove.append(ref["ili_name"]) - break + layers_to_remove.append(ref_to_item_iliname) # Finally, remove the ref layers that we've found are not BAGS OF from the list, # as well as the relations where they are involved (i.e., referenced/referencing) From 5dfba25e25df072ce606a78c92fa082088e10320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Wed, 29 Oct 2025 20:26:04 -0500 Subject: [PATCH 3/7] [tests] Test for fix #1082 (Filtering out BAG OF Catalogue Relations) --- tests/test_projectgen.py | 44 +++++++++++++++++++ .../ilimodels/BagOfNoMappingMetaAttr.ili | 35 +++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index f4cf2cc..f8eeb3c 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -4884,6 +4884,50 @@ def test_catalogue_reference_layer_list_of_geopackage(self): assert layer_count_before == layer_count_after assert relation_count_before == relation_count_after + def test_catalogue_reference_layer_bag_of_postgis_no_mapping(self): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2pg + importer.configuration = iliimporter_config(importer.tool) + importer.configuration.ilifile = testdata_path( + "ilimodels/BagOfNoMappingMetaAttr.ili" + ) + importer.configuration.ilimodels = "NoArrayMapping" + importer.configuration.dbschema = ( + "catalogue_ref_bag_of_no_mapping_{:%Y%m%d%H%M%S%f}".format( + datetime.datetime.now() + ) + ) + + importer.configuration.srs_code = 2056 + importer.configuration.inheritance = "smart2" + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + assert importer.run() == iliimporter.Importer.SUCCESS + + generator = Generator( + DbIliMode.ili2pg, + get_pg_connection_string(), + importer.configuration.inheritance, + importer.configuration.dbschema, + ) + + available_layers = generator.layers() + relations, bags_of = generator.relations(available_layers) + + layer_count_before = len(available_layers) + relation_count_before = len(relations) + + available_layers, relations = generator.suppress_catalogue_reference_layers( + available_layers, relations, bags_of + ) + + layer_count_after = len(available_layers) + relation_count_after = len(relations) + + # Test that no reference layer and therefore, no relation, has been removed + assert layer_count_before == layer_count_after + assert relation_count_before == relation_count_after + def test_catalogue_reference_layer_no_bag_of_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg diff --git a/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili b/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili new file mode 100644 index 0000000..9b5ad2e --- /dev/null +++ b/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili @@ -0,0 +1,35 @@ +INTERLIS 2.3; + +MODEL NoArrayMapping (en) +AT "https://signedav.github.io/usabilitydave/models" +VERSION "2020-06-22" = + IMPORTS CatalogueObjects_V1; + + TOPIC JSON_Arr = + + CLASS CatItem + EXTENDS CatalogueObjects_V1.Catalogues.Item = + Name : TEXT*25; + END CatItem; + + STRUCTURE RefCat + EXTENDS CatalogueObjects_V1.Catalogues.CatalogueReference = + Reference (EXTENDED) : MANDATORY REFERENCE TO (EXTERNAL) CatItem; + END RefCat; + + CLASS SimpleItem = + Name : TEXT*25; + END SimpleItem; + + STRUCTURE RefSimple = + Reference: MANDATORY REFERENCE TO (EXTERNAL) SimpleItem; + END RefSimple; + + CLASS TheClass = + !!This becomes a multiselectable value relation + BagofCatItem: BAG {0..*} OF RefCat; + BagofSimpleItem: BAG {0..*} OF RefSimple; + END TheClass; + END JSON_Arr; !! of TOPIC + +END NoArrayMapping. !! of MODEL From 09ed6a428a5b1dfe64416e213b592cc1107f75fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Thu, 30 Oct 2025 17:42:53 -0500 Subject: [PATCH 4/7] GeoPackage: Make sure BAGs OF are identified, even if they don't have a mapping=ARRAY meta attr --- modelbaker/dbconnector/db_connector.py | 1 + modelbaker/dbconnector/gpkg_connector.py | 29 ++++- tests/test_domain_class_relations.py | 34 ++---- tests/test_projectgen.py | 141 ++++++++++++++++++++++- 4 files changed, 176 insertions(+), 29 deletions(-) diff --git a/modelbaker/dbconnector/db_connector.py b/modelbaker/dbconnector/db_connector.py index 1fef556..8b9ece5 100644 --- a/modelbaker/dbconnector/db_connector.py +++ b/modelbaker/dbconnector/db_connector.py @@ -206,6 +206,7 @@ def get_bags_of_info(self): target_layer_name cardinality_max cardinality_min + mapping_type """ return [] diff --git a/modelbaker/dbconnector/gpkg_connector.py b/modelbaker/dbconnector/gpkg_connector.py index 90781ff..e8337c2 100644 --- a/modelbaker/dbconnector/gpkg_connector.py +++ b/modelbaker/dbconnector/gpkg_connector.py @@ -625,7 +625,8 @@ def get_bags_of_info(self): cursor = self.conn.cursor() cursor.execute( """SELECT cprop.tablename as current_layer_name, cprop.columnname as attribute, cprop.setting as target_layer_name, - meta_attrs_cardinality_min.attr_value as cardinality_min, meta_attrs_cardinality_max.attr_value as cardinality_max + meta_attrs_cardinality_min.attr_value as cardinality_min, meta_attrs_cardinality_max.attr_value as cardinality_max, + meta_attrs_array.attr_value as mapping_type FROM T_ILI2DB_COLUMN_PROP as cprop LEFT JOIN T_ILI2DB_CLASSNAME as cname ON cname.sqlname = cprop.tablename @@ -636,7 +637,31 @@ def get_bags_of_info(self): LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_max ON LOWER(meta_attrs_cardinality_max.ilielement) = LOWER(cname.iliname||'.'||cprop.columnname) AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' AND meta_attrs_array.attr_value = 'ARRAY' - """ + + UNION ALL + + -- Select BAGs OF with no mapping + SELECT cprop.setting as current_layer_name + , cprop.columnname as attribute + , cprop.tablename as target_layer_name + , meta_attrs_cardinality_min.attr_value as cardinality_min + , meta_attrs_cardinality_max.attr_value as cardinality_max + , '' as mapping_type + FROM T_ILI2DB_COLUMN_PROP as cprop + -- Get only structures (therefore, no LEFT JOIN here) + JOIN T_ILI2DB_TABLE_PROP as tprop + ON tprop.tag = 'ch.ehi.ili2db.tableKind' and tprop.setting = 'STRUCTURE' and tprop.tablename = cprop.tablename + -- Get target table + LEFT JOIN T_ILI2DB_ATTRNAME as aname + ON aname.target = cprop.setting and colowner = cprop.tablename and cprop.columnname = aname.sqlname + -- Get cardinalities + LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_max + ON meta_attrs_cardinality_max.ilielement = aname.iliname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' + LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_min + ON meta_attrs_cardinality_min.ilielement = aname.iliname AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' + -- Only FKs, only :M relations (therefore, BAGs OF) + WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' and meta_attrs_cardinality_max.attr_value not in ('0','1') + """ ) bags_of_info = cursor.fetchall() cursor.close() diff --git a/tests/test_domain_class_relations.py b/tests/test_domain_class_relations.py index 8432e2c..13f9b92 100644 --- a/tests/test_domain_class_relations.py +++ b/tests/test_domain_class_relations.py @@ -2750,7 +2750,7 @@ def test_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): # Test BAGs OF ENUM expected_bags_of_enum = [ [ - "belasteter_standort_geo_lage_punkt", + "belasteter_standort_geo_lage_polygon", "deponietyp", "0..*", "deponietyp", @@ -2758,7 +2758,7 @@ def test_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): "dispName", ], [ - "belasteter_standort_geo_lage_punkt", + "belasteter_standort_geo_lage_polygon", "untersuchungsmassnahmen", "1..*", "untersmassn", @@ -2767,17 +2767,17 @@ def test_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): ], [ "belasteter_standort_geo_lage_polygon", - "deponietyp", + "belasteter_standort_parzellenverweis", "0..*", - "deponietyp", + "parzellenidentifikation", "T_Id", "dispName", ], [ "belasteter_standort_geo_lage_polygon", - "untersuchungsmassnahmen", - "1..*", - "untersmassn", + "belasteter_standort_egrid", + "0..*", + "egrid_", "T_Id", "dispName", ], @@ -2801,7 +2801,7 @@ def test_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): value_field, ] in expected_bags_of_enum - assert count == 2 + assert count == len(expected_bags_of_enum) def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): # Schema Import @@ -2874,22 +2874,6 @@ def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): # Test BAGs OF ENUM expected_bags_of_enum = [ - [ - "belasteter_standort_geo_lage_punkt", - "deponietyp", - "0..*", - "deponietyp", - "iliCode", - "dispName", - ], - [ - "belasteter_standort_geo_lage_punkt", - "untersuchungsmassnahmen", - "1..*", - "untersmassn", - "iliCode", - "dispName", - ], [ "belasteter_standort_geo_lage_polygon", "deponietyp", @@ -2926,7 +2910,7 @@ def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): value_field, ] in expected_bags_of_enum - assert count == 2 + assert count == len(expected_bags_of_enum) def test_domain_class_relations_Hazard_Mapping_V1_2_postgis(self): # Test and ili file with lots of comments inside. diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index f8eeb3c..63f7319 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -2379,6 +2379,102 @@ def test_bagof_cardinalities_geopackage(self): ["catarrays_None", "catbag_1", "1..*", "refitemitem", "T_Id", "dispName"], ["catarrays_None", "catlist_0", "0..*", "refitemitem", "T_Id", "dispName"], ["catarrays_None", "catlist_1", "1..*", "refitemitem", "T_Id", "dispName"], + [ + "catarrays_None", + "catarrays_catlistnoarray_1", + "1..*", + "refitem", + "T_Id", + "dispName", + ], + [ + "catarrays_None", + "catarrays_catlistnoarray_0", + "0..*", + "refitem", + "T_Id", + "dispName", + ], + [ + "catarrays_None", + "catarrays_catbagnoarray_0", + "0..*", + "refitem", + "T_Id", + "dispName", + ], + [ + "catarrays_None", + "catarrays_catbagnoarray_1", + "1..*", + "refitem", + "T_Id", + "dispName", + ], + [ + "enumarrays_None", + "enumarrays_enumlistnoarray_1", + "1..*", + "ei_typ", + "T_Id", + "dispName", + ], + [ + "enumarrays_None", + "enumarrays_enumlistnoarray_0", + "0..*", + "ei_typ", + "T_Id", + "dispName", + ], + [ + "enumarrays_None", + "enumarrays_enumbagnoarray_1", + "1..*", + "ei_typ", + "T_Id", + "dispName", + ], + [ + "enumarrays_None", + "enumarrays_enumbagnoarray_0", + "0..*", + "ei_typ", + "T_Id", + "dispName", + ], + [ + "textarrays_None", + "textarrays_textbagnoarray_1", + "1..*", + "structtext", + "T_Id", + "dispName", + ], + [ + "textarrays_None", + "textarrays_textbagnoarray_0", + "0..*", + "structtext", + "T_Id", + "dispName", + ], + [ + "textarrays_None", + "textarrays_textlistnoarray_0", + "0..*", + "structtext", + "T_Id", + "dispName", + ], + [ + "textarrays_None", + "textarrays_textlistnoarray_1", + "1..*", + "structtext", + "T_Id", + "dispName", + ], ] count = 0 @@ -2399,7 +2495,7 @@ def test_bagof_cardinalities_geopackage(self): value_field, ] in expected_bags_of_enum - assert count == 8 + assert count == len(expected_bags_of_enum) # Test widget type and constraints count = 0 @@ -4884,7 +4980,7 @@ def test_catalogue_reference_layer_list_of_geopackage(self): assert layer_count_before == layer_count_after assert relation_count_before == relation_count_after - def test_catalogue_reference_layer_bag_of_postgis_no_mapping(self): + def test_catalogue_reference_layer_bag_of_no_mapping_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg importer.configuration = iliimporter_config(importer.tool) @@ -4928,6 +5024,47 @@ def test_catalogue_reference_layer_bag_of_postgis_no_mapping(self): assert layer_count_before == layer_count_after assert relation_count_before == relation_count_after + def test_catalogue_reference_layer_bag_of_no_mapping_geopackage(self): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2gpkg + importer.configuration = iliimporter_config(importer.tool) + importer.configuration.ilifile = testdata_path( + "ilimodels/BagOfNoMappingMetaAttr.ili" + ) + importer.configuration.ilimodels = "NoArrayMapping" + importer.configuration.dbfile = os.path.join( + self.basetestpath, + "tmp_catalogue_ref_bag_of_no_mapping_{:%Y%m%d%H%M%S%f}.gpkg".format( + datetime.datetime.now() + ), + ) + importer.configuration.srs_code = 2056 + importer.configuration.inheritance = "smart2" + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + assert importer.run() == iliimporter.Importer.SUCCESS + config_manager = GpkgCommandConfigManager(importer.configuration) + uri = config_manager.get_uri() + + generator = Generator(DbIliMode.ili2gpkg, uri, "smart2") + + available_layers = generator.layers() + relations, bags_of = generator.relations(available_layers) + + layer_count_before = len(available_layers) + relation_count_before = len(relations) + + available_layers, relations = generator.suppress_catalogue_reference_layers( + available_layers, relations, bags_of + ) + + layer_count_after = len(available_layers) + relation_count_after = len(relations) + + # Test that no reference layer and therefore, no relation, has been removed + assert layer_count_before == layer_count_after + assert relation_count_before == relation_count_after + def test_catalogue_reference_layer_no_bag_of_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg From 7cda67cd3830db9314956fb02d3dbdf948c58806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Thu, 30 Oct 2025 21:02:49 -0500 Subject: [PATCH 5/7] Make sure the BAGs OF get a ValueRelation widget only if there's a mapping=ARRAY meta attr set --- modelbaker/dataobjects/project.py | 4 ++++ modelbaker/generator/generator.py | 1 + 2 files changed, 5 insertions(+) diff --git a/modelbaker/dataobjects/project.py b/modelbaker/dataobjects/project.py index 46afb7c..8187cf8 100644 --- a/modelbaker/dataobjects/project.py +++ b/modelbaker/dataobjects/project.py @@ -280,6 +280,10 @@ def create( for layer_name, bag_of_enum in self.bags_of_enum.items(): current_layer = None for attribute, bag_of_enum_info in bag_of_enum.items(): + mapping_type = bag_of_enum_info[5] + if mapping_type.lower() != "array": + continue + layer_obj = bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] diff --git a/modelbaker/generator/generator.py b/modelbaker/generator/generator.py index 0ddfcc8..8ffc9ed 100644 --- a/modelbaker/generator/generator.py +++ b/modelbaker/generator/generator.py @@ -650,6 +650,7 @@ def relations(self, layers, filter_layer_list=[]): layer_map[record["target_layer_name"]][0], self._db_connector.tid, self._db_connector.dispName, + record["mapping_type"], ] unique_current_layer_name = "{}_{}".format( record["current_layer_name"], layer.geometry_column From b61de327ac4ef9dcb6cf8f3ef1892aecccf5946f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Wed, 5 Nov 2025 21:02:15 -0500 Subject: [PATCH 6/7] get_bags_of_info(): Go back to original SQL query, returning mapping_type --- modelbaker/dataobjects/project.py | 2 +- modelbaker/dbconnector/gpkg_connector.py | 26 +- modelbaker/dbconnector/pg_connector.py | 26 +- modelbaker/generator/generator.py | 22 +- pytest.ini | 4 + tests/test_domain_class_relations.py | 139 ++++---- tests/test_projectgen.py | 327 ++++++++++++------ .../ilimodels/ArrayMappingCatalogue.ili | 36 ++ .../gebaeude_bag_of_no_mapping_V1_6.ili | 71 ++++ 9 files changed, 414 insertions(+), 239 deletions(-) create mode 100644 pytest.ini create mode 100644 tests/testdata/ilimodels/ArrayMappingCatalogue.ili create mode 100644 tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili diff --git a/modelbaker/dataobjects/project.py b/modelbaker/dataobjects/project.py index 8187cf8..dfd5837 100644 --- a/modelbaker/dataobjects/project.py +++ b/modelbaker/dataobjects/project.py @@ -281,7 +281,7 @@ def create( current_layer = None for attribute, bag_of_enum_info in bag_of_enum.items(): mapping_type = bag_of_enum_info[5] - if mapping_type.lower() != "array": + if mapping_type is None or mapping_type.lower() != "array": continue layer_obj = bag_of_enum_info[0] diff --git a/modelbaker/dbconnector/gpkg_connector.py b/modelbaker/dbconnector/gpkg_connector.py index e8337c2..4b3e164 100644 --- a/modelbaker/dbconnector/gpkg_connector.py +++ b/modelbaker/dbconnector/gpkg_connector.py @@ -636,31 +636,7 @@ def get_bags_of_info(self): ON LOWER(meta_attrs_cardinality_min.ilielement) = LOWER(cname.iliname||'.'||cprop.columnname) AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_max ON LOWER(meta_attrs_cardinality_max.ilielement) = LOWER(cname.iliname||'.'||cprop.columnname) AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' - WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' AND meta_attrs_array.attr_value = 'ARRAY' - - UNION ALL - - -- Select BAGs OF with no mapping - SELECT cprop.setting as current_layer_name - , cprop.columnname as attribute - , cprop.tablename as target_layer_name - , meta_attrs_cardinality_min.attr_value as cardinality_min - , meta_attrs_cardinality_max.attr_value as cardinality_max - , '' as mapping_type - FROM T_ILI2DB_COLUMN_PROP as cprop - -- Get only structures (therefore, no LEFT JOIN here) - JOIN T_ILI2DB_TABLE_PROP as tprop - ON tprop.tag = 'ch.ehi.ili2db.tableKind' and tprop.setting = 'STRUCTURE' and tprop.tablename = cprop.tablename - -- Get target table - LEFT JOIN T_ILI2DB_ATTRNAME as aname - ON aname.target = cprop.setting and colowner = cprop.tablename and cprop.columnname = aname.sqlname - -- Get cardinalities - LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_max - ON meta_attrs_cardinality_max.ilielement = aname.iliname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' - LEFT JOIN T_ILI2DB_META_ATTRS as meta_attrs_cardinality_min - ON meta_attrs_cardinality_min.ilielement = aname.iliname AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' - -- Only FKs, only :M relations (therefore, BAGs OF) - WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' and meta_attrs_cardinality_max.attr_value not in ('0','1') + WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' """ ) bags_of_info = cursor.fetchall() diff --git a/modelbaker/dbconnector/pg_connector.py b/modelbaker/dbconnector/pg_connector.py index 94440aa..84b6298 100644 --- a/modelbaker/dbconnector/pg_connector.py +++ b/modelbaker/dbconnector/pg_connector.py @@ -815,31 +815,7 @@ def get_bags_of_info(self): ON meta_attrs_cardinality_min.ilielement ILIKE cname.iliname||'.'||cprop.columnname AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_max ON meta_attrs_cardinality_max.ilielement ILIKE cname.iliname||'.'||cprop.columnname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' - WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' AND meta_attrs_array.attr_value = 'ARRAY' - - UNION ALL - - -- Select BAGs OF with no mapping - SELECT cprop.setting as current_layer_name - , cprop.columnname as attribute - , cprop.tablename as target_layer_name - , meta_attrs_cardinality_min.attr_value as cardinality_min - , meta_attrs_cardinality_max.attr_value as cardinality_max - , '' as mapping_type - FROM {schema}.t_ili2db_column_prop as cprop - -- Get only structures (therefore, no LEFT JOIN here) - JOIN {schema}.t_ili2db_table_prop as tprop - ON tprop.tag = 'ch.ehi.ili2db.tableKind' and tprop.setting = 'STRUCTURE' and tprop.tablename = cprop.tablename - -- Get target table - LEFT JOIN {schema}.t_ili2db_attrname as aname - ON aname.target = cprop.setting and colowner = cprop.tablename and cprop.columnname = aname.sqlname - -- Get cardinalities - LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_max - ON meta_attrs_cardinality_max.ilielement = aname.iliname AND meta_attrs_cardinality_max.attr_name = 'ili2db.ili.attrCardinalityMax' - LEFT JOIN {schema}.{t_ili2db_meta_attrs} as meta_attrs_cardinality_min - ON meta_attrs_cardinality_min.ilielement = aname.iliname AND meta_attrs_cardinality_min.attr_name = 'ili2db.ili.attrCardinalityMin' - -- Only FKs, only :M relations (therefore, BAGs OF) - WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' and meta_attrs_cardinality_max.attr_value not in ('0','1') + WHERE cprop.tag = 'ch.ehi.ili2db.foreignKey' """ ).format( schema=sql.Identifier(self.schema), diff --git a/modelbaker/generator/generator.py b/modelbaker/generator/generator.py index 8ffc9ed..e66253f 100644 --- a/modelbaker/generator/generator.py +++ b/modelbaker/generator/generator.py @@ -644,9 +644,9 @@ def relations(self, layers, filter_layer_list=[]): if record["current_layer_name"] == layer.name: new_item_list = [ layer, - record["cardinality_min"] - + ".." - + record["cardinality_max"], + record["cardinality_min"] + ".." + record["cardinality_max"] + if record["cardinality_min"] and record["cardinality_max"] + else "", layer_map[record["target_layer_name"]][0], self._db_connector.tid, self._db_connector.dispName, @@ -688,7 +688,7 @@ def suppress_catalogue_reference_layers(available_layers, relations, bags_of_enu # Remove reference layer if they are not BAG OF for item in catalogue_items: - is_bag_of = False + ref_required = False # First get the ref pointing to the item ref_to_item_iliname, ref_to_item = "", "" @@ -705,12 +705,14 @@ def suppress_catalogue_reference_layers(available_layers, relations, bags_of_enu for bag_of_layer_k, bag_of_layer_v in bags_of_enum.items(): for bag_of_attr_k, bag_of_data in bag_of_layer_v.items(): if ( - item["name"] == bag_of_data[2].name # mapping=ARRAY - or ref_to_item == bag_of_data[2].name # no mapping - ): # BAG OF's target_layer_name - is_bag_of = True - - if is_bag_of: + ref_to_item + == bag_of_data[0].name # Ref as origin of a relation + and item["name"] + != bag_of_data[2].name # that does not point to the item + ): # So ref must be pointing to a layer + ref_required = True + + if ref_required: # It's a BAG OF, leave it, cause users will need it to add data continue diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..0736786 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +markers = + bagof: marks tests dealing with BAGs OF + catalogue: marks tests dealing with catalogue diff --git a/tests/test_domain_class_relations.py b/tests/test_domain_class_relations.py index 13f9b92..dbed030 100644 --- a/tests/test_domain_class_relations.py +++ b/tests/test_domain_class_relations.py @@ -22,6 +22,7 @@ import shutil import tempfile +import pytest from qgis.core import Qgis, QgsProject from qgis.testing import start_app, unittest @@ -1480,6 +1481,7 @@ def test_ili2db3_domain_class_relations_ZG_Abfallsammelstellen_ZEBA_V1_geopackag for expected_relation in expected_relations: assert expected_relation in relations_dicts + @pytest.mark.bagof def test_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_postgis(self): # Schema Import importer = iliimporter.Importer() @@ -1688,25 +1690,27 @@ def test_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_postgis(sel ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == 9 + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of def test_ili2db3_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_postgis( self, @@ -2149,25 +2153,27 @@ def test_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_geopackage( ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == 9 + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of def test_ili2db3_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_geopackage( self, @@ -2400,6 +2406,7 @@ def test_ili2db3_domain_structure_relations_ZG_Naturschutz_und_Erholung_V1_0_geo assert count == 9 + @pytest.mark.bagof def test_domain_structure_relations_KbS_LV95_V1_3_postgis(self): # Schema Import importer = iliimporter.Importer() @@ -2502,58 +2509,44 @@ def test_domain_structure_relations_KbS_LV95_V1_3_postgis(self): "dispname", ], [ - "belasteter_standort_geo_lage_polygon", - "belasteter_standort_parzellenverweis", - "0..*", - "parzellenidentifikation", - "t_id", - "dispname", - ], - [ - "belasteter_standort_geo_lage_polygon", - "belasteter_standort_egrid", - "0..*", - "egrid_", - "t_id", - "dispname", - ], - [ - "belasteter_standort_geo_lage_punkt", + "parzellenidentifikation_None", "belasteter_standort_parzellenverweis", - "0..*", - "parzellenidentifikation", + "", # "0..*", + "belasteter_standort", "t_id", "dispname", ], [ - "belasteter_standort_geo_lage_punkt", + "egrid__None", "belasteter_standort_egrid", - "0..*", - "egrid_", + "", # "0..*", + "belasteter_standort", "t_id", "dispname", ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == len(expected_bags_of_enum) + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_postgis(self): # Schema Import @@ -2766,42 +2759,44 @@ def test_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): "dispName", ], [ - "belasteter_standort_geo_lage_polygon", + "parzellenidentifikation_None", "belasteter_standort_parzellenverweis", - "0..*", - "parzellenidentifikation", + "", # "0..*", + "belasteter_standort", "T_Id", "dispName", ], [ - "belasteter_standort_geo_lage_polygon", + "egrid__None", "belasteter_standort_egrid", - "0..*", - "egrid_", + "", # "0..*", + "belasteter_standort", "T_Id", "dispName", ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == len(expected_bags_of_enum) + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of def test_ili2db3_domain_structure_relations_KbS_LV95_V1_3_geopackage(self): # Schema Import diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index 63f7319..e96b367 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -26,6 +26,7 @@ import tempfile from decimal import Decimal +import pytest import yaml from qgis.core import Qgis, QgsEditFormConfig, QgsProject, QgsRelation from qgis.PyQt.QtCore import QEventLoop, Qt, QTimer @@ -2056,6 +2057,7 @@ def test_meta_attr_order_toml_mssql(self): assert count == 1 + @pytest.mark.bagof def test_bagof_cardinalities_postgis(self): # Schema Import importer = iliimporter.Importer() @@ -2104,122 +2106,124 @@ def test_bagof_cardinalities_postgis(self): ["catarrays_None", "catlist_0", "0..*", "refitemitem", "t_id", "dispname"], ["catarrays_None", "catlist_1", "1..*", "refitemitem", "t_id", "dispname"], [ - "catarrays_None", + "refitem_None", "catarrays_catlistnoarray_1", - "1..*", - "refitem", + "", # "1..*", + "catarrays", "t_id", "dispname", ], [ - "catarrays_None", + "refitem_None", "catarrays_catlistnoarray_0", - "0..*", - "refitem", + "", # "0..*", + "catarrays", "t_id", "dispname", ], [ - "catarrays_None", + "refitem_None", "catarrays_catbagnoarray_0", - "0..*", - "refitem", + "", # "0..*", + "catarrays", "t_id", "dispname", ], [ - "catarrays_None", + "refitem_None", "catarrays_catbagnoarray_1", - "1..*", - "refitem", + "", # "1..*", + "catarrays", "t_id", "dispname", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumlistnoarray_1", - "1..*", - "ei_typ", + "", # "1..*", + "enumarrays", "t_id", "dispname", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumlistnoarray_0", - "0..*", - "ei_typ", + "", # "0..*", + "enumarrays", "t_id", "dispname", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumbagnoarray_1", - "1..*", - "ei_typ", + "", # "1..*", + "enumarrays", "t_id", "dispname", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumbagnoarray_0", - "0..*", - "ei_typ", + "", # "0..*", + "enumarrays", "t_id", "dispname", ], [ - "textarrays_None", + "structtext_None", "textarrays_textbagnoarray_1", - "1..*", - "structtext", + "", # "1..*", + "textarrays", "t_id", "dispname", ], [ - "textarrays_None", + "structtext_None", "textarrays_textbagnoarray_0", - "0..*", - "structtext", + "", # "0..*", + "textarrays", "t_id", "dispname", ], [ - "textarrays_None", + "structtext_None", "textarrays_textlistnoarray_0", - "0..*", - "structtext", + "", # "0..*", + "textarrays", "t_id", "dispname", ], [ - "textarrays_None", + "structtext_None", "textarrays_textlistnoarray_1", - "1..*", - "structtext", + "", # "1..*", + "textarrays", "t_id", "dispname", ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == len(expected_bags_of_enum) + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of # Test widget type and constraints count = 0 @@ -2329,6 +2333,7 @@ def test_bagof_cardinalities_postgis(self): ) assert count == 2 + @pytest.mark.bagof def test_bagof_cardinalities_geopackage(self): # Schema Import importer = iliimporter.Importer() @@ -2380,122 +2385,124 @@ def test_bagof_cardinalities_geopackage(self): ["catarrays_None", "catlist_0", "0..*", "refitemitem", "T_Id", "dispName"], ["catarrays_None", "catlist_1", "1..*", "refitemitem", "T_Id", "dispName"], [ - "catarrays_None", + "refitem_None", "catarrays_catlistnoarray_1", - "1..*", - "refitem", + "", # "1..*", + "catarrays", "T_Id", "dispName", ], [ - "catarrays_None", + "refitem_None", "catarrays_catlistnoarray_0", - "0..*", - "refitem", + "", # "0..*", + "catarrays", "T_Id", "dispName", ], [ - "catarrays_None", + "refitem_None", "catarrays_catbagnoarray_0", - "0..*", - "refitem", + "", # "0..*", + "catarrays", "T_Id", "dispName", ], [ - "catarrays_None", + "refitem_None", "catarrays_catbagnoarray_1", - "1..*", - "refitem", + "", # "1..*", + "catarrays", "T_Id", "dispName", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumlistnoarray_1", - "1..*", - "ei_typ", + "", # "1..*", + "enumarrays", "T_Id", "dispName", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumlistnoarray_0", - "0..*", - "ei_typ", + "", # "0..*", + "enumarrays", "T_Id", "dispName", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumbagnoarray_1", - "1..*", - "ei_typ", + "", # "1..*", + "enumarrays", "T_Id", "dispName", ], [ - "enumarrays_None", + "ei_typ_None", "enumarrays_enumbagnoarray_0", - "0..*", - "ei_typ", + "", # "0..*", + "enumarrays", "T_Id", "dispName", ], [ - "textarrays_None", + "structtext_None", "textarrays_textbagnoarray_1", - "1..*", - "structtext", + "", # "1..*", + "textarrays", "T_Id", "dispName", ], [ - "textarrays_None", + "structtext_None", "textarrays_textbagnoarray_0", - "0..*", - "structtext", + "", # "0..*", + "textarrays", "T_Id", "dispName", ], [ - "textarrays_None", + "structtext_None", "textarrays_textlistnoarray_0", - "0..*", - "structtext", + "", # "0..*", + "textarrays", "T_Id", "dispName", ], [ - "textarrays_None", + "structtext_None", "textarrays_textlistnoarray_1", - "1..*", - "structtext", + "", # "1..*", + "textarrays", "T_Id", "dispName", ], ] - count = 0 + obtained_bags_of = [] for layer_name, bag_of_enum in bags_of_enum.items(): for attribute, bag_of_enum_info in bag_of_enum.items(): - count += 1 bag_of_enum_info[0] cardinality = bag_of_enum_info[1] domain_table = bag_of_enum_info[2] key_field = bag_of_enum_info[3] value_field = bag_of_enum_info[4] - assert [ - layer_name, - attribute, - cardinality, - domain_table.name, - key_field, - value_field, - ] in expected_bags_of_enum + obtained_bags_of.append( + [ + layer_name, + attribute, + cardinality, + domain_table.name, + key_field, + value_field, + ] + ) - assert count == len(expected_bags_of_enum) + for bag_of in expected_bags_of_enum: + assert bag_of in obtained_bags_of # Test widget type and constraints count = 0 @@ -4814,6 +4821,8 @@ def test_array_mapping_mssql(self): assert count == 1 + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_bag_of_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg @@ -4852,10 +4861,58 @@ def test_catalogue_reference_layer_bag_of_postgis(self): layer_count_after = len(available_layers) relation_count_after = len(relations) - # Test that no reference layer and therefore, no relation, has been removed - assert layer_count_before == layer_count_after - assert relation_count_before == relation_count_after + # Test that one reference layer and one relation have been removed + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 + @pytest.mark.bagof + @pytest.mark.catalogue + def test_catalogue_reference_layer_bag_of_mapping_array_postgis(self): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2pg + importer.configuration = iliimporter_config(importer.tool) + importer.configuration.ilifile = testdata_path( + "ilimodels/ArrayMappingCatalogue.ili" + ) + importer.configuration.ilimodels = "ArrayMappingCatalogue" + importer.configuration.dbschema = ( + "catalogue_ref_bag_of_array_mapping_{:%Y%m%d%H%M%S%f}".format( + datetime.datetime.now() + ) + ) + + importer.configuration.srs_code = 2056 + importer.configuration.inheritance = "smart2" + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + assert importer.run() == iliimporter.Importer.SUCCESS + + generator = Generator( + DbIliMode.ili2pg, + get_pg_connection_string(), + importer.configuration.inheritance, + importer.configuration.dbschema, + ) + + available_layers = generator.layers() + relations, bags_of = generator.relations(available_layers) + + layer_count_before = len(available_layers) + relation_count_before = len(relations) + + available_layers, relations = generator.suppress_catalogue_reference_layers( + available_layers, relations, bags_of + ) + + layer_count_after = len(available_layers) + relation_count_after = len(relations) + + # Test that one reference layer and one relation have been removed + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 + + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_bag_of_geopackage(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2gpkg @@ -4893,9 +4950,57 @@ def test_catalogue_reference_layer_bag_of_geopackage(self): layer_count_after = len(available_layers) relation_count_after = len(relations) - # Test that no reference layer and therefore, no relation, has been removed - assert layer_count_before == layer_count_after - assert relation_count_before == relation_count_after + # Test that one reference layer and one relation have been removed + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 + + @pytest.mark.bagof + @pytest.mark.catalogue + def test_catalogue_reference_layer_bag_of_no_mapping_2_postgis(self): + importer = iliimporter.Importer() + importer.tool = DbIliMode.ili2pg + importer.configuration = iliimporter_config(importer.tool) + importer.configuration.ilifile = testdata_path( + "ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili" + ) + importer.configuration.ilimodels = "Gebaeudeinventar_Bag_Of_No_Mapping_V1_6" + importer.configuration.dbschema = ( + "catalogue_ref_bag_of_no_mapping_2_{:%Y%m%d%H%M%S%f}".format( + datetime.datetime.now() + ) + ) + + importer.configuration.srs_code = 2056 + importer.configuration.inheritance = "smart2" + importer.stdout.connect(self.print_info) + importer.stderr.connect(self.print_error) + assert importer.run() == iliimporter.Importer.SUCCESS + + generator = Generator( + DbIliMode.ili2pg, + get_pg_connection_string(), + importer.configuration.inheritance, + importer.configuration.dbschema, + ) + + available_layers = generator.layers() + relations, bags_of = generator.relations(available_layers) + + layer_count_before = len(available_layers) + relation_count_before = len(relations) + + available_layers, relations = generator.suppress_catalogue_reference_layers( + available_layers, relations, bags_of + ) + + layer_count_after = len(available_layers) + relation_count_after = len(relations) + + # Test that one reference layer and one relation have been removed + # BAG {0..1} OF gets implemented as a direct relation to item table, + # that is, no structure reference table is needed as link table. + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 def test_catalogue_reference_layer_list_of_postgis(self): importer = iliimporter.Importer() @@ -4935,10 +5040,12 @@ def test_catalogue_reference_layer_list_of_postgis(self): layer_count_after = len(available_layers) relation_count_after = len(relations) - # Test that no reference layer and therefore, no relation, has been removed - assert layer_count_before == layer_count_after - assert relation_count_before == relation_count_after + # Test that one reference layer and one relation have been removed + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_list_of_geopackage(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2gpkg @@ -4976,10 +5083,12 @@ def test_catalogue_reference_layer_list_of_geopackage(self): layer_count_after = len(available_layers) relation_count_after = len(relations) - # Test that no reference layer and therefore, no relation, has been removed - assert layer_count_before == layer_count_after - assert relation_count_before == relation_count_after + # Test that one reference layer and one relation have been removed + assert layer_count_before == layer_count_after + 1 + assert relation_count_before == relation_count_after + 1 + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_bag_of_no_mapping_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg @@ -5024,6 +5133,8 @@ def test_catalogue_reference_layer_bag_of_no_mapping_postgis(self): assert layer_count_before == layer_count_after assert relation_count_before == relation_count_after + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_bag_of_no_mapping_geopackage(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2gpkg @@ -5065,6 +5176,8 @@ def test_catalogue_reference_layer_bag_of_no_mapping_geopackage(self): assert layer_count_before == layer_count_after assert relation_count_before == relation_count_after + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_no_bag_of_postgis(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2pg @@ -5105,6 +5218,8 @@ def test_catalogue_reference_layer_no_bag_of_postgis(self): assert layer_count_before == layer_count_after + 1 assert relation_count_before == relation_count_after + 1 + @pytest.mark.bagof + @pytest.mark.catalogue def test_catalogue_reference_layer_no_bag_of_geopackage(self): importer = iliimporter.Importer() importer.tool = DbIliMode.ili2gpkg diff --git a/tests/testdata/ilimodels/ArrayMappingCatalogue.ili b/tests/testdata/ilimodels/ArrayMappingCatalogue.ili new file mode 100644 index 0000000..05d3220 --- /dev/null +++ b/tests/testdata/ilimodels/ArrayMappingCatalogue.ili @@ -0,0 +1,36 @@ +INTERLIS 2.3; + +MODEL ArrayMappingCatalogue (en) +AT "https://signedav.github.io/usabilitydave/models" +VERSION "2020-06-22" = + IMPORTS CatalogueObjects_V1; + + TOPIC JSON_Arr = + + CLASS CatItem + EXTENDS CatalogueObjects_V1.Catalogues.Item = + Name : TEXT*25; + END CatItem; + + STRUCTURE RefCat + EXTENDS CatalogueObjects_V1.Catalogues.CatalogueReference = + Reference (EXTENDED) : MANDATORY REFERENCE TO (EXTERNAL) CatItem; + END RefCat; + + CLASS SimpleItem = + Name : TEXT*25; + END SimpleItem; + + STRUCTURE RefSimple = + Reference: MANDATORY REFERENCE TO (EXTERNAL) SimpleItem; + END RefSimple; + + CLASS TheClass = + !!This becomes a multiselectable value relation + !!@ili2db.mapping=ARRAY + BagofCatItem: BAG {0..*} OF RefCat; + BagofSimpleItem: BAG {0..*} OF RefSimple; + END TheClass; + END JSON_Arr; !! of TOPIC + +END ArrayMappingCatalogue. !! of MODEL diff --git a/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili b/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili new file mode 100644 index 0000000..b94067d --- /dev/null +++ b/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili @@ -0,0 +1,71 @@ +INTERLIS 2.3; + +MODEL Gebaeudeinventar_Bag_Of_No_Mapping_V1_6 (de) + +AT "mailto:signedav@localhost" + +VERSION "2023-01-19" = + IMPORTS GeometryCHLV95_V1, CatalogueObjects_V1, LocalisationCH_V1; + + TOPIC Katalog = + + CLASS Heizungstyp_Item + EXTENDS CatalogueObjects_V1.Catalogues.Item = + Code : MANDATORY TEXT; + Beschreibung : MANDATORY LocalisationCH_V1.MultilingualText; + END Heizungstyp_Item; + + STRUCTURE Heizungstyp_Ref + EXTENDS CatalogueObjects_V1.Catalogues.MandatoryCatalogueReference = + Reference (EXTENDED) : MANDATORY REFERENCE TO (EXTERNAL) Heizungstyp_Item; + END Heizungstyp_Ref; + + END Katalog; + + TOPIC Gebaeude = + DEPENDS ON Gebaeudeinventar_Bag_Of_No_Mapping_V1_6.Katalog; + + DOMAIN + Kanton = ( + AG,AI,AR,BE,BL,BS,FR,GE,GL,GR,JU,LU,NE,NW,OW,SG,SH,SO,SZ,TG,TI,UR,VD,VS,ZG,ZH + ); + + CLASS Gebaeude = + + EGID : MANDATORY TEXT*16; + Kantonskuerzel : MANDATORY Kanton; + Grundstuecksnummer : 0 .. 99999; + Name : TEXT; + Koordinaten : MANDATORY GeometryCHLV95_V1.Coord2; + Status : MANDATORY TEXT; + Bauperiode : MANDATORY TEXT; + Flaeche : 0.00 .. 99999.99; + Geschosse : 0 .. 999; + Zivilschutzraum : MANDATORY BOOLEAN; + Heizung3 : BAG {0..1} OF Gebaeudeinventar_Bag_Of_No_Mapping_V1_6.Katalog.Heizungstyp_Ref; + Datum_Heizung : INTERLIS.XMLDate; + + UNIQUE EGID; + SET CONSTRAINT WHERE DEFINED (Heizung3) : + DEFINED (Datum_Heizung); + + END Gebaeude; + + CLASS Adresse = + AdrID : MANDATORY TEXT*16; + Strasse : TEXT*200; + Nummer : TEXT*8; + PLZ : 0 .. 9999; + Ort : MANDATORY TEXT*200; + + UNIQUE AdrID; + END Adresse; + + ASSOCIATION AdresseGebaeude = + GebaeudeAdresse -- {1..*} Adresse; + Gebaeude -<#> {1} Gebaeude; + END AdresseGebaeude; + + END Gebaeude; + +END Gebaeudeinventar_Bag_Of_No_Mapping_V1_6. From 5f96cad8e2ffd6bedd5c6fdc9d70acc0b6ae9f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Germ=C3=A1n=20Carrillo?= Date: Thu, 6 Nov 2025 11:52:13 -0500 Subject: [PATCH 7/7] Address review --- tests/test_projectgen.py | 8 +++----- tests/testdata/ilimodels/ArrayMappingCatalogue.ili | 2 +- tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili | 1 - .../ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/test_projectgen.py b/tests/test_projectgen.py index e96b367..78abff6 100644 --- a/tests/test_projectgen.py +++ b/tests/test_projectgen.py @@ -4996,11 +4996,9 @@ def test_catalogue_reference_layer_bag_of_no_mapping_2_postgis(self): layer_count_after = len(available_layers) relation_count_after = len(relations) - # Test that one reference layer and one relation have been removed - # BAG {0..1} OF gets implemented as a direct relation to item table, - # that is, no structure reference table is needed as link table. - assert layer_count_before == layer_count_after + 1 - assert relation_count_before == relation_count_after + 1 + # Test that no reference layer, and therefore no relation, has been removed. + assert layer_count_before == layer_count_after + assert relation_count_before == relation_count_after def test_catalogue_reference_layer_list_of_postgis(self): importer = iliimporter.Importer() diff --git a/tests/testdata/ilimodels/ArrayMappingCatalogue.ili b/tests/testdata/ilimodels/ArrayMappingCatalogue.ili index 05d3220..0e0f00b 100644 --- a/tests/testdata/ilimodels/ArrayMappingCatalogue.ili +++ b/tests/testdata/ilimodels/ArrayMappingCatalogue.ili @@ -26,7 +26,7 @@ VERSION "2020-06-22" = END RefSimple; CLASS TheClass = - !!This becomes a multiselectable value relation + !!BagofCatItem becomes a multiselectable value relation, BagofSimpleItem is linked via many to many relation. !!@ili2db.mapping=ARRAY BagofCatItem: BAG {0..*} OF RefCat; BagofSimpleItem: BAG {0..*} OF RefSimple; diff --git a/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili b/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili index 9b5ad2e..d498133 100644 --- a/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili +++ b/tests/testdata/ilimodels/BagOfNoMappingMetaAttr.ili @@ -26,7 +26,6 @@ VERSION "2020-06-22" = END RefSimple; CLASS TheClass = - !!This becomes a multiselectable value relation BagofCatItem: BAG {0..*} OF RefCat; BagofSimpleItem: BAG {0..*} OF RefSimple; END TheClass; diff --git a/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili b/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili index b94067d..ac08263 100644 --- a/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili +++ b/tests/testdata/ilimodels/gebaeude_bag_of_no_mapping_V1_6.ili @@ -42,7 +42,7 @@ VERSION "2023-01-19" = Flaeche : 0.00 .. 99999.99; Geschosse : 0 .. 999; Zivilschutzraum : MANDATORY BOOLEAN; - Heizung3 : BAG {0..1} OF Gebaeudeinventar_Bag_Of_No_Mapping_V1_6.Katalog.Heizungstyp_Ref; + Heizung3 : BAG {0..5} OF Gebaeudeinventar_Bag_Of_No_Mapping_V1_6.Katalog.Heizungstyp_Ref; Datum_Heizung : INTERLIS.XMLDate; UNIQUE EGID;