Skip to content

Commit c4926d6

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

2 files changed

Lines changed: 384 additions & 19 deletions

File tree

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

0 commit comments

Comments
 (0)