Skip to content

Commit f1f8d06

Browse files
authored
Merge pull request #73 from dev-five-git/rm-jsonb
Remove jsonb
2 parents 7c6d271 + b37f0bc commit f1f8d06

11 files changed

Lines changed: 110 additions & 43 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"changes":{"crates/vespertide-planner/Cargo.toml":"Patch","crates/vespertide-naming/Cargo.toml":"Patch","crates/vespertide-cli/Cargo.toml":"Patch","crates/vespertide-macro/Cargo.toml":"Patch","crates/vespertide-config/Cargo.toml":"Patch","crates/vespertide-core/Cargo.toml":"Patch","crates/vespertide-exporter/Cargo.toml":"Patch","crates/vespertide/Cargo.toml":"Patch","crates/vespertide-loader/Cargo.toml":"Patch","crates/vespertide-query/Cargo.toml":"Patch"},"note":"Remove jsonb and update SKILL","date":"2026-01-16T03:28:50.936265200Z"}

Cargo.lock

Lines changed: 10 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SKILL.md

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ The schema URL provides:
3434

3535
---
3636

37+
## Post-Edit Validation (MANDATORY)
38+
39+
**After EVERY edit to a model file, ALWAYS run these checks:**
40+
41+
```bash
42+
# 1. Check for parsing errors and schema violations
43+
vespertide diff
44+
45+
# 2. Preview generated SQL to verify correctness
46+
vespertide sql
47+
```
48+
49+
**Verify the output:**
50+
- `vespertide diff` shows expected changes (no unexpected additions/removals)
51+
- `vespertide sql` generates valid SQL for your target database
52+
- IDE shows no red squiggles (schema validation errors)
53+
- All required fields (`name`, `type`, `nullable`) are present
54+
55+
**Only proceed to `vespertide revision` after verification passes.**
56+
57+
---
58+
3759
## Installation
3860

3961
```bash
@@ -201,8 +223,7 @@ vespertide revision -m "add status column"
201223
| `"interval"` | INTERVAL | Time duration |
202224
| `"bytea"` | BYTEA | Binary data |
203225
| `"uuid"` | UUID | UUIDs |
204-
| `"json"` | JSON | JSON data |
205-
| `"jsonb"` | JSONB | Binary JSON (indexable, recommended) |
226+
| `"json"` | JSON | JSON data (cross-database compatible) |
206227
| `"inet"` | INET | IPv4/IPv6 address |
207228
| `"cidr"` | CIDR | Network address |
208229
| `"macaddr"` | MACADDR | MAC address |
@@ -261,7 +282,9 @@ vespertide revision -m "add status column"
261282
> - Better for frequently-changing value sets
262283
> - Works identically across PostgreSQL, MySQL, SQLite
263284
264-
#### Custom Type (fallback)
285+
#### Custom Type (AVOID - last resort only)
286+
287+
> **WARNING**: Avoid custom types. They break cross-database compatibility. Use built-in types or redesign your schema.
265288
266289
```json
267290
{ "kind": "custom", "custom_type": "POINT" }
@@ -508,7 +531,7 @@ Both columns with `"primary_key": true` creates a **single composite primary key
508531
"nullable": false,
509532
"default": "'pending'"
510533
},
511-
{ "name": "metadata", "type": "jsonb", "nullable": true },
534+
{ "name": "metadata", "type": "json", "nullable": true },
512535
{ "name": "created_at", "type": "timestamptz", "nullable": false, "default": "NOW()" },
513536
{ "name": "updated_at", "type": "timestamptz", "nullable": true }
514537
]
@@ -638,7 +661,7 @@ Both columns with `"primary_key": true` creates a **single composite primary key
638661
1. **Use enums for status/category fields** - Prefer over text + CHECK
639662
2. **Use integer enums for expandable sets** - No migration needed for new values
640663
3. **Use `timestamptz` over `timestamp`** - Timezone-aware is safer
641-
4. **Use `jsonb` over `json`** - Indexable and faster
664+
4. **Use `json` type for JSON data** - Works across all backends (PostgreSQL, MySQL, SQLite)
642665

643666
### MUST NOT DO
644667

