omnilib / aiosqlite

asyncio bridge to the standard sqlite3 module

Home Page:https://aiosqlite.omnilib.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

cursor.lastrowid sometimes returning zero instead of the correct value

pfmoore opened this issue · comments

Description

I'm seeing somewhat unreliable behaviour with the lastrowid property of a cursor. I'm doing INSERT ... ON CONFLICT DO UPDATE statements on multiple rows:

    for rel in releases:
        for file in releases[rel]:
            async with db.execute(PROJECT_FILES_SQL, project_files_args(file)) as cursor:
                file_id = cursor.lastrowid
                print(file_id, name, file.get("filename"))

What I'm seeing is many rows with a file_id of zero, but when I query the database directly, the file_id is not zero, and has a perfectly valid value.

Unfortunately, I cannot reproduce this in a smaller test case. I suspect that this could be a race condition, with the lastrowid value being "lost" because another statement is executed on the database thread before lastrowid is called, but I don't have enough understanding of asyncio to be sure if that's possible.

Details

  • OS: Windows 10
  • Python version: 3.8.5
  • aiosqlite version: 0.15.0
  • Can you repro on 'main' branch? Not tried
  • Can you repro in a clean virtualenv? Not tried

@pfmoore There is definitely a race in how set_result() and set_exception() are called within the inner thread's run() method. Based on the report in #80, I submitted #89. I don't know whether this will resolve your specific issue, but I thought it was worth mentioning.

Please disregard, I don't think this has anything to do with #80 after all.

I'm not certain, since I wasn't able to reproduce the issue, but I don't think this would be an issue with aiosqlite, as aiosqlite.Cursor.lastrowid is just a proxy to the underlying sqlite3.Cursor.lastrowid. Unless you are aware of sqlite3 reusing existing/open cursors, I'm not sure how this would happen.

This is probably not a problem with this wrapper as the others stated already. I noticed this behaviour with the default sqlite3 module if an ON CONFLICT clause applies. In this case I used the unique keys that caused the conflict to retrieve the row after insert.

A small example, that should work with the default sqlite3 module and which may be useful to reproduce the issue with this wrapper:

cursor.execute('''
    CREATE TABLE IF NOT EXISTS foo (
        id       INTEGER PRIMARY KEY AUTOINCREMENT,
        project  INTEGER,
        name     TEXT,
        UNIQUE(project)
    )
''')
cursor.execute('''
    INSERT INTO foo (project, name)
    VALUES (?, ?)
    ON CONFLICT (project) DO
    UPDATE SET name = ?
''', (project_id, name, name))

# lastrowid is 0 if on conflict clause applies
if cursor.lastrowid > 0:
    row_id = cursor.lastrowid
else:
    cursor.execute('SELECT id FROM foo WHERE project = ?',
                   (project_id,))
    row_id = cursor.fetchone()[0]