Repository: django/django
File: django/db/models/lookups.py (Python)

Code before:

    get_db_prep_lookup_value_is_iterable = True

    def get_prep_lookup(self):
        if hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        contains_expr = False
        prepared_values = []
        for rhs_value in self.rhs:
            if hasattr(rhs_value, "resolve_expression"):
                # An expression will be handled by the database but can coexist
                # alongside real values.
                contains_expr = True
            elif (
                self.prepare_rhs
                and hasattr(self.lhs, "output_field")
                and hasattr(self.lhs.output_field, "get_prep_value")
            ):
                rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
            prepared_values.append(rhs_value)
        if contains_expr:
            return ExpressionList(
                *[
                    (
                        Value(prep_value, self.lhs.output_field)
                        if not hasattr(prep_value, "as_sql")
                        else prep_value
                    )
                    for prep_value in prepared_values
                ]
            )
        return prepared_values

    def process_rhs(self, compiler, connection):
        if self.rhs_is_direct_value():
            # rhs should be an iterable of values. Use batch_process_rhs()
            # to prepare/transform those values.
            return self.batch_process_rhs(compiler, connection)
        elif isinstance(self.rhs, ExpressionList):
            # rhs contains at least one expression. Unwrap them and delegate
            # to batch_process_rhs() to prepare/transform those values.
            copy = self.copy()
            copy.rhs = self.rhs.get_source_expressions()
            return copy.process_rhs(compiler, connection)
        else:
            return super().process_rhs(compiler, connection)

    def resolve_expression_parameter(self, compiler, connection, sql, param):
        params = [param]
        if hasattr(param, "resolve_expression"):
            param = param.resolve_expression(compiler.query)

Review comment (bug):
Can you say more about how you worked out this should check for compilables and not resolvables? I get no failures when I change this to `"resolve_expression"`.

Code after:

    get_db_prep_lookup_value_is_iterable = True

    def get_prep_lookup(self):
        if hasattr(self.rhs, "resolve_expression"):
            return self.rhs
        if any(hasattr(value, "resolve_expression") for value in self.rhs):
            # Wrap direct values in Value expressions so they are handled by
            # the database at compilation time, along with other expressions.
            return ExpressionList(
                *[
                    (
                        value
                        if hasattr(value, "resolve_expression")
                        else Value(value, self.lhs.output_field)
                    )
                    for value in self.rhs
                ]
            )
        prepared_values = []
        for rhs_value in self.rhs:
            if (
                self.prepare_rhs
                and hasattr(self.lhs, "output_field")
                and hasattr(self.lhs.output_field, "get_prep_value")
            ):
                rhs_value = self.lhs.output_field.get_prep_value(rhs_value)
            prepared_values.append(rhs_value)
        return prepared_values

    def process_rhs(self, compiler, connection):
        if self.rhs_is_direct_value():
            # rhs should be an iterable of values. Use batch_process_rhs()
            # to prepare/transform those values.
            return self.batch_process_rhs(compiler, connection)
        elif isinstance(self.rhs, ExpressionList):
            # rhs contains at least one expression. Unwrap them and delegate
            # to batch_process_rhs() to prepare/transform those values.
            copy = self.copy()
            copy.rhs = self.rhs.get_source_expressions()
            return copy.process_rhs(compiler, connection)
        else:
            return super().process_rhs(compiler, connection)

    def resolve_expression_parameter(self, compiler, connection, sql, param):
        params = [param]
        if hasattr(param, "resolve_expression"):
            param = param.resolve_expression(compiler.query)
        if hasattr(param, "as_sql"):
            sql, params = compiler.compile(param)
        return sql, params