Thank you for the help! The approach with the context-sensitive default function worked out well for me. I especially like how it keeps the "magic" close by to the rest of the code, hopefully reducing surprises when I have to come back to this code eventually.
I have an idea for a "namespaced sequence factory" which I think might be cool. I’ll post it to the group here if it works out. Thanks again, Scott Colby On Oct 4, 2019, 19:12 -0400, Mike Bayer <mike...@zzzcomputing.com>, wrote: > > > On Fri, Oct 4, 2019, at 3:57 PM, Scott Colby wrote: > > Hello, > > > > I am attempting to implement a "gapless" sequence using SQLAlchemy's > > event listeners instead of DB-level triggers inspired by > > [this](https://stackoverflow.com/questions/9984196/postgresql-gapless-sequences) > > and similar approaches. (In fact, I don't particularly care about > > the gapless property but more about a "namespaced" sequence: my > > application has many parents, each with many children. I want to > > give the children numbers grouped by their parent.) > > > > I have created a minimal example which I have uploaded as a gist > > [here](https://gist.github.com/scolby33/e09624839a2b422312113d22b4e56da5). > > It can be run as `python namespaced_sequences.py` or provided with > > an optional argument specifying a DB URL, e.g., > > `python namespaced_sequences.py "postgresql://..."`. > > > > The example contains the declarative classes, the necessary boilerplate > > for setting up SQLAlchemy, my event listeners, and a main function > > that exercises the code with asserts specifying my desired behavior. > > > > The event listener `insert_child_number_sequence` seems to work > > correctly: upon the insertion of a row in the `parent` table, a > > matching row is inserted in the `child_number_sequence` table. > > > > My problem arises with the `set_child_number` listener. As can be > > seen when running the example, the ORM properly updates the > > `child_number_sequence` row with SQL like > > > > UPDATE child_number_sequence > > SET last_child_number=(child_number_sequence.last_child_number + 1) > > WHERE child_number_sequence.parent_id = 1; > > > > This is what I would expect for an atomic increment in SQL. > > > > But the insert of the new child issues SQL like > > > > INSERT INTO child (parent_id, child_number) > > VALUES (1, child_number_sequence.last_child_number + 1)); > > > > which fails with `no such column: child_number_sequence.last_child_number` > > (on SQLite; `missing FROM-clause entry for table "child_number_sequence"` > > on PostgreSQL). > > > > I think I can see what is going on here--my assignment in Python > > is propagating directly to the parameters used in the `INSERT` > > statement. In PostgreSQL, I would instead use a `RETURNING` clause > > to get the new value and in SQLite, I would use `BEGIN IMMEDIATE` > > to ensure that I have a write lock on the DB before reading anything. > > > > I attempted several approaches to have SQLAlchemy use the new value > > as computed by the database. For instance, adding > > `session.add(child_number_sequence); session.refresh(child_number_sequence)` > > between the increment and the assignment of the value to > > `instance.child_number`. Interestingly, this issues two `SELECT`s > > but completely swallows the `UPDATE`; the child gets assigned child > > number 0. > > > > I considered using the statements API to use the PostgreSQL `RETURNING` > > clause or `SELECT FOR UPDATE` but I would prefer to only have one > > code path for PostgresSQL and SQLite (and others?) if possible. > > > > well you definitely have to have slightly different code for RETURNING vs. > non. I would also lose the ORM stuff for this sequence (e.g. the > ChildNumberSequenceClass) as it's making things complicated and instead do > what you need to do with the before_insert handler, where you have access to > the Connection being used to emit SQL. Run the UPDATE (or INSERT) that you > need on the sequence table using Core > connection.execute(table.update().values(...)), and use RETURNING if the > dialect supports it (use connection.dialect.implicit_returning), otherwise > retrieve it using a SELECT. Then populate that value on the object that is > passed to before_insert(). > > Alternatively, you could do this whole thing using a Python side default as > well. the approach at > https://docs.sqlalchemy.org/en/13/core/defaults.html#context-sensitive-default-functions > illustrates a "context", which has a cursor right there, however in this > case you'd be using raw SQL so is not as abstracted as you might prefer. > > > > > > > > > > > > > > > > Unfortunately, I'm stumped as how to do this. What is the right > > approach to implement this pattern? > > > > Thank you, > > Scott Colby > > > > > > -- > > SQLAlchemy - > > The Python SQL Toolkit and Object Relational Mapper > > > > http://www.sqlalchemy.org/ > > > > To post example code, please provide an MCVE: Minimal, Complete, and > > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > > description. > > --- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To unsubscribe from this group and stop receiving emails from it, send an > > email to sqlalchemy+unsubscr...@googlegroups.com. > > To view this discussion on the web visit > > https://groups.google.com/d/msgid/sqlalchemy/4a1d8ea8-fbeb-4158-a791-3c4780602c5a%40googlegroups.com. > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to a topic in the Google > Groups "sqlalchemy" group. > To unsubscribe from this topic, visit > https://groups.google.com/d/topic/sqlalchemy/pIKZRRjjM7A/unsubscribe. > To unsubscribe from this group and all its topics, send an email to > sqlalchemy+unsubscr...@googlegroups.com. > To view this discussion on the web visit > https://groups.google.com/d/msgid/sqlalchemy/e32c4cd1-f866-4e65-9c5a-34d795c84a92%40www.fastmail.com. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/sqlalchemy/82d6a4be-e15c-4f3d-b82f-3d5ffb5358b3%40Spark.