From b9126862accc2809bf3ff9ca8fea51ef60ebab15 Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Sun, 14 Dec 2025 23:08:13 +0900 Subject: [PATCH] Add uuid type --- .../changepack_log_8nNa9C1XPsaEK-8prlu5N.json | 1 + CLAUDE.md | 27 +++++++++++- Cargo.lock | 16 +++---- README.md | 42 +++++++++++++------ SKILL.md | 30 +++++++++---- crates/vespertide-query/src/sql.rs | 1 + 6 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 .changepacks/changepack_log_8nNa9C1XPsaEK-8prlu5N.json diff --git a/.changepacks/changepack_log_8nNa9C1XPsaEK-8prlu5N.json b/.changepacks/changepack_log_8nNa9C1XPsaEK-8prlu5N.json new file mode 100644 index 0000000..fd20127 --- /dev/null +++ b/.changepacks/changepack_log_8nNa9C1XPsaEK-8prlu5N.json @@ -0,0 +1 @@ +{"changes":{"crates/vespertide/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch"},"note":"Add UUID type","date":"2025-12-14T14:08:05.559501100Z"} \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 735c763..bd7a2f2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,7 +50,9 @@ cargo run -p vespertide-cli -- log ### Crate Responsibilities -- **vespertide-core**: Data structures (`TableDef`, `ColumnDef`, `MigrationAction`, `MigrationPlan`, constraints, indexes) +- **vespertide-core**: Data structures (`TableDef`, `ColumnDef`, `ColumnType`, `MigrationAction`, `MigrationPlan`, constraints, indexes) + - `ColumnType` enum with `Simple(SimpleColumnType)` and `Complex(ComplexColumnType)` variants + - `ColumnType::to_sql()` and `ColumnType::to_rust_type()` methods for type conversion - **vespertide-planner**: - `schema_from_plans()`: Replays applied migrations to reconstruct baseline schema - `diff_schemas()`: Compares two schemas and generates migration actions @@ -58,9 +60,11 @@ cargo run -p vespertide-cli -- log - `apply_action()`: Applies a single migration action to a schema (used during replay) - `validate_*()`: Validates schemas and migration plans - **vespertide-query**: Converts `MigrationAction` → PostgreSQL SQL with bind parameters + - Uses `ColumnType::to_sql()` method for SQL type conversion - **vespertide-config**: Manages `vespertide.json` (models/migrations directories, naming case preferences) - **vespertide-cli**: Command-line interface implementation - **vespertide-exporter**: Exports schemas to other formats (e.g., SeaORM entities) + - Uses `ColumnType::to_rust_type(nullable)` method for Rust type conversion - **vespertide-schema-gen**: Generates JSON Schema files for validation - **vespertide-macro**: Placeholder for future runtime migration executor @@ -80,7 +84,7 @@ When creating `ColumnDef` instances in tests or code, you must initialize ALL fi ```rust ColumnDef { name: "id".into(), - r#type: ColumnType::Integer, + r#type: ColumnType::Simple(SimpleColumnType::Integer), nullable: false, default: None, comment: None, @@ -93,6 +97,25 @@ ColumnDef { These inline fields (added recently) allow constraints to be defined directly on columns in addition to table-level `TableConstraint` definitions. +### ColumnType Structure +`ColumnType` is an enum with two variants: +- `Simple(SimpleColumnType)`: Built-in types like `Integer`, `Text`, `Boolean`, etc. +- `Complex(ComplexColumnType)`: Types with parameters like `Varchar { length }` or `Custom { custom_type }` + +**Important**: In Rust code, always use `ColumnType::Simple(SimpleColumnType::Integer)` instead of the old `ColumnType::Integer` syntax. The `From` trait is implemented for convenience: +```rust +// These are equivalent: +ColumnType::Simple(SimpleColumnType::Integer) +SimpleColumnType::Integer.into() +``` + +### ColumnType Methods +`ColumnType` provides two utility methods: +- `to_sql()`: Returns the PostgreSQL SQL type string (e.g., `"INTEGER"`, `"VARCHAR(255)"`) +- `to_rust_type(nullable: bool)`: Returns the Rust type string for SeaORM entity generation (e.g., `"i32"` or `"Option"`) + +These methods replace the old standalone functions `column_type_sql()` and `rust_type()`. + ### Foreign Key Definition Foreign keys can be defined inline on columns via the `foreign_key` field: diff --git a/Cargo.lock b/Cargo.lock index 86a114e..83ee82d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2831,7 +2831,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vespertide" -version = "0.1.4" +version = "0.1.5" dependencies = [ "vespertide-core", "vespertide-macro", @@ -2839,7 +2839,7 @@ dependencies = [ [[package]] name = "vespertide-cli" -version = "0.1.4" +version = "0.1.5" dependencies = [ "anyhow", "chrono", @@ -2860,7 +2860,7 @@ dependencies = [ [[package]] name = "vespertide-config" -version = "0.1.4" +version = "0.1.5" dependencies = [ "clap", "serde", @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "vespertide-core" -version = "0.1.4" +version = "0.1.5" dependencies = [ "schemars", "serde", @@ -2877,7 +2877,7 @@ dependencies = [ [[package]] name = "vespertide-exporter" -version = "0.1.4" +version = "0.1.5" dependencies = [ "insta", "rstest", @@ -2887,7 +2887,7 @@ dependencies = [ [[package]] name = "vespertide-macro" -version = "0.1.4" +version = "0.1.5" dependencies = [ "proc-macro2", "quote", @@ -2902,7 +2902,7 @@ dependencies = [ [[package]] name = "vespertide-planner" -version = "0.1.4" +version = "0.1.5" dependencies = [ "rstest", "thiserror", @@ -2911,7 +2911,7 @@ dependencies = [ [[package]] name = "vespertide-query" -version = "0.1.4" +version = "0.1.5" dependencies = [ "rstest", "thiserror", diff --git a/README.md b/README.md index 0bbe579..141fcaa 100644 --- a/README.md +++ b/README.md @@ -63,10 +63,10 @@ Models are JSON files in the `models/` directory: "$schema": "https://raw.githubusercontent.com/dev-five-git/vespertide/refs/heads/main/schemas/model.schema.json", "name": "user", "columns": [ - { "name": "id", "type": "Integer", "nullable": false, "primary_key": true }, - { "name": "email", "type": "Text", "nullable": false, "unique": true }, - { "name": "name", "type": "Text", "nullable": false }, - { "name": "created_at", "type": "Timestamp", "nullable": false, "default": "NOW()" } + { "name": "id", "type": "integer", "nullable": false, "primary_key": true }, + { "name": "email", "type": "text", "nullable": false, "unique": true }, + { "name": "name", "type": "text", "nullable": false }, + { "name": "created_at", "type": "timestamptz", "nullable": false, "default": "NOW()" } ], "constraints": [], "indexes": [] @@ -75,16 +75,32 @@ Models are JSON files in the `models/` directory: ### Column Types +**Simple Types** (string values in JSON): | Type | PostgreSQL | |------|------------| -| `"Integer"` | INTEGER | -| `"BigInt"` | BIGINT | -| `"Text"` | TEXT | -| `"Boolean"` | BOOLEAN | -| `"Timestamp"` | TIMESTAMP | -| `{ "Custom": "UUID" }` | UUID | -| `{ "Custom": "JSONB" }` | JSONB | -| `{ "Custom": "DECIMAL(10,2)" }` | DECIMAL(10,2) | +| `"integer"` | INTEGER | +| `"big_int"` | BIGINT | +| `"text"` | TEXT | +| `"boolean"` | BOOLEAN | +| `"timestamp"` | TIMESTAMP | +| `"timestamptz"` | TIMESTAMPTZ | +| `"uuid"` | UUID | +| `"jsonb"` | JSONB | +| `"small_int"` | SMALLINT | +| `"real"` | REAL | +| `"double_precision"` | DOUBLE PRECISION | +| `"date"` | DATE | +| `"time"` | TIME | +| `"bytea"` | BYTEA | +| `"json"` | JSON | +| `"inet"` | INET | +| `"cidr"` | CIDR | +| `"macaddr"` | MACADDR | + +**Complex Types** (object values in JSON): +- `{ "kind": "varchar", "length": 255 }` → VARCHAR(255) +- `{ "kind": "custom", "custom_type": "DECIMAL(10,2)" }` → DECIMAL(10,2) +- `{ "kind": "custom", "custom_type": "UUID" }` → UUID ### Inline Constraints @@ -93,7 +109,7 @@ Constraints can be defined directly on columns: ```json { "name": "user_id", - "type": "Integer", + "type": "integer", "nullable": false, "foreign_key": { "ref_table": "user", diff --git a/SKILL.md b/SKILL.md index 0dd326d..6e02261 100644 --- a/SKILL.md +++ b/SKILL.md @@ -73,7 +73,11 @@ Models are JSON files in the `models/` directory: ## Column Types -### Built-in Types +Column types in JSON can be either simple (string) or complex (object) values. + +### Simple Types (Built-in) + +Simple types are represented as strings in JSON (snake_case): | Type | PostgreSQL | Use Cases | |------|------------|-----------| @@ -96,17 +100,27 @@ Models are JSON files in the `models/` directory: | `"cidr"` | CIDR | Network address | | `"macaddr"` | MACADDR | MAC address | -### Custom Types +**Note**: In JSON, simple types are written as lowercase strings (e.g., `"integer"`, `"text"`). The Rust enum uses `SimpleColumnType` wrapped in `ColumnType::Simple()`. -For types not covered above: +### Complex Types +Complex types are represented as objects with a `kind` field: + +**VARCHAR with length:** ```json -{ "custom": "DECIMAL(10,2)" } -{ "custom": "VARCHAR(255)" } -{ "custom": "NUMERIC(20,8)" } -{ "custom": "INTERVAL" } +{ "kind": "varchar", "length": 255 } ``` +**Custom types:** +```json +{ "kind": "custom", "custom_type": "DECIMAL(10,2)" } +{ "kind": "custom", "custom_type": "NUMERIC(20,8)" } +{ "kind": "custom", "custom_type": "INTERVAL" } +{ "kind": "custom", "custom_type": "UUID" } +``` + +**Note**: In Rust code, complex types are represented as `ColumnType::Complex(ComplexColumnType::Varchar { length })` or `ColumnType::Complex(ComplexColumnType::Custom { custom_type })`. + ## Inline Constraints ### Primary Key @@ -230,7 +244,7 @@ Reference actions: `"Cascade"`, `"Restrict"`, `"SetNull"`, `"SetDefault"`, `"NoA "columns": [ { "name": "id", "type": "uuid", "nullable": false, "primary_key": true, "default": "gen_random_uuid()" }, { "name": "customer_id", "type": "integer", "nullable": false, "foreign_key": { "ref_table": "customer", "ref_columns": ["id"], "on_delete": "Restrict" }, "index": true }, - { "name": "total_amount", "type": { "custom": "DECIMAL(10,2)" }, "nullable": false }, + { "name": "total_amount", "type": { "kind": "custom", "custom_type": "DECIMAL(10,2)" }, "nullable": false }, { "name": "status", "type": "text", "nullable": false, "default": "'pending'" }, { "name": "metadata", "type": "jsonb", "nullable": true }, { "name": "created_at", "type": "timestamptz", "nullable": false, "default": "NOW()" } diff --git a/crates/vespertide-query/src/sql.rs b/crates/vespertide-query/src/sql.rs index 902f5e0..b70f203 100644 --- a/crates/vespertide-query/src/sql.rs +++ b/crates/vespertide-query/src/sql.rs @@ -836,6 +836,7 @@ mod tests { #[case(ColumnType::Simple(SimpleColumnType::Text), "TEXT")] #[case(ColumnType::Simple(SimpleColumnType::Boolean), "BOOLEAN")] #[case(ColumnType::Simple(SimpleColumnType::Timestamp), "TIMESTAMP")] + #[case(ColumnType::Simple(SimpleColumnType::Uuid), "UUID")] #[case(ColumnType::Complex(ComplexColumnType::Custom { custom_type: "VARCHAR(255)".to_string() }), "VARCHAR(255)")] fn test_column_type_sql(#[case] ty: ColumnType, #[case] expected: &str) { assert_eq!(ty.to_sql(), expected);