Skip to content

Commit 12709e4

Browse files
committed
Fill test coverage gaps for vector search hardening
- Add multi-sort expression test: ORDER BY _score DESC, name ASC correctly rejects the non-_score field (VectorSearchQueryBuilderTest) - Add case-insensitive argument name lookup test to verify TABLE='x' resolves same as table='x' (Implementation test) - Add non-numeric option fallback test: verifies string options are quoted in JSON output (VectorSearchIndexTest) - Add 4 integration tests: ORDER BY _score DESC succeeds, ORDER BY non-score rejects, ORDER BY _score ASC rejects, LIMIT within k succeeds (VectorSearchIT, now 16 tests) Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
1 parent ca81010 commit 12709e4

4 files changed

Lines changed: 122 additions & 0 deletions

File tree

integ-test/src/test/java/org/opensearch/sql/sql/VectorSearchIT.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,73 @@ public void testMissingRequiredOptionRejects() throws IOException {
202202

203203
assertThat(ex.getMessage(), containsString("Missing required option"));
204204
}
205+
206+
// ── Sort restriction validation ─────────────────────────────────────────
207+
208+
@Test
209+
public void testOrderByScoreDescExplainSucceeds() throws IOException {
210+
String explain =
211+
explainQuery(
212+
"SELECT v._id, v._score "
213+
+ "FROM vectorSearch(table='"
214+
+ TEST_INDEX
215+
+ "', field='embedding', "
216+
+ "vector='[1.0, 2.0]', option='k=5') AS v "
217+
+ "ORDER BY v._score DESC "
218+
+ "LIMIT 5");
219+
220+
assertTrue(
221+
"Explain should succeed with ORDER BY _score DESC:\n" + explain,
222+
explain.contains("wrapper"));
223+
}
224+
225+
@Test
226+
public void testOrderByNonScoreFieldRejects() throws IOException {
227+
ResponseException ex =
228+
expectThrows(
229+
ResponseException.class,
230+
() ->
231+
executeQuery(
232+
"SELECT v._id FROM vectorSearch(table='"
233+
+ TEST_INDEX
234+
+ "', field='embedding', "
235+
+ "vector='[1.0, 2.0]', option='k=5') AS v "
236+
+ "ORDER BY v.firstname ASC "
237+
+ "LIMIT 5"));
238+
239+
assertThat(ex.getMessage(), containsString("unsupported sort expression"));
240+
}
241+
242+
@Test
243+
public void testOrderByScoreAscRejects() throws IOException {
244+
ResponseException ex =
245+
expectThrows(
246+
ResponseException.class,
247+
() ->
248+
executeQuery(
249+
"SELECT v._id FROM vectorSearch(table='"
250+
+ TEST_INDEX
251+
+ "', field='embedding', "
252+
+ "vector='[1.0, 2.0]', option='k=5') AS v "
253+
+ "ORDER BY v._score ASC "
254+
+ "LIMIT 5"));
255+
256+
assertThat(ex.getMessage(), containsString("_score ASC is not supported"));
257+
}
258+
259+
// ── LIMIT validation ───────────────────────────────────────────────────
260+
261+
@Test
262+
public void testExplainLimitWithinKSucceeds() throws IOException {
263+
String explain =
264+
explainQuery(
265+
"SELECT v._id, v._score "
266+
+ "FROM vectorSearch(table='"
267+
+ TEST_INDEX
268+
+ "', field='embedding', "
269+
+ "vector='[1.0, 2.0]', option='k=10') AS v "
270+
+ "LIMIT 5");
271+
272+
assertTrue("Explain should succeed with LIMIT <= k:\n" + explain, explain.contains("wrapper"));
273+
}
205274
}

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/VectorSearchIndexTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,21 @@ void buildKnnQueryJsonNumericOptionRenderedUnquoted() {
124124
assertTrue(json.contains("\"k\":5"), "Numeric option should be unquoted");
125125
}
126126

127+
@Test
128+
void buildKnnQueryJsonNonNumericOptionRenderedQuoted() {
129+
LinkedHashMap<String, String> options = new LinkedHashMap<>();
130+
options.put("k", "5");
131+
options.put("method", "hnsw");
132+
133+
VectorSearchIndex index =
134+
new VectorSearchIndex(
135+
client, settings, "test-index", "embedding", new float[] {1.0f}, options);
136+
137+
String json = index.buildKnnQueryJson();
138+
assertTrue(json.contains("\"method\":\"hnsw\""), "Non-numeric option should be quoted");
139+
assertTrue(json.contains("\"k\":5"), "Numeric option should be unquoted");
140+
}
141+
127142
@Test
128143
void isInstanceOfOpenSearchIndex() {
129144
VectorSearchIndex index =

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/VectorSearchTableFunctionImplementationTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,21 @@ void testNaNMinScoreThrows() {
373373
assertTrue(ex.getMessage().contains("must be a finite number"));
374374
}
375375

376+
@Test
377+
void testCaseInsensitiveArgLookup() {
378+
FunctionName functionName = FunctionName.of("vectorsearch");
379+
List<Expression> args =
380+
List.of(
381+
DSL.namedArgument("TABLE", DSL.literal("my-index")),
382+
DSL.namedArgument("FIELD", DSL.literal("embedding")),
383+
DSL.namedArgument("VECTOR", DSL.literal("[1.0, 2.0]")),
384+
DSL.namedArgument("OPTION", DSL.literal("k=5")));
385+
VectorSearchTableFunctionImplementation impl =
386+
new VectorSearchTableFunctionImplementation(functionName, args, client, settings);
387+
Table table = impl.applyArguments();
388+
assertTrue(table instanceof VectorSearchIndex);
389+
}
390+
376391
private VectorSearchTableFunctionImplementation createImpl() {
377392
return createImplWithArgs("my-index", "embedding", "[1.0, 2.0, 3.0]", "k=5");
378393
}

opensearch/src/test/java/org/opensearch/sql/opensearch/storage/scan/VectorSearchQueryBuilderTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,29 @@ void pushDownSortNonScoreFieldRejected() {
175175
assertTrue(ex.getMessage().contains("unsupported sort expression"));
176176
}
177177

178+
@Test
179+
void pushDownSortMultipleExpressionsRejectsNonScore() {
180+
var requestBuilder = createRequestBuilder();
181+
var knnQuery = new WrapperQueryBuilder("{\"knn\":{}}");
182+
var builder = new VectorSearchQueryBuilder(requestBuilder, knnQuery, Map.of("k", "5"));
183+
184+
var dummyChild = new LogicalValues(Collections.emptyList());
185+
var sort =
186+
new org.opensearch.sql.planner.logical.LogicalSort(
187+
dummyChild,
188+
List.of(
189+
org.apache.commons.lang3.tuple.ImmutablePair.of(
190+
org.opensearch.sql.ast.tree.Sort.SortOption.DEFAULT_DESC,
191+
new ReferenceExpression("_score", ExprCoreType.FLOAT)),
192+
org.apache.commons.lang3.tuple.ImmutablePair.of(
193+
org.opensearch.sql.ast.tree.Sort.SortOption.DEFAULT_ASC,
194+
new ReferenceExpression("name", STRING))));
195+
196+
ExpressionEvaluationException ex =
197+
assertThrows(ExpressionEvaluationException.class, () -> builder.pushDownSort(sort));
198+
assertTrue(ex.getMessage().contains("unsupported sort expression"));
199+
}
200+
178201
@Test
179202
void pushDownSortScoreAscRejected() {
180203
var requestBuilder = createRequestBuilder();

0 commit comments

Comments
 (0)