Skip to content

Commit 268924d

Browse files
authored
feat: adding support and samples for jsonb (#851)
* changes for testing in postgres * changes for jsonb * samples * linting * linting * Revert "linting" This reverts commit 8563815. * Revert "linting" This reverts commit 4910f59. * Revert "samples" This reverts commit ba80e5a. * samples * lint * changes as per comments * removing file * changes as per review * Update pg_snippets.py * Update pg_snippets.py * Update pg_snippets.py * Update pg_snippets.py * Update pg_snippets.py
1 parent 57cbf4d commit 268924d

File tree

6 files changed

+185
-2
lines changed

6 files changed

+185
-2
lines changed

‎google/cloud/spanner_v1/param_types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
NUMERIC = Type(code=TypeCode.NUMERIC)
3232
JSON = Type(code=TypeCode.JSON)
3333
PG_NUMERIC = Type(code=TypeCode.NUMERIC, type_annotation=TypeAnnotationCode.PG_NUMERIC)
34+
PG_JSONB = Type(code=TypeCode.JSON, type_annotation=TypeAnnotationCode.PG_JSONB)
3435

3536

3637
def Array(element_type):

‎samples/samples/pg_snippets.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from google.cloud import spanner, spanner_admin_database_v1
2929
from google.cloud.spanner_admin_database_v1.types.common import DatabaseDialect
3030
from google.cloud.spanner_v1 import param_types
31+
from google.cloud.spanner_v1.data_types import JsonObject
3132

3233
OPERATION_TIMEOUT_SECONDS = 240
3334

@@ -1342,6 +1343,133 @@ def query_data_with_query_options(instance_id, database_id):
13421343
# [END spanner_postgresql_query_with_query_options]
13431344

13441345

1346+
# [START spanner_postgresql_jsonb_add_column]
1347+
def add_jsonb_column(instance_id, database_id):
1348+
"""
1349+
Alters Venues tables in the database adding a JSONB column.
1350+
You can create the table by running the `create_table_with_datatypes`
1351+
sample or by running this DDL statement against your database:
1352+
CREATE TABLE Venues (
1353+
VenueId BIGINT NOT NULL,
1354+
VenueName character varying(100),
1355+
VenueInfo BYTEA,
1356+
Capacity BIGINT,
1357+
OutdoorVenue BOOL,
1358+
PopularityScore FLOAT8,
1359+
Revenue NUMERIC,
1360+
LastUpdateTime SPANNER.COMMIT_TIMESTAMP NOT NULL,
1361+
PRIMARY KEY (VenueId))
1362+
"""
1363+
# instance_id = "your-spanner-instance"
1364+
# database_id = "your-spanner-db-id"
1365+
1366+
spanner_client = spanner.Client()
1367+
instance = spanner_client.instance(instance_id)
1368+
database = instance.database(database_id)
1369+
1370+
operation = database.update_ddl(
1371+
["ALTER TABLE Venues ADD COLUMN VenueDetails JSONB"]
1372+
)
1373+
1374+
print("Waiting for operation to complete...")
1375+
operation.result(OPERATION_TIMEOUT_SECONDS)
1376+
1377+
print(
1378+
'Altered table "Venues" on database {} on instance {}.'.format(
1379+
database_id, instance_id
1380+
)
1381+
)
1382+
1383+
1384+
# [END spanner_postgresql_jsonb_add_column]
1385+
1386+
1387+
# [START spanner_postgresql_jsonb_update_data]
1388+
def update_data_with_jsonb(instance_id, database_id):
1389+
"""Updates Venues tables in the database with the JSONB
1390+
column.
1391+
This updates the `VenueDetails` column which must be created before
1392+
running this sample. You can add the column by running the
1393+
`add_jsonb_column` sample or by running this DDL statement
1394+
against your database:
1395+
ALTER TABLE Venues ADD COLUMN VenueDetails JSONB
1396+
"""
1397+
# instance_id = "your-spanner-instance"
1398+
# database_id = "your-spanner-db-id"
1399+
1400+
spanner_client = spanner.Client()
1401+
instance = spanner_client.instance(instance_id)
1402+
database = instance.database(database_id)
1403+
1404+
"""
1405+
PG JSONB takes the last value in the case of duplicate keys.
1406+
PG JSONB sorts first by key length and then lexicographically with
1407+
equivalent key length.
1408+
"""
1409+
1410+
with database.batch() as batch:
1411+
batch.update(
1412+
table="Venues",
1413+
columns=("VenueId", "VenueDetails"),
1414+
values=[
1415+
(
1416+
4,
1417+
JsonObject(
1418+
[
1419+
JsonObject({"name": None, "open": True}),
1420+
JsonObject(
1421+
{"name": "room 2", "open": False}
1422+
),
1423+
]
1424+
),
1425+
),
1426+
(19, JsonObject(rating=9, open=True)),
1427+
(
1428+
42,
1429+
JsonObject(
1430+
{
1431+
"name": None,
1432+
"open": {"Monday": True, "Tuesday": False},
1433+
"tags": ["large", "airy"],
1434+
}
1435+
),
1436+
),
1437+
],
1438+
)
1439+
1440+
print("Updated data.")
1441+
1442+
1443+
# [END spanner_postgresql_jsonb_update_data]
1444+
1445+
# [START spanner_postgresql_jsonb_query_parameter]
1446+
def query_data_with_jsonb_parameter(instance_id, database_id):
1447+
"""Queries sample data using SQL with a JSONB parameter."""
1448+
# instance_id = "your-spanner-instance"
1449+
# database_id = "your-spanner-db-id"
1450+
1451+
spanner_client = spanner.Client()
1452+
instance = spanner_client.instance(instance_id)
1453+
database = instance.database(database_id)
1454+
1455+
param = {"p1": 2}
1456+
param_type = {"p1": param_types.INT64}
1457+
1458+
with database.snapshot() as snapshot:
1459+
results = snapshot.execute_sql(
1460+
"SELECT venueid, venuedetails FROM Venues"
1461+
+ " WHERE CAST(venuedetails ->> 'rating' AS INTEGER) > $1",
1462+
params=param,
1463+
param_types=param_type,
1464+
)
1465+
1466+
for row in results:
1467+
print("VenueId: {}, VenueDetails: {}".format(*row))
1468+
1469+
1470+
# [END spanner_postgresql_jsonb_query_parameter]
1471+
1472+
13451473
if __name__ == "__main__": # noqa: C901
13461474
parser = argparse.ArgumentParser(
13471475
description=__doc__,

‎samples/samples/pg_snippets_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,25 @@ def test_create_client_with_query_options(capsys, instance_id, sample_database):
449449
assert "VenueId: 4, VenueName: Venue 4, LastUpdateTime:" in out
450450
assert "VenueId: 19, VenueName: Venue 19, LastUpdateTime:" in out
451451
assert "VenueId: 42, VenueName: Venue 42, LastUpdateTime:" in out
452+
453+
454+
@pytest.mark.dependency(name="add_jsonb_column", depends=["insert_datatypes_data"])
455+
def test_add_jsonb_column(capsys, instance_id, sample_database):
456+
snippets.add_jsonb_column(instance_id, sample_database.database_id)
457+
out, _ = capsys.readouterr()
458+
assert "Waiting for operation to complete..." in out
459+
assert 'Altered table "Venues" on database ' in out
460+
461+
462+
@pytest.mark.dependency(name="update_data_with_jsonb", depends=["add_jsonb_column"])
463+
def test_update_data_with_jsonb(capsys, instance_id, sample_database):
464+
snippets.update_data_with_jsonb(instance_id, sample_database.database_id)
465+
out, _ = capsys.readouterr()
466+
assert "Updated data." in out
467+
468+
469+
@pytest.mark.dependency(depends=["update_data_with_jsonb"])
470+
def test_query_data_with_jsonb_parameter(capsys, instance_id, sample_database):
471+
snippets.query_data_with_jsonb_parameter(instance_id, sample_database.database_id)
472+
out, _ = capsys.readouterr()
473+
assert "VenueId: 19, VenueDetails: {'open': True, 'rating': 9}" in out

‎tests/_fixtures.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
string_value VARCHAR(16),
137137
timestamp_value TIMESTAMPTZ,
138138
numeric_value NUMERIC,
139+
jsonb_value JSONB,
139140
PRIMARY KEY (pkey) );
140141
CREATE TABLE counters (
141142
name VARCHAR(1024),

‎tests/system/test_session_api.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
LIVE_ALL_TYPES_COLUMNS[:1]
9090
+ LIVE_ALL_TYPES_COLUMNS[1:7:2]
9191
+ LIVE_ALL_TYPES_COLUMNS[9:17:2]
92+
+ ("jsonb_value",)
9293
)
9394

9495
AllTypesRowData = collections.namedtuple("AllTypesRowData", LIVE_ALL_TYPES_COLUMNS)
@@ -120,7 +121,7 @@
120121
AllTypesRowData(pkey=108, timestamp_value=NANO_TIME),
121122
AllTypesRowData(pkey=109, numeric_value=NUMERIC_1),
122123
AllTypesRowData(pkey=110, json_value=JSON_1),
123-
AllTypesRowData(pkey=111, json_value=[JSON_1, JSON_2]),
124+
AllTypesRowData(pkey=111, json_value=JsonObject([JSON_1, JSON_2])),
124125
# empty array values
125126
AllTypesRowData(pkey=201, int_array=[]),
126127
AllTypesRowData(pkey=202, bool_array=[]),
@@ -184,12 +185,13 @@
184185
PostGresAllTypesRowData(pkey=107, timestamp_value=SOME_TIME),
185186
PostGresAllTypesRowData(pkey=108, timestamp_value=NANO_TIME),
186187
PostGresAllTypesRowData(pkey=109, numeric_value=NUMERIC_1),
188+
PostGresAllTypesRowData(pkey=110, jsonb_value=JSON_1),
187189
)
188190

