1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: Apache-2.0
//! Full-text search operations dispatched to the Data Plane.
use nodedb_types::SurrogateBitmap;
/// Full-text search physical operations.
#[derive(
Debug,
Clone,
PartialEq,
serde::Serialize,
serde::Deserialize,
zerompk::ToMessagePack,
zerompk::FromMessagePack,
)]
pub enum TextOp {
/// BM25 full-text search on the inverted index.
Search {
collection: String,
query: String,
top_k: usize,
/// Enable fuzzy matching (Levenshtein) for typo tolerance.
fuzzy: bool,
/// Pre-computed bitmap of eligible surrogates (from prefilter evaluation).
/// `None` = no prefilter; all postings are eligible.
prefilter: Option<SurrogateBitmap>,
/// RLS post-score filters (serialized `Vec<ScanFilter>`).
/// Applied after BM25 scoring, before returning to client.
/// Result count may be less than requested `top_k`.
rls_filters: Vec<u8>,
},
/// Full-collection scan with per-row BM25 score injection.
///
/// Scans every document in the collection, runs FTS scoring for each
/// document against `query`, and returns all documents with a score
/// column appended under `score_alias`. Documents that do not match the
/// query receive a `null` score. This is the physical plan used when
/// `bm25_score(field, term)` appears as a SELECT projection without a
/// restricting WHERE clause — all rows must be present in the result set
/// so the query planner cannot emit the hit-only `TextOp::Search` shape.
BM25ScoreScan {
collection: String,
query: String,
/// Column name under which the BM25 score is injected into each row.
score_alias: String,
/// Enable fuzzy matching for the scoring pass.
fuzzy: bool,
},
/// Exact phrase search: all terms must appear consecutively in the document.
///
/// Unlike `Search` (BM25 scoring), phrase search returns only documents
/// where the query terms appear as an exact contiguous sequence. Scoring
/// is positional: documents with the phrase closer to the start rank higher.
PhraseSearch {
collection: String,
/// Ordered sequence of terms to match as a phrase.
terms: Vec<String>,
top_k: usize,
/// Pre-computed bitmap of eligible surrogates (from prefilter evaluation).
prefilter: Option<nodedb_types::SurrogateBitmap>,
},
/// Hybrid search: vector similarity + BM25 text, fused via RRF.
HybridSearch {
collection: String,
query_vector: Vec<f32>,
query_text: String,
top_k: usize,
ef_search: usize,
fuzzy: bool,
/// Weight for vector results in RRF (0.0–1.0). Default: 0.5.
vector_weight: f32,
filter_bitmap: Option<SurrogateBitmap>,
/// RLS post-fusion filters.
rls_filters: Vec<u8>,
/// SELECT-list alias the response should use for the RRF score
/// column. `None` means the executor uses the fixed internal name
/// `rrf_score`. Set by the planner from the SELECT alias for the
/// `rrf_score(...)` call.
score_alias: Option<String>,
},
/// Index a document into the inverted FTS index.
///
/// Used by the sync path when a Lite client sends an `FtsIndex` frame.
/// Origin assigns a surrogate for `(collection, doc_id)` on the Control
/// Plane before dispatch; `surrogate` is the pre-assigned value.
FtsIndexDoc {
collection: String,
/// Pre-assigned global surrogate for `(collection, doc_id)`.
surrogate: nodedb_types::Surrogate,
/// Concatenated text to index.
text: String,
},
/// Remove a document from the inverted FTS index.
///
/// Used by the sync path when a Lite client sends an `FtsDelete` frame.
FtsDeleteDoc {
collection: String,
/// Pre-assigned global surrogate for `(collection, doc_id)`.
surrogate: nodedb_types::Surrogate,
},
/// Three-source hybrid search: vector + BM25 text + graph BFS, fused via weighted RRF.
///
/// Extends `HybridSearch` with an optional graph BFS leg. The graph leg
/// performs a BFS from `graph_seed_id` up to `graph_depth` hops, filtering
/// edges by `graph_edge_label` when set. All three ranked lists are passed
/// to `reciprocal_rank_fusion_weighted` with per-source k-constants.
HybridSearchTriple {
collection: String,
query_vector: Vec<f32>,
query_text: String,
/// Node id used as the BFS seed for the graph leg.
graph_seed_id: String,
/// Maximum BFS depth from the seed node.
graph_depth: usize,
/// Edge label filter for graph BFS. `None` = all edges.
graph_edge_label: Option<String>,
top_k: usize,
ef_search: usize,
fuzzy: bool,
/// Per-source RRF k constants: (vector_k, text_k, graph_k).
rrf_k: (f64, f64, f64),
filter_bitmap: Option<SurrogateBitmap>,
/// RLS post-fusion filters.
rls_filters: Vec<u8>,
/// SELECT-list alias for the fused RRF score column.
score_alias: Option<String>,
},
}