feat(models): add support for generated and identity columns
Release / test (push) Successful in 3m13s
Release / release (push) Successful in 1m52s
Release / pkg-aur (push) Successful in 1m39s
Release / pkg-deb (push) Successful in 2m15s
Release / pkg-rpm (push) Failing after 46s

* extend Column struct with Generated, GenerationExpression, Identity, and IdentityGeneration fields
* update queryColumns to handle new column properties
* modify migration writer to avoid altering generated and identity columns
* add tests to ensure correct handling of generated and identity columns
This commit is contained in:
2026-09-20 18:33:12 +02:00
parent a84592374c
commit 52f4642d97
6 changed files with 227 additions and 22 deletions
+27 -1
View File
@@ -462,6 +462,12 @@ func (w *Writer) GenerateAlterColumnTypeStatements(schema *models.Schema) ([]str
for _, table := range schema.Tables {
columns := getSortedColumns(table.Columns)
for _, col := range columns {
if col.Generated || col.Identity {
// Type is derived from the generation expression (generated columns) or
// tied to the backing sequence (identity columns); leave both alone here
// rather than risk an ALTER COLUMN TYPE Postgres won't accept cleanly.
continue
}
targetType := effectiveAlterColumnSQLType(col)
stmt, err := w.executor.ExecuteAlterColumnTypeWithCheck(AlterColumnTypeWithCheckData{
SchemaName: schema.Name,
@@ -492,6 +498,14 @@ func (w *Writer) GenerateAlterColumnDefaultStatements(schema *models.Schema) ([]
for _, table := range schema.Tables {
columns := getSortedColumns(table.Columns)
for _, col := range columns {
if col.Generated || col.Identity {
// Generated columns: Postgres stores the generation expression in
// pg_attrdef just like a real default, so the guard template would see
// it as a stray default and emit DROP DEFAULT, which Postgres rejects.
// Identity columns never carry a DEFAULT at all (they're driven by a
// backing sequence), so there is nothing for this generator to manage.
continue
}
setDefault, defaultVal := formatColumnDefaultSQL(col)
stmt, err := w.executor.ExecuteAlterColumnDefaultWithCheck(AlterColumnDefaultWithCheckData{
SchemaName: schema.Name,
@@ -605,13 +619,25 @@ func (w *Writer) generateColumnDefinition(col *models.Column) string {
parts = append(parts, effectiveColumnSQLType(col))
// GENERATED ALWAYS ... STORED columns are computed from an expression and
// cannot also carry a DEFAULT, so this takes the place of the DEFAULT clause.
if col.Generated && col.GenerationExpression != "" {
parts = append(parts, fmt.Sprintf("GENERATED ALWAYS AS (%s) STORED", col.GenerationExpression))
}
// GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY columns are driven by an
// internal sequence, not a literal DEFAULT, and cannot carry one either.
if col.Identity && col.IdentityGeneration != "" {
parts = append(parts, fmt.Sprintf("GENERATED %s AS IDENTITY", col.IdentityGeneration))
}
// NOT NULL
if col.NotNull {
parts = append(parts, "NOT NULL")
}
// DEFAULT
if col.Default != nil {
if !col.Generated && !col.Identity && col.Default != nil {
switch v := col.Default.(type) {
case string:
parts = append(parts, fmt.Sprintf("DEFAULT %s", writers.QuoteDefaultValue(stripBackticks(v), col.Type)))