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