From 3d29aa812eb17697ac2bfc6a86948fab83576238 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 07:28:09 +0000 Subject: [PATCH 1/9] Fall back to raw SQL for `TEMPORARY` tables Temporary tables are not representable as `pgroll` `OpCreateTable` operations. --- pkg/sql2pgroll/create_table.go | 16 ++++++++++++++++ pkg/sql2pgroll/create_table_test.go | 20 ++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 5add694a..a386d435 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -12,6 +12,12 @@ import ( // convertCreateStmt converts a CREATE TABLE statement to a pgroll operation. func convertCreateStmt(stmt *pgq.CreateStmt) (migrations.Operations, error) { + // Check if the statement can be converted + if !canConvertCreateStatement(stmt) { + return nil, nil + } + + // Convert the column definitions columns := make([]migrations.Column, 0, len(stmt.TableElts)) for _, elt := range stmt.TableElts { column, err := convertColumnDef(elt.GetColumnDef()) @@ -29,6 +35,16 @@ func convertCreateStmt(stmt *pgq.CreateStmt) (migrations.Operations, error) { }, nil } +// canConvertCreateTableStatement returns true iff `stmt` can be converted to a +// pgroll operation. +func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { + // Temporary tables are not supported + if stmt.GetRelation().GetRelpersistence() == "t" { + return false + } + return true +} + func convertColumnDef(col *pgq.ColumnDef) (*migrations.Column, error) { // Convert the column type typeString, err := pgq.DeparseTypeName(col.TypeName) diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index acc980b0..0e075f45 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -72,3 +72,23 @@ func TestConvertCreateTableStatements(t *testing.T) { }) } } + +func TestUnconvertableCreateTableStatements(t *testing.T) { + t.Parallel() + + tests := []string{ + // Temporary tables are not representable as a pgroll operation + "CREATE TEMPORARY TABLE foo(a int)", + } + + for _, sql := range tests { + t.Run(sql, func(t *testing.T) { + ops, err := sql2pgroll.Convert(sql) + require.NoError(t, err) + + require.Len(t, ops, 1) + + assert.Equal(t, expect.RawSQLOp(sql), ops[0]) + }) + } +} From 839e4a5370e7eedcff2958777f0124f749219195 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 07:33:12 +0000 Subject: [PATCH 2/9] Fall back to raw SQL for `UNLOGGED` tables Unlogged tables are not representable as `pgroll` `OpCreateTable` operations. --- pkg/sql2pgroll/create_table.go | 4 ++-- pkg/sql2pgroll/create_table_test.go | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index a386d435..2b50c6d1 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -38,8 +38,8 @@ func convertCreateStmt(stmt *pgq.CreateStmt) (migrations.Operations, error) { // canConvertCreateTableStatement returns true iff `stmt` can be converted to a // pgroll operation. func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { - // Temporary tables are not supported - if stmt.GetRelation().GetRelpersistence() == "t" { + // Temporary and unlogged tables are not supported + if stmt.GetRelation().GetRelpersistence() != "p" { return false } return true diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 0e075f45..e7432ada 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -77,8 +77,10 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { t.Parallel() tests := []string{ - // Temporary tables are not representable as a pgroll operation + // Temporary and unlogged tables are not representable as a pgroll + // operation "CREATE TEMPORARY TABLE foo(a int)", + "CREATE UNLOGGED TABLE foo(a int)", } for _, sql := range tests { From 2ec9e49c291b8c3c669737b6585f323f1ada6113 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 07:35:29 +0000 Subject: [PATCH 3/9] Fall back to raw SQL for `IF NOT EXISTS` `CREATE TABLE IF NOT EXISTS` is not representable as a `pgroll` operation. --- pkg/sql2pgroll/create_table.go | 4 ++++ pkg/sql2pgroll/create_table_test.go | 1 + 2 files changed, 5 insertions(+) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 2b50c6d1..6dc3b326 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -42,6 +42,10 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { if stmt.GetRelation().GetRelpersistence() != "p" { return false } + // CREATE TABLE IF NOT EXISTS is not supported + if stmt.GetIfNotExists() { + return false + } return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index e7432ada..a6cea756 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -81,6 +81,7 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // operation "CREATE TEMPORARY TABLE foo(a int)", "CREATE UNLOGGED TABLE foo(a int)", + "CREATE TABLE IF NOT EXISTS foo(a int)", } for _, sql := range tests { From ae02568e7e095370850102f2a53f8838cbdbf2fc Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 07:46:40 +0000 Subject: [PATCH 4/9] Fall back to raw SQL for `INHERITS` clause `CREATE TABLE ... INHERITS` is not representable as a `pgroll` operation. --- pkg/sql2pgroll/create_table.go | 12 ++++++++---- pkg/sql2pgroll/create_table_test.go | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 6dc3b326..e305fb12 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -38,15 +38,19 @@ func convertCreateStmt(stmt *pgq.CreateStmt) (migrations.Operations, error) { // canConvertCreateTableStatement returns true iff `stmt` can be converted to a // pgroll operation. func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { + switch { // Temporary and unlogged tables are not supported - if stmt.GetRelation().GetRelpersistence() != "p" { + case stmt.GetRelation().GetRelpersistence() != "p": return false - } // CREATE TABLE IF NOT EXISTS is not supported - if stmt.GetIfNotExists() { + case stmt.GetIfNotExists(): + return false + // Table inheritance is not supported + case len(stmt.GetInhRelations()) != 0: return false + default: + return true } - return true } func convertColumnDef(col *pgq.ColumnDef) (*migrations.Column, error) { diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index a6cea756..299a5dce 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -82,6 +82,7 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { "CREATE TEMPORARY TABLE foo(a int)", "CREATE UNLOGGED TABLE foo(a int)", "CREATE TABLE IF NOT EXISTS foo(a int)", + "CREATE TABLE foo(a int) INHERITS (bar)", } for _, sql := range tests { From ac92492cc7acde8307791f3e41379b6a380cb516 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 07:55:40 +0000 Subject: [PATCH 5/9] Fall back to raw SQL for `PARTITION` clause Paritioned tables are not representable as an `OpCreateTable` operation. --- pkg/sql2pgroll/create_table.go | 3 +++ pkg/sql2pgroll/create_table_test.go | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index e305fb12..023d8eed 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -48,6 +48,9 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { // Table inheritance is not supported case len(stmt.GetInhRelations()) != 0: return false + // Paritioned tables are not supported + case stmt.GetPartspec() != nil: + return false default: return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 299a5dce..6730efcf 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -77,12 +77,15 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { t.Parallel() tests := []string{ - // Temporary and unlogged tables are not representable as a pgroll - // operation + // CREATE TABLE options that are not representable as `pgroll` operations. "CREATE TEMPORARY TABLE foo(a int)", "CREATE UNLOGGED TABLE foo(a int)", "CREATE TABLE IF NOT EXISTS foo(a int)", "CREATE TABLE foo(a int) INHERITS (bar)", + + // Any kind of partitioning is not supported + "CREATE TABLE foo(a int) PARTITION BY RANGE (a)", + "CREATE TABLE foo(a int) PARTITION BY LIST (a)", } for _, sql := range tests { From 4ddd354c5d16ceeabb846a7de0c9d43812a1ad30 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 08:00:18 +0000 Subject: [PATCH 6/9] Fall back to raw SQL for `USING` clause Different table access methods are not representable as an `OpCreateTable` operation. --- pkg/sql2pgroll/create_table.go | 3 +++ pkg/sql2pgroll/create_table_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 023d8eed..847ead2a 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -51,6 +51,9 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { // Paritioned tables are not supported case stmt.GetPartspec() != nil: return false + // Specifying an access method is not supported + case stmt.GetAccessMethod() != "": + return false default: return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 6730efcf..ff236fba 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -86,6 +86,9 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // Any kind of partitioning is not supported "CREATE TABLE foo(a int) PARTITION BY RANGE (a)", "CREATE TABLE foo(a int) PARTITION BY LIST (a)", + + // Specifying a table access method is not supported + "CREATE TABLE foo(a int) USING bar", } for _, sql := range tests { From 2ba36218415033da633160ed9302015f44e1c439 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 08:06:39 +0000 Subject: [PATCH 7/9] Fall back to raw SQL for `WITH` clause Storage options are not representable as an `OpCreateTable` operation. --- pkg/sql2pgroll/create_table.go | 3 +++ pkg/sql2pgroll/create_table_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 847ead2a..88fbbc7c 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -54,6 +54,9 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { // Specifying an access method is not supported case stmt.GetAccessMethod() != "": return false + // Specifying storage options is not supported + case len(stmt.GetOptions()) != 0: + return false default: return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index ff236fba..a7f9e91d 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -89,6 +89,9 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // Specifying a table access method is not supported "CREATE TABLE foo(a int) USING bar", + + // Specifying storage options is not supported + "CREATE TABLE foo(a int) WITH (fillfactor=70)", } for _, sql := range tests { From a0fceb5e89ffa141f04220ccd99637143a192f3a Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 08:15:03 +0000 Subject: [PATCH 8/9] Fall back to raw SQL for `ON COMMIT` clause On commit options are not representable as an `OpCreateTable` operation. --- pkg/sql2pgroll/create_table.go | 3 +++ pkg/sql2pgroll/create_table_test.go | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index 88fbbc7c..fd862a4c 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -57,6 +57,9 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { // Specifying storage options is not supported case len(stmt.GetOptions()) != 0: return false + // ON COMMIT options are not supported + case stmt.GetOncommit() != pgq.OnCommitAction_ONCOMMIT_NOOP: + return false default: return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index a7f9e91d..1417daf4 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -92,6 +92,11 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // Specifying storage options is not supported "CREATE TABLE foo(a int) WITH (fillfactor=70)", + + // ON COMMMIT options are not supported. These options are syntactically + // valid for all tables, but Postgres will reject them for non-temporary + // tables. We err on the side of caution and reject them for all tables. + "CREATE TABLE foo(a int) ON COMMIT DROP", } for _, sql := range tests { From bcb4802a11b6ada93b3b6cb62deaa924d52590b8 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Wed, 18 Dec 2024 08:22:46 +0000 Subject: [PATCH 9/9] Fall back to raw SQL for `TABLESPACE` clause Tablespace options are not representable as an `OpCreateTable` operation. --- pkg/sql2pgroll/create_table.go | 3 +++ pkg/sql2pgroll/create_table_test.go | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pkg/sql2pgroll/create_table.go b/pkg/sql2pgroll/create_table.go index fd862a4c..4ea6baea 100644 --- a/pkg/sql2pgroll/create_table.go +++ b/pkg/sql2pgroll/create_table.go @@ -60,6 +60,9 @@ func canConvertCreateStatement(stmt *pgq.CreateStmt) bool { // ON COMMIT options are not supported case stmt.GetOncommit() != pgq.OnCommitAction_ONCOMMIT_NOOP: return false + // Setting a tablespace is not supported + case stmt.GetTablespacename() != "": + return false default: return true } diff --git a/pkg/sql2pgroll/create_table_test.go b/pkg/sql2pgroll/create_table_test.go index 1417daf4..62388a0f 100644 --- a/pkg/sql2pgroll/create_table_test.go +++ b/pkg/sql2pgroll/create_table_test.go @@ -77,10 +77,14 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { t.Parallel() tests := []string{ - // CREATE TABLE options that are not representable as `pgroll` operations. + // Temporary and unlogged tables are not supported "CREATE TEMPORARY TABLE foo(a int)", "CREATE UNLOGGED TABLE foo(a int)", + + // The IF NOT EXISTS clause is not supported "CREATE TABLE IF NOT EXISTS foo(a int)", + + // Table inheritance is not supported "CREATE TABLE foo(a int) INHERITS (bar)", // Any kind of partitioning is not supported @@ -97,6 +101,9 @@ func TestUnconvertableCreateTableStatements(t *testing.T) { // valid for all tables, but Postgres will reject them for non-temporary // tables. We err on the side of caution and reject them for all tables. "CREATE TABLE foo(a int) ON COMMIT DROP", + + // Specifying a tablespace is not supported + "CREATE TABLE foo(a int) TABLESPACE bar", } for _, sql := range tests {