1use 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}