1+ /*
2+ * Copyright OpenSearch Contributors
3+ * SPDX-License-Identifier: Apache-2.0
4+ */
5+
6+ package org .opensearch .sql .expression .operator .predicate ;
7+
8+ import java .util .concurrent .ConcurrentHashMap ;
9+ import lombok .EqualsAndHashCode ;
10+ import lombok .Getter ;
11+ import lombok .ToString ;
12+ import org .pcre4j .Pcre4j ;
13+ import org .pcre4j .jna .Pcre2 ;
14+ import org .pcre4j .regex .Pattern ;
15+ import org .pcre4j .regex .Matcher ;
16+ import org .opensearch .sql .data .model .ExprBooleanValue ;
17+ import org .opensearch .sql .data .model .ExprValue ;
18+ import org .opensearch .sql .data .model .ExprValueUtils ;
19+ import org .opensearch .sql .data .type .ExprCoreType ;
20+ import org .opensearch .sql .data .type .ExprType ;
21+ import org .opensearch .sql .expression .Expression ;
22+ import org .opensearch .sql .expression .ExpressionNodeVisitor ;
23+ import org .opensearch .sql .expression .env .Environment ;
24+ import org .opensearch .sql .expression .function .FunctionName ;
25+
26+ /**
27+ * Expression for PCRE-compatible regex matching using JPCRE2.
28+ * Supports full PCRE features including:
29+ * - Named groups (?<name>...)
30+ * - Lookahead/lookbehind (including variable-length)
31+ * - Backreferences
32+ * - Recursion (?R) and named recursion (?&name)
33+ * - Conditionals (?(condition)yes|no)
34+ * - Inline flags (?i), (?m), (?s), etc.
35+ */
36+ @ ToString
37+ @ EqualsAndHashCode
38+ public class RegexMatch implements Expression {
39+ @ Getter
40+ private final Expression field ;
41+
42+ @ Getter
43+ private final Expression pattern ;
44+
45+ @ Getter
46+ private final boolean negated ;
47+
48+ // Pattern cache to avoid recompiling the same patterns
49+ private static final ConcurrentHashMap <String , Pattern > patternCache =
50+ new ConcurrentHashMap <>();
51+
52+ // Maximum cache size to prevent memory issues
53+ private static final int MAX_CACHE_SIZE = 1000 ;
54+
55+ // Initialize PCRE4J with JNA backend (done once)
56+ static {
57+ Pcre4j .setup (new Pcre2 ());
58+ }
59+
60+ public RegexMatch (Expression field , Expression pattern , boolean negated ) {
61+ this .field = field ;
62+ this .pattern = pattern ;
63+ this .negated = negated ;
64+ }
65+
66+ @ Override
67+ public ExprValue valueOf (Environment <Expression , ExprValue > valueEnv ) {
68+ ExprValue fieldValue = field .valueOf (valueEnv );
69+ ExprValue patternValue = pattern .valueOf (valueEnv );
70+
71+ // Handle null/missing values
72+ if (fieldValue .isNull () || fieldValue .isMissing () ||
73+ patternValue .isNull () || patternValue .isMissing ()) {
74+ return ExprValueUtils .booleanValue (false );
75+ }
76+
77+ String text = fieldValue .stringValue ();
78+ String regex = patternValue .stringValue ();
79+
80+ try {
81+ // Get compiled pattern from cache or compile new one
82+ Pattern compiledPattern = getCompiledPattern (regex );
83+
84+ // Create matcher and check for match
85+ Matcher matcher = compiledPattern .matcher (text );
86+ boolean matches = matcher .find (); // Use find() for partial match like SPL
87+
88+ // Apply negation if needed
89+ return ExprValueUtils .booleanValue (negated ? !matches : matches );
90+
91+ } catch (Exception e ) {
92+ // Log error and return false on pattern compilation/matching errors
93+ // In production, you'd want proper logging here
94+ System .err .println ("Regex error: " + e .getMessage ());
95+ return ExprValueUtils .booleanValue (false );
96+ }
97+ }
98+
99+ /**
100+ * Get compiled pattern from cache or compile and cache it.
101+ */
102+ private Pattern getCompiledPattern (String regex ) {
103+ // Check cache size and clear if needed (simple LRU-like behavior)
104+ if (patternCache .size () > MAX_CACHE_SIZE ) {
105+ patternCache .clear ();
106+ }
107+
108+ return patternCache .computeIfAbsent (regex , r -> {
109+ // Compile with PCRE2 defaults
110+ // pcre4j compiles the pattern with full PCRE2 support
111+ return Pattern .compile (r );
112+ });
113+ }
114+
115+ @ Override
116+ public ExprType type () {
117+ return ExprCoreType .BOOLEAN ;
118+ }
119+
120+ @ Override
121+ public <T , C > T accept (ExpressionNodeVisitor <T , C > visitor , C context ) {
122+ // This will be implemented when we add the visitor pattern for expressions
123+ return visitor .visitNode (this , context );
124+ }
125+ }
0 commit comments