use serde_json::Value;
use crate::catalog::indexes::sugar_index_name;
use crate::catalog::{CatalogIndexKind, ExpandedIndex};
use crate::schema::{schema_document, IndexKind, TableSchema};
fn sugar_kind_of(kind: CatalogIndexKind) -> Option<&'static str> {
match kind {
CatalogIndexKind::Exact => Some("exact"),
CatalogIndexKind::Sorted => Some("sorted"),
CatalogIndexKind::Fulltext => Some("fulltext"),
CatalogIndexKind::Ttl => None,
}
}
fn sugar_of<'a>(table: &str, index: &'a ExpandedIndex) -> Option<(&'a str, CatalogIndexKind)> {
let [field] = index.fields.as_slice() else { return None };
if index.where_.is_some() || index.expire_after_seconds.is_some() {
return None;
}
let kind = sugar_kind_of(index.kind)?;
(index.name == sugar_index_name(table, field, kind)).then_some((field.as_str(), index.kind))
}
fn served_schema(schema: &TableSchema, served: &[ExpandedIndex]) -> TableSchema {
let table = schema.table.clone();
let mut reconciled = schema.clone();
for index in served {
let Some((field, kind)) = sugar_of(&table, index) else {
continue;
};
let Some(spec) = reconciled.fields.get_mut(field) else {
continue;
};
match kind {
CatalogIndexKind::Exact | CatalogIndexKind::Sorted => {
let expanded = if kind == CatalogIndexKind::Exact {
IndexKind::Exact
} else {
IndexKind::Sorted
};
spec.index = Some(spec.index.unwrap_or(expanded));
spec.unique = spec.unique || index.unique;
}
CatalogIndexKind::Fulltext => spec.fulltext = true,
CatalogIndexKind::Ttl => {}
}
}
reconciled
}
pub fn describe_table_document(table: &super::Table) -> Value {
schema_document(&served_schema(table.schema(), table.declared_indexes()))
}
#[cfg(test)]
mod tests {
use serde_json::json;
use crate::schema::parse_schema;
use super::*;
fn pairs_schema() -> TableSchema {
parse_schema(&json!({
"schemaVersion": 1,
"table": "pairs",
"primaryKey": "id",
"fields": {
"id": { "type": "string" },
"a": { "type": "string" },
"b": { "type": "int" },
},
}))
.expect("the fixture is a usable schema")
}
fn index(name: &str, fields: &[&str], kind: CatalogIndexKind) -> ExpandedIndex {
ExpandedIndex {
name: name.to_string(),
fields: fields.iter().map(|field| (*field).to_string()).collect(),
kind,
unique: false,
where_: None,
expire_after_seconds: None,
}
}
#[test]
fn folds_a_served_sugar_index_back_into_the_field_modifier() {
let served = vec![
index("pairs_a_exact", &["a"], CatalogIndexKind::Exact),
index("pairs_b_sorted", &["b"], CatalogIndexKind::Sorted),
index("pairs_a_fulltext", &["a"], CatalogIndexKind::Fulltext),
];
let document = schema_document(&served_schema(&pairs_schema(), &served));
assert_eq!(
document["fields"]["a"],
json!({ "type": "string", "fulltext": true, "index": "exact" })
);
assert_eq!(document["fields"]["b"], json!({ "type": "int", "index": "sorted" }));
}
#[test]
fn leaves_an_index_named_by_hand_out_of_the_modifiers() {
let served = vec![
index("t_ab", &["a", "b"], CatalogIndexKind::Sorted),
index("by_a", &["a"], CatalogIndexKind::Exact),
];
let document = schema_document(&served_schema(&pairs_schema(), &served));
assert_eq!(document["fields"]["a"], json!({ "type": "string" }));
assert_eq!(document["fields"]["b"], json!({ "type": "int" }));
assert!(document.as_object().unwrap().get("indexes").is_none());
}
#[test]
fn leaves_a_partial_or_ttl_index_out_even_under_a_sugar_name() {
let mut partial = index("pairs_a_exact", &["a"], CatalogIndexKind::Exact);
partial.where_ = Some(vec![crate::catalog::CatalogPredicate {
field: "b".to_string(),
op: ">".to_string(),
value: json!(0),
}]);
let mut expiring = index("pairs_b_sorted", &["b"], CatalogIndexKind::Sorted);
expiring.expire_after_seconds = Some(60);
let document = schema_document(&served_schema(&pairs_schema(), &[partial, expiring]));
assert_eq!(document["fields"]["a"], json!({ "type": "string" }));
assert_eq!(document["fields"]["b"], json!({ "type": "int" }));
}
#[test]
fn keeps_the_kind_the_schema_declares_where_the_served_sugar_disagrees() {
let schema = parse_schema(&json!({
"schemaVersion": 1,
"table": "pairs",
"primaryKey": "id",
"fields": {
"id": { "type": "string" },
"a": { "type": "string", "index": "sorted" },
},
}))
.expect("the fixture is a usable schema");
let mut unique_sugar = index("pairs_a_exact", &["a"], CatalogIndexKind::Exact);
unique_sugar.unique = true;
let document = schema_document(&served_schema(&schema, &[unique_sugar]));
assert_eq!(
document["fields"]["a"],
json!({ "type": "string", "unique": true, "index": "sorted" })
);
}
}