Skip to content

[sqlalchemy-spanner]: implement get_multi_unique_constraints() to avoid N+1 INFORMATION_SCHEMA queries during reflection #18837

Open

Description

@rocksnow1942

Determine this is the right repository

  • I determined this is the correct repository in which to report this bug.

Summary of the issue

SpannerDialect implements get_multi_columns, get_multi_pk_constraint, get_multi_foreign_keys, and get_multi_indexes, but does not implement get_multi_unique_constraints. As a result, MetaData.reflect() and Inspector._get_reflection_info() fall back to DefaultDialect.get_multi_unique_constraints(), issuing one INFORMATION_SCHEMA query per table instead of one query per schema.

Environment details

  • OS: Linux
  • Python version: 3.13
  • sqlalchemy-spanner version: 1.17.2 (also reproduced on main / 1.20.1 at eb8a76628adf10093959600071a18f50285c2ecf)
  • sqlalchemy version: 2.0.52

Problem description

SQLAlchemy 2.0's Inspector._get_reflection_info() (used by MetaData.reflect() and Inspector.reflect_table()) calls:

  • get_multi_columns
  • get_multi_pk_constraint
  • get_multi_foreign_keys
  • get_multi_indexes
  • get_multi_unique_constraints

Because SpannerDialect does not override get_multi_unique_constraints, SQLAlchemy's DefaultDialect.get_multi_unique_constraints loops over every table and calls SpannerDialect.get_unique_constraints(connection, table_name, schema) individually (1 query per table).

On a Spanner schema with ~125 tables, reflecting the schema via MetaData.reflect() executes 125 separate get_unique_constraints queries (taking ~15–20 seconds against Cloud Spanner or the Spanner emulator) whereas columns, PKs, FKs, and indexes only take 1 query each.

Suggested fix

Implement SpannerDialect.get_multi_unique_constraints(self, connection, schema=None, filter_names=None, scope=None, kind=None, **kw) (and have get_unique_constraints delegate to get_multi_unique_constraints(..., filter_names=[table_name]), matching get_columns, get_pk_constraint, get_foreign_keys, and get_indexes).

Reproduction steps: code

from google.cloud.sqlalchemy_spanner.sqlalchemy_spanner import SpannerDialect

dialect = SpannerDialect()
for method in (
    "get_multi_columns",
    "get_multi_pk_constraint",
    "get_multi_foreign_keys",
    "get_multi_indexes",
    "get_multi_unique_constraints",
):
    print(f"{method}: overridden={dialect._overrides_default(method)}")

Actual output

get_multi_columns: overridden=True
get_multi_pk_constraint: overridden=True
get_multi_foreign_keys: overridden=True
get_multi_indexes: overridden=True
get_multi_unique_constraints: overridden=False

Expected output

get_multi_unique_constraints is implemented on SpannerDialect so multi-table reflection executes a single query per schema.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    api: spannerIssues related to the Spanner API.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions