From: Austin Graham Date: Wed, 18 Mar 2026 14:57:13 +0000 (-0400) Subject: Allow escaped quotes in Postgres quoted identifier X-Git-Tag: rel_2_1_0b2~22^2 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=c7412ac909125db873b9ddff0dafb8d92034d0a7;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Allow escaped quotes in Postgres quoted identifier ### Description Issue: https://github.com/sqlalchemy/sqlalchemy/issues/10902 Hello! This is my first PR here, so please let me know what I may have missed in terms of having a valuable contribution. I was looking through issues to grab an easy first one, and found this. Looks like someone else was going to have a go at it, but never did. I simply added a small change to the FK regex in for Postgres that allows anything not quotes alongside escaped double quotes. Test is included for the scenario mentioned in the issue. Alongside that, I didn't see a test for general quoted strings, so I added another one that includes spaces and dashes, in my experience common things to be used inside quoted identifiers. A manual test as well: DB setup: ``` austin_test_bug=# CREATE TABLE """test_parent_table-quoted""" (id SERIAL PRIMARY KEY, val INTEGER); CREATE TABLE austin_test_bug=# CREATE TABLE test_child_table_ref_quoted (id SERIAL, parent INTEGER, CONSTRAINT fk_parent FOREIGN KEY (parent) REFERENCES """test_parent_table-quoted"""(id)); CREATE TABLE austin_test_bug=# \d+ List of relations Schema | Name | Type | Owner | Persistence | Access method | Size | Description --------+------------------------------------+----------+----------+-------------+---------------+------------+------------- public | "test_parent_table-quoted" | table | postgres | permanent | heap | 0 bytes | public | "test_parent_table-quoted"_id_seq | sequence | postgres | permanent | | 8192 bytes | public | test_child_table_ref_quoted | table | postgres | permanent | heap | 0 bytes | public | test_child_table_ref_quoted_id_seq | sequence | postgres | permanent | | 8192 bytes | (4 rows) ``` And the python: ``` >>> from sqlalchemy import create_engine, inspect >>> engine = create_engine('postgresql://scott:tiger@localhost:5432/austin_test_bug') >>> connection = engine.connect() >>> inspect(connection).get_multi_foreign_keys() {(None, '"test_parent_table-quoted"'): [], (None, 'test_child_table_ref_quoted'): [{'name': 'fk_parent', 'constrained_columns': ['parent'], 'referred_schema': None, 'referred_table': '"test_parent_table-quoted"', 'referred_columns': ['id'], 'options': {}, 'comment': None}]} ``` ### Checklist This pull request is: - [ ] A documentation / typographical / small typing error fix - Good to go, no issue or tests are needed - [x] A short code fix - please include the issue number, and create an issue if none exists, which must include a complete example of the issue. one line code fixes without an issue and demonstration will not be accepted. - Please include: `Fixes: #` in the commit message - please include tests. one line code fixes without tests will not be accepted. - [ ] A new feature implementation - please include the issue number, and create an issue if none exists, which must include a complete example of how the feature would look. - Please include: `Fixes: #` in the commit message - please include tests. **Have a nice day!** Fixes: #10902 Closes: #13179 Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/13179 Pull-request-sha: 8890dc3250a30fc62b8f15cd1da1353b81524c00 Change-Id: I185c2cb062740551ab931368de602054eb5a4acd --- diff --git a/doc/build/changelog/unreleased_20/10902.rst b/doc/build/changelog/unreleased_20/10902.rst new file mode 100644 index 0000000000..7ef60abba4 --- /dev/null +++ b/doc/build/changelog/unreleased_20/10902.rst @@ -0,0 +1,7 @@ +.. change:: + :tags: postgresql, bug + :tickets: 10902 + + Fixed regular expression used when reflecting foreign keys in PostgreSQL to + support escaped quotes in table names. + Pull request courtesy of Austin Graham diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index dfc8651bb7..25d6b6fbea 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -4830,7 +4830,7 @@ class PGDialect(default.DefaultDialect): @util.memoized_property def _fk_regex_pattern(self): # optionally quoted token - qtoken = r'(?:"[^"]+"|[\w]+?)' + qtoken = r'(?:"(?:[^"]|"")+"|[\w]+?)' # https://www.postgresql.org/docs/current/static/sql-createtable.html return re.compile( diff --git a/test/dialect/postgresql/test_reflection.py b/test/dialect/postgresql/test_reflection.py index 5f3aa87a3b..57e795ad3a 100644 --- a/test/dialect/postgresql/test_reflection.py +++ b/test/dialect/postgresql/test_reflection.py @@ -1181,6 +1181,29 @@ class RegexTest(fixtures.TestBase): referred_schema='"schema_スキーマ"', ), ), + # Tests quoted identifiers containing characters + # not valid in unquoted SQL identifiers + ( + 'FOREIGN KEY ("tid 1", "tid-2") ' + 'REFERENCES some_schema."some table"(id1, id2)', + _fk_match( + '"tid 1", "tid-2"', + '"some table"', + "id1, id2", + referred_schema="some_schema", + ), + ), + # Tests more quotes escaped within a quote string + ( + "FOREIGN KEY (tid_1, tid_2) " + 'REFERENCES some_schema."""some_table"""(id1, id2)', + _fk_match( + "tid_1, tid_2", + '"""some_table"""', + "id1, id2", + referred_schema="some_schema", + ), + ), ) def test_fk_parsing(self, condef, expected):