use super::{SchemaExpressionIndexKeyItemInfo, SchemaInfo};
use crate::{
db::IndexState,
model::{entity::EntityModel, field::FieldModel},
};
use std::fmt::Write;
#[must_use]
pub(in crate::db) fn show_indexes_for_model(model: &EntityModel) -> Vec<String> {
show_indexes_for_model_with_runtime_state(model, None)
}
#[must_use]
pub(in crate::db) fn show_indexes_for_model_with_runtime_state(
model: &EntityModel,
runtime_state: Option<IndexState>,
) -> Vec<String> {
let mut indexes = Vec::with_capacity(model.indexes.len().saturating_add(1));
indexes.push(render_index_listing_line(
"PRIMARY KEY",
None,
&primary_key_fields_from_model(model),
None,
runtime_state,
Some("generated"),
));
for index in model.indexes {
indexes.push(render_index_listing_line(
if index.is_unique() {
"UNIQUE INDEX"
} else {
"INDEX"
},
Some(index.name()),
index.fields(),
index.predicate(),
runtime_state,
Some("generated"),
));
}
indexes
}
#[must_use]
pub(in crate::db) fn show_indexes_for_schema_info_with_runtime_state(
schema: &SchemaInfo,
runtime_state: Option<IndexState>,
) -> Vec<String> {
let mut indexes = Vec::with_capacity(
schema
.field_path_indexes()
.len()
.saturating_add(schema.expression_indexes().len())
.saturating_add(1),
);
if !schema.primary_key_names().is_empty() {
let primary_key_fields = primary_key_fields_from_schema(schema);
indexes.push(render_index_listing_line(
"PRIMARY KEY",
None,
&primary_key_fields,
None,
runtime_state,
Some("generated"),
));
}
for index in schema.field_path_indexes() {
let fields: Vec<String> = index
.fields()
.iter()
.map(|field| field.path().join("."))
.collect();
let field_refs: Vec<&str> = fields.iter().map(String::as_str).collect();
indexes.push(render_index_listing_line(
if index.unique() {
"UNIQUE INDEX"
} else {
"INDEX"
},
Some(index.name()),
&field_refs,
index.predicate_sql(),
runtime_state,
Some(if index.generated() {
"generated"
} else {
"ddl"
}),
));
}
for index in schema.expression_indexes() {
let fields: Vec<String> = index
.key_items()
.iter()
.map(|item| match item {
SchemaExpressionIndexKeyItemInfo::FieldPath(field) => field.path().join("."),
SchemaExpressionIndexKeyItemInfo::Expression(expression) => {
expression.canonical_text().to_string()
}
})
.collect();
let field_refs: Vec<&str> = fields.iter().map(String::as_str).collect();
indexes.push(render_index_listing_line(
if index.unique() {
"UNIQUE INDEX"
} else {
"INDEX"
},
Some(index.name()),
&field_refs,
index.predicate_sql(),
runtime_state,
Some(if index.generated() {
"generated"
} else {
"ddl"
}),
));
}
indexes
}
fn primary_key_fields_from_model(model: &EntityModel) -> Vec<&'static str> {
model
.primary_key_model()
.fields()
.iter()
.map(FieldModel::name)
.collect()
}
fn primary_key_fields_from_schema(schema: &SchemaInfo) -> Vec<&str> {
schema
.primary_key_names()
.iter()
.map(String::as_str)
.collect()
}
fn render_index_listing_line(
kind: &str,
name: Option<&str>,
fields: &[&str],
predicate_sql: Option<&str>,
runtime_state: Option<IndexState>,
origin: Option<&str>,
) -> String {
let mut rendered = String::with_capacity(48 + fields.len().saturating_mul(16));
rendered.push_str(kind);
if let Some(name) = name {
rendered.push(' ');
rendered.push_str(name);
}
rendered.push_str(" (");
for (index, field) in fields.iter().enumerate() {
if index > 0 {
rendered.push_str(", ");
}
rendered.push_str(field);
}
rendered.push(')');
if let Some(predicate_sql) = predicate_sql {
let _ = write!(rendered, " WHERE {predicate_sql}");
}
if let Some(state) = runtime_state {
let _ = write!(rendered, " [state={}]", state.as_str());
}
if let Some(origin) = origin {
let _ = write!(rendered, " [origin={origin}]");
}
rendered
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
model::{EntityModel, FieldKind, FieldModel, IndexModel, PrimaryKeyModel},
testing::entity_model_from_static,
};
static SCALAR_FIELDS: [FieldModel; 1] = [FieldModel::generated("id", FieldKind::Nat64)];
static COMPOSITE_FIELDS: [FieldModel; 3] = [
FieldModel::generated("tenant_id", FieldKind::Nat64),
FieldModel::generated("local_id", FieldKind::Nat64),
FieldModel::generated("label", FieldKind::Text { max_len: None }),
];
static EMPTY_INDEXES: [&IndexModel; 0] = [];
static SCALAR_MODEL: EntityModel = entity_model_from_static(
"schema::format::tests::ScalarEntity",
"ScalarEntity",
&SCALAR_FIELDS[0],
0,
&SCALAR_FIELDS,
&EMPTY_INDEXES,
);
static COMPOSITE_PK_FIELDS: [&FieldModel; 2] = [&COMPOSITE_FIELDS[0], &COMPOSITE_FIELDS[1]];
static COMPOSITE_MODEL: EntityModel = EntityModel::generated_with_primary_key_model(
"schema::format::tests::CompositeEntity",
"CompositeEntity",
1,
PrimaryKeyModel::ordered(&COMPOSITE_PK_FIELDS),
0,
&COMPOSITE_FIELDS,
&EMPTY_INDEXES,
);
#[test]
fn show_indexes_for_model_keeps_scalar_primary_key_format() {
let indexes = show_indexes_for_model(&SCALAR_MODEL);
assert_eq!(indexes[0], "PRIMARY KEY (id) [origin=generated]");
}
#[test]
fn show_indexes_for_model_lists_composite_primary_key_fields() {
let indexes = show_indexes_for_model(&COMPOSITE_MODEL);
assert_eq!(
indexes[0],
"PRIMARY KEY (tenant_id, local_id) [origin=generated]",
);
}
#[test]
fn show_indexes_for_schema_info_lists_composite_primary_key_fields() {
let schema = SchemaInfo::cached_for_generated_entity_model(&COMPOSITE_MODEL);
let indexes = show_indexes_for_schema_info_with_runtime_state(schema, None);
assert_eq!(
indexes[0],
"PRIMARY KEY (tenant_id, local_id) [origin=generated]",
);
}
}