Skip to content

Commit e1bad97

Browse files
committed
Add support for nulls_not_distinct option in create_constraint operations
Add support for `nulls_not_distinct` option in `create_constraint` operations for unique constraints.
1 parent c15326b commit e1bad97

File tree

7 files changed

+157
-8
lines changed

7 files changed

+157
-8
lines changed

docs/operations/create_constraint.mdx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Required fields: `name`, `table`, `type`, `up`, `down`.
1818
"type": "unique"| "check" | "foreign_key",
1919
"check": "SQL expression for CHECK constraint",
2020
"no_inherit": "true|false",
21+
"nulls_not_distinct": "true|false",
2122
"references": {
2223
"name": "name of foreign key reference",
2324
"table": "name of referenced table",
@@ -62,6 +63,26 @@ Add a multi-column unique constraint on the `tickets` table:
6263

6364
<ExampleSnippet example="44_add_table_unique_constraint.json" language="json" />
6465

66+
Add a unique constraint with `nulls_not_distinct` option on the `users` table:
67+
68+
```json
69+
{
70+
"create_constraint": {
71+
"table": "users",
72+
"name": "unique_email",
73+
"columns": ["email"],
74+
"type": "unique",
75+
"nulls_not_distinct": true,
76+
"up": {
77+
"email": "email"
78+
},
79+
"down": {
80+
"email": "email"
81+
}
82+
}
83+
}
84+
```
85+
6586
### Add a `CHECK` constraint
6687

6788
Add a check constraint on the `sellers_name` and `sellers_zip` fields on the `ticket` table. The `up` SQL expression ensures that pairs of values not meeting the new constraint on the old columns are data migrated to values that meet the new constraint in the new columns:

internal/jsonschema/jsonschema_test.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// SPDX-License-Identifier: Apache-2.0
2-
31
package jsonschema
42

53
import (
@@ -52,3 +50,71 @@ func TestJSONSchemaValidation(t *testing.T) {
5250
})
5351
}
5452
}
53+
54+
func TestNullsNotDistinctOption(t *testing.T) {
55+
t.Parallel()
56+
57+
compiler := jsonschema.NewCompiler()
58+
sch, err := compiler.Compile(schemaPath)
59+
assert.NoError(t, err)
60+
61+
testCases := []struct {
62+
name string
63+
jsonData string
64+
shouldValidate bool
65+
}{
66+
{
67+
name: "valid nulls_not_distinct",
68+
jsonData: `{
69+
"create_constraint": {
70+
"table": "users",
71+
"name": "unique_email",
72+
"columns": ["email"],
73+
"type": "unique",
74+
"nulls_not_distinct": true,
75+
"up": {
76+
"email": "email"
77+
},
78+
"down": {
79+
"email": "email"
80+
}
81+
}
82+
}`,
83+
shouldValidate: true,
84+
},
85+
{
86+
name: "invalid nulls_not_distinct for non-unique constraint",
87+
jsonData: `{
88+
"create_constraint": {
89+
"table": "users",
90+
"name": "check_email",
91+
"columns": ["email"],
92+
"type": "check",
93+
"check": "email IS NOT NULL",
94+
"nulls_not_distinct": true,
95+
"up": {
96+
"email": "email"
97+
},
98+
"down": {
99+
"email": "email"
100+
}
101+
}
102+
}`,
103+
shouldValidate: false,
104+
},
105+
}
106+
107+
for _, tc := range testCases {
108+
t.Run(tc.name, func(t *testing.T) {
109+
var v map[string]any
110+
assert.NoError(t, json.Unmarshal([]byte(tc.jsonData), &v))
111+
112+
err = sch.Validate(v)
113+
if tc.shouldValidate && err != nil {
114+
t.Errorf("%#v", err)
115+
} else if !tc.shouldValidate && err == nil {
116+
t.Errorf("expected %q to be invalid", tc.name)
117+
}
118+
})
119+
}
120+
}

internal/jsonschema/testdata/create-constraint-1-invalid-check.txtar

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,23 @@ Check constraint must have the option 'check' configured.
2020
"my_column": "my_column"
2121
}
2222
}
23+
},
24+
{
25+
"create_constraint": {
26+
"name": "my_invalid_unique",
27+
"table": "my_table",
28+
"type": "unique",
29+
"columns": [
30+
"my_column"
31+
],
32+
"nulls_not_distinct": true,
33+
"up": {
34+
"my_column": "my_column"
35+
},
36+
"down": {
37+
"my_column": "my_column"
38+
}
39+
}
2340
}
2441
]
2542
}

internal/jsonschema/testdata/create-constraint-2-invalid-no-inherit.txtar

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,41 @@ Only check constraints have no_inherit flag.
2121
"my_column": "my_column"
2222
}
2323
}
24+
},
25+
{
26+
"create_constraint": {
27+
"name": "my_invalid_unique",
28+
"table": "my_table",
29+
"type": "unique",
30+
"columns": [
31+
"my_column"
32+
],
33+
"nulls_not_distinct": true,
34+
"up": {
35+
"my_column": "my_column"
36+
},
37+
"down": {
38+
"my_column": "my_column"
39+
}
40+
}
41+
},
42+
{
43+
"create_constraint": {
44+
"name": "my_invalid_check_with_nulls_not_distinct",
45+
"table": "my_table",
46+
"type": "check",
47+
"columns": [
48+
"my_column"
49+
],
50+
"nulls_not_distinct": true,
51+
"check": "my_column IS NOT NULL",
52+
"up": {
53+
"my_column": "my_column"
54+
},
55+
"down": {
56+
"my_column": "my_column"
57+
}
58+
}
2459
}
2560
]
2661
}

pkg/migrations/op_create_constraint.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
// SPDX-License-Identifier: Apache-2.0
2-
31
package migrations
42

53
import (
@@ -82,7 +80,7 @@ func (o *OpCreateConstraint) Start(ctx context.Context, conn db.DB, latestSchema
8280

8381
switch o.Type {
8482
case OpCreateConstraintTypeUnique:
85-
return table, createUniqueIndexConcurrently(ctx, conn, s.Name, o.Name, table.Name, temporaryNames(o.Columns))
83+
return table, createUniqueIndexConcurrently(ctx, conn, s.Name, o.Name, table.Name, temporaryNames(o.Columns), o.NullsNotDistinct)
8684
case OpCreateConstraintTypeCheck:
8785
return table, o.addCheckConstraint(ctx, conn, table.Name)
8886
case OpCreateConstraintTypeForeignKey:
@@ -279,6 +277,10 @@ func (o *OpCreateConstraint) Validate(ctx context.Context, s *schema.Schema) err
279277
}
280278
}
281279

280+
if o.Type != OpCreateConstraintTypeUnique && o.NullsNotDistinct {
281+
return fmt.Errorf("nulls_not_distinct can only be true for unique constraints")
282+
}
283+
282284
return nil
283285
}
284286

pkg/migrations/types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// SPDX-License-Identifier: Apache-2.0
2-
// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT.
3-
41
package migrations
52

63
import "github.com/oapi-codegen/nullable"
@@ -288,6 +285,9 @@ type OpCreateConstraint struct {
288285
// Do not propagate constraint to child tables
289286
NoInherit bool `json:"no_inherit,omitempty"`
290287

288+
// Nulls not distinct constraint
289+
NullsNotDistinct bool `json:"nulls_not_distinct,omitempty"`
290+
291291
// Reference to the foreign key
292292
References *TableForeignKeyReference `json:"references,omitempty"`
293293

schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,11 @@
848848
"type": "boolean",
849849
"default": false
850850
},
851+
"nulls_not_distinct": {
852+
"description": "Nulls not distinct constraint",
853+
"type": "boolean",
854+
"default": false
855+
},
851856
"references": {
852857
"description": "Reference to the foreign key",
853858
"$ref": "#/$defs/TableForeignKeyReference"
@@ -877,6 +882,9 @@
877882
},
878883
"no_inherit": {
879884
"const": false
885+
},
886+
"nulls_not_distinct": {
887+
"const": false
880888
}
881889
},
882890
"required": ["columns", "references"]

0 commit comments

Comments
 (0)