Skip to content

Commit c85202f

Browse files
committed
fixes
Signed-off-by: Kai Huang <ahkcs@amazon.com>
1 parent 580d32e commit c85202f

2 files changed

Lines changed: 167 additions & 60 deletions

File tree

core/src/main/java/org/opensearch/sql/expression/function/CollectionUDF/MVFindFunctionImpl.java

Lines changed: 102 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77

88
import java.util.List;
99
import java.util.regex.Pattern;
10+
import java.util.regex.PatternSyntaxException;
1011
import org.apache.calcite.adapter.enumerable.NotNullImplementor;
1112
import org.apache.calcite.adapter.enumerable.NullPolicy;
12-
import org.apache.calcite.adapter.enumerable.RexImpTable;
1313
import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
1414
import org.apache.calcite.linq4j.tree.Expression;
15+
import org.apache.calcite.linq4j.tree.Expressions;
1516
import org.apache.calcite.linq4j.tree.Types;
1617
import org.apache.calcite.rex.RexCall;
17-
import org.apache.calcite.schema.impl.ScalarFunctionImpl;
18+
import org.apache.calcite.rex.RexLiteral;
1819
import org.apache.calcite.sql.type.OperandTypes;
1920
import org.apache.calcite.sql.type.ReturnTypes;
2021
import org.apache.calcite.sql.type.SqlReturnTypeInference;
@@ -35,7 +36,7 @@
3536
*/
3637
public class MVFindFunctionImpl extends ImplementorUDF {
3738
public MVFindFunctionImpl() {
38-
super(new MVFindImplementor(), NullPolicy.ALL);
39+
super(new MVFindImplementor(), NullPolicy.ANY);
3940
}
4041

4142
@Override
@@ -45,55 +46,130 @@ public SqlReturnTypeInference getReturnTypeInference() {
4546

4647
@Override
4748
public UDFOperandMetadata getOperandMetadata() {
48-
return UDFOperandMetadata.wrap(
49-
OperandTypes.family(SqlTypeFamily.ARRAY, SqlTypeFamily.CHARACTER));
49+
// Accept ARRAY and either CHARACTER or NUMERIC (coerced to string)
50+
return UDFOperandMetadata.wrap(OperandTypes.family(SqlTypeFamily.ARRAY, SqlTypeFamily.ANY));
5051
}
5152

5253
public static class MVFindImplementor implements NotNullImplementor {
5354
@Override
5455
public Expression implement(
5556
RexToLixTranslator translator, RexCall call, List<Expression> translatedOperands) {
56-
ScalarFunctionImpl function =
57-
(ScalarFunctionImpl)
58-
ScalarFunctionImpl.create(
59-
Types.lookupMethod(MVFindFunctionImpl.class, "eval", Object[].class));
60-
return function.getImplementor().implement(translator, call, RexImpTable.NullAs.NULL);
57+
Expression arrayExpr = translatedOperands.get(0);
58+
Expression patternExpr = translatedOperands.get(1);
59+
60+
// Check if regex pattern is a literal - compile at planning time
61+
if (call.operands.size() >= 2 && call.operands.get(1) instanceof RexLiteral) {
62+
RexLiteral patternLiteral = (RexLiteral) call.operands.get(1);
63+
Object literalValue = patternLiteral.getValue();
64+
65+
if (literalValue != null) {
66+
// Convert numeric or other types to string for regex matching
67+
String patternString = literalValue.toString();
68+
try {
69+
// Compile pattern at planning time and validate
70+
Pattern compiledPattern = Pattern.compile(patternString);
71+
// Generate code that uses the pre-compiled pattern
72+
return Expressions.call(
73+
Types.lookupMethod(
74+
MVFindFunctionImpl.class, "evalWithPattern", List.class, Pattern.class),
75+
arrayExpr,
76+
Expressions.constant(compiledPattern, Pattern.class));
77+
} catch (PatternSyntaxException e) {
78+
// Convert to IllegalArgumentException so it's treated as a client error (400)
79+
throw new IllegalArgumentException(
80+
String.format("Invalid regex pattern '%s': %s", patternString, e.getDescription()),
81+
e);
82+
}
83+
}
84+
}
85+
86+
// For null or dynamic patterns, use evalWithString
87+
return Expressions.call(
88+
Types.lookupMethod(MVFindFunctionImpl.class, "evalWithString", List.class, Object.class),
89+
arrayExpr,
90+
patternExpr);
6191
}
6292
}
6393

6494
/**
65-
* Evaluates the mvfind function.
95+
* Core mvfind logic that searches for a pattern in an array.
6696
*
67-
* @param args args[0] is the array (List<Object>), args[1] is the regex pattern (String)
97+
* @param array The array to search
98+
* @param pattern The pre-compiled regex pattern
6899
* @return The 0-based index of the first matching element, or null if no match
69100
*/
70-
public static Object eval(Object... args) {
71-
if (args == null || args.length < 2) {
101+
private static Integer mvfindCore(List<Object> array, Pattern pattern) {
102+
for (int i = 0; i < array.size(); i++) {
103+
Object element = array.get(i);
104+
if (element != null) {
105+
String strValue = element.toString();
106+
if (pattern.matcher(strValue).find()) {
107+
return i; // Return 0-based index
108+
}
109+
}
110+
}
111+
return null; // No match found
112+
}
113+
114+
/**
115+
* Evaluates mvfind with a pre-compiled Pattern (for literal patterns compiled at planning time).
116+
*
117+
* @param array The array to search
118+
* @param pattern The pre-compiled regex pattern
119+
* @return The 0-based index of the first matching element, or null if no match
120+
*/
121+
public static Integer evalWithPattern(List<Object> array, Pattern pattern) {
122+
if (array == null || pattern == null) {
72123
return null;
73124
}
74125

75-
List<Object> array = (List<Object>) args[0];
76-
String regex = (String) args[1];
126+
try {
127+
return mvfindCore(array, pattern);
128+
} catch (Exception e) {
129+
throw new RuntimeException("Error in mvfind function: " + e.getMessage(), e);
130+
}
131+
}
77132

133+
/**
134+
* Evaluates mvfind with a String pattern. Compiles the regex pattern and executes search.
135+
*
136+
* @param array The array to search
137+
* @param regex The regex pattern string
138+
* @return The 0-based index of the first matching element, or null if no match
139+
*/
140+
public static Integer mvfind(List<Object> array, String regex) {
78141
if (array == null || regex == null) {
79142
return null;
80143
}
81144

82145
try {
83146
Pattern pattern = Pattern.compile(regex);
84-
for (int i = 0; i < array.size(); i++) {
85-
Object element = array.get(i);
86-
if (element != null) {
87-
String strValue = element.toString();
88-
if (pattern.matcher(strValue).find()) {
89-
return i; // Return 0-based index
90-
}
91-
}
92-
}
147+
return mvfindCore(array, pattern);
148+
} catch (PatternSyntaxException e) {
149+
// Invalid regex is a client error (400)
150+
throw new IllegalArgumentException(
151+
String.format("Invalid regex pattern '%s': %s", regex, e.getDescription()), e);
93152
} catch (Exception e) {
153+
// Other unexpected errors
94154
throw new RuntimeException("Error in mvfind function: " + e.getMessage(), e);
95155
}
156+
}
96157

97-
return null; // No match found
158+
/**
159+
* Evaluates mvfind with type coercion support (for dynamic patterns at runtime). Converts numeric
160+
* or other types to string before pattern compilation.
161+
*
162+
* @param array The array to search
163+
* @param regex The regex pattern (String, Number, or any object with toString())
164+
* @return The 0-based index of the first matching element, or null if no match
165+
*/
166+
public static Integer evalWithString(List<Object> array, Object regex) {
167+
if (array == null || regex == null) {
168+
return null;
169+
}
170+
171+
// Support type coercion: convert numeric or other types to string
172+
String patternString = regex.toString();
173+
return mvfind(array, patternString);
98174
}
99175
}

0 commit comments

Comments
 (0)