github-actions[bot] commented on code in PR #66050:
URL: https://github.com/apache/doris/pull/66050#discussion_r3828956954


##########
be/src/exprs/function/function_regexp.cpp:
##########
@@ -188,6 +195,87 @@ struct RegexpExtractEngine {
             }
         }
     }
+
+    // Spark semantics used by the explicit group-index form: search the 
original subject
+    // (so `^` stays anchored to the original string), emit successful 
zero-width matches
+    // (including one terminal-position match), and advance one full UTF-8 
character.
+    void match_all_and_extract_spark(const char* data, size_t size, int index,
+                                     std::vector<std::string>& results) const {
+        if (index < 0) {
+            return;
+        }
+        if (is_re2()) {
+            int max_matches = 1 + re2_regex->NumberOfCapturingGroups();
+            if (index >= max_matches) {
+                return;
+            }
+
+            size_t pos = 0;
+            while (pos <= size) {
+                std::vector<re2::StringPiece> matches(max_matches);
+                // Search within the original subject starting from pos, so 
`^` stays
+                // anchored to the beginning of the original string.
+                bool success = re2_regex->Match(re2::StringPiece(data, size), 
pos, size,
+                                                re2::RE2::UNANCHORED, 
matches.data(), max_matches);
+                if (!success) {
+                    break;
+                }
+                const re2::StringPiece& whole = matches[0];
+                // Extract the capturing group with the given index
+                if (static_cast<size_t>(index) < matches.size()) {
+                    const re2::StringPiece& group = matches[index];
+                    if (group.data() != nullptr) {
+                        results.emplace_back(group.data(), group.size());
+                    } else {
+                        results.emplace_back();
+                    }
+                }
+                if (whole.empty()) {
+                    if (whole.data() >= data + size) {
+                        break;
+                    }
+                    // Advance one full UTF-8 character FROM THE MATCH 
LOCATION, never
+                    // into a continuation byte and never rescanning the same 
match.
+                    pos = (whole.data() - data) +
+                          
get_utf8_byte_length(static_cast<uint8_t>(*whole.data()));
+                } else {
+                    // Advance past the match via its pointer into the 
original subject.
+                    pos = (whole.data() - data) + whole.size();
+                }
+            }
+        } else if (is_boost()) {
+            const char* search_start = data;
+            const char* search_end = data + size;
+            boost::match_results<const char*> matches;
+
+            // Keep the original subject start reachable: match_prev_avail lets
+            // look-behind assertions see characters before search_start, and
+            // match_not_bob keeps `\A` anchored to the start of the original
+            // buffer (Boost's documented repeated-regex_search idiom; with
+            // match_prev_avail set, `^` is decided by the preceding 
character).
+            while (search_start <= search_end &&

Review Comment:
   [P1] Preserve previous-match state for `\G`
   
   `\G` is stateful: Spark reuses one Java `Matcher`, so on `ab` the pattern 
`\G` matches only at offset 0; after that zero-width match, `find()` advances 
while the previous match still ended at 0. This loop instead starts a fresh 
Boost search at each new `search_start`, so each suffix origin is treated as a 
new continuation point and the array form emits empty matches at offsets 0, 1, 
and 2 (the string form shares the same engine). Please preserve continuation 
state across searches or reject `\G`, and add a Boost-path regression for this 
case.



##########
be/src/exprs/function/function_regexp.cpp:
##########
@@ -188,6 +195,87 @@ struct RegexpExtractEngine {
             }
         }
     }
+
+    // Spark semantics used by the explicit group-index form: search the 
original subject
+    // (so `^` stays anchored to the original string), emit successful 
zero-width matches
+    // (including one terminal-position match), and advance one full UTF-8 
character.
+    void match_all_and_extract_spark(const char* data, size_t size, int index,
+                                     std::vector<std::string>& results) const {
+        if (index < 0) {
+            return;
+        }
+        if (is_re2()) {
+            int max_matches = 1 + re2_regex->NumberOfCapturingGroups();
+            if (index >= max_matches) {
+                return;
+            }
+
+            size_t pos = 0;
+            while (pos <= size) {
+                std::vector<re2::StringPiece> matches(max_matches);
+                // Search within the original subject starting from pos, so 
`^` stays
+                // anchored to the beginning of the original string.
+                bool success = re2_regex->Match(re2::StringPiece(data, size), 
pos, size,
+                                                re2::RE2::UNANCHORED, 
matches.data(), max_matches);
+                if (!success) {
+                    break;
+                }
+                const re2::StringPiece& whole = matches[0];
+                // Extract the capturing group with the given index
+                if (static_cast<size_t>(index) < matches.size()) {
+                    const re2::StringPiece& group = matches[index];
+                    if (group.data() != nullptr) {
+                        results.emplace_back(group.data(), group.size());
+                    } else {
+                        results.emplace_back();
+                    }
+                }
+                if (whole.empty()) {
+                    if (whole.data() >= data + size) {
+                        break;
+                    }
+                    // Advance one full UTF-8 character FROM THE MATCH 
LOCATION, never
+                    // into a continuation byte and never rescanning the same 
match.
+                    pos = (whole.data() - data) +

Review Comment:
   [P1] Preserve Spark's supplementary-character empty matches
   
   Spark delegates this loop to Java `Matcher.find()`, whose progress after an 
empty match is one UTF-16 code unit. For `regexp_extract_all_array('😀a', 'a*', 
0)`, Java emits four entries: empty matches at UTF-16 offsets 0 and 1, `a` at 
2:3, and the terminal empty match. This branch instead advances from byte 0 
over the whole four-byte UTF-8 character, so it returns only `["", "a", ""]`; 
the Boost branch makes the same jump. Since the explicit-index path is 
documented as Spark semantics, please either emulate the UTF-16 iteration 
positions without passing invalid byte offsets to the engines or narrow that 
contract, and add supplementary-character cases for both outputs.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to