Exemple #1
0
 def test_add(self):
     # start with the same children of node1 then add an item
     node3 = Node(self.node1_children)
     node3_added_child = ("c", 3)
     # add() returns the added data
     self.assertEqual(node3.add(node3_added_child, Node.default), node3_added_child)
     # we added exactly one item, len() should reflect that
     self.assertEqual(len(self.node1) + 1, len(node3))
     self.assertEqual(str(node3), "(DEFAULT: ('a', 1), ('b', 2), ('c', 3))")
Exemple #2
0
 def test_add(self):
     # start with the same children of node1 then add an item
     node3 = Node(self.node1_children)
     node3_added_child = ('c', 3)
     # add() returns the added data
     self.assertEqual(node3.add(node3_added_child, Node.default),
                      node3_added_child)
     # we added exactly one item, len() should reflect that
     self.assertEqual(len(self.node1) + 1, len(node3))
     self.assertEqual(str(node3), "(DEFAULT: ('a', 1), ('b', 2), ('c', 3))")
Exemple #3
0
 def test_add_eq_child_mixed_connector(self):
     node = Node(["a", "b"], OR)
     self.assertEqual(node.add("a", AND), "a")
     self.assertEqual(node, Node([Node(["a", "b"], OR), "a"], AND))
Exemple #4
0
class MongoQuery(object):
    AND = 'AND'
    OR = 'OR'
    COLON = ':'
    DEFAULT = 'DEFAULT'

    def __init__(self, model):
        self.model = model
        self.where = Node()
        self.order_by = []
        self.fields = set()
        self.default_ordering = True
        self.select_related = True #Hack for admin-interface: django/contrib/admin/views/main.py line 210
        self._field_names = {}
        self.target_collection_prefix=None

    def _setTargetCollectionPrefix(self, prefix_str=None):
        """
        This method enables redirecting the entire model onto another collection.
        Note: this should be used VERY carefully as it will redirect all reading and writing.
        Use prefix_str=None to cancel the redirection.
        E.g. if prefix_str='acme_' and the db name is 'customers', a new db will be used, called 'acme_customers'.
        """
        self.target_collection_prefix=prefix_str

    def _column_name(self, field_name):
        if isinstance(field_name, (list, tuple)):
            return [self._column_name(f) for f in field_name]
        
        if not field_name in self._field_names:
            opt = self.model._meta

            if field_name == 'pk':
                field = opt.pk
            else:
                field = opt.get_field(field_name)
            
            self._field_names[field_name] = field.get_attname_column()[1]
        
        return self._field_names[field_name]

    def clear_ordering(self, force_empty=False):
        self.order_by = []
        if force_empty:
            self.default_ordering = False
        
    def add_ordering(self, ordering):
        if ordering:
            self.order_by.extend(ordering)
        else:
            self.default_ordering = False
        
    def can_filter(self):
        """
        For SQL returns False is result already fetched
        """
        return True
    
    def set_fields(self, fields):
        self.fields.update(set(fields))
    
    def get_fields(self):
        return list(self.fields)
    
    def clone(self):
        obj = self.__class__(self.model)
        obj.where = Node()
        obj.where = deepcopy(self.where)
        obj.order_by = deepcopy(self.order_by)
        obj.fields = deepcopy(self.fields)
        obj.target_collection_prefix = self.target_collection_prefix
        return obj
    
    def add_q(self, q_object):
        """
        Adds a Q-object to the current filter.

        Can also be used to add anything that has an 'add_to_query()' method.
        See also the method / property 'spec', that operates on the tree structure created in this method,
            kept in self.where
        """
        if hasattr(q_object, 'add_to_query'):
            # Complex custom objects are responsible for adding themselves.
            q_object.add_to_query(self)
        else:
            if self.where and q_object.connector != self.AND and len(q_object) > 1:
                self.where.start_subtree(self.AND)
                subtree = True
            else:
                subtree = False
            connector = self.AND
            for child in q_object.children:
                self.where.start_subtree(connector)
                if isinstance(child, Node):
                    self.add_q(child)
                else:
                    mq = MongoQ(False, child[0],child[1])
                    self.where.add(mq,self.COLON)
                self.where.end_subtree()
                connector = q_object.connector
            if q_object.negated:
                self.where.negate()
            if subtree:
                self.where.end_subtree()

    def get_count(self):
        return self.get_result().count()
    
    def has_results(self):
        return bool(self.get_count())
    
    def get_result(self):
        cursor = self.collection.find(self.spec, fields=self.fields and self._column_name(list(self.fields)) or None)

        sort = self.sort
        
        if sort:
            cursor = cursor.sort(sort)
        
        return cursor
    
    def update(self, values):
        data = {}
        for key, val in values.items():
            data[self._column_name(key)] = fix_value(val)

        self.collection.update(self.spec, {"$set": data})
    
    def delete(self):
        self.collection.remove(self.spec)
    
    @property
    def sort(self):
        output = []
        
        default_ordering = self.model._meta.ordering
        
        if not self.order_by and self.default_ordering and default_ordering:
            order_by = default_ordering
        else:
            order_by = self.order_by
        
        for item in order_by:
            if item.startswith('-'):
                output.append((self._column_name(item[1:]), pymongo.DESCENDING))
            else:
                output.append((self._column_name(item), pymongo.ASCENDING))
                
        return output

    def _process_where_node(self, oq, spec):
        if oq.connector != self.DEFAULT:
            if oq.connector == self.COLON:
                q = oq.children[0]
                column_orig = self._column_name(q._get_field_first_part())
                column = q.getDotNotationFieldName(self.model)
                qspec = q.spec(self.model)

                if not column in spec:
                    spec[column] = qspec
                else:
                    filter = spec[column]
                    if not isinstance(spec[column], dict) or not isinstance(qspec, dict):
                        raise MongoQuerySetExeption('Does not support = and <,>,=<,=> in one query (column:%s)' % column)
                    spec[column].update(qspec)
            elif oq.connector == self.AND:
                r = {}
                spec1 = {}
                for a_item in oq.children:
                    if a_item.connector == self.DEFAULT and len(a_item.children) == 1:
                        a_item = a_item.children[0]
                    if a_item.connector != self.DEFAULT:
                        r.update(self._process_where_node(a_item,spec1))
                spec = r
                if oq.negated:
                    spec = negate_where_clause(spec)
            elif oq.connector == self.OR:
                r = []
                for a_item in oq.children:
                    if a_item.connector != self.DEFAULT:
                        spec1 = {}
                        r.append(self._process_where_node(a_item,spec1))
                spec = self._or_operation_optimization({'$or': r})
                if oq.negated:
                    spec = negate_where_clause(spec)
        return spec


    def _or_operation_optimization(self, spec):
        """
        This method creates a very simple optimization to the case where there is a complete $or where statement,
        where all the items are in a form that can be replaced with in $in statement.
        For example, the following can be optimized:
          {'$or': [{'time_on_site': 10.0}, {'time_on_site': 25.0}, {'time_on_site': 275.0}, {'time_on_site': 148.0}]}
        However, the following CAN NOT be optimized:
          {'$or': [{'time_on_site': 10.0}, {'time_on_site': 25.0}, {'time_on_site': 275.0}, {'visit_count': 12.0}]}
          (because not all parameters are identical)
        Returns the original spec if can't be optimized, or a new $in where statement in the format:
          { x : { $in : [ a, b ] } }
        See also http://www.mongodb.org/display/DOCS/OR+operations+in+query+expressions for more information
        """
        param = None
        values = []
        for item in spec['$or']:
            p = item.keys()
            if len(p)!=1:
                return spec
            if param is None:
                param = p[0]
            if param != p[0]:
                return spec
            if isinstance(item[param], dict):
                return spec
            values.append(item[param])

        res = {param: {'$in': values}}
        return res


    @property
    def spec(self):
        if not self.where:
            return None
        
        spec = {}
        
