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
14 changes: 9 additions & 5 deletions internal/diff/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,9 @@ func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector
stmt += fmt.Sprintf(" DEFAULT %s", *column.DefaultValue)
}

// Don't add NOT NULL for identity columns or SERIAL columns as they are implicitly NOT NULL
// Don't add NOT NULL for identity columns, SERIAL columns, or generated columns as they are implicitly NOT NULL
// Also skip NOT NULL if we're adding PRIMARY KEY inline (PRIMARY KEY implies NOT NULL)
// For generated columns, include NOT NULL if explicitly specified (but before GENERATED clause)
if !column.IsNullable && column.Identity == nil && !isSerialColumn(column) && pkConstraint == nil {
if !column.IsNullable && column.Identity == nil && !isSerialColumn(column) && pkConstraint == nil && !column.IsGenerated {
stmt += " NOT NULL"
}

Expand All @@ -607,14 +606,19 @@ func (td *tableDiff) generateAlterTableStatements(targetSchema string, collector
}
}

// Add PRIMARY KEY inline before generated column syntax (PostgreSQL requires this order)
if pkConstraint != nil && column.IsGenerated {
stmt += " PRIMARY KEY"
}
Comment on lines +609 to +612
Copy link

Copilot AI Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PRIMARY KEY constraint is now added in two separate locations (lines 610-612 and 621-623) with conditional logic based on whether the column is generated. This creates duplicated logic that could be error-prone. Consider consolidating this into a single location or extracting it into a helper function to improve maintainability.

Copilot uses AI. Check for mistakes.

// Add generated column syntax
if column.IsGenerated && column.GeneratedExpr != nil {
// TODO: Add support for GENERATED ALWAYS AS (...) VIRTUAL when PostgreSQL 18 is supported
stmt += fmt.Sprintf(" GENERATED ALWAYS AS (%s) STORED", *column.GeneratedExpr)
}

// Add PRIMARY KEY inline if present
if pkConstraint != nil {
// Add PRIMARY KEY inline if present (for non-generated columns)
if pkConstraint != nil && !column.IsGenerated {
stmt += " PRIMARY KEY"
}

Expand Down
2 changes: 0 additions & 2 deletions ir/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -763,8 +763,6 @@ func (p *Parser) parseColumnDef(colDef *pg_query.ColumnDef, position int, schema
if generatedExpr != "" {
column.GeneratedExpr = &generatedExpr
column.IsGenerated = true
// Generated columns are implicitly NOT NULL
column.IsNullable = false
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion testdata/diff/create_table/add_column_generated/diff.sql
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
ALTER TABLE merge_request ADD COLUMN iid integer NOT NULL GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;
ALTER TABLE merge_request
ADD COLUMN iid integer PRIMARY KEY GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;
ALTER TABLE merge_request ADD COLUMN title text GENERATED ALWAYS AS (data ->> 'title') STORED;
3 changes: 2 additions & 1 deletion testdata/diff/create_table/add_column_generated/new.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
CREATE TABLE public.merge_request (
data jsonb NOT NULL,
iid integer NOT NULL GENERATED ALWAYS AS ((data ->> 'iid')::integer) STORED
iid integer PRIMARY KEY GENERATED ALWAYS AS ((data ->> 'iid')::integer) STORED,
title text GENERATED ALWAYS AS (data ->> 'title') STORED
);
10 changes: 8 additions & 2 deletions testdata/diff/create_table/add_column_generated/plan.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.0.0",
"pgschema_version": "1.1.1",
"pgschema_version": "1.2.0",
"created_at": "1970-01-01T00:00:00Z",
"source_fingerprint": {
"hash": "67e51d7bd6b2b020d7a95dbe7f8e5f9bd9ec2e3f4068b9ed09bb63b79e656354"
Expand All @@ -9,10 +9,16 @@
{
"steps": [
{
"sql": "ALTER TABLE merge_request ADD COLUMN iid integer NOT NULL GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;",
"sql": "ALTER TABLE merge_request\nADD COLUMN iid integer PRIMARY KEY GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;",
"type": "table.column",
"operation": "create",
"path": "public.merge_request.iid"
},
{
"sql": "ALTER TABLE merge_request ADD COLUMN title text GENERATED ALWAYS AS (data ->> 'title') STORED;",
"type": "table.column",
"operation": "create",
"path": "public.merge_request.title"
}
]
}
Expand Down
5 changes: 4 additions & 1 deletion testdata/diff/create_table/add_column_generated/plan.sql
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
ALTER TABLE merge_request ADD COLUMN iid integer NOT NULL GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;
ALTER TABLE merge_request
ADD COLUMN iid integer PRIMARY KEY GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;

ALTER TABLE merge_request ADD COLUMN title text GENERATED ALWAYS AS (data ->> 'title') STORED;
6 changes: 5 additions & 1 deletion testdata/diff/create_table/add_column_generated/plan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ Summary by type:
Tables:
~ merge_request
+ iid (column)
+ title (column)

DDL to be executed:
--------------------------------------------------

ALTER TABLE merge_request ADD COLUMN iid integer NOT NULL GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;
ALTER TABLE merge_request
ADD COLUMN iid integer PRIMARY KEY GENERATED ALWAYS AS (CAST(data ->> 'iid' AS int)) STORED;

ALTER TABLE merge_request ADD COLUMN title text GENERATED ALWAYS AS (data ->> 'title') STORED;