Repository: django/django
File: django/contrib/postgres/search.py (Python)

Code before:
        if weight is not None and (
            not isinstance(weight, str) or weight.lower() not in {"a", "b", "c", "d"}
        ):
            raise ValueError(
                f"Weight must be one of 'A', 'B', 'C', and 'D', got '{weight}'."
            )

        self.prefix = prefix
        self.invert = invert
        self.weight = weight
        super().__init__(value, output_field=output_field)

    def process_rhs(self, compiler, connection):
        escaped_value = psql_escape(self.value)
        if is_psycopg3:
            from psycopg.adapt import Dumper

            class StringDumper(Dumper):
                def dump(self, obj):
                    return bytes(obj, "utf-8")

            param = StringDumper(str).quote(escaped_value).decode()
        else:
            from psycopg2.extensions import adapt

            param = adapt(escaped_value).getquoted().decode("latin-1")

        label = ""
        if self.prefix:
            label += "*"
        if self.weight:
            label += self.weight

        if label:
            param = f"{param}:{label}"
        if self.invert:
            param = f"!{param}"

        return "%s", (param,)

    def as_sql(self, compiler, connection):
        return self.process_rhs(compiler, connection)

    def __invert__(self):
        cloned = super().copy()
        cloned.invert = not self.invert
        return cloned


class CombinedLexeme(LexemeCombinable, CombinedExpression):
    _output_field = SearchQueryField()

Review comment (bug):
`UnicodeEncodeError` escapes for emoji. I'm assuming that's fine, but just wondering if you think there's a place we should document this since it applies for the psycopg2 implementation only.

Or, perhaps we should fail at the python level with an informative exception if a non-utf-8 char is used, since adding emoji seems to be ignored by postgres (no tests fail when I adjust the prefixes to end with emoji).

Code after:

    def __and__(self, other):
        return self._combine(other, self.BITAND, False)

    def __rand__(self, other):
        return self._combine(other, self.BITAND, True)


class Lexeme(LexemeCombinable, Value):
    _output_field = SearchQueryField()

    def __init__(
        self, value, output_field=None, *, invert=False, prefix=False, weight=None
    ):
        if value == "":
            raise ValueError("Lexeme value cannot be empty.")

        if not isinstance(value, str):
            raise TypeError(
                f"Lexeme value must be a string, got {value.__class__.__name__}."
            )

        if weight is not None and (
            not isinstance(weight, str) or weight.lower() not in {"a", "b", "c", "d"}
        ):
            raise ValueError(
                f"Weight must be one of 'A', 'B', 'C', and 'D', got {weight!r}."
            )

        self.prefix = prefix
        self.invert = invert
        self.weight = weight
        super().__init__(value, output_field=output_field)

    def as_sql(self, compiler, connection):
        param = quote_lexeme(self.value)
        label = ""
        if self.prefix:
            label += "*"
        if self.weight:
            label += self.weight

        if label:
            param = f"{param}:{label}"
        if self.invert:
            param = f"!{param}"

        return "%s", (param,)

    def __invert__(self):
        cloned = self.copy()