Skip to content

Commit 3572c8b

Browse files
test(sqlalchemy-spanner): add mockserver 15-matrix tests and sample for types.Uuid vs types.UUID
1 parent b60699e commit 3572c8b

2 files changed

Lines changed: 373 additions & 17 deletions

File tree

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Copyright 2026 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import annotations
16+
17+
import uuid
18+
from uuid import UUID
19+
20+
from sample_helper import run_sample
21+
from sqlalchemy import create_engine, select, types
22+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
23+
24+
registry.register(
25+
"spanner",
26+
"google.cloud.sqlalchemy_spanner.sqlalchemy_spanner",
27+
"SpannerDialect",
28+
)
29+
30+
31+
class Base(DeclarativeBase):
32+
pass
33+
34+
35+
# -----------------------------------------------------------------------------
36+
# GUIDELINES FOR USERS / CUSTOMERS:
37+
#
38+
# 1. types.Uuid() (CamelCase - Recommended):
39+
# - supports_native_uuid = False (default): Compiles to `STRING(36)` in DDL.
40+
# - supports_native_uuid = True: Compiles to native `UUID` in DDL.
41+
# - Works seamlessly for inserts and queries across both configurations.
42+
#
43+
# 2. types.UUID() (All-Caps - Native Spanner Type):
44+
# - IMPORTANT: Use `types.UUID()` ONLY when `supports_native_uuid = True`
45+
# is enabled on your dialect/engine for native Cloud Spanner UUID columns.
46+
#
47+
# 3. types.Uuid(native_uuid=False) (Explicit Override):
48+
# - Forces legacy `STRING(36)` storage even when `supports_native_uuid = True`.
49+
# -----------------------------------------------------------------------------
50+
51+
52+
class CustomerLegacy(Base):
53+
"""Uses types.Uuid() -> Compiles to STRING(36) by default (flag disabled)."""
54+
55+
__tablename__ = "customers_legacy"
56+
id: Mapped[UUID] = mapped_column(
57+
types.Uuid(), primary_key=True, default=uuid.uuid4
58+
)
59+
name: Mapped[str] = mapped_column()
60+
61+
62+
class CustomerNative(Base):
63+
"""Uses types.UUID() -> ALWAYS compiles to native UUID type in Spanner DDL,
64+
65+
even when supports_native_uuid=False.
66+
"""
67+
68+
__tablename__ = "customers_native"
69+
id: Mapped[UUID] = mapped_column(
70+
types.UUID(), primary_key=True, default=uuid.uuid4
71+
)
72+
name: Mapped[str] = mapped_column()
73+
74+
75+
class CustomerExplicitString(Base):
76+
"""Uses types.Uuid(native_uuid=False) -> ALWAYS compiles to STRING(36)."""
77+
78+
__tablename__ = "customers_explicit_string"
79+
id: Mapped[UUID] = mapped_column(
80+
types.Uuid(native_uuid=False), primary_key=True, default=uuid.uuid4
81+
)
82+
name: Mapped[str] = mapped_column()
83+
84+
85+
def run_with_flag_disabled():
86+
"""Scenario 1: supports_native_uuid = False (Default).
87+
- types.Uuid() emits STRING(36) in DDL.
88+
- types.Uuid(native_uuid=False) emits STRING(36) in DDL.
89+
"""
90+
print("\n=========================================================================")
91+
print("SCENARIO 1: Flag Disabled (supports_native_uuid=False)")
92+
print("=========================================================================")
93+
engine = create_engine(
94+
"spanner:///projects/sample-project/"
95+
"instances/sample-instance/"
96+
"databases/sample-database",
97+
echo=True,
98+
)
99+
engine.dialect.supports_native_uuid = False
100+
Base.metadata.create_all(engine)
101+
102+
with Session(engine) as session:
103+
cust_legacy = CustomerLegacy(name="Alice_Legacy_FlagDisabled")
104+
cust_override = CustomerExplicitString(name="Diana_String_FlagDisabled")
105+
session.add_all([cust_legacy, cust_override])
106+
session.commit()
107+
108+
fetched_legacy = session.scalars(
109+
select(CustomerLegacy).filter_by(id=cust_legacy.id)
110+
).one()
111+
print(
112+
f"--> [Flag Disabled] Legacy Customer ID ({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
113+
)
114+
115+
fetched_override = session.scalars(
116+
select(CustomerExplicitString).filter_by(id=cust_override.id)
117+
).one()
118+
print(
119+
"--> [Flag Disabled] Explicit STRING Customer ID "
120+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
121+
)
122+
123+
124+
def run_with_flag_enabled():
125+
"""Scenario 2: supports_native_uuid = True.
126+
- types.Uuid() emits UUID in DDL when flag enabled.
127+
- types.UUID() ALWAYS emits UUID in DDL.
128+
- types.Uuid(native_uuid=False) explicitly emits STRING(36).
129+
"""
130+
print("\n=========================================================================")
131+
print("SCENARIO 2: Flag Enabled (supports_native_uuid=True)")
132+
print("=========================================================================")
133+
engine = create_engine(
134+
"spanner:///projects/sample-project/"
135+
"instances/sample-instance/"
136+
"databases/sample-database",
137+
echo=True,
138+
)
139+
engine.dialect.supports_native_uuid = True
140+
Base.metadata.create_all(engine)
141+
142+
with Session(engine) as session:
143+
cust_legacy = CustomerLegacy(name="Charlie_Legacy_FlagEnabled")
144+
cust_native = CustomerNative(name="Bob_Native_FlagEnabled")
145+
cust_override = CustomerExplicitString(name="Diana_String_FlagEnabled")
146+
session.add_all([cust_legacy, cust_native, cust_override])
147+
session.commit()
148+
149+
fetched_legacy = session.scalars(
150+
select(CustomerLegacy).filter_by(id=cust_legacy.id)
151+
).one()
152+
print(
153+
f"--> [Flag Enabled] Legacy Customer ID ({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
154+
)
155+
156+
fetched_native = session.scalars(
157+
select(CustomerNative).filter_by(id=cust_native.id)
158+
).one()
159+
print(
160+
f"--> [Flag Enabled] Native Customer ID ({type(fetched_native.id).__name__}): {fetched_native.id}"
161+
)
162+
163+
fetched_override = session.scalars(
164+
select(CustomerExplicitString).filter_by(id=cust_override.id)
165+
).one()
166+
print(
167+
"--> [Flag Enabled] Explicit STRING Customer ID "
168+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
169+
)
170+
171+
172+
def uuid_sample():
173+
"""Demonstrates how to use types.Uuid vs types.UUID with Spanner SQLAlchemy
174+
under both supports_native_uuid=False and supports_native_uuid=True configurations.
175+
"""
176+
run_with_flag_disabled()
177+
run_with_flag_enabled()
178+
179+
180+
if __name__ == "__main__":
181+
run_sample(uuid_sample)

0 commit comments

Comments
 (0)