Merged python-657 branch
This commit is contained in:
@@ -47,8 +47,20 @@ class BaseValueManager(object):
|
||||
:rtype: boolean
|
||||
|
||||
"""
|
||||
if self.explicit:
|
||||
return self.value != self.previous_value
|
||||
|
||||
if isinstance(self.column, BaseContainerColumn):
|
||||
default_value = self.column.get_default()
|
||||
if self.column._val_is_null(default_value):
|
||||
return not self.column._val_is_null(self.value) and self.value != self.previous_value
|
||||
elif self.previous_value is None:
|
||||
return self.value != default_value
|
||||
|
||||
return self.value != self.previous_value
|
||||
|
||||
return False
|
||||
|
||||
def reset_previous_value(self):
|
||||
self.previous_value = deepcopy(self.value)
|
||||
|
||||
@@ -57,6 +69,7 @@ class BaseValueManager(object):
|
||||
|
||||
def setval(self, val):
|
||||
self.value = val
|
||||
self.explicit = True
|
||||
|
||||
def delval(self):
|
||||
self.value = None
|
||||
|
||||
@@ -407,8 +407,6 @@ class BaseModel(object):
|
||||
value = column.to_python(value)
|
||||
value_mngr = column.value_manager(self, column, value)
|
||||
value_mngr.explicit = name in values
|
||||
if not value_mngr.explicit and column.has_default:
|
||||
value_mngr.previous_value = value
|
||||
self._values[name] = value_mngr
|
||||
|
||||
def __repr__(self):
|
||||
@@ -486,11 +484,12 @@ class BaseModel(object):
|
||||
klass = cls
|
||||
|
||||
instance = klass(**values)
|
||||
instance._set_persisted()
|
||||
instance._set_persisted(force=True)
|
||||
return instance
|
||||
|
||||
def _set_persisted(self):
|
||||
for v in self._values.values():
|
||||
def _set_persisted(self, force=False):
|
||||
# ensure we don't modify to any values not affected by the last save/update
|
||||
for v in [v for v in self._values.values() if v.changed or force]:
|
||||
v.reset_previous_value()
|
||||
v.explicit = False
|
||||
self._is_persisted = True
|
||||
@@ -591,6 +590,10 @@ class BaseModel(object):
|
||||
|
||||
return cls._table_name
|
||||
|
||||
def _set_column_value(self, name, value):
|
||||
"""Function to change a column value without changing the value manager states"""
|
||||
self._values[name].value = value # internal assignement, skip the main setter
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Cleans and validates the field values
|
||||
@@ -600,7 +603,7 @@ class BaseModel(object):
|
||||
if v is None and not self._values[name].explicit and col.has_default:
|
||||
v = col.get_default()
|
||||
val = col.validate(v)
|
||||
setattr(self, name, val)
|
||||
self._set_column_value(name, val)
|
||||
|
||||
# Let an instance be used like a dict of its columns keys/values
|
||||
def __iter__(self):
|
||||
|
||||
@@ -1457,6 +1457,9 @@ class DMLQuery(object):
|
||||
if self.instance._values[name].changed:
|
||||
nulled_fields.add(col.db_field_name)
|
||||
continue
|
||||
if col.has_default and not self.instance._values[name].changed:
|
||||
# Ensure default columns included in a save() are marked as explicit, to get them *persisted* properly
|
||||
self.instance._values[name].explicit = True
|
||||
insert.add_assignment(col, getattr(self.instance, name, None))
|
||||
|
||||
# skip query execution if it's empty
|
||||
|
||||
@@ -30,7 +30,6 @@ from cassandra.cqlengine.management import drop_table
|
||||
from cassandra.cqlengine.models import Model
|
||||
from cassandra.query import SimpleStatement
|
||||
from cassandra.util import Date, Time
|
||||
from cassandra.cqltypes import Int32Type
|
||||
from cassandra.cqlengine.statements import SelectStatement, DeleteStatement, WhereClause
|
||||
from cassandra.cqlengine.operators import EqualsOperator
|
||||
|
||||
@@ -483,37 +482,6 @@ class TestUpdating(BaseCassEngTestCase):
|
||||
self.assertTrue(self.instance._values['count'].previous_value is None)
|
||||
self.assertTrue(self.instance.count is None)
|
||||
|
||||
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
|
||||
"""
|
||||
class ModelWithDefault(Model):
|
||||
id = columns.Integer(primary_key=True)
|
||||
mf = columns.Map(columns.Integer, columns.Integer)
|
||||
dummy = columns.Integer(default=42)
|
||||
sync_table(ModelWithDefault)
|
||||
|
||||
initial = ModelWithDefault(id=1, mf={0: 0}, dummy=0)
|
||||
initial.save()
|
||||
|
||||
session = cassandra.cluster.Cluster().connect()
|
||||
session.execute('USE ' + DEFAULT_KEYSPACE)
|
||||
self.assertEqual(
|
||||
list(session.execute('SELECT * from model_with_default'))[0].dummy, 0
|
||||
)
|
||||
|
||||
second = ModelWithDefault(id=1)
|
||||
second.update(mf={0: 1})
|
||||
self.assertEqual(
|
||||
list(session.execute('SELECT * from model_with_default'))[0].dummy, 0
|
||||
)
|
||||
|
||||
def test_previous_value_tracking_on_instantiation_with_default(self):
|
||||
|
||||
class TestDefaultValueTracking(Model):
|
||||
@@ -546,16 +514,12 @@ class TestUpdating(BaseCassEngTestCase):
|
||||
# yet.
|
||||
self.assertTrue(instance._values['id'].previous_value is None)
|
||||
self.assertTrue(instance._values['int1'].previous_value is None)
|
||||
self.assertTrue(instance._values['int2'].previous_value is None)
|
||||
self.assertTrue(instance._values['int3'].previous_value is None)
|
||||
self.assertTrue(instance._values['int4'].previous_value is None)
|
||||
self.assertTrue(instance._values['int5'].previous_value is None)
|
||||
self.assertTrue(instance._values['int6'].previous_value is None)
|
||||
|
||||
# When a column has a default value and that field has no explicit value specified at
|
||||
# the instance creation, the previous_value should be set to the default value to
|
||||
# avoid any undesired update
|
||||
self.assertEqual(instance._values['int2'].previous_value, 456)
|
||||
self.assertIsNotNone(instance._values['int4'])
|
||||
|
||||
# All explicitely set columns, and those with default values are
|
||||
# flagged has changed.
|
||||
self.assertTrue(set(instance.get_changed_columns()) == set([
|
||||
|
||||
@@ -134,3 +134,157 @@ class ModelUpdateTests(BaseCassEngTestCase):
|
||||
m0 = TestUpdateModel.create(count=5, text='monkey')
|
||||
with self.assertRaises(ValidationError):
|
||||
m0.update(partition=uuid4())
|
||||
|
||||
|
||||
class ModelWithDefault(Model):
|
||||
id = columns.Integer(primary_key=True)
|
||||
mf = columns.Map(columns.Integer, columns.Integer)
|
||||
dummy = 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)
|
||||
|
||||
|
||||
class ModelWithDefaultTests(BaseCassEngTestCase):
|
||||
def setUp(self):
|
||||
sync_table(ModelWithDefault)
|
||||
|
||||
def tearDown(self):
|
||||
drop_table(ModelWithDefault)
|
||||
|
||||
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
|
||||
"""
|
||||
initial = ModelWithDefault(id=1, mf={0: 0}, dummy=0)
|
||||
initial.save()
|
||||
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 0, 'mf': {0: 0}})
|
||||
|
||||
second = ModelWithDefault(id=1)
|
||||
second.update(mf={0: 1})
|
||||
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 0, 'mf': {0: 1}})
|
||||
|
||||
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
|
||||
|
||||
@test_category object_mapper
|
||||
:return:
|
||||
"""
|
||||
initial = ModelWithDefault(id=1)
|
||||
initial.mf = {0: 0}
|
||||
initial.dummy = 42
|
||||
initial.update()
|
||||
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 42, 'mf': {0: 0}})
|
||||
|
||||
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()
|
||||
|
||||
obj.update(dummy=None)
|
||||
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': None, 'mf': {0: 0}})
|
||||
|
||||
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}
|
||||
|
||||
item.save()
|
||||
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': None, 'mf': {1: 2}})
|
||||
|
||||
def test_collections(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, 2: 1}, dummy=1).save()
|
||||
item = ModelWithDefault.filter(id=1).first()
|
||||
|
||||
item.update(mf={2:1})
|
||||
self.assertEqual(ModelWithDefault.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 1, 'mf': {2: 1}})
|
||||
|
||||
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)
|
||||
item = ModelWithDefaultCollection.create(id=1, mf={1: 1}, dummy=1).save()
|
||||
self.assertEqual(ModelWithDefaultCollection.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 1, 'mf': {1: 1}})
|
||||
|
||||
item.update(mf={2: 2})
|
||||
self.assertEqual(ModelWithDefaultCollection.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 1, 'mf': {2: 2}})
|
||||
|
||||
item.update(mf=None)
|
||||
self.assertEqual(ModelWithDefaultCollection.objects().all().get()._as_dict(),
|
||||
{'id': 1, 'dummy': 1, 'mf': {}})
|
||||
|
||||
item = ModelWithDefaultCollection.create(id=2, dummy=2).save()
|
||||
self.assertEqual(ModelWithDefaultCollection.objects().all().get(id=2)._as_dict(),
|
||||
{'id': 2, 'dummy': 2, 'mf': {2: 2}})
|
||||
|
||||
item.update(mf={1: 1, 4: 4})
|
||||
self.assertEqual(ModelWithDefaultCollection.objects().all().get(id=2)._as_dict(),
|
||||
{'id': 2, 'dummy': 2, 'mf': {1: 1, 4: 4}})
|
||||
|
||||
drop_table(ModelWithDefaultCollection)
|
||||
|
||||
@@ -290,6 +290,34 @@ class QueryUpdateTests(BaseCassEngTestCase):
|
||||
text_map__remove=["bar"]
|
||||
)
|
||||
|
||||
@execute_count(3)
|
||||
def test_an_extra_delete_is_not_sent(self):
|
||||
"""
|
||||
Test to ensure that an extra DELETE is not sent if an object is read
|
||||
from the DB with a None value
|
||||
|
||||
@since 3.9
|
||||
@jira_ticket PYTHON-719
|
||||
@expected_result only three queries are executed, the first one for
|
||||
inserting the object, the second one for reading it, and the third
|
||||
one for updating it
|
||||
|
||||
@test_category object_mapper
|
||||
"""
|
||||
partition = uuid4()
|
||||
cluster = 1
|
||||
|
||||
TestQueryUpdateModel.objects.create(
|
||||
partition=partition, cluster=cluster)
|
||||
|
||||
obj = TestQueryUpdateModel.objects(
|
||||
partition=partition, cluster=cluster).first()
|
||||
|
||||
self.assertFalse(any([obj._values[column].deleted for column in obj._values]))
|
||||
|
||||
obj.text = 'foo'
|
||||
obj.save()
|
||||
|
||||
|
||||
class StaticDeleteModel(Model):
|
||||
example_id = columns.Integer(partition_key=True, primary_key=True, default=uuid4)
|
||||
|
||||
Reference in New Issue
Block a user