Skip to content

Commit 4693696

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

2 files changed

Lines changed: 386 additions & 19 deletions

File tree

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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.dialects import registry
23+
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
24+
25+
registry.register(
26+
"spanner",
27+
"google.cloud.sqlalchemy_spanner.sqlalchemy_spanner",
28+
"SpannerDialect",
29+
)
30+
31+
32+
class Base(DeclarativeBase):
33+
pass
34+
35+
36+
# -----------------------------------------------------------------------------
37+
# GUIDELINES FOR USERS / CUSTOMERS:
38+
#
39+
# 1. types.Uuid() (CamelCase - Recommended):
40+
# - supports_native_uuid = False (default): Compiles to `STRING(36)`.
41+
# - supports_native_uuid = True: Compiles to `UUID`.
42+
# - Works seamlessly for inserts and queries across both configurations.
43+
#
44+
# 2. types.UUID() (All-Caps - Native Spanner Type):
45+
# - IMPORTANT: Use `types.UUID()` ONLY when `supports_native_uuid = True`
46+
# is enabled on your dialect/engine for native Cloud Spanner UUID columns.
47+
#
48+
# 3. types.Uuid(native_uuid=False) (Explicit Override):
49+
# - Forces `STRING(36)` storage even when `supports_native_uuid = True`.
50+
# -----------------------------------------------------------------------------
51+
52+
53+
class CustomerLegacy(Base):
54+
"""Uses types.Uuid() -> Compiles to STRING(36) by default."""
55+
56+
__tablename__ = "customers_legacy"
57+
id: Mapped[UUID] = mapped_column(
58+
types.Uuid(), primary_key=True, default=uuid.uuid4
59+
)
60+
name: Mapped[str] = mapped_column()
61+
62+
63+
class CustomerNative(Base):
64+
"""Uses types.UUID() -> ALWAYS compiles to native UUID type in Spanner DDL,
65+
66+
even when supports_native_uuid=False.
67+
"""
68+
69+
__tablename__ = "customers_native"
70+
id: Mapped[UUID] = mapped_column(
71+
types.UUID(), primary_key=True, default=uuid.uuid4
72+
)
73+
name: Mapped[str] = mapped_column()
74+
75+
76+
class CustomerExplicitString(Base):
77+
"""Uses types.Uuid(native_uuid=False) -> ALWAYS compiles to STRING(36)."""
78+
79+
__tablename__ = "customers_explicit_string"
80+
id: Mapped[UUID] = mapped_column(
81+
types.Uuid(native_uuid=False), primary_key=True, default=uuid.uuid4
82+
)
83+
name: Mapped[str] = mapped_column()
84+
85+
86+
def run_with_flag_disabled():
87+
"""Scenario 1: supports_native_uuid = False (Default).
88+
89+
- types.Uuid() emits STRING(36) in DDL.
90+
- types.Uuid(native_uuid=False) emits STRING(36) in DDL.
91+
"""
92+
sep = "=" * 73
93+
print(f"\n{sep}")
94+
print("SCENARIO 1: Flag Disabled (supports_native_uuid=False)")
95+
print(sep)
96+
engine = create_engine(
97+
"spanner:///projects/sample-project/"
98+
"instances/sample-instance/"
99+
"databases/sample-database",
100+
echo=True,
101+
)
102+
engine.dialect.supports_native_uuid = False
103+
Base.metadata.create_all(engine)
104+
105+
with Session(engine) as session:
106+
cust_legacy = CustomerLegacy(name="Alice_Legacy_FlagDisabled")
107+
cust_override = CustomerExplicitString(
108+
name="Diana_String_FlagDisabled"
109+
)
110+
session.add_all([cust_legacy, cust_override])
111+
session.commit()
112+
113+
fetched_legacy = session.scalars(
114+
select(CustomerLegacy).filter_by(id=cust_legacy.id)
115+
).one()
116+
print(
117+
"--> [Flag Disabled] Legacy Customer ID "
118+
f"({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
119+
)
120+
121+
fetched_override = session.scalars(
122+
select(CustomerExplicitString).filter_by(id=cust_override.id)
123+
).one()
124+
print(
125+
"--> [Flag Disabled] Explicit STRING Customer ID "
126+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
127+
)
128+
129+
130+
def run_with_flag_enabled():
131+
"""Scenario 2: supports_native_uuid = True.
132+
133+
- types.Uuid() emits UUID in DDL when flag enabled.
134+
- types.UUID() ALWAYS emits UUID in DDL.
135+
- types.Uuid(native_uuid=False) explicitly emits STRING(36).
136+
"""
137+
sep = "=" * 73
138+
print(f"\n{sep}")
139+
print("SCENARIO 2: Flag Enabled (supports_native_uuid=True)")
140+
print(sep)
141+
engine = create_engine(
142+
"spanner:///projects/sample-project/"
143+
"instances/sample-instance/"
144+
"databases/sample-database",
145+
echo=True,
146+
)
147+
engine.dialect.supports_native_uuid = True
148+
Base.metadata.create_all(engine)
149+
150+
with Session(engine) as session:
151+
cust_legacy = CustomerLegacy(name="Charlie_Legacy_FlagEnabled")
152+
cust_native = CustomerNative(name="Bob_Native_FlagEnabled")
153+
cust_override = CustomerExplicitString(name="Diana_String_FlagEnabled")
154+
session.add_all([cust_legacy, cust_native, cust_override])
155+
session.commit()
156+
157+
fetched_legacy = session.scalars(
158+
select(CustomerLegacy).filter_by(id=cust_legacy.id)
159+
).one()
160+
print(
161+
"--> [Flag Enabled] Legacy Customer ID "
162+
f"({type(fetched_legacy.id).__name__}): {fetched_legacy.id}"
163+
)
164+
165+
fetched_native = session.scalars(
166+
select(CustomerNative).filter_by(id=cust_native.id)
167+
).one()
168+
print(
169+
"--> [Flag Enabled] Native Customer ID "
170+
f"({type(fetched_native.id).__name__}): {fetched_native.id}"
171+
)
172+
173+
fetched_override = session.scalars(
174+
select(CustomerExplicitString).filter_by(id=cust_override.id)
175+
).one()
176+
print(
177+
"--> [Flag Enabled] Explicit STRING Customer ID "
178+
f"({type(fetched_override.id).__name__}): {fetched_override.id}"
179+
)
180+
181+
182+
def uuid_sample():
183+
"""Demonstrates how to use types.Uuid vs types.UUID with Spanner SQLAlchemy
184+
185+
under both supports_native_uuid=False and supports_native_uuid=True.
186+
"""
187+
run_with_flag_disabled()
188+
run_with_flag_enabled()
189+
190+
191+
if __name__ == "__main__":
192+
run_sample(uuid_sample)

0 commit comments

Comments
 (0)