]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
factor single-table reflection wrappers into common mixin
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 5 Jun 2026 20:02:02 +0000 (16:02 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 10 Jun 2026 19:21:28 +0000 (15:21 -0400)
Introduced _BackendsMultiReflection mixin in engine/default.py
that provides the get_columns(), get_pk_constraint(),
get_foreign_keys(), get_indexes(), get_unique_constraints(),
get_check_constraints(), get_table_comment(), and
get_table_options() single-table methods, each delegating to
the corresponding get_multi_* method with
filter_names=[table_name].

PostgreSQL, Oracle, and MSSQL dialects now inherit from this
mixin instead of duplicating the wrapper pattern.  Oracle
retains its _value_or_raise() override which applies
normalize_name() for case-folding.  MSSQL overrides
get_unique_constraints(), get_check_constraints(), and
get_table_options() with NotImplementedError since it has no
native get_multi_* for those yet.

Change-Id: If68bbf94b3956348fc7ae8179ff093d63dcdfbe2

lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/oracle/base.py
lib/sqlalchemy/dialects/postgresql/base.py
lib/sqlalchemy/engine/default.py

index b313acdbd9edb19210d57bd022b02ed55774f77f..9d348eb92e1662cc4f88c0f158b29f117ea1c732 100644 (file)
@@ -3072,7 +3072,7 @@ def _schema_elements(schema):
     return dbname, owner
 
 
-class MSDialect(default.DefaultDialect):
+class MSDialect(default._BackendsMultiReflection, default.DefaultDialect):
     # will assume it's at least mssql2005
     name = "mssql"
     supports_statement_cache = True
@@ -3959,22 +3959,6 @@ index_info AS (
         lookup = {n.lower(): n for n in filter_names}
         return lambda n: lookup.get(n.lower(), n)
 
-    @staticmethod
-    def _value_or_raise(data, table, schema):
-        """Unwrap a single ``(schema, table)`` entry from a multi-method
-        result, raising :exc:`.NoSuchTableError` when missing.
-
-        Mirrors PostgreSQL's helper of the same name. Used by the
-        single-table reflection wrappers that delegate to the multi
-        implementation.
-        """
-        try:
-            return dict(data)[(schema, table)]
-        except KeyError:
-            raise exc.NoSuchTableError(
-                f"{schema}.{table}" if schema else table
-            ) from None
-
     @_db_plus_owner_multi
     def get_multi_columns(
         self,
@@ -4791,64 +4775,17 @@ index_info AS (
                 exec_opts={"schema_translate_map": {"sys": "tempdb.sys"}},
             )
 
-    # --- Single-table reflection wrappers (delegate to multi) ---
+    # override mixin methods for which this dialect has no native
+    # get_multi_* implementation, preventing infinite recursion
+    # through DefaultDialect._default_multi_reflect
 
-    @reflection.cache
-    def get_columns(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_columns(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
-    @reflection.cache
-    def get_pk_constraint(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_pk_constraint(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
-    @reflection.cache
-    def get_foreign_keys(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_foreign_keys(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
+    def get_unique_constraints(
+        self, connection, table_name, schema=None, **kw
+    ):
+        raise NotImplementedError()
 
-    @reflection.cache
-    def get_indexes(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_indexes(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
+    def get_check_constraints(self, connection, table_name, schema=None, **kw):
+        raise NotImplementedError()
 
-    @reflection.cache
-    def get_table_comment(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_table_comment(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
+    def get_table_options(self, connection, table_name, schema=None, **kw):
+        raise NotImplementedError()
index a27306e067a68d46f66575c2b944cbaaa0e28e23..fbe0c6c90ad18ad084070045e954d760a0633315 100644 (file)
@@ -2083,7 +2083,7 @@ class OracleExecutionContext(default.DefaultExecutionContext):
             )
 
 
-class OracleDialect(default.DefaultDialect):
+class OracleDialect(default._BackendsMultiReflection, default.DefaultDialect):
     name = "oracle"
     supports_statement_cache = True
     supports_alter = True
@@ -2738,21 +2738,6 @@ class OracleDialect(default.DefaultDialect):
         else:
             return False, {}
 
-    @reflection.cache
-    def get_table_options(self, connection, table_name, schema=None, **kw):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_table_options(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _table_options_query(
         self, owner, scope, kind, has_filter_names, has_mat_views
@@ -2876,22 +2861,6 @@ class OracleDialect(default.DefaultDialect):
 
         return options.items()
 
-    @reflection.cache
-    def get_columns(self, connection, table_name, schema=None, **kw):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-
-        data = self.get_multi_columns(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     def _run_batches(
         self, connection, query, dblink, returns_long, mappings, all_objects
     ):
@@ -3158,21 +3127,6 @@ class OracleDialect(default.DefaultDialect):
                 identity["oracle_order"] = value == "Y"
         return identity
 
-    @reflection.cache
-    def get_table_comment(self, connection, table_name, schema=None, **kw):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_table_comment(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _comment_query(self, owner, scope, kind, has_filter_names):
         # NOTE: all_tab_comments / all_mview_comments have a row for all
@@ -3270,21 +3224,6 @@ class OracleDialect(default.DefaultDialect):
             for table, comment in result
         )
 
-    @reflection.cache
-    def get_indexes(self, connection, table_name, schema=None, **kw):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_indexes(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _index_query(self, owner):
         return (
@@ -3452,21 +3391,6 @@ class OracleDialect(default.DefaultDialect):
             )
         )
 
-    @reflection.cache
-    def get_pk_constraint(self, connection, table_name, schema=None, **kw):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_pk_constraint(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _constraint_query(self, owner):
         local = dictionary.all_cons_columns.alias("local")
@@ -3590,27 +3514,6 @@ class OracleDialect(default.DefaultDialect):
             )
         )
 
-    @reflection.cache
-    def get_foreign_keys(
-        self,
-        connection,
-        table_name,
-        schema=None,
-        **kw,
-    ):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_foreign_keys(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @_handle_synonyms_decorator
     def get_multi_foreign_keys(
         self,
@@ -3745,23 +3648,6 @@ class OracleDialect(default.DefaultDialect):
             )
         )
 
-    @reflection.cache
-    def get_unique_constraints(
-        self, connection, table_name, schema=None, **kw
-    ):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_unique_constraints(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @_handle_synonyms_decorator
     def get_multi_unique_constraints(
         self,
@@ -3886,24 +3772,6 @@ class OracleDialect(default.DefaultDialect):
         else:
             return rp
 
-    @reflection.cache
-    def get_check_constraints(
-        self, connection, table_name, schema=None, include_all=False, **kw
-    ):
-        """Supported kw arguments are: ``dblink`` to reflect via a db link;
-        ``oracle_resolve_synonyms`` to resolve names to synonyms
-        """
-        data = self.get_multi_check_constraints(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            include_all=include_all,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @_handle_synonyms_decorator
     def get_multi_check_constraints(
         self,
index 97a553bec162b6c91f41efc8e9282129b495e377..1312fe033b7c00660dd9171f5168aa41cb94495a 100644 (file)
@@ -3424,7 +3424,7 @@ class PGDeferrableConnectionCharacteristic(
         return dialect.get_deferrable(dbapi_conn)
 
 
-class PGDialect(default.DefaultDialect):
+class PGDialect(default._BackendsMultiReflection, default.DefaultDialect):
     name = "postgresql"
     supports_statement_cache = True
     supports_alter = True
@@ -3980,14 +3980,6 @@ class PGDialect(default.DefaultDialect):
         else:
             return res
 
-    def _value_or_raise(self, data, table, schema):
-        try:
-            return dict(data)[(schema, table)]
-        except KeyError:
-            raise exc.NoSuchTableError(
-                f"{schema}.{table}" if schema else table
-            ) from None
-
     def _prepare_filter_names(self, filter_names):
         if filter_names:
             return True, {"filter_names": filter_names}
@@ -4006,18 +3998,6 @@ class PGDialect(default.DefaultDialect):
             relkinds += pg_catalog.RELKINDS_MAT_VIEW
         return relkinds
 
-    @reflection.cache
-    def get_table_options(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_table_options(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _table_options_query(self, schema, has_filter_names, scope, kind):
         inherits_sq = (
@@ -4130,18 +4110,6 @@ class PGDialect(default.DefaultDialect):
 
         return table_options.items()
 
-    @reflection.cache
-    def get_columns(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_columns(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _columns_query(self, schema, has_filter_names, scope, kind):
         # NOTE: the query with the default and identity options scalar
@@ -4739,18 +4707,6 @@ class PGDialect(default.DefaultDialect):
                 else:
                     yield tablename, None, None, None, None
 
-    @reflection.cache
-    def get_pk_constraint(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_pk_constraint(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     def get_multi_pk_constraint(
         self, connection, schema, filter_names, scope, kind, **kw
     ):
@@ -4784,26 +4740,6 @@ class PGDialect(default.DefaultDialect):
             for table_name, cols, pk_name, comment, opts in result
         )
 
-    @reflection.cache
-    def get_foreign_keys(
-        self,
-        connection,
-        table_name,
-        schema=None,
-        postgresql_ignore_search_path=False,
-        **kw,
-    ):
-        data = self.get_multi_foreign_keys(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            postgresql_ignore_search_path=postgresql_ignore_search_path,
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _foreing_key_query(self, schema, has_filter_names, scope, kind):
         pg_class_ref = pg_catalog.pg_class.alias("cls_ref")
@@ -5015,18 +4951,6 @@ class PGDialect(default.DefaultDialect):
             table_fks.append(fkey_d)
         return fkeys.items()
 
-    @reflection.cache
-    def get_indexes(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_indexes(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @util.memoized_property
     def _index_query(self):
         # NOTE: pg_index is used as from two times to improve performance,
@@ -5341,20 +5265,6 @@ class PGDialect(default.DefaultDialect):
                     table_indexes.append(index)
         return indexes.items()
 
-    @reflection.cache
-    def get_unique_constraints(
-        self, connection, table_name, schema=None, **kw
-    ):
-        data = self.get_multi_unique_constraints(
-            connection,
-            schema=schema,
-            filter_names=[table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     def get_multi_unique_constraints(
         self,
         connection,
@@ -5389,18 +5299,6 @@ class PGDialect(default.DefaultDialect):
             uniques[(schema, table_name)].append(uc_dict)
         return uniques.items()
 
-    @reflection.cache
-    def get_table_comment(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_table_comment(
-            connection,
-            schema,
-            [table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _comment_query(self, schema, has_filter_names, scope, kind):
         relkinds = self._kind_to_relkinds(kind)
@@ -5445,18 +5343,6 @@ class PGDialect(default.DefaultDialect):
             for table, comment in result
         )
 
-    @reflection.cache
-    def get_check_constraints(self, connection, table_name, schema=None, **kw):
-        data = self.get_multi_check_constraints(
-            connection,
-            schema,
-            [table_name],
-            scope=ObjectScope.ANY,
-            kind=ObjectKind.ANY,
-            **kw,
-        )
-        return self._value_or_raise(data, table_name, schema)
-
     @lru_cache()
     def _check_constraint_query(self, schema, has_filter_names, scope, kind):
         relkinds = self._kind_to_relkinds(kind)
index 833ce0496422620bb33918b5eeaf664c72a1c01d..05dd336d476c20a316c003dc44bb098bdb18ce64 100644 (file)
@@ -44,6 +44,7 @@ import weakref
 from . import characteristics
 from . import cursor as _cursor
 from . import interfaces
+from . import reflection
 from .base import Connection
 from .interfaces import CacheStats
 from .interfaces import DBAPICursor
@@ -119,6 +120,121 @@ SERVER_SIDE_CURSOR_RE = re.compile(r"\s*SELECT", re.I | re.UNICODE)
 ) = list(CacheStats)
 
 
+class _BackendsMultiReflection(Dialect):
+    """Mixin providing single-table reflection wrappers that delegate to
+    the corresponding ``get_multi_*`` methods.
+
+    Used by dialects that implement native multi-table reflection
+    (PostgreSQL, Oracle, MSSQL).
+    """
+
+    def _value_or_raise(self, data, table, schema):
+        try:
+            return dict(data)[(schema, table)]
+        except KeyError:
+            raise exc.NoSuchTableError(
+                f"{schema}.{table}" if schema else table
+            ) from None
+
+    @reflection.cache
+    def get_columns(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_columns(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_table_options(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_table_options(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_pk_constraint(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_pk_constraint(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_foreign_keys(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_foreign_keys(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_indexes(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_indexes(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_unique_constraints(
+        self, connection, table_name, schema=None, **kw
+    ):
+        data = self.get_multi_unique_constraints(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_check_constraints(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_check_constraints(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+    @reflection.cache
+    def get_table_comment(self, connection, table_name, schema=None, **kw):
+        data = self.get_multi_table_comment(
+            connection,
+            schema=schema,
+            filter_names=[table_name],
+            scope=ObjectScope.ANY,
+            kind=ObjectKind.ANY,
+            **kw,
+        )
+        return self._value_or_raise(data, table_name, schema)
+
+
 class DefaultDialect(Dialect):
     """Default implementation of Dialect"""