Merged python-657 branch

This commit is contained in:
bjmb
2017-04-05 14:10:42 -04:00
6 changed files with 210 additions and 45 deletions

View File

@@ -47,7 +47,19 @@ class BaseValueManager(object):
:rtype: boolean :rtype: boolean
""" """
return self.value != self.previous_value 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): def reset_previous_value(self):
self.previous_value = deepcopy(self.value) self.previous_value = deepcopy(self.value)
@@ -57,6 +69,7 @@ class BaseValueManager(object):
def setval(self, val): def setval(self, val):
self.value = val self.value = val
self.explicit = True
def delval(self): def delval(self):
self.value = None self.value = None

View File

@@ -407,8 +407,6 @@ class BaseModel(object):
value = column.to_python(value) value = column.to_python(value)
value_mngr = column.value_manager(self, column, value) value_mngr = column.value_manager(self, column, value)
value_mngr.explicit = name in values 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 self._values[name] = value_mngr
def __repr__(self): def __repr__(self):
@@ -486,11 +484,12 @@ class BaseModel(object):
klass = cls klass = cls
instance = klass(**values) instance = klass(**values)
instance._set_persisted() instance._set_persisted(force=True)
return instance return instance
def _set_persisted(self): def _set_persisted(self, force=False):
for v in self._values.values(): # 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.reset_previous_value()
v.explicit = False v.explicit = False
self._is_persisted = True self._is_persisted = True
@@ -591,6 +590,10 @@ class BaseModel(object):
return cls._table_name 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): def validate(self):
""" """
Cleans and validates the field values 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: if v is None and not self._values[name].explicit and col.has_default:
v = col.get_default() v = col.get_default()
val = col.validate(v) 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 # Let an instance be used like a dict of its columns keys/values
def __iter__(self): def __iter__(self):

View File

@@ -1457,6 +1457,9 @@ class DMLQuery(object):
if self.instance._values[name].changed: if self.instance._values[name].changed:
nulled_fields.add(col.db_field_name) nulled_fields.add(col.db_field_name)
continue 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)) insert.add_assignment(col, getattr(self.instance, name, None))
# skip query execution if it's empty # skip query execution if it's empty

View File

@@ -30,7 +30,6 @@ from cassandra.cqlengine.management import drop_table
from cassandra.cqlengine.models import Model from cassandra.cqlengine.models import Model
from cassandra.query import SimpleStatement from cassandra.query import SimpleStatement
from cassandra.util import Date, Time from cassandra.util import Date, Time
from cassandra.cqltypes import Int32Type
from cassandra.cqlengine.statements import SelectStatement, DeleteStatement, WhereClause from cassandra.cqlengine.statements import SelectStatement, DeleteStatement, WhereClause
from cassandra.cqlengine.operators import EqualsOperator 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._values['count'].previous_value is None)
self.assertTrue(self.instance.count 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): def test_previous_value_tracking_on_instantiation_with_default(self):
class TestDefaultValueTracking(Model): class TestDefaultValueTracking(Model):
@@ -546,16 +514,12 @@ class TestUpdating(BaseCassEngTestCase):
# yet. # yet.
self.assertTrue(instance._values['id'].previous_value is None) self.assertTrue(instance._values['id'].previous_value is None)
self.assertTrue(instance._values['int1'].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['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['int5'].previous_value is None)
self.assertTrue(instance._values['int6'].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 # All explicitely set columns, and those with default values are
# flagged has changed. # flagged has changed.
self.assertTrue(set(instance.get_changed_columns()) == set([ self.assertTrue(set(instance.get_changed_columns()) == set([

View File

@@ -134,3 +134,157 @@ class ModelUpdateTests(BaseCassEngTestCase):
m0 = TestUpdateModel.create(count=5, text='monkey') m0 = TestUpdateModel.create(count=5, text='monkey')
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
m0.update(partition=uuid4()) 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)

View File

@@ -290,6 +290,34 @@ class QueryUpdateTests(BaseCassEngTestCase):
text_map__remove=["bar"] 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): class StaticDeleteModel(Model):
example_id = columns.Integer(partition_key=True, primary_key=True, default=uuid4) example_id = columns.Integer(partition_key=True, primary_key=True, default=uuid4)