8000 feat: Added support for check constraint (#679) · googleapis/python-spanner-django@42352c0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 42352c0

Browse files
authored
feat: Added support for check constraint (#679)
* feat: Added support for check constraint * fix: change decimal out of scale ProgramingError to ValueError * fix: skip check_constraints tests when running on emmulator * fix: remove check constraint for emulator
1 parent 11bc9c2 commit 42352c0

File tree

8 files changed

+95
-13
lines changed

8 files changed

+95
-13
lines changed

django_spanner/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# https://developers.google.com/open-source/licenses/bsd
66

77
import datetime
8+
import os
89

910
# Monkey-patch AutoField to generate a random value since Cloud Spanner can't
1011
# do that.
@@ -24,6 +25,8 @@
2425

2526
__version__ = pkg_resources.get_distribution("django-google-spanner").version
2627

28+
USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None
29+
2730
check_django_compatability()
2831
register_expressions()
2932
register_functions()

django_spanner/features.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
184184
"db_functions.comparison.test_cast.CastTests.test_cast_to_decimal_field",
185185
"model_fields.test_decimalfield.DecimalFieldTests.test_fetch_from_db_without_float_rounding",
186186
"model_fields.test_decimalfield.DecimalFieldTests.test_roundtrip_with_trailing_zeros",
187-
# No CHECK constraints in Spanner.
187+
# Spanner does not support unsigned integer field.
188188
"model_fields.test_integerfield.PositiveIntegerFieldTests.test_negative_values",
189189
# Spanner doesn't support the variance the standard deviation database
190190
# functions:

django_spanner/schema.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django.db import NotSupportedError
88
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
99
from django_spanner._opentelemetry_tracing import trace_call
10+
from django_spanner import USE_EMULATOR
1011

1112

1213
class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
@@ -472,8 +473,13 @@ def _alter_column_type_sql(self, model, old_field, new_field, new_type):
472473
)
473474

474475
def _check_sql(self, name, check):
475-
# Spanner doesn't support CHECK constraints.
476-
return None
476+
# Emulator does not support check constraints yet.
477+
if USE_EMULATOR:
478+
return None
479+
return self.sql_constraint % {
480+
"name": self.quote_name(name),
481+
"constraint": self.sql_check_constraint % {"check": check},
482+
}
477483

478484
def _unique_sql(self, model, fields, name, condition=None):
479485
# Inline constraints aren't supported, so create the index separately.

noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def lint(session):
4343
session.run("flake8", "django_spanner", "tests")
4444

4545

46-
@nox.session(python="3.6")
46+
@nox.session(python=DEFAULT_PYTHON_VERSION)
4747
def blacken(session):
4848
"""Run black.
4949

tests/system/django_spanner/models.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,16 @@ class Number(models.Model):
2121

2222
def __str__(self):
2323
return str(self.num)
24+
25+
26+
class Event(models.Model):
27+
start_date = models.DateTimeField()
28+
end_date = models.DateTimeField()
29+
30+
class Meta:
31+
constraints = [
32+
models.CheckConstraint(
33+
check=models.Q(end_date__gt=models.F("start_date")),
34+
name="check_start_date",
35+
),
36+
]
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2021 Google LLC
2+
#
3+
# Use of this source code is governed by a BSD-style
4+
# license that can be found in the LICENSE file or at
5+
# https://developers.google.com/open-source/licenses/bsd
6+
7+
from .models import Event
8+
from django.test import TransactionTestCase
9+
import datetime
10+
import unittest
11+
from django.utils import timezone
12+
from google.api_core.exceptions import OutOfRange
13+
from django.db import connection
14+
from django_spanner import USE_EMULATOR
15+
from tests.system.django_spanner.utils import (
16+
setup_instance,
17+
teardown_instance,
18+
setup_database,
19+
teardown_database,
20+
)
21+
22+
23+
@unittest.skipIf(
24+
USE_EMULATOR, "Check Constraint is not implemented in emulator."
25+
)
26+
class TestCheckConstraint(TransactionTestCase):
27+
@classmethod
28+
def setUpClass(cls):
29+
setup_instance()
30+
setup_database()
31+
with connection.schema_editor() as editor:
32+
# Create the table
33+
editor.create_model(Event)
34+
35+
@classmethod
36+
def tearDownClass(cls):
37+
with connection.schema_editor() as editor:
38+
# delete the table
39+
editor.delete_model(Event)
40+
teardown_database()
41+
teardown_instance()
42+
43+
def test_insert_valid_value(self):
44+
"""
45+
Tests model object creation with Event model.
46+
"""
47+
now = timezone.now()
48+
now_plus_10 = now + datetime.timedelta(minutes=10)
49+
event_valid = Event(start_date=now, end_date=now_plus_10)
50+
event_valid.save()
51+
qs1 = Event.objects.filter().values("start_date")
52+
self.assertEqual(qs1[0]["start_date"], now)
53+
# Delete data from Event table.
54+
Event.objects.all().delete()
55+
56+
def test_insert_invalid_value(self):
57+
"""
58+
Tests model object creation with invalid data in Event model.
59+
"""
60+
now = timezone.now()
61+
now_minus_1_day = now - timezone.timedelta(days=1)
62+
event_invalid = Event(start_date=now, end_date=now_minus_1_day)
63+
with self.assertRaises(OutOfRange):
64+
event_invalid.save()

tests/system/django_spanner/test_decimal.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66

77
from .models import Author, Number
88
from django.test import TransactionTestCase
9-
from django.db import connection, ProgrammingError
9+
from django.db import connection
1010
from decimal import Decimal
1111
from tests.system.django_spanner.utils import (
1212
setup_instance,
1313
teardown_instance,
1414
setup_database,
1515
teardown_database,
16-
USE_EMULATOR,
1716
)
1817

1918

@@ -87,12 +86,8 @@ def test_decimal_precision_limit(self):
8786
Tests decimal object precission limit.
8887
"""
8988
num_val = Number(num=Decimal(1) / Decimal(3))
90-
if USE_EMULATOR:
91-
with self.assertRaises(ValueError):
92-
num_val.save()
93-
else:
94-
with self.assertRaises(ProgrammingError):
95-
num_val.save()
89+
with self.assertRaises(ValueError):
90+
num_val.save()
9691

9792
def test_decimal_update(self):
9893
"""

tests/system/django_spanner/utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
from test_utils.retry import RetryErrors
1616

1717
from django_spanner.creation import DatabaseCreation
18+
from django_spanner import USE_EMULATOR
1819

1920
CREATE_INSTANCE = (
2021
os.getenv("GOOGLE_CLOUD_TESTS_CREATE_SPANNER_INSTANCE") is not None
2122
)
22-
USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None
23+
2324
SPANNER_OPERATION_TIMEOUT_IN_SECONDS = int(
2425
os.getenv("SPANNER_OPERATION_TIMEOUT_IN_SECONDS", 60)
2526
)

0 commit comments

Comments
 (0)
0