]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commit
fix catastrophic backtracking in sqlite inline unique reflection regex
authorJavid Khan <dxbjavid@gmail.com>
Wed, 8 Jul 2026 14:20:43 +0000 (10:20 -0400)
committerSQLAlchemy Gerrit Bot <sqla-tester@gerrit.sqlalchemy.org>
Wed, 8 Jul 2026 23:03:28 +0000 (23:03 +0000)
commit121a17bb32046b980f120abca4793aad116aa23c
treef5f32da60c0dbdb122228a61d40ab19ea8424e35
parent4009b58467c0f76d318a336894d754adb9ff4f74
fix catastrophic backtracking in sqlite inline unique reflection regex

Fixes: #13419
### Description

While reflecting a SQLite table, `get_unique_constraints` scans the stored
`CREATE TABLE` text (taken verbatim from `sqlite_master.sql`) with an
`INLINE_UNIQUE_PATTERN` to spot inline `UNIQUE` columns. The tail of that
pattern is `[\t ]+[a-z0-9_ ]+?[\t ]+UNIQUE`, where the lazy middle class
itself contains a space, so all three quantifiers can lay claim to the same
space character. When a column definition contains a run of whitespace that
isn't followed by `UNIQUE`, the engine tries every way of splitting that run
across the three quantifiers, which is cubic in the length of the run.

SQLite keeps the original whitespace of a `CREATE TABLE` statement in
`sqlite_master`, so any account that can create a table can leave a long gap
in a column definition and make later reflection of that schema hang.
A small reproduction:

```python
from sqlalchemy import create_engine, inspect

e = create_engine("sqlite://")
with e.begin() as c:
    c.exec_driver_sql(
        "CREATE TABLE t (x INTEGER" + " " * 1000 + "NOT NULL, "
        "y INTEGER NOT NULL UNIQUE)"
    )
inspect(e).get_unique_constraints("t")   # ~11s before, instant after
```

The fix tokenises the inter-keyword whitespace with disjoint classes,
`[\t ]+[a-z0-9_]+(?:[\t ]+[a-z0-9_]+)*?[\t ]+UNIQUE`, so a space only ever
belongs to a separator and never to a token. Valid inline `UNIQUE` columns
reflect exactly as before; I have added a regression test that reflects a
table carrying a long whitespace run and runs in linear time on the new
pattern.

### Checklist

This pull request is:

- [x] A short code fix

Closes: #13398
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13398
Pull-request-sha: e398806f1243897f25c6ef0697200a49df464243

Change-Id: I7e394357162c9d3aad6c7a72d9276321d404ca64
doc/build/changelog/unreleased_20/13419.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/sqlite/base.py
test/dialect/sqlite/test_reflection.py