Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic Extension support. #43

Merged
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
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pg-schema-diff plan --dsn "postgres://postgres:postgres@localhost:5432/postgres"
- Partitions
- Functions/Triggers (functions created by extensions are ignored)
- Sequences
- Extensions
Copy link
Collaborator

Choose a reason for hiding this comment

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

🥳


*A comprehensive set of features to ensure the safety of planned migrations:*
- Dangerous operations are flagged as hazards and must be approved before a migration can be applied.
Expand Down Expand Up @@ -142,5 +143,3 @@ an object, it will be treated as a drop and an add
# Contributing
This project is in its early stages. We appreciate all the feature/bug requests we receive, but we have limited cycles
to review direct code contributions at this time. See [Contributing](CONTRIBUTING.md) to learn more.


10 changes: 2 additions & 8 deletions internal/migration_acceptance_tests/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@ func (suite *acceptanceTestSuite) runTestCases(acceptanceTestCases []acceptanceT
}

func (suite *acceptanceTestSuite) runSubtest(tc acceptanceTestCase, expects expectations, planOpts []diff.PlanOpt) {
// onDbInitQueries will be run on both the old database before the migration and the new database before pg_dump
onDbInitQueries := []string{
// Enable an extension to enforce that diffing works with extensions enabled
`CREATE EXTENSION amcheck;`,
}

// normalize the subtest
if expects.outputState == nil {
expects.outputState = tc.newSchemaDDL
Expand All @@ -98,7 +92,7 @@ func (suite *acceptanceTestSuite) runSubtest(tc acceptanceTestCase, expects expe
suite.Require().NoError(err)
defer oldDb.DropDB()
// Apply the old schema
suite.Require().NoError(applyDDL(oldDb, append(onDbInitQueries, tc.oldSchemaDDL...)))
suite.Require().NoError(applyDDL(oldDb, tc.oldSchemaDDL))

// Migrate the old DB
oldDBConnPool, err := sql.Open("pgx", oldDb.GetDSN())
Expand Down Expand Up @@ -144,7 +138,7 @@ func (suite *acceptanceTestSuite) runSubtest(tc acceptanceTestCase, expects expe
oldDbDump, err := pgdump.GetDump(oldDb, pgdump.WithSchemaOnly())
suite.Require().NoError(err)

newDbDump := suite.directlyRunDDLAndGetDump(append(onDbInitQueries, expects.outputState...))
newDbDump := suite.directlyRunDDLAndGetDump(expects.outputState)
suite.Equal(newDbDump, oldDbDump, prettySprintPlan(plan))

// Make sure no diff is found if we try to regenerate a plan
Expand Down
72 changes: 72 additions & 0 deletions internal/migration_acceptance_tests/extensions_cases_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package migration_acceptance_tests

import "github.com/stripe/pg-schema-diff/pkg/diff"

var extensionAcceptanceTestCases = []acceptanceTestCase{
{
name: "no-op",
oldSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;
CREATE EXTENSION amcheck;
`,
},
newSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;
CREATE EXTENSION amcheck;
`,
},
vanillaExpectations: expectations{
empty: true,
},
dataPackingExpectations: expectations{
empty: true,
},
},
{
name: "create multiple extensions",
oldSchemaDDL: []string{},
newSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;
CREATE EXTENSION amcheck;
`,
},
},
{
name: "drop one extension",
oldSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;
CREATE EXTENSION amcheck;
`,
},
newSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;
`,
},
expectedHazardTypes: []diff.MigrationHazardType{diff.MigrationHazardTypeHasUntrackableDependencies},
},
{
name: "upgrade an extension implicitly and explicitly",
oldSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm WITH VERSION '1.5';
CREATE EXTENSION amcheck WITH VERSION '1.3';
`,
},
newSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm WITH VERSION '1.6';
CREATE EXTENSION AMCHECK;
`,
},
expectedHazardTypes: []diff.MigrationHazardType{diff.MigrationHazardTypeExtensionVersionUpgrade},
},
}

func (suite *acceptanceTestSuite) TestExtensionAcceptanceTestCases() {
alexrhee-stripe marked this conversation as resolved.
Show resolved Hide resolved
suite.runTestCases(extensionAcceptanceTestCases)
}
19 changes: 19 additions & 0 deletions internal/migration_acceptance_tests/function_cases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,25 @@ var functionAcceptanceTestCases = []acceptanceTestCase{
`},
expectedHazardTypes: []diff.MigrationHazardType{diff.MigrationHazardTypeHasUntrackableDependencies},
},
{
name: "Create function with an extensinon that also creates functions installed",
oldSchemaDDL: []string{
`
CREATE EXTENSION amcheck;
`,
},
newSchemaDDL: []string{
`
CREATE EXTENSION amcheck;

CREATE FUNCTION add(a integer, b integer) RETURNS integer
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT
RETURN a + b;
`,
},
},
{
name: "Drop functions (with conflicting names)",
oldSchemaDDL: []string{
Expand Down
25 changes: 20 additions & 5 deletions internal/migration_acceptance_tests/schema_cases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ var schemaAcceptanceTests = []acceptanceTestCase{
name: "No-op",
oldSchemaDDL: []string{
`
CREATE EXTENSION amcheck;

CREATE TABLE fizz(
);

Expand Down Expand Up @@ -71,6 +73,8 @@ var schemaAcceptanceTests = []acceptanceTestCase{
},
newSchemaDDL: []string{
`
CREATE EXTENSION amcheck;

CREATE TABLE fizz(
);

Expand Down Expand Up @@ -138,9 +142,11 @@ var schemaAcceptanceTests = []acceptanceTestCase{
},
},
{
name: "Drop table, Add Table, Drop seq, Add Seq, Drop Funcs, Add Funcs, Drop Triggers, Add Triggers",
name: "Drop table, Add Table, Drop Seq, Add Seq, Drop Funcs, Add Funcs, Drop Triggers, Add Triggers, Create Extension, Drop Extension, Create Index Using Extension",
oldSchemaDDL: []string{
`
CREATE EXTENSION amcheck;

CREATE TABLE fizz(
);

Expand Down Expand Up @@ -206,6 +212,8 @@ var schemaAcceptanceTests = []acceptanceTestCase{
},
newSchemaDDL: []string{
`
CREATE EXTENSION pg_trgm;

CREATE TABLE fizz(
);

Expand Down Expand Up @@ -262,12 +270,14 @@ var schemaAcceptanceTests = []acceptanceTestCase{
foo INT,
bar DOUBLE PRECISION NOT NULL DEFAULT 8.8,
fizz timestamptz DEFAULT CURRENT_TIMESTAMP,
buzz REAL NOT NULL
buzz REAL NOT NULL,
quux TEXT
);
ALTER TABLE bar ADD CONSTRAINT "FOO_CHECK" CHECK ( foo < bar );
CREATE INDEX bar_normal_idx ON bar(bar);
CREATE INDEX bar_another_normal_id ON bar(bar DESC, fizz DESC);
CREATE UNIQUE INDEX bar_unique_idx on bar(fizz, buzz);
CREATE UNIQUE INDEX bar_unique_idx ON bar(fizz, buzz);
CREATE INDEX gin_index ON bar USING gin (quux gin_trgm_ops);

CREATE FUNCTION check_content() RETURNS TRIGGER AS $$
BEGIN
Expand All @@ -286,15 +296,18 @@ var schemaAcceptanceTests = []acceptanceTestCase{
expectedHazardTypes: []diff.MigrationHazardType{
diff.MigrationHazardTypeDeletesData,
diff.MigrationHazardTypeHasUntrackableDependencies,
diff.MigrationHazardTypeIndexBuild,
},
dataPackingExpectations: expectations{
outputState: []string{
`
CREATE EXTENSION pg_trgm;

CREATE TABLE fizz(
);

CREATE SEQUENCE new_foobar_sequence
AS SMALLINT
AS SMALLINT
INCREMENT BY 4
MINVALUE 10 MAXVALUE 200
START WITH 20 CACHE 10 NO CYCLE
Expand Down Expand Up @@ -346,12 +359,14 @@ var schemaAcceptanceTests = []acceptanceTestCase{
foo INT,
bar DOUBLE PRECISION NOT NULL DEFAULT 8.8,
fizz timestamptz DEFAULT CURRENT_TIMESTAMP,
buzz REAL NOT NULL
buzz REAL NOT NULL,
quux TEXT
);
ALTER TABLE bar ADD CONSTRAINT "FOO_CHECK" CHECK ( foo < bar );
CREATE INDEX bar_normal_idx ON bar(bar);
CREATE INDEX bar_another_normal_id ON bar(bar DESC, fizz DESC);
CREATE UNIQUE INDEX bar_unique_idx on bar(fizz, buzz);
CREATE INDEX gin_index ON bar USING gin (quux gin_trgm_ops);

CREATE FUNCTION check_content() RETURNS TRIGGER AS $$
BEGIN
Expand Down
12 changes: 12 additions & 0 deletions internal/queries/queries.sql
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,15 @@ AND NOT EXISTS (
AND ext_depend.objid = pg_seq.seqrelid
AND ext_depend.deptype = 'e'
);

-- name: GetExtensions :many
SELECT
ext.oid,
ext.extname::TEXT AS extension_name,
ext.extversion AS extension_version,
extension_namespace.nspname::TEXT AS schema_name
FROM pg_catalog.pg_namespace AS extension_namespace
INNER JOIN
pg_catalog.pg_extension AS ext
ON ext.extnamespace = extension_namespace.oid
WHERE extension_namespace.nspname = 'public';
48 changes: 48 additions & 0 deletions internal/queries/queries.sql.go

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

49 changes: 40 additions & 9 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ func (o SchemaQualifiedName) IsEmpty() bool {
}

type Schema struct {
Tables []Table
Indexes []Index
Extensions []Extension
alexrhee-stripe marked this conversation as resolved.
Show resolved Hide resolved
Tables []Table
Indexes []Index

Sequences []Sequence
Functions []Function
Expand All @@ -70,8 +71,8 @@ func (s Schema) Normalize() Schema {
s.Tables = normTables

s.Indexes = sortSchemaObjectsByName(s.Indexes)

s.Sequences = sortSchemaObjectsByName(s.Sequences)
s.Extensions = sortSchemaObjectsByName(s.Extensions)

var normFunctions []Function
for _, function := range sortSchemaObjectsByName(s.Functions) {
Expand Down Expand Up @@ -104,6 +105,11 @@ func (s Schema) Hash() (string, error) {
return fmt.Sprintf("%x", hashVal), nil
}

type Extension struct {
SchemaQualifiedName
Version string
}

type Table struct {
Name string
Columns []Column
Expand Down Expand Up @@ -283,10 +289,15 @@ func (t Trigger) GetName() string {
return t.OwningTable.GetFQEscapedName() + "_" + t.EscapedName
}

// GetPublicSchema fetches the "public" schema. It is a non-atomic operation
// GetPublicSchema fetches the "public" schema. It is a non-atomic operation.
func GetPublicSchema(ctx context.Context, db queries.DBTX) (Schema, error) {
q := queries.New(db)

extensions, err := fetchExtensions(ctx, q)
if err != nil {
return Schema{}, fmt.Errorf("fetchExtensions: %w", err)
}

tables, err := fetchTables(ctx, q)
if err != nil {
return Schema{}, fmt.Errorf("fetchTables: %w", err)
Expand All @@ -313,14 +324,34 @@ func GetPublicSchema(ctx context.Context, db queries.DBTX) (Schema, error) {
}

return Schema{
Tables: tables,
Indexes: indexes,
Sequences: sequences,
Functions: functions,
Triggers: triggers,
Extensions: extensions,
Tables: tables,
Indexes: indexes,
Sequences: sequences,
Functions: functions,
Triggers: triggers,
}, nil
}

func fetchExtensions(ctx context.Context, q *queries.Queries) ([]Extension, error) {
rawExtensions, err := q.GetExtensions(ctx)
if err != nil {
return nil, fmt.Errorf("GetExtensions(): %w", err)
}

var extensions []Extension
for _, e := range rawExtensions {
extensions = append(extensions, Extension{
SchemaQualifiedName: SchemaQualifiedName{
EscapedName: EscapeIdentifier(e.ExtensionName),
SchemaName: e.SchemaName,
},
Version: e.ExtensionVersion,
})
}
return extensions, nil
}

func fetchTables(ctx context.Context, q *queries.Queries) ([]Table, error) {
rawTables, err := q.GetTables(ctx)
if err != nil {
Expand Down
Loading