273 lines
10 KiB
Python
273 lines
10 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.
|
|
try:
|
|
import unittest2 as unittest
|
|
except ImportError:
|
|
import unittest # noqa
|
|
|
|
import mock
|
|
import six
|
|
from uuid import uuid4
|
|
|
|
from cassandra.cqlengine import columns
|
|
from cassandra.cqlengine.management import sync_table, drop_table
|
|
from cassandra.cqlengine.models import Model
|
|
from cassandra.cqlengine.query import BatchQuery, LWTException
|
|
from cassandra.cqlengine.statements import ConditionalClause
|
|
|
|
from tests.integration.cqlengine.base import BaseCassEngTestCase
|
|
from tests.integration import CASSANDRA_VERSION, greaterthancass20
|
|
|
|
|
|
class TestConditionalModel(Model):
|
|
id = columns.UUID(primary_key=True, default=uuid4)
|
|
count = columns.Integer()
|
|
text = columns.Text(required=False)
|
|
|
|
|
|
@unittest.skipUnless(CASSANDRA_VERSION >= '2.0.0', "conditionals only supported on cassandra 2.0 or higher")
|
|
class TestConditional(BaseCassEngTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestConditional, cls).setUpClass()
|
|
sync_table(TestConditionalModel)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(TestConditional, cls).tearDownClass()
|
|
drop_table(TestConditionalModel)
|
|
|
|
def test_update_using_conditional(self):
|
|
t = TestConditionalModel.create(text='blah blah')
|
|
t.text = 'new blah'
|
|
with mock.patch.object(self.session, 'execute') as m:
|
|
t.iff(text='blah blah').save()
|
|
|
|
args = m.call_args
|
|
self.assertIn('IF "text" = %(0)s', args[0][0].query_string)
|
|
|
|
def test_update_conditional_success(self):
|
|
t = TestConditionalModel.create(text='blah blah', count=5)
|
|
id = t.id
|
|
t.text = 'new blah'
|
|
t.iff(text='blah blah').save()
|
|
|
|
updated = TestConditionalModel.objects(id=id).first()
|
|
self.assertEqual(updated.count, 5)
|
|
self.assertEqual(updated.text, 'new blah')
|
|
|
|
def test_update_failure(self):
|
|
t = TestConditionalModel.create(text='blah blah')
|
|
t.text = 'new blah'
|
|
t = t.iff(text='something wrong')
|
|
|
|
with self.assertRaises(LWTException) as assertion:
|
|
t.save()
|
|
|
|
self.assertEqual(assertion.exception.existing, {
|
|
'text': 'blah blah',
|
|
'[applied]': False,
|
|
})
|
|
|
|
def test_blind_update(self):
|
|
t = TestConditionalModel.create(text='blah blah')
|
|
t.text = 'something else'
|
|
uid = t.id
|
|
|
|
with mock.patch.object(self.session, 'execute') as m:
|
|
TestConditionalModel.objects(id=uid).iff(text='blah blah').update(text='oh hey der')
|
|
|
|
args = m.call_args
|
|
self.assertIn('IF "text" = %(1)s', args[0][0].query_string)
|
|
|
|
def test_blind_update_fail(self):
|
|
t = TestConditionalModel.create(text='blah blah')
|
|
t.text = 'something else'
|
|
uid = t.id
|
|
qs = TestConditionalModel.objects(id=uid).iff(text='Not dis!')
|
|
with self.assertRaises(LWTException) as assertion:
|
|
qs.update(text='this will never work')
|
|
|
|
self.assertEqual(assertion.exception.existing, {
|
|
'text': 'blah blah',
|
|
'[applied]': False,
|
|
})
|
|
|
|
def test_conditional_clause(self):
|
|
tc = ConditionalClause('some_value', 23)
|
|
tc.set_context_id(3)
|
|
|
|
self.assertEqual('"some_value" = %(3)s', six.text_type(tc))
|
|
self.assertEqual('"some_value" = %(3)s', str(tc))
|
|
|
|
def test_batch_update_conditional(self):
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
id = t.id
|
|
with BatchQuery() as b:
|
|
t.batch(b).iff(count=5).update(text='something else')
|
|
|
|
updated = TestConditionalModel.objects(id=id).first()
|
|
self.assertEqual(updated.text, 'something else')
|
|
|
|
b = BatchQuery()
|
|
updated.batch(b).iff(count=6).update(text='and another thing')
|
|
with self.assertRaises(LWTException) as assertion:
|
|
b.execute()
|
|
|
|
self.assertEqual(assertion.exception.existing, {
|
|
'id': id,
|
|
'count': 5,
|
|
'[applied]': False,
|
|
})
|
|
|
|
updated = TestConditionalModel.objects(id=id).first()
|
|
self.assertEqual(updated.text, 'something else')
|
|
|
|
def test_delete_conditional(self):
|
|
# DML path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
t.iff(count=9999).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
t.iff(count=5).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0)
|
|
|
|
# QuerySet path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
TestConditionalModel.objects(id=t.id).iff(count=9999).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
TestConditionalModel.objects(id=t.id).iff(count=5).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0)
|
|
|
|
@greaterthancass20
|
|
def test_delete_lwt_ne(self):
|
|
"""
|
|
Test to ensure that deletes using IF and not equals are honored correctly
|
|
|
|
@since 3.2
|
|
@jira_ticket PYTHON-328
|
|
@expected_result Delete conditional with NE should be honored
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
|
|
# DML path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
t.iff(count__ne=5).delete()
|
|
t.iff(count__ne=2).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0)
|
|
|
|
# QuerySet path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
TestConditionalModel.objects(id=t.id).iff(count__ne=5).delete()
|
|
TestConditionalModel.objects(id=t.id).iff(count__ne=2).delete()
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 0)
|
|
|
|
@greaterthancass20
|
|
def test_update_lwt_ne(self):
|
|
"""
|
|
Test to ensure that update using IF and not equals are honored correctly
|
|
|
|
@since 3.2
|
|
@jira_ticket PYTHON-328
|
|
@expected_result update conditional with NE should be honored
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
|
|
# DML path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
t.iff(count__ne=5).update(text='nothing')
|
|
t.iff(count__ne=2).update(text='nothing')
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).first().text, 'nothing')
|
|
t.delete()
|
|
|
|
# QuerySet path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
TestConditionalModel.objects(id=t.id).iff(count__ne=5).update(text='nothing')
|
|
TestConditionalModel.objects(id=t.id).iff(count__ne=2).update(text='nothing')
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).first().text, 'nothing')
|
|
t.delete()
|
|
|
|
def test_update_to_none(self):
|
|
# This test is done because updates to none are split into deletes
|
|
# for old versions of cassandra. Can be removed when we drop that code
|
|
# https://github.com/datastax/python-driver/blob/3.1.1/cassandra/cqlengine/query.py#L1197-L1200
|
|
|
|
# DML path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
t.iff(count=9999).update(text=None)
|
|
self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text)
|
|
t.iff(count=5).update(text=None)
|
|
self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text)
|
|
|
|
# QuerySet path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).count(), 1)
|
|
with self.assertRaises(LWTException):
|
|
TestConditionalModel.objects(id=t.id).iff(count=9999).update(text=None)
|
|
self.assertIsNotNone(TestConditionalModel.objects(id=t.id).first().text)
|
|
TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None)
|
|
self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text)
|
|
|
|
def test_column_delete_after_update(self):
|
|
# DML path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
t.iff(count=5).update(text=None, count=6)
|
|
|
|
self.assertIsNone(t.text)
|
|
self.assertEqual(t.count, 6)
|
|
|
|
# QuerySet path
|
|
t = TestConditionalModel.create(text='something', count=5)
|
|
TestConditionalModel.objects(id=t.id).iff(count=5).update(text=None, count=6)
|
|
|
|
self.assertIsNone(TestConditionalModel.objects(id=t.id).first().text)
|
|
self.assertEqual(TestConditionalModel.objects(id=t.id).first().count, 6)
|
|
|
|
def test_conditional_without_instance(self):
|
|
"""
|
|
Test to ensure that the iff method is honored if it's called
|
|
directly from the Model class
|
|
|
|
@jira_ticket PYTHON-505
|
|
@expected_result the value is updated
|
|
|
|
@test_category object_mapper
|
|
"""
|
|
uuid = uuid4()
|
|
TestConditionalModel.create(id=uuid, text='test_for_cassandra', count=5)
|
|
|
|
# This uses the iff method directly from the model class without
|
|
# an instance having been created
|
|
TestConditionalModel.iff(count=5).filter(id=uuid).update(text=None, count=6)
|
|
|
|
t = TestConditionalModel.filter(id=uuid).first()
|
|
self.assertIsNone(t.text)
|
|
self.assertEqual(t.count, 6)
|