77
88import java .util .List ;
99import java .util .regex .Pattern ;
10+ import java .util .regex .PatternSyntaxException ;
1011import org .apache .calcite .adapter .enumerable .NotNullImplementor ;
1112import org .apache .calcite .adapter .enumerable .NullPolicy ;
12- import org .apache .calcite .adapter .enumerable .RexImpTable ;
1313import org .apache .calcite .adapter .enumerable .RexToLixTranslator ;
1414import org .apache .calcite .linq4j .tree .Expression ;
15+ import org .apache .calcite .linq4j .tree .Expressions ;
1516import org .apache .calcite .linq4j .tree .Types ;
1617import org .apache .calcite .rex .RexCall ;
17- import org .apache .calcite .schema . impl . ScalarFunctionImpl ;
18+ import org .apache .calcite .rex . RexLiteral ;
1819import org .apache .calcite .sql .type .OperandTypes ;
1920import org .apache .calcite .sql .type .ReturnTypes ;
2021import org .apache .calcite .sql .type .SqlReturnTypeInference ;
3536 */
3637public 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