Skip to content

Commit 66fa086

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

2 files changed

Lines changed: 393 additions & 19 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
from uuid import UUID, uuid4
18+
19+
from sample_helper import run_sample
20+
from sqlalchemy import create_engine, select, types
21+
from sqlalchemy.dialects import registry
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)`.
40+
# - supports_native_uuid = True: Compiles to `UUID`.
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 `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."""
54+
55+
__tablename__ = "customers_legacy"
56+
id: Mapped[UUID] = mapped_column(types.Uuid(), primary_key=True, default=uuid4)
57+
name: Mapped[str] = mapped_column()
58+
59+
60+
class CustomerNative(Base):
61+
"""Uses types.UUID() -> ALWAYS compiles to native UUID type in Spanner DDL,
62+
63+
even when supports_native_uuid=False.
64+
"""
65+
66+
__tablename__ = "customers_native"
67+
id: Mapped[UUID] = mapped_column(types.UUID(), primary_key=True, default=uuid4)
68+
name: Mapped[str] = mapped_column()
69+
70+
71+
class CustomerExplicitString(Base):
72+
"""Uses types.Uuid(native_uuid=False) -> ALWAYS compiles to STRING(36)."""
73+
74+
__tablename__ = "customers_explicit_string"
75+
id: Mapped[UUID] = mapped_column(
76+
types.Uuid(native_uuid=False),
77+
primary_key=True,
78+
default=uuid4,
79+
)
80+
name: Mapped[str] = mapped_column()
81+
82+
83+
def run_with_flag_disabled():
84+
"""Scenario 1: supports_native_uuid = False (Default).
85+
86+
- types.Uuid() emits STRING(36) in DDL.
87+
- types.Uuid(native_uuid=False) emits STRING(36) in DDL.
88+
"""
89+
sep = "=" * 73
90+
print(f"\n{sep}")
91+
print("SCENARIO 1: Flag Disabled (supports_native_uuid=False)")
92+
print(sep)
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+
"--> [Flag Disabled] Legacy Customer ID "
113+
f"({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
114+
)
115+
116+
fetched_override = session.scalars(
117+
select(CustomerExplicitString).filter_by(id=cust_override.id)
118+
).one()
119+
print(
120+
"--> [Flag Disabled] Explicit STRING Customer ID "
121+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
122+
)
123+
124+
125+
def run_with_flag_enabled():
126+
"""Scenario 2: supports_native_uuid = True.
127+
128+
- types.Uuid() emits UUID in DDL when flag enabled.
129+
- types.UUID() ALWAYS emits UUID in DDL.
130+
- types.Uuid(native_uuid=False) explicitly emits STRING(36).
131+
"""
132+
sep = "=" * 73
133+
print(f"\n{sep}")
134+
print("SCENARIO 2: Flag Enabled (supports_native_uuid=True)")
135+
print(sep)
136+
engine = create_engine(
137+
"spanner:///projects/sample-project/"
138+
"instances/sample-instance/"
139+
"databases/sample-database",
140+
echo=True,
141+
)
142+
engine.dialect.supports_native_uuid = True
143+
Base.metadata.create_all(engine)
144+
145+
with Session(engine) as session:
146+
cust_legacy = CustomerLegacy(name="Charlie_Legacy_FlagEnabled")
147+
cust_native = CustomerNative(name="Bob_Native_FlagEnabled")
148+
cust_override = CustomerExplicitString(name="Diana_String_FlagEnabled")
149+
session.add_all([cust_legacy, cust_native, cust_override])
150+
session.commit()
151+
152+
fetched_legacy = session.scalars(
153+
select(CustomerLegacy).filter_by(id=cust_legacy.id)
154+
).one()
155+
print(
156+
"--> [Flag Enabled] Legacy Customer ID "
157+
f"({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
158+
)
159+
160+
fetched_native = session.scalars(
161+
select(CustomerNative).filter_by(id=cust_native.id)
162+
).one()
163+
print(
164+
"--> [Flag Enabled] Native Customer ID "
165+
f"({type(fetched_native.id).__name__}): {fetched_native.id}"
166+
)
167+
168+
fetched_override = session.scalars(
169+
select(CustomerExplicitString).filter_by(id=cust_override.id)
170+
).one()
171+
print(
172+
"--> [Flag Enabled] Explicit STRING Customer ID "
173+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
174+
)
175+
176+
177+
def uuid_sample():
178+
"""Demonstrates how to use types.Uuid vs types.UUID with Spanner SQLAlchemy
179+
180+
under both supports_native_uuid=False and supports_native_uuid=True.
181+
"""
182+
run_with_flag_disabled()
183+
run_with_flag_enabled()
184+
185+
186+
if __name__ == "__main__":
187+
run_sample(uuid_sample)

0 commit comments

Comments
 (0)