189191
if _helpers.USE_EMULATOR:
190192
ALL_TYPES_COLUMNS = EMULATOR_ALL_TYPES_COLUMNS
191193
ALL_TYPES_ROWDATA = EMULATOR_ALL_TYPES_ROWDATA
192-
elif _helpers.DATABASE_DIALECT:
194+
elif _helpers.DATABASE_DIALECT == "POSTGRESQL":
193195
ALL_TYPES_COLUMNS = POSTGRES_ALL_TYPES_COLUMNS
194196
ALL_TYPES_ROWDATA = POSTGRES_ALL_TYPES_ROWDATA
195197
else:
@@ -2105,6 +2107,18 @@ def test_execute_sql_w_json_bindings(
21052107
)
21062108

21072109

2110+
def test_execute_sql_w_jsonb_bindings(
2111+
not_emulator, not_google_standard_sql, sessions_database, database_dialect
2112+
):
2113+
_bind_test_helper(
2114+
sessions_database,
2115+
database_dialect,
2116+
spanner_v1.param_types.PG_JSONB,
2117+
JSON_1,
2118+
[JSON_1, JSON_2],
2119+
)
2120+
2121+
21082122
def test_execute_sql_w_query_param_struct(sessions_database, not_postgres):
21092123
name = "Phred"
21102124
count = 123

‎tests/unit/test_param_types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,20 @@ def test_it(self):
5454
)
5555

5656
self.assertEqual(found, expected)
57+
58+
59+
class Test_JsonbParamType(unittest.TestCase):
60+
def test_it(self):
61+
from google.cloud.spanner_v1 import Type
62+
from google.cloud.spanner_v1 import TypeCode
63+
from google.cloud.spanner_v1 import TypeAnnotationCode
64+
from google.cloud.spanner_v1 import param_types
65+
66+
expected = Type(
67+
code=TypeCode.JSON,
68+
type_annotation=TypeAnnotationCode(TypeAnnotationCode.PG_JSONB),
69+
)
70+
71+
found = param_types.PG_JSONB
72+
73+
self.assertEqual(found, expected)

0 commit comments

Comments
 (0)