feat(content-drive): field-filter resolver — Tag (DB) + index clauses via search strategies (#36384)#36474
Conversation
…se search strategies) with tests (#36384)
|
Tick the box to add this pull request to the merge queue (same as
|
|
Claude finished @ihoffmann-dot's task in 10m 36s —— View job Code Review — Content Drive field-filter resolver (PR #36474)Reviewed against New Issues
Notes (non-blocking, confirm intent)
No blocking issues — the two Medium routing/scoping items are worth resolving or explicitly signing off before merge, but the default path is correct and tested. |
|
Re: the "Known semantic to confirm with FE" note on Multi-Select/Checkbox — On the FE these render as a multi-pick listbox: the user ticks several values expecting "match any of these" (OR). Two concerns with the current AND behavior (from reusing
So the FE expectation is OR for Multi-Select/Checkbox. Could we special-case those two to OR the tokens rather than inheriting the text strategy's AND? If there's a reason to keep AND, happy to talk it through — just flagging before this merges since the behavior would diverge from what the UI implies. Drafted by Claude Code on my behalf. |
fabrizzio-dotCMS
left a comment
There was a problem hiding this comment.
Reviewed the resolver slice (stacked on #36463). Clean implementation — reusing the shared FieldStrategy handlers instead of hand-rolling Lucene is the right call, and the INDEX↔SQL routing is respected. One blocking item (test not wired into CI, inline) plus coverage gaps below.
Coverage gaps worth closing:
- The new IT only exercises Text (index), Tag (DB), Date range and the 3 validations. Missing: Select/Radio SCALAR equals, Multi-Select/Checkbox MULTI (exactly the AND-vs-OR semantic you flagged — deserves a test pinning the current behavior), Boolean checkbox, and Category (the permission-filtered path the security note relies on). Also: open-ended range (
*bound), out-of-scope type → 400, kind-mismatch → 400, Relationship → 400. - No test goes through JSON deserialization. The IT builds
DriveRequestFormvia the builder, souserSearchable(Map<String,Object>) never passes through Jackson.inferKind'sBoolean/Map/Iterablebranches are validated with Java types, not with what Jackson actually produces from a JSON body. A Postman test onPOST /v1/drive/searchwould cover the real HTTP/JSON contract and the 400s — the existingContentDriveResource.postman_collection.jsoncurrently has 0 references touserSearchable. Recommend adding one. - A pure unit test for
ContentDriveFieldFilterResolver(see the note on #36463) still applies.
| */ | ||
| @ApplicationScoped | ||
| @RunWith(DataProviderWeldRunner.class) | ||
| public class ContentDriveFieldFilterTest extends IntegrationTestBase { |
There was a problem hiding this comment.
🔴 This IT isn't registered in any suite, so it won't run in CI. Failsafe in dotcms-integration/pom.xml only includes MainSuite*/Junit5Suite*/QuickSuite/OpenSearchUpgradeSuite; standalone classes aren't discovered. The sibling drive tests are wired into MainSuite3a (lines 70-71) — this one isn't, so the 8 "passing locally" tests never execute in the pipeline.
Fix: add ContentDriveFieldFilterTest.class to MainSuite3a. And since the Text/Date cases depend on the index, consider also adding it to OpenSearchUpgradeSuite so it's validated under the ES→OS migration matrix.
| final Field field = criteria.getField(); | ||
| final ContentType contentType = criteria.getContentType(); | ||
| final String luceneFieldName = contentType.variable() + "." + field.variable(); | ||
| final Function<FieldContext, String> handler = FieldHandlerRegistry.getHandler(field.type()); |
There was a problem hiding this comment.
getHandler returns getOrDefault(type, ctx -> BLANK), so an INDEX-routed field type without a registered handler silently produces no clause → the criterion returns everything unfiltered instead of failing. All 11 current index-routed types (PR 1's isIndexRouted) are registered, so this is correct today, but the coupling is silent. Consider a fail-fast/WARN when bucket == INDEX and the resolved handler yields BLANK for a non-empty value, so a future type added to isIndexRouted without a handler doesn't degrade to "returns everything".
| ? criteria.getRangeTo() : "*"; | ||
| return from + " TO " + to; | ||
| case MULTI: | ||
| return String.join(",", criteria.getValues()); |
There was a problem hiding this comment.
MULTI joins values by comma into TextFieldStrategy, which ANDs the tokens (the semantic you flagged). Recommend confirming the OR-vs-AND expectation with FE before they consume it. Related and not called out in the description: Boolean Checkbox also routes through the TEXT handler group with "true"/"false" — worth a quick confirm that it matches an indexed boolean checkbox correctly.
| "Field-filter test data ready under " + testAssetPath); | ||
| } | ||
|
|
||
| private static Date date(final int year) { |
There was a problem hiding this comment.
new Date(year - 1900, 0, 1) is deprecated and JVM-local-timezone; the date-range boundary assertions could get flaky on a non-UTC CI agent. Prefer an explicit UTC Instant/ISO-8601 string.
Proposed Changes
Second slice (PR 2 of 3) of the Content Drive field-based search backend — issue #36384. Builds on #36463 (contract + plumbing) and implements the actual resolution of
userSearchablefield filters into DB predicates and index clauses, per the ADR-0018 routing contract.BrowserAPIImpl.buildBaseESQuerynow appends the per-field Lucene clauses for index-routed criteria (Text/Textarea/WYSIWYG, Select/Radio, Multi-Select/Checkbox, Boolean, Date/Time/Date-Time, Category). The field-value → Lucene translation is delegated to the shared strategies incom.dotcms.rest.api.v1.content.search(FieldHandlerRegistry+FieldContext) so the syntax matches the modern content search — no hand-rolled Lucene. Clauses are injected into the existing hybrid per-chunk narrowing, so they AND with the text term and the DB candidate set.selectQuerynow appends atag/tag_inodesub-select for Tag criteria, resolved DB-side regardless of ES filtering to preserve read-your-writes. Tag names within a field combine with OR; criteria across fields combine with AND. Category stays index-only (no SQL tree resolution).FieldSearchCriteriato the value string its strategy expects (scalar, comma-joined multi,true/false,from TO torange with*for open bounds).FieldSearchCriterianow carries itsContentType(needed to build thect.fieldLucene name and provide strategy context).HYBRID_SINGLE_CHUNKED_QUERY_ES.Checklist
ContentDriveFieldFilterTest(integration): text→index, tag→DB (read-your-writes), tag OR within field, date range→index, index+DB AND composition, and the three 400 validations. 8/8 passing locally against the integration environment (Postgres + OpenSearch).CategoryFieldStrategy's per-user permission filtering; all field values are validated against the content type schema (PR 1); tag/field values are bound as SQL parameters (no concatenation of user input into SQL).Additional Info
/v1/drive/searchis@Hidden→ noopenapi.yamlchange.TextFieldStrategy, which ANDs the tokens (matches the current content-search behavior) rather than OR-ing them. Category and Tag are OR-in-list. If FE expects OR for multi-select, we can special-case it — flagged rather than silently diverging from content-search.tag_inode.field_var_nameto avoid false negatives from legacy rows; a content type with two tag fields could cross-match — acceptable v1 limitation, easy to tighten later.Screenshots
N/A — backend only.