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
46 changes: 15 additions & 31 deletions cmd/migrate_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,17 @@ func runPlanAndApplyTest(t *testing.T, ctx context.Context, container *struct {
t.Fatalf("Failed to read setup.sql: %v", err)
}
if len(strings.TrimSpace(string(setupContent))) > 0 {
// Execute setup.sql to target database
if err := executeSQL(ctx, containerHost, portMapped, dbName, string(setupContent)); err != nil {
t.Fatalf("Failed to execute setup.sql: %v", err)
t.Fatalf("Failed to execute setup.sql to target database: %v", err)
}

// Also execute setup.sql to shared embedded postgres instance
// This ensures both databases have the setup objects available
embeddedConn, _, _, _, _, _ := testutil.ConnectToPostgres(t, sharedEmbeddedPG)
defer embeddedConn.Close()
if _, err := embeddedConn.ExecContext(ctx, string(setupContent)); err != nil {
t.Fatalf("Failed to execute setup.sql to embedded postgres: %v", err)
}
}
}
Expand All @@ -259,44 +268,19 @@ func runPlanAndApplyTest(t *testing.T, ctx context.Context, container *struct {
}

// STEP 2: Test plan command with new.sql as target
// If setup.sql exists, create a temporary combined file (setup + new)
schemaFileForPlan := tc.newFile
if _, err := os.Stat(tc.setupFile); err == nil {
setupContent, err := os.ReadFile(tc.setupFile)
if err != nil {
t.Fatalf("Failed to read setup.sql for plan: %v", err)
}
newContent, err := os.ReadFile(tc.newFile)
if err != nil {
t.Fatalf("Failed to read new.sql for plan: %v", err)
}

// Create temporary combined file
combinedContent := string(setupContent) + "\n\n" + string(newContent)
tmpFile, err := os.CreateTemp("", "pgschema_test_*.sql")
if err != nil {
t.Fatalf("Failed to create temporary file: %v", err)
}
defer tmpFile.Close()
defer os.Remove(tmpFile.Name())

if _, err := tmpFile.WriteString(combinedContent); err != nil {
t.Fatalf("Failed to write to temporary file: %v", err)
}
schemaFileForPlan = tmpFile.Name()
}

testPlanOutputs(t, container, dbName, schemaFileForPlan, tc.planSQLFile, tc.planJSONFile, tc.planTXTFile)
// Note: setup.sql has already been executed to both databases in STEP 0,
// so we only need to use new.sql for plan and apply operations
testPlanOutputs(t, container, dbName, tc.newFile, tc.planSQLFile, tc.planJSONFile, tc.planTXTFile)

if !*generate {
// STEP 3: Apply the migration using apply command
err = applySchemaChanges(containerHost, portMapped, dbName, container.User, container.Password, "public", schemaFileForPlan)
err = applySchemaChanges(containerHost, portMapped, dbName, container.User, container.Password, "public", tc.newFile)
if err != nil {
t.Fatalf("Failed to apply schema changes using pgschema apply: %v", err)
}

// STEP 4: Test idempotency - plan should produce no changes
secondPlanOutput, err := generatePlanSQLFormatted(containerHost, portMapped, dbName, container.User, container.Password, "public", schemaFileForPlan)
secondPlanOutput, err := generatePlanSQLFormatted(containerHost, portMapped, dbName, container.User, container.Password, "public", tc.newFile)
if err != nil {
t.Fatalf("Failed to generate plan SQL for idempotency check: %v", err)
}
Expand Down
28 changes: 11 additions & 17 deletions internal/diff/diff_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package diff

import (
"context"
"os"
"path/filepath"
"strings"
"testing"

"github.com/pgschema/pgschema/internal/postgres"
"github.com/pgschema/pgschema/ir"
"github.com/pgschema/pgschema/testutil"
)

Expand Down Expand Up @@ -53,17 +53,6 @@ func buildSQLFromSteps(diffs []Diff) string {
return sqlOutput.String()
}

// parseSQLWithSetup applies optional setup SQL before main SQL, then parses to IR
// This allows tests to have shared setup (e.g., types in different schemas) before the main schema
func parseSQLWithSetup(t *testing.T, setup, sql string) *ir.IR {
t.Helper()

// Combine setup and main SQL with separator if both exist
combinedSQL := setup + "\n\n" + sql

return testutil.ParseSQLToIR(t, sharedTestPostgres, combinedSQL, "public")
}

// TestDiffFromFiles runs file-based diff tests from testdata directory.
// It walks through the testdata/diff directory structure looking for test cases
// that contain old.sql, new.sql, and plan.sql files. For each test case,
Expand Down Expand Up @@ -165,13 +154,18 @@ func runFileBasedDiffTest(t *testing.T, oldFile, newFile, diffFile, testName str

// Read optional setup.sql (for cross-schema setup, extension types, etc.)
setupFile := filepath.Join(filepath.Dir(oldFile), "setup.sql")
var setupSQL string
if _, err := os.Stat(setupFile); err == nil {
setupContent, err := os.ReadFile(setupFile)
if err != nil {
t.Fatalf("Failed to read setup.sql: %v", err)
}
setupSQL = string(setupContent)

// Execute setup.sql once before parsing old/new SQL
// This creates shared infrastructure (e.g., custom types in utils schema)
// that will be available to both old.sql and new.sql
if _, err := conn.ExecContext(context.Background(), string(setupContent)); err != nil {
t.Fatalf("Failed to execute setup.sql: %v", err)
}
}

// Read old DDL
Expand All @@ -192,9 +186,9 @@ func runFileBasedDiffTest(t *testing.T, oldFile, newFile, diffFile, testName str
t.Fatalf("Failed to read plan.sql: %v", err)
}

// Parse DDL to IR (with optional setup SQL)
oldIR := parseSQLWithSetup(t, setupSQL, string(oldDDL))
newIR := parseSQLWithSetup(t, setupSQL, string(newDDL))
// Parse DDL to IR (setup.sql already applied, so just parse old/new SQL directly)
oldIR := testutil.ParseSQLToIR(t, sharedTestPostgres, string(oldDDL), "public")
newIR := testutil.ParseSQLToIR(t, sharedTestPostgres, string(newDDL), "public")

// Run diff
diffs := GenerateMigration(oldIR, newIR, "public")
Expand Down
14 changes: 14 additions & 0 deletions ir/queries/queries.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE users ADD COLUMN metadata utils.hstore;
ALTER TABLE users ADD COLUMN fqdn utils.citext NOT NULL;
ALTER TABLE users ADD COLUMN description utils.custom_text;
ALTER TABLE users ADD COLUMN status utils.custom_enum DEFAULT 'active';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- New state: Add columns using extension types, custom domain, and enum
-- Types are created via setup.sql
-- This tests GitHub #144 fix and PR #145 functionality:
-- - Extension types in external schemas (hstore) should be qualified
-- - Custom domains and enums in external schemas should be qualified

CREATE TABLE public.users (
id bigint PRIMARY KEY,
username text NOT NULL,
created_at timestamp DEFAULT CURRENT_TIMESTAMP,
-- Extension type from utils schema: must be qualified
metadata utils.hstore,
fqdn utils.citext NOT NULL,
-- Custom domain from utils schema: must be qualified
description utils.custom_text,
-- Enum type from utils schema: must be qualified
status utils.custom_enum DEFAULT 'active'
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"version": "1.0.0",
"pgschema_version": "1.4.1",
"created_at": "1970-01-01T00:00:00Z",
"source_fingerprint": {
"hash": "a9e46b459eae7b043790b1744ca6210ad942beafbef337ae3b787e120a2474eb"
},
"groups": [
{
"steps": [
{
"sql": "ALTER TABLE users ADD COLUMN metadata utils.hstore;",
"type": "table.column",
"operation": "create",
"path": "public.users.metadata"
},
{
"sql": "ALTER TABLE users ADD COLUMN fqdn utils.citext NOT NULL;",
"type": "table.column",
"operation": "create",
"path": "public.users.fqdn"
},
{
"sql": "ALTER TABLE users ADD COLUMN description utils.custom_text;",
"type": "table.column",
"operation": "create",
"path": "public.users.description"
},
{
"sql": "ALTER TABLE users ADD COLUMN status utils.custom_enum DEFAULT 'active';",
"type": "table.column",
"operation": "create",
"path": "public.users.status"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ALTER TABLE users ADD COLUMN metadata utils.hstore;

ALTER TABLE users ADD COLUMN fqdn utils.citext NOT NULL;

ALTER TABLE users ADD COLUMN description utils.custom_text;

ALTER TABLE users ADD COLUMN status utils.custom_enum DEFAULT 'active';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Plan: 1 to modify.

Summary by type:
tables: 1 to modify

Tables:
~ users
+ description (column)
+ fqdn (column)
+ metadata (column)
+ status (column)

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

ALTER TABLE users ADD COLUMN metadata utils.hstore;

ALTER TABLE users ADD COLUMN fqdn utils.citext NOT NULL;

ALTER TABLE users ADD COLUMN description utils.custom_text;

ALTER TABLE users ADD COLUMN status utils.custom_enum DEFAULT 'active';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Setup: Create extension type, custom domain, and enum to test type qualification
-- This reproduces GitHub #144 and validates PR #145 fixes
-- All types from external schemas (not the target schema) should be schema-qualified
-- This includes:
-- - Extension types (hstore)
-- - Custom domains and enums

CREATE SCHEMA IF NOT EXISTS utils;

CREATE DOMAIN utils.custom_text AS text;

CREATE TYPE utils.custom_enum AS ENUM ('active', 'inactive', 'pending');

-- hstore type stays in utils schema
CREATE EXTENSION IF NOT EXISTS hstore SCHEMA utils;
CREATE EXTENSION IF NOT EXISTS citext SCHEMA utils;
3 changes: 0 additions & 3 deletions testdata/diff/create_table/add_column_custom_type/diff.sql

This file was deleted.

13 changes: 0 additions & 13 deletions testdata/diff/create_table/add_column_custom_type/new.sql

This file was deleted.

26 changes: 0 additions & 26 deletions testdata/diff/create_table/add_column_custom_type/plan.json

This file was deleted.

3 changes: 0 additions & 3 deletions testdata/diff/create_table/add_column_custom_type/plan.sql

This file was deleted.

16 changes: 0 additions & 16 deletions testdata/diff/create_table/add_column_custom_type/plan.txt

This file was deleted.

14 changes: 0 additions & 14 deletions testdata/diff/create_table/add_column_custom_type/setup.sql

This file was deleted.