-
Notifications
You must be signed in to change notification settings - Fork 20
FEAT: Adding cursor.lastrowid attribute #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request implements the lastrowid
attribute for the Cursor
class, providing DB-API 2.0 compliance by allowing retrieval of the last inserted row's identity value. The implementation ensures lastrowid
is only set for single-row INSERT operations and remains None
for other operations or batch executions.
- Added a read-only
lastrowid
property that queries@@IDENTITY
after successful single-row INSERTs - Reset
lastrowid
toNone
for non-INSERT operations, multi-row INSERTs, andexecutemany
calls - Comprehensive test coverage for various scenarios including identity and non-identity tables
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
File | Description |
---|---|
mssql_python/cursor.py | Implements lastrowid property with logic to detect single-row INSERTs and query @@IDENTITY |
tests/test_004_cursor.py | Adds extensive test cases covering all lastrowid scenarios and edge cases |
Comments suppressed due to low confidence (1)
tests/test_004_cursor.py:1359
- This test doesn't verify that the INSERT actually succeeded and affected multiple rows. Consider adding an assertion to check
cursor.rowcount == 3
to ensure the test is validating the correct scenario.
cursor.execute("INSERT INTO #test_lastrowid (name) VALUES ('test1'), ('test2'), ('test3')")
# Use @@IDENTITY which persists across statement boundaries | ||
identity_query = "SELECT @@IDENTITY" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using @@IDENTITY
can return incorrect values if triggers on the target table insert into other tables with identity columns. Consider using SCOPE_IDENTITY()
instead, which returns the last identity value inserted in the current scope and session.
# Use @@IDENTITY which persists across statement boundaries | |
identity_query = "SELECT @@IDENTITY" | |
# Use SCOPE_IDENTITY() to safely retrieve the last identity value in the current scope | |
identity_query = "SELECT SCOPE_IDENTITY()" |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried this but SCOPE_IDENTITY() only works within the same scope/batch.
That is the reason I used @@IDENTITY, even pyodbc is using IDENTITY and not SCOPE_IDENTITY()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually its a valid comment, since @@IDENTITY
doesn't exactly produce expected results sometimes because it goes out of scope, example
-- Suppose TableA and TableB both have identity columns
-- TableA has an INSERT trigger that inserts a row into TableB
INSERT INTO TableA (name) VALUES ('foo');
SELECT @@IDENTITY; -- Returns TableB's identity (from the trigger), NOT TableA!
SELECT SCOPE_IDENTITY();-- Returns TableA's identity (your insert)
I believe pyodbc has not implemented this at all - checked their code, could you double check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the same reasoning for @@IDENTITY vs SCOPE_IDENTITY(). With @@IDENTITY there are chances of getting bugs due to triggers. Let's discuss where you see the challenge @jahnvi480
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a few limitations on ODBC w.r.t implementation and usage of this attribute, added comments in detail. I believe we can discuss this in detail and park it for now.
Additionally, this is an optional DBAPI extension - https://peps.python.org/pep-0249/#lastrowid and is not implemented by pyodbc due to the above mentioned limitations. Lets discuss this in next meeting.
self._lastrowid = None | ||
|
||
# Check if this was a single INSERT operation that affected exactly one row | ||
if (self.rowcount == 1 and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should rethink lastrowid
implementation. Parsing SQL queries on the client side is pretty fragile—it’s easy to miss edge cases or break with slightly different valid SQL. Just checked, pyodbc doesn’t support lastrowid, probably for these reasons - pls correct me if I'm wrong.
It’s also worth noting that this is partly a limitation of SQL Server and ODBC—they don’t provide a reliable way to get the last inserted ID automatically, which is why we can just let users run SELECT SCOPE_IDENTITY()/@@IDENTITY themselves if they need it.
# Use @@IDENTITY which persists across statement boundaries | ||
identity_query = "SELECT @@IDENTITY" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually its a valid comment, since @@IDENTITY
doesn't exactly produce expected results sometimes because it goes out of scope, example
-- Suppose TableA and TableB both have identity columns
-- TableA has an INSERT trigger that inserts a row into TableB
INSERT INTO TableA (name) VALUES ('foo');
SELECT @@IDENTITY; -- Returns TableB's identity (from the trigger), NOT TableA!
SELECT SCOPE_IDENTITY();-- Returns TableA's identity (your insert)
I believe pyodbc has not implemented this at all - checked their code, could you double check.
… jahnvi/cursor_lastrowid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment for @@IDENTITY VS SCOPE_IDENTITY() .. Let's discuss where the challenge is @jahnvi480 @@IDENTITY
can return unexpected results if there are triggers on the table that perform additional inserts.
Work Item / Issue Reference
Summary
This pull request introduces support for the
lastrowid
property in theCursor
class, allowing users to retrieve the ID of the last inserted row after a singleINSERT
operation. The implementation ensures thatlastrowid
is set only for single-row inserts and is reset or set toNone
for other types of operations, in line with DB-API semantics. Comprehensive tests are added to verify correct behavior in various scenarios.Core feature addition:
lastrowid
property to theCursor
class, which tracks the ID of the last inserted row for single-rowINSERT
operations. The property is set toNone
for other operations or if the database does not support identity columns.Implementation details:
execute
method,lastrowid
is reset at the start of each execution. After a successful single-rowINSERT
, the code queries@@IDENTITY
to obtain the new row ID. For multi-row inserts or non-INSERT
statements,lastrowid
remainsNone
.executemany
method,lastrowid
is explicitly reset toNone
, as its semantics are undefined for batch operations.Testing and validation:
lastrowid
behavior for single inserts, multiple inserts, batch inserts (executemany
), non-INSERT
operations, tables without identity columns, and to verify thatlastrowid
is read-only.