482 lines
19 KiB
Python
482 lines
19 KiB
Python
from datetime import datetime
|
|
import time
|
|
from uuid import uuid1, uuid4
|
|
|
|
from cqlengine.tests.base import BaseCassEngTestCase
|
|
|
|
from cqlengine.exceptions import ModelException
|
|
from cqlengine import functions
|
|
from cqlengine.management import create_table
|
|
from cqlengine.management import delete_table
|
|
from cqlengine.models import Model
|
|
from cqlengine import columns
|
|
from cqlengine import query
|
|
|
|
class TestModel(Model):
|
|
test_id = columns.Integer(primary_key=True)
|
|
attempt_id = columns.Integer(primary_key=True)
|
|
description = columns.Text()
|
|
expected_result = columns.Integer()
|
|
test_result = columns.Integer()
|
|
|
|
class IndexedTestModel(Model):
|
|
test_id = columns.Integer(primary_key=True)
|
|
attempt_id = columns.Integer(index=True)
|
|
description = columns.Text()
|
|
expected_result = columns.Integer()
|
|
test_result = columns.Integer(index=True)
|
|
|
|
class TestMultiClusteringModel(Model):
|
|
one = columns.Integer(primary_key=True)
|
|
two = columns.Integer(primary_key=True)
|
|
three = columns.Integer(primary_key=True)
|
|
|
|
|
|
class TestQuerySetOperation(BaseCassEngTestCase):
|
|
|
|
def test_query_filter_parsing(self):
|
|
"""
|
|
Tests the queryset filter method parses it's kwargs properly
|
|
"""
|
|
query1 = TestModel.objects(test_id=5)
|
|
assert len(query1._where) == 1
|
|
|
|
op = query1._where[0]
|
|
assert isinstance(op, query.EqualsOperator)
|
|
assert op.value == 5
|
|
|
|
query2 = query1.filter(expected_result__gte=1)
|
|
assert len(query2._where) == 2
|
|
|
|
op = query2._where[1]
|
|
assert isinstance(op, query.GreaterThanOrEqualOperator)
|
|
assert op.value == 1
|
|
|
|
def test_using_invalid_column_names_in_filter_kwargs_raises_error(self):
|
|
"""
|
|
Tests that using invalid or nonexistant column names for filter args raises an error
|
|
"""
|
|
with self.assertRaises(query.QueryException):
|
|
query0 = TestModel.objects(nonsense=5)
|
|
|
|
def test_where_clause_generation(self):
|
|
"""
|
|
Tests the where clause creation
|
|
"""
|
|
query1 = TestModel.objects(test_id=5)
|
|
ids = [o.query_value.identifier for o in query1._where]
|
|
where = query1._where_clause()
|
|
assert where == '"test_id" = :{}'.format(*ids)
|
|
|
|
query2 = query1.filter(expected_result__gte=1)
|
|
ids = [o.query_value.identifier for o in query2._where]
|
|
where = query2._where_clause()
|
|
assert where == '"test_id" = :{} AND "expected_result" >= :{}'.format(*ids)
|
|
|
|
|
|
def test_querystring_generation(self):
|
|
"""
|
|
Tests the select querystring creation
|
|
"""
|
|
|
|
def test_queryset_is_immutable(self):
|
|
"""
|
|
Tests that calling a queryset function that changes it's state returns a new queryset
|
|
"""
|
|
query1 = TestModel.objects(test_id=5)
|
|
assert len(query1._where) == 1
|
|
|
|
query2 = query1.filter(expected_result__gte=1)
|
|
assert len(query2._where) == 2
|
|
|
|
def test_the_all_method_clears_where_filter(self):
|
|
"""
|
|
Tests that calling all on a queryset with previously defined filters returns a queryset with no filters
|
|
"""
|
|
query1 = TestModel.objects(test_id=5)
|
|
assert len(query1._where) == 1
|
|
|
|
query2 = query1.filter(expected_result__gte=1)
|
|
assert len(query2._where) == 2
|
|
|
|
query3 = query2.all()
|
|
assert len(query3._where) == 0
|
|
|
|
def test_defining_only_and_defer_fails(self):
|
|
"""
|
|
Tests that trying to add fields to either only or defer, or doing so more than once fails
|
|
"""
|
|
|
|
def test_defining_only_or_defer_on_nonexistant_fields_fails(self):
|
|
"""
|
|
Tests that setting only or defer fields that don't exist raises an exception
|
|
"""
|
|
|
|
class BaseQuerySetUsage(BaseCassEngTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(BaseQuerySetUsage, cls).setUpClass()
|
|
delete_table(TestModel)
|
|
delete_table(IndexedTestModel)
|
|
create_table(TestModel)
|
|
create_table(IndexedTestModel)
|
|
create_table(TestMultiClusteringModel)
|
|
|
|
TestModel.objects.create(test_id=0, attempt_id=0, description='try1', expected_result=5, test_result=30)
|
|
TestModel.objects.create(test_id=0, attempt_id=1, description='try2', expected_result=10, test_result=30)
|
|
TestModel.objects.create(test_id=0, attempt_id=2, description='try3', expected_result=15, test_result=30)
|
|
TestModel.objects.create(test_id=0, attempt_id=3, description='try4', expected_result=20, test_result=25)
|
|
|
|
TestModel.objects.create(test_id=1, attempt_id=0, description='try5', expected_result=5, test_result=25)
|
|
TestModel.objects.create(test_id=1, attempt_id=1, description='try6', expected_result=10, test_result=25)
|
|
TestModel.objects.create(test_id=1, attempt_id=2, description='try7', expected_result=15, test_result=25)
|
|
TestModel.objects.create(test_id=1, attempt_id=3, description='try8', expected_result=20, test_result=20)
|
|
|
|
TestModel.objects.create(test_id=2, attempt_id=0, description='try9', expected_result=50, test_result=40)
|
|
TestModel.objects.create(test_id=2, attempt_id=1, description='try10', expected_result=60, test_result=40)
|
|
TestModel.objects.create(test_id=2, attempt_id=2, description='try11', expected_result=70, test_result=45)
|
|
TestModel.objects.create(test_id=2, attempt_id=3, description='try12', expected_result=75, test_result=45)
|
|
|
|
IndexedTestModel.objects.create(test_id=0, attempt_id=0, description='try1', expected_result=5, test_result=30)
|
|
IndexedTestModel.objects.create(test_id=1, attempt_id=1, description='try2', expected_result=10, test_result=30)
|
|
IndexedTestModel.objects.create(test_id=2, attempt_id=2, description='try3', expected_result=15, test_result=30)
|
|
IndexedTestModel.objects.create(test_id=3, attempt_id=3, description='try4', expected_result=20, test_result=25)
|
|
|
|
IndexedTestModel.objects.create(test_id=4, attempt_id=0, description='try5', expected_result=5, test_result=25)
|
|
IndexedTestModel.objects.create(test_id=5, attempt_id=1, description='try6', expected_result=10, test_result=25)
|
|
IndexedTestModel.objects.create(test_id=6, attempt_id=2, description='try7', expected_result=15, test_result=25)
|
|
IndexedTestModel.objects.create(test_id=7, attempt_id=3, description='try8', expected_result=20, test_result=20)
|
|
|
|
IndexedTestModel.objects.create(test_id=8, attempt_id=0, description='try9', expected_result=50, test_result=40)
|
|
IndexedTestModel.objects.create(test_id=9, attempt_id=1, description='try10', expected_result=60, test_result=40)
|
|
IndexedTestModel.objects.create(test_id=10, attempt_id=2, description='try11', expected_result=70, test_result=45)
|
|
IndexedTestModel.objects.create(test_id=11, attempt_id=3, description='try12', expected_result=75, test_result=45)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(BaseQuerySetUsage, cls).tearDownClass()
|
|
delete_table(TestModel)
|
|
delete_table(IndexedTestModel)
|
|
delete_table(TestMultiClusteringModel)
|
|
|
|
class TestQuerySetCountSelectionAndIteration(BaseQuerySetUsage):
|
|
|
|
def test_count(self):
|
|
assert TestModel.objects.count() == 12
|
|
|
|
q = TestModel.objects(test_id=0)
|
|
assert q.count() == 4
|
|
|
|
def test_iteration(self):
|
|
q = TestModel.objects(test_id=0)
|
|
#tuple of expected attempt_id, expected_result values
|
|
compare_set = set([(0,5), (1,10), (2,15), (3,20)])
|
|
for t in q:
|
|
val = t.attempt_id, t.expected_result
|
|
assert val in compare_set
|
|
compare_set.remove(val)
|
|
assert len(compare_set) == 0
|
|
|
|
q = TestModel.objects(attempt_id=3).allow_filtering()
|
|
assert len(q) == 3
|
|
#tuple of expected test_id, expected_result values
|
|
compare_set = set([(0,20), (1,20), (2,75)])
|
|
for t in q:
|
|
val = t.test_id, t.expected_result
|
|
assert val in compare_set
|
|
compare_set.remove(val)
|
|
assert len(compare_set) == 0
|
|
|
|
def test_multiple_iterations_work_properly(self):
|
|
""" Tests that iterating over a query set more than once works """
|
|
q = TestModel.objects(test_id=0)
|
|
#tuple of expected attempt_id, expected_result values
|
|
compare_set = set([(0,5), (1,10), (2,15), (3,20)])
|
|
for t in q:
|
|
val = t.attempt_id, t.expected_result
|
|
assert val in compare_set
|
|
compare_set.remove(val)
|
|
assert len(compare_set) == 0
|
|
|
|
#try it again
|
|
compare_set = set([(0,5), (1,10), (2,15), (3,20)])
|
|
for t in q:
|
|
val = t.attempt_id, t.expected_result
|
|
assert val in compare_set
|
|
compare_set.remove(val)
|
|
assert len(compare_set) == 0
|
|
|
|
def test_multiple_iterators_are_isolated(self):
|
|
"""
|
|
tests that the use of one iterator does not affect the behavior of another
|
|
"""
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
iter1 = iter(q)
|
|
iter2 = iter(q)
|
|
for attempt_id in expected_order:
|
|
assert iter1.next().attempt_id == attempt_id
|
|
assert iter2.next().attempt_id == attempt_id
|
|
|
|
def test_get_success_case(self):
|
|
"""
|
|
Tests that the .get() method works on new and existing querysets
|
|
"""
|
|
m = TestModel.objects.get(test_id=0, attempt_id=0)
|
|
assert isinstance(m, TestModel)
|
|
assert m.test_id == 0
|
|
assert m.attempt_id == 0
|
|
|
|
q = TestModel.objects(test_id=0, attempt_id=0)
|
|
m = q.get()
|
|
assert isinstance(m, TestModel)
|
|
assert m.test_id == 0
|
|
assert m.attempt_id == 0
|
|
|
|
q = TestModel.objects(test_id=0)
|
|
m = q.get(attempt_id=0)
|
|
assert isinstance(m, TestModel)
|
|
assert m.test_id == 0
|
|
assert m.attempt_id == 0
|
|
|
|
def test_get_doesnotexist_exception(self):
|
|
"""
|
|
Tests that get calls that don't return a result raises a DoesNotExist error
|
|
"""
|
|
with self.assertRaises(TestModel.DoesNotExist):
|
|
TestModel.objects.get(test_id=100)
|
|
|
|
def test_get_multipleobjects_exception(self):
|
|
"""
|
|
Tests that get calls that return multiple results raise a MultipleObjectsReturned error
|
|
"""
|
|
with self.assertRaises(TestModel.MultipleObjectsReturned):
|
|
TestModel.objects.get(test_id=1)
|
|
|
|
def test_allow_filtering_flag(self):
|
|
"""
|
|
"""
|
|
|
|
class TestQuerySetOrdering(BaseQuerySetUsage):
|
|
|
|
def test_order_by_success_case(self):
|
|
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
for model, expect in zip(q,expected_order):
|
|
assert model.attempt_id == expect
|
|
|
|
q = q.order_by('-attempt_id')
|
|
expected_order.reverse()
|
|
for model, expect in zip(q,expected_order):
|
|
assert model.attempt_id == expect
|
|
|
|
def test_ordering_by_non_second_primary_keys_fail(self):
|
|
|
|
with self.assertRaises(query.QueryException):
|
|
q = TestModel.objects(test_id=0).order_by('test_id')
|
|
|
|
def test_ordering_by_non_primary_keys_fails(self):
|
|
with self.assertRaises(query.QueryException):
|
|
q = TestModel.objects(test_id=0).order_by('description')
|
|
|
|
def test_ordering_on_indexed_columns_fails(self):
|
|
with self.assertRaises(query.QueryException):
|
|
q = IndexedTestModel.objects(test_id=0).order_by('attempt_id')
|
|
|
|
def test_ordering_on_multiple_clustering_columns(self):
|
|
TestMultiClusteringModel.create(one=1, two=1, three=4)
|
|
TestMultiClusteringModel.create(one=1, two=1, three=2)
|
|
TestMultiClusteringModel.create(one=1, two=1, three=5)
|
|
TestMultiClusteringModel.create(one=1, two=1, three=1)
|
|
TestMultiClusteringModel.create(one=1, two=1, three=3)
|
|
|
|
results = TestMultiClusteringModel.objects.filter(one=1, two=1).order_by('-two', '-three')
|
|
assert [r.three for r in results] == [5, 4, 3, 2, 1]
|
|
|
|
results = TestMultiClusteringModel.objects.filter(one=1, two=1).order_by('two', 'three')
|
|
assert [r.three for r in results] == [1, 2, 3, 4, 5]
|
|
|
|
results = TestMultiClusteringModel.objects.filter(one=1, two=1).order_by('two').order_by('three')
|
|
assert [r.three for r in results] == [1, 2, 3, 4, 5]
|
|
|
|
|
|
class TestQuerySetSlicing(BaseQuerySetUsage):
|
|
|
|
def test_out_of_range_index_raises_error(self):
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
with self.assertRaises(IndexError):
|
|
q[10]
|
|
|
|
def test_array_indexing_works_properly(self):
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
for i in range(len(q)):
|
|
assert q[i].attempt_id == expected_order[i]
|
|
|
|
def test_negative_indexing_works_properly(self):
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
assert q[-1].attempt_id == expected_order[-1]
|
|
assert q[-2].attempt_id == expected_order[-2]
|
|
|
|
def test_slicing_works_properly(self):
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
for model, expect in zip(q[1:3], expected_order[1:3]):
|
|
assert model.attempt_id == expect
|
|
|
|
def test_negative_slicing(self):
|
|
q = TestModel.objects(test_id=0).order_by('attempt_id')
|
|
expected_order = [0,1,2,3]
|
|
for model, expect in zip(q[-3:], expected_order[-3:]):
|
|
assert model.attempt_id == expect
|
|
for model, expect in zip(q[:-1], expected_order[:-1]):
|
|
assert model.attempt_id == expect
|
|
|
|
class TestQuerySetValidation(BaseQuerySetUsage):
|
|
|
|
def test_primary_key_or_index_must_be_specified(self):
|
|
"""
|
|
Tests that queries that don't have an equals relation to a primary key or indexed field fail
|
|
"""
|
|
with self.assertRaises(query.QueryException):
|
|
q = TestModel.objects(test_result=25)
|
|
[i for i in q]
|
|
|
|
def test_primary_key_or_index_must_have_equal_relation_filter(self):
|
|
"""
|
|
Tests that queries that don't have non equal (>,<, etc) relation to a primary key or indexed field fail
|
|
"""
|
|
with self.assertRaises(query.QueryException):
|
|
q = TestModel.objects(test_id__gt=0)
|
|
[i for i in q]
|
|
|
|
|
|
def test_indexed_field_can_be_queried(self):
|
|
"""
|
|
Tests that queries on an indexed field will work without any primary key relations specified
|
|
"""
|
|
q = IndexedTestModel.objects(test_result=25)
|
|
count = q.count()
|
|
assert q.count() == 4
|
|
|
|
class TestQuerySetDelete(BaseQuerySetUsage):
|
|
|
|
def test_delete(self):
|
|
TestModel.objects.create(test_id=3, attempt_id=0, description='try9', expected_result=50, test_result=40)
|
|
TestModel.objects.create(test_id=3, attempt_id=1, description='try10', expected_result=60, test_result=40)
|
|
TestModel.objects.create(test_id=3, attempt_id=2, description='try11', expected_result=70, test_result=45)
|
|
TestModel.objects.create(test_id=3, attempt_id=3, description='try12', expected_result=75, test_result=45)
|
|
|
|
assert TestModel.objects.count() == 16
|
|
assert TestModel.objects(test_id=3).count() == 4
|
|
|
|
TestModel.objects(test_id=3).delete()
|
|
|
|
assert TestModel.objects.count() == 12
|
|
assert TestModel.objects(test_id=3).count() == 0
|
|
|
|
def test_delete_without_partition_key(self):
|
|
""" Tests that attempting to delete a model without defining a partition key fails """
|
|
with self.assertRaises(query.QueryException):
|
|
TestModel.objects(attempt_id=0).delete()
|
|
|
|
def test_delete_without_any_where_args(self):
|
|
""" Tests that attempting to delete a whole table without any arguments will fail """
|
|
with self.assertRaises(query.QueryException):
|
|
TestModel.objects(attempt_id=0).delete()
|
|
|
|
class TestQuerySetConnectionHandling(BaseQuerySetUsage):
|
|
|
|
def test_conn_is_returned_after_filling_cache(self):
|
|
"""
|
|
Tests that the queryset returns it's connection after it's fetched all of it's results
|
|
"""
|
|
q = TestModel.objects(test_id=0)
|
|
#tuple of expected attempt_id, expected_result values
|
|
compare_set = set([(0,5), (1,10), (2,15), (3,20)])
|
|
for t in q:
|
|
val = t.attempt_id, t.expected_result
|
|
assert val in compare_set
|
|
compare_set.remove(val)
|
|
|
|
assert q._con is None
|
|
assert q._cur is None
|
|
|
|
def test_conn_is_returned_after_queryset_is_garbage_collected(self):
|
|
""" Tests that the connection is returned to the connection pool after the queryset is gc'd """
|
|
from cqlengine.connection import ConnectionPool
|
|
# The queue size can be 1 if we just run this file's tests
|
|
# It will be 2 when we run 'em all
|
|
initial_size = ConnectionPool._queue.qsize()
|
|
q = TestModel.objects(test_id=0)
|
|
v = q[0]
|
|
assert ConnectionPool._queue.qsize() == initial_size - 1
|
|
|
|
del q
|
|
assert ConnectionPool._queue.qsize() == initial_size
|
|
|
|
class TimeUUIDQueryModel(Model):
|
|
partition = columns.UUID(primary_key=True)
|
|
time = columns.TimeUUID(primary_key=True)
|
|
data = columns.Text(required=False)
|
|
|
|
class TestMinMaxTimeUUIDFunctions(BaseCassEngTestCase):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(TestMinMaxTimeUUIDFunctions, cls).setUpClass()
|
|
create_table(TimeUUIDQueryModel)
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
super(TestMinMaxTimeUUIDFunctions, cls).tearDownClass()
|
|
delete_table(TimeUUIDQueryModel)
|
|
|
|
def test_success_case(self):
|
|
""" Test that the min and max time uuid functions work as expected """
|
|
pk = uuid4()
|
|
TimeUUIDQueryModel.create(partition=pk, time=uuid1(), data='1')
|
|
time.sleep(0.2)
|
|
TimeUUIDQueryModel.create(partition=pk, time=uuid1(), data='2')
|
|
time.sleep(0.2)
|
|
midpoint = datetime.utcnow()
|
|
time.sleep(0.2)
|
|
TimeUUIDQueryModel.create(partition=pk, time=uuid1(), data='3')
|
|
time.sleep(0.2)
|
|
TimeUUIDQueryModel.create(partition=pk, time=uuid1(), data='4')
|
|
time.sleep(0.2)
|
|
|
|
q = TimeUUIDQueryModel.filter(partition=pk, time__lte=functions.MaxTimeUUID(midpoint))
|
|
q = [d for d in q]
|
|
assert len(q) == 2
|
|
datas = [d.data for d in q]
|
|
assert '1' in datas
|
|
assert '2' in datas
|
|
|
|
q = TimeUUIDQueryModel.filter(partition=pk, time__gte=functions.MinTimeUUID(midpoint))
|
|
assert len(q) == 2
|
|
datas = [d.data for d in q]
|
|
assert '3' in datas
|
|
assert '4' in datas
|
|
|
|
|
|
class TestInOperator(BaseQuerySetUsage):
|
|
|
|
def test_success_case(self):
|
|
q = TestModel.filter(test_id__in=[0,1])
|
|
assert q.count() == 8
|
|
|
|
|
|
class TestValuesList(BaseQuerySetUsage):
|
|
def test_values_list(self):
|
|
q = TestModel.objects.filter(test_id=0, attempt_id=1)
|
|
item = q.values_list('test_id', 'attempt_id', 'description', 'expected_result', 'test_result').first()
|
|
assert item == [0, 1, 'try2', 10, 30]
|
|
|
|
item = q.values_list('expected_result', flat=True).first()
|
|
assert item == 10
|
|
|