Skip to main content

hawdb_sql/
parameters.rs

1// Copyright 2026 Nowledge
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use crate::{
16    DeleteStatement, ExprKind, InsertStatement, SelectProjection, SelectStatement,
17    SqlArithmeticOperand, SqlAssignment, SqlAssignmentValue, SqlBound, SqlColumnDefault,
18    SqlExpression, SqlStatement, SqlValue, UpdateStatement,
19};
20use hawdb_core::{HawDBError, Result};
21use std::collections::BTreeSet;
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct PreparedPostgresStatement {
25    pub statement: SqlStatement,
26    pub parameters: Vec<PostgresParameterMetadata>,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct PostgresParameterMetadata {
31    pub position: usize,
32}
33
34pub fn prepare_postgres_sql(input: &str) -> Result<PreparedPostgresStatement> {
35    let statement = crate::parse_postgres_sql(input)?;
36    let mut positions = BTreeSet::new();
37    collect_statement_parameters(&statement, &mut positions);
38    if !positions.is_empty()
39        && matches!(
40            &statement,
41            SqlStatement::CreateTable(_)
42                | SqlStatement::CreateIndex(_)
43                | SqlStatement::AlterTableAddColumn(_)
44        )
45    {
46        return Err(HawDBError::Semantic(
47            "PostgreSQL schema statements do not accept parameters".to_string(),
48        ));
49    }
50    let maximum = positions.last().copied().unwrap_or(0);
51    let expected = (1..=maximum).collect::<BTreeSet<_>>();
52    if positions != expected {
53        return Err(HawDBError::Semantic(format!(
54            "PostgreSQL parameter positions must be dense from $1 through ${maximum}; found {positions:?}"
55        )));
56    }
57    Ok(PreparedPostgresStatement {
58        statement,
59        parameters: positions
60            .into_iter()
61            .map(|position| PostgresParameterMetadata { position })
62            .collect(),
63    })
64}
65
66fn collect_statement_parameters(statement: &SqlStatement, positions: &mut BTreeSet<usize>) {
67    match statement {
68        SqlStatement::Explain(explain) => {
69            collect_statement_parameters(&explain.statement, positions)
70        }
71        SqlStatement::Select(select) => collect_select_parameters(select, positions),
72        SqlStatement::Insert(insert) => collect_insert_parameters(insert, positions),
73        SqlStatement::Update(update) => collect_update_parameters(update, positions),
74        SqlStatement::Delete(delete) => collect_delete_parameters(delete, positions),
75        SqlStatement::CreateTable(create) => {
76            for column in &create.columns {
77                if let Some(value) = &column.default {
78                    collect_column_default_parameter(value, positions);
79                }
80            }
81        }
82        SqlStatement::AlterTableAddColumn(alter) => {
83            if let Some(value) = &alter.column.default {
84                collect_column_default_parameter(value, positions);
85            }
86        }
87        SqlStatement::CreateIndex(_) => {}
88    }
89}
90
91fn collect_column_default_parameter(default: &SqlColumnDefault, positions: &mut BTreeSet<usize>) {
92    if let SqlColumnDefault::Literal(value) = default {
93        collect_value_parameter(value, positions);
94    }
95}
96
97fn collect_select_parameters(select: &SelectStatement, positions: &mut BTreeSet<usize>) {
98    for projection in &select.projection {
99        if let SelectProjection::Expression { expression, .. } = projection {
100            collect_expression_parameters(expression, positions);
101        }
102    }
103    for join in &select.joins {
104        collect_expression_parameters(&join.on, positions);
105    }
106    if let Some(predicate) = &select.selection {
107        collect_expression_parameters(predicate, positions);
108    }
109    if let Some(predicate) = &select.having {
110        collect_expression_parameters(predicate, positions);
111    }
112    for order in &select.order_by {
113        collect_expression_parameters(&order.expression, positions);
114    }
115    collect_bound_parameter(select.limit, positions);
116    collect_bound_parameter(select.offset, positions);
117}
118
119fn collect_insert_parameters(insert: &InsertStatement, positions: &mut BTreeSet<usize>) {
120    for row in &insert.rows {
121        for value in row {
122            collect_value_parameter(value, positions);
123        }
124    }
125    if let Some(conflict) = &insert.on_conflict
126        && let crate::SqlConflictAction::DoUpdate(assignments) = &conflict.action
127    {
128        collect_assignment_parameters(assignments, positions);
129    }
130}
131
132fn collect_update_parameters(update: &UpdateStatement, positions: &mut BTreeSet<usize>) {
133    collect_assignment_parameters(&update.assignments, positions);
134    if let Some(predicate) = &update.selection {
135        collect_expression_parameters(predicate, positions);
136    }
137}
138
139fn collect_delete_parameters(delete: &DeleteStatement, positions: &mut BTreeSet<usize>) {
140    if let Some(predicate) = &delete.selection {
141        collect_expression_parameters(predicate, positions);
142    }
143}
144
145fn collect_assignment_parameters(assignments: &[SqlAssignment], positions: &mut BTreeSet<usize>) {
146    for assignment in assignments {
147        match &assignment.value {
148            SqlAssignmentValue::Value(value) => collect_value_parameter(value, positions),
149            SqlAssignmentValue::Column(_) => {}
150            SqlAssignmentValue::Arithmetic { left, right, .. } => {
151                collect_arithmetic_operand_parameter(left, positions);
152                collect_arithmetic_operand_parameter(right, positions);
153            }
154        }
155    }
156}
157
158fn collect_arithmetic_operand_parameter(
159    operand: &SqlArithmeticOperand,
160    positions: &mut BTreeSet<usize>,
161) {
162    if let SqlArithmeticOperand::Value(value) = operand {
163        collect_value_parameter(value, positions);
164    }
165}
166
167fn collect_expression_parameters(expression: &SqlExpression, positions: &mut BTreeSet<usize>) {
168    expression.visit(&mut |expression| {
169        if let ExprKind::Value(value) = &expression.kind {
170            collect_value_parameter(value, positions);
171        }
172    });
173}
174
175fn collect_value_parameter(value: &SqlValue, positions: &mut BTreeSet<usize>) {
176    if let SqlValue::Parameter(position) = value {
177        positions.insert(*position);
178    }
179}
180
181fn collect_bound_parameter(bound: Option<SqlBound>, positions: &mut BTreeSet<usize>) {
182    if let Some(SqlBound::Parameter(position)) = bound {
183        positions.insert(position);
184    }
185}