Skip to content
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

feat: Implement option 'delete_rows' of argument 'if_exists' in 'DataFrame.to_sql' API. #60376

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

gmcrocetti
Copy link

@gmcrocetti
Copy link
Author

@WillAyd I chose the name delete_rows instead of delete_replace because the behavior of replace right now is of recreate - as you mentioned - and delete_rows means exactly what is going on behind the scenes.

@erfannariman tagging you due to your help/interest during the lifecycle of this issue.

@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from e443d8d to 33cd0d6 Compare November 20, 2024 14:04
@gmcrocetti gmcrocetti marked this pull request as draft November 20, 2024 14:04
pandas/tests/io/test_sql.py Outdated Show resolved Hide resolved
pandas/tests/io/test_sql.py Outdated Show resolved Hide resolved
pandas/tests/io/test_sql.py Show resolved Hide resolved
@WillAyd WillAyd added the IO SQL to_sql, read_sql, read_sql_query label Nov 20, 2024
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch 3 times, most recently from 3c33249 to 1ef5a87 Compare November 22, 2024 01:10
@gmcrocetti gmcrocetti requested a review from WillAyd November 22, 2024 12:26
@gmcrocetti gmcrocetti marked this pull request as ready for review November 22, 2024 12:26
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch 3 times, most recently from b71c0d9 to 1843040 Compare December 17, 2024 13:45
pandas/io/sql.py Outdated Show resolved Hide resolved
pandas/tests/io/test_sql.py Outdated Show resolved Hide resolved
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from 15bda94 to 2eb19e7 Compare December 27, 2024 18:03
@gmcrocetti gmcrocetti requested a review from WillAyd December 27, 2024 18:04
Copy link
Member

@WillAyd WillAyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that the test failures are related. Restarted so let's see...

My remaining feedback is rather minor; overall I think the implementation looks good.

@mroeschke care to take a look?

pandas/io/sql.py Show resolved Hide resolved
pandas/tests/io/test_sql.py Outdated Show resolved Hide resolved
pandas/io/sql.py Outdated Show resolved Hide resolved
pandas/io/sql.py Outdated Show resolved Hide resolved
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from 5f6ab41 to d1b01d2 Compare January 3, 2025 14:42
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from d1b01d2 to 3e8813f Compare January 3, 2025 14:45
pandas/io/sql.py Outdated Show resolved Hide resolved
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from 77dc01c to 4c8fcda Compare January 3, 2025 17:43
@gmcrocetti gmcrocetti requested a review from WillAyd January 3, 2025 17:45
@gmcrocetti
Copy link
Author

gmcrocetti commented Jan 3, 2025

@WillAyd it seems new tests have introduced some sort of "lock" on pytest. It is affecting 3.11, 3.12 and "Future infer strings" using a single CPU (it works using multiple). Do you have any hunch ?

NVM. CI is fine now.

with pandasSQL.run_transaction() as cur:
cur.execute(table_stmt)

if conn_name != "sqlite_buildin" and "adbc" not in conn_name:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think to make this test more manageable we can create a generic pd.SQLError and use that whenever these are raised instead. I don't think there's a lot of value to exposing the error messages from the underlying libraries to end users, and it would help simplify this test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I totally agree on that 💯 .
pandas should definitely provide a common interface for SQL-related errors. But I believe this is out of the current scope ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think we already have one in place - check pandas.errors.DatabaseError. If you catch where these problems are in the implementation and just do raise DatabaseError from exc then you can also just catch the DatabaseError in the test

Copy link
Author

@gmcrocetti gmcrocetti Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, sure.
So as of now the only place raising pd.errors.DatabaseError is the method execute.
The new implementation delete_rows is using cursor.execute instead - which is at the driver level.
So the options we have to comply with what you suggested is:

  1. raise pd.errors.DatabaseError from delete_rows. The issue being other methods (append, replace) would not raise it.
  2. We start using self.execute but I tried in the past and it only works for SQLite because run_transaction closes the connection while ADBC and sqlalchemy don't.

Do you see another option or have a preference ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where within the class hierarchies does that execute get called? It seems like there is where you can catch the exception from the library and re-raise a more generic one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm OK. We might need to fix that up as a precursor to this then - seems like something is off

Copy link
Author

@gmcrocetti gmcrocetti Jan 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok...so let's try to unravel it:
Here is the SQLAlchemy's implementation for execute.

There's no exception handling in this method as in SQLite and ADBC cases.

A problem with the current implementation is that the code that inserts is separated from the one deleting records.
So.... Even if delete_rows is implemented using self.execute it can happen that the insertion procedure doesn't - which is the case for SQLDatabase. I truly see the pain and wish we could implement the entire operation in a single method/function. But the codebase is not ready for that and would required some refactoring.

I really believe this is out of scope...please let me know what you think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate wanting to get this in but I don't think the issue is out of scope - we would just be choosing to ignore it, which delays the problem for a later date.

Do you see a reasonable path to a pre-cursor that can fix some of the implementation issues with execute and get us back to a place where we consistently use the internal class methods, rather than cherry-picking calls to a third party execute?

Copy link
Author

@gmcrocetti gmcrocetti Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate wanting to get this in but I don't think the issue is out of scope - we would just be choosing to ignore it, which delays the problem for a later date.

And just to clarify, the issue we're discussing is the lack of a common interface for database related errors ?


I have sent a patch. It touches parts of code that are out of my league but that are required for the new test test_delete_rows_is_atomic to work - using self.execute.

It is not meant to be a bullet proof and solve previously existing problems (but it definitely changes existing APIs) but might be a middle-ground to what is to come and solves it for the newly introduced delete_rows.

Please, let me know if that is feasible.

BTW, thanks a ton for the hard work reviewing it multiple times.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WillAyd LMK as soon you have the time to review :). TY

pandas/tests/io/test_sql.py Outdated Show resolved Hide resolved
@gmcrocetti gmcrocetti requested a review from WillAyd January 8, 2025 19:03
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch 4 times, most recently from 0fccb84 to 49e0bcc Compare January 9, 2025 16:43
gmcrocetti and others added 10 commits January 17, 2025 17:55
Co-authored-by: Matthew Roeschke <[email protected]>
…das-dev#60532)

* first

* second

* Update object_array.py

* third

* ascii

* ascii2

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* ascii3

* style

* style

* style

* style

* docs

* reset

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update doc/source/whatsnew/v3.0.0.rst

---------

Co-authored-by: Abby VeCasey <[email protected]>
Co-authored-by: Matthew Roeschke <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
@gmcrocetti gmcrocetti force-pushed the issue-37210-to-sql-truncate branch from 8cadb78 to b0c2eff Compare January 17, 2025 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
IO SQL to_sql, read_sql, read_sql_query
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ENH: DataFrame.to_sql with if_exists='replace' should do truncate table instead of drop table
5 participants