Skip to content

fix(mongo): CRUD collection routing (targetEntity->type->name), selector must not be shadowed#190

Merged
ake2l merged 3 commits into
developmentfrom
fix/mongo-crud-collection-fallback
Jul 6, 2026
Merged

fix(mongo): CRUD collection routing (targetEntity->type->name), selector must not be shadowed#190
ake2l merged 3 commits into
developmentfrom
fix/mongo-crud-collection-fallback

Conversation

@ake2l

@ake2l ake2l commented Jul 6, 2026

Copy link
Copy Markdown
Member

Summary

On top of #189: the RDBMS exporter's update/upsert/delete resolve their target table via resolve_target_entity_from_metadata (targetEntity -> type -> name), so a plain target='db.update' iterate carrying only the statement name works. The mongo exporter instead demanded explicit type/selector metadata and raised 'type' or 'selector' statement's attribute is missing — a converted <iterate source='db' sourceEntity='db_order' target='db.update'> has neither. Fixes the shop-mongo demo's aggregate write-back and unblocks the mongodb-delete/upserter demos.

A real regression was found and fixed before this went further (second commit): the first fix's _routing() helper unconditionally injected a name-based targetEntity fallback via dict.setdefault, even when the metadata already carried a selector. Since MongoDBClient.update/upsert/delete all check targetEntity before falling through to selector, this silently redirected every selector-driven write whose statement name doesn't match the selector's own collection — the common idiom of naming a cleanup/delete step for what it does (name="delete", name="cleanup"), used throughout the existing test suite. 7 pre-existing tests failed as a result (confirmed by running them, not by reading the diff): test_mongodb_reference (all 3), test_mongodb_consumer, test_mongodb_cross_collection, test_mongodb_cyclic_source, test_mongodb_with_type — each because a cleanup step silently no-oped on the real collection, leaking stale data into the next run.

_routing() now only injects the name-based fallback when neither targetEntity nor selector is already present. The one existing unit test meant to guard this had a fixture where the statement name coincidentally matched the selector's collection, so it passed under the buggy code too — replaced with a discriminating case (different names) and verified by temporarily reverting the fix.

Third commit: a DSL E2E matrix (test_mongodb_routing_matrix/) proving the routing precedence end to end against a live mongo — insert (name/type/targetEntity precedence), update/upsert/delete (name-only fallback + selector-not-shadowed), and the read/source side (selector -> sourceEntity -> type, deliberately no name fallback). Verified discriminating the same way: reverting the fix fails exactly the 3 selector-routing cases.

Test plan

  • pytest tests_ce/unit_tests/test_mongo_crud_routing.py — 4/4 passed
  • RUNTIME_ENVIRONMENT=development pytest across all mongo external-service folders + the new test_mongodb_routing_matrix — 65 passed, 1 skipped
  • Reverted the fix, confirmed the new matrix test and 7 previously-broken tests fail exactly as expected; restored the fix, confirmed all green again
  • mypy datamimic_ce and ruff check — clean

ake2l added 3 commits July 6, 2026 09:14
…RDBMS parity)

The RDBMS exporter's update/upsert/delete resolve the target table via
resolve_target_entity_from_metadata (targetEntity -> type -> name), so a plain
target='db.update' iterate that carries only the statement name works. The mongo
exporter instead demanded explicit type/selector metadata and raised "'type' or
'selector' statement's attribute is missing" - a converted store-update iterate
(<iterate source='db' sourceEntity='db_order' target='db.update'>) has neither.

All three mongo CRUD ops now route through one _routing() helper mirroring the
RDBMS precedence, preserving any selector filter. Fixes the shop-mongodb demo's
aggregate write-back and unblocks the mongodb-delete/upserter demos.
Review of the previous commit (be3e296) before push found a real regression it
introduced: MongoDBExporter._routing() unconditionally injected a name-based
META_TARGET_ENTITY fallback via dict.setdefault, even when the write metadata
already carried a META_SELECTOR. Since MongoDBClient.update/upsert/delete all
dispatch "if META_TARGET_ENTITY in query" before checking for a selector, this
silently redirected every selector-driven update/upsert/delete whose statement
name doesn't match the selector's own collection - the common real-world idiom
of naming a cleanup/delete step for what it does, not for the collection it
targets (name="delete", name="cleanup", ...), used throughout the existing test
suite. 7 of the pre-existing mongo tests failed as a result (confirmed by running
them, not by static reading) - test_mongodb_reference (all 3), test_mongodb_consumer,
test_mongodb_cross_collection, test_mongodb_cyclic_source, test_mongodb_with_type -
each because a cleanup/delete step silently no-oped on the real collection while
"succeeding" against a bogus one, leaving stale data for the next run.

_routing() now only injects the name-based fallback when NEITHER targetEntity NOR
selector is already present - a selector always resolves its own collection.
Also fixed the one existing unit test that was supposed to guard this
(test_selector_filter_preserved_alongside_resolved_collection): its own fixture
used the same string for both the statement name and the selector's collection,
so the assertion held even under the buggy code and could not have caught this.
Replaced with a discriminating case using different names, and confirmed by
temporarily reverting the fix - the new test fails exactly as expected, the
7 previously-broken tests fail exactly as before, and mypy/ruff stay clean either way.
Neither the routing precedence (name -> type -> targetEntity for writes,
selector -> sourceEntity -> type for reads) nor the "selector must not be
shadowed" invariant had DSL-level coverage before this - only Python unit tests
on MongoDBExporter._routing() in isolation, which is exactly what let the
previous commit's regression through (its own fixture coincidentally used a
matching name/collection). This exercises every combination through the real
engine against a live mongo:

- INSERT: name-only fallback, type wins over name, targetEntity wins over type.
- UPDATE: name-only fallback via a genuinely metadata-free write (sourceEntity
  drives the read but is never added to write metadata, mirroring the actual
  reported gap - "<iterate source='db' sourceEntity='db_order'
  target='db.update'>"), and selector routing survives a mismatched statement
  name (the regression just fixed).
- UPSERT / DELETE: selector routing survives a mismatched statement name,
  including upsert's insert-new-doc path.
- READ (source side): sourceEntity wins over type, type-only works, selector
  wins regardless of a mismatched sourceEntity (type+selector together is
  itself a model-validation error, so that combination isn't reachable).

Verified discriminating: temporarily reverted the fix and confirmed exactly the
3 selector-routing tests fail (the other 3, which don't depend on the fix,
still pass) - this test would have caught the regression the previous commit
introduced.
@sonarqubecloud

sonarqubecloud Bot commented Jul 6, 2026

Copy link
Copy Markdown

@ake2l
ake2l merged commit aedb97e into development Jul 6, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant