mirror of
https://github.com/aljazceru/turso.git
synced 2026-02-19 23:15:28 +01:00
codegen: add support for descending indexes
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user