#        for oq in self.where.children:
#            spec = self._process_where_node(oq, spec)
        spec = self._process_where_node(self.where, spec)

        return spec
    
    @property
    def collection(self):
        return get_collection(self.model,self.target_collection_prefix)
    
    def _prepare_before_insert(self, doc):
        if isinstance(doc, (list, tuple)):
            return [self._prepare_before_insert(d) for d in doc]
        
        data = {}
        
        for name, value in doc.items():
            data[self._column_name(name)] = value

        return data
    
    def insert(self, docs):
        return self.collection.insert(self._prepare_before_insert(docs), safe=True)
Exemple #5
0
 def test_add_eq_child_mixed_connector(self):
     node = Node(['a', 'b'], 'OR')
     self.assertEqual(node.add('a', 'AND'), 'a')
     self.assertEqual(node, Node([Node(['a', 'b'], 'OR'), 'a'], 'AND'))
Exemple #6
0
class MongoQuery(object):
    AND = 'AND'
    OR = 'OR'
    COLON = ':'
    DEFAULT = 'DEFAULT'

    def __init__(self, model):
        self.model = model
        self.where = Node()
        self.order_by = []
        self.fields = set()
        self.default_ordering = True
        self.select_related = True  #Hack for admin-interface: django/contrib/admin/views/main.py line 210
        self._field_names = {}
        self.target_collection_prefix = None

    def _setTargetCollectionPrefix(self, prefix_str=None):
        """
        This method enables redirecting the entire model onto another collection.
        Note: this should be used VERY carefully as it will redirect all reading and writing.
        Use prefix_str=None to cancel the redirection.
        E.g. if prefix_str='acme_' and the db name is 'customers', a new db will be used, called 'acme_customers'.
        """
        self.target_collection_prefix = prefix_str

    def _column_name(self, field_name):
        if isinstance(field_name, (list, tuple)):
            return [self._column_name(f) for f in field_name]

        if not field_name in self._field_names:
            opt = self.model._meta

            if field_name == 'pk':
                field = opt.pk
            else:
                field = opt.get_field(field_name)

            self._field_names[field_name] = field.get_attname_column()[1]

        return self._field_names[field_name]

    def clear_ordering(self, force_empty=False):
        self.order_by = []
        if force_empty:
            self.default_ordering = False

    def add_ordering(self, ordering):
        if ordering:
            self.order_by.extend(ordering)
        else:
            self.default_ordering = False

    def can_filter(self):
        """
        For SQL returns False is result already fetched
        """
        return True

    def set_fields(self, fields):
        self.fields.update(set(fields))

    def get_fields(self):
        return list(self.fields)

    def clone(self):
        obj = self.__class__(self.model)
        obj.where = Node()
        obj.where = deepcopy(self.where)
        obj.order_by = deepcopy(self.order_by)
        obj.fields = deepcopy(self.fields)
        obj.target_collection_prefix = self.target_collection_prefix
        return obj

    def add_q(self, q_object):
        """
        Adds a Q-object to the current filter.

        Can also be used to add anything that has an 'add_to_query()' method.
        See also the method / property 'spec', that operates on the tree structure created in this method,
            kept in self.where
        """
        if hasattr(q_object, 'add_to_query'):
            # Complex custom objects are responsible for adding themselves.
            q_object.add_to_query(self)
        else:
            if self.where and q_object.connector != self.AND and len(
                    q_object) > 1:
                self.where.start_subtree(self.AND)
                subtree = True
            else:
                subtree = False
            connector = self.AND
            for child in q_object.children:
                self.where.start_subtree(connector)
                if isinstance(child, Node):
                    self.add_q(child)
                else:
                    mq = MongoQ(False, child[0], child[1])
                    self.where.add(mq, self.COLON)
                self.where.end_subtree()
                connector = q_object.connector
            if q_object.negated:
                self.where.negate()
            if subtree:
                self.where.end_subtree()

    def get_count(self):
        return self.get_result().count()

    def has_results(self):
        return bool(self.get_count())

    def get_result(self):
        cursor = self.collection.find(
            self.spec,
            fields=self.fields and self._column_name(list(self.fields))
            or None)

        sort = self.sort

        if sort:
            cursor = cursor.sort(sort)

        return cursor

    def update(self, values):
        data = {}
        for key, val in values.items():
            data[self._column_name(key)] = fix_value(val)

        self.collection.update(self.spec, {"$set": data})

    def delete(self):
        self.collection.remove(self.spec)

    @property
    def sort(self):
        output = []

        default_ordering = self.model._meta.ordering

        if not self.order_by and self.default_ordering and default_ordering:
            order_by = default_ordering
        else:
            order_by = self.order_by

        for item in order_by:
            if item.startswith('-'):
                output.append(
                    (self._column_name(item[1:]), pymongo.DESCENDING))
            else:
                output.append((self._column_name(item), pymongo.ASCENDING))

        return output

    def _process_where_node(self, oq, spec):
        if oq.connector != self.DEFAULT:
            if oq.connector == self.COLON:
                q = oq.children[0]
                column_orig = self._column_name(q._get_field_first_part())
                column = q.getDotNotationFieldName(self.model)
                qspec = q.spec(self.model)

                if not column in spec:
                    spec[column] = qspec
                else:
                    filter = spec[column]
                    if not isinstance(spec[column], dict) or not isinstance(
                            qspec, dict):
                        raise MongoQuerySetExeption(
                            'Does not support = and <,>,=<,=> in one query (column:%s)'
                            % column)
                    spec[column].update(qspec)
            elif oq.connector == self.AND:
                r = {}
                spec1 = {}
                for a_item in oq.children:
                    if a_item.connector == self.DEFAULT and len(
                            a_item.children) == 1:
                        a_item = a_item.children[0]
                    if a_item.connector != self.DEFAULT:
                        r.update(self._process_where_node(a_item, spec1))
                spec = r
                if oq.negated:
                    spec = negate_where_clause(spec)
            elif oq.connector == self.OR:
                r = []
                for a_item in oq.children:
                    if a_item.connector != self.DEFAULT:
                        spec1 = {}
                        r.append(self._process_where_node(a_item, spec1))
                spec = self._or_operation_optimization({'$or': r})
                if oq.negated:
                    spec = negate_where_clause(spec)
        return spec

    def _or_operation_optimization(self, spec):
        """
        This method creates a very simple optimization to the case where there is a complete $or where statement,
        where all the items are in a form that can be replaced with in $in statement.
        For example, the following can be optimized:
          {'$or': [{'time_on_site': 10.0}, {'time_on_site': 25.0}, {'time_on_site': 275.0}, {'time_on_site': 148.0}]}
        However, the following CAN NOT be optimized:
          {'$or': [{'time_on_site': 10.0}, {'time_on_site': 25.0}, {'time_on_site': 275.0}, {'visit_count': 12.0}]}
          (because not all parameters are identical)
        Returns the original spec if can't be optimized, or a new $in where statement in the format:
          { x : { $in : [ a, b ] } }
        See also http://www.mongodb.org/display/DOCS/OR+operations+in+query+expressions for more information
        """
        param = None
        values = []
        for item in spec['$or']:
            p = item.keys()
            if len(p) != 1:
                return spec
            if param is None:
                param = p[0]
            if param != p[0]:
                return spec
            if isinstance(item[param], dict):
                return spec
            values.append(item[param])

        res = {param: {'$in': values}}
        return res

    @property
    def spec(self):
        if not self.where:
            return None

        spec = {}

        #        for oq in self.where.children:
        #            spec = self._process_where_node(oq, spec)
        spec = self._process_where_node(self.where, spec)

        return spec

    @property
    def collection(self):
        return get_collection(self.model, self.target_collection_prefix)

    def _prepare_before_insert(self, doc):
        if isinstance(doc, (list, tuple)):
            return [self._prepare_before_insert(d) for d in doc]

        data = {}

        for name, value in doc.items():
            data[self._column_name(name)] = value

        return data

    def insert(self, docs):
        return self.collection.insert(self._prepare_before_insert(docs),
                                      safe=True)