"""Base Expression class — separated to avoid circular imports with Column."""
from __future__ import annotations
import abc
from enum import StrEnum
from typing import Any, Literal
[docs]
class LogicalOperator(StrEnum):
"""SQL logical operators."""
AND = "AND"
OR = "OR"
[docs]
class ComparisonOperator(StrEnum):
"""SQL comparison operators."""
EQ = "="
NE = "<>"
GT = ">"
GTE = ">="
LT = "<"
LTE = "<="
[docs]
class Expression(abc.ABC):
"""Base class for SQL expressions."""
[docs]
@abc.abstractmethod
def to_sql(self, params: list[Any]) -> str:
"""Generate SQL string with parameterized values."""
def __and__(self, other: Expression) -> Expression:
from derp.orm.query.expressions import LogicalOp
return LogicalOp(LogicalOperator.AND, (self, other))
def __or__(self, other: Expression) -> Expression:
from derp.orm.query.expressions import LogicalOp
return LogicalOp(LogicalOperator.OR, (self, other))
def __invert__(self) -> Expression:
from derp.orm.query.expressions import UnaryOp
return UnaryOp("NOT", self)
def __eq__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.EQ, to_expr(other))
def __ne__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.NE, to_expr(other))
def __lt__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.LT, to_expr(other))
def __le__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.LTE, to_expr(other))
def __gt__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.GT, to_expr(other))
def __ge__(self, other: Any) -> Any:
from derp.orm.query.expressions import BinaryOp, to_expr
return BinaryOp(self, ComparisonOperator.GTE, to_expr(other))
[docs]
def in_(self, values: Any) -> Any:
from derp.orm.query.expressions import InList, InSubquery
if hasattr(values, "build"):
return InSubquery(self, values, negated=False)
return InList(self, tuple(values), negated=False)
[docs]
def not_in(self, values: Any) -> Any:
from derp.orm.query.expressions import InList, InSubquery
if hasattr(values, "build"):
return InSubquery(self, values, negated=True)
return InList(self, tuple(values), negated=True)
[docs]
def like(self, pattern: str) -> Any:
from derp.orm.query.expressions import Like
return Like(self, pattern, case_insensitive=False)
[docs]
def ilike(self, pattern: str) -> Any:
from derp.orm.query.expressions import Like
return Like(self, pattern, case_insensitive=True)
[docs]
def is_null(self) -> Any:
from derp.orm.query.expressions import NullCheck
return NullCheck(self, is_null=True)
[docs]
def is_not_null(self) -> Any:
from derp.orm.query.expressions import NullCheck
return NullCheck(self, is_null=False)
[docs]
def between(self, low: Any, high: Any) -> Any:
from derp.orm.query.expressions import Between
return Between(self, low, high)
[docs]
def matches(
self,
query: str,
*,
language: str = "english",
style: Literal["websearch", "plain", "phrase"] = "websearch",
stored: bool = False,
) -> Any:
"""Full-text search match using ``@@``.
*stored*: set to ``True`` when the column is a pre-computed
tsvector (skips the ``to_tsvector()`` wrapper).
*style*: ``"websearch"`` (default), ``"plain"``, or ``"phrase"``.
"""
from derp.orm.query.expressions import TSMatch, _TSQueryStyle
return TSMatch(self, query, language, _TSQueryStyle.from_short(style), stored)
[docs]
def ts_rank(
self,
query: str,
*,
language: str = "english",
style: Literal["websearch", "plain", "phrase"] = "websearch",
stored: bool = False,
) -> Any:
"""Full-text search rank for ORDER BY.
*stored*: set to ``True`` when the column is a pre-computed
tsvector (skips the ``to_tsvector()`` wrapper).
"""
from derp.orm.query.expressions import TSRank, _TSQueryStyle
return TSRank(self, query, language, _TSQueryStyle.from_short(style), stored)
[docs]
def ts_headline(
self,
query: str,
*,
language: str = "english",
style: Literal["websearch", "plain", "phrase"] = "websearch",
max_words: int | None = None,
min_words: int | None = None,
max_fragments: int | None = None,
start_sel: str | None = None,
stop_sel: str | None = None,
fragment_delimiter: str | None = None,
highlight_all: bool | None = None,
short_word: int | None = None,
) -> Any:
"""Highlighted search snippet for display in results."""
from derp.orm.query.expressions import TSHeadline, _TSQueryStyle
opts = {
"max_words": max_words,
"min_words": min_words,
"max_fragments": max_fragments,
"start_sel": start_sel,
"stop_sel": stop_sel,
"fragment_delimiter": fragment_delimiter,
"highlight_all": highlight_all,
"short_word": short_word,
}
return TSHeadline(self, query, language, _TSQueryStyle.from_short(style), opts)