@akal/pg-conformance 0.0.0-stage → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +199 -2
- package/corpus/data.json +122 -0
- package/corpus/equivalences.json +44 -0
- package/corpus/hard-cases.json +452 -0
- package/corpus/migrations.json +515 -0
- package/corpus/objects.json +202 -0
- package/corpus/ordering.json +201 -0
- package/fingerprint.sql +245 -0
- package/index.d.ts +147 -0
- package/index.js +87 -0
- package/package.json +46 -4
- package/src/Conformance.php +97 -0
- package/state.sql +234 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"id": "enum_label_removed",
|
|
4
|
+
"category": "enum",
|
|
5
|
+
"description": "An enum loses a label while columns, an array column and defaults use it. There is no DROP VALUE: the columns move to a new type, keeping every row.",
|
|
6
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
7
|
+
"after": "CREATE TYPE st AS ENUM ('new', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
8
|
+
"preserve": [
|
|
9
|
+
"SELECT id, s::text, arr::text, s2::text FROM o ORDER BY id"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": "enum_labels_reordered",
|
|
14
|
+
"category": "enum",
|
|
15
|
+
"description": "An enum's labels are reordered; the type's sort order changes, the rows do not.",
|
|
16
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
17
|
+
"after": "CREATE TYPE st AS ENUM ('shipped','paid','new');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
18
|
+
"preserve": [
|
|
19
|
+
"SELECT id, s::text, arr::text, s2::text FROM o ORDER BY id"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "enum_label_added",
|
|
24
|
+
"category": "enum",
|
|
25
|
+
"description": "An enum gains a label (ALTER TYPE ... ADD VALUE ... AFTER). Its reverse is a removal.",
|
|
26
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
27
|
+
"after": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');",
|
|
28
|
+
"preserve": [
|
|
29
|
+
"SELECT id, s::text, arr::text, s2::text FROM o ORDER BY id"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "enum_label_removed_under_readers",
|
|
34
|
+
"category": "enum",
|
|
35
|
+
"description": "A label removal with views, a materialized view, a policy, a trigger condition, a generated column, a partial index, a CHECK, a unique key and a foreign key from another table all using the columns.",
|
|
36
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');CREATE VIEW v AS SELECT id, s FROM o WHERE s2 IS NOT NULL; CREATE VIEW v2 AS SELECT * FROM v; CREATE MATERIALIZED VIEW mv AS SELECT s FROM o; CREATE INDEX mv_s ON mv (s); ALTER TABLE o ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON o USING (s = 'new'); CREATE FUNCTION tf() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE UPDATE ON o FOR EACH ROW WHEN (NEW.s = 'new') EXECUTE FUNCTION tf(); ALTER TABLE o ADD COLUMN g boolean GENERATED ALWAYS AS (s = 'new') STORED; CREATE INDEX o_p ON o (id) WHERE s = 'shipped'; ALTER TABLE o ADD CONSTRAINT o_ck CHECK (s <> 'shipped' OR s2 IS NOT NULL); ALTER TABLE o ADD CONSTRAINT o_u UNIQUE (s, id); CREATE TABLE r (id int, s st, FOREIGN KEY (s, id) REFERENCES o (s, id));",
|
|
37
|
+
"after": "CREATE TYPE st AS ENUM ('new', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');CREATE VIEW v AS SELECT id, s FROM o WHERE s2 IS NOT NULL; CREATE VIEW v2 AS SELECT * FROM v; CREATE MATERIALIZED VIEW mv AS SELECT s FROM o; CREATE INDEX mv_s ON mv (s); ALTER TABLE o ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON o USING (s = 'new'); CREATE FUNCTION tf() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE UPDATE ON o FOR EACH ROW WHEN (NEW.s = 'new') EXECUTE FUNCTION tf(); ALTER TABLE o ADD COLUMN g boolean GENERATED ALWAYS AS (s = 'new') STORED; CREATE INDEX o_p ON o (id) WHERE s = 'shipped'; ALTER TABLE o ADD CONSTRAINT o_ck CHECK (s <> 'shipped' OR s2 IS NOT NULL); ALTER TABLE o ADD CONSTRAINT o_u UNIQUE (s, id); CREATE TABLE r (id int, s st, FOREIGN KEY (s, id) REFERENCES o (s, id));",
|
|
38
|
+
"preserve": [
|
|
39
|
+
"SELECT id, s::text, s2::text FROM o ORDER BY id"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"id": "enum_label_added_under_readers",
|
|
44
|
+
"category": "enum",
|
|
45
|
+
"description": "The same readers, with a label added instead; reverting it is a removal under all of them.",
|
|
46
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');CREATE VIEW v AS SELECT id, s FROM o WHERE s2 IS NOT NULL; CREATE VIEW v2 AS SELECT * FROM v; CREATE MATERIALIZED VIEW mv AS SELECT s FROM o; CREATE INDEX mv_s ON mv (s); ALTER TABLE o ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON o USING (s = 'new'); CREATE FUNCTION tf() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE UPDATE ON o FOR EACH ROW WHEN (NEW.s = 'new') EXECUTE FUNCTION tf(); ALTER TABLE o ADD COLUMN g boolean GENERATED ALWAYS AS (s = 'new') STORED; CREATE INDEX o_p ON o (id) WHERE s = 'shipped'; ALTER TABLE o ADD CONSTRAINT o_ck CHECK (s <> 'shipped' OR s2 IS NOT NULL); ALTER TABLE o ADD CONSTRAINT o_u UNIQUE (s, id); CREATE TABLE r (id int, s st, FOREIGN KEY (s, id) REFERENCES o (s, id));",
|
|
47
|
+
"after": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');CREATE VIEW v AS SELECT id, s FROM o WHERE s2 IS NOT NULL; CREATE VIEW v2 AS SELECT * FROM v; CREATE MATERIALIZED VIEW mv AS SELECT s FROM o; CREATE INDEX mv_s ON mv (s); ALTER TABLE o ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON o USING (s = 'new'); CREATE FUNCTION tf() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE UPDATE ON o FOR EACH ROW WHEN (NEW.s = 'new') EXECUTE FUNCTION tf(); ALTER TABLE o ADD COLUMN g boolean GENERATED ALWAYS AS (s = 'new') STORED; CREATE INDEX o_p ON o (id) WHERE s = 'shipped'; ALTER TABLE o ADD CONSTRAINT o_ck CHECK (s <> 'shipped' OR s2 IS NOT NULL); ALTER TABLE o ADD CONSTRAINT o_u UNIQUE (s, id); CREATE TABLE r (id int, s st, FOREIGN KEY (s, id) REFERENCES o (s, id));",
|
|
48
|
+
"preserve": [
|
|
49
|
+
"SELECT id, s::text, s2::text FROM o ORDER BY id"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "enum_label_removed_with_constraint_naming_it",
|
|
54
|
+
"category": "enum",
|
|
55
|
+
"description": "The label being removed is named by a CHECK and a partial index that go with it; they cannot be put back around the swap.",
|
|
56
|
+
"before": "CREATE TYPE st AS ENUM ('new', 'paid', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');ALTER TABLE o ADD CONSTRAINT ck_paid CHECK (s <> 'paid'); CREATE INDEX o_paid ON o (id) WHERE s = 'paid';",
|
|
57
|
+
"after": "CREATE TYPE st AS ENUM ('new', 'shipped');CREATE TABLE o (id int PRIMARY KEY, s st NOT NULL DEFAULT 'new', arr st[] DEFAULT '{new}', s2 st); INSERT INTO o VALUES (1,'new','{new,shipped}','shipped'),(2,'shipped',NULL,'new');"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "identity_kind_changed",
|
|
61
|
+
"category": "identity",
|
|
62
|
+
"description": "GENERATED BY DEFAULT becomes ALWAYS on a column with rows. Dropping and re-adding the identity restarts the sequence and the next insert collides.",
|
|
63
|
+
"before": "CREATE TABLE t (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
64
|
+
"after": "CREATE TABLE t (id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
65
|
+
"preserve": [
|
|
66
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"id": "identity_options_changed",
|
|
71
|
+
"category": "identity",
|
|
72
|
+
"description": "An identity gains INCREMENT BY 10; the sequence keeps its position.",
|
|
73
|
+
"before": "CREATE TABLE t (id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
74
|
+
"after": "CREATE TABLE t (id int GENERATED ALWAYS AS IDENTITY (INCREMENT BY 10) PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
75
|
+
"preserve": [
|
|
76
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "identity_retyped",
|
|
81
|
+
"category": "identity",
|
|
82
|
+
"description": "An identity column goes from int to bigint.",
|
|
83
|
+
"before": "CREATE TABLE t (id int GENERATED ALWAYS AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
84
|
+
"after": "CREATE TABLE t (id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
85
|
+
"preserve": [
|
|
86
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "plain_column_becomes_identity",
|
|
91
|
+
"category": "identity",
|
|
92
|
+
"description": "A plain integer key with rows becomes an identity; its sequence must start past the existing values.",
|
|
93
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY, v int); INSERT INTO t VALUES (1,1),(2,2);",
|
|
94
|
+
"after": "CREATE TABLE t (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, v int);",
|
|
95
|
+
"preserve": [
|
|
96
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "serial_becomes_identity",
|
|
101
|
+
"category": "serial",
|
|
102
|
+
"description": "A serial column becomes an identity: its default and owned sequence go, the identity starts past the rows.",
|
|
103
|
+
"before": "CREATE TABLE t (id serial PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
104
|
+
"after": "CREATE TABLE t (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, v int);",
|
|
105
|
+
"preserve": [
|
|
106
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "identity_becomes_serial",
|
|
111
|
+
"category": "serial",
|
|
112
|
+
"description": "An identity becomes a serial column: a sequence owned by it, a nextval default, started past the rows.",
|
|
113
|
+
"before": "CREATE TABLE t (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
114
|
+
"after": "CREATE TABLE t (id serial PRIMARY KEY, v int);",
|
|
115
|
+
"preserve": [
|
|
116
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": "serial_becomes_bigserial",
|
|
121
|
+
"category": "serial",
|
|
122
|
+
"description": "serial becomes bigserial. ALTER COLUMN TYPE does not retype the sequence, which must follow or it still stops at 2^31.",
|
|
123
|
+
"before": "CREATE TABLE t (id serial, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
124
|
+
"after": "CREATE TABLE t (id bigserial, v int);INSERT INTO t (v) SELECT g FROM generate_series(1,5) g;",
|
|
125
|
+
"preserve": [
|
|
126
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"id": "column_storage_and_compression",
|
|
131
|
+
"category": "storage",
|
|
132
|
+
"description": "A column gains COMPRESSION lz4 and another STORAGE EXTERNAL.",
|
|
133
|
+
"before": "CREATE TABLE t (id int, body text, blob bytea);",
|
|
134
|
+
"after": "CREATE TABLE t (id int, body text COMPRESSION lz4, blob bytea); ALTER TABLE t ALTER COLUMN blob SET STORAGE EXTERNAL;"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "retype_resets_storage_and_compression",
|
|
138
|
+
"category": "storage",
|
|
139
|
+
"description": "A column is retyped and must keep its storage and compression, which ALTER COLUMN TYPE resets to the new type's defaults.",
|
|
140
|
+
"before": "CREATE TABLE t (id int, body varchar(200));",
|
|
141
|
+
"after": "CREATE TABLE t (id int, body text COMPRESSION lz4); ALTER TABLE t ALTER COLUMN body SET STORAGE EXTERNAL;"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"id": "generated_expression_changed",
|
|
145
|
+
"category": "generated",
|
|
146
|
+
"description": "A stored generated column's expression changes; it can only be dropped and re-added, recomputed.",
|
|
147
|
+
"before": "CREATE TABLE t (id int primary key, a numeric, g numeric GENERATED ALWAYS AS (a * 2) STORED); INSERT INTO t VALUES (1, 2);",
|
|
148
|
+
"after": "CREATE TABLE t (id int primary key, a numeric, g numeric GENERATED ALWAYS AS (a * 3) STORED); INSERT INTO t VALUES (1, 2);",
|
|
149
|
+
"preserve": [
|
|
150
|
+
"SELECT id, a FROM t ORDER BY id"
|
|
151
|
+
]
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"id": "plain_column_becomes_generated",
|
|
155
|
+
"category": "generated",
|
|
156
|
+
"description": "A plain column becomes a stored generated one.",
|
|
157
|
+
"before": "CREATE TABLE t (id int, a int, b int GENERATED ALWAYS AS (a*2) STORED); INSERT INTO t (id,a) VALUES (1,2);",
|
|
158
|
+
"after": "CREATE TABLE t (id int, a int, b int); INSERT INTO t VALUES (1,2,4);"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "generated_column_becomes_plain",
|
|
162
|
+
"category": "generated",
|
|
163
|
+
"description": "A stored generated column becomes plain (DROP EXPRESSION), keeping its values.",
|
|
164
|
+
"before": "CREATE TABLE t (id int, a int, b int); INSERT INTO t VALUES (1,2,4);",
|
|
165
|
+
"after": "CREATE TABLE t (id int, a int, b int GENERATED ALWAYS AS (a*2) STORED); INSERT INTO t (id,a) VALUES (1,2);"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"id": "retype_under_views_and_policies",
|
|
169
|
+
"category": "dependants",
|
|
170
|
+
"description": "Columns retyped (numeric precision, text to uuid) under a security_invoker view with grants and a policy, which PostgreSQL refuses to retype under.",
|
|
171
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY, a numeric(10,2), uid text); CREATE VIEW v WITH (security_invoker=true) AS SELECT id, a FROM t; GRANT SELECT ON v TO PUBLIC; ALTER TABLE t ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON t USING (uid IS NOT NULL);",
|
|
172
|
+
"after": "CREATE TABLE t (id int PRIMARY KEY, a numeric(12,2), uid uuid); CREATE VIEW v WITH (security_invoker=true) AS SELECT id, a FROM t; GRANT SELECT ON v TO PUBLIC; ALTER TABLE t ENABLE ROW LEVEL SECURITY; CREATE POLICY p ON t USING (uid IS NOT NULL);",
|
|
173
|
+
"minPgVersion": 15
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"id": "retype_partitioned_column",
|
|
177
|
+
"category": "dependants",
|
|
178
|
+
"description": "A column retyped on a partitioned table read by a view; the partitions follow the parent.",
|
|
179
|
+
"before": "CREATE TABLE p (id int, a numeric(10,2)) PARTITION BY RANGE (id); CREATE TABLE p1 PARTITION OF p FOR VALUES FROM (0) TO (100); CREATE VIEW pv AS SELECT a FROM p;",
|
|
180
|
+
"after": "CREATE TABLE p (id int, a numeric(12,2)) PARTITION BY RANGE (id); CREATE TABLE p1 PARTITION OF p FOR VALUES FROM (0) TO (100); CREATE VIEW pv AS SELECT a FROM p;"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "new_enum_typed_column",
|
|
184
|
+
"category": "creation_order",
|
|
185
|
+
"description": "A new column typed by a new enum: the type is created first, and dropped last when reverting.",
|
|
186
|
+
"before": "CREATE TABLE t (id int);",
|
|
187
|
+
"after": "CREATE TYPE e AS ENUM ('a'); CREATE TABLE t (id int, c e);"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"id": "new_domain_over_new_enum",
|
|
191
|
+
"category": "creation_order",
|
|
192
|
+
"description": "A new column typed by a new domain over a new enum.",
|
|
193
|
+
"before": "CREATE TABLE t (id int);",
|
|
194
|
+
"after": "CREATE TYPE e AS ENUM ('a'); CREATE DOMAIN d AS e; CREATE TABLE t (id int, c d);"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"id": "new_sequence_default",
|
|
198
|
+
"category": "creation_order",
|
|
199
|
+
"description": "A new column defaulting to nextval() of a new sequence.",
|
|
200
|
+
"before": "CREATE TABLE t (id int);",
|
|
201
|
+
"after": "CREATE SEQUENCE s; CREATE TABLE t (id int DEFAULT nextval('s'));"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"id": "new_function_in_default",
|
|
205
|
+
"category": "creation_order",
|
|
206
|
+
"description": "A new column whose default calls a new function: the function must exist first, although functions are otherwise created after tables.",
|
|
207
|
+
"before": "CREATE TABLE t (id int);",
|
|
208
|
+
"after": "CREATE FUNCTION f() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT 7 $$;CREATE TABLE t (id int DEFAULT f());"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"id": "new_function_in_check",
|
|
212
|
+
"category": "creation_order",
|
|
213
|
+
"description": "A new CHECK constraint calling a new function.",
|
|
214
|
+
"before": "CREATE TABLE t (id int);",
|
|
215
|
+
"after": "CREATE FUNCTION f() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT 7 $$;CREATE TABLE t (id int CHECK (id < f() * 100));"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "new_function_in_index",
|
|
219
|
+
"category": "creation_order",
|
|
220
|
+
"description": "A new expression index calling a new function.",
|
|
221
|
+
"before": "CREATE TABLE t (id int);",
|
|
222
|
+
"after": "CREATE FUNCTION f() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT 7 $$;CREATE TABLE t (id int); CREATE INDEX t_f ON t ((id + f()));"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"id": "new_function_with_arguments_in_default",
|
|
226
|
+
"category": "creation_order",
|
|
227
|
+
"description": "A new default calling a new function with arguments, identified by its signature.",
|
|
228
|
+
"before": "CREATE TABLE t (id int);",
|
|
229
|
+
"after": "CREATE FUNCTION addn(x integer) RETURNS integer LANGUAGE sql IMMUTABLE AS $$ SELECT x + 1 $$; CREATE TABLE t (id int DEFAULT addn(1));"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "new_trigger_and_its_function",
|
|
233
|
+
"category": "creation_order",
|
|
234
|
+
"description": "A new trigger with its new trigger function.",
|
|
235
|
+
"before": "CREATE TABLE t (id int);",
|
|
236
|
+
"after": "CREATE TABLE t (id int); CREATE FUNCTION tfn() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE INSERT ON t FOR EACH ROW EXECUTE FUNCTION tfn();"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "new_table_typed_by_new_enum",
|
|
240
|
+
"category": "creation_order",
|
|
241
|
+
"description": "A new table with a column typed by a new enum: the type is created before the table, and dropped after it when reverting.",
|
|
242
|
+
"before": "CREATE TABLE keep (id int);",
|
|
243
|
+
"after": "CREATE TABLE keep (id int); CREATE TYPE e AS ENUM ('a'); CREATE TABLE t (id int, c e);"
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"id": "new_domain_typed_column",
|
|
247
|
+
"category": "creation_order",
|
|
248
|
+
"description": "A new column typed by a new domain with a CHECK: the domain is created first, and dropped last when reverting.",
|
|
249
|
+
"before": "CREATE TABLE t (id int);",
|
|
250
|
+
"after": "CREATE DOMAIN d AS int CHECK (VALUE > 0); CREATE TABLE t (id int, c d);"
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"id": "new_function_with_defaulted_argument_in_default_and_check",
|
|
254
|
+
"category": "creation_order",
|
|
255
|
+
"description": "A new function whose second argument has a default, called with one argument from a column default and a CHECK: identified by its full signature, and created before both.",
|
|
256
|
+
"before": "CREATE TABLE t (id int);",
|
|
257
|
+
"after": "CREATE FUNCTION addn(x integer, y text DEFAULT $$a$$) RETURNS integer LANGUAGE sql IMMUTABLE AS $$ SELECT x + 1 $$; CREATE TABLE t (id int DEFAULT addn(1), c int CHECK (c < addn(100)));"
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"id": "default_changed_to_call_new_function",
|
|
261
|
+
"category": "creation_order",
|
|
262
|
+
"description": "An existing column's default changes to call a new function: the function is created before the default is set, and dropped after it is restored when reverting.",
|
|
263
|
+
"before": "CREATE TABLE t (id int DEFAULT 0);",
|
|
264
|
+
"after": "CREATE FUNCTION addn(x integer, y text DEFAULT $$a$$) RETURNS integer LANGUAGE sql IMMUTABLE AS $$ SELECT x + 1 $$; CREATE TABLE t (id int DEFAULT addn(5));"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"id": "new_function_reading_new_table",
|
|
268
|
+
"category": "creation_order",
|
|
269
|
+
"description": "A new SQL function reading a new table: SQL function bodies are checked when created, so the table comes first, and goes last when reverting.",
|
|
270
|
+
"before": "CREATE TABLE keep (id int);",
|
|
271
|
+
"after": "CREATE TABLE keep (id int); CREATE TABLE t (id int); CREATE FUNCTION n() RETURNS bigint LANGUAGE sql AS $$ SELECT count(*) FROM t $$;"
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"id": "removed_enum_used_everywhere",
|
|
275
|
+
"category": "removal_order",
|
|
276
|
+
"description": "An enum the desired schema no longer has, used by a domain, a column, a function argument and a view: it is dropped last, and recreated first when reverting.",
|
|
277
|
+
"before": "CREATE TYPE e AS ENUM ('a','b'); CREATE DOMAIN d AS e; CREATE TABLE t (id int, c e); CREATE FUNCTION fe(x e) RETURNS int LANGUAGE sql AS $$ SELECT 1 $$; CREATE VIEW v AS SELECT id, c, 'a'::e AS k FROM t;",
|
|
278
|
+
"after": "CREATE TABLE t (id int);"
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
"id": "removed_sequence_and_function_in_defaults",
|
|
282
|
+
"category": "removal_order",
|
|
283
|
+
"description": "A sequence and a function used only by defaults of columns that go too.",
|
|
284
|
+
"before": "CREATE SEQUENCE s; CREATE FUNCTION g() RETURNS int LANGUAGE sql AS $$ SELECT 1 $$; CREATE TABLE t (id int DEFAULT nextval('s'), x int DEFAULT g());",
|
|
285
|
+
"after": "CREATE TABLE t (id int);"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"id": "removed_table_typed_by_removed_enum",
|
|
289
|
+
"category": "removal_order",
|
|
290
|
+
"description": "A table with a view on it, typed by an enum that goes with it.",
|
|
291
|
+
"before": "CREATE TYPE e AS ENUM ('a'); CREATE TABLE t (id int); CREATE TABLE t2 (id int, c e); CREATE VIEW v2 AS SELECT * FROM t2;",
|
|
292
|
+
"after": "CREATE TABLE t (id int);"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"id": "removed_function_reading_removed_table",
|
|
296
|
+
"category": "removal_order",
|
|
297
|
+
"description": "A SQL function reading a table that goes too; when reverting, the table must be back before the function body is validated.",
|
|
298
|
+
"before": "CREATE TABLE t (id int); CREATE TABLE t2 (id int); CREATE FUNCTION n() RETURNS bigint LANGUAGE sql AS $$ SELECT count(*) FROM t2 $$;",
|
|
299
|
+
"after": "CREATE TABLE t (id int);"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"id": "removed_trigger_on_removed_table",
|
|
303
|
+
"category": "removal_order",
|
|
304
|
+
"description": "A trigger and its function on a table that goes too.",
|
|
305
|
+
"before": "CREATE TABLE t (id int); CREATE TABLE t2 (id int); CREATE FUNCTION tfn() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; CREATE TRIGGER tr BEFORE INSERT ON t2 FOR EACH ROW EXECUTE FUNCTION tfn();",
|
|
306
|
+
"after": "CREATE TABLE t (id int);"
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
"id": "removed_composite_over_removed_enum",
|
|
310
|
+
"category": "removal_order",
|
|
311
|
+
"description": "A column typed by a composite holding an enum; all three go.",
|
|
312
|
+
"before": "CREATE TYPE e AS ENUM ('a'); CREATE TYPE c AS (x e, y int); CREATE TABLE t (id int, v c);",
|
|
313
|
+
"after": "CREATE TABLE t (id int);"
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
"id": "removed_function_in_check_and_index",
|
|
317
|
+
"category": "removal_order",
|
|
318
|
+
"description": "A function used by a CHECK and an expression index that go first.",
|
|
319
|
+
"before": "CREATE FUNCTION lim() RETURNS int LANGUAGE sql IMMUTABLE AS $$ SELECT 10 $$; CREATE TABLE t (id int CHECK (id < lim())); CREATE INDEX t_l ON t ((id + lim()));",
|
|
320
|
+
"after": "CREATE TABLE t (id int);"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"id": "foreign_key_onto_another_schema",
|
|
324
|
+
"category": "foreign_key",
|
|
325
|
+
"description": "A foreign key onto a table in another schema, as every Supabase table referencing auth.users has; the reference must keep its schema.",
|
|
326
|
+
"before": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b)); CREATE TABLE profiles (id int, user_id int);",
|
|
327
|
+
"after": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b)); CREATE TABLE profiles (id int, user_id int REFERENCES app.users (id));"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"id": "foreign_key_over_two_columns",
|
|
331
|
+
"category": "foreign_key",
|
|
332
|
+
"description": "A foreign key over two columns; the reference must list both.",
|
|
333
|
+
"before": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b)); CREATE TABLE c (x int, y int);",
|
|
334
|
+
"after": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b)); CREATE TABLE c (x int, y int, FOREIGN KEY (x, y) REFERENCES p (a, b));"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"id": "foreign_keys_on_new_table",
|
|
338
|
+
"category": "foreign_key",
|
|
339
|
+
"description": "A new table with both a key onto another schema's table and a key over two columns: each reference must keep its schema and every column.",
|
|
340
|
+
"before": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b));",
|
|
341
|
+
"after": "CREATE SCHEMA app; CREATE TABLE app.users (id int PRIMARY KEY); CREATE TABLE p (a int, b int, PRIMARY KEY (a, b)); CREATE TABLE profiles (id int, user_id int REFERENCES app.users (id), x int, y int, FOREIGN KEY (x, y) REFERENCES p (a, b));"
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
"id": "objects_in_quoted_schema",
|
|
345
|
+
"category": "schemas",
|
|
346
|
+
"description": "A schema, table, columns and index whose names need quoting — spaces, capitals, a reserved word, non-ASCII — with a view and a column comment. Every name must stay quoted, in the right schema.",
|
|
347
|
+
"before": "CREATE SCHEMA \"App Data\"; CREATE TABLE \"App Data\".\"Order Items\" (\"ID\" int PRIMARY KEY, \"Qty\" int, \"select\" text);",
|
|
348
|
+
"after": "CREATE SCHEMA \"App Data\"; CREATE TABLE \"App Data\".\"Order Items\" (\"ID\" int PRIMARY KEY, \"Qty\" int NOT NULL DEFAULT 1, \"select\" text, \"ünïcödé\" text); CREATE INDEX \"Qty Idx\" ON \"App Data\".\"Order Items\" (\"Qty\"); CREATE VIEW \"App Data\".\"Big Orders\" AS SELECT \"ID\", \"Qty\" FROM \"App Data\".\"Order Items\" WHERE \"Qty\" > 10; COMMENT ON COLUMN \"App Data\".\"Order Items\".\"select\" IS 'reserved word';"
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"id": "objects_in_another_schema",
|
|
352
|
+
"category": "schemas",
|
|
353
|
+
"description": "An enum, a table with a deferrable foreign key and a function, all in a schema other than public. A tool that looks only at public reports the two databases identical.",
|
|
354
|
+
"before": "CREATE SCHEMA app; CREATE TABLE app.accounts (id int PRIMARY KEY);",
|
|
355
|
+
"after": "CREATE SCHEMA app; CREATE TYPE app.tier AS ENUM ('free', 'pro'); CREATE TABLE app.accounts (id int PRIMARY KEY, tier app.tier NOT NULL DEFAULT 'free'); CREATE TABLE app.invoices (id int PRIMARY KEY, account_id int REFERENCES app.accounts (id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED); CREATE FUNCTION app.tier_of(int) RETURNS app.tier LANGUAGE sql STABLE AS $$ SELECT tier FROM app.accounts WHERE id = $1 $$;"
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"id": "function_overloads",
|
|
359
|
+
"category": "routines",
|
|
360
|
+
"description": "Three functions of one name told apart by their arguments: one's body changes, one is unchanged, one is new and has a defaulted argument.",
|
|
361
|
+
"before": "CREATE FUNCTION f(x int) RETURNS int LANGUAGE sql AS $$ SELECT x $$; CREATE FUNCTION f(x text) RETURNS text LANGUAGE sql AS $$ SELECT x $$;",
|
|
362
|
+
"after": "CREATE FUNCTION f(x int) RETURNS int LANGUAGE sql AS $$ SELECT x + 1 $$; CREATE FUNCTION f(x text) RETURNS text LANGUAGE sql AS $$ SELECT x $$; CREATE FUNCTION f(x int, y int DEFAULT 0) RETURNS int LANGUAGE sql AS $$ SELECT x + y $$;"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"id": "partitions_added_with_default_and_index",
|
|
366
|
+
"category": "partitions",
|
|
367
|
+
"description": "A range-partitioned table gains a partition, a default partition and an index on the parent. The index must reach every partition, the new ones included.",
|
|
368
|
+
"before": "CREATE TABLE ev (id int, at date NOT NULL, kind text) PARTITION BY RANGE (at); CREATE TABLE ev_2025 PARTITION OF ev FOR VALUES FROM ('2025-01-01') TO ('2026-01-01');",
|
|
369
|
+
"after": "CREATE TABLE ev (id int, at date NOT NULL, kind text) PARTITION BY RANGE (at); CREATE TABLE ev_2025 PARTITION OF ev FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); CREATE TABLE ev_2026 PARTITION OF ev FOR VALUES FROM ('2026-01-01') TO ('2027-01-01'); CREATE TABLE ev_other PARTITION OF ev DEFAULT; CREATE INDEX ev_kind ON ev (kind);"
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"id": "rls_forced_with_restrictive_policy",
|
|
373
|
+
"category": "rls",
|
|
374
|
+
"description": "Row level security enabled and forced, a permissive SELECT policy and a restrictive INSERT policy with a WITH CHECK.",
|
|
375
|
+
"before": "CREATE TABLE docs (id int PRIMARY KEY, owner text, body text);",
|
|
376
|
+
"after": "CREATE TABLE docs (id int PRIMARY KEY, owner text, body text); ALTER TABLE docs ENABLE ROW LEVEL SECURITY; ALTER TABLE docs FORCE ROW LEVEL SECURITY; CREATE POLICY docs_read ON docs FOR SELECT USING (owner = current_user); CREATE POLICY docs_write ON docs AS RESTRICTIVE FOR INSERT WITH CHECK (owner = current_user AND length(body) < 1000);"
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
"id": "trigger_with_condition_and_indexed_matview",
|
|
380
|
+
"category": "triggers",
|
|
381
|
+
"description": "A trigger on an UPDATE OF one column with a WHEN condition, its function, and a materialized view with a unique index and a comment.",
|
|
382
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY, n int, touched timestamptz);",
|
|
383
|
+
"after": "CREATE TABLE t (id int PRIMARY KEY, n int, touched timestamptz); CREATE FUNCTION touch() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.touched := now(); RETURN NEW; END $$; CREATE TRIGGER t_touch BEFORE UPDATE OF n ON t FOR EACH ROW WHEN (OLD.n IS DISTINCT FROM NEW.n) EXECUTE FUNCTION touch(); CREATE MATERIALIZED VIEW t_sum AS SELECT count(*) AS c, sum(n) AS s FROM t; CREATE UNIQUE INDEX t_sum_c ON t_sum (c); COMMENT ON MATERIALIZED VIEW t_sum IS 'rollup';"
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
"id": "column_types_defaults_and_collation",
|
|
387
|
+
"category": "types",
|
|
388
|
+
"description": "Columns with an array default, a jsonb default, an interval default, a domain with its own default and CHECK, an explicit collation and an identity with START and INCREMENT.",
|
|
389
|
+
"before": "CREATE TABLE ty (id int PRIMARY KEY);",
|
|
390
|
+
"after": "CREATE DOMAIN pos AS numeric(12,4) DEFAULT 0 CHECK (VALUE >= 0); CREATE TABLE ty (id int PRIMARY KEY, tags text[] DEFAULT '{}', meta jsonb NOT NULL DEFAULT '{\"a\": 1}'::jsonb, wait interval DEFAULT '5 minutes', amt pos, name text COLLATE \"C\", seq_n bigint GENERATED BY DEFAULT AS IDENTITY (START WITH 100 INCREMENT BY 5));"
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
"id": "sequence_shared_and_owned_with_options",
|
|
394
|
+
"category": "sequences",
|
|
395
|
+
"description": "A standalone sequence with START and CACHE, used by two tables' defaults and owned by one table's column. Its options must survive, and it is a sequence of its own, not a serial.",
|
|
396
|
+
"before": "CREATE TABLE a (id int PRIMARY KEY);",
|
|
397
|
+
"after": "CREATE SEQUENCE shared_seq START 1000 CACHE 10; CREATE TABLE a (id int PRIMARY KEY DEFAULT nextval('shared_seq')); CREATE TABLE b (id int PRIMARY KEY DEFAULT nextval('shared_seq')); ALTER SEQUENCE shared_seq OWNED BY a.id;"
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"id": "deferrable_unique_check_and_partial_expression_index",
|
|
401
|
+
"category": "constraints",
|
|
402
|
+
"description": "A deferrable UNIQUE constraint, a CHECK across two columns and a partial unique index on an expression.",
|
|
403
|
+
"before": "CREATE TABLE cu (id int PRIMARY KEY, email text, a int, b int);",
|
|
404
|
+
"after": "CREATE TABLE cu (id int PRIMARY KEY, email text, a int, b int, CONSTRAINT cu_email UNIQUE (email) DEFERRABLE, CONSTRAINT cu_ab CHECK (a < b)); CREATE UNIQUE INDEX cu_lower ON cu (lower(email)) WHERE email IS NOT NULL;"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
"id": "policy_follows_column_retype_beside_serial_to_identity",
|
|
408
|
+
"category": "ordering",
|
|
409
|
+
"description": "A policy compares a column to uid() once the column becomes uuid, while another table's serial becomes an identity. The policy can only be created after the retype; run before it, it fails with: operator does not exist: text = uuid.",
|
|
410
|
+
"before": "CREATE FUNCTION uid() RETURNS uuid LANGUAGE sql STABLE AS $$ SELECT '00000000-0000-0000-0000-000000000001'::uuid $$; CREATE TABLE a_orders (id serial PRIMARY KEY, total int); CREATE TABLE p_profiles (id int PRIMARY KEY, user_id text); ALTER TABLE p_profiles ENABLE ROW LEVEL SECURITY; CREATE POLICY own ON p_profiles USING (user_id = uid()::text);",
|
|
411
|
+
"after": "CREATE FUNCTION uid() RETURNS uuid LANGUAGE sql STABLE AS $$ SELECT '00000000-0000-0000-0000-000000000001'::uuid $$; CREATE TABLE a_orders (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, total int); CREATE TABLE p_profiles (id int PRIMARY KEY, user_id uuid); ALTER TABLE p_profiles ENABLE ROW LEVEL SECURITY; CREATE POLICY own ON p_profiles USING (user_id = uid());"
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
"id": "schema_created_with_references_across_schemas",
|
|
415
|
+
"category": "schemas",
|
|
416
|
+
"description": "A new schema with a type and a table whose key references a new table in public, and a public view reading the new schema. The schema comes first, the referenced table before the one referencing it whatever their schemas' names, and the view last; the reverse drops the schema once it is empty.",
|
|
417
|
+
"before": "CREATE TABLE unrelated (id int PRIMARY KEY);",
|
|
418
|
+
"after": "CREATE TABLE unrelated (id int PRIMARY KEY); CREATE TABLE customers (id int PRIMARY KEY); CREATE SCHEMA billing; CREATE TYPE billing.state AS ENUM ('open', 'paid'); CREATE TABLE billing.invoices (id int PRIMARY KEY, customer_id int NOT NULL REFERENCES public.customers (id), state billing.state NOT NULL DEFAULT 'open'); CREATE VIEW open_invoices AS SELECT id, customer_id FROM billing.invoices WHERE state = 'open';"
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"id": "same_table_name_in_two_schemas",
|
|
422
|
+
"category": "schemas",
|
|
423
|
+
"description": "Tables of one name in two schemas change differently; each change must land on its own table.",
|
|
424
|
+
"before": "CREATE SCHEMA a; CREATE SCHEMA b; CREATE TABLE a.t (id int PRIMARY KEY); CREATE TABLE b.t (id int PRIMARY KEY, y int); INSERT INTO a.t VALUES (1), (2); INSERT INTO b.t VALUES (1, 5);",
|
|
425
|
+
"after": "CREATE SCHEMA a; CREATE SCHEMA b; CREATE TABLE a.t (id int PRIMARY KEY, x text NOT NULL DEFAULT ''); CREATE TABLE b.t (id int PRIMARY KEY); CREATE INDEX t_x ON a.t (x); INSERT INTO a.t VALUES (1, 'p'), (2, 'q'); INSERT INTO b.t VALUES (1);",
|
|
426
|
+
"preserve": [
|
|
427
|
+
"SELECT id FROM a.t ORDER BY id",
|
|
428
|
+
"SELECT id FROM b.t ORDER BY id"
|
|
429
|
+
]
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"id": "indexed_column_added",
|
|
433
|
+
"category": "indexes",
|
|
434
|
+
"description": "A column arrives with an index on it. Dropping the column takes the index with it, so the reverse drops the index first; dropped after, it no longer exists.",
|
|
435
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY); INSERT INTO t VALUES (1), (2);",
|
|
436
|
+
"after": "CREATE TABLE t (id int PRIMARY KEY, x text); CREATE INDEX t_x ON t (x); CREATE UNIQUE INDEX t_lower_x ON t (lower(x)) WHERE x IS NOT NULL; INSERT INTO t VALUES (1, 'a'), (2, 'b');",
|
|
437
|
+
"preserve": [
|
|
438
|
+
"SELECT id FROM t ORDER BY id"
|
|
439
|
+
]
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"id": "comment_on_a_column_added_last",
|
|
443
|
+
"category": "comments",
|
|
444
|
+
"description": "A commented column the target gains after its other columns, so its position differs from the source's. The same comment on the same column is no difference: comments are compared by the column's name, never its position.",
|
|
445
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY, note text); INSERT INTO t VALUES (1, 'a');",
|
|
446
|
+
"after": "CREATE TABLE t (id int PRIMARY KEY, tier text NOT NULL DEFAULT 'free', note text); COMMENT ON COLUMN t.tier IS 'billing tier'; COMMENT ON COLUMN t.note IS 'free text'; INSERT INTO t VALUES (1, 'free', 'a');",
|
|
447
|
+
"preserve": [
|
|
448
|
+
"SELECT id, note FROM t ORDER BY id"
|
|
449
|
+
]
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
"id": "check_added_not_valid",
|
|
453
|
+
"category": "constraints",
|
|
454
|
+
"description": "A check added NOT VALID over a row it does not hold for. It must arrive NOT VALID: validated, as it would be inside CREATE TABLE, it would refuse the row it was added beside.",
|
|
455
|
+
"before": "CREATE TABLE t (id int PRIMARY KEY, n int); INSERT INTO t VALUES (1, -5), (2, 3);",
|
|
456
|
+
"after": "CREATE TABLE t (id int PRIMARY KEY, n int); INSERT INTO t VALUES (1, -5), (2, 3); ALTER TABLE t ADD CONSTRAINT t_n_positive CHECK (n > 0) NOT VALID;",
|
|
457
|
+
"preserve": [
|
|
458
|
+
"SELECT id, n FROM t ORDER BY id"
|
|
459
|
+
]
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"id": "circular_foreign_keys",
|
|
463
|
+
"category": "constraints",
|
|
464
|
+
"description": "Two new tables referencing each other. No order creates both with their keys: one key is added once both exist, and the reverse drops it before either table.",
|
|
465
|
+
"before": "CREATE TABLE unrelated (id int PRIMARY KEY);",
|
|
466
|
+
"after": "CREATE TABLE unrelated (id int PRIMARY KEY); CREATE TABLE a (id int PRIMARY KEY, b_id int); CREATE TABLE b (id int PRIMARY KEY, a_id int REFERENCES a (id) DEFERRABLE INITIALLY DEFERRED); ALTER TABLE a ADD CONSTRAINT a_b_fk FOREIGN KEY (b_id) REFERENCES b (id) DEFERRABLE INITIALLY DEFERRED;"
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
"id": "subpartitioned_partition_added",
|
|
470
|
+
"category": "partitions",
|
|
471
|
+
"description": "A partition that is partitioned in turn, with partitions of its own. Created as a plain partition, its own partitions have nowhere to go.",
|
|
472
|
+
"before": "CREATE TABLE h (id int, r text, s text) PARTITION BY LIST (r); CREATE TABLE h_us PARTITION OF h FOR VALUES IN ('us'); INSERT INTO h VALUES (1, 'us', 'a');",
|
|
473
|
+
"after": "CREATE TABLE h (id int, r text, s text) PARTITION BY LIST (r); CREATE TABLE h_us PARTITION OF h FOR VALUES IN ('us'); CREATE TABLE h_eu PARTITION OF h FOR VALUES IN ('eu') PARTITION BY LIST (s); CREATE TABLE h_eu_a PARTITION OF h_eu FOR VALUES IN ('a'); CREATE TABLE h_eu_b PARTITION OF h_eu FOR VALUES IN ('b'); INSERT INTO h VALUES (1, 'us', 'a');",
|
|
474
|
+
"preserve": [
|
|
475
|
+
"SELECT id, r, s FROM h ORDER BY id"
|
|
476
|
+
]
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
"id": "partition_added_with_its_own_constraint_and_index",
|
|
480
|
+
"category": "partitions",
|
|
481
|
+
"description": "A new partition carrying a check and an index of its own, beside the parent's index it inherits. Declared against the parent alone, it arrives without them.",
|
|
482
|
+
"before": "CREATE TABLE h (id int, d date, n int) PARTITION BY RANGE (d); CREATE INDEX h_d ON h (d); CREATE TABLE h_2024 PARTITION OF h FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); INSERT INTO h VALUES (1, '2024-03-01', 2);",
|
|
483
|
+
"after": "CREATE TABLE h (id int, d date, n int) PARTITION BY RANGE (d); CREATE INDEX h_d ON h (d); CREATE TABLE h_2024 PARTITION OF h FOR VALUES FROM ('2024-01-01') TO ('2025-01-01'); CREATE TABLE h_2025 PARTITION OF h FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); ALTER TABLE h_2025 ADD CONSTRAINT h_2025_n CHECK (n > 0); CREATE INDEX h_2025_own ON h_2025 (n); INSERT INTO h VALUES (1, '2024-03-01', 2);",
|
|
484
|
+
"preserve": [
|
|
485
|
+
"SELECT id, d, n FROM h ORDER BY id"
|
|
486
|
+
]
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
"id": "exclusion_constraint_needing_an_extension",
|
|
490
|
+
"category": "extensions",
|
|
491
|
+
"description": "An exclusion constraint whose operator class comes from btree_gist. The extension is installed first, and the reverse drops it after the constraint.",
|
|
492
|
+
"before": "CREATE TABLE bookings (id int PRIMARY KEY, room int, during tsrange);",
|
|
493
|
+
"after": "CREATE EXTENSION IF NOT EXISTS btree_gist; CREATE TABLE bookings (id int PRIMARY KEY, room int, during tsrange, cancelled boolean DEFAULT false, CONSTRAINT no_double_booking EXCLUDE USING gist (room WITH =, during WITH &&) WHERE (NOT cancelled));"
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
"id": "serial_default_dropped",
|
|
497
|
+
"category": "identity",
|
|
498
|
+
"description": "A serial column's default dropped. The sequence stays, still owned by the column; dropping it too loses its position.",
|
|
499
|
+
"before": "CREATE TABLE t (id serial PRIMARY KEY, v int); INSERT INTO t (v) VALUES (1), (2);",
|
|
500
|
+
"after": "CREATE TABLE t (id serial PRIMARY KEY, v int); INSERT INTO t (v) VALUES (1), (2); ALTER TABLE t ALTER id DROP DEFAULT;",
|
|
501
|
+
"preserve": [
|
|
502
|
+
"SELECT id, v FROM t ORDER BY id"
|
|
503
|
+
]
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
"id": "quoted_identifiers",
|
|
507
|
+
"category": "names",
|
|
508
|
+
"description": "Names holding double quotes, spaces and capitals: a table, its columns, a check and a unique constraint, an index, a view, an enum type, a policy and comments. Each must be written with its quotes doubled; one written between plain quotes is a syntax error that fails the whole migration.",
|
|
509
|
+
"before": "CREATE TABLE \"Odd \"\"t\"\"\" (id int PRIMARY KEY); INSERT INTO \"Odd \"\"t\"\"\" (id) VALUES (1), (2);",
|
|
510
|
+
"after": "CREATE TYPE \"Mood \"\"m\"\"\" AS ENUM ('a', 'b');CREATE TABLE \"Odd \"\"t\"\"\" (id int PRIMARY KEY, \"Col \"\"c\"\"\" text CONSTRAINT \"chk \"\"x\"\"\" CHECK (\"Col \"\"c\"\"\" <> ''), \"m \"\"q\"\"\" \"Mood \"\"m\"\"\", CONSTRAINT \"uq \"\"u\"\"\" UNIQUE (\"Col \"\"c\"\"\")); INSERT INTO \"Odd \"\"t\"\"\" (id) VALUES (1), (2);CREATE INDEX \"ix \"\"i\"\"\" ON \"Odd \"\"t\"\"\" (\"m \"\"q\"\"\");CREATE VIEW \"v \"\"w\"\"\" AS SELECT id, \"Col \"\"c\"\"\" FROM \"Odd \"\"t\"\"\";ALTER TABLE \"Odd \"\"t\"\"\" ENABLE ROW LEVEL SECURITY;CREATE POLICY \"say \"\"hi\"\" there\" ON \"Odd \"\"t\"\"\" FOR SELECT USING (true);COMMENT ON POLICY \"say \"\"hi\"\" there\" ON \"Odd \"\"t\"\"\" IS 'a \"quoted\" note';COMMENT ON COLUMN \"Odd \"\"t\"\"\".\"Col \"\"c\"\"\" IS 'c';",
|
|
511
|
+
"preserve": [
|
|
512
|
+
"SELECT id FROM \"Odd \"\"t\"\"\" ORDER BY id"
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
]
|