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
@@ -85,6 +85,7 @@ export const postsRelations = relations(posts, ({ one }) => ({
## Notes
- `.generatedAlwaysAs(sql`expr`)` sets `Generated` + `GenerationExpression`; `.generatedAlwaysAsIdentity()` / `.generatedByDefaultAsIdentity()` set `Identity` + `IdentityGeneration`
- Supports both PostgreSQL and MySQL Drizzle schemas
- Extracts relationship information from `relations` definitions
- Schema defaults to `public` for PostgreSQL
+47 -1
View File
@@ -495,9 +495,24 @@ func (r *Reader) parseColumnModifiers(line string, column *models.Column, table
}
}
// Check for .generatedAlwaysAsIdentity()
// Check for .generatedAlwaysAsIdentity() / .generatedByDefaultAsIdentity()
if strings.Contains(line, ".generatedAlwaysAsIdentity()") {
column.AutoIncrement = true
column.Identity = true
column.IdentityGeneration = "ALWAYS"
}
if strings.Contains(line, ".generatedByDefaultAsIdentity()") {
column.AutoIncrement = true
column.Identity = true
column.IdentityGeneration = "BY DEFAULT"
}
// Check for .generatedAlwaysAs(sql`expr`) (generated column)
if idx := strings.Index(line, ".generatedAlwaysAs("); idx != -1 {
if expr, ok := parseGeneratedAlwaysAs(line[idx+len(".generatedAlwaysAs("):]); ok {
column.Generated = true
column.GenerationExpression = expr
}
}
// Check for .references(() => otherTable.column)
@@ -615,3 +630,34 @@ func (r *Reader) varNameToTableName(varName string) string {
// For now, assume variable name matches table name
return varName
}
// parseGeneratedAlwaysAs extracts the SQL expression from the argument list of
// generatedAlwaysAs(...), which follows the opening parenthesis in rest. The expression is
// the contents of a sql`...` template (backticks inside it are escaped with a backslash)
// or, failing that, a quoted string.
func parseGeneratedAlwaysAs(rest string) (string, bool) {
rest = strings.TrimSpace(rest)
rest = strings.TrimPrefix(rest, "sql")
if rest == "" {
return "", false
}
quote := rest[0]
if quote != '`' && quote != '\'' && quote != '"' {
return "", false
}
var sb strings.Builder
for i := 1; i < len(rest); i++ {
ch := rest[i]
if ch == '\\' && i+1 < len(rest) {
i++
sb.WriteByte(rest[i])
continue
}
if ch == quote {
return strings.TrimSpace(sb.String()), true
}
sb.WriteByte(ch)
}
return "", false
}