postgresqlsqlalchemyreferential-integrity

Referential Integrity and Transactions in SqlAlchemy


I am attempting to insert data into two tables, the latter of which references the former. Something like:

CREATE TABLE foo (foo_id bigserial primary key, name varchar)
CREATE TABLE bar (bar_id bigserial primary key, name varchar,
                  foo_id int constraint foo_fkey references foo)

I want to add both rows within a transaction, so that if anything fails, nothing is written to the database. If I simply add both objects to my session, I don't get the auto-incremented foo_id, so I add the foo object, do a flush (which populates the id), then set up the bar object, then flush again (I have even more work to do later, but not referenced by this relationship. On that second flush, I'm getting a referential-integrity error because the row in foo hasn't actually been written.

I can accomplish this easily in SQL:

BEGIN TRANSACTION;
INSERT INTO foo (name) values('foo');
INSERT INTO bar (name, foo_id) VALUES('bar', currval(pg_get_serial_sequence('foo', 'foo_id')));
COMMIT TRANSACTION;

Is there a way for me to temporarily suspend RI checking? Or, is there a better way for me to get the new foo_id instead of doing a flush?

Here's the kind of sqla code I'm executing:

session.begin()
f = Foo()
f.name = 'Foo'
session.add(f)
session.flush()
b = Bar()
b.name = 'Bar'
b.foo_id = f.foo_id
session.add(b)
session.flush()
...
session.commit()
session.close()

I use the bar_id value later, which is why I need the second flush. It is on that second flush that the error is happening.


Solution

  • I can't reproduce the integrity error on the second flush. Assuming a model setup with a one to one relationship like this:

    class Foo(Base):
        __tablename__ = 'foo'
    
        id = sa.Column(sa.Integer, primary_key=True)
        bar = orm.relationship('Bar', back_populates='foo', uselist=False)
    
    
    class Bar(Base):
        __tablename__ = 'bar'
    
        id = sa.Column(sa.Integer, primary_key=True)
        foo_id = sa.Column(sa.Integer, sa.ForeignKey('foo.id'))
        foo = orm.relationship('Foo', back_populates='bar')
    

    The the output of these actions:

    # No explicit id, no flush, commit
    foo1 = Foo()
    bar1 = Bar(foo=foo1)
    session.add(bar1)
    session.commit()
    print(f'foo {foo1.id}, bar {bar1.id}')
    
    # No explicit id, flush
    bar2 = Bar()
    foo2 = Foo(bar=bar2)
    session.add(foo2)
    session.flush()
    print(f'foo {foo2.id}, bar {bar2.id}')
    session.commit()
    
    
    # Explicit id, two flushes
    foo3 = Foo()
    session.add(foo3)
    session.flush()
    bar3 = Bar()
    bar3.foo_id = foo3.id
    session.add(bar3)
    session.flush()
    print(f'foo {foo3.id}, bar {bar3.id}')
    session.commit()
    

    is what I would expect:

    foo 1, bar 1
    foo 2, bar 2
    foo 3, bar 3