// Copyright (c) 2023-2026 ParadeDB, Inc. // // This file is part of ParadeDB - Postgres for Search and Analytics // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use crate::api::AsCStr; use crate::postgres::customscan::joinscan::build::{ChildProjection, JoinCSClause}; use pgrx::pg_sys; use pgrx::pg_sys::AsPgCStr; use pgrx::PgList; use serde::{Deserialize, Serialize}; pub const SCORE_COL_NAME: &str = "score"; /// Describes how a single output column of the JoinScan CustomScan is produced. #[derive(Debug, Clone, Serialize, Deserialize)] pub enum OutputColumnInfo { /// A regular column backed by a Var in a source relation. Var { plan_position: usize, rti: pg_sys::Index, original_attno: i16, }, /// A `paradedb.score()` call whose value comes from the DataFusion batch. Score { plan_position: usize, rti: pg_sys::Index, }, /// A column pruned by a semi/anti join or a non-Var, non-score expression. /// Always emits NULL at execution time. Pruned, } impl From<&OutputColumnInfo> for ChildProjection { fn from(info: &OutputColumnInfo) -> Self { match info { OutputColumnInfo::Score { rti, .. } => ChildProjection::Score { rti: *rti }, OutputColumnInfo::Var { rti, original_attno, .. } => ChildProjection::Column { rti: *rti, attno: *original_attno, }, OutputColumnInfo::Pruned => ChildProjection::Column { rti: 0, attno: 0 }, } } } /// Private data stored in the CustomPath/CustomScan for join operations. #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct PrivateData { /// The join clause containing all information about both sides and the join itself. pub join_clause: JoinCSClause, /// Mapping of output column positions to their source relation and original attribute numbers. /// This is populated during planning (before setrefs) and used during execution. pub output_columns: Vec, /// Serialized DataFusion LogicalPlan from planning phase. pub logical_plan: Option, } impl PrivateData { pub fn new(join_clause: JoinCSClause) -> Self { Self { join_clause, output_columns: Vec::new(), logical_plan: None, } } /// Returns a reference to the join clause. pub fn join_clause(&self) -> &JoinCSClause { &self.join_clause } /// Returns a mutable reference to the join clause. pub fn join_clause_mut(&mut self) -> &mut JoinCSClause { &mut self.join_clause } } impl From<*mut pg_sys::List> for PrivateData { fn from(list: *mut pg_sys::List) -> Self { unsafe { let list = PgList::::from_pg(list); let node = list.get_ptr(0).unwrap(); let content = node .as_c_str() .unwrap() .to_str() .expect("string node should be valid utf8"); serde_json::from_str(content).unwrap() } } } impl From for *mut pg_sys::List { fn from(value: PrivateData) -> Self { let content = serde_json::to_string(&value).unwrap(); unsafe { let mut ser = PgList::new(); ser.push(pg_sys::makeString(content.as_pg_cstr()).cast::()); ser.into_pg() } } }