Skip to content

Commit ca6da43

Browse files
committed
[BugFix] Surface dedicated error for wildcard or multi-target table in vectorSearch()
vectorSearch() requires a single concrete index because top-k semantics, dimension checks, and the embedded filter JSON are not defined across heterogeneous shards. Values like 'sql_vector_*' or 'idx_a,idx_b' were already rejected by the generic SAFE_FIELD_NAME character-class regex, but surfaced the fallback message "must contain only alphanumeric characters, dots, underscores, or hyphens", which does not tell the user what the actual constraint is. This change adds a dedicated check (before the regex) that flags '*' and ',' in the table name and surfaces: "Invalid table name '...': vectorSearch() requires a single concrete index or alias; wildcards ('*') and multi-target patterns (comma-separated) are not supported" No DSL or execution-path change. Accepted inputs are unchanged (simple names, dotted names, hyphens, underscores all still pass). Adds four unit tests (bare '*', trailing '*', mid-name '*', multi-target ',') and two integration tests pinning the new message on real queries. Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
1 parent c0faf7c commit ca6da43

3 files changed

Lines changed: 86 additions & 0 deletions

File tree

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,39 @@ public void testInvalidTableNameRejected() throws IOException {
399399
assertThat(ex.getMessage(), containsString("Invalid table name"));
400400
}
401401

402+
@Test
403+
public void testWildcardTableRejectedWithDedicatedMessage() throws IOException {
404+
// Wildcards in a table name fan out to multiple indices, which vectorSearch() does not
405+
// support (top-k semantics, dimension checks, and embedded filter JSON are not defined
406+
// across heterogeneous shards). Surface a dedicated user-facing error instead of the
407+
// generic "must contain only alphanumeric..." fallback.
408+
ResponseException ex =
409+
expectThrows(
410+
ResponseException.class,
411+
() ->
412+
executeQuery(
413+
"SELECT v._id FROM vectorSearch(table='sql_vector_*', field='f', "
414+
+ "vector='[1.0]', option='k=5') AS v"));
415+
416+
assertThat(ex.getMessage(), containsString("Invalid table name"));
417+
assertThat(ex.getMessage(), containsString("wildcards"));
418+
assertThat(ex.getMessage(), containsString("single concrete index"));
419+
}
420+
421+
@Test
422+
public void testMultiTargetTableRejectedWithDedicatedMessage() throws IOException {
423+
ResponseException ex =
424+
expectThrows(
425+
ResponseException.class,
426+
() ->
427+
executeQuery(
428+
"SELECT v._id FROM vectorSearch(table='idx_a,idx_b', field='f', "
429+
+ "vector='[1.0]', option='k=5') AS v"));
430+
431+
assertThat(ex.getMessage(), containsString("Invalid table name"));
432+
assertThat(ex.getMessage(), containsString("multi-target"));
433+
}
434+
402435
@Test
403436
public void testDuplicateNamedArgRejected() throws IOException {
404437
// Previously this crashed the server with 500 ArrayIndexOutOfBoundsException. Must now

opensearch/src/main/java/org/opensearch/sql/opensearch/storage/VectorSearchTableFunctionImplementation.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,19 @@ private void validateNamedArgs() {
234234
* its own error message.
235235
*/
236236
private void validateTableName(String tableName) {
237+
// Flag wildcard and multi-target patterns with a dedicated message before the generic
238+
// character-class check, so users get an actionable hint instead of "must contain only
239+
// alphanumeric characters, dots, ...". vectorSearch() targets a single index because
240+
// top-k semantics, dimension checks, and the embedded filter JSON are not defined across
241+
// heterogeneous shards.
242+
if (tableName.indexOf('*') >= 0 || tableName.indexOf(',') >= 0) {
243+
throw new ExpressionEvaluationException(
244+
String.format(
245+
"Invalid table name '%s': vectorSearch() requires a single concrete index or alias;"
246+
+ " wildcards ('*') and multi-target patterns (comma-separated) are not"
247+
+ " supported",
248+
tableName));
249+
}
237250
if (!SAFE_FIELD_NAME.matcher(tableName).matches()) {
238251
throw new ExpressionEvaluationException(
239252
String.format(

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,46 @@ void applyArguments_rejectsDoubleDotTable() {
498498
assertTrue(ex.getMessage().contains("Invalid table name"));
499499
}
500500

501+
@Test
502+
void applyArguments_rejectsWildcardTableWithDedicatedMessage() {
503+
VectorSearchTableFunctionImplementation impl =
504+
createImplWithArgs("sql_vector_*", "embedding", "[1.0, 2.0]", "k=5");
505+
ExpressionEvaluationException ex =
506+
assertThrows(ExpressionEvaluationException.class, () -> impl.applyArguments());
507+
assertTrue(ex.getMessage().contains("Invalid table name"));
508+
assertTrue(ex.getMessage().contains("wildcards ('*')"));
509+
assertTrue(ex.getMessage().contains("single concrete index"));
510+
}
511+
512+
@Test
513+
void applyArguments_rejectsBareStarTableWithDedicatedMessage() {
514+
VectorSearchTableFunctionImplementation impl =
515+
createImplWithArgs("*", "embedding", "[1.0, 2.0]", "k=5");
516+
ExpressionEvaluationException ex =
517+
assertThrows(ExpressionEvaluationException.class, () -> impl.applyArguments());
518+
assertTrue(ex.getMessage().contains("wildcards ('*')"));
519+
}
520+
521+
@Test
522+
void applyArguments_rejectsMultiTargetTableWithDedicatedMessage() {
523+
VectorSearchTableFunctionImplementation impl =
524+
createImplWithArgs("idx_a,idx_b", "embedding", "[1.0, 2.0]", "k=5");
525+
ExpressionEvaluationException ex =
526+
assertThrows(ExpressionEvaluationException.class, () -> impl.applyArguments());
527+
assertTrue(ex.getMessage().contains("Invalid table name"));
528+
assertTrue(ex.getMessage().contains("multi-target"));
529+
assertTrue(ex.getMessage().contains("single concrete index"));
530+
}
531+
532+
@Test
533+
void applyArguments_rejectsMidNameStarTable() {
534+
VectorSearchTableFunctionImplementation impl =
535+
createImplWithArgs("foo*bar", "embedding", "[1.0, 2.0]", "k=5");
536+
ExpressionEvaluationException ex =
537+
assertThrows(ExpressionEvaluationException.class, () -> impl.applyArguments());
538+
assertTrue(ex.getMessage().contains("wildcards ('*')"));
539+
}
540+
501541
@Test
502542
void validateNamedArgs_rejectsDuplicateNames() {
503543
// Two occurrences of "table" reach the Implementation layer directly (bypassing the resolver).

0 commit comments

Comments
 (0)