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


##########
be/src/storage/index/snii/snii_index_reader.cpp:
##########
@@ -402,6 +408,33 @@ Status execute_snii_query(const 
::doris::snii::reader::LogicalIndexReader& logic
                                                     max_expansions);
         emitted_to_sink = true;
         break;
+    case InvertedIndexQueryType::LIKE_GRAM_QUERY:
+    case InvertedIndexQueryType::REGEXP_GRAM_QUERY: {
+        // Compile against the same physical dictionary that supplies the 
postings. Current
+        // policies can differ from the scheme that was used when this segment 
was written.
+        const auto& scheme = logical_reader.gram_scheme();

Review Comment:
   Fixed in c2722622203. `SniiIndexReader` now resolves the scheme the current 
analyzer cuts with for every analyzed query type (MATCH_ANY / MATCH_ALL / 
MATCH_PHRASE / MATCH_PHRASE_PREFIX / MATCH_PHRASE_EDGE) -- the caller's 
provider, else the analyzer named by the index properties -- compares it with 
the segment's persisted scheme once the logical reader is open, and returns 
`INVERTED_INDEX_EVALUATE_SKIPPED` on a mismatch so the scalar predicate 
answers. Analyzed queries on a gram-family index no longer enter the result 
cache, whose key carries no scheme identity.
   
   Tests: 
`LikeGramBindingTest.MatchIsSkippedWhenTheCurrentSchemeDiffersFromTheSegments` 
(a dense-4 analyzer over a dense-3 segment is skipped; the segment's own scheme 
answers the query), and `test_gram_policy_recovery` now runs MATCH_ANY / 
MATCH_ALL with the index on and off on the recovered table and on the mixed 
dense-3 / dense-4 table.



##########
be/src/storage/index/snii/snii_index_writer.cpp:
##########
@@ -44,13 +46,97 @@ 
SniiIndexColumnWriter::SniiIndexColumnWriter(IndexFileWriter* index_file_writer,
           _index_meta(index_meta),
           _is_char(value_type == FieldType::OLAP_FIELD_TYPE_CHAR) {}
 
+// Gram-family detection (Rulings R21/R22): the scheme comes only from the 
single analyzer
+// provider the writer created itself -- the same provider both produces the 
actual tokenizer and
+// answers "am I gram family?", so the two cannot drift. A built-in analyzer
+// (standard/english/...) goes through BuiltinAnalyzerProvider and the base 
class default, which
+// is always nullopt, and the policy manager is never consulted (consulting it 
would throw
+// "Policy not found" and make every built-in-analyzer index impossible to 
build).
+void SniiIndexColumnWriter::_apply_gram_family_scheme(
+        const inverted_index::AnalyzerProviderPtr& analyzer_provider) {
+    if (analyzer_provider != nullptr) {
+        _gram_scheme = analyzer_provider->gram_scheme();
+    }
+    // An index-level char_filter is wrapped around the reader by the writer 
itself
+    // (create_reader) and is invisible to the provider: once one exists, the 
stored term is no
+    // longer equal to GramExtractor.extract(raw column value), breaking the 
row invariant the
+    // query side (phase C) relies on, so this is treated as "not gram family" 
(fail-safe, for
+    // the same reason as R22).
+    if (!_analyzer_config.char_filter_map.empty()) {
+        _gram_scheme.reset();
+    }
+    DCHECK(!_gram_scheme.has_value() || _should_analyzer);
+    if (!_gram_scheme.has_value() || !_has_positions) {
+        return;
+    }
+    // R15: a gram-family hit forces a degradation to docs-only (the gram 
index does not support
+    // phrase positions), and it has to happen before SpimiTermBuffer is fixed 
by _has_positions.
+    LOG(INFO) << "gram-family analyzer forces docs-only index, ignoring 
support_phrase for index "
+              << _index_meta->index_id();
+    _has_positions = false;
+    _config = ::doris::snii::format::IndexConfig::kDocsOnly;
+}
+
+// Arms the density solve for this segment. The configured density stays as 
the fallback: it is
+// what a segment gets when the feature is off, when the sample carries no 
window of the
+// promised length, or when nothing at all is written.
+void SniiIndexColumnWriter::_arm_density_calibration() {
+    if (!_gram_scheme.has_value() || 
!config::enable_gram_index_adaptive_density) {

Review Comment:
   Fixed in 2cc0776c891: calibration is armed only for `SPARSE` schemes. A 
dense writer cuts rows as they arrive and charges no sample. Test: 
`GramDensityCalibrationTest.DenseSchemesAreNotCalibrated`.



##########
be/src/storage/index/snii/snii_index_writer.cpp:
##########
@@ -211,6 +281,20 @@ Status SniiIndexColumnWriter::add_values(const std::string 
/*name*/, const void*
     }
     const auto* v = reinterpret_cast<const Slice*>(values);
     for (size_t i = 0; i < count; ++i) {
+        if (_density_calibrating) {
+            // Held back, not dropped: this row is tokenized once the rate is 
known. Feeding
+            // the solver here costs one linear pass over the row and retains 
nothing of it.
+            _density_solver->observe(std::string_view(v->data, v->size));
+            _density_sample.emplace_back(_rid, std::string(v->data, v->size));

Review Comment:
   Fixed in 2cc0776c891: each held-back row is charged its payload plus 
`sizeof(std::pair<uint32_t, std::string>)`, so the cap is reached by row count 
as well as by bytes (400 empty rows finish a 4 KiB budget), and the solver's 
256 KiB histogram is charged to the memory reporter for exactly as long as it 
is resident. The monotonic-queue scratch is reused across rows and bounded by 
the longest row, so it is not charged separately. Tests: 
`GramDensityCalibrationTest.EmptyRowsStillReachTheSampleCap`, 
`CharPaddingIsNeitherEvidenceNorRetained`.



##########
be/src/storage/index/snii/snii_index_writer.cpp:
##########
@@ -211,6 +281,20 @@ Status SniiIndexColumnWriter::add_values(const std::string 
/*name*/, const void*
     }
     const auto* v = reinterpret_cast<const Slice*>(values);
     for (size_t i = 0; i < count; ++i) {
+        if (_density_calibrating) {
+            // Held back, not dropped: this row is tokenized once the rate is 
known. Feeding
+            // the solver here costs one linear pass over the row and retains 
nothing of it.
+            _density_solver->observe(std::string_view(v->data, v->size));

Review Comment:
   Fixed in 2cc0776c891: a CHAR value is observed and retained up to its first 
NUL, exactly as `_add_value_tokens` tokenizes it, and the solver folds ASCII 
case before hashing when the scheme has `lower_case`, as `GramExtractor` folds 
the row. Tests: `GramDensityTest.FoldsCaseWhenTheSchemeDoes`, 
`GramDensityCalibrationTest.CharPaddingIsNeitherEvidenceNorRetained`, 
`GramDensityCalibrationTest.FoldingSchemesFoldTheEvidence`.



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