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: Added support for check constraint #679

Merged
merged 6 commits into from Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions django_spanner/__init__.py
Expand Up @@ -5,6 +5,7 @@
# https://developers.google.com/open-source/licenses/bsd

import datetime
import os

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

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

USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None

check_django_compatability()
register_expressions()
register_functions()
Expand Down
2 changes: 1 addition & 1 deletion django_spanner/features.py
Expand Up @@ -184,7 +184,7 @@ class DatabaseFeatures(BaseDatabaseFeatures):
"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",
# No CHECK constraints in Spanner.
# 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:
Expand Down
10 changes: 8 additions & 2 deletions django_spanner/schema.py
Expand Up @@ -7,6 +7,7 @@
from django.db import NotSupportedError
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django_spanner._opentelemetry_tracing import trace_call
from django_spanner import USE_EMULATOR


class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
Expand Down Expand Up @@ -472,8 +473,13 @@ def _alter_column_type_sql(self, model, old_field, new_field, new_type):
)

def _check_sql(self, name, check):
# Spanner doesn't support CHECK constraints.
return None
# Emulator does not support check constraints yet.
if USE_EMULATOR:
return None
return self.sql_constraint % {
"name": self.quote_name(name),
"constraint": self.sql_check_constraint % {"check": check},
}

def _unique_sql(self, model, fields, name, condition=None):
# Inline constraints aren't supported, so create the index separately.
Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Expand Up @@ -43,7 +43,7 @@ def lint(session):
session.run("flake8", "django_spanner", "tests")


@nox.session(python="3.6")
@nox.session(python=DEFAULT_PYTHON_VERSION)
def blacken(session):
"""Run black.

Expand Down
13 changes: 13 additions & 0 deletions tests/system/django_spanner/models.py
Expand Up @@ -21,3 +21,16 @@ class Number(models.Model):

def __str__(self):
return str(self.num)


class Event(models.Model):
start_date = models.DateTimeField()
end_date = models.DateTimeField()

class Meta:
constraints = [
models.CheckConstraint(
check=models.Q(end_date__gt=models.F("start_date")),
name="check_start_date",
),
]
64 changes: 64 additions & 0 deletions tests/system/django_spanner/test_check_constraint.py
@@ -0,0 +1,64 @@
# 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 Event
from django.test import TransactionTestCase
import datetime
import unittest
from django.utils import timezone
from google.api_core.exceptions import OutOfRange
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, "Check Constraint is not implemented in emulator."
)
class TestCheckConstraint(TransactionTestCase):
@classmethod
def setUpClass(cls):
setup_instance()
setup_database()
with connection.schema_editor() as editor:
# Create the table
editor.create_model(Event)

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

def test_insert_valid_value(self):
"""
Tests model object creation with Event model.
"""
now = timezone.now()
now_plus_10 = now + datetime.timedelta(minutes=10)
event_valid = Event(start_date=now, end_date=now_plus_10)
event_valid.save()
qs1 = Event.objects.filter().values("start_date")
self.assertEqual(qs1[0]["start_date"], now)
# Delete data from Event table.
Event.objects.all().delete()

def test_insert_invalid_value(self):
"""
Tests model object creation with invalid data in Event model.
"""
now = timezone.now()
now_minus_1_day = now - timezone.timedelta(days=1)
event_invalid = Event(start_date=now, end_date=now_minus_1_day)
with self.assertRaises(OutOfRange):
event_invalid.save()
11 changes: 3 additions & 8 deletions tests/system/django_spanner/test_decimal.py
Expand Up @@ -6,14 +6,13 @@

from .models import Author, Number
from django.test import TransactionTestCase
from django.db import connection, ProgrammingError
from django.db import connection
from decimal import Decimal
from tests.system.django_spanner.utils import (
setup_instance,
teardown_instance,
setup_database,
teardown_database,
USE_EMULATOR,
)


Expand Down Expand Up @@ -87,12 +86,8 @@ def test_decimal_precision_limit(self):
Tests decimal object precission limit.
"""
num_val = Number(num=Decimal(1) / Decimal(3))
if USE_EMULATOR:
with self.assertRaises(ValueError):
num_val.save()
else:
with self.assertRaises(ProgrammingError):
num_val.save()
with self.assertRaises(ValueError):
num_val.save()

def test_decimal_update(self):
"""
Expand Down
3 changes: 2 additions & 1 deletion tests/system/django_spanner/utils.py
Expand Up @@ -15,11 +15,12 @@
from test_utils.retry import RetryErrors

from django_spanner.creation import DatabaseCreation
from django_spanner import USE_EMULATOR

CREATE_INSTANCE = (
os.getenv("GOOGLE_CLOUD_TESTS_CREATE_SPANNER_INSTANCE") is not None
)
USE_EMULATOR = os.getenv("SPANNER_EMULATOR_HOST") is not None

SPANNER_OPERATION_TIMEOUT_IN_SECONDS = int(
os.getenv("SPANNER_OPERATION_TIMEOUT_IN_SECONDS", 60)
)
Expand Down