Source code for neosqlite.collection.expr_evaluator.sql_converters.set
"""SQL converters for set operators."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
pass
from .base import BaseSqlMixin
[docs]
class SetMixin(BaseSqlMixin):
"""$setUnion / $setIntersection / $setDifference / $setIsSubset / $anyElementTrue / $allElementsTrue → SQL."""
[docs]
def _convert_set_operator(
self, operator: str, operands: Any
) -> tuple[str, list[Any]]:
"""Convert set operators to SQL using json_each."""
# Get the appropriate json_each function based on SQLite version
json_each = self.json_each_function
json_group_array = self.json_group_array_function
# Normalize operands for operators that accept single values
if operator in (
"$anyElementTrue",
"$allElementsTrue",
) and not isinstance(operands, list):
operands = [operands]
match operator:
case "$setEquals":
if len(operands) != 2:
raise ValueError("$setEquals requires exactly 2 operands")
array1_sql, array1_params = self._convert_operand_to_sql(
operands[0]
)
array2_sql, array2_params = self._convert_operand_to_sql(
operands[1]
)
# Bidirectional subset check: A ⊆ B AND B ⊆ A.
# Each array reference appears twice, so its parameters
# must appear twice in the binding list, in SQL order:
# array1, array2, array2, array1.
sql = f"""
(
NOT EXISTS (
SELECT 1 FROM {json_each}({array1_sql}) AS a1
WHERE NOT EXISTS (
SELECT 1 FROM {json_each}({array2_sql}) AS a2
WHERE a2.value = a1.value
)
)
AND
NOT EXISTS (
SELECT 1 FROM {json_each}({array2_sql}) AS a2
WHERE NOT EXISTS (
SELECT 1 FROM {json_each}({array1_sql}) AS a1
WHERE a1.value = a2.value
)
)
)
"""
return (
sql,
array1_params
+ array2_params
+ array2_params
+ array1_params,
)
case "$setIntersection":
if len(operands) != 2:
raise ValueError(
"$setIntersection requires exactly 2 operands"
)
array1_sql, array1_params = self._convert_operand_to_sql(
operands[0]
)
array2_sql, array2_params = self._convert_operand_to_sql(
operands[1]
)
# SELECT elements from array1 that exist in array2
sql = f"""
(SELECT json({json_group_array}(DISTINCT a1.value))
FROM {json_each}({array1_sql}) AS a1
WHERE EXISTS (SELECT 1 FROM {json_each}({array2_sql}) AS a2 WHERE a2.value = a1.value))
"""
return sql, array1_params + array2_params
case "$setUnion":
if len(operands) != 2:
raise ValueError("$setUnion requires exactly 2 operands")
array1_sql, array1_params = self._convert_operand_to_sql(
operands[0]
)
array2_sql, array2_params = self._convert_operand_to_sql(
operands[1]
)
# SELECT DISTINCT elements from both arrays
sql = f"""
(SELECT json({json_group_array}(DISTINCT value))
FROM (
SELECT value FROM {json_each}({array1_sql})
UNION
SELECT value FROM {json_each}({array2_sql})
))
"""
return sql, array1_params + array2_params
case "$setDifference":
if len(operands) != 2:
raise ValueError(
"$setDifference requires exactly 2 operands"
)
array1_sql, array1_params = self._convert_operand_to_sql(
operands[0]
)
array2_sql, array2_params = self._convert_operand_to_sql(
operands[1]
)
# SELECT elements from array1 that don't exist in array2
sql = f"""
(SELECT json({json_group_array}(a1.value))
FROM {json_each}({array1_sql}) AS a1
WHERE NOT EXISTS (SELECT 1 FROM {json_each}({array2_sql}) AS a2 WHERE a2.value = a1.value))
"""
return sql, array1_params + array2_params
case "$setIsSubset":
if len(operands) != 2:
raise ValueError("$setIsSubset requires exactly 2 operands")
array1_sql, array1_params = self._convert_operand_to_sql(
operands[0]
)
array2_sql, array2_params = self._convert_operand_to_sql(
operands[1]
)
# Check if all elements of array1 exist in array2
sql = f"""
(
NOT EXISTS (
SELECT 1 FROM {json_each}({array1_sql}) AS a1
WHERE NOT EXISTS (SELECT 1 FROM {json_each}({array2_sql}) AS a2 WHERE a2.value = a1.value)
)
)
"""
return sql, array1_params + array2_params
case "$anyElementTrue":
if len(operands) != 1:
raise ValueError(
"$anyElementTrue requires exactly 1 operand"
)
array_sql, array_params = self._convert_operand_to_sql(
operands[0]
)
# Check if any element is truthy (not false, null, or 0)
sql = f"""
(
EXISTS (
SELECT 1 FROM {json_each}({array_sql}) AS a
WHERE a.value IS NOT NULL AND a.value != 0 AND a.value != json('false') AND a.value != json('null')
)
)
"""
return sql, array_params
case "$allElementsTrue":
if len(operands) != 1:
raise ValueError(
"$allElementsTrue requires exactly 1 operand"
)
array_sql, array_params = self._convert_operand_to_sql(
operands[0]
)
# Check if all elements are truthy (no false, null, or 0 elements)
# Empty array returns True (vacuous truth, matching Python's all([]))
sql = f"""
(
NOT EXISTS (
SELECT 1 FROM {json_each}({array_sql}) AS a
WHERE a.value IS NULL OR a.value = 0 OR a.value = json('false') OR a.value = json('null')
)
)
"""
return sql, array_params
case _:
raise NotImplementedError(
f"Set operator {operator} not supported in SQL tier"
)