diff --git a/internal/plan/plan.go b/internal/plan/plan.go index d1c67044..cdb4b49c 100644 --- a/internal/plan/plan.go +++ b/internal/plan/plan.go @@ -34,8 +34,9 @@ type Directive struct { // Step represents a single execution step with SQL and optional directive type Step struct { - SQL string `json:"sql"` - Directive *Directive `json:"directive,omitempty"` + SQL string `json:"sql"` + Directive *Directive `json:"directive,omitempty"` + CanRunInTransaction bool `json:"can_run_in_transaction"` // Metadata for summary generation Type string `json:"type,omitempty"` // e.g., "table", "index" Operation string `json:"operation,omitempty"` // e.g., "create", "alter", "drop" @@ -174,11 +175,12 @@ func groupDiffs(diffs []diff.Diff) []ExecutionGroup { // For operations with rewrites, create one step per rewrite statement for _, rewriteStep := range rewriteSteps { step := Step{ - SQL: rewriteStep.SQL, - Type: d.Type.String(), - Operation: d.Operation.String(), - Path: d.Path, - Directive: rewriteStep.Directive, + SQL: rewriteStep.SQL, + Type: d.Type.String(), + Operation: d.Operation.String(), + Path: d.Path, + Directive: rewriteStep.Directive, + CanRunInTransaction: rewriteStep.CanRunInTransaction, } // Check if this step needs isolation (has directive or cannot run in transaction) @@ -202,10 +204,11 @@ func groupDiffs(diffs []diff.Diff) []ExecutionGroup { // For operations without rewrites, create one step per canonical statement for _, stmt := range d.Statements { step := Step{ - SQL: stmt.SQL, - Type: d.Type.String(), - Operation: d.Operation.String(), - Path: d.Path, + SQL: stmt.SQL, + Type: d.Type.String(), + Operation: d.Operation.String(), + Path: d.Path, + CanRunInTransaction: true, } // Canonical statements don't have directives transactionalSteps = append(transactionalSteps, step) @@ -385,6 +388,20 @@ func FromJSON(jsonData []byte) (*Plan, error) { if err := json.Unmarshal(jsonData, &plan); err != nil { return nil, fmt.Errorf("failed to unmarshal plan JSON: %w", err) } + + // Backward compat: old plans may lack can_run_in_transaction. + // JSON unmarshaling defaults missing bools to false, but the correct + // default for most steps is true (only CONCURRENTLY steps are non-transactional). + // We detect missing-by-absence-of-CONCURRENTLY, which covers the canonical case. + for i := range plan.Groups { + for j := range plan.Groups[i].Steps { + s := &plan.Groups[i].Steps[j] + if !s.CanRunInTransaction && !strings.Contains(s.SQL, "CONCURRENTLY") { + s.CanRunInTransaction = true + } + } + } + return &plan, nil } diff --git a/testdata/diff/migrate/v1/plan.json b/testdata/diff/migrate/v1/plan.json index 2db2222a..08c6fc62 100644 --- a/testdata/diff/migrate/v1/plan.json +++ b/testdata/diff/migrate/v1/plan.json @@ -12,37 +12,43 @@ "sql": "CREATE TABLE IF NOT EXISTS department (\n dept_no text,\n dept_name text NOT NULL,\n CONSTRAINT department_pkey PRIMARY KEY (dept_no)\n);", "type": "table", "operation": "create", - "path": "public.department" + "path": "public.department", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS employee (\n emp_no SERIAL,\n birth_date date NOT NULL,\n first_name text NOT NULL,\n last_name text NOT NULL,\n gender text NOT NULL,\n hire_date date NOT NULL,\n CONSTRAINT employee_pkey PRIMARY KEY (emp_no)\n);", "type": "table", "operation": "create", - "path": "public.employee" + "path": "public.employee", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS dept_emp (\n emp_no integer,\n dept_no text,\n from_date date NOT NULL,\n to_date date NOT NULL,\n CONSTRAINT dept_emp_pkey PRIMARY KEY (emp_no, dept_no),\n CONSTRAINT dept_emp_dept_no_fkey FOREIGN KEY (dept_no) REFERENCES department (dept_no),\n CONSTRAINT dept_emp_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no)\n);", "type": "table", "operation": "create", - "path": "public.dept_emp" + "path": "public.dept_emp", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS dept_manager (\n emp_no integer,\n dept_no text,\n from_date date NOT NULL,\n to_date date NOT NULL,\n CONSTRAINT dept_manager_pkey PRIMARY KEY (emp_no, dept_no),\n CONSTRAINT dept_manager_dept_no_fkey FOREIGN KEY (dept_no) REFERENCES department (dept_no),\n CONSTRAINT dept_manager_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no)\n);", "type": "table", "operation": "create", - "path": "public.dept_manager" + "path": "public.dept_manager", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS salary (\n emp_no integer,\n amount integer NOT NULL,\n from_date date,\n to_date date NOT NULL,\n CONSTRAINT salary_pkey PRIMARY KEY (emp_no, from_date),\n CONSTRAINT salary_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no)\n);", "type": "table", "operation": "create", - "path": "public.salary" + "path": "public.salary", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS title (\n emp_no integer,\n title text,\n from_date date,\n to_date date,\n CONSTRAINT title_pkey PRIMARY KEY (emp_no, title, from_date),\n CONSTRAINT title_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no)\n);", "type": "table", "operation": "create", - "path": "public.title" + "path": "public.title", + "can_run_in_transaction": true } ] } diff --git a/testdata/diff/migrate/v2/plan.json b/testdata/diff/migrate/v2/plan.json index b90e8fa7..bbb9335b 100644 --- a/testdata/diff/migrate/v2/plan.json +++ b/testdata/diff/migrate/v2/plan.json @@ -12,91 +12,106 @@ "sql": "ALTER TABLE department\nADD CONSTRAINT department_dept_name_key UNIQUE (dept_name);", "type": "table.constraint", "operation": "create", - "path": "public.department.department_dept_name_key" + "path": "public.department.department_dept_name_key", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp DROP CONSTRAINT dept_emp_dept_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.dept_emp.dept_emp_dept_no_fkey" + "path": "public.dept_emp.dept_emp_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp\nADD CONSTRAINT dept_emp_dept_no_fkey FOREIGN KEY (dept_no) REFERENCES department (dept_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.dept_emp.dept_emp_dept_no_fkey" + "path": "public.dept_emp.dept_emp_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp VALIDATE CONSTRAINT dept_emp_dept_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.dept_emp.dept_emp_dept_no_fkey" + "path": "public.dept_emp.dept_emp_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp DROP CONSTRAINT dept_emp_emp_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.dept_emp.dept_emp_emp_no_fkey" + "path": "public.dept_emp.dept_emp_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp\nADD CONSTRAINT dept_emp_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.dept_emp.dept_emp_emp_no_fkey" + "path": "public.dept_emp.dept_emp_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_emp VALIDATE CONSTRAINT dept_emp_emp_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.dept_emp.dept_emp_emp_no_fkey" + "path": "public.dept_emp.dept_emp_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager DROP CONSTRAINT dept_manager_dept_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.dept_manager.dept_manager_dept_no_fkey" + "path": "public.dept_manager.dept_manager_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager\nADD CONSTRAINT dept_manager_dept_no_fkey FOREIGN KEY (dept_no) REFERENCES department (dept_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.dept_manager.dept_manager_dept_no_fkey" + "path": "public.dept_manager.dept_manager_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager VALIDATE CONSTRAINT dept_manager_dept_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.dept_manager.dept_manager_dept_no_fkey" + "path": "public.dept_manager.dept_manager_dept_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager DROP CONSTRAINT dept_manager_emp_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.dept_manager.dept_manager_emp_no_fkey" + "path": "public.dept_manager.dept_manager_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager\nADD CONSTRAINT dept_manager_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.dept_manager.dept_manager_emp_no_fkey" + "path": "public.dept_manager.dept_manager_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE dept_manager VALIDATE CONSTRAINT dept_manager_emp_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.dept_manager.dept_manager_emp_no_fkey" + "path": "public.dept_manager.dept_manager_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE employee\nADD CONSTRAINT employee_gender_check CHECK (gender IN ('M'::text, 'F'::text)) NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.employee.employee_gender_check" + "path": "public.employee.employee_gender_check", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE employee VALIDATE CONSTRAINT employee_gender_check;", "type": "table.constraint", "operation": "create", - "path": "public.employee.employee_gender_check" + "path": "public.employee.employee_gender_check", + "can_run_in_transaction": true } ] }, @@ -106,7 +121,8 @@ "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_employee_hire_date ON employee (hire_date);", "type": "table.index", "operation": "create", - "path": "public.employee.idx_employee_hire_date" + "path": "public.employee.idx_employee_hire_date", + "can_run_in_transaction": false } ] }, @@ -120,7 +136,8 @@ }, "type": "table.index", "operation": "create", - "path": "public.employee.idx_employee_hire_date" + "path": "public.employee.idx_employee_hire_date", + "can_run_in_transaction": true } ] }, @@ -130,19 +147,22 @@ "sql": "ALTER TABLE salary DROP CONSTRAINT salary_emp_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.salary.salary_emp_no_fkey" + "path": "public.salary.salary_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE salary\nADD CONSTRAINT salary_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.salary.salary_emp_no_fkey" + "path": "public.salary.salary_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE salary VALIDATE CONSTRAINT salary_emp_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.salary.salary_emp_no_fkey" + "path": "public.salary.salary_emp_no_fkey", + "can_run_in_transaction": true } ] }, @@ -152,7 +172,8 @@ "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_salary_amount ON salary (amount);", "type": "table.index", "operation": "create", - "path": "public.salary.idx_salary_amount" + "path": "public.salary.idx_salary_amount", + "can_run_in_transaction": false } ] }, @@ -166,7 +187,8 @@ }, "type": "table.index", "operation": "create", - "path": "public.salary.idx_salary_amount" + "path": "public.salary.idx_salary_amount", + "can_run_in_transaction": true } ] }, @@ -176,19 +198,22 @@ "sql": "ALTER TABLE title DROP CONSTRAINT title_emp_no_fkey;", "type": "table.constraint", "operation": "drop", - "path": "public.title.title_emp_no_fkey" + "path": "public.title.title_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE title\nADD CONSTRAINT title_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no) ON DELETE CASCADE NOT VALID;", "type": "table.constraint", "operation": "create", - "path": "public.title.title_emp_no_fkey" + "path": "public.title.title_emp_no_fkey", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE title VALIDATE CONSTRAINT title_emp_no_fkey;", "type": "table.constraint", "operation": "create", - "path": "public.title.title_emp_no_fkey" + "path": "public.title.title_emp_no_fkey", + "can_run_in_transaction": true } ] } diff --git a/testdata/diff/migrate/v3/plan.json b/testdata/diff/migrate/v3/plan.json index 038f4b1d..a2ec4220 100644 --- a/testdata/diff/migrate/v3/plan.json +++ b/testdata/diff/migrate/v3/plan.json @@ -12,25 +12,29 @@ "sql": "CREATE TABLE IF NOT EXISTS audit (\n id SERIAL,\n operation text NOT NULL,\n query text,\n user_name text NOT NULL,\n changed_at timestamptz DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT audit_pkey PRIMARY KEY (id)\n);", "type": "table", "operation": "create", - "path": "public.audit" + "path": "public.audit", + "can_run_in_transaction": true }, { "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" + "path": "public.audit.idx_audit_changed_at", + "can_run_in_transaction": true }, { "sql": "CREATE OR REPLACE FUNCTION log_dml_operations()\nRETURNS trigger\nLANGUAGE plpgsql\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", "operation": "create", - "path": "public.log_dml_operations" + "path": "public.log_dml_operations", + "can_run_in_transaction": true }, { "sql": "CREATE OR REPLACE TRIGGER salary_log_trigger\n AFTER UPDATE OR DELETE ON salary\n FOR EACH ROW\n EXECUTE FUNCTION log_dml_operations();", "type": "table.trigger", "operation": "create", - "path": "public.salary.salary_log_trigger" + "path": "public.salary.salary_log_trigger", + "can_run_in_transaction": true } ] } diff --git a/testdata/diff/migrate/v4/plan.json b/testdata/diff/migrate/v4/plan.json index 09771770..e9ec2ccf 100644 --- a/testdata/diff/migrate/v4/plan.json +++ b/testdata/diff/migrate/v4/plan.json @@ -12,31 +12,36 @@ "sql": "CREATE OR REPLACE PROCEDURE simple_salary_update(\n IN p_emp_no integer,\n IN p_amount integer\n)\nLANGUAGE plpgsql\nAS $$\nBEGIN\n -- Simple update of salary amount\n UPDATE salary \n SET amount = p_amount \n WHERE emp_no = p_emp_no \n AND to_date = '9999-01-01';\n \n RAISE NOTICE 'Updated salary for employee % to $%', p_emp_no, p_amount;\nEND;\n$$;", "type": "procedure", "operation": "create", - "path": "public.simple_salary_update" + "path": "public.simple_salary_update", + "can_run_in_transaction": true }, { "sql": "CREATE OR REPLACE VIEW dept_emp_latest_date AS\n SELECT emp_no,\n max(from_date) AS from_date,\n max(to_date) AS to_date\n FROM dept_emp\n GROUP BY emp_no;", "type": "view", "operation": "create", - "path": "public.dept_emp_latest_date" + "path": "public.dept_emp_latest_date", + "can_run_in_transaction": true }, { "sql": "CREATE OR REPLACE VIEW current_dept_emp AS\n SELECT l.emp_no,\n d.dept_no,\n l.from_date,\n l.to_date\n FROM dept_emp d\n JOIN dept_emp_latest_date l ON d.emp_no = l.emp_no AND d.from_date = l.from_date AND l.to_date = d.to_date;", "type": "view", "operation": "create", - "path": "public.current_dept_emp" + "path": "public.current_dept_emp", + "can_run_in_transaction": true }, { "sql": "CREATE MATERIALIZED VIEW IF NOT EXISTS employee_salary_summary AS\n SELECT d.dept_no,\n d.dept_name,\n count(DISTINCT e.emp_no) AS employee_count,\n avg(s.amount) AS avg_salary,\n max(s.amount) AS max_salary,\n min(s.amount) AS min_salary\n FROM employee e\n JOIN dept_emp de ON e.emp_no = de.emp_no\n JOIN department d ON de.dept_no = d.dept_no\n JOIN salary s ON e.emp_no = s.emp_no\n WHERE de.to_date = '9999-01-01'::date AND s.to_date = '9999-01-01'::date\n GROUP BY d.dept_no, d.dept_name;", "type": "materialized_view", "operation": "create", - "path": "public.employee_salary_summary" + "path": "public.employee_salary_summary", + "can_run_in_transaction": true }, { "sql": "CREATE INDEX IF NOT EXISTS idx_emp_salary_summary_dept_no ON employee_salary_summary (dept_no);", "type": "materialized_view.index", "operation": "create", - "path": "public.employee_salary_summary.idx_emp_salary_summary_dept_no" + "path": "public.employee_salary_summary.idx_emp_salary_summary_dept_no", + "can_run_in_transaction": true } ] }, @@ -46,7 +51,8 @@ "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_operation ON audit (operation);", "type": "table.index", "operation": "create", - "path": "public.audit.idx_audit_operation" + "path": "public.audit.idx_audit_operation", + "can_run_in_transaction": false } ] }, @@ -60,7 +66,8 @@ }, "type": "table.index", "operation": "create", - "path": "public.audit.idx_audit_operation" + "path": "public.audit.idx_audit_operation", + "can_run_in_transaction": true } ] }, @@ -70,7 +77,8 @@ "sql": "CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_audit_username ON audit (user_name);", "type": "table.index", "operation": "create", - "path": "public.audit.idx_audit_username" + "path": "public.audit.idx_audit_username", + "can_run_in_transaction": false } ] }, @@ -84,7 +92,8 @@ }, "type": "table.index", "operation": "create", - "path": "public.audit.idx_audit_username" + "path": "public.audit.idx_audit_username", + "can_run_in_transaction": true } ] }, @@ -94,13 +103,15 @@ "sql": "CREATE OR REPLACE TRIGGER salary_log_trigger\n AFTER UPDATE OR DELETE ON salary\n FOR EACH ROW\n EXECUTE FUNCTION log_dml_operations('payroll', 'high');", "type": "table.trigger", "operation": "alter", - "path": "public.salary.salary_log_trigger" + "path": "public.salary.salary_log_trigger", + "can_run_in_transaction": true }, { "sql": "CREATE OR REPLACE FUNCTION log_dml_operations()\nRETURNS trigger\nLANGUAGE plpgsql\nVOLATILE\nAS $$\nDECLARE\n table_category TEXT;\n log_level TEXT;\nBEGIN\n -- Get arguments passed from trigger (if any)\n -- TG_ARGV[0] is the first argument, TG_ARGV[1] is the second\n table_category := COALESCE(TG_ARGV[0], 'default');\n log_level := COALESCE(TG_ARGV[1], 'standard');\n\n IF (TG_OP = 'INSERT') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'INSERT [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'UPDATE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'UPDATE [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN NEW;\n ELSIF (TG_OP = 'DELETE') THEN\n INSERT INTO audit (operation, query, user_name)\n VALUES (\n 'DELETE [' || table_category || ':' || log_level || ']',\n current_query(),\n current_user\n );\n RETURN OLD;\n END IF;\n RETURN NULL;\nEND;\n$$;", "type": "function", "operation": "alter", - "path": "public.log_dml_operations" + "path": "public.log_dml_operations", + "can_run_in_transaction": true } ] } diff --git a/testdata/diff/migrate/v5/plan.json b/testdata/diff/migrate/v5/plan.json index a75a4230..ff2bfadd 100644 --- a/testdata/diff/migrate/v5/plan.json +++ b/testdata/diff/migrate/v5/plan.json @@ -12,73 +12,85 @@ "sql": "DROP PROCEDURE IF EXISTS simple_salary_update(IN p_emp_no integer, IN p_amount integer);", "type": "procedure", "operation": "drop", - "path": "public.simple_salary_update" + "path": "public.simple_salary_update", + "can_run_in_transaction": true }, { "sql": "DROP TABLE IF EXISTS title CASCADE;", "type": "table", "operation": "drop", - "path": "public.title" + "path": "public.title", + "can_run_in_transaction": true }, { "sql": "DROP TABLE IF EXISTS dept_manager CASCADE;", "type": "table", "operation": "drop", - "path": "public.dept_manager" + "path": "public.dept_manager", + "can_run_in_transaction": true }, { "sql": "CREATE TYPE employee_status AS ENUM (\n 'active',\n 'inactive',\n 'terminated'\n);", "type": "type", "operation": "create", - "path": "public.employee_status" + "path": "public.employee_status", + "can_run_in_transaction": true }, { "sql": "CREATE TABLE IF NOT EXISTS employee_status_log (\n id SERIAL,\n emp_no integer NOT NULL,\n status employee_status NOT NULL,\n effective_date date DEFAULT CURRENT_DATE NOT NULL,\n notes text,\n CONSTRAINT employee_status_log_pkey PRIMARY KEY (id),\n CONSTRAINT employee_status_log_emp_no_fkey FOREIGN KEY (emp_no) REFERENCES employee (emp_no) ON DELETE CASCADE\n);", "type": "table", "operation": "create", - "path": "public.employee_status_log" + "path": "public.employee_status_log", + "can_run_in_transaction": true }, { "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" + "path": "public.employee_status_log.idx_employee_status_log_effective_date", + "can_run_in_transaction": true }, { "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" + "path": "public.employee_status_log.idx_employee_status_log_emp_no", + "can_run_in_transaction": true }, { "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", "operation": "create", - "path": "public.employee_status_log.employee_status_log_trigger" + "path": "public.employee_status_log.employee_status_log_trigger", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE audit ENABLE ROW LEVEL SECURITY;", "type": "table.rls", "operation": "create", - "path": "public.audit" + "path": "public.audit", + "can_run_in_transaction": true }, { "sql": "CREATE POLICY audit_insert_system ON audit FOR INSERT TO PUBLIC WITH CHECK (true);", "type": "table.policy", "operation": "create", - "path": "public.audit.audit_insert_system" + "path": "public.audit.audit_insert_system", + "can_run_in_transaction": true }, { "sql": "CREATE POLICY audit_user_isolation ON audit TO PUBLIC USING (user_name = CURRENT_USER);", "type": "table.policy", "operation": "create", - "path": "public.audit.audit_user_isolation" + "path": "public.audit.audit_user_isolation", + "can_run_in_transaction": true }, { "sql": "ALTER TABLE employee ADD COLUMN status employee_status DEFAULT 'active'::employee_status NOT NULL;", "type": "table.column", "operation": "create", - "path": "public.employee.status" + "path": "public.employee.status", + "can_run_in_transaction": true } ] }