use crate::query::BoolExpr;
use std::any::TypeId;
use std::borrow::Cow;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PropertyMeta {
pub field_name: Cow<'static, str>,
pub column_name: Cow<'static, str>,
pub type_id: TypeId,
pub type_name: Cow<'static, str>,
pub is_primary_key: bool,
pub is_auto_increment: bool,
pub is_sequence: bool,
pub sequence_name: Option<Cow<'static, str>>,
pub is_required: bool,
pub is_foreign_key: bool,
pub is_concurrency_token: bool,
pub max_length: Option<usize>,
pub is_unique: bool,
pub has_index: bool,
pub is_not_mapped: bool,
pub column_type: Option<Cow<'static, str>>,
pub precision: Option<u32>,
pub scale: Option<u32>,
pub is_fixed_length: bool,
pub collation: Option<Cow<'static, str>>,
pub comment: Option<Cow<'static, str>>,
pub computed_sql: Option<Cow<'static, str>>,
pub computed_stored: bool,
pub default_value: Option<Cow<'static, str>>,
}
impl PropertyMeta {
pub fn is_computed(&self) -> bool {
self.computed_sql.is_some()
}
}
pub struct PropertyMetaBuilder {
meta: PropertyMeta,
}
impl PropertyMetaBuilder {
pub fn new(field_name: &'static str, type_id: TypeId, type_name: &'static str) -> Self {
Self {
meta: PropertyMeta {
field_name: Cow::Borrowed(field_name),
column_name: Cow::Borrowed(field_name),
type_id,
type_name: Cow::Borrowed(type_name),
is_primary_key: false,
is_auto_increment: false,
is_sequence: false,
sequence_name: None,
is_required: false,
is_foreign_key: false,
is_concurrency_token: false,
max_length: None,
is_unique: false,
has_index: false,
is_not_mapped: false,
column_type: None,
precision: None,
scale: None,
is_fixed_length: false,
collation: None,
comment: None,
computed_sql: None,
computed_stored: false,
default_value: None,
},
}
}
pub fn column_name(mut self, name: &'static str) -> Self {
self.meta.column_name = Cow::Borrowed(name);
self
}
pub fn is_primary_key(mut self, v: bool) -> Self {
self.meta.is_primary_key = v;
self
}
pub fn is_auto_increment(mut self, v: bool) -> Self {
self.meta.is_auto_increment = v;
self
}
pub fn is_sequence(mut self, v: bool) -> Self {
self.meta.is_sequence = v;
self
}
pub fn sequence_name(mut self, name: &'static str) -> Self {
self.meta.sequence_name = Some(Cow::Borrowed(name));
self
}
pub fn is_required(mut self, v: bool) -> Self {
self.meta.is_required = v;
self
}
pub fn is_foreign_key(mut self, v: bool) -> Self {
self.meta.is_foreign_key = v;
self
}
pub fn is_concurrency_token(mut self, v: bool) -> Self {
self.meta.is_concurrency_token = v;
self
}
pub fn max_length(mut self, n: usize) -> Self {
self.meta.max_length = Some(n);
self
}
pub fn is_unique(mut self, v: bool) -> Self {
self.meta.is_unique = v;
self
}
pub fn has_index(mut self, v: bool) -> Self {
self.meta.has_index = v;
self
}
pub fn is_not_mapped(mut self, v: bool) -> Self {
self.meta.is_not_mapped = v;
self
}
pub fn column_type(mut self, name: &'static str) -> Self {
self.meta.column_type = Some(Cow::Borrowed(name));
self
}
pub fn precision(mut self, precision: u32, scale: u32) -> Self {
self.meta.precision = Some(precision);
self.meta.scale = Some(scale);
self
}
pub fn is_fixed_length(mut self, v: bool) -> Self {
self.meta.is_fixed_length = v;
self
}
pub fn collation(mut self, name: &'static str) -> Self {
self.meta.collation = Some(Cow::Borrowed(name));
self
}
pub fn comment(mut self, text: &'static str) -> Self {
self.meta.comment = Some(Cow::Borrowed(text));
self
}
pub fn computed_sql(mut self, sql: &'static str, stored: bool) -> Self {
self.meta.computed_sql = Some(Cow::Borrowed(sql));
self.meta.computed_stored = stored;
self
}
pub fn default_value(mut self, sql: &'static str) -> Self {
self.meta.default_value = Some(Cow::Borrowed(sql));
self
}
pub fn build(self) -> PropertyMeta {
self.meta
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavigationKind {
BelongsTo,
HasOne,
HasMany,
ManyToMany,
}
#[derive(Debug, Clone)]
pub struct NavigationMeta {
pub field_name: Cow<'static, str>,
pub kind: NavigationKind,
pub related_type_id: TypeId,
pub related_type_name: Cow<'static, str>,
pub foreign_key_field: Option<Cow<'static, str>>,
pub inverse_navigation: Option<Cow<'static, str>>,
pub through_type_id: Option<TypeId>,
pub through_table: Option<Cow<'static, str>>,
pub through_parent_fk: Option<Cow<'static, str>>,
pub through_related_fk: Option<Cow<'static, str>>,
pub through_parent_fk_index: usize,
pub through_related_fk_index: usize,
pub related_table: Option<Cow<'static, str>>,
pub fk_column: Option<Cow<'static, str>>,
pub referenced_key_column: Option<Cow<'static, str>>,
pub fk_row_index: usize,
pub pk_row_index: usize,
pub related_entity_meta: Option<fn() -> EntityTypeMeta>,
pub delete_behavior: Option<crate::relations::DeleteBehavior>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum IndexColumn {
Column(Cow<'static, str>),
Expression(Cow<'static, str>),
}
impl IndexColumn {
pub fn as_str(&self) -> &str {
match self {
IndexColumn::Column(s) | IndexColumn::Expression(s) => s.as_ref(),
}
}
pub fn is_expression(&self) -> bool {
matches!(self, IndexColumn::Expression(_))
}
}
#[derive(Debug, Clone, Default)]
pub struct IndexMeta {
pub name: Option<Cow<'static, str>>,
pub columns: Vec<IndexColumn>,
pub is_unique: bool,
pub filter: Option<BoolExpr>,
pub include: Vec<Cow<'static, str>>,
}
impl IndexMeta {
pub fn derive_name(&self, table: &str) -> String {
let mut name = format!("ix_{table}");
for col in &self.columns {
if col.is_expression() {
let mut hash: u64 = 1469598103934665603;
for b in col.as_str().as_bytes() {
hash ^= *b as u64;
hash = hash.wrapping_mul(1099511628211);
}
name.push_str(&format!("_expr{hash:08x}"));
} else {
name.push('_');
name.push_str(col.as_str());
}
}
sanitize_index_name(&name)
}
}
pub(crate) fn sanitize_index_name(raw: &str) -> String {
let mut out: String = raw
.chars()
.map(|c| {
if c.is_ascii_alphanumeric() || c == '_' {
c
} else {
'_'
}
})
.collect();
if out.len() > 64 {
let mut truncated: String = out.chars().take(55).collect();
let mut hash: u64 = 1469598103934665603;
for b in raw.as_bytes() {
hash ^= *b as u64;
hash = hash.wrapping_mul(1099511628211);
}
truncated.push('_');
truncated.push_str(&format!("{hash:08x}"));
out = truncated;
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForeignKeyMeta {
pub name: Option<Cow<'static, str>>,
pub columns: Vec<Cow<'static, str>>,
pub referenced_table: Cow<'static, str>,
pub referenced_columns: Vec<Cow<'static, str>>,
pub on_delete: Option<Cow<'static, str>>,
}
impl ForeignKeyMeta {
pub fn derive_name(&self, table: &str) -> String {
let cols: Vec<&str> = self.columns.iter().map(|c| c.as_ref()).collect();
sanitize_index_name(&format!(
"fk_{table}_{}_{}",
cols.join("_"),
self.referenced_table
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckMeta {
pub name: Cow<'static, str>,
pub expression: Cow<'static, str>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TableOptions {
pub engine: Option<Cow<'static, str>>,
pub charset: Option<Cow<'static, str>>,
pub collate: Option<Cow<'static, str>>,
}
impl TableOptions {
pub fn is_empty(&self) -> bool {
self.engine.is_none() && self.charset.is_none() && self.collate.is_none()
}
}
#[derive(Debug, Clone)]
pub struct EntityTypeMeta {
pub type_id: TypeId,
pub type_name: Cow<'static, str>,
pub table_name: Cow<'static, str>,
pub properties: Vec<PropertyMeta>,
pub navigations: Vec<NavigationMeta>,
pub primary_keys: Vec<Cow<'static, str>>,
pub indexes: Vec<IndexMeta>,
pub foreign_keys: Vec<ForeignKeyMeta>,
pub checks: Vec<CheckMeta>,
pub table_options: TableOptions,
pub is_view: bool,
pub is_keyless: bool,
pub comment: Option<Cow<'static, str>>,
#[doc(hidden)]
pub property_index: std::sync::OnceLock<HashMap<String, usize>>,
#[doc(hidden)]
pub navigation_index: std::sync::OnceLock<HashMap<String, usize>>,
}
impl EntityTypeMeta {
pub fn find_property(&self, field_name: &str) -> Option<&PropertyMeta> {
let idx = self
.property_index
.get_or_init(|| {
self.properties
.iter()
.enumerate()
.map(|(i, p)| (p.field_name.to_string(), i))
.collect()
})
.get(field_name)
.copied();
idx.and_then(|i| self.properties.get(i))
}
pub fn find_navigation(&self, field_name: &str) -> Option<&NavigationMeta> {
let idx = self
.navigation_index
.get_or_init(|| {
self.navigations
.iter()
.enumerate()
.map(|(i, n)| (n.field_name.to_string(), i))
.collect()
})
.get(field_name)
.copied();
idx.and_then(|i| self.navigations.get(i))
}
pub fn primary_key_property(&self) -> Option<&PropertyMeta> {
self.properties.iter().find(|p| p.is_primary_key)
}
pub fn mapped_scalar_properties(&self) -> impl Iterator<Item = &PropertyMeta> {
self.properties.iter().filter(|p| !p.is_not_mapped)
}
pub fn writable_scalar_properties(&self) -> impl Iterator<Item = &PropertyMeta> {
self.properties
.iter()
.filter(|p| !p.is_not_mapped && !p.is_computed())
}
}
impl Default for EntityTypeMeta {
fn default() -> Self {
Self {
type_id: TypeId::of::<()>(),
type_name: Cow::Borrowed(""),
table_name: Cow::Borrowed(""),
properties: Vec::new(),
navigations: Vec::new(),
primary_keys: Vec::new(),
indexes: Vec::new(),
foreign_keys: Vec::new(),
checks: Vec::new(),
table_options: TableOptions::default(),
is_view: false,
is_keyless: false,
comment: None,
property_index: std::sync::OnceLock::new(),
navigation_index: std::sync::OnceLock::new(),
}
}
}