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