### What problem does this PR solve?
Elasticsearch scans can return values that fail SQL predicates when
indexed values differ from the values Doris reads, or when a scan
switches all selected columns from docvalues to source. Parallel alias
scans can also bypass alias filters/routing, and supported mapping
shapes can fail during planning.
For example, projecting a normalized keyword forces a source read for
its siblings. A numeric alias then reads NULL although its docvalue is
5; a declared empty ARRAY remains nonnull although ES considers it
missing; a date-only string reads local midnight instead of the indexed
UTC midnight. Boolean empty strings and coerced integer strings must
also remain readable: `""` means false and `"1.2e3"` means 1200. Keeping
their indexed predicates while pruning SQL residuals returns incorrect
rows.
#### Execution flow
1. Select a populated mapping independently of alias response order.
Distinguish typeless flags/root objects from legacy type wrappers,
including a legacy type named `runtime`, and preserve array annotations.
Accept empty/configuration-only alias backings during safety analysis
while retaining standalone schema diagnostics.
2. Resolve the selected keyword query path and docvalue read path
separately on every backing, including filter-only columns. Fetch index
mapping defaults with the existing metadata request and honor per-field
overrides for every alias backing; inherited `ignore_malformed` can
remove indexed values while retaining source values. Ignore
transformations on unused sibling paths and unrelated source filters.
Missing/read-changing paths disable the affected docvalue entry. Root
`enabled:false` and disabled source (`enabled:false` or `mode:disabled`)
restrict every affected column.
3. Determine the final scan-wide read mode before building DSL. In
source mode, STRING/ARRAY predicates and predicates on rounded numbers,
aliases, scripted fields and default dates retain local residuals
without narrowing the ES query. Eligible pure docvalue projections
preserve their indexed-value behavior; scalar `_id` metadata remains
pushable.
4. Keep searchability separate from value equivalence: supported
doc-value-only fields remain queryable with `index=false`. Require
existence for SQL inequalities. Normalize retained equality negation,
double negation and Boolean negation inside the connector. Retained
ranges keep the original ES query complement with an existence guard: an
inverse existential range changes matching on arrays such as [1,2]. This
preserves NULL rejection when optimizer NOT simplification is disabled.
5. Pass residual tracking and the existing early-LIMIT restrictions
through the current scan contract. Native `esquery` remains remote-only:
native-only expressions and independent AND conjuncts work; an
indivisible Boolean expression requiring local native evaluation fails
explicitly during planning.
6. Use both readers' shared parser for supported source representations.
Boolean empty strings become false in scalar and ARRAY paths. Scalar
numeric/Boolean/date singleton source arrays read their element; empty
and null-only arrays read NULL, while STRING/JSONB and declared ARRAY
containers remain intact. JSONB singleton, empty and null-element arrays
retain their original JSON container. Declared ARRAYs preserve nullable
members; numeric empty strings become SQL NULL in scalar and ARRAY
paths, while Boolean empty strings remain false. Integer coercion
follows the ES family: byte/short/integer strings use double rounding
before truncation, while long strings retain exact decimal/exponent
handling and 64-bit precision. Bounds are checked before narrowing.
Declared integer ARRAY elements share the family-specific conversion.
Numeric floating tokens are converted directly with checked bounds,
preserving numeric INT64_MIN and the Java upper-endpoint conversion.
Floating strings and narrow integer strings accept Java hexadecimal
exponents, type suffixes and outer ASCII whitespace; FLOAT strings round
directly to float rather than through double. Epoch-millis source dates
accept zero, short/signed decimal strings and fractional numeric values,
with checked bounds and second-precision floor semantics. Four-digit
default-format years retain their calendar interpretation; pre-epoch
formatted docvalues use the same floor.
7. For a source read involving disabled-source backings, pass an
optional `read_full_source` flag through the existing ES property map.
BE requests `_source:true`: disabled hits have missing values, while
enabled alias backings retain their source. Ordinary source scans keep
field projection and pure docvalue scans keep docvalue requests.
8. Scan aliases once by their original name so ES applies alias filters
and search routing without duplicate shared-shard scans. Physical
indices retain shard-parallel scans.
#### Effects and tradeoffs
This change preserves source values, supported decoding and SQL
filtering across both readers and read modes. Source fallbacks can fetch
more candidate rows because unsafe predicates remain local. Mixed
aliases containing disabled-source backings can fetch additional source
fields to avoid ES rejecting projected-source requests. Alias scans use
one coordinating scroll to preserve alias semantics; physical-index
parallelism remains. No throughput improvement is claimed. There are no
new public SQL/catalog/session properties or SPI/Thrift/Protobuf
definitions; the optional internal flag uses the existing flat property
map.
#### Known limitation
Mixed-generation FE/BE execution during smooth upgrades is outside this
change's scope. Source plans depending on the updated decoder require
the updated BE for correct results; this PR does not add a
backend-generation gate.
### Release note
Fix Elasticsearch source/index value discrepancies, supported
Boolean/floating/integer source decoding, default epoch-millis date
decoding, disabled-source reads, legacy/configuration-only mapping
handling, retained negation NULL semantics and alias filter/routing
bypass. Preserve eligible doc-value-only queries and reject native-query
combinations requiring local evaluation during planning.
### Check List (For Author)
- Test:
- [x] ES connector reactor package: 163 reported tests, 162 passed, one
optional ES 9 compatibility class skipped; zero failures/errors. All
nine reactor modules passed FE Checkstyle.
- [x] Standard `build.sh --be` ASAN build and all 28
`EsScrollParserTest` unit tests passed. The combined parser/reader run
reported 47 tests: 46 passed and one optional environment-dependent live
smoke test skipped; the separate live regression run exercised both
readers. Clang-format 16, scoped clang-tidy and build hygiene passed for
all affected C++ files. The local BE ELF requires no GLIBC version above
2.10.
- [x] Reproduced the reported alias/script, ARRAY, date-only,
disabled-source, boolean, root-disabled, integer and retained-NOT
failures on the previous head with live ES 8.11.3. FE/BE unit tests also
demonstrated failures before the relevant fixes. Legacy ES6 type
compatibility is covered by mapping-response unit tests.
- [x] Seven live ES 8.11.3 suites passed 1,860 stored checks and 1,764
independent SQL comparisons, preserving 88 expected native planning
errors. All 1,780 prior stored checks remain unchanged.
- [x] `test_es_source_semantics`: 612 stored / 516 independent checks;
`test_es_numeric_source_semantics`: 264 / 264;
`test_es_mapping_boundaries`: 272 / 272;
`test_es_source_fallback_types`: 304 / 304;
`test_es_scalar_materialization`: 160 / 160;
`test_es_source_value_boundaries`: 168 / 168.
- [x] New source-boundary coverage reproduces all six review findings:
JSONB singleton/empty/null-element containers, numeric-double INT64_MIN
and upper endpoint, nullable declared ARRAYs, numeric empty strings,
Java numeric grammar, direct FLOAT rounding, and inherited
malformed/coercion settings with field overrides and mixed aliases. All
eight reader/docvalue/parallel combinations passed. New BE and FE
controls failed before their corresponding fixes.
- [x] `test_es_epoch_millis` adds 80 stored checks and 80 independent
comparisons across both readers, docvalue/parallel modes and UTC/+08:00.
It covers normalized-sibling source fallback, zero/short/fractional
epoch strings, numeric doubles, negative instants, NULL/empty/singleton
arrays, local predicates and pure-docvalue controls. All outputs were
generated by the repository runner and verified without generation; all
80 sections also match an independent date/predicate computation. New
unit coverage checks legacy/V2 date types, declared arrays,
calendar-year precedence and invalid numeric/civil-year bounds.
- [x] New scalar-materialization coverage includes floating/Boolean/date
singleton, empty and null-only arrays; narrow integer rounding
boundaries and NaN, 64-bit precision controls, declared integer ARRAYs
and retained range NOT on multi-value numeric docvalues. New coverage
spans both readers, docvalue modes and parallel modes, source fallback
plus pure-docvalue controls, disabled source indices/mixed aliases,
populated disabled roots, non-UTC date-only values and retained NOT with
optimizer simplification disabled. Session settings are restored. New
outputs were generated by the repository runner and verified separately
without generation; the final date control avoids dependence on the FE
JVM timezone.
- [x] Original FE package built with `build.sh --fe`; connector updates
packaged through the documented module reactor recipe and deployed with
the rebuilt BE for live validation.
- Behavior changed:
- [x] Yes: correctness fallbacks, supported mapping/decoding/query
handling and native-only planning restrictions above.
- Does this need documentation?
- [x] No new public SQL syntax or configuration.
Guide for test cases
General Case
-
Write "def" before variable names; otherwise, they will be global variables and may be affected by other cases running in parallel.
Problematic code:
ret = ***Correct code:
def ret = *** -
Avoid setting global session variables or modifying cluster configurations in cases, as it may affect other cases.
Problematic code:
sql """set global enable_pipeline_x_engine=true;"""Correct code:
sql """set enable_pipeline_x_engine=true;""" -
If it is necessary to set global variables or modify cluster configurations, specify the case to run in a nonConcurrent manner.
-
For cases involving time-related operations, it is best to use fixed time values instead of dynamic values like the
now()function to prevent cases from failing after some time.Problematic code:
sql """select count(*) from table where created < now();"""Correct code:
sql """select count(*) from table where created < '2023-11-13';""" -
After streamloading in a case, add a sync to ensure stability when executing in a multi-FE environment.
Problematic code:
streamLoad { ... } sql """select count(*) from table """Correct code:
streamLoad { ... } sql """sync""" sql """select count(*) from table """ -
For UDF cases, make sure to copy the corresponding JAR file to all BE machines.
-
Do not create the same table in different cases under the same directory to avoid conflicts.
-
Cases injected should be marked as nonConcurrent and ensured injection to be removed after running the case.
-
Docker case run in a docker cluster. The docker cluster is new created and independent, not contains history data, not affect other cluster.
Docker case can add/drop/start/stop/restart fe and be, and specify fe and be num.
Example will see demo_p0/docker_action.groovy
Read the annotation carefully in the example file.
Also read the doris-compose readme.
-
Do not use raw
Thread.startorThread.startDaemonto run SQL in a case. Use the frameworkthreadorextraThreadhelper and callfuture.get()to wait for completion, so exceptions are reported to the case that created the thread.Problematic code:
def t = Thread.start { sql """insert into tbl values (1)""" } t.join()Correct code:
def future = thread("insert-thread") { sql """insert into tbl values (1)""" } future.get()
Compatibility case
Refers to the resources or rules created on the initial cluster during FE testing or upgrade testing, which can still be used normally after the cluster restart or upgrade, such as permissions, UDF, etc.
These cases need to be split into two files, load.groovy and xxxx.groovy, placed in a folder, and tagged with the restart_fe group label, example.