Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_8nNa9C1XPsaEK-8prlu5N.json
Original file line number Diff line number Diff line change
@@ -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"}
27 changes: 25 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,21 @@ 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
- `plan_next_migration()`: Combines baseline reconstruction + diffing to create the next migration
- `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

Expand All @@ -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,
Expand All @@ -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<i32>"`)

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:

Expand Down
16 changes: 8 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 29 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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": []
Expand All @@ -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

Expand All @@ -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",
Expand Down
30 changes: 22 additions & 8 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|------|------------|-----------|
Expand All @@ -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
Expand Down Expand Up @@ -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()" }
Expand Down
1 change: 1 addition & 0 deletions crates/vespertide-query/src/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down