use crate::{
db::schema::{FieldType, field_type_from_model_kind},
model::{
entity::EntityModel,
field::{FieldKind, FieldModel},
},
};
use std::sync::{Mutex, OnceLock};
type SchemaFieldEntry = (&'static str, SchemaFieldInfo);
type CachedSchemaEntries = Vec<(&'static str, &'static SchemaInfo)>;
fn schema_field_info<'a>(
fields: &'a [SchemaFieldEntry],
name: &str,
) -> Option<&'a SchemaFieldInfo> {
fields
.binary_search_by_key(&name, |(field_name, _)| *field_name)
.ok()
.map(|index| &fields[index].1)
}
#[derive(Clone, Debug)]
struct SchemaFieldInfo {
ty: FieldType,
kind: FieldKind,
}
#[derive(Clone, Debug)]
pub(crate) struct SchemaInfo {
fields: Vec<SchemaFieldEntry>,
}
impl SchemaInfo {
fn from_trusted_field_models(fields: &[FieldModel]) -> Self {
let mut fields = fields
.iter()
.map(|field| {
(
field.name(),
SchemaFieldInfo {
ty: field_type_from_model_kind(&field.kind()),
kind: field.kind(),
},
)
})
.collect::<Vec<_>>();
fields.sort_unstable_by_key(|(field_name, _)| *field_name);
Self { fields }
}
fn from_trusted_entity_model(model: &EntityModel) -> Self {
Self::from_trusted_field_models(model.fields())
}
#[must_use]
pub(crate) fn field(&self, name: &str) -> Option<&FieldType> {
schema_field_info(self.fields.as_slice(), name).map(|field| &field.ty)
}
#[must_use]
pub(crate) fn field_kind(&self, name: &str) -> Option<&FieldKind> {
schema_field_info(self.fields.as_slice(), name).map(|field| &field.kind)
}
#[must_use]
pub(crate) fn from_field_models(fields: &[FieldModel]) -> Self {
Self::from_trusted_field_models(fields)
}
pub(crate) fn cached_for_entity_model(model: &EntityModel) -> &'static Self {
static CACHE: OnceLock<Mutex<CachedSchemaEntries>> = OnceLock::new();
let cache = CACHE.get_or_init(|| Mutex::new(CachedSchemaEntries::new()));
let mut guard = cache.lock().expect("schema info cache mutex poisoned");
if let Some(cached) = guard
.iter()
.find(|(entity_path, _)| *entity_path == model.path())
.map(|(_, schema)| *schema)
{
return cached;
}
let schema = Box::leak(Box::new(Self::from_trusted_entity_model(model)));
guard.push((model.path(), schema));
schema
}
}
#[cfg(test)]
mod tests {
use crate::{
db::schema::SchemaInfo,
model::{
entity::EntityModel,
field::{FieldKind, FieldModel},
index::IndexModel,
},
testing::entity_model_from_static,
};
static FIELDS: [FieldModel; 2] = [
FieldModel::generated("name", FieldKind::Text),
FieldModel::generated("id", FieldKind::Ulid),
];
static INDEXES: [&IndexModel; 0] = [];
static MODEL: EntityModel = entity_model_from_static(
"schema::info::tests::Entity",
"Entity",
&FIELDS[1],
1,
&FIELDS,
&INDEXES,
);
#[test]
fn cached_for_entity_model_reuses_one_schema_instance() {
let first = SchemaInfo::cached_for_entity_model(&MODEL);
let second = SchemaInfo::cached_for_entity_model(&MODEL);
assert!(std::ptr::eq(first, second));
assert!(first.field("id").is_some());
assert!(first.field("name").is_some());
}
}