From f4ea72d1fc67f9761557f70011a0cc96ff0fbfcb Mon Sep 17 00:00:00 2001 From: Walt Askew Date: Thu, 12 Jun 2025 17:21:52 +0000 Subject: [PATCH] feat: Support Server-Side Checks for Enums Spanner uses protos for enums. Creating a column like Column("an_enum", Enum("A", "B", "C")) will result in a String column. Setting supports_native_enum to False allows SQLAlchemy to generate check constraints to enforce the enum values if the create_constraint=True flag is passed to the Enum constructor. Fixes: #686 --- google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py | 7 ++++++- test/mockserver_tests/test_basics.py | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py index 8e935fb2..ada3eca9 100644 --- a/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py +++ b/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py @@ -798,10 +798,15 @@ class SpannerDialect(DefaultDialect): supports_sequences = True sequences_optional = False supports_identity_columns = True - supports_native_enum = True supports_native_boolean = True supports_native_decimal = True supports_statement_cache = True + # Spanner uses protos for enums. Creating a column like + # Column("an_enum", Enum("A", "B", "C")) will result in a String + # column. Setting supports_native_enum to False allows SQLAlchemy + # to generate check constraints to enforce the enum values if the + # create_constraint=True flag is passed to the Enum constructor. + supports_native_enum = False postfetch_lastrowid = False insert_returning = True diff --git a/test/mockserver_tests/test_basics.py b/test/mockserver_tests/test_basics.py index 3e422885..7d40e874 100644 --- a/test/mockserver_tests/test_basics.py +++ b/test/mockserver_tests/test_basics.py @@ -27,6 +27,7 @@ func, text, BigInteger, + Enum, ) from sqlalchemy.orm import Session, DeclarativeBase, Mapped, mapped_column from sqlalchemy.testing import eq_, is_instance_of @@ -120,6 +121,7 @@ def test_create_table(self): metadata, Column("user_id", Integer, primary_key=True), Column("user_name", String(16), nullable=False), + Column("status", Enum("a", "b", "cee", create_constraint=True)), ) metadata.create_all(engine) requests = self.database_admin_service.requests @@ -130,7 +132,9 @@ def test_create_table(self): "CREATE TABLE users (\n" "\tuser_id INT64 NOT NULL " "GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), \n" - "\tuser_name STRING(16) NOT NULL\n" + "\tuser_name STRING(16) NOT NULL, \n" + "\tstatus STRING(3), \n" + "\tCHECK (status IN ('a', 'b', 'cee'))\n" ") PRIMARY KEY (user_id)", requests[0].statements[0], )