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::CreateSearchIndex(_)
79        | SqlStatement::DropSearchIndex(_) => {}
80        SqlStatement::Insert(insert) => collect_insert_parameters(insert, positions),
81        SqlStatement::Update(update) => collect_update_parameters(update, positions),
82        SqlStatement::Delete(delete) => collect_delete_parameters(delete, positions),
83        SqlStatement::CreateTable(create) => {
84            for column in &create.columns {
85                if let Some(value) = &column.default {
86                    collect_column_default_parameter(value, positions);
87                }
88            }
89        }
90        SqlStatement::AlterTableAddColumn(alter) => {
91            if let Some(value) = &alter.column.default {
92                collect_column_default_parameter(value, positions);
93            }
94        }
95        SqlStatement::CreateIndex(_) => {}
96        SqlStatement::Branch(branch) => collect_branch_parameters(branch, positions),
97    }
98}
99
100fn collect_branch_parameters(branch: &BranchSqlStatement, positions: &mut BTreeSet<usize>) {
101    match branch {
102        BranchSqlStatement::ShowBranches(statement) => {
103            collect_bound_parameter(Some(statement.limit), positions);
104            collect_bound_parameter(statement.offset, positions);
105        }
106        BranchSqlStatement::ShowBranch(statement) => {
107            collect_branch_selector(&statement.selector, positions)
108        }
109        BranchSqlStatement::UseBranch(selector) => collect_branch_selector(selector, positions),
110        BranchSqlStatement::ShowCurrentBranch => {}
111        BranchSqlStatement::CreateBranch(statement) => {
112            if let Some(name) = &statement.name {
113                collect_branch_value(name, positions);
114            }
115            collect_branch_selector(&statement.source, positions);
116            collect_bound_parameter(Some(statement.expected_source_revision), positions);
117            collect_branch_value(&statement.request_key, positions);
118            if let Some(owner) = &statement.owner {
119                collect_branch_value(owner, positions);
120            }
121        }
122        BranchSqlStatement::DropBranch(statement) => {
123            collect_branch_value(&statement.id, positions);
124            collect_bound_parameter(Some(statement.expected_metadata_revision), positions);
125        }
126    }
127}
128
129fn collect_branch_selector(selector: &BranchSqlSelector, positions: &mut BTreeSet<usize>) {
130    let (BranchSqlSelector::Name(value) | BranchSqlSelector::Id(value)) = selector;
131    collect_branch_value(value, positions);
132}
133
134fn collect_branch_value(value: &BranchSqlValue, positions: &mut BTreeSet<usize>) {
135    if let BranchSqlValue::Parameter(position) = value {
136        positions.insert(*position);
137    }
138}
139
140fn collect_column_default_parameter(default: &SqlColumnDefault, positions: &mut BTreeSet<usize>) {
141    if let SqlColumnDefault::Literal(value) = default {
142        collect_value_parameter(value, positions);
143    }
144}
145
146fn collect_select_parameters(select: &SelectStatement, positions: &mut BTreeSet<usize>) {
147    if let Some(search) = select.vector_index() {
148        for value in [&search.index, &search.embedding, &search.top_k] {
149            collect_value_parameter(value, positions);
150        }
151        if let Some(value) = &search.max_work {
152            collect_value_parameter(value, positions);
153        }
154        if let Some(value) = &search.candidates {
155            collect_value_parameter(value, positions);
156        }
157    }
158    if let Some(search) = select.text_search() {
159        for value in [&search.index, &search.query, &search.top_k] {
160            collect_value_parameter(value, positions);
161        }
162    }
163    for projection in &select.projection {
164        if let SelectProjection::Expression { expression, .. } = projection {
165            collect_expression_parameters(expression, positions);
166        }
167    }
168    for join in &select.joins {
169        collect_expression_parameters(&join.on, positions);
170    }
171    if let Some(predicate) = &select.selection {
172        collect_expression_parameters(predicate, positions);
173    }
174    if let Some(predicate) = &select.having {
175        collect_expression_parameters(predicate, positions);
176    }
177    for order in &select.order_by {
178        collect_expression_parameters(&order.expression, positions);
179    }
180    collect_bound_parameter(select.limit, positions);
181    collect_bound_parameter(select.offset, positions);
182}
183
184fn collect_insert_parameters(insert: &InsertStatement, positions: &mut BTreeSet<usize>) {
185    for row in &insert.rows {
186        for value in row {
187            collect_value_parameter(value, positions);
188        }
189    }
190    if let Some(conflict) = &insert.on_conflict
191        && let crate::SqlConflictAction::DoUpdate(assignments) = &conflict.action
192    {
193        collect_assignment_parameters(assignments, positions);
194    }
195}
196
197fn collect_update_parameters(update: &UpdateStatement, positions: &mut BTreeSet<usize>) {
198    collect_assignment_parameters(&update.assignments, positions);
199    if let Some(predicate) = &update.selection {
200        collect_expression_parameters(predicate, positions);
201    }
202}
203
204fn collect_delete_parameters(delete: &DeleteStatement, positions: &mut BTreeSet<usize>) {
205    if let Some(predicate) = &delete.selection {
206        collect_expression_parameters(predicate, positions);
207    }
208}
209
210fn collect_assignment_parameters(assignments: &[SqlAssignment], positions: &mut BTreeSet<usize>) {
211    for assignment in assignments {
212        match &assignment.value {
213            SqlAssignmentValue::Value(value) => collect_value_parameter(value, positions),
214            SqlAssignmentValue::Expression(expression) => {
215                collect_expression_parameters(expression, positions)
216            }
217            SqlAssignmentValue::Column(_) => {}
218            SqlAssignmentValue::Arithmetic { left, right, .. } => {
219                collect_arithmetic_operand_parameter(left, positions);
220                collect_arithmetic_operand_parameter(right, positions);
221            }
222        }
223    }
224}
225
226fn collect_arithmetic_operand_parameter(
227    operand: &SqlArithmeticOperand,
228    positions: &mut BTreeSet<usize>,
229) {
230    if let SqlArithmeticOperand::Value(value) = operand {
231        collect_value_parameter(value, positions);
232    }
233}
234
235fn collect_expression_parameters(expression: &SqlExpression, positions: &mut BTreeSet<usize>) {
236    expression.visit(&mut |expression| {
237        if let ExprKind::Value(SqlValue::Parameter(position)) = &expression.kind {
238            positions.insert(*position);
239        }
240    });
241}
242
243fn collect_value_parameter(value: &SqlValue, positions: &mut BTreeSet<usize>) {
244    match value {
245        SqlValue::Parameter(position) => {
246            positions.insert(*position);
247        }
248        SqlValue::Expression(expression) => collect_expression_parameters(expression, positions),
249        SqlValue::Literal(_) => {}
250    }
251}
252
253fn collect_bound_parameter(bound: Option<SqlBound>, positions: &mut BTreeSet<usize>) {
254    if let Some(SqlBound::Parameter(position)) = bound {
255        positions.insert(position);
256    }
257}