nodedb_sql/planner/join/array_arm.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! ARRAY_* table-valued function as a JOIN source.
4//!
5//! When a JOIN's left or right relation is `ARRAY_SLICE(...)` (or
6//! sibling read TVFs), the planner lowers it to the corresponding
7//! `SqlPlan::Array*` variant directly — bypassing catalog lookup.
8//! The TVF's output rows participate in the join key resolution like
9//! any other relation; the dispatch handler emits `(coords, attrs)`
10//! tuples that include the array's surrogate-equivalent identity
11//! columns so equi-join keys work naturally.
12
13use sqlparser::ast;
14
15use crate::error::Result;
16use crate::temporal::TemporalScope;
17use crate::types::{SqlCatalog, SqlPlan};
18
19/// If `rel` is a `ARRAY_*(...)` TVF call, return the corresponding
20/// `SqlPlan::Array*` node. Otherwise return `Ok(None)` so the caller
21/// falls through to the named-table scan path.
22pub(super) fn try_plan_relation(
23 rel: &ast::TableFactor,
24 catalog: &dyn SqlCatalog,
25 temporal: TemporalScope,
26) -> Result<Option<SqlPlan>> {
27 let name = match rel {
28 ast::TableFactor::Table {
29 name,
30 args: Some(_),
31 ..
32 } => name,
33 _ => return Ok(None),
34 };
35 let fn_name = crate::parser::normalize::normalize_object_name_checked(name)?;
36 if !is_array_tvf(&fn_name) {
37 return Ok(None);
38 }
39
40 // Reuse the SELECT * FROM ARRAY_*(...) planner by wrapping the
41 // single relation into a one-element TableWithJoins. Both call
42 // sites need exactly the same validation (arg arity, schema
43 // resolution, dim/attr name checks) — re-deriving it here would
44 // duplicate that logic and let the two paths drift.
45 let twj = ast::TableWithJoins {
46 relation: rel.clone(),
47 joins: Vec::new(),
48 };
49 super::super::array_fn::try_plan_array_table_fn(std::slice::from_ref(&twj), catalog, temporal)
50}
51
52fn is_array_tvf(name: &str) -> bool {
53 matches!(
54 name,
55 "array_slice" | "array_project" | "array_agg" | "array_elementwise"
56 )
57}