Skip to content

Commit 401d13a

Browse files
committed
add new test for creating constraints
1 parent e224389 commit 401d13a

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

pkg/migrations/op_common_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,13 @@ func UniqueConstraintMustExist(t *testing.T, db *sql.DB, schema, table, constrai
253253
}
254254
}
255255

256+
func UniqueNullsNotDistinctConstraintMustExist(t *testing.T, db *sql.DB, schema, table, constraint string) {
257+
t.Helper()
258+
if !uniqueNullsNotDistinctConstraintExists(t, db, schema, table, constraint) {
259+
t.Fatalf("Expected unique constraint %q to exist", constraint)
260+
}
261+
}
262+
256263
func ValidatedForeignKeyMustExist(t *testing.T, db *sql.DB, schema, table, constraint string) {
257264
t.Helper()
258265
if !foreignKeyExists(t, db, schema, table, constraint, true, migrations.ForeignKeyActionNOACTION, migrations.ForeignKeyActionNOACTION) {
@@ -316,6 +323,13 @@ func IndexDescendingMustExist(t *testing.T, db *sql.DB, schema, table, index str
316323
}
317324
}
318325

326+
func IndexUniqueNullsNotDistinctMustExist(t *testing.T, db *sql.DB, schema, table, index string) {
327+
t.Helper()
328+
if !indexUniqueNullsNotDistinctExists(t, db, schema, table, index) {
329+
t.Fatalf("Expected unique index with nulls not distinct %q to exist", index)
330+
}
331+
}
332+
319333
func IndexMustNotExist(t *testing.T, db *sql.DB, schema, table, index string) {
320334
t.Helper()
321335
if indexExists(t, db, schema, table, index) {
@@ -364,6 +378,33 @@ func indexExists(t *testing.T, db *sql.DB, schema, table, index string) bool {
364378
return exists
365379
}
366380

381+
func indexUniqueNullsNotDistinctExists(t *testing.T, db *sql.DB, schema, table, index string) bool {
382+
t.Helper()
383+
384+
var exists bool
385+
err := db.QueryRow(`
386+
SELECT EXISTS (
387+
SELECT
388+
1
389+
FROM
390+
pg_index
391+
JOIN pg_class ON pg_class.oid = pg_index.indexrelid
392+
JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
393+
WHERE
394+
pg_namespace.nspname = $1
395+
AND pg_index.indrelid = $2::regclass
396+
AND pg_index.indexrelid = $3::regclass
397+
AND pg_index.indisunique
398+
AND pg_index.indnullsnotdistinct
399+
)`,
400+
schema, table, index).Scan(&exists)
401+
if err != nil {
402+
t.Fatal(err)
403+
}
404+
405+
return exists
406+
}
407+
367408
func indexDescendingExists(t *testing.T, db *sql.DB, schema, table, index string, columnIdx int) bool {
368409
t.Helper()
369410

@@ -445,6 +486,28 @@ func uniqueConstraintExists(t *testing.T, db *sql.DB, schema, table, constraint
445486
return exists
446487
}
447488

489+
func uniqueNullsNotDistinctConstraintExists(t *testing.T, db *sql.DB, schema, table, constraint string) bool {
490+
t.Helper()
491+
492+
var exists bool
493+
err := db.QueryRow(`
494+
SELECT EXISTS (
495+
SELECT 1
496+
FROM pg_constraint
497+
JOIN pg_index ON pg_index.indexrelid = pg_constraint.conindid
498+
WHERE pg_constraint.conrelid = $1::regclass
499+
AND pg_constraint.conname = $2
500+
AND pg_constraint.contype = 'u'
501+
AND pg_index.indnullsnotdistinct
502+
)`,
503+
fmt.Sprintf("%s.%s", schema, table), constraint).Scan(&exists)
504+
if err != nil {
505+
t.Fatal(err)
506+
}
507+
508+
return exists
509+
}
510+
448511
func referentialAction(a migrations.ForeignKeyAction) string {
449512
switch a {
450513
case migrations.ForeignKeyActionNOACTION:

pkg/migrations/op_create_constraint_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,108 @@ func TestCreateConstraint(t *testing.T) {
172172
}, testutils.CheckViolationErrorCode)
173173
},
174174
},
175+
{
176+
name: "create unique constraint on single column where nulls not distinct",
177+
minPgMajorVersion: 15,
178+
migrations: []migrations.Migration{
179+
{
180+
Name: "01_add_table",
181+
Operations: migrations.Operations{
182+
&migrations.OpCreateTable{
183+
Name: "users",
184+
Columns: []migrations.Column{
185+
{
186+
Name: "id",
187+
Type: "serial",
188+
Pk: true,
189+
},
190+
{
191+
Name: "name",
192+
Type: "varchar(255)",
193+
Nullable: true,
194+
},
195+
},
196+
},
197+
},
198+
},
199+
{
200+
Name: "02_create_constraint",
201+
Operations: migrations.Operations{
202+
&migrations.OpCreateConstraint{
203+
Name: "unique_name",
204+
Table: "users",
205+
Type: "unique",
206+
Columns: []string{"name"},
207+
Up: map[string]string{
208+
"name": "random()",
209+
},
210+
Down: map[string]string{
211+
"name": "name",
212+
},
213+
NullsNotDistinct: true,
214+
},
215+
},
216+
},
217+
},
218+
afterStart: func(t *testing.T, db *sql.DB, schema string) {
219+
// The index has been created on the underlying table.
220+
IndexUniqueNullsNotDistinctMustExist(t, db, schema, "users", "unique_name")
221+
222+
// Inserting values into the old schema that violate uniqueness should succeed.
223+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
224+
"name": "alice",
225+
})
226+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
227+
"name": "alice",
228+
})
229+
// NULLs are considered distinct, so this succeeds.
230+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
231+
"id": "300",
232+
})
233+
MustInsert(t, db, schema, "01_add_table", "users", map[string]string{
234+
"id": "301",
235+
})
236+
237+
// Inserting values into the new schema that violate uniqueness should fail.
238+
MustInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
239+
"id": "102",
240+
})
241+
MustNotInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
242+
"id": "103",
243+
}, testutils.UniqueViolationErrorCode)
244+
},
245+
afterRollback: func(t *testing.T, db *sql.DB, schema string) {
246+
// The index has been dropped from the the underlying table.
247+
IndexMustNotExist(t, db, schema, "users", "uniue_name")
248+
249+
// Functions, triggers and temporary columns are dropped.
250+
TableMustBeCleanedUp(t, db, schema, "users", "name")
251+
},
252+
afterComplete: func(t *testing.T, db *sql.DB, schema string) {
253+
// Functions, triggers and temporary columns are dropped.
254+
TableMustBeCleanedUp(t, db, schema, "users", "name")
255+
256+
// The index is transferred to a constraint.
257+
UniqueNullsNotDistinctConstraintMustExist(t, db, schema, "users", "unique_name")
258+
259+
// Inserting values into the new schema that violate uniqueness should fail.
260+
MustInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
261+
"name": "carol",
262+
})
263+
MustNotInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
264+
"name": "carol",
265+
}, testutils.UniqueViolationErrorCode)
266+
267+
// Inserting values into the new schema that violate uniqueness should fail.
268+
// Uniqueness violated because NULLs are not distinct.
269+
MustInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
270+
"id": "200",
271+
})
272+
MustNotInsert(t, db, schema, "02_create_constraint", "users", map[string]string{
273+
"id": "201",
274+
}, testutils.UniqueViolationErrorCode)
275+
},
276+
},
175277
{
176278
name: "create unique constraint on multiple columns",
177279
migrations: []migrations.Migration{

0 commit comments

Comments
 (0)