airborne12 commented on code in PR #67918:
URL: https://github.com/apache/doris/pull/67918#discussion_r4045723717


##########
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:
   Fixed in 4119d0bf2e3. When provenance tracking is enabled, plain readers now 
publish UTF-8 rune boundaries; character-filter correction is applied only when 
a filter exists.
   
   `PinyinFilterTest.TestWordDelimiterPreservesPlainTokenizerOffsetsAfterReset` 
reproduced incorrect subterm offsets before the fix and passes afterward. It 
covers both plain keyword and standard tokenizers, composed 
word-delimiter/pinyin processing, and reset/reuse. The relevant BE ASAN test 
selection passed (137 tests).
   



##########
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:
   Fixed in 4119d0bf2e3. Named `char_replace` components now use the same 
effective byte-set canonicalization as outer filters: pattern order and 
duplicate bytes are ignored, self-replacements are removed, and an entirely 
no-op filter contributes no identity.
   
   
`AnalyzerIdentityBuilderTest.testNamedCharReplaceIdentityUsesEffectiveByteSet` 
and CREATE/ALTER duplicate-validation tests cover reordered, duplicate, and 
no-op patterns. The native `test_analyzer_identity_semantics` regression suite 
also passed those DDL 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:
   Fixed in 4119d0bf2e3. FE resolves the saved policy spelling before MATCH 
serialization and canonicalizes only built-ins. BE preserves that resolved 
spelling for custom-provider dispatch and reader selection, so `IK`/`ik` and 
case-distinct legacy policies no longer share a reader key. Legacy parser 
properties remain case-insensitive.
   
   Validation covers replayed policy selection, implicit/explicit MATCH Thrift 
serialization, real Nereids MATCH translation, BE provider dispatch, and 
selection between actual mock readers with colliding names. The BE 
dispatch/reader tests failed before the fix and pass afterward; after 
synchronizing master, the relevant selections passed 137 BE ASAN and 115 FE 
tests. Both native analyzer regression suites passed, including ordinary 
implicit MATCH, uppercase explicit custom aliases, and uppercase built-in IK. 
This is component-level replay coverage plus current-version end-to-end 
coverage, not a full mixed-version cluster upgrade test.
   



##########
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:
   Fixed in 4119d0bf2e3. Both IK identity paths now retain runtime mode and 
disabled-lowercase state: analyzer-only IK defaults to max-word, while legacy 
parser IK defaults to smart. Explicit legacy max-word remains equivalent to 
analyzer-only max-word when their lowercase settings agree.
   
   
`AnalyzerIdentityBuilderTest.testDisabledLowercaseIkModesHaveDistinctIdentities`
 and CREATE/ALTER duplicate-validation tests cover these comparisons. The 
native `test_analyzer_identity_semantics` regression suite also passed the 
accepted smart/max-word pair and the rejected equivalent max-word pair.
   



##########
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:
   Fixed in 4119d0bf2e3. The synthetic built-in IK identity now applies only to 
canonical lowercase `ik`; case-distinct legacy `IK` follows the exact saved 
policy, consistent with BE dispatch.
   
   `AnalyzerIdentityBuilderTest.testReplayedExactIkAnalyzerUsesCustomIdentity` 
covers replayed `IK` and an equivalent custom standard analyzer. The identity 
regression reproduced the collision before the fix and now passes; 
corresponding CREATE/ALTER duplicate-validation tests also pass in the relevant 
FE test selection.
   



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