Skip to content

Commit 9ad596c

Browse files
authored
Feat/Index Cover (#296)
* feat: support index cover & fix is_null can't use index * chore: add unit test for index cover & fix string_encode and string_decode * chore: add test for composite index trailing columns cover * chore: fix null_first in sort
1 parent b9e77c7 commit 9ad596c

File tree

18 files changed

+1142
-196
lines changed

18 files changed

+1142
-196
lines changed

Makefile

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Helper variables (override on invocation if needed).
2+
CARGO ?= cargo
3+
WASM_PACK ?= wasm-pack
4+
SQLLOGIC_PATH ?= tests/slt/**/*.slt
5+
6+
.PHONY: test test-wasm test-slt test-all wasm-build
7+
8+
## Run default Rust tests in the current environment (non-WASM).
9+
test:
10+
$(CARGO) test
11+
12+
## Build the WebAssembly package (artifact goes to ./pkg).
13+
wasm-build:
14+
$(WASM_PACK) build --release --target nodejs
15+
16+
## Execute wasm-bindgen tests under Node.js (wasm32 target).
17+
test-wasm:
18+
$(WASM_PACK) test --node --release
19+
20+
## Run the sqllogictest harness against the configured .slt suite.
21+
test-slt:
22+
$(CARGO) run -p sqllogictest-test -- --path $(SQLLOGIC_PATH)
23+
24+
## Convenience target to run every suite in sequence.
25+
test-all: test test-wasm test-slt

src/execution/dql/index_scan.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,31 @@ use crate::planner::operator::table_scan::TableScanOperator;
44
use crate::storage::{Iter, StatisticsMetaCache, TableCache, Transaction, ViewCache};
55
use crate::throw;
66
use crate::types::index::IndexMetaRef;
7+
use crate::types::serialize::TupleValueSerializableImpl;
78

89
pub(crate) struct IndexScan {
910
op: TableScanOperator,
1011
index_by: IndexMetaRef,
1112
ranges: Vec<Range>,
13+
covered_deserializers: Option<Vec<TupleValueSerializableImpl>>,
1214
}
1315

14-
impl From<(TableScanOperator, IndexMetaRef, Range)> for IndexScan {
15-
fn from((op, index_by, range): (TableScanOperator, IndexMetaRef, Range)) -> Self {
16+
impl
17+
From<(
18+
TableScanOperator,
19+
IndexMetaRef,
20+
Range,
21+
Option<Vec<TupleValueSerializableImpl>>,
22+
)> for IndexScan
23+
{
24+
fn from(
25+
(op, index_by, range, covered_deserializers): (
26+
TableScanOperator,
27+
IndexMetaRef,
28+
Range,
29+
Option<Vec<TupleValueSerializableImpl>>,
30+
),
31+
) -> Self {
1632
let ranges = match range {
1733
Range::SortedRanges(ranges) => ranges,
1834
range => vec![range],
@@ -22,6 +38,7 @@ impl From<(TableScanOperator, IndexMetaRef, Range)> for IndexScan {
2238
op,
2339
index_by,
2440
ranges,
41+
covered_deserializers,
2542
}
2643
}
2744
}
@@ -51,6 +68,7 @@ impl<'a, T: Transaction + 'a> ReadExecutor<'a, T> for IndexScan {
5168
self.index_by,
5269
self.ranges,
5370
with_pk,
71+
self.covered_deserializers,
5472
)
5573
);
5674

src/execution/dql/sort.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ impl SortBy {
183183
let mut key = BumpBytes::new_in(arena);
184184

185185
expr.eval(Some((tuple, schema)))?
186-
.memcomparable_encode(&mut key)?;
187-
if !asc {
188-
for byte in key.iter_mut() {
186+
.memcomparable_encode_with_null_order(&mut key, *nulls_first)?;
187+
188+
if !asc && key.len() > 1 {
189+
for byte in key.iter_mut().skip(1) {
189190
*byte ^= 0xFF;
190191
}
191192
}
192-
key.push(if *nulls_first { u8::MIN } else { u8::MAX });
193193
full_key.extend(key);
194194
}
195195
sort_keys.put((i, full_key))

src/execution/dql/top_k.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ fn top_sort<'a>(
4848
{
4949
let mut key = BumpBytes::new_in(arena);
5050
expr.eval(Some((&tuple, &**schema)))?
51-
.memcomparable_encode(&mut key)?;
52-
if !asc {
53-
for byte in key.iter_mut() {
51+
.memcomparable_encode_with_null_order(&mut key, *nulls_first)?;
52+
if !asc && key.len() > 1 {
53+
for byte in key.iter_mut().skip(1) {
5454
*byte ^= 0xFF;
5555
}
5656
}
57-
key.push(if *nulls_first { u8::MIN } else { u8::MAX });
5857
full_key.extend(key);
5958
}
6059

src/execution/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,11 @@ pub fn build_read<'a, T: Transaction + 'a>(
124124
if let Some(PhysicalOption::IndexScan(IndexInfo {
125125
meta,
126126
range: Some(range),
127+
covered_deserializers,
127128
})) = plan.physical_option
128129
{
129-
IndexScan::from((op, meta, range)).execute(cache, transaction)
130+
IndexScan::from((op, meta, range, covered_deserializers))
131+
.execute(cache, transaction)
130132
} else {
131133
SeqScan::from(op).execute(cache, transaction)
132134
}

src/optimizer/core/memo.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ mod tests {
212212
max: Bound::Unbounded,
213213
}
214214
])),
215+
covered_deserializers: None,
215216
}))
216217
);
217218

src/optimizer/rule/implementation/dql/table_scan.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ impl<T: Transaction> ImplementationRule<T> for IndexScanImplementation {
7979
{
8080
let mut row_count = statistics_meta.collect_count(range)?;
8181

82-
if !matches!(index_info.meta.ty, IndexType::PrimaryKey { .. }) {
82+
if index_info.covered_deserializers.is_none()
83+
&& !matches!(index_info.meta.ty, IndexType::PrimaryKey { .. })
84+
{
8385
// need to return table query(non-covering index)
8486
row_count *= 2;
8587
}

src/optimizer/rule/normalization/pushdown_predicates.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ use crate::types::index::{IndexInfo, IndexMetaRef, IndexType};
1313
use crate::types::value::DataValue;
1414
use crate::types::LogicalType;
1515
use itertools::Itertools;
16-
use std::mem;
1716
use std::ops::Bound;
1817
use std::sync::LazyLock;
18+
use std::{mem, slice};
1919

2020
static PUSH_PREDICATE_THROUGH_JOIN: LazyLock<Pattern> = LazyLock::new(|| Pattern {
2121
predicate: |op| matches!(op, Operator::Filter(_)),
@@ -215,12 +215,17 @@ impl NormalizationRule for PushPredicateIntoScan {
215215
fn apply(&self, node_id: HepNodeId, graph: &mut HepGraph) -> Result<(), DatabaseError> {
216216
if let Operator::Filter(op) = graph.operator(node_id).clone() {
217217
if let Some(child_id) = graph.eldest_child_at(node_id) {
218-
if let Operator::TableScan(child_op) = graph.operator_mut(child_id) {
219-
//FIXME: now only support `unique` and `primary key`
220-
for IndexInfo { meta, range } in &mut child_op.index_infos {
218+
if let Operator::TableScan(scan_op) = graph.operator_mut(child_id) {
219+
for IndexInfo {
220+
meta,
221+
range,
222+
covered_deserializers,
223+
} in &mut scan_op.index_infos
224+
{
221225
if range.is_some() {
222226
continue;
223227
}
228+
// range detach
224229
*range = match meta.ty {
225230
IndexType::PrimaryKey { is_multiple: false }
226231
| IndexType::Unique
@@ -232,6 +237,28 @@ impl NormalizationRule for PushPredicateIntoScan {
232237
Self::composite_range(&op, meta)?
233238
}
234239
};
240+
// try index covered
241+
let mut deserializers = Vec::with_capacity(meta.column_ids.len());
242+
let mut cover_count = 0;
243+
let index_column_types = match &meta.value_ty {
244+
LogicalType::Tuple(tys) => tys,
245+
ty => slice::from_ref(ty),
246+
};
247+
for (i, column_id) in meta.column_ids.iter().enumerate() {
248+
for column in scan_op.columns.values() {
249+
deserializers.push(
250+
if column.id().map(|id| &id == column_id).unwrap_or(false) {
251+
cover_count += 1;
252+
column.datatype().serializable()
253+
} else {
254+
index_column_types[i].skip_serializable()
255+
},
256+
);
257+
}
258+
}
259+
if cover_count == scan_op.columns.len() {
260+
*covered_deserializers = Some(deserializers)
261+
}
235262
}
236263
}
237264
}

src/planner/operator/table_scan.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl TableScanOperator {
4848
.map(|meta| IndexInfo {
4949
meta: meta.clone(),
5050
range: None,
51+
covered_deserializers: None,
5152
})
5253
.collect_vec();
5354

src/storage/memory.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ mod wasm_tests {
218218
max: Bound::Included(DataValue::Int32(2)),
219219
}],
220220
true,
221+
None,
221222
)?;
222223

223224
let mut result = Vec::new();
@@ -349,6 +350,7 @@ mod native_tests {
349350
max: Bound::Included(DataValue::Int32(2)),
350351
}],
351352
true,
353+
None,
352354
)?;
353355

354356
let mut result = Vec::new();

0 commit comments

Comments
 (0)