1use crate::ast::*;
16use hawdb_core::{HawDBError, Result, Value};
17use sqlparser::ast::Spanned;
18use sqlparser::ast::{
19 BinaryOperator, Distinct, DuplicateTreatment, Expr, FunctionArg, FunctionArgExpr,
20 FunctionArguments, GroupByExpr, Ident, JoinConstraint, JoinOperator, LimitClause, LockClause,
21 LockType, ObjectName, ObjectNamePart, OrderByKind, OrderBySort, SelectItem as ParserSelectItem,
22 SetExpr, Statement as ParserStatement, TableAlias, TableFactor, Value as ParserValue,
23 ValueWithSpan,
24};
25use sqlparser::dialect::PostgreSqlDialect;
26use sqlparser::parser::Parser;
27
28mod branch;
29mod mutation;
30mod schema;
31
32#[cfg(test)]
33mod clause_tests;
34
35#[cfg(test)]
36mod compatibility_tests;
37
38fn reject_unsupported_clauses(
39 statement: &'static str,
40 clauses: &[(&'static str, bool)],
41) -> Result<()> {
42 if let Some((clause, _)) = clauses.iter().find(|(_, present)| *present) {
43 return Err(HawDBError::Semantic(format!(
44 "unsupported PostgreSQL {statement} clause: {clause}"
45 )));
46 }
47 Ok(())
48}
49
50use mutation::{lower_delete_statement, lower_insert_statement, lower_update_statement};
51use schema::{
52 lower_alter_table_statement, lower_create_index_statement, lower_create_table_statement,
53};
54
55pub fn parse_postgres_sql(input: &str) -> Result<SqlStatement> {
56 if let Some(statement) = branch::parse_branch_statement(input)? {
57 return Ok(statement);
58 }
59 let dialect = PostgreSqlDialect {};
60 let statements = Parser::parse_sql(&dialect, input)
61 .map_err(|error| HawDBError::Parse(format!("failed to parse PostgreSQL SQL: {error}")))?;
62 let [statement] = statements.as_slice() else {
63 return Err(HawDBError::Parse(
64 "expected exactly one PostgreSQL SQL statement".to_string(),
65 ));
66 };
67 lower_statement(statement)
68}
69
70fn lower_statement(statement: &ParserStatement) -> Result<SqlStatement> {
71 match statement {
72 ParserStatement::Explain {
73 describe_alias,
74 analyze,
75 verbose,
76 query_plan,
77 estimate,
78 statement,
79 format,
80 options,
81 } => {
82 if !matches!(describe_alias, sqlparser::ast::DescribeAlias::Explain)
83 || *verbose
84 || *query_plan
85 || *estimate
86 || format.is_some()
87 || options.as_ref().is_some_and(|options| !options.is_empty())
88 {
89 return Err(HawDBError::Semantic(
90 "unsupported PostgreSQL EXPLAIN option".to_string(),
91 ));
92 }
93 let statement = lower_statement(statement)?;
94 if !matches!(statement, SqlStatement::Select(_)) {
95 return Err(HawDBError::Semantic(
96 "EXPLAIN only supports relational SELECT".to_string(),
97 ));
98 }
99 Ok(SqlStatement::Explain(SqlExplainStatement {
100 analyze: *analyze,
101 statement: Box::new(statement),
102 }))
103 }
104 ParserStatement::Query(query) => lower_select_statement(query),
105 ParserStatement::Insert(insert) => lower_insert_statement(insert),
106 ParserStatement::Update(update) => lower_update_statement(update),
107 ParserStatement::Delete(delete) => lower_delete_statement(delete),
108 ParserStatement::CreateTable(create) => lower_create_table_statement(create),
109 ParserStatement::CreateIndex(create) => lower_create_index_statement(create),
110 ParserStatement::AlterTable(alter) => lower_alter_table_statement(alter),
111 _ => Err(HawDBError::Semantic(
112 "unsupported PostgreSQL statement kind".to_string(),
113 )),
114 }
115}
116
117fn lower_select_statement(query: &sqlparser::ast::Query) -> Result<SqlStatement> {
118 if query.with.is_some()
119 || query.fetch.is_some()
120 || query.for_clause.is_some()
121 || query.settings.is_some()
122 || query.format_clause.is_some()
123 || !query.pipe_operators.is_empty()
124 {
125 return Err(HawDBError::Semantic(
126 "unsupported PostgreSQL SELECT clause".to_string(),
127 ));
128 }
129 let SetExpr::Select(select) = query.body.as_ref() else {
130 return Err(HawDBError::Semantic(
131 "set operations and nested queries are not supported".to_string(),
132 ));
133 };
134 if select.top.is_some()
135 || select.into.is_some()
136 || select.prewhere.is_some()
137 || !select.lateral_views.is_empty()
138 || !select.connect_by.is_empty()
139 || !select.cluster_by.is_empty()
140 || !select.distribute_by.is_empty()
141 || !select.sort_by.is_empty()
142 || !select.named_window.is_empty()
143 || select.qualify.is_some()
144 || select.value_table_mode.is_some()
145 {
146 return Err(HawDBError::Semantic(
147 "unsupported PostgreSQL SELECT feature".to_string(),
148 ));
149 }
150 let projection = lower_projection(&select.projection)?;
151 let distinct = lower_distinct(select.distinct.as_ref())?;
152
153 let (from_name, from_alias, joins) = if select.from.is_empty() {
154 (None, None, Vec::new())
155 } else {
156 let from = &select.from[0];
157 let (from_name, from_alias) = lower_table_factor(&from.relation)?;
158 let mut joins = from
159 .joins
160 .iter()
161 .map(|join| lower_join(join, 0))
162 .collect::<Result<Vec<_>>>()?;
163 for from in &select.from[1..] {
164 let on_scope_start = joins.len() + 1;
165 let (table, alias) = lower_table_factor(&from.relation)?;
166 joins.push(SqlJoin {
167 kind: SqlJoinKind::Inner,
168 table,
169 alias,
170 on: crate::Expr::value(SqlValue::Literal(Value::Bool(true))),
171 on_scope_start,
172 });
173 for join in &from.joins {
174 joins.push(lower_join(join, on_scope_start)?);
175 }
176 }
177 (Some(from_name), from_alias, joins)
178 };
179
180 let statement = SelectStatement {
181 projection,
182 distinct,
183 from: from_name,
184 from_alias,
185 joins,
186 selection: select
187 .selection
188 .as_ref()
189 .map(|expr| lower_expression(expr, ExpressionPosition::Predicate))
190 .transpose()?,
191 group_by: lower_group_by(&select.group_by)?,
192 having: select
193 .having
194 .as_ref()
195 .map(|expression| lower_expression(expression, ExpressionPosition::Having))
196 .transpose()?,
197 order_by: lower_order_by(query.order_by.as_ref())?,
198 limit: lower_limit(query.limit_clause.as_ref())?,
199 offset: lower_offset(query.limit_clause.as_ref())?,
200 lock_strength: lower_lock_strength(&query.locks)?,
201 };
202
203 if statement.from.is_none()
204 && (statement.selection.is_some()
205 || !statement.group_by.is_empty()
206 || statement.having.is_some()
207 || !statement.order_by.is_empty()
208 || statement.limit.is_some()
209 || statement.offset.is_some()
210 || statement.lock_strength.is_some())
211 {
212 return Err(HawDBError::Semantic(
213 "PostgreSQL SELECT without FROM does not support WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET, or locking clauses".to_string(),
214 ));
215 }
216
217 Ok(SqlStatement::Select(statement))
218}
219
220fn lower_lock_strength(locks: &[LockClause]) -> Result<Option<SqlLockStrength>> {
221 let ([] | [_]) = locks else {
222 return Err(HawDBError::Semantic(
223 "PostgreSQL SELECT supports at most one locking clause".to_string(),
224 ));
225 };
226 let Some(lock) = locks.first() else {
227 return Ok(None);
228 };
229 if lock.of.is_some() || lock.nonblock.is_some() {
230 return Err(HawDBError::Semantic(
231 "FOR UPDATE/SHARE OF, NOWAIT, and SKIP LOCKED are not supported".to_string(),
232 ));
233 }
234 Ok(Some(match lock.lock_type {
235 LockType::Share => SqlLockStrength::Share,
236 LockType::Update => SqlLockStrength::Update,
237 }))
238}
239
240fn lower_projection(items: &[ParserSelectItem]) -> Result<Vec<SelectProjection>> {
241 items
242 .iter()
243 .map(|item| match item {
244 ParserSelectItem::Wildcard(_) => Ok(SelectProjection::Wildcard),
245 ParserSelectItem::UnnamedExpr(expr) => lower_projection_expression(expr, None),
246 ParserSelectItem::ExprWithAlias { expr, alias } => {
247 lower_projection_expression(expr, Some(normalize_ident(alias)))
248 }
249 ParserSelectItem::QualifiedWildcard(_, _) => Err(HawDBError::Semantic(
250 "qualified wildcards are not supported".to_string(),
251 )),
252 ParserSelectItem::ExprWithAliases { .. } => Err(HawDBError::Semantic(
253 "multiple projection aliases are not supported".to_string(),
254 )),
255 })
256 .collect()
257}
258
259fn lower_projection_expression(expr: &Expr, alias: Option<String>) -> Result<SelectProjection> {
260 Ok(SelectProjection::Expression {
261 expression: lower_expression(expr, ExpressionPosition::Scalar)?,
262 alias,
263 })
264}
265
266fn lower_order_by(order_by: Option<&sqlparser::ast::OrderBy>) -> Result<Vec<SqlOrderItem>> {
267 let Some(order_by) = order_by else {
268 return Ok(Vec::new());
269 };
270 let OrderByKind::Expressions(expressions) = &order_by.kind else {
271 return Err(HawDBError::Semantic(
272 "ORDER BY ALL is not supported".to_string(),
273 ));
274 };
275 expressions
276 .iter()
277 .map(|item| {
278 Ok(SqlOrderItem {
279 expression: lower_expression(&item.expr, ExpressionPosition::Column)?,
280 direction: lower_order_direction(item.options.sort.as_ref())?,
281 nulls: match item.options.nulls_first {
282 Some(true) => SqlNullOrder::First,
283 Some(false) => SqlNullOrder::Last,
284 None => SqlNullOrder::DialectDefault,
285 },
286 })
287 })
288 .collect()
289}
290
291fn lower_order_direction(sort: Option<&OrderBySort>) -> Result<SqlOrderDirection> {
292 match sort {
293 Some(OrderBySort::Desc) => Ok(SqlOrderDirection::Desc),
294 Some(OrderBySort::Asc) | None => Ok(SqlOrderDirection::Asc),
295 Some(OrderBySort::Using(_)) => Err(HawDBError::Semantic(
296 "ORDER BY USING is not supported".to_string(),
297 )),
298 }
299}
300
301fn lower_limit(limit_clause: Option<&LimitClause>) -> Result<Option<SqlBound>> {
302 let Some(limit_clause) = limit_clause else {
303 return Ok(None);
304 };
305 match limit_clause {
306 LimitClause::LimitOffset { limit, .. } => limit
307 .as_ref()
308 .map(lower_nonnegative_integer_expr)
309 .transpose(),
310 LimitClause::OffsetCommaLimit { limit, .. } => {
311 Ok(Some(lower_nonnegative_integer_expr(limit)?))
312 }
313 }
314}
315
316fn lower_offset(limit_clause: Option<&LimitClause>) -> Result<Option<SqlBound>> {
317 let Some(limit_clause) = limit_clause else {
318 return Ok(None);
319 };
320 match limit_clause {
321 LimitClause::LimitOffset { offset, .. } => offset
322 .as_ref()
323 .map(|offset| lower_nonnegative_integer_expr(&offset.value))
324 .transpose(),
325 LimitClause::OffsetCommaLimit { offset, .. } => {
326 Ok(Some(lower_nonnegative_integer_expr(offset)?))
327 }
328 }
329}
330
331#[derive(Clone, Copy)]
332pub(super) enum ExpressionPosition {
333 Predicate,
334 Having,
335 HavingScalar,
336 Scalar,
337 Column,
338 Value,
339}
340
341pub(super) fn lower_expression(expr: &Expr, position: ExpressionPosition) -> Result<crate::Expr> {
344 use ExpressionPosition::{Column, Having, HavingScalar, Predicate, Scalar, Value};
345 let having = matches!(position, Having | HavingScalar);
346 let operand = if having { HavingScalar } else { Column };
347 let lower = |expr: &Expr, position| lower_expression(expr, position).map(Box::new);
348 let kind = match position {
349 Column => ExprKind::Column(lower_column_expr(expr)?),
350 Value => ExprKind::Value(lower_literal_expr(expr)?),
351 Scalar | HavingScalar => match expr {
352 Expr::Nested(inner) if having => lower_expression(inner, HavingScalar)?.kind,
353 Expr::Identifier(_) | Expr::CompoundIdentifier(_) => {
354 ExprKind::Column(lower_column_expr(expr)?)
355 }
356 Expr::Value(_) | Expr::Nested(_) | Expr::UnaryOp { .. } => {
357 ExprKind::Value(lower_literal_expr(expr)?)
358 }
359 Expr::Function(function) => lower_function_expression(function, position)?,
360 _ => {
361 return Err(HawDBError::Semantic(format!(
362 "unsupported PostgreSQL projection expression {expr}"
363 )))
364 }
365 },
366 Predicate | Having => match expr {
367 Expr::BinaryOp { left, op, right } => match op {
368 BinaryOperator::And => {
369 ExprKind::And(lower(left, position)?, lower(right, position)?)
370 }
371 BinaryOperator::Or => ExprKind::Or(lower(left, position)?, lower(right, position)?),
372 BinaryOperator::Eq
373 | BinaryOperator::NotEq
374 | BinaryOperator::Lt
375 | BinaryOperator::LtEq
376 | BinaryOperator::Gt
377 | BinaryOperator::GtEq => ExprKind::Compare {
378 left: lower(left, operand)?,
379 op: lower_comparison_op(op),
380 right: lower(
381 right,
382 if having {
383 HavingScalar
384 } else {
385 match right.as_ref() {
386 Expr::Identifier(_) | Expr::CompoundIdentifier(_) => Column,
387 _ => Value,
388 }
389 },
390 )?,
391 },
392 _ => {
393 return Err(HawDBError::Semantic(format!(
394 "unsupported PostgreSQL predicate operator {op}"
395 )))
396 }
397 },
398 Expr::Nested(inner) => lower_expression(inner, position)?.kind,
399 Expr::UnaryOp {
400 op: sqlparser::ast::UnaryOperator::Not,
401 expr,
402 } => ExprKind::Not(lower(expr, position)?),
403 Expr::InList {
404 expr,
405 list,
406 negated,
407 } => ExprKind::InList {
408 left: lower(expr, operand)?,
409 values: list
410 .iter()
411 .map(|value| lower_expression(value, if having { HavingScalar } else { Value }))
412 .collect::<Result<_>>()?,
413 negated: *negated,
414 },
415 Expr::Like {
416 negated,
417 any,
418 expr,
419 pattern,
420 escape_char,
421 } => lower_like_expression(
422 expr,
423 pattern,
424 *negated,
425 *any,
426 escape_char.as_deref(),
427 false,
428 having,
429 )?,
430 Expr::ILike {
431 negated,
432 any,
433 expr,
434 pattern,
435 escape_char,
436 } => lower_like_expression(
437 expr,
438 pattern,
439 *negated,
440 *any,
441 escape_char.as_deref(),
442 true,
443 having,
444 )?,
445 Expr::IsNull(expr) => ExprKind::IsNull {
446 expression: lower(expr, operand)?,
447 negated: false,
448 },
449 Expr::IsNotNull(expr) => ExprKind::IsNull {
450 expression: lower(expr, operand)?,
451 negated: true,
452 },
453 _ if having => lower_expression(expr, HavingScalar)?.kind,
454 _ => {
455 return Err(HawDBError::Semantic(format!(
456 "unsupported PostgreSQL predicate expression {expr}"
457 )))
458 }
459 },
460 };
461 let span = expr.span();
462 Ok(crate::Expr {
463 kind,
464 span: SqlSourceSpan {
465 start: SqlSourceLocation {
466 line: span.start.line,
467 column: span.start.column,
468 },
469 end: SqlSourceLocation {
470 line: span.end.line,
471 column: span.end.column,
472 },
473 },
474 })
475}
476
477fn lower_like_expression(
478 expr: &Expr,
479 pattern: &Expr,
480 negated: bool,
481 any: bool,
482 escape_char: Option<&Expr>,
483 case_insensitive: bool,
484 having: bool,
485) -> Result<ExprKind> {
486 if any {
487 return Err(HawDBError::Semantic(
488 "PostgreSQL LIKE ANY is not supported".to_string(),
489 ));
490 }
491 Ok(ExprKind::Like {
492 left: Box::new(lower_expression(
493 expr,
494 if having {
495 ExpressionPosition::HavingScalar
496 } else {
497 ExpressionPosition::Column
498 },
499 )?),
500 pattern: Box::new(lower_expression(
501 pattern,
502 if having {
503 ExpressionPosition::HavingScalar
504 } else {
505 ExpressionPosition::Value
506 },
507 )?),
508 case_insensitive,
509 negated,
510 escape: lower_like_escape(escape_char)?,
511 })
512}
513
514fn lower_like_escape(escape_char: Option<&Expr>) -> Result<SqlLikeEscape> {
515 let Some(escape_char) = escape_char else {
516 return Ok(SqlLikeEscape::Character('\\'));
517 };
518 let escape = match escape_char {
519 Expr::Value(value) => value.value.clone().into_string(),
520 _ => None,
521 };
522 let Some(escape) = escape else {
523 return Err(HawDBError::Semantic(
524 "LIKE ESCAPE must be a string literal".to_string(),
525 ));
526 };
527 let mut characters = escape.chars();
528 let Some(character) = characters.next() else {
529 return Ok(SqlLikeEscape::Disabled);
530 };
531 if characters.next().is_some() {
532 return Err(HawDBError::Semantic(
533 "LIKE ESCAPE must contain at most one Unicode scalar".to_string(),
534 ));
535 }
536 Ok(SqlLikeEscape::Character(character))
537}
538
539fn lower_distinct(distinct: Option<&Distinct>) -> Result<bool> {
540 match distinct {
541 None | Some(Distinct::All) => Ok(false),
542 Some(Distinct::Distinct) => Ok(true),
543 Some(Distinct::On(_)) => Err(HawDBError::Semantic(
544 "PostgreSQL DISTINCT ON is not supported".to_string(),
545 )),
546 }
547}
548
549fn lower_group_by(group_by: &GroupByExpr) -> Result<Vec<SqlColumnRef>> {
550 match group_by {
551 GroupByExpr::Expressions(expressions, modifiers) if modifiers.is_empty() => expressions
552 .iter()
553 .map(lower_column_expr)
554 .collect::<Result<Vec<_>>>(),
555 GroupByExpr::Expressions(_, _) | GroupByExpr::All(_) => Err(HawDBError::Semantic(
556 "PostgreSQL GROUP BY modifiers and GROUP BY ALL are not supported".to_string(),
557 )),
558 }
559}
560
561pub(super) fn lower_table_factor(table: &TableFactor) -> Result<(SqlTableName, Option<String>)> {
562 let TableFactor::Table { name, alias, .. } = table else {
563 return Err(HawDBError::Semantic(
564 "PostgreSQL relational SQL supports base tables only".to_string(),
565 ));
566 };
567 Ok((lower_table_name(name)?, lower_table_alias(alias.as_ref())?))
568}
569
570fn lower_table_alias(alias: Option<&TableAlias>) -> Result<Option<String>> {
571 let Some(alias) = alias else {
572 return Ok(None);
573 };
574 if !alias.columns.is_empty() {
575 return Err(HawDBError::Semantic(
576 "PostgreSQL table column aliases are not supported".to_string(),
577 ));
578 }
579 if alias.at.is_some() {
580 return Err(HawDBError::Semantic(
581 "PostgreSQL table AT aliases are not supported".to_string(),
582 ));
583 }
584 Ok(Some(normalize_ident(&alias.name)))
585}
586
587fn lower_join(join: &sqlparser::ast::Join, on_scope_start: usize) -> Result<SqlJoin> {
588 let (table, alias) = lower_table_factor(&join.relation)?;
589 if matches!(
590 join.join_operator,
591 JoinOperator::CrossJoin(JoinConstraint::None)
592 ) {
593 return Ok(SqlJoin {
594 kind: SqlJoinKind::Inner,
595 table,
596 alias,
597 on: crate::Expr::value(SqlValue::Literal(Value::Bool(true))),
598 on_scope_start,
599 });
600 }
601 let (kind, constraint) = match &join.join_operator {
602 JoinOperator::Join(constraint) | JoinOperator::Inner(constraint) => {
603 (SqlJoinKind::Inner, constraint)
604 }
605 JoinOperator::Left(constraint) | JoinOperator::LeftOuter(constraint) => {
606 (SqlJoinKind::Left, constraint)
607 }
608 other => {
609 return Err(HawDBError::Semantic(format!(
610 "unsupported PostgreSQL join operator {other:?}"
611 )));
612 }
613 };
614 let JoinConstraint::On(on) = constraint else {
615 return Err(HawDBError::Semantic(
616 "PostgreSQL joins require an ON predicate".to_string(),
617 ));
618 };
619 Ok(SqlJoin {
620 kind,
621 table,
622 alias,
623 on: lower_expression(on, ExpressionPosition::Predicate)?,
624 on_scope_start,
625 })
626}
627
628fn lower_function_expression(
629 function: &sqlparser::ast::Function,
630 argument_position: ExpressionPosition,
631) -> Result<ExprKind> {
632 if function.uses_odbc_syntax
633 || !matches!(function.parameters, FunctionArguments::None)
634 || function.null_treatment.is_some()
635 || function.over.is_some()
636 || !function.within_group.is_empty()
637 {
638 return Err(HawDBError::Semantic(
639 "unsupported PostgreSQL function clause".to_string(),
640 ));
641 }
642 let name_parts = object_name_parts(&function.name)?;
643 let [name] = name_parts.as_slice() else {
644 return Err(HawDBError::Semantic(
645 "qualified PostgreSQL function names are not supported".to_string(),
646 ));
647 };
648 let FunctionArguments::List(arguments) = &function.args else {
649 return Err(HawDBError::Semantic(
650 "PostgreSQL functions require an argument list".to_string(),
651 ));
652 };
653 if !arguments.clauses.is_empty() {
654 return Err(HawDBError::Semantic(
655 "PostgreSQL function argument clauses are not supported".to_string(),
656 ));
657 }
658 let distinct = match arguments.duplicate_treatment {
659 None | Some(DuplicateTreatment::All) => false,
660 Some(DuplicateTreatment::Distinct) => true,
661 };
662 let arguments = arguments
663 .args
664 .iter()
665 .map(|argument| match argument {
666 FunctionArg::Unnamed(FunctionArgExpr::Expr(expr)) => {
667 lower_expression(expr, argument_position).map(SqlFunctionArgument::Expression)
668 }
669 FunctionArg::Unnamed(FunctionArgExpr::Wildcard) => Ok(SqlFunctionArgument::Wildcard),
670 _ => Err(HawDBError::Semantic(
671 "named and qualified-wildcard PostgreSQL function arguments are not supported"
672 .to_string(),
673 )),
674 })
675 .collect::<Result<Vec<_>>>()?;
676 let filter = function
677 .filter
678 .as_deref()
679 .map(|expr| lower_expression(expr, ExpressionPosition::Predicate).map(Box::new))
680 .transpose()?;
681 match name.as_str() {
682 "count" | "sum" => Ok(ExprKind::Function {
683 name: name.clone(),
684 arguments,
685 distinct,
686 filter,
687 }),
688 "min" | "max" | "coalesce" | "octet_length" | "uuidv7" | "version" if filter.is_none() => {
689 Ok(ExprKind::Function {
690 name: name.clone(),
691 arguments,
692 distinct,
693 filter,
694 })
695 }
696 "min" | "max" | "coalesce" | "octet_length" | "uuidv7" | "version" => {
697 Err(HawDBError::Semantic(
698 "FILTER is supported only for COUNT and SUM aggregates".to_string(),
699 ))
700 }
701 _ => Err(HawDBError::Semantic(format!(
702 "unsupported PostgreSQL function {name}"
703 ))),
704 }
705}
706
707fn lower_comparison_op(op: &BinaryOperator) -> SqlComparisonOp {
708 match op {
709 BinaryOperator::Eq => SqlComparisonOp::Eq,
710 BinaryOperator::NotEq => SqlComparisonOp::NotEq,
711 BinaryOperator::Lt => SqlComparisonOp::Lt,
712 BinaryOperator::LtEq => SqlComparisonOp::Lte,
713 BinaryOperator::Gt => SqlComparisonOp::Gt,
714 BinaryOperator::GtEq => SqlComparisonOp::Gte,
715 _ => unreachable!("checked by caller"),
716 }
717}
718
719pub(super) fn lower_table_name(name: &ObjectName) -> Result<SqlTableName> {
720 let parts = object_name_parts(name)?;
721 match parts.as_slice() {
722 [name] => Ok(SqlTableName {
723 schema: None,
724 name: name.clone(),
725 }),
726 [schema, name] => Ok(SqlTableName {
727 schema: Some(schema.clone()),
728 name: name.clone(),
729 }),
730 _ => Err(HawDBError::Semantic(
731 "PostgreSQL SELECT currently supports one- or two-part table names".to_string(),
732 )),
733 }
734}
735
736pub(super) fn lower_column_expr(expr: &Expr) -> Result<SqlColumnRef> {
737 match expr {
738 Expr::Identifier(ident) => Ok(SqlColumnRef {
739 qualifier: None,
740 name: normalize_ident(ident),
741 }),
742 Expr::CompoundIdentifier(parts) => match parts.as_slice() {
743 [qualifier, name] => Ok(SqlColumnRef {
744 qualifier: Some(normalize_ident(qualifier)),
745 name: normalize_ident(name),
746 }),
747 _ => Err(HawDBError::Semantic(
748 "PostgreSQL SELECT currently supports one- or two-part column names".to_string(),
749 )),
750 },
751 _ => Err(HawDBError::Semantic(format!(
752 "expected a column reference, got {expr}"
753 ))),
754 }
755}
756
757pub(super) fn lower_literal_expr(expr: &Expr) -> Result<SqlValue> {
758 match expr {
759 Expr::Value(value) => lower_value(value),
760 Expr::Nested(inner) => lower_literal_expr(inner),
761 Expr::UnaryOp {
762 op: sqlparser::ast::UnaryOperator::Minus,
763 expr,
764 } => match lower_literal_expr(expr)? {
765 SqlValue::Literal(Value::Int(value)) => Ok(SqlValue::Literal(Value::Int(-value))),
766 SqlValue::Literal(Value::Float(value)) => Ok(SqlValue::Literal(Value::Float(-value))),
767 value => Err(HawDBError::Semantic(format!(
768 "cannot negate literal value {value}"
769 ))),
770 },
771 _ => Err(HawDBError::Semantic(format!(
772 "expected a literal value, got {expr}"
773 ))),
774 }
775}
776
777fn lower_value(value: &ValueWithSpan) -> Result<SqlValue> {
778 match &value.value {
779 ParserValue::Boolean(value) => Ok(SqlValue::Literal(Value::Bool(*value))),
780 ParserValue::Null => Ok(SqlValue::Literal(Value::Null)),
781 ParserValue::Number(raw, _) => lower_number(raw).map(SqlValue::Literal),
782 ParserValue::SingleQuotedString(value)
783 | ParserValue::DoubleQuotedString(value)
784 | ParserValue::TripleSingleQuotedString(value)
785 | ParserValue::TripleDoubleQuotedString(value)
786 | ParserValue::EscapedStringLiteral(value)
787 | ParserValue::UnicodeStringLiteral(value) => {
788 Ok(SqlValue::Literal(Value::String(value.clone())))
789 }
790 ParserValue::Placeholder(raw) => Ok(SqlValue::Parameter(postgres_parameter_position(raw)?)),
791 _ => Err(HawDBError::Semantic(format!(
792 "unsupported PostgreSQL literal {value}"
793 ))),
794 }
795}
796
797fn lower_number(raw: &str) -> Result<Value> {
798 if raw.contains('.') {
799 raw.parse::<f64>().map(Value::Float).map_err(|error| {
800 HawDBError::Semantic(format!("invalid PostgreSQL number {raw}: {error}"))
801 })
802 } else {
803 raw.parse::<i64>().map(Value::Int).map_err(|error| {
804 HawDBError::Semantic(format!("invalid PostgreSQL integer {raw}: {error}"))
805 })
806 }
807}
808
809fn lower_nonnegative_integer_expr(expr: &Expr) -> Result<SqlBound> {
810 let value = lower_literal_expr(expr)?;
811 match value {
812 SqlValue::Literal(Value::Int(value)) if value >= 0 => Ok(SqlBound::Literal(value as u64)),
813 SqlValue::Parameter(position) => Ok(SqlBound::Parameter(position)),
814 _ => Err(HawDBError::Semantic(
815 "LIMIT/OFFSET must be non-negative integers or PostgreSQL parameters".to_string(),
816 )),
817 }
818}
819
820fn postgres_parameter_position(raw: &str) -> Result<usize> {
821 let Some(raw) = raw.strip_prefix('$') else {
822 return Err(HawDBError::Semantic(
823 "PostgreSQL parameters must use one-based $n syntax".to_string(),
824 ));
825 };
826 let position = raw.parse::<usize>().map_err(|_| {
827 HawDBError::Semantic("PostgreSQL parameters must use one-based $n syntax".to_string())
828 })?;
829 if position == 0 {
830 return Err(HawDBError::Semantic(
831 "PostgreSQL parameters are one-based".to_string(),
832 ));
833 }
834 Ok(position)
835}
836
837pub(super) fn object_name_parts(name: &ObjectName) -> Result<Vec<String>> {
838 name.0
839 .iter()
840 .map(|part| match part {
841 ObjectNamePart::Identifier(ident) => Ok(normalize_ident(ident)),
842 _ => Err(HawDBError::Semantic(
843 "object name functions are not supported".to_string(),
844 )),
845 })
846 .collect()
847}
848
849pub(super) fn normalize_ident(ident: &Ident) -> String {
850 if ident.quote_style.is_some() {
851 ident.value.clone()
852 } else {
853 ident.value.to_ascii_lowercase()
854 }
855}