Skip to content

Commit c2c97db

Browse files
authored
Fix boolean coercion from numeric values for wildcard index queries (opensearch-project#5269) (opensearch-project#5293)
When querying across indices with conflicting field mappings (boolean vs text), numeric values like 0 were not coerced to boolean, causing "node must be a boolean, found NUMBER" error. Added numeric handling to parseBooleanValue() consistent with ObjectContent.booleanValue(). Signed-off-by: Heng Qian <qianheng@amazon.com>
1 parent a72e462 commit c2c97db

File tree

4 files changed

+109
-0
lines changed

4 files changed

+109
-0
lines changed

integ-test/src/test/java/org/opensearch/sql/ppl/DataTypeIT.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,47 @@ public void testNumericFieldFromString() throws Exception {
145145
client().performRequest(deleteRequest);
146146
}
147147

148+
@Test
149+
public void testBooleanFieldFromNumberAcrossWildcardIndices() throws Exception {
150+
// Reproduce issue #5269: querying across indices where same field has conflicting types
151+
// (boolean vs text) and the text-typed index stores a numeric value like 0.
152+
String indexBool = "repro_bool_test_bb";
153+
String indexText = "repro_bool_test_aa";
154+
155+
try {
156+
// Create index with boolean mapping
157+
Request createBool = new Request("PUT", "/" + indexBool);
158+
createBool.setJsonEntity(
159+
"{\"mappings\":{\"properties\":{\"flag\":{\"type\":\"boolean\"},"
160+
+ "\"startTime\":{\"type\":\"date_nanos\"}}}}");
161+
client().performRequest(createBool);
162+
163+
// Create index with text mapping
164+
Request createText = new Request("PUT", "/" + indexText);
165+
createText.setJsonEntity(
166+
"{\"mappings\":{\"properties\":{\"flag\":{\"type\":\"text\"},"
167+
+ "\"startTime\":{\"type\":\"date_nanos\"}}}}");
168+
client().performRequest(createText);
169+
170+
// Insert boolean value into boolean-typed index
171+
Request insertBool = new Request("PUT", "/" + indexBool + "/_doc/1?refresh=true");
172+
insertBool.setJsonEntity("{\"startTime\":\"2026-03-25T20:25:00.000Z\",\"flag\":false}");
173+
client().performRequest(insertBool);
174+
175+
// Insert numeric value into text-typed index
176+
Request insertText = new Request("PUT", "/" + indexText + "/_doc/1?refresh=true");
177+
insertText.setJsonEntity("{\"startTime\":\"2026-03-24T20:25:00.000Z\",\"flag\":0}");
178+
client().performRequest(insertText);
179+
180+
// Query across both indices with wildcard — should not throw an error
181+
JSONObject result = executeQuery("source=repro_bool_test_* | fields flag");
182+
assertEquals(2, result.getJSONArray("datarows").length());
183+
} finally {
184+
client().performRequest(new Request("DELETE", "/" + indexBool));
185+
client().performRequest(new Request("DELETE", "/" + indexText));
186+
}
187+
}
188+
148189
@Test
149190
public void testBooleanFieldFromString() throws Exception {
150191
final int docId = 2;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
setup:
2+
- do:
3+
indices.create:
4+
index: issue5269_bool
5+
body:
6+
settings:
7+
number_of_shards: 1
8+
number_of_replicas: 0
9+
mappings:
10+
properties:
11+
flag:
12+
type: boolean
13+
startTime:
14+
type: date_nanos
15+
16+
- do:
17+
indices.create:
18+
index: issue5269_text
19+
body:
20+
settings:
21+
number_of_shards: 1
22+
number_of_replicas: 0
23+
mappings:
24+
properties:
25+
flag:
26+
type: text
27+
startTime:
28+
type: date_nanos
29+
30+
- do:
31+
bulk:
32+
refresh: true
33+
body:
34+
- '{"index": {"_index": "issue5269_bool", "_id": "1"}}'
35+
- '{"startTime": "2026-03-25T20:25:00.000Z", "flag": false}'
36+
- '{"index": {"_index": "issue5269_text", "_id": "1"}}'
37+
- '{"startTime": "2026-03-24T20:25:00.000Z", "flag": 0}'
38+
39+
---
40+
teardown:
41+
- do:
42+
indices.delete:
43+
index: issue5269_bool
44+
ignore_unavailable: true
45+
- do:
46+
indices.delete:
47+
index: issue5269_text
48+
ignore_unavailable: true
49+
50+
---
51+
"Issue 5269: PPL wildcard query across indices with boolean/text mapping conflict should not error":
52+
- skip:
53+
features:
54+
- headers
55+
- do:
56+
headers:
57+
Content-Type: 'application/json'
58+
ppl:
59+
body:
60+
query: source=issue5269_* | fields flag
61+
62+
- match: { total: 2 }
63+
- length: { datarows: 2 }

opensearch/src/main/java/org/opensearch/sql/opensearch/data/utils/OpenSearchJsonContent.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ private boolean parseBooleanValue(JsonNode node) {
212212
return node.booleanValue();
213213
} else if (node.isTextual()) {
214214
return Boolean.parseBoolean(node.textValue());
215+
} else if (node.isNumber()) {
216+
return node.intValue() != 0;
215217
} else {
216218
if (LOG.isDebugEnabled()) {
217219
LOG.debug("node '{}' must be a boolean", node);

opensearch/src/test/java/org/opensearch/sql/opensearch/data/value/OpenSearchExprValueFactoryTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,9 @@ public void constructIp() {
234234
public void constructBoolean() {
235235
assertAll(
236236
() -> assertEquals(booleanValue(true), tupleValue("{\"boolV\":true}").get("boolV")),
237+
() -> assertEquals(booleanValue(false), tupleValue("{\"boolV\":false}").get("boolV")),
238+
() -> assertEquals(booleanValue(true), tupleValue("{\"boolV\":1}").get("boolV")),
239+
() -> assertEquals(booleanValue(false), tupleValue("{\"boolV\":0}").get("boolV")),
237240
() -> assertEquals(booleanValue(true), constructFromObject("boolV", true)),
238241
() -> assertEquals(booleanValue(true), constructFromObject("boolV", "true")),
239242
() -> assertEquals(booleanValue(true), constructFromObject("boolV", 1)),

0 commit comments

Comments
 (0)