From 6ab09b13a4f388fd048afeacbf592f56abc4e75e Mon Sep 17 00:00:00 2001 From: avison9 Date: Fri, 25 Sep 2026 22:49:39 +0100 Subject: [PATCH] A table name glued to a multi-line column list no longer crashes the Postgres reader CREATE TABLE IF NOT EXISTS reactions( with the column list starting on the next line made cdclint panic (slice bounds out of range [-1:]). The reader found the column list by searching the statement for the name's word, but the word splitter folds whitespace inside parentheses, so a name glued to a paren that opens a multi-line list is not in the text verbatim and the index came back -1. The column list is now taken from the first parenthesis after TABLE, which the name cannot contain. Found by reading Mattermost's migrations (v10.11.0), where three files have this shape (000016_create_reactions, 000043_thread_memberships, 000098_create_post_acknowledgements). The same-line form, reports(id ...), already worked and still does. --- internal/source/postgres/postgres.go | 7 ++++++- internal/source/postgres/postgres_test.go | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/internal/source/postgres/postgres.go b/internal/source/postgres/postgres.go index b59354c..e6d35b3 100644 --- a/internal/source/postgres/postgres.go +++ b/internal/source/postgres/postgres.go @@ -109,7 +109,12 @@ func createTable(src *model.Source, text string, pos model.Pos) { name = name[:p] } schema, table := splitQualified(name) - body, rest, ok := ddl.Body(text[strings.Index(text, w[i]):]) + // The column list is the first parenthesis after TABLE. Searching for + // the name's word instead panicked when the name was glued to a paren + // that opens a multi-line list ("reactions(\n userid ..."): the word + // splitter folds that list's whitespace, so the word is not in the text + // and the index is -1. Found on Mattermost's migrations. + body, rest, ok := ddl.Body(text[strings.Index(strings.ToUpper(text), "TABLE")+len("TABLE"):]) if !ok { return } diff --git a/internal/source/postgres/postgres_test.go b/internal/source/postgres/postgres_test.go index caa6049..731fb7c 100644 --- a/internal/source/postgres/postgres_test.go +++ b/internal/source/postgres/postgres_test.go @@ -67,3 +67,19 @@ func formatSlice(s []string) string { } return out + "]" } + +func TestANameGluedToAMultiLineColumnListDoesNotPanic(t *testing.T) { + // Mattermost 000016_create_reactions.up.sql, as written. + src := &model.Source{} + text := "CREATE TABLE IF NOT EXISTS reactions(\n userid VARCHAR(26) NOT NULL,\n postid VARCHAR(26) NOT NULL\n);" + if err := Apply(src, "000016_create_reactions.up.sql", text); err != nil { + t.Fatal(err) + } + r := src.Table("public", "reactions") + if r == nil || len(r.Columns) != 2 || r.Columns[0].Name != "userid" || r.Columns[1].Name != "postid" { + t.Fatalf("public.reactions = %+v", r) + } + if r.Columns[1].Pos.Line != 3 { + t.Errorf("postid line = %d, want 3", r.Columns[1].Pos.Line) + } +}