// 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()
}
}
}