[BugFix] Return 400 instead of 500 on vectorSearch() argument-count mismatch#5589
[BugFix] Return 400 instead of 500 on vectorSearch() argument-count mismatch#5589mengweieric wants to merge 1 commit into
Conversation
46c49fd to
3b1f8de
Compare
A vectorSearch() call with more arguments than its resolved signature declares (for example a duplicate named argument such as table='x', table='x') crashed with an unchecked IndexOutOfBoundsException surfaced as HTTP 500. The resolver returns a fixed-arity signature regardless of how many arguments it was called with, so castArguments looped over the supplied arguments while indexing into the shorter resolved-type list and ran off the end. Guard the argument count against the resolved signature in castArguments and throw an ExpressionEvaluationException, which maps to a clean 400, before any indexing. This protects every custom function resolver, not only vectorSearch(). Signed-off-by: Eric Wei <mengwei.eric@gmail.com>
3b1f8de to
5dda148
Compare
| // surfaces as HTTP 500. Reject that overflow with a clean ExpressionEvaluationException. | ||
| // Under-arity is left to the resolver's own validation, which produces a more specific | ||
| // message; it cannot overflow the resolved type list. | ||
| if (arguments.size() > targetTypes.size()) { |
There was a problem hiding this comment.
This is method is very generic. So the changes here will fix all such mismatch argument case for similar functions?
There was a problem hiding this comment.
Right, that's intended, though the real scope is narrower than it looks. Normal functions never reach this path, since DefaultFunctionResolver rejects an arity mismatch at match() first. It only matters for custom resolvers that hand roll resolve() to return a fixed arity signature without size matching the input, like the table function resolvers (vectorSearch, and the Prometheus ones follow the same shape). So it covers all fixed arity custom resolvers, which is why I put the guard here rather than only in vectorSearch.
There was a problem hiding this comment.
Yeah, > is on purpose. The crash only happens on overflow, when there are more args than the resolved signature, so the cast loop runs off the end of targetTypes. Fewer args can't overflow, and under arity is already caught upstream anyway: normal functions reject it at the resolver via match() returning NOT_MATCH, and vectorSearch catches it in validateArguments with the specific "requires 4 arguments (table, field, vector, option)" message. So != would just shadow those two better messages with this generic one. One side effect worth noting: over vs under arity now produce slightly different wording, both 400s, so no functional difference.
| // Under-arity is left to the resolver's own validation, which produces a more specific | ||
| // message; it cannot overflow the resolved type list. | ||
| if (arguments.size() > targetTypes.size()) { | ||
| throw new ExpressionEvaluationException( |
There was a problem hiding this comment.
This should be IllegalArgumentException or Syntax exception?
There was a problem hiding this comment.
ExpressionEvaluationException is the right one here. In JdbcResponseFormatter the status comes out as (SyntaxCheckException || QueryEngineException) ? 400 : 503, and ExpressionEvaluationException extends QueryEngineException, so it maps to 400. IllegalArgumentException is neither, so it would land on the 503 branch and bring back a 5xx for bad input. It's also what the vectorSearch resolver already throws for its other validations, so this keeps it consistent.
| // surfaces as HTTP 500. Reject that overflow with a clean ExpressionEvaluationException. | ||
| // Under-arity is left to the resolver's own validation, which produces a more specific | ||
| // message; it cannot overflow the resolved type list. | ||
| if (arguments.size() > targetTypes.size()) { |
There was a problem hiding this comment.
This is method is very generic. So the changes here will fix all such mismatch argument case for similar functions?
Description
A
vectorSearch()table-function call with more arguments than its resolved signature declares crashes with an uncheckedIndexOutOfBoundsExceptionthat surfaces as HTTP 500 instead of a clean 400. The simplest trigger is a duplicate named argument:returns:
{"error":{"reason":"There was internal problem at backend","details":"Index 4 out of bounds for length 4","type":"ArrayIndexOutOfBoundsException"},"status":500}Root cause
VectorSearchTableFunctionResolver.resolve()returns a fixed-arity resolved signature (four STRING params) regardless of how many arguments it was called with. InBuiltinFunctionRepository.resolve(), when the supplied argument list is longer than the resolved type list, the call falls through tocastArguments, whose loop iterates overarguments.size()while indexing into the shortertargetTypeslist. With five arguments and four declared types it readstargetTypes.get(4)and throws. The resolver's own arity and duplicate-name validation lives inside the builder lambda and never runs because the crash happens earlier, during cast-wrapper construction.Fix
Guard the argument count against the resolved signature in
castArgumentsand throw anExpressionEvaluationException(which maps to a clean 400) before any indexing. The fix is at the generic repository layer, so it protects every customFunctionResolver, not onlyvectorSearch().Testing
:core:testand:opensearch:testpass.BuiltinFunctionRepositoryTest.resolve_should_not_index_past_resolved_signature(reproduces the crash at the root-cause layer) andVectorSearchTableFunctionResolverTest.resolve_rejectsDuplicateNamedArgumentExceedingArity(the five-argument duplicate shape).VectorSearchIT.testDuplicateNamedArgumentRejectsWith400asserting the request returns 400, not 500. NOTE: this integration test could not be executed locally becausemaincurrently has an unrelated pre-existing compile error in the plugin module (org.opensearch.analytics.QueryRequestContextconstructor mismatch inRestUnifiedQueryAction) that blocks:integ-test:integTest. CI should exercise it.spotlessJavaCheckpasses oncore,opensearch, andinteg-test.Check List
--signoff.Draft pending local IT verification once the unrelated
maincompile break is resolved.