pub use crate::ast::ComputedKind;
use crate::ast::{ReferentialAction, StorageStrategy};
use crate::span::Span;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq)]
pub struct SchemaIr {
pub datasource: Option<DatasourceIr>,
pub generator: Option<GeneratorIr>,
pub models: HashMap<String, ModelIr>,
pub enums: HashMap<String, EnumIr>,
pub composite_types: HashMap<String, CompositeTypeIr>,
}
impl SchemaIr {
pub fn new() -> Self {
Self {
datasource: None,
generator: None,
models: HashMap::new(),
enums: HashMap::new(),
composite_types: HashMap::new(),
}
}
pub fn get_model(&self, name: &str) -> Option<&ModelIr> {
self.models.get(name)
}
pub fn get_enum(&self, name: &str) -> Option<&EnumIr> {
self.enums.get(name)
}
pub fn get_composite_type(&self, name: &str) -> Option<&CompositeTypeIr> {
self.composite_types.get(name)
}
}
impl Default for SchemaIr {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct DatasourceIr {
pub name: String,
pub provider: String,
pub url: String,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum InterfaceKind {
#[default]
Sync,
Async,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GeneratorIr {
pub name: String,
pub provider: String,
pub output: Option<String>,
pub interface: InterfaceKind,
pub recursive_type_depth: usize,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ModelIr {
pub logical_name: String,
pub db_name: String,
pub fields: Vec<FieldIr>,
pub primary_key: PrimaryKeyIr,
pub unique_constraints: Vec<UniqueConstraintIr>,
pub indexes: Vec<IndexIr>,
pub check_constraints: Vec<String>,
pub span: Span,
}
impl ModelIr {
pub fn find_field(&self, name: &str) -> Option<&FieldIr> {
self.fields.iter().find(|f| f.logical_name == name)
}
pub fn scalar_fields(&self) -> impl Iterator<Item = &FieldIr> {
self.fields
.iter()
.filter(|f| !matches!(f.field_type, ResolvedFieldType::Relation(_)))
}
pub fn relation_fields(&self) -> impl Iterator<Item = &FieldIr> {
self.fields
.iter()
.filter(|f| matches!(f.field_type, ResolvedFieldType::Relation(_)))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldIr {
pub logical_name: String,
pub db_name: String,
pub field_type: ResolvedFieldType,
pub is_required: bool,
pub is_array: bool,
pub storage_strategy: Option<StorageStrategy>,
pub default_value: Option<DefaultValue>,
pub is_unique: bool,
pub is_updated_at: bool,
pub computed: Option<(String, ComputedKind)>,
pub check: Option<String>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResolvedFieldType {
Scalar(ScalarType),
Enum {
enum_name: String,
},
Relation(RelationIr),
CompositeType {
type_name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScalarType {
String,
Boolean,
Int,
BigInt,
Float,
Decimal {
precision: u32,
scale: u32,
},
DateTime,
Bytes,
Json,
Uuid,
Jsonb,
Xml,
Char {
length: u32,
},
VarChar {
length: u32,
},
}
impl ScalarType {
pub fn rust_type(&self) -> &'static str {
match self {
ScalarType::String => "String",
ScalarType::Boolean => "bool",
ScalarType::Int => "i32",
ScalarType::BigInt => "i64",
ScalarType::Float => "f64",
ScalarType::Decimal { .. } => "rust_decimal::Decimal",
ScalarType::DateTime => "chrono::NaiveDateTime",
ScalarType::Bytes => "Vec<u8>",
ScalarType::Json => "serde_json::Value",
ScalarType::Uuid => "uuid::Uuid",
ScalarType::Jsonb => "serde_json::Value",
ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "String",
}
}
pub fn supported_by(self, provider: DatabaseProvider) -> bool {
match self {
ScalarType::Jsonb | ScalarType::Xml => provider == DatabaseProvider::Postgres,
ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
matches!(
provider,
DatabaseProvider::Postgres | DatabaseProvider::Mysql
)
}
_ => true,
}
}
pub fn supported_providers(self) -> &'static str {
match self {
ScalarType::Jsonb | ScalarType::Xml => "PostgreSQL only",
ScalarType::Char { .. } | ScalarType::VarChar { .. } => "PostgreSQL and MySQL",
_ => "all databases",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RelationIr {
pub name: Option<String>,
pub target_model: String,
pub fields: Vec<String>,
pub references: Vec<String>,
pub on_delete: Option<ReferentialAction>,
pub on_update: Option<ReferentialAction>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DefaultValue {
String(String),
Number(String),
Boolean(bool),
EnumVariant(String),
Function(FunctionCall),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FunctionCall {
pub name: String,
pub args: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrimaryKeyIr {
Single(String),
Composite(Vec<String>),
}
impl PrimaryKeyIr {
pub fn fields(&self) -> Vec<&str> {
match self {
PrimaryKeyIr::Single(field) => vec![field.as_str()],
PrimaryKeyIr::Composite(fields) => fields.iter().map(|s| s.as_str()).collect(),
}
}
pub fn is_single(&self) -> bool {
matches!(self, PrimaryKeyIr::Single(_))
}
pub fn is_composite(&self) -> bool {
matches!(self, PrimaryKeyIr::Composite(_))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct UniqueConstraintIr {
pub fields: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IndexType {
BTree,
Hash,
Gin,
Gist,
Brin,
FullText,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseIndexTypeError;
impl fmt::Display for ParseIndexTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown index type")
}
}
impl std::error::Error for ParseIndexTypeError {}
impl FromStr for IndexType {
type Err = ParseIndexTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"btree" => Ok(IndexType::BTree),
"hash" => Ok(IndexType::Hash),
"gin" => Ok(IndexType::Gin),
"gist" => Ok(IndexType::Gist),
"brin" => Ok(IndexType::Brin),
"fulltext" => Ok(IndexType::FullText),
_ => Err(ParseIndexTypeError),
}
}
}
impl IndexType {
pub fn supported_by(self, provider: DatabaseProvider) -> bool {
match self {
IndexType::BTree => true,
IndexType::Hash => matches!(
provider,
DatabaseProvider::Postgres | DatabaseProvider::Mysql
),
IndexType::Gin | IndexType::Gist | IndexType::Brin => {
provider == DatabaseProvider::Postgres
}
IndexType::FullText => provider == DatabaseProvider::Mysql,
}
}
pub fn supported_providers(self) -> &'static str {
match self {
IndexType::BTree => "all databases",
IndexType::Hash => "PostgreSQL and MySQL",
IndexType::Gin => "PostgreSQL only",
IndexType::Gist => "PostgreSQL only",
IndexType::Brin => "PostgreSQL only",
IndexType::FullText => "MySQL only",
}
}
pub fn as_str(self) -> &'static str {
match self {
IndexType::BTree => "BTree",
IndexType::Hash => "Hash",
IndexType::Gin => "Gin",
IndexType::Gist => "Gist",
IndexType::Brin => "Brin",
IndexType::FullText => "FullText",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DatabaseProvider {
Postgres,
Mysql,
Sqlite,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseDatabaseProviderError;
impl fmt::Display for ParseDatabaseProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown database provider")
}
}
impl std::error::Error for ParseDatabaseProviderError {}
impl FromStr for DatabaseProvider {
type Err = ParseDatabaseProviderError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"postgresql" => Ok(DatabaseProvider::Postgres),
"mysql" => Ok(DatabaseProvider::Mysql),
"sqlite" => Ok(DatabaseProvider::Sqlite),
_ => Err(ParseDatabaseProviderError),
}
}
}
impl DatabaseProvider {
pub const ALL: &'static [&'static str] = &["postgresql", "mysql", "sqlite"];
pub fn as_str(self) -> &'static str {
match self {
DatabaseProvider::Postgres => "postgresql",
DatabaseProvider::Mysql => "mysql",
DatabaseProvider::Sqlite => "sqlite",
}
}
pub fn display_name(self) -> &'static str {
match self {
DatabaseProvider::Postgres => "PostgreSQL",
DatabaseProvider::Mysql => "MySQL",
DatabaseProvider::Sqlite => "SQLite",
}
}
}
impl std::fmt::Display for DatabaseProvider {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClientProvider {
Rust,
Python,
JavaScript,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseClientProviderError;
impl fmt::Display for ParseClientProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("unknown client provider")
}
}
impl std::error::Error for ParseClientProviderError {}
impl FromStr for ClientProvider {
type Err = ParseClientProviderError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"nautilus-client-rs" => Ok(ClientProvider::Rust),
"nautilus-client-py" => Ok(ClientProvider::Python),
"nautilus-client-js" => Ok(ClientProvider::JavaScript),
_ => Err(ParseClientProviderError),
}
}
}
impl ClientProvider {
pub const ALL: &'static [&'static str] = &[
"nautilus-client-rs",
"nautilus-client-py",
"nautilus-client-js",
];
pub fn as_str(self) -> &'static str {
match self {
ClientProvider::Rust => "nautilus-client-rs",
ClientProvider::Python => "nautilus-client-py",
ClientProvider::JavaScript => "nautilus-client-js",
}
}
}
impl fmt::Display for ClientProvider {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct IndexIr {
pub fields: Vec<String>,
pub index_type: Option<IndexType>,
pub name: Option<String>,
pub map: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct EnumIr {
pub logical_name: String,
pub variants: Vec<String>,
pub span: Span,
}
impl EnumIr {
pub fn has_variant(&self, name: &str) -> bool {
self.variants.iter().any(|v| v == name)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompositeFieldIr {
pub logical_name: String,
pub db_name: String,
pub field_type: ResolvedFieldType,
pub is_required: bool,
pub is_array: bool,
pub storage_strategy: Option<StorageStrategy>,
pub span: Span,
}
#[derive(Debug, Clone, PartialEq)]
pub struct CompositeTypeIr {
pub logical_name: String,
pub fields: Vec<CompositeFieldIr>,
pub span: Span,
}