feat: generated and identity columns for mssql, sqlite, drizzle, typeorm, bun and gorm readers

- mssql: write computed columns (AS (expr) PERSISTED) and identity; read computed definition and identity
- sqlite: write GENERATED ALWAYS AS (expr) STORED; read generated columns via table_xinfo and parse the expression
- drizzle: generatedAlwaysAs / generated(Always|ByDefault)AsIdentity, read and write
- typeorm: asExpression/generatedType and identity decorators, read and write; decorator scan is now quote-aware
- bun, gorm readers: read generated and identity markers
- readmes updated
This commit is contained in:
2026-10-02 23:04:04 +02:00
parent bbab5ce936
commit 9cc10715e3
29 changed files with 789 additions and 18 deletions
+1
View File
@@ -229,6 +229,7 @@ func main() {
## Notes
- Generated columns are written as `col TYPE GENERATED ALWAYS AS (expr) STORED` (no DEFAULT)
- Schema flattening is **always enabled** for SQLite output (cannot be disabled); the default schema (`public`/`main`) produces bare table names, other schemas are prefixed
- Constraint and index names are prefixed with the flattened table name to avoid collisions
- Generated SQL is compatible with SQLite 3.x
@@ -1,7 +1,7 @@
CREATE TABLE {{quote_ident (qualified_table_name .Schema .Name)}} (
{{- $hasAutoIncrement := false}}
{{- range $i, $col := .Columns}}{{if $i}},{{end}}
{{quote_ident $col.Name}} {{map_type $col.Type}}{{column_collate $col}}{{if is_autoincrement $col}}{{$hasAutoIncrement = true}} PRIMARY KEY AUTOINCREMENT{{else}}{{if $col.NotNull}} NOT NULL{{end}}{{if ne (format_default $col) ""}} DEFAULT {{format_default $col}}{{end}}{{end}}
{{quote_ident $col.Name}} {{map_type $col.Type}}{{column_collate $col}}{{if and $col.Generated $col.GenerationExpression}} GENERATED ALWAYS AS ({{$col.GenerationExpression}}) STORED{{if $col.NotNull}} NOT NULL{{end}}{{else if is_autoincrement $col}}{{$hasAutoIncrement = true}} PRIMARY KEY AUTOINCREMENT{{else}}{{if $col.NotNull}} NOT NULL{{end}}{{if ne (format_default $col) ""}} DEFAULT {{format_default $col}}{{end}}{{end}}
{{- end}}
{{- if and .PrimaryKey (not $hasAutoIncrement)}}{{if gt (len .Columns) 0}},{{end}}
PRIMARY KEY ({{range $i, $colName := .PrimaryKey.Columns}}{{if $i}}, {{end}}{{quote_ident $colName}}{{end}})
+36
View File
@@ -421,3 +421,39 @@ func TestQuoteIdentifier(t *testing.T) {
}
}
}
func TestWriteDatabase_GeneratedColumn(t *testing.T) {
table := models.InitTable("people", "public")
first := models.InitColumn("first", "people", "public")
first.Type = "text"
first.Sequence = 1
table.Columns["first"] = first
full := models.InitColumn("full_name", "people", "public")
full.Type = "text"
full.Sequence = 2
full.NotNull = true
full.Generated = true
full.GenerationExpression = "first || ' ' || first"
full.Default = "ignored"
table.Columns["full_name"] = full
schema := models.InitSchema("public")
schema.Tables = append(schema.Tables, table)
db := models.InitDatabase("testdb")
db.Schemas = append(db.Schemas, schema)
var buf bytes.Buffer
writer := NewWriter(&writers.WriterOptions{})
writer.writer = &buf
if err := writer.WriteDatabase(db); err != nil {
t.Fatalf("WriteDatabase failed: %v", err)
}
want := `"full_name" TEXT GENERATED ALWAYS AS (first || ' ' || first) STORED NOT NULL`
if !strings.Contains(buf.String(), want) {
t.Errorf("missing %q in:\n%s", want, buf.String())
}
if strings.Contains(buf.String(), "DEFAULT") && strings.Contains(buf.String(), "ignored") {
t.Errorf("generated column must not carry a DEFAULT:\n%s", buf.String())
}
}