use std::sync::Arc;
use super::Planner;
use super::util::{extract_table_from_context, key_lit_to_expr};
use crate::err::Error;
use crate::exec::ExecOperator;
use crate::exec::operators::{
CurrentValueSource, EdgeTableSpec, Filter, GraphEdgeScan, GraphScanOutput, Limit, OrderByField,
RandomShuffle, ReferenceScan, ReferenceScanOutput, SortDirection,
};
use crate::exec::parts::LookupDirection;
use crate::exec::planner::select::SelectPipelineConfig;
use crate::expr::{Expr, Literal};
use crate::val::TableName;
pub(crate) struct TargetVertexPlan {
pub direction: LookupDirection,
pub edge_tables: Vec<EdgeTableSpec>,
pub target_tables: Vec<TableName>,
}
impl<'ctx> Planner<'ctx> {
pub(crate) async fn plan_index_function(
&self,
name: &str,
mut ast_args: Vec<Expr>,
) -> Result<Arc<dyn crate::exec::PhysicalExpr>, Error> {
use crate::exec::function::{IndexContext, IndexContextKind};
use crate::exec::physical_expr::function::IndexFunctionExec;
let registry = self.function_registry();
let func = registry.get_index_function(name).ok_or_else(|| Error::Query {
message: format!("Index function '{}' not found in registry", name),
})?;
let index_ctx = match func.index_context_kind() {
IndexContextKind::FullText => {
let ref_idx = func.index_ref_arg_index().ok_or_else(|| Error::Query {
message: format!(
"Index function '{}': FullText functions must declare an index_ref_arg_index",
name
),
})?;
if ref_idx >= ast_args.len() {
return Err(Error::Query {
message: format!(
"Index function '{}' requires at least {} arguments",
name,
ref_idx + 1
),
});
}
let match_ref_ast = ast_args.remove(ref_idx);
let match_ref = match match_ref_ast {
Expr::Literal(Literal::Integer(n)) if (0..=255).contains(&n) => n as u8,
Expr::Literal(Literal::Float(n))
if (0.0..=255.0).contains(&n) && n.fract() == 0.0 =>
{
n as u8
}
_ => {
return Err(Error::Query {
message: format!(
"Index function '{}': index_ref argument must be a literal integer in range 0..255",
name
),
});
}
};
let matches_ctx = self.ctx.get_matches_context().ok_or_else(|| Error::Query {
message: format!(
"Index function '{}': no MATCHES clause found in WHERE condition",
name
),
})?;
let match_ctx = matches_ctx
.resolve(match_ref, extract_table_from_context(self.ctx))
.map_err(|e| Error::Query {
message: format!("Index function '{}': {}", name, e),
})?;
IndexContext::FullText(match_ctx)
}
IndexContextKind::Knn => {
if let Some(ref_idx) = func.index_ref_arg_index()
&& ref_idx < ast_args.len()
{
ast_args.remove(ref_idx);
}
let knn_ctx = self.ctx.get_knn_context().ok_or_else(|| Error::Query {
message: format!(
"Index function '{}': no KNN operator found in WHERE condition",
name
),
})?;
IndexContext::Knn(Arc::clone(knn_ctx))
}
};
let mut phys_args = Vec::with_capacity(ast_args.len());
for arg in ast_args {
phys_args.push(self.physical_expr(arg).await?);
}
let func_ctx = func.required_context();
Ok(Arc::new(IndexFunctionExec {
name: name.to_string(),
arguments: phys_args,
index_ctx,
func_required_context: func_ctx,
}))
}
pub(crate) async fn convert_order_list(
&self,
order_list: crate::expr::order::OrderList,
) -> Result<Vec<OrderByField>, Error> {
let mut fields = Vec::with_capacity(order_list.len());
for order_field in order_list {
let expr: Arc<dyn crate::exec::PhysicalExpr> =
self.convert_idiom(order_field.value).await?;
let direction = if order_field.direction {
SortDirection::Asc
} else {
SortDirection::Desc
};
fields.push(OrderByField {
expr,
direction,
collate: order_field.collate,
numeric: order_field.numeric,
});
}
Ok(fields)
}
pub(crate) async fn convert_range_bound(
&self,
bound: &std::ops::Bound<crate::expr::RecordIdKeyLit>,
) -> Result<std::ops::Bound<Arc<dyn crate::exec::PhysicalExpr>>, Error> {
match bound {
std::ops::Bound::Unbounded => Ok(std::ops::Bound::Unbounded),
std::ops::Bound::Included(lit) => {
let expr = key_lit_to_expr(lit)?;
let phys = self.physical_expr(expr).await?;
Ok(std::ops::Bound::Included(phys))
}
std::ops::Bound::Excluded(lit) => {
let expr = key_lit_to_expr(lit)?;
let phys = self.physical_expr(expr).await?;
Ok(std::ops::Bound::Excluded(phys))
}
}
}
pub(crate) async fn plan_lookup(
&self,
lookup: crate::expr::lookup::Lookup,
) -> Result<Arc<dyn ExecOperator>, Error> {
let input: Arc<dyn ExecOperator> = Arc::new(CurrentValueSource::new());
self.plan_lookup_with_input(input, lookup).await
}
pub(crate) async fn plan_lookup_with_input(
&self,
input: Arc<dyn ExecOperator>,
crate::expr::lookup::Lookup {
kind,
expr,
only: _,
what,
cond,
split,
group,
order,
limit,
start,
alias: _,
}: crate::expr::lookup::Lookup,
) -> Result<Arc<dyn ExecOperator>, Error> {
let needs_full_pipeline = expr.is_some() || group.is_some();
let needs_full_records = needs_full_pipeline || cond.is_some() || split.is_some();
let output_mode = if needs_full_records {
GraphScanOutput::FullEdge
} else {
GraphScanOutput::TargetId
};
let base_scan: Arc<dyn ExecOperator> = match &kind {
crate::expr::lookup::LookupKind::Graph(dir) => {
let mut edge_tables: Vec<EdgeTableSpec> = Vec::with_capacity(what.len());
for s in what {
let spec = match s {
crate::expr::lookup::LookupSubject::Table {
table,
..
} => EdgeTableSpec {
table,
range_start: std::ops::Bound::Unbounded,
range_end: std::ops::Bound::Unbounded,
},
crate::expr::lookup::LookupSubject::Range {
table,
range,
..
} => {
let range_start = self.convert_range_bound(&range.start).await?;
let range_end = self.convert_range_bound(&range.end).await?;
EdgeTableSpec {
table,
range_start,
range_end,
}
}
};
edge_tables.push(spec);
}
let scan = GraphEdgeScan::new(
input,
LookupDirection::from(dir),
edge_tables,
output_mode,
self.version.clone(),
);
let scan =
if cond.is_none() && split.is_none() && order.is_none() && group.is_none() {
if let Some(crate::expr::limit::Limit(crate::expr::Expr::Literal(
crate::expr::Literal::Integer(n),
))) = &limit
{
let offset = match &start {
Some(crate::expr::start::Start(crate::expr::Expr::Literal(
crate::expr::Literal::Integer(s),
))) => *s as usize,
_ => 0,
};
scan.with_limit(*n as usize + offset)
} else {
scan
}
} else {
scan
};
Arc::new(scan)
}
crate::expr::lookup::LookupKind::Reference => {
let (referencing_table, referencing_field, range_start, range_end) =
if let Some(subject) = what.first() {
match subject {
crate::expr::lookup::LookupSubject::Table {
table,
referencing_field,
} => (
Some(table.clone()),
referencing_field.clone(),
std::ops::Bound::Unbounded,
std::ops::Bound::Unbounded,
),
crate::expr::lookup::LookupSubject::Range {
table,
referencing_field,
range,
} => {
let rs = self.convert_range_bound(&range.start).await?;
let re = self.convert_range_bound(&range.end).await?;
(Some(table.clone()), referencing_field.clone(), rs, re)
}
}
} else {
(None, None, std::ops::Bound::Unbounded, std::ops::Bound::Unbounded)
};
let ref_output_mode = if needs_full_records {
ReferenceScanOutput::FullRecord
} else {
ReferenceScanOutput::RecordId
};
Arc::new(ReferenceScan::new(
input,
referencing_table,
referencing_field,
ref_output_mode,
range_start,
range_end,
self.version.clone(),
))
}
};
if needs_full_pipeline {
let config = SelectPipelineConfig {
where_clause: match cond {
Some(c) => crate::exec::planner::select::WhereClauseState::Original(c),
None => crate::exec::planner::select::WhereClauseState::None,
},
split,
group,
order,
limit,
start,
omit: vec![],
tempfiles: false,
};
self.plan_pipeline(base_scan, expr, config).await
} else {
let filtered: Arc<dyn ExecOperator> = if let Some(cond) = cond {
let predicate = self.physical_expr(cond.0).await?;
Arc::new(Filter::new(base_scan, predicate))
} else {
base_scan
};
let split_op: Arc<dyn ExecOperator> = if let Some(splits) = split {
Arc::new(crate::exec::operators::Split::new(
filtered,
splits.into_iter().map(|s| s.0).collect(),
))
} else {
filtered
};
let sorted: Arc<dyn ExecOperator> = match order {
Some(crate::expr::order::Ordering::Order(order_list)) => {
let order_by = self.convert_order_list(order_list).await?;
Arc::new(crate::exec::operators::Sort::new(split_op, order_by))
}
Some(crate::expr::order::Ordering::Random) => {
Arc::new(RandomShuffle::new(split_op, None))
}
None => split_op,
};
let limited: Arc<dyn ExecOperator> = if limit.is_some() || start.is_some() {
let limit_expr = match limit {
Some(l) => Some(self.physical_expr(l.0).await?),
None => None,
};
let offset_expr = match start {
Some(s) => Some(self.physical_expr(s.0).await?),
None => None,
};
Arc::new(Limit::new(sorted, limit_expr, offset_expr))
} else {
sorted
};
Ok(limited)
}
}
pub(crate) async fn try_fast_path_pair(
&self,
edge: &crate::expr::lookup::Lookup,
vertex: &crate::expr::lookup::Lookup,
) -> Result<Option<TargetVertexPlan>, Error> {
use crate::expr::lookup::{LookupKind, LookupSubject};
let edge_dir = match (&edge.kind, &vertex.kind) {
(LookupKind::Graph(d1), LookupKind::Graph(d2)) if d1 == d2 => d1,
_ => return Ok(None),
};
if self.version.is_some() {
return Ok(None);
}
if matches!(edge_dir, crate::expr::Dir::Both) {
return Ok(None);
}
let lookup_is_plain = |l: &crate::expr::lookup::Lookup| {
l.expr.is_none()
&& l.cond.is_none()
&& l.split.is_none()
&& l.group.is_none()
&& l.order.is_none()
&& l.limit.is_none()
&& l.start.is_none()
&& l.alias.is_none()
&& !l.only
};
if !lookup_is_plain(edge) || !lookup_is_plain(vertex) {
return Ok(None);
}
let mut edge_tables: Vec<EdgeTableSpec> = Vec::with_capacity(edge.what.len());
for s in &edge.what {
let LookupSubject::Table {
table,
..
} = s
else {
return Ok(None);
};
edge_tables.push(EdgeTableSpec {
table: table.clone(),
range_start: std::ops::Bound::Unbounded,
range_end: std::ops::Bound::Unbounded,
});
}
if edge_tables.is_empty() {
return Ok(None);
}
let mut target_tables: Vec<TableName> = Vec::with_capacity(vertex.what.len());
for s in &vertex.what {
let LookupSubject::Table {
table,
..
} = s
else {
return Ok(None);
};
target_tables.push(table.clone());
}
if target_tables.is_empty() {
return Ok(None);
}
let Some(txn) = self.txn() else {
return Ok(None);
};
let (Some(ns), Some(db)) = (self.ns(), self.db()) else {
return Ok(None);
};
if !self.should_check_perms_for_view(ns, db) {
return Ok(Some(TargetVertexPlan {
direction: LookupDirection::from(edge_dir),
edge_tables,
target_tables,
}));
}
for spec in &edge_tables {
use crate::catalog::providers::TableProvider;
let td = match txn.get_tb_by_name(ns, db, &spec.table, None).await {
Ok(Some(td)) => td,
_ => return Ok(None),
};
if !matches!(td.permissions.select, crate::catalog::Permission::Full) {
return Ok(None);
}
}
Ok(Some(TargetVertexPlan {
direction: LookupDirection::from(edge_dir),
edge_tables,
target_tables,
}))
}
pub(crate) async fn plan_target_vertex_scan(
&self,
input: Arc<dyn ExecOperator>,
plan: TargetVertexPlan,
) -> Result<Arc<dyn ExecOperator>, Error> {
let scan = GraphEdgeScan::new(
input,
plan.direction,
plan.edge_tables,
GraphScanOutput::TargetVertex,
self.version.clone(),
)
.with_target_tables(plan.target_tables);
Ok(Arc::new(scan))
}
}