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