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 direct_url: Option<String>,
78 pub span: Span,
80}
81
82impl DatasourceIr {
83 pub fn runtime_url(&self) -> &str {
88 if !self.url.is_empty() {
89 &self.url
90 } else {
91 self.direct_url.as_deref().unwrap_or(&self.url)
92 }
93 }
94
95 pub fn admin_url(&self) -> &str {
100 self.direct_url.as_deref().unwrap_or(&self.url)
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Default)]
106pub enum InterfaceKind {
107 #[default]
110 Sync,
111 Async,
113}
114
115#[derive(Debug, Clone, PartialEq)]
117pub struct GeneratorIr {
118 pub name: String,
120 pub provider: String,
122 pub output: Option<String>,
124 pub interface: InterfaceKind,
127 pub recursive_type_depth: usize,
129 pub span: Span,
131}
132
133#[derive(Debug, Clone, PartialEq)]
135pub struct ModelIr {
136 pub logical_name: String,
138 pub db_name: String,
140 pub fields: Vec<FieldIr>,
142 pub primary_key: PrimaryKeyIr,
144 pub unique_constraints: Vec<UniqueConstraintIr>,
146 pub indexes: Vec<IndexIr>,
148 pub check_constraints: Vec<String>,
150 pub span: Span,
152}
153
154impl ModelIr {
155 pub fn find_field(&self, name: &str) -> Option<&FieldIr> {
157 self.fields.iter().find(|f| f.logical_name == name)
158 }
159
160 pub fn scalar_fields(&self) -> impl Iterator<Item = &FieldIr> {
162 self.fields
163 .iter()
164 .filter(|f| !matches!(f.field_type, ResolvedFieldType::Relation(_)))
165 }
166
167 pub fn relation_fields(&self) -> impl Iterator<Item = &FieldIr> {
169 self.fields
170 .iter()
171 .filter(|f| matches!(f.field_type, ResolvedFieldType::Relation(_)))
172 }
173}
174
175#[derive(Debug, Clone, PartialEq)]
177pub struct FieldIr {
178 pub logical_name: String,
180 pub db_name: String,
182 pub field_type: ResolvedFieldType,
184 pub is_required: bool,
186 pub is_array: bool,
188 pub storage_strategy: Option<StorageStrategy>,
190 pub default_value: Option<DefaultValue>,
192 pub is_unique: bool,
194 pub is_updated_at: bool,
196 pub computed: Option<(String, ComputedKind)>,
198 pub check: Option<String>,
200 pub span: Span,
202}
203
204#[derive(Debug, Clone, PartialEq)]
206pub enum ResolvedFieldType {
207 Scalar(ScalarType),
209 Enum {
211 enum_name: String,
213 },
214 Relation(RelationIr),
216 CompositeType {
218 type_name: String,
220 },
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq)]
225pub enum ScalarType {
226 String,
228 Boolean,
230 Int,
232 BigInt,
234 Float,
236 Decimal {
238 precision: u32,
240 scale: u32,
242 },
243 DateTime,
245 Bytes,
247 Json,
249 Uuid,
251 Jsonb,
253 Xml,
255 Char {
257 length: u32,
259 },
260 VarChar {
262 length: u32,
264 },
265}
266
267impl ScalarType {
268 pub fn rust_type(&self) -> &'static str {
270 match self {
271 ScalarType::String => "String",
272 ScalarType::Boolean => "bool",
273 ScalarType::Int => "i32",
274 ScalarType::BigInt => "i64",
275 ScalarType::Float => "f64",
276 ScalarType::Decimal { .. } => "rust_decimal::Decimal",
277 ScalarType::DateTime => "chrono::NaiveDateTime",
278 ScalarType::Bytes => "Vec<u8>",
279 ScalarType::Json => "serde_json::Value",
280 ScalarType::Uuid => "uuid::Uuid",
281 ScalarType::Jsonb => "serde_json::Value",
282 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "String",
283 }
284 }
285
286 pub fn supported_by(self, provider: DatabaseProvider) -> bool {
288 match self {
289 ScalarType::Jsonb | ScalarType::Xml => provider == DatabaseProvider::Postgres,
290 ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
291 matches!(
292 provider,
293 DatabaseProvider::Postgres | DatabaseProvider::Mysql
294 )
295 }
296 _ => true,
297 }
298 }
299
300 pub fn supported_providers(self) -> &'static str {
302 match self {
303 ScalarType::Jsonb | ScalarType::Xml => "PostgreSQL only",
304 ScalarType::Char { .. } | ScalarType::VarChar { .. } => "PostgreSQL and MySQL",
305 _ => "all databases",
306 }
307 }
308}
309
310#[derive(Debug, Clone, PartialEq)]
312pub struct RelationIr {
313 pub name: Option<String>,
315 pub target_model: String,
317 pub fields: Vec<String>,
319 pub references: Vec<String>,
321 pub on_delete: Option<ReferentialAction>,
323 pub on_update: Option<ReferentialAction>,
325}
326
327#[derive(Debug, Clone, PartialEq)]
329pub enum DefaultValue {
330 String(String),
332 Number(String),
334 Boolean(bool),
336 EnumVariant(String),
338 Function(FunctionCall),
340}
341
342#[derive(Debug, Clone, PartialEq)]
344pub struct FunctionCall {
345 pub name: String,
347 pub args: Vec<String>,
349}
350
351#[derive(Debug, Clone, PartialEq)]
353pub enum PrimaryKeyIr {
354 Single(String),
356 Composite(Vec<String>),
358}
359
360impl PrimaryKeyIr {
361 pub fn fields(&self) -> Vec<&str> {
363 match self {
364 PrimaryKeyIr::Single(field) => vec![field.as_str()],
365 PrimaryKeyIr::Composite(fields) => fields.iter().map(|s| s.as_str()).collect(),
366 }
367 }
368
369 pub fn is_single(&self) -> bool {
371 matches!(self, PrimaryKeyIr::Single(_))
372 }
373
374 pub fn is_composite(&self) -> bool {
376 matches!(self, PrimaryKeyIr::Composite(_))
377 }
378}
379
380#[derive(Debug, Clone, PartialEq)]
382pub struct UniqueConstraintIr {
383 pub fields: Vec<String>,
385}
386
387#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub enum IndexType {
393 BTree,
395 Hash,
397 Gin,
399 Gist,
401 Brin,
403 FullText,
405}
406
407#[derive(Debug, Clone, PartialEq, Eq)]
409pub struct ParseIndexTypeError;
410
411impl fmt::Display for ParseIndexTypeError {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 f.write_str("unknown index type")
414 }
415}
416
417impl std::error::Error for ParseIndexTypeError {}
418
419impl FromStr for IndexType {
420 type Err = ParseIndexTypeError;
421
422 fn from_str(s: &str) -> Result<Self, Self::Err> {
423 match s.to_ascii_lowercase().as_str() {
424 "btree" => Ok(IndexType::BTree),
425 "hash" => Ok(IndexType::Hash),
426 "gin" => Ok(IndexType::Gin),
427 "gist" => Ok(IndexType::Gist),
428 "brin" => Ok(IndexType::Brin),
429 "fulltext" => Ok(IndexType::FullText),
430 _ => Err(ParseIndexTypeError),
431 }
432 }
433}
434
435impl IndexType {
436 pub fn supported_by(self, provider: DatabaseProvider) -> bool {
438 match self {
439 IndexType::BTree => true,
440 IndexType::Hash => matches!(
441 provider,
442 DatabaseProvider::Postgres | DatabaseProvider::Mysql
443 ),
444 IndexType::Gin | IndexType::Gist | IndexType::Brin => {
445 provider == DatabaseProvider::Postgres
446 }
447 IndexType::FullText => provider == DatabaseProvider::Mysql,
448 }
449 }
450
451 pub fn supported_providers(self) -> &'static str {
453 match self {
454 IndexType::BTree => "all databases",
455 IndexType::Hash => "PostgreSQL and MySQL",
456 IndexType::Gin => "PostgreSQL only",
457 IndexType::Gist => "PostgreSQL only",
458 IndexType::Brin => "PostgreSQL only",
459 IndexType::FullText => "MySQL only",
460 }
461 }
462
463 pub fn as_str(self) -> &'static str {
465 match self {
466 IndexType::BTree => "BTree",
467 IndexType::Hash => "Hash",
468 IndexType::Gin => "Gin",
469 IndexType::Gist => "Gist",
470 IndexType::Brin => "Brin",
471 IndexType::FullText => "FullText",
472 }
473 }
474}
475
476#[derive(Debug, Clone, Copy, PartialEq, Eq)]
485pub enum DatabaseProvider {
486 Postgres,
488 Mysql,
490 Sqlite,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq)]
496pub struct ParseDatabaseProviderError;
497
498impl fmt::Display for ParseDatabaseProviderError {
499 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
500 f.write_str("unknown database provider")
501 }
502}
503
504impl std::error::Error for ParseDatabaseProviderError {}
505
506impl FromStr for DatabaseProvider {
507 type Err = ParseDatabaseProviderError;
508
509 fn from_str(s: &str) -> Result<Self, Self::Err> {
510 match s {
511 "postgresql" => Ok(DatabaseProvider::Postgres),
512 "mysql" => Ok(DatabaseProvider::Mysql),
513 "sqlite" => Ok(DatabaseProvider::Sqlite),
514 _ => Err(ParseDatabaseProviderError),
515 }
516 }
517}
518
519impl DatabaseProvider {
520 pub const ALL: &'static [&'static str] = &["postgresql", "mysql", "sqlite"];
522
523 pub fn as_str(self) -> &'static str {
525 match self {
526 DatabaseProvider::Postgres => "postgresql",
527 DatabaseProvider::Mysql => "mysql",
528 DatabaseProvider::Sqlite => "sqlite",
529 }
530 }
531
532 pub fn display_name(self) -> &'static str {
534 match self {
535 DatabaseProvider::Postgres => "PostgreSQL",
536 DatabaseProvider::Mysql => "MySQL",
537 DatabaseProvider::Sqlite => "SQLite",
538 }
539 }
540}
541
542impl std::fmt::Display for DatabaseProvider {
543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544 f.write_str(self.as_str())
545 }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq)]
557pub enum ClientProvider {
558 Rust,
560 Python,
562 JavaScript,
564}
565
566#[derive(Debug, Clone, PartialEq, Eq)]
568pub struct ParseClientProviderError;
569
570impl fmt::Display for ParseClientProviderError {
571 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
572 f.write_str("unknown client provider")
573 }
574}
575
576impl std::error::Error for ParseClientProviderError {}
577
578impl FromStr for ClientProvider {
579 type Err = ParseClientProviderError;
580
581 fn from_str(s: &str) -> Result<Self, Self::Err> {
582 match s {
583 "nautilus-client-rs" => Ok(ClientProvider::Rust),
584 "nautilus-client-py" => Ok(ClientProvider::Python),
585 "nautilus-client-js" => Ok(ClientProvider::JavaScript),
586 _ => Err(ParseClientProviderError),
587 }
588 }
589}
590
591impl ClientProvider {
592 pub const ALL: &'static [&'static str] = &[
594 "nautilus-client-rs",
595 "nautilus-client-py",
596 "nautilus-client-js",
597 ];
598
599 pub fn as_str(self) -> &'static str {
601 match self {
602 ClientProvider::Rust => "nautilus-client-rs",
603 ClientProvider::Python => "nautilus-client-py",
604 ClientProvider::JavaScript => "nautilus-client-js",
605 }
606 }
607}
608
609impl fmt::Display for ClientProvider {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 f.write_str(self.as_str())
612 }
613}
614
615#[derive(Debug, Clone, PartialEq)]
617pub struct IndexIr {
618 pub fields: Vec<String>,
620 pub index_type: Option<IndexType>,
623 pub name: Option<String>,
625 pub map: Option<String>,
628}
629
630#[derive(Debug, Clone, PartialEq)]
632pub struct EnumIr {
633 pub logical_name: String,
635 pub variants: Vec<String>,
637 pub span: Span,
639}
640
641impl EnumIr {
642 pub fn has_variant(&self, name: &str) -> bool {
644 self.variants.iter().any(|v| v == name)
645 }
646}
647
648#[derive(Debug, Clone, PartialEq)]
652pub struct CompositeFieldIr {
653 pub logical_name: String,
655 pub db_name: String,
657 pub field_type: ResolvedFieldType,
659 pub is_required: bool,
661 pub is_array: bool,
663 pub storage_strategy: Option<StorageStrategy>,
665 pub span: Span,
667}
668
669#[derive(Debug, Clone, PartialEq)]
671pub struct CompositeTypeIr {
672 pub logical_name: String,
674 pub fields: Vec<CompositeFieldIr>,
676 pub span: Span,
678}