diff --git a/integ-test/src/test/java/org/opensearch/sql/api/UnifiedQueryOpenSearchIT.java b/integ-test/src/test/java/org/opensearch/sql/api/UnifiedQueryOpenSearchIT.java index df7557976f0..996d06c227a 100644 --- a/integ-test/src/test/java/org/opensearch/sql/api/UnifiedQueryOpenSearchIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/api/UnifiedQueryOpenSearchIT.java @@ -72,7 +72,8 @@ public void cleanUp() throws Exception { public void testSimplePPLQueryExecution() throws Exception { String pplQuery = String.format( - "source = opensearch.%s | fields firstname, age | where age > 30 | head 3", + "source = opensearch.%s | where age > 30 and account_number in (1, 6, 18) | fields" + + " firstname, age", TEST_INDEX_ACCOUNT); RelNode logicalPlan = planner.plan(pplQuery); diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/CalciteComplexPoolIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/CalciteComplexPoolIT.java index b419c8043d3..3be25127b7e 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/CalciteComplexPoolIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/CalciteComplexPoolIT.java @@ -46,7 +46,7 @@ public void testParseCommandDispatchesToComplexPool() throws IOException { executeQuery( String.format( "source=%s | parse address '(?\\\\d+) (?.*)'" - + " | fields number, street | head 1", + + " | sort account_number | fields number, street | head 1", TEST_INDEX_BANK)); verifyDataRows(result, rows("880", "Holmes Lane")); diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteArrayFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteArrayFunctionIT.java index 0f5b2bb5649..aa935ab6e56 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteArrayFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteArrayFunctionIT.java @@ -955,8 +955,8 @@ public void testMvmapWithOtherFieldReference() throws IOException { JSONObject actual = executeQuery( String.format( - "source=%s | eval arr = array(1, 2, 3), result = mvmap(arr, arr * age) | head 1 |" - + " fields age, result", + "source=%s | eval arr = array(1, 2, 3), result = mvmap(arr, arr * age) | sort" + + " account_number | head 1 | fields age, result", TEST_INDEX_BANK)); verifySchema(actual, schema("age", "int"), schema("result", "array")); diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteBinCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteBinCommandIT.java index a04a34a0b41..c6d247699c0 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteBinCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteBinCommandIT.java @@ -441,8 +441,8 @@ public void testBinTimestampSpan7Days() throws IOException { JSONObject result = executeQuery( String.format( - "source=%s | bin @timestamp span=7day | fields" - + " @timestamp, value | sort @timestamp | head 3", + "source=%s | eval original_timestamp = @timestamp | bin @timestamp span=7day |" + + " sort original_timestamp | head 3 | fields @timestamp, value", TEST_INDEX_TIME_DATA)); verifySchema(result, schema("@timestamp", null, "timestamp"), schema("value", null, "int")); verifyDataRows( @@ -457,8 +457,8 @@ public void testBinTimestampSpan6Days() throws IOException { JSONObject result = executeQuery( String.format( - "source=%s | bin @timestamp span=6day | fields" - + " @timestamp, value | sort @timestamp | head 3", + "source=%s | eval original_timestamp = @timestamp | bin @timestamp span=6day |" + + " sort original_timestamp | head 3 | fields @timestamp, value", TEST_INDEX_TIME_DATA)); verifySchema(result, schema("@timestamp", null, "timestamp"), schema("value", null, "int")); verifyDataRows( diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteFieldsCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteFieldsCommandIT.java index 445ae4e7439..350a255b9d4 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteFieldsCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteFieldsCommandIT.java @@ -573,10 +573,14 @@ public void testTableWithDuplicateWildcardMatches() throws IOException { public void testFieldsAndTableEquivalence() throws IOException { JSONObject fieldsResult = executeQuery( - String.format("source=%s | fields firstname, lastname | head 3", TEST_INDEX_ACCOUNT)); + String.format( + "source=%s | where account_number in (1, 6, 13) | fields firstname, lastname", + TEST_INDEX_ACCOUNT)); JSONObject tableResult = executeQuery( - String.format("source=%s | table firstname, lastname | head 3", TEST_INDEX_ACCOUNT)); + String.format( + "source=%s | where account_number in (1, 6, 13) | table firstname, lastname", + TEST_INDEX_ACCOUNT)); verifySchema(fieldsResult, schema("firstname", "string"), schema("lastname", "string")); verifySchema(tableResult, schema("firstname", "string"), schema("lastname", "string")); @@ -592,11 +596,13 @@ public void testSpaceDelimitedEquivalentToCommaDelimited() throws IOException { JSONObject commaResult = executeQuery( String.format( - "source=%s | fields firstname, lastname, age | head 3", TEST_INDEX_ACCOUNT)); + "source=%s | where account_number in (1, 6, 13) | fields firstname, lastname, age", + TEST_INDEX_ACCOUNT)); JSONObject spaceResult = executeQuery( String.format( - "source=%s | fields firstname lastname age | head 3", TEST_INDEX_ACCOUNT)); + "source=%s | where account_number in (1, 6, 13) | fields firstname lastname age", + TEST_INDEX_ACCOUNT)); verifySchema( commaResult, diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java index 383ae5e400f..6ded538e643 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMultisearchCommandIT.java @@ -129,9 +129,10 @@ public void testMultisearchWithFieldsProjection() throws IOException { JSONObject result = executeQuery( String.format( - "| multisearch [search source=%s | where gender = \\\"M\\\" | fields" - + " firstname, lastname, balance] [search source=%s | where gender = \\\"F\\\"" - + " | fields firstname, lastname, balance] | head 5", + "| multisearch [search source=%s | where gender = \\\"M\\\" and account_number in" + + " (1, 6, 18) | fields firstname, lastname, balance] [search source=%s | where" + + " gender = \\\"F\\\" and account_number in (13, 25) | fields firstname," + + " lastname, balance]", TEST_INDEX_ACCOUNT, TEST_INDEX_ACCOUNT)); verifySchema( @@ -145,8 +146,8 @@ public void testMultisearchWithFieldsProjection() throws IOException { rows("Amber", "Duke", 39225L), rows("Hattie", "Bond", 5686L), rows("Dale", "Adams", 4180L), - rows("Elinor", "Ratliff", 16418L), - rows("Mcgee", "Mooney", 18612L)); + rows("Nanette", "Bates", 32838L), + rows("Virginia", "Ayala", 40540L)); } @Test diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMvCombineCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMvCombineCommandIT.java index 1cf535bd71b..c09e23ff7f6 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMvCombineCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteMvCombineCommandIT.java @@ -68,7 +68,14 @@ public void testMvCombine_basicGroupCollapsesToOneRow() throws IOException { schema("tags", null, "string"), schema("packets_str", null, "array")); - verifyDataRows(result, rows("10.0.0.1", 100, "t1", List.of("10", "20", "30"))); + JSONArray row = result.getJSONArray("datarows").getJSONArray(0); + Assertions.assertEquals("10.0.0.1", row.getString(0)); + Assertions.assertEquals(100, row.getLong(1)); + Assertions.assertEquals("t1", row.getString(2)); + List packets = new ArrayList<>(); + row.getJSONArray(3).forEach(value -> packets.add(value.toString())); + Collections.sort(packets); + Assertions.assertEquals(List.of("10", "20", "30"), packets); } @Test diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEnhancedCoalesceIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEnhancedCoalesceIT.java index 35c99c92b42..99787070c3d 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEnhancedCoalesceIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLEnhancedCoalesceIT.java @@ -44,8 +44,8 @@ public void testCoalesceBasic() throws IOException { JSONObject actual = executeQuery( String.format( - "source=%s | eval result = coalesce(name, age, 0) | fields name, age, result |" - + " head 3", + "source=%s | eval result = coalesce(name, age, 0) | sort - age | fields name, age," + + " result | head 3", TEST_INDEX_STATE_COUNTRY_WITH_NULL)); verifySchema( @@ -61,7 +61,7 @@ public void testCoalesceWithMixedTypes() throws IOException { executeQuery( String.format( "source=%s | eval result = coalesce(name, age, 'fallback') |" - + " fields name, age, result | head 3", + + " sort - age | fields name, age, result | head 3", TEST_INDEX_STATE_COUNTRY_WITH_NULL)); verifySchema( @@ -170,8 +170,8 @@ public void testCoalesceWithAllNonExistentFields() throws IOException { JSONObject actual = executeQuery( String.format( - "source=%s | eval result = coalesce(field1, field2, field3) | fields name, result |" - + " head 1", + "source=%s | eval result = coalesce(field1, field2, field3) | sort - age | fields" + + " name, result | head 1", TEST_INDEX_STATE_COUNTRY_WITH_NULL)); // When every COALESCE operand is missing/null, the result has no known type (see #5175). diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLGraphLookupIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLGraphLookupIT.java index 62e872be945..69517b281db 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLGraphLookupIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLGraphLookupIT.java @@ -5,6 +5,8 @@ package org.opensearch.sql.calcite.remote; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_GRAPH_AIRPORTS; import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_GRAPH_EMPLOYEES; import static org.opensearch.sql.legacy.TestsConstants.TEST_INDEX_GRAPH_TRAVELERS; @@ -14,10 +16,16 @@ import static org.opensearch.sql.util.MatcherUtils.verifySchema; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.Test; import org.opensearch.sql.ppl.PPLIntegTestCase; @@ -63,6 +71,127 @@ private static Map mapOf(Object... keysAndValues) { return map; } + /** + * Row matcher for graphLookup results whose collected arrays come back in a shard-dependent + * order. + * + *

graphLookup gathers traversal results into an array, and the order of elements within that + * array is not defined across shards. A multi-shard run therefore returns the same set of nodes + * (same values, same {@code depth}/{@code numConnections}, same cardinality) as a single-shard + * run, only in a different order. Unlike {@link + * org.opensearch.sql.util.MatcherUtils#rows(Object...)}, which compares row cells with + * order-sensitive {@link JSONArray#similar}, this matcher relaxes ordering only within nested + * arrays and objects. The top-level row cells are still compared positionally: cell {@code + * i} of the actual row must match cell {@code i} of the expected row. This preserves column + * identity, so a swap of two same-typed top-level columns is still rejected, while collected + * array order may vary across shards. It is scoped to this test class so the relaxed comparison + * never leaks into other suites. + */ + private static TypeSafeMatcher rowsUnordered(Object... expectedObjects) { + return new TypeSafeMatcher<>() { + @Override + protected boolean matchesSafely(JSONArray array) { + JSONArray expected = new JSONArray(expectedObjects); + if (array.length() != expected.length()) { + return false; + } + // Compare top-level cells positionally so column identity is preserved; only descend into + // nested arrays/objects with order-insensitive comparison. + for (int i = 0; i < expected.length(); i++) { + if (!jsonEqualsIgnoringOrder(array.get(i), expected.get(i))) { + return false; + } + } + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText(new JSONArray(expectedObjects).toString()); + } + }; + } + + /** Recursively compares two JSON values, treating every array as an unordered multiset. */ + private static boolean jsonEqualsIgnoringOrder(Object a, Object b) { + if (a instanceof JSONArray && b instanceof JSONArray) { + return jsonArrayEqualsIgnoringNestedOrder((JSONArray) a, (JSONArray) b); + } + if (a instanceof JSONObject && b instanceof JSONObject) { + JSONObject ao = (JSONObject) a; + JSONObject bo = (JSONObject) b; + if (ao.keySet().size() != bo.keySet().size()) { + return false; + } + for (String key : ao.keySet()) { + if (!bo.has(key) || !jsonEqualsIgnoringOrder(ao.get(key), bo.get(key))) { + return false; + } + } + return true; + } + return scalarEquals(a, b); + } + + private static boolean jsonArrayEqualsIgnoringNestedOrder(JSONArray actual, JSONArray expected) { + if (actual.length() != expected.length()) { + return false; + } + List remaining = new ArrayList<>(); + for (int i = 0; i < expected.length(); i++) { + remaining.add(expected.get(i)); + } + for (int i = 0; i < actual.length(); i++) { + Object actualElement = actual.get(i); + boolean matched = false; + for (Iterator it = remaining.iterator(); it.hasNext(); ) { + if (jsonEqualsIgnoringOrder(actualElement, it.next())) { + it.remove(); + matched = true; + break; + } + } + if (!matched) { + return false; + } + } + return true; + } + + private static boolean scalarEquals(Object a, Object b) { + boolean aNull = a == null || a == JSONObject.NULL; + boolean bNull = b == null || b == JSONObject.NULL; + if (aNull || bNull) { + return aNull && bNull; + } + if (a instanceof Number && b instanceof Number) { + return ((Number) a).doubleValue() == ((Number) b).doubleValue(); + } + return a.toString().equals(b.toString()); + } + + /** + * Regression test for {@link #rowsUnordered(Object...)}. Proves the matcher relaxes ordering only + * inside nested arrays/objects while keeping top-level column positions and cardinality + * significant. This runs without a cluster so it always executes as part of the suite. + */ + @Test + public void testRowsUnorderedMatcherPreservesColumnPositions() { + // 1. A shard-dependent reorder inside a nested collected array still matches. + Matcher matcher = rowsUnordered("Jeff", List.of("BOS", "JFK", "LAX")); + JSONArray nestedReordered = new JSONArray(List.of("Jeff", List.of("LAX", "BOS", "JFK"))); + assertTrue("nested array reorder should match", matcher.matches(nestedReordered)); + + // 2. A swap of two same-typed top-level columns must NOT match (column identity preserved). + Matcher swapMatcher = rowsUnordered("BOS", "JFK"); + JSONArray swapped = new JSONArray(List.of("JFK", "BOS")); + assertFalse("same-type top-level column swap must not match", swapMatcher.matches(swapped)); + + // 3. A missing value inside the nested array (different cardinality) must NOT match. + JSONArray missingNested = new JSONArray(List.of("Jeff", List.of("BOS", "JFK"))); + assertFalse("missing nested element must not match", matcher.matches(missingNested)); + } + // ==================== Employee Hierarchy Tests ==================== /** Test 1: Basic employee hierarchy traversal. Find all managers in the reporting chain. */ @@ -398,9 +527,11 @@ public void testTravelerReachableAirportsWithMaxDepth() throws IOException { schema("name", "string"), schema("nearestAirport", "string"), schema("reachableAirports", "array")); + // graphLookup collects reachable airports into an array whose element order is shard-dependent; + // compare values/connects/cardinality while ignoring nested array order. verifyDataRows( result, - rows( + rowsUnordered( "Jeff", "BOS", List.of( @@ -708,9 +839,11 @@ public void testBatchModeEmployeeHierarchy() throws IOException { TEST_INDEX_GRAPH_EMPLOYEES, TEST_INDEX_GRAPH_EMPLOYEES)); verifySchema(result, schema("reportsTo", "array"), schema("reportingHierarchy", "array")); + // Batch mode returns a single row; the source-rows array and the hierarchy array both come + // back in a shard-dependent order, so compare them as unordered multisets. verifyDataRows( result, - rows( + rowsUnordered( List.of( Map.of("name", "Dev", "reportsTo", "Eliot", "id", 1), Map.of("name", "Asya", "reportsTo", "Ron", "id", 5)), @@ -746,7 +879,7 @@ public void testBatchModeTravelersAirports() throws IOException { // - lookupResults: airports reachable from JFK and BOS within maxDepth=1 verifyDataRows( result, - rows( + rowsUnordered( List.of( Map.of("name", "Dev", "nearestAirport", "JFK"), Map.of("name", "Eliot", "nearestAirport", "JFK"), @@ -850,7 +983,7 @@ public void testTopLevelGraphLookupLiteralList() throws IOException { // because Ron.reportsTo=Andrew is in visited set verifyDataRows( result, - rows( + rowsUnordered( (Object) List.of( Map.of("name", "Eliot", "reportsTo", "Ron", "id", 2), diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLJoinIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLJoinIT.java index cdefe15155c..b599a4ebdcc 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLJoinIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalcitePPLJoinIT.java @@ -15,6 +15,7 @@ import static org.opensearch.sql.util.MatcherUtils.schema; import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; import static org.opensearch.sql.util.MatcherUtils.verifyDataRowsInOrder; +import static org.opensearch.sql.util.MatcherUtils.verifyDataRowsSome; import static org.opensearch.sql.util.MatcherUtils.verifyNumOfRows; import static org.opensearch.sql.util.MatcherUtils.verifySchema; @@ -898,13 +899,20 @@ public void testJoinWithFieldListMaxEqualsOne() throws IOException { String.format( "source=%s | join type=inner max=1 name,year,month %s | fields name, country", TEST_INDEX_STATE_COUNTRY, TEST_INDEX_OCCUPATION)); - verifyDataRows( + // max=1 keeps a single OCCUPATION match per left row, and the co-named country column resolves + // to the OCCUPATION (right) side (see Jake -> England). David has two OCCUPATION rows that are + // identical on the join field list (name=David, year=2023, month=4) and differ only in the + // projected country (USA from the Doctor row, Canada from the Unemployed row), so which one + // survives max=1 is undefined and cannot be disambiguated by any ordering. Assert the four + // deterministic rows and that max=1 collapses David's two matches to a single row (total of 5, + // not 6); do not pin David's undefined country. + verifyNumOfRows(actual2, 5); + verifyDataRowsSome( actual2, rows("Jake", "England"), rows("Jane", "Canada"), rows("John", "Canada"), - rows("Hello", "USA"), - rows("David", "USA")); + rows("Hello", "USA")); } @Test @@ -946,13 +954,20 @@ public void testJoinWhenLegacyNotPreferred() throws IOException { } catch (IOException e) { fail(); } - verifyDataRows( + // max=1 keeps a single OCCUPATION match per left row, and the co-named country column + // resolves to the OCCUPATION (right) side. David has two OCCUPATION rows identical on the + // join field list (name=David, year=2023, month=4) that differ only in the projected + // country (USA/Canada), so which one survives max=1 is undefined and cannot be + // disambiguated by any ordering. Assert the four deterministic rows and that max=1 + // collapses David's two matches to a single row (total of 5, not 6); do not pin David's + // undefined country. + verifyNumOfRows(actual2, 5); + verifyDataRowsSome( actual2, rows("Jake", "England"), rows("Jane", "Canada"), rows("John", "Canada"), - rows("Hello", "USA"), - rows("David", "USA")); + rows("Hello", "USA")); }); } diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteTransposeCommandIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteTransposeCommandIT.java index 676cf162b03..8302e8d6599 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteTransposeCommandIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/remote/CalciteTransposeCommandIT.java @@ -18,6 +18,13 @@ public class CalciteTransposeCommandIT extends PPLIntegTestCase { + private static final String ACCOUNT_STREAM = + "makeresults format=csv data='firstname:string,age:int,balance:long\\n" + + "Amber,32,39225\\nHattie,36,5686\\nNanette,28,32838\\nDale,33,4180\\n" + + "Elinor,36,16418\\nVirginia,39,40540\\nDillard,34,48086\\nMcgee,39,18612\\n" + + "Aurelia,37,34487\\nFulton,23,29104\\nBurton,31,14097\\nJosie,32,14992\\n" + + "Hughes,30,6077\\nHall,25,44214\\nDeidre,33,38172'"; + @Override public void init() throws Exception { super.init(); @@ -33,11 +40,11 @@ public void init() throws Exception { */ @Test public void testTranspose() throws IOException { + // `head` selects rows by input encounter order, which is not stable across shards. Use the + // deterministic ACCOUNT_STREAM for exact head/transpose examples; other tests in this class + // retain the real index path for field-name collisions and aggregate inputs. var result = - executeQuery( - String.format( - "source=%s | head 5 | fields firstname, age, balance | transpose", - TEST_INDEX_ACCOUNT)); + executeQuery(ACCOUNT_STREAM + " | head 5 | fields firstname, age, balance | transpose"); // Verify that we get original rows plus totals row verifySchema( @@ -63,10 +70,7 @@ public void testTranspose() throws IOException { @Test public void testTransposeLimit() throws IOException { var result = - executeQuery( - String.format( - "source=%s | head 10 | fields firstname , age, balance | transpose 14", - TEST_INDEX_ACCOUNT)); + executeQuery(ACCOUNT_STREAM + " | head 10 | fields firstname, age, balance | transpose 14"); // Verify that we get original rows plus totals row verifySchema( @@ -119,10 +123,7 @@ public void testTransposeLimit() throws IOException { @Test public void testTransposeLowerLimit() throws IOException { var result = - executeQuery( - String.format( - "source=%s | head 15 | fields firstname , age, balance | transpose 5", - TEST_INDEX_ACCOUNT)); + executeQuery(ACCOUNT_STREAM + " | head 15 | fields firstname, age, balance | transpose 5"); // Verify that we get original rows plus totals row verifySchema( @@ -192,10 +193,9 @@ public void testTransposeWithValueFieldNameCollision() throws IOException { public void testTransposeColumnName() throws IOException { var result = executeQuery( - String.format( - "source=%s | head 5 | fields firstname, age, balance | transpose 5" - + " column_name='column_names'", - TEST_INDEX_ACCOUNT)); + ACCOUNT_STREAM + + " | head 5 | fields firstname, age, balance | transpose 5" + + " column_name='column_names'"); // Verify that we get original rows plus totals row verifySchema( diff --git a/integ-test/src/test/java/org/opensearch/sql/calcite/tpch/CalcitePPLTpchIT.java b/integ-test/src/test/java/org/opensearch/sql/calcite/tpch/CalcitePPLTpchIT.java index 0642b8f5651..4bed4cad729 100644 --- a/integ-test/src/test/java/org/opensearch/sql/calcite/tpch/CalcitePPLTpchIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/calcite/tpch/CalcitePPLTpchIT.java @@ -170,7 +170,7 @@ public void testQ6() throws IOException { String ppl = sanitize(loadFromFile("tpch/queries/q6.ppl")); JSONObject actual = executeQuery(ppl); verifySchemaInOrder(actual, schema("revenue", "double")); - verifyDataRows(actual, rows(77949.9186)); + verifyDataRows(actual, closeTo(77949.9186)); } @Test @@ -224,9 +224,13 @@ public void testQ10() throws IOException { schema("c_comment", "string")); verifyNumOfRows(actual, 20); actual = executeQuery(ppl + "| head 1"); + // `revenue` is a floating-point SUM whose accumulation order depends on how documents are + // partitioned across shards, so a multi-shard run yields the same value up to a tiny rounding + // difference (282635.1719 vs 282635.17189999996). Use closeTo to compare numerics within + // tolerance while still asserting the non-numeric columns exactly. verifyDataRows( actual, - rows( + closeTo( 121, "Customer#000000121", 282635.17189999996, diff --git a/integ-test/src/test/java/org/opensearch/sql/legacy/CursorIT.java b/integ-test/src/test/java/org/opensearch/sql/legacy/CursorIT.java index 0b0a510cb3d..dc4f87ddd2a 100644 --- a/integ-test/src/test/java/org/opensearch/sql/legacy/CursorIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/legacy/CursorIT.java @@ -499,7 +499,20 @@ public void verifySchema(JSONArray schemaOne, JSONArray schemaTwo) { } public void verifyDataRows(JSONArray dataRowsOne, JSONArray dataRowsTwo) { - assertTrue(dataRowsOne.similar(dataRowsTwo)); + // The paginated and non-paginated APIs must return the same complete set of rows, but none of + // these queries impose a total ordering: three have no ORDER BY at all, and the ORDER BY + // variants sort on a non-unique column (balance), so the order among equal-key rows is not + // contractual. On a multi-shard index the scroll (paginated) and search (non-paginated) paths + // materialize the same rows in different orders, so JSONArray.similar()'s positional comparison + // fails even though the row sets are equivalent. Compare as multisets to assert complete-row + // equivalence (full coverage and totals) without depending on an order that is not contractual. + List rowsOne = new ArrayList<>(); + dataRowsOne.iterator().forEachRemaining(o -> rowsOne.add(o.toString())); + List rowsTwo = new ArrayList<>(); + dataRowsTwo.iterator().forEachRemaining(o -> rowsTwo.add(o.toString())); + rowsOne.sort(null); + rowsTwo.sort(null); + assertEquals(rowsOne, rowsTwo); } public String executeFetchAsStringQuery(String query, String fetchSize, String requestType) diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/GeoPointFormatsIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/GeoPointFormatsIT.java index 3f2c6ccbb62..a88f08e30b3 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/GeoPointFormatsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/GeoPointFormatsIT.java @@ -34,13 +34,18 @@ public void testReadingGeopoints() throws IOException { executeQuery( String.format("search source=%s | head 5 | fields point ", TEST_INDEX_GEOPOINT)); verifySchema(result, schema("point", null, "geo_point")); - verifyDataRows( - result, - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71))); + // geo_point values are stored with Lucene's lossy 32-bit lat/lon encoding, so the decoded + // coordinates differ from the source (40.71, 74) by a sub-meter epsilon (e.g. 40.70999996736646 + // / 73.99999994784594). Assert each returned point within tolerance instead of exact equality. + // The five source documents all describe the same point in different formats, so any five rows + // selected by `head` satisfy this regardless of shard scan order. + JSONArray dataRows = result.getJSONArray("datarows"); + assertEquals(5, dataRows.length()); + for (int i = 0; i < dataRows.length(); i++) { + JSONObject point = ((JSONArray) dataRows.get(i)).getJSONObject(0); + assertEquals(40.71, point.getDouble("lat"), GeopointFormatsIT.TOLERANCE); + assertEquals(74, point.getDouble("lon"), GeopointFormatsIT.TOLERANCE); + } } @Test diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/MatchPhrasePrefixIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/MatchPhrasePrefixIT.java index 4023cc92d79..200e8fa2d70 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/MatchPhrasePrefixIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/MatchPhrasePrefixIT.java @@ -89,7 +89,7 @@ public void zero_term_query_all() throws IOException { // ORDER BY ... LIMIT helps make the test understandable. String query = "source = %s| WHERE match_phrase_prefix(Title, 'in to', analyzer=english," - + " zero_terms_query='ALL') | head 1 | fields Title"; + + " zero_terms_query='ALL') | sort Id | head 1 | fields Title"; JSONObject result = executeQuery(String.format(query, TEST_INDEX_BEER)); verifyDataRows(result, rows("How do you mull beer?")); } diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/MathematicalFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/MathematicalFunctionIT.java index 0a81ed92ad6..52c30ac5bf5 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/MathematicalFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/MathematicalFunctionIT.java @@ -736,7 +736,8 @@ public void testEvalComplexExpression() throws IOException { JSONObject result = executeQuery( String.format( - "source=%s | eval f = sum(age, 5) + avg(10, 20) | fields f | head 5", + "source=%s | sort account_number | eval f = sum(age, 5) + avg(10, 20) | fields f |" + + " head 5", TEST_INDEX_BANK)); verifySchema(result, schema("f", null, "double")); // sum(age, 5) + avg(10, 20) = (age + 5) + 15.0 @@ -760,7 +761,8 @@ public void testEvalSumWithMultipleFields() throws IOException { JSONObject result = executeQuery( String.format( - "source=%s | eval f = sum(age, age, 10) | fields f | head 5", TEST_INDEX_BANK)); + "source=%s | sort account_number | eval f = sum(age, age, 10) | fields f | head 5", + TEST_INDEX_BANK)); if (isCalciteEnabled()) { verifySchema(result, schema("f", null, "bigint")); } else { @@ -775,7 +777,8 @@ public void testEvalAvgWithExpression() throws IOException { JSONObject result = executeQuery( String.format( - "source=%s | eval f = avg(age * 2, 10) | fields f | head 5", TEST_INDEX_BANK)); + "source=%s | sort account_number | eval f = avg(age * 2, 10) | fields f | head 5", + TEST_INDEX_BANK)); verifySchema(result, schema("f", null, "double")); // avg(age * 2, 10) = (age * 2 + 10) / 2 = age + 5 verifyDataRows(result, rows(37.0), rows(41.0), rows(33.0), rows(38.0), rows(41.0)); diff --git a/integ-test/src/test/java/org/opensearch/sql/ppl/SettingsIT.java b/integ-test/src/test/java/org/opensearch/sql/ppl/SettingsIT.java index 16e1e1a532b..90dd4c93a6e 100644 --- a/integ-test/src/test/java/org/opensearch/sql/ppl/SettingsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/ppl/SettingsIT.java @@ -25,13 +25,19 @@ public void init() throws Exception { public void testQuerySizeLimit() throws IOException { // Default setting, fetch 200 rows from query JSONObject result = - executeQuery(String.format("search source=%s age>35 | fields firstname", TEST_INDEX_BANK)); + executeQuery( + String.format( + "search source=%s age>35 | sort account_number | fields firstname", + TEST_INDEX_BANK)); verifyDataRows(result, rows("Hattie"), rows("Elinor"), rows("Virginia")); // Fetch 1 rows from query setQuerySizeLimit(1); result = - executeQuery(String.format("search source=%s age>35 | fields firstname", TEST_INDEX_BANK)); + executeQuery( + String.format( + "search source=%s age>35 | sort account_number | fields firstname", + TEST_INDEX_BANK)); verifyDataRows(result, rows("Hattie")); } @@ -41,7 +47,8 @@ public void testQuerySizeLimit_NoPushdown() throws IOException { JSONObject result = executeQuery( String.format( - "search source=%s | eval a = 1 | where age>35 | fields firstname", + "search source=%s | eval a = 1 | where age>35 | sort account_number | fields" + + " firstname", TEST_INDEX_BANK)); verifyDataRows(result, rows("Hattie"), rows("Elinor"), rows("Virginia")); @@ -50,7 +57,8 @@ public void testQuerySizeLimit_NoPushdown() throws IOException { result = executeQuery( String.format( - "search source=%s | eval a = 1 | where age>35 | fields firstname", + "search source=%s | eval a = 1 | where age>35 | sort account_number | fields" + + " firstname", TEST_INDEX_BANK)); verifyDataRows(result, rows("Hattie"), rows("Elinor")); @@ -59,7 +67,8 @@ public void testQuerySizeLimit_NoPushdown() throws IOException { result = executeQuery( String.format( - "search source=%s | eval a = 1 | where age>35 | fields firstname", + "search source=%s | eval a = 1 | where age>35 | sort account_number | fields" + + " firstname", TEST_INDEX_BANK)); verifyDataRows(result, rows("Hattie")); } diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/ConditionalIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/ConditionalIT.java index 88c1bd54ce0..b79e04bc5f4 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/ConditionalIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/ConditionalIT.java @@ -119,7 +119,7 @@ public void nullifWithNotNullInputTestOne() { "SELECT NULLIF(firstname, 'Amber JOHnny') as testnullif " + "FROM " + TEST_INDEX_BANK_WITH_NULL_VALUES - + " limit 2 ", + + " ORDER BY account_number limit 2 ", "jdbc")); verifySchema(response, schema("NULLIF(firstname, 'Amber JOHnny')", "testnullif", "keyword")); verifyDataRows(response, rows(LITERAL_NULL.value()), rows("Hattie")); diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java index 2435256fcb5..0989481fc6b 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/DateTimeFunctionIT.java @@ -506,8 +506,8 @@ public void testExtractWithDatetime() throws IOException { JSONObject datetimeResult = executeQuery( String.format( - "SELECT extract(DAY_SECOND FROM timestamp(cast(datetime0 AS STRING))) FROM %s LIMIT" - + " 1", + "SELECT extract(DAY_SECOND FROM timestamp(cast(datetime0 AS STRING))) FROM %s ORDER" + + " BY `key` LIMIT 1", TEST_INDEX_CALCS)); verifyDataRows(datetimeResult, rows(9101735)); } @@ -517,7 +517,8 @@ public void testExtractWithTime() throws IOException { JSONObject timeResult = executeQuery( String.format( - "SELECT extract(HOUR_SECOND FROM time0) FROM %s LIMIT 1", TEST_INDEX_CALCS)); + "SELECT extract(HOUR_SECOND FROM time0) FROM %s ORDER BY `key` LIMIT 1", + TEST_INDEX_CALCS)); verifyDataRows(timeResult, rows(210732)); } @@ -526,7 +527,8 @@ public void testExtractWithDate() throws IOException { JSONObject dateResult = executeQuery( String.format( - "SELECT extract(YEAR_MONTH FROM date0) FROM %s LIMIT 1", TEST_INDEX_CALCS)); + "SELECT extract(YEAR_MONTH FROM date0) FROM %s ORDER BY `key` LIMIT 1", + TEST_INDEX_CALCS)); verifyDataRows(dateResult, rows(200404)); } @@ -579,15 +581,21 @@ public void testHourFunctionAliasesReturnTheSameResults() throws IOException { @Test public void testLastDay() throws IOException { JSONObject result = - executeQuery(String.format("SELECT last_day(date0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + executeQuery( + String.format( + "SELECT last_day(date0) FROM %s ORDER BY `key` LIMIT 3", TEST_INDEX_CALCS)); verifyDataRows(result, rows("2004-04-30"), rows("1972-07-31"), rows("1975-11-30")); result = - executeQuery(String.format("SELECT last_day(date0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + executeQuery( + String.format( + "SELECT last_day(date0) FROM %s ORDER BY `key` LIMIT 3", TEST_INDEX_CALCS)); verifyDataRows(result, rows("2004-04-30"), rows("1972-07-31"), rows("1975-11-30")); result = - executeQuery(String.format("SELECT last_day(date0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + executeQuery( + String.format( + "SELECT last_day(date0) FROM %s ORDER BY `key` LIMIT 3", TEST_INDEX_CALCS)); verifyDataRows(result, rows("2004-04-30"), rows("1972-07-31"), rows("1975-11-30")); } @@ -822,7 +830,10 @@ public void testQuarter() throws IOException { @Test public void testSecToTime() throws IOException { JSONObject result = - executeQuery(String.format("SELECT sec_to_time(balance) FROM %s LIMIT 3", TEST_INDEX_BANK)); + executeQuery( + String.format( + "SELECT sec_to_time(balance) FROM %s ORDER BY account_number LIMIT 3", + TEST_INDEX_BANK)); verifyDataRows(result, rows("10:53:45"), rows("01:34:46"), rows("09:07:18")); } @@ -904,7 +915,7 @@ public void testStrToDate() throws IOException { executeQuery( String.format( "SELECT str_to_date(CAST(birthdate AS STRING)," - + " '%%Y-%%m-%%d %%h:%%i:%%s') FROM %s LIMIT 2", + + " '%%Y-%%m-%%d %%h:%%i:%%s') FROM %s ORDER BY account_number LIMIT 2", TEST_INDEX_BANK)); verifyDataRows(result, rows("2017-10-23 00:00:00"), rows("2017-11-20 00:00:00")); @@ -912,7 +923,8 @@ public void testStrToDate() throws IOException { result = executeQuery( String.format( - "SELECT str_to_date(CAST(birthdate AS STRING)," + " '%%Y %%s') FROM %s LIMIT 2", + "SELECT str_to_date(CAST(birthdate AS STRING)," + + " '%%Y %%s') FROM %s ORDER BY account_number LIMIT 2", TEST_INDEX_BANK)); verifyDataRows(result, rows((Object) null), rows((Object) null)); @@ -920,7 +932,8 @@ public void testStrToDate() throws IOException { result = executeQuery( String.format( - "SELECT str_to_date(firstname," + " '%%Y-%%m-%%d %%h:%%i:%%s') FROM %s LIMIT 2", + "SELECT str_to_date(firstname," + + " '%%Y-%%m-%%d %%h:%%i:%%s') FROM %s ORDER BY account_number LIMIT 2", TEST_INDEX_BANK)); verifyDataRows(result, rows((Object) null), rows((Object) null)); @@ -997,7 +1010,9 @@ public void testSubDateWithInterval() throws IOException { public void testTimstampadd() throws IOException { JSONObject result = executeQuery( - String.format("SELECT timestampadd(WEEK, 2, time0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + String.format( + "SELECT timestampadd(WEEK, 2, time0) FROM %s ORDER BY `key` LIMIT 3", + TEST_INDEX_CALCS)); verifyDataRows( result, @@ -1011,7 +1026,8 @@ public void testTimstampdiff() throws IOException { JSONObject result = executeQuery( String.format( - "SELECT timestampdiff(DAY, time0, datetime0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + "SELECT timestampdiff(DAY, time0, datetime0) FROM %s ORDER BY `key` LIMIT 3", + TEST_INDEX_CALCS)); verifyDataRows(result, rows(38176), rows(38191), rows(38198)); } @@ -1041,19 +1057,23 @@ public void testToDays() throws IOException { @Test public void testToSeconds() throws IOException { JSONObject result = - executeQuery(String.format("select to_seconds(date0) FROM %s LIMIT 2", TEST_INDEX_CALCS)); + executeQuery( + String.format( + "select to_seconds(date0) FROM %s ORDER BY `key` LIMIT 2", TEST_INDEX_CALCS)); verifyDataRows(result, rows(63249206400L), rows(62246275200L)); result = executeQuery( String.format( - "SELECT to_seconds(timestamp(cast(datetime0 AS string))) FROM %s LIMIT 2", + "SELECT to_seconds(timestamp(cast(datetime0 AS string))) FROM %s ORDER BY `key`" + + " LIMIT 2", TEST_INDEX_CALCS)); verifyDataRows(result, rows(63256587455L), rows(63258064234L)); result = executeQuery( - String.format("select to_seconds(datetime0) FROM %s LIMIT 2", TEST_INDEX_CALCS)); + String.format( + "select to_seconds(datetime0) FROM %s ORDER BY `key` LIMIT 2", TEST_INDEX_CALCS)); verifyDataRows(result, rows(63256587455L), rows(63258064234L)); } @@ -1095,7 +1115,9 @@ public void testWeek() throws IOException { @Test public void testWeekday() throws IOException { JSONObject result = - executeQuery(String.format("SELECT weekday(date0) FROM %s LIMIT 3", TEST_INDEX_CALCS)); + executeQuery( + String.format( + "SELECT weekday(date0) FROM %s ORDER BY `key` LIMIT 3", TEST_INDEX_CALCS)); verifyDataRows(result, rows(3), rows(1), rows(2)); } @@ -1154,7 +1176,8 @@ public void testYearweek() throws IOException { JSONObject result = executeQuery( String.format( - "SELECT yearweek(time0), yearweek(time0, 4) FROM %s LIMIT 2", TEST_INDEX_CALCS)); + "SELECT yearweek(time0), yearweek(time0, 4) FROM %s ORDER BY `key` LIMIT 2", + TEST_INDEX_CALCS)); verifyDataRows(result, rows(189952, 189952), rows(189953, 190001)); } diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/GeopointFormatsIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/GeopointFormatsIT.java index fc79618fc07..1659fe2dbed 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/GeopointFormatsIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/GeopointFormatsIT.java @@ -6,13 +6,10 @@ package org.opensearch.sql.sql; import static org.opensearch.sql.util.Capability.GEOPOINT_TYPE; -import static org.opensearch.sql.util.MatcherUtils.rows; import static org.opensearch.sql.util.MatcherUtils.schema; -import static org.opensearch.sql.util.MatcherUtils.verifyDataRows; import static org.opensearch.sql.util.MatcherUtils.verifySchema; import java.io.IOException; -import java.util.Map; import org.apache.commons.lang3.tuple.Pair; import org.json.JSONArray; import org.json.JSONObject; @@ -33,13 +30,18 @@ public void testReadingGeopoints() throws IOException { String query = String.format("SELECT point FROM %s LIMIT 5", Index.GEOPOINTS.getName()); JSONObject result = executeJdbcRequest(query); verifySchema(result, schema("point", null, "geo_point")); - verifyDataRows( - result, - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71)), - rows(Map.of("lon", 74, "lat", 40.71))); + // geo_point values are stored with Lucene's lossy 32-bit lat/lon encoding, so the decoded + // coordinates differ from the source (40.71, 74) by a sub-meter epsilon. Assert each returned + // point within tolerance instead of exact equality. The five source documents all describe the + // same point in different formats, so any five rows selected by LIMIT satisfy this regardless + // of shard scan order. + JSONArray dataRows = result.getJSONArray("datarows"); + assertEquals(5, dataRows.length()); + for (int i = 0; i < dataRows.length(); i++) { + JSONObject point = ((JSONArray) dataRows.get(i)).getJSONObject(0); + assertEquals(40.71, point.getDouble("lat"), TOLERANCE); + assertEquals(74, point.getDouble("lon"), TOLERANCE); + } } public static final double TOLERANCE = 1E-5; diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/IdentifierIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/IdentifierIT.java index 50ddc0324d2..57851aee9bf 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/IdentifierIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/IdentifierIT.java @@ -15,6 +15,10 @@ import static org.opensearch.sql.util.TestUtils.performRequest; import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.opensearch.client.Request; @@ -130,11 +134,19 @@ public void testMetafieldIdentifierRoutingSelectTest() throws IOException { var datarows = result.getJSONArray("datarows"); assertEquals(6, datarows.length()); + // The returned row order is not stable across a multi-shard index (each doc is routed to a + // different shard), so sort rows by their unique _id metadata value before asserting. + List sortedRows = new ArrayList<>(); + for (int i = 0; i < datarows.length(); i++) { + sortedRows.add(datarows.getJSONArray(i)); + } + sortedRows.sort(Comparator.comparing((JSONArray row) -> row.getString(1))); + // note that _routing in the SELECT clause returns the shard for (int i = 0; i < 6; i++) { - assertEquals("test" + i, datarows.getJSONArray(i).getString(1)); - assertEquals(index, datarows.getJSONArray(i).getString(2)); - assertTrue(datarows.getJSONArray(i).getString(3).contains("[" + index + "]")); + assertEquals("test" + i, sortedRows.get(i).getString(1)); + assertEquals(index, sortedRows.get(i).getString(2)); + assertTrue(sortedRows.get(i).getString(3).contains("[" + index + "]")); } } diff --git a/integ-test/src/test/java/org/opensearch/sql/sql/WildcardQueryIT.java b/integ-test/src/test/java/org/opensearch/sql/sql/WildcardQueryIT.java index 8123f887f23..17606987f12 100644 --- a/integ-test/src/test/java/org/opensearch/sql/sql/WildcardQueryIT.java +++ b/integ-test/src/test/java/org/opensearch/sql/sql/WildcardQueryIT.java @@ -22,19 +22,19 @@ protected void init() throws Exception { @Test public void test_wildcard_query_asterisk_function() throws IOException { - String expected = "test wildcard"; + String expected = "test backslash wildcard \\_"; String query1 = "SELECT KeywordBody FROM " + TEST_INDEX_WILDCARD - + " WHERE wildcard_query(KeywordBody, 't*') LIMIT 1"; + + " WHERE wildcard_query(KeywordBody, 't*') ORDER BY KeywordBody LIMIT 1"; JSONObject result1 = executeJdbcRequest(query1); verifyDataRows(result1, rows(expected)); String query2 = "SELECT KeywordBody FROM " + TEST_INDEX_WILDCARD - + " WHERE wildcardquery(KeywordBody, 't*') LIMIT 1"; + + " WHERE wildcardquery(KeywordBody, 't*') ORDER BY KeywordBody LIMIT 1"; JSONObject result2 = executeJdbcRequest(query2); verifyDataRows(result2, rows(expected)); } diff --git a/integ-test/src/test/resources/indexDefinitions/date_time_index_mapping.json b/integ-test/src/test/resources/indexDefinitions/date_time_index_mapping.json index 6f1d465a823..968ff31d6e3 100644 --- a/integ-test/src/test/resources/indexDefinitions/date_time_index_mapping.json +++ b/integ-test/src/test/resources/indexDefinitions/date_time_index_mapping.json @@ -3,6 +3,9 @@ "properties": { "birthday": { "type": "date" + }, + "login_time": { + "type": "date" } } }