'How to `SET CONSTRAINTS ... DEFERRED` in SQLALchemy Core
I am using SQLAlchemy and PostgreSQL. Postgres supports doing deferred constraints, which allows us to postpone checking the constraints on a table until the end of the transaction.
For example, in SQLAlchemy, I might define a table like this:
t_group_categories = Table('group_categories', metadata,
Column('id', Integer, primary_key=True),
Column('group_id', Integer, ForeignKey('groups.id', deferrable=True))
)
SQLAlchemy would generate a CREATE TABLE statement that would look something like:
CREATE TABLE group_categories
(
id serial NOT NULL,
group_id integer,
CONSTRAINT group_categories_pkey PRIMARY KEY (id),
CONSTRAINT group_categories_group_id_fkey FOREIGN KEY (group_id)
REFERENCES groups (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE INITIALLY IMMEDIATE
)
To my understanding, the DEFERRABLE INITIALLY IMMEDIATE means that the FOREIGN KEY constraint
will act as if it is a non-deferrable constraint, unless it is explicitly told
otherwise, which is exactly what I want.
The problem is that I can't seem to find any information on how to get the SQLAlchemy core to actually issue a SET CONSTRAINTS ... DEFERRED command while inside of a transaction.
For example, say that I have the following code:
connection = engine.connect()
...
with connection.begin() as transaction:
# Create a group
r = connection.execute(
t_groups.insert().values(...)
)
group_id = r.inserted_primary_key
# Assign a category to the group (ERROR!)
r2 = connection.execute(
t_group_categories.insert().values(group_id=group_id, ...)
)
The first block simply creates a new group. The second block then tries to assign the group that
we just created a category. The problem is that without the special SET CONSTRAINTS ... DEFERRED,
we can't actually create the group_categories entry without violating the foreign key constraints
on the table, since the transaction hasn't committed yet.
What I want to do in this instance is defer checking the constraint until after the transaction commits. How can I actually defer constraints checks until after the transaction completes?
Notes:
- The question at
How to SET CONSTRAINTS DEFERRED in sqlalchemy expression language? is similar, but the OP was interested in using
DEFERRABLE INITIALLY DEFERRED, which I would prefer not to do. Instead (if possible), I want to keep my constraint asDEFERRABLE INITIALLY IMMEDIATEand explicitly mark instances where the constraint needs to be deferred. - SQLAlchemy is the one that generated the
DEFERRABLE INITIALLY DEFERREDconstraint, so I am hoping/assuming that it has an expressive way of actually using this constraint on the other side (namely an expression language way of issuing theSET CONSTRAINTS ... DEFERRED.
Update:
- Doing
connection.execute("SET CONSTRAINTS ALL DEFERRED")also doesn't seem to have any effect; I am still getting an IntegrityError. - Doing
connection.execute("SET CONSTRAINTS group_categories_group_id_fkey DEFERRED")inside the transaction block also gives back an IntegrityError.
Solution 1:[1]
I could defer the constraint by using a metadata naming convention to ensure that the constraint had a name which could be used in the SET CONSTRAINTS statement.
convention = {
'ix': 'ix_%(column_0_label)s',
'uq': 'uq_%(table_name)s_%(column_0_name)s',
'ck': 'ck_%(table_name)s_%(constraint_name)s',
'fk': 'fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s',
'pk': 'pk_%(table_name)s',
}
metadata = sa.MetaData(naming_convention=convention)
...
# Specify "initially immediate"
t_group_categories = sa.Table(
'group_categories',
metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column(
'group_id',
sa.Integer,
sa.ForeignKey('groups.id', deferrable=True, initially='IMMEDIATE'),
),
)
engine = sa.create_engine('postgresql:///test', echo=True, future=True)
metadata.drop_all(engine)
metadata.create_all(engine)
constraint, = t_group_categories.foreign_key_constraints
with engine.begin() as conn:
conn.execute(sa.text(f'SET CONSTRAINTS "{constraint.name}" DEFERRED'))
# Passing id before the group is created will trigger an immediate constraint.
conn.execute(t_group_categories.insert().values(group_id=1))
conn.execute(t_groups.insert())
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | snakecharmerb |
