[BUG] A field mapped as different types across indices returns changed or null values with HTTP 200
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 35/100
Direzione di ricerca
Reproduce the mixed_a/mixed_b case with the provided curl commands, then read OpenSearchDescribeIndexRequest.getFieldTypes, MergeRuleHelper, OpenSearchResponse.addParsedHitsToBuilder, and OpenSearchExprValueFactory.parse. Trace how the merged field type is applied to each hit and compare the result with _source. Done means the query no longer silently changes or drops valid values, with behavior matching the agreed mismatch policy.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Query Information
PPL Command/Query:
source=mixed_* | fields host, f
f is long in mixed_a and date in mixed_b, which is what a rollover mapping change leaves behind.
Expected Result: HTTP 200 with both values as stored, or HTTP 400 naming f and its type in each index. No numeric or date type holds both values.
Actual Result: HTTP 200, with one of the two values silently wrong. Which one depends on the merged type of f. When describe mixed_* reports f as timestamp, the long document's 5 comes back as 1970-01-01 00:00:00.005. That is 5 read as epoch milliseconds, a unit taken from mixed_b's date mapping, while mixed_a stores it as a plain number.
{
"schema": [
{
"name": "host",
"type": "string"
},
{
"name": "f",
"type": "timestamp"
}
],
"datarows": [
[
"a",
"1970-01-01 00:00:00.005"
],
[
"b",
"2026-01-01 00:00:00"
]
],
"total": 2,
"size": 2
}
When it reports bigint, the same query returns ["a", 5] and ["b", null], so the date is gone. A raw _search over mixed_* returns "f": 5 and "f": "2026-01-01T00:00:00Z" in _source, so the stored data is intact.
The same happens for many other pairs, with the wrong value in bold.
f in index A |
f in index B |
merged type of f |
A's document returns | B's document returns |
|---|---|---|---|---|
keyword "300" |
byte 1 |
tinyint |
44 | 1 |
integer 300 |
byte 1 |
tinyint |
44 | 1 |
long 5000000000 |
byte 1 |
tinyint |
0 | 1 |
long 5000000000 |
integer 3 |
int |
705032704 | 3 |
integer 40000 |
short 1 |
smallint |
-25536 | 1 |
keyword "300" |
date |
timestamp |
1970-01-01 00:00:00.3 | 2026-01-01 00:00:00 |
byte 1 |
date |
timestamp |
1970-01-01 00:00:00.001 | 2026-01-01 00:00:00 |
integer 3 |
date |
timestamp |
1970-01-01 00:00:00.003 | 2026-01-01 00:00:00 |
long 5 |
date |
timestamp |
1970-01-01 00:00:00.005 | 2026-01-01 00:00:00 |
byte 1 |
date |
tinyint |
1 | null |
integer 3 |
date |
int |
3 | null |
long 5 |
date |
bigint |
5 | null |
text "hello world" |
byte 1 |
tinyint |
null | 1 |
text "hello world" |
integer 3 |
int |
null | 3 |
text "hello world" |
long 5 |
bigint |
null | 5 |
text "hello world" |
date |
timestamp |
null | 2026-01-01 00:00:00 |
keyword "abc" |
long 5 |
bigint |
null | 5 |
double 1.5 |
long 5 |
bigint |
null | 5 |
float 2.5 |
integer 3 |
int |
null | 3 |
The date value is 2026-01-01T00:00:00Z throughout. The merged type is whichever index the mapping merge reads last, as #5752 explains. On the same node it changed when only the index names changed. Under the other merged type each pair returned both values without loss, as strings when that type is string. The exception is the number and date pairs, which are wrong under both. These pairs lost no value under any merged type seen:
textagainstkeyword, which always merges tostringkeyword"300"againstinteger,longordouble, where the string converts to the numberkeyword"true"againstboolean, and akeywordholding a valid address againstipbyte,integerandlongagainst each other while every value fits the narrower type
Dataset Information
Dataset/Schema Type
- OpenTelemetry (OTEL)
- Simple Schema for Observability (SS4O)
- Open Cybersecurity Schema Framework (OCSF)
- Custom (details below)
Index Mapping
PUT mixed_a { "mappings": { "properties": { "host": { "type": "keyword" }, "f": { "type": "long" } } } }
PUT mixed_b { "mappings": { "properties": { "host": { "type": "keyword" }, "f": { "type": "date" } } } }
Sample Data
POST mixed_a/_doc/1?refresh=true { "host": "a", "f": 5 }
POST mixed_b/_doc/1?refresh=true { "host": "b", "f": "2026-01-01T00:00:00Z" }
Each index alone returns its own value correctly.
Bug Description
Issue Summary
PPL gives a field one type for the whole index pattern and decodes every document's _source value as that type, including documents from indices that map the field differently.
OpenSearchDescribeIndexRequest.getFieldTypes merges the per-index mappings through MergeRuleHelper. Only three shapes get a rule of their own. They are text or match_only_text against keyword, text with a keyword subfield against text without one, and two object or two nested fields, which merge their subfields. Every other pair falls through to LatestRule. Its mergeInto is target.put(key, source), so the last index read wins. The method returns only the merged map. Its own comment at line 100 reads // TODO possible collision if two indices have fields with the same name and different mappings.
OpenSearchResponse.addParsedHitsToBuilder then passes each hit's hit.getSourceAsString() to OpenSearchExprValueFactory.construct, whose type map is the merged one. The Calcite engine and the v2 engine share this path. A value from the losing index goes wrong in one of three ways.
- Integers narrow without a range check.
OpenSearchJsonContent.byteValueat line 50 isreturn (byte) parseLongValue(value());, andintValueandshortValueat lines 35 and 45 cast the same way.parseLongValuealso accepts a numeric string, so"300"becomes the long 300 and then the byte 44. The same cast narrows on a single index too whenindex.mapping.ignore_malformedis on, where300in abytefield returns44. The #5618 catch in item 2 does not help, since the cast returns a value and never throws. - A value that does not parse becomes null.
OpenSearchExprValueFactory.parsewraps the per-type decode incatch (Exception e) { return ExprNullValue.of(); }at lines 237 to 241, with no log line. #5618 added that catch forignore_malformeddocuments."hello world","abc"or a date string under a number,1.5or2.5under an integer type, and"hello world"undertimestampall end there. - A number reads as epoch milliseconds under a date.
createOpenSearchDateTypeturns a JSON number intoInstant.ofEpochMilliat lines 361 to 372. It multiplies by 1000 first only when thedatemapping listsepoch_second. On this node, with"format": "strict_date_optional_time||epoch_second"onmixed_b, the5came back as1970-01-01 00:00:05. A string that matches no mapped format falls back at line 342 toDateFieldMapper.getDefaultDateTimeFormatter(). That formatter isstrict_date_optional_time||epoch_millis, so"300"becomes 300 ms after the epoch.
This is a concrete case of the mismatched-data half of #5610, which lists null on mismatch as one candidate policy. Here the value is valid in its own index, so a null hides real data, and the narrowing and epoch cases do not return null at all but invent a value. The node log records no warning or error for any of these requests.
Pushdown off and Calcite off return the same wrong value
With plugins.calcite.pushdown.enabled=false, and separately with plugins.calcite.enabled=false, source=mixed_* | fields host, f returned a body byte-identical to the one under Actual Result. The wrong value does not come from a pushed-down operation. The projection pushes down only as _source filtering, and the decode above runs on both engines.
Later commands inherit the wrong value, and stats narrows its own result
- A self
joinonfloses the rows whose key decoded to null.longagainstdateunderbigintandtextagainstlongunderbiginteach return 1 of 2 rows. #5825 notes the first case. - For
longagainstdateunderbigint,where isnull(f)returns no rows, because it pushes down asmust_not existsand the stored value exists.head 10 | where isnull(f), which filters in memory, returns the row thatfieldsshows as null. The same filter gives two answers depending on where it runs. stats max(f)computes over the indexed values, andObjectContentthen narrows the result to the merged type.long5000000000againstintegerunderintreturns 2147483647, andinteger40000againstshortundersmallintreturns -25536.longagainstdateunderbigintreturns 1767225600000, the date's epoch milliseconds, whilefieldsshows that same value as null.
Steps to Reproduce
curl -s -XPUT "localhost:9200/mixed_a" -H 'Content-Type: application/json' \
-d '{"mappings":{"properties":{"host":{"type":"keyword"},"f":{"type":"long"}}}}'
curl -s -XPUT "localhost:9200/mixed_b" -H 'Content-Type: application/json' \
-d '{"mappings":{"properties":{"host":{"type":"keyword"},"f":{"type":"date"}}}}'
curl -s -XPOST "localhost:9200/mixed_a/_doc/1?refresh=true" -H 'Content-Type: application/json' -d '{"host":"a","f":5}'
curl -s -XPOST "localhost:9200/mixed_b/_doc/1?refresh=true" -H 'Content-Type: application/json' -d '{"host":"b","f":"2026-01-01T00:00:00Z"}'
# 1. The merged type of f, bigint or timestamp depending on the node
curl -s -XPOST "localhost:9200/_plugins/_ppl" -H 'Content-Type: application/json' \
-d '{"query":"describe mixed_* | where COLUMN_NAME = '\''f'\'' | fields TYPE_NAME"}'
# 2. Returns HTTP 200 with one of the two values changed or null
curl -s -XPOST "localhost:9200/_plugins/_ppl" -H 'Content-Type: application/json' \
-d '{"query":"source=mixed_* | fields host, f"}' -w '\nHTTP %{http_code}\n'
# 3. The raw search returns both values as stored
curl -s -XPOST "localhost:9200/mixed_*/_search?filter_path=hits.hits._source" -H 'Content-Type: application/json' -d '{}'
This pair is wrong under either merged type, so it reproduces on any node.
Impact
In an index-pattern deployment, one rolled-over index that maps a field differently makes queries over the whole pattern return wrong values with HTTP 200 and no warning. Counts, filters, joins and charts built on the field are then wrong in ways a user cannot see from the response. Which value is wrong can change between nodes and after a restart, because the merged type does.
Environment Information
OpenSearch Version: main at 67aed0ee8, 3.10.0-SNAPSHOT, one node, with the defaults plugins.calcite.enabled=true, plugins.calcite.pushdown.enabled=true and plugins.calcite.fallback.allowed=false.
Additional Details:
- #5752 is the same
LatestRulemerge forobjectagainst a scalar, and explains why the merged type differs between nodes. It covers only theobjectpairs, so every pair above is outside it.
- Lingua principale
- Java
- Stelle
- 175
- Fork
- 232
- Merge medio
- 3g 3h
- PR unite (30g)
- 42
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di opensearch-project/sql
-
[Enhancement] Make the flat_object read depth cap configurable instead of a constantForse già presa @RyanL1997 l’ha presa 9 giorni fa. Apertaenhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
opensearch-project/sql#5842 ·
I maintainer di solito rispondono entro 1 giorno
-
[BUG] PromQL queries fail with InvalidTypeIdException when metric has a label named "type"Forse già presa @nagendramohan l’ha presa 71 giorni fa. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
opensearch-project/sql#5684 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
[DOC] Calcite settings documentation missing examplesForse già presa @AzazelSensei l’ha presa 29 giorni fa. Apertadocumentation PPL
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
opensearch-project/sql#4806 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[RFC] Widen numeric mapping conflicts across indices to one deterministic typeForse già presa @cnoramut l’ha presa oggi. Apertauntriaged
Difficoltà 4/5 3-5 giorni Idoneità per principianti 25/100
opensearch-project/sql#5901 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
bug PPL
Difficoltà 3/5 1-2 giorni Idoneità per principianti 66/100
opensearch-project/sql#5900 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di opensearch-project/sql
Issue simili
-
human-required sync-failed
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
Azure/osdu-spi-file#63 ·
I maintainer di solito rispondono entro 1 giorno
-
frontend quick win
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
CodeForPhilly/benefit-decision-toolkit#571 ·
I maintainer di solito rispondono entro 1 giorno
-
approved
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
checkstyle/checkstyle#22129 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Add Hardcharger JDBC driverForse già presa @prmoore77 l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno