starting to write the QuerySet class
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
#cqlengine exceptions
|
||||
class ModelException(BaseException): pass
|
||||
class ValidationError(BaseException): pass
|
||||
class CQLEngineException(BaseException): pass
|
||||
class ModelException(CQLEngineException): pass
|
||||
class ValidationError(CQLEngineException): pass
|
||||
|
||||
class QueryException(CQLEngineException): pass
|
||||
|
||||
@@ -1,18 +1,58 @@
|
||||
from collections import namedtuple
|
||||
import copy
|
||||
|
||||
from cqlengine.connection import get_connection
|
||||
from cqlengine.exceptions import QueryException
|
||||
|
||||
#CQL 3 reference:
|
||||
#http://www.datastax.com/docs/1.1/references/cql/index
|
||||
|
||||
class Query(object):
|
||||
WhereFilter = namedtuple('WhereFilter', ['column', 'operator', 'value'])
|
||||
|
||||
pass
|
||||
class QueryOperatorException(QueryException): pass
|
||||
|
||||
class QueryOperator(object):
|
||||
symbol = None
|
||||
|
||||
@classmethod
|
||||
def get_operator(cls, symbol):
|
||||
if not hasattr(cls, 'opmap'):
|
||||
QueryOperator.opmap = {}
|
||||
def _recurse(klass):
|
||||
if klass.symbol:
|
||||
QueryOperator.opmap[klass.symbol.upper()] = klass
|
||||
for subklass in klass.__subclasses__():
|
||||
_recurse(subklass)
|
||||
pass
|
||||
_recurse(QueryOperator)
|
||||
try:
|
||||
return QueryOperator.opmap[symbol.upper()]
|
||||
except KeyError:
|
||||
raise QueryOperatorException("{} doesn't map to a QueryOperator".format(symbol))
|
||||
|
||||
class EqualsOperator(QueryOperator):
|
||||
symbol = 'EQ'
|
||||
|
||||
class InOperator(QueryOperator):
|
||||
symbol = 'IN'
|
||||
|
||||
class GreaterThanOperator(QueryOperator):
|
||||
symbol = "GT"
|
||||
|
||||
class GreaterThanOrEqualOperator(QueryOperator):
|
||||
symbol = "GTE"
|
||||
|
||||
class LessThanOperator(QueryOperator):
|
||||
symbol = "LT"
|
||||
|
||||
class LessThanOrEqualOperator(QueryOperator):
|
||||
symbol = "LTE"
|
||||
|
||||
class QuerySet(object):
|
||||
#TODO: querysets should be immutable
|
||||
#TODO: querysets should be executed lazily
|
||||
#TODO: conflicting filter args should raise exception unless a force kwarg is supplied
|
||||
#TODO: support specifying offset and limit (use slice) (maybe return a mutated queryset)
|
||||
#TODO: support specifying columns to exclude or select only
|
||||
|
||||
#CQL supports ==, >, >=, <, <=, IN (a,b,c,..n)
|
||||
#REVERSE, LIMIT
|
||||
@@ -21,9 +61,22 @@ class QuerySet(object):
|
||||
def __init__(self, model, query_args={}):
|
||||
super(QuerySet, self).__init__()
|
||||
self.model = model
|
||||
self.query_args = query_args
|
||||
self.column_family_name = self.model.objects.column_family_name
|
||||
|
||||
#Where clause filters
|
||||
self._where = []
|
||||
|
||||
#ordering arguments
|
||||
self._order = []
|
||||
|
||||
#subset selection
|
||||
self._limit = None
|
||||
self._start = None
|
||||
|
||||
#see the defer and only methods
|
||||
self._defer_fields = []
|
||||
self._only_fields = []
|
||||
|
||||
self._cursor = None
|
||||
|
||||
#----query generation / execution----
|
||||
@@ -31,7 +84,16 @@ class QuerySet(object):
|
||||
conn = get_connection()
|
||||
self._cursor = conn.cursor()
|
||||
|
||||
def _generate_querystring(self):
|
||||
def _where_clause(self):
|
||||
"""
|
||||
Returns a where clause based on the given filter args
|
||||
"""
|
||||
pass
|
||||
|
||||
def _select_query(self):
|
||||
"""
|
||||
Returns a select clause based on the given filter args
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@@ -67,16 +129,36 @@ class QuerySet(object):
|
||||
pass
|
||||
|
||||
def all(self):
|
||||
return QuerySet(self.model)
|
||||
clone = copy.deepcopy(self)
|
||||
clone._where = []
|
||||
return clone
|
||||
|
||||
def _parse_filter_arg(self, arg, val):
|
||||
statement = arg.split('__')
|
||||
if len(statement) == 1:
|
||||
return WhereFilter(arg, None, val)
|
||||
elif len(statement) == 2:
|
||||
return WhereFilter(statement[0], statement[1], val)
|
||||
else:
|
||||
raise QueryException("Can't parse '{}'".format(arg))
|
||||
|
||||
def filter(self, **kwargs):
|
||||
qargs = copy.deepcopy(self.query_args)
|
||||
qargs.update(kwargs)
|
||||
return QuerySet(self.model, query_args=qargs)
|
||||
#add arguments to the where clause filters
|
||||
clone = copy.deepcopy(self)
|
||||
for arg, val in kwargs.items():
|
||||
raw_statement = self._parse_filter_arg(arg, val)
|
||||
#resolve column and operator
|
||||
try:
|
||||
column = self.model._columns[raw_statement.column]
|
||||
except KeyError:
|
||||
raise QueryException("Can't resolve column name: '{}'".format(raw_statement.column))
|
||||
|
||||
def exclude(self, **kwargs):
|
||||
""" Need to invert the logic for all kwargs """
|
||||
pass
|
||||
operator = QueryOperator.get_operator(raw_statement.operator)
|
||||
|
||||
statement = WhereFilter(column, operator, val)
|
||||
clone._where.append(statement)
|
||||
|
||||
return clone
|
||||
|
||||
def count(self):
|
||||
""" Returns the number of rows matched by this query """
|
||||
@@ -95,6 +177,32 @@ class QuerySet(object):
|
||||
self._cursor.execute(qs, {self.model._pk_name:pk})
|
||||
return self._get_next()
|
||||
|
||||
def _only_or_defer(self, action, fields):
|
||||
clone = copy.deepcopy(self)
|
||||
if clone._defer_fields or clone._only_fields:
|
||||
raise QueryException("QuerySet alread has only or defer fields defined")
|
||||
|
||||
#check for strange fields
|
||||
missing_fields = [f for f in fields if f not in self.model._columns.keys()]
|
||||
if missing_fields:
|
||||
raise QueryException("Can't resolve fields {} in {}".format(', '.join(missing_fields), self.model.__name__))
|
||||
|
||||
if action == 'defer':
|
||||
clone._defer_fields = fields
|
||||
elif action == 'only':
|
||||
clone._only_fields = fields
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
return clone
|
||||
|
||||
def only(self, fields):
|
||||
""" Load only these fields for the returned query """
|
||||
return self._only_or_defer('only', fields)
|
||||
|
||||
def defer(self, fields):
|
||||
""" Don't load these fields for the returned query """
|
||||
return self._only_or_defer('defer', fields)
|
||||
|
||||
#----writes----
|
||||
def save(self, instance):
|
||||
@@ -137,7 +245,7 @@ class QuerySet(object):
|
||||
cur.execute(qs, field_values)
|
||||
|
||||
#----delete---
|
||||
def delete(self):
|
||||
def delete(self, columns=[]):
|
||||
"""
|
||||
Deletes the contents of a query
|
||||
"""
|
||||
|
||||
0
cqlengine/tests/query/__init__.py
Normal file
0
cqlengine/tests/query/__init__.py
Normal file
52
cqlengine/tests/query/test_queryset.py
Normal file
52
cqlengine/tests/query/test_queryset.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from cqlengine.tests.base import BaseCassEngTestCase
|
||||
|
||||
from cqlengine.exceptions import ModelException
|
||||
from cqlengine.models import Model
|
||||
from cqlengine import columns
|
||||
|
||||
class TestQuerySet(BaseCassEngTestCase):
|
||||
|
||||
def test_query_filter_parsing(self):
|
||||
"""
|
||||
Tests the queryset filter method
|
||||
"""
|
||||
|
||||
def test_where_clause_generation(self):
|
||||
"""
|
||||
Tests the where clause creation
|
||||
"""
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
def test_queryset_slicing(self):
|
||||
"""
|
||||
Check that the limit and start is implemented as iterator slices
|
||||
"""
|
||||
|
||||
def test_proper_delete_behavior(self):
|
||||
"""
|
||||
Tests that deleting the contents of a queryset works properly
|
||||
"""
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
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_fields_fails(self):
|
||||
"""
|
||||
Tests that setting only or defer fields that don't exist raises an exception
|
||||
"""
|
||||
Reference in New Issue
Block a user