@@ -61,19 +61,17 @@ def __init__(self, url, **kwargs):
6161 if not os .path .isfile (matches .group (1 )):
6262 raise RuntimeError ("not a file: {}" .format (matches .group (1 )))
6363
64- # Create engine, disabling SQLAlchemy's own autocommit mode, raising exception if back end's module not installed
65- self ._engine = sqlalchemy .create_engine (url , ** kwargs ).execution_options (autocommit = False )
64+ # Create engine, disabling SQLAlchemy's own autocommit mode raising exception if back end's module not installed;
65+ # without isolation_level, PostgreSQL warns with "there is already a transaction in progress" for our own BEGIN and
66+ # "there is no transaction in progress" for our own COMMIT
67+ self ._engine = sqlalchemy .create_engine (url , ** kwargs ).execution_options (autocommit = False , isolation_level = "AUTOCOMMIT" )
6668
6769 # Get logger
6870 self ._logger = logging .getLogger ("cs50" )
6971
7072 # Listener for connections
7173 def connect (dbapi_connection , connection_record ):
7274
73- # Disable underlying API's own emitting of BEGIN and COMMIT so we can ourselves
74- # https://docs.sqlalchemy.org/en/13/dialects/sqlite.html#serializable-isolation-savepoints-transactional-ddl
75- dbapi_connection .isolation_level = None
76-
7775 # Enable foreign key constraints
7876 if type (dbapi_connection ) is sqlite3 .Connection : # If back end is sqlite
7977 cursor = dbapi_connection .cursor ()
@@ -353,12 +351,30 @@ def teardown_appcontext(exception):
353351
354352 # If INSERT, return primary key value for a newly inserted row (or None if none)
355353 elif command == "INSERT" :
354+
355+ # If PostgreSQL
356356 if self ._engine .url .get_backend_name () == "postgresql" :
357- try :
358- result = connection .execute ("SELECT LASTVAL()" )
359- ret = result .first ()[0 ]
360- except sqlalchemy .exc .OperationalError : # If lastval is not yet defined for this connection
361- ret = None
357+
358+ # Return LASTVAL() or NULL, avoiding
359+ # "(psycopg2.errors.ObjectNotInPrerequisiteState) lastval is not yet defined in this session",
360+ # a la https://stackoverflow.com/a/24186770/5156190;
361+ # cf. https://www.psycopg.org/docs/errors.html re 55000
362+ result = connection .execute ("""
363+ CREATE OR REPLACE FUNCTION _LASTVAL()
364+ RETURNS integer LANGUAGE plpgsql
365+ AS $$
366+ BEGIN
367+ BEGIN
368+ RETURN (SELECT LASTVAL());
369+ EXCEPTION
370+ WHEN SQLSTATE '55000' THEN RETURN NULL;
371+ END;
372+ END $$;
373+ SELECT _LASTVAL();
374+ """ )
375+ ret = result .first ()[0 ]
376+
377+ # If not PostgreSQL
362378 else :
363379 ret = result .lastrowid if result .rowcount == 1 else None
364380
0 commit comments