374 lines
14 KiB
Python
374 lines
14 KiB
Python
# Copyright 2013-2017 DataStax, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from uuid import uuid4
|
|
|
|
from mock import patch
|
|
from cassandra.cqlengine import ValidationError
|
|
|
|
from tests.integration import greaterthancass21
|
|
from tests.integration.cqlengine.base import BaseCassEngTestCase
|
|
from cassandra.cqlengine.models import Model
|
|
from cassandra.cqlengine import columns
|
|
from cassandra.cqlengine.management import sync_table, drop_table
|
|
from cassandra.cqlengine.usertype import UserType
|
|
|
|
class TestUpdateModel(Model):
|
|
|
|
partition = columns.UUID(primary_key=True, default=uuid4)
|
|
cluster = columns.UUID(primary_key=True, default=uuid4)
|
|
count = columns.Integer(required=False)
|
|
text = columns.Text(required=False, index=True)
|
|
|
|
|
|
class ModelUpdateTests(BaseCassEngTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(ModelUpdateTests, cls).setUpClass()
|
|
sync_table(TestUpdateModel)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(ModelUpdateTests, cls).tearDownClass()
|
|
drop_table(TestUpdateModel)
|
|
|
|
def test_update_model(self):
|
|
""" tests calling udpate on models with no values passed in """
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
|
|
# independently save over a new count value, unknown to original instance
|
|
m1 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster)
|
|
m1.count = 6
|
|
m1.save()
|
|
|
|
# update the text, and call update
|
|
m0.text = 'monkey land'
|
|
m0.update()
|
|
|
|
# database should reflect both updates
|
|
m2 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster)
|
|
self.assertEqual(m2.count, m1.count)
|
|
self.assertEqual(m2.text, m0.text)
|
|
|
|
#This shouldn't raise a Validation error as the PR is not changing
|
|
m0.update(partition=m0.partition, cluster=m0.cluster)
|
|
|
|
#Assert a ValidationError is risen if the PR changes
|
|
with self.assertRaises(ValidationError):
|
|
m0.update(partition=m0.partition, cluster=20)
|
|
|
|
# Assert a ValidationError is risen if the columns doesn't exist
|
|
with self.assertRaises(ValidationError):
|
|
m0.update(invalid_column=20)
|
|
|
|
def test_update_values(self):
|
|
""" tests calling update on models with values passed in """
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
|
|
# independently save over a new count value, unknown to original instance
|
|
m1 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster)
|
|
m1.count = 6
|
|
m1.save()
|
|
|
|
# update the text, and call update
|
|
m0.update(text='monkey land')
|
|
self.assertEqual(m0.text, 'monkey land')
|
|
|
|
# database should reflect both updates
|
|
m2 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster)
|
|
self.assertEqual(m2.count, m1.count)
|
|
self.assertEqual(m2.text, m0.text)
|
|
|
|
def test_noop_model_direct_update(self):
|
|
""" Tests that calling update on a model with no changes will do nothing. """
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m0.update()
|
|
assert execute.call_count == 0
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m0.update(count=5)
|
|
assert execute.call_count == 0
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m0.update(partition=m0.partition)
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m0.update(cluster=m0.cluster)
|
|
|
|
def test_noop_model_assignation_update(self):
|
|
""" Tests that assigning the same value on a model will do nothing. """
|
|
# Create object and fetch it back to eliminate any hidden variable
|
|
# cache effect.
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
m1 = TestUpdateModel.get(partition=m0.partition, cluster=m0.cluster)
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m1.save()
|
|
assert execute.call_count == 0
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m1.count = 5
|
|
m1.save()
|
|
assert execute.call_count == 0
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m1.partition = m0.partition
|
|
m1.save()
|
|
assert execute.call_count == 0
|
|
|
|
with patch.object(self.session, 'execute') as execute:
|
|
m1.cluster = m0.cluster
|
|
m1.save()
|
|
assert execute.call_count == 0
|
|
|
|
def test_invalid_update_kwarg(self):
|
|
""" tests that passing in a kwarg to the update method that isn't a column will fail """
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
with self.assertRaises(ValidationError):
|
|
m0.update(numbers=20)
|
|
|
|
def test_primary_key_update_failure(self):
|
|
""" tests that attempting to update the value of a primary key will fail """
|
|
m0 = TestUpdateModel.create(count=5, text='monkey')
|
|
with self.assertRaises(ValidationError):
|
|
m0.update(partition=uuid4())
|
|
|
|
|
|
class UDT(UserType):
|
|
age = columns.Integer()
|
|
mf = columns.Map(columns.Integer, columns.Integer)
|
|
dummy_udt = columns.Integer(default=42)
|
|
time_col = columns.Time()
|
|
|
|
|
|
class ModelWithDefault(Model):
|
|
id = columns.Integer(primary_key=True)
|
|
mf = columns.Map(columns.Integer, columns.Integer)
|
|
dummy = columns.Integer(default=42)
|
|
udt = columns.UserDefinedType(UDT)
|
|
udt_default = columns.UserDefinedType(UDT, default=UDT(age=1, mf={2:2}))
|
|
|
|
|
|
class UDTWithDefault(UserType):
|
|
age = columns.Integer()
|
|
mf = columns.Map(columns.Integer, columns.Integer, default={2:2})
|
|
dummy_udt = columns.Integer(default=42)
|
|
|
|
|
|
class ModelWithDefaultCollection(Model):
|
|
id = columns.Integer(primary_key=True)
|
|
mf = columns.Map(columns.Integer, columns.Integer, default={2:2})
|
|
dummy = columns.Integer(default=42)
|
|
udt = columns.UserDefinedType(UDT)
|
|
udt_default = columns.UserDefinedType(UDT, default=UDT(age=1, mf={2: 2}))
|
|
|
|
@greaterthancass21
|
|
class ModelWithDefaultTests(BaseCassEngTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.udt_default = UDT(age=1, mf={2:2}, dummy_udt=42)
|
|
|
|
def setUp(self):
|
|
sync_table(ModelWithDefault)
|
|
sync_table(ModelWithDefaultCollection)
|
|
|
|
def tearDown(self):
|
|
drop_table(ModelWithDefault)
|
|
drop_table(ModelWithDefaultCollection)
|
|
|
|
def test_value_override_with_default(self):
|
|
"""
|
|
Updating a row with a new Model instance shouldn't set columns to defaults
|
|
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result column value should not change
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
first_udt = UDT(age=1, mf={2:2}, dummy_udt=0)
|
|
initial = ModelWithDefault(id=1, mf={0: 0}, dummy=0, udt=first_udt, udt_default=first_udt)
|
|
initial.save()
|
|
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': 0, 'mf': {0: 0}, "udt": first_udt, "udt_default": first_udt})
|
|
|
|
second_udt = UDT(age=1, mf={3: 3}, dummy_udt=12)
|
|
second = ModelWithDefault(id=1)
|
|
second.update(mf={0: 1}, udt=second_udt)
|
|
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': 0, 'mf': {0: 1}, "udt": second_udt, "udt_default": first_udt})
|
|
|
|
def test_value_is_written_if_is_default(self):
|
|
"""
|
|
Check if the we try to update with the default value, the update
|
|
happens correctly
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result column value should be updated
|
|
:return:
|
|
"""
|
|
initial = ModelWithDefault(id=1)
|
|
initial.mf = {0: 0}
|
|
initial.dummy = 42
|
|
initial.udt_default = self.udt_default
|
|
initial.update()
|
|
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': 42, 'mf': {0: 0}, "udt": None, "udt_default": self.udt_default})
|
|
|
|
def test_null_update_is_respected(self):
|
|
"""
|
|
Check if the we try to update with None under particular
|
|
circumstances, it works correctly
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result column value should be updated to None
|
|
|
|
@test_category object_mapper
|
|
:return:
|
|
"""
|
|
ModelWithDefault.create(id=1, mf={0: 0}).save()
|
|
|
|
q = ModelWithDefault.objects.all().allow_filtering()
|
|
obj = q.filter(id=1).get()
|
|
|
|
updated_udt = UDT(age=1, mf={2:2}, dummy_udt=None)
|
|
obj.update(dummy=None, udt_default=updated_udt)
|
|
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': None, 'mf': {0: 0}, "udt": None, "udt_default": updated_udt})
|
|
|
|
def test_only_set_values_is_updated(self):
|
|
"""
|
|
Test the updates work as expected when an object is deleted
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result the non updated column is None and the
|
|
updated column has the set value
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
|
|
ModelWithDefault.create(id=1, mf={1: 1}, dummy=1).save()
|
|
|
|
item = ModelWithDefault.filter(id=1).first()
|
|
ModelWithDefault.objects(id=1).delete()
|
|
item.mf = {1: 2}
|
|
udt, udt_default = UDT(age=1, mf={2:3}), UDT(age=1, mf={2:3})
|
|
item.udt, item.udt_default = udt, udt_default
|
|
item.save()
|
|
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': None, 'mf': {1: 2}, "udt": udt, "udt_default": udt_default})
|
|
|
|
def test_collections(self):
|
|
"""
|
|
Test the updates work as expected on Map objects
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result the row is updated when the Map object is
|
|
reduced
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
udt, udt_default = UDT(age=1, mf={1: 1, 2: 1}), UDT(age=1, mf={1: 1, 2: 1})
|
|
|
|
ModelWithDefault.create(id=1, mf={1: 1, 2: 1}, dummy=1, udt=udt, udt_default=udt_default).save()
|
|
item = ModelWithDefault.filter(id=1).first()
|
|
|
|
udt, udt_default = UDT(age=1, mf={2: 1}), UDT(age=1, mf={2: 1})
|
|
item.update(mf={2:1}, udt=udt, udt_default=udt_default)
|
|
self.assertEqual(ModelWithDefault.get()._as_dict(),
|
|
{'id': 1, 'dummy': 1, 'mf': {2: 1}, "udt": udt, "udt_default": udt_default})
|
|
|
|
def test_collection_with_default(self):
|
|
"""
|
|
Test the updates work as expected when an object is deleted
|
|
@since 3.9
|
|
@jira_ticket PYTHON-657
|
|
@expected_result the non updated column is None and the
|
|
updated column has the set value
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
sync_table(ModelWithDefaultCollection)
|
|
|
|
udt, udt_default = UDT(age=1, mf={6: 6}), UDT(age=1, mf={6: 6})
|
|
|
|
item = ModelWithDefaultCollection.create(id=1, mf={1: 1}, dummy=1, udt=udt, udt_default=udt_default).save()
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(),
|
|
{'id': 1, 'dummy': 1, 'mf': {1: 1}, "udt": udt, "udt_default": udt_default})
|
|
|
|
udt, udt_default = UDT(age=1, mf={5: 5}), UDT(age=1, mf={5: 5})
|
|
item.update(mf={2: 2}, udt=udt, udt_default=udt_default)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(),
|
|
{'id': 1, 'dummy': 1, 'mf': {2: 2}, "udt": udt, "udt_default": udt_default})
|
|
|
|
udt, udt_default = UDT(age=1, mf=None), UDT(age=1, mf=None)
|
|
expected_udt, expected_udt_default = UDT(age=1, mf={}), UDT(age=1, mf={})
|
|
item.update(mf=None, udt=udt, udt_default=udt_default)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=1)._as_dict(),
|
|
{'id': 1, 'dummy': 1, 'mf': {}, "udt": expected_udt, "udt_default": expected_udt_default})
|
|
|
|
udt_default = UDT(age=1, mf={2:2}, dummy_udt=42)
|
|
item = ModelWithDefaultCollection.create(id=2, dummy=2)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(),
|
|
{'id': 2, 'dummy': 2, 'mf': {2: 2}, "udt": None, "udt_default": udt_default})
|
|
|
|
udt, udt_default = UDT(age=1, mf={1: 1, 6: 6}), UDT(age=1, mf={1: 1, 6: 6})
|
|
item.update(mf={1: 1, 4: 4}, udt=udt, udt_default=udt_default)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(),
|
|
{'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default})
|
|
|
|
item.update(udt_default=None)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(),
|
|
{'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": None})
|
|
|
|
udt_default = UDT(age=1, mf={2:2})
|
|
item.update(udt_default=udt_default)
|
|
self.assertEqual(ModelWithDefaultCollection.objects.get(id=2)._as_dict(),
|
|
{'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}, "udt": udt, "udt_default": udt_default})
|
|
|
|
|
|
def test_udt_to_python(self):
|
|
"""
|
|
Test the to_python and to_database are correctly called on UDTs
|
|
@since 3.10
|
|
@jira_ticket PYTHON-743
|
|
@expected_result the int value is correctly converted to utils.Time
|
|
and written to C*
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
item = ModelWithDefault(id=1)
|
|
item.save()
|
|
|
|
# We update time_col this way because we want to hit
|
|
# the to_python method from UserDefinedType, otherwise to_python
|
|
# would be called in UDT.__init__
|
|
user_to_update = UDT()
|
|
user_to_update.time_col = 10
|
|
|
|
item.update(udt=user_to_update)
|
|
|
|
udt, udt_default = UDT(time_col=10), UDT(age=1, mf={2:2})
|
|
self.assertEqual(ModelWithDefault.objects.get(id=1)._as_dict(),
|
|
{'id': 1, 'dummy': 42, 'mf': {}, "udt": udt, "udt_default": udt_default})
|