use crate::span::Span;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Schema {
pub declarations: Vec<Declaration>,
pub span: Span,
}
impl Schema {
pub fn new(declarations: Vec<Declaration>, span: Span) -> Self {
Self { declarations, span }
}
pub fn models(&self) -> impl Iterator<Item = &ModelDecl> {
self.declarations.iter().filter_map(|d| match d {
Declaration::Model(m) => Some(m),
_ => None,
})
}
pub fn enums(&self) -> impl Iterator<Item = &EnumDecl> {
self.declarations.iter().filter_map(|d| match d {
Declaration::Enum(e) => Some(e),
_ => None,
})
}
pub fn types(&self) -> impl Iterator<Item = &TypeDecl> {
self.declarations.iter().filter_map(|d| match d {
Declaration::Type(t) => Some(t),
_ => None,
})
}
pub fn datasource(&self) -> Option<&DatasourceDecl> {
self.declarations.iter().find_map(|d| match d {
Declaration::Datasource(ds) => Some(ds),
_ => None,
})
}
pub fn generator(&self) -> Option<&GeneratorDecl> {
self.declarations.iter().find_map(|d| match d {
Declaration::Generator(g) => Some(g),
_ => None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Declaration {
Datasource(DatasourceDecl),
Generator(GeneratorDecl),
Model(ModelDecl),
Enum(EnumDecl),
Type(TypeDecl),
}
impl Declaration {
pub fn span(&self) -> Span {
match self {
Declaration::Datasource(d) => d.span,
Declaration::Generator(g) => g.span,
Declaration::Model(m) => m.span,
Declaration::Enum(e) => e.span,
Declaration::Type(t) => t.span,
}
}
pub fn name(&self) -> &str {
match self {
Declaration::Datasource(d) => &d.name.value,
Declaration::Generator(g) => &g.name.value,
Declaration::Model(m) => &m.name.value,
Declaration::Enum(e) => &e.name.value,
Declaration::Type(t) => &t.name.value,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DatasourceDecl {
pub name: Ident,
pub fields: Vec<ConfigField>,
pub span: Span,
}
impl DatasourceDecl {
pub fn find_field(&self, name: &str) -> Option<&ConfigField> {
self.fields.iter().find(|f| f.name.value == name)
}
pub fn provider(&self) -> Option<&str> {
self.find_field("provider").and_then(|f| match &f.value {
Expr::Literal(Literal::String(s, _)) => Some(s.as_str()),
_ => None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GeneratorDecl {
pub name: Ident,
pub fields: Vec<ConfigField>,
pub span: Span,
}
impl GeneratorDecl {
pub fn find_field(&self, name: &str) -> Option<&ConfigField> {
self.fields.iter().find(|f| f.name.value == name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConfigField {
pub name: Ident,
pub value: Expr,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModelDecl {
pub name: Ident,
pub fields: Vec<FieldDecl>,
pub attributes: Vec<ModelAttribute>,
pub span: Span,
}
impl ModelDecl {
pub fn find_field(&self, name: &str) -> Option<&FieldDecl> {
self.fields.iter().find(|f| f.name.value == name)
}
pub fn table_name(&self) -> &str {
self.attributes
.iter()
.find_map(|attr| match attr {
ModelAttribute::Map(name) => Some(name.as_str()),
_ => None,
})
.unwrap_or(&self.name.value)
}
pub fn has_composite_key(&self) -> bool {
self.attributes
.iter()
.any(|attr| matches!(attr, ModelAttribute::Id(_)))
}
pub fn relation_fields(&self) -> impl Iterator<Item = &FieldDecl> {
self.fields.iter().filter(|f| {
f.has_relation_attribute() || matches!(f.field_type, FieldType::UserType(_))
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldDecl {
pub name: Ident,
pub field_type: FieldType,
pub modifier: FieldModifier,
pub attributes: Vec<FieldAttribute>,
pub span: Span,
}
impl FieldDecl {
pub fn is_optional(&self) -> bool {
matches!(self.modifier, FieldModifier::Optional)
}
pub fn is_not_null(&self) -> bool {
matches!(self.modifier, FieldModifier::NotNull)
}
pub fn is_array(&self) -> bool {
matches!(self.modifier, FieldModifier::Array)
}
pub fn find_attribute(&self, kind: &str) -> Option<&FieldAttribute> {
self.attributes.iter().find(|attr| {
matches!(
(kind, attr),
("id", FieldAttribute::Id)
| ("unique", FieldAttribute::Unique)
| ("default", FieldAttribute::Default(_, _))
| ("map", FieldAttribute::Map(_))
| ("relation", FieldAttribute::Relation { .. })
| ("check", FieldAttribute::Check { .. })
)
})
}
pub fn has_relation_attribute(&self) -> bool {
self.attributes
.iter()
.any(|attr| matches!(attr, FieldAttribute::Relation { .. }))
}
pub fn column_name(&self) -> &str {
self.attributes
.iter()
.find_map(|attr| match attr {
FieldAttribute::Map(name) => Some(name.as_str()),
_ => None,
})
.unwrap_or(&self.name.value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldModifier {
None,
Optional,
NotNull,
Array,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldType {
String,
Boolean,
Int,
BigInt,
Float,
Decimal {
precision: u32,
scale: u32,
},
DateTime,
Bytes,
Json,
Uuid,
Jsonb,
Xml,
Char {
length: u32,
},
VarChar {
length: u32,
},
UserType(String),
}
impl fmt::Display for FieldType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FieldType::String => write!(f, "String"),
FieldType::Boolean => write!(f, "Boolean"),
FieldType::Int => write!(f, "Int"),
FieldType::BigInt => write!(f, "BigInt"),
FieldType::Float => write!(f, "Float"),
FieldType::Decimal { precision, scale } => {
write!(f, "Decimal({}, {})", precision, scale)
}
FieldType::DateTime => write!(f, "DateTime"),
FieldType::Bytes => write!(f, "Bytes"),
FieldType::Json => write!(f, "Json"),
FieldType::Uuid => write!(f, "Uuid"),
FieldType::Jsonb => write!(f, "Jsonb"),
FieldType::Xml => write!(f, "Xml"),
FieldType::Char { length } => write!(f, "Char({})", length),
FieldType::VarChar { length } => write!(f, "VarChar({})", length),
FieldType::UserType(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageStrategy {
Native,
Json,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ComputedKind {
Stored,
Virtual,
}
#[derive(Debug, Clone, PartialEq)]
pub enum FieldAttribute {
Id,
Unique,
Default(Expr, Span),
Map(String),
Store {
strategy: StorageStrategy,
span: Span,
},
Relation {
name: Option<String>,
fields: Option<Vec<Ident>>,
references: Option<Vec<Ident>>,
on_delete: Option<ReferentialAction>,
on_update: Option<ReferentialAction>,
span: Span,
},
UpdatedAt {
span: Span,
},
Computed {
expr: crate::sql_expr::SqlExpr,
kind: ComputedKind,
span: Span,
},
Check {
expr: crate::bool_expr::BoolExpr,
span: Span,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReferentialAction {
Cascade,
Restrict,
NoAction,
SetNull,
SetDefault,
}
impl fmt::Display for ReferentialAction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ReferentialAction::Cascade => write!(f, "Cascade"),
ReferentialAction::Restrict => write!(f, "Restrict"),
ReferentialAction::NoAction => write!(f, "NoAction"),
ReferentialAction::SetNull => write!(f, "SetNull"),
ReferentialAction::SetDefault => write!(f, "SetDefault"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ModelAttribute {
Map(String),
Id(Vec<Ident>),
Unique(Vec<Ident>),
Index {
fields: Vec<Ident>,
index_type: Option<Ident>,
name: Option<String>,
map: Option<String>,
},
Check {
expr: crate::bool_expr::BoolExpr,
span: Span,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumDecl {
pub name: Ident,
pub variants: Vec<EnumVariant>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TypeDecl {
pub name: Ident,
pub fields: Vec<FieldDecl>,
pub span: Span,
}
impl TypeDecl {
pub fn find_field(&self, name: &str) -> Option<&FieldDecl> {
self.fields.iter().find(|f| f.name.value == name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumVariant {
pub name: Ident,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Ident {
pub value: String,
pub span: Span,
}
impl Ident {
pub fn new(value: String, span: Span) -> Self {
Self { value, span }
}
}
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.value)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Literal(Literal),
FunctionCall {
name: Ident,
args: Vec<Expr>,
span: Span,
},
Array {
elements: Vec<Expr>,
span: Span,
},
NamedArg {
name: Ident,
value: Box<Expr>,
span: Span,
},
Ident(Ident),
}
impl Expr {
pub fn span(&self) -> Span {
match self {
Expr::Literal(lit) => lit.span(),
Expr::FunctionCall { span, .. } => *span,
Expr::Array { span, .. } => *span,
Expr::NamedArg { span, .. } => *span,
Expr::Ident(ident) => ident.span,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
String(String, Span),
Number(String, Span),
Boolean(bool, Span),
}
impl Literal {
pub fn span(&self) -> Span {
match self {
Literal::String(_, span) => *span,
Literal::Number(_, span) => *span,
Literal::Boolean(_, span) => *span,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_modifier() {
assert_eq!(FieldModifier::None, FieldModifier::None);
assert_ne!(FieldModifier::Optional, FieldModifier::Array);
}
#[test]
fn test_field_type_display() {
assert_eq!(FieldType::String.to_string(), "String");
assert_eq!(FieldType::Int.to_string(), "Int");
assert_eq!(
FieldType::Decimal {
precision: 10,
scale: 2
}
.to_string(),
"Decimal(10, 2)"
);
}
#[test]
fn test_ident() {
let ident = Ident::new("test".to_string(), Span::new(0, 4));
assert_eq!(ident.value, "test");
assert_eq!(ident.to_string(), "test");
}
#[test]
fn test_referential_action_display() {
assert_eq!(ReferentialAction::Cascade.to_string(), "Cascade");
assert_eq!(ReferentialAction::SetNull.to_string(), "SetNull");
}
#[test]
fn test_model_table_name() {
let model = ModelDecl {
name: Ident::new("User".to_string(), Span::new(0, 4)),
fields: vec![],
attributes: vec![ModelAttribute::Map("users".to_string())],
span: Span::new(0, 10),
};
assert_eq!(model.table_name(), "users");
}
#[test]
fn test_model_table_name_default() {
let model = ModelDecl {
name: Ident::new("User".to_string(), Span::new(0, 4)),
fields: vec![],
attributes: vec![],
span: Span::new(0, 10),
};
assert_eq!(model.table_name(), "User");
}
#[test]
fn test_field_column_name() {
let field = FieldDecl {
name: Ident::new("userId".to_string(), Span::new(0, 6)),
field_type: FieldType::Int,
modifier: FieldModifier::None,
attributes: vec![FieldAttribute::Map("user_id".to_string())],
span: Span::new(0, 20),
};
assert_eq!(field.column_name(), "user_id");
}
#[test]
fn test_schema_helpers() {
let schema = Schema {
declarations: vec![
Declaration::Model(ModelDecl {
name: Ident::new("User".to_string(), Span::new(0, 4)),
fields: vec![],
attributes: vec![],
span: Span::new(0, 10),
}),
Declaration::Enum(EnumDecl {
name: Ident::new("Role".to_string(), Span::new(0, 4)),
variants: vec![],
span: Span::new(0, 10),
}),
],
span: Span::new(0, 100),
};
assert_eq!(schema.models().count(), 1);
assert_eq!(schema.enums().count(), 1);
assert!(schema.datasource().is_none());
}
}