-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstatement_test.go
More file actions
245 lines (232 loc) · 8.4 KB
/
Copy pathstatement_test.go
File metadata and controls
245 lines (232 loc) · 8.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package statement
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseOneKinds(t *testing.T) {
tests := []struct {
name string
sql string
want Statement
}{
{
name: "alter table unqualified",
sql: "ALTER TABLE users ADD COLUMN age int",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table schema-qualified",
sql: "ALTER TABLE billing.invoices DROP COLUMN note",
want: Statement{kind: KindAlterTable, schema: "billing", table: "invoices"},
},
{
name: "alter table quoted mixed-case identifier",
sql: `ALTER TABLE "Order Items" ADD COLUMN qty int`,
want: Statement{kind: KindAlterTable, table: "Order Items"},
},
{
name: "alter table if exists",
sql: "ALTER TABLE IF EXISTS users ADD COLUMN age int",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table rename to parses as RenameStmt but is a table target",
sql: "ALTER TABLE users RENAME TO users_old",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table rename column",
sql: "ALTER TABLE users RENAME COLUMN a TO b",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table rename constraint",
sql: "ALTER TABLE users RENAME CONSTRAINT users_pk TO users_pkey",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table rename schema-qualified",
sql: "ALTER TABLE billing.users RENAME COLUMN a TO b",
want: Statement{kind: KindAlterTable, schema: "billing", table: "users"},
},
{
name: "alter table set schema parses as AlterObjectSchemaStmt but is a table target",
sql: "ALTER TABLE users SET SCHEMA archive",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter table owner to",
sql: "ALTER TABLE users OWNER TO app_owner",
want: Statement{kind: KindAlterTable, table: "users"},
},
{
name: "alter view rename is not a table target",
sql: "ALTER VIEW v RENAME TO w",
want: Statement{kind: KindOther},
},
{
name: "alter index rename is not a table target",
sql: "ALTER INDEX i RENAME TO j",
want: Statement{kind: KindOther},
},
{
name: "alter sequence set schema is not a table target",
sql: "ALTER SEQUENCE s SET SCHEMA archive",
want: Statement{kind: KindOther},
},
{
name: "create index",
sql: "CREATE INDEX idx_users_email ON users (email)",
want: Statement{kind: KindCreateIndex, table: "users", buildsIndex: true},
},
{
name: "create index schema-qualified",
sql: "CREATE INDEX idx_users_email ON billing.users (email)",
want: Statement{kind: KindCreateIndex, schema: "billing", table: "users", buildsIndex: true},
},
{
name: "create unique index concurrently is a concurrent index statement",
sql: "CREATE UNIQUE INDEX CONCURRENTLY idx ON users (email)",
want: Statement{kind: KindCreateIndex, table: "users", concurrent: true, buildsIndex: true},
},
{
name: "drop index",
sql: "DROP INDEX idx_users_email",
want: Statement{kind: KindDropIndex},
},
{
name: "drop index concurrently",
sql: "DROP INDEX CONCURRENTLY idx_users_email",
want: Statement{kind: KindDropIndex, concurrent: true},
},
{
name: "reindex table",
sql: "REINDEX TABLE users",
want: Statement{kind: KindReindex},
},
{
name: "reindex index",
sql: "REINDEX INDEX idx_users_email",
want: Statement{kind: KindReindex},
},
{
name: "reindex table concurrently",
sql: "REINDEX TABLE CONCURRENTLY users",
want: Statement{kind: KindReindex, concurrent: true},
},
{
name: "alter index parses as AlterTableStmt but is not a table target",
sql: "ALTER INDEX idx_users_email SET (fillfactor = 90)",
want: Statement{kind: KindOther},
},
{
name: "drop table is not a drop-index",
sql: "DROP TABLE users",
want: Statement{kind: KindOther},
},
{
name: "create table",
sql: "CREATE TABLE t (id int)",
want: Statement{kind: KindCreateTable, table: "t"},
},
{
name: "dml",
sql: "UPDATE users SET age = 1",
want: Statement{kind: KindOther},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseOne(tt.sql)
require.NoError(t, err)
tt.want.sql = tt.sql
assert.Equal(t, tt.want, got)
})
}
}
func TestParseOneRejectsInvalidSQL(t *testing.T) {
_, err := ParseOne("ALTER TABEL users ADD COLUMN age int")
require.Error(t, err)
assert.Contains(t, err.Error(), "parse statement")
}
func TestParseOneRejectsMultipleStatements(t *testing.T) {
_, err := ParseOne("ALTER TABLE a ADD COLUMN x int; ALTER TABLE b ADD COLUMN y int")
require.ErrorIs(t, err, ErrNotOneStatement)
}
// A second statement smuggled behind a legitimate ALTER never yields a
// Statement at all — the executor only accepts what ParseOne constructs, so
// multi-statement SQL is unrepresentable downstream (invariant ST-7).
func TestParseOneRejectsSmuggledStatement(t *testing.T) {
_, err := ParseOne("ALTER TABLE t ADD COLUMN a int; DROP TABLE victim")
require.ErrorIs(t, err, ErrNotOneStatement)
}
func TestParseOneRejectsEmptyInput(t *testing.T) {
_, err := ParseOne("")
require.ErrorIs(t, err, ErrNotOneStatement)
}
func TestKindString(t *testing.T) {
assert.Equal(t, "ALTER TABLE", KindAlterTable.String())
assert.Equal(t, "CREATE INDEX", KindCreateIndex.String())
assert.Equal(t, "DROP INDEX", KindDropIndex.String())
assert.Equal(t, "REINDEX", KindReindex.String())
assert.Equal(t, "other", KindOther.String())
}
// Canonical is the report's one rendering per change: the diff door's
// quoted generation and the alter door's hand-written text converge on the
// same string, so a consumer hashing or displaying report SQL sees one
// spelling regardless of front door.
func TestCanonicalConvergesQuotingAcrossFrontDoors(t *testing.T) {
generated, err := Canonical(`ALTER TABLE "t_1"."t" DROP COLUMN "doomed"`)
require.NoError(t, err)
submitted, err := Canonical("ALTER TABLE t_1.t DROP COLUMN doomed")
require.NoError(t, err)
assert.Equal(t, generated, submitted)
// Identifiers that need quoting keep it.
kept, err := Canonical(`ALTER TABLE "Mixed Case" DROP COLUMN c`)
require.NoError(t, err)
assert.Contains(t, kept, `"Mixed Case"`)
}
func TestCanonicalRefusesNotExactlyOneStatement(t *testing.T) {
_, err := Canonical("ALTER TABLE t DROP COLUMN a; ALTER TABLE t DROP COLUMN b")
require.ErrorIs(t, err, ErrNotOneStatement)
_, err = Canonical("not sql")
require.Error(t, err)
}
// Reprinting through the deparser drops comments, so Canonical refuses
// commented input rather than silently discarding content.
func TestCanonicalRefusesCommentedInput(t *testing.T) {
_, err := Canonical("ALTER TABLE t DROP COLUMN a -- doomed")
require.ErrorIs(t, err, ErrCommentLoss)
_, err = Canonical("ALTER TABLE t /* keep */ DROP COLUMN a")
require.ErrorIs(t, err, ErrCommentLoss)
}
func TestBuildsIndex(t *testing.T) {
tests := []struct {
name string
sql string
want bool
}{
{"create index", "CREATE INDEX i ON s.t (c)", true},
{"create index concurrently", "CREATE INDEX CONCURRENTLY i ON s.t (c)", true},
{"add unique constraint", "ALTER TABLE s.t ADD CONSTRAINT u UNIQUE (c)", true},
{"add primary key", "ALTER TABLE s.t ADD CONSTRAINT p PRIMARY KEY (c)", true},
{"add exclusion constraint", "ALTER TABLE s.t ADD CONSTRAINT x EXCLUDE USING gist (c WITH =)", true},
{"add column with inline unique", "ALTER TABLE s.t ADD COLUMN e int UNIQUE", true},
{"add column with inline primary key", "ALTER TABLE s.t ADD COLUMN e int PRIMARY KEY", true},
{"unique using index adopts an existing index", "ALTER TABLE s.t ADD CONSTRAINT u UNIQUE USING INDEX u", false},
{"primary key using index adopts an existing index", "ALTER TABLE s.t ADD CONSTRAINT p PRIMARY KEY USING INDEX p", false},
{"plain add column", "ALTER TABLE s.t ADD COLUMN e int", false},
{"check constraint builds nothing", "ALTER TABLE s.t ADD CONSTRAINT c CHECK (e > 0)", false},
{"foreign key builds nothing on the referencing table", "ALTER TABLE s.t ADD CONSTRAINT fk FOREIGN KEY (e) REFERENCES s.p (id)", false},
{"rewriting type change rebuilds existing indexes only", "ALTER TABLE s.t ALTER COLUMN c TYPE bigint", false},
{"set not null", "ALTER TABLE s.t ALTER COLUMN c SET NOT NULL", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
st, err := ParseOne(tt.sql)
require.NoError(t, err)
assert.Equal(t, tt.want, st.BuildsIndex())
})
}
}