diff --git a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
index 7777a2ac034..e80010fd2d5 100644
--- a/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
+++ b/core/src/main/java/org/opensearch/sql/expression/function/BuiltinFunctionName.java
@@ -75,6 +75,7 @@ public enum BuiltinFunctionName {
MVAPPEND(FunctionName.of("mvappend")),
MVJOIN(FunctionName.of("mvjoin")),
MVINDEX(FunctionName.of("mvindex")),
+ MVFIND(FunctionName.of("mvfind")),
MVZIP(FunctionName.of("mvzip")),
SPLIT(FunctionName.of("split")),
MVDEDUP(FunctionName.of("mvdedup")),
diff --git a/core/src/main/java/org/opensearch/sql/expression/function/CollectionUDF/MVFindFunctionImpl.java b/core/src/main/java/org/opensearch/sql/expression/function/CollectionUDF/MVFindFunctionImpl.java
new file mode 100644
index 00000000000..9c189bf2ff5
--- /dev/null
+++ b/core/src/main/java/org/opensearch/sql/expression/function/CollectionUDF/MVFindFunctionImpl.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.opensearch.sql.expression.function.CollectionUDF;
+
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import org.apache.calcite.adapter.enumerable.NotNullImplementor;
+import org.apache.calcite.adapter.enumerable.NullPolicy;
+import org.apache.calcite.adapter.enumerable.RexToLixTranslator;
+import org.apache.calcite.linq4j.tree.Expression;
+import org.apache.calcite.linq4j.tree.Expressions;
+import org.apache.calcite.linq4j.tree.Types;
+import org.apache.calcite.rex.RexCall;
+import org.apache.calcite.rex.RexLiteral;
+import org.apache.calcite.sql.type.OperandTypes;
+import org.apache.calcite.sql.type.ReturnTypes;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
+import org.apache.calcite.sql.type.SqlTypeFamily;
+import org.opensearch.sql.expression.function.ImplementorUDF;
+import org.opensearch.sql.expression.function.UDFOperandMetadata;
+
+/**
+ * MVFIND function implementation that finds the index of the first element in a multivalue array
+ * that matches a regular expression.
+ *
+ *
Usage: mvfind(array, regex)
+ *
+ *
Returns the 0-based index of the first array element matching the regex pattern, or NULL if no
+ * match is found.
+ *
+ *
Example: mvfind(array('apple', 'banana', 'apricot'), 'ban.*') returns 1
+ */
+public class MVFindFunctionImpl extends ImplementorUDF {
+ public MVFindFunctionImpl() {
+ super(new MVFindImplementor(), NullPolicy.ANY);
+ }
+
+ @Override
+ public SqlReturnTypeInference getReturnTypeInference() {
+ return ReturnTypes.INTEGER_NULLABLE;
+ }
+
+ @Override
+ public UDFOperandMetadata getOperandMetadata() {
+ // Accept ARRAY and STRING for the regex pattern
+ return UDFOperandMetadata.wrap(
+ OperandTypes.family(SqlTypeFamily.ARRAY, SqlTypeFamily.CHARACTER));
+ }
+
+ public static class MVFindImplementor implements NotNullImplementor {
+ @Override
+ public Expression implement(
+ RexToLixTranslator translator, RexCall call, List translatedOperands) {
+ Expression arrayExpr = translatedOperands.get(0);
+ Expression patternExpr = translatedOperands.get(1);
+
+ // Check if regex pattern is a literal - compile at planning time
+ if (call.operands.size() >= 2 && call.operands.get(1) instanceof RexLiteral) {
+ RexLiteral patternLiteral = (RexLiteral) call.operands.get(1);
+ Expression literalPatternExpr = tryCompileLiteralPattern(patternLiteral, arrayExpr);
+ if (literalPatternExpr != null) {
+ return literalPatternExpr;
+ }
+ }
+
+ // For dynamic patterns, use evalWithString
+ return Expressions.call(
+ Types.lookupMethod(MVFindFunctionImpl.class, "evalWithString", List.class, String.class),
+ arrayExpr,
+ patternExpr);
+ }
+
+ private static Expression tryCompileLiteralPattern(
+ RexLiteral patternLiteral, Expression arrayExpr) {
+ // Use getValueAs(String.class) to correctly unwrap Calcite NlsString
+ String patternString = patternLiteral.getValueAs(String.class);
+ if (patternString == null) {
+ return null;
+ }
+ try {
+ // Compile pattern at planning time and validate
+ Pattern compiledPattern = Pattern.compile(patternString);
+ // Generate code that uses the pre-compiled pattern
+ return Expressions.call(
+ Types.lookupMethod(
+ MVFindFunctionImpl.class, "evalWithPattern", List.class, Pattern.class),
+ arrayExpr,
+ Expressions.constant(compiledPattern, Pattern.class));
+ } catch (PatternSyntaxException e) {
+ // Convert to IllegalArgumentException so it's treated as a client error (400)
+ throw new IllegalArgumentException(
+ String.format("Invalid regex pattern '%s': %s", patternString, e.getDescription()), e);
+ }
+ }
+ }
+
+ private static Integer mvfindCore(List