]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix get_columns sqlite reflection rejecting tables with WITHOUT_ROWID and/or STRICT...
authorTip ten Brink <75669206+tiptenbrink@users.noreply.github.com>
Mon, 15 Sep 2025 12:58:40 +0000 (08:58 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 15 Sep 2025 13:48:33 +0000 (09:48 -0400)
Fixed issue where SQLite table reflection would fail for tables using
``WITHOUT ROWID`` and/or ``STRICT`` table options when the table contained
generated columns. The regular expression used to parse ``CREATE TABLE``
statements for generated column detection has been updated to properly
handle these SQLite table options that appear after the column definitions.
Pull request courtesy Tip ten Brink.

Fixes: #12864
Closes: #12865
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/12865
Pull-request-sha: 833420afc72f1918a9ca315209ce6664a18ba07f

Change-Id: Ia25c05746055a66234531b4f1275bd3a2f3b44ad

doc/build/changelog/unreleased_20/12864.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/sqlite/base.py
test/dialect/test_sqlite.py

diff --git a/doc/build/changelog/unreleased_20/12864.rst b/doc/build/changelog/unreleased_20/12864.rst
new file mode 100644 (file)
index 0000000..f8d1e5b
--- /dev/null
@@ -0,0 +1,10 @@
+.. change::
+    :tags: bug, sqlite
+    :tickets: 12864
+
+    Fixed issue where SQLite table reflection would fail for tables using
+    ``WITHOUT ROWID`` and/or ``STRICT`` table options when the table contained
+    generated columns. The regular expression used to parse ``CREATE TABLE``
+    statements for generated column detection has been updated to properly
+    handle these SQLite table options that appear after the column definitions.
+    Pull request courtesy Tip ten Brink.
index d1abf26c3c5c31aa2e192deb56802f826a14163c..6530b7eb5c0aa7b5363078da94b5d1e7db3b3e5d 100644 (file)
@@ -2377,7 +2377,10 @@ class SQLiteDialect(default.DefaultDialect):
                 )
                 # remove create table
                 match = re.match(
-                    r"create table .*?\((.*)\)$",
+                    (
+                        r"create table .*?\((.*)\)"
+                        r"(?:\s*,?\s*(?:WITHOUT\s+ROWID|STRICT))*$"
+                    ),
                     tablesql.strip(),
                     re.DOTALL | re.IGNORECASE,
                 )
index 05c8ea250d225a3673c473607180c455bbd3914f..97fced12006ee752c628003502a4aed4db6994fc 100644 (file)
@@ -3980,6 +3980,25 @@ class ComputedReflectionTest(fixtures.TestBase):
                 x INTEGER GENERATED ALWAYS AS (INSTR(s, ",")) STORED,
                 y INTEGER GENERATED ALWAYS AS (INSTR(x, ",")) STORED
             );""",
+            """CREATE TABLE test9 (
+                id INTEGER PRIMARY KEY,
+                s VARCHAR,
+                x VARCHAR GENERATED ALWAYS AS (s || 'x')
+            ) WITHOUT ROWID;""",
+            """CREATE TABLE test10 (
+                s TEXT,
+                x TEXT GENERATED ALWAYS AS (s || 'x')
+            ) STRICT;""",
+            """CREATE TABLE test11 (
+                id INTEGER PRIMARY KEY,
+                s TEXT,
+                x TEXT GENERATED ALWAYS AS (s || 'x')
+            ) STRICT, WITHOUT ROWID;""",
+            """CREATE TABLE test12 (
+                id INTEGER PRIMARY KEY,
+                s TEXT,
+                x TEXT GENERATED ALWAYS AS (s || 'x')
+            ) WITHOUT ROWID, STRICT;""",
         ]
 
         with testing.db.begin() as conn:
@@ -4013,6 +4032,10 @@ class ComputedReflectionTest(fixtures.TestBase):
             "x": {"text": 'INSTR(s, ",")', "stored": True},
             "y": {"text": 'INSTR(x, ",")', "stored": True},
         },
+        "test9": {"x": {"text": "s || 'x'", "stored": False}},
+        "test10": {"x": {"text": "s || 'x'", "stored": False}},
+        "test11": {"x": {"text": "s || 'x'", "stored": False}},
+        "test12": {"x": {"text": "s || 'x'", "stored": False}},
     }
 
     def test_reflection(self, connection):