Skip to content

Commit 27eb48c

Browse files
Fix constraint rendering for expressions and foreign key constraint types (#7512) (#7784)
(cherry picked from commit 05b0ebb) Co-authored-by: Michelle Ark <[email protected]>
1 parent 7f2bdbf commit 27eb48c

File tree

6 files changed

+435
-38
lines changed

6 files changed

+435
-38
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
kind: Fixes
2+
body: 'Constraint rendering fixes: wrap check expression in parentheses, foreign key
3+
''references'', support expression in all constraint types'
4+
time: 2023-05-04T14:06:42.545193-04:00
5+
custom:
6+
Author: MichelleArk
7+
Issue: 7417 7480 7416

core/dbt/adapters/base/impl.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,20 +1318,26 @@ def _parse_column_constraint(cls, raw_constraint: Dict[str, Any]) -> ColumnLevel
13181318
def render_column_constraint(cls, constraint: ColumnLevelConstraint) -> Optional[str]:
13191319
"""Render the given constraint as DDL text. Should be overriden by adapters which need custom constraint
13201320
rendering."""
1321-
if constraint.type == ConstraintType.check and constraint.expression:
1322-
return f"check {constraint.expression}"
1321+
constraint_expression = constraint.expression or ""
1322+
1323+
rendered_column_constraint = None
1324+
if constraint.type == ConstraintType.check and constraint_expression:
1325+
rendered_column_constraint = f"check ({constraint_expression})"
13231326
elif constraint.type == ConstraintType.not_null:
1324-
return "not null"
1327+
rendered_column_constraint = f"not null {constraint_expression}"
13251328
elif constraint.type == ConstraintType.unique:
1326-
return "unique"
1329+
rendered_column_constraint = f"unique {constraint_expression}"
13271330
elif constraint.type == ConstraintType.primary_key:
1328-
return "primary key"
1329-
elif constraint.type == ConstraintType.foreign_key:
1330-
return "foreign key"
1331-
elif constraint.type == ConstraintType.custom and constraint.expression:
1332-
return constraint.expression
1333-
else:
1334-
return None
1331+
rendered_column_constraint = f"primary key {constraint_expression}"
1332+
elif constraint.type == ConstraintType.foreign_key and constraint_expression:
1333+
rendered_column_constraint = f"references {constraint_expression}"
1334+
elif constraint.type == ConstraintType.custom and constraint_expression:
1335+
rendered_column_constraint = constraint_expression
1336+
1337+
if rendered_column_constraint:
1338+
rendered_column_constraint = rendered_column_constraint.strip()
1339+
1340+
return rendered_column_constraint
13351341

13361342
@available
13371343
@classmethod
@@ -1398,13 +1404,15 @@ def render_model_constraint(cls, constraint: ModelLevelConstraint) -> Optional[s
13981404
constraint_prefix = f"constraint {constraint.name} " if constraint.name else ""
13991405
column_list = ", ".join(constraint.columns)
14001406
if constraint.type == ConstraintType.check and constraint.expression:
1401-
return f"{constraint_prefix}check {constraint.expression}"
1407+
return f"{constraint_prefix}check ({constraint.expression})"
14021408
elif constraint.type == ConstraintType.unique:
1403-
return f"{constraint_prefix}unique ({column_list})"
1409+
constraint_expression = f" {constraint.expression}" if constraint.expression else ""
1410+
return f"{constraint_prefix}unique{constraint_expression} ({column_list})"
14041411
elif constraint.type == ConstraintType.primary_key:
1405-
return f"{constraint_prefix}primary key ({column_list})"
1406-
elif constraint.type == ConstraintType.foreign_key:
1407-
return f"{constraint_prefix}foreign key ({column_list})"
1412+
constraint_expression = f" {constraint.expression}" if constraint.expression else ""
1413+
return f"{constraint_prefix}primary key{constraint_expression} ({column_list})"
1414+
elif constraint.type == ConstraintType.foreign_key and constraint.expression:
1415+
return f"{constraint_prefix}foreign key ({column_list}) references {constraint.expression}"
14081416
elif constraint.type == ConstraintType.custom and constraint.expression:
14091417
return f"{constraint_prefix}{constraint.expression}"
14101418
else:

core/dbt/contracts/graph/nodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def is_valid(cls, item):
187187
class ColumnLevelConstraint(dbtClassMixin):
188188
type: ConstraintType
189189
name: Optional[str] = None
190+
# expression is a user-provided field that will depend on the constraint type.
191+
# It could be a predicate (check type), or a sequence sql keywords (e.g. unique type),
192+
# so the vague naming of 'expression' is intended to capture this range.
190193
expression: Optional[str] = None
191194
warn_unenforced: bool = (
192195
True # Warn if constraint cannot be enforced by platform but will be in DDL

tests/adapter/dbt/tests/adapter/constraints/fixtures.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,17 @@
1212
'2019-01-01' as date_day
1313
"""
1414

15+
foreign_key_model_sql = """
16+
{{
17+
config(
18+
materialized = "table"
19+
)
20+
}}
21+
22+
select
23+
1 as id
24+
"""
25+
1526
my_model_view_sql = """
1627
{{
1728
config(
@@ -53,6 +64,22 @@
5364
'2019-01-01' as date_day
5465
"""
5566

67+
# force dependency on foreign_key_model so that foreign key constraint is enforceable
68+
my_model_wrong_order_depends_on_fk_sql = """
69+
{{
70+
config(
71+
materialized = "table"
72+
)
73+
}}
74+
75+
-- depends_on: {{ ref('foreign_key_model') }}
76+
77+
select
78+
'blue' as color,
79+
1 as id,
80+
'2019-01-01' as date_day
81+
"""
82+
5683
my_model_view_wrong_order_sql = """
5784
{{
5885
config(
@@ -80,6 +107,24 @@
80107
'2019-01-01' as date_day
81108
"""
82109

110+
# force dependency on foreign_key_model so that foreign key constraint is enforceable
111+
my_model_incremental_wrong_order_depends_on_fk_sql = """
112+
{{
113+
config(
114+
materialized = "incremental",
115+
on_schema_change='append_new_columns'
116+
)
117+
}}
118+
119+
-- depends_on: {{ ref('foreign_key_model') }}
120+
121+
select
122+
'blue' as color,
123+
1 as id,
124+
'2019-01-01' as date_day
125+
"""
126+
127+
83128
# model columns name different to schema definitions
84129
my_model_wrong_name_sql = """
85130
{{
@@ -223,6 +268,95 @@
223268
- type: primary_key
224269
- type: check
225270
expression: (id > 0)
271+
- type: check
272+
expression: id >= 1
273+
tests:
274+
- unique
275+
- name: color
276+
data_type: text
277+
- name: date_day
278+
data_type: text
279+
- name: my_model_error
280+
config:
281+
contract:
282+
enforced: true
283+
columns:
284+
- name: id
285+
data_type: integer
286+
description: hello
287+
constraints:
288+
- type: not_null
289+
- type: primary_key
290+
- type: check
291+
expression: (id > 0)
292+
tests:
293+
- unique
294+
- name: color
295+
data_type: text
296+
- name: date_day
297+
data_type: text
298+
- name: my_model_wrong_order
299+
config:
300+
contract:
301+
enforced: true
302+
columns:
303+
- name: id
304+
data_type: integer
305+
description: hello
306+
constraints:
307+
- type: not_null
308+
- type: primary_key
309+
- type: check
310+
expression: (id > 0)
311+
tests:
312+
- unique
313+
- name: color
314+
data_type: text
315+
- name: date_day
316+
data_type: text
317+
- name: my_model_wrong_name
318+
config:
319+
contract:
320+
enforced: true
321+
columns:
322+
- name: id
323+
data_type: integer
324+
description: hello
325+
constraints:
326+
- type: not_null
327+
- type: primary_key
328+
- type: check
329+
expression: (id > 0)
330+
tests:
331+
- unique
332+
- name: color
333+
data_type: text
334+
- name: date_day
335+
data_type: text
336+
"""
337+
338+
model_fk_constraint_schema_yml = """
339+
version: 2
340+
models:
341+
- name: my_model
342+
config:
343+
contract:
344+
enforced: true
345+
columns:
346+
- name: id
347+
quote: true
348+
data_type: integer
349+
description: hello
350+
constraints:
351+
- type: not_null
352+
- type: primary_key
353+
- type: check
354+
expression: (id > 0)
355+
- type: check
356+
expression: id >= 1
357+
- type: foreign_key
358+
expression: {schema}.foreign_key_model (id)
359+
- type: unique
226360
tests:
227361
- unique
228362
- name: color
@@ -286,6 +420,16 @@
286420
data_type: text
287421
- name: date_day
288422
data_type: text
423+
- name: foreign_key_model
424+
config:
425+
contract:
426+
enforced: true
427+
columns:
428+
- name: id
429+
data_type: integer
430+
constraints:
431+
- type: unique
432+
- type: primary_key
289433
"""
290434

291435
constrained_model_schema_yml = """
@@ -298,11 +442,16 @@
298442
constraints:
299443
- type: check
300444
expression: (id > 0)
445+
- type: check
446+
expression: id >= 1
301447
- type: primary_key
302448
columns: [ id ]
303449
- type: unique
304450
columns: [ color, date_day ]
305451
name: strange_uniqueness_requirement
452+
- type: foreign_key
453+
columns: [ id ]
454+
expression: {schema}.foreign_key_model (id)
306455
columns:
307456
- name: id
308457
quote: true
@@ -316,6 +465,16 @@
316465
data_type: text
317466
- name: date_day
318467
data_type: text
468+
- name: foreign_key_model
469+
config:
470+
contract:
471+
enforced: true
472+
columns:
473+
- name: id
474+
data_type: integer
475+
constraints:
476+
- type: unique
477+
- type: primary_key
319478
"""
320479

321480

0 commit comments

Comments
 (0)