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