Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(db_api): support JSON data type #627

Merged
merged 13 commits into from Nov 22, 2021
1 change: 1 addition & 0 deletions google/cloud/spanner_dbapi/cursor.py
Expand Up @@ -223,6 +223,7 @@ def execute(self, sql, args=None):
ResultsChecksum(),
classification == parse_utils.STMT_INSERT,
)

(self._result_set, self._checksum,) = self.connection.run_statement(
statement
)
Expand Down
8 changes: 3 additions & 5 deletions google/cloud/spanner_v1/_helpers.py
Expand Up @@ -17,7 +17,6 @@
import datetime
import decimal
import math
import json

import six

Expand Down Expand Up @@ -168,9 +167,8 @@ def _make_value_pb(value):
_assert_numeric_precision_and_scale(value)
return Value(string_value=str(value))
if isinstance(value, JsonObject):
return Value(
string_value=json.dumps(value, sort_keys=True, separators=(",", ":"),)
)
return Value(string_value=value.serialize())

raise ValueError("Unknown type: %s" % (value,))


Expand Down Expand Up @@ -245,7 +243,7 @@ def _parse_value_pb(value_pb, field_type):
elif type_code == TypeCode.NUMERIC:
return decimal.Decimal(value_pb.string_value)
elif type_code == TypeCode.JSON:
return value_pb.string_value
return JsonObject.from_str(value_pb.string_value)
else:
raise ValueError("Unknown type: %s" % (field_type,))

Expand Down
33 changes: 32 additions & 1 deletion google/cloud/spanner_v1/data_types.py
Expand Up @@ -14,6 +14,8 @@

"""Custom data types for spanner."""

import json


class JsonObject(dict):
"""
Expand All @@ -22,4 +24,33 @@ class JsonObject(dict):
normal parameters and JSON parameters.
"""

pass
def __init__(self, *args, **kwargs):
self._is_null = (args, kwargs) == ((), {}) or args == (None,)
if not self._is_null:
super(JsonObject, self).__init__(*args, **kwargs)

@classmethod
def from_str(cls, str_repr):
"""Initiate an object from its `str` representation.

Args:
str_repr (str): JSON text representation.

Returns:
JsonObject: JSON object.
"""
if str_repr == "null":
return cls()

return cls(json.loads(str_repr))

def serialize(self):
"""Return the object text representation.

Returns:
str: JSON object text representation.
"""
if self._is_null:
return None

return json.dumps(self, sort_keys=True, separators=(",", ":"))
16 changes: 12 additions & 4 deletions tests/unit/test__helpers.py
Expand Up @@ -569,14 +569,22 @@ def test_w_json(self):
from google.cloud.spanner_v1 import Type
from google.cloud.spanner_v1 import TypeCode

VALUE = json.dumps(
{"id": 27863, "Name": "Anamika"}, sort_keys=True, separators=(",", ":")
)
VALUE = {"id": 27863, "Name": "Anamika"}
str_repr = json.dumps(VALUE, sort_keys=True, separators=(",", ":"))

field_type = Type(code=TypeCode.JSON)
value_pb = Value(string_value=VALUE)
value_pb = Value(string_value=str_repr)

self.assertEqual(self._callFUT(value_pb, field_type), VALUE)

VALUE = None
str_repr = json.dumps(VALUE, sort_keys=True, separators=(",", ":"))

field_type = Type(code=TypeCode.JSON)
value_pb = Value(string_value=str_repr)

self.assertEqual(self._callFUT(value_pb, field_type), {})

def test_w_unknown_type(self):
from google.protobuf.struct_pb2 import Value
from google.cloud.spanner_v1 import Type
Expand Down