@@ -648,6 +671,9 @@ Both columns with `"primary_key": true` creates a **single composite primary key
648671
4. **Never use table-level constraints** - Except for CHECK expressions only
649672
5. **Never manually create/edit migration files** - Only `fill_with` exception
650673
6. **Never manually edit exported ORM files** - Use `vespertide export` to regenerate
674+
7. **Never use `jsonb` type** - Use `json` instead (JSONB not supported in SQLite)
675+
8. **Never use custom types** - Use built-in types only for cross-database compatibility
676+
9. **Never use array types** - Use a separate join table instead (arrays not supported in SQLite)
651677

652678
### Naming Conventions
653679

@@ -675,7 +701,7 @@ boolean Flags
675701
date, time, timestamp, timestamptz Time
676702
interval Duration
677703
uuid UUIDs
678-
json, jsonb JSON
704+
json JSON
679705
bytea Binary
680706
inet, cidr, macaddr Network
681707
xml XML

crates/vespertide-core/src/schema/column.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ impl ColumnType {
8787
SimpleColumnType::Interval => "String".to_string(),
8888
SimpleColumnType::Bytea => "Vec<u8>".to_string(),
8989
SimpleColumnType::Uuid => "Uuid".to_string(),
90-
SimpleColumnType::Json | SimpleColumnType::Jsonb => "Json".to_string(),
90+
SimpleColumnType::Json => "Json".to_string(),
91+
// SimpleColumnType::Jsonb => "Json".to_string(),
9192
SimpleColumnType::Inet | SimpleColumnType::Cidr => "String".to_string(),
9293
SimpleColumnType::Macaddr => "String".to_string(),
9394
SimpleColumnType::Xml => "String".to_string(),
@@ -139,7 +140,7 @@ pub enum SimpleColumnType {
139140

140141
// JSON types
141142
Json,
142-
Jsonb,
143+
// Jsonb,
143144

144145
// Network types
145146
Inet,
@@ -263,7 +264,7 @@ mod tests {
263264
#[case(SimpleColumnType::Bytea, "Vec<u8>")]
264265
#[case(SimpleColumnType::Uuid, "Uuid")]
265266
#[case(SimpleColumnType::Json, "Json")]
266-
#[case(SimpleColumnType::Jsonb, "Json")]
267+
// #[case(SimpleColumnType::Jsonb, "Json")]
267268
#[case(SimpleColumnType::Inet, "String")]
268269
#[case(SimpleColumnType::Cidr, "String")]
269270
#[case(SimpleColumnType::Macaddr, "String")]
@@ -294,7 +295,7 @@ mod tests {
294295
#[case(SimpleColumnType::Bytea, "Option<Vec<u8>>")]
295296
#[case(SimpleColumnType::Uuid, "Option<Uuid>")]
296297
#[case(SimpleColumnType::Json, "Option<Json>")]
297-
#[case(SimpleColumnType::Jsonb, "Option<Json>")]
298+
// #[case(SimpleColumnType::Jsonb, "Option<Json>")]
298299
#[case(SimpleColumnType::Inet, "Option<String>")]
299300
#[case(SimpleColumnType::Cidr, "Option<String>")]
300301
#[case(SimpleColumnType::Macaddr, "Option<String>")]

crates/vespertide-exporter/src/seaorm/mod.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ fn render_column(
219219
attrs.push(formatted);
220220
}
221221

222+
// For custom types, add column_type attribute with the custom type value
223+
if let ColumnType::Complex(ComplexColumnType::Custom { custom_type }) = &column.r#type {
224+
attrs.push(format!("column_type = \"{}\"", custom_type));
225+
}
226+
222227
// Output attribute if any
223228
if !attrs.is_empty() {
224229
lines.push(format!(" #[sea_orm({})]", attrs.join(", ")));
@@ -235,6 +240,16 @@ fn render_column(
235240
enum_type
236241
}
237242
}
243+
// JSONB custom type should use Json rust type
244+
ColumnType::Complex(ComplexColumnType::Custom { custom_type })
245+
if custom_type.to_uppercase() == "JSONB" =>
246+
{
247+
if column.nullable {
248+
"Option<Json>".to_string()
249+
} else {
250+
"Json".to_string()
251+
}
252+
}
238253
_ => column.r#type.to_rust_type(column.nullable),
239254
};
240255

@@ -1035,7 +1050,6 @@ mod helper_tests {
10351050
#[case(ColumnType::Simple(SimpleColumnType::Bytea), false, "Vec<u8>")]
10361051
#[case(ColumnType::Simple(SimpleColumnType::Uuid), false, "Uuid")]
10371052
#[case(ColumnType::Simple(SimpleColumnType::Json), false, "Json")]
1038-
#[case(ColumnType::Simple(SimpleColumnType::Jsonb), false, "Json")]
10391053
#[case(ColumnType::Simple(SimpleColumnType::Inet), false, "String")]
10401054
#[case(ColumnType::Simple(SimpleColumnType::Cidr), false, "String")]
10411055
#[case(ColumnType::Simple(SimpleColumnType::Macaddr), false, "String")]
@@ -2244,6 +2258,17 @@ mod tests {
22442258
TableConstraint::PrimaryKey { columns: vec!["id".into()], auto_increment: false },
22452259
],
22462260
})]
2261+
#[case("jsonb_custom_type", TableDef {
2262+
name: "json_struct".into(),
2263+
description: None,
2264+
columns: vec![
2265+
ColumnDef { name: "id".into(), r#type: ColumnType::Simple(SimpleColumnType::Integer), nullable: false, default: None, comment: None, primary_key: Some(PrimaryKeySyntax::Bool(true)), unique: None, index: None, foreign_key: None },
2266+
ColumnDef { name: "json_data".into(), r#type: ColumnType::Simple(SimpleColumnType::Json), nullable: false, default: None, comment: None, primary_key: None, unique: None, index: None, foreign_key: None },
2267+
ColumnDef { name: "jsonb_data".into(), r#type: ColumnType::Complex(ComplexColumnType::Custom { custom_type: "JSONB".into() }), nullable: false, default: None, comment: None, primary_key: None, unique: None, index: None, foreign_key: None },
2268+
ColumnDef { name: "jsonb_nullable".into(), r#type: ColumnType::Complex(ComplexColumnType::Custom { custom_type: "jsonb".into() }), nullable: true, default: None, comment: None, primary_key: None, unique: None, index: None, foreign_key: None },
2269+
],
2270+
constraints: vec![],
2271+
})]
22472272
fn render_entity_snapshots(#[case] name: &str, #[case] table: TableDef) {
22482273
let rendered = render_entity(&table);
22492274
with_settings!({ snapshot_suffix => format!("params_{}", name) }, {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
source: crates/vespertide-exporter/src/seaorm/mod.rs
3+
expression: rendered
4+
---
5+
use sea_orm::entity::prelude::*;
6+
use serde::{Deserialize, Serialize};
7+
8+
#[sea_orm::model]
9+
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
10+
#[sea_orm(table_name = "json_struct")]
11+
pub struct Model {
12+
#[sea_orm(primary_key)]
13+
pub id: i32,
14+
pub json_data: Json,
15+
#[sea_orm(column_type = "JSONB")]
16+
pub jsonb_data: Json,
17+
#[sea_orm(column_type = "jsonb")]
18+
pub jsonb_nullable: Option<Json>,
19+
}
20+
21+
impl ActiveModelBehavior for ActiveModel {}

crates/vespertide-exporter/src/sqlalchemy/mod.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<'a> UsedTypes<'a> {
6565
self.sa_types.insert("Uuid");
6666
self.needs_uuid = true;
6767
}
68-
SimpleColumnType::Json | SimpleColumnType::Jsonb => {
68+
SimpleColumnType::Json => {
6969
self.sa_types.insert("JSON");
7070
}
7171
SimpleColumnType::Inet | SimpleColumnType::Cidr | SimpleColumnType::Macaddr => {
@@ -461,7 +461,7 @@ fn column_type_to_python(col_type: &ColumnType, nullable: bool) -> String {
461461
SimpleColumnType::Interval => "str",
462462
SimpleColumnType::Bytea => "bytes",
463463
SimpleColumnType::Uuid => "UUID",
464-
SimpleColumnType::Json | SimpleColumnType::Jsonb => "dict",
464+
SimpleColumnType::Json => "dict",
465465
SimpleColumnType::Inet | SimpleColumnType::Cidr => "str",
466466
SimpleColumnType::Macaddr => "str",
467467
SimpleColumnType::Xml => "str",
@@ -505,7 +505,7 @@ fn column_type_to_sqlalchemy(col_type: &ColumnType) -> String {
505505
SimpleColumnType::Interval => "Interval".into(),
506506
SimpleColumnType::Bytea => "LargeBinary".into(),
507507
SimpleColumnType::Uuid => "Uuid".into(),
508-
SimpleColumnType::Json | SimpleColumnType::Jsonb => "JSON".into(),
508+
SimpleColumnType::Json => "JSON".into(),
509509
SimpleColumnType::Inet | SimpleColumnType::Cidr | SimpleColumnType::Macaddr => {
510510
"String(255)".into()
511511
}
@@ -829,7 +829,6 @@ mod tests {
829829
col("bytea_col", ColumnType::Simple(SimpleColumnType::Bytea)),
830830
col("uuid_col", ColumnType::Simple(SimpleColumnType::Uuid)),
831831
col("json_col", ColumnType::Simple(SimpleColumnType::Json)),
832-
col("jsonb_col", ColumnType::Simple(SimpleColumnType::Jsonb)),
833832
col("inet_col", ColumnType::Simple(SimpleColumnType::Inet)),
834833
col("cidr_col", ColumnType::Simple(SimpleColumnType::Cidr)),
835834
col("macaddr_col", ColumnType::Simple(SimpleColumnType::Macaddr)),
@@ -1241,13 +1240,6 @@ mod tests {
12411240
assert!(used.sa_types.contains("JSON"));
12421241
}
12431242

1244-
#[test]
1245-
fn test_used_types_jsonb() {
1246-
let mut used = UsedTypes::default();
1247-
used.add_column_type(&ColumnType::Simple(SimpleColumnType::Jsonb), false);
1248-
assert!(used.sa_types.contains("JSON"));
1249-
}
1250-
12511243
#[test]
12521244
fn test_used_types_inet() {
12531245
let mut used = UsedTypes::default();

crates/vespertide-exporter/src/sqlmodel/mod.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ fn column_type_to_python(col_type: &ColumnType, nullable: bool) -> String {
448448
SimpleColumnType::Interval => "str",
449449
SimpleColumnType::Bytea => "bytes",
450450
SimpleColumnType::Uuid => "UUID",
451-
SimpleColumnType::Json | SimpleColumnType::Jsonb => "dict",
451+
SimpleColumnType::Json => "dict",
452452
SimpleColumnType::Inet | SimpleColumnType::Cidr => "str",
453453
SimpleColumnType::Macaddr => "str",
454454
SimpleColumnType::Xml => "str",
@@ -826,7 +826,6 @@ mod tests {
826826
col("bytea_col", ColumnType::Simple(SimpleColumnType::Bytea)),
827827
col("uuid_col", ColumnType::Simple(SimpleColumnType::Uuid)),
828828
col("json_col", ColumnType::Simple(SimpleColumnType::Json)),
829-
col("jsonb_col", ColumnType::Simple(SimpleColumnType::Jsonb)),
830829
col("inet_col", ColumnType::Simple(SimpleColumnType::Inet)),
831830
col("cidr_col", ColumnType::Simple(SimpleColumnType::Cidr)),
832831
col("macaddr_col", ColumnType::Simple(SimpleColumnType::Macaddr)),
@@ -854,7 +853,6 @@ mod tests {
854853
assert!(result.contains("bytea_col: bytes"));
855854
assert!(result.contains("uuid_col: UUID"));
856855
assert!(result.contains("json_col: dict"));
857-
assert!(result.contains("jsonb_col: dict"));
858856
assert!(result.contains("inet_col: str"));
859857
assert!(result.contains("cidr_col: str"));
860858
assert!(result.contains("macaddr_col: str"));

crates/vespertide-loader/src/migrations.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,9 +324,11 @@ actions:
324324
assert!(err_msg.contains("CARGO_MANIFEST_DIR environment variable not set"));
325325

326326
// Restore the original value if it existed
327-
// Note: In a test environment, we don't restore to avoid affecting other tests
328-
// The serial_test ensures tests run sequentially
329-
drop(original);
327+
if let Some(val) = original {
328+
unsafe {
329+
env::set_var("CARGO_MANIFEST_DIR", val);
330+
}
331+
}
330332
}
331333

332334
#[test]

crates/vespertide-loader/src/models.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,12 @@ mod tests {
374374
let err_msg = result.unwrap_err().to_string();
375375
assert!(err_msg.contains("CARGO_MANIFEST_DIR environment variable not set"));
376376

377-
drop(original);
377+
// Restore the original value if it existed
378+
if let Some(val) = original {
379+
unsafe {
380+
env::set_var("CARGO_MANIFEST_DIR", val);
381+
}
382+
}
378383
}
379384

380385
#[test]

0 commit comments

Comments
 (0)