codegen: add support for descending indexes

This commit is contained in:
Jussi Saurio
2025-04-13 15:18:15 +03:00
parent b1073da4a5
commit 1189b7a288
4 changed files with 377 additions and 153 deletions

View File

@@ -845,27 +845,37 @@ fn emit_seek(
is_index: bool,
) -> Result<()> {
let Some(seek) = seek_def.seek.as_ref() else {
assert!(seek_def.iter_dir == IterationDirection::Backwards, "A SeekDef without a seek operation should only be used in backwards iteration direction");
program.emit_insn(Insn::Last {
cursor_id: seek_cursor_id,
pc_if_empty: loop_end,
});
// If there is no seek key, we start from the first or last row of the index,
// depending on the iteration direction.
match seek_def.iter_dir {
IterationDirection::Forwards => {
program.emit_insn(Insn::Rewind {
cursor_id: seek_cursor_id,
pc_if_empty: loop_end,
});
}
IterationDirection::Backwards => {
program.emit_insn(Insn::Last {
cursor_id: seek_cursor_id,
pc_if_empty: loop_end,
});
}
}
return Ok(());
};
// We allocated registers for the full index key, but our seek key might not use the full index key.
// Later on for the termination condition we will overwrite the NULL registers.
// See [crate::translate::optimizer::build_seek_def] for more details about in which cases we do and don't use the full index key.
for i in 0..seek_def.key.len() {
let reg = start_reg + i;
if i >= seek.len {
if seek_def.null_pad_unset_cols() {
if seek.null_pad {
program.emit_insn(Insn::Null {
dest: reg,
dest_end: None,
});
}
} else {
let expr = &seek_def.key[i];
let expr = &seek_def.key[i].0;
translate_expr(program, Some(tables), &expr, reg, &t_ctx.resolver)?;
// If the seek key column is not verifiably non-NULL, we need check whether it is NULL,
// and if so, jump to the loop end.
@@ -879,7 +889,7 @@ fn emit_seek(
}
}
}
let num_regs = if seek_def.null_pad_unset_cols() {
let num_regs = if seek.null_pad {
seek_def.key.len()
} else {
seek.len
@@ -943,19 +953,46 @@ fn emit_seek_termination(
program.resolve_label(loop_start, program.offset());
return Ok(());
};
let num_regs = termination.len;
// If the seek termination was preceded by a seek (which happens in most cases),
// we can re-use the registers that were allocated for the full index key.
let start_idx = seek_def.seek.as_ref().map_or(0, |seek| seek.len);
for i in start_idx..termination.len {
// How many non-NULL values were used for seeking.
let seek_len = seek_def.seek.as_ref().map_or(0, |seek| seek.len);
// How many values will be used for the termination condition.
let num_regs = if termination.null_pad {
seek_def.key.len()
} else {
termination.len
};
for i in 0..seek_def.key.len() {
let reg = start_reg + i;
translate_expr(
program,
Some(tables),
&seek_def.key[i],
reg,
&t_ctx.resolver,
)?;
let is_last = i == seek_def.key.len() - 1;
// For all index key values apart from the last one, we are guaranteed to use the same values
// as were used for the seek, so we don't need to emit them again.
if i < seek_len && !is_last {
continue;
}
// For the last index key value, we need to emit a NULL if the termination condition is NULL-padded.
// See [SeekKey::null_pad] and [crate::translate::optimizer::build_seek_def] for why this is the case.
if i >= termination.len && !termination.null_pad {
continue;
}
if is_last && termination.null_pad {
program.emit_insn(Insn::Null {
dest: reg,
dest_end: None,
});
// if the seek key is shorter than the termination key, we need to translate the remaining suffix of the termination key.
// if not, we just reuse what was emitted for the seek.
} else if seek_len < termination.len {
translate_expr(
program,
Some(tables),
&seek_def.key[i].0,
reg,
&t_ctx.resolver,
)?;
}
}
program.resolve_label(loop_start, program.offset());
let mut rowid_reg = None;

View File

@@ -779,6 +779,7 @@ pub fn try_extract_index_search_from_where_clause(
pub struct IndexConstraint {
position_in_where_clause: (usize, BinaryExprSide),
operator: ast::Operator,
index_column_sort_order: SortOrder,
}
/// Helper enum for [IndexConstraint] to indicate which side of a binary comparison expression is being compared to the index column.
@@ -898,6 +899,7 @@ fn find_index_constraints(
out_constraints.push(IndexConstraint {
operator: *operator,
position_in_where_clause: (position_in_where_clause, BinaryExprSide::Rhs),
index_column_sort_order: index.columns[position_in_index].order,
});
found = true;
break;
@@ -907,6 +909,7 @@ fn find_index_constraints(
out_constraints.push(IndexConstraint {
operator: opposite_cmp_op(*operator), // swap the operator since e.g. if condition is 5 >= x, we want to use x <= 5
position_in_where_clause: (position_in_where_clause, BinaryExprSide::Lhs),
index_column_sort_order: index.columns[position_in_index].order,
});
found = true;
break;
@@ -963,7 +966,7 @@ pub fn build_seek_def_from_index_constraints(
} else {
*rhs
};
key.push(cmp_expr);
key.push((cmp_expr, constraint.index_column_sort_order));
}
// We know all but potentially the last term is an equality, so we can use the operator of the last term
@@ -995,46 +998,80 @@ pub fn build_seek_def_from_index_constraints(
/// 2. In contrast, having (x=10 AND y>20) forms a valid index key GT(x:10, y:20) because after the seek, we can simply terminate as soon as x > 10,
/// i.e. use GT(x:10, y:20) as the [SeekKey] and GT(x:10) as the [TerminationKey].
///
/// The preceding examples are for an ascending index. The logic is similar for descending indexes, but an important distinction is that
/// since a descending index is laid out in reverse order, the comparison operators are reversed, e.g. LT becomes GT, LE becomes GE, etc.
/// So when you see e.g. a SeekOp::GT below for a descending index, it actually means that we are seeking the first row where the index key is LESS than the seek key.
///
fn build_seek_def(
op: ast::Operator,
iter_dir: IterationDirection,
key: Vec<ast::Expr>,
key: Vec<(ast::Expr, SortOrder)>,
) -> Result<SeekDef> {
let key_len = key.len();
let sort_order_of_last_key = key.last().unwrap().1;
// For the commented examples below, keep in mind that since a descending index is laid out in reverse order, the comparison operators are reversed, e.g. LT becomes GT, LE becomes GE, etc.
// Also keep in mind that index keys are compared based on the number of columns given, so for example:
// - if key is GT(x:10), then (x=10, y=usize::MAX) is not GT because only X is compared. (x=11, y=<any>) is GT.
// - if key is GT(x:10, y:20), then (x=10, y=21) is GT because both X and Y are compared.
// - if key is GT(x:10, y:NULL), then (x=10, y=0) is GT because NULL is always LT in index key comparisons.
Ok(match (iter_dir, op) {
// Forwards, EQ:
// Example: (x=10 AND y=20)
// Seek key: GE(x:10, y:20)
// Termination key: GT(x:10, y:20)
// Seek key: start from the first GE(x:10, y:20)
// Termination key: end at the first GT(x:10, y:20)
// Ascending vs descending doesn't matter because all the comparisons are equalities.
(IterationDirection::Forwards, ast::Operator::Equals) => SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
null_pad: false,
op: SeekOp::GE,
}),
termination: Some(TerminationKey {
len: key_len,
null_pad: false,
op: SeekOp::GT,
}),
},
// Forwards, GT:
// Example: (x=10 AND y>20)
// Seek key: GT(x:10, y:20)
// Termination key: GT(x:10)
// Ascending index example: (x=10 AND y>20)
// Seek key: start from the first GT(x:10, y:20), e.g. (x=10, y=21)
// Termination key: end at the first GT(x:10), e.g. (x=11, y=0)
//
// Descending index example: (x=10 AND y>20)
// Seek key: start from the first LE(x:10), e.g. (x=10, y=usize::MAX), so reversed -> GE(x:10)
// Termination key: end at the first LE(x:10, y:20), e.g. (x=10, y=20) so reversed -> GE(x:10, y:20)
(IterationDirection::Forwards, ast::Operator::Greater) => {
let termination_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len, key_len - 1, SeekOp::GT, SeekOp::GT)
} else {
(
key_len - 1,
key_len,
SeekOp::LE.reverse(),
SeekOp::LE.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
op: SeekOp::GT,
}),
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: false,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: SeekOp::GT,
op: termination_op,
null_pad: false,
})
} else {
None
@@ -1042,22 +1079,42 @@ fn build_seek_def(
}
}
// Forwards, GE:
// Example: (x=10 AND y>=20)
// Seek key: GE(x:10, y:20)
// Termination key: GT(x:10)
// Ascending index example: (x=10 AND y>=20)
// Seek key: start from the first GE(x:10, y:20), e.g. (x=10, y=20)
// Termination key: end at the first GT(x:10), e.g. (x=11, y=0)
//
// Descending index example: (x=10 AND y>=20)
// Seek key: start from the first LE(x:10), e.g. (x=10, y=usize::MAX), so reversed -> GE(x:10)
// Termination key: end at the first LT(x:10, y:20), e.g. (x=10, y=19), so reversed -> GT(x:10, y:20)
(IterationDirection::Forwards, ast::Operator::GreaterEquals) => {
let termination_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len, key_len - 1, SeekOp::GE, SeekOp::GT)
} else {
(
key_len - 1,
key_len,
SeekOp::LE.reverse(),
SeekOp::LT.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
op: SeekOp::GE,
}),
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: false,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: SeekOp::GT,
op: termination_op,
null_pad: false,
})
} else {
None
@@ -1065,70 +1122,142 @@ fn build_seek_def(
}
}
// Forwards, LT:
// Example: (x=10 AND y<20)
// Seek key: GT(x:10, y: NULL) // NULL is always LT, indicating we only care about x
// Termination key: GE(x:10, y:20)
(IterationDirection::Forwards, ast::Operator::Less) => SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len - 1,
op: SeekOp::GT,
}),
termination: Some(TerminationKey {
len: key_len,
op: SeekOp::GE,
}),
},
// Ascending index example: (x=10 AND y<20)
// Seek key: start from the first GT(x:10, y: NULL), e.g. (x=10, y=0)
// Termination key: end at the first GE(x:10, y:20), e.g. (x=10, y=20)
//
// Descending index example: (x=10 AND y<20)
// Seek key: start from the first LT(x:10, y:20), e.g. (x=10, y=19), so reversed -> GT(x:10, y:20)
// Termination key: end at the first LT(x:10), e.g. (x=9, y=usize::MAX), so reversed -> GE(x:10, NULL); i.e. GE the smallest possible (x=10, y) combination (NULL is always LT)
(IterationDirection::Forwards, ast::Operator::Less) => {
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len - 1, key_len, SeekOp::GT, SeekOp::GE)
} else {
(key_len, key_len - 1, SeekOp::GT, SeekOp::GE)
};
SeekDef {
key,
iter_dir,
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: sort_order_of_last_key == SortOrder::Asc,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: termination_op,
null_pad: sort_order_of_last_key == SortOrder::Desc,
})
} else {
None
},
}
}
// Forwards, LE:
// Example: (x=10 AND y<=20)
// Seek key: GE(x:10, y:NULL) // NULL is always LT, indicating we only care about x
// Termination key: GT(x:10, y:20)
(IterationDirection::Forwards, ast::Operator::LessEquals) => SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len - 1,
op: SeekOp::GE,
}),
termination: Some(TerminationKey {
len: key_len,
op: SeekOp::GT,
}),
},
// Ascending index example: (x=10 AND y<=20)
// Seek key: start from the first GE(x:10, y:NULL), e.g. (x=10, y=0)
// Termination key: end at the first GT(x:10, y:20), e.g. (x=10, y=21)
//
// Descending index example: (x=10 AND y<=20)
// Seek key: start from the first LE(x:10, y:20), e.g. (x=10, y=20) so reversed -> GE(x:10, y:20)
// Termination key: end at the first LT(x:10), e.g. (x=9, y=usize::MAX), so reversed -> GE(x:10, NULL); i.e. GE the smallest possible (x=10, y) combination (NULL is always LT)
(IterationDirection::Forwards, ast::Operator::LessEquals) => {
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len - 1, key_len, SeekOp::GT, SeekOp::GT)
} else {
(
key_len,
key_len - 1,
SeekOp::LE.reverse(),
SeekOp::LE.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: sort_order_of_last_key == SortOrder::Asc,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: termination_op,
null_pad: sort_order_of_last_key == SortOrder::Desc,
})
} else {
None
},
}
}
// Backwards, EQ:
// Example: (x=10 AND y=20)
// Seek key: LE(x:10, y:20)
// Termination key: LT(x:10, y:20)
// Seek key: start from the last LE(x:10, y:20)
// Termination key: end at the first LT(x:10, y:20)
// Ascending vs descending doesn't matter because all the comparisons are equalities.
(IterationDirection::Backwards, ast::Operator::Equals) => SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
op: SeekOp::LE,
null_pad: false,
}),
termination: Some(TerminationKey {
len: key_len,
op: SeekOp::LT,
null_pad: false,
}),
},
// Backwards, LT:
// Example: (x=10 AND y<20)
// Seek key: LT(x:10, y:20)
// Termination key: LT(x:10)
// Ascending index example: (x=10 AND y<20)
// Seek key: start from the last LT(x:10, y:20), e.g. (x=10, y=19)
// Termination key: end at the first LE(x:10, NULL), e.g. (x=9, y=usize::MAX)
//
// Descending index example: (x=10 AND y<20)
// Seek key: start from the last GT(x:10, y:NULL), e.g. (x=10, y=0) so reversed -> LT(x:10, NULL)
// Termination key: end at the first GE(x:10, y:20), e.g. (x=10, y=20) so reversed -> LE(x:10, y:20)
(IterationDirection::Backwards, ast::Operator::Less) => {
let termination_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len, key_len - 1, SeekOp::LT, SeekOp::LE)
} else {
(
key_len - 1,
key_len,
SeekOp::GT.reverse(),
SeekOp::GE.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
op: SeekOp::LT,
}),
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: sort_order_of_last_key == SortOrder::Desc,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: SeekOp::LT,
op: termination_op,
null_pad: sort_order_of_last_key == SortOrder::Asc,
})
} else {
None
@@ -1136,22 +1265,42 @@ fn build_seek_def(
}
}
// Backwards, LE:
// Example: (x=10 AND y<=20)
// Seek key: LE(x:10, y:20)
// Termination key: LT(x:10)
// Ascending index example: (x=10 AND y<=20)
// Seek key: start from the last LE(x:10, y:20), e.g. (x=10, y=20)
// Termination key: end at the first LT(x:10, NULL), e.g. (x=9, y=usize::MAX)
//
// Descending index example: (x=10 AND y<=20)
// Seek key: start from the last GT(x:10, NULL), e.g. (x=10, y=0) so reversed -> LT(x:10, NULL)
// Termination key: end at the first GT(x:10, y:20), e.g. (x=10, y=21) so reversed -> LT(x:10, y:20)
(IterationDirection::Backwards, ast::Operator::LessEquals) => {
let termination_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len, key_len - 1, SeekOp::LE, SeekOp::LE)
} else {
(
key_len - 1,
key_len,
SeekOp::GT.reverse(),
SeekOp::GT.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: Some(SeekKey {
len: key_len,
op: SeekOp::LE,
}),
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: seek_op,
null_pad: sort_order_of_last_key == SortOrder::Desc,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: SeekOp::LT,
op: termination_op,
null_pad: sort_order_of_last_key == SortOrder::Asc,
})
} else {
None
@@ -1159,49 +1308,89 @@ fn build_seek_def(
}
}
// Backwards, GT:
// Example: (x=10 AND y>20)
// Seek key: LE(x:10) // try to find the last row where x = 10, not considering y at all.
// Termination key: LE(x:10, y:20)
// Ascending index example: (x=10 AND y>20)
// Seek key: start from the last LE(x:10), e.g. (x=10, y=usize::MAX)
// Termination key: end at the first LE(x:10, y:20), e.g. (x=10, y=20)
//
// Descending index example: (x=10 AND y>20)
// Seek key: start from the last GT(x:10, y:20), e.g. (x=10, y=21) so reversed -> LT(x:10, y:20)
// Termination key: end at the first GT(x:10), e.g. (x=11, y=0) so reversed -> LT(x:10)
(IterationDirection::Backwards, ast::Operator::Greater) => {
let seek_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len - 1, key_len, SeekOp::LE, SeekOp::LE)
} else {
(
key_len,
key_len - 1,
SeekOp::GT.reverse(),
SeekOp::GT.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: SeekOp::LE,
op: seek_op,
null_pad: false,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: termination_op,
null_pad: false,
})
} else {
None
},
termination: Some(TerminationKey {
len: key_len,
op: SeekOp::LE,
}),
}
}
// Backwards, GE:
// Example: (x=10 AND y>=20)
// Seek key: LE(x:10) // try to find the last row where x = 10, not considering y at all.
// Termination key: LT(x:10, y:20)
// Ascending index example: (x=10 AND y>=20)
// Seek key: start from the last LE(x:10), e.g. (x=10, y=usize::MAX)
// Termination key: end at the first LT(x:10, y:20), e.g. (x=10, y=19)
//
// Descending index example: (x=10 AND y>=20)
// Seek key: start from the last GE(x:10, y:20), e.g. (x=10, y=20) so reversed -> LE(x:10, y:20)
// Termination key: end at the first GT(x:10), e.g. (x=11, y=0) so reversed -> LT(x:10)
(IterationDirection::Backwards, ast::Operator::GreaterEquals) => {
let seek_key_len = key_len - 1;
let (seek_key_len, termination_key_len, seek_op, termination_op) =
if sort_order_of_last_key == SortOrder::Asc {
(key_len - 1, key_len, SeekOp::LE, SeekOp::LT)
} else {
(
key_len,
key_len - 1,
SeekOp::GE.reverse(),
SeekOp::GT.reverse(),
)
};
SeekDef {
key,
iter_dir,
seek: if seek_key_len > 0 {
Some(SeekKey {
len: seek_key_len,
op: SeekOp::LE,
op: seek_op,
null_pad: false,
})
} else {
None
},
termination: if termination_key_len > 0 {
Some(TerminationKey {
len: termination_key_len,
op: termination_op,
null_pad: false,
})
} else {
None
},
termination: Some(TerminationKey {
len: key_len,
op: SeekOp::LT,
}),
}
}
(_, op) => {
@@ -1252,7 +1441,8 @@ pub fn try_extract_rowid_search_expression(
| ast::Operator::Less
| ast::Operator::LessEquals => {
let rhs_owned = rhs.take_ownership();
let seek_def = build_seek_def(*operator, iter_dir, vec![rhs_owned])?;
let seek_def =
build_seek_def(*operator, iter_dir, vec![(rhs_owned, SortOrder::Asc)])?;
return Ok(Some(Search::Seek {
index: None,
seek_def,
@@ -1280,7 +1470,8 @@ pub fn try_extract_rowid_search_expression(
| ast::Operator::LessEquals => {
let lhs_owned = lhs.take_ownership();
let op = opposite_cmp_op(*operator);
let seek_def = build_seek_def(op, iter_dir, vec![lhs_owned])?;
let seek_def =
build_seek_def(op, iter_dir, vec![(lhs_owned, SortOrder::Asc)])?;
return Ok(Some(Search::Seek {
index: None,
seek_def,

View File

@@ -1,5 +1,5 @@
use core::fmt;
use limbo_sqlite3_parser::ast;
use limbo_sqlite3_parser::ast::{self, SortOrder};
use std::{
cmp::Ordering,
fmt::{Display, Formatter},
@@ -391,10 +391,10 @@ impl TableReference {
pub struct SeekDef {
/// The key to use when seeking and when terminating the scan that follows the seek.
/// For example, given:
/// - CREATE INDEX i ON t (x, y)
/// - CREATE INDEX i ON t (x, y desc)
/// - SELECT * FROM t WHERE x = 1 AND y >= 30
/// The key is [1, 30]
pub key: Vec<ast::Expr>,
/// The key is [(1, ASC), (30, DESC)]
pub key: Vec<(ast::Expr, SortOrder)>,
/// The condition to use when seeking. See [SeekKey] for more details.
pub seek: Option<SeekKey>,
/// The condition to use when terminating the scan that follows the seek. See [TerminationKey] for more details.
@@ -403,35 +403,22 @@ pub struct SeekDef {
pub iter_dir: IterationDirection,
}
impl SeekDef {
/// Whether we should null pad unset columns when seeking.
/// This is only done for forward seeks.
/// The reason it is done is that sometimes our full index key is not used in seeking.
/// See [SeekKey] for more details.
///
/// For example, given:
/// - CREATE INDEX i ON t (x, y)
/// - SELECT * FROM t WHERE x = 1 AND y < 30
/// We want to seek to the first row where x = 1, and then iterate forwards.
/// In this case, the seek key is GT(1, NULL) since '30' cannot be used to seek (since we want y < 30),
/// and any value of y will be greater than NULL.
///
/// In backwards iteration direction, we do not null pad because we want to seek to the last row that matches the seek key.
/// For example, given:
/// - CREATE INDEX i ON t (x, y)
/// - SELECT * FROM t WHERE x = 1 AND y > 30 ORDER BY y
/// We want to seek to the last row where x = 1, and then iterate backwards.
/// In this case, the seek key is just LE(1) so any row with x = 1 will be a match.
pub fn null_pad_unset_cols(&self) -> bool {
self.iter_dir == IterationDirection::Forwards
}
}
/// A condition to use when seeking.
#[derive(Debug, Clone)]
pub struct SeekKey {
/// How many columns from [SeekDef::key] are used in seeking.
pub len: usize,
/// Whether to NULL pad the last column of the seek key to match the length of [SeekDef::key].
/// The reason it is done is that sometimes our full index key is not used in seeking,
/// but we want to find the lowest value that matches the non-null prefix of the key.
/// For example, given:
/// - CREATE INDEX i ON t (x, y)
/// - SELECT * FROM t WHERE x = 1 AND y < 30
/// We want to seek to the first row where x = 1, and then iterate forwards.
/// In this case, the seek key is GT(1, NULL) since NULL is always LT in index key comparisons.
/// We can't use just GT(1) because in index key comparisons, only the given number of columns are compared,
/// so this means any index keys with (x=1) will compare equal, e.g. (x=1, y=usize::MAX) will compare equal to the seek key (x:1)
pub null_pad: bool,
/// The comparison operator to use when seeking.
pub op: SeekOp,
}
@@ -441,6 +428,9 @@ pub struct SeekKey {
pub struct TerminationKey {
/// How many columns from [SeekDef::key] are used in terminating the scan that follows the seek.
pub len: usize,
/// Whether to NULL pad the last column of the termination key to match the length of [SeekDef::key].
/// See [SeekKey::null_pad].
pub null_pad: bool,
/// The comparison operator to use when terminating the scan that follows the seek.
pub op: SeekOp,
}

View File

@@ -12,6 +12,7 @@ use crate::{
},
printf::exec_printf,
},
types::compare_immutable,
};
use std::{borrow::BorrowMut, rc::Rc, sync::Arc};
@@ -2053,9 +2054,11 @@ pub fn op_idx_ge(
let record_from_regs = make_record(&state.registers, start_reg, num_regs);
let pc = if let Some(ref idx_record) = *cursor.record() {
// Compare against the same number of values
let ord = idx_record.get_values()[..record_from_regs.len()]
.partial_cmp(&record_from_regs.get_values()[..])
.unwrap();
let idx_values = idx_record.get_values();
let idx_values = &idx_values[..record_from_regs.len()];
let record_values = record_from_regs.get_values();
let record_values = &record_values[..idx_values.len()];
let ord = compare_immutable(&idx_values, &record_values, cursor.index_key_sort_order);
if ord.is_ge() {
target_pc.to_offset_int()
} else {
@@ -2111,9 +2114,10 @@ pub fn op_idx_le(
let record_from_regs = make_record(&state.registers, start_reg, num_regs);
let pc = if let Some(ref idx_record) = *cursor.record() {
// Compare against the same number of values
let ord = idx_record.get_values()[..record_from_regs.len()]
.partial_cmp(&record_from_regs.get_values()[..])
.unwrap();
let idx_values = idx_record.get_values();
let idx_values = &idx_values[..record_from_regs.len()];
let record_values = record_from_regs.get_values();
let ord = compare_immutable(&idx_values, &record_values, cursor.index_key_sort_order);
if ord.is_le() {
target_pc.to_offset_int()
} else {
@@ -2151,9 +2155,10 @@ pub fn op_idx_gt(
let record_from_regs = make_record(&state.registers, start_reg, num_regs);
let pc = if let Some(ref idx_record) = *cursor.record() {
// Compare against the same number of values
let ord = idx_record.get_values()[..record_from_regs.len()]
.partial_cmp(&record_from_regs.get_values()[..])
.unwrap();
let idx_values = idx_record.get_values();
let idx_values = &idx_values[..record_from_regs.len()];
let record_values = record_from_regs.get_values();
let ord = compare_immutable(&idx_values, &record_values, cursor.index_key_sort_order);
if ord.is_gt() {
target_pc.to_offset_int()
} else {
@@ -2191,9 +2196,10 @@ pub fn op_idx_lt(
let record_from_regs = make_record(&state.registers, start_reg, num_regs);
let pc = if let Some(ref idx_record) = *cursor.record() {
// Compare against the same number of values
let ord = idx_record.get_values()[..record_from_regs.len()]
.partial_cmp(&record_from_regs.get_values()[..])
.unwrap();
let idx_values = idx_record.get_values();
let idx_values = &idx_values[..record_from_regs.len()];
let record_values = record_from_regs.get_values();
let ord = compare_immutable(&idx_values, &record_values, cursor.index_key_sort_order);
if ord.is_lt() {
target_pc.to_offset_int()
} else {