use kevy_index::{Cursor, IndexValue, ValType};
use super::super::wire::{decode_cursor, unhex};
use super::filter::{FilterArg, parse_filter, parse_sort_dir};
pub(crate) enum Shape {
Range {
min: Vec<u8>,
max: Vec<u8>,
},
Eq {
value: Vec<u8>,
},
Where(kevy_index::WhereClause),
Verify,
}
pub(crate) struct SubQuery {
pub(in crate::cmd_index_query) name: Vec<u8>,
pub(in crate::cmd_index_query) shape: Shape,
}
pub(crate) struct Query {
pub(crate) name: Vec<u8>,
pub(crate) shape: Shape,
pub(crate) limit: usize,
pub(crate) cursor_raw: Option<Vec<u8>>,
pub(crate) fields: Vec<Vec<u8>>,
pub(crate) filters: Vec<FilterArg>,
pub(crate) sort: Option<(Vec<u8>, bool)>,
pub(crate) distinct: Option<Vec<u8>>,
pub(crate) facets: Vec<Vec<u8>>,
pub(crate) offset: usize,
}
fn is_scalar_keyword(a: &[u8]) -> bool {
a.eq_ignore_ascii_case(b"LIMIT")
|| a.eq_ignore_ascii_case(b"CURSOR")
|| a.eq_ignore_ascii_case(b"FIELDS")
|| a.eq_ignore_ascii_case(b"FILTER")
|| a.eq_ignore_ascii_case(b"SORT")
|| a.eq_ignore_ascii_case(b"DISTINCT")
|| a.eq_ignore_ascii_case(b"FACET")
|| a.eq_ignore_ascii_case(b"OFFSET")
}
impl Query {
pub(crate) fn parse(argv: &[Vec<u8>]) -> Option<Query> {
let verb = argv.first()?;
if verb.eq_ignore_ascii_case(b"IDX.VERIFY") {
return Some(Query { shape: Shape::Verify, ..Query::bare(argv.get(1)?.clone()) });
}
let name = argv.get(1)?.clone();
let mode = argv.get(2)?;
let (shape, i) = if mode.eq_ignore_ascii_case(b"RANGE") {
(Shape::Range { min: argv.get(3)?.clone(), max: argv.get(4)?.clone() }, 5)
} else if mode.eq_ignore_ascii_case(b"EQ") {
(Shape::Eq { value: argv.get(3)?.clone() }, 4)
} else if mode.eq_ignore_ascii_case(b"WHERE") {
let (w, next) = kevy_index::parse_where(argv, 3, is_scalar_keyword)?;
(Shape::Where(w), next)
} else {
return None;
};
let mut q = Query { shape, ..Query::bare(name) };
q.parse_tail(argv, i)?;
q.limit = q.limit.clamp(1, 10_000);
q.offset = q.offset.min(10_000);
Some(q)
}
fn bare(name: Vec<u8>) -> Query {
Query {
name,
shape: Shape::Verify,
limit: 100,
cursor_raw: None,
fields: Vec::new(),
filters: Vec::new(),
sort: None,
distinct: None,
facets: Vec::new(),
offset: 0,
}
}
fn parse_tail(&mut self, argv: &[Vec<u8>], mut i: usize) -> Option<()> {
while i < argv.len() {
let a = &argv[i];
if a.eq_ignore_ascii_case(b"LIMIT") {
self.limit = std::str::from_utf8(argv.get(i + 1)?).ok()?.parse().ok()?;
i += 2;
} else if a.eq_ignore_ascii_case(b"CURSOR") {
self.cursor_raw = Some(argv.get(i + 1)?.clone());
i += 2;
} else if a.eq_ignore_ascii_case(b"OFFSET") {
self.offset = std::str::from_utf8(argv.get(i + 1)?).ok()?.parse().ok()?;
i += 2;
} else if a.eq_ignore_ascii_case(b"FILTER") {
let (f, next) = parse_filter(argv, i)?;
self.filters.push(f);
i = next;
} else if a.eq_ignore_ascii_case(b"SORT") {
self.sort = Some((argv.get(i + 1)?.clone(), parse_sort_dir(argv.get(i + 2)?)?));
i += 3;
} else if a.eq_ignore_ascii_case(b"DISTINCT") {
self.distinct = Some(argv.get(i + 1)?.clone());
i += 2;
} else if a.eq_ignore_ascii_case(b"FACET") {
let mut j = i + 1;
while j < argv.len() && !is_scalar_keyword(&argv[j]) {
self.facets.push(argv[j].clone());
j += 1;
}
if self.facets.is_empty() {
return None;
}
i = j;
} else if a.eq_ignore_ascii_case(b"FIELDS") {
self.fields = argv[i + 1..].to_vec();
if self.fields.is_empty() {
return None;
}
break;
} else {
return None;
}
}
Some(())
}
pub(crate) fn selects(&self) -> bool {
self.sort.is_some() || self.distinct.is_some() || !self.facets.is_empty() || self.offset > 0
}
pub(crate) fn has_clauses(&self) -> bool {
self.selects() || !self.filters.is_empty()
}
pub(in crate::cmd_index_query) fn bounds(
&self,
ty: ValType,
now: i64,
) -> Option<(IndexValue, IndexValue)> {
match &self.shape {
Shape::Range { min, max } => Some((
kevy_index::parse_literal_bound(ty, min, now)?,
kevy_index::parse_literal_bound(ty, max, now)?,
)),
Shape::Eq { value } => {
let v = kevy_index::parse_literal_bound(ty, value, now)?;
Some((v.clone(), v))
}
Shape::Where(_) | Shape::Verify => None,
}
}
pub(in crate::cmd_index_query) fn bounds_for(
&self,
spec: &kevy_index::IndexSpec,
now: i64,
) -> Result<(IndexValue, IndexValue), Vec<u8>> {
if let Shape::Where(w) = &self.shape {
let Some(cols) = &spec.composite else {
return Err(crate::cmd_index_query::query_claused::clause_chunk(
kevy_index::WHERE_NOT_COMPOSITE,
));
};
let (lo, hi) = kevy_index::composite_bounds(cols, w, now)
.map_err(|e| crate::cmd_index_query::query_claused::clause_chunk(&e))?;
return Ok((IndexValue::Str(lo), IndexValue::Str(hi)));
}
self.bounds(spec.ty, now).ok_or_else(|| vec![crate::cmd_index_query::ST_BADARGS])
}
pub(in crate::cmd_index_query) fn cursor(&self, _ty: ValType) -> Option<Cursor> {
self.cursor_raw.as_deref().and_then(decode_cursor)
}
}
pub(crate) struct ComposeQuery {
pub(crate) and: bool,
pub(crate) a: SubQuery,
pub(crate) b: SubQuery,
pub(crate) limit: usize,
pub(crate) cursor_key: Option<Vec<u8>>,
pub(crate) fields: Vec<Vec<u8>>,
}
impl ComposeQuery {
pub(crate) fn parse(argv: &[Vec<u8>]) -> Option<ComposeQuery> {
if !argv.first()?.eq_ignore_ascii_case(b"IDX.QUERY")
|| !argv.get(1)?.eq_ignore_ascii_case(b"COMPOSE")
{
return None;
}
let mode = argv.get(2)?;
let and = if mode.eq_ignore_ascii_case(b"AND") {
true
} else if mode.eq_ignore_ascii_case(b"OR") {
false
} else {
return None;
};
let (a, i) = parse_sub(argv, 3)?;
let (b, mut i) = parse_sub(argv, i)?;
let mut limit = 100usize;
let mut cursor_key = None;
let mut fields = Vec::new();
while i < argv.len() {
let t = &argv[i];
if t.eq_ignore_ascii_case(b"LIMIT") {
limit = std::str::from_utf8(argv.get(i + 1)?).ok()?.parse().ok()?;
i += 2;
} else if t.eq_ignore_ascii_case(b"CURSOR") {
let raw = argv.get(i + 1)?;
cursor_key = if raw == b"0" { None } else { Some(unhex(raw)?) };
i += 2;
} else if t.eq_ignore_ascii_case(b"FIELDS") {
fields = argv[i + 1..].to_vec();
if fields.is_empty() {
return None;
}
break;
} else {
return None;
}
}
Some(ComposeQuery { and, a, b, limit: limit.clamp(1, 10_000), cursor_key, fields })
}
}
fn parse_sub(argv: &[Vec<u8>], i: usize) -> Option<(SubQuery, usize)> {
let name = argv.get(i)?.clone();
let mode = argv.get(i + 1)?;
if mode.eq_ignore_ascii_case(b"RANGE") {
Some((
SubQuery {
name,
shape: Shape::Range {
min: argv.get(i + 2)?.clone(),
max: argv.get(i + 3)?.clone(),
},
},
i + 4,
))
} else if mode.eq_ignore_ascii_case(b"EQ") {
Some((SubQuery { name, shape: Shape::Eq { value: argv.get(i + 2)?.clone() } }, i + 3))
} else {
None
}
}