kvesteri / sqlalchemy-continuum

Versioning extension for SQLAlchemy.
BSD 3-Clause "New" or "Revised" License
580 stars 126 forks source link

Deleting all entries of a given table is not tracked properly. #284

Open aditya051293 opened 2 years ago

aditya051293 commented 2 years ago

When deleting all the records from a table sqlalchmy-continuum throws below error.

def positional_args_to_dict(self, op, statement, params):
    """
    On some drivers (eg sqlite) generated INSERT statements use positional
    args instead of key value dictionary. This method converts positional
    args to key value dict.

    :param statement: SQL statement string
    :param params: tuple or dict of statement parameters
    """
    if isinstance(params, tuple):
        parameters = {}
        if op == Operation.DELETE:
            regexp = '^DELETE FROM (.+?) WHERE'
            match = re.match(regexp, statement)
            tablename = match.groups()[0].strip('"').strip("'").strip('`')
            AttributeError: 'NoneType' object has no attribute 'groups'

Debug Notes:

when using table.delete() sqlalchemy generates statement as 'DELETE FROM table'. Thus, regex match fails.

And match is NoneType here https://github.com/kvesteri/sqlalchemy-continuum/blob/92eeb1edd8d1ffc9463d0123296dc2d8720cdb3a/sqlalchemy_continuum/manager.py#L499

NOTE : Only correcting the regex here may not help because params will also not be passed by 'before_cursor_execute' event in this case. Thus, there will be no information to create entries in the version table.

sqlalchemy version: 1.4.39 mysql version: 5.7.39

indiVar0508 commented 2 years ago

Hi @aditya051293 ,

I tried to replicate the issue in my local and i was able to replicate the issue, thanks for the debug notes as you said if the table is being deleted then event_flush does not provide params attribute as there is nothing to filter so have added one more condition to verify if params are not empty it won't try to build params and return empty dictionary in #283, post which it doesn't try to match with regex provided and crosses that part of code.

But after this i get KeyError for UoW for connection which seems similar to #275, same also happens if i run for mysql server, Not sure as of now how UoW internal dictionary is being populated wrt connections

Traceback (most recent call last):
  File "/home/indivar/github/sqlalchemy-continuum/sqlalchemy_continuum/manager.py", line 413, in append_association_operation
    uow = self.units_of_work[conn]
KeyError: <sqlalchemy.engine.base.Connection object at 0x7f1c830c4160>

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "distdemo_books.py", line 63, in <module>
    db.execute(q)
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/orm/session.py", line 1712, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1631, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", line 332, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1498, in _execute_clauseelement
    ret = self._execute_context(
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/engine/base.py", line 1761, in _execute_context
    statement, parameters = fn(
  File "/home/indivar/github/py_envs/continuum_env/lib/python3.8/site-packages/sqlalchemy/engine/events.py", line 128, in wrap_before_cursor_execute
    orig_fn(
  File "/home/indivar/github/sqlalchemy-continuum/sqlalchemy_continuum/manager.py", line 475, in track_association_operations
    self.append_association_operation(
  File "/home/indivar/github/sqlalchemy-continuum/sqlalchemy_continuum/manager.py", line 416, in append_association_operation
    uow = self.units_of_work[conn.engine]
KeyError: Engine(sqlite:///distdb.sqlite)