Open mcooper-pi opened 10 months ago
Thanks for raising this @mcooper-pi.
Could you share the code that you are using to configure your primary key constraint?
I gave it a quick shot, and I didn't get the same SQL or error as you. See below for details.
models/my_model.sql
{{ config(materialized="table") }}
select 1 as id
models/_models.yml
models:
- name: my_model
config:
contract:
enforced: true
constraints:
- type: primary_key
columns: ["id"]
columns:
- name: id
data_type: integer
or alternatively:
models/_models.yml
models:
- name: my_model
config:
contract:
enforced: true
columns:
- name: id
data_type: integer
dbt build
dbt build --full-refresh
create table "postgres"."dbt_dbeatty"."my_model__dbt_tmp"
(
id integer primary key
)
;
insert into "postgres"."dbt_dbeatty"."my_model__dbt_tmp" (
id
)
(
select id
from (
select 1 as id
) as model_subq
);
alter table "postgres"."dbt_dbeatty"."my_model" rename to "my_model__dbt_backup"
alter table "postgres"."dbt_dbeatty"."my_model__dbt_tmp" rename to "my_model"
Taken from my models/_models.yml
file (object names changed):
models:
- name: int__incremental_model
description: model desc here
config:
contract:
enforced: true
materialized: incremental
unique_key: unq_column
on_schema_change: append_new_columns
constraints:
- type: primary_key
columns:
- column_pk
name: pk_constraint_name
columns:
- name: column_pk
data_type: bigint
constraints:
- type: not_null
tests:
- not_null
- unique
I think the only material difference is that I am setting a specific name for the constraint.
How does it behave if you remove name: pk_constraint_name
?
That appears to do the trick. That model also has a named unique constraint and a named check constraint, and when running it after removing the primary key constraint name, it errors out on the unique constraint name. It's interesting though - if I remove both name properties for the primary key and unique constraints, it builds successfully. The behavior does not persist with the check name.
Looking at the database table in DataGrip, the primary key and unique constraints are both categorized as keys, and the check constraint is just a check. I have to assume the check doesn't have the same uniqueness restriction as the keys do.
Thanks for checking that @mcooper-pi
Ideally, the error message wouldn't be this:
21:31:55 Database Error in model my_model (models/my_model.sql)
21:31:55 relation "pk_constraint_abc" already exists
But would be something like this instead:
21:31:55 Database Error in model my_model (models/my_model.sql)
21:31:55 Constraint with name "pk_constraint_abc" already exists. Consider removing the `name` property from this constraint to avoid this error.
Remove any human-friendly constraint name
s from primary keys definitions that look like this:
constraints:
- type: primary_key
columns: [<first_column>, <second_column>, ...]
name: human_friendly_name
So this instead:
constraints:
- type: primary_key
columns: [<first_column>, <second_column>, ...]
# Removed the line below to prevent Database Error in model my_model (models/my_model.sql)
# name: human_friendly_name
@dbeatty10, so it sounds like it's working as you would expect it to be, but the error message isn't as specific enough? Or are you saying that's just for a workaround until the named constraint will work?
@dbeatty10, so it sounds like it's working as you would expect it to be, but the error message isn't as specific enough? Or are you saying that's just for a workaround until the named constraint will work?
Both:
The first one is probably easier to implement and can be done independently of the second. The second could be trickier and is probably not be a high priority fix (since there is a reasonable workaround and low impact to be without a constraint name)
dbt build --full-refresh
on dbt-postgresMaybe we we just make the constraint name
as only dbt metadata for dbt-postgres (rather than also being postgres metadata). i.e. execute SQL like this:
primary key (table_pk)
instead of this:
constraint pk_int__table_name primary key (table_pk)
The Postgres docs mention that the value of named constraints are twofold:
This clarifies error messages and allows you to refer to the constraint when you need to change it
So if we remove the constraint names from the DDL, then error messages may not have a have a human-readable constraint name, and users may not be able to refer to the constraint to change it.
These both sound acceptable to give up in order to avoid this problem.
Here is the base implementation that adds constraint {constraint.name}
.
If this issue only affects dbt-postgres, then it would need to override render_model_constraint
with a custom implementation that removes this constraint naming.
If this issue affects many/most adapters, then we could just remove the constraint naming by default (but adapters could add it back in by overriding render_model_constraint
).
Maybe we we just make the constraint name as only dbt metadata for dbt-postgres
This seems like a good idea. Personally I would rather be able to maintain my naming conventions and have it build successfully but have to track down errors, than to have it not build at all.
I appreciate you looking into this. Is there anything else you need from my end?
I appreciate you looking into this. Is there anything else you need from my end?
Thank you for finding and writing it up 🙏
We don't need anything else from your end now that we've fully reproduced the issue.
Moving to dbt-adapters
now that the relevant implementation logic has also moved there. This is still low priority for us, given the provided workaround. If we decide on a narrower fix for dbt-postgres
only, we can move there.
Is this a new bug in dbt-core?
Current Behavior
When running the command
dbt-postgres build --full-refresh --select <model name>.sql
I get the following error:Upon inspecting the SQL code in
dbt.log
, I see that the--full-refresh
command is building an exact replica of my table with the suffix__dbt_tmp
. The error appears to stem from this table creating using the exact constraint name as my existing table. Postgres requires all constraint names to be unique regardless of the table it exists on, so this replica table does not create and throws the error.Expected Behavior
I would expect the incremental table to be re-populated with data as if
is_incremental()
returnsFalse
instead ofTrue
.Steps To Reproduce
dbt-postgres build
and build the modeldbt-postgres build --full-refresh
Database Error in model <model name> (<model name> .sql) relation "<primary key constraint name>" already exists
Relevant log output
Environment
Which database adapter are you using with dbt?
postgres
Additional Context
No response