def _fixMaybeFilters(projectPreds): from itertools import groupby projectPreds.sort() for k, v in groupby(projectPreds, lambda v: (v[0], v[1])): v = list(v) if all(not ismaybe for (joinname, projectname, ismaybe, pred) in v): continue # no MAYBEs for this project # these are filters that have a predicate equivalent to the project # if the predicate isn't the maybe one, remove it # if it is and the filter has another predicate that not a simple selector # or tries to match null, move the maybe predicate to its own filter # otherwise make sure jointype = 'l' for joinname, projectname, ismaybe, pred in v: filter = pred.parent if not filter or not filter.parent: continue separate = False assert len(pred.siblings) <= 1 for sib in pred.siblings: # if not simpleSelector(other): separate = True # XXX refactor with SimpleEngine._findSimplePredicates() if not isinstance(sib, Eq): separate = True break elif isinstance(sib.left, Project): other = sib.right elif isinstance(sib.right, Project): other = sib.left else: separate = True break if not other.isIndependent(): separate = True elif other == Constant(None): separate = True if separate: filter.complexPredicates = True filter.removeLabel(projectname, OBJECT) for project in sib.depthfirst(): if isinstance(project, Project) and project.name == OBJECT: # convert OBJECT to name project.fields = [projectname] # if mabye move pred to separate join condition # otherwise just remove it if not ismaybe: pred.parent = None else: filter.parent.parent.appendArg(JoinConditionOp(Filter(pred, objectlabel=projectname), join="l")) elif ismaybe: if filter.parent.join not in ["i", "l"]: raise QueryException('property with "maybe" can not be used here', filter.parent) filter.parent.join = "l"
def buildJoins(self, root): # combine joins that have the same label: joins, removedJoins = self._joinLabeledJoins() validateTree(root) joinsInDocOrder = [] self._findJoinsInDocOrder(root, joinsInDocOrder, removedJoins) assert not self.orphanedJoins, "orphaned joins left-over: %s" % self.orphanedJoins assert set(joinsInDocOrder) == set(joins), "missing join in doc: %s" % (set(joins) - set(joinsInDocOrder)) joinsInDocOrder, simpleJoins, complexJoins = self._findJoinPreds(root, joinsInDocOrder) # next, in reverse document order (i.e. start with the most nested joins) # join together simpleJoinPreds that reference each other # need to follow this order to ensure that the outermost joins are on the # left-side otherwise right outer joins (the "maybe" operator) will break validateTree(*joinsInDocOrder) joinFromLabel = {} for join in joinsInDocOrder: joinFromLabel[join.name] = join for i in xrange(len(joinsInDocOrder) - 1, -1, -1): simpleJoins = self._makeSimpleJoins(joinsInDocOrder[i:], joinFromLabel, simpleJoins) assert not simpleJoins # now join together complexJoins as crossjoins # and set the filter as a complex predicate for filter, refs, ismaybe in complexJoins: if ismaybe: raise QueryException("MAYBE operation not supported on complex join predicates") filter.complexPredicates = True # find parent join to join with: give preference to one # that already joined with another join parentjoin = None otherjoins = [] for name in refs: candidate = joinFromLabel.get(name) if not candidate: raise QueryException("unreferenced label '%s' in complex join" % name) if candidate.parent and not isinstance(candidate.parent, Select): candidate = getTopJoin(candidate, root.where) if parentjoin and parentjoin is not candidate: raise QueryException( "Not supported: complex join that" "joins more than one join that is already joined" ) else: parentjoin = candidate elif candidate is root.where: parentjoin = candidate else: otherjoins.append(candidate) for join in otherjoins: if not parentjoin: if root.where: parentjoin = root.where else: if filter.parent.parent is not join: join.appendArg(filter) # move filter to parentjoin root.appendArg(join) if parentjoin: assert parentjoin is not join, (join.name, root) parentjoin.appendArg(JoinConditionOp(join, filter, "x")) # next, make any non-empty labeled joins that we haven't yet merged # into another join a cross-join for join in joinsInDocOrder: if join.maybe: raise QueryException("MAYBE operation not supported on uncorrelated filter sets") if not join.parent and join.args: if not root.where: root.appendArg(join) else: root.where.appendArg(JoinConditionOp(join, join.name, "x")) assert all(join.parent or not join.args for join in joinsInDocOrder) # finally, removed empty joins from nested selects for join in joinsInDocOrder: if isinstance(join.parent, Select) and join.parent.parent and not join.args: join.parent = None