feat(plugin-mongodb): filter on nested object and array-element fields - #2318
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…and keep BETWEEN bounds apart
…ing' into feat/mongodb-nested-field-filtering
|
Self-review found six defects in the first commit, fixed in The type coercion was inert in the running app. Every MongoDB BETWEEN sent the joined string as its lower bound. Four more:
All three behaviour changes verified against the live MongoDB 7, with each bug reproducing as an empty result first:
181 tests pass, build and MongoDB plugin build clean, lint 0 violations.
|
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…ing' into feat/mongodb-nested-field-filtering
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…ing' into feat/mongodb-nested-field-filtering
Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
…ing' into feat/mongodb-nested-field-filtering
|
Also fixes #2322 (folded in at your request), commit Root cause is one line. case "/": output += "\\/" // NumberText.swift:212Escaping One fix covers both places the issue names. The JSON result view ( Blast radius, since The guard that caught this. Tests: three new cases in Verified: 250/250 in TableProCore, 226/226 across the app-side JSON and MongoDB suites ( |
Fixes #2315.
Fixes #2322.
What was wrong
The filter column picker is fed one flat list: the loaded grid result's columns (
MainEditorContentView.swift:757). For MongoDB that list isBsonDocumentFlattener.unionColumns, top-level keys only, with a nested object or an array of objects collapsed into a singleJSON-typed column. So there was no way to filter oncustomer.countryoritems.sku.The driver already computed dotted paths.
BsonDocumentFlattener.fieldPathswalks nested objects and the objects an array holds, and it is exposed on the driver protocol assampleFieldPaths. Its only consumer was mongo-shell autocomplete. Two things genuinely did not exist:typeNamemaps.documentand.arrayboth to"JSON", andPluginFieldPathcarried only{path, typeName, depth}.PluginQueryFiltercarried(column, op, value, isCaseSensitive), and the condition list was flat and 1:1 with filter rows.The fix
Dot notation is wiring.
TableFilter.columnNameis an unvalidated freeStringwith no membership check anywhere in the path, andbuildConditionalready emitted"customer.country": …correctly for a dotted column. The picker now merges the driver's sampled paths into the column list, grouped under their top-level parent, capped at depth 2 and 60 items. A button beside it opens a searchable list of every path, which is also the route to a field the sample missed: typing a path there uses it.$elemMatchis a narrow refactor. It is inherently a grouping (N rows collapse to one clause), which cannot be an operator:FilterOperatoris one global enum shared by every database type, and a MongoDB-only case would hard-code one driver into it. SoPluginQueryFiltergainedelementScope, andbuildFilterDocumentbecame partition-then-emit. A row on an array-crossing path gets an any element / same element control; rows sharing a prefix and set to same element collapse into one$elemMatch.$elemMatchis never substituted silently. With a single condition it is still a different query from dot notation (it also excludes documents whose field is not an array), so it is emitted only when asked for.Type coercion was not optional. MongoDB compares only within a BSON type.
MongoDBPluginDriver.buildFilteredQueryaccepted acolumnKindsargument and discarded it, so a Date, ObjectId or Decimal128 range filter was compared as a string and returned nothing, with no error. Nestedcustomer.registeredAtanditems.shippedAtleaves were previously unreachable as filter targets, so this new surface would have shipped broken on arrival. The driver now keeps the sampled kind per path and the builder emits$date,$oidand$numberDecimal.Fields dot notation cannot address are excluded. A key holding a literal
.or opening with$is skipped along with everything beneath it. The server reads the dot as a path separator, so offering such a field would match a different one. The docs point at$getFieldinside$expras the escape hatch.Also fixed, in the same files and on the same control
Each of these is a silent wrong result on the filter panel a user reaches this feature through.
buildFilterDocumentreturned{}when every condition dropped. BecauseasPluginQueryFilterjoinedvalue + "," + secondValue, any comma in a between bound made the split fail, so the filter silently widened to every document while the panel reported it applied.PluginQueryFilternow carriessecondValueintact, and the all-dropped guard fails closed with{"_id": {"$in": []}}instead of match-all. The joinedvalueis unchanged for the other plugins that parse it.__RAW__. It is the default first row, and it is exactly the workaround a user hits for this issue. It now takes a filter document, the wayElasticsearchQueryBuilder.rawColumnalready does, and is labelled Raw Filter on a database that does not speak SQL.{}is left to stand, since that is MongoDB's own spelling for match-everything.=with Match Case off became an anchored$regex, which only ever matches a string, soage = 28returned nothing andage != 28returned everything. Case now applies to text kinds only.Verified
Every semantic proven against a live MongoDB 7 (
mongo:7in Docker) seeded with the reporter's exact documents, parsing each filter withEJSON.parseso it goes through the same Extended JSON path asbson_new_from_json:{"customer.country": "US"}{"items.sku": "A100"}items.price>500ANDitems.name=Laptop, any element$elemMatch){"_id": {"$in": []}}(fail-closed)createdAt >= 2024as a string (before)createdAt >= 2024as$date(after)_id > <oid>as a string (before)_id > <oid>as$oid(after)The two before/after pairs are the type-bracketing bug reproducing and then fixed.
scripts/check-mongodb-filter-shapes.shis committed: it compiles a C probe against the realLibs/libbson_*.aand parses all 24 filter shapes the builder can emit, including$andnested inside$elemMatch. The builder assembles Extended JSON by hand and the Swift tests only compare strings, so nothing else would catch a shape that stops parsing after a libbson bump. All 24 parse under 1.28.1.Also run:
generate,build(PASS), 163 unit tests across six suites (PASS),lintonTablePro,Plugins/MongoDBDriverPlugin,Plugins/TableProPluginKitandTableProTests(0 violations, docs references clean).Two existing tests changed on purpose.
filterDocumentBetweenInvalidandfilterDocumentUnknownOpasserteddoc == "{}", pinning the fail-open behaviour as correct. They now assert the fail-closed filter. This is a deliberate spec change, not a test bent to match new output.ABI
PluginFieldPathgainedarrayPrefixesandPluginQueryFiltergainedsecondValueandelementScope. Both shipped initializers keep their exact signatures and are marked@_disfavoredOverload, with the new fields on new overloads, per the rule inCLAUDE.mdthat a defaulted parameter on an existing public init broke every shipped plugin in 0.49.0. This is additive, so nocurrentPluginKitVersionbump.MongoDB is registry-only, so this needs a plugin release to reach users.
Not done
->>/#>>generation and the numeric-cast decision live inFilterSQLGenerator. ThesampleFieldPathshook stays open for it.SortColumnResolverresolves names positionally against the flat column list, so a nested path is unreachable as a sort key. Nothing silently wrong is introduced; it is documented as a limitation.$elemMatchoperators whose semantics I did not verify, so those paths filter with dot notation only rather than emitting a query I could not prove. Documented as a limitation.Testing gaps
No
TableProUITestscoverage. The flow needs a live MongoDB and the registry-only MongoDB plugin installed, so it does not run deterministically in CI.No screenshots. I got as far as a sandboxed Debug build with the MongoDB plugin installed and the seeded collection reachable, but the seeded connection did not decode and driving the window to the right state reliably needed more UI automation than the change warrants. The docs prose in
docs/features/filtering.mdxdescribes the control and its two array modes with a table rather than pointing at an image that does not exist. A screenshot of the column picker and the any element / same element control is worth adding fordocs/features/filtering.mdx#nested-fieldswhen someone has the plugin installed against a nested collection.