From 3d505697de1a8e22bbae79bc1bf44906fe578f46 Mon Sep 17 00:00:00 2001 From: Emmanuel Keller Date: Mon, 16 Sep 2024 16:49:22 +0100 Subject: [PATCH] Query planner to support mixed number types on range queries (#4747) --- core/src/idx/planner/executor.rs | 374 +++++++++++++++++++++++---- core/src/idx/planner/iterators.rs | 140 ++++++++-- core/src/idx/planner/plan.rs | 2 +- sdk/tests/define.rs | 417 ++++++++++++++++++++++++++++++ 4 files changed, 863 insertions(+), 70 deletions(-) diff --git a/core/src/idx/planner/executor.rs b/core/src/idx/planner/executor.rs index 8fd8b579..2768b145 100644 --- a/core/src/idx/planner/executor.rs +++ b/core/src/idx/planner/executor.rs @@ -12,9 +12,10 @@ use crate::idx::ft::{FtIndex, MatchRef}; use crate::idx::planner::checker::{HnswConditionChecker, MTreeConditionChecker}; use crate::idx::planner::iterators::{ IndexEqualThingIterator, IndexJoinThingIterator, IndexRangeThingIterator, - IndexUnionThingIterator, IteratorRecord, IteratorRef, KnnIterator, KnnIteratorResult, - MatchesThingIterator, MultipleIterators, ThingIterator, UniqueEqualThingIterator, - UniqueJoinThingIterator, UniqueRangeThingIterator, UniqueUnionThingIterator, + IndexUnionThingIterator, IteratorRange, IteratorRecord, IteratorRef, KnnIterator, + KnnIteratorResult, MatchesThingIterator, MultipleIterators, ThingIterator, + UniqueEqualThingIterator, UniqueJoinThingIterator, UniqueRangeThingIterator, + UniqueUnionThingIterator, ValueType, }; use crate::idx::planner::knn::{KnnBruteForceResult, KnnPriorityList}; use crate::idx::planner::plan::IndexOperator::Matches; @@ -391,7 +392,7 @@ impl QueryExecutor { Ok(match io.op() { IndexOperator::Equality(value) | IndexOperator::Exactness(value) => { if let Value::Number(n) = value.as_ref() { - let values = Self::get_number_variants(n); + let values = Self::get_equal_number_variants(n); if values.len() == 1 { Some(Self::new_index_equal_iterator(irf, opt, &ix, &values[0])?) } else { @@ -444,47 +445,224 @@ impl QueryExecutor { Ok(ThingIterator::Multiples(Box::new(MultipleIterators::new(iterators)))) } - /// This function takes a reference to a `Number` enum and returns a vector of `Value` enum. - /// The `Number` enum can be either an `Int`, `Float`, or `Decimal`. - /// The function first initializes an empty vector with a capacity of 3 to store the converted values. - /// It then matches on the input number and performs the appropriate conversions. - /// For `Int`, it pushes the original `Int` value, the equivalent `Float` value, and if possible, the equivalent `Decimal` value. - /// For `Float`, it pushes the original `Float` value, the truncated `Int` value if it is a whole number, and if possible, the equivalent `Decimal` value. - /// For `Decimal`, it pushes the equivalent `Int` value if it is representable as an `i64`, and the equivalent `Float` value if it is representable as an `f64`. - /// Finally, it returns the vector of converted values. - fn get_number_variants(n: &Number) -> Vec { - let mut values = Vec::with_capacity(3); + /// This function takes a reference to a `Number` enum and a conversion function `float_to_int`. + /// It returns a tuple containing the variants of the `Number` as `Option`, `Option`, and `Option`. + /// + /// The `Number` enum can be one of the following: + /// - `Int(i64)`: Integer value. + /// - `Float(f64)`: Floating point value. + /// - `Decimal(Decimal)`: Decimal value. + /// + /// The function performs the following conversions based on the type of the `Number`: + /// - For `Int`, it returns the original `Int` value as `Option`, the equivalent `Float` value as `Option`, and the equivalent `Decimal` value as `Option`. + /// - For `Float`, it uses the provided `float_to_int` function to convert the `Float` to `Option`, returns the original `Float` value as `Option`, and the equivalent `Decimal` value as `Option`. + /// - For `Decimal`, it converts the `Decimal` to `Option` (if representable as `i64`), returns the equivalent `Float` value as `Option` (if representable as `f64`), and the original `Decimal` value as `Option`. + /// + /// # Parameters + /// - `n`: A reference to a `Number` enum. + /// - `float_to_int`: A function that converts a reference to `f64` to `Option`. + /// + /// # Returns + /// A tuple of `(Option, Option, Option)` representing the converted variants of the input `Number`. + fn get_number_variants( + n: &Number, + float_to_int: F, + ) -> (Option, Option, Option) + where + F: Fn(&f64) -> Option, + { + let oi; + let of; + let od; match n { Number::Int(i) => { - values.push(Number::Int(*i).into()); - values.push(Number::Float(*i as f64).into()); - if let Some(d) = Decimal::from_i64(*i) { - values.push(Number::Decimal(d.normalize()).into()); - } + oi = Some(*i); + of = Some(*i as f64); + od = Decimal::from_i64(*i); } Number::Float(f) => { - values.push(Number::Float(*f).into()); - if f.trunc().eq(f) { - values.push(Number::Int(*f as i64).into()); - } - if let Some(d) = Decimal::from_f64(*f) { - values.push(Number::Decimal(d.normalize()).into()); - } + oi = float_to_int(f); + of = Some(*f); + od = Decimal::from_f64(*f); } Number::Decimal(d) => { - values.push(Number::Decimal(d.normalize()).into()); - if let Some(i) = d.to_i64() { - values.push(Number::Int(i).into()); - } - if let Some(f) = d.to_f64() { - values.push(Number::Float(f).into()); - } + oi = d.to_i64(); + of = d.to_f64(); + od = Some(*d); } }; - println!("VALUES: {:?}", values); + (oi, of, od) + } + fn get_equal_number_variants(n: &Number) -> Vec { + let (oi, of, od) = Self::get_number_variants(n, |f| { + if f.trunc().eq(f) { + f.to_i64() + } else { + None + } + }); + let mut values = Vec::with_capacity(3); + if let Some(i) = oi { + values.push(Number::Int(i).into()); + } + if let Some(f) = of { + values.push(Number::Float(f).into()); + } + if let Some(d) = od { + values.push(Number::Decimal(d).into()); + } values } + fn get_range_number_from_variants(n: &Number) -> (Option, Option, Option) { + Self::get_number_variants(n, |f| f.floor().to_i64()) + } + + fn get_range_number_to_variants(n: &Number) -> (Option, Option, Option) { + Self::get_number_variants(n, |f| f.ceil().to_i64()) + } + + fn get_from_range_number_variants<'a>(from: &Number, from_inc: bool) -> Vec> { + let (from_i, from_f, from_d) = Self::get_range_number_from_variants(from); + let mut vec = Vec::with_capacity(3); + if let Some(from) = from_i { + vec.push(IteratorRange::new( + ValueType::NumberInt, + RangeValue { + value: Number::Int(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Value::None, + inclusive: false, + }, + )); + } + if let Some(from) = from_f { + vec.push(IteratorRange::new( + ValueType::NumberFloat, + RangeValue { + value: Number::Float(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Value::None, + inclusive: false, + }, + )); + } + if let Some(from) = from_d { + vec.push(IteratorRange::new( + ValueType::NumberDecimal, + RangeValue { + value: Number::Decimal(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Value::None, + inclusive: false, + }, + )); + } + vec + } + + fn get_to_range_number_variants<'a>(to: &Number, to_inc: bool) -> Vec> { + let (from_i, from_f, from_d) = Self::get_range_number_to_variants(to); + let mut vec = Vec::with_capacity(3); + if let Some(to) = from_i { + vec.push(IteratorRange::new( + ValueType::NumberInt, + RangeValue { + value: Value::None, + inclusive: false, + }, + RangeValue { + value: Number::Int(to).into(), + inclusive: to_inc, + }, + )); + } + if let Some(to) = from_f { + vec.push(IteratorRange::new( + ValueType::NumberFloat, + RangeValue { + value: Value::None, + inclusive: false, + }, + RangeValue { + value: Number::Float(to).into(), + inclusive: to_inc, + }, + )); + } + if let Some(to) = from_d { + vec.push(IteratorRange::new( + ValueType::NumberDecimal, + RangeValue { + value: Value::None, + inclusive: false, + }, + RangeValue { + value: Number::Decimal(to).into(), + inclusive: to_inc, + }, + )); + } + vec + } + + fn get_ranges_number_variants<'a>( + from: &Number, + from_inc: bool, + to: &Number, + to_inc: bool, + ) -> Vec> { + let (from_i, from_f, from_d) = Self::get_range_number_from_variants(from); + let (to_i, to_f, to_d) = Self::get_range_number_to_variants(to); + let mut vec = Vec::with_capacity(3); + if let (Some(from), Some(to)) = (from_i, to_i) { + vec.push(IteratorRange::new( + ValueType::NumberInt, + RangeValue { + value: Number::Int(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Number::Int(to).into(), + inclusive: to_inc, + }, + )); + } + if let (Some(from), Some(to)) = (from_f, to_f) { + vec.push(IteratorRange::new( + ValueType::NumberFloat, + RangeValue { + value: Number::Float(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Number::Float(to).into(), + inclusive: to_inc, + }, + )); + } + if let (Some(from), Some(to)) = (from_d, to_d) { + vec.push(IteratorRange::new( + ValueType::NumberDecimal, + RangeValue { + value: Number::Decimal(from).into(), + inclusive: from_inc, + }, + RangeValue { + value: Number::Decimal(to).into(), + inclusive: to_inc, + }, + )); + } + vec + } + fn new_range_iterator( &self, opt: &Options, @@ -495,26 +673,44 @@ impl QueryExecutor { if let Some(ix) = self.get_index_def(ir) { match ix.index { Index::Idx => { - return Ok(Some(ThingIterator::IndexRange(IndexRangeThingIterator::new( + let ranges = Self::get_ranges_variants(from, to); + if let Some(ranges) = ranges { + if ranges.len() == 1 { + return Ok(Some(Self::new_index_range_iterator( + ir, opt, ix, &ranges[0], + )?)); + } else { + return Ok(Some(Self::new_multiple_index_range_iterator( + ir, opt, ix, &ranges, + )?)); + } + } + return Ok(Some(Self::new_index_range_iterator( ir, - opt.ns()?, - opt.db()?, - &ix.what, - &ix.name, - from, - to, - )))) + opt, + ix, + &IteratorRange::new_ref(ValueType::None, from, to), + )?)); } Index::Uniq => { - return Ok(Some(ThingIterator::UniqueRange(UniqueRangeThingIterator::new( + let ranges = Self::get_ranges_variants(from, to); + if let Some(ranges) = ranges { + if ranges.len() == 1 { + return Ok(Some(Self::new_unique_range_iterator( + ir, opt, ix, &ranges[0], + )?)); + } else { + return Ok(Some(Self::new_multiple_unique_range_iterator( + ir, opt, ix, &ranges, + )?)); + } + } + return Ok(Some(Self::new_unique_range_iterator( ir, - opt.ns()?, - opt.db()?, - &ix.what, - &ix.name, - from, - to, - )))) + opt, + ix, + &IteratorRange::new_ref(ValueType::None, from, to), + )?)); } _ => {} } @@ -522,6 +718,82 @@ impl QueryExecutor { Ok(None) } + fn get_ranges_variants<'a>( + from: &'a RangeValue, + to: &'a RangeValue, + ) -> Option>> { + match (&from.value, &to.value) { + (Value::Number(from_n), Value::Number(to_n)) => { + Some(Self::get_ranges_number_variants(from_n, from.inclusive, to_n, to.inclusive)) + } + (Value::Number(from_n), Value::None) => { + Some(Self::get_from_range_number_variants(from_n, from.inclusive)) + } + (Value::None, Value::Number(to_n)) => { + Some(Self::get_to_range_number_variants(to_n, to.inclusive)) + } + _ => None, + } + } + + fn new_index_range_iterator( + ir: IndexRef, + opt: &Options, + ix: &DefineIndexStatement, + range: &IteratorRange, + ) -> Result { + Ok(ThingIterator::IndexRange(IndexRangeThingIterator::new( + ir, + opt.ns()?, + opt.db()?, + &ix.what, + &ix.name, + range, + ))) + } + + fn new_unique_range_iterator( + ir: IndexRef, + opt: &Options, + ix: &DefineIndexStatement, + range: &IteratorRange<'_>, + ) -> Result { + Ok(ThingIterator::UniqueRange(UniqueRangeThingIterator::new( + ir, + opt.ns()?, + opt.db()?, + &ix.what, + &ix.name, + range, + ))) + } + + fn new_multiple_index_range_iterator( + ir: IndexRef, + opt: &Options, + ix: &DefineIndexStatement, + ranges: &[IteratorRange], + ) -> Result { + let mut iterators = VecDeque::with_capacity(ranges.len()); + for range in ranges { + iterators.push_back(Self::new_index_range_iterator(ir, opt, ix, range)?); + } + Ok(ThingIterator::Multiples(Box::new(MultipleIterators::new(iterators)))) + } + + fn new_multiple_unique_range_iterator( + ir: IndexRef, + opt: &Options, + ix: &DefineIndexStatement, + ranges: &[IteratorRange<'_>], + ) -> Result { + let mut iterators = VecDeque::with_capacity(ranges.len()); + for range in ranges { + iterators.push_back(Self::new_unique_range_iterator(ir, opt, ix, range)?); + } + Ok(ThingIterator::Multiples(Box::new(MultipleIterators::new(iterators)))) + } + async fn new_unique_index_iterator( &self, opt: &Options, @@ -532,7 +804,7 @@ impl QueryExecutor { Ok(match io.op() { IndexOperator::Equality(value) | IndexOperator::Exactness(value) => { if let Value::Number(n) = value.as_ref() { - let values = Self::get_number_variants(n); + let values = Self::get_equal_number_variants(n); if values.len() == 1 { Some(Self::new_unique_equal_iterator(irf, opt, ix, &values[0])?) } else { diff --git a/core/src/idx/planner/iterators.rs b/core/src/idx/planner/iterators.rs index 075b0596..2957ae4d 100644 --- a/core/src/idx/planner/iterators.rs +++ b/core/src/idx/planner/iterators.rs @@ -9,8 +9,10 @@ use crate::key::index::Index; use crate::kvs::Key; use crate::kvs::Transaction; use crate::sql::statements::DefineIndexStatement; -use crate::sql::{Array, Ident, Thing, Value}; +use crate::sql::{Array, Ident, Number, Thing, Value}; use radix_trie::Trie; +use rust_decimal::Decimal; +use std::borrow::Cow; use std::collections::VecDeque; use std::sync::Arc; @@ -247,6 +249,96 @@ impl RangeScan { } } +pub(super) struct IteratorRange<'a> { + value_type: ValueType, + from: Cow<'a, RangeValue>, + to: Cow<'a, RangeValue>, +} + +impl<'a> IteratorRange<'a> { + pub(super) fn new(t: ValueType, from: RangeValue, to: RangeValue) -> Self { + IteratorRange { + value_type: t, + from: Cow::Owned(from), + to: Cow::Owned(to), + } + } + + pub(super) fn new_ref(t: ValueType, from: &'a RangeValue, to: &'a RangeValue) -> Self { + IteratorRange { + value_type: t, + from: Cow::Borrowed(from), + to: Cow::Borrowed(to), + } + } +} + +// When we know the type of the range values, we have the opportunity +// to restrict the key range to the exact prefixes according to the type. +#[derive(Copy, Clone)] +pub(super) enum ValueType { + None, + NumberInt, + NumberFloat, + NumberDecimal, +} + +impl ValueType { + fn prefix_beg(&self, ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident) -> Vec { + match self { + Self::None => Index::prefix_beg(ns, db, ix_what, ix_name), + Self::NumberInt => Index::prefix_ids_beg( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Int(i64::MIN))]), + ), + Self::NumberFloat => Index::prefix_ids_beg( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Float(f64::MIN))]), + ), + Self::NumberDecimal => Index::prefix_ids_beg( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Decimal(Decimal::MIN))]), + ), + } + } + + fn prefix_end(&self, ns: &str, db: &str, ix_what: &Ident, ix_name: &Ident) -> Vec { + match self { + Self::None => Index::prefix_end(ns, db, ix_what, ix_name), + Self::NumberInt => Index::prefix_ids_end( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Int(i64::MAX))]), + ), + Self::NumberFloat => Index::prefix_ids_end( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Float(f64::MAX))]), + ), + Self::NumberDecimal => Index::prefix_ids_end( + ns, + db, + ix_what, + ix_name, + &Array(vec![Value::Number(Number::Decimal(Decimal::MAX))]), + ), + } + } +} + pub(crate) struct IndexRangeThingIterator { irf: IteratorRef, r: RangeScan, @@ -259,14 +351,13 @@ impl IndexRangeThingIterator { db: &str, ix_what: &Ident, ix_name: &Ident, - from: &RangeValue, - to: &RangeValue, + range: &IteratorRange<'_>, ) -> Self { - let beg = Self::compute_beg(ns, db, ix_what, ix_name, from); - let end = Self::compute_end(ns, db, ix_what, ix_name, to); + let beg = Self::compute_beg(ns, db, ix_what, ix_name, &range.from, range.value_type); + let end = Self::compute_end(ns, db, ix_what, ix_name, &range.to, range.value_type); Self { irf, - r: RangeScan::new(beg, from.inclusive, end, to.inclusive), + r: RangeScan::new(beg, range.from.inclusive, end, range.to.inclusive), } } @@ -281,7 +372,12 @@ impl IndexRangeThingIterator { value: Value::None, inclusive: true, }; - Self::new(irf, ns, db, ix_what, ix_name, &full_range, &full_range) + let range = IteratorRange { + value_type: ValueType::None, + from: Cow::Borrowed(&full_range), + to: Cow::Borrowed(&full_range), + }; + Self::new(irf, ns, db, ix_what, ix_name, &range) } fn compute_beg( @@ -290,9 +386,10 @@ impl IndexRangeThingIterator { ix_what: &Ident, ix_name: &Ident, from: &RangeValue, + value_type: ValueType, ) -> Vec { if from.value == Value::None { - return Index::prefix_beg(ns, db, ix_what, ix_name); + return value_type.prefix_beg(ns, db, ix_what, ix_name); } let fd = Array::from(from.value.to_owned()); if from.inclusive { @@ -308,9 +405,10 @@ impl IndexRangeThingIterator { ix_what: &Ident, ix_name: &Ident, to: &RangeValue, + value_type: ValueType, ) -> Vec { if to.value == Value::None { - return Index::prefix_end(ns, db, ix_what, ix_name); + return value_type.prefix_end(ns, db, ix_what, ix_name); } let fd = Array::from(to.value.to_owned()); if to.inclusive { @@ -571,14 +669,13 @@ impl UniqueRangeThingIterator { db: &str, ix_what: &Ident, ix_name: &Ident, - from: &RangeValue, - to: &RangeValue, + range: &IteratorRange<'_>, ) -> Self { - let beg = Self::compute_beg(ns, db, ix_what, ix_name, from); - let end = Self::compute_end(ns, db, ix_what, ix_name, to); + let beg = Self::compute_beg(ns, db, ix_what, ix_name, &range.from, range.value_type); + let end = Self::compute_end(ns, db, ix_what, ix_name, &range.to, range.value_type); Self { irf, - r: RangeScan::new(beg, from.inclusive, end, to.inclusive), + r: RangeScan::new(beg, range.from.inclusive, end, range.to.inclusive), done: false, } } @@ -589,11 +686,16 @@ impl UniqueRangeThingIterator { db: &str, ix: &DefineIndexStatement, ) -> Self { - let full_range = RangeValue { + let value = RangeValue { value: Value::None, inclusive: true, }; - Self::new(irf, ns, db, &ix.what, &ix.name, &full_range, &full_range) + let range = IteratorRange { + value_type: ValueType::None, + from: Cow::Borrowed(&value), + to: Cow::Borrowed(&value), + }; + Self::new(irf, ns, db, &ix.what, &ix.name, &range) } fn compute_beg( @@ -602,9 +704,10 @@ impl UniqueRangeThingIterator { ix_what: &Ident, ix_name: &Ident, from: &RangeValue, + value_type: ValueType, ) -> Vec { if from.value == Value::None { - return Index::prefix_beg(ns, db, ix_what, ix_name); + return value_type.prefix_beg(ns, db, ix_what, ix_name); } Index::new(ns, db, ix_what, ix_name, &Array::from(from.value.to_owned()), None) .encode() @@ -617,9 +720,10 @@ impl UniqueRangeThingIterator { ix_what: &Ident, ix_name: &Ident, to: &RangeValue, + value_type: ValueType, ) -> Vec { if to.value == Value::None { - return Index::prefix_end(ns, db, ix_what, ix_name); + return value_type.prefix_end(ns, db, ix_what, ix_name); } Index::new(ns, db, ix_what, ix_name, &Array::from(to.value.to_owned()), None) .encode() diff --git a/core/src/idx/planner/plan.rs b/core/src/idx/planner/plan.rs index 8b49e129..08363d21 100644 --- a/core/src/idx/planner/plan.rs +++ b/core/src/idx/planner/plan.rs @@ -298,7 +298,7 @@ impl IndexOption { } } -#[derive(Debug, Default, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)] pub(super) struct RangeValue { pub(super) value: Value, pub(super) inclusive: bool, diff --git a/sdk/tests/define.rs b/sdk/tests/define.rs index aaf62bc7..ac28fd1b 100644 --- a/sdk/tests/define.rs +++ b/sdk/tests/define.rs @@ -797,6 +797,423 @@ async fn define_statement_unique_index_numbers() -> Result<(), Error> { Ok(()) } +fn check_range(t: &mut Test, result: &str, explain: &str) -> Result<(), Error> { + for _ in 0..2 { + t.expect_val(result)?; + } + t.expect_val(explain)?; + Ok(()) +} + +async fn define_statement_mixed_numbers_range(unique: bool) -> Result<(), Error> { + let sql = format!( + " + DEFINE INDEX index ON TABLE test COLUMNS number {}; + CREATE test:int0 SET number = 0; + CREATE test:float0 SET number = 0.5; + CREATE test:int1 SET number = 1; + CREATE test:float1 SET number = 1.5; + CREATE test:int2 SET number = 2; + SELECT * FROM test WITH NOINDEX WHERE number > 0 AND number < 2 ORDER BY id; + SELECT * FROM test WHERE number > 0 AND number < 2 ORDER BY id; + SELECT * FROM test WHERE number > 0 AND number < 2 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number > 0.1 AND number < 2 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 AND number < 2 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 AND number < 2 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number > 0.1 AND number < 1.5 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 AND number < 1.5 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 AND number < 1.5 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number > 0 AND number < 1.5 ORDER BY id; + SELECT * FROM test WHERE number > 0 AND number < 1.5 ORDER BY id; + SELECT * FROM test WHERE number > 0 AND number < 1.5 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number > 0.1 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 ORDER BY id; + SELECT * FROM test WHERE number > 0.1 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number > 0 ORDER BY id; + SELECT * FROM test WHERE number > 0 ORDER BY id; + SELECT * FROM test WHERE number > 0 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number < 2 ORDER BY id; + SELECT * FROM test WHERE number < 2 ORDER BY id; + SELECT * FROM test WHERE number < 2 ORDER BY id EXPLAIN; + SELECT * FROM test WITH NOINDEX WHERE number < 1.9 ORDER BY id; + SELECT * FROM test WHERE number < 1.9 ORDER BY id; + SELECT * FROM test WHERE number < 1.9 ORDER BY id EXPLAIN; + ", + if unique { + "UNIQUE" + } else { + "" + } + ); + let mut t = Test::new(&sql).await?; + t.expect_size(30)?; + t.skip_ok(6)?; + // number > 0 AND number < 2 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0 + }, + index: 'index', + to: { + inclusive: false, + value: 2 + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number > 0.1 AND number < 2 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0.1f + }, + index: 'index', + to: { + inclusive: false, + value: 2 + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number > 0.1 AND number < 1.5 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0.1f + }, + index: 'index', + to: { + inclusive: false, + value: 1.5f + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number > 0 AND number < 1.5 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0 + }, + index: 'index', + to: { + inclusive: false, + value: 1.5f + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number > 0.1 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int1, + number: 1 + }, + { + id: test:int2, + number: 2 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0.1f + }, + index: 'index', + to: { + inclusive: false, + value: NONE + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number > 0 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int1, + number: 1 + }, + { + id: test:int2, + number: 2 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: 0 + }, + index: 'index', + to: { + inclusive: false, + value: NONE + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number < 2 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int0, + number: 0 + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: NONE + }, + index: 'index', + to: { + inclusive: false, + value: 2 + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // number < 1.9 + check_range( + &mut t, + "[ + { + id: test:float0, + number: 0.5f + }, + { + id: test:float1, + number: 1.5f + }, + { + id: test:int0, + number: 0 + }, + { + id: test:int1, + number: 1 + } + ]", + "[ + { + detail: { + plan: { + from: { + inclusive: false, + value: NONE + }, + index: 'index', + to: { + inclusive: false, + value: 1.9f + } + }, + table: 'test' + }, + operation: 'Iterate Index' + }, + { + detail: { + type: 'Memory' + }, + operation: 'Collector' + } + ]", + )?; + // + Ok(()) +} + +#[tokio::test] +async fn define_statement_index_mixed_numbers_range() -> Result<(), Error> { + define_statement_mixed_numbers_range(false).await +} + +#[tokio::test] +async fn define_statement_unique_mixed_numbers_range() -> Result<(), Error> { + define_statement_mixed_numbers_range(true).await +} + #[tokio::test] async fn define_statement_index_concurrently() -> Result<(), Error> { let sql = "