From 2d0e6c78b06f4ac7e11c3dff2d8926b88aea0718 Mon Sep 17 00:00:00 2001 From: L33gn21 Date: Sat, 23 May 2026 18:31:53 +0900 Subject: [PATCH 1/4] feat(exporter): add Prisma ORM exporter Add Prisma as a fifth ORM backend that converts `TableDef` schemas into a single `schema.prisma` file (datasource, generator, enums, models). Why Prisma is wired differently from the other backends: - Prisma is a single-schema language: datasource (provider, url, relationMode) and generator (client output) must live in the same file as the models. Other ORMs only emit model code per file, so they need no such meta-config. A dedicated `PrismaConfig` (provider / client_output / relation_mode) is therefore added to `vespertide-config`. - The existing export pipeline is N models -> N files (walk_models -> per-model render -> per-file write -> mod chain). Prisma is N models -> 1 file with globally-deduplicated enum blocks and a single datasource/generator header. `cmd_export` short-circuits with `if matches!(orm, OrmArg::Prisma) { return cmd_export_prisma }` and assembles the whole schema via `PrismaExporterWithConfig::render_schema`. Test design (see `prisma/TESTING.md`): - 3 layers - Layer 1: single-entity rendering (no FK); Layer 2: schema-aware rendering with relations; Layer 3: edge cases. - All assertions use `insta::assert_snapshot!` (no `contains` checks) for precise regression detection. - `rstest` parameterization for type x nullable matrix, default-value variants, on_delete/on_update actions, and singularize cases. - Helpers (`col`, `col_null`, `col_with_default`, `pk`, `uniq`, `idx`, `fk`, `table`, `render_schema_all`) keep cases compact. - 65 tests / 54 snapshots cover: column type x nullable (25 types), PK variants, single/composite unique, single/composite index, 9 default-value forms, enums (string/integer/nullable/default/dedup), description and column comment, `@@map`, has-many, has-one (unique FK), nullable FK, multiple FKs to same table, self-reference, on_delete/on_update (6 actions), composite FK ignored, plural-> singular back-relations, reserved-identifier table/column names, and special characters in descriptions/defaults. Co-Authored-By: Claude Opus 4.7 --- crates/vespertide-cli/src/commands/export.rs | 44 +- crates/vespertide-config/src/config.rs | 53 + crates/vespertide-config/src/lib.rs | 2 +- crates/vespertide-exporter/src/lib.rs | 4 +- crates/vespertide-exporter/src/orm.rs | 9 +- .../vespertide-exporter/src/prisma/TESTING.md | 323 ++++ crates/vespertide-exporter/src/prisma/mod.rs | 1387 +++++++++++++++++ ...rter__prisma__tests__always_emits_map.snap | 9 + ...exporter__prisma__tests__arbitrary_fn.snap | 10 + ...e_exporter__prisma__tests__bool_false.snap | 10 + ...de_exporter__prisma__tests__bool_true.snap | 10 + ...pertide_exporter__prisma__tests__both.snap | 18 + ...ertide_exporter__prisma__tests__boxes.snap | 18 + ...tide_exporter__prisma__tests__cascade.snap | 18 + ...e_exporter__prisma__tests__categories.snap | 18 + ...isma__tests__column_comment_multiline.snap | 11 + ...prisma__tests__column_comment_present.snap | 11 + ...er__prisma__tests__column_type_matrix.snap | 423 +++++ ...__prisma__tests__composite_fk_ignored.snap | 19 + ...ter__prisma__tests__current_timestamp.snap | 10 + ...isma__tests__default_value_with_quote.snap | 10 + ..._prisma__tests__description_multiline.snap | 12 + ...rter__prisma__tests__description_none.snap | 9 + ...r__prisma__tests__description_present.snap | 10 + ...sma__tests__description_special_chars.snap | 28 + ...a__tests__enum_dedup_multiple_columns.snap | 16 + ...er__prisma__tests__enum_default_value.snap | 15 + ...exporter__prisma__tests__enum_integer.snap | 16 + ...xporter__prisma__tests__enum_nullable.snap | 15 + ...er__prisma__tests__enum_string_mapped.snap | 16 + ...ts__enum_string_screaming_snake_match.snap | 15 + ...rter__prisma__tests__fallback_keyword.snap | 10 + ...orter__prisma__tests__gen_random_uuid.snap | 10 + ...porter__prisma__tests__has_many_basic.snap | 19 + ...ter__prisma__tests__has_one_unique_fk.snap | 18 + ...orter__prisma__tests__index_composite.snap | 12 + ...er__prisma__tests__index_single_named.snap | 11 + ...__prisma__tests__index_single_unnamed.snap | 11 + ...orter__prisma__tests__integer_literal.snap | 10 + ...prisma__tests__multiple_fk_same_table.snap | 21 + ...de_exporter__prisma__tests__no_action.snap | 18 + ...spertide_exporter__prisma__tests__now.snap | 10 + ..._exporter__prisma__tests__nullable_fk.snap | 18 + ...exporter__prisma__tests__pk_composite.snap | 11 + ...tide_exporter__prisma__tests__pk_none.snap | 10 + ...risma__tests__pk_single_autoincrement.snap | 10 + ...ma__tests__pk_single_no_autoincrement.snap | 10 + ...ertide_exporter__prisma__tests__posts.snap | 18 + ...ests__reserved_identifier_column_name.snap | 14 + ...tests__reserved_identifier_table_name.snap | 45 + ...ide_exporter__prisma__tests__restrict.snap | 18 + ...ter__prisma__tests__self_reference_fk.snap | 13 + ..._exporter__prisma__tests__set_default.snap | 18 + ...ide_exporter__prisma__tests__set_null.snap | 18 + ...porter__prisma__tests__string_literal.snap | 10 + ...prisma__tests__unique_composite_named.snap | 12 + ...isma__tests__unique_composite_unnamed.snap | 12 + ...r__prisma__tests__unique_single_named.snap | 10 + ..._prisma__tests__unique_single_unnamed.snap | 10 + ...ertide_exporter__prisma__tests__users.snap | 18 + ...rter__prisma__tests__uuid_generate_v4.snap | 10 + ...tests__render_entity_snapshots@prisma.snap | 9 + ...r_entity_with_schema_snapshots@prisma.snap | 9 + 63 files changed, 3017 insertions(+), 5 deletions(-) create mode 100644 crates/vespertide-exporter/src/prisma/TESTING.md create mode 100644 crates/vespertide-exporter/src/prisma/mod.rs create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_table_name.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap create mode 100644 crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap create mode 100644 crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_snapshots@prisma.snap create mode 100644 crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_with_schema_snapshots@prisma.snap diff --git a/crates/vespertide-cli/src/commands/export.rs b/crates/vespertide-cli/src/commands/export.rs index da274037..90ef1705 100644 --- a/crates/vespertide-cli/src/commands/export.rs +++ b/crates/vespertide-cli/src/commands/export.rs @@ -7,7 +7,10 @@ use futures::future::try_join_all; use tokio::fs; use vespertide_config::VespertideConfig; use vespertide_core::TableDef; -use vespertide_exporter::{Orm, render_entity_with_schema, seaorm::SeaOrmExporterWithConfig}; +use vespertide_exporter::{ + Orm, render_entity_with_schema, prisma::PrismaExporterWithConfig, + seaorm::SeaOrmExporterWithConfig, +}; use crate::utils::load_config; @@ -17,6 +20,7 @@ pub enum OrmArg { Sqlalchemy, Sqlmodel, Jpa, + Prisma, } impl From for Orm { @@ -26,6 +30,7 @@ impl From for Orm { OrmArg::Sqlalchemy => Orm::SqlAlchemy, OrmArg::Sqlmodel => Orm::SqlModel, OrmArg::Jpa => Orm::Jpa, + OrmArg::Prisma => Orm::Prisma, } } } @@ -49,6 +54,11 @@ pub async fn cmd_export(orm: OrmArg, export_dir: Option) -> Result<()> let target_root = resolve_export_dir(export_dir, &config); + // Prisma uses a single-file output strategy + if matches!(orm, OrmArg::Prisma) { + return cmd_export_prisma(config, normalized_models, target_root).await; + } + // Clean the export directory before regenerating let orm_kind: Orm = orm.into(); clean_export_dir(&target_root, orm_kind).await?; @@ -203,6 +213,7 @@ async fn clean_export_dir(root: &Path, orm: Orm) -> Result<()> { Orm::SeaOrm => "rs", Orm::SqlAlchemy | Orm::SqlModel => "py", Orm::Jpa => "java", + Orm::Prisma => "prisma", }; clean_dir_recursive(root, ext).await?; @@ -295,6 +306,7 @@ fn build_output_path(root: &Path, rel_path: &Path, orm: Orm) -> PathBuf { Orm::SeaOrm => "rs", Orm::SqlAlchemy | Orm::SqlModel => "py", Orm::Jpa => "java", + Orm::Prisma => "prisma", }; // Java requires filename to match PascalCase class name let file_stem = if matches!(orm, Orm::Jpa) { @@ -395,6 +407,36 @@ async fn ensure_mod_chain(root: &Path, rel_path: &Path) -> Result<()> { Ok(()) } +async fn cmd_export_prisma( + config: VespertideConfig, + normalized_models: Vec<(TableDef, PathBuf)>, + target_root: PathBuf, +) -> Result<()> { + let all_tables: Vec = normalized_models.iter().map(|(t, _)| t.clone()).collect(); + let content = PrismaExporterWithConfig::new(config.prisma()).render_schema(&all_tables); + + clean_export_dir(&target_root, Orm::Prisma).await?; + + if !target_root.exists() { + fs::create_dir_all(&target_root) + .await + .with_context(|| format!("create export dir {}", target_root.display()))?; + } + + let out_path = target_root.join("schema.prisma"); + fs::write(&out_path, &content) + .await + .with_context(|| format!("write {}", out_path.display()))?; + + println!( + "Exported {} model(s) -> {}", + normalized_models.len(), + out_path.display() + ); + + Ok(()) +} + #[async_recursion::async_recursion] async fn walk_models( root: &Path, diff --git a/crates/vespertide-config/src/config.rs b/crates/vespertide-config/src/config.rs index 0b7a4822..b862cb29 100644 --- a/crates/vespertide-config/src/config.rs +++ b/crates/vespertide-config/src/config.rs @@ -77,6 +77,50 @@ impl SeaOrmConfig { } } +/// Prisma-specific export configuration. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase")] +pub struct PrismaConfig { + /// Database provider: postgresql, mysql, sqlite, sqlserver, mongodb, cockroachdb. + #[serde(default = "default_prisma_provider")] + pub provider: String, + /// Optional output path for the generated Prisma client. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub client_output: Option, + /// Optional relationMode override ("foreignKeys" or "prisma"). + #[serde(default, skip_serializing_if = "Option::is_none")] + pub relation_mode: Option, +} + +fn default_prisma_provider() -> String { + "postgresql".to_string() +} + +impl Default for PrismaConfig { + fn default() -> Self { + Self { + provider: default_prisma_provider(), + client_output: None, + relation_mode: None, + } + } +} + +impl PrismaConfig { + pub fn provider(&self) -> &str { + &self.provider + } + + pub fn client_output(&self) -> Option<&str> { + self.client_output.as_deref() + } + + pub fn relation_mode(&self) -> Option<&str> { + self.relation_mode.as_deref() + } +} + /// Top-level vespertide configuration. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -98,6 +142,9 @@ pub struct VespertideConfig { /// SeaORM-specific export configuration. #[serde(default)] pub seaorm: SeaOrmConfig, + /// Prisma-specific export configuration. + #[serde(default)] + pub prisma: PrismaConfig, /// Prefix to add to all table names (including migration version table). /// Default: "" (no prefix) #[serde(default)] @@ -120,6 +167,7 @@ impl Default for VespertideConfig { migration_filename_pattern: default_migration_filename_pattern(), model_export_dir: default_model_export_dir(), seaorm: SeaOrmConfig::default(), + prisma: PrismaConfig::default(), prefix: String::new(), } } @@ -171,6 +219,11 @@ impl VespertideConfig { &self.seaorm } + /// Prisma-specific export configuration. + pub fn prisma(&self) -> &PrismaConfig { + &self.prisma + } + /// Prefix to add to all table names. pub fn prefix(&self) -> &str { &self.prefix diff --git a/crates/vespertide-config/src/lib.rs b/crates/vespertide-config/src/lib.rs index 1ec2e86a..e517dd30 100644 --- a/crates/vespertide-config/src/lib.rs +++ b/crates/vespertide-config/src/lib.rs @@ -2,7 +2,7 @@ pub mod config; pub mod file_format; pub mod name_case; -pub use config::{SeaOrmConfig, VespertideConfig, default_migration_filename_pattern}; +pub use config::{PrismaConfig, SeaOrmConfig, VespertideConfig, default_migration_filename_pattern}; pub use file_format::FileFormat; pub use name_case::NameCase; diff --git a/crates/vespertide-exporter/src/lib.rs b/crates/vespertide-exporter/src/lib.rs index 22da1da2..02e8dc62 100644 --- a/crates/vespertide-exporter/src/lib.rs +++ b/crates/vespertide-exporter/src/lib.rs @@ -1,14 +1,16 @@ //! Helpers to convert `TableDef` models into ORM-specific representations -//! such as SeaORM, SQLAlchemy, SQLModel, and JPA. +//! such as SeaORM, SQLAlchemy, SQLModel, JPA, and Prisma. pub mod jpa; pub mod orm; +pub mod prisma; pub mod seaorm; pub mod sqlalchemy; pub mod sqlmodel; pub use jpa::JpaExporter; pub use orm::{Orm, OrmExporter, render_entity, render_entity_with_schema}; +pub use prisma::PrismaExporter; pub use seaorm::{SeaOrmExporter, render_entity as render_seaorm_entity}; pub use sqlalchemy::SqlAlchemyExporter; pub use sqlmodel::SqlModelExporter; diff --git a/crates/vespertide-exporter/src/orm.rs b/crates/vespertide-exporter/src/orm.rs index cf16fd98..7d343abb 100644 --- a/crates/vespertide-exporter/src/orm.rs +++ b/crates/vespertide-exporter/src/orm.rs @@ -1,8 +1,8 @@ use vespertide_core::TableDef; use crate::{ - jpa::JpaExporter, seaorm::SeaOrmExporter, sqlalchemy::SqlAlchemyExporter, - sqlmodel::SqlModelExporter, + jpa::JpaExporter, prisma::PrismaExporter, seaorm::SeaOrmExporter, + sqlalchemy::SqlAlchemyExporter, sqlmodel::SqlModelExporter, }; /// Supported ORM targets. @@ -12,6 +12,7 @@ pub enum Orm { SqlAlchemy, SqlModel, Jpa, + Prisma, } /// Standardized exporter interface for all supported ORMs. @@ -36,6 +37,7 @@ pub fn render_entity(orm: Orm, table: &TableDef) -> Result { Orm::SqlAlchemy => SqlAlchemyExporter.render_entity(table), Orm::SqlModel => SqlModelExporter.render_entity(table), Orm::Jpa => JpaExporter.render_entity(table), + Orm::Prisma => PrismaExporter.render_entity(table), } } @@ -50,6 +52,7 @@ pub fn render_entity_with_schema( Orm::SqlAlchemy => SqlAlchemyExporter.render_entity_with_schema(table, schema), Orm::SqlModel => SqlModelExporter.render_entity_with_schema(table, schema), Orm::Jpa => JpaExporter.render_entity_with_schema(table, schema), + Orm::Prisma => PrismaExporter.render_entity_with_schema(table, schema), } } @@ -87,6 +90,7 @@ mod tests { #[case("sqlalchemy", Orm::SqlAlchemy)] #[case("sqlmodel", Orm::SqlModel)] #[case("jpa", Orm::Jpa)] + #[case("prisma", Orm::Prisma)] fn test_render_entity_snapshots(#[case] name: &str, #[case] orm: Orm) { let table = simple_table(); let result = render_entity(orm, &table); @@ -101,6 +105,7 @@ mod tests { #[case("sqlalchemy", Orm::SqlAlchemy)] #[case("sqlmodel", Orm::SqlModel)] #[case("jpa", Orm::Jpa)] + #[case("prisma", Orm::Prisma)] fn test_render_entity_with_schema_snapshots(#[case] name: &str, #[case] orm: Orm) { let table = simple_table(); let schema = vec![table.clone()]; diff --git a/crates/vespertide-exporter/src/prisma/TESTING.md b/crates/vespertide-exporter/src/prisma/TESTING.md new file mode 100644 index 00000000..4cdd21fe --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/TESTING.md @@ -0,0 +1,323 @@ +# Prisma Exporter — Test Specification + +## Rules + +- **FK가 포함된 순간 Layer 2.** `TableConstraint::ForeignKey`가 하나라도 있는 `TableDef`는 `render_entity_with_schema`를 사용하고 Layer 2에 작성한다. +- 모든 출력 검증은 `assert_snapshot!` 사용. `assert!(result.contains(...))` 방식은 사용하지 않는다. +- `rstest`의 `#[case]`는 동일한 구조의 케이스를 파라미터만 바꿀 때 사용한다. +- 스냅샷 파일은 `src/snapshots/` 아래에 자동 생성된다. 처음 실행 시 `cargo insta accept`로 승인. +- 테스트 코드 내 반복되는 `ColumnDef` 생성은 `fn col(name, ty) -> ColumnDef` 헬퍼를 사용한다. + +--- + +## Layer 1 — 단일 엔티티 (FK 없음) + +`render_entity(table)` 하나로 검증 가능한 모든 케이스. + +### 1-1. 컬럼 타입 × nullable + +모든 `SimpleColumnType`과 `ComplexColumnType`을 `nullable=false` / `nullable=true` 조합으로 커버한다. + +`rstest #[case(type, nullable)]` 형태로 하나의 파라미터 테스트로 작성한다. + +| 타입 | Prisma 출력 타입 | native 속성 | +|------|----------------|------------| +| SmallInt | `Int` | `@db.SmallInt` | +| Integer | `Int` | 없음 | +| BigInt | `BigInt` | 없음 | +| Real | `Float` | `@db.Real` | +| DoublePrecision | `Float` | 없음 | +| Text | `String` | `@db.Text` | +| Boolean | `Boolean` | 없음 | +| Date | `DateTime` | `@db.Date` | +| Time | `DateTime` | `@db.Time` | +| Timestamp | `DateTime` | `@db.Timestamp` | +| Timestamptz | `DateTime` | `@db.Timestamptz` | +| Interval | `String` | `@db.Interval` | +| Bytea | `Bytes` | 없음 | +| Uuid | `String` | `@db.Uuid` | +| Json | `Json` | 없음 | +| Inet | `String` | `@db.Inet` | +| Cidr | `String` | `@db.Cidr` | +| Macaddr | `String` | `@db.Macaddr` | +| Xml | `String` | `@db.Xml` | +| Varchar(n) | `String` | `@db.VarChar(n)` | +| Char(n) | `String` | `@db.Char(n)` | +| Numeric(p, s) | `Decimal` | `@db.Decimal(p, s)` | +| Custom("citext") | `Unsupported("citext")` | 없음 | +| Enum (string) | `EnumName` | 없음 | +| Enum (integer) | `EnumName` | 없음 (주석으로 값 표기) | + +nullable=true이면 타입 뒤에 `?`가 붙는다. `Unsupported(...)` 타입도 마찬가지. + +**현재 상태:** 미구현. `test_all_simple_column_types` 없음. + +--- + +### 1-2. PK 변형 + +| 케이스 | 설명 | +|--------|------| +| 단일 PK, auto_increment=true | `@id @default(autoincrement())` | +| 단일 PK, auto_increment=false | `@id` | +| Composite PK | 컬럼에 `@id` 없음, 테이블 하단에 `@@id([col1, col2])` | +| PK 없음 | `@@id` 없음, 컬럼에 `@id` 없음 | + +**현재 상태:** `test_basic_table`이 단일 PK auto_increment=true만 커버. 나머지 미구현. + +--- + +### 1-3. Unique 제약 + +| 케이스 | 설명 | +|--------|------| +| 단일 컬럼, named | 컬럼에 `@unique(map: "uq_name")` | +| 단일 컬럼, unnamed | 컬럼에 `@unique` | +| Composite, named | `@@unique([col1, col2], name: "uq_name")` | +| Composite, unnamed | `@@unique([col1, col2])` | + +단일 컬럼 unique는 컬럼 인라인에, composite는 테이블 하단에 생성된다. + +**현재 상태:** `test_basic_table`이 단일 unnamed unique만 커버. composite 미구현. + +--- + +### 1-4. Index + +| 케이스 | 설명 | +|--------|------| +| 단일 컬럼, named | `@@index([col], name: "ix_name")` | +| 단일 컬럼, unnamed | `@@index([col])` | +| Composite | `@@index([col1, col2])` | + +**현재 상태:** `test_table_with_indexes`가 named + unnamed 단일 컬럼 커버. composite 미구현. + +--- + +### 1-5. Default 값 + +Default 포맷은 입력 문자열 패턴에 따라 다르게 변환된다. + +| 입력 | 출력 | 적용 타입 | +|------|------|----------| +| `"true"` / `"false"` | `@default(true)` / `@default(false)` | Boolean | +| `"now()"` / `"CURRENT_TIMESTAMP"` | `@default(now())` | Timestamp, Timestamptz | +| `"gen_random_uuid()"` / `"uuid_generate_v4()"` | `@default(uuid())` | Uuid | +| 임의 함수 `"func()"` | `@default(dbgenerated("func()"))` | 모든 타입 | +| 문자열 리터럴 `"'foo'"` | `@default("foo")` | Text, Varchar 등 | +| Enum 값 문자열 `"'pending'"` | `@default(EnumName.PENDING)` | Enum(string) | +| 숫자 `"42"` | `@default(42)` | Integer, BigInt 등 | +| fallback: 함수도 아니고 따옴표도 없고 숫자도 아닌 값 `"CURRENT_DATE"` | `@default(dbgenerated("CURRENT_DATE"))` | Date 등 | + +`prisma_default_attr` 분기 순서: bool → now/CURRENT_TIMESTAMP → uuid 함수 → 임의 함수(`(` 포함) → 따옴표 문자열 → 숫자 → **fallback**. fallback은 앞의 모든 조건을 통과한 값으로, `CURRENT_DATE`나 `SYSDATE`처럼 괄호 없는 DB 키워드가 해당된다. + +**현재 상태:** `test_table_with_default_values`가 boolean, integer, now()만 커버. 나머지 미구현. + +--- + +### 1-6. Enum + +| 케이스 | 설명 | +|--------|------| +| String enum, 값이 screaming_snake와 동일 | `@map` 없음 | +| String enum, 값이 다름 (`"pending"` → `PENDING`) | `PENDING @map("pending")` | +| Integer enum | 각 variant에 `// = {value}` 주석 | +| nullable enum | 타입에 `?` | +| Enum에 default 값 | `@default(EnumName.VARIANT)` | +| 동일 enum을 여러 컬럼에서 사용 | enum 블록 중복 없이 한 번만 출력 | + +**현재 상태:** `test_table_with_enum` (string), `test_table_with_integer_enum` 존재. nullable, default, dedup 미구현. + +--- + +### 1-7. Description / Comment + +| 케이스 | 설명 | +|--------|------| +| `table.description = Some("...")` | 모델 위에 `/// description` | +| `table.description = None` | `///` 없음 | +| `column.comment = Some("...")` | 컬럼 위에 `/// comment` | +| 멀티라인 description | 줄마다 `///` | + +**현재 상태:** `test_basic_table`이 컬럼 comment + description 있는 케이스 커버. description=None, 멀티라인 미구현. + +--- + +### 1-8. @@map + +모든 모델은 Prisma 모델명(PascalCase)과 테이블명(snake_case)이 다를 경우 `@@map("table_name")`을 항상 출력한다. `test_basic_table`에서 확인 가능하나 명시적 테스트 없음. + +--- + +## Layer 2 — 관계 (FK 포함) + +`render_entity_with_schema(table, schema)`를 사용한다. 모든 케이스에서 최소 두 개의 `TableDef`가 필요하다. + +### 2-1. has-many (기본 역방향) + +``` +posts.user_id → users.id +``` + +- `users` 모델에 `posts Post[]` 역방향 필드 생성 +- 관계 이름 없음 (`@relation` 속성 없음) + +**현재 상태:** `test_with_schema_back_relations` 존재. 단 `assert!(contains)` 방식 → `assert_snapshot!`으로 교체 필요. + +--- + +### 2-2. has-one (FK에 unique 제약) + +``` +profiles.user_id → users.id (profiles.user_id에 Unique 제약 있음) +``` + +- `users` 모델에 `profiles Profile?` (배열 아닌 단수 + nullable) + +**현재 상태:** 미구현. + +--- + +### 2-3. nullable FK + +``` +posts.user_id (nullable=true) → users.id +``` + +- 순방향: `user User?` (타입에 `?`) +- 역방향: 영향 없음 (여전히 `posts Post[]`) + +**현재 상태:** 미구현. + +--- + +### 2-4. 동일 테이블 다중 FK → relation 이름 자동 부여 + +``` +posts.author_id → users.id +posts.reviewer_id → users.id +``` + +- 두 FK 모두 같은 테이블을 참조 → 충돌 방지를 위해 `@relation("PostsAuthor")`, `@relation("PostsReviewer")` 자동 생성 +- `users` 모델에도 각각에 대응하는 역방향 필드에 동일 relation 이름 + +**현재 상태:** 미구현. + +--- + +### 2-5. 자기참조 FK + +``` +categories.parent_id → categories.id +``` + +- 자기참조는 `needs_name = true` → relation 이름 자동 부여 +- 순방향과 역방향이 같은 모델 안에 공존 + +**현재 상태:** 미구현. + +--- + +### 2-6. on_delete / on_update 액션 + +| `ReferenceAction` | Prisma 출력 | +|-------------------|------------| +| Cascade | `onDelete: Cascade` | +| Restrict | `onDelete: Restrict` | +| SetNull | `onDelete: SetNull` | +| SetDefault | `onDelete: SetDefault` | +| NoAction | `onDelete: NoAction` | + +`on_delete`와 `on_update` 각각 독립적으로 생성된다. None이면 해당 속성 생략. + +**현재 상태:** 미구현. + +--- + +### 2-7. Composite FK — 무시 + +``` +TableConstraint::ForeignKey { columns: ["org_id", "user_id"], ... } +``` + +컬럼이 2개 이상인 FK는 현재 처리하지 않고 무시된다 (`columns.len() == 1` 조건). 이 동작을 명시적으로 검증한다. (순방향 관계 필드 미생성, 역방향도 미생성) + +**현재 상태:** 미구현. + +--- + +### 2-8. 역방향 필드명 singularize + +테이블명 → 모델명 변환 규칙: + +| 테이블명 | 모델명 (역방향 타입) | +|---------|-------------------| +| `posts` | `Post` | +| `categories` | `Category` | +| `boxes` | `Box` | +| `users` | `User` | + +`singularize` 함수의 변환 결과를 통합 테스트로 검증한다. + +**현재 상태:** `test_pluralize` (유틸 단위 테스트만 존재). 역방향 필드에서 실제로 singularize가 적용된 통합 케이스 없음. + +--- + +## Layer 3 — 예외 처리 + +### 3-1. 식별자 충돌 (언어/도구 예약어) + +Prisma 스키마 키워드가 테이블명 또는 컬럼명으로 사용된 경우, 출력이 파싱 오류를 일으키지 않아야 한다. + +| 예약어 종류 | 예시 | +|-----------|------| +| Prisma 블록 키워드 | `model`, `enum`, `type`, `generator`, `datasource`, `view` | +| SQL 예약어 | `order`, `select`, `user`, `group`, `check`, `index` | +| Prisma 필드 속성 | `id`, `unique`, `default`, `map`, `relation` | + +테이블명에 예약어가 오면 `@@map`으로, 컬럼명에 예약어가 오면 필드명으로 그대로 출력된다. 현재 이스케이프 처리가 없으므로 출력 결과를 스냅샷으로 고정한 뒤, 향후 이스케이프 구현 시 테스트가 실패하여 변경을 감지한다. + +**현재 상태:** 미구현. + +--- + +### 3-2. 특수문자로 인한 코드 구조 파괴 + +`description` 또는 `comment`에 코드 구조를 깨뜨릴 수 있는 문자가 포함된 경우. + +| 입력 | 기대 동작 | +|------|---------| +| `description = "line1\nline2"` | 줄마다 `///` 분리하여 출력 | +| `comment = "has \"quotes\""` | `"` 이스케이프 처리 또는 그대로 `///` 주석으로 출력 | +| `comment = "has } brace"` | 중괄호가 모델 블록을 닫지 않아야 함 | +| `default = "'val with \" quote'"` | `@default(...)` 안에서 `"` 이스케이프 처리 | + +**현재 상태:** 미구현. + +--- + +## 현재 구현 상태 요약 + +| 테스트 | Layer | 상태 | 비고 | +|--------|-------|------|------| +| `test_basic_table` | 1 | ✓ | PK(auto_inc), unique, comment, description | +| `test_table_with_enum` | 1 | ✓ | String enum | +| `test_table_with_integer_enum` | 1 | ✓ | Integer enum | +| `test_table_with_indexes` | 1 | ✓ | named + unnamed 단일 index | +| `test_table_with_default_values` | 1 | ✓ | boolean, integer, now() | +| `test_table_with_foreign_key` | **2** | ⚠ | render_entity 사용 중 → render_entity_with_schema로 이전 필요 | +| `test_with_schema_back_relations` | 2 | ⚠ | assert_snapshot! 으로 교체 필요 | +| 전체 컬럼 타입 × nullable | 1 | ✗ | | +| Composite PK | 1 | ✗ | | +| Composite unique/index | 1 | ✗ | | +| Enum nullable / default / dedup | 1 | ✗ | | +| Description=None, multiline | 1 | ✗ | | +| Default (string, uuid, dbgenerated) | 1 | ✗ | | +| has-one (unique FK) | 2 | ✗ | | +| nullable FK | 2 | ✗ | | +| 다중 FK to same table | 2 | ✗ | | +| 자기참조 FK | 2 | ✗ | | +| on_delete / on_update | 2 | ✗ | | +| Composite FK 무시 | 2 | ✗ | | +| 예약어 | 3 | ✗ | | +| 특수문자 | 3 | ✗ | | \ No newline at end of file diff --git a/crates/vespertide-exporter/src/prisma/mod.rs b/crates/vespertide-exporter/src/prisma/mod.rs new file mode 100644 index 00000000..508b8d91 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/mod.rs @@ -0,0 +1,1387 @@ +use std::collections::{HashMap, HashSet}; + +use crate::orm::OrmExporter; +use vespertide_config::PrismaConfig; +use vespertide_core::schema::column::{ColumnType, ComplexColumnType, EnumValues, SimpleColumnType}; +use vespertide_core::schema::constraint::TableConstraint; +use vespertide_core::schema::reference::ReferenceAction; +use vespertide_core::TableDef; + +pub struct PrismaExporter; + +impl OrmExporter for PrismaExporter { + fn render_entity(&self, table: &TableDef) -> Result { + Ok(render_entity(table)) + } + + fn render_entity_with_schema( + &self, + table: &TableDef, + schema: &[TableDef], + ) -> Result { + Ok(render_entity_with_schema(table, schema)) + } +} + +/// Prisma exporter with configuration support. +/// +/// Assembles a complete `schema.prisma` file from a full table list. +pub struct PrismaExporterWithConfig<'a> { + pub config: &'a PrismaConfig, +} + +impl<'a> PrismaExporterWithConfig<'a> { + pub fn new(config: &'a PrismaConfig) -> Self { + Self { config } + } + + /// Render a complete `schema.prisma` file for all tables. + /// + /// Output order: datasource → generator → (globally deduped) enum blocks → model blocks. + pub fn render_schema(&self, tables: &[TableDef]) -> String { + let mut seen_enums: HashSet = HashSet::new(); + let mut enum_blocks: Vec = Vec::new(); + for table in tables { + for (name, values) in collect_table_enums(table) { + if seen_enums.insert(name.to_string()) { + enum_blocks.push(render_enum(name, values)); + } + } + } + + let mut parts: Vec = Vec::new(); + + let mut datasource = vec![ + "datasource db {".to_string(), + format!(" provider = \"{}\"", self.config.provider()), + " url = env(\"DATABASE_URL\")".to_string(), + ]; + if let Some(rm) = self.config.relation_mode() { + datasource.push(format!(" relationMode = \"{}\"", rm)); + } + datasource.push("}".to_string()); + parts.push(datasource.join("\n")); + + let mut generator = vec![ + "generator client {".to_string(), + " provider = \"prisma-client-js\"".to_string(), + ]; + if let Some(output) = self.config.client_output() { + generator.push(format!(" output = \"{}\"", output)); + } + generator.push("}".to_string()); + parts.push(generator.join("\n")); + + parts.extend(enum_blocks); + + for table in tables { + parts.push(render_model(table, tables)); + } + + parts.join("\n\n") + "\n" + } +} + +fn collect_table_enums<'a>(table: &'a TableDef) -> Vec<(&'a str, &'a EnumValues)> { + let mut seen = HashSet::new(); + let mut result = Vec::new(); + for col in &table.columns { + if let ColumnType::Complex(ComplexColumnType::Enum { name, values }) = &col.r#type { + if seen.insert(name.as_str()) { + result.push((name.as_str(), values)); + } + } + } + result +} + +/// Render enum blocks + model block without schema context (no back-relations). +pub fn render_entity(table: &TableDef) -> String { + render_entity_with_schema(table, &[]) +} + +/// Render enum blocks + model block with full schema context (includes back-relations). +pub fn render_entity_with_schema(table: &TableDef, schema: &[TableDef]) -> String { + let mut parts: Vec = Vec::new(); + for (name, values) in collect_table_enums(table) { + parts.push(render_enum(name, values)); + } + parts.push(render_model(table, schema)); + parts.join("\n\n") +} + +fn render_enum(name: &str, values: &EnumValues) -> String { + let enum_name = to_pascal_case(name); + let mut lines = Vec::new(); + lines.push(format!("enum {} {{", enum_name)); + match values { + EnumValues::String(vals) => { + for val in vals { + let variant = to_screaming_snake(val); + if variant == *val { + lines.push(format!(" {}", variant)); + } else { + lines.push(format!(" {} @map(\"{}\")", variant, val)); + } + } + } + EnumValues::Integer(vals) => { + // Prisma doesn't support integer enums natively; emit as string variants with comment + for val in vals { + let variant = to_screaming_snake(&val.name); + lines.push(format!(" {} // = {}", variant, val.value)); + } + } + } + lines.push("}".into()); + lines.join("\n") +} + +struct PkInfo { + columns: Vec, + auto_increment: bool, +} + +fn extract_pk_info(constraints: &[TableConstraint]) -> PkInfo { + for c in constraints { + if let TableConstraint::PrimaryKey { auto_increment, columns } = c { + return PkInfo { columns: columns.clone(), auto_increment: *auto_increment }; + } + } + PkInfo { columns: Vec::new(), auto_increment: false } +} + +struct FkInfo<'a> { + ref_table: &'a str, + ref_cols: &'a [String], + on_delete: Option<&'a ReferenceAction>, + on_update: Option<&'a ReferenceAction>, +} + +fn render_model(table: &TableDef, schema: &[TableDef]) -> String { + let mut lines: Vec = Vec::new(); + + if let Some(desc) = &table.description { + for line in desc.lines() { + lines.push(format!("/// {}", line)); + } + } + + let model_name = to_pascal_case(&singularize(&table.name)); + lines.push(format!("model {} {{", model_name)); + + let pk_info = extract_pk_info(&table.constraints); + let pk_columns: HashSet<&str> = pk_info.columns.iter().map(|s| s.as_str()).collect(); + let is_composite_pk = pk_info.columns.len() > 1; + + let unique_single: HashMap<&str, Option<&str>> = table.constraints.iter() + .filter_map(|c| { + if let TableConstraint::Unique { name, columns, .. } = c { + if columns.len() == 1 { Some((columns[0].as_str(), name.as_deref())) } else { None } + } else { None } + }) + .collect(); + + // FK lookup by column + let fk_by_col: HashMap<&str, FkInfo<'_>> = table.constraints.iter() + .filter_map(|c| { + if let TableConstraint::ForeignKey { columns, ref_table, ref_columns, on_delete, on_update, .. } = c { + if columns.len() == 1 { + Some(( + columns[0].as_str(), + FkInfo { + ref_table: ref_table.as_str(), + ref_cols: ref_columns.as_slice(), + on_delete: on_delete.as_ref(), + on_update: on_update.as_ref(), + }, + )) + } else { None } + } else { None } + }) + .collect(); + + // Count FKs per ref_table for disambiguation detection + let mut ref_table_fk_count: HashMap<&str, usize> = HashMap::new(); + for fk in fk_by_col.values() { + *ref_table_fk_count.entry(fk.ref_table).or_default() += 1; + } + + // Render scalar fields + inline relation fields + for col in &table.columns { + let col_name = col.name.as_str(); + let in_pk = pk_columns.contains(col_name); + let is_single_pk = in_pk && !is_composite_pk; + let auto_inc = is_single_pk && pk_info.auto_increment; + let is_unique = unique_single.get(col_name).copied(); + + if let Some(ref comment) = col.comment { + lines.push(format!(" /// {}", comment.replace('\n', " "))); + } + + let (type_str, native_attr) = column_type_to_prisma(&col.r#type, col.nullable); + let mut attrs: Vec = Vec::new(); + + if is_single_pk { + attrs.push("@id".into()); + if auto_inc { + attrs.push("@default(autoincrement())".into()); + } + } + + if !auto_inc { + if let Some(ref default) = col.default { + attrs.push(prisma_default_attr(default.to_sql(), &col.r#type)); + } + } + + if let Some(unique_name) = is_unique { + if !is_single_pk { + match unique_name { + Some(n) => attrs.push(format!("@unique(map: \"{}\")", n)), + None => attrs.push("@unique".into()), + } + } + } + + if let Some(ref native) = native_attr { + attrs.push(native.clone()); + } + + let attrs_str = if attrs.is_empty() { + String::new() + } else { + format!(" {}", attrs.join(" ")) + }; + + lines.push(format!(" {} {}{}", col_name, type_str, attrs_str)); + + // Emit inline relation field for FK columns + if let Some(fk) = fk_by_col.get(col_name) { + let rel_field_name = infer_relation_field_name(col_name); + let rel_model = to_pascal_case(&singularize(fk.ref_table)); + let rel_type = if col.nullable { + format!("{}?", rel_model) + } else { + rel_model.clone() + }; + + let multi_fk = ref_table_fk_count.get(fk.ref_table).copied().unwrap_or(0) > 1; + let is_self_ref = fk.ref_table == table.name.as_str(); + let needs_name = multi_fk || is_self_ref; + + let mut rel_args: Vec = Vec::new(); + if needs_name { + let rel_name = format!( + "{}{}", + to_pascal_case(&table.name), + to_pascal_case(&rel_field_name) + ); + rel_args.push(format!("\"{}\"", rel_name)); + } + rel_args.push(format!("fields: [{}]", col_name)); + rel_args.push(format!( + "references: [{}]", + fk.ref_cols.iter().map(|s| s.as_str()).collect::>().join(", ") + )); + if let Some(od) = fk.on_delete { + rel_args.push(format!("onDelete: {}", reference_action_to_prisma(od))); + } + if let Some(ou) = fk.on_update { + rel_args.push(format!("onUpdate: {}", reference_action_to_prisma(ou))); + } + + lines.push(format!( + " {} {} @relation({})", + rel_field_name, + rel_type, + rel_args.join(", ") + )); + } + } + + // Back-relations from schema context + if !schema.is_empty() { + let back_rels = collect_back_relations(&table.name, schema); + for br in &back_rels { + let (field_name, rel_type) = back_rel_field(br); + let rel_attr = match &br.relation_name { + Some(name) => format!(" @relation(\"{}\")", name), + None => String::new(), + }; + lines.push(format!(" {} {}{}", field_name, rel_type, rel_attr)); + } + } + + // Blank line before model-level attributes + lines.push(String::new()); + + // Composite PK + if is_composite_pk { + lines.push(format!(" @@id([{}])", pk_info.columns.join(", "))); + } + + // Composite unique constraints + for c in &table.constraints { + if let TableConstraint::Unique { name, columns } = c { + if columns.len() > 1 { + let cols = columns.join(", "); + if let Some(n) = name { + lines.push(format!(" @@unique([{}], name: \"{}\")", cols, n)); + } else { + lines.push(format!(" @@unique([{}])", cols)); + } + } + } + } + + // All index constraints + for c in &table.constraints { + if let TableConstraint::Index { name, columns } = c { + let cols = columns.join(", "); + if let Some(n) = name { + lines.push(format!(" @@index([{}], name: \"{}\")", cols, n)); + } else { + lines.push(format!(" @@index([{}])", cols)); + } + } + } + + // @@map (always present since model is PascalCase but table is snake_case) + lines.push(format!(" @@map(\"{}\")", table.name)); + lines.push("}".into()); + + lines.join("\n") +} + +struct BackRelation { + source_table: String, + fk_col: String, + is_one_to_one: bool, + relation_name: Option, +} + +fn back_rel_field(br: &BackRelation) -> (String, String) { + let source_pascal = to_pascal_case(&singularize(&br.source_table)); + let rel_type = if br.is_one_to_one { + format!("{}?", source_pascal) + } else { + format!("{}[]", source_pascal) + }; + + // source_table is already the plural table name — use it directly + let field_name = if br.relation_name.is_some() { + let rel_field = infer_relation_field_name(&br.fk_col); + if br.is_one_to_one { + format!("{}_{}", rel_field, br.source_table) + } else { + format!("{}_{}", rel_field, &br.source_table) + } + } else if br.is_one_to_one { + br.source_table.clone() + } else { + br.source_table.clone() + }; + + (field_name, rel_type) +} + +fn collect_back_relations(target_table: &str, schema: &[TableDef]) -> Vec { + let mut result = Vec::new(); + + for source in schema { + let fks_to_target: Vec<(&str, &[String])> = source.constraints.iter() + .filter_map(|c| { + if let TableConstraint::ForeignKey { columns, ref_table, ref_columns, .. } = c { + if ref_table.as_str() == target_table && columns.len() == 1 { + Some((columns[0].as_str(), ref_columns.as_slice())) + } else { None } + } else { None } + }) + .collect(); + + if fks_to_target.is_empty() { continue; } + + let multi_fk = fks_to_target.len() > 1; + let is_self_ref = source.name.as_str() == target_table; + + for (fk_col, _) in &fks_to_target { + let is_unique = source.constraints.iter().any(|c| { + matches!(c, TableConstraint::Unique { columns, .. } + if columns.len() == 1 && columns[0].as_str() == *fk_col) + }); + + let needs_name = multi_fk || is_self_ref; + let relation_name = if needs_name { + let rel_field = infer_relation_field_name(fk_col); + Some(format!( + "{}{}", + to_pascal_case(&source.name), + to_pascal_case(&rel_field) + )) + } else { + None + }; + + result.push(BackRelation { + source_table: source.name.clone(), + fk_col: fk_col.to_string(), + is_one_to_one: is_unique, + relation_name, + }); + } + } + + result +} + +fn column_type_to_prisma(ty: &ColumnType, nullable: bool) -> (String, Option) { + let q = if nullable { "?" } else { "" }; + + match ty { + ColumnType::Simple(simple) => { + let (base, native) = match simple { + SimpleColumnType::SmallInt => ("Int", Some("@db.SmallInt")), + SimpleColumnType::Integer => ("Int", None), + SimpleColumnType::BigInt => ("BigInt", None), + SimpleColumnType::Real => ("Float", Some("@db.Real")), + SimpleColumnType::DoublePrecision => ("Float", None), + SimpleColumnType::Text => ("String", Some("@db.Text")), + SimpleColumnType::Boolean => ("Boolean", None), + SimpleColumnType::Date => ("DateTime", Some("@db.Date")), + SimpleColumnType::Time => ("DateTime", Some("@db.Time")), + SimpleColumnType::Timestamp => ("DateTime", Some("@db.Timestamp")), + SimpleColumnType::Timestamptz => ("DateTime", Some("@db.Timestamptz")), + SimpleColumnType::Interval => ("String", Some("@db.Interval")), + SimpleColumnType::Bytea => ("Bytes", None), + SimpleColumnType::Uuid => ("String", Some("@db.Uuid")), + SimpleColumnType::Json => ("Json", None), + SimpleColumnType::Inet => ("String", Some("@db.Inet")), + SimpleColumnType::Cidr => ("String", Some("@db.Cidr")), + SimpleColumnType::Macaddr => ("String", Some("@db.Macaddr")), + SimpleColumnType::Xml => ("String", Some("@db.Xml")), + }; + (format!("{}{}", base, q), native.map(str::to_string)) + } + ColumnType::Complex(complex) => match complex { + ComplexColumnType::Varchar { length } => { + (format!("String{}", q), Some(format!("@db.VarChar({})", length))) + } + ComplexColumnType::Char { length } => { + (format!("String{}", q), Some(format!("@db.Char({})", length))) + } + ComplexColumnType::Numeric { precision, scale } => { + (format!("Decimal{}", q), Some(format!("@db.Decimal({}, {})", precision, scale))) + } + ComplexColumnType::Custom { custom_type } => { + (format!("Unsupported(\"{}\"){}", custom_type, q), None) + } + ComplexColumnType::Enum { name, .. } => { + (format!("{}{}", to_pascal_case(name), q), None) + } + }, + } +} + +fn prisma_default_attr(default_sql: String, col_type: &ColumnType) -> String { + if default_sql == "true" { + return "@default(true)".into(); + } + if default_sql == "false" { + return "@default(false)".into(); + } + + let lower = default_sql.to_lowercase(); + if lower.contains("now()") || lower.starts_with("current_timestamp") { + return "@default(now())".into(); + } + if lower.contains("gen_random_uuid()") + || lower.contains("uuid_generate_v4()") + || lower.contains("newid()") + { + return "@default(uuid())".into(); + } + + // Any remaining function call → dbgenerated + if default_sql.contains('(') { + let escaped = default_sql.replace('"', "\\\""); + return format!("@default(dbgenerated(\"{}\"))", escaped); + } + + // String literal with quotes — may be an enum value + if default_sql.starts_with('\'') || default_sql.starts_with('"') { + let stripped = default_sql.trim_matches(|c| c == '\'' || c == '"'); + if let ColumnType::Complex(ComplexColumnType::Enum { name, values }) = col_type { + if let EnumValues::String(variants) = values { + if variants.iter().any(|v| v.as_str() == stripped) { + let variant = to_screaming_snake(stripped); + return format!("@default({})", variant); + } + } + } + return format!("@default(\"{}\")", stripped.replace('"', "\\\"")); + } + + // Numeric + if default_sql.parse::().is_ok() { + return format!("@default({})", default_sql); + } + + // Fallback + let escaped = default_sql.replace('"', "\\\""); + format!("@default(dbgenerated(\"{}\"))", escaped) +} + +fn reference_action_to_prisma(action: &ReferenceAction) -> &'static str { + match action { + ReferenceAction::Cascade => "Cascade", + ReferenceAction::Restrict => "Restrict", + ReferenceAction::SetNull => "SetNull", + ReferenceAction::SetDefault => "SetDefault", + ReferenceAction::NoAction => "NoAction", + } +} + +fn infer_relation_field_name(fk_col: &str) -> String { + fk_col.strip_suffix("_id").unwrap_or(fk_col).to_string() +} + +fn to_pascal_case(s: &str) -> String { + s.split('_') + .map(|word| { + let mut chars = word.chars(); + match chars.next() { + None => String::new(), + Some(first) => first.to_uppercase().chain(chars).collect(), + } + }) + .collect() +} + +fn to_screaming_snake(s: &str) -> String { + let mut result = String::new(); + let mut prev_lower = false; + for ch in s.chars() { + if ch.is_uppercase() && prev_lower { + result.push('_'); + } + if ch.is_alphanumeric() { + result.push(ch.to_ascii_uppercase()); + prev_lower = ch.is_lowercase(); + } else { + result.push('_'); + prev_lower = false; + } + } + result.trim_end_matches('_').to_string() +} + +fn singularize(s: &str) -> String { + if s.ends_with("ies") { + format!("{}y", &s[..s.len() - 3]) + } else if s.ends_with("ses") || s.ends_with("xes") || s.ends_with("ches") || s.ends_with("shes") { + s[..s.len() - 2].to_string() + } else if s.ends_with('s') && !s.ends_with("ss") { + s[..s.len() - 1].to_string() + } else { + s.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use insta::assert_snapshot; + use rstest::rstest; + use vespertide_core::schema::column::NumValue; + use vespertide_core::{ColumnDef, DefaultValue, TableDef}; + + // ── Helpers ────────────────────────────────────────────────────────────── + + fn col(name: &str, ty: ColumnType) -> ColumnDef { + ColumnDef { + name: name.to_string(), + r#type: ty, + nullable: false, + default: None, + comment: None, + primary_key: None, + unique: None, + index: None, + foreign_key: None, + } + } + + fn col_null(name: &str, ty: ColumnType) -> ColumnDef { + ColumnDef { nullable: true, ..col(name, ty) } + } + + fn col_with_default(name: &str, ty: ColumnType, default: DefaultValue) -> ColumnDef { + ColumnDef { default: Some(default), ..col(name, ty) } + } + + fn col_with_comment(name: &str, ty: ColumnType, comment: &str) -> ColumnDef { + ColumnDef { comment: Some(comment.to_string()), ..col(name, ty) } + } + + fn pk(cols: &[&str], auto_inc: bool) -> TableConstraint { + TableConstraint::PrimaryKey { + auto_increment: auto_inc, + columns: cols.iter().map(|s| s.to_string()).collect(), + } + } + + fn uniq(name: Option<&str>, cols: &[&str]) -> TableConstraint { + TableConstraint::Unique { + name: name.map(str::to_string), + columns: cols.iter().map(|s| s.to_string()).collect(), + } + } + + fn idx(name: Option<&str>, cols: &[&str]) -> TableConstraint { + TableConstraint::Index { + name: name.map(str::to_string), + columns: cols.iter().map(|s| s.to_string()).collect(), + } + } + + fn fk( + cols: &[&str], + ref_table: &str, + ref_cols: &[&str], + on_delete: Option, + on_update: Option, + ) -> TableConstraint { + TableConstraint::ForeignKey { + name: None, + columns: cols.iter().map(|s| s.to_string()).collect(), + ref_table: ref_table.to_string(), + ref_columns: ref_cols.iter().map(|s| s.to_string()).collect(), + on_delete, + on_update, + } + } + + fn table(name: &str, desc: Option<&str>, cols: Vec, constraints: Vec) -> TableDef { + TableDef { + name: name.to_string(), + description: desc.map(str::to_string), + columns: cols, + constraints, + } + } + + fn render_schema_all(schema: &[TableDef]) -> String { + schema + .iter() + .map(|t| render_entity_with_schema(t, schema)) + .collect::>() + .join("\n\n") + } + + // ── 1-1. Column type × nullable matrix ────────────────────────────────── + + #[test] + fn test_column_type_matrix() { + let cases: Vec<(&str, ColumnType)> = vec![ + ("small_int", ColumnType::Simple(SimpleColumnType::SmallInt)), + ("integer", ColumnType::Simple(SimpleColumnType::Integer)), + ("big_int", ColumnType::Simple(SimpleColumnType::BigInt)), + ("real", ColumnType::Simple(SimpleColumnType::Real)), + ("double_precision", ColumnType::Simple(SimpleColumnType::DoublePrecision)), + ("text", ColumnType::Simple(SimpleColumnType::Text)), + ("boolean", ColumnType::Simple(SimpleColumnType::Boolean)), + ("date", ColumnType::Simple(SimpleColumnType::Date)), + ("time", ColumnType::Simple(SimpleColumnType::Time)), + ("timestamp", ColumnType::Simple(SimpleColumnType::Timestamp)), + ("timestamptz", ColumnType::Simple(SimpleColumnType::Timestamptz)), + ("interval", ColumnType::Simple(SimpleColumnType::Interval)), + ("bytea", ColumnType::Simple(SimpleColumnType::Bytea)), + ("uuid", ColumnType::Simple(SimpleColumnType::Uuid)), + ("json", ColumnType::Simple(SimpleColumnType::Json)), + ("inet", ColumnType::Simple(SimpleColumnType::Inet)), + ("cidr", ColumnType::Simple(SimpleColumnType::Cidr)), + ("macaddr", ColumnType::Simple(SimpleColumnType::Macaddr)), + ("xml", ColumnType::Simple(SimpleColumnType::Xml)), + ("varchar_255", ColumnType::Complex(ComplexColumnType::Varchar { length: 255 })), + ("char_10", ColumnType::Complex(ComplexColumnType::Char { length: 10 })), + ("numeric_10_2", ColumnType::Complex(ComplexColumnType::Numeric { precision: 10, scale: 2 })), + ("custom_citext", ColumnType::Complex(ComplexColumnType::Custom { custom_type: "citext".to_string() })), + ("enum_string", ColumnType::Complex(ComplexColumnType::Enum { + name: "my_status".to_string(), + values: EnumValues::String(vec!["active".to_string(), "inactive".to_string()]), + })), + ("enum_integer", ColumnType::Complex(ComplexColumnType::Enum { + name: "my_level".to_string(), + values: EnumValues::Integer(vec![ + NumValue { name: "Low".to_string(), value: 0 }, + NumValue { name: "High".to_string(), value: 1 }, + ]), + })), + ]; + + let mut output = String::new(); + for (label, ty) in cases { + for (nullable, suffix) in [(false, "not_null"), (true, "null")] { + let val_col = if nullable { col_null("val", ty.clone()) } else { col("val", ty.clone()) }; + let t = table( + "items", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), val_col], + vec![pk(&["id"], false)], + ); + output.push_str(&format!("--- {}_{} ---\n{}\n\n", label, suffix, render_entity(&t))); + } + } + assert_snapshot!(output); + } + + // ── 1-2. PK variants ──────────────────────────────────────────────────── + + #[test] + fn test_pk_single_autoincrement() { + let t = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("name", ColumnType::Simple(SimpleColumnType::Text))], + vec![pk(&["id"], true)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_pk_single_no_autoincrement() { + let t = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Uuid)), col("name", ColumnType::Simple(SimpleColumnType::Text))], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_pk_composite() { + let t = table( + "user_roles", + None, + vec![ + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("role_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["user_id", "role_id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_pk_none() { + let t = table( + "logs", + None, + vec![ + col("message", ColumnType::Simple(SimpleColumnType::Text)), + col("created_at", ColumnType::Simple(SimpleColumnType::Timestamptz)), + ], + vec![], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── 1-3. Unique constraints ────────────────────────────────────────────── + + #[test] + fn test_unique_single_named() { + let t = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("email", ColumnType::Simple(SimpleColumnType::Text))], + vec![pk(&["id"], true), uniq(Some("uq_users__email"), &["email"])], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_unique_single_unnamed() { + let t = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("email", ColumnType::Simple(SimpleColumnType::Text))], + vec![pk(&["id"], true), uniq(None, &["email"])], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_unique_composite_named() { + let t = table( + "memberships", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("org_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id"], true), uniq(Some("uq_memberships__user_id_org_id"), &["user_id", "org_id"])], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_unique_composite_unnamed() { + let t = table( + "memberships", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("org_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id"], true), uniq(None, &["user_id", "org_id"])], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── 1-4. Index ─────────────────────────────────────────────────────────── + + #[test] + fn test_index_single_named() { + let t = table( + "articles", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("created_at", ColumnType::Simple(SimpleColumnType::Timestamptz))], + vec![pk(&["id"], false), idx(Some("ix_articles__created_at"), &["created_at"])], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_index_single_unnamed() { + let t = table( + "articles", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("title", ColumnType::Simple(SimpleColumnType::Text))], + vec![pk(&["id"], false), idx(None, &["title"])], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_index_composite() { + let t = table( + "events", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("created_at", ColumnType::Simple(SimpleColumnType::Timestamptz)), + ], + vec![pk(&["id"], false), idx(Some("ix_events__user_id_created_at"), &["user_id", "created_at"])], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── 1-5. Default values ────────────────────────────────────────────────── + + #[rstest] + #[case::bool_true("bool_true", ColumnType::Simple(SimpleColumnType::Boolean), DefaultValue::Bool(true))] + #[case::bool_false("bool_false", ColumnType::Simple(SimpleColumnType::Boolean), DefaultValue::Bool(false))] + #[case::now("now", ColumnType::Simple(SimpleColumnType::Timestamptz), DefaultValue::String("now()".into()))] + #[case::current_timestamp("current_timestamp", ColumnType::Simple(SimpleColumnType::Timestamptz), DefaultValue::String("CURRENT_TIMESTAMP".into()))] + #[case::gen_random_uuid("gen_random_uuid", ColumnType::Simple(SimpleColumnType::Uuid), DefaultValue::String("gen_random_uuid()".into()))] + #[case::uuid_generate_v4("uuid_generate_v4", ColumnType::Simple(SimpleColumnType::Uuid), DefaultValue::String("uuid_generate_v4()".into()))] + #[case::arbitrary_fn("arbitrary_fn", ColumnType::Simple(SimpleColumnType::Integer), DefaultValue::String("nextval('my_seq')".into()))] + #[case::string_literal("string_literal", ColumnType::Simple(SimpleColumnType::Text), DefaultValue::String("'foo'".into()))] + #[case::integer_literal("integer_literal", ColumnType::Simple(SimpleColumnType::Integer), DefaultValue::Integer(42))] + #[case::fallback_keyword("fallback_keyword", ColumnType::Simple(SimpleColumnType::Date), DefaultValue::String("CURRENT_DATE".into()))] + fn test_default_attr_variants( + #[case] label: &str, + #[case] ty: ColumnType, + #[case] default: DefaultValue, + ) { + let t = table( + "items", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col_with_default("val", ty, default)], + vec![pk(&["id"], false)], + ); + assert_snapshot!(label, render_entity(&t)); + } + + // ── 1-6. Enum ──────────────────────────────────────────────────────────── + + #[test] + fn test_enum_string_screaming_snake_match() { + let ty = ColumnType::Complex(ComplexColumnType::Enum { + name: "status".to_string(), + values: EnumValues::String(vec!["ACTIVE".to_string(), "INACTIVE".to_string()]), + }); + let t = table( + "accounts", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("status", ty)], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_enum_string_mapped() { + let ty = ColumnType::Complex(ComplexColumnType::Enum { + name: "order_status".to_string(), + values: EnumValues::String(vec!["pending".to_string(), "shipped".to_string(), "delivered".to_string()]), + }); + let t = table( + "orders", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("status", ty)], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_enum_integer() { + let ty = ColumnType::Complex(ComplexColumnType::Enum { + name: "priority_level".to_string(), + values: EnumValues::Integer(vec![ + NumValue { name: "Low".to_string(), value: 0 }, + NumValue { name: "Medium".to_string(), value: 1 }, + NumValue { name: "High".to_string(), value: 2 }, + ]), + }); + let t = table( + "tasks", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col("priority", ty)], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_enum_nullable() { + let ty = ColumnType::Complex(ComplexColumnType::Enum { + name: "order_status".to_string(), + values: EnumValues::String(vec!["pending".to_string(), "shipped".to_string()]), + }); + let t = table( + "orders", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer)), col_null("status", ty)], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_enum_default_value() { + let ty = ColumnType::Complex(ComplexColumnType::Enum { + name: "order_status".to_string(), + values: EnumValues::String(vec!["pending".to_string(), "shipped".to_string()]), + }); + let t = table( + "orders", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_with_default("status", ty, DefaultValue::String("'pending'".into())), + ], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_enum_dedup_multiple_columns() { + let enum_ty = || ColumnType::Complex(ComplexColumnType::Enum { + name: "role_type".to_string(), + values: EnumValues::String(vec!["admin".to_string(), "member".to_string()]), + }); + let t = table( + "org_members", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("role", enum_ty()), + col("backup_role", enum_ty()), + ], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── 1-7. Description / Comment ─────────────────────────────────────────── + + #[test] + fn test_description_present() { + let t = table( + "users", + Some("User accounts table"), + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_description_none() { + let t = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_description_multiline() { + let t = table( + "users", + Some("First line\nSecond line\nThird line"), + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_column_comment_present() { + let t = table( + "users", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_with_comment("email", ColumnType::Simple(SimpleColumnType::Text), "User's email address"), + ], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_column_comment_multiline() { + // Production code replaces '\n' with ' ' in column comments — snapshot locks in that behavior + let t = table( + "users", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_with_comment("email", ColumnType::Simple(SimpleColumnType::Text), "line1\nline2"), + ], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── 1-8. @@map ─────────────────────────────────────────────────────────── + + #[test] + fn test_always_emits_map() { + let t = table( + "user_profiles", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── Layer 2 — Relations ────────────────────────────────────────────────── + + #[test] + fn test_has_many_basic() { + let users = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let posts = table( + "posts", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("title", ColumnType::Simple(SimpleColumnType::Text)), + ], + vec![pk(&["id"], true), fk(&["user_id"], "users", &["id"], None, None)], + ); + let schema = vec![users, posts]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[test] + fn test_has_one_unique_fk() { + let users = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let profiles = table( + "profiles", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![ + pk(&["id"], true), + uniq(None, &["user_id"]), + fk(&["user_id"], "users", &["id"], None, None), + ], + ); + let schema = vec![users, profiles]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[test] + fn test_nullable_fk() { + let users = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let posts = table( + "posts", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_null("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id"], true), fk(&["user_id"], "users", &["id"], None, None)], + ); + let schema = vec![users, posts]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[test] + fn test_multiple_fk_same_table() { + let users = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let posts = table( + "posts", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("author_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("reviewer_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![ + pk(&["id"], true), + fk(&["author_id"], "users", &["id"], None, None), + fk(&["reviewer_id"], "users", &["id"], None, None), + ], + ); + let schema = vec![users, posts]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[test] + fn test_self_reference_fk() { + let categories = table( + "categories", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_null("parent_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("name", ColumnType::Simple(SimpleColumnType::Text)), + ], + vec![pk(&["id"], true), fk(&["parent_id"], "categories", &["id"], None, None)], + ); + let schema = vec![categories]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[rstest] + #[case::cascade("cascade", ReferenceAction::Cascade, None)] + #[case::restrict("restrict", ReferenceAction::Restrict, None)] + #[case::set_null("set_null", ReferenceAction::SetNull, None)] + #[case::set_default("set_default", ReferenceAction::SetDefault, None)] + #[case::no_action("no_action", ReferenceAction::NoAction, None)] + #[case::both("both", ReferenceAction::Cascade, Some(ReferenceAction::Restrict))] + fn test_reference_actions( + #[case] label: &str, + #[case] on_delete: ReferenceAction, + #[case] on_update: Option, + ) { + let users = table( + "users", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let posts = table( + "posts", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id"], true), fk(&["user_id"], "users", &["id"], Some(on_delete), on_update)], + ); + let schema = vec![users, posts]; + assert_snapshot!(label, render_schema_all(&schema)); + } + + #[test] + fn test_composite_fk_ignored() { + let orgs = table( + "orgs", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id", "user_id"], false)], + ); + let memberships = table( + "memberships", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("org_id", ColumnType::Simple(SimpleColumnType::Integer)), + col("member_user_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![ + pk(&["id"], false), + fk(&["org_id", "member_user_id"], "orgs", &["id", "user_id"], None, None), + ], + ); + let schema = vec![orgs, memberships]; + assert_snapshot!(render_schema_all(&schema)); + } + + #[rstest] + #[case::posts("posts")] + #[case::categories("categories")] + #[case::boxes("boxes")] + #[case::users("users")] + fn test_singularize_in_back_relations(#[case] child_table: &str) { + let parent = table( + "refs", + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], true)], + ); + let child = table( + child_table, + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col("ref_id", ColumnType::Simple(SimpleColumnType::Integer)), + ], + vec![pk(&["id"], true), fk(&["ref_id"], "refs", &["id"], None, None)], + ); + let schema = vec![parent, child]; + assert_snapshot!(child_table, render_schema_all(&schema)); + } + + // ── Layer 3 — Edge cases ───────────────────────────────────────────────── + + #[test] + fn test_reserved_identifier_table_name() { + let reserved = ["select", "order", "model", "enum", "type", "group"]; + let mut output = String::new(); + for name in reserved { + let t = table( + name, + None, + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + output.push_str(&format!("--- {} ---\n{}\n\n", name, render_entity(&t))); + } + assert_snapshot!(output); + } + + #[test] + fn test_reserved_identifier_column_name() { + let reserved_cols = ["default", "unique", "relation", "map", "index"]; + let mut cols = vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))]; + cols.extend(reserved_cols.iter().map(|name| col(name, ColumnType::Simple(SimpleColumnType::Text)))); + let t = table("things", None, cols, vec![pk(&["id"], false)]); + assert_snapshot!(render_entity(&t)); + } + + #[test] + fn test_description_special_chars() { + let cases: &[(&str, &str)] = &[ + ("newline", "line1\nline2"), + ("double_quote", "has \"quotes\""), + ("closing_brace", "has } brace"), + ]; + let mut output = String::new(); + for (label, desc) in cases { + let t = table( + "things", + Some(desc), + vec![col("id", ColumnType::Simple(SimpleColumnType::Integer))], + vec![pk(&["id"], false)], + ); + output.push_str(&format!("--- {} ---\n{}\n\n", label, render_entity(&t))); + } + assert_snapshot!(output); + } + + #[test] + fn test_default_value_with_quote() { + let t = table( + "items", + None, + vec![ + col("id", ColumnType::Simple(SimpleColumnType::Integer)), + col_with_default("name", ColumnType::Simple(SimpleColumnType::Text), DefaultValue::String("'val with \\\" quote'".into())), + ], + vec![pk(&["id"], false)], + ); + assert_snapshot!(render_entity(&t)); + } + + // ── Utility unit tests (preserved) ────────────────────────────────────── + + #[rstest] + #[case("hello_world", "HelloWorld")] + #[case("user_id", "UserId")] + #[case("simple", "Simple")] + fn test_to_pascal_case(#[case] input: &str, #[case] expected: &str) { + assert_eq!(to_pascal_case(input), expected); + } + + #[rstest] + #[case("pending", "PENDING")] + #[case("shipped", "SHIPPED")] + #[case("order_status", "ORDER_STATUS")] + fn test_to_screaming_snake(#[case] input: &str, #[case] expected: &str) { + assert_eq!(to_screaming_snake(input), expected); + } + + fn pluralize(s: &str) -> String { + if s.ends_with('s') || s.ends_with('x') || s.ends_with("ch") || s.ends_with("sh") { + format!("{}es", s) + } else if s.ends_with('y') + && !matches!(s.chars().rev().nth(1), Some('a' | 'e' | 'o' | 'u')) + { + format!("{}ies", &s[..s.len() - 1]) + } else { + format!("{}s", s) + } + } + + #[rstest] + #[case("post", "posts")] + #[case("user", "users")] + #[case("article", "articles")] + #[case("box", "boxes")] + #[case("category", "categories")] + fn test_pluralize(#[case] input: &str, #[case] expected: &str) { + assert_eq!(pluralize(input), expected); + } +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap new file mode 100644 index 00000000..be42ced7 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap @@ -0,0 +1,9 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model UserProfile { + id Int @id + + @@map("user_profiles") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap new file mode 100644 index 00000000..1af42be1 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val Int @default(dbgenerated("nextval('my_seq')")) + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap new file mode 100644 index 00000000..952ec0ff --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val Boolean @default(false) + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap new file mode 100644 index 00000000..5a1b9766 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val Boolean @default(true) + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap new file mode 100644 index 00000000..b9bd13ce --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Restrict) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap new file mode 100644 index 00000000..ae4f39c3 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Ref { + id Int @id @default(autoincrement()) + boxes Box[] + + @@map("refs") +} + +model Box { + id Int @id @default(autoincrement()) + ref_id Int + ref Ref @relation(fields: [ref_id], references: [id]) + + @@map("boxes") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap new file mode 100644 index 00000000..d5cfdf6e --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap new file mode 100644 index 00000000..97760b25 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Ref { + id Int @id @default(autoincrement()) + categories Category[] + + @@map("refs") +} + +model Category { + id Int @id @default(autoincrement()) + ref_id Int + ref Ref @relation(fields: [ref_id], references: [id]) + + @@map("categories") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap new file mode 100644 index 00000000..0a8f15d1 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap @@ -0,0 +1,11 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id + /// line1 line2 + email String @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap new file mode 100644 index 00000000..96955fde --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap @@ -0,0 +1,11 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id + /// User's email address + email String @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap new file mode 100644 index 00000000..53bd3029 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap @@ -0,0 +1,423 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: output +--- +--- small_int_not_null --- +model Item { + id Int @id + val Int @db.SmallInt + + @@map("items") +} + +--- small_int_null --- +model Item { + id Int @id + val Int? @db.SmallInt + + @@map("items") +} + +--- integer_not_null --- +model Item { + id Int @id + val Int + + @@map("items") +} + +--- integer_null --- +model Item { + id Int @id + val Int? + + @@map("items") +} + +--- big_int_not_null --- +model Item { + id Int @id + val BigInt + + @@map("items") +} + +--- big_int_null --- +model Item { + id Int @id + val BigInt? + + @@map("items") +} + +--- real_not_null --- +model Item { + id Int @id + val Float @db.Real + + @@map("items") +} + +--- real_null --- +model Item { + id Int @id + val Float? @db.Real + + @@map("items") +} + +--- double_precision_not_null --- +model Item { + id Int @id + val Float + + @@map("items") +} + +--- double_precision_null --- +model Item { + id Int @id + val Float? + + @@map("items") +} + +--- text_not_null --- +model Item { + id Int @id + val String @db.Text + + @@map("items") +} + +--- text_null --- +model Item { + id Int @id + val String? @db.Text + + @@map("items") +} + +--- boolean_not_null --- +model Item { + id Int @id + val Boolean + + @@map("items") +} + +--- boolean_null --- +model Item { + id Int @id + val Boolean? + + @@map("items") +} + +--- date_not_null --- +model Item { + id Int @id + val DateTime @db.Date + + @@map("items") +} + +--- date_null --- +model Item { + id Int @id + val DateTime? @db.Date + + @@map("items") +} + +--- time_not_null --- +model Item { + id Int @id + val DateTime @db.Time + + @@map("items") +} + +--- time_null --- +model Item { + id Int @id + val DateTime? @db.Time + + @@map("items") +} + +--- timestamp_not_null --- +model Item { + id Int @id + val DateTime @db.Timestamp + + @@map("items") +} + +--- timestamp_null --- +model Item { + id Int @id + val DateTime? @db.Timestamp + + @@map("items") +} + +--- timestamptz_not_null --- +model Item { + id Int @id + val DateTime @db.Timestamptz + + @@map("items") +} + +--- timestamptz_null --- +model Item { + id Int @id + val DateTime? @db.Timestamptz + + @@map("items") +} + +--- interval_not_null --- +model Item { + id Int @id + val String @db.Interval + + @@map("items") +} + +--- interval_null --- +model Item { + id Int @id + val String? @db.Interval + + @@map("items") +} + +--- bytea_not_null --- +model Item { + id Int @id + val Bytes + + @@map("items") +} + +--- bytea_null --- +model Item { + id Int @id + val Bytes? + + @@map("items") +} + +--- uuid_not_null --- +model Item { + id Int @id + val String @db.Uuid + + @@map("items") +} + +--- uuid_null --- +model Item { + id Int @id + val String? @db.Uuid + + @@map("items") +} + +--- json_not_null --- +model Item { + id Int @id + val Json + + @@map("items") +} + +--- json_null --- +model Item { + id Int @id + val Json? + + @@map("items") +} + +--- inet_not_null --- +model Item { + id Int @id + val String @db.Inet + + @@map("items") +} + +--- inet_null --- +model Item { + id Int @id + val String? @db.Inet + + @@map("items") +} + +--- cidr_not_null --- +model Item { + id Int @id + val String @db.Cidr + + @@map("items") +} + +--- cidr_null --- +model Item { + id Int @id + val String? @db.Cidr + + @@map("items") +} + +--- macaddr_not_null --- +model Item { + id Int @id + val String @db.Macaddr + + @@map("items") +} + +--- macaddr_null --- +model Item { + id Int @id + val String? @db.Macaddr + + @@map("items") +} + +--- xml_not_null --- +model Item { + id Int @id + val String @db.Xml + + @@map("items") +} + +--- xml_null --- +model Item { + id Int @id + val String? @db.Xml + + @@map("items") +} + +--- varchar_255_not_null --- +model Item { + id Int @id + val String @db.VarChar(255) + + @@map("items") +} + +--- varchar_255_null --- +model Item { + id Int @id + val String? @db.VarChar(255) + + @@map("items") +} + +--- char_10_not_null --- +model Item { + id Int @id + val String @db.Char(10) + + @@map("items") +} + +--- char_10_null --- +model Item { + id Int @id + val String? @db.Char(10) + + @@map("items") +} + +--- numeric_10_2_not_null --- +model Item { + id Int @id + val Decimal @db.Decimal(10, 2) + + @@map("items") +} + +--- numeric_10_2_null --- +model Item { + id Int @id + val Decimal? @db.Decimal(10, 2) + + @@map("items") +} + +--- custom_citext_not_null --- +model Item { + id Int @id + val Unsupported("citext") + + @@map("items") +} + +--- custom_citext_null --- +model Item { + id Int @id + val Unsupported("citext")? + + @@map("items") +} + +--- enum_string_not_null --- +enum MyStatus { + ACTIVE @map("active") + INACTIVE @map("inactive") +} + +model Item { + id Int @id + val MyStatus + + @@map("items") +} + +--- enum_string_null --- +enum MyStatus { + ACTIVE @map("active") + INACTIVE @map("inactive") +} + +model Item { + id Int @id + val MyStatus? + + @@map("items") +} + +--- enum_integer_not_null --- +enum MyLevel { + LOW // = 0 + HIGH // = 1 +} + +model Item { + id Int @id + val MyLevel + + @@map("items") +} + +--- enum_integer_null --- +enum MyLevel { + LOW // = 0 + HIGH // = 1 +} + +model Item { + id Int @id + val MyLevel? + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap new file mode 100644 index 00000000..c4997a57 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap @@ -0,0 +1,19 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Org { + id Int + user_id Int + + @@id([id, user_id]) + @@map("orgs") +} + +model Membership { + id Int @id + org_id Int + member_user_id Int + + @@map("memberships") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap new file mode 100644 index 00000000..eed0bba9 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val DateTime @default(now()) @db.Timestamptz + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap new file mode 100644 index 00000000..ae13185f --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + name String @default("val with \\" quote") @db.Text + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap new file mode 100644 index 00000000..fab99063 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap @@ -0,0 +1,12 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +/// First line +/// Second line +/// Third line +model User { + id Int @id + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap new file mode 100644 index 00000000..08c9f558 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap @@ -0,0 +1,9 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap new file mode 100644 index 00000000..5acae84b --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +/// User accounts table +model User { + id Int @id + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap new file mode 100644 index 00000000..90fe2ceb --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap @@ -0,0 +1,28 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: output +--- +--- newline --- +/// line1 +/// line2 +model Thing { + id Int @id + + @@map("things") +} + +--- double_quote --- +/// has "quotes" +model Thing { + id Int @id + + @@map("things") +} + +--- closing_brace --- +/// has } brace +model Thing { + id Int @id + + @@map("things") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap new file mode 100644 index 00000000..85ddebb8 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap @@ -0,0 +1,16 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum RoleType { + ADMIN @map("admin") + MEMBER @map("member") +} + +model OrgMember { + id Int @id + role RoleType + backup_role RoleType + + @@map("org_members") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap new file mode 100644 index 00000000..0f0b8e38 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap @@ -0,0 +1,15 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum OrderStatus { + PENDING @map("pending") + SHIPPED @map("shipped") +} + +model Order { + id Int @id + status OrderStatus @default(PENDING) + + @@map("orders") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap new file mode 100644 index 00000000..06f4fd59 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap @@ -0,0 +1,16 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum PriorityLevel { + LOW // = 0 + MEDIUM // = 1 + HIGH // = 2 +} + +model Task { + id Int @id + priority PriorityLevel + + @@map("tasks") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap new file mode 100644 index 00000000..aae1b971 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap @@ -0,0 +1,15 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum OrderStatus { + PENDING @map("pending") + SHIPPED @map("shipped") +} + +model Order { + id Int @id + status OrderStatus? + + @@map("orders") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap new file mode 100644 index 00000000..f197ffc5 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap @@ -0,0 +1,16 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum OrderStatus { + PENDING @map("pending") + SHIPPED @map("shipped") + DELIVERED @map("delivered") +} + +model Order { + id Int @id + status OrderStatus + + @@map("orders") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap new file mode 100644 index 00000000..47ac5219 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap @@ -0,0 +1,15 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +enum Status { + ACTIVE + INACTIVE +} + +model Account { + id Int @id + status Status + + @@map("accounts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap new file mode 100644 index 00000000..3e6f8207 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val DateTime @default(dbgenerated("CURRENT_DATE")) @db.Date + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap new file mode 100644 index 00000000..cfa428db --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val String @default(uuid()) @db.Uuid + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap new file mode 100644 index 00000000..dd0e9ba3 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap @@ -0,0 +1,19 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id]) + title String @db.Text + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap new file mode 100644 index 00000000..1a3238af --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + profiles Profile? + + @@map("users") +} + +model Profile { + id Int @id @default(autoincrement()) + user_id Int @unique + user User @relation(fields: [user_id], references: [id]) + + @@map("profiles") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap new file mode 100644 index 00000000..ae4373e7 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap @@ -0,0 +1,12 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Event { + id Int @id + user_id Int + created_at DateTime @db.Timestamptz + + @@index([user_id, created_at], name: "ix_events__user_id_created_at") + @@map("events") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap new file mode 100644 index 00000000..02ab033a --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap @@ -0,0 +1,11 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Article { + id Int @id + created_at DateTime @db.Timestamptz + + @@index([created_at], name: "ix_articles__created_at") + @@map("articles") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap new file mode 100644 index 00000000..ef41b885 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap @@ -0,0 +1,11 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Article { + id Int @id + title String @db.Text + + @@index([title]) + @@map("articles") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap new file mode 100644 index 00000000..bf9c536a --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val Int @default(42) + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap new file mode 100644 index 00000000..2066f96d --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap @@ -0,0 +1,21 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + author_posts Post[] @relation("PostsAuthor") + reviewer_posts Post[] @relation("PostsReviewer") + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + author_id Int + author User @relation("PostsAuthor", fields: [author_id], references: [id]) + reviewer_id Int + reviewer User @relation("PostsReviewer", fields: [reviewer_id], references: [id]) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap new file mode 100644 index 00000000..08c53b13 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: NoAction) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap new file mode 100644 index 00000000..eed0bba9 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val DateTime @default(now()) @db.Timestamptz + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap new file mode 100644 index 00000000..096116c3 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int? + user User? @relation(fields: [user_id], references: [id]) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap new file mode 100644 index 00000000..5f166789 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap @@ -0,0 +1,11 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model UserRole { + user_id Int + role_id Int + + @@id([user_id, role_id]) + @@map("user_roles") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap new file mode 100644 index 00000000..ff950f90 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Log { + message String @db.Text + created_at DateTime @db.Timestamptz + + @@map("logs") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap new file mode 100644 index 00000000..7c35ef01 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id @default(autoincrement()) + name String @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap new file mode 100644 index 00000000..c0edfe5f --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id String @id @db.Uuid + name String @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap new file mode 100644 index 00000000..cd601a85 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Ref { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("refs") +} + +model Post { + id Int @id @default(autoincrement()) + ref_id Int + ref Ref @relation(fields: [ref_id], references: [id]) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap new file mode 100644 index 00000000..c57b252e --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap @@ -0,0 +1,14 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Thing { + id Int @id + default String @db.Text + unique String @db.Text + relation String @db.Text + map String @db.Text + index String @db.Text + + @@map("things") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_table_name.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_table_name.snap new file mode 100644 index 00000000..ba9ef4d1 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_table_name.snap @@ -0,0 +1,45 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: output +--- +--- select --- +model Select { + id Int @id + + @@map("select") +} + +--- order --- +model Order { + id Int @id + + @@map("order") +} + +--- model --- +model Model { + id Int @id + + @@map("model") +} + +--- enum --- +model Enum { + id Int @id + + @@map("enum") +} + +--- type --- +model Type { + id Int @id + + @@map("type") +} + +--- group --- +model Group { + id Int @id + + @@map("group") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap new file mode 100644 index 00000000..d3a191d1 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: Restrict) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap new file mode 100644 index 00000000..26369f35 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap @@ -0,0 +1,13 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Category { + id Int @id @default(autoincrement()) + parent_id Int? + parent Category? @relation("CategoriesParent", fields: [parent_id], references: [id]) + name String @db.Text + parent_categories Category[] @relation("CategoriesParent") + + @@map("categories") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap new file mode 100644 index 00000000..3b39b99d --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: SetDefault) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap new file mode 100644 index 00000000..58654ddf --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model User { + id Int @id @default(autoincrement()) + posts Post[] + + @@map("users") +} + +model Post { + id Int @id @default(autoincrement()) + user_id Int + user User @relation(fields: [user_id], references: [id], onDelete: SetNull) + + @@map("posts") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap new file mode 100644 index 00000000..a66ae3e9 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val String @default("foo") @db.Text + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap new file mode 100644 index 00000000..de6343c6 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap @@ -0,0 +1,12 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Membership { + id Int @id @default(autoincrement()) + user_id Int + org_id Int + + @@unique([user_id, org_id], name: "uq_memberships__user_id_org_id") + @@map("memberships") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap new file mode 100644 index 00000000..fcddf28a --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap @@ -0,0 +1,12 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Membership { + id Int @id @default(autoincrement()) + user_id Int + org_id Int + + @@unique([user_id, org_id]) + @@map("memberships") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap new file mode 100644 index 00000000..ad183dec --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id @default(autoincrement()) + email String @unique(map: "uq_users__email") @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap new file mode 100644 index 00000000..6f5d8888 --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model User { + id Int @id @default(autoincrement()) + email String @unique @db.Text + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap new file mode 100644 index 00000000..d6986ccd --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap @@ -0,0 +1,18 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_schema_all(&schema) +--- +model Ref { + id Int @id @default(autoincrement()) + users User[] + + @@map("refs") +} + +model User { + id Int @id @default(autoincrement()) + ref_id Int + ref Ref @relation(fields: [ref_id], references: [id]) + + @@map("users") +} diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap new file mode 100644 index 00000000..cfa428db --- /dev/null +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap @@ -0,0 +1,10 @@ +--- +source: crates/vespertide-exporter/src/prisma/mod.rs +expression: render_entity(&t) +--- +model Item { + id Int @id + val String @default(uuid()) @db.Uuid + + @@map("items") +} diff --git a/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_snapshots@prisma.snap b/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_snapshots@prisma.snap new file mode 100644 index 00000000..f07a3938 --- /dev/null +++ b/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_snapshots@prisma.snap @@ -0,0 +1,9 @@ +--- +source: crates/vespertide-exporter/src/orm.rs +expression: result.unwrap() +--- +model Test { + id Int @id + + @@map("test") +} diff --git a/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_with_schema_snapshots@prisma.snap b/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_with_schema_snapshots@prisma.snap new file mode 100644 index 00000000..f07a3938 --- /dev/null +++ b/crates/vespertide-exporter/src/snapshots/vespertide_exporter__orm__tests__render_entity_with_schema_snapshots@prisma.snap @@ -0,0 +1,9 @@ +--- +source: crates/vespertide-exporter/src/orm.rs +expression: result.unwrap() +--- +model Test { + id Int @id + + @@map("test") +} From da60cf2e4c44f90cce4168d5779c667eb53e155a Mon Sep 17 00:00:00 2001 From: L33gn21 Date: Sat, 23 May 2026 19:10:17 +0900 Subject: [PATCH 2/4] Fix(exporter) backslash escaping in Prisma string default values --- crates/vespertide-exporter/src/prisma/mod.rs | 2 +- ...rtide_exporter__prisma__tests__default_value_with_quote.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/vespertide-exporter/src/prisma/mod.rs b/crates/vespertide-exporter/src/prisma/mod.rs index 508b8d91..f9c4dafb 100644 --- a/crates/vespertide-exporter/src/prisma/mod.rs +++ b/crates/vespertide-exporter/src/prisma/mod.rs @@ -519,7 +519,7 @@ fn prisma_default_attr(default_sql: String, col_type: &ColumnType) -> String { } } } - return format!("@default(\"{}\")", stripped.replace('"', "\\\"")); + return format!("@default(\"{}\")", stripped.replace('\\', "\\\\").replace('"', "\\\"")); } // Numeric diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap index ae13185f..f9f60cfd 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap @@ -4,7 +4,7 @@ expression: render_entity(&t) --- model Item { id Int @id - name String @default("val with \\" quote") @db.Text + name String @default("val with \\\" quote") @db.Text @@map("items") } From de29610481fa40428e95577e14a96a5088f3fc97 Mon Sep 17 00:00:00 2001 From: L33gn21 Date: Mon, 25 May 2026 19:19:48 +0900 Subject: [PATCH 3/4] chore: remove TESTING.md from repository Co-Authored-By: Claude Sonnet 4.6 --- .../vespertide-exporter/src/prisma/TESTING.md | 323 ------------------ 1 file changed, 323 deletions(-) delete mode 100644 crates/vespertide-exporter/src/prisma/TESTING.md diff --git a/crates/vespertide-exporter/src/prisma/TESTING.md b/crates/vespertide-exporter/src/prisma/TESTING.md deleted file mode 100644 index 4cdd21fe..00000000 --- a/crates/vespertide-exporter/src/prisma/TESTING.md +++ /dev/null @@ -1,323 +0,0 @@ -# Prisma Exporter — Test Specification - -## Rules - -- **FK가 포함된 순간 Layer 2.** `TableConstraint::ForeignKey`가 하나라도 있는 `TableDef`는 `render_entity_with_schema`를 사용하고 Layer 2에 작성한다. -- 모든 출력 검증은 `assert_snapshot!` 사용. `assert!(result.contains(...))` 방식은 사용하지 않는다. -- `rstest`의 `#[case]`는 동일한 구조의 케이스를 파라미터만 바꿀 때 사용한다. -- 스냅샷 파일은 `src/snapshots/` 아래에 자동 생성된다. 처음 실행 시 `cargo insta accept`로 승인. -- 테스트 코드 내 반복되는 `ColumnDef` 생성은 `fn col(name, ty) -> ColumnDef` 헬퍼를 사용한다. - ---- - -## Layer 1 — 단일 엔티티 (FK 없음) - -`render_entity(table)` 하나로 검증 가능한 모든 케이스. - -### 1-1. 컬럼 타입 × nullable - -모든 `SimpleColumnType`과 `ComplexColumnType`을 `nullable=false` / `nullable=true` 조합으로 커버한다. - -`rstest #[case(type, nullable)]` 형태로 하나의 파라미터 테스트로 작성한다. - -| 타입 | Prisma 출력 타입 | native 속성 | -|------|----------------|------------| -| SmallInt | `Int` | `@db.SmallInt` | -| Integer | `Int` | 없음 | -| BigInt | `BigInt` | 없음 | -| Real | `Float` | `@db.Real` | -| DoublePrecision | `Float` | 없음 | -| Text | `String` | `@db.Text` | -| Boolean | `Boolean` | 없음 | -| Date | `DateTime` | `@db.Date` | -| Time | `DateTime` | `@db.Time` | -| Timestamp | `DateTime` | `@db.Timestamp` | -| Timestamptz | `DateTime` | `@db.Timestamptz` | -| Interval | `String` | `@db.Interval` | -| Bytea | `Bytes` | 없음 | -| Uuid | `String` | `@db.Uuid` | -| Json | `Json` | 없음 | -| Inet | `String` | `@db.Inet` | -| Cidr | `String` | `@db.Cidr` | -| Macaddr | `String` | `@db.Macaddr` | -| Xml | `String` | `@db.Xml` | -| Varchar(n) | `String` | `@db.VarChar(n)` | -| Char(n) | `String` | `@db.Char(n)` | -| Numeric(p, s) | `Decimal` | `@db.Decimal(p, s)` | -| Custom("citext") | `Unsupported("citext")` | 없음 | -| Enum (string) | `EnumName` | 없음 | -| Enum (integer) | `EnumName` | 없음 (주석으로 값 표기) | - -nullable=true이면 타입 뒤에 `?`가 붙는다. `Unsupported(...)` 타입도 마찬가지. - -**현재 상태:** 미구현. `test_all_simple_column_types` 없음. - ---- - -### 1-2. PK 변형 - -| 케이스 | 설명 | -|--------|------| -| 단일 PK, auto_increment=true | `@id @default(autoincrement())` | -| 단일 PK, auto_increment=false | `@id` | -| Composite PK | 컬럼에 `@id` 없음, 테이블 하단에 `@@id([col1, col2])` | -| PK 없음 | `@@id` 없음, 컬럼에 `@id` 없음 | - -**현재 상태:** `test_basic_table`이 단일 PK auto_increment=true만 커버. 나머지 미구현. - ---- - -### 1-3. Unique 제약 - -| 케이스 | 설명 | -|--------|------| -| 단일 컬럼, named | 컬럼에 `@unique(map: "uq_name")` | -| 단일 컬럼, unnamed | 컬럼에 `@unique` | -| Composite, named | `@@unique([col1, col2], name: "uq_name")` | -| Composite, unnamed | `@@unique([col1, col2])` | - -단일 컬럼 unique는 컬럼 인라인에, composite는 테이블 하단에 생성된다. - -**현재 상태:** `test_basic_table`이 단일 unnamed unique만 커버. composite 미구현. - ---- - -### 1-4. Index - -| 케이스 | 설명 | -|--------|------| -| 단일 컬럼, named | `@@index([col], name: "ix_name")` | -| 단일 컬럼, unnamed | `@@index([col])` | -| Composite | `@@index([col1, col2])` | - -**현재 상태:** `test_table_with_indexes`가 named + unnamed 단일 컬럼 커버. composite 미구현. - ---- - -### 1-5. Default 값 - -Default 포맷은 입력 문자열 패턴에 따라 다르게 변환된다. - -| 입력 | 출력 | 적용 타입 | -|------|------|----------| -| `"true"` / `"false"` | `@default(true)` / `@default(false)` | Boolean | -| `"now()"` / `"CURRENT_TIMESTAMP"` | `@default(now())` | Timestamp, Timestamptz | -| `"gen_random_uuid()"` / `"uuid_generate_v4()"` | `@default(uuid())` | Uuid | -| 임의 함수 `"func()"` | `@default(dbgenerated("func()"))` | 모든 타입 | -| 문자열 리터럴 `"'foo'"` | `@default("foo")` | Text, Varchar 등 | -| Enum 값 문자열 `"'pending'"` | `@default(EnumName.PENDING)` | Enum(string) | -| 숫자 `"42"` | `@default(42)` | Integer, BigInt 등 | -| fallback: 함수도 아니고 따옴표도 없고 숫자도 아닌 값 `"CURRENT_DATE"` | `@default(dbgenerated("CURRENT_DATE"))` | Date 등 | - -`prisma_default_attr` 분기 순서: bool → now/CURRENT_TIMESTAMP → uuid 함수 → 임의 함수(`(` 포함) → 따옴표 문자열 → 숫자 → **fallback**. fallback은 앞의 모든 조건을 통과한 값으로, `CURRENT_DATE`나 `SYSDATE`처럼 괄호 없는 DB 키워드가 해당된다. - -**현재 상태:** `test_table_with_default_values`가 boolean, integer, now()만 커버. 나머지 미구현. - ---- - -### 1-6. Enum - -| 케이스 | 설명 | -|--------|------| -| String enum, 값이 screaming_snake와 동일 | `@map` 없음 | -| String enum, 값이 다름 (`"pending"` → `PENDING`) | `PENDING @map("pending")` | -| Integer enum | 각 variant에 `// = {value}` 주석 | -| nullable enum | 타입에 `?` | -| Enum에 default 값 | `@default(EnumName.VARIANT)` | -| 동일 enum을 여러 컬럼에서 사용 | enum 블록 중복 없이 한 번만 출력 | - -**현재 상태:** `test_table_with_enum` (string), `test_table_with_integer_enum` 존재. nullable, default, dedup 미구현. - ---- - -### 1-7. Description / Comment - -| 케이스 | 설명 | -|--------|------| -| `table.description = Some("...")` | 모델 위에 `/// description` | -| `table.description = None` | `///` 없음 | -| `column.comment = Some("...")` | 컬럼 위에 `/// comment` | -| 멀티라인 description | 줄마다 `///` | - -**현재 상태:** `test_basic_table`이 컬럼 comment + description 있는 케이스 커버. description=None, 멀티라인 미구현. - ---- - -### 1-8. @@map - -모든 모델은 Prisma 모델명(PascalCase)과 테이블명(snake_case)이 다를 경우 `@@map("table_name")`을 항상 출력한다. `test_basic_table`에서 확인 가능하나 명시적 테스트 없음. - ---- - -## Layer 2 — 관계 (FK 포함) - -`render_entity_with_schema(table, schema)`를 사용한다. 모든 케이스에서 최소 두 개의 `TableDef`가 필요하다. - -### 2-1. has-many (기본 역방향) - -``` -posts.user_id → users.id -``` - -- `users` 모델에 `posts Post[]` 역방향 필드 생성 -- 관계 이름 없음 (`@relation` 속성 없음) - -**현재 상태:** `test_with_schema_back_relations` 존재. 단 `assert!(contains)` 방식 → `assert_snapshot!`으로 교체 필요. - ---- - -### 2-2. has-one (FK에 unique 제약) - -``` -profiles.user_id → users.id (profiles.user_id에 Unique 제약 있음) -``` - -- `users` 모델에 `profiles Profile?` (배열 아닌 단수 + nullable) - -**현재 상태:** 미구현. - ---- - -### 2-3. nullable FK - -``` -posts.user_id (nullable=true) → users.id -``` - -- 순방향: `user User?` (타입에 `?`) -- 역방향: 영향 없음 (여전히 `posts Post[]`) - -**현재 상태:** 미구현. - ---- - -### 2-4. 동일 테이블 다중 FK → relation 이름 자동 부여 - -``` -posts.author_id → users.id -posts.reviewer_id → users.id -``` - -- 두 FK 모두 같은 테이블을 참조 → 충돌 방지를 위해 `@relation("PostsAuthor")`, `@relation("PostsReviewer")` 자동 생성 -- `users` 모델에도 각각에 대응하는 역방향 필드에 동일 relation 이름 - -**현재 상태:** 미구현. - ---- - -### 2-5. 자기참조 FK - -``` -categories.parent_id → categories.id -``` - -- 자기참조는 `needs_name = true` → relation 이름 자동 부여 -- 순방향과 역방향이 같은 모델 안에 공존 - -**현재 상태:** 미구현. - ---- - -### 2-6. on_delete / on_update 액션 - -| `ReferenceAction` | Prisma 출력 | -|-------------------|------------| -| Cascade | `onDelete: Cascade` | -| Restrict | `onDelete: Restrict` | -| SetNull | `onDelete: SetNull` | -| SetDefault | `onDelete: SetDefault` | -| NoAction | `onDelete: NoAction` | - -`on_delete`와 `on_update` 각각 독립적으로 생성된다. None이면 해당 속성 생략. - -**현재 상태:** 미구현. - ---- - -### 2-7. Composite FK — 무시 - -``` -TableConstraint::ForeignKey { columns: ["org_id", "user_id"], ... } -``` - -컬럼이 2개 이상인 FK는 현재 처리하지 않고 무시된다 (`columns.len() == 1` 조건). 이 동작을 명시적으로 검증한다. (순방향 관계 필드 미생성, 역방향도 미생성) - -**현재 상태:** 미구현. - ---- - -### 2-8. 역방향 필드명 singularize - -테이블명 → 모델명 변환 규칙: - -| 테이블명 | 모델명 (역방향 타입) | -|---------|-------------------| -| `posts` | `Post` | -| `categories` | `Category` | -| `boxes` | `Box` | -| `users` | `User` | - -`singularize` 함수의 변환 결과를 통합 테스트로 검증한다. - -**현재 상태:** `test_pluralize` (유틸 단위 테스트만 존재). 역방향 필드에서 실제로 singularize가 적용된 통합 케이스 없음. - ---- - -## Layer 3 — 예외 처리 - -### 3-1. 식별자 충돌 (언어/도구 예약어) - -Prisma 스키마 키워드가 테이블명 또는 컬럼명으로 사용된 경우, 출력이 파싱 오류를 일으키지 않아야 한다. - -| 예약어 종류 | 예시 | -|-----------|------| -| Prisma 블록 키워드 | `model`, `enum`, `type`, `generator`, `datasource`, `view` | -| SQL 예약어 | `order`, `select`, `user`, `group`, `check`, `index` | -| Prisma 필드 속성 | `id`, `unique`, `default`, `map`, `relation` | - -테이블명에 예약어가 오면 `@@map`으로, 컬럼명에 예약어가 오면 필드명으로 그대로 출력된다. 현재 이스케이프 처리가 없으므로 출력 결과를 스냅샷으로 고정한 뒤, 향후 이스케이프 구현 시 테스트가 실패하여 변경을 감지한다. - -**현재 상태:** 미구현. - ---- - -### 3-2. 특수문자로 인한 코드 구조 파괴 - -`description` 또는 `comment`에 코드 구조를 깨뜨릴 수 있는 문자가 포함된 경우. - -| 입력 | 기대 동작 | -|------|---------| -| `description = "line1\nline2"` | 줄마다 `///` 분리하여 출력 | -| `comment = "has \"quotes\""` | `"` 이스케이프 처리 또는 그대로 `///` 주석으로 출력 | -| `comment = "has } brace"` | 중괄호가 모델 블록을 닫지 않아야 함 | -| `default = "'val with \" quote'"` | `@default(...)` 안에서 `"` 이스케이프 처리 | - -**현재 상태:** 미구현. - ---- - -## 현재 구현 상태 요약 - -| 테스트 | Layer | 상태 | 비고 | -|--------|-------|------|------| -| `test_basic_table` | 1 | ✓ | PK(auto_inc), unique, comment, description | -| `test_table_with_enum` | 1 | ✓ | String enum | -| `test_table_with_integer_enum` | 1 | ✓ | Integer enum | -| `test_table_with_indexes` | 1 | ✓ | named + unnamed 단일 index | -| `test_table_with_default_values` | 1 | ✓ | boolean, integer, now() | -| `test_table_with_foreign_key` | **2** | ⚠ | render_entity 사용 중 → render_entity_with_schema로 이전 필요 | -| `test_with_schema_back_relations` | 2 | ⚠ | assert_snapshot! 으로 교체 필요 | -| 전체 컬럼 타입 × nullable | 1 | ✗ | | -| Composite PK | 1 | ✗ | | -| Composite unique/index | 1 | ✗ | | -| Enum nullable / default / dedup | 1 | ✗ | | -| Description=None, multiline | 1 | ✗ | | -| Default (string, uuid, dbgenerated) | 1 | ✗ | | -| has-one (unique FK) | 2 | ✗ | | -| nullable FK | 2 | ✗ | | -| 다중 FK to same table | 2 | ✗ | | -| 자기참조 FK | 2 | ✗ | | -| on_delete / on_update | 2 | ✗ | | -| Composite FK 무시 | 2 | ✗ | | -| 예약어 | 3 | ✗ | | -| 특수문자 | 3 | ✗ | | \ No newline at end of file From a41057de860a75d4806fc8601d58c60f867ea397 Mon Sep 17 00:00:00 2001 From: L33gn21 Date: Mon, 25 May 2026 19:38:33 +0900 Subject: [PATCH 4/4] refactor(exporter): use plural PascalCase for Prisma model names --- crates/vespertide-exporter/src/prisma/mod.rs | 19 +--- ...rter__prisma__tests__always_emits_map.snap | 2 +- ...exporter__prisma__tests__arbitrary_fn.snap | 2 +- ...e_exporter__prisma__tests__bool_false.snap | 2 +- ...de_exporter__prisma__tests__bool_true.snap | 2 +- ...pertide_exporter__prisma__tests__both.snap | 8 +- ...ertide_exporter__prisma__tests__boxes.snap | 8 +- ...tide_exporter__prisma__tests__cascade.snap | 8 +- ...e_exporter__prisma__tests__categories.snap | 8 +- ...isma__tests__column_comment_multiline.snap | 2 +- ...prisma__tests__column_comment_present.snap | 2 +- ...er__prisma__tests__column_type_matrix.snap | 100 +++++++++--------- ...__prisma__tests__composite_fk_ignored.snap | 4 +- ...ter__prisma__tests__current_timestamp.snap | 2 +- ...isma__tests__default_value_with_quote.snap | 2 +- ..._prisma__tests__description_multiline.snap | 2 +- ...rter__prisma__tests__description_none.snap | 2 +- ...r__prisma__tests__description_present.snap | 2 +- ...sma__tests__description_special_chars.snap | 6 +- ...a__tests__enum_dedup_multiple_columns.snap | 2 +- ...er__prisma__tests__enum_default_value.snap | 2 +- ...exporter__prisma__tests__enum_integer.snap | 2 +- ...xporter__prisma__tests__enum_nullable.snap | 2 +- ...er__prisma__tests__enum_string_mapped.snap | 2 +- ...ts__enum_string_screaming_snake_match.snap | 2 +- ...rter__prisma__tests__fallback_keyword.snap | 2 +- ...orter__prisma__tests__gen_random_uuid.snap | 2 +- ...porter__prisma__tests__has_many_basic.snap | 8 +- ...ter__prisma__tests__has_one_unique_fk.snap | 8 +- ...orter__prisma__tests__index_composite.snap | 2 +- ...er__prisma__tests__index_single_named.snap | 2 +- ...__prisma__tests__index_single_unnamed.snap | 2 +- ...orter__prisma__tests__integer_literal.snap | 2 +- ...prisma__tests__multiple_fk_same_table.snap | 12 +-- ...de_exporter__prisma__tests__no_action.snap | 8 +- ...spertide_exporter__prisma__tests__now.snap | 2 +- ..._exporter__prisma__tests__nullable_fk.snap | 8 +- ...exporter__prisma__tests__pk_composite.snap | 2 +- ...tide_exporter__prisma__tests__pk_none.snap | 2 +- ...risma__tests__pk_single_autoincrement.snap | 2 +- ...ma__tests__pk_single_no_autoincrement.snap | 2 +- ...ertide_exporter__prisma__tests__posts.snap | 8 +- ...ests__reserved_identifier_column_name.snap | 2 +- ...ide_exporter__prisma__tests__restrict.snap | 8 +- ...ter__prisma__tests__self_reference_fk.snap | 6 +- ..._exporter__prisma__tests__set_default.snap | 8 +- ...ide_exporter__prisma__tests__set_null.snap | 8 +- ...porter__prisma__tests__string_literal.snap | 2 +- ...prisma__tests__unique_composite_named.snap | 2 +- ...isma__tests__unique_composite_unnamed.snap | 2 +- ...r__prisma__tests__unique_single_named.snap | 2 +- ..._prisma__tests__unique_single_unnamed.snap | 2 +- ...ertide_exporter__prisma__tests__users.snap | 8 +- ...rter__prisma__tests__uuid_generate_v4.snap | 2 +- 54 files changed, 155 insertions(+), 166 deletions(-) diff --git a/crates/vespertide-exporter/src/prisma/mod.rs b/crates/vespertide-exporter/src/prisma/mod.rs index f9c4dafb..937205d5 100644 --- a/crates/vespertide-exporter/src/prisma/mod.rs +++ b/crates/vespertide-exporter/src/prisma/mod.rs @@ -167,7 +167,7 @@ fn render_model(table: &TableDef, schema: &[TableDef]) -> String { } } - let model_name = to_pascal_case(&singularize(&table.name)); + let model_name = to_pascal_case(&table.name); lines.push(format!("model {} {{", model_name)); let pk_info = extract_pk_info(&table.constraints); @@ -259,7 +259,7 @@ fn render_model(table: &TableDef, schema: &[TableDef]) -> String { // Emit inline relation field for FK columns if let Some(fk) = fk_by_col.get(col_name) { let rel_field_name = infer_relation_field_name(col_name); - let rel_model = to_pascal_case(&singularize(fk.ref_table)); + let rel_model = to_pascal_case(fk.ref_table); let rel_type = if col.nullable { format!("{}?", rel_model) } else { @@ -362,7 +362,7 @@ struct BackRelation { } fn back_rel_field(br: &BackRelation) -> (String, String) { - let source_pascal = to_pascal_case(&singularize(&br.source_table)); + let source_pascal = to_pascal_case(&br.source_table); let rel_type = if br.is_one_to_one { format!("{}?", source_pascal) } else { @@ -576,17 +576,6 @@ fn to_screaming_snake(s: &str) -> String { result.trim_end_matches('_').to_string() } -fn singularize(s: &str) -> String { - if s.ends_with("ies") { - format!("{}y", &s[..s.len() - 3]) - } else if s.ends_with("ses") || s.ends_with("xes") || s.ends_with("ches") || s.ends_with("shes") { - s[..s.len() - 2].to_string() - } else if s.ends_with('s') && !s.ends_with("ss") { - s[..s.len() - 1].to_string() - } else { - s.to_string() - } -} #[cfg(test)] mod tests { @@ -1264,7 +1253,7 @@ mod tests { #[case::categories("categories")] #[case::boxes("boxes")] #[case::users("users")] - fn test_singularize_in_back_relations(#[case] child_table: &str) { + fn test_back_relations(#[case] child_table: &str) { let parent = table( "refs", None, diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap index be42ced7..c51e2087 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__always_emits_map.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model UserProfile { +model UserProfiles { id Int @id @@map("user_profiles") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap index 1af42be1..60a0c859 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__arbitrary_fn.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val Int @default(dbgenerated("nextval('my_seq')")) diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap index 952ec0ff..57e484fa 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_false.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val Boolean @default(false) diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap index 5a1b9766..9190d341 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__bool_true.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val Boolean @default(true) diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap index b9bd13ce..d98f14d1 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__both.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Restrict) + user Users @relation(fields: [user_id], references: [id], onDelete: Cascade, onUpdate: Restrict) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap index ae4f39c3..3352dc8e 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__boxes.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Ref { +model Refs { id Int @id @default(autoincrement()) - boxes Box[] + boxes Boxes[] @@map("refs") } -model Box { +model Boxes { id Int @id @default(autoincrement()) ref_id Int - ref Ref @relation(fields: [ref_id], references: [id]) + ref Refs @relation(fields: [ref_id], references: [id]) @@map("boxes") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap index d5cfdf6e..85f0c5b1 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__cascade.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: Cascade) + user Users @relation(fields: [user_id], references: [id], onDelete: Cascade) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap index 97760b25..4550209e 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__categories.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Ref { +model Refs { id Int @id @default(autoincrement()) - categories Category[] + categories Categories[] @@map("refs") } -model Category { +model Categories { id Int @id @default(autoincrement()) ref_id Int - ref Ref @relation(fields: [ref_id], references: [id]) + ref Refs @relation(fields: [ref_id], references: [id]) @@map("categories") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap index 0a8f15d1..ae110329 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_multiline.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id /// line1 line2 email String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap index 96955fde..21460d9a 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_comment_present.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id /// User's email address email String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap index 53bd3029..1d34ac1f 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__column_type_matrix.snap @@ -3,7 +3,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: output --- --- small_int_not_null --- -model Item { +model Items { id Int @id val Int @db.SmallInt @@ -11,7 +11,7 @@ model Item { } --- small_int_null --- -model Item { +model Items { id Int @id val Int? @db.SmallInt @@ -19,7 +19,7 @@ model Item { } --- integer_not_null --- -model Item { +model Items { id Int @id val Int @@ -27,7 +27,7 @@ model Item { } --- integer_null --- -model Item { +model Items { id Int @id val Int? @@ -35,7 +35,7 @@ model Item { } --- big_int_not_null --- -model Item { +model Items { id Int @id val BigInt @@ -43,7 +43,7 @@ model Item { } --- big_int_null --- -model Item { +model Items { id Int @id val BigInt? @@ -51,7 +51,7 @@ model Item { } --- real_not_null --- -model Item { +model Items { id Int @id val Float @db.Real @@ -59,7 +59,7 @@ model Item { } --- real_null --- -model Item { +model Items { id Int @id val Float? @db.Real @@ -67,7 +67,7 @@ model Item { } --- double_precision_not_null --- -model Item { +model Items { id Int @id val Float @@ -75,7 +75,7 @@ model Item { } --- double_precision_null --- -model Item { +model Items { id Int @id val Float? @@ -83,7 +83,7 @@ model Item { } --- text_not_null --- -model Item { +model Items { id Int @id val String @db.Text @@ -91,7 +91,7 @@ model Item { } --- text_null --- -model Item { +model Items { id Int @id val String? @db.Text @@ -99,7 +99,7 @@ model Item { } --- boolean_not_null --- -model Item { +model Items { id Int @id val Boolean @@ -107,7 +107,7 @@ model Item { } --- boolean_null --- -model Item { +model Items { id Int @id val Boolean? @@ -115,7 +115,7 @@ model Item { } --- date_not_null --- -model Item { +model Items { id Int @id val DateTime @db.Date @@ -123,7 +123,7 @@ model Item { } --- date_null --- -model Item { +model Items { id Int @id val DateTime? @db.Date @@ -131,7 +131,7 @@ model Item { } --- time_not_null --- -model Item { +model Items { id Int @id val DateTime @db.Time @@ -139,7 +139,7 @@ model Item { } --- time_null --- -model Item { +model Items { id Int @id val DateTime? @db.Time @@ -147,7 +147,7 @@ model Item { } --- timestamp_not_null --- -model Item { +model Items { id Int @id val DateTime @db.Timestamp @@ -155,7 +155,7 @@ model Item { } --- timestamp_null --- -model Item { +model Items { id Int @id val DateTime? @db.Timestamp @@ -163,7 +163,7 @@ model Item { } --- timestamptz_not_null --- -model Item { +model Items { id Int @id val DateTime @db.Timestamptz @@ -171,7 +171,7 @@ model Item { } --- timestamptz_null --- -model Item { +model Items { id Int @id val DateTime? @db.Timestamptz @@ -179,7 +179,7 @@ model Item { } --- interval_not_null --- -model Item { +model Items { id Int @id val String @db.Interval @@ -187,7 +187,7 @@ model Item { } --- interval_null --- -model Item { +model Items { id Int @id val String? @db.Interval @@ -195,7 +195,7 @@ model Item { } --- bytea_not_null --- -model Item { +model Items { id Int @id val Bytes @@ -203,7 +203,7 @@ model Item { } --- bytea_null --- -model Item { +model Items { id Int @id val Bytes? @@ -211,7 +211,7 @@ model Item { } --- uuid_not_null --- -model Item { +model Items { id Int @id val String @db.Uuid @@ -219,7 +219,7 @@ model Item { } --- uuid_null --- -model Item { +model Items { id Int @id val String? @db.Uuid @@ -227,7 +227,7 @@ model Item { } --- json_not_null --- -model Item { +model Items { id Int @id val Json @@ -235,7 +235,7 @@ model Item { } --- json_null --- -model Item { +model Items { id Int @id val Json? @@ -243,7 +243,7 @@ model Item { } --- inet_not_null --- -model Item { +model Items { id Int @id val String @db.Inet @@ -251,7 +251,7 @@ model Item { } --- inet_null --- -model Item { +model Items { id Int @id val String? @db.Inet @@ -259,7 +259,7 @@ model Item { } --- cidr_not_null --- -model Item { +model Items { id Int @id val String @db.Cidr @@ -267,7 +267,7 @@ model Item { } --- cidr_null --- -model Item { +model Items { id Int @id val String? @db.Cidr @@ -275,7 +275,7 @@ model Item { } --- macaddr_not_null --- -model Item { +model Items { id Int @id val String @db.Macaddr @@ -283,7 +283,7 @@ model Item { } --- macaddr_null --- -model Item { +model Items { id Int @id val String? @db.Macaddr @@ -291,7 +291,7 @@ model Item { } --- xml_not_null --- -model Item { +model Items { id Int @id val String @db.Xml @@ -299,7 +299,7 @@ model Item { } --- xml_null --- -model Item { +model Items { id Int @id val String? @db.Xml @@ -307,7 +307,7 @@ model Item { } --- varchar_255_not_null --- -model Item { +model Items { id Int @id val String @db.VarChar(255) @@ -315,7 +315,7 @@ model Item { } --- varchar_255_null --- -model Item { +model Items { id Int @id val String? @db.VarChar(255) @@ -323,7 +323,7 @@ model Item { } --- char_10_not_null --- -model Item { +model Items { id Int @id val String @db.Char(10) @@ -331,7 +331,7 @@ model Item { } --- char_10_null --- -model Item { +model Items { id Int @id val String? @db.Char(10) @@ -339,7 +339,7 @@ model Item { } --- numeric_10_2_not_null --- -model Item { +model Items { id Int @id val Decimal @db.Decimal(10, 2) @@ -347,7 +347,7 @@ model Item { } --- numeric_10_2_null --- -model Item { +model Items { id Int @id val Decimal? @db.Decimal(10, 2) @@ -355,7 +355,7 @@ model Item { } --- custom_citext_not_null --- -model Item { +model Items { id Int @id val Unsupported("citext") @@ -363,7 +363,7 @@ model Item { } --- custom_citext_null --- -model Item { +model Items { id Int @id val Unsupported("citext")? @@ -376,7 +376,7 @@ enum MyStatus { INACTIVE @map("inactive") } -model Item { +model Items { id Int @id val MyStatus @@ -389,7 +389,7 @@ enum MyStatus { INACTIVE @map("inactive") } -model Item { +model Items { id Int @id val MyStatus? @@ -402,7 +402,7 @@ enum MyLevel { HIGH // = 1 } -model Item { +model Items { id Int @id val MyLevel @@ -415,7 +415,7 @@ enum MyLevel { HIGH // = 1 } -model Item { +model Items { id Int @id val MyLevel? diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap index c4997a57..d7591adf 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__composite_fk_ignored.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Org { +model Orgs { id Int user_id Int @@ -10,7 +10,7 @@ model Org { @@map("orgs") } -model Membership { +model Memberships { id Int @id org_id Int member_user_id Int diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap index eed0bba9..5ab2f39e 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__current_timestamp.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val DateTime @default(now()) @db.Timestamptz diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap index f9f60cfd..2f539313 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__default_value_with_quote.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id name String @default("val with \\\" quote") @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap index fab99063..57d2820d 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_multiline.snap @@ -5,7 +5,7 @@ expression: render_entity(&t) /// First line /// Second line /// Third line -model User { +model Users { id Int @id @@map("users") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap index 08c9f558..fd132bb8 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_none.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id @@map("users") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap index 5acae84b..f660078a 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_present.snap @@ -3,7 +3,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- /// User accounts table -model User { +model Users { id Int @id @@map("users") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap index 90fe2ceb..e2b1feb5 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__description_special_chars.snap @@ -5,7 +5,7 @@ expression: output --- newline --- /// line1 /// line2 -model Thing { +model Things { id Int @id @@map("things") @@ -13,7 +13,7 @@ model Thing { --- double_quote --- /// has "quotes" -model Thing { +model Things { id Int @id @@map("things") @@ -21,7 +21,7 @@ model Thing { --- closing_brace --- /// has } brace -model Thing { +model Things { id Int @id @@map("things") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap index 85ddebb8..5b93f515 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_dedup_multiple_columns.snap @@ -7,7 +7,7 @@ enum RoleType { MEMBER @map("member") } -model OrgMember { +model OrgMembers { id Int @id role RoleType backup_role RoleType diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap index 0f0b8e38..f78edb6e 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_default_value.snap @@ -7,7 +7,7 @@ enum OrderStatus { SHIPPED @map("shipped") } -model Order { +model Orders { id Int @id status OrderStatus @default(PENDING) diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap index 06f4fd59..cf406b54 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_integer.snap @@ -8,7 +8,7 @@ enum PriorityLevel { HIGH // = 2 } -model Task { +model Tasks { id Int @id priority PriorityLevel diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap index aae1b971..7c03f33d 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_nullable.snap @@ -7,7 +7,7 @@ enum OrderStatus { SHIPPED @map("shipped") } -model Order { +model Orders { id Int @id status OrderStatus? diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap index f197ffc5..cbc8a8ce 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_mapped.snap @@ -8,7 +8,7 @@ enum OrderStatus { DELIVERED @map("delivered") } -model Order { +model Orders { id Int @id status OrderStatus diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap index 47ac5219..6f373ae2 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__enum_string_screaming_snake_match.snap @@ -7,7 +7,7 @@ enum Status { INACTIVE } -model Account { +model Accounts { id Int @id status Status diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap index 3e6f8207..90385374 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__fallback_keyword.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val DateTime @default(dbgenerated("CURRENT_DATE")) @db.Date diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap index cfa428db..66b5ef94 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__gen_random_uuid.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val String @default(uuid()) @db.Uuid diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap index dd0e9ba3..e4e177db 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_many_basic.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id]) + user Users @relation(fields: [user_id], references: [id]) title String @db.Text @@map("posts") diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap index 1a3238af..fab6fa07 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__has_one_unique_fk.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - profiles Profile? + profiles Profiles? @@map("users") } -model Profile { +model Profiles { id Int @id @default(autoincrement()) user_id Int @unique - user User @relation(fields: [user_id], references: [id]) + user Users @relation(fields: [user_id], references: [id]) @@map("profiles") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap index ae4373e7..5a344de9 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_composite.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Event { +model Events { id Int @id user_id Int created_at DateTime @db.Timestamptz diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap index 02ab033a..1339c8cc 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_named.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Article { +model Articles { id Int @id created_at DateTime @db.Timestamptz diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap index ef41b885..cb57ebb9 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__index_single_unnamed.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Article { +model Articles { id Int @id title String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap index bf9c536a..fae18a6b 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__integer_literal.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val Int @default(42) diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap index 2066f96d..b24a1fa8 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__multiple_fk_same_table.snap @@ -2,20 +2,20 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - author_posts Post[] @relation("PostsAuthor") - reviewer_posts Post[] @relation("PostsReviewer") + author_posts Posts[] @relation("PostsAuthor") + reviewer_posts Posts[] @relation("PostsReviewer") @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) author_id Int - author User @relation("PostsAuthor", fields: [author_id], references: [id]) + author Users @relation("PostsAuthor", fields: [author_id], references: [id]) reviewer_id Int - reviewer User @relation("PostsReviewer", fields: [reviewer_id], references: [id]) + reviewer Users @relation("PostsReviewer", fields: [reviewer_id], references: [id]) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap index 08c53b13..1d5fa761 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__no_action.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: NoAction) + user Users @relation(fields: [user_id], references: [id], onDelete: NoAction) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap index eed0bba9..5ab2f39e 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__now.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val DateTime @default(now()) @db.Timestamptz diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap index 096116c3..f6cf236f 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__nullable_fk.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int? - user User? @relation(fields: [user_id], references: [id]) + user Users? @relation(fields: [user_id], references: [id]) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap index 5f166789..96b48708 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_composite.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model UserRole { +model UserRoles { user_id Int role_id Int diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap index ff950f90..dcf691c2 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_none.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Log { +model Logs { message String @db.Text created_at DateTime @db.Timestamptz diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap index 7c35ef01..6dbc2f1f 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_autoincrement.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id @default(autoincrement()) name String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap index c0edfe5f..4716f576 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__pk_single_no_autoincrement.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id String @id @db.Uuid name String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap index cd601a85..bd4b4542 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__posts.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Ref { +model Refs { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("refs") } -model Post { +model Posts { id Int @id @default(autoincrement()) ref_id Int - ref Ref @relation(fields: [ref_id], references: [id]) + ref Refs @relation(fields: [ref_id], references: [id]) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap index c57b252e..02fa5afe 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__reserved_identifier_column_name.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Thing { +model Things { id Int @id default String @db.Text unique String @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap index d3a191d1..5971af88 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__restrict.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: Restrict) + user Users @relation(fields: [user_id], references: [id], onDelete: Restrict) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap index 26369f35..03df5e10 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__self_reference_fk.snap @@ -2,12 +2,12 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Category { +model Categories { id Int @id @default(autoincrement()) parent_id Int? - parent Category? @relation("CategoriesParent", fields: [parent_id], references: [id]) + parent Categories? @relation("CategoriesParent", fields: [parent_id], references: [id]) name String @db.Text - parent_categories Category[] @relation("CategoriesParent") + parent_categories Categories[] @relation("CategoriesParent") @@map("categories") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap index 3b39b99d..0a1d72ec 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_default.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: SetDefault) + user Users @relation(fields: [user_id], references: [id], onDelete: SetDefault) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap index 58654ddf..614d7733 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__set_null.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model User { +model Users { id Int @id @default(autoincrement()) - posts Post[] + posts Posts[] @@map("users") } -model Post { +model Posts { id Int @id @default(autoincrement()) user_id Int - user User @relation(fields: [user_id], references: [id], onDelete: SetNull) + user Users @relation(fields: [user_id], references: [id], onDelete: SetNull) @@map("posts") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap index a66ae3e9..d08234d0 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__string_literal.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val String @default("foo") @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap index de6343c6..6f0796c0 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_named.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Membership { +model Memberships { id Int @id @default(autoincrement()) user_id Int org_id Int diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap index fcddf28a..aa0b8ab5 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_composite_unnamed.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Membership { +model Memberships { id Int @id @default(autoincrement()) user_id Int org_id Int diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap index ad183dec..bb693ec6 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_named.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id @default(autoincrement()) email String @unique(map: "uq_users__email") @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap index 6f5d8888..2c160565 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__unique_single_unnamed.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model User { +model Users { id Int @id @default(autoincrement()) email String @unique @db.Text diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap index d6986ccd..00a71683 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__users.snap @@ -2,17 +2,17 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_schema_all(&schema) --- -model Ref { +model Refs { id Int @id @default(autoincrement()) - users User[] + users Users[] @@map("refs") } -model User { +model Users { id Int @id @default(autoincrement()) ref_id Int - ref Ref @relation(fields: [ref_id], references: [id]) + ref Refs @relation(fields: [ref_id], references: [id]) @@map("users") } diff --git a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap index cfa428db..66b5ef94 100644 --- a/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap +++ b/crates/vespertide-exporter/src/prisma/snapshots/vespertide_exporter__prisma__tests__uuid_generate_v4.snap @@ -2,7 +2,7 @@ source: crates/vespertide-exporter/src/prisma/mod.rs expression: render_entity(&t) --- -model Item { +model Items { id Int @id val String @default(uuid()) @db.Uuid