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