def test_expressions(self): self.assertEqual( repr(Case(When(a=1))), "<Case: CASE WHEN <Q: (AND: ('a', 1))> THEN Value(None), ELSE Value(None)>" ) self.assertEqual(repr(Col('alias', 'field')), "Col(alias, field)") self.assertEqual(repr(Date('published', 'exact')), "Date(published, exact)") self.assertEqual(repr(DateTime('published', 'exact', utc)), "DateTime(published, exact, %s)" % utc) self.assertEqual(repr(F('published')), "F(published)") self.assertEqual(repr(F('cost') + F('tax')), "<CombinedExpression: F(cost) + F(tax)>") self.assertEqual( repr(ExpressionWrapper( F('cost') + F('tax'), models.IntegerField())), "ExpressionWrapper(F(cost) + F(tax))") self.assertEqual(repr(Func('published', function='TO_CHAR')), "Func(F(published), function=TO_CHAR)") self.assertEqual(repr(OrderBy(Value(1))), 'OrderBy(Value(1), descending=False)') self.assertEqual(repr(Random()), "Random()") self.assertEqual(repr(RawSQL('table.col', [])), "RawSQL(table.col, [])") self.assertEqual(repr(Ref('sum_cost', Sum('cost'))), "Ref(sum_cost, Sum(F(cost)))") self.assertEqual(repr(Value(1)), "Value(1)")
def resolve_ref_temp_fix(self, name, allow_joins=True, reuse=None, summarize=False): if not allow_joins and LOOKUP_SEP in name: raise FieldError( "Joined field references are not permitted in this query") if name in self.annotations: if summarize: # Summarize currently means we are doing an aggregate() query # which is executed as a wrapped subquery if any of the # aggregate() elements reference an existing annotation. In # that case we need to return a Ref to the subquery's annotation. return Ref(name, self.annotation_select[name]) else: return self.annotations[name] else: field_list = name.split(LOOKUP_SEP) field, sources, opts, join_list, path = self.setup_joins( field_list, self.get_meta(), self.get_initial_alias(), reuse) targets, _, join_list = self.trim_joins(sources, join_list, path) if len(targets) > 1: raise FieldError("Referencing multicolumn fields with F() objects " "isn't supported") if reuse is not None: reuse.update(join_list) col = targets[0].get_col(join_list[-1], sources[0]) return col
def resolve_ref(self, name, allow_joins=True, reuse=None, summarize=False, *args, **kwargs): # This method is used to resolve field names in complex expressions. If # a queryable property is used in such an expression, it needs to be # auto-annotated (while taking the stack into account) and returned. query_path = QueryPath(name) if self._queryable_property_stack: query_path = self._queryable_property_stack[ -1].relation_path + query_path property_annotation = self._auto_annotate(query_path, full_group_by=ValuesQuerySet is not None) if property_annotation: if summarize: # Outer queries for aggregations need refs to annotations of # the inner queries. from django.db.models.expressions import Ref return Ref(name, property_annotation) return property_annotation return super(QueryablePropertiesQueryMixin, self).resolve_ref(name, allow_joins, reuse, summarize, *args, **kwargs)
def get_order_by(self): """ See original get_group_by at django.db.models.sql.compiler>SQLCompiler """ if self.query.extra_order_by: ordering = self.query.extra_order_by elif not self.query.default_ordering: ordering = self.query.order_by else: ordering = (self.query.order_by or self.query.get_meta().ordering or []) if self.query.standard_ordering: asc, desc = ORDER_DIR['ASC'] else: asc, desc = ORDER_DIR['DESC'] order_by = [] for field in ordering: if hasattr(field, 'resolve_expression'): if not isinstance(field, OrderBy): field = field.asc() if not self.query.standard_ordering: field.reverse_ordering() order_by.append((field, False)) continue if field == '?': # random order_by.append((OrderBy(Random()), False)) continue col, order = get_order_dir(field, asc) descending = True if order == 'DESC' else False if col in self.query.annotation_select: # Reference to expression in SELECT clause order_by.append((OrderBy(Ref(col, self.query.annotation_select[col]), descending=descending), True)) continue if col in self.query.annotations: # References to an expression which is masked out of the SELECT clause order_by.append((OrderBy(self.query.annotations[col], descending=descending), False)) continue if '.' in field: # This came in through an extra(order_by=...) addition. Pass it # on verbatim. table, col = col.split('.', 1) order_by.append((OrderBy(RawSQL( '%s.%s' % (self.quote_name_unless_alias(table), col), []), descending=descending), False)) continue if not self.query._extra or col not in self.query._extra: # 'col' is of the form 'field' or 'field1__field2' or # '-field1__field2__field', etc. order_by.extend( self.find_ordering_name(field, self.query.get_meta(), default_order=asc)) else: if col not in self.query.extra_select: order_by.append((OrderBy(RawSQL(*self.query.extra[col]), descending=descending), False)) else: order_by.append((OrderBy(Ref(col, RawSQL(*self.query.extra[col])), descending=descending), True)) result = [] # changed from set() to [] seen = [] for expr, is_ref in order_by: if self.query.combinator: src = expr.get_source_expressions()[0] # Relabel order by columns to raw numbers if this is a combined # query; necessary since the columns can't be referenced by the # fully qualified name and the simple column names may collide. for idx, (sel_expr, _, col_alias) in enumerate(self.select): if is_ref and col_alias == src.refs: src = src.source elif col_alias: continue if src == sel_expr: expr.set_source_expressions([RawSQL('%d' % (idx + 1), ())]) break else: raise DatabaseError( 'ORDER BY term does not match any column in the result set.' ) resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None) sql, params = self.compile(resolved) # Don't add the same column twice, but the order direction is # not taken into account so we strip it. When this entire method # is refactored into expressions, then we can check each part as we # generate it. without_ordering = self.ordering_parts.search(sql).group(1) if (without_ordering, tuple(params)) in seen: continue # changed from add to append seen.append((without_ordering, tuple(params))) result.append((resolved, (sql, params, is_ref))) return result
def get_order_by(self): """ Returns a list of 2-tuples of form (expr, (sql, params)) for the ORDER BY clause. The order_by clause can alter the select clause (for example it can add aliases to clauses that do not yet have one, or it can add totally new select clauses). """ if self.query.extra_order_by: ordering = self.query.extra_order_by elif not self.query.default_ordering: ordering = self.query.order_by else: ordering = (self.query.order_by or self.query.get_meta().ordering or []) if self.query.standard_ordering: asc, desc = ORDER_DIR['ASC'] else: asc, desc = ORDER_DIR['DESC'] order_by = [] for pos, field in enumerate(ordering): if hasattr(field, 'resolve_expression'): if not isinstance(field, OrderBy): field = field.asc() if not self.query.standard_ordering: field.reverse_ordering() order_by.append((field, False)) continue if field == '?': # random order_by.append((OrderBy(Random()), False)) continue col, order = get_order_dir(field, asc) descending = True if order == 'DESC' else False if col in self.query.annotation_select: order_by.append( (OrderBy(Ref(col, self.query.annotation_select[col]), descending=descending), True)) continue if '.' in field: # This came in through an extra(order_by=...) addition. Pass it # on verbatim. table, col = col.split('.', 1) order_by.append((OrderBy(RawSQL( '%s.%s' % (self.quote_name_unless_alias(table), col), []), descending=descending), False)) continue if not self.query._extra or col not in self.query._extra: # 'col' is of the form 'field' or 'field1__field2' or # '-field1__field2__field', etc. order_by.extend( self.find_ordering_name(field, self.query.get_meta(), default_order=asc)) else: if col not in self.query.extra_select: order_by.append((OrderBy(RawSQL(*self.query.extra[col]), descending=descending), False)) else: order_by.append( (OrderBy(Ref(col, RawSQL(*self.query.extra[col])), descending=descending), True)) result = [] seen = set() for expr, is_ref in order_by: resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None) sql, params = self.compile(resolved) # Don't add the same column twice, but the order direction is # not taken into account so we strip it. When this entire method # is refactored into expressions, then we can check each part as we # generate it. without_ordering = self.ordering_parts.search(sql).group(1) if (without_ordering, tuple(params)) in seen: continue seen.add((without_ordering, tuple(params))) result.append((resolved, (sql, params, is_ref))) return result
def get_order_by(self): """ Return a list of 2-tuples of form (expr, (sql, params, is_ref)) for the ORDER BY clause. The order_by clause can alter the select clause (for example it can add aliases to clauses that do not yet have one, or it can add totally new select clauses). """ if self.query.extra_order_by: ordering = self.query.extra_order_by elif not self.query.default_ordering: ordering = self.query.order_by elif self.query.order_by: ordering = self.query.order_by elif self.query.get_meta().ordering: ordering = self.query.get_meta().ordering self._meta_ordering = ordering else: ordering = [] if self.query.standard_ordering: asc, desc = ORDER_DIR['ASC'] else: asc, desc = ORDER_DIR['DESC'] order_by = [] for field in ordering: if hasattr(field, 'resolve_expression'): if isinstance(field, Value): # output_field must be resolved for constants. field = Cast(field, field.output_field) if not isinstance(field, OrderBy): field = field.asc() if not self.query.standard_ordering: field = field.copy() field.reverse_ordering() order_by.append((field, True)) else: order_by.append((field, False)) continue if field == '?': # random order_by.append((OrderBy(Random()), False)) continue col, order = get_order_dir(field, asc) descending = order == 'DESC' if col in self.query.annotation_select: # Reference to expression in SELECT clause order_by.append( (OrderBy(Ref(col, self.query.annotation_select[col]), descending=descending), True)) continue if col in self.query.annotations: # References to an expression which is masked out of the SELECT # clause. if self.query.combinator and self.select: # Don't use the resolved annotation because other # combined queries might define it differently. expr = F(col) else: expr = self.query.annotations[col] if isinstance(expr, Value): # output_field must be resolved for constants. expr = Cast(expr, expr.output_field) order_by.append((OrderBy(expr, descending=descending), False)) continue if '.' in field: # This came in through an extra(order_by=...) addition. Pass it # on verbatim. table, col = col.split('.', 1) order_by.append((OrderBy(RawSQL( '%s.%s' % (self.quote_name_unless_alias(table), col), []), descending=descending), False)) continue if not self.query.extra or col not in self.query.extra: if self.query.combinator and self.select: # Don't use the first model's field because other # combined queries might define it differently. order_by.append((OrderBy(F(col), descending=descending), False)) else: # 'col' is of the form 'field' or 'field1__field2' or # '-field1__field2__field', etc. order_by.extend( self.find_ordering_name(field, self.query.get_meta(), default_order=asc)) else: if col not in self.query.extra_select: order_by.append((OrderBy(RawSQL(*self.query.extra[col]), descending=descending), False)) else: order_by.append( (OrderBy(Ref(col, RawSQL(*self.query.extra[col])), descending=descending), True)) result = [] seen = set() for expr, is_ref in order_by: resolved = expr.resolve_expression(self.query, allow_joins=True, reuse=None) if self.query.combinator and self.select: src = resolved.get_source_expressions()[0] expr_src = expr.get_source_expressions()[0] # Relabel order by columns to raw numbers if this is a combined # query; necessary since the columns can't be referenced by the # fully qualified name and the simple column names may collide. for idx, (sel_expr, _, col_alias) in enumerate(self.select): if is_ref and col_alias == src.refs: src = src.source elif col_alias and not (isinstance(expr_src, F) and col_alias == expr_src.name): continue if src == sel_expr: resolved.set_source_expressions( [RawSQL('%d' % (idx + 1), ())]) break else: if col_alias: raise DatabaseError( 'ORDER BY term does not match any column in the result set.' ) # Add column used in ORDER BY clause without an alias to # the selected columns. order_by_idx = len(self.query.select) + 1 col_name = f'__orderbycol{order_by_idx}' for q in self.query.combined_queries: q.add_annotation(expr_src, col_name) self.query.add_select_col(src, col_name) resolved.set_source_expressions( [RawSQL(f'{order_by_idx}', ())]) sql, params = self.compile(resolved) # Don't add the same column twice, but the order direction is # not taken into account so we strip it. When this entire method # is refactored into expressions, then we can check each part as we # generate it. without_ordering = self.ordering_parts.search(sql)[1] params_hash = make_hashable(params) if (without_ordering, params_hash) in seen: continue seen.add((without_ordering, params_hash)) result.append((resolved, (sql, params, is_ref))) return result