implement COLLATE and NUMERIC ordering in ORDER BY clauses
This commit is contained in:
parent
4bc3b299aa
commit
4c8d9dbb63
5 changed files with 81 additions and 22 deletions
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -32,6 +32,12 @@ dependencies = [
|
||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "any_ascii"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70033777eb8b5124a81a1889416543dddef2de240019b674c81285a2635a7e1e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.56"
|
version = "1.0.56"
|
||||||
|
@ -1123,6 +1129,15 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lexical-sort"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c09e4591611e231daf4d4c685a66cb0410cc1e502027a20ae55f2bb9e997207a"
|
||||||
|
dependencies = [
|
||||||
|
"any_ascii",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.121"
|
version = "0.2.121"
|
||||||
|
@ -2242,6 +2257,7 @@ dependencies = [
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
"geo",
|
"geo",
|
||||||
"indxdb",
|
"indxdb",
|
||||||
|
"lexical-sort",
|
||||||
"log",
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
"nanoid",
|
"nanoid",
|
||||||
|
|
|
@ -26,6 +26,7 @@ futures = "0.3.21"
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
geo = { version = "0.19.0", features = ["use-serde"] }
|
geo = { version = "0.19.0", features = ["use-serde"] }
|
||||||
indxdb = { version = "0.2.0", optional = true }
|
indxdb = { version = "0.2.0", optional = true }
|
||||||
|
lexical-sort = "0.3.1"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
md-5 = "0.10.1"
|
md-5 = "0.10.1"
|
||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
|
|
|
@ -296,8 +296,8 @@ impl Iterator {
|
||||||
a.partial_cmp(&b)
|
a.partial_cmp(&b)
|
||||||
}
|
}
|
||||||
false => match order.direction {
|
false => match order.direction {
|
||||||
true => a.compare(b, &order.order),
|
true => a.compare(b, &order.order, order.collate, order.numeric),
|
||||||
false => b.compare(a, &order.order),
|
false => b.compare(a, &order.order, order.collate, order.numeric),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
//
|
//
|
||||||
|
|
|
@ -4,14 +4,20 @@ use crate::sql::value::Value;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn compare(&self, other: &Self, path: &[Part]) -> Option<Ordering> {
|
pub fn compare(
|
||||||
|
&self,
|
||||||
|
other: &Self,
|
||||||
|
path: &[Part],
|
||||||
|
collate: bool,
|
||||||
|
numeric: bool,
|
||||||
|
) -> Option<Ordering> {
|
||||||
match path.first() {
|
match path.first() {
|
||||||
// Get the current path part
|
// Get the current path part
|
||||||
Some(p) => match (self, other) {
|
Some(p) => match (self, other) {
|
||||||
// Current path part is an object
|
// Current path part is an object
|
||||||
(Value::Object(a), Value::Object(b)) => match p {
|
(Value::Object(a), Value::Object(b)) => match p {
|
||||||
Part::Field(f) => match (a.value.get(&f.name), b.value.get(&f.name)) {
|
Part::Field(f) => match (a.value.get(&f.name), b.value.get(&f.name)) {
|
||||||
(Some(a), Some(b)) => a.compare(b, path.next()),
|
(Some(a), Some(b)) => a.compare(b, path.next(), collate, numeric),
|
||||||
(Some(_), None) => Some(Ordering::Greater),
|
(Some(_), None) => Some(Ordering::Greater),
|
||||||
(None, Some(_)) => Some(Ordering::Less),
|
(None, Some(_)) => Some(Ordering::Less),
|
||||||
(_, _) => Some(Ordering::Equal),
|
(_, _) => Some(Ordering::Equal),
|
||||||
|
@ -22,7 +28,7 @@ impl Value {
|
||||||
(Value::Array(a), Value::Array(b)) => match p {
|
(Value::Array(a), Value::Array(b)) => match p {
|
||||||
Part::All => {
|
Part::All => {
|
||||||
for (a, b) in a.value.iter().zip(b.value.iter()) {
|
for (a, b) in a.value.iter().zip(b.value.iter()) {
|
||||||
match a.compare(b, path.next()) {
|
match a.compare(b, path.next(), collate, numeric) {
|
||||||
Some(Ordering::Equal) => continue,
|
Some(Ordering::Equal) => continue,
|
||||||
None => continue,
|
None => continue,
|
||||||
o => return o,
|
o => return o,
|
||||||
|
@ -35,20 +41,20 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Part::First => match (a.value.first(), b.value.first()) {
|
Part::First => match (a.value.first(), b.value.first()) {
|
||||||
(Some(a), Some(b)) => a.compare(b, path.next()),
|
(Some(a), Some(b)) => a.compare(b, path.next(), collate, numeric),
|
||||||
(Some(_), None) => Some(Ordering::Greater),
|
(Some(_), None) => Some(Ordering::Greater),
|
||||||
(None, Some(_)) => Some(Ordering::Less),
|
(None, Some(_)) => Some(Ordering::Less),
|
||||||
(_, _) => Some(Ordering::Equal),
|
(_, _) => Some(Ordering::Equal),
|
||||||
},
|
},
|
||||||
Part::Last => match (a.value.first(), b.value.first()) {
|
Part::Last => match (a.value.first(), b.value.first()) {
|
||||||
(Some(a), Some(b)) => a.compare(b, path.next()),
|
(Some(a), Some(b)) => a.compare(b, path.next(), collate, numeric),
|
||||||
(Some(_), None) => Some(Ordering::Greater),
|
(Some(_), None) => Some(Ordering::Greater),
|
||||||
(None, Some(_)) => Some(Ordering::Less),
|
(None, Some(_)) => Some(Ordering::Less),
|
||||||
(_, _) => Some(Ordering::Equal),
|
(_, _) => Some(Ordering::Equal),
|
||||||
},
|
},
|
||||||
Part::Index(i) => {
|
Part::Index(i) => {
|
||||||
match (a.value.get(i.to_usize()), b.value.get(i.to_usize())) {
|
match (a.value.get(i.to_usize()), b.value.get(i.to_usize())) {
|
||||||
(Some(a), Some(b)) => a.compare(b, path.next()),
|
(Some(a), Some(b)) => a.compare(b, path.next(), collate, numeric),
|
||||||
(Some(_), None) => Some(Ordering::Greater),
|
(Some(_), None) => Some(Ordering::Greater),
|
||||||
(None, Some(_)) => Some(Ordering::Less),
|
(None, Some(_)) => Some(Ordering::Less),
|
||||||
(_, _) => Some(Ordering::Equal),
|
(_, _) => Some(Ordering::Equal),
|
||||||
|
@ -56,7 +62,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
for (a, b) in a.value.iter().zip(b.value.iter()) {
|
for (a, b) in a.value.iter().zip(b.value.iter()) {
|
||||||
match a.compare(b, path) {
|
match a.compare(b, path, collate, numeric) {
|
||||||
Some(Ordering::Equal) => continue,
|
Some(Ordering::Equal) => continue,
|
||||||
None => continue,
|
None => continue,
|
||||||
o => return o,
|
o => return o,
|
||||||
|
@ -70,10 +76,15 @@ impl Value {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Ignore everything else
|
// Ignore everything else
|
||||||
(a, b) => a.compare(b, path.next()),
|
(a, b) => a.compare(b, path.next(), collate, numeric),
|
||||||
},
|
},
|
||||||
// No more parts so get the value
|
// No more parts so get the value
|
||||||
None => self.partial_cmp(other),
|
None => match (collate, numeric) {
|
||||||
|
(true, true) => self.natural_lexical_cmp(other),
|
||||||
|
(true, false) => self.lexical_cmp(other),
|
||||||
|
(false, true) => self.natural_cmp(other),
|
||||||
|
_ => self.partial_cmp(other),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +101,7 @@ mod tests {
|
||||||
let idi = Idiom::default();
|
let idi = Idiom::default();
|
||||||
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,7 +110,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something");
|
let idi = Idiom::parse("test.something");
|
||||||
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +119,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something");
|
let idi = Idiom::parse("test.something");
|
||||||
let one = Value::parse("{ test: { other: null } }");
|
let one = Value::parse("{ test: { other: null } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
let two = Value::parse("{ test: { other: null, something: 123 } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Less));
|
assert_eq!(res, Some(Ordering::Less));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +128,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something");
|
let idi = Idiom::parse("test.something");
|
||||||
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
let one = Value::parse("{ test: { other: null, something: 456 } }");
|
||||||
let two = Value::parse("{ test: { other: null } }");
|
let two = Value::parse("{ test: { other: null } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +137,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [4, 5, 6] } }");
|
let one = Value::parse("{ test: { other: null, something: [4, 5, 6] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +146,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [1, 2, 3, 4, 5, 6] } }");
|
let one = Value::parse("{ test: { other: null, something: [1, 2, 3, 4, 5, 6] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +155,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let one = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, 2, 3, 4, 5, 6] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, 2, 3, 4, 5, 6] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Less));
|
assert_eq!(res, Some(Ordering::Less));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +164,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: null } }");
|
let one = Value::parse("{ test: { other: null, something: null } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Less));
|
assert_eq!(res, Some(Ordering::Less));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,7 +173,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [4, 5, 6] } }");
|
let one = Value::parse("{ test: { other: null, something: [4, 5, 6] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: null } }");
|
let two = Value::parse("{ test: { other: null, something: null } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +182,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [1, null, 3] } }");
|
let one = Value::parse("{ test: { other: null, something: [1, null, 3] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Less));
|
assert_eq!(res, Some(Ordering::Less));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +191,7 @@ mod tests {
|
||||||
let idi = Idiom::parse("test.something.*");
|
let idi = Idiom::parse("test.something.*");
|
||||||
let one = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
let one = Value::parse("{ test: { other: null, something: [1, 2, 3] } }");
|
||||||
let two = Value::parse("{ test: { other: null, something: [1, null, 3] } }");
|
let two = Value::parse("{ test: { other: null, something: [1, null, 3] } }");
|
||||||
let res = one.compare(&two, &idi);
|
let res = one.compare(&two, &idi, false, false);
|
||||||
assert_eq!(res, Some(Ordering::Greater));
|
assert_eq!(res, Some(Ordering::Greater));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -913,6 +913,37 @@ impl Value {
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------
|
||||||
|
// Sorting operations
|
||||||
|
// -----------------------------------
|
||||||
|
|
||||||
|
pub fn lexical_cmp(&self, other: &Value) -> Option<Ordering> {
|
||||||
|
match (self, other) {
|
||||||
|
(Value::Strand(a), Value::Strand(b)) => {
|
||||||
|
Some(lexical_sort::lexical_cmp(&a.value, &b.value))
|
||||||
|
}
|
||||||
|
_ => self.partial_cmp(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn natural_cmp(&self, other: &Value) -> Option<Ordering> {
|
||||||
|
match (self, other) {
|
||||||
|
(Value::Strand(a), Value::Strand(b)) => {
|
||||||
|
Some(lexical_sort::natural_cmp(&a.value, &b.value))
|
||||||
|
}
|
||||||
|
_ => self.partial_cmp(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn natural_lexical_cmp(&self, other: &Value) -> Option<Ordering> {
|
||||||
|
match (self, other) {
|
||||||
|
(Value::Strand(a), Value::Strand(b)) => {
|
||||||
|
Some(lexical_sort::natural_lexical_cmp(&a.value, &b.value))
|
||||||
|
}
|
||||||
|
_ => self.partial_cmp(other),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Value {
|
impl fmt::Display for Value {
|
||||||
|
|
Loading…
Reference in a new issue