1pub use crate::ast::ComputedKind;
8use crate::ast::{ReferentialAction, StorageStrategy};
9use crate::span::Span;
10use std::collections::HashMap;
11use std::fmt;
12use std::str::FromStr;
13
14#[derive(Debug, Clone, PartialEq)]
16pub struct SchemaIr {
17 pub datasource: Option<DatasourceIr>,
19 pub generator: Option<GeneratorIr>,
21 pub models: HashMap<String, ModelIr>,
23 pub enums: HashMap<String, EnumIr>,
25 pub composite_types: HashMap<String, CompositeTypeIr>,
27}
28
29impl SchemaIr {
30 pub fn new() -> Self {
32 Self {
33 datasource: None,
34 generator: None,
35 models: HashMap::new(),
36 enums: HashMap::new(),
37 composite_types: HashMap::new(),
38 }
39 }
40
41 pub fn get_model(&self, name: &str) -> Option<&ModelIr> {
43 self.models.get(name)
44 }
45
46 pub fn get_enum(&self, name: &str) -> Option<&EnumIr> {
48 self.enums.get(name)
49 }
50
51 pub fn get_composite_type(&self, name: &str) -> Option<&CompositeTypeIr> {
53 self.composite_types.get(name)
54 }
55}
56
57impl Default for SchemaIr {
58 fn default() -> Self {
59 Self::new()
60 }
61}
62
63#[derive(Debug, Clone, PartialEq)]
65pub struct DatasourceIr {
66 pub name: String,
68 pub provider: String,
70 pub url: String,
72 pub span: Span,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Default)]
78pub enum InterfaceKind {
79 #[default]
82 Sync,
83 Async,
85}
86
87#[derive(Debug, Clone, PartialEq)]
89pub struct GeneratorIr {
90 pub name: String,
92 pub provider: String,
94 pub output: Option<String>,
96 pub interface: InterfaceKind,
99 pub recursive_type_depth: usize,
101 pub span: Span,
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub struct ModelIr {
108 pub logical_name: String,
110 pub db_name: String,
112 pub fields: Vec<FieldIr>,
114 pub primary_key: PrimaryKeyIr,
116 pub unique_constraints: Vec<UniqueConstraintIr>,
118 pub indexes: Vec<IndexIr>,
120 pub check_constraints: Vec<String>,
122 pub span: Span,
124}
125
126impl ModelIr {
127 pub fn find_field(&self, name: &str) -> Option<&FieldIr> {
129 self.fields.iter().find(|f| f.logical_name == name)
130 }
131
132 pub fn scalar_fields(&self) -> impl Iterator<Item = &FieldIr> {
134 self.fields
135 .iter()
136 .filter(|f| !matches!(f.field_type, ResolvedFieldType::Relation(_)))
137 }
138
139 pub fn relation_fields(&self) -> impl Iterator<Item = &FieldIr> {
141 self.fields
142 .iter()
143 .filter(|f| matches!(f.field_type, ResolvedFieldType::Relation(_)))
144 }
145}
146
147#[derive(Debug, Clone, PartialEq)]
149pub struct FieldIr {
150 pub logical_name: String,
152 pub db_name: String,
154 pub field_type: ResolvedFieldType,
156 pub is_required: bool,
158 pub is_array: bool,
160 pub storage_strategy: Option<StorageStrategy>,
162 pub default_value: Option<DefaultValue>,
164 pub is_unique: bool,
166 pub is_updated_at: bool,
168 pub computed: Option<(String, ComputedKind)>,
170 pub check: Option<String>,
172 pub span: Span,
174}
175
176#[derive(Debug, Clone, PartialEq)]
178pub enum ResolvedFieldType {
179 Scalar(ScalarType),
181 Enum {
183 enum_name: String,
185 },
186 Relation(RelationIr),
188 CompositeType {
190 type_name: String,
192 },
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197pub enum ScalarType {
198 String,
200 Boolean,
202 Int,
204 BigInt,
206 Float,
208 Decimal {
210 precision: u32,
212 scale: u32,
214 },
215 DateTime,
217 Bytes,
219 Json,
221 Uuid,
223 Jsonb,
225 Xml,
227 Char {
229 length: u32,
231 },
232 VarChar {
234 length: u32,
236 },
237}
238
239impl ScalarType {
240 pub fn rust_type(&self) -> &'static str {
242 match self {
243 ScalarType::String => "String",
244 ScalarType::Boolean => "bool",
245 ScalarType::Int => "i32",
246 ScalarType::BigInt => "i64",
247 ScalarType::Float => "f64",
248 ScalarType::Decimal { .. } => "rust_decimal::Decimal",
249 ScalarType::DateTime => "chrono::NaiveDateTime",
250 ScalarType::Bytes => "Vec<u8>",
251 ScalarType::Json => "serde_json::Value",
252 ScalarType::Uuid => "uuid::Uuid",
253 ScalarType::Jsonb => "serde_json::Value",
254 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "String",
255 }
256 }
257
258 pub fn supported_by(self, provider: DatabaseProvider) -> bool {
260 match self {
261 ScalarType::Jsonb | ScalarType::Xml => provider == DatabaseProvider::Postgres,
262 ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
263 matches!(
264 provider,
265 DatabaseProvider::Postgres | DatabaseProvider::Mysql
266 )
267 }
268 _ => true,
269 }
270 }
271
272 pub fn supported_providers(self) -> &'static str {
274 match self {
275 ScalarType::Jsonb | ScalarType::Xml => "PostgreSQL only",
276 ScalarType::Char { .. } | ScalarType::VarChar { .. } => "PostgreSQL and MySQL",
277 _ => "all databases",
278 }
279 }
280}
281
282#[derive(Debug, Clone, PartialEq)]
284pub struct RelationIr {
285 pub name: Option<String>,
287 pub target_model: String,
289 pub fields: Vec<String>,
291 pub references: Vec<String>,
293 pub on_delete: Option<ReferentialAction>,
295 pub on_update: Option<ReferentialAction>,
297}
298
299#[derive(Debug, Clone, PartialEq)]
301pub enum DefaultValue {
302 String(String),
304 Number(String),
306 Boolean(bool),
308 EnumVariant(String),
310 Function(FunctionCall),
312}
313
314#[derive(Debug, Clone, PartialEq)]
316pub struct FunctionCall {
317 pub name: String,
319 pub args: Vec<String>,
321}
322
323#[derive(Debug, Clone, PartialEq)]
325pub enum PrimaryKeyIr {
326 Single(String),
328 Composite(Vec<String>),
330}
331
332impl PrimaryKeyIr {
333 pub fn fields(&self) -> Vec<&str> {
335 match self {
336 PrimaryKeyIr::Single(field) => vec![field.as_str()],
337 PrimaryKeyIr::Composite(fields) => fields.iter().map(|s| s.as_str()).collect(),
338 }
339 }
340
341 pub fn is_single(&self) -> bool {
343 matches!(self, PrimaryKeyIr::Single(_))
344 }
345
346 pub fn is_composite(&self) -> bool {
348 matches!(self, PrimaryKeyIr::Composite(_))
349 }
350}
351
352#[derive(Debug, Clone, PartialEq)]
354pub struct UniqueConstraintIr {
355 pub fields: Vec<String>,
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
364pub enum IndexType {
365 BTree,
367 Hash,
369 Gin,
371 Gist,
373 Brin,
375 FullText,
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
381pub struct ParseIndexTypeError;
382
383impl fmt::Display for ParseIndexTypeError {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.write_str("unknown index type")
386 }
387}
388
389impl std::error::Error for ParseIndexTypeError {}
390
391impl FromStr for IndexType {
392 type Err = ParseIndexTypeError;
393
394 fn from_str(s: &str) -> Result<Self, Self::Err> {
395 match s.to_ascii_lowercase().as_str() {
396 "btree" => Ok(IndexType::BTree),
397 "hash" => Ok(IndexType::Hash),
398 "gin" => Ok(IndexType::Gin),
399 "gist" => Ok(IndexType::Gist),
400 "brin" => Ok(IndexType::Brin),
401 "fulltext" => Ok(IndexType::FullText),
402 _ => Err(ParseIndexTypeError),
403 }
404 }
405}
406
407impl IndexType {
408 pub fn supported_by(self, provider: DatabaseProvider) -> bool {
410 match self {
411 IndexType::BTree => true,
412 IndexType::Hash => matches!(
413 provider,
414 DatabaseProvider::Postgres | DatabaseProvider::Mysql
415 ),
416 IndexType::Gin | IndexType::Gist | IndexType::Brin => {
417 provider == DatabaseProvider::Postgres
418 }
419 IndexType::FullText => provider == DatabaseProvider::Mysql,
420 }
421 }
422
423 pub fn supported_providers(self) -> &'static str {
425 match self {
426 IndexType::BTree => "all databases",
427 IndexType::Hash => "PostgreSQL and MySQL",
428 IndexType::Gin => "PostgreSQL only",
429 IndexType::Gist => "PostgreSQL only",
430 IndexType::Brin => "PostgreSQL only",
431 IndexType::FullText => "MySQL only",
432 }
433 }
434
435 pub fn as_str(self) -> &'static str {
437 match self {
438 IndexType::BTree => "BTree",
439 IndexType::Hash => "Hash",
440 IndexType::Gin => "Gin",
441 IndexType::Gist => "Gist",
442 IndexType::Brin => "Brin",
443 IndexType::FullText => "FullText",
444 }
445 }
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq)]
457pub enum DatabaseProvider {
458 Postgres,
460 Mysql,
462 Sqlite,
464}
465
466#[derive(Debug, Clone, PartialEq, Eq)]
468pub struct ParseDatabaseProviderError;
469
470impl fmt::Display for ParseDatabaseProviderError {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 f.write_str("unknown database provider")
473 }
474}
475
476impl std::error::Error for ParseDatabaseProviderError {}
477
478impl FromStr for DatabaseProvider {
479 type Err = ParseDatabaseProviderError;
480
481 fn from_str(s: &str) -> Result<Self, Self::Err> {
482 match s {
483 "postgresql" => Ok(DatabaseProvider::Postgres),
484 "mysql" => Ok(DatabaseProvider::Mysql),
485 "sqlite" => Ok(DatabaseProvider::Sqlite),
486 _ => Err(ParseDatabaseProviderError),
487 }
488 }
489}
490
491impl DatabaseProvider {
492 pub const ALL: &'static [&'static str] = &["postgresql", "mysql", "sqlite"];
494
495 pub fn as_str(self) -> &'static str {
497 match self {
498 DatabaseProvider::Postgres => "postgresql",
499 DatabaseProvider::Mysql => "mysql",
500 DatabaseProvider::Sqlite => "sqlite",
501 }
502 }
503
504 pub fn display_name(self) -> &'static str {
506 match self {
507 DatabaseProvider::Postgres => "PostgreSQL",
508 DatabaseProvider::Mysql => "MySQL",
509 DatabaseProvider::Sqlite => "SQLite",
510 }
511 }
512}
513
514impl std::fmt::Display for DatabaseProvider {
515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516 f.write_str(self.as_str())
517 }
518}
519
520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
529pub enum ClientProvider {
530 Rust,
532 Python,
534 JavaScript,
536}
537
538#[derive(Debug, Clone, PartialEq, Eq)]
540pub struct ParseClientProviderError;
541
542impl fmt::Display for ParseClientProviderError {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f.write_str("unknown client provider")
545 }
546}
547
548impl std::error::Error for ParseClientProviderError {}
549
550impl FromStr for ClientProvider {
551 type Err = ParseClientProviderError;
552
553 fn from_str(s: &str) -> Result<Self, Self::Err> {
554 match s {
555 "nautilus-client-rs" => Ok(ClientProvider::Rust),
556 "nautilus-client-py" => Ok(ClientProvider::Python),
557 "nautilus-client-js" => Ok(ClientProvider::JavaScript),
558 _ => Err(ParseClientProviderError),
559 }
560 }
561}
562
563impl ClientProvider {
564 pub const ALL: &'static [&'static str] = &[
566 "nautilus-client-rs",
567 "nautilus-client-py",
568 "nautilus-client-js",
569 ];
570
571 pub fn as_str(self) -> &'static str {
573 match self {
574 ClientProvider::Rust => "nautilus-client-rs",
575 ClientProvider::Python => "nautilus-client-py",
576 ClientProvider::JavaScript => "nautilus-client-js",
577 }
578 }
579}
580
581impl fmt::Display for ClientProvider {
582 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583 f.write_str(self.as_str())
584 }
585}
586
587#[derive(Debug, Clone, PartialEq)]
589pub struct IndexIr {
590 pub fields: Vec<String>,
592 pub index_type: Option<IndexType>,
595 pub name: Option<String>,
597 pub map: Option<String>,
600}
601
602#[derive(Debug, Clone, PartialEq)]
604pub struct EnumIr {
605 pub logical_name: String,
607 pub variants: Vec<String>,
609 pub span: Span,
611}
612
613impl EnumIr {
614 pub fn has_variant(&self, name: &str) -> bool {
616 self.variants.iter().any(|v| v == name)
617 }
618}
619
620#[derive(Debug, Clone, PartialEq)]
624pub struct CompositeFieldIr {
625 pub logical_name: String,
627 pub db_name: String,
629 pub field_type: ResolvedFieldType,
631 pub is_required: bool,
633 pub is_array: bool,
635 pub storage_strategy: Option<StorageStrategy>,
637 pub span: Span,
639}
640
641#[derive(Debug, Clone, PartialEq)]
643pub struct CompositeTypeIr {
644 pub logical_name: String,
646 pub fields: Vec<CompositeFieldIr>,
648 pub span: Span,
650}