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