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


##########
be/src/storage/index/inverted/tokenizer/tokenizer.h:
##########
@@ -41,10 +45,50 @@ class DorisTokenizer : public Tokenizer, public 
DorisTokenStream {
     // Only use the parameterless reset method
     void reset() override { _in = _in_pending; };
 
+    std::span<const int32_t> get_source_byte_offsets() const override {
+        return _source_byte_offsets_enabled ? std::span<const int32_t> 
{_source_byte_offsets}
+                                            : std::span<const int32_t> {};
+    }
+
+    void set_source_byte_offsets_enabled(bool enabled) override {
+        _source_byte_offsets_enabled = enabled;
+    }
+
 protected:
+    int32_t correct_source_offset(int32_t offset) const {
+        const auto* char_filter = dynamic_cast<const 
DorisCharFilter*>(_in.get());
+        return char_filter == nullptr ? offset : 
char_filter->correct_offset(offset);
+    }
+
+    void set_source_byte_offsets(std::string_view term, int32_t source_start) {
+        _source_byte_offsets.clear();
+        const auto* char_filter = dynamic_cast<const 
DorisCharFilter*>(_in.get());
+        if (!_source_byte_offsets_enabled || char_filter == nullptr) {

Review Comment:
   [P1] Publish identity provenance for plain tokenizer inputs
   
   This branch also returns when tracking is enabled but `_in` is an ordinary 
reader. In the valid `keyword -> word_delimiter -> 
pinyin(ignore_pinyin_offset=false)` chain over `liu-de`, Pinyin's opt-in 
reaches Keyword, but Keyword publishes an empty map. WordDelimiter therefore 
gives both generated `liu` and `de` the upstream `[0,6)` span, and Pinyin 
preserves those whole-token spans instead of `[0,3)` and `[4,6)`. Standard case 
splitting has the same path. This is distinct from the earlier character-filter 
Keyword/Standard and IK-to-WordDelimiter threads because those sources already 
produce a map. Please build raw UTF-8 rune boundaries whenever tracking is 
enabled, applying `correct_offset()` only when a character filter exists, and 
cover reset/reuse for both plain tokenizers.



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/invertedindex/AnalyzerIdentityBuilder.java:
##########
@@ -45,14 +47,60 @@ public static String buildAnalyzerIdentity(
         }
 
         if (!Strings.isNullOrEmpty(preferredAnalyzer)) {
+            String builtinIkIdentity = 
resolveBuiltinIkAnalyzerIdentity(properties, preferredAnalyzer);

Review Comment:
   [P1] Resolve exact custom analyzers before the built-in IK shortcut
   
   A pre-upgrade Turkish-locale FE could persist an exact ANALYZER policy named 
`IK` because the old default-locale normalization produced dotless `ı`. Current 
property canonicalization preserves that exact spelling, and BE's 
case-sensitive dispatch executes the saved custom policy. This 
`equalsIgnoreCase` shortcut nevertheless assigns built-in max-word identity 
before consulting the exact policy. For example, `IK={tokenizer=standard}` is 
then considered different from an equivalent custom standard analyzer and equal 
to runtime-distinct built-in max-word IK, corrupting both CREATE and ALTER 
duplicate checks. This is distinct from the prior lifecycle thread because 
exact runtime binding now succeeds; this semantic consumer bypasses it. Please 
resolve an exact policy first and use the synthetic built-in identity only when 
no exact binding exists, with replay coverage for the historical `IK` name.



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/invertedindex/AnalyzerIdentityBuilder.java:
##########
@@ -154,43 +205,60 @@ private static String resolveComponentIdentity(String 
name, IndexPolicyTypeEnum
             return "";
         }
 
-        // Check if it's a built-in component
-        if (expectedType == IndexPolicyTypeEnum.TOKENIZER
-                && IndexPolicy.BUILTIN_TOKENIZERS.contains(name)) {
-            return name;
-        }
-
-        // For custom component, get its properties
+        // Existing named policies take precedence over built-ins for upgrade 
compatibility.
         try {
             Env env = Env.getCurrentEnv();
-            if (env == null || env.getIndexPolicyMgr() == null) {
-                return name;
-            }
-
-            IndexPolicy policy = env.getIndexPolicyMgr().getPolicyByName(name);
-            if (policy == null || policy.getType() != expectedType) {
-                return name;
-            }
-            if (policy.isInvalid()) {
-                return "invalid-policy:" + policy.getId() + ":" + 
policy.getName();
+            if (env != null && env.getIndexPolicyMgr() != null) {
+                IndexPolicy policy = 
env.getIndexPolicyMgr().getPolicyByName(name);
+                if (policy != null && policy.getType() == expectedType) {
+                    if (policy.isInvalid()) {
+                        return "invalid-policy:" + policy.getId() + ":" + 
policy.getName();
+                    }
+                    Map<String, String> props = policy.getProperties();
+                    if (props != null && !props.isEmpty()) {
+                        TreeMap<String, String> sortedProps = new 
TreeMap<>(props);
+                        String type = sortedProps.get(IndexPolicy.PROP_TYPE);
+                        String normalizedType = 
normalizeBuiltinComponentName(type, expectedType);
+                        if (normalizedType != null) {
+                            if ("empty".equals(normalizedType)) {
+                                return "";
+                            }
+                            if (sortedProps.size() == 1) {
+                                return normalizedType;
+                            }
+                            sortedProps.put(IndexPolicy.PROP_TYPE, 
normalizedType);
+                        }
+                        if (expectedType == IndexPolicyTypeEnum.TOKENIZER
+                                && 
"ngram".equals(sortedProps.get(IndexPolicy.PROP_TYPE))) {
+                            // This setting only limits policy creation; it 
does not change emitted tokens.
+                            sortedProps.remove(PROP_MAX_NGRAM_DIFF);
+                        }
+                        return sortedProps.toString();

Review Comment:
   [P1] Canonicalize named `char_replace` policies here too
   
   This raw map serialization bypasses the new byte-set canonicalizer used for 
index-level outer filters. Two valid named filters such as 
`cf_ab={type=char_replace,pattern=ab,replacement=x}` and 
`cf_ba={type=char_replace,pattern=ba,replacement=x}` therefore give otherwise 
identical analyzers different identities. BE loads both patterns into the same 
`bitset<256>`, so order and duplicates are discarded and the runtime streams 
are identical; replacement-byte entries are no-ops as well. CREATE and ALTER 
can consequently admit duplicate indexes. This is a separate named-component 
expansion path from the existing outer-filter thread. Please apply the same 
effective byte-set rules when resolving named CHAR_FILTER policies and cover 
reordered, duplicate, and no-op patterns in both duplicate consumers.



##########
fe/fe-core/src/main/java/org/apache/doris/analysis/invertedindex/AnalyzerIdentityBuilder.java:
##########
@@ -45,14 +47,60 @@ public static String buildAnalyzerIdentity(
         }
 
         if (!Strings.isNullOrEmpty(preferredAnalyzer)) {
+            String builtinIkIdentity = 
resolveBuiltinIkAnalyzerIdentity(properties, preferredAnalyzer);
+            if (builtinIkIdentity != null) {
+                return appendOuterCharFilterIdentity(builtinIkIdentity, 
properties);
+            }
             // For custom analyzer/normalizer, resolve to underlying config to 
build identity
-            return resolveAnalyzerIdentity(preferredAnalyzer, 
defaultAnalyzerKey, log);
+            return appendOuterCharFilterIdentity(
+                    resolveAnalyzerIdentity(preferredAnalyzer, 
defaultAnalyzerKey, log), properties);
         }
 
         if (Strings.isNullOrEmpty(parser) || 
parserNone.equalsIgnoreCase(parser)) {
             return defaultAnalyzerKey;
         }
-        return parser;
+        String legacyIkIdentity = resolveLegacyIkIdentity(properties, parser);
+        if (legacyIkIdentity != null) {
+            return appendOuterCharFilterIdentity(legacyIkIdentity, properties);
+        }
+        return appendOuterCharFilterIdentity(parser, properties);
+    }
+
+    private static String resolveBuiltinIkAnalyzerIdentity(
+            Map<String, String> properties, String analyzer) {
+        // BE defaults analyzer=ik to max-word mode. It has the built-in 
ik_max_word base
+        // identity when no index-level tokenizer option changes its behavior; 
the caller
+        // appends any outer char-filter identity separately.
+        if 
(!InvertedIndexProperties.INVERTED_INDEX_PARSER_IK.equalsIgnoreCase(analyzer.trim()))
 {
+            return null;
+        }
+        String lowerCase = 
properties.get(InvertedIndexProperties.INVERTED_INDEX_PARSER_LOWERCASE_KEY);
+        if (!Strings.isNullOrEmpty(lowerCase) && 
!Boolean.TRUE.toString().equalsIgnoreCase(lowerCase)) {
+            return null;

Review Comment:
   [P1] Keep disabled-lowercase IK modes distinct
   
   When this returns null, `{analyzer=ik,lower_case=false}` falls through 
`resolveAnalyzerIdentity("ik")` and gets the literal identity `ik`. The sibling 
legacy guard does the same for `{parser=ik,lower_case=false}`, so line 66 also 
returns `ik`. Their runtime defaults differ: analyzer-only IK falls back to 
coarse/max-word mode, while legacy parser IK defaults to smart mode. These 
valid configurations can emit different terms, yet both CREATE and ALTER reject 
them as duplicates. This cross-branch collision is distinct from the prior 
legacy-versus-custom lower-case thread and the enabled-lowercase analyzer 
default fix. Please retain both runtime mode and lowercase state in the 
fallback identity and test smart/max-word comparisons in both duplicate paths.



##########
be/src/runtime/index_policy/index_policy_mgr.cpp:
##########
@@ -41,13 +41,72 @@ class SingleAnalyzerProvider final : public 
segment_v2::inverted_index::Analyzer
 
 const std::unordered_set<std::string> IndexPolicyMgr::BUILTIN_NORMALIZERS = 
{"lowercase"};
 
-std::string IndexPolicyMgr::normalize_name(const std::string& name) {
+std::string IndexPolicyMgr::trim_name(const std::string& name) {
     std::string result = name;
     boost::algorithm::trim(result);
+    return result;
+}
+
+std::string IndexPolicyMgr::normalize_name(const std::string& name) {
+    std::string result = trim_name(name);
     boost::algorithm::to_lower(result);
     return result;
 }
 
+const TIndexPolicy* IndexPolicyMgr::find_policy_by_name_locked(const 
std::string& name) const {
+    const std::string exact_name = trim_name(name);
+    if (auto exact_it = _exact_name_to_id.find(exact_name); exact_it != 
_exact_name_to_id.end()) {

Review Comment:
   [P1] Preserve the exact analyzer binding through MATCH dispatch
   
   With a replayed exact `IK={tokenizer=keyword}` policy, current index 
properties retain `IK`; both CLucene and SNII writers use case-sensitive 
custom-analyzer dispatch and reach this exact map, so `abc def` is indexed as 
one keyword term. MATCH takes a different path: FE `AnalyzerSelector` 
lowercases the implicit index analyzer (and explicit `USING ANALYZER IK`), then 
BE `AnalyzerConfigParser` classifies `ik` as the built-in max-word analyzer. 
The normalized reader key still selects the custom-written index, so query and 
index tokenization diverge and can return false negatives. This is distinct 
from the FE duplicate-identity issue and the earlier validation/DROP lifecycle 
thread. Please carry an exact analyzer spelling or resolved policy ID through 
query analysis and use a collision-safe reader-selection identity, with 
implicit and explicit MATCH upgrade tests.



-- 
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