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