| @@ -55,57 +55,6 @@ class SimplificationVisitor(QNodeVisitor): | ||||
|         return combined_query | ||||
|  | ||||
|  | ||||
| class QueryTreeTransformerVisitor(QNodeVisitor): | ||||
|     """Transforms the query tree in to a form that may be used with MongoDB. | ||||
|     """ | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         if combination.operation == combination.AND: | ||||
|             # MongoDB doesn't allow us to have too many $or operations in our | ||||
|             # queries, so the aim is to move the ORs up the tree to one | ||||
|             # 'master' $or. Firstly, we must find all the necessary parts (part | ||||
|             # of an AND combination or just standard Q object), and store them | ||||
|             # separately from the OR parts. | ||||
|             or_groups = [] | ||||
|             and_parts = [] | ||||
|             for node in combination.children: | ||||
|                 if isinstance(node, QCombination): | ||||
|                     if node.operation == node.OR: | ||||
|                         # Any of the children in an $or component may cause | ||||
|                         # the query to succeed | ||||
|                         or_groups.append(node.children) | ||||
|                     elif node.operation == node.AND: | ||||
|                         and_parts.append(node) | ||||
|                 elif isinstance(node, Q): | ||||
|                     and_parts.append(node) | ||||
|  | ||||
|             # Now we combine the parts into a usable query. AND together all of | ||||
|             # the necessary parts. Then for each $or part, create a new query | ||||
|             # that ANDs the necessary part with the $or part. | ||||
|             clauses = [] | ||||
|             for or_group in product(*or_groups): | ||||
|                 q_object = reduce(lambda a, b: a & b, and_parts, Q()) | ||||
|                 q_object = reduce(lambda a, b: a & b, or_group, q_object) | ||||
|                 clauses.append(q_object) | ||||
|             # Finally, $or the generated clauses in to one query. Each of the | ||||
|             # clauses is sufficient for the query to succeed. | ||||
|             return reduce(lambda a, b: a | b, clauses, Q()) | ||||
|  | ||||
|         if combination.operation == combination.OR: | ||||
|             children = [] | ||||
|             # Crush any nested ORs in to this combination as MongoDB doesn't | ||||
|             # support nested $or operations | ||||
|             for node in combination.children: | ||||
|                 if (isinstance(node, QCombination) and | ||||
|                     node.operation == combination.OR): | ||||
|                     children += node.children | ||||
|                 else: | ||||
|                     children.append(node) | ||||
|             combination.children = children | ||||
|  | ||||
|         return combination | ||||
|  | ||||
|  | ||||
| class QueryCompilerVisitor(QNodeVisitor): | ||||
|     """Compiles the nodes in a query tree to a PyMongo-compatible query | ||||
|     dictionary. | ||||
| @@ -115,45 +64,14 @@ class QueryCompilerVisitor(QNodeVisitor): | ||||
|         self.document = document | ||||
|  | ||||
|     def visit_combination(self, combination): | ||||
|         operator = "$and" | ||||
|         if combination.operation == combination.OR: | ||||
|             return {'$or': combination.children} | ||||
|         elif combination.operation == combination.AND: | ||||
|             return self._mongo_query_conjunction(combination.children) | ||||
|         return combination | ||||
|             operator = "$or" | ||||
|         return {operator: combination.children} | ||||
|  | ||||
|     def visit_query(self, query): | ||||
|         return transform.query(self.document, **query.query) | ||||
|  | ||||
|     def _mongo_query_conjunction(self, queries): | ||||
|         """Merges Mongo query dicts - effectively &ing them together. | ||||
|         """ | ||||
|         combined_query = {} | ||||
|         for query in queries: | ||||
|             for field, ops in query.items(): | ||||
|                 if field not in combined_query: | ||||
|                     combined_query[field] = ops | ||||
|                 else: | ||||
|                     # The field is already present in the query the only way | ||||
|                     # we can merge is if both the existing value and the new | ||||
|                     # value are operation dicts, reject anything else | ||||
|                     if (not isinstance(combined_query[field], dict) or | ||||
|                         not isinstance(ops, dict)): | ||||
|                         message = 'Conflicting values for ' + field | ||||
|                         raise InvalidQueryError(message) | ||||
|  | ||||
|                     current_ops = set(combined_query[field].keys()) | ||||
|                     new_ops = set(ops.keys()) | ||||
|                     # Make sure that the same operation isn't applied more than | ||||
|                     # once to a single field | ||||
|                     intersection = current_ops.intersection(new_ops) | ||||
|                     if intersection: | ||||
|                         msg = 'Duplicate query conditions: ' | ||||
|                         raise InvalidQueryError(msg + ', '.join(intersection)) | ||||
|  | ||||
|                     # Right! We've got two non-overlapping dicts of operations! | ||||
|                     combined_query[field].update(copy.deepcopy(ops)) | ||||
|         return combined_query | ||||
|  | ||||
|  | ||||
| class QNode(object): | ||||
|     """Base class for nodes in query trees. | ||||
| @@ -164,7 +82,6 @@ class QNode(object): | ||||
|  | ||||
|     def to_query(self, document): | ||||
|         query = self.accept(SimplificationVisitor()) | ||||
|         query = query.accept(QueryTreeTransformerVisitor()) | ||||
|         query = query.accept(QueryCompilerVisitor(document)) | ||||
|         return query | ||||
|  | ||||
| @@ -205,7 +122,8 @@ class QCombination(QNode): | ||||
|             # If the child is a combination of the same type, we can merge its | ||||
|             # children directly into this combinations children | ||||
|             if isinstance(node, QCombination) and node.operation == operation: | ||||
|                 self.children += node.children | ||||
|                 # self.children += node.children | ||||
|                 self.children.append(node) | ||||
|             else: | ||||
|                 self.children.append(node) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user