Skip to main content

dbrest_core/plan/
call_plan.rs

1//! CallPlan types for dbrest
2//!
3//! Defines the plan types for RPC function calls.
4//! Matches the Haskell `CallPlan` (`FunctionCall`) data type.
5
6use compact_str::CompactString;
7use std::collections::HashMap;
8
9use crate::api_request::types::FieldName;
10use crate::schema_cache::routine::{Routine, RoutineParam};
11use crate::types::identifiers::QualifiedIdentifier;
12
13use super::types::CoercibleSelectField;
14
15// ==========================================================================
16// CallPlan
17// ==========================================================================
18
19/// A function call plan
20///
21/// Represents a resolved plan for invoking a PostgreSQL function.
22#[derive(Debug, Clone)]
23pub struct CallPlan {
24    /// Qualified identifier of the function
25    pub qi: QualifiedIdentifier,
26    /// Resolved parameters
27    pub params: CallParams,
28    /// Arguments to pass to the function
29    pub args: CallArgs,
30    /// Whether the function returns a scalar
31    pub scalar: bool,
32    /// Whether the function returns SETOF scalar
33    pub set_of_scalar: bool,
34    /// Fields that have filters (for security-definer checks)
35    pub filter_fields: Vec<FieldName>,
36    /// RETURNING columns
37    pub returning: Vec<CoercibleSelectField>,
38}
39
40// ==========================================================================
41// CallParams
42// ==========================================================================
43
44/// Resolved function parameters
45#[derive(Debug, Clone)]
46pub enum CallParams {
47    /// Named parameters (the common case)
48    KeyParams(Vec<RoutineParam>),
49    /// Single positional parameter (for functions taking a single JSON/text arg)
50    OnePosParam(RoutineParam),
51}
52
53impl CallParams {
54    /// Get all resolved parameters
55    pub fn params(&self) -> &[RoutineParam] {
56        match self {
57            CallParams::KeyParams(params) => params,
58            CallParams::OnePosParam(param) => std::slice::from_ref(param),
59        }
60    }
61}
62
63// ==========================================================================
64// CallArgs
65// ==========================================================================
66
67/// Arguments to pass to the function
68#[derive(Debug, Clone)]
69pub enum CallArgs {
70    /// Direct key-value arguments (from query params or form data)
71    DirectArgs(HashMap<CompactString, RpcParamValue>),
72    /// JSON body argument (passed directly to function)
73    JsonArgs(Option<bytes::Bytes>),
74}
75
76/// Value for an RPC parameter
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum RpcParamValue {
79    /// A single fixed value
80    Fixed(CompactString),
81    /// A variadic list of values
82    Variadic(Vec<CompactString>),
83}
84
85// ==========================================================================
86// toRpcParams
87// ==========================================================================
88
89/// Convert query parameters and routine metadata into RPC parameters
90///
91/// Matches the Haskell `toRpcParams` function.
92/// Variadic parameters are split on commas; regular parameters are passed as-is.
93pub fn to_rpc_params(
94    routine: &Routine,
95    params: &[(CompactString, CompactString)],
96) -> HashMap<CompactString, RpcParamValue> {
97    let mut result = HashMap::new();
98
99    for (key, value) in params {
100        if let Some(rp) = routine.get_param(key) {
101            if rp.is_variadic {
102                let values: Vec<CompactString> = value
103                    .split(',')
104                    .map(|v| CompactString::from(v.trim()))
105                    .collect();
106                result.insert(key.clone(), RpcParamValue::Variadic(values));
107            } else {
108                result.insert(key.clone(), RpcParamValue::Fixed(value.clone()));
109            }
110        } else {
111            // Unknown param — include as fixed value (validated downstream)
112            result.insert(key.clone(), RpcParamValue::Fixed(value.clone()));
113        }
114    }
115
116    result
117}
118
119// ==========================================================================
120// Tests
121// ==========================================================================
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::test_helpers::*;
127
128    #[test]
129    fn test_to_rpc_params_fixed() {
130        let routine = test_routine()
131            .param(test_param().name("id").pg_type("integer").build())
132            .param(test_param().name("name").pg_type("text").build())
133            .build();
134
135        let params = vec![("id".into(), "42".into()), ("name".into(), "alice".into())];
136
137        let rpc_params = to_rpc_params(&routine, &params);
138        assert_eq!(
139            rpc_params.get("id"),
140            Some(&RpcParamValue::Fixed("42".into()))
141        );
142        assert_eq!(
143            rpc_params.get("name"),
144            Some(&RpcParamValue::Fixed("alice".into()))
145        );
146    }
147
148    #[test]
149    fn test_to_rpc_params_variadic() {
150        let routine = test_routine()
151            .param(
152                test_param()
153                    .name("ids")
154                    .pg_type("integer")
155                    .is_variadic(true)
156                    .build(),
157            )
158            .is_variadic(true)
159            .build();
160
161        let params = vec![("ids".into(), "1,2,3".into())];
162
163        let rpc_params = to_rpc_params(&routine, &params);
164        assert_eq!(
165            rpc_params.get("ids"),
166            Some(&RpcParamValue::Variadic(vec![
167                "1".into(),
168                "2".into(),
169                "3".into()
170            ]))
171        );
172    }
173
174    #[test]
175    fn test_to_rpc_params_unknown_param() {
176        let routine = test_routine()
177            .param(test_param().name("id").build())
178            .build();
179
180        let params = vec![("unknown_key".into(), "value".into())];
181
182        let rpc_params = to_rpc_params(&routine, &params);
183        assert_eq!(
184            rpc_params.get("unknown_key"),
185            Some(&RpcParamValue::Fixed("value".into()))
186        );
187    }
188
189    #[test]
190    fn test_call_params_key_params() {
191        let p = test_param().name("x").build();
192        let cp = CallParams::KeyParams(vec![p]);
193        assert_eq!(cp.params().len(), 1);
194    }
195
196    #[test]
197    fn test_call_params_one_pos() {
198        let p = test_param().name("body").pg_type("json").build();
199        let cp = CallParams::OnePosParam(p);
200        assert_eq!(cp.params().len(), 1);
201        assert_eq!(cp.params()[0].name.as_str(), "body");
202    }
203}