Source code for neosqlite.collection.expr_evaluator.sql_converters.logical
"""SQL converters for logical operators."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
pass
from .base import BaseSqlMixin
[docs]
class LogicalMixin(BaseSqlMixin):
"""$and / $or / $not / $nor → SQL."""
[docs]
def _convert_logical_operator(
self, operator: str, operands: Any
) -> tuple[str, list[Any]]:
"""Convert logical operators ($and, $or, $not, $nor) to SQL."""
# Normalize operands for $not to handle both formats:
# {$not: {expression}} and {$not: [expression]}
if operator == "$not" and not isinstance(operands, list):
operands = [operands]
if not isinstance(operands, list):
raise ValueError(f"{operator} requires a list of expressions")
if operator == "$not":
if len(operands) != 1:
raise ValueError("$not requires exactly one operand")
inner_sql, inner_params = self._convert_expr_to_sql(operands[0])
return f"NOT ({inner_sql})", inner_params
# $and, $or, $nor
if len(operands) < 2:
raise ValueError(f"{operator} requires at least 2 operands")
sql_parts = []
all_params = []
for operand in operands:
operand_sql, operand_params = self._convert_expr_to_sql(operand)
sql_parts.append(f"({operand_sql})")
all_params.extend(operand_params)
match operator:
case "$and":
sql = " AND ".join(sql_parts)
case "$or":
sql = " OR ".join(sql_parts)
case "$nor":
sql = f"NOT ({' OR '.join(sql_parts)})"
case _:
raise ValueError(f"Unknown logical operator: {operator}")
return sql, all_params