Skip to content

Commit 449bbc0

Browse files
authored
fix(optimizer): preserve residual index predicates (#368)
1 parent 8b20e11 commit 449bbc0

19 files changed

Lines changed: 1054 additions & 422 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<p align="center">
2020
<a href="https://github.com/KipData/KiteSQL/actions/workflows/ci.yml"><img src="https://github.com/KipData/KiteSQL/actions/workflows/ci.yml/badge.svg" alt="CI"></img></a>
2121
<a href="https://deepwiki.com/KipData/KiteSQL"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></img></a>
22-
<a href="https://discord.gg/89cxtD2g"><img src="https://img.shields.io/badge/Discord-Join_us-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
22+
<a href="https://discord.gg/dU8eVGpJ8h"><img src="https://img.shields.io/badge/Discord-Join_us-5865F2?logo=discord&logoColor=white" alt="Discord"></a>
2323
<a href="https://crates.io/crates/kite_sql/"><img src="https://img.shields.io/crates/v/kite_sql.svg"></a>
2424
<a href="https://github.com/KipData/KiteSQL" target="_blank">
2525
<img src="https://img.shields.io/github/stars/KipData/KiteSQL.svg?style=social" alt="github star"/>

scripts/run_tpcc_matrix.sh

Lines changed: 0 additions & 177 deletions
This file was deleted.

src/db.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ fn default_optimizer_pipeline() -> HepOptimizerPipeline {
340340
vec![
341341
NormalizationRuleImpl::LimitProjectTranspose,
342342
NormalizationRuleImpl::PushLimitThroughJoin,
343-
NormalizationRuleImpl::PushLimitIntoTableScan,
344343
],
345344
)
346345
.before_batch(
@@ -370,6 +369,11 @@ fn default_optimizer_pipeline() -> HepOptimizerPipeline {
370369
HepBatchStrategy::once_topdown(),
371370
vec![NormalizationRuleImpl::EvaluatorBind],
372371
)
372+
.after_batch(
373+
"Limit Into Scan".to_string(),
374+
HepBatchStrategy::fix_point_topdown(10),
375+
vec![NormalizationRuleImpl::PushLimitIntoTableScan],
376+
)
373377
.implementations(vec![
374378
// DQL
375379
ImplementationRuleImpl::SimpleAggregate,

src/execution/dml/update.rs

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,14 @@ use crate::iter_ext::Itertools;
2323
use crate::planner::operator::update::UpdateOperator;
2424
use crate::planner::LogicalPlan;
2525
use crate::storage::Transaction;
26-
use crate::types::index::Index;
26+
use crate::types::index::{Index, IndexMeta, IndexType};
2727
use crate::types::tuple::{Schema, Tuple};
2828
use crate::types::tuple_builder::TupleBuilder;
29-
use std::{collections::HashMap, mem};
29+
use crate::types::ColumnId;
30+
use std::{
31+
collections::{HashMap, HashSet},
32+
mem,
33+
};
3034

3135
pub struct Update {
3236
table_name: TableName,
@@ -79,6 +83,24 @@ impl<'a, T: Transaction + 'a> WriteExecutor<'a, T> for Update {
7983
}
8084
}
8185

86+
impl Update {
87+
fn index_needs_update(
88+
index_meta: &IndexMeta,
89+
updated_column_ids: &HashSet<ColumnId>,
90+
updates_primary_key: bool,
91+
) -> bool {
92+
if matches!(index_meta.ty, IndexType::PrimaryKey { .. }) {
93+
return false;
94+
}
95+
96+
updates_primary_key
97+
|| index_meta
98+
.column_ids
99+
.iter()
100+
.any(|column_id| updated_column_ids.contains(column_id))
101+
}
102+
}
103+
82104
impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Update {
83105
fn next_tuple(
84106
&mut self,
@@ -91,8 +113,14 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Update {
91113
};
92114

93115
let mut exprs_map = HashMap::with_capacity(self.value_exprs.len());
116+
let mut updated_column_ids = HashSet::with_capacity(self.value_exprs.len());
94117
for (column, expr) in self.value_exprs.drain(..) {
95-
exprs_map.insert(plan_arena.column(column).id(), expr);
118+
let column = plan_arena.column(column);
119+
let column_id = column
120+
.id()
121+
.ok_or_else(|| DatabaseError::column_not_found(column.name().to_string()))?;
122+
updated_column_ids.insert(column_id);
123+
exprs_map.insert(column_id, expr);
96124
}
97125

98126
let table_cache = arena.context().table_cache();
@@ -104,6 +132,13 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Update {
104132
.transpose()?
105133
};
106134
if let Some(table_snapshot) = table_snapshot {
135+
let updates_primary_key = table_snapshot.primary_key_indices.iter().any(|index| {
136+
table_snapshot
137+
.columns
138+
.get(*index)
139+
.and_then(|column| plan_arena.column(*column).id())
140+
.is_some_and(|column_id| updated_column_ids.contains(&column_id))
141+
});
107142
let serializers = self
108143
.input_schema
109144
.iter()
@@ -118,17 +153,30 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Update {
118153
let Some(old_pk) = arena.result_tuple().pk.clone() else {
119154
continue;
120155
};
121-
for (index_meta, exprs) in table_snapshot.index_metas.iter() {
156+
157+
let mut old_index_values = Vec::new();
158+
for (index_offset, (index_meta, exprs)) in
159+
table_snapshot.index_metas.iter().enumerate()
160+
{
122161
let index_meta = plan_arena.index(*index_meta);
123-
with_projection_tmp_value(arena, None, exprs, |arena, value| {
124-
let mut state = arena.local_state(plan_arena);
125-
let (transaction, table_codec) = state.transaction_codec_mut();
126-
let index = Index::new(index_meta.id, &value, index_meta.ty);
127-
transaction.del_index(table_codec, &self.table_name, &index, &old_pk)
162+
if !Self::index_needs_update(
163+
index_meta,
164+
&updated_column_ids,
165+
updates_primary_key,
166+
) {
167+
continue;
168+
}
169+
170+
with_projection_tmp_value(arena, None, exprs, |_, value| {
171+
old_index_values.push((index_offset, value));
172+
Ok(())
128173
})?;
129174
}
130175
for (i, column) in self.input_schema.iter().enumerate() {
131-
if let Some(expr) = exprs_map.get(&plan_arena.column(*column).id()) {
176+
let Some(column_id) = plan_arena.column(*column).id() else {
177+
continue;
178+
};
179+
if let Some(expr) = exprs_map.get(&column_id) {
132180
let value = expr.eval(Some(arena.result_tuple()))?;
133181
arena.result_tuple_mut().values[i] = value;
134182
}
@@ -140,19 +188,35 @@ impl<'a, T: Transaction + 'a> ExecutorNode<'a, T> for Update {
140188
);
141189
arena.result_tuple_mut().pk = Some(new_pk.clone());
142190

143-
if new_pk != old_pk {
191+
let primary_key_changed = new_pk != old_pk;
192+
if primary_key_changed {
144193
let mut state = arena.local_state(plan_arena);
145194
let (transaction, table_codec) = state.transaction_codec_mut();
146195
transaction.remove_tuple(table_codec, &self.table_name, &old_pk)?;
147196
is_overwrite = false;
148197
}
149-
for (index_meta, exprs) in table_snapshot.index_metas.iter() {
198+
199+
for (index_offset, old_value) in old_index_values {
200+
let (index_meta, exprs) = &table_snapshot.index_metas[index_offset];
150201
let index_meta = plan_arena.index(*index_meta);
202+
let index_id = index_meta.id;
203+
let index_ty = index_meta.ty;
151204
with_projection_tmp_value(arena, None, exprs, |arena, value| {
205+
if !primary_key_changed && old_value == value {
206+
return Ok(());
207+
}
208+
152209
let mut state = arena.local_state(plan_arena);
153210
let (transaction, table_codec) = state.transaction_codec_mut();
154-
let index = Index::new(index_meta.id, &value, index_meta.ty);
155-
transaction.add_index(table_codec, &self.table_name, index, &new_pk)
211+
let old_index = Index::new(index_id, &old_value, index_ty);
212+
transaction.del_index(
213+
table_codec,
214+
&self.table_name,
215+
&old_index,
216+
&old_pk,
217+
)?;
218+
let new_index = Index::new(index_id, &value, index_ty);
219+
transaction.add_index(table_codec, &self.table_name, new_index, &new_pk)
156220
})?;
157221
}
158222

0 commit comments

Comments
 (0)