From 1557940d5511d84baf52b4df50f52dc0a7cc8df9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 18:45:08 +0000 Subject: [PATCH 1/3] Support XML documents mixing several XSD schemas Complex types were keyed by their local name only, so two types sharing a local name in different namespaces were mapped to the same table. The second one was silently merged into the first, or dropped when the name collision was mistaken for a recursive definition. Types are now keyed per XSD type, appending a numeric suffix when local names collide. Add a documentation page on combining several schemas, covering wrapper schemas replacing xs:any wildcards with the payload schema, and a caveat section about wildcards. Refs cre-dev/xml2db#77 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TGCUjv7869FF3vgK1ezSV9 --- docs/how_it_works.md | 7 ++ docs/multiple_schemas.md | 116 ++++++++++++++++++ mkdocs.yml | 1 + src/xml2db/model.py | 45 +++++-- .../imported_namespaces/main.xsd | 29 +++++ .../imported_namespaces/other.xsd | 16 +++ .../imported_namespaces/xml/basket1.xml | 22 ++++ tests/test_imported_namespaces.py | 52 ++++++++ 8 files changed, 280 insertions(+), 8 deletions(-) create mode 100644 docs/multiple_schemas.md create mode 100644 tests/sample_models/imported_namespaces/main.xsd create mode 100644 tests/sample_models/imported_namespaces/other.xsd create mode 100644 tests/sample_models/imported_namespaces/xml/basket1.xml create mode 100644 tests/test_imported_namespaces.py diff --git a/docs/how_it_works.md b/docs/how_it_works.md index 484da0a..e946368 100644 --- a/docs/how_it_works.md +++ b/docs/how_it_works.md @@ -124,6 +124,13 @@ the process much more complex. Whenever a field which would introduce a dependen discarded with a warning, which means that the corresponding data in XML files will not be imported. The rest of the data should be processed correctly. +#### Wildcards + +Elements declared as `xs:any` are discarded with a warning, because a wildcard does not tell which elements may +appear, and therefore which tables to create. When the content of such an element follows a known schema, declaring it +explicitly in a wrapper schema makes it importable, see +[Combining several XSD schemas](multiple_schemas.md). + #### Mixed content elements XML elements with mixed content can contain both text and children elements (tags). `xml2db` offers partial support for diff --git a/docs/multiple_schemas.md b/docs/multiple_schemas.md new file mode 100644 index 0000000..59a7ff5 --- /dev/null +++ b/docs/multiple_schemas.md @@ -0,0 +1,116 @@ +--- +title: "Combining several XSD schemas" +description: "How to load XML documents which mix several XML schemas with xml2db, by writing a wrapper XSD which imports them and replaces xs:any wildcards." +--- + +# Combining several XSD schemas + +`xml2db` builds a data model from a single XSD file, but that file can pull in as many other schemas as needed with +`xs:import` (for another namespace) and `xs:include` (for the same namespace). Documents mixing several namespaces are +therefore supported, provided that a single schema describes the whole document. + +Two situations require a bit of work: + +* the container schema declares its payload as a wildcard (`xs:any`), so it does not say which elements may appear, +* the schemas are complete, but no single file references all of them. + +Both are solved by writing a small wrapper schema of your own, which imports the other schemas and declares the +payload explicitly. + +## Replacing a wildcard with the payload schema + +`xml2db` ignores `xs:any` children and logs a warning, because a wildcard gives no structure to map to tables. This +is common in container formats: protocol envelopes such as SOAP or SRU carry an arbitrary payload, which its own +schema describes. + +The example below covers the [SRU](https://www.loc.gov/standards/sru/) API of the French national library, which +returns [MarcXchange](https://www.loc.gov/standards/iso25577/) records inside an SRU envelope. The SRU schema declares +`recordData` as a wildcard, so the wrapper declares it as holding a `record` element from the MarcXchange namespace: + +``` xml title="sru-marcxchange.xsd" linenums="1" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +Save `marcxchange-2-0.xsd` next to the wrapper, or point `schemaLocation` to its URL, and load documents as usual: + +``` py linenums="1" +from xml2db import DataModel + +model = DataModel( + xsd_file="sru-marcxchange.xsd", + short_name="sru_marcxchange", + connection_string="duckdb:///sru.duckdb", +) +model.create_db_schema() +model.create_all_tables() + +document = model.parse_xml("search_retrieve_response.xml") +document.insert_into_target_tables() +``` + +The MarcXchange records are then queryable, joining the `record` table with the `datafield` and `subfield` tables +derived from the imported schema. + +## Writing the wrapper + +A few points worth checking when writing a wrapper schema: + +* **Element order**: `xs:sequence` requires the exact order declared in the schema. Servers do not always follow the + order of the reference schema, `xs:all` accepts any order for elements occurring at most once. +* **Undeclared elements**: elements missing from the wrapper are parsed and ignored, so a partial wrapper covering only + the elements you need is fine. +* **Type names**: `xml2db` identifies complex types by their local name, ignoring their namespace. Two types named + `recordType` in two namespaces get the keys `recordType` and `recordType_1`, with a warning. Table names are derived + from element names and remain unaffected, but naming types explicitly in your own schema, as `srwRecordType` above, + keeps the model easier to read and to configure. +* **Validation**: `parse_xml` does not validate documents unless `skip_validation=False` is passed. Payloads often + deviate from their reference schema on details such as patterns or `xs:ID` attributes. If validation matters, relax + the corresponding facets in your local copy of the schema. +* **Recursive types**: fields introducing a cycle are discarded with a warning, as described in + [Caveats](how_it_works.md#recursive-xsd). The MarcXchange `embeddeddata` element is one of them, so embedded records + are not imported. diff --git a/mkdocs.yml b/mkdocs.yml index 1850517..4765761 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,6 +10,7 @@ nav: - Introduction: "index.md" - Getting started: "getting_started.md" - Configuring: "configuring.md" + - Combining several XSD schemas: "multiple_schemas.md" - How it works: "how_it_works.md" - CLI usage: "cli.md" - API: diff --git a/src/xml2db/model.py b/src/xml2db/model.py index 049fd6a..a901d4a 100644 --- a/src/xml2db/model.py +++ b/src/xml2db/model.py @@ -117,6 +117,7 @@ def __init__( self.temp_prefix = str(uuid4())[:8] if temp_prefix is None else temp_prefix self.tables = {} + self.types_keys = {} self.names_types_map = {} self.root_table = None @@ -274,6 +275,40 @@ def _build_model(self): for tb in self.fk_ordered_tables: tb.build_sqlalchemy_tables() + def _get_type_key(self, node) -> str: + """Get the key identifying the XSD type of a node among the data model tables. + + Types are keyed by their local name, which is not unique when a schema imports other schemas: two namespaces + may define different types sharing the same local name. A numeric suffix is appended in this case, so that + each XSD type gets its own table. + + Args: + node: the XSD node whose type key is needed + + Returns: + The key of the type in `self.tables`. + """ + xsd_type = getattr(node, "type", None) + if xsd_type is None: + return self.data_flow_name + if xsd_type in self.types_keys: + return self.types_keys[xsd_type] + key = xsd_type.local_name + if key is None: + key = node.local_name + reserved_keys = set(self.types_keys.values()) + if key in reserved_keys: + i = 1 + while f"{key}_{i}" in reserved_keys: + i += 1 + key = f"{key}_{i}" + logger.warning( + f"two different XSD types share the local name '{xsd_type.local_name or node.local_name}', " + f"the second one is mapped to the table key '{key}'" + ) + self.types_keys[xsd_type] = key + return key + def _parse_tree(self, parent_node: xmlschema.XsdElement, nodes_path: list = None): """Parse a node of an XML schema recursively and create a target data model without any simplification @@ -292,13 +327,7 @@ def _parse_tree(self, parent_node: xmlschema.XsdElement, nodes_path: list = None """ # find current node type and name and returns corresponding table if it already exists - parent_type = ( - parent_node.type.local_name - if hasattr(parent_node, "type") - else self.data_flow_name - ) - if parent_type is None: - parent_type = parent_node.local_name + parent_type = self._get_type_key(parent_node) nodes_path = (nodes_path if nodes_path else []) + [parent_type] @@ -511,7 +540,7 @@ def get_occurs(particle): elif ct.is_complex(): # ignoring recursive definitions by skipping these fields - if child.type.local_name in nodes_path: + if self._get_type_key(child) in nodes_path: logger.warning( f"type '{child.type.local_name}' contains a recursive definition" ) diff --git a/tests/sample_models/imported_namespaces/main.xsd b/tests/sample_models/imported_namespaces/main.xsd new file mode 100644 index 0000000..7119a60 --- /dev/null +++ b/tests/sample_models/imported_namespaces/main.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/sample_models/imported_namespaces/other.xsd b/tests/sample_models/imported_namespaces/other.xsd new file mode 100644 index 0000000..8146f74 --- /dev/null +++ b/tests/sample_models/imported_namespaces/other.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + diff --git a/tests/sample_models/imported_namespaces/xml/basket1.xml b/tests/sample_models/imported_namespaces/xml/basket1.xml new file mode 100644 index 0000000..94f3b4b --- /dev/null +++ b/tests/sample_models/imported_namespaces/xml/basket1.xml @@ -0,0 +1,22 @@ + + + BSK-1 + + Apples + + APL + 3 + + + APL-2 + 5 + + + + Oranges + + ORG + 7 + + + diff --git a/tests/test_imported_namespaces.py b/tests/test_imported_namespaces.py new file mode 100644 index 0000000..f960510 --- /dev/null +++ b/tests/test_imported_namespaces.py @@ -0,0 +1,52 @@ +import os + +from xml2db import DataModel +from xml2db.xml_converter import XMLConverter +from .conftest import models_path + +xsd_path = os.path.join(models_path, "imported_namespaces", "main.xsd") +xml_path = os.path.join(models_path, "imported_namespaces", "xml", "basket1.xml") + + +def build_model(): + return DataModel(str(xsd_path), short_name="imported_namespaces") + + +def iter_nodes(node): + """Yield all nodes of a document tree recursively""" + yield node + for values in node[1].values(): + for value in values: + if isinstance(value, tuple): + yield from iter_nodes(value) + + +def test_imported_namespace_types_are_not_merged(): + """Types sharing a local name across namespaces are mapped to distinct tables""" + + model = build_model() + + assert sorted(model.tables.keys()) == ["basketType", "itemType", "itemType_1"] + assert sorted(model.names_types_map.keys()) == ["basket", "item", "item_1"] + assert sorted(model.tables["itemType"].columns.keys()) == ["label"] + assert sorted(model.tables["itemType_1"].columns.keys()) == ["code", "quantity"] + + +def test_imported_namespace_fields_are_parsed(): + """Content defined in the imported schema is parsed into its own nodes""" + + model = build_model() + converter = XMLConverter(model) + + parsed_recursive = converter.parse_xml(xml_path, skip_validation=False) + parsed_iterative = converter.parse_xml( + xml_path, skip_validation=False, iterparse=True + ) + + assert parsed_recursive == parsed_iterative + + imported_nodes = [ + node for node in iter_nodes(parsed_recursive) if node[0] == "itemType_1" + ] + assert [node[1]["code"][0] for node in imported_nodes] == ["APL", "APL-2", "ORG"] + assert [node[1]["quantity"][0] for node in imported_nodes] == [3, 5, 7] From 7e1f005d8a170cf33ee38a85c868318dd15a0bd2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 20:17:54 +0000 Subject: [PATCH 2/3] Test the type name collision in orders.xsd, trim the documentation Replace the dedicated test model and test module with a sub-case of the orders sample model: 'detail' is declared twice with a different anonymous complex type, so both types share a local name. The existing parametrized tests cover it, and the model output snapshots are regenerated. Replace the page on combining several schemas with a short note in the getting started guide, and point the wildcards caveat to it. Document in CLAUDE.md that bug fixes should stay minimal and be covered by sub-cases of existing sample models and tests. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DYgbAh7586edNcD8HXyvcP --- CLAUDE.md | 19 +++ docs/getting_started.md | 10 ++ docs/how_it_works.md | 2 +- docs/multiple_schemas.md | 116 ------------------ mkdocs.yml | 1 - .../imported_namespaces/main.xsd | 29 ----- .../imported_namespaces/other.xsd | 16 --- .../imported_namespaces/xml/basket1.xml | 22 ---- tests/sample_models/orders/orders.xsd | 19 +++ .../orders/orders_ddl_mssql_version0.sql | 44 +++++++ .../orders/orders_ddl_mssql_version1.sql | 46 +++++++ .../orders/orders_ddl_mssql_version2.sql | 44 +++++++ .../orders/orders_ddl_mysql_version0.sql | 44 +++++++ .../orders/orders_ddl_mysql_version1.sql | 46 +++++++ .../orders/orders_ddl_mysql_version2.sql | 44 +++++++ .../orders/orders_ddl_postgresql_version0.sql | 44 +++++++ .../orders/orders_ddl_postgresql_version1.sql | 46 +++++++ .../orders/orders_ddl_postgresql_version2.sql | 44 +++++++ .../orders/orders_erd_version0.md | 10 ++ .../orders/orders_erd_version1.md | 10 ++ .../orders/orders_erd_version2.md | 10 ++ .../orders/orders_source_tree_version0.txt | 8 +- .../orders/orders_source_tree_version1.txt | 8 +- .../orders/orders_source_tree_version2.txt | 8 +- .../orders/orders_target_tree_version0.txt | 8 +- .../orders/orders_target_tree_version1.txt | 8 +- .../orders/orders_target_tree_version2.txt | 8 +- tests/sample_models/orders/xml/order3.xml | 12 ++ tests/test_imported_namespaces.py | 52 -------- tests/test_models_output.py | 16 +++ 30 files changed, 551 insertions(+), 243 deletions(-) delete mode 100644 docs/multiple_schemas.md delete mode 100644 tests/sample_models/imported_namespaces/main.xsd delete mode 100644 tests/sample_models/imported_namespaces/other.xsd delete mode 100644 tests/sample_models/imported_namespaces/xml/basket1.xml delete mode 100644 tests/test_imported_namespaces.py diff --git a/CLAUDE.md b/CLAUDE.md index 33e63c4..e42d2d0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -57,6 +57,25 @@ cd tests/sample_models && python models.py then commit the updated snapshot files alongside the code change. +## Fixing bugs + +Aim at the smallest change that fixes the bug. Keep the fix local to the code that is wrong, do +not refactor around it, and do not widen the scope to related cases that are not reported. + +Cover the fix by extending what already exists rather than adding new files: + +- Add the failing construct as a sub-case of an existing sample model, typically + `tests/sample_models/orders/orders.xsd` with matching data in `tests/sample_models/orders/xml/`, + and comment in the XSD what the construct tests. New sample models are for new kinds of schema, + not for single edge cases. +- Add assertions to the existing test module covering that area instead of creating a test file + for one case. The parametrized tests pick up sample models and XML files automatically, so a + construct added to a sample model is exercised by the parsing, round-trip and insertion tests + without new test code. +- Regenerate the snapshots when the data model changes, as described above. + +Check that the bug reproduces before the fix and is gone after it. + ## Writing style - After any code change, check whether docstrings, inline docs, or `docs/` pages need updating and update them as part of the same task. diff --git a/docs/getting_started.md b/docs/getting_started.md index 60d0a9c..54110bc 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -51,6 +51,16 @@ xml2db render schema.xsd --format source-tree xml2db render schema.xsd --format ddl --db-type postgresql ``` +!!! note "Documents mixing several schemas" + + `DataModel` reads a single XSD file, but that file can pull in others with `xs:import` (for + another namespace) or `xs:include` (for the same one), so documents mixing several schemas work + as long as one schema describes the whole document. Two things to watch: elements declared as + `xs:any` are skipped, so a payload described by another schema has to be declared explicitly in + a wrapper schema of your own; and complex types are identified by their local name, ignoring + the namespace, so two types sharing a local name get a numeric suffix and a warning. Give your + own types distinct names to keep the model readable. + See [Configuring your data model](configuring.md) for a full description of the available config options. ## Importing XML files diff --git a/docs/how_it_works.md b/docs/how_it_works.md index e946368..4b20efc 100644 --- a/docs/how_it_works.md +++ b/docs/how_it_works.md @@ -129,7 +129,7 @@ data should be processed correctly. Elements declared as `xs:any` are discarded with a warning, because a wildcard does not tell which elements may appear, and therefore which tables to create. When the content of such an element follows a known schema, declaring it explicitly in a wrapper schema makes it importable, see -[Combining several XSD schemas](multiple_schemas.md). +[Exploring the data model](getting_started.md#exploring-the-data-model). #### Mixed content elements diff --git a/docs/multiple_schemas.md b/docs/multiple_schemas.md deleted file mode 100644 index 59a7ff5..0000000 --- a/docs/multiple_schemas.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: "Combining several XSD schemas" -description: "How to load XML documents which mix several XML schemas with xml2db, by writing a wrapper XSD which imports them and replaces xs:any wildcards." ---- - -# Combining several XSD schemas - -`xml2db` builds a data model from a single XSD file, but that file can pull in as many other schemas as needed with -`xs:import` (for another namespace) and `xs:include` (for the same namespace). Documents mixing several namespaces are -therefore supported, provided that a single schema describes the whole document. - -Two situations require a bit of work: - -* the container schema declares its payload as a wildcard (`xs:any`), so it does not say which elements may appear, -* the schemas are complete, but no single file references all of them. - -Both are solved by writing a small wrapper schema of your own, which imports the other schemas and declares the -payload explicitly. - -## Replacing a wildcard with the payload schema - -`xml2db` ignores `xs:any` children and logs a warning, because a wildcard gives no structure to map to tables. This -is common in container formats: protocol envelopes such as SOAP or SRU carry an arbitrary payload, which its own -schema describes. - -The example below covers the [SRU](https://www.loc.gov/standards/sru/) API of the French national library, which -returns [MarcXchange](https://www.loc.gov/standards/iso25577/) records inside an SRU envelope. The SRU schema declares -`recordData` as a wildcard, so the wrapper declares it as holding a `record` element from the MarcXchange namespace: - -``` xml title="sru-marcxchange.xsd" linenums="1" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -Save `marcxchange-2-0.xsd` next to the wrapper, or point `schemaLocation` to its URL, and load documents as usual: - -``` py linenums="1" -from xml2db import DataModel - -model = DataModel( - xsd_file="sru-marcxchange.xsd", - short_name="sru_marcxchange", - connection_string="duckdb:///sru.duckdb", -) -model.create_db_schema() -model.create_all_tables() - -document = model.parse_xml("search_retrieve_response.xml") -document.insert_into_target_tables() -``` - -The MarcXchange records are then queryable, joining the `record` table with the `datafield` and `subfield` tables -derived from the imported schema. - -## Writing the wrapper - -A few points worth checking when writing a wrapper schema: - -* **Element order**: `xs:sequence` requires the exact order declared in the schema. Servers do not always follow the - order of the reference schema, `xs:all` accepts any order for elements occurring at most once. -* **Undeclared elements**: elements missing from the wrapper are parsed and ignored, so a partial wrapper covering only - the elements you need is fine. -* **Type names**: `xml2db` identifies complex types by their local name, ignoring their namespace. Two types named - `recordType` in two namespaces get the keys `recordType` and `recordType_1`, with a warning. Table names are derived - from element names and remain unaffected, but naming types explicitly in your own schema, as `srwRecordType` above, - keeps the model easier to read and to configure. -* **Validation**: `parse_xml` does not validate documents unless `skip_validation=False` is passed. Payloads often - deviate from their reference schema on details such as patterns or `xs:ID` attributes. If validation matters, relax - the corresponding facets in your local copy of the schema. -* **Recursive types**: fields introducing a cycle are discarded with a warning, as described in - [Caveats](how_it_works.md#recursive-xsd). The MarcXchange `embeddeddata` element is one of them, so embedded records - are not imported. diff --git a/mkdocs.yml b/mkdocs.yml index 4765761..1850517 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -10,7 +10,6 @@ nav: - Introduction: "index.md" - Getting started: "getting_started.md" - Configuring: "configuring.md" - - Combining several XSD schemas: "multiple_schemas.md" - How it works: "how_it_works.md" - CLI usage: "cli.md" - API: diff --git a/tests/sample_models/imported_namespaces/main.xsd b/tests/sample_models/imported_namespaces/main.xsd deleted file mode 100644 index 7119a60..0000000 --- a/tests/sample_models/imported_namespaces/main.xsd +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/sample_models/imported_namespaces/other.xsd b/tests/sample_models/imported_namespaces/other.xsd deleted file mode 100644 index 8146f74..0000000 --- a/tests/sample_models/imported_namespaces/other.xsd +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - diff --git a/tests/sample_models/imported_namespaces/xml/basket1.xml b/tests/sample_models/imported_namespaces/xml/basket1.xml deleted file mode 100644 index 94f3b4b..0000000 --- a/tests/sample_models/imported_namespaces/xml/basket1.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - BSK-1 - - Apples - - APL - 3 - - - APL-2 - 5 - - - - Oranges - - ORG - 7 - - - diff --git a/tests/sample_models/orders/orders.xsd b/tests/sample_models/orders/orders.xsd index beb0724..fba11c3 100644 --- a/tests/sample_models/orders/orders.xsd +++ b/tests/sample_models/orders/orders.xsd @@ -92,6 +92,17 @@ + + + + + + + + + @@ -100,6 +111,14 @@ + + + + + + + + diff --git a/tests/sample_models/orders/orders_ddl_mssql_version0.sql b/tests/sample_models/orders/orders_ddl_mssql_version0.sql index e7c4a05..7b09fa5 100644 --- a/tests/sample_models/orders/orders_ddl_mssql_version0.sql +++ b/tests/sample_models/orders/orders_ddl_mssql_version0.sql @@ -20,6 +20,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL IDENTITY, + reference VARCHAR(1000) NULL, + carrier VARCHAR(1000) NULL, + record_hash BINARY(20) NULL, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY CLUSTERED (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL IDENTITY, + weight DOUBLE PRECISION NULL, + unit VARCHAR(1000) NULL, + record_hash BINARY(20) NULL, + CONSTRAINT cx_pk_detail PRIMARY KEY CLUSTERED (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_max_length ( pk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length INTEGER NOT NULL IDENTITY, id VARCHAR(1000) NULL, @@ -58,6 +78,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length INTEGER NOT NULL, @@ -96,6 +124,14 @@ CREATE TABLE shiporder_item ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders INTEGER NOT NULL IDENTITY, batch_id VARCHAR(1000) NULL, @@ -114,6 +150,10 @@ CREATE TABLE orders_shiporder ( FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder) ) +CREATE CLUSTERED INDEX ix_fk_item_detail ON item_detail (fk_item, fk_detail) + +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + CREATE CLUSTERED INDEX ix_fk_item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length ON item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_item, fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length_fk_intfeature_with_peculiarly__7aff ON item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) @@ -126,6 +166,10 @@ CREATE CLUSTERED INDEX ix_fk_shiporder_item ON shiporder_item (fk_shiporder, fk_ CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) +CREATE CLUSTERED INDEX ix_fk_shiporder_detail_detail_1 ON shiporder_detail_detail_1 (fk_shiporder, fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + CREATE CLUSTERED INDEX ix_fk_orders_shiporder ON orders_shiporder (fk_orders, fk_shiporder) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) diff --git a/tests/sample_models/orders/orders_ddl_mssql_version1.sql b/tests/sample_models/orders/orders_ddl_mssql_version1.sql index 51f3837..cdab862 100644 --- a/tests/sample_models/orders/orders_ddl_mssql_version1.sql +++ b/tests/sample_models/orders/orders_ddl_mssql_version1.sql @@ -21,6 +21,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL IDENTITY, + reference VARCHAR(1000) NULL, + carrier VARCHAR(1000) NULL, + record_hash BINARY(16) NULL, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY CLUSTERED (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL IDENTITY, + weight DOUBLE PRECISION NULL, + unit VARCHAR(1000) NULL, + record_hash BINARY(16) NULL, + CONSTRAINT cx_pk_detail PRIMARY KEY CLUSTERED (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_max_length ( pk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length INTEGER NOT NULL IDENTITY, id VARCHAR(1000) NULL, @@ -55,6 +75,15 @@ CREATE TABLE shiporder ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders INTEGER NOT NULL IDENTITY, batch_id VARCHAR(1000) NULL, @@ -96,6 +125,15 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length INTEGER NOT NULL, @@ -113,10 +151,18 @@ CREATE TABLE item_product_features_stringfeature ( FOREIGN KEY(fk_stringfeature) REFERENCES stringfeature (pk_stringfeature) ) +CREATE CLUSTERED INDEX ix_fk_shiporder_detail_detail_1 ON shiporder_detail_detail_1 (fk_shiporder, fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + CREATE CLUSTERED INDEX ix_fk_orders_shiporder ON orders_shiporder (fk_orders, fk_shiporder) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) +CREATE CLUSTERED INDEX ix_fk_item_detail ON item_detail (fk_item, fk_detail) + +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + CREATE CLUSTERED INDEX ix_fk_item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length ON item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_item, fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length_fk_intfeature_with_peculiarly__7aff ON item_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) diff --git a/tests/sample_models/orders/orders_ddl_mssql_version2.sql b/tests/sample_models/orders/orders_ddl_mssql_version2.sql index f06326a..0770166 100644 --- a/tests/sample_models/orders/orders_ddl_mssql_version2.sql +++ b/tests/sample_models/orders/orders_ddl_mssql_version2.sql @@ -31,6 +31,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL IDENTITY, + reference VARCHAR(1000) NULL, + carrier VARCHAR(1000) NULL, + xml2db_record_hash BINARY(20) NULL, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY CLUSTERED (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL IDENTITY, + weight DOUBLE PRECISION NULL, + unit VARCHAR(1000) NULL, + xml2db_record_hash BINARY(20) NULL, + CONSTRAINT cx_pk_detail PRIMARY KEY CLUSTERED (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_max_length ( pk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length INTEGER NOT NULL IDENTITY, id VARCHAR(1000) NULL, @@ -95,6 +115,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE shiporder ( pk_shiporder INTEGER NOT NULL IDENTITY, temp_pk_shiporder INTEGER NULL, @@ -128,6 +156,14 @@ CREATE TABLE shiporder_item ( FOREIGN KEY(fk_item) REFERENCES item (pk_item) ) + +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + CREATE CLUSTERED INDEX ix_fk_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length ON product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_product, fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) CREATE INDEX ix_product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length_fk_intfeature_with_peculiarly_long__3ab3 ON product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length (fk_intfeature_with_peculiarly_long_suffix_which_overflow_max_length) @@ -136,7 +172,15 @@ CREATE CLUSTERED INDEX ix_fk_product_features_stringfeature ON product_features_ CREATE INDEX ix_product_features_stringfeature_fk_stringfeature ON product_features_stringfeature (fk_stringfeature) +CREATE CLUSTERED INDEX ix_fk_item_detail ON item_detail (fk_item, fk_detail) + +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + CREATE CLUSTERED INDEX ix_fk_shiporder_item ON shiporder_item (fk_shiporder, fk_item) CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) +CREATE CLUSTERED INDEX ix_fk_shiporder_detail_detail_1 ON shiporder_detail_detail_1 (fk_shiporder, fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + diff --git a/tests/sample_models/orders/orders_ddl_mysql_version0.sql b/tests/sample_models/orders/orders_ddl_mysql_version0.sql index 6da9028..7b9a141 100644 --- a/tests/sample_models/orders/orders_ddl_mysql_version0.sql +++ b/tests/sample_models/orders/orders_ddl_mysql_version0.sql @@ -20,6 +20,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL AUTO_INCREMENT, + reference VARCHAR(255), + carrier VARCHAR(255), + record_hash BINARY(20), + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL AUTO_INCREMENT, + weight DOUBLE, + unit VARCHAR(255), + record_hash BINARY(20), + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_ove_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which__85b659b INTEGER NOT NULL AUTO_INCREMENT, id VARCHAR(255), @@ -58,6 +78,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_779d1ac ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which__00590e9 INTEGER NOT NULL, @@ -96,6 +124,14 @@ CREATE TABLE shiporder_item ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders INTEGER NOT NULL AUTO_INCREMENT, batch_id VARCHAR(255), @@ -114,6 +150,10 @@ CREATE TABLE orders_shiporder ( FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder) ) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_779d_b099 ON item_product_features_intfeature_with_peculiarly_779d1ac (fk_intfeature_with_peculiarly_long_suffix_which__00590e9) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_779d_4520 ON item_product_features_intfeature_with_peculiarly_779d1ac (fk_item) @@ -126,6 +166,10 @@ CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) CREATE INDEX ix_shiporder_item_fk_shiporder ON shiporder_item (fk_shiporder) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + CREATE INDEX ix_orders_shiporder_fk_orders ON orders_shiporder (fk_orders) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) diff --git a/tests/sample_models/orders/orders_ddl_mysql_version1.sql b/tests/sample_models/orders/orders_ddl_mysql_version1.sql index 8a381d3..54fff89 100644 --- a/tests/sample_models/orders/orders_ddl_mysql_version1.sql +++ b/tests/sample_models/orders/orders_ddl_mysql_version1.sql @@ -21,6 +21,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL AUTO_INCREMENT, + reference VARCHAR(255), + carrier VARCHAR(255), + record_hash BINARY(16), + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL AUTO_INCREMENT, + weight DOUBLE, + unit VARCHAR(255), + record_hash BINARY(16), + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_ove_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which__85b659b INTEGER NOT NULL AUTO_INCREMENT, id VARCHAR(255), @@ -55,6 +75,15 @@ CREATE TABLE shiporder ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders INTEGER NOT NULL AUTO_INCREMENT, batch_id VARCHAR(255), @@ -96,6 +125,15 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_779d1ac ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which__00590e9 INTEGER NOT NULL, @@ -113,10 +151,18 @@ CREATE TABLE item_product_features_stringfeature ( FOREIGN KEY(fk_stringfeature) REFERENCES stringfeature (pk_stringfeature) ) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + CREATE INDEX ix_orders_shiporder_fk_orders ON orders_shiporder (fk_orders) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_779d_b099 ON item_product_features_intfeature_with_peculiarly_779d1ac (fk_intfeature_with_peculiarly_long_suffix_which__00590e9) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_779d_4520 ON item_product_features_intfeature_with_peculiarly_779d1ac (fk_item) diff --git a/tests/sample_models/orders/orders_ddl_mysql_version2.sql b/tests/sample_models/orders/orders_ddl_mysql_version2.sql index e8d17d9..a23a0c2 100644 --- a/tests/sample_models/orders/orders_ddl_mysql_version2.sql +++ b/tests/sample_models/orders/orders_ddl_mysql_version2.sql @@ -31,6 +31,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 INTEGER NOT NULL AUTO_INCREMENT, + reference VARCHAR(255), + carrier VARCHAR(255), + xml2db_record_hash BINARY(20), + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + +CREATE TABLE detail ( + pk_detail INTEGER NOT NULL AUTO_INCREMENT, + weight DOUBLE, + unit VARCHAR(255), + xml2db_record_hash BINARY(20), + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_ove_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which__85b659b INTEGER NOT NULL AUTO_INCREMENT, id VARCHAR(255), @@ -95,6 +115,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE shiporder ( pk_shiporder INTEGER NOT NULL AUTO_INCREMENT, temp_pk_shiporder INTEGER, @@ -128,6 +156,14 @@ CREATE TABLE shiporder_item ( FOREIGN KEY(fk_item) REFERENCES item (pk_item) ) + +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + CREATE INDEX ix_product_features_intfeature_with_peculiarly_long_82a4_da9b ON product_features_intfeature_with_peculiarly_long_82a4847 (fk_intfeature_with_peculiarly_long_suffix_which__00590e9) CREATE INDEX ix_product_features_intfeature_with_peculiarly_long_82a4_6910 ON product_features_intfeature_with_peculiarly_long_82a4847 (fk_product) @@ -136,7 +172,15 @@ CREATE INDEX ix_product_features_stringfeature_fk_product ON product_features_st CREATE INDEX ix_product_features_stringfeature_fk_stringfeature ON product_features_stringfeature (fk_stringfeature) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) CREATE INDEX ix_shiporder_item_fk_shiporder ON shiporder_item (fk_shiporder) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + diff --git a/tests/sample_models/orders/orders_ddl_postgresql_version0.sql b/tests/sample_models/orders/orders_ddl_postgresql_version0.sql index fdbd56c..2d61440 100644 --- a/tests/sample_models/orders/orders_ddl_postgresql_version0.sql +++ b/tests/sample_models/orders/orders_ddl_postgresql_version0.sql @@ -20,6 +20,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 SERIAL NOT NULL, + reference VARCHAR(1000), + carrier VARCHAR(1000), + record_hash BYTEA, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail SERIAL NOT NULL, + weight DOUBLE PRECISION, + unit VARCHAR(1000), + record_hash BYTEA, + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_m_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which_overflo_85b659b SERIAL NOT NULL, id VARCHAR(1000), @@ -58,6 +78,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_long_s_779d1ac ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which_overflo_00590e9 INTEGER NOT NULL, @@ -96,6 +124,14 @@ CREATE TABLE shiporder_item ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders SERIAL NOT NULL, batch_id VARCHAR(1000), @@ -114,6 +150,10 @@ CREATE TABLE orders_shiporder ( FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder) ) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_lon_36ea ON item_product_features_intfeature_with_peculiarly_long_s_779d1ac (fk_intfeature_with_peculiarly_long_suffix_which_overflo_00590e9) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_lon_124e ON item_product_features_intfeature_with_peculiarly_long_s_779d1ac (fk_item) @@ -126,6 +166,10 @@ CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) CREATE INDEX ix_shiporder_item_fk_shiporder ON shiporder_item (fk_shiporder) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + CREATE INDEX ix_orders_shiporder_fk_orders ON orders_shiporder (fk_orders) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) diff --git a/tests/sample_models/orders/orders_ddl_postgresql_version1.sql b/tests/sample_models/orders/orders_ddl_postgresql_version1.sql index 03dce19..1408455 100644 --- a/tests/sample_models/orders/orders_ddl_postgresql_version1.sql +++ b/tests/sample_models/orders/orders_ddl_postgresql_version1.sql @@ -21,6 +21,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 SERIAL NOT NULL, + reference VARCHAR(1000), + carrier VARCHAR(1000), + record_hash BYTEA, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (record_hash) +) + + +CREATE TABLE detail ( + pk_detail SERIAL NOT NULL, + weight DOUBLE PRECISION, + unit VARCHAR(1000), + record_hash BYTEA, + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_m_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which_overflo_85b659b SERIAL NOT NULL, id VARCHAR(1000), @@ -55,6 +75,15 @@ CREATE TABLE shiporder ( ) +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + + CREATE TABLE orders ( pk_orders SERIAL NOT NULL, batch_id VARCHAR(1000), @@ -96,6 +125,15 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + xml2db_row_number INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE item_product_features_intfeature_with_peculiarly_long_s_779d1ac ( fk_item INTEGER NOT NULL, fk_intfeature_with_peculiarly_long_suffix_which_overflo_00590e9 INTEGER NOT NULL, @@ -113,10 +151,18 @@ CREATE TABLE item_product_features_stringfeature ( FOREIGN KEY(fk_stringfeature) REFERENCES stringfeature (pk_stringfeature) ) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + CREATE INDEX ix_orders_shiporder_fk_orders ON orders_shiporder (fk_orders) CREATE INDEX ix_orders_shiporder_fk_shiporder ON orders_shiporder (fk_shiporder) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_lon_36ea ON item_product_features_intfeature_with_peculiarly_long_s_779d1ac (fk_intfeature_with_peculiarly_long_suffix_which_overflo_00590e9) CREATE INDEX ix_item_product_features_intfeature_with_peculiarly_lon_124e ON item_product_features_intfeature_with_peculiarly_long_s_779d1ac (fk_item) diff --git a/tests/sample_models/orders/orders_ddl_postgresql_version2.sql b/tests/sample_models/orders/orders_ddl_postgresql_version2.sql index 4ad1cd1..9a696cd 100644 --- a/tests/sample_models/orders/orders_ddl_postgresql_version2.sql +++ b/tests/sample_models/orders/orders_ddl_postgresql_version2.sql @@ -31,6 +31,26 @@ CREATE TABLE orderperson ( ) +CREATE TABLE detail_1 ( + pk_detail_1 SERIAL NOT NULL, + reference VARCHAR(1000), + carrier VARCHAR(1000), + xml2db_record_hash BYTEA, + CONSTRAINT cx_pk_detail_1 PRIMARY KEY (pk_detail_1), + CONSTRAINT detail_1_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + +CREATE TABLE detail ( + pk_detail SERIAL NOT NULL, + weight DOUBLE PRECISION, + unit VARCHAR(1000), + xml2db_record_hash BYTEA, + CONSTRAINT cx_pk_detail PRIMARY KEY (pk_detail), + CONSTRAINT detail_xml2db_record_hash UNIQUE (xml2db_record_hash) +) + + CREATE TABLE intfeature_with_peculiarly_long_suffix_which_overflow_m_5868736 ( pk_intfeature_with_peculiarly_long_suffix_which_overflo_85b659b SERIAL NOT NULL, id VARCHAR(1000), @@ -95,6 +115,14 @@ CREATE TABLE item ( ) +CREATE TABLE item_detail ( + fk_item INTEGER NOT NULL, + fk_detail INTEGER NOT NULL, + FOREIGN KEY(fk_item) REFERENCES item (pk_item), + FOREIGN KEY(fk_detail) REFERENCES detail (pk_detail) +) + + CREATE TABLE shiporder ( pk_shiporder SERIAL NOT NULL, temp_pk_shiporder INTEGER, @@ -128,6 +156,14 @@ CREATE TABLE shiporder_item ( FOREIGN KEY(fk_item) REFERENCES item (pk_item) ) + +CREATE TABLE shiporder_detail_detail_1 ( + fk_shiporder INTEGER NOT NULL, + fk_detail_1 INTEGER NOT NULL, + FOREIGN KEY(fk_shiporder) REFERENCES shiporder (pk_shiporder), + FOREIGN KEY(fk_detail_1) REFERENCES detail_1 (pk_detail_1) +) + CREATE INDEX ix_product_features_intfeature_with_peculiarly_long_suf_63f4 ON product_features_intfeature_with_peculiarly_long_suffix_82a4847 (fk_intfeature_with_peculiarly_long_suffix_which_overflo_00590e9) CREATE INDEX ix_product_features_intfeature_with_peculiarly_long_suf_0375 ON product_features_intfeature_with_peculiarly_long_suffix_82a4847 (fk_product) @@ -136,7 +172,15 @@ CREATE INDEX ix_product_features_stringfeature_fk_product ON product_features_st CREATE INDEX ix_product_features_stringfeature_fk_stringfeature ON product_features_stringfeature (fk_stringfeature) +CREATE INDEX ix_item_detail_fk_detail ON item_detail (fk_detail) + +CREATE INDEX ix_item_detail_fk_item ON item_detail (fk_item) + CREATE INDEX ix_shiporder_item_fk_item ON shiporder_item (fk_item) CREATE INDEX ix_shiporder_item_fk_shiporder ON shiporder_item (fk_shiporder) +CREATE INDEX ix_shiporder_detail_detail_1_fk_detail_1 ON shiporder_detail_detail_1 (fk_detail_1) + +CREATE INDEX ix_shiporder_detail_detail_1_fk_shiporder ON shiporder_detail_detail_1 (fk_shiporder) + diff --git a/tests/sample_models/orders/orders_erd_version0.md b/tests/sample_models/orders/orders_erd_version0.md index c3c803b..d4d4a7e 100644 --- a/tests/sample_models/orders/orders_erd_version0.md +++ b/tests/sample_models/orders/orders_erd_version0.md @@ -8,12 +8,14 @@ erDiagram shiporder ||--|| orderperson : "orderperson" shiporder ||--o| orderperson : "shipto" shiporder ||--|{ item : "item*" + shiporder ||--o{ detail_1 : "detail*" shiporder { string orderid dateTime processed_at } item ||--o| orderperson : "delivery_from" item ||--o| orderperson : "delivery_to" + item ||--o{ detail : "detail*" item ||--o{ intfeature_with_peculiarly_long_suffix_which_overflow_max_length : "product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length*" item ||--o{ stringfeature : "product_features_stringfeature*" item { @@ -32,6 +34,14 @@ erDiagram string id integer value } + detail { + decimal weight + string unit + } + detail_1 { + string reference + string carrier + } orderperson { string name_attr string name diff --git a/tests/sample_models/orders/orders_erd_version1.md b/tests/sample_models/orders/orders_erd_version1.md index 2b44020..fddb4fa 100644 --- a/tests/sample_models/orders/orders_erd_version1.md +++ b/tests/sample_models/orders/orders_erd_version1.md @@ -2,6 +2,7 @@ erDiagram item ||--o| orderperson : "delivery_from" item ||--o| orderperson : "delivery_to" + item ||--o{ detail : "detail*" item ||--o{ intfeature_with_peculiarly_long_suffix_which_overflow_max_length : "product_features_intfeature_with_peculiarly_long_suffix_which_overflow_max_length*" item ||--o{ stringfeature : "product_features_stringfeature*" item { @@ -20,6 +21,7 @@ erDiagram shiporder ||--|| orderperson : "orderperson" shiporder ||--o| orderperson : "shipto" shiporder ||--|{ item : "item" + shiporder ||--o{ detail_1 : "detail*" shiporder { string orderid dateTime processed_at @@ -32,6 +34,14 @@ erDiagram string id integer value } + detail { + decimal weight + string unit + } + detail_1 { + string reference + string carrier + } orderperson { string name_attr string name diff --git a/tests/sample_models/orders/orders_erd_version2.md b/tests/sample_models/orders/orders_erd_version2.md index fba046e..69cb202 100644 --- a/tests/sample_models/orders/orders_erd_version2.md +++ b/tests/sample_models/orders/orders_erd_version2.md @@ -2,6 +2,7 @@ erDiagram shiporder ||--o| orderperson : "shipto" shiporder ||--|{ item : "item*" + shiporder ||--o{ detail_1 : "detail*" shiporder { string orderid dateTime processed_at @@ -22,6 +23,7 @@ erDiagram item ||--|| product : "product" item ||--o| orderperson : "delivery_from" item ||--o| orderperson : "delivery_to" + item ||--o{ detail : "detail*" item { string note integer quantity @@ -42,6 +44,14 @@ erDiagram string id integer value } + detail { + decimal weight + string unit + } + detail_1 { + string reference + string carrier + } orderperson { string name_attr string name diff --git a/tests/sample_models/orders/orders_source_tree_version0.txt b/tests/sample_models/orders/orders_source_tree_version0.txt index 5563bcf..10f16bf 100644 --- a/tests/sample_models/orders/orders_source_tree_version0.txt +++ b/tests/sample_models/orders/orders_source_tree_version0.txt @@ -92,4 +92,10 @@ orders: lei[0, 1]: string coordinates[0, 1]: string extra[0, 1]: - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/orders_source_tree_version1.txt b/tests/sample_models/orders/orders_source_tree_version1.txt index 5563bcf..10f16bf 100644 --- a/tests/sample_models/orders/orders_source_tree_version1.txt +++ b/tests/sample_models/orders/orders_source_tree_version1.txt @@ -92,4 +92,10 @@ orders: lei[0, 1]: string coordinates[0, 1]: string extra[0, 1]: - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/orders_source_tree_version2.txt b/tests/sample_models/orders/orders_source_tree_version2.txt index 5563bcf..10f16bf 100644 --- a/tests/sample_models/orders/orders_source_tree_version2.txt +++ b/tests/sample_models/orders/orders_source_tree_version2.txt @@ -92,4 +92,10 @@ orders: lei[0, 1]: string coordinates[0, 1]: string extra[0, 1]: - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/orders_target_tree_version0.txt b/tests/sample_models/orders/orders_target_tree_version0.txt index 9e46c7f..f9cad4a 100644 --- a/tests/sample_models/orders/orders_target_tree_version0.txt +++ b/tests/sample_models/orders/orders_target_tree_version0.txt @@ -72,4 +72,10 @@ orders: companyId_type[0, 1]: string companyId_value[0, 1]: string coordinates[0, 1]: string - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/orders_target_tree_version1.txt b/tests/sample_models/orders/orders_target_tree_version1.txt index 2fefeb5..c9c5595 100644 --- a/tests/sample_models/orders/orders_target_tree_version1.txt +++ b/tests/sample_models/orders/orders_target_tree_version1.txt @@ -76,4 +76,10 @@ orders: companyId_bic[0, 1]: string companyId_lei[0, 1]: string coordinates[0, 1]: string - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/orders_target_tree_version2.txt b/tests/sample_models/orders/orders_target_tree_version2.txt index 96df78b..e2ca71f 100644 --- a/tests/sample_models/orders/orders_target_tree_version2.txt +++ b/tests/sample_models/orders/orders_target_tree_version2.txt @@ -72,4 +72,10 @@ orders: companyId_type[0, 1]: string companyId_value[0, 1]: string coordinates[0, 1]: string - a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string \ No newline at end of file + a_very_long_field_type_that_makes_col_name_exceeds_max_identifier_length[0, 1]: string + detail[0, None]: + weight[1, 1]: decimal + unit[1, 1]: string + detail[0, None]: + reference[1, 1]: string + carrier[1, 1]: string \ No newline at end of file diff --git a/tests/sample_models/orders/xml/order3.xml b/tests/sample_models/orders/xml/order3.xml index 238dad9..2cf229d 100644 --- a/tests/sample_models/orders/xml/order3.xml +++ b/tests/sample_models/orders/xml/order3.xml @@ -47,6 +47,14 @@ FR + + 12.5 + kg + + + 0.8 + m3 + @@ -67,5 +75,9 @@ + + BL-2023-45 + Acme Transport + diff --git a/tests/test_imported_namespaces.py b/tests/test_imported_namespaces.py deleted file mode 100644 index f960510..0000000 --- a/tests/test_imported_namespaces.py +++ /dev/null @@ -1,52 +0,0 @@ -import os - -from xml2db import DataModel -from xml2db.xml_converter import XMLConverter -from .conftest import models_path - -xsd_path = os.path.join(models_path, "imported_namespaces", "main.xsd") -xml_path = os.path.join(models_path, "imported_namespaces", "xml", "basket1.xml") - - -def build_model(): - return DataModel(str(xsd_path), short_name="imported_namespaces") - - -def iter_nodes(node): - """Yield all nodes of a document tree recursively""" - yield node - for values in node[1].values(): - for value in values: - if isinstance(value, tuple): - yield from iter_nodes(value) - - -def test_imported_namespace_types_are_not_merged(): - """Types sharing a local name across namespaces are mapped to distinct tables""" - - model = build_model() - - assert sorted(model.tables.keys()) == ["basketType", "itemType", "itemType_1"] - assert sorted(model.names_types_map.keys()) == ["basket", "item", "item_1"] - assert sorted(model.tables["itemType"].columns.keys()) == ["label"] - assert sorted(model.tables["itemType_1"].columns.keys()) == ["code", "quantity"] - - -def test_imported_namespace_fields_are_parsed(): - """Content defined in the imported schema is parsed into its own nodes""" - - model = build_model() - converter = XMLConverter(model) - - parsed_recursive = converter.parse_xml(xml_path, skip_validation=False) - parsed_iterative = converter.parse_xml( - xml_path, skip_validation=False, iterparse=True - ) - - assert parsed_recursive == parsed_iterative - - imported_nodes = [ - node for node in iter_nodes(parsed_recursive) if node[0] == "itemType_1" - ] - assert [node[1]["code"][0] for node in imported_nodes] == ["APL", "APL-2", "ORG"] - assert [node[1]["quantity"][0] for node in imported_nodes] == [3, 5, 7] diff --git a/tests/test_models_output.py b/tests/test_models_output.py index b558fed..50c54d9 100644 --- a/tests/test_models_output.py +++ b/tests/test_models_output.py @@ -85,3 +85,19 @@ def test_model_ddl(test_config): ) assert actual == expected + + +def test_same_local_name_types_get_distinct_tables(): + """A test to check that two types sharing a local name are not merged + + orders.xsd declares 'detail' twice with a different anonymous complex type, in itemtype and in + shipordertype. Both types have the local name 'detail', and each one gets its own table. + """ + + model = DataModel( + str(os.path.join(models_path, "orders", "orders.xsd")), + short_name="orders", + ) + + assert sorted(model.tables["detail"].columns.keys()) == ["unit", "weight"] + assert sorted(model.tables["detail_1"].columns.keys()) == ["carrier", "reference"] From aff3917ab7d47df7552caa7fabb08b9bb7b401f5 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 21 Sep 2026 20:30:25 +0000 Subject: [PATCH 3/3] Trim the note on schemas and drop the redundant test The wildcards and type naming details in the getting started note are already covered by the caveats page, and the cross reference to the note is not needed there. The model output snapshots already fail when two types sharing a local name are merged, so the added test checked nothing new. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DYgbAh7586edNcD8HXyvcP --- docs/getting_started.md | 6 +----- docs/how_it_works.md | 3 +-- tests/test_models_output.py | 16 ---------------- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/docs/getting_started.md b/docs/getting_started.md index 54110bc..829211e 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -55,11 +55,7 @@ xml2db render schema.xsd --format ddl --db-type postgresql `DataModel` reads a single XSD file, but that file can pull in others with `xs:import` (for another namespace) or `xs:include` (for the same one), so documents mixing several schemas work - as long as one schema describes the whole document. Two things to watch: elements declared as - `xs:any` are skipped, so a payload described by another schema has to be declared explicitly in - a wrapper schema of your own; and complex types are identified by their local name, ignoring - the namespace, so two types sharing a local name get a numeric suffix and a warning. Give your - own types distinct names to keep the model readable. + as long as one schema describes the whole document. See [Configuring your data model](configuring.md) for a full description of the available config options. diff --git a/docs/how_it_works.md b/docs/how_it_works.md index 4b20efc..7a23846 100644 --- a/docs/how_it_works.md +++ b/docs/how_it_works.md @@ -128,8 +128,7 @@ data should be processed correctly. Elements declared as `xs:any` are discarded with a warning, because a wildcard does not tell which elements may appear, and therefore which tables to create. When the content of such an element follows a known schema, declaring it -explicitly in a wrapper schema makes it importable, see -[Exploring the data model](getting_started.md#exploring-the-data-model). +explicitly in a wrapper schema makes it importable. #### Mixed content elements diff --git a/tests/test_models_output.py b/tests/test_models_output.py index 50c54d9..b558fed 100644 --- a/tests/test_models_output.py +++ b/tests/test_models_output.py @@ -85,19 +85,3 @@ def test_model_ddl(test_config): ) assert actual == expected - - -def test_same_local_name_types_get_distinct_tables(): - """A test to check that two types sharing a local name are not merged - - orders.xsd declares 'detail' twice with a different anonymous complex type, in itemtype and in - shipordertype. Both types have the local name 'detail', and each one gets its own table. - """ - - model = DataModel( - str(os.path.join(models_path, "orders", "orders.xsd")), - short_name="orders", - ) - - assert sorted(model.tables["detail"].columns.keys()) == ["unit", "weight"] - assert sorted(model.tables["detail_1"].columns.keys()) == ["carrier", "reference"]