diff --git a/internal/ir/parser.go b/internal/ir/parser.go index ae95b73d..492c0443 100644 --- a/internal/ir/parser.go +++ b/internal/ir/parser.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "regexp" + "sort" "strconv" "strings" @@ -1056,15 +1057,21 @@ func (p *Parser) parseConstraint(constraint *pg_query.Constraint, schemaName, ta if constraint.Conname != "" { constraintName = constraint.Conname } else { - // For foreign key constraints, use FkAttrs if Keys is empty - var nameKeys []*pg_query.Node - if constraintType == ConstraintTypeForeignKey && len(constraint.Keys) == 0 && len(constraint.FkAttrs) > 0 { - nameKeys = constraint.FkAttrs + // For CHECK constraints, extract column names from the expression + if constraintType == ConstraintTypeCheck && constraint.RawExpr != nil { + columnNames := p.extractColumnNamesFromExpression(constraint.RawExpr) + constraintName = p.generateConstraintNameFromColumns(constraintType, tableName, columnNames) } else { - nameKeys = constraint.Keys + // For other constraint types, use the Keys field + var nameKeys []*pg_query.Node + if constraintType == ConstraintTypeForeignKey && len(constraint.Keys) == 0 && len(constraint.FkAttrs) > 0 { + nameKeys = constraint.FkAttrs + } else { + nameKeys = constraint.Keys + } + // Generate default name based on type and columns + constraintName = p.generateConstraintName(constraintType, tableName, nameKeys) } - // Generate default name based on type and columns - constraintName = p.generateConstraintName(constraintType, tableName, nameKeys) } c := &Constraint{ @@ -1217,6 +1224,75 @@ func (p *Parser) generateConstraintName(constraintType ConstraintType, tableName } } +// generateConstraintNameFromColumns generates a constraint name from column names +func (p *Parser) generateConstraintNameFromColumns(constraintType ConstraintType, tableName string, columnNames []string) string { + var suffix string + switch constraintType { + case ConstraintTypePrimaryKey: + suffix = "pkey" + case ConstraintTypeUnique: + suffix = "key" + case ConstraintTypeForeignKey: + suffix = "fkey" + case ConstraintTypeCheck: + suffix = "check" + default: + suffix = "constraint" + } + + // Primary keys in PostgreSQL always use table_pkey format, never include column names + if constraintType == ConstraintTypePrimaryKey { + return fmt.Sprintf("%s_%s", tableName, suffix) + } + + if len(columnNames) == 0 { + return fmt.Sprintf("%s_%s", tableName, suffix) + } + + // For CHECK constraints, match PostgreSQL's actual naming behavior: + // - Single column: tableName_columnName_check + // - Zero or multiple columns: tableName_check (PostgreSQL doesn't include column names for complex expressions) + if constraintType == ConstraintTypeCheck { + if len(columnNames) == 1 { + // Single column CHECK constraint: include the column name + constraintName := fmt.Sprintf("%s_%s_%s", tableName, columnNames[0], suffix) + + // PostgreSQL has a 63-character limit for identifiers + if len(constraintName) > 63 { + // Truncate to fit within limit, keeping suffix + maxPrefixLen := 63 - len(suffix) - 1 + if maxPrefixLen > 0 { + constraintName = constraintName[:maxPrefixLen] + "_" + suffix + } + } + return constraintName + } else { + // Zero or multiple columns: use simple tableName_check format + return fmt.Sprintf("%s_%s", tableName, suffix) + } + } + + // For UNIQUE and FOREIGN KEY constraints, include all column names + if constraintType == ConstraintTypeUnique || constraintType == ConstraintTypeForeignKey { + // Join all column names for unique and foreign key constraints + allColumns := strings.Join(columnNames, "_") + constraintName := fmt.Sprintf("%s_%s_%s", tableName, allColumns, suffix) + + // PostgreSQL has a 63-character limit for identifiers + if len(constraintName) > 63 { + // Truncate to fit within limit, keeping suffix + maxPrefixLen := 63 - len(suffix) - 1 + if maxPrefixLen > 0 { + constraintName = constraintName[:maxPrefixLen] + "_" + suffix + } + } + return constraintName + } + + // Default fallback - use first column + return fmt.Sprintf("%s_%s_%s", tableName, columnNames[0], suffix) +} + // mapReferentialAction maps pg_query referential action to string func (p *Parser) mapReferentialAction(action string) string { switch action { @@ -1260,6 +1336,74 @@ func (p *Parser) extractExpressionText(expr *pg_query.Node) string { } } +// extractColumnNamesFromExpression recursively extracts column names from CHECK constraint expressions +func (p *Parser) extractColumnNamesFromExpression(expr *pg_query.Node) []string { + if expr == nil { + return nil + } + + var columnNames []string + columnSet := make(map[string]bool) // Use map to avoid duplicates + + p.collectColumnNamesFromNode(expr, columnSet) + + // Convert map keys to sorted slice + for columnName := range columnSet { + columnNames = append(columnNames, columnName) + } + + // Sort for consistent ordering + sort.Strings(columnNames) + + return columnNames +} + +// collectColumnNamesFromNode recursively collects column names from AST nodes +func (p *Parser) collectColumnNamesFromNode(node *pg_query.Node, columnSet map[string]bool) { + if node == nil { + return + } + + switch n := node.Node.(type) { + case *pg_query.Node_ColumnRef: + // Extract column name from ColumnRef + if len(n.ColumnRef.Fields) > 0 { + if str := n.ColumnRef.Fields[len(n.ColumnRef.Fields)-1].GetString_(); str != nil { + columnName := str.Sval + // Only include simple column names (not qualified with table names) + if !strings.Contains(columnName, ".") { + columnSet[columnName] = true + } + } + } + case *pg_query.Node_AExpr: + // Recursively process left and right expressions + p.collectColumnNamesFromNode(n.AExpr.Lexpr, columnSet) + p.collectColumnNamesFromNode(n.AExpr.Rexpr, columnSet) + case *pg_query.Node_BoolExpr: + // Recursively process all arguments in boolean expressions + for _, arg := range n.BoolExpr.Args { + p.collectColumnNamesFromNode(arg, columnSet) + } + case *pg_query.Node_FuncCall: + // Recursively process function arguments + if n.FuncCall.Args != nil { + for _, arg := range n.FuncCall.Args { + p.collectColumnNamesFromNode(arg, columnSet) + } + } + case *pg_query.Node_TypeCast: + // Recursively process the argument being cast + p.collectColumnNamesFromNode(n.TypeCast.Arg, columnSet) + case *pg_query.Node_List: + // Recursively process list items + for _, item := range n.List.Items { + p.collectColumnNamesFromNode(item, columnSet) + } + // For other node types (constants, etc.), we don't need to extract column names + } +} + // parseAExpr parses arithmetic/comparison expressions func (p *Parser) parseAExpr(expr *pg_query.A_Expr) string { // Handle IN expressions diff --git a/internal/plan/plan.go b/internal/plan/plan.go index be9aacdb..e2211bba 100644 --- a/internal/plan/plan.go +++ b/internal/plan/plan.go @@ -140,10 +140,19 @@ func groupDiffs(diffs []diff.Diff) []ExecutionGroup { var groups []ExecutionGroup var transactionalSteps []Step + // Track newly created tables to avoid concurrent rewrites for their indexes + newlyCreatedTables := make(map[string]bool) + for _, d := range diffs { + if d.Type == diff.DiffTypeTable && d.Operation == diff.DiffOperationCreate { + // Extract table name from path (schema.table) + newlyCreatedTables[d.Path] = true + } + } + // Convert diffs to steps for _, d := range diffs { // Try to generate rewrites if online operations are enabled - rewriteSteps := generateRewrite(d) + rewriteSteps := generateRewrite(d, newlyCreatedTables) if len(rewriteSteps) > 0 { // For operations with rewrites, create one step per rewrite statement diff --git a/internal/plan/rewrite.go b/internal/plan/rewrite.go index 37883c7e..1eb84c46 100644 --- a/internal/plan/rewrite.go +++ b/internal/plan/rewrite.go @@ -16,13 +16,18 @@ type RewriteStep struct { } // generateRewrite generates rewrite steps for a diff if online operations are enabled -func generateRewrite(d diff.Diff) []RewriteStep { +func generateRewrite(d diff.Diff, newlyCreatedTables map[string]bool) []RewriteStep { // Dispatch to specific rewrite generators based on diff type and source switch d.Type { case diff.DiffTypeTableIndex: switch d.Operation { case diff.DiffOperationCreate: if index, ok := d.Source.(*ir.Index); ok { + // Skip rewrite for indexes on newly created tables + tableKey := index.Schema + "." + index.Table + if newlyCreatedTables[tableKey] { + return nil // No rewrite needed for indexes on new tables + } return generateIndexRewrite(index) } case diff.DiffOperationAlter: @@ -37,6 +42,11 @@ func generateRewrite(d diff.Diff) []RewriteStep { case diff.DiffTypeTableConstraint: if d.Operation == diff.DiffOperationCreate { if constraint, ok := d.Source.(*ir.Constraint); ok { + // Skip rewrite for constraints on newly created tables + tableKey := constraint.Schema + "." + constraint.Table + if newlyCreatedTables[tableKey] { + return nil // No rewrite needed for constraints on new tables + } switch constraint.Type { case ir.ConstraintTypeCheck: return generateConstraintRewrite(constraint) diff --git a/testdata/diff/create_table/add_table_like/plan.json b/testdata/diff/create_table/add_table_like/plan.json index 94ab782b..3bff4e0b 100644 --- a/testdata/diff/create_table/add_table_like/plan.json +++ b/testdata/diff/create_table/add_table_like/plan.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "pgschema_version": "1.0.2", + "pgschema_version": "1.0.3", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { "hash": "bcda23084995439e43e6387779062084fe7c9fab8123ca140161baf8ec4d2be6" @@ -25,35 +25,13 @@ "type": "table.comment", "operation": "create", "path": "public.users" - } - ] - }, - { - "steps": [ - { - "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS users_created_at_idx ON users (created_at);", - "type": "table.index", - "operation": "create", - "path": "public.users.users_created_at_idx" - } - ] - }, - { - "steps": [ + }, { - "sql": "SELECT \n COALESCE(i.indisvalid, false) as done,\n CASE \n WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total\n ELSE 0\n END as progress\nFROM pg_class c\nLEFT JOIN pg_index i ON c.oid = i.indexrelid\nLEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid\nWHERE c.relname = 'users_created_at_idx';", - "directive": { - "type": "wait", - "message": "Creating index users_created_at_idx" - }, + "sql": "CREATE INDEX IF NOT EXISTS users_created_at_idx ON users (created_at);", "type": "table.index", "operation": "create", "path": "public.users.users_created_at_idx" - } - ] - }, - { - "steps": [ + }, { "sql": "COMMENT ON COLUMN _template_timestamps.created_at IS NULL;", "type": "table.column.comment", diff --git a/testdata/diff/create_table/add_table_like/plan.sql b/testdata/diff/create_table/add_table_like/plan.sql index dbb817e6..03acf92f 100644 --- a/testdata/diff/create_table/add_table_like/plan.sql +++ b/testdata/diff/create_table/add_table_like/plan.sql @@ -15,18 +15,6 @@ CREATE TABLE IF NOT EXISTS users ( COMMENT ON TABLE users IS 'Template for timestamp fields'; -CREATE INDEX CONCURRENTLY IF NOT EXISTS users_created_at_idx ON users (created_at); - --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'users_created_at_idx'; +CREATE INDEX IF NOT EXISTS users_created_at_idx ON users (created_at); COMMENT ON COLUMN _template_timestamps.created_at IS NULL; diff --git a/testdata/diff/create_table/add_table_like/plan.txt b/testdata/diff/create_table/add_table_like/plan.txt index 6b24bed2..f1e6d2c0 100644 --- a/testdata/diff/create_table/add_table_like/plan.txt +++ b/testdata/diff/create_table/add_table_like/plan.txt @@ -14,7 +14,6 @@ Tables: DDL to be executed: -------------------------------------------------- --- Transaction Group #1 CREATE TABLE IF NOT EXISTS products ( id SERIAL PRIMARY KEY, created_at timestamptz DEFAULT now() NOT NULL, @@ -32,21 +31,6 @@ CREATE TABLE IF NOT EXISTS users ( COMMENT ON TABLE users IS 'Template for timestamp fields'; --- Transaction Group #2 -CREATE INDEX CONCURRENTLY IF NOT EXISTS users_created_at_idx ON users (created_at); - --- Transaction Group #3 --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'users_created_at_idx'; - --- Transaction Group #4 +CREATE INDEX IF NOT EXISTS users_created_at_idx ON users (created_at); + COMMENT ON COLUMN _template_timestamps.created_at IS NULL; diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/diff.sql b/testdata/diff/create_table/add_table_no_online_rewrite/diff.sql new file mode 100644 index 00000000..982c91b4 --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/diff.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS companies ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS departments ( + id integer PRIMARY KEY, + name text NOT NULL, + company_id integer NOT NULL REFERENCES companies (id), + budget numeric(10,2), + created_at timestamp DEFAULT now(), + CHECK (budget > 0) +); + +CREATE INDEX IF NOT EXISTS idx_departments_name ON departments (name); diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/new.sql b/testdata/diff/create_table/add_table_no_online_rewrite/new.sql new file mode 100644 index 00000000..82c6827e --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/new.sql @@ -0,0 +1,17 @@ +-- First create a referenced table for FK constraint +CREATE TABLE public.companies ( + id integer PRIMARY KEY, + name text NOT NULL +); + +-- Main table with constraints and index +CREATE TABLE public.departments ( + id integer PRIMARY KEY, + name text NOT NULL, + company_id integer NOT NULL REFERENCES companies(id), + budget numeric(10,2), + created_at timestamp DEFAULT now(), + CHECK (budget > 0) +); + +CREATE INDEX idx_departments_name ON public.departments (name); \ No newline at end of file diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/old.sql b/testdata/diff/create_table/add_table_no_online_rewrite/old.sql new file mode 100644 index 00000000..68cc3483 --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/old.sql @@ -0,0 +1 @@ +-- Empty schema (no tables) \ No newline at end of file diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/plan.json b/testdata/diff/create_table/add_table_no_online_rewrite/plan.json new file mode 100644 index 00000000..af279a32 --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/plan.json @@ -0,0 +1,32 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.0.3", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "965b1131737c955e24c7f827c55bd78e4cb49a75adfd04229e0ba297376f5085" + }, + "groups": [ + { + "steps": [ + { + "sql": "CREATE TABLE IF NOT EXISTS companies (\n id integer PRIMARY KEY,\n name text NOT NULL\n);", + "type": "table", + "operation": "create", + "path": "public.companies" + }, + { + "sql": "CREATE TABLE IF NOT EXISTS departments (\n id integer PRIMARY KEY,\n name text NOT NULL,\n company_id integer NOT NULL REFERENCES companies (id),\n budget numeric(10,2),\n created_at timestamp DEFAULT now(),\n CHECK (budget > 0)\n);", + "type": "table", + "operation": "create", + "path": "public.departments" + }, + { + "sql": "CREATE INDEX IF NOT EXISTS idx_departments_name ON departments (name);", + "type": "table.index", + "operation": "create", + "path": "public.departments.idx_departments_name" + } + ] + } + ] +} diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/plan.sql b/testdata/diff/create_table/add_table_no_online_rewrite/plan.sql new file mode 100644 index 00000000..982c91b4 --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/plan.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS companies ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS departments ( + id integer PRIMARY KEY, + name text NOT NULL, + company_id integer NOT NULL REFERENCES companies (id), + budget numeric(10,2), + created_at timestamp DEFAULT now(), + CHECK (budget > 0) +); + +CREATE INDEX IF NOT EXISTS idx_departments_name ON departments (name); diff --git a/testdata/diff/create_table/add_table_no_online_rewrite/plan.txt b/testdata/diff/create_table/add_table_no_online_rewrite/plan.txt new file mode 100644 index 00000000..4ccce282 --- /dev/null +++ b/testdata/diff/create_table/add_table_no_online_rewrite/plan.txt @@ -0,0 +1,28 @@ +Plan: 2 to add. + +Summary by type: + tables: 2 to add + +Tables: + + companies + + departments + + idx_departments_name (index) + +DDL to be executed: +-------------------------------------------------- + +CREATE TABLE IF NOT EXISTS companies ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE IF NOT EXISTS departments ( + id integer PRIMARY KEY, + name text NOT NULL, + company_id integer NOT NULL REFERENCES companies (id), + budget numeric(10,2), + created_at timestamp DEFAULT now(), + CHECK (budget > 0) +); + +CREATE INDEX IF NOT EXISTS idx_departments_name ON departments (name); diff --git a/testdata/diff/migrate/v3/plan.json b/testdata/diff/migrate/v3/plan.json index a3975c27..d22ce689 100644 --- a/testdata/diff/migrate/v3/plan.json +++ b/testdata/diff/migrate/v3/plan.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "pgschema_version": "1.0.0", + "pgschema_version": "1.0.3", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { "hash": "b07ab6318b6ff348aa5554a1f6e1a1ec9ad6b987a6d47e455fbdf97f1b0b96fb" @@ -13,35 +13,13 @@ "type": "table", "operation": "create", "path": "public.audit" - } - ] - }, - { - "steps": [ - { - "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_changed_at ON audit (changed_at);", - "type": "table.index", - "operation": "create", - "path": "public.audit.idx_audit_changed_at" - } - ] - }, - { - "steps": [ + }, { - "sql": "SELECT \n COALESCE(i.indisvalid, false) as done,\n CASE \n WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total\n ELSE 0\n END as progress\nFROM pg_class c\nLEFT JOIN pg_index i ON c.oid = i.indexrelid\nLEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid\nWHERE c.relname = 'idx_audit_changed_at';", - "directive": { - "type": "wait", - "message": "Creating index idx_audit_changed_at" - }, + "sql": "CREATE INDEX IF NOT EXISTS idx_audit_changed_at ON audit (changed_at);", "type": "table.index", "operation": "create", "path": "public.audit.idx_audit_changed_at" - } - ] - }, - { - "steps": [ + }, { "sql": "CREATE OR REPLACE FUNCTION log_dml_operations()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY INVOKER\nVOLATILE\nAS $$\nBEGIN\n IF (TG_OP = 'INSERT') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES ('INSERT', current_query(), current_user);\n RETURN NEW;\n ELSIF (TG_OP = 'UPDATE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES ('UPDATE', current_query(), current_user);\n RETURN NEW;\n ELSIF (TG_OP = 'DELETE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES ('DELETE', current_query(), current_user);\n RETURN OLD;\n END IF;\n RETURN NULL;\nEND;\n$$;", "type": "function", diff --git a/testdata/diff/migrate/v3/plan.sql b/testdata/diff/migrate/v3/plan.sql index bb402d0e..42748a5c 100644 --- a/testdata/diff/migrate/v3/plan.sql +++ b/testdata/diff/migrate/v3/plan.sql @@ -6,19 +6,7 @@ CREATE TABLE IF NOT EXISTS audit ( changed_at timestamptz DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_changed_at ON audit (changed_at); - --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_audit_changed_at'; +CREATE INDEX IF NOT EXISTS idx_audit_changed_at ON audit (changed_at); CREATE OR REPLACE FUNCTION log_dml_operations() RETURNS trigger diff --git a/testdata/diff/migrate/v3/plan.txt b/testdata/diff/migrate/v3/plan.txt index 97395b89..222133a0 100644 --- a/testdata/diff/migrate/v3/plan.txt +++ b/testdata/diff/migrate/v3/plan.txt @@ -16,7 +16,6 @@ Tables: DDL to be executed: -------------------------------------------------- --- Transaction Group #1 CREATE TABLE IF NOT EXISTS audit ( id SERIAL PRIMARY KEY, operation text NOT NULL, @@ -25,23 +24,8 @@ CREATE TABLE IF NOT EXISTS audit ( changed_at timestamptz DEFAULT CURRENT_TIMESTAMP ); --- Transaction Group #2 -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_changed_at ON audit (changed_at); +CREATE INDEX IF NOT EXISTS idx_audit_changed_at ON audit (changed_at); --- Transaction Group #3 --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_audit_changed_at'; - --- Transaction Group #4 CREATE OR REPLACE FUNCTION log_dml_operations() RETURNS trigger LANGUAGE plpgsql diff --git a/testdata/diff/migrate/v5/plan.json b/testdata/diff/migrate/v5/plan.json index f81046a9..7c6b473a 100644 --- a/testdata/diff/migrate/v5/plan.json +++ b/testdata/diff/migrate/v5/plan.json @@ -37,59 +37,19 @@ "type": "table", "operation": "create", "path": "public.employee_status_log" - } - ] - }, - { - "steps": [ - { - "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date);", - "type": "table.index", - "operation": "create", - "path": "public.employee_status_log.idx_employee_status_log_effective_date" - } - ] - }, - { - "steps": [ + }, { - "sql": "SELECT \n COALESCE(i.indisvalid, false) as done,\n CASE \n WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total\n ELSE 0\n END as progress\nFROM pg_class c\nLEFT JOIN pg_index i ON c.oid = i.indexrelid\nLEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid\nWHERE c.relname = 'idx_employee_status_log_effective_date';", - "directive": { - "type": "wait", - "message": "Creating index idx_employee_status_log_effective_date" - }, + "sql": "CREATE INDEX IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date);", "type": "table.index", "operation": "create", "path": "public.employee_status_log.idx_employee_status_log_effective_date" - } - ] - }, - { - "steps": [ - { - "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no);", - "type": "table.index", - "operation": "create", - "path": "public.employee_status_log.idx_employee_status_log_emp_no" - } - ] - }, - { - "steps": [ + }, { - "sql": "SELECT \n COALESCE(i.indisvalid, false) as done,\n CASE \n WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total\n ELSE 0\n END as progress\nFROM pg_class c\nLEFT JOIN pg_index i ON c.oid = i.indexrelid\nLEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid\nWHERE c.relname = 'idx_employee_status_log_emp_no';", - "directive": { - "type": "wait", - "message": "Creating index idx_employee_status_log_emp_no" - }, + "sql": "CREATE INDEX IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no);", "type": "table.index", "operation": "create", "path": "public.employee_status_log.idx_employee_status_log_emp_no" - } - ] - }, - { - "steps": [ + }, { "sql": "CREATE OR REPLACE TRIGGER employee_status_log_trigger\n AFTER INSERT OR UPDATE ON employee_status_log\n FOR EACH ROW\n EXECUTE FUNCTION log_dml_operations('hr', 'medium');", "type": "table.trigger", diff --git a/testdata/diff/migrate/v5/plan.sql b/testdata/diff/migrate/v5/plan.sql index f69bd315..dd92355c 100644 --- a/testdata/diff/migrate/v5/plan.sql +++ b/testdata/diff/migrate/v5/plan.sql @@ -18,33 +18,9 @@ CREATE TABLE IF NOT EXISTS employee_status_log ( notes text ); -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date); - --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_employee_status_log_effective_date'; - -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no); - --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_employee_status_log_emp_no'; +CREATE INDEX IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date); + +CREATE INDEX IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no); CREATE OR REPLACE TRIGGER employee_status_log_trigger AFTER INSERT OR UPDATE ON employee_status_log diff --git a/testdata/diff/migrate/v5/plan.txt b/testdata/diff/migrate/v5/plan.txt index bdd9d628..f885e362 100644 --- a/testdata/diff/migrate/v5/plan.txt +++ b/testdata/diff/migrate/v5/plan.txt @@ -28,7 +28,6 @@ Tables: DDL to be executed: -------------------------------------------------- --- Transaction Group #1 DROP PROCEDURE IF EXISTS simple_salary_update(integer, integer); DROP TABLE IF EXISTS title CASCADE; @@ -49,39 +48,10 @@ CREATE TABLE IF NOT EXISTS employee_status_log ( notes text ); --- Transaction Group #2 -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date); - --- Transaction Group #3 --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_employee_status_log_effective_date'; - --- Transaction Group #4 -CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no); - --- Transaction Group #5 --- pgschema:wait -SELECT - COALESCE(i.indisvalid, false) as done, - CASE - WHEN p.blocks_total > 0 THEN p.blocks_done * 100 / p.blocks_total - ELSE 0 - END as progress -FROM pg_class c -LEFT JOIN pg_index i ON c.oid = i.indexrelid -LEFT JOIN pg_stat_progress_create_index p ON c.oid = p.index_relid -WHERE c.relname = 'idx_employee_status_log_emp_no'; - --- Transaction Group #6 +CREATE INDEX IF NOT EXISTS idx_employee_status_log_effective_date ON employee_status_log (effective_date); + +CREATE INDEX IF NOT EXISTS idx_employee_status_log_emp_no ON employee_status_log (emp_no); + CREATE OR REPLACE TRIGGER employee_status_log_trigger AFTER INSERT OR UPDATE ON employee_status_log FOR EACH ROW