]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Fix reflection of long expressions in postgresql
authorFederico Caselli <cfederico87@gmail.com>
Fri, 7 Apr 2023 18:35:37 +0000 (20:35 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Fri, 7 Apr 2023 18:36:45 +0000 (20:36 +0200)
Fixed issue that prevented reflection of expression based indexes
with long expressions in PostgreSQL. The expression where erroneously
truncated to the identifier length (that's 63 bytes by default).

Fixes: #9615
Change-Id: I50727b0699e08fa25f10f3c94dcf8b79534bfb75

doc/build/changelog/unreleased_20/9615.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/testing/suite/test_reflection.py

diff --git a/doc/build/changelog/unreleased_20/9615.rst b/doc/build/changelog/unreleased_20/9615.rst
new file mode 100644 (file)
index 0000000..ccc3757
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, postgresql
+    :tickets: 9615
+
+    Fixed issue that prevented reflection of expression based indexes
+    with long expressions in PostgreSQL. The expression where erroneously
+    truncated to the identifier length (that's 63 bytes by default).
index 18f31ce47079fd707178b253c2985ba1943b4bd5..4d299b9183d38063a8f7b2a8df5fb8be20b8a043 100644 (file)
@@ -3915,6 +3915,8 @@ class PGDialect(default.DefaultDialect):
             select(
                 pg_catalog.pg_class.c.relname,
                 pg_catalog.pg_constraint.c.conname,
+                # NOTE: avoid calling pg_get_constraintdef when not needed
+                # to speed up the query
                 sql.case(
                     (
                         pg_catalog.pg_constraint.c.oid.is_not(None),
@@ -4121,7 +4123,10 @@ class PGDialect(default.DefaultDialect):
                             idx_sq.c.indexrelid, idx_sq.c.ord + 1, True
                         ),
                     ),
-                    else_=pg_catalog.pg_attribute.c.attname,
+                    # NOTE: need to cast this since attname is of type "name"
+                    # that's limited to 63 bytes, while pg_get_indexdef
+                    # returns "text" so it may get cut
+                    else_=sql.cast(pg_catalog.pg_attribute.c.attname, TEXT()),
                 ).label("element"),
                 (idx_sq.c.attnum == 0).label("is_expr"),
             )
@@ -4169,9 +4174,9 @@ class PGDialect(default.DefaultDialect):
                 pg_catalog.pg_index.c.indoption,
                 pg_class_index.c.reloptions,
                 pg_catalog.pg_am.c.amname,
+                # NOTE: pg_get_expr is very fast so this case has almost no
+                # performance impact
                 sql.case(
-                    # pg_get_expr is very fast so this case has almost no
-                    # performance impact
                     (
                         pg_catalog.pg_index.c.indpred.is_not(None),
                         pg_catalog.pg_get_expr(
@@ -4179,7 +4184,7 @@ class PGDialect(default.DefaultDialect):
                             pg_catalog.pg_index.c.indrelid,
                         ),
                     ),
-                    else_=sql.null(),
+                    else_=None,
                 ).label("filter_definition"),
                 indnkeyatts,
                 cols_sq.c.elements,
@@ -4455,6 +4460,8 @@ class PGDialect(default.DefaultDialect):
             select(
                 pg_catalog.pg_class.c.relname,
                 pg_catalog.pg_constraint.c.conname,
+                # NOTE: avoid calling pg_get_constraintdef when not needed
+                # to speed up the query
                 sql.case(
                     (
                         pg_catalog.pg_constraint.c.oid.is_not(None),
index 8b7cb8cbc8309059684d77d62ccc4840ef8c3ae2..5927df065fccee7f6edfe0acde4093b2338de1e3 100644 (file)
@@ -2397,7 +2397,8 @@ class ComponentReflectionTestExtra(ComparesIndexes, fixtures.TestBase):
         )
 
         Index("t_idx", func.lower(t.c.x), t.c.z, func.lower(t.c.y))
-
+        long_str = "long string " * 100
+        Index("t_idx_long", func.coalesce(t.c.x, long_str))
         Index("t_idx_2", t.c.x)
 
         metadata.create_all(connection)
@@ -2424,24 +2425,41 @@ class ComponentReflectionTestExtra(ComparesIndexes, fixtures.TestBase):
 
         completeIndex(expected[0])
 
-        class filtering_str(str):
+        class lower_index_str(str):
             def __eq__(self, other):
                 # test that lower and x or y are in the string
                 return "lower" in other and ("x" in other or "y" in other)
 
+        class coalesce_index_str(str):
+            def __eq__(self, other):
+                # test that coalesce and the string is in other
+                return "coalesce" in other.lower() and long_str in other
+
         if testing.requires.reflect_indexes_with_expressions.enabled:
             expr_index = {
                 "name": "t_idx",
                 "column_names": [None, "z", None],
                 "expressions": [
-                    filtering_str("lower(x)"),
+                    lower_index_str("lower(x)"),
                     "z",
-                    filtering_str("lower(y)"),
+                    lower_index_str("lower(y)"),
                 ],
                 "unique": False,
             }
             completeIndex(expr_index)
             expected.insert(0, expr_index)
+
+            expr_index_long = {
+                "name": "t_idx_long",
+                "column_names": [None],
+                "expressions": [
+                    coalesce_index_str(f"coalesce(x, '{long_str}')")
+                ],
+                "unique": False,
+            }
+            completeIndex(expr_index_long)
+            expected.append(expr_index_long)
+
             eq_(insp.get_indexes("t"), expected)
             m2 = MetaData()
             t2 = Table("t", m2, autoload_with=connection)