Skip to content

Commit 7952799

Browse files
olavloitebhatt4982
andauthored
test: add tests for using json array (#534)
* test: add tests for using json array Adds test to verify that using JSON with both an array and a dict work as expected. Fixes #404 * chore: remove GetSession checks in tests --------- Co-authored-by: Sanjeev Bhatt <[email protected]>
1 parent e9df810 commit 7952799

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright 2024 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 sqlalchemy import String, BigInteger
16+
from sqlalchemy.sql.sqltypes import JSON
17+
from sqlalchemy.orm import DeclarativeBase
18+
from sqlalchemy.orm import Mapped
19+
from sqlalchemy.orm import mapped_column
20+
21+
22+
class Base(DeclarativeBase):
23+
pass
24+
25+
26+
class Venue(Base):
27+
__tablename__ = "venues"
28+
id: Mapped[int] = mapped_column(BigInteger, primary_key=True)
29+
name: Mapped[str] = mapped_column(String)
30+
description = mapped_column(JSON)

test/mockserver_tests/test_json.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Copyright 2024 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 sqlalchemy import create_engine, select
16+
from sqlalchemy.orm import Session
17+
from sqlalchemy.testing import eq_, is_instance_of
18+
from google.cloud.spanner_v1 import (
19+
FixedSizePool,
20+
ResultSet,
21+
BatchCreateSessionsRequest,
22+
ExecuteSqlRequest,
23+
CommitRequest,
24+
BeginTransactionRequest,
25+
TypeCode,
26+
JsonObject,
27+
)
28+
from test.mockserver_tests.mock_server_test_base import (
29+
MockServerTestBase,
30+
add_result,
31+
add_update_count,
32+
)
33+
from google.cloud.spanner_admin_database_v1 import UpdateDatabaseDdlRequest
34+
import google.cloud.spanner_v1.types.type as spanner_type
35+
import google.cloud.spanner_v1.types.result_set as result_set
36+
37+
38+
class TestJson(MockServerTestBase):
39+
def test_create_table(self):
40+
from test.mockserver_tests.json_model import Base
41+
42+
add_result(
43+
"""SELECT true
44+
FROM INFORMATION_SCHEMA.TABLES
45+
WHERE TABLE_SCHEMA="" AND TABLE_NAME="venues"
46+
LIMIT 1
47+
""",
48+
ResultSet(),
49+
)
50+
engine = create_engine(
51+
"spanner:///projects/p/instances/i/databases/d",
52+
connect_args={"client": self.client, "pool": FixedSizePool(size=10)},
53+
)
54+
Base.metadata.create_all(engine)
55+
requests = self.database_admin_service.requests
56+
eq_(1, len(requests))
57+
is_instance_of(requests[0], UpdateDatabaseDdlRequest)
58+
eq_(1, len(requests[0].statements))
59+
eq_(
60+
"CREATE TABLE venues (\n"
61+
"\tid INT64 NOT NULL, \n"
62+
"\tname STRING(MAX) NOT NULL, \n"
63+
"\tdescription JSON\n"
64+
") PRIMARY KEY (id)",
65+
requests[0].statements[0],
66+
)
67+
68+
def test_insert_dict(self):
69+
self._test_insert_json(
70+
{"type": "Stadium", "size": "Great"}, '{"size":"Great","type":"Stadium"}'
71+
)
72+
73+
def test_insert_array(self):
74+
self._test_insert_json(
75+
[{"type": "Stadium", "size": "Great"}],
76+
'[{"size":"Great","type":"Stadium"}]',
77+
)
78+
79+
def _test_insert_json(self, description, expected):
80+
from test.mockserver_tests.json_model import Venue
81+
82+
add_update_count(
83+
"INSERT INTO venues (id, name, description) VALUES (@a0, @a1, @a2)", 1
84+
)
85+
engine = create_engine(
86+
"spanner:///projects/p/instances/i/databases/d",
87+
connect_args={"client": self.client, "pool": FixedSizePool(size=10)},
88+
)
89+
90+
with Session(engine) as session:
91+
venue = Venue(id=1, name="Test", description=description)
92+
session.add(venue)
93+
session.commit()
94+
95+
# Verify the requests that we got.
96+
requests = self.spanner_service.requests
97+
eq_(4, len(requests))
98+
is_instance_of(requests[0], BatchCreateSessionsRequest)
99+
is_instance_of(requests[1], BeginTransactionRequest)
100+
is_instance_of(requests[2], ExecuteSqlRequest)
101+
is_instance_of(requests[3], CommitRequest)
102+
request: ExecuteSqlRequest = requests[2]
103+
eq_(3, len(request.params))
104+
eq_("1", request.params["a0"])
105+
eq_("Test", request.params["a1"])
106+
eq_(expected, request.params["a2"])
107+
eq_(TypeCode.INT64, request.param_types["a0"].code)
108+
eq_(TypeCode.STRING, request.param_types["a1"].code)
109+
eq_(TypeCode.JSON, request.param_types["a2"].code)
110+
111+
def test_select_dict(self):
112+
self._test_select_json(
113+
'{"size":"Great","type":"Stadium"}',
114+
JsonObject({"size": "Great", "type": "Stadium"}),
115+
)
116+
117+
def test_select_array(self):
118+
self._test_select_json(
119+
'[{"size":"Great","type":"Stadium"}]',
120+
JsonObject([{"size": "Great", "type": "Stadium"}]),
121+
)
122+
123+
def _test_select_json(self, description, expected):
124+
from test.mockserver_tests.json_model import Venue
125+
126+
sql = "SELECT venues.id, venues.name, venues.description \n" "FROM venues"
127+
add_venue_query_result(sql, description)
128+
engine = create_engine(
129+
"spanner:///projects/p/instances/i/databases/d",
130+
connect_args={"client": self.client, "pool": FixedSizePool(size=10)},
131+
)
132+
133+
with Session(engine.execution_options(read_only=True)) as session:
134+
venue = session.execute(select(Venue)).first()[0]
135+
eq_(venue.description, expected)
136+
137+
138+
def add_venue_query_result(sql: str, description: str):
139+
result = result_set.ResultSet(
140+
dict(
141+
metadata=result_set.ResultSetMetadata(
142+
dict(
143+
row_type=spanner_type.StructType(
144+
dict(
145+
fields=[
146+
spanner_type.StructType.Field(
147+
dict(
148+
name="id",
149+
type=spanner_type.Type(
150+
dict(code=spanner_type.TypeCode.INT64)
151+
),
152+
)
153+
),
154+
spanner_type.StructType.Field(
155+
dict(
156+
name="name",
157+
type=spanner_type.Type(
158+
dict(code=spanner_type.TypeCode.STRING)
159+
),
160+
)
161+
),
162+
spanner_type.StructType.Field(
163+
dict(
164+
name="description",
165+
type=spanner_type.Type(
166+
dict(code=spanner_type.TypeCode.JSON)
167+
),
168+
)
169+
),
170+
]
171+
)
172+
)
173+
)
174+
),
175+
)
176+
)
177+
result.rows.extend(
178+
[
179+
(
180+
"1",
181+
"Test",
182+
description,
183+
),
184+
]
185+
)
186+
add_result(sql, result)

0 commit comments

Comments
 (0)