Spec: TextField in a numeric column makes the whole contentlet unindexable (#37272) - #37393
Spec: TextField in a numeric column makes the whole contentlet unindexable (#37272)#37393fabrizzio-dotCMS wants to merge 3 commits into
Conversation
…xable (#37272) Issue-resolution spec only (PR 1 of 2). No implementation. loadFields picks the serialization branch by storage column name rather than by the value, so a Text field whose value is a String but whose column is numeric reaches DecimalFormat.format() and throws. The per-field catch logs at WARN and then rethrows, so one bad field discards the whole contentlet. Both defects date to the initial trunk import (2012); the ES->OpenSearch work only made them visible. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @fabrizzio-dotCMS's task in 1m 34s —— View job SDK Compatibility Analysis
Result: No SDK-breaking change detected. This PR is spec-only (per the PR description: "PR 1 of 2 — spec only. Carries None of the SDK-relevant surfaces are affected:
This matches the categories doc's own non-breaking calibration example: "Test-only or documentation-only changes." No comment or label added, per instructions — the implementation (PR 2, once opened) will need this same check applied to the actual code change. |
…pec (#37272) Addresses the four points raised in review of #37393, plus one they surfaced indirectly and which turned out to be the more consequential of the two. The _dotraw key, not the numeric key, is load-bearing. loadFields writes the padded string to <field>_text (:1009, :1118); that entry is lowercased (:590) and renamed _text -> _dotraw (:598), and the _text key is then dropped by default (:607-610), so _dotraw is the only survivor. It is mapped keyword, and addBuilderSort appends _dotraw to every sort field (ContentFactoryIndexOperationsES:412-413). The zero-padding is therefore the only reason ordering by a numeric field works. The previous AC-007 wrote the raw text there, which silently reorders any listing sorted by that field. Changes: - AC-007 is now the _dotraw invariant: padded or absent, never raw text, with an end-to-end sort assertion. Forced by the mapping, not a design choice. - AC-008 decides the numeric key separately: omit rather than emit 0, since a 0 is indistinguishable from a genuine 0 in ranges, sorts and aggregations. Recorded as C-3 with the rejected alternative and its reason. - Best-effort conversion must cover the float path: the failing branch matches float% and integer%, and NumberUtil has no toFloat/toDouble. - Mapping is long for integer% but double for float% - corrected throughout. - "byte-identical" -> "identical emitted map entries", with the Integer 54 vs Long 54 class-identity trap called out (AC-009). - New AC-010: the happy path must emit no WARN at all. - Disambiguated the Data-considerations sentence that could be read as either "must not write a non-numeric string to the numeric key" or "must not write the numeric key at all". Spec only - no implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed all four points, plus one that came out of chasing the first one and turned out to be the bigger of the two. Pushed in a95a723. @dario-daza — re-review please, the The one that wasn't on the list:
|
|
Review: I think the spec looks solid, root cause is right, and the sortOrder call in C-1 is a genuinely good catch. Three things I'd want tightened before I approve:
One and two are the blockers for me. Three is just wording, but they're the kind of wording that turns into a bug. |
1ea0f8d to
cc5b051
Compare
PR 1 of 2 — spec only. Carries
spec.mdand nothing else. Please review it as a spec, not as code: is this the right problem, scoped right, with measurable criteria? Once approved I branch off this branch and open PR 2 with the implementation. No need to wait for this to merge.Refs #37272
TL;DR
What's broken: a Text field whose value is stored as a
Stringbut whose backing column is numeric (integer1) makes the entire contentlet disappear from the index. Silent — the save works, the UI flags nothing, the reindex shows only a failure count.Why:
ESMappingAPIImpl.loadFieldspicks the serialization branch by the storage column name instead of by the value."integer1"starts withinteger, so the String reachesDecimalFormat.format(), which accepts onlyNumberand always throws. The per-fieldcatchthen logs at WARN and rethrows, aborting the whole document.Context worth knowing before reviewing:
TextField.acceptedDataTypes()includesINTEGER/FLOAT, and dotCMS ships content types shaped this way (htmlpageasset.sortOrder,Vanity URL.order). What decides the failure is the Java class of the value, not the modelling.e8ef584ec9— initial trunk import, 2012. The ES→OpenSearch work neither introduced nor worsened it; the bulk refresh just pushed enough documents through the same path to expose it.ESMappingAPIImplis the shared mapper for both engines (OSBulkHelper:250calls the sametoMap), so the fix covers ES and OS at once.Proposed fix
Best-effort conversion at index time. If the value converts, the numeric branch behaves exactly as today. If it does not, emit
0under the numeric key (consistent with the field'slongmapping) with the original text preserved in_dotraw, plus a WARN naming the field and content type. And a failing field no longer kills the document.Out of scope, with the reasoning written down
FieldFactoryImpl:478). Forbidding the combination means removingINTEGERfromTextField.acceptedDataTypes(), which breaks dotCMS's own built-in content types at bootstrap.Field.fieldValue()respectingdataType()— the preventive counterpart, and it would also close theImportStarterUtilroute, but it changes every save of every contentlet in the system. Different blast radius, deserves its own cycle.Identified risk
ImportStarterUtilwrites contentlet JSON verbatim, bypassing the API entirely (:894→toMutableContentlet,:782→ factorysave). Every user-facing path coerces (new editor, legacy editor, Content Import portlet and the new import job all funnel throughsetContentletProperty→integerStrategy), so the starter import is the most plausible provenance of the observed rows — and it explains why the defect cannot be reproduced through the UI. Named as a known gap; not fixed here.What to scrutinise
AC-005.
htmlpageasset.sortOrderhas exactly this field shape, and its zero-padded_dotraw(0000000000000000054.000000000000000000) is what makes lexicographic sorting equal numeric sorting for page ordering. That document must stay byte-identical. A type-based rule instead of a value-based one would have broken page ordering on every installation — that trade-off is recorded under Resolved Decisions → C-1.🤖 Generated with Claude Code