Skip to content

Commit 3952c47

Browse files
feat(sqlalchemy-spanner): native UUID data type support in dialect
Adds native Spanner UUID type support to SQLAlchemy dialect: - Registers "UUID": types.UUID in _type_map for reflection - Registers types.UUID: "UUID" in _type_map_inv - Implements visit_UUID and visit_uuid in SpannerTypeCompiler
1 parent 5b2da81 commit 3952c47

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

packages/sqlalchemy-spanner/google/cloud/sqlalchemy_spanner/sqlalchemy_spanner.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def process(value):
120120
"TIMESTAMP": types.TIMESTAMP,
121121
"ARRAY": types.ARRAY,
122122
"JSON": types.JSON,
123+
"UUID": types.UUID,
123124
}
124125

125126

@@ -138,6 +139,8 @@ def process(value):
138139
types.TIMESTAMP: "TIMESTAMP",
139140
types.Integer: "INT64",
140141
types.NullType: "INT64",
142+
types.UUID: "UUID",
143+
types.Uuid: "UUID",
141144
}
142145

143146
_compound_keywords = {
@@ -764,6 +767,15 @@ class SpannerTypeCompiler(GenericTypeCompiler):
764767
Maps SQLAlchemy types to Spanner data types.
765768
"""
766769

770+
def visit_UUID(self, type_, **kw):
771+
return "UUID"
772+
773+
def visit_uuid(self, type_, **kw):
774+
if not type_.native_uuid or not self.dialect.supports_native_uuid:
775+
return self.visit_CHAR(types.CHAR(36), **kw)
776+
else:
777+
return self.visit_UUID(type_, **kw)
778+
767779
def visit_INTEGER(self, type_, **kw):
768780
return "INT64"
769781

@@ -846,6 +858,7 @@ class SpannerDialect(DefaultDialect):
846858
supports_identity_columns = True
847859
supports_native_boolean = True
848860
supports_native_decimal = True
861+
supports_native_uuid = True
849862
supports_statement_cache = True
850863
# Spanner uses protos for enums. Creating a column like
851864
# Column("an_enum", Enum("A", "B", "C")) will result in a String
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Copyright 2026 Google LLC
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 sqlalchemy import Column, MetaData, Table, types
16+
from sqlalchemy.schema import CreateTable
17+
from sqlalchemy.testing import eq_, fixtures
18+
from google.cloud.sqlalchemy_spanner.sqlalchemy_spanner import (
19+
SpannerDialect,
20+
_type_map,
21+
_type_map_inv,
22+
)
23+
24+
25+
class UuidTest(fixtures.TestBase):
26+
def test_uuid_type_mapping(self):
27+
"""Test UUID is registered in _type_map and _type_map_inv."""
28+
assert "UUID" in _type_map
29+
eq_(_type_map["UUID"], types.UUID)
30+
assert types.UUID in _type_map_inv
31+
eq_(_type_map_inv[types.UUID], "UUID")
32+
assert types.Uuid in _type_map_inv
33+
eq_(_type_map_inv[types.Uuid], "UUID")
34+
35+
def test_uuid_designate_type(self):
36+
"""Test reflecting UUID type string returns types.UUID."""
37+
dialect = SpannerDialect()
38+
assert dialect.supports_native_uuid is True
39+
col_type = dialect._designate_type("UUID")
40+
eq_(col_type, types.UUID)
41+
42+
def test_uuid_ddl_compilation_native_enabled(self):
43+
"""Test DDL compilation for UUID column emits UUID type when native."""
44+
dialect = SpannerDialect()
45+
metadata = MetaData()
46+
table = Table(
47+
"test_uuid_table",
48+
metadata,
49+
Column("user_id", types.Uuid, primary_key=True),
50+
)
51+
statement = str(CreateTable(table).compile(dialect=dialect)).strip()
52+
assert "user_id UUID NOT NULL" in statement
53+
54+
def test_uuid_ddl_compilation_native_disabled(self):
55+
"""Test DDL compilation falls back to STRING(36) when native_uuid is False."""
56+
dialect = SpannerDialect()
57+
dialect.supports_native_uuid = False
58+
metadata = MetaData()
59+
table = Table(
60+
"test_uuid_table",
61+
metadata,
62+
Column("user_id", types.Uuid, primary_key=True),
63+
)
64+
statement = str(CreateTable(table).compile(dialect=dialect)).strip()
65+
assert "user_id STRING(36) NOT NULL" in statement

0 commit comments

Comments
 (0)