Skip to content
Open
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
23 changes: 22 additions & 1 deletion pkg/migration/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ type MigrationFile struct {
Statements []string
}

var migrateFilePattern = regexp.MustCompile(`^([0-9]+)_(.*)\.sql$`)
var (
migrateFilePattern = regexp.MustCompile(`^([0-9]+)_(.*)\.sql$`)
typeNamePattern = regexp.MustCompile(`type "([^"]+)" does not exist`)
)

func NewMigrationFromFile(path string, fsys fs.FS) (*MigrationFile, error) {
lines, err := parseFile(path, fsys)
Expand Down Expand Up @@ -96,6 +99,14 @@ func (m *MigrationFile) ExecBatch(ctx context.Context, conn *pgx.Conn) error {
if len(pgErr.Detail) > 0 {
msg = append(msg, pgErr.Detail)
}
// Provide helpful hint for extension type errors (SQLSTATE 42704: undefined_object)
if typeName := extractTypeName(pgErr.Message); len(typeName) > 0 && pgErr.Code == "42704" {
msg = append(msg, "")
msg = append(msg, "Hint: This type may be defined in a schema that's not in your search_path.")
msg = append(msg, " Use schema-qualified type references to avoid this error:")
msg = append(msg, fmt.Sprintf(" CREATE TABLE example (col extensions.%s);", typeName))
msg = append(msg, " Learn more: supabase migration new --help")
}
}
msg = append(msg, fmt.Sprintf("At statement: %d", i), stat)
return errors.Errorf("%w\n%s", err, strings.Join(msg, "\n"))
Expand All @@ -120,6 +131,16 @@ func markError(stat string, pos int) string {
return strings.Join(lines, "\n")
}

// extractTypeName extracts the type name from PostgreSQL error messages like:
// 'type "ltree" does not exist' -> "ltree"
func extractTypeName(errMsg string) string {
matches := typeNamePattern.FindStringSubmatch(errMsg)
if len(matches) > 1 {
return matches[1]
}
return ""
}

func (m *MigrationFile) insertVersionSQL(conn *pgx.Conn, batch *pgconn.Batch) error {
value := pgtype.TextArray{}
if err := value.Set(m.Statements); err != nil {
Expand Down
43 changes: 43 additions & 0 deletions pkg/migration/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,47 @@ func TestMigrationFile(t *testing.T) {
assert.ErrorContains(t, err, "ERROR: schema \"public\" already exists (SQLSTATE 42P06)")
assert.ErrorContains(t, err, "At statement: 0\ncreate schema public")
})

t.Run("provides helpful hint for extension type errors", func(t *testing.T) {
migration := MigrationFile{
Statements: []string{"CREATE TABLE test (path ltree NOT NULL)"},
Version: "0",
}
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(migration.Statements[0]).
ReplyError("42704", `type "ltree" does not exist`).
Query(INSERT_MIGRATION_VERSION, "0", "", migration.Statements).
Reply("INSERT 0 1")
// Run test
err := migration.ExecBatch(context.Background(), conn.MockClient(t))
// Check error
assert.ErrorContains(t, err, `type "ltree" does not exist`)
assert.ErrorContains(t, err, "Hint: This type may be defined in a schema")
assert.ErrorContains(t, err, "extensions.ltree")
assert.ErrorContains(t, err, "supabase migration new --help")
assert.ErrorContains(t, err, "At statement: 0")
})

t.Run("provides generic hint when type name cannot be extracted", func(t *testing.T) {
migration := MigrationFile{
Statements: []string{"CREATE TABLE test (id custom_type)"},
Version: "0",
}
// Setup mock postgres
conn := pgtest.NewConn()
defer conn.Close(t)
conn.Query(migration.Statements[0]).
ReplyError("42704", `type does not exist`).
Query(INSERT_MIGRATION_VERSION, "0", "", migration.Statements).
Reply("INSERT 0 1")
// Run test
err := migration.ExecBatch(context.Background(), conn.MockClient(t))
// Check error
assert.ErrorContains(t, err, "type does not exist")
assert.ErrorContains(t, err, "Hint: This type may be defined in a schema")
assert.ErrorContains(t, err, "extensions.<type_name>")
assert.ErrorContains(t, err, "supabase migration new --help")
})
}
Loading