Skip to content

Conversation

jahnvi480
Copy link
Contributor

@jahnvi480 jahnvi480 commented Aug 5, 2025

Work Item / Issue Reference

AB#34897


Summary

This pull request introduces support for the lastrowid property in the Cursor class, allowing users to retrieve the ID of the last inserted row after a single INSERT operation. The implementation ensures that lastrowid is set only for single-row inserts and is reset or set to None 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:

  • Added a read-only lastrowid property to the Cursor class, which tracks the ID of the last inserted row for single-row INSERT operations. The property is set to None for other operations or if the database does not support identity columns.

Implementation details:

  • In the execute method, lastrowid is reset at the start of each execution. After a successful single-row INSERT, the code queries @@IDENTITY to obtain the new row ID. For multi-row inserts or non-INSERT statements, lastrowid remains None.
  • In the executemany method, lastrowid is explicitly reset to None, as its semantics are undefined for batch operations.

Testing and validation:

  • Added extensive tests to cover lastrowid behavior for single inserts, multiple inserts, batch inserts (executemany), non-INSERT operations, tables without identity columns, and to verify that lastrowid is read-only.

@Copilot Copilot AI review requested due to automatic review settings August 5, 2025 08:37
@github-actions github-actions bot added the pr-size: medium Moderate update size label Aug 5, 2025
Copy link
Contributor

@Copilot Copilot AI left a 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 to None for non-INSERT operations, multi-row INSERTs, and executemany 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')")

Comment on lines +643 to +644
# Use @@IDENTITY which persists across statement boundaries
identity_query = "SELECT @@IDENTITY"
Copy link
Preview

Copilot AI Aug 5, 2025

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.

Suggested change
# 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.

Copy link
Contributor Author

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()

Copy link
Collaborator

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.

Copy link
Contributor

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

@github-actions github-actions bot added pr-size: medium Moderate update size and removed pr-size: medium Moderate update size labels Aug 5, 2025
Copy link
Collaborator

@bewithgaurav bewithgaurav left a 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
Copy link
Collaborator

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.

Comment on lines +643 to +644
# Use @@IDENTITY which persists across statement boundaries
identity_query = "SELECT @@IDENTITY"
Copy link
Collaborator

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.

@jahnvi480 jahnvi480 marked this pull request as draft August 6, 2025 12:27
@github-actions github-actions bot added pr-size: medium Moderate update size and removed pr-size: medium Moderate update size labels Aug 13, 2025
Copy link
Contributor

@sumitmsft sumitmsft left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr-size: medium Moderate update size
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants