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    BranchSqlSelector, BranchSqlStatement, BranchSqlValue, DeleteStatement, ExprKind,
17    InsertStatement, SelectProjection, SelectStatement, SqlArithmeticOperand, SqlAssignment,
18    SqlAssignmentValue, SqlBound, SqlColumnDefault, SqlExpression, SqlStatement, SqlValue,
19    UpdateStatement,
20};
21use hawdb_core::{HawDBError, Result};
22use std::collections::BTreeSet;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct PreparedPostgresStatement {
26    pub statement: SqlStatement,
27    pub parameters: Vec<PostgresParameterMetadata>,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct PostgresParameterMetadata {
32    pub position: usize,
33}
34
35pub fn prepare_postgres_sql(input: &str) -> Result<PreparedPostgresStatement> {
36    let statement = crate::parse_postgres_sql(input)?;
37    let mut positions = BTreeSet::new();
38    collect_statement_parameters(&statement, &mut positions);
39    if !positions.is_empty()
40        && matches!(
41            &statement,
42            SqlStatement::CreateTable(_)
43                | SqlStatement::CreateIndex(_)
44                | SqlStatement::AlterTableAddColumn(_)
45        )
46    {
47        return Err(HawDBError::Semantic(
48            "PostgreSQL schema statements do not accept parameters".to_string(),
49        ));
50    }
51    let maximum = positions.last().copied().unwrap_or(0);
52    let expected = (1..=maximum).collect::<BTreeSet<_>>();
53    if positions != expected {
54        return Err(HawDBError::Semantic(format!(
55            "PostgreSQL parameter positions must be dense from $1 through ${maximum}; found {positions:?}"
56        )));
57    }
58    Ok(PreparedPostgresStatement {
59        statement,
60        parameters: positions
61            .into_iter()
62            .map(|position| PostgresParameterMetadata { position })
63            .collect(),
64    })
65}
66
67fn collect_statement_parameters(statement: &SqlStatement, positions: &mut BTreeSet<usize>) {
68    match statement {
69        SqlStatement::Explain(explain) => {
70            collect_statement_parameters(&explain.statement, positions)
71        }
72        SqlStatement::Select(select) => collect_select_parameters(select, positions),
73        SqlStatement::Insert(insert) => collect_insert_parameters(insert, positions),
74        SqlStatement::Update(update) => collect_update_parameters(update, positions),
75        SqlStatement::Delete(delete) => collect_delete_parameters(delete, positions),
76        SqlStatement::CreateTable(create) => {
77            for column in &create.columns {
78                if let Some(value) = &column.default {
79                    collect_column_default_parameter(value, positions);
80                }
81            }
82        }
83        SqlStatement::AlterTableAddColumn(alter) => {
84            if let Some(value) = &alter.column.default {
85                collect_column_default_parameter(value, positions);
86            }
87        }
88        SqlStatement::CreateIndex(_) => {}
89        SqlStatement::Branch(branch) => collect_branch_parameters(branch, positions),
90    }
91}
92
93fn collect_branch_parameters(branch: &BranchSqlStatement, positions: &mut BTreeSet<usize>) {
94    match branch {
95        BranchSqlStatement::ShowBranches(statement) => {
96            collect_bound_parameter(Some(statement.limit), positions);
97            collect_bound_parameter(statement.offset, positions);
98        }
99        BranchSqlStatement::ShowBranch(statement) => {
100            collect_branch_selector(&statement.selector, positions)
101        }
102        BranchSqlStatement::UseBranch(selector) => collect_branch_selector(selector, positions),
103        BranchSqlStatement::ShowCurrentBranch => {}
104        BranchSqlStatement::CreateBranch(statement) => {
105            if let Some(name) = &statement.name {
106                collect_branch_value(name, positions);
107            }
108            collect_branch_selector(&statement.source, positions);
109            collect_bound_parameter(Some(statement.expected_source_revision), positions);
110            collect_branch_value(&statement.request_key, positions);
111            if let Some(owner) = &statement.owner {
112                collect_branch_value(owner, positions);
113            }
114        }
115        BranchSqlStatement::DropBranch(statement) => {
116            collect_branch_value(&statement.id, positions);
117            collect_bound_parameter(Some(statement.expected_metadata_revision), positions);
118        }
119    }
120}
121
122fn collect_branch_selector(selector: &BranchSqlSelector, positions: &mut BTreeSet<usize>) {
123    let (BranchSqlSelector::Name(value) | BranchSqlSelector::Id(value)) = selector;
124    collect_branch_value(value, positions);
125}
126
127fn collect_branch_value(value: &BranchSqlValue, positions: &mut BTreeSet<usize>) {
128    if let BranchSqlValue::Parameter(position) = value {
129        positions.insert(*position);
130    }
131}
132
133fn collect_column_default_parameter(default: &SqlColumnDefault, positions: &mut BTreeSet<usize>) {
134    if let SqlColumnDefault::Literal(value) = default {
135        collect_value_parameter(value, positions);
136    }
137}
138
139fn collect_select_parameters(select: &SelectStatement, positions: &mut BTreeSet<usize>) {
140    if let Some(search) = &select.vector_index {
141        for value in [
142            &search.index,
143            &search.embedding,
144            &search.candidates,
145            &search.top_k,
146        ] {
147            collect_value_parameter(value, positions);
148        }
149    }
150    for projection in &select.projection {
151        if let SelectProjection::Expression { expression, .. } = projection {
152            collect_expression_parameters(expression, positions);
153        }
154    }
155    for join in &select.joins {
156        collect_expression_parameters(&join.on, positions);
157    }
158    if let Some(predicate) = &select.selection {
159        collect_expression_parameters(predicate, positions);
160    }
161    if let Some(predicate) = &select.having {
162        collect_expression_parameters(predicate, positions);
163    }
164    for order in &select.order_by {
165        collect_expression_parameters(&order.expression, positions);
166    }
167    collect_bound_parameter(select.limit, positions);
168    collect_bound_parameter(select.offset, positions);
169}
170
171fn collect_insert_parameters(insert: &InsertStatement, positions: &mut BTreeSet<usize>) {
172    for row in &insert.rows {
173        for value in row {
174            collect_value_parameter(value, positions);
175        }
176    }
177    if let Some(conflict) = &insert.on_conflict
178        && let crate::SqlConflictAction::DoUpdate(assignments) = &conflict.action
179    {
180        collect_assignment_parameters(assignments, positions);
181    }
182}
183
184fn collect_update_parameters(update: &UpdateStatement, positions: &mut BTreeSet<usize>) {
185    collect_assignment_parameters(&update.assignments, positions);
186    if let Some(predicate) = &update.selection {
187        collect_expression_parameters(predicate, positions);
188    }
189}
190
191fn collect_delete_parameters(delete: &DeleteStatement, positions: &mut BTreeSet<usize>) {
192    if let Some(predicate) = &delete.selection {
193        collect_expression_parameters(predicate, positions);
194    }
195}
196
197fn collect_assignment_parameters(assignments: &[SqlAssignment], positions: &mut BTreeSet<usize>) {
198    for assignment in assignments {
199        match &assignment.value {
200            SqlAssignmentValue::Value(value) => collect_value_parameter(value, positions),
201            SqlAssignmentValue::Column(_) => {}
202            SqlAssignmentValue::Arithmetic { left, right, .. } => {
203                collect_arithmetic_operand_parameter(left, positions);
204                collect_arithmetic_operand_parameter(right, positions);
205            }
206        }
207    }
208}
209
210fn collect_arithmetic_operand_parameter(
211    operand: &SqlArithmeticOperand,
212    positions: &mut BTreeSet<usize>,
213) {
214    if let SqlArithmeticOperand::Value(value) = operand {
215        collect_value_parameter(value, positions);
216    }
217}
218
219fn collect_expression_parameters(expression: &SqlExpression, positions: &mut BTreeSet<usize>) {
220    expression.visit(&mut |expression| {
221        if let ExprKind::Value(value) = &expression.kind {
222            collect_value_parameter(value, positions);
223        }
224    });
225}
226
227fn collect_value_parameter(value: &SqlValue, positions: &mut BTreeSet<usize>) {
228    if let SqlValue::Parameter(position) = value {
229        positions.insert(*position);
230    }
231}
232
233fn collect_bound_parameter(bound: Option<SqlBound>, positions: &mut BTreeSet<usize>) {
234    if let Some(SqlBound::Parameter(position)) = bound {
235        positions.insert(position);
236    }
237}