use crate::Partitioning;
use crate::table::TableSpec;
use crate::{IndexKind, IndexSpec, ValType};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Described {
Bulk(Vec<u8>),
Array(Vec<Described>),
}
pub(crate) fn b(v: impl AsRef<[u8]>) -> Described {
Described::Bulk(v.as_ref().to_vec())
}
pub(crate) fn n(v: impl ToString) -> Described {
Described::Bulk(v.to_string().into_bytes())
}
pub(crate) fn flag(v: bool) -> Described {
n(u8::from(v))
}
pub(crate) fn argv(words: Vec<Vec<u8>>) -> Described {
Described::Array(words.into_iter().map(Described::Bulk).collect())
}
pub fn describe_index<'a>(
s: &IndexSpec,
tables: impl IntoIterator<Item = &'a TableSpec>,
) -> Described {
describe_index_partitioned(s, &Partitioning::Local, tables)
}
pub fn describe_index_partitioned<'a>(
s: &IndexSpec,
part: &Partitioning,
tables: impl IntoIterator<Item = &'a TableSpec>,
) -> Described {
let owner = owner_of(tables, &s.name);
let fields = s.fields.iter().map(|f| Described::Array(vec![b(&f.name), n(f.weight)]));
let values = s.values.iter().map(|v| Described::Array(vec![b(&v.name), b(v.ty.tag())]));
let composite = match &s.composite {
None => b("-"),
Some(cols) => Described::Array(
cols.iter()
.map(|c| Described::Array(vec![b(&c.name), b(c.ty.tag()), b(order(c.order))]))
.collect(),
),
};
Described::Array(vec![
b("name"),
b(&s.name),
b("prefix"),
b(&s.prefix),
b("kind"),
b(s.kind.tag()),
b("type"),
b(s.ty.tag()),
b("fields"),
Described::Array(fields.collect()),
b("values"),
Described::Array(values.collect()),
b("positions"),
flag(s.with_positions),
b("maxmem"),
n(s.max_bytes),
b("groupby"),
s.group_by.as_ref().map_or_else(|| b("-"), b),
b("ann"),
index_ann(s),
b("composite"),
composite,
b("table"),
owner.map_or_else(|| b("-"), |t| b(&t.name)),
b("partitioning"),
partitioning_described(s, part),
b("declaration"),
declaration_described(owner.is_some(), s, part),
])
}
fn declaration_described(from_table: bool, s: &IndexSpec, part: &Partitioning) -> Described {
if from_table || s.composite.is_some() {
b("-")
} else {
argv(index_declaration_partitioned(s, part))
}
}
fn partitioning_described(s: &IndexSpec, part: &Partitioning) -> Described {
match part {
Partitioning::Local => b("local"),
Partitioning::Global { splits } => {
let splits = splits.iter().map(|p| b(s.split_point_text(p))).collect();
Described::Array(vec![b("global"), Described::Array(splits)])
}
}
}
fn index_ann(s: &IndexSpec) -> Described {
let Some(a) = &s.ann else { return b("-") };
Described::Array(vec![
b("dim"),
n(a.dim),
b("distance"),
b(distance_tag(a.distance)),
b("m"),
n(a.m),
b("ef"),
n(a.ef),
])
}
pub fn owner_of<'a>(
tables: impl IntoIterator<Item = &'a TableSpec>,
index: &[u8],
) -> Option<&'a TableSpec> {
tables
.into_iter()
.find(|t| t.compile().is_ok_and(|compiled| compiled.iter().any(|c| c.name == index)))
}
pub fn index_declaration(s: &IndexSpec) -> Vec<Vec<u8>> {
index_declaration_partitioned(s, &Partitioning::Local)
}
pub fn index_declaration_partitioned(s: &IndexSpec, part: &Partitioning) -> Vec<Vec<u8>> {
let mut w: Vec<Vec<u8>> =
vec![b"IDX.CREATE".to_vec(), s.name.clone(), b"ON".to_vec(), b"PREFIX".to_vec()];
w.push(s.prefix.clone());
let weighted = s.fields.iter().any(|f| f.weight != 1.0);
if s.kind == IndexKind::Text && (s.fields.len() > 1 || weighted) {
w.push(b"FIELDS".to_vec());
w.extend(s.fields.iter().map(|f| f.name.clone()));
if weighted {
w.push(b"WEIGHTS".to_vec());
w.extend(s.fields.iter().map(|f| f.weight.to_string().into_bytes()));
}
} else {
w.push(b"FIELD".to_vec());
w.extend(s.fields.iter().map(|f| f.name.clone()));
}
w.extend([b"TYPE".to_vec(), s.ty.tag().into(), b"KIND".to_vec(), s.kind.tag().into()]);
index_options(s, &mut w);
if let Partitioning::Global { splits } = part {
w.extend([b"PARTITION".to_vec(), b"global".to_vec()]);
for p in splits {
w.extend([b"SPLIT".to_vec(), s.split_point_text(p)]);
}
}
w
}
fn index_options(s: &IndexSpec, w: &mut Vec<Vec<u8>>) {
if s.with_positions {
w.extend([b"WITH".to_vec(), b"POSITIONS".to_vec()]);
}
if !s.values.is_empty() {
w.push(b"VALUES".to_vec());
w.extend(s.values.iter().map(|v| v.name.clone()));
if s.values.iter().any(|v| v.ty != ValType::Str) {
w.push(b"TYPES".to_vec());
w.extend(s.values.iter().map(|v| v.ty.tag().as_bytes().to_vec()));
}
}
if s.max_bytes != 0 {
w.extend([b"MAXMEM".to_vec(), s.max_bytes.to_string().into()]);
}
if let Some(g) = &s.group_by {
w.extend([b"GROUPBY".to_vec(), g.clone()]);
}
if let Some(a) = &s.ann {
w.extend([b"DIM".to_vec(), a.dim.to_string().into()]);
w.extend([b"DISTANCE".to_vec(), distance_tag(a.distance).into()]);
w.extend([b"M".to_vec(), a.m.to_string().into(), b"EF".to_vec(), a.ef.to_string().into()]);
}
}
pub(crate) fn order(o: kevy_text::SortOrder) -> &'static str {
match o {
kevy_text::SortOrder::Asc => "asc",
kevy_text::SortOrder::Desc => "desc",
}
}
fn distance_tag(code: u8) -> &'static str {
match code {
0 => "cosine",
1 => "l2",
2 => "ip",
_ => "unknown",
}
}
pub use crate::describe_table::{
describe_table, describe_table_partitioned, table_declaration, table_declaration_partitioned,
};
pub use crate::describe_view::{describe_view, view_declaration};
#[cfg(test)]
#[path = "describe_tests.rs"]
mod tests;