Source code for neosqlite.collection.temporary_table_aggregation.core
from __future__ import annotations
import logging
from typing import Any
from ..._sqlite import sqlite3
logger = logging.getLogger(__name__)
[docs]
def can_process_with_temporary_tables(pipeline: list[dict[str, Any]]) -> bool:
"""
Determine if a pipeline can be processed with temporary tables.
This function checks if all stages in an aggregation pipeline are supported
by the temporary table processing approach. It verifies that each stage in
the pipeline is one of the supported stage types.
Additionally, it handles special cases for text search operations:
- Pure text search operations are supported with hybrid processing
- Text search with simple unwind operations are supported (uses Python text search on temp tables)
- Complex nested unwinds (multiple unwinds or dotted paths) fall back to Python
Args:
pipeline (list[dict[str, Any]]): List of aggregation pipeline stages to check
Returns:
bool: True if all stages in the pipeline are supported and can be processed
with temporary tables, False otherwise
"""
# Check if all stages are supported
# Note: $merge and $redact require Python fallback for full functionality
supported_stages = {
"$addFields",
"$bucket",
"$bucketAuto",
"$densify",
"$facet",
"$fill",
"$graphLookup",
"$group",
"$limit",
"$lookup",
"$match",
"$project",
"$replaceRoot",
"$replaceWith",
"$sample",
"$setWindowFields",
"$skip",
"$sort",
"$unionWith",
"$unset",
"$unwind",
}
# Count unwind stages and check for complex patterns
unwind_count = 0
has_nested_unwind = False
for stage in pipeline:
stage_name = next(iter(stage.keys()))
if stage_name not in supported_stages:
return False
if stage_name == "$match":
match_spec = stage["$match"]
if "$jsonSchema" in match_spec:
return False
if stage_name in ("$setWindowFields", "$fill"):
if sqlite3.sqlite_version_info < (3, 25, 0):
return False
if stage_name == "$unwind":
unwind_count += 1
unwind_spec = stage["$unwind"]
# Check for nested/dotted paths which are complex
if isinstance(unwind_spec, str) and "." in unwind_spec:
has_nested_unwind = True
elif isinstance(unwind_spec, dict):
path = unwind_spec.get("path", "")
if "." in path:
has_nested_unwind = True
# Multiple unwinds or nested paths are complex - fall back to Python
if unwind_count > 1 or has_nested_unwind:
return False
return True
[docs]
def execute_2nd_tier_aggregation(
query_engine,
pipeline: list[dict[str, Any]],
batch_size: int = 101,
) -> list[dict[str, Any]]:
"""
Execute aggregation pipeline using temporary table approach for complex pipelines.
This function is designed to be called as the second tier in a three-tier processing system:
1. First tier (QueryEngine): Try existing SQL optimization for simple pipelines
2. Second tier (this function): Try temporary table approach for complex pipelines
3. Third tier (QueryEngine): Fall back to Python implementation for unsupported operations
This function focuses specifically on processing complex pipelines that the current
NeoSQLite SQL optimization cannot handle efficiently, using temporary tables for better performance.
Args:
query_engine: The NeoSQLite QueryEngine instance to use for processing
pipeline (list[dict[str, Any]]): List of aggregation pipeline stages to process
batch_size (int): Batch size for fetching results from temporary tables
Returns:
list[dict[str, Any]]: List of result documents after processing the pipeline
"""
# Check if we should force fallback for benchmarking/debugging
from ..query_helper import get_force_fallback
from . import TemporaryTableAggregationProcessor
if get_force_fallback():
raise NotImplementedError(
"Temporary table aggregation skipped due to force fallback flag"
)
# Process the pipeline with temporary tables if possible
if can_process_with_temporary_tables(pipeline):
try:
processor = TemporaryTableAggregationProcessor(
query_engine.collection, query_engine
)
return processor.process_pipeline(pipeline, batch_size=batch_size)
except Exception as e:
logger.debug(
f"Temporary table aggregation failed, fallback required: {e}"
)
raise NotImplementedError(
f"Temporary table aggregation failed, fallback required: {e}"
)
# If we can't process with temporary tables, signal for fallback.
raise NotImplementedError(
"Pipeline not supported by temporary table aggregation."
)