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: add support for json type #714

Merged
merged 46 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
04a7745
performance files
asthamohta Jun 29, 2021
7a99ca1
test_benchmark
asthamohta Jul 5, 2021
79d94f7
performance testing changes
asthamohta Jul 7, 2021
aad550c
changes in benchmark performance for prod
asthamohta Jul 7, 2021
cd8d080
Merge branch 'googleapis:master' into performance
asthamohta Jul 7, 2021
ba0faee
changes to number of runs
asthamohta Jul 7, 2021
974f690
Merge branch 'performance' of github.com:asthamohta/python-spanner-dj…
asthamohta Jul 7, 2021
155aa13
adding comments
asthamohta Jul 7, 2021
25f9a75
linting changes
asthamohta Jul 7, 2021
8cdd431
Merge branch 'googleapis:master' into stable/3.2.x
asthamohta Jul 21, 2021
bd2ae62
3.2 changes
asthamohta Jul 21, 2021
2ebb7a9
Merge branch 'stable/3.2.x' of github.com:asthamohta/python-spanner-d…
asthamohta Jul 21, 2021
9a81474
adding version change
asthamohta Jul 21, 2021
069fcc4
lint changes and resmoving performance changes
asthamohta Jul 21, 2021
853526e
version changes
asthamohta Jul 21, 2021
11bc9c2
chore: fix release build (#659)
busunkim96 Jul 22, 2021
42352c0
feat: Added support for check constraint (#679)
vi3k6i5 Jul 23, 2021
74f2269
docs: update docs to show decimal field support and check constraints…
vi3k6i5 Jul 26, 2021
f3e8fc2
test: Performance Testing (#675)
asthamohta Jul 27, 2021
96a809d
chore: release 2.2.1b2 (#685)
vi3k6i5 Jul 27, 2021
f5bf523
chore: release 2.2.1b2 (#687)
release-please[bot] Jul 28, 2021
5f9750e
fix: Bump version number after 2.2.1b2 release (#688)
vi3k6i5 Jul 29, 2021
2144d09
chor: Update repo to say beta release instead of alpha (#691)
vi3k6i5 Jul 30, 2021
e1caf28
chore: release 2.2.1b3 (#693)
vi3k6i5 Jul 30, 2021
c86dd26
chore: release 2.2.1b3 (#694)
release-please[bot] Jul 30, 2021
a8f2aac
fix: Bump version number after 2.2.1b3 release (#696)
vi3k6i5 Aug 3, 2021
ed404f5
docs: lint fix for samples (#697)
vi3k6i5 Aug 6, 2021
08b80ce
Docs: fix changelog link and sample examples. (#700)
vi3k6i5 Aug 19, 2021
4643876
docs: update dbapi location in overview asset file (#702)
vi3k6i5 Aug 21, 2021
9d9d360
chore: migrate to main branch (#706)
dandhlee Sep 2, 2021
578f5bc
fix: added fixes for latest feature changes in django 3.2
vi3k6i5 Sep 13, 2021
c9c1019
fix: fixes for running tests for django3.2
vi3k6i5 Sep 16, 2021
393fe0d
Merge branch 'main' of github.com:googleapis/python-spanner-django in…
vi3k6i5 Sep 16, 2021
012fc99
Merge branch 'stable/3.2.x' of github.com:googleapis/python-spanner-d…
vi3k6i5 Sep 16, 2021
a2ae17a
fix: change django repo path
vi3k6i5 Sep 16, 2021
97c7ea0
test: test fixes for order by nulls first and last
vi3k6i5 Sep 17, 2021
d5b567c
docs: fix readme link target
vi3k6i5 Sep 17, 2021
22c4131
test: set default auto field type in test settings
vi3k6i5 Sep 20, 2021
69c3c08
fix: update features to skip tests that are not support by spanner
vi3k6i5 Sep 20, 2021
9d53a06
fix: remove choices module from django3.2 as it has been removed from…
vi3k6i5 Sep 20, 2021
8d677b4
feat: add json support
vi3k6i5 Sep 30, 2021
f421fbf
Merge branch 'stable/3.2.x' of github.com:googleapis/python-spanner-d…
vi3k6i5 Sep 30, 2021
8500b39
fix: correct JsonObject import path
vi3k6i5 Oct 5, 2021
81fa32f
fix: table_type is not supported in emulator
vi3k6i5 Oct 5, 2021
d050879
!fix: update dependency for json support
vi3k6i5 Oct 5, 2021
c6beb13
fix: views are not supported by spanner
vi3k6i5 Oct 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions django_spanner/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@
from uuid import uuid4

import pkg_resources
from google.cloud.spanner_v1 import JsonObject
from django.db.models.fields import (
AutoField,
SmallAutoField,
BigAutoField,
Field,
)
from django.db.models import JSONField

# Monkey-patch google.DatetimeWithNanoseconds's __eq__ compare against
# datetime.datetime.
Expand Down Expand Up @@ -59,6 +61,17 @@ def autofield_init(self, *args, **kwargs):
SmallAutoField.validators = []
BigAutoField.validators = []


def get_prep_value(self, value):
# Json encoding and decoding for spanner is done in python-spanner.
if not isinstance(value, JsonObject) and isinstance(value, dict):
return JsonObject(value)

return value


JSONField.get_prep_value = get_prep_value

old_datetimewithnanoseconds_eq = getattr(
DatetimeWithNanoseconds, "__eq__", None
)
Expand Down
1 change: 1 addition & 0 deletions django_spanner/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
"DateField": "DATE",
"DateTimeField": "TIMESTAMP",
"DecimalField": "NUMERIC",
"JSONField": "JSON",
"DurationField": "INT64",
"EmailField": "STRING(%(max_length)s)",
"FileField": "STRING(%(max_length)s)",
Expand Down
31 changes: 13 additions & 18 deletions django_spanner/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from django.db.backends.base.features import BaseDatabaseFeatures
from django.db.utils import InterfaceError
from django_spanner import USE_EMULATOR


class DatabaseFeatures(BaseDatabaseFeatures):
Expand All @@ -34,8 +35,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
supports_column_check_constraints = True
supports_table_check_constraints = True
supports_order_by_nulls_modifier = False
# Spanner does not support json
supports_json_field = False
if USE_EMULATOR:
# Emulator does not support json.
supports_json_field = False
else:
supports_json_field = True
supports_primitives_in_json_field = False
# Spanner does not support SELECTing an arbitrary expression that also
# appears in the GROUP BY clause.
Expand Down Expand Up @@ -67,7 +71,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"model_fields.test_autofield.SmallAutoFieldTests.test_redundant_backend_range_validators",
# Spanner does not support deferred unique constraints
"migrations.test_operations.OperationTests.test_create_model_with_deferred_unique_constraint",
# Spanner does not support JSON objects
# Spanner does not support JSON object query on fields.
"db_functions.comparison.test_json_object.JSONObjectTests.test_empty",
"db_functions.comparison.test_json_object.JSONObjectTests.test_basic",
"db_functions.comparison.test_json_object.JSONObjectTests.test_expressions",
Expand Down Expand Up @@ -268,17 +272,11 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"timezones.tests.NewDatabaseTests.test_query_datetimes",
# using NULL with + crashes: https://github.com/googleapis/python-spanner-django/issues/201
"annotations.tests.NonAggregateAnnotationTestCase.test_combined_annotation_commutative",
# Spanner loses DecimalField precision due to conversion to float:
# https://github.com/googleapis/python-spanner-django/pull/133#pullrequestreview-328482925
"aggregation.tests.AggregateTestCase.test_decimal_max_digits_has_no_effect",
"aggregation.tests.AggregateTestCase.test_related_aggregate",
# Spanner does not support custom precision on DecimalField
"db_functions.comparison.test_cast.CastTests.test_cast_to_decimal_field",
"model_fields.test_decimalfield.DecimalFieldTests.test_fetch_from_db_without_float_rounding",
"model_fields.test_decimalfield.DecimalFieldTests.test_roundtrip_with_trailing_zeros",
# Spanner does not support unsigned integer field.
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
# Spanner doesn't support the variance the standard deviation database
# functions:
# Spanner doesn't support the variance the standard deviation database functions on full population.
"aggregation.test_filter_argument.FilteredAggregateTests.test_filtered_numerical_aggregates",
"aggregation_regress.tests.AggregationTests.test_stddev",
# SELECT list expression references <column> which is neither grouped
Expand Down Expand Up @@ -358,12 +356,8 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"transaction_hooks.tests.TestConnectionOnCommit.test_discards_hooks_from_rolled_back_savepoint",
"transaction_hooks.tests.TestConnectionOnCommit.test_inner_savepoint_rolled_back_with_outer",
"transaction_hooks.tests.TestConnectionOnCommit.test_inner_savepoint_does_not_affect_outer",
# Spanner doesn't support views.
"inspectdb.tests.InspectDBTransactionalTests.test_include_views",
"introspection.tests.IntrospectionTests.test_table_names_with_views",
# Fields: JSON, GenericIPAddressField are mapped to String in Spanner
# Field: GenericIPAddressField is mapped to String in Spanner
"inspectdb.tests.InspectDBTestCase.test_field_types",
"inspectdb.tests.InspectDBTestCase.test_json_field",
# BigIntegerField is mapped to IntegerField in Spanner
"inspectdb.tests.InspectDBTestCase.test_number_field_types",
# No sequence for AutoField in Spanner.
Expand Down Expand Up @@ -479,6 +473,9 @@ class DatabaseFeatures(BaseDatabaseFeatures):
if os.environ.get("SPANNER_EMULATOR_HOST", None):
# Some code isn't yet supported by the Spanner emulator.
skip_tests += (
# Views are not supported by emulator
"inspectdb.tests.InspectDBTransactionalTests.test_include_views", # noqa
"introspection.tests.IntrospectionTests.test_table_names_with_views", # noqa
# Untyped parameters are not supported:
# https://github.com/GoogleCloudPlatform/cloud-spanner-emulator#features-and-limitations
"auth_tests.test_views.PasswordResetTest.test_confirm_custom_reset_url_token_link_redirects_to_set_password_page", # noqa
Expand Down Expand Up @@ -1588,7 +1585,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"queries.tests.Queries1Tests.test_ticket2306", # noqa
"queries.tests.Queries1Tests.test_ticket2400", # noqa
"queries.tests.Queries1Tests.test_ticket2496", # noqa
# "queries.tests.Queries1Tests.test_ticket2902", # noqa
"queries.tests.Queries1Tests.test_ticket3037", # noqa
"queries.tests.Queries1Tests.test_ticket3141", # noqa
"queries.tests.Queries1Tests.test_ticket4358", # noqa
Expand Down Expand Up @@ -1812,7 +1808,6 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"sitemaps_tests.test_http.HTTPSitemapTests.test_paged_sitemap", # noqa
"sitemaps_tests.test_http.HTTPSitemapTests.test_requestsite_sitemap", # noqa
"sitemaps_tests.test_http.HTTPSitemapTests.test_simple_custom_sitemap", # noqa
# "sitemaps_tests.test_http.HTTPSitemapTests.test_simple_i18nsitemap_index", # noqa
"sitemaps_tests.test_http.HTTPSitemapTests.test_alternate_i18n_sitemap_index", # noqa
"sitemaps_tests.test_http.HTTPSitemapTests.test_alternate_i18n_sitemap_limited", # noqa
"sitemaps_tests.test_http.HTTPSitemapTests.test_alternate_i18n_sitemap_xdefault", # noqa
Expand Down
31 changes: 30 additions & 1 deletion django_spanner/introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from django.db.models import Index
from google.cloud.spanner_v1 import TypeCode
from django_spanner import USE_EMULATOR


class DatabaseIntrospection(BaseDatabaseIntrospection):
Expand All @@ -25,7 +26,28 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
TypeCode.STRING: "CharField",
TypeCode.TIMESTAMP: "DateTimeField",
TypeCode.NUMERIC: "DecimalField",
TypeCode.JSON: "JSONField",
}
if USE_EMULATOR:
# Emulator does not support table_type yet.
# https://github.com/GoogleCloudPlatform/cloud-spanner-emulator/issues/43
LIST_TABLE_SQL = """
SELECT
t.table_name, t.table_name
FROM
information_schema.tables AS t
WHERE
t.table_catalog = '' and t.table_schema = ''
"""
else:
LIST_TABLE_SQL = """
SELECT
t.table_name, t.table_type
FROM
information_schema.tables AS t
WHERE
t.table_catalog = '' and t.table_schema = ''
"""

def get_field_type(self, data_type, description):
"""A hook for a Spanner database to use the cursor description to
Expand Down Expand Up @@ -53,8 +75,15 @@ def get_table_list(self, cursor):
:rtype: list
:returns: A list of table and view names in the current database.
"""
results = cursor.run_sql_in_snapshot(self.LIST_TABLE_SQL)
tables = []
# The second TableInfo field is 't' for table or 'v' for view.
return [TableInfo(row[0], "t") for row in cursor.list_tables()]
for row in results:
table_type = "t"
if row[1] == "VIEW":
table_type = "v"
tables.append(TableInfo(row[0], table_type))
return tables

def get_table_description(self, cursor, table_name):
"""Return a description of the table with the DB-API cursor.description
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# 'Development Status :: 4 - Beta'
# 'Development Status :: 5 - Production/Stable'
release_status = "Development Status :: 4 - Beta"
dependencies = ["sqlparse >= 0.3.0", "google-cloud-spanner >= 3.0.0"]
dependencies = ["sqlparse >= 0.3.0", "google-cloud-spanner >= 3.11.1"]
extras = {
"tracing": [
"opentelemetry-api >= 1.1.0",
Expand Down
2 changes: 1 addition & 1 deletion testing/constraints-3.6.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# e.g., if setup.py has "foo >= 1.14.0, < 2.0.0dev",
# Then this file should have foo==1.14.0
sqlparse==0.3.0
google-cloud-spanner==3.0.0
google-cloud-spanner==3.11.1
opentelemetry-api==1.1.0
opentelemetry-sdk==1.1.0
opentelemetry-instrumentation==0.20b0
4 changes: 4 additions & 0 deletions tests/system/django_spanner/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ class Meta:
name="check_start_date",
),
]


class Detail(models.Model):
value = models.JSONField()
48 changes: 48 additions & 0 deletions tests/system/django_spanner/test_json_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright 2021 Google LLC
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd

from .models import Detail
import unittest
from django.test import TransactionTestCase
from django.db import connection
from django_spanner import USE_EMULATOR
from tests.system.django_spanner.utils import (
setup_instance,
teardown_instance,
setup_database,
teardown_database,
)


@unittest.skipIf(USE_EMULATOR, "Jsonfield is not implemented in emulator.")
class TestJsonField(TransactionTestCase):
@classmethod
def setUpClass(cls):
setup_instance()
setup_database()
with connection.schema_editor() as editor:
# Create the tables
editor.create_model(Detail)

@classmethod
def tearDownClass(cls):
with connection.schema_editor() as editor:
# delete the table
editor.delete_model(Detail)
teardown_database()
teardown_instance()

def test_insert_and_fetch_value(self):
"""
Tests model object creation with Detail model.
Inserting json data into the model and retrieving it.
"""
json_data = Detail(value={"name": "Jakob", "age": "26"})
json_data.save()
qs1 = Detail.objects.all()
self.assertEqual(qs1[0].value, {"name": "Jakob", "age": "26"})
# Delete data from Detail table.
Detail.objects.all().delete()
4 changes: 2 additions & 2 deletions tests/unit/django_spanner/test_introspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def test_get_table_list(self):
cursor = mock.MagicMock()

def list_tables(*args, **kwargs):
return [["Table_1"], ["Table_2"]]
return [["Table_1", "t"], ["Table_2", "t"]]

cursor.list_tables = list_tables
cursor.run_sql_in_snapshot = list_tables
table_list = db_introspection.get_table_list(cursor=cursor)
self.assertEqual(
table_list,
Expand Down