• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / pg_graphql / 19839478330

01 Dec 2025 10:20PM UTC coverage: 83.863% (-7.5%) from 91.36%
19839478330

Pull #613

github

web-flow
Merge 73f7cb2de into 7951d27bb
Pull Request #613: Transpiler re-write to use an AST vs string concatenation

3931 of 5222 new or added lines in 20 files covered. (75.28%)

2 existing lines in 1 file now uncovered.

10160 of 12115 relevant lines covered (83.86%)

919.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.75
/src/ast/transpile_function_call.rs
1
//! AST-based transpilation for FunctionCallBuilder
2
//!
3
//! This module implements the ToAst trait for FunctionCallBuilder, converting it
4
//! to a type-safe AST that can be rendered to SQL. FunctionCallBuilder handles
5
//! top-level function calls in queries and mutations.
6

7
use super::{
8
    add_param_from_json, apply_type_cast, build_function_connection_subquery_full,
9
    build_node_object_expr, coalesce, AstBuildContext, ToAst,
10
};
11
use crate::ast::{
12
    Expr, FromClause, FunctionArg, FunctionCall, Ident, Literal, ParamCollector, SelectColumn,
13
    SelectStmt, Stmt,
14
};
15
use crate::builder::{FuncCallReturnTypeBuilder, FunctionCallBuilder};
16
use crate::error::GraphQLResult;
17

18
/// The result of transpiling a FunctionCallBuilder to AST
19
pub struct FunctionCallAst {
20
    /// The complete SQL statement
21
    pub stmt: Stmt,
22
}
23

24
impl ToAst for FunctionCallBuilder {
25
    type Ast = FunctionCallAst;
26

27
    fn to_ast(&self, params: &mut ParamCollector) -> GraphQLResult<Self::Ast> {
93✔
28
        let ctx = AstBuildContext::new();
93✔
29
        let block_name = ctx.block_name.clone();
93✔
30

31
        // Build the function call arguments
32
        let args = build_function_args(&self.args_builder, params)?;
93✔
33

34
        // Build the function call expression
35
        let func_call = FunctionCall::with_schema(
93✔
36
            self.function.schema_name.clone(),
93✔
37
            self.function.name.clone(),
93✔
38
            args,
93✔
39
        );
40

41
        // Build the query based on return type
42
        let select_expr = match &self.return_type_builder {
93✔
43
            FuncCallReturnTypeBuilder::Scalar | FuncCallReturnTypeBuilder::List => {
44
                // SELECT to_jsonb(schema.func(args)::type_adjustment)
45
                let func_expr = Expr::FunctionCall(func_call);
83✔
46
                let adjusted_expr = apply_type_cast(func_expr, self.function.type_oid);
83✔
47
                func_call_expr("to_jsonb", vec![adjusted_expr])
83✔
48
            }
49
            FuncCallReturnTypeBuilder::Node(node_builder) => {
9✔
50
                // SELECT coalesce((SELECT node_object FROM schema.func(args) AS block WHERE NOT (block IS NULL)), null::jsonb)
51
                let object_expr =
9✔
52
                    build_node_object_expr(&node_builder.selections, &block_name, params)?;
9✔
53

54
                // Handle empty selections
55
                let object_expr = if node_builder.selections.is_empty() {
9✔
NEW
56
                    func_call_expr("jsonb_build_object", vec![])
×
57
                } else {
58
                    object_expr
9✔
59
                };
60

61
                // Build: NOT (block_name IS NULL)
62
                let not_null_check = Expr::UnaryOp {
9✔
63
                    op: super::UnaryOperator::Not,
9✔
64
                    expr: Box::new(Expr::IsNull {
9✔
65
                        expr: Box::new(Expr::Column(super::ColumnRef::new(block_name.as_str()))),
9✔
66
                        negated: false,
9✔
67
                    }),
9✔
68
                };
9✔
69

70
                // Build the inner subquery
71
                let inner_subquery = SelectStmt {
9✔
72
                    ctes: vec![],
9✔
73
                    columns: vec![SelectColumn::expr(object_expr)],
9✔
74
                    from: Some(FromClause::Function {
9✔
75
                        call: func_call,
9✔
76
                        alias: Ident::new(&block_name),
9✔
77
                    }),
9✔
78
                    where_clause: Some(not_null_check),
9✔
79
                    group_by: vec![],
9✔
80
                    having: None,
9✔
81
                    order_by: vec![],
9✔
82
                    limit: None,
9✔
83
                    offset: None,
9✔
84
                };
9✔
85

86
                // Wrap in coalesce with null::jsonb fallback
87
                coalesce(vec![
9✔
88
                    Expr::Subquery(Box::new(inner_subquery)),
9✔
89
                    Expr::Cast {
9✔
90
                        expr: Box::new(Expr::Literal(Literal::Null)),
9✔
91
                        target_type: super::type_name_to_sql_type("jsonb"),
9✔
92
                    },
9✔
93
                ])
94
            }
95
            FuncCallReturnTypeBuilder::Connection(conn_builder) => {
1✔
96
                // Build a connection query using the function as the FROM source
97
                // This returns an Expr::Subquery containing the full connection query
98
                build_function_connection_subquery_full(conn_builder, func_call, params)?
1✔
99
            }
100
        };
101

102
        // For Connection type, the select_expr is already a subquery that returns the result
103
        // For other types, we need to wrap in SELECT
104

105
        // Build the final SELECT statement
106
        let select = SelectStmt {
93✔
107
            ctes: vec![],
93✔
108
            columns: vec![SelectColumn::expr(select_expr)],
93✔
109
            from: None,
93✔
110
            where_clause: None,
93✔
111
            group_by: vec![],
93✔
112
            having: None,
93✔
113
            order_by: vec![],
93✔
114
            limit: None,
93✔
115
            offset: None,
93✔
116
        };
93✔
117

118
        Ok(FunctionCallAst {
93✔
119
            stmt: Stmt::Select(select),
93✔
120
        })
93✔
121
    }
93✔
122
}
123

124
/// Build function call arguments from FuncCallArgsBuilder
125
fn build_function_args(
93✔
126
    args_builder: &crate::builder::FuncCallArgsBuilder,
93✔
127
    params: &mut ParamCollector,
93✔
128
) -> GraphQLResult<Vec<FunctionArg>> {
93✔
129
    let mut args = Vec::new();
93✔
130

131
    for (arg_meta, arg_value) in &args_builder.args {
220✔
132
        if let Some(arg) = arg_meta {
127✔
133
            // Build the parameter expression with type cast
134
            let param_expr = add_param_from_json(params, arg_value, &arg.type_name)?;
126✔
135

136
            // Create a named argument: name => value
137
            args.push(FunctionArg::named(arg.name.as_str(), param_expr));
126✔
138
        }
1✔
139
    }
140

141
    Ok(args)
93✔
142
}
93✔
143

144
/// Helper to create a function call expression
145
fn func_call_expr(name: &str, args: Vec<Expr>) -> Expr {
83✔
146
    Expr::FunctionCall(FunctionCall::new(
83✔
147
        name,
83✔
148
        args.into_iter().map(FunctionArg::unnamed).collect(),
83✔
149
    ))
83✔
150
}
83✔
151

152
#[cfg(test)]
153
mod tests {
154
    // Integration tests via pg_regress are more appropriate for this module
155
    // since it requires a full GraphQL schema and function setup.
156
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc