def test_nested_query(): expected = Exact( ExpressionWrapper(F('char_field'), output_field=models.CharField()), Value('')) query = Q(Q(char_field='')) expanded = expand_query(FakeModel, query) assert are_equal(expected, expanded)
def test_negated_empty_as_a_sibling(): expected = Exact( ExpressionWrapper(F('int_field'), output_field=IntegerField()), Value(1)) query = Q(~Q(), Q(int_field=1)) expanded = expand_query(FakeModel, query) assert are_equal(expected, expanded) assert wrap(expanded).as_python(dict(int_field=1))
def test_combining_empty_with_query(): expected = Exact( ExpressionWrapper(F('int_field'), output_field=IntegerField()), Value(1)) query = Q() & Q(int_field=1) expanded = expand_query(FakeModel, query) assert are_equal(expected, expanded) assert wrap(expanded).as_python(dict(int_field=1))
def test_and_query(): expected = And( Exact( ExpressionWrapper(F('char_field'), output_field=models.CharField()), Value('')), Exact( ExpressionWrapper(F('int_field'), output_field=models.IntegerField()), Value(1)), ) queries = [ # Q(char_field='', int_field=1), # Order is not guaranteed! Q(Q(char_field=''), int_field=1), Q(Q(char_field=''), Q(int_field=1)), Q(char_field='') & Q(int_field=1), ] for query in queries: expanded = expand_query(FakeModel, query) assert are_equal(expected, expanded)
def validate(self, model, instance, exclude=None, using=DEFAULT_DB_ALIAS): queryset = model._default_manager.using(using) if self.fields: lookup_kwargs = {} for field_name in self.fields: if exclude and field_name in exclude: return field = model._meta.get_field(field_name) lookup_value = getattr(instance, field.attname) if lookup_value is None or ( lookup_value == "" and connections[using].features.interprets_empty_strings_as_nulls ): # A composite constraint containing NULL value cannot cause # a violation since NULL != NULL in SQL. return lookup_kwargs[field.name] = lookup_value queryset = queryset.filter(**lookup_kwargs) else: # Ignore constraints with excluded fields. if exclude: for expression in self.expressions: for expr in expression.flatten(): if isinstance(expr, F) and expr.name in exclude: return replacement_map = instance._get_field_value_map( meta=model._meta, exclude=exclude ) expressions = [ Exact(expr, expr.replace_references(replacement_map)) for expr in self.expressions ] queryset = queryset.filter(*expressions) model_class_pk = instance._get_pk_val(model._meta) if not instance._state.adding and model_class_pk is not None: queryset = queryset.exclude(pk=model_class_pk) if not self.condition: if queryset.exists(): if self.expressions: raise ValidationError(self.get_violation_error_message()) # When fields are defined, use the unique_error_message() for # backward compatibility. for model, constraints in instance.get_constraints(): for constraint in constraints: if constraint is self: raise ValidationError( instance.unique_error_message(model, self.fields) ) else: against = instance._get_field_value_map(meta=model._meta, exclude=exclude) try: if (self.condition & Exists(queryset.filter(self.condition))).check( against, using=using ): raise ValidationError(self.get_violation_error_message()) except FieldError: pass
def test_negated_query(): expected = Not( Exact( ExpressionWrapper(F('char_field'), output_field=models.CharField()), Value(''))) query_1 = ~Q(char_field='') query_2 = Q(~Q(char_field='')) expanded_1 = expand_query(FakeModel, query_1) expanded_2 = expand_query(FakeModel, query_2) assert are_equal(expected, expanded_1) assert are_equal(expected, expanded_2)
def test_lookup_comparison() -> None: q1 = Exact( Lower( ExpressionWrapper(F('char_field'), output_field=models.CharField())), Value('')) q2 = Exact( Lower( ExpressionWrapper(F('char_field'), output_field=models.CharField())), Value('')) q3 = GreaterThan( ExpressionWrapper(F('int_field'), output_field=models.IntegerField()), Value(2)) q4 = Exact( Lower( ExpressionWrapper(F('char_field'), output_field=models.TextField())), Value('')) assert are_equal(q1, q2) assert not are_equal(q1, q3) assert not are_equal(q1, q4)
def test_char_expression() -> None: expected = Exact( Lower( ExpressionWrapper(F('char_field'), output_field=models.CharField())), Value('')) query_1 = Q(char_field__lower='') query_2 = Q(char_field__lower__exact='') expanded_1 = expand_query(FakeModel, query_1) expanded_2 = expand_query(FakeModel, query_2) assert are_equal(expected, expanded_1) assert are_equal(expected, expanded_2)
def is_single_row_update(self): where = self.query.where match = getattr(self.query, 'match', {}) node = None if len(where.children) == 1: node = where.children[0] elif match: meta = self.query.model._meta pk_match = match.get(meta.pk.attname) if pk_match is not None: pk_value = list(pk_match.dict.keys())[0] return Exact(meta.pk.get_col(meta.db_table), pk_value) if not isinstance(node, Exact): node = None elif not node.lhs.field.primary_key: node = None return node
def as_sql(self, compiler, connection): """ Return the SQL version of the where clause and the value to be substituted in. Return '', [] if this node matches everything, None, [] if this node is empty, and raise EmptyResultSet if this node can't match anything. """ result = [] result_params = [] if self.connector == AND: full_needed, empty_needed = len(self.children), 1 else: full_needed, empty_needed = 1, len(self.children) if self.connector == XOR and not connection.features.supports_logical_xor: # Convert if the database doesn't support XOR: # a XOR b XOR c XOR ... # to: # (a OR b OR c OR ...) AND (a + b + c + ...) == 1 lhs = self.__class__(self.children, OR) rhs_sum = reduce( operator.add, (Case(When(c, then=1), default=0) for c in self.children), ) rhs = Exact(1, rhs_sum) return self.__class__([lhs, rhs], AND, self.negated).as_sql(compiler, connection) for child in self.children: try: sql, params = compiler.compile(child) except EmptyResultSet: empty_needed -= 1 else: if sql: result.append(sql) result_params.extend(params) else: full_needed -= 1 # Check if this node matches nothing or everything. # First check the amount of full nodes and empty nodes # to make this node empty/full. # Now, check if this node is full/empty using the # counts. if empty_needed == 0: if self.negated: return "", [] else: raise EmptyResultSet if full_needed == 0: if self.negated: raise EmptyResultSet else: return "", [] conn = " %s " % self.connector sql_string = conn.join(result) if sql_string: if self.negated: # Some backends (Oracle at least) need parentheses # around the inner SQL in the negated case, even if the # inner SQL contains just a single expression. sql_string = "NOT (%s)" % sql_string elif len(result) > 1 or self.resolved: sql_string = "(%s)" % sql_string return sql_string, result_params