From 174abaf37f4aa1d642dfc16c98000da909e6d871 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 29 Aug 2026 23:14:14 -0400 Subject: [PATCH] fmt: fix gaps that dropped tokens --- Cargo.lock | 1 + crates/squawk_fmt/Cargo.toml | 1 + crates/squawk_fmt/src/fmt.rs | 1323 +++++++++-------- .../tests/after/alter_default_privileges.snap | 25 +- .../tests/after/alter_foreign_table.snap | 7 + .../tests/after/alter_operator.snap | 3 +- .../tests/after/alter_property_graph.snap | 13 +- .../tests/after/alter_publication.snap | 6 + .../tests/after/alter_sequence.snap | 15 + .../tests/after/alter_subscription.snap | 9 + .../squawk_fmt/tests/after/alter_table.snap | 113 ++ .../tests/after/compound_select.snap | 11 + .../tests/after/create_aggregate.snap | 26 + .../tests/after/create_function.snap | 26 +- .../squawk_fmt/tests/after/create_index.snap | 16 + .../tests/after/create_operator.snap | 49 + .../tests/after/create_property_graph.snap | 8 + .../tests/after/create_publication.snap | 9 + .../squawk_fmt/tests/after/create_schema.snap | 24 + .../tests/after/create_sequence.snap | 16 + .../tests/after/create_subscription.snap | 13 + .../squawk_fmt/tests/after/create_table.snap | 19 +- .../tests/after/create_trigger.snap | 20 + .../squawk_fmt/tests/after/create_type.snap | 8 + .../tests/after/create_user_mapping.snap | 3 + crates/squawk_fmt/tests/after/delete.snap | 12 + .../tests/after/drop_user_mapping.snap | 4 + crates/squawk_fmt/tests/after/from.snap | 161 +- crates/squawk_fmt/tests/after/merge.snap | 13 + .../squawk_fmt/tests/after/paren_select.snap | 15 + .../tests/after/select_clauses.snap | 23 + .../squawk_fmt/tests/after/select_expr.snap | 66 + .../squawk_fmt/tests/after/select_into.snap | 3 + crates/squawk_fmt/tests/after/set.snap | 25 +- crates/squawk_fmt/tests/after/table.snap | 3 + .../tests/after/table_constraints.snap | 67 +- .../tests/after/transaction_control.snap | 21 + crates/squawk_fmt/tests/after/values.snap | 3 + .../tests/before/alter_default_privileges.sql | 4 + .../tests/before/alter_foreign_table.sql | 2 + .../tests/before/alter_property_graph.sql | 2 + .../tests/before/alter_publication.sql | 4 + .../tests/before/alter_sequence.sql | 4 + .../tests/before/alter_subscription.sql | 4 + .../squawk_fmt/tests/before/alter_table.sql | 52 + .../tests/before/compound_select.sql | 3 + .../tests/before/create_aggregate.sql | 8 + .../tests/before/create_function.sql | 8 +- .../squawk_fmt/tests/before/create_index.sql | 8 + .../tests/before/create_operator.sql | 18 + .../tests/before/create_property_graph.sql | 2 + .../tests/before/create_publication.sql | 4 + .../squawk_fmt/tests/before/create_schema.sql | 12 + .../tests/before/create_sequence.sql | 4 + .../tests/before/create_subscription.sql | 4 + .../squawk_fmt/tests/before/create_table.sql | 12 + .../tests/before/create_trigger.sql | 4 + .../squawk_fmt/tests/before/create_type.sql | 4 + .../tests/before/create_user_mapping.sql | 2 + crates/squawk_fmt/tests/before/delete.sql | 4 + .../tests/before/drop_user_mapping.sql | 2 + crates/squawk_fmt/tests/before/from.sql | 10 + crates/squawk_fmt/tests/before/merge.sql | 4 + .../squawk_fmt/tests/before/paren_select.sql | 7 + .../tests/before/select_clauses.sql | 9 + .../squawk_fmt/tests/before/select_expr.sql | 17 + .../squawk_fmt/tests/before/select_into.sql | 3 + crates/squawk_fmt/tests/before/set.sql | 18 +- crates/squawk_fmt/tests/before/table.sql | 3 + .../tests/before/table_constraints.sql | 33 + .../tests/before/transaction_control.sql | 16 + crates/squawk_fmt/tests/before/values.sql | 3 + crates/squawk_ide/src/ast_nav.rs | 82 +- crates/squawk_ide/src/binder.rs | 63 +- crates/squawk_ide/src/classify.rs | 6 +- .../code_actions/rewrite_select_as_table.rs | 20 +- crates/squawk_ide/src/completion.rs | 20 +- crates/squawk_ide/src/expand_selection.rs | 2 +- crates/squawk_ide/src/folding_ranges.rs | 2 +- crates/squawk_ide/src/hover.rs | 2 +- crates/squawk_ide/src/resolve.rs | 83 +- crates/squawk_linter/src/analyze.rs | 12 +- .../src/rules/require_timeout_settings.rs | 1 + .../src/generated/syntax_kind.rs | 8 +- crates/squawk_parser/src/grammar.rs | 221 ++- .../tests/data/err/create_table.sql | 3 + .../tests/data/err/routine_body.sql | 11 + .../squawk_parser/tests/data/err/select.sql | 3 + crates/squawk_parser/tests/data/err/set.sql | 8 + .../tests/data/ok/alter_function.sql | 30 +- .../tests/data/ok/alter_procedure.sql | 6 + .../tests/data/ok/alter_routine.sql | 24 +- .../tests/data/ok/create_collation.sql | 5 + .../tests/data/ok/create_function.sql | 8 +- .../tests/data/ok/create_operator.sql | 32 + .../tests/data/ok/create_procedure.sql | 13 +- .../data/ok/create_text_search_config.sql | 5 + .../tests/data/ok/create_type.sql | 10 + .../squawk_parser/tests/data/ok/schemas.sql | 4 + crates/squawk_parser/tests/data/ok/select.sql | 7 + .../tests/data/ok/select_operators.sql | 3 + .../snapshots/tests__alter_function_ok.snap | 240 ++- .../snapshots/tests__alter_procedure_ok.snap | 51 +- .../tests__alter_property_graph_ok.snap | 490 +++--- .../snapshots/tests__alter_routine_ok.snap | 184 ++- .../snapshots/tests__alter_table_ok.snap | 44 +- .../snapshots/tests__create_collation_ok.snap | 30 + .../snapshots/tests__create_function_ok.snap | 260 ++-- .../snapshots/tests__create_operator_ok.snap | 237 +++ .../snapshots/tests__create_procedure_ok.snap | 216 ++- .../snapshots/tests__create_table_err.snap | 40 + .../tests__create_text_search_config_ok.snap | 38 + .../snapshots/tests__create_type_ok.snap | 111 ++ .../tests__drop_user_mapping_ok.snap | 8 +- .../snapshots/tests__routine_body_err.snap | 269 ++++ .../tests/snapshots/tests__schemas_ok.snap | 69 + ...ests__select_compound_union_select_ok.snap | 10 +- .../tests/snapshots/tests__select_err.snap | 41 + .../tests/snapshots/tests__select_ok.snap | 108 ++ .../snapshots/tests__select_operators_ok.snap | 26 + .../tests/snapshots/tests__set_err.snap | 84 ++ crates/squawk_syntax/src/ast.rs | 2 +- .../squawk_syntax/src/ast/generated/nodes.rs | 1254 ++++++++++++---- crates/squawk_syntax/src/ast/node_ext.rs | 90 +- crates/squawk_syntax/src/ast/traits.rs | 27 + crates/squawk_syntax/src/postgresql.ungram | 211 +-- ...__test__begin_atomic_stmts_validation.snap | 196 +-- ..._test__conflicting_options_validation.snap | 512 +++++++ ...x__test__empty_column_list_validation.snap | 344 +++-- crates/squawk_syntax/src/validation.rs | 110 +- .../validation/conflicting_options.sql | 49 + .../validation/empty_column_list.sql | 2 + crates/xtask/src/sync_pg.rs | 17 + .../regression_suite/collate.icu.utf8.sql | 2 +- .../regression_suite/collate.linux.utf8.sql | 2 +- postgres/regression_suite/collate.sql | 2 +- postgres/regression_suite/constraints.sql | 2 +- .../regression_suite/create_function_sql.sql | 2 +- .../regression_suite/sqljson_jsontable.sql | 2 +- 139 files changed, 6424 insertions(+), 2126 deletions(-) create mode 100644 crates/squawk_parser/tests/data/err/routine_body.sql create mode 100644 crates/squawk_parser/tests/data/err/set.sql create mode 100644 crates/squawk_parser/tests/snapshots/tests__routine_body_err.snap create mode 100644 crates/squawk_parser/tests/snapshots/tests__set_err.snap create mode 100644 crates/squawk_syntax/src/snapshots/squawk_syntax__test__conflicting_options_validation.snap create mode 100644 crates/squawk_syntax/test_data/validation/conflicting_options.sql diff --git a/Cargo.lock b/Cargo.lock index 5d7a81191..60c292b8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2450,6 +2450,7 @@ dependencies = [ "camino", "clap", "dir-test", + "either", "insta", "itertools", "rowan", diff --git a/crates/squawk_fmt/Cargo.toml b/crates/squawk_fmt/Cargo.toml index a193907fb..179092ab6 100644 --- a/crates/squawk_fmt/Cargo.toml +++ b/crates/squawk_fmt/Cargo.toml @@ -25,6 +25,7 @@ squawk-line-index.workspace = true rowan.workspace = true clap.workspace = true anyhow.workspace = true +either.workspace = true annotate-snippets.workspace = true [dev-dependencies] diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index e37fc18bb..ea1f05467 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use either::Either; use itertools::Itertools; use rowan::Direction; use squawk_line_index::{LineEnding, UniversalNewlines, find_newline}; @@ -70,20 +71,13 @@ fn build_begin<'a>(begin: &ast::Begin) -> Doc<'a> { .append(Doc::text(keyword)); } if let Some(modes) = begin.transaction_mode_list() { - let mode_docs = modes.transaction_modes().map(|mode| { - ( - leading_comments(mode.syntax()).append(build_keyword_node(mode.syntax())), - mode.syntax().clone(), - ) - }); - if let Some(modes_doc) = build_comma_separated_docs(mode_docs) { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(modes.syntax())) - .append(modes_doc) - .nest(2), - ); - } + let comments = leading_comments(modes.syntax()); + doc = doc.append( + Doc::line_or_space() + .append(comments) + .append(build_transaction_mode_list(modes)) + .nest(2), + ); } doc.group().append(build_semicolon(begin.semicolon_token())) @@ -627,17 +621,29 @@ fn build_delete<'a>(delete: &ast::Delete) -> Doc<'a> { .append(leading_comments(alias.syntax())) .append(build_optional_as_alias(alias)); } - if let Some(using_clause) = delete.using_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(using_clause.syntax())) - .append(build_using_clause(using_clause)); + let using_clause = delete.using_clause(); + let using_has_join = using_clause.as_ref().is_some_and(|using| { + using + .items() + .any(|item| matches!(item, ast::FromListItem::JoinExpr(_))) + }); + if let Some(using_clause) = using_clause { + let clause = + leading_comments(using_clause.syntax()).append(build_using_clause(using_clause)); + doc = if using_has_join { + doc.append(Doc::hard_line().append(clause).nest(2)) + } else { + doc.append(Doc::line_or_space()).append(clause) + }; } if let Some(where_clause) = delete.where_clause_or_current_of() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(where_clause.syntax())) + let clause = leading_comments(where_clause.syntax()) .append(build_where_clause_or_current_of(where_clause)); + doc = if using_has_join { + doc.append(Doc::hard_line().append(clause).nest(2)) + } else { + doc.append(Doc::line_or_space()).append(clause) + }; } if let Some(returning_clause) = delete.returning_clause() { doc = doc @@ -671,10 +677,11 @@ fn build_using_clause<'a>(using_clause: ast::UsingClause) -> Doc<'a> { .using_token() .map(|token| leading_comments_token(&token).append(Doc::text("using"))) .unwrap_or_else(Doc::nil); - let items = using_clause.from_items().map(|item| { + let items = using_clause.items().map(|item| { + let syntax = item.syntax().clone(); ( - leading_comments(item.syntax()).append(build_from_item(item.clone())), - item.syntax().clone(), + leading_comments(item.syntax()).append(build_from_list_item(item)), + syntax, ) }); if let Some(items) = build_comma_separated_docs(items) { @@ -925,11 +932,11 @@ fn build_merge<'a>(merge: &ast::Merge) -> Doc<'a> { fn build_using_on_clause<'a>(using: ast::UsingOnClause) -> Doc<'a> { let mut doc = Doc::text("using"); - if let Some(item) = using.from_item() { + if let Some(item) = using.from_list_item() { doc = doc .append(Doc::space()) .append(leading_comments(item.syntax())) - .append(build_from_item(item)); + .append(build_from_list_item(item)); } if let Some(on_clause) = using.on_clause() { doc = doc @@ -1565,23 +1572,11 @@ fn build_create_trigger<'a>(stmt: &ast::CreateTrigger) -> Doc<'a> { .nest(2), ); } - for option in [ - stmt.deferrable_constraint_option() - .map(|node| node.syntax().clone()), - stmt.not_deferrable_constraint_option() - .map(|node| node.syntax().clone()), - stmt.initially_deferred_constraint_option() - .map(|node| node.syntax().clone()), - stmt.initially_immediate_constraint_option() - .map(|node| node.syntax().clone()), - ] - .into_iter() - .flatten() - { + for option in stmt.constraint_options() { doc = doc.append( Doc::hard_line() - .append(leading_comments(&option)) - .append(build_keyword_node(&option)) + .append(leading_comments(option.syntax())) + .append(build_constraint_option(option)) .nest(2), ); } @@ -1893,6 +1888,14 @@ fn build_create_function<'a>(create_function: &ast::CreateFunction) -> Doc<'a> { ); } } + if let Some(body) = create_function.body() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(body.syntax())) + .append(build_routine_body(body)) + .nest(2), + ); + } doc.append(build_semicolon(create_function.semicolon_token())) } @@ -1925,21 +1928,23 @@ fn build_function_params<'a>(params: impl Iterator) -> Option fn build_function_param<'a>(param: ast::Param) -> Doc<'a> { let mut doc = Doc::nil(); - if let Some(mode) = param.mode() { - doc = doc - .append(leading_comments(mode.syntax())) - .append(build_keyword_node(mode.syntax())); - } - if let Some(name) = param.name() { - if param.mode().is_some() { + let mut has_prefix = false; + for part in param.mode_and_name() { + if has_prefix { doc = doc.append(Doc::space()); } - doc = doc - .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); + has_prefix = true; + doc = doc.append(match part { + Either::Left(mode) => { + leading_comments(mode.syntax()).append(build_keyword_node(mode.syntax())) + } + Either::Right(name) => { + leading_comments(name.syntax()).append(build_name(name.syntax())) + } + }); } if let Some(ty) = param.ty() { - if param.mode().is_some() || param.name().is_some() { + if has_prefix { doc = doc.append(Doc::space()); } doc = doc @@ -2014,7 +2019,6 @@ fn build_function_ret_type<'a>(ret_type: ast::RetType) -> Doc<'a> { fn build_function_option<'a>(option: ast::FuncOption) -> Doc<'a> { match option { ast::FuncOption::AsFuncOption(option) => build_as_function_option(option), - ast::FuncOption::BeginFuncOptionList(options) => build_begin_function_option_list(options), ast::FuncOption::CostFuncOption(option) => { build_literal_function_option("cost", option.literal()) } @@ -2050,7 +2054,6 @@ fn build_function_option<'a>(option: ast::FuncOption) -> Doc<'a> { } doc } - ast::FuncOption::ReturnFuncOption(option) => build_return_function_option(option), ast::FuncOption::RowsFuncOption(option) => { build_literal_function_option("rows", option.literal()) } @@ -2108,7 +2111,14 @@ fn build_function_option<'a>(option: ast::FuncOption) -> Doc<'a> { } } -fn build_begin_function_option_list<'a>(options: ast::BeginFuncOptionList) -> Doc<'a> { +fn build_routine_body<'a>(body: ast::RoutineBody) -> Doc<'a> { + match body { + ast::RoutineBody::AtomicBody(atomic_body) => build_atomic_body(atomic_body), + ast::RoutineBody::ReturnStmt(return_stmt) => build_return_stmt(return_stmt), + } +} + +fn build_atomic_body<'a>(options: ast::AtomicBody) -> Doc<'a> { let mut doc = Doc::text("begin"); if let Some(atomic) = options.atomic_token() { doc = doc @@ -2117,11 +2127,11 @@ fn build_begin_function_option_list<'a>(options: ast::BeginFuncOptionList) -> Do .append(Doc::text("atomic")); } - for option in options.begin_func_options() { + for option in options.routine_body_stmts() { let option_comments = leading_comments(option.syntax()); let option_doc = match option { - ast::BeginFuncOption::ReturnFuncOption(option) => build_return_function_option(option), - ast::BeginFuncOption::Stmt(stmt) => build_stmt(stmt), + ast::RoutineBodyStmt::ReturnStmt(option) => build_return_stmt(option), + ast::RoutineBodyStmt::Stmt(stmt) => build_stmt(stmt), }; doc = doc.append( Doc::hard_line() @@ -2973,43 +2983,15 @@ fn build_table_and_columns<'a>(table: ast::TableAndColumns) -> Doc<'a> { } fn build_do<'a>(do_stmt: &ast::Do) -> Doc<'a> { - let language = do_stmt.do_language(); - let body = do_stmt.body(); - let language_before_body = match (&language, &body) { - (Some(language), Some(body)) => { - language.syntax().text_range().start() < body.syntax().text_range().start() - } - _ => false, - }; - let mut doc = Doc::text("do"); - if language_before_body { - if let Some(language) = language.as_ref() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(language.syntax())) - .append(build_do_language(language.clone())) - .nest(2), - ); - } - } - if let Some(body) = body { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(body.syntax())) - .append(build_literal(body)) - .nest(2), - ); - } - if !language_before_body { - if let Some(language) = language { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(language.syntax())) - .append(build_do_language(language)) - .nest(2), - ); - } + for part in do_stmt.language_and_body() { + let part_doc = match part { + Either::Left(language) => { + leading_comments(language.syntax()).append(build_do_language(language)) + } + Either::Right(body) => leading_comments(body.syntax()).append(build_literal(body)), + }; + doc = doc.append(Doc::line_or_space().append(part_doc).nest(2)); } doc.group() @@ -4470,7 +4452,7 @@ fn build_comment_object_on<'a>( doc.group() } -fn build_return_function_option<'a>(option: ast::ReturnFuncOption) -> Doc<'a> { +fn build_return_stmt<'a>(option: ast::ReturnStmt) -> Doc<'a> { let mut doc = Doc::text("return"); if let Some(expr) = option.expr() { doc = doc @@ -4747,11 +4729,11 @@ fn build_create_user_mapping<'a>(stmt: &ast::CreateUserMapping) -> Doc<'a> { .append(leading_comments_token(&token)) .append(Doc::text("for")); } - if let Some(role) = stmt.role_ref() { + if let Some(role) = stmt.user_mapping_role() { doc = doc .append(Doc::space()) .append(leading_comments(role.syntax())) - .append(build_role_ref(&role)); + .append(build_user_mapping_role(&role)); } if let Some(server) = stmt.server_clause() { doc = doc.append( @@ -5154,10 +5136,27 @@ fn build_create_schema_target<'a>(target: ast::CreateSchemaTarget) -> Doc<'a> { fn build_schema_element<'a>(element: ast::SchemaElement) -> Doc<'a> { match element { + ast::SchemaElement::CreateAggregate(stmt) => build_create_aggregate(&stmt), + ast::SchemaElement::CreateCollation(stmt) => build_create_collation(&stmt), + ast::SchemaElement::CreateDomain(stmt) => build_create_domain(&stmt), + ast::SchemaElement::CreateFunction(stmt) => build_create_function(&stmt), ast::SchemaElement::CreateIndex(stmt) => build_create_index(&stmt), + ast::SchemaElement::CreateOperator(stmt) => build_create_operator(&stmt), + ast::SchemaElement::CreateProcedure(stmt) => build_create_procedure(&stmt), ast::SchemaElement::CreateSequence(stmt) => build_create_sequence(&stmt), ast::SchemaElement::CreateTable(stmt) => build_create_table(&stmt), + ast::SchemaElement::CreateTextSearchConfiguration(stmt) => { + build_create_text_search_configuration(&stmt) + } + ast::SchemaElement::CreateTextSearchDictionary(stmt) => { + build_create_text_search_dictionary(&stmt) + } + ast::SchemaElement::CreateTextSearchParser(stmt) => build_create_text_search_parser(&stmt), + ast::SchemaElement::CreateTextSearchTemplate(stmt) => { + build_create_text_search_template(&stmt) + } ast::SchemaElement::CreateTrigger(stmt) => build_create_trigger(&stmt), + ast::SchemaElement::CreateType(stmt) => build_create_type(&stmt), ast::SchemaElement::CreateView(stmt) => build_create_view(&stmt), ast::SchemaElement::Grant(stmt) => build_grant(&stmt), } @@ -5938,6 +5937,14 @@ fn build_create_procedure<'a>(stmt: &ast::CreateProcedure) -> Doc<'a> { option_leading = Doc::nil(); } } + if let Some(body) = stmt.body() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(body.syntax())) + .append(build_routine_body(body)) + .nest(2), + ); + } doc.append(build_semicolon(stmt.semicolon_token())) } @@ -7344,6 +7351,22 @@ fn build_role_ref<'a>(role: &ast::RoleRef) -> Doc<'a> { .append(Doc::space()) .append(leading_comments_token(&ident)) .append(build_name(role.syntax())); + } else if let Some((token, keyword)) = role + .current_role_token() + .map(|token| (token, "current_role")) + .or_else(|| { + role.current_user_token() + .map(|token| (token, "current_user")) + }) + .or_else(|| { + role.session_user_token() + .map(|token| (token, "session_user")) + }) + { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); } doc } else if role.ident_token().is_some() { @@ -7353,6 +7376,16 @@ fn build_role_ref<'a>(role: &ast::RoleRef) -> Doc<'a> { } } +fn build_user_mapping_role<'a>(role: &ast::UserMappingRole) -> Doc<'a> { + if role.user_token().is_some() { + Doc::text("user") + } else { + role.role_ref() + .map(|role| build_role_ref(&role)) + .unwrap_or_else(Doc::nil) + } +} + fn build_set_role_target<'a>(target: ast::SetRoleTarget) -> Doc<'a> { match target { ast::SetRoleTarget::Literal(literal) => build_literal(literal), @@ -7440,14 +7473,25 @@ fn build_set_session_auth<'a>(set: &ast::SetSessionAuth) -> Doc<'a> { } fn build_transaction_mode_list<'a>(list: ast::TransactionModeList) -> Doc<'a> { - let modes = list.transaction_modes().map(|mode| { - let syntax = mode.syntax().clone(); - ( - leading_comments(&syntax).append(build_keyword_node(&syntax)), - syntax, - ) - }); - build_comma_separated_docs(modes).unwrap_or_else(Doc::nil) + let mut modes = list.transaction_modes(); + let Some(first) = modes.next() else { + return Doc::nil(); + }; + let mut previous = first; + let mut doc = build_keyword_node(previous.syntax()); + for mode in modes { + if previous.comma_after().is_some() { + doc = doc + .append(trailing_comments(previous.syntax())) + .append(Doc::text(",")); + } + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(mode.syntax())) + .append(build_keyword_node(mode.syntax())); + previous = mode; + } + doc } fn build_set_transaction<'a>(set: &ast::SetTransaction) -> Doc<'a> { @@ -7627,8 +7671,11 @@ fn build_set_time_zone<'a>(target: ast::SetTimeZone) -> Doc<'a> { let value_doc = match value.clone() { ast::ConfigValue::ConfigValueName(name) => build_name(name.syntax()), ast::ConfigValue::Literal(literal) => build_literal(literal), + ast::ConfigValue::PrefixExpr(expr) => build_prefix_expr(expr), }; Some(leading_comments(value.syntax()).append(value_doc)) + } else if let Some(interval) = target.cast_expr() { + Some(leading_comments(interval.syntax()).append(build_cast_expr(interval))) } else if let Some(default) = target.default_token() { Some(leading_comments_token(&default).append(Doc::text("default"))) } else { @@ -7696,6 +7743,7 @@ fn build_config_assignment<'a>(assignment: ast::ConfigAssignment) -> Doc<'a> { let value_doc = match value { ast::ConfigValue::ConfigValueName(name) => build_name(name.syntax()), ast::ConfigValue::Literal(literal) => build_literal(literal), + ast::ConfigValue::PrefixExpr(expr) => build_prefix_expr(expr), }; (leading_comments(&syntax).append(value_doc), syntax) }); @@ -7706,11 +7754,6 @@ fn build_config_assignment<'a>(assignment: ast::ConfigAssignment) -> Doc<'a> { .append(Doc::space()) .append(leading_comments_token(&default)) .append(Doc::text("default")); - } else if let Some(null) = values.null_token() { - doc = doc - .append(Doc::space()) - .append(leading_comments_token(&null)) - .append(Doc::text("null")); } doc.group() } @@ -7777,13 +7820,15 @@ fn build_create_index<'a>(create_index: &ast::CreateIndex) -> Doc<'a> { on_doc = on_doc.append(Doc::line_or_space()).append(using_doc); doc = doc.append(Doc::hard_line().append(on_doc.group()).nest(2)); } else { + doc = doc + .append(Doc::line_or_space().append(on_doc).nest(2)) + .group(); if let Some(items) = create_index.partition_item_list() { - on_doc = on_doc + doc = doc .append(Doc::space()) .append(leading_comments(items.syntax())) .append(build_create_table_partition_items(items)); } - doc = doc.append(Doc::line_or_space().append(on_doc).nest(2)); } if let Some(include) = create_index.index_include_clause() { let mut include_doc = leading_comments(include.syntax()).append(Doc::text("include")); @@ -8732,40 +8777,35 @@ fn build_column<'a>(column: &ast::Column) -> Doc<'a> { .append(leading_comments(ty.syntax())) .append(build_type(ty)); } - if let Some(storage) = column.storage() { - let mut clause = Doc::text("storage"); - if let Some(mode) = storage.storage_mode() { - clause = clause - .append(Doc::space()) - .append(leading_comments(mode.syntax())) - .append(build_keyword_node(mode.syntax())); - } - doc = append_column_clause(doc, storage.syntax(), clause); - } - if let Some(compression) = column.compression_method() { - let mut clause = Doc::text("compression"); - if let Some(method) = compression.compression_method_name() { - clause = clause - .append(Doc::space()) - .append(leading_comments(method.syntax())) - .append(build_keyword_node(method.syntax())); - } - doc = append_column_clause(doc, compression.syntax(), clause); - } - if let Some(options) = column.with_options() { - doc = append_column_clause(doc, options.syntax(), build_keyword_node(options.syntax())); - } - if let Some(options) = column.alter_option_list() { - let syntax = options.syntax().clone(); - doc = append_column_clause(doc, &syntax, build_alter_option_list(&options)); - } - if let Some(collate) = column.collate() { - let syntax = collate.syntax().clone(); - doc = append_column_clause(doc, &syntax, build_collate_expr(collate)); - } - for constraint in column.constraints() { - let syntax = constraint.syntax().clone(); - doc = append_column_clause(doc, &syntax, build_column_constraint(constraint)); + for clause in column.clauses() { + let syntax = clause.syntax().clone(); + let clause = match clause { + ast::ColumnClause::Storage(storage) => { + let mut doc = Doc::text("storage"); + if let Some(mode) = storage.storage_mode() { + doc = doc + .append(Doc::space()) + .append(leading_comments(mode.syntax())) + .append(build_keyword_node(mode.syntax())); + } + doc + } + ast::ColumnClause::CompressionMethod(compression) => { + let mut doc = Doc::text("compression"); + if let Some(method) = compression.compression_method_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(build_keyword_node(method.syntax())); + } + doc + } + ast::ColumnClause::WithOptions(options) => build_keyword_node(options.syntax()), + ast::ColumnClause::AlterOptionList(options) => build_alter_option_list(&options), + ast::ColumnClause::Collate(collate) => build_collate_expr(collate), + ast::ColumnClause::ColumnConstraint(constraint) => build_column_constraint(constraint), + }; + doc = append_column_clause(doc, &syntax, clause); } doc.group() } @@ -8956,27 +8996,11 @@ fn build_references_constraint<'a>(constraint: ast::ReferencesConstraint) -> Doc .append(leading_comments(match_type.syntax())) .append(build_keyword_node(match_type.syntax())); } - if let Some(action) = constraint.on_delete_action() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(action.syntax())) - .append(build_reference_action( - action.on_token(), - action.delete_token(), - "delete", - action.ref_action(), - )); - } - if let Some(action) = constraint.on_update_action() { + for action in constraint.referential_actions() { doc = doc .append(Doc::line_or_space()) .append(leading_comments(action.syntax())) - .append(build_reference_action( - action.on_token(), - action.update_token(), - "update", - action.ref_action(), - )); + .append(build_referential_action(action)); } append_constraint_options(doc, constraint.constraint_options()) .nest(2) @@ -9111,7 +9135,7 @@ fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { doc } ast::SequenceOption::OptionCache(option) => { - append_optional_literal(Doc::text("cache"), option.literal()) + append_optional_expr(Doc::text("cache"), option.expr()) } ast::SequenceOption::OptionIncrement(option) => { let mut doc = option @@ -9124,13 +9148,13 @@ fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { .append(leading_comments_token(&by)) .append(Doc::text("by")); } - append_optional_literal(doc, option.literal()) + append_optional_expr(doc, option.expr()) } ast::SequenceOption::OptionMaxValue(option) => { - append_optional_literal(Doc::text("maxvalue"), option.literal()) + append_optional_expr(Doc::text("maxvalue"), option.expr()) } ast::SequenceOption::OptionMinValue(option) => { - append_optional_literal(Doc::text("minvalue"), option.literal()) + append_optional_expr(Doc::text("minvalue"), option.expr()) } ast::SequenceOption::OptionRestart(option) => { let mut doc = option @@ -9143,7 +9167,7 @@ fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { .append(leading_comments_token(&with)) .append(Doc::text("with")); } - append_optional_literal(doc, option.literal()) + append_optional_expr(doc, option.expr()) } ast::SequenceOption::OptionStart(option) => { let mut doc = option @@ -9156,7 +9180,7 @@ fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { .append(leading_comments_token(&with)) .append(Doc::text("with")); } - append_optional_literal(doc, option.literal()) + append_optional_expr(doc, option.expr()) } ast::SequenceOption::OptionOwnedBy(option) => { let mut doc = option @@ -9213,6 +9237,14 @@ fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { } } +fn append_optional_expr<'a>(doc: Doc<'a>, expr: Option) -> Doc<'a> { + expr.map_or(doc.clone(), |expr| { + doc.append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)) + }) +} + fn append_optional_literal<'a>(doc: Doc<'a>, literal: Option) -> Doc<'a> { literal.map_or(doc.clone(), |literal| { doc.append(Doc::space()) @@ -9228,6 +9260,9 @@ fn build_table_constraint<'a>(constraint: ast::TableConstraint) -> Doc<'a> { ast::TableConstraint::ForeignKeyConstraint(constraint) => { build_foreign_key_constraint(constraint) } + ast::TableConstraint::NotNullConstraint(constraint) => { + build_domain_constraint(constraint.into()) + } ast::TableConstraint::PrimaryKeyConstraint(constraint) => { build_primary_key_constraint(constraint) } @@ -9239,6 +9274,10 @@ fn build_constraint_name_clause<'a>(clause: Option) - let Some(clause) = clause else { return Doc::nil(); }; + build_constraint_name(&clause).append(Doc::space()) +} + +fn build_constraint_name<'a>(clause: &ast::ConstraintNameClause) -> Doc<'a> { let mut doc = Doc::text("constraint"); if let Some(name) = clause.constraint_name() { doc = doc @@ -9246,7 +9285,7 @@ fn build_constraint_name_clause<'a>(clause: Option) - .append(leading_comments(name.syntax())) .append(build_name(name.syntax())); } - doc.append(Doc::space()) + doc } fn build_check_constraint<'a>(constraint: ast::CheckConstraint) -> Doc<'a> { @@ -9308,6 +9347,8 @@ fn build_primary_key_constraint<'a>(constraint: ast::PrimaryKeyConstraint) -> Do .append(Doc::space()) .append(leading_comments(parameters.syntax())) .append(build_index_parameters(parameters)); + } else { + doc = doc.append(comments_before(parameters.syntax().clone())); } } let options = append_constraint_options(Doc::nil(), constraint.constraint_options()).nest(2); @@ -9332,6 +9373,8 @@ fn build_unique_constraint<'a>(constraint: ast::UniqueConstraint) -> Doc<'a> { .append(Doc::space()) .append(leading_comments(parameters.syntax())) .append(build_index_parameters(parameters)); + } else { + doc = doc.append(comments_before(parameters.syntax().clone())); } } let options = append_constraint_options(Doc::nil(), constraint.constraint_options()).nest(2); @@ -9499,9 +9542,16 @@ fn build_attribute_list_with_layout<'a>(list: &ast::AttributeList, multiline: bo item = item.append(comments_before(dot)).append(Doc::text(".")); } if let Some(name) = option.name() { + let name_doc = if name.default_token().is_some() { + Doc::text("default") + } else if name.join_token().is_some() { + Doc::text("join") + } else { + build_name(name.syntax()) + }; item = item .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); + .append(name_doc); } if let Some(eq) = option.eq_token() { item = item @@ -9538,26 +9588,52 @@ fn build_attribute_list_with_layout<'a>(list: &ast::AttributeList, multiline: bo fn build_attribute_value<'a>(value: ast::AttributeValue) -> Doc<'a> { if let Some(literal) = value.literal() { build_literal(literal) + } else if let Some(prefix_expr) = value.prefix_expr() { + build_prefix_expr(prefix_expr) } else if let Some(ty) = value.ty() { build_type(ty) } else if let Some(custom_op) = value.custom_op() { build_custom_operator(custom_op) + } else if value.eq_token().is_some() { + Doc::text("=") + } else if value.l_angle_token().is_some() { + Doc::text("<") + } else if value.r_angle_token().is_some() { + Doc::text(">") + } else if value.lteq_token().is_some() { + Doc::text("<=") + } else if value.gteq_token().is_some() { + Doc::text(">=") + } else if value.neqb_token().is_some() { + Doc::text("<>") + } else if value.neq_token().is_some() { + Doc::text("!=") + } else if value.plus_token().is_some() { + Doc::text("+") + } else if value.minus_token().is_some() { + Doc::text("-") + } else if value.star_token().is_some() { + Doc::text("*") + } else if value.slash_token().is_some() { + Doc::text("/") + } else if value.percent_token().is_some() { + Doc::text("%") + } else if value.caret_token().is_some() { + Doc::text("^") + } else if value.any_token().is_some() { + Doc::text("any") } else if value.none_token().is_some() { Doc::text("none") - } else if let Some(op) = value.op() { - if value.operator_token().is_some() { - let mut doc = Doc::text("operator"); - if let Some(l_paren) = value.l_paren_token() { - doc = doc.append(comments_before(l_paren)); - } - doc = doc.append(Doc::text("(")).append(build_operator(&op)); - if let Some(r_paren) = value.r_paren_token() { - doc = doc.append(comments_before(r_paren)); - } - doc.append(Doc::text(")")) - } else { - build_operator(&op) - } + } else if value.primary_token().is_some() { + Doc::text("primary") + } else if value.default_token().is_some() { + Doc::text("default") + } else if value.null_token().is_some() { + Doc::text("null") + } else if value.constraint_token().is_some() { + Doc::text("constraint") + } else if let Some(operator_call) = value.operator_call() { + build_operator_call(&operator_call) } else { Doc::nil() } @@ -9594,26 +9670,67 @@ fn append_constraint_options<'a>( doc = doc .append(Doc::line_or_space()) .append(leading_comments(option.syntax())) - .append(build_keyword_node(option.syntax())); + .append(build_constraint_option(option)); } doc } +fn build_constraint_option<'a>(option: ast::ConstraintOption) -> Doc<'a> { + match option { + ast::ConstraintOption::DeferrableConstraintOption(option) => { + build_keyword_tokens([(option.deferrable_token(), "deferrable")]) + } + ast::ConstraintOption::Enforced(option) => { + build_keyword_tokens([(option.enforced_token(), "enforced")]) + } + ast::ConstraintOption::Inherit(option) => { + build_keyword_tokens([(option.inherit_token(), "inherit")]) + } + ast::ConstraintOption::InitiallyDeferredConstraintOption(option) => build_keyword_tokens([ + (option.initially_token(), "initially"), + (option.deferred_token(), "deferred"), + ]), + ast::ConstraintOption::InitiallyImmediateConstraintOption(option) => { + build_keyword_tokens([ + (option.initially_token(), "initially"), + (option.immediate_token(), "immediate"), + ]) + } + ast::ConstraintOption::NoInherit(option) => build_keyword_tokens([ + (option.no_token(), "no"), + (option.inherit_token(), "inherit"), + ]), + ast::ConstraintOption::NotDeferrableConstraintOption(option) => build_keyword_tokens([ + (option.not_token(), "not"), + (option.deferrable_token(), "deferrable"), + ]), + ast::ConstraintOption::NotEnforced(option) => build_keyword_tokens([ + (option.not_token(), "not"), + (option.enforced_token(), "enforced"), + ]), + ast::ConstraintOption::NotValid(option) => { + build_keyword_tokens([(option.not_token(), "not"), (option.valid_token(), "valid")]) + } + } +} + fn build_foreign_key_constraint<'a>(constraint: ast::ForeignKeyConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + let constraint_name = constraint.constraint_name_clause(); + let has_constraint_name = constraint_name.is_some(); + let mut body = Doc::nil(); if let Some(foreign) = constraint.foreign_token() { - doc = doc + body = body .append(leading_comments_token(&foreign)) .append(Doc::text("foreign")); } if let Some(key) = constraint.key_token() { - doc = doc + body = body .append(Doc::space()) .append(leading_comments_token(&key)) .append(Doc::text("key")); } if let Some(columns) = constraint.from_columns() { - doc = doc + body = body .append(Doc::space()) .append(leading_comments(columns.syntax())) .append(build_foreign_key_column_list(columns)); @@ -9643,65 +9760,99 @@ fn build_foreign_key_constraint<'a>(constraint: ast::ForeignKeyConstraint) -> Do ); } if let Some(references_clause) = references_clause { - doc = doc.append(Doc::line_or_space().append(references_clause).nest(2)); + let clause = Doc::line_or_space().append(references_clause); + body = body.append(if has_constraint_name { + clause + } else { + clause.nest(2) + }); } if let Some(match_type) = constraint.match_type() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(match_type.syntax())) - .append(build_keyword_node(match_type.syntax())) - .nest(2), - ); + let clause = Doc::line_or_space() + .append(leading_comments(match_type.syntax())) + .append(build_keyword_node(match_type.syntax())); + body = body.append(if has_constraint_name { + clause + } else { + clause.nest(2) + }); } - if let Some(action) = constraint.on_delete_action() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(action.syntax())) - .append(build_reference_action( - action.on_token(), - action.delete_token(), - "delete", - action.ref_action(), - )) - .nest(2), - ); + for action in constraint.referential_actions() { + let clause = Doc::line_or_space() + .append(leading_comments(action.syntax())) + .append(build_referential_action(action)); + body = body.append(if has_constraint_name { + clause + } else { + clause.nest(2) + }); } - if let Some(action) = constraint.on_update_action() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(action.syntax())) - .append(build_reference_action( - action.on_token(), - action.update_token(), - "update", - action.ref_action(), - )) - .nest(2), - ); + let options = append_constraint_options(Doc::nil(), constraint.constraint_options()); + body = body.append(if has_constraint_name { + options + } else { + options.nest(2) + }); + + if let Some(constraint_name) = constraint_name { + build_constraint_name(&constraint_name) + .append(Doc::line_or_space().append(body.group()).nest(2)) + .group() + } else { + body.group() } - let options = append_constraint_options(Doc::nil(), constraint.constraint_options()).nest(2); - doc.append(options).group() } fn build_foreign_key_column_list<'a>(list: ast::ForeignKeyColumnList) -> Doc<'a> { - let suffix = list.period_column().map(|period| { - let mut doc = Doc::space() - .append(leading_comments(period.syntax())) - .append(Doc::text("period")); + let doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let mut items = list + .column_name_refs() + .map(|name| { + ( + leading_comments(name.syntax()).append(build_name(name.syntax())), + name.syntax().clone(), + ) + }) + .collect::>(); + if let Some(period) = list.period_column() { + let mut period_doc = period + .period_token() + .map(|_| leading_comments(period.syntax()).append(Doc::text("period"))) + .unwrap_or_else(Doc::nil); if let Some(name) = period.name() { - doc = doc + period_doc = period_doc .append(Doc::space()) .append(leading_comments(name.syntax())) .append(build_name(name.syntax())); } - doc - }); - build_column_names( - list.l_paren_token(), - list.column_name_refs(), - suffix, - list.r_paren_token(), - ) + items.push((period_doc, period.syntax().clone())); + } + let mut body = build_comma_separated_docs(items.into_iter()).unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_referential_action<'a>(action: ast::ReferentialAction) -> Doc<'a> { + match action { + ast::ReferentialAction::OnDeleteAction(action) => build_reference_action( + action.on_token(), + action.delete_token(), + "delete", + action.ref_action(), + ), + ast::ReferentialAction::OnUpdateAction(action) => build_reference_action( + action.on_token(), + action.update_token(), + "update", + action.ref_action(), + ), + } } fn build_reference_action<'a>( @@ -9960,35 +10111,10 @@ fn build_values<'a>(values: &ast::Values) -> Doc<'a> { } doc = doc.append(values_doc.group()); - if let Some(order_by) = values.order_by_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in values.locking_clauses() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = values.limit_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(fetch) = values.fetch_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(fetch.syntax())) - .append(build_fetch_clause(fetch)); - } - if let Some(offset) = values.offset_clause() { + for clause in values.tail_clauses() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); + .append(build_select_tail_clause(clause)); } doc.append(build_semicolon(values.semicolon_token())) @@ -10036,35 +10162,10 @@ fn build_table<'a>(table: &ast::Table) -> Doc<'a> { } doc = doc.append(table_doc.group()); - if let Some(order_by) = table.order_by_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in table.locking_clauses() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = table.limit_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(fetch) = table.fetch_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(fetch.syntax())) - .append(build_fetch_clause(fetch)); - } - if let Some(offset) = table.offset_clause() { + for clause in table.tail_clauses() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); + .append(build_select_tail_clause(clause)); } doc.append(build_semicolon(table.semicolon_token())).group() @@ -10198,29 +10299,10 @@ fn build_select_into<'a>(select_into: &ast::SelectInto) -> Doc<'a> { .append(leading_comments(window.syntax())) .append(build_window_clause(window)); } - if let Some(order_by) = select_into.order_by_clause() { + for clause in select_into.tail_clauses() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in select_into.locking_clauses() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = select_into.limit_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(offset) = select_into.offset_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); + .append(build_select_tail_clause(clause)); } if let Some(filter) = select_into.filter_clause() { doc = doc @@ -10716,22 +10798,32 @@ fn build_publication_object<'a>(object: ast::PublicationObject) -> Doc<'a> { match object { ast::PublicationObject::PublicationObjectCurrentSchema(_) => Doc::text("current_schema"), ast::PublicationObject::PublicationObjectTable(object) => { - let mut doc = Doc::text("table"); + let mut doc = Doc::nil(); + let mut has_prefix = false; + if let Some(token) = object.table_token() { + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text("table")); + has_prefix = true; + } if let Some(token) = object.only_token() { + if has_prefix { + doc = doc.append(Doc::space()); + } doc = doc - .append(Doc::space()) .append(leading_comments_token(&token)) .append(Doc::text("only")); + has_prefix = true; } let parenthesized = object.l_paren_token().is_some(); if let Some(l_paren) = object.l_paren_token() { - doc = doc - .append(Doc::space()) - .append(comments_before(l_paren)) - .append(Doc::text("(")); + if has_prefix { + doc = doc.append(Doc::space()); + } + doc = doc.append(comments_before(l_paren)).append(Doc::text("(")); } if let Some(table) = object.table_name_ref() { - if !parenthesized { + if !parenthesized && has_prefix { doc = doc.append(Doc::space()); } doc = doc.append(leading_comments(table.syntax())); @@ -10906,6 +10998,7 @@ fn append_commented_table_ref<'a>(mut doc: Doc<'a>, table: Option(action: ast::AlterTableAction) -> Doc<'a> { match action { + ast::AlterTableAction::AlterOptionList(node) => build_alter_option_list(&node), ast::AlterTableAction::AddColumn(node) => { let mut doc = build_keyword_tokens([(node.add_token(), "add"), (node.column_token(), "column")]); @@ -10943,12 +11036,15 @@ fn build_alter_foreign_table_action<'a>(action: ast::AlterTableAction) -> Doc<'a ast::AlterTableAction::AddConstraint(node) => { let mut doc = Doc::text("add"); if let Some(constraint) = node.constraint() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(constraint.syntax())) - .append(build_domain_constraint(constraint)) - .nest(2), - ); + let is_foreign_key = + matches!(&constraint, ast::Constraint::ForeignKeyConstraint(_)); + let constraint_doc = leading_comments(constraint.syntax()) + .append(build_domain_constraint(constraint)); + doc = if is_foreign_key { + doc.append(Doc::space()).append(constraint_doc) + } else { + doc.append(Doc::line_or_space().append(constraint_doc).nest(2)) + }; } doc.group() } @@ -10974,38 +11070,11 @@ fn build_alter_foreign_table_action<'a>(action: ast::AlterTableAction) -> Doc<'a (node.constraint_token(), "constraint"), ]); doc = append_commented_constraint_ref(doc, node.constraint_name_ref()); - if let Some(option) = node.deferrable_constraint_option() { + for option in node.constraint_options() { doc = doc .append(Doc::space()) .append(leading_comments(option.syntax())) - .append(Doc::text("deferrable")); - } - if let Some(option) = node.not_deferrable_constraint_option() { - doc = doc - .append(Doc::space()) - .append(leading_comments(option.syntax())) - .append(build_keyword_tokens([ - (option.not_token(), "not"), - (option.deferrable_token(), "deferrable"), - ])); - } - if let Some(option) = node.initially_deferred_constraint_option() { - doc = doc - .append(Doc::space()) - .append(leading_comments(option.syntax())) - .append(build_keyword_tokens([ - (option.initially_token(), "initially"), - (option.deferred_token(), "deferred"), - ])); - } - if let Some(option) = node.initially_immediate_constraint_option() { - doc = doc - .append(Doc::space()) - .append(leading_comments(option.syntax())) - .append(build_keyword_tokens([ - (option.initially_token(), "initially"), - (option.immediate_token(), "immediate"), - ])); + .append(build_constraint_option(option)); } doc } @@ -11279,9 +11348,10 @@ fn build_alter_foreign_table_action<'a>(action: ast::AlterTableAction) -> Doc<'a (node.oids_token(), "oids"), ]), ast::AlterTableAction::TableRenameTo(node) => { - let target = node - .table_name() - .map(|name| leading_comments(name.syntax()).append(build_name(name.syntax()))); + let target = node.table_name().and_then(|name| { + name.path() + .map(|path| leading_comments(name.syntax()).append(build_path(&path))) + }); build_rename_to(node.to_token(), target) } ast::AlterTableAction::ValidateConstraint(node) => append_commented_constraint_ref( @@ -11297,37 +11367,44 @@ fn build_alter_foreign_table_action<'a>(action: ast::AlterTableAction) -> Doc<'a (node.partitions_token(), "partitions"), ]); if let Some(list) = node.table_name_ref_list() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(list.syntax())) - .append(build_table_name_ref_list(list)) - .nest(2), - ); + doc = doc + .append(Doc::space()) + .append(leading_comments(list.syntax())) + .append(build_table_name_ref_list(list)); } if let Some(into) = node.into_token() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments_token(&into)) - .append(Doc::text("into")) - .nest(2), - ); + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&into)) + .append(Doc::text("into")); + } + if let Some(table_name) = node.table_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table_name.syntax())); + if let Some(path) = table_name.path() { + doc = doc.append(build_path(&path)); + } } - doc = append_commented_name(doc, node.table_name()); doc.group() } ast::AlterTableAction::SplitPartition(node) => { let mut doc = build_keyword_tokens([ (node.split_token(), "split"), (node.partition_token(), "partition"), - (node.into_token(), "into"), ]); + doc = append_commented_table_ref(doc, node.table_name_ref()); + if let Some(into) = node.into_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&into)) + .append(Doc::text("into")); + } if let Some(list) = node.partition_list() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(list.syntax())) - .append(build_partition_list(list)) - .nest(2), - ); + doc = doc + .append(Doc::space()) + .append(leading_comments(list.syntax())) + .append(build_partition_list(list)); } doc.group() } @@ -11550,21 +11627,21 @@ fn build_alter_column_option<'a>(option: ast::AlterColumnOption) -> Doc<'a> { .nest(2), ); } - if let Some(using) = n.using_token() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments_token(&using)) - .append(Doc::text("using")) - .nest(2), - ); - } - if let Some(expr) = n.expr() { + if let Some(using) = n.using_expr() { doc = doc.append( Doc::line_or_space() - .append(leading_comments(expr.syntax())) - .append(build_expr(expr)) + .append(leading_comments(using.syntax())) + .append(build_keyword_tokens([(using.using_token(), "using")])) .nest(2), ); + if let Some(expr) = using.expr() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)) + .nest(2), + ); + } } doc.group() } @@ -13287,18 +13364,31 @@ fn build_alter_property_graph_action<'a>(action: ast::AlterPropertyGraphAction) build_rename_to(n.to_token(), target) } ast::AlterPropertyGraphAction::AddVertexEdgeTables(n) => { - let mut doc = Doc::text("add"); - if let Some(x) = n.vertex_tables() { - doc = doc - .append(Doc::space()) - .append(leading_comments(x.syntax())) - .append(build_vertex_tables(x)); + let mut doc = Doc::nil(); + let mut has_vertex_tables = false; + if let Some(add) = n.add_vertex_tables() { + doc = doc.append(build_keyword_tokens([(add.add_token(), "add")])); + if let Some(tables) = add.vertex_tables() { + doc = doc + .append(Doc::space()) + .append(leading_comments(tables.syntax())) + .append(build_vertex_tables(tables)); + } + has_vertex_tables = true; } - if let Some(x) = n.edge_tables() { + if let Some(add) = n.add_edge_tables() { + if has_vertex_tables { + doc = doc.append(Doc::line_or_space()); + } doc = doc - .append(Doc::space()) - .append(leading_comments(x.syntax())) - .append(build_edge_tables(x)); + .append(leading_comments(add.syntax())) + .append(build_keyword_tokens([(add.add_token(), "add")])); + if let Some(tables) = add.edge_tables() { + doc = doc + .append(Doc::space()) + .append(leading_comments(tables.syntax())) + .append(build_edge_tables(tables)); + } } doc } @@ -13695,6 +13785,7 @@ fn build_edge_table_def<'a>(n: ast::EdgeTableDef) -> Doc<'a> { x.key_token(), x.column_ref_list(), x.references_table(), + x.vertex_table_ref(), )) .nest(2), ); @@ -13709,6 +13800,7 @@ fn build_edge_table_def<'a>(n: ast::EdgeTableDef) -> Doc<'a> { x.key_token(), x.column_ref_list(), x.references_table(), + x.vertex_table_ref(), )) .nest(2), ); @@ -13757,10 +13849,17 @@ fn build_vertex_reference<'a>( key_token: Option, cols: Option, refs: Option, + vertex: Option, ) -> Doc<'a> { let mut d = kind_token .map(|token| leading_comments_token(&token).append(Doc::text(keyword))) .unwrap_or_else(Doc::nil); + if let Some(vertex) = vertex { + d = d + .append(Doc::space()) + .append(leading_comments(vertex.syntax())) + .append(build_name(vertex.syntax())); + } if let Some(token) = key_token { d = d .append(Doc::space()) @@ -15394,27 +15493,26 @@ fn build_alter_default_privileges_action<'a>(action: ast::AlterDefaultPrivileges ast::AlterDefaultPrivilegesAction::GrantDefaultPrivileges(grant) => { let mut doc = Doc::text("grant"); if let Some(privileges) = grant.privileges() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(privileges.syntax())) - .append(build_privileges(privileges)) - .nest(2), - ); - } - if let Some(on) = grant.on_token() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments_token(&on)) - .append(Doc::text("on")); + .append(Doc::space()) + .append(leading_comments(privileges.syntax())) + .append(build_privileges(privileges).nest(2)); } + let mut on_clause = grant + .on_token() + .map(|on| leading_comments_token(&on).append(Doc::text("on"))); if let Some(target) = grant.privilege_target() { - doc = doc.append( - Doc::line_or_space() + on_clause = Some( + on_clause + .unwrap_or_else(Doc::nil) + .append(Doc::space()) .append(leading_comments(target.syntax())) - .append(build_privilege_target(target)) - .nest(2), + .append(build_privilege_target(target)), ); } + if let Some(on_clause) = on_clause { + doc = doc.append(Doc::line_or_space()).append(on_clause); + } if let Some(to) = grant.to_token() { doc = doc .append(Doc::line_or_space()) @@ -15441,40 +15539,42 @@ fn build_alter_default_privileges_action<'a>(action: ast::AlterDefaultPrivileges } ast::AlterDefaultPrivilegesAction::RevokeDefaultPrivileges(revoke) => { let mut doc = Doc::text("revoke"); - if let Some(option) = revoke.grant_option_for() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(option.syntax())) - .append(build_keyword_tokens([ - (option.grant_token(), "grant"), - (option.option_token(), "option"), - (option.for_token(), "for"), - ])) - .nest(2), - ); + let grant_option = revoke.grant_option_for(); + let has_grant_option = grant_option.is_some(); + if let Some(option) = grant_option { + doc = doc + .append(Doc::space()) + .append(leading_comments(option.syntax())) + .append(build_keyword_tokens([ + (option.grant_token(), "grant"), + (option.option_token(), "option"), + (option.for_token(), "for"), + ])); } if let Some(privileges) = revoke.privileges() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(privileges.syntax())) - .append(build_privileges(privileges)) - .nest(2), - ); - } - if let Some(on) = revoke.on_token() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments_token(&on)) - .append(Doc::text("on")); + let privileges_doc = + leading_comments(privileges.syntax()).append(build_privileges(privileges)); + doc = if has_grant_option { + doc.append(Doc::line_or_space().append(privileges_doc).nest(2)) + } else { + doc.append(Doc::space()).append(privileges_doc.nest(2)) + }; } + let mut on_clause = revoke + .on_token() + .map(|on| leading_comments_token(&on).append(Doc::text("on"))); if let Some(target) = revoke.privilege_target() { - doc = doc.append( - Doc::line_or_space() + on_clause = Some( + on_clause + .unwrap_or_else(Doc::nil) + .append(Doc::space()) .append(leading_comments(target.syntax())) - .append(build_privilege_target(target)) - .nest(2), + .append(build_privilege_target(target)), ); } + if let Some(on_clause) = on_clause { + doc = doc.append(Doc::line_or_space()).append(on_clause); + } if let Some(from) = revoke.from_token() { doc = doc .append(Doc::line_or_space()) @@ -15558,6 +15658,12 @@ fn build_domain_constraint<'a>(constraint: ast::Constraint) -> Doc<'a> { (node.not_token(), "not"), (node.null_token(), "null"), ])); + if let Some(column) = node.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } append_constraint_options(doc, node.constraint_options()) .nest(2) .group() @@ -17598,11 +17704,11 @@ fn build_drop_user_mapping<'a>(stmt: &ast::DropUserMapping) -> Doc<'a> { .append(leading_comments_token(&for_token)) .append(Doc::text("for")); } - if let Some(role) = stmt.role_ref() { + if let Some(role) = stmt.user_mapping_role() { doc = doc.append( Doc::line_or_space() .append(leading_comments(role.syntax())) - .append(build_role_ref(&role)) + .append(build_user_mapping_role(&role)) .nest(2), ); } @@ -17821,35 +17927,10 @@ fn build_select_doc_ungrouped<'a>(select: &ast::Select) -> Doc<'a> { .append(leading_comments(window.syntax())) .append(build_window_clause(window)); } - if let Some(order_by) = select.order_by_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in select.locking_clauses() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = select.limit_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(fetch) = select.fetch_clause() { + for clause in select.tail_clauses() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(fetch.syntax())) - .append(build_fetch_clause(fetch)); - } - if let Some(offset) = select.offset_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); + .append(build_select_tail_clause(clause)); } if let Some(filter) = select.filter_clause() { doc = doc @@ -17864,30 +17945,41 @@ fn build_select_doc_ungrouped<'a>(select: &ast::Select) -> Doc<'a> { } fn build_from_clause<'a>(from: ast::FromClause) -> Doc<'a> { - let from_items = from.from_items().map(|item| { + let mut single_item = from.items(); + let single_json_table = matches!( + single_item.next(), + Some(ast::FromListItem::FromItem( + ast::FromItem::JsonTableFromItem(_) + )) + ) && single_item.next().is_none(); + + let items = from.items().map(|item| { let syntax = item.syntax().clone(); ( - leading_comments(item.syntax()).append(build_from_item(item)), - syntax, - ) - }); - let join_exprs = from.join_exprs().map(|join_expr| { - let syntax = join_expr.syntax().clone(); - ( - leading_comments(join_expr.syntax()).append(build_join_expr(join_expr)), + leading_comments(item.syntax()).append(build_from_list_item(item)), syntax, ) }); - let body = build_comma_separated_docs(from_items.chain(join_exprs)).unwrap_or_else(Doc::nil); + let body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + let body = if single_json_table { + body + } else { + body.nest(2) + }; + + Doc::text("from").append(Doc::space()).append(body) +} - Doc::text("from").append(Doc::space()).append(body.nest(2)) +fn build_from_list_item<'a>(item: ast::FromListItem) -> Doc<'a> { + match item { + ast::FromListItem::FromItem(item) => build_from_item(item), + ast::FromListItem::JoinExpr(join_expr) => build_join_expr(join_expr), + } } fn build_join_expr<'a>(join_expr: ast::JoinExpr) -> Doc<'a> { - let mut doc = if let Some(left) = join_expr.join_expr() { - leading_comments(left.syntax()).append(build_join_expr(left)) - } else if let Some(left) = join_expr.from_item() { - leading_comments(left.syntax()).append(build_from_item(left)) + let mut doc = if let Some(left) = join_expr.from_list_item() { + leading_comments(left.syntax()).append(build_from_list_item(left)) } else { Doc::nil() }; @@ -17914,11 +18006,11 @@ fn build_join<'a>(join: ast::Join) -> Doc<'a> { .append(leading_comments(join_type.syntax())) .append(build_keyword_node(join_type.syntax())); } - if let Some(item) = join.from_item() { + if let Some(item) = join.from_list_item() { doc = doc .append(Doc::space()) .append(leading_comments(item.syntax())) - .append(build_from_item(item)); + .append(build_from_list_item(item)); } if let Some(condition) = join.join_condition() { match condition { @@ -18069,10 +18161,17 @@ fn build_select_variant<'a>(select: ast::SelectVariant) -> Doc<'a> { } } +fn build_compound_select_operand<'a>(operand: ast::CompoundSelectOperand) -> Doc<'a> { + match operand { + ast::CompoundSelectOperand::SelectVariant(select) => build_select_variant(select), + ast::CompoundSelectOperand::ParenExpr(expr) => build_paren_expr(expr), + } +} + fn build_compound_select<'a>(select: &ast::CompoundSelect) -> Doc<'a> { let mut doc = select - .lhs() - .map(build_select_variant) + .lhs_operand() + .map(build_compound_select_operand) .unwrap_or_else(Doc::nil); if let Some(op) = select.op() { @@ -18096,41 +18195,16 @@ fn build_compound_select<'a>(select: &ast::CompoundSelect) -> Doc<'a> { doc = doc.append(Doc::line_or_space()).append(op_doc); } - if let Some(rhs) = select.rhs() { + if let Some(rhs) = select.rhs_operand() { doc = doc .append(Doc::line_or_space()) .append(leading_comments(rhs.syntax())) - .append(build_select_variant(rhs)); - } - if let Some(order_by) = select.order_by_clause() { - doc = doc - .append(Doc::hard_line()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in select.locking_clauses() { - doc = doc - .append(Doc::hard_line()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = select.limit_clause() { - doc = doc - .append(Doc::hard_line()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(fetch) = select.fetch_clause() { - doc = doc - .append(Doc::hard_line()) - .append(leading_comments(fetch.syntax())) - .append(build_fetch_clause(fetch)); + .append(build_compound_select_operand(rhs)); } - if let Some(offset) = select.offset_clause() { + for clause in select.tail_clauses() { doc = doc .append(Doc::hard_line()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); + .append(build_select_tail_clause(clause)); } doc.append(build_semicolon(select.semicolon_token())) @@ -18248,6 +18322,7 @@ fn build_paren_select<'a>(select: ast::ParenSelect) -> Doc<'a> { .select() .map(|select| leading_comments(select.syntax()).append(build_select_variant(select))) .unwrap_or_else(Doc::nil); + if let Some(r_paren) = select.r_paren_token() { body = body.append(comments_before(r_paren)); } @@ -18257,35 +18332,10 @@ fn build_paren_select<'a>(select: ast::ParenSelect) -> Doc<'a> { .group(); doc = doc.append(paren_doc); - if let Some(order_by) = select.order_by_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(order_by.syntax())) - .append(build_order_by_clause(order_by)); - } - for locking in select.locking_clauses() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(locking.syntax())) - .append(build_locking_clause(locking)); - } - if let Some(limit) = select.limit_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(limit.syntax())) - .append(build_limit_clause(limit)); - } - if let Some(offset) = select.offset_clause() { + for clause in select.tail_clauses() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(offset.syntax())) - .append(build_offset_clause(offset)); - } - if let Some(fetch) = select.fetch_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(fetch.syntax())) - .append(build_fetch_clause(fetch)); + .append(build_select_tail_clause(clause)); } doc.append(build_semicolon(select.semicolon_token())) @@ -18948,13 +18998,14 @@ fn build_relation_from_item<'a>(relation: ast::RelationFromItem) -> Doc<'a> { .append(leading_comments_token(&star)) .append(Doc::text("*")); } + doc = doc.append(build_from_alias(relation.alias())); if let Some(tablesample) = relation.tablesample_clause() { doc = doc .append(Doc::space()) .append(leading_comments(tablesample.syntax())) .append(build_tablesample_clause(tablesample)); } - doc.append(build_from_alias(relation.alias())) + doc } fn build_tablesample_clause<'a>(tablesample: ast::TablesampleClause) -> Doc<'a> { @@ -19255,11 +19306,11 @@ fn build_array_expr<'a>(array_expr: ast::ArrayExpr) -> Doc<'a> { doc = doc.append(Doc::text("array")); }; - if let Some(select) = array_expr.select() { + if let Some(select) = array_expr.select_variant() { if let Some(l_paren) = array_expr.l_paren_token() { doc = doc.append(comments_before(l_paren)); } - let mut body = leading_comments(select.syntax()).append(build_select_doc(&select)); + let mut body = leading_comments(select.syntax()).append(build_select_variant(select)); if let Some(r_paren) = array_expr.r_paren_token() { body = body.append(comments_before(r_paren)); } @@ -19268,10 +19319,21 @@ fn build_array_expr<'a>(array_expr: ast::ArrayExpr) -> Doc<'a> { .append(Doc::text(")")) .group() } else { - if let Some(l_brack) = array_expr.l_brack_token() { - doc = doc.append(comments_before(l_brack)); + let (l_delimiter, r_delimiter, opening, closing) = + if let Some(l_paren) = array_expr.l_paren_token() { + (Some(l_paren), array_expr.r_paren_token(), "(", ")") + } else { + ( + array_expr.l_brack_token(), + array_expr.r_brack_token(), + "[", + "]", + ) + }; + if let Some(l_delimiter) = l_delimiter { + doc = doc.append(comments_before(l_delimiter)); } - doc = doc.append(Doc::text("[")); + doc = doc.append(Doc::text(opening)); let exprs = array_expr.exprs().map(|expr| { let syntax = expr.syntax().clone(); @@ -19279,10 +19341,12 @@ fn build_array_expr<'a>(array_expr: ast::ArrayExpr) -> Doc<'a> { (doc, syntax) }); let mut body = build_comma_separated_docs(exprs).unwrap_or_else(Doc::nil); - if let Some(r_brack) = array_expr.r_brack_token() { - body = body.append(comments_before(r_brack)); + if let Some(r_delimiter) = r_delimiter { + body = body.append(comments_before(r_delimiter)); } - doc.append(wrap_body(body)).append(Doc::text("]")).group() + doc.append(wrap_body(body)) + .append(Doc::text(closing)) + .group() } } @@ -19383,10 +19447,8 @@ fn build_tuple_expr<'a>(tuple_expr: ast::TupleExpr) -> Doc<'a> { let exprs = build_comma_separated_exprs(tuple_expr.exprs()); let has_exprs = exprs.is_some(); let mut body = exprs.unwrap_or_else(Doc::nil); - if !has_exprs { - if let Some(r_paren) = tuple_expr.r_paren_token() { - body = body.append(comments_before(r_paren)); - } + if !has_exprs && let Some(r_paren) = tuple_expr.r_paren_token() { + body = body.append(comments_before(r_paren)); } doc.append(wrap_body(body)).append(Doc::text(")")).group() @@ -19430,31 +19492,7 @@ fn build_call_expr_with_spacing<'a>(call_expr: ast::CallExpr, space_before_paren doc = doc .append(comments_before(arg_list.syntax().clone())) .append(build_call_arg_list(arg_list)); - if let Some(within_clause) = call_expr.within_clause() { - doc = doc - .append(Doc::space()) - .append(leading_comments(within_clause.syntax())) - .append(build_within_clause(within_clause)); - } - if let Some(filter_clause) = call_expr.filter_clause() { - doc = doc - .append(Doc::space()) - .append(leading_comments(filter_clause.syntax())) - .append(build_filter_clause(filter_clause)); - } - if let Some(null_treatment) = call_expr.null_treatment() { - doc = doc - .append(Doc::space()) - .append(leading_comments(null_treatment.syntax())) - .append(build_null_treatment(null_treatment)); - } - if let Some(over_clause) = call_expr.over_clause() { - doc = doc - .append(Doc::space()) - .append(leading_comments(over_clause.syntax())) - .append(build_over_clause(over_clause)); - } - doc + build_call_expr_postfix_clauses(doc, &call_expr) } else if let Some(all_fn) = call_expr.all_fn() { build_parenthesized_expr_or_select_fn( "all", @@ -19486,7 +19524,7 @@ fn build_call_expr_with_spacing<'a>(call_expr: ast::CallExpr, space_before_paren } else if let Some(graph_table_fn) = call_expr.graph_table_fn() { build_graph_table_fn(graph_table_fn) } else if let Some(json_array_agg_fn) = call_expr.json_array_agg_fn() { - build_json_array_agg_fn(json_array_agg_fn) + build_call_expr_postfix_clauses(build_json_array_agg_fn(json_array_agg_fn), &call_expr) } else if let Some(json_array_fn) = call_expr.json_array_fn() { build_json_array_fn(json_array_fn) } else if let Some(json_exists_fn) = call_expr.json_exists_fn() { @@ -19494,7 +19532,7 @@ fn build_call_expr_with_spacing<'a>(call_expr: ast::CallExpr, space_before_paren } else if let Some(json_fn) = call_expr.json_fn() { build_json_fn(json_fn) } else if let Some(json_object_agg_fn) = call_expr.json_object_agg_fn() { - build_json_object_agg_fn(json_object_agg_fn) + build_call_expr_postfix_clauses(build_json_object_agg_fn(json_object_agg_fn), &call_expr) } else if let Some(json_object_fn) = call_expr.json_object_fn() { build_json_object_fn(json_object_fn) } else if let Some(json_query_fn) = call_expr.json_query_fn() { @@ -19540,6 +19578,34 @@ fn build_call_expr_with_spacing<'a>(call_expr: ast::CallExpr, space_before_paren } } +fn build_call_expr_postfix_clauses<'a>(mut doc: Doc<'a>, call_expr: &ast::CallExpr) -> Doc<'a> { + if let Some(within_clause) = call_expr.within_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(within_clause.syntax())) + .append(build_within_clause(within_clause)); + } + if let Some(filter_clause) = call_expr.filter_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(filter_clause.syntax())) + .append(build_filter_clause(filter_clause)); + } + if let Some(null_treatment) = call_expr.null_treatment() { + doc = doc + .append(Doc::space()) + .append(leading_comments(null_treatment.syntax())) + .append(build_null_treatment(null_treatment)); + } + if let Some(over_clause) = call_expr.over_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(over_clause.syntax())) + .append(build_over_clause(over_clause)); + } + doc +} + fn build_graph_table_fn<'a>(graph_table_fn: ast::GraphTableFn) -> Doc<'a> { let mut doc = Doc::text("graph_table"); if let Some(l_paren) = graph_table_fn.l_paren_token() { @@ -21507,20 +21573,20 @@ fn build_call_arg_list<'a>(arg_list: ast::ArgList) -> Doc<'a> { } fn build_call_arg<'a>(arg: ast::Arg) -> Doc<'a> { - let mut doc = if let Some(named_arg) = arg.named_arg() { - build_named_call_arg(named_arg) - } else { - let mut doc = Doc::nil(); - if arg.variadic_token().is_some() { - doc = doc.append(Doc::text("variadic")).append(Doc::space()); - } - if let Some(expr) = arg.expr() { - doc = doc - .append(leading_comments(expr.syntax())) - .append(build_expr(expr)); - } - doc - }; + let mut doc = Doc::nil(); + if arg.variadic_token().is_some() { + doc = doc.append(Doc::text("variadic")).append(Doc::space()); + } + if let Some(named_arg) = arg.named_arg() { + doc = doc + .append(leading_comments(named_arg.syntax())) + .append(build_named_call_arg(named_arg)); + } else if let Some(expr) = arg.expr() { + doc = doc + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(order_by_clause) = arg.order_by_clause() { doc = doc .append(Doc::space()) @@ -21530,6 +21596,17 @@ fn build_call_arg<'a>(arg: ast::Arg) -> Doc<'a> { doc } +fn build_select_tail_clause<'a>(clause: ast::SelectTailClause) -> Doc<'a> { + let doc = leading_comments(clause.syntax()); + match clause { + ast::SelectTailClause::OrderByClause(clause) => doc.append(build_order_by_clause(clause)), + ast::SelectTailClause::LockingClause(clause) => doc.append(build_locking_clause(clause)), + ast::SelectTailClause::LimitClause(clause) => doc.append(build_limit_clause(clause)), + ast::SelectTailClause::OffsetClause(clause) => doc.append(build_offset_clause(clause)), + ast::SelectTailClause::FetchClause(clause) => doc.append(build_fetch_clause(clause)), + } +} + fn build_order_by_clause<'a>(clause: ast::OrderByClause) -> Doc<'a> { let mut doc = Doc::text("order").append(Doc::space()); if let Some(by_token) = clause.by_token() { @@ -21738,15 +21815,15 @@ fn build_cast_expr<'a>(cast_expr: ast::CastExpr) -> Doc<'a> { if let Some(l_paren) = cast_expr.l_paren_token() { doc = doc.append(comments_before(l_paren)); } - let mut body = leading_comments(expr.syntax()) + let body = leading_comments(expr.syntax()) .append(build_expr(expr)) .append(Doc::line_or_space()) .append(leading_comments_token(&as_token)) .append(Doc::text("as")) .append(Doc::line_or_space()) .append(leading_comments(ty.syntax())) - .append(build_type(ty)) - .group(); + .append(build_type(ty)); + let mut body = body.group(); if let Some(r_paren) = cast_expr.r_paren_token() { body = body.append(comments_before(r_paren)); } @@ -21814,14 +21891,10 @@ fn build_paren_expr<'a>(paren_expr: ast::ParenExpr) -> Doc<'a> { body = body .append(leading_comments(compound_select.syntax())) .append(build_compound_select(&compound_select)); - } else if let Some(from_item) = paren_expr.from_item() { + } else if let Some(item) = paren_expr.from_list_item() { body = body - .append(leading_comments(from_item.syntax())) - .append(build_from_item(from_item)); - } else if let Some(join_expr) = paren_expr.join_expr() { - body = body - .append(leading_comments(join_expr.syntax())) - .append(build_join_expr(join_expr)); + .append(leading_comments(item.syntax())) + .append(build_from_list_item(item)); } else if let Some(select) = paren_expr.select() { body = body .append(leading_comments(select.syntax())) @@ -21846,7 +21919,34 @@ fn build_paren_expr<'a>(paren_expr: ast::ParenExpr) -> Doc<'a> { fn build_postfix_expr<'a>(postfix_expr: ast::PostfixExpr) -> Doc<'a> { let expr = build_expr(postfix_expr.expr().unwrap()); - let op = match postfix_expr.op().unwrap() { + let op = postfix_expr.op().unwrap(); + expr.append(Doc::space()) + .append(leading_comments_postfix_op(&op)) + .append(build_postfix_op(op)) +} + +fn leading_comments_postfix_op<'a>(op: &ast::PostfixOp) -> Doc<'a> { + match op { + ast::PostfixOp::AtLocal(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsJson(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsJsonArray(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsJsonObject(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsJsonScalar(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsJsonValue(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNormalized(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotJson(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotJsonArray(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotJsonObject(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotJsonScalar(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotJsonValue(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNotNormalized(node) => leading_comments(node.syntax()), + ast::PostfixOp::IsNull(token) => leading_comments_token(token), + ast::PostfixOp::NotNull(token) => leading_comments_token(token), + } +} + +fn build_postfix_op<'a>(op: ast::PostfixOp) -> Doc<'a> { + match op { ast::PostfixOp::AtLocal(n) => { build_two_keywords(n.at_token(), "at", n.local_token(), "local") } @@ -21942,8 +22042,7 @@ fn build_postfix_expr<'a>(postfix_expr: ast::PostfixExpr) -> Doc<'a> { n.unicode_normal_form(), n.normalized_token(), ), - }; - expr.append(Doc::space()).append(op) + } } fn build_postfix_keywords<'a>( diff --git a/crates/squawk_fmt/tests/after/alter_default_privileges.snap b/crates/squawk_fmt/tests/after/alter_default_privileges.snap index b7b749edd..9aebf3d12 100644 --- a/crates/squawk_fmt/tests/after/alter_default_privileges.snap +++ b/crates/squawk_fmt/tests/after/alter_default_privileges.snap @@ -4,6 +4,25 @@ input_file: crates/squawk_fmt/tests/before/alter_default_privileges.sql --- alter default privileges grant select on tables to reporting; +alter default privileges + grant update + on tables + to + public, + current_user, + group /* session role */ session_user, + group /* current role */ current_role + with grant option; + +alter default privileges + revoke grant option for + select + on tables + from + reporting, + group /* current user */ current_user + cascade; + alter default privileges for role app_owner, @@ -35,11 +54,9 @@ alter /* default keyword */ default /* privileges keyword */ privileges /* schemas clause */ in /* schema keyword */ schema /* first schema */ exceedingly_long_application_schema, /* second schema */ exceedingly_long_audit_schema - /* action */ grant - /* privilege */ select, + /* action */ grant /* privilege */ select, /* another privilege */ update - /* on keyword */ on - /* target */ tables + /* on keyword */ on /* target */ tables /* to keyword */ to /* grantee */ exceedingly_long_reporting_role /* with clause */ with /* grant keyword */ grant /* option keyword */ option/* semicolon */; diff --git a/crates/squawk_fmt/tests/after/alter_foreign_table.snap b/crates/squawk_fmt/tests/after/alter_foreign_table.snap index 2e120694c..fbce12c50 100644 --- a/crates/squawk_fmt/tests/after/alter_foreign_table.snap +++ b/crates/squawk_fmt/tests/after/alter_foreign_table.snap @@ -19,3 +19,10 @@ alter foreign table public.items alter foreign table if exists public.items owner to role_with_a_very_long_descriptive_name; + +alter foreign table ft1 + /* options */ options /* open */ ( + /* drop */ drop /* name */ delimiter /* comma */, + /* set */ set /* name */ quote /* value */ '~' /* comma */, + /* add */ add /* name */ escape /* value */ '@' /* close */ + ); diff --git a/crates/squawk_fmt/tests/after/alter_operator.snap b/crates/squawk_fmt/tests/after/alter_operator.snap index e30990a85..bee5b21ce 100644 --- a/crates/squawk_fmt/tests/after/alter_operator.snap +++ b/crates/squawk_fmt/tests/after/alter_operator.snap @@ -1,6 +1,5 @@ --- source: crates/squawk_fmt/tests/tests.rs -assertion_line: 28 input_file: crates/squawk_fmt/tests/before/alter_operator.sql --- alter /* operator */ operator /* signature */ public.+ /* left paren */( @@ -11,7 +10,7 @@ alter /* operator */ operator /* signature */ public.+ /* left paren */( alter operator public.##(none, integer) set ( restrict = schema_a.restrict_function_with_a_very_long_name, - "join" = schema_a.join_function_with_a_very_long_name, + join = schema_a.join_function_with_a_very_long_name, hashes, merges ); diff --git a/crates/squawk_fmt/tests/after/alter_property_graph.snap b/crates/squawk_fmt/tests/after/alter_property_graph.snap index 21ad21cdd..189f3755c 100644 --- a/crates/squawk_fmt/tests/after/alter_property_graph.snap +++ b/crates/squawk_fmt/tests/after/alter_property_graph.snap @@ -1,6 +1,5 @@ --- source: crates/squawk_fmt/tests/tests.rs -assertion_line: 28 input_file: crates/squawk_fmt/tests/before/alter_property_graph.sql --- alter /* property */ property /* graph */ graph /* if */ if /* exists */ exists /* name */ public.social_graph @@ -12,6 +11,18 @@ alter property graph graph_with_an_exceptionally_long_descriptive_name merchant_accounts key (merchant_identifier) properties all columns ); +alter property graph social_graph + /* first add */ add /* vertex tables */ vertex tables (people key (id)) + /* second add */ add /* edge tables */ edge tables ( + follows + /* source */ source /* source key */ key (follower_id) references people ( + id + ) + /* destination */ destination /* destination key */ key ( + followed_id + ) references people (id) + ); + alter property graph social_graph alter /* kind */ vertex /* table */ table /* element */ people alter /* label */ label /* name */ person add /* properties */ properties ( full_name, diff --git a/crates/squawk_fmt/tests/after/alter_publication.snap b/crates/squawk_fmt/tests/after/alter_publication.snap index e2240a61e..d766caf9b 100644 --- a/crates/squawk_fmt/tests/after/alter_publication.snap +++ b/crates/squawk_fmt/tests/after/alter_publication.snap @@ -7,6 +7,12 @@ alter publication selected_tables alter publication selected_tables drop table public.old_accounts; +alter publication selected_tables + add table public.first_table, /* before omitted table */ public.second_table; + +alter publication selected_tables + set tables in schema first_schema, /* before omitted schema */ second_schema; + alter publication selected_tables set table public.accounts (id) where (id > 200); diff --git a/crates/squawk_fmt/tests/after/alter_sequence.snap b/crates/squawk_fmt/tests/after/alter_sequence.snap index 0909bf0e8..a4ee5044e 100644 --- a/crates/squawk_fmt/tests/after/alter_sequence.snap +++ b/crates/squawk_fmt/tests/after/alter_sequence.snap @@ -11,6 +11,21 @@ alter sequence public.invoice_number cycle owned by public.invoices.invoice_number; +alter sequence public.descending_sequence + restart with -24 + start with -24 + increment by -4 + minvalue -36 + maxvalue -5 + cycle; + +alter sequence public.commented_descending_sequence + restart with /* before restart sign */ - /* before restart value */ 24 + start with /* before start sign */ - /* before start value */ 24 + increment by /* before increment sign */ - /* before increment value */ 4 + minvalue /* before minimum sign */ - /* before minimum value */ 36 + maxvalue /* before maximum sign */ - /* before maximum value */ 5; + alter sequence if exists public.invoice_number as bigint no minvalue diff --git a/crates/squawk_fmt/tests/after/alter_subscription.snap b/crates/squawk_fmt/tests/after/alter_subscription.snap index 56918655b..a7ebe14c4 100644 --- a/crates/squawk_fmt/tests/after/alter_subscription.snap +++ b/crates/squawk_fmt/tests/after/alter_subscription.snap @@ -9,6 +9,15 @@ alter subscription local_sub server publisher_server; alter subscription local_sub set (slot_name = new_slot, synchronous_commit = local); +alter subscription regress_testsub4 + set (origin = any, max_retention_duration = -1); + +alter subscription regress_testsub4 + set ( + /* before origin */ origin /* before equals */ = /* before any */ any /* after any */, + /* before retention */ max_retention_duration = /* before minus */ - /* before one */ 1 /* after one */ + ); + alter subscription local_sub add publication another_publication, third_publication diff --git a/crates/squawk_fmt/tests/after/alter_table.snap b/crates/squawk_fmt/tests/after/alter_table.snap index ee35eb996..81cd70f1d 100644 --- a/crates/squawk_fmt/tests/after/alter_table.snap +++ b/crates/squawk_fmt/tests/after/alter_table.snap @@ -16,6 +16,18 @@ alter table reporting.customer_activity add constraint positive_activity_count check (activity_count > 0), alter constraint positive_activity_count deferrable initially deferred; +alter table tx1 rename to a1; + +alter table temp_view_test.tt1 rename to tmp1; + +alter /* table */ table /* old table */ temp_view_test.tt1 + /* rename */ rename /* to */ to /* new table */ tmp1/* semicolon */; + +alter table temporal_children + add constraint temporal_children_parent_fk + foreign key (parent_id, period valid_at) + references temporal_parents (id, period valid_at); + alter table reporting.customer_activity attach partition reporting.customer_activity_2025 for values from ('2025-01-01') to ('2026-01-01'); @@ -50,3 +62,104 @@ alter /* table */ table /* all */ all /* in */ in /* tablespace keyword */ table /* second role */ role_two /* set */ set /* tablespace */ tablespace /* new tablespace */ new_tablespace /* nowait */ nowait/* semicolon */; + +alter table only parent_table + alter constraint inherited_check_constraint enforced; + +alter table parent_table + alter constraint inherited_check_constraint not enforced; + +alter table parent_table alter constraint inherited_not_null_constraint inherit; + +alter table parent_table + alter constraint inherited_not_null_constraint no inherit; + +alter /* table */ table /* relation */ parent_table + /* alter */ alter /* constraint */ constraint /* constraint name */ inherited_check_constraint /* enforced */ enforced/* semicolon */; + +alter /* table */ table /* relation */ parent_table + /* alter */ alter /* constraint */ constraint /* constraint name */ inherited_check_constraint /* not */ not /* enforced */ enforced/* semicolon */; + +alter /* table */ table /* relation */ parent_table + /* alter */ alter /* constraint */ constraint /* constraint name */ inherited_not_null_constraint /* inherit */ inherit/* semicolon */; + +alter /* table */ table /* relation */ parent_table + /* alter */ alter /* constraint */ constraint /* constraint name */ inherited_not_null_constraint /* no */ no /* inherit */ inherit/* semicolon */; + +alter table child_table + add not null a_very_long_inherited_column_name no inherit; + +alter /* table */ table /* relation */ child_table + /* add */ add + /* not */ not /* null */ null /* column */ inherited_column + /* no */ no /* inherit */ inherit/* semicolon */; + +alter table parent_table + alter constraint source_ordered_constraint /* enforced */ enforced /* not */ not /* deferrable */ deferrable; + +alter table parent_table + alter constraint invalid_constraint /* not */ not /* valid */ valid; + +alter table comment_test alter column id set data type int collate "C"; + +alter table extraordinarily_long_partition_name + alter extraordinarily_long_column_name type char(2) collate "POSIX"; + +alter /* table */ table /* relation */ comment_test + /* alter */ alter /* column */ column /* column name */ description + /* set */ set /* data */ data /* type keyword */ type /* type */ character varying(100) + /* collate */ collate /* collation */ "POSIX" + /* using */ using + /* expression */ description::character varying/* semicolon */; + +alter table sales_range + merge partitions (sales_jan2022, sales_feb2022) into sales_jan_feb2022; + +alter table partitions_merge_schema.extraordinarily_long_sales_range + merge partitions ( + partitions_merge_schema.extraordinarily_long_sales_january_2022, + partitions_merge_schema.extraordinarily_long_sales_february_2022 + ) + into partitions_merge_schema.extraordinarily_long_sales_january_february_2022; + +alter /* table */ table /* relation */ partitions_merge_schema.sales_range + /* merge */ merge /* partitions */ partitions /* left paren */ ( + /* first partition */ partitions_merge_schema.sales_jan2022 /* first comma */, + /* second partition */ sales_feb2022, + /* third partition */ sales_mar2022 /* right paren */ + ) + /* into */ into /* target partition */ partitions_merge_schema./* target name */ sales_jan_feb_mar2022/* semicolon */; + +alter table sales_range + split partition sales_feb_mar_apr2022 into ( + partition sales_feb2022 for values from ('2022-02-01') to ('2022-03-01'), + partition sales_mar2022 for values in ('2022-03-01', '2022-04-01'), + partition sales_hash for values with (modulus 4, remainder 1), + partition sales_others default + ); + +alter /* table */ table /* relation */ sales_range + /* split */ split /* partition */ partition /* source */ partition_split_schema./* source name */ sales_all /* into */ into /* list left */ ( + /* first partition */ partition /* first name */ partition_split_schema./* name */ sales_first + /* for */ for /* values */ values /* from */ from /* lower left */ ( + /* lower */ '2022-01-01' /* lower right */ + ) + /* to */ to /* upper left */ ( + /* upper */ '2022-02-01' /* upper right */ + ) /* comma */, + /* second partition */ partition /* second name */ sales_second + /* for */ for /* values */ values /* in */ in /* in left */( + /* first value */ 'one' /* value comma */, + /* second value */ 'two' /* in right */ + ), + /* default partition */ partition /* default name */ sales_other + /* default */ default /* list right */ + )/* semicolon */; + +alter table options_test alter column metadata set (json = constraint); + +alter /* table */ table /* relation */ options_test + /* alter */ alter /* column */ column /* name */ metadata + /* set */ set /* left paren */ ( + /* option */ json /* equals */ = /* value */ constraint /* right paren */ + )/* semicolon */; diff --git a/crates/squawk_fmt/tests/after/compound_select.snap b/crates/squawk_fmt/tests/after/compound_select.snap index 60fda8019..224991264 100644 --- a/crates/squawk_fmt/tests/after/compound_select.snap +++ b/crates/squawk_fmt/tests/after/compound_select.snap @@ -51,3 +51,14 @@ select 1 union all -- before right select select 2; + +select 1 +union +select 2 +offset 1 +limit 2; +select 1 +union +select 2 +limit 1 +for update; diff --git a/crates/squawk_fmt/tests/after/create_aggregate.snap b/crates/squawk_fmt/tests/after/create_aggregate.snap index 7e7797ad8..122f912cd 100644 --- a/crates/squawk_fmt/tests/after/create_aggregate.snap +++ b/crates/squawk_fmt/tests/after/create_aggregate.snap @@ -53,3 +53,29 @@ create aggregate extraordinarily_long_schema_name.extraordinarily_long_ordered_s sfunc = extraordinarily_long_schema_name.extraordinarily_long_transition_function_name, stype = internal ); + +create aggregate alt_agg2 ( + sfunc1 = int4mi, + basetype = int4, + stype1 = int4, + initcond = -100 +); + +create aggregate commented_negative ( + sfunc = transition_function, + stype = bigint, + initcond /* before equals */ = /* before minus */ - /* after minus */ 100 /* after value */ +); + +create aggregate public.old_style_sort_operator ( + sfunc = public.sort_state, + basetype = integer, + stype = bigint, + sortop = < +); + +create aggregate public.commented_sort_operator(integer) ( + sfunc = public.sort_state, + stype = bigint, + /* sort operator */ sortop /* equals */ = /* less-than operator */ < /* closing parenthesis */ +); diff --git a/crates/squawk_fmt/tests/after/create_function.snap b/crates/squawk_fmt/tests/after/create_function.snap index c64a9f7ee..3b2c2822a 100644 --- a/crates/squawk_fmt/tests/after/create_function.snap +++ b/crates/squawk_fmt/tests/after/create_function.snap @@ -44,7 +44,6 @@ create function option_examples( ) returns text external security invoker called on null input - returns null on null input not leakproof stable window @@ -56,6 +55,11 @@ create function option_examples( language 'sql' as $$ select null::text $$; +create function returns_null_on_null_input_example() returns text + returns null on null input + language sql + as $$ select null::text $$; + -- comments in every position create /*a*/ or /*b*/ replace /*c*/ function /*d*/ app /*e*/./*f*/ commented( /*g*/ in /*h*/ value /*i*/ integer /*j*/ default /*k*/ 1 /*l*/, @@ -108,6 +112,26 @@ create function external_add(integer, integer) returns integer as '$libdir/example', 'external_add' language c; +create function test_enc_conversion( + bytea, + name, + name, + bool, + validlen out int, + result out bytea +) + as 'regresslib', 'test_enc_conversion' + language c + strict; + +create function mode_after_name( + input /*bp1*/ in /*bp2*/ bytea, + result /*bp3*/ out /*bp4*/ bytea, + error /*bp5*/ out /*bp6*/ text +) returns record + language sql + as $$ select null $$; + create function commented_external() returns integer as /*bo*/ '$libdir/example' /*bp*/, /*bq*/ 'commented_external' /*br*/ language c; diff --git a/crates/squawk_fmt/tests/after/create_index.snap b/crates/squawk_fmt/tests/after/create_index.snap index 6c7cce11b..6e8bd6937 100644 --- a/crates/squawk_fmt/tests/after/create_index.snap +++ b/crates/squawk_fmt/tests/after/create_index.snap @@ -48,3 +48,19 @@ create /*a*/ unique /*b*/ index /*c*/ concurrently /*d*/ if /*e*/ not /*f*/ exis ) /*at*/ tablespace /*au*/ fastspace /*av*/ where /*aw*/ active /*ax*/ and email is not null/*ay*/; + +create index on t ( + a text_pattern_ops (strength = primary, variant = default), + b text_pattern_ops (strength = primary) +); + +-- operator class parameters with comments +create index on t ( + a /*ba*/ text_pattern_ops /*bb*/ ( + /*bc*/ strength /*bd*/ = /*be*/ primary /*bf*/, + /*bg*/ variant /*bh*/ = /*bi*/ default /*bj*/ + ), + /*bk*/ b /*bl*/ text_pattern_ops /*bm*/ ( + /*bn*/ strength /*bo*/ = /*bp*/ primary /*bq*/ + ) +); diff --git a/crates/squawk_fmt/tests/after/create_operator.snap b/crates/squawk_fmt/tests/after/create_operator.snap index d73ce2b94..ff89bb426 100644 --- a/crates/squawk_fmt/tests/after/create_operator.snap +++ b/crates/squawk_fmt/tests/after/create_operator.snap @@ -6,6 +6,33 @@ create operator > ( function = greater_than ); +create operator === ( + leftarg = integer, + rightarg = integer, + procedure = int4eq, + commutator = = +); + +create operator === ( + leftarg = integer, + rightarg = integer, + procedure = int4eq, + negator = <> +); + +create operator = ( + commutator /* assignment */ = /* equal operator */ = /* comma */, + negator = /* not equal operator */ <> /* closing parenthesis */ +); + +create operator = ( + join = eqjoinsel +); + +create operator <> ( + /* join option */ join /* equals */ = /* selectivity function */ eqjoinsel /* closing parenthesis */ +); + create operator extraordinarily_long_schema_name.## ( function = extraordinarily_long_schema_name.extraordinarily_long_operator_function_name, leftarg = extraordinarily_long_schema_name.extraordinarily_long_left_argument_type, @@ -21,3 +48,25 @@ create /* operator */ operator /* schema */ app /* dot */./* symbol */ # /* attr /* left argument */ leftarg /* second equals */ = /* type */ integer /* comma */, /* marker */ hashes /* closing parenthesis */ )/* semicolon */; + +create operator + ( + commutator = +, + negator = -, + restrict = *, + join = % +); + +create operator + ( + commutator = >, + negator = >=, + restrict = <=, + join = != +); + +create operator + ( + commutator = operator(pg_catalog.+) +); + +create operator + ( + commutator /* before qualified operator */ = /* before operator keyword */ operator /* before open paren */(/* before operator */ pg_catalog.+ /* before close paren */) /* before semicolon */ +); diff --git a/crates/squawk_fmt/tests/after/create_property_graph.snap b/crates/squawk_fmt/tests/after/create_property_graph.snap index 6ba48288c..0ee647b61 100644 --- a/crates/squawk_fmt/tests/after/create_property_graph.snap +++ b/crates/squawk_fmt/tests/after/create_property_graph.snap @@ -10,6 +10,14 @@ create property graph social destination key (followed_id) references people (id) ); +create property graph social + vertex tables (people, organizations) + edge tables ( + employment + /* source */ source /* source table */ people + /* destination */ destination /* destination table */ organizations + ); + create unlogged property graph extraordinarily_long_social_relationship_property_graph vertex tables ( extraordinarily_long_people_table key ( diff --git a/crates/squawk_fmt/tests/after/create_publication.snap b/crates/squawk_fmt/tests/after/create_publication.snap index 5db240a81..c966e8f7e 100644 --- a/crates/squawk_fmt/tests/after/create_publication.snap +++ b/crates/squawk_fmt/tests/after/create_publication.snap @@ -21,6 +21,15 @@ create publication selected_tables create publication no_tables with (publish = 'insert'); +create publication omitted_table_keywords + for + table first_table, + /* before omitted table */ public.second_table where (id > 100), + /* before omitted only */ only third_table; + +create publication omitted_schema_keywords + for tables in schema first_schema, /* before omitted schema */ second_schema; + create /* after create */ publication /* after publication */ commented_pub for /* after for */ table /* after table */ only /* after only */(/* before table name */ public.commented /* before close */)/* before star */ * /* before columns */ ( diff --git a/crates/squawk_fmt/tests/after/create_schema.snap b/crates/squawk_fmt/tests/after/create_schema.snap index 52dd7c84c..6417cf0f3 100644 --- a/crates/squawk_fmt/tests/after/create_schema.snap +++ b/crates/squawk_fmt/tests/after/create_schema.snap @@ -17,3 +17,27 @@ create /* schema */ schema /* if */ if /* not */ not /* exists */ exists create /* schema */ schema /* authorization */ authorization /* role */ schema_owner/* end */; + +create schema + authorization regress_create_schema_role + create function schema_not_existing.func(int) returns int + as 'SELECT $1' + language sql; + +create schema + extraordinarily_long_application_reporting_and_data_warehouse_schema + authorization extraordinarily_long_application_reporting_and_data_warehouse_owner_role + create function extraordinarily_long_application_reporting_and_data_warehouse_schema.calculate_an_extraordinarily_long_result( + an_extraordinarily_long_input_parameter integer + ) returns an_extraordinarily_long_result_type + language sql + immutable + as 'SELECT $1'; + +create /* schema keyword */ schema + /* authorization keyword */ authorization /* role */ schema_owner + /* schema element */ create /* function keyword */ function /* function name */ app /* separator */./* local name */ calculate( + /* parameter */ value integer + ) /* return type */ returns integer /* language option */ + language /* language */ sql + /* as option */ as /* body */ 'SELECT $1'/* end */; diff --git a/crates/squawk_fmt/tests/after/create_sequence.snap b/crates/squawk_fmt/tests/after/create_sequence.snap index 2fec1cb91..e49ea0ba1 100644 --- a/crates/squawk_fmt/tests/after/create_sequence.snap +++ b/crates/squawk_fmt/tests/after/create_sequence.snap @@ -4,6 +4,22 @@ input_file: crates/squawk_fmt/tests/before/create_sequence.sql --- create sequence invoice_number_seq; +create sequence + descending_seq + increment by -1 + minvalue -100000 + maxvalue +20 + start with -10 + cache +5; + +create sequence + commented_descending_seq + increment by /* before sign */ - /* before value */ 1 + minvalue /* before minimum sign */ - /* before minimum */ 100000 + maxvalue /* before maximum sign */ + /* before maximum */ 20 + start with /* before start sign */ - /* before start */ 10 + cache /* before cache sign */ + /* before cache */ 5; + create temporary sequence if not exists extraordinarily_long_schema_name.extraordinarily_long_invoice_number_sequence as bigint diff --git a/crates/squawk_fmt/tests/after/create_subscription.snap b/crates/squawk_fmt/tests/after/create_subscription.snap index b619836ee..c96acbda2 100644 --- a/crates/squawk_fmt/tests/after/create_subscription.snap +++ b/crates/squawk_fmt/tests/after/create_subscription.snap @@ -10,6 +10,19 @@ create subscription local_sub create subscription server_sub server publisher_server publication all_changes; +create subscription regress_testsub + connection 'dbname=regress_doesnotexist' + publication testpub + with (connect = false, max_retention_duration = -1); + +create subscription regress_testsub + connection 'dbname=regress_doesnotexist' + publication testpub + with ( + connect = false, + max_retention_duration /* before equals */ = /* before minus */ - /* before one */ 1 /* after one */ + ); + create /* after create */ subscription /* before name */ commented_sub connection /* before connection */ 'host=localhost' publication /* before publication */ all_changes, diff --git a/crates/squawk_fmt/tests/after/create_table.snap b/crates/squawk_fmt/tests/after/create_table.snap index 3b41fc71c..d55bd9ffb 100644 --- a/crates/squawk_fmt/tests/after/create_table.snap +++ b/crates/squawk_fmt/tests/after/create_table.snap @@ -1,6 +1,5 @@ --- source: crates/squawk_fmt/tests/tests.rs -assertion_line: 28 input_file: crates/squawk_fmt/tests/before/create_table.sql --- create table u (); @@ -135,3 +134,21 @@ create table column_option_comments ( /*ay*/ sequence /*az*/ name /*ba*/ public.seq /*bb*/ ) ); + +-- preserve column clause source order when collate follows constraints +create table collate_after_primary_key (id varchar primary key collate "C"); + +create table collate_between_constraints ( + id varchar not null collate "C" unique +); + +-- preserve comments when collate follows or occurs between constraints +create table extraordinarily_long_collated_column_clause_order ( + first_identifier /* before type */ varchar + /* before primary */ primary /* before key */ key /* before collate */ + collate /* before collation */ "C", + second_identifier varchar + /* before not */ not /* before null */ null + /* before second collate */ collate /* before second collation */ "POSIX" + /* before unique */ unique +); diff --git a/crates/squawk_fmt/tests/after/create_trigger.snap b/crates/squawk_fmt/tests/after/create_trigger.snap index 22702a3e6..f18c38e6d 100644 --- a/crates/squawk_fmt/tests/after/create_trigger.snap +++ b/crates/squawk_fmt/tests/after/create_trigger.snap @@ -41,6 +41,26 @@ create trigger a_trigger_with_a_very_long_name 'a long argument value' ); +create constraint trigger constraint_options + after insert + on foo + not valid + no inherit + not enforced + enforced + for each row + execute function foo(); + +create constraint trigger commented_constraint_options + after insert + on foo + /*a*/ not /*b*/ valid + /*c*/ no /*d*/ inherit + /*e*/ not /*f*/ enforced + /*g*/ enforced + /*h*/ for each row + execute function foo(); + -- comments in every position create /*a*/ or /*b*/ replace /*c*/ constraint /*d*/ trigger /*e*/ commented_trigger /*f*/ instead /*g*/ of diff --git a/crates/squawk_fmt/tests/after/create_type.snap b/crates/squawk_fmt/tests/after/create_type.snap index 16dc1c67b..6e391784e 100644 --- a/crates/squawk_fmt/tests/after/create_type.snap +++ b/crates/squawk_fmt/tests/after/create_type.snap @@ -26,6 +26,14 @@ create type complex_base ( internallength = 16 ); +create type null_default_base ( + default = null +); + +create type commented_null_default_base ( + default /* equals */ = /* null value */ null /* close */ +); + -- comments in every composite type position create /* type */ type /* name */ commented_composite /* as */ as /* open */ ( /* field */ first_field /* type */ text diff --git a/crates/squawk_fmt/tests/after/create_user_mapping.snap b/crates/squawk_fmt/tests/after/create_user_mapping.snap index 96070cd5e..aed9e857a 100644 --- a/crates/squawk_fmt/tests/after/create_user_mapping.snap +++ b/crates/squawk_fmt/tests/after/create_user_mapping.snap @@ -4,6 +4,9 @@ input_file: crates/squawk_fmt/tests/before/create_user_mapping.sql --- create user mapping for app_user server app_server; +create user mapping for /* special user target */ user + /* server */ server app_server; + create user mapping if not exists for extraordinarily_long_application_reporting_user server extraordinarily_long_foreign_data_server options ( diff --git a/crates/squawk_fmt/tests/after/delete.snap b/crates/squawk_fmt/tests/after/delete.snap index 4d4a163a0..798a81069 100644 --- a/crates/squawk_fmt/tests/after/delete.snap +++ b/crates/squawk_fmt/tests/after/delete.snap @@ -44,6 +44,18 @@ with deleted as ( select * from deleted; +delete from t3 + using t1 join t2 using (a) + where t3.x > t1.a; + +delete from t3 + /*before using*/ using /*before t1*/ t1 + /*before join*/ join /*before t2*/ t2 + /*before join using*/ using /*before columns*/ ( + /*before a*/ a /*after a*/ + ) + /*before where*/ where t3.x > t1.a; + /*before*/ delete /*a*/ from /*b*/ foo /*c*/ for /*d*/ portion /*e*/ of /*f*/ valid_at /*g*/ from /*h*/ 1 /*i*/ to /*j*/ 2 /*k*/ as /*l*/ f diff --git a/crates/squawk_fmt/tests/after/drop_user_mapping.snap b/crates/squawk_fmt/tests/after/drop_user_mapping.snap index 29afef086..7a1aa9160 100644 --- a/crates/squawk_fmt/tests/after/drop_user_mapping.snap +++ b/crates/squawk_fmt/tests/after/drop_user_mapping.snap @@ -4,6 +4,10 @@ input_file: crates/squawk_fmt/tests/before/drop_user_mapping.sql --- drop user mapping for app_user server app_server; +drop user mapping for + /* special user target */ user + /* server */ server app_server; + drop user mapping if exists for extraordinarily_long_application_reporting_user_name server extraordinarily_long_foreign_data_server_name; diff --git a/crates/squawk_fmt/tests/after/from.snap b/crates/squawk_fmt/tests/after/from.snap index 516056841..60e1854f4 100644 --- a/crates/squawk_fmt/tests/after/from.snap +++ b/crates/squawk_fmt/tests/after/from.snap @@ -12,6 +12,11 @@ from foo /* before alias */ as /* before alias name */ f /* before open paren */ /* after comma */ display_name /* before close paren */ ); select * from users tablesample bernoulli (10) repeatable (42); +select t.id from test_tablesample as t tablesample system (50) repeatable (0); +select * +from users /* before alias */ as /* before alias name */ u /* before tablesample */ tablesample /* before method */ bernoulli /* before sample opening paren */( + /* before percentage */ 10 /* before sample closing paren */ + ) /* before repeatable */ repeatable /* before seed opening paren */(/* before seed */ 42 /* before seed closing paren */); select * from generate_series(1, 3); select * from lateral generate_series(1, 3) with ordinality as g (n, ord); select * @@ -104,78 +109,75 @@ from /* before lateral */ lateral /* before xmltable */ xmltable /* before openi ) /* before alias */ as /* before alias name */ parsed; select * from json_table( - doc, - '$[*]' - columns ( - ord for ordinality, - value text path '$.value', - has_value bool exists path '$.value', - nested path '$.items[*]' columns (item text path '$') - ) - plan (items) - ) as jt; + doc, + '$[*]' + columns ( + ord for ordinality, + value text path '$.value', + has_value bool exists path '$.value', + nested path '$.items[*]' columns (item text path '$') + ) + plan (items) +) as jt; select * from json_table( - doc, - '$' - columns ( - nested '$.a' as a columns (x int), - nested '$.b' as b columns (y int) - ) - plan (a cross b) - ) jt; + doc, + '$' + columns (nested '$.a' as a columns (x int), nested '$.b' as b columns (y int)) + plan (a cross b) +) jt; select * from json_table(doc, '$' columns (x int) plan default (inner, union)) jt; select * from lateral json_table( - a_very_long_json_document_expression - format json, - a_very_long_json_path_expression as a_very_long_json_path_name - passing a_very_long_json_passing_expression as a_very_long_json_variable_name + a_very_long_json_document_expression + format json, + a_very_long_json_path_expression as a_very_long_json_path_name + passing a_very_long_json_passing_expression as a_very_long_json_variable_name + columns ( + a_very_long_ordinality_column_name for ordinality, + a_very_long_value_column_name a_very_long_json_value_type + format json + path '$.a_very_long_value_path_expression_that_forces_wrapping' + with unconditional array wrapper + keep quotes on scalar string + default a_very_long_default_expression on empty + error on error, + a_very_long_exists_column_name boolean + exists + path '$.a_very_long_exists_path_expression_that_forces_wrapping' + false on error, + nested path a_very_long_nested_path_expression as a_very_long_nested_path_name columns ( - a_very_long_ordinality_column_name for ordinality, - a_very_long_value_column_name a_very_long_json_value_type - format json - path '$.a_very_long_value_path_expression_that_forces_wrapping' - with unconditional array wrapper - keep quotes on scalar string - default a_very_long_default_expression on empty - error on error, - a_very_long_exists_column_name boolean - exists - path '$.a_very_long_exists_path_expression_that_forces_wrapping' - false on error, - nested path a_very_long_nested_path_expression as a_very_long_nested_path_name - columns ( - a_very_long_nested_column_name a_very_long_nested_column_type - path '$.a_very_long_nested_column_path_expression_that_forces_wrapping' - ) + a_very_long_nested_column_name a_very_long_nested_column_type + path '$.a_very_long_nested_column_path_expression_that_forces_wrapping' ) - error on error - ) as a_very_long_json_table_alias; + ) + error on error +) as a_very_long_json_table_alias; select * from /* before lateral */ lateral /* before json table */ json_table /* before opening paren */( - /* before document */ doc - /* before format */ format /* before json */ json /* before comma */, - /* before path */ '$[*]' /* before path as */ as /* before path name */ root - /* before passing */ passing /* before argument */ x /* before argument as */ as /* before variable */ foo - /* before columns */ columns /* before columns opening paren */( - /* before ordinality column */ ord /* before for */ for /* before ordinality */ ordinality /* before column comma */, - /* before value column */ value /* before type */ text - /* before path keyword */ path /* before column path */ '$.value' /* before second column comma */, - /* before exists column */ has_value /* before exists type */ bool - /* before exists */ exists - /* before exists path */ path /* before exists path expression */ '$.value' /* before nested comma */, - /* before nested */ nested /* before nested path */ path /* before nested expression */ '$.items[*]' /* before nested as */ as /* before nested name */ items - /* before nested columns */ columns /* before nested opening paren */( - /* before nested column */ item /* before nested type */ text /* before nested closing paren */ - ) /* before columns closing paren */ - ) - /* before plan */ plan /* before plan opening paren */( - /* before plan name */ items /* before plan closing paren */ - ) - /* before on error */ error /* before on */ on /* before error */ error /* before closing paren */ - ) /* before alias */ as /* before alias name */ jt; + /* before document */ doc + /* before format */ format /* before json */ json /* before comma */, + /* before path */ '$[*]' /* before path as */ as /* before path name */ root + /* before passing */ passing /* before argument */ x /* before argument as */ as /* before variable */ foo + /* before columns */ columns /* before columns opening paren */( + /* before ordinality column */ ord /* before for */ for /* before ordinality */ ordinality /* before column comma */, + /* before value column */ value /* before type */ text + /* before path keyword */ path /* before column path */ '$.value' /* before second column comma */, + /* before exists column */ has_value /* before exists type */ bool + /* before exists */ exists + /* before exists path */ path /* before exists path expression */ '$.value' /* before nested comma */, + /* before nested */ nested /* before nested path */ path /* before nested expression */ '$.items[*]' /* before nested as */ as /* before nested name */ items + /* before nested columns */ columns /* before nested opening paren */( + /* before nested column */ item /* before nested type */ text /* before nested closing paren */ + ) /* before columns closing paren */ + ) + /* before plan */ plan /* before plan opening paren */( + /* before plan name */ items /* before plan closing paren */ + ) + /* before on error */ error /* before on */ on /* before error */ error /* before closing paren */ +) /* before alias */ as /* before alias name */ jt; select * /* before from */ from /* before item */ only /* before relation */ public /* before dot */./* before table */ foo /* before star */ * /* before alias */ as /* before alias name */ f /* before item comma */, /* before second item */ other /* before second alias */ o; @@ -212,6 +214,19 @@ select * from users join profiles on users.id = profiles.user_id; select * from users left outer join profiles using (user_id) as matched_users; +select relname, pg_get_indexdef +from pg_class left join pg_inherits on inhrelid = oid, + lateral pg_get_indexdef(pg_class.oid); + +select * +from /* before joined relation */ pg_class + /* before join */ left /* before outer */ outer /* before join keyword */ join /* before joined table */ pg_inherits /* before on */ on + /* before left operand */ inhrelid /* before equals */ + = /* before right operand */ oid /* before comma */, + /* before lateral item */ lateral /* before function */ pg_get_indexdef /* before opening paren */( + /* before argument */ pg_class.oid /* before closing paren */ + ); + select * from a right join b on true, c full outer join d on true, @@ -243,3 +258,25 @@ from a_very_long_schema_name.a_very_long_table_name left outer join another_very_long_schema_name.another_very_long_table_name on a_very_long_schema_name.a_very_long_table_name.a_very_long_column_name = another_very_long_schema_name.another_very_long_table_name.another_very_long_column_name; + +select + s.stxrelid::regclass as relation, + array_agg(a.attname order by u.ord) as stats_columns +from pg_statistic_ext s + cross join lateral unnest(s.stxkeys::int2[]) with ordinality as u ( + attnum, + ord + ) + join pg_attribute a on a.attrelid = s.stxrelid and a.attnum = u.attnum; + +select * +from statistics /* before base alias */ s + /* before cross */ cross /* before cross join */ join /* before lateral */ lateral /* before function */ unnest /* before function opening paren */( + /* before argument */ s.keys /* before cast */::int2[] /* before function closing paren */ + ) /* before with */ with /* before ordinality */ ordinality /* before function alias */ as /* before function alias name */ u /* before alias opening paren */ ( + /* before first alias column */ attnum /* before alias comma */, + /* before second alias column */ ord /* before alias closing paren */ + ) + /* before nested join */ join /* before nested relation */ attributes /* before nested alias */ a /* before on */ on + /* before left operand */ a.attnum /* before equals */ + = /* before right operand */ u.attnum; diff --git a/crates/squawk_fmt/tests/after/merge.snap b/crates/squawk_fmt/tests/after/merge.snap index 152a83d82..50b988d74 100644 --- a/crates/squawk_fmt/tests/after/merge.snap +++ b/crates/squawk_fmt/tests/after/merge.snap @@ -43,6 +43,19 @@ when not matched by target then ) returning an_extremely_long_target_alias.organization_identifier; +merge into target t +using source1 s1 inner join source2 s2 on s1.id = s2.id on t.id = s1.id +when not matched then + insert values (s1.id, s2.value); + +merge into target t +using /* before left source */ source1 /* before left alias */ s1 +/* before inner */ inner /* before join */ join /* before right source */ source2 /* before right alias */ s2 /* before join on */ on + /* before join condition */ s1.id = s2.id +/* before merge on */ on /* before merge condition */ t.id = s1.id +when matched then + delete; + /* before merge */ merge /* before into */ into /* before target */ only /* before target open */(/* before schema */ public /* before dot */./* before table */ target /* before target close */) /* before as */ as /* before target alias */ t /* before using */ using /* before source */ source /* before source as */ as /* before source alias */ s diff --git a/crates/squawk_fmt/tests/after/paren_select.snap b/crates/squawk_fmt/tests/after/paren_select.snap index ff168d838..528ea8c4a 100644 --- a/crates/squawk_fmt/tests/after/paren_select.snap +++ b/crates/squawk_fmt/tests/after/paren_select.snap @@ -32,3 +32,18 @@ with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* (/* before select */ select 1 /* before close */) /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties; + +select * from (((select 1) union select 2) limit 1) as t; + +select * +from ( + /* after outer open */ ( + (/* before left select */ select 1 /* before left close */) + /* before union */ union + /* before right select */ select 2 /* before compound close */ + ) + /* before inner limit */ limit /* before limit value */ 1 /* before outer close */ + ) /* before alias */ as t; + +((select 1) limit 2) order by 3; +(select 1) offset 1 limit 2; diff --git a/crates/squawk_fmt/tests/after/select_clauses.snap b/crates/squawk_fmt/tests/after/select_clauses.snap index 89c1e7045..c276a9dc8 100644 --- a/crates/squawk_fmt/tests/after/select_clauses.snap +++ b/crates/squawk_fmt/tests/after/select_clauses.snap @@ -57,3 +57,26 @@ with /* before recursive */ recursive /* before cte */ cte/* before columns */ ( select x /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties; + +select ''::text as five, unique1, unique2, stringu1 +from onek +order by unique1 +/* before offset */ offset 990 +/* before limit */ limit 5; + +select thousand +from onek +where thousand < 5 +order by thousand +/* before fetch */ fetch first 1 row with ties +/* before locking */ for update skip locked; + +select thousand +from onek +where thousand < 995 +order by thousand +/* before offset */ offset 10 +/* before fetch */ fetch first 5 rows only; + +select x from foo offset 1 limit 2; +select x from foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/after/select_expr.snap b/crates/squawk_fmt/tests/after/select_expr.snap index 281a4c3f3..974431d1d 100644 --- a/crates/squawk_fmt/tests/after/select_expr.snap +++ b/crates/squawk_fmt/tests/after/select_expr.snap @@ -6,6 +6,20 @@ select -- array expr array[1, 2], array(select 1), + array(select '1 4'::int2vector union all select '1 2'::int2vector), + /* before array query */ array /* before opening paren */( + /* before left select */ select /* before left target */ 1 + /* before union */ union /* before all */ all + /* before right select */ select + /* before right target */ 2 /* before closing paren */ + ) /* after array query */, + array( + select a_very_long_first_array_select_expression + from a_very_long_first_array_select_relation + union all + select a_very_long_second_array_select_expression + from a_very_long_second_array_select_relation + ), array[[1, 2], [3, 4]], /* before array */ array /* before opening bracket */[ /* before first */ 1 /* before comma */, @@ -34,6 +48,23 @@ select [a_very_long_first, a_very_long_second], [a_very_long_third, a_very_long_fourth] ], + array(((select 1))), + array( + ( + ( + select a_very_long_array_select_expression + from a_very_long_array_select_relation_name + ) + ) + ), + /* before nested array */ array /* before outer opening paren */( + /* after outer opening paren */ ( + /* after middle opening paren */ ( + /* before select */ select + /* before target */ 1 /* before inner closing paren */ + ) /* before middle closing paren */ + ) /* before outer closing paren */ + ), -- between expr 2 between 1 and 3, 2 not between 1 and 3, @@ -250,6 +281,16 @@ select foo(/* before distinct */ distinct /* after distinct */ 1), foo(variadic xs), foo(/* before variadic */ variadic /* after variadic */ xs), + foo(variadic arr => array[2, 3]), + foo( + /* before variadic */ variadic /* before name */ arr /* before arrow */ => /* before value */ array[ + 2, + 3 + ] + ), + a_very_long_function_name( + variadic a_very_long_named_argument => a_very_long_variadic_argument_expression_that_forces_wrapping + ), foo(a => 1, b := 2), foo( /* before name */ a /* before arrow */ => /* before value */ 1 /* before comma */, @@ -305,6 +346,18 @@ select /* before returning */ returning /* before type */ jsonb /* before returning format */ format /* before returning json */ json /* before closing paren */ ), + json_arrayagg(v) filter (where v > 0), + json_objectagg(k: v) filter (where v is not null) over ( + partition by category order by k + ), + json_arrayagg(v) /* before filter */ filter /* before filter opening */ ( + /* before where */ where /* before condition */ v + > 0 /* before filter closing */ + ) /* before over */ over /* before over opening */ ( + /* before partition */ partition /* before by */ by /* before partition expr */ category + /* before order */ order /* before order by */ by + /* before sort */ v /* before over closing */ + ), json_array(), json_array(1, 2 format json returning jsonb), json_array( @@ -632,6 +685,17 @@ select ((1)), (/* before expr */ 1 /* before closing paren */), (select 1), + ((select 2) union select 2), + (((select 2)) union select 2), + /* before compound opening paren */ ( + /* after compound opening paren */ ( + /* before inner select */ select + /* before inner target */ 2 /* before inner closing paren */ + ) + /* before union */ union + /* before right select */ select + /* before right target */ 2 /* before compound closing paren */ + ), /* before opening paren */ ( /* before select */ select /* before target */ x /* before from */ from /* before relation */ things /* before closing paren */ @@ -687,6 +751,8 @@ select x is not normalized, x is not nfkd normalized, x is /* before not */ not /* before json */ json /* before array */ array /* before with */ with /* before unique */ unique /* before keys */ keys, + null::json is json, + null::json /* before is */ is /* before json */ json /* before with */ with /* before unique */ unique /* before keys */ keys, x is /* before normalized not */ not /* before form */ nfkd /* before normalized */ normalized, a_very_long_json_postfix_input_expression_that_forces_wrapping is json array with unique keys, a_very_long_normalized_postfix_input_expression_that_forces_wrapping is not nfkd normalized, diff --git a/crates/squawk_fmt/tests/after/select_into.snap b/crates/squawk_fmt/tests/after/select_into.snap index c85cb2283..9528d9359 100644 --- a/crates/squawk_fmt/tests/after/select_into.snap +++ b/crates/squawk_fmt/tests/after/select_into.snap @@ -69,3 +69,6 @@ from source /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ source /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows; + +select 1 into foo offset 1 limit 2; +select 1 into foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/after/set.snap b/crates/squawk_fmt/tests/after/set.snap index cdea8b83d..151ac044f 100644 --- a/crates/squawk_fmt/tests/after/set.snap +++ b/crates/squawk_fmt/tests/after/set.snap @@ -12,7 +12,14 @@ set foo from current; set foo = default; -set foo to null; +set foo to off; + +set search_path = myschema, public; + +set + /* before list parameter */ search_path + /* before list equals */ = /* before first value */ myschema /* before list comma */, + /* before second value */ public/* before list semicolon */; set foo to a, 10.0, 1, 'foo', true, false; @@ -30,6 +37,22 @@ set time zone default; set time zone local; +set local time zone -8; + +set time zone interval '-08:00' hour to minute; + +set time zone interval(2) '-08:00'; + +set extra_float_digits = -1; + +set + /* before negative config parameter */ extra_float_digits + /* before negative config equals */ = /* before config minus */ - /* before config number */ 1/* before negative config semicolon */; + +set + /* before negative scope */ local + /* before negative time */ time /* before negative zone */ zone /* before minus */ - /* before timezone number */ 8/* before negative timezone semicolon */; + set an_intentionally_long_config_namespace.an_intentionally_long_config_group.an_intentionally_long_parameter_name to an_intentionally_long_value_name, another_intentionally_long_value_name; diff --git a/crates/squawk_fmt/tests/after/table.snap b/crates/squawk_fmt/tests/after/table.snap index d1ea402d7..06b276dce 100644 --- a/crates/squawk_fmt/tests/after/table.snap +++ b/crates/squawk_fmt/tests/after/table.snap @@ -49,3 +49,6 @@ with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* table /* before relation */ foo /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties/* before semicolon */; + +table foo offset 1 limit 2; +table foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/after/table_constraints.snap b/crates/squawk_fmt/tests/after/table_constraints.snap index 248c1d39b..cf8652b37 100644 --- a/crates/squawk_fmt/tests/after/table_constraints.snap +++ b/crates/squawk_fmt/tests/after/table_constraints.snap @@ -35,10 +35,11 @@ create table named_constraints ( a_very_long_check_left_hand_expression_name > a_very_long_check_right_hand_expression_name ), - constraint parent_fk foreign key ( - first_very_long_foreign_key_column_name, - second_very_long_foreign_key_column_name - ) + constraint parent_fk + foreign key ( + first_very_long_foreign_key_column_name, + second_very_long_foreign_key_column_name + ) references public.parents ( first_very_long_referenced_column_name, second_very_long_referenced_column_name @@ -70,6 +71,45 @@ create table named_constraints ( deferrable ); +create table inherited_not_null_constraints ( + inherited_column bigint, + not null inherited_column no inherit, + constraint inherited_not_null not null inherited_column +); + +create table commented_inherited_not_null_constraint ( + inherited_column bigint, + /* constraint */ constraint /* name */ inherited_not_null /* not */ not /* null */ null /* column */ inherited_column + /* no */ no /* inherit */ inherit +); + +create table temporal_constraints ( + id bigint, + parent_id bigint, + valid_at daterange, + primary key (id, valid_at without overlaps), + foreign key (parent_id, period valid_at) + references temporal_parents (id, period valid_at) +); + +create table commented_temporal_constraints ( + id bigint, + parent_id bigint, + valid_at daterange, + primary /* before primary key */ key /* before primary opening paren */ ( + id /* before primary comma */, + /* before valid column */ valid_at /* before without */ without /* before overlaps */ overlaps /* before primary closing paren */ + ), + foreign /* before foreign key */ key /* before from opening paren */ ( + parent_id /* before period comma */, + /* before from period */ period /* before from period column */ valid_at /* before from closing paren */ + ) + /* before references */ references /* before referenced table */ temporal_parents /* before to opening paren */ ( + id /* before referenced period comma */, + /* before referenced period */ period /* before referenced period column */ valid_at /* before to closing paren */ + ) +); + create table using_indexes ( id bigint, unique using index existing_unique, @@ -95,9 +135,10 @@ create table commented_constraints ( /* before check expression */ id > 0 /* before check closing paren */ ) /* before not */ not /* before valid */ valid, - constraint named_fk /* before foreign */ foreign /* before key */ key /* before from opening paren */ ( - /* before from column */ parent_id /* before from closing paren */ - ) + constraint named_fk + /* before foreign */ foreign /* before key */ key /* before from opening paren */ ( + /* before from column */ parent_id /* before from closing paren */ + ) /* before references */ references /* before table */ public /* before dot */./* before table name */ parents /* before to opening paren */ ( /* before to column */ id /* before to closing paren */ ) @@ -125,3 +166,15 @@ create table commented_constraints ( ) /* before initially */ initially /* before immediate */ immediate ); + +create table source_ordered_referential_actions ( + id bigint, + parent_id bigint + references parents + /* before update on */ on /* before update */ update /* before update action */ cascade + /* before delete on */ on /* before delete */ delete /* before delete action */ restrict, + foreign key (parent_id) + references parents (id) + /* before update on */ on /* before update */ update /* before update action */ set /* before update null */ null + /* before delete on */ on /* before delete */ delete /* before delete action */ no /* before action */ action +); diff --git a/crates/squawk_fmt/tests/after/transaction_control.snap b/crates/squawk_fmt/tests/after/transaction_control.snap index 3da4ede67..a1007bde4 100644 --- a/crates/squawk_fmt/tests/after/transaction_control.snap +++ b/crates/squawk_fmt/tests/after/transaction_control.snap @@ -10,6 +10,27 @@ start transaction isolation level serializable, read write, deferrable; begin transaction isolation level repeatable read, read only, not deferrable; +begin + isolation level read committed + read only + read write + deferrable + not deferrable; + +start transaction + isolation level read committed + read only + read write + deferrable + not deferrable; + +begin + /* before first mode */ isolation /* before level */ level /* before read */ read /* before committed */ committed + /* before read only */ read /* before only */ only + /* before read write */ read /* before write */ write + /* before deferrable */ deferrable + /* before not */ not /* before final deferrable */ deferrable/* before semicolon */; + commit; end work; diff --git a/crates/squawk_fmt/tests/after/values.snap b/crates/squawk_fmt/tests/after/values.snap index e310f3f61..0f862efed 100644 --- a/crates/squawk_fmt/tests/after/values.snap +++ b/crates/squawk_fmt/tests/after/values.snap @@ -69,3 +69,6 @@ with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* values /* before row */ (/* before expression */ 1 /* before row close */) /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties/* before semicolon */; + +values (1) offset 1 limit 2; +values (1) limit 2 for update; diff --git a/crates/squawk_fmt/tests/before/alter_default_privileges.sql b/crates/squawk_fmt/tests/before/alter_default_privileges.sql index 69ea28531..2c07d81d2 100644 --- a/crates/squawk_fmt/tests/before/alter_default_privileges.sql +++ b/crates/squawk_fmt/tests/before/alter_default_privileges.sql @@ -1,5 +1,9 @@ alter default privileges grant select on tables to reporting; +alter default privileges grant update on tables to public, current_user, group /* session role */ session_user, group /* current role */ current_role with grant option; + +alter default privileges revoke grant option for select on tables from reporting, group /* current user */ current_user cascade; + alter default privileges for role app_owner, migrations in schema public, audit grant all privileges on sequences to app_user, reporting with grant option; alter default privileges for user app_owner revoke grant option for insert, update on tables from app_user cascade; diff --git a/crates/squawk_fmt/tests/before/alter_foreign_table.sql b/crates/squawk_fmt/tests/before/alter_foreign_table.sql index fe74e12c4..ca7e80efd 100644 --- a/crates/squawk_fmt/tests/before/alter_foreign_table.sql +++ b/crates/squawk_fmt/tests/before/alter_foreign_table.sql @@ -5,3 +5,5 @@ ALTER FOREIGN TABLE public.items ALTER COLUMN quantity SET DEFAULT 100, ALTER CO ALTER FOREIGN TABLE public.items ADD CONSTRAINT positive_quantity CHECK (quantity > 0), RENAME COLUMN old_name TO new_name; ALTER FOREIGN TABLE IF EXISTS public.items OWNER TO role_with_a_very_long_descriptive_name; + +ALTER FOREIGN TABLE ft1 /* options */ OPTIONS /* open */ (/* drop */ DROP /* name */ delimiter /* comma */, /* set */ SET /* name */ quote /* value */ '~' /* comma */, /* add */ ADD /* name */ escape /* value */ '@' /* close */); diff --git a/crates/squawk_fmt/tests/before/alter_property_graph.sql b/crates/squawk_fmt/tests/before/alter_property_graph.sql index c8cd8c17f..d22b60348 100644 --- a/crates/squawk_fmt/tests/before/alter_property_graph.sql +++ b/crates/squawk_fmt/tests/before/alter_property_graph.sql @@ -2,6 +2,8 @@ alter /* property */ property /* graph */ graph /* if */ if /* exists */ exists alter property graph graph_with_an_exceptionally_long_descriptive_name add vertex tables (customer_accounts key (customer_identifier) no properties, merchant_accounts key (merchant_identifier) properties all columns); +alter property graph social_graph /* first add */ add /* vertex tables */ vertex tables (people key (id)) /* second add */ add /* edge tables */ edge tables (follows /* source */ source /* source key */ key (follower_id) references people (id) /* destination */ destination /* destination key */ key (followed_id) references people (id)); + alter property graph social_graph alter /* kind */ vertex /* table */ table /* element */ people alter /* label */ label /* name */ person add /* properties */ properties (full_name, birth_date as date_of_birth); alter property graph social_graph alter relationship table follows alter label connection drop properties (created_at, source_system); diff --git a/crates/squawk_fmt/tests/before/alter_publication.sql b/crates/squawk_fmt/tests/before/alter_publication.sql index 8634ab16a..38516b75b 100644 --- a/crates/squawk_fmt/tests/before/alter_publication.sql +++ b/crates/squawk_fmt/tests/before/alter_publication.sql @@ -2,6 +2,10 @@ alter publication selected_tables add table public.new_accounts, tables in schem alter publication selected_tables drop table public.old_accounts; +alter publication selected_tables add table public.first_table, /* before omitted table */ public.second_table; + +alter publication selected_tables set tables in schema first_schema, /* before omitted schema */ second_schema; + alter publication selected_tables set table public.accounts (id) where (id > 200); alter publication everything set all tables, all sequences except (table private.tokens, audit.logs); diff --git a/crates/squawk_fmt/tests/before/alter_sequence.sql b/crates/squawk_fmt/tests/before/alter_sequence.sql index 7d054b060..dfba68df2 100644 --- a/crates/squawk_fmt/tests/before/alter_sequence.sql +++ b/crates/squawk_fmt/tests/before/alter_sequence.sql @@ -1,5 +1,9 @@ alter sequence public.invoice_number increment by 10 minvalue 1 maxvalue 999999999 start with 100 cache 50 cycle owned by public.invoices.invoice_number; +alter sequence public.descending_sequence restart with -24 start with -24 increment by -4 minvalue -36 maxvalue -5 cycle; + +alter sequence public.commented_descending_sequence restart with /* before restart sign */ - /* before restart value */ 24 start with /* before start sign */ - /* before start value */ 24 increment by /* before increment sign */ - /* before increment value */ 4 minvalue /* before minimum sign */ - /* before minimum value */ 36 maxvalue /* before maximum sign */ - /* before maximum value */ 5; + alter sequence if exists public.invoice_number as bigint no minvalue no maxvalue restart with 500 no cycle owned by none; alter sequence public.invoice_number set logged; diff --git a/crates/squawk_fmt/tests/before/alter_subscription.sql b/crates/squawk_fmt/tests/before/alter_subscription.sql index 78f5bdec2..08218db2a 100644 --- a/crates/squawk_fmt/tests/before/alter_subscription.sql +++ b/crates/squawk_fmt/tests/before/alter_subscription.sql @@ -4,6 +4,10 @@ alter subscription local_sub server publisher_server; alter subscription local_sub set (slot_name = new_slot, synchronous_commit = local); +alter subscription regress_testsub4 set (origin = any, max_retention_duration = -1); + +alter subscription regress_testsub4 set (/* before origin */ origin /* before equals */ = /* before any */ any /* after any */, /* before retention */ max_retention_duration = /* before minus */ - /* before one */ 1 /* after one */); + alter subscription local_sub add publication another_publication, third_publication with (copy_data = false); alter subscription local_sub set publication all_changes with (refresh = true); diff --git a/crates/squawk_fmt/tests/before/alter_table.sql b/crates/squawk_fmt/tests/before/alter_table.sql index 33d7a1a14..05901adeb 100644 --- a/crates/squawk_fmt/tests/before/alter_table.sql +++ b/crates/squawk_fmt/tests/before/alter_table.sql @@ -4,6 +4,14 @@ ALTER TABLE IF EXISTS ONLY (reporting.customer_activity) RENAME COLUMN old_activ ALTER TABLE reporting.customer_activity ADD CONSTRAINT positive_activity_count CHECK (activity_count > 0), ALTER CONSTRAINT positive_activity_count DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE tx1 RENAME TO a1; + +ALTER TABLE temp_view_test.tt1 RENAME TO tmp1; + +ALTER /* table */ TABLE /* old table */ temp_view_test.tt1 /* rename */ RENAME /* to */ TO /* new table */ tmp1 /* semicolon */; + +ALTER TABLE temporal_children ADD CONSTRAINT temporal_children_parent_fk FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES temporal_parents (id, PERIOD valid_at); + ALTER TABLE reporting.customer_activity ATTACH PARTITION reporting.customer_activity_2025 FOR VALUES FROM ('2025-01-01') TO ('2026-01-01'); ALTER TABLE reporting.customer_activity DETACH PARTITION reporting.customer_activity_2024 CONCURRENTLY; @@ -17,3 +25,47 @@ ALTER TABLE ALL IN TABLESPACE old_reporting_tablespace OWNED BY analytics_owner, ALTER /* table */ TABLE /* if */ IF /* exists */ EXISTS /* only */ ONLY /* left paren */ (/* relation */ reporting.commented_customer_activity /* right paren */) /* action */ ALTER /* column */ COLUMN /* column name */ activity_description /* set */ SET /* default */ DEFAULT /* expression */ 'A very long default activity description used to verify formatter wrapping behavior', /* comma */ RENAME /* column */ COLUMN /* old name */ old_activity_code /* to */ TO /* new name */ current_activity_code /* semicolon */; ALTER /* table */ TABLE /* all */ ALL /* in */ IN /* tablespace keyword */ TABLESPACE /* old tablespace */ old_tablespace /* owned */ OWNED /* by */ BY /* first role */ role_one, /* second role */ role_two /* set */ SET /* tablespace */ TABLESPACE /* new tablespace */ new_tablespace /* nowait */ NOWAIT /* semicolon */; + +ALTER TABLE ONLY parent_table ALTER CONSTRAINT inherited_check_constraint ENFORCED; + +ALTER TABLE parent_table ALTER CONSTRAINT inherited_check_constraint NOT ENFORCED; + +ALTER TABLE parent_table ALTER CONSTRAINT inherited_not_null_constraint INHERIT; + +ALTER TABLE parent_table ALTER CONSTRAINT inherited_not_null_constraint NO INHERIT; + +ALTER /* table */ TABLE /* relation */ parent_table /* alter */ ALTER /* constraint */ CONSTRAINT /* constraint name */ inherited_check_constraint /* enforced */ ENFORCED /* semicolon */; + +ALTER /* table */ TABLE /* relation */ parent_table /* alter */ ALTER /* constraint */ CONSTRAINT /* constraint name */ inherited_check_constraint /* not */ NOT /* enforced */ ENFORCED /* semicolon */; + +ALTER /* table */ TABLE /* relation */ parent_table /* alter */ ALTER /* constraint */ CONSTRAINT /* constraint name */ inherited_not_null_constraint /* inherit */ INHERIT /* semicolon */; + +ALTER /* table */ TABLE /* relation */ parent_table /* alter */ ALTER /* constraint */ CONSTRAINT /* constraint name */ inherited_not_null_constraint /* no */ NO /* inherit */ INHERIT /* semicolon */; + +ALTER TABLE child_table ADD NOT NULL a_very_long_inherited_column_name NO INHERIT; + +ALTER /* table */ TABLE /* relation */ child_table /* add */ ADD /* not */ NOT /* null */ NULL /* column */ inherited_column /* no */ NO /* inherit */ INHERIT /* semicolon */; + +ALTER TABLE parent_table ALTER CONSTRAINT source_ordered_constraint /* enforced */ ENFORCED /* not */ NOT /* deferrable */ DEFERRABLE; + +ALTER TABLE parent_table ALTER CONSTRAINT invalid_constraint /* not */ NOT /* valid */ VALID; + +ALTER TABLE comment_test ALTER COLUMN id SET DATA TYPE int COLLATE "C"; + +ALTER TABLE extraordinarily_long_partition_name ALTER extraordinarily_long_column_name TYPE char (2) COLLATE "POSIX"; + +ALTER /* table */ TABLE /* relation */ comment_test /* alter */ ALTER /* column */ COLUMN /* column name */ description /* set */ SET /* data */ DATA /* type keyword */ TYPE /* type */ character varying(100) /* collate */ COLLATE /* collation */ "POSIX" /* using */ USING /* expression */ description::character varying /* semicolon */; + +ALTER TABLE sales_range MERGE PARTITIONS (sales_jan2022, sales_feb2022) INTO sales_jan_feb2022; + +ALTER TABLE partitions_merge_schema.extraordinarily_long_sales_range MERGE PARTITIONS (partitions_merge_schema.extraordinarily_long_sales_january_2022, partitions_merge_schema.extraordinarily_long_sales_february_2022) INTO partitions_merge_schema.extraordinarily_long_sales_january_february_2022; + +ALTER /* table */ TABLE /* relation */ partitions_merge_schema.sales_range /* merge */ MERGE /* partitions */ PARTITIONS /* left paren */ (/* first partition */ partitions_merge_schema.sales_jan2022 /* first comma */, /* second partition */ sales_feb2022, /* third partition */ sales_mar2022 /* right paren */) /* into */ INTO /* target partition */ partitions_merge_schema./* target name */sales_jan_feb_mar2022 /* semicolon */; + +ALTER TABLE sales_range SPLIT PARTITION sales_feb_mar_apr2022 INTO (PARTITION sales_feb2022 FOR VALUES FROM ('2022-02-01') TO ('2022-03-01'), PARTITION sales_mar2022 FOR VALUES IN ('2022-03-01', '2022-04-01'), PARTITION sales_hash FOR VALUES WITH (MODULUS 4, REMAINDER 1), PARTITION sales_others DEFAULT); + +ALTER /* table */ TABLE /* relation */ sales_range /* split */ SPLIT /* partition */ PARTITION /* source */ partition_split_schema./* source name */sales_all /* into */ INTO /* list left */ (/* first partition */ PARTITION /* first name */ partition_split_schema./* name */sales_first /* for */ FOR /* values */ VALUES /* from */ FROM /* lower left */ (/* lower */ '2022-01-01' /* lower right */) /* to */ TO /* upper left */ (/* upper */ '2022-02-01' /* upper right */) /* comma */, /* second partition */ PARTITION /* second name */ sales_second /* for */ FOR /* values */ VALUES /* in */ IN /* in left */ (/* first value */ 'one' /* value comma */, /* second value */ 'two' /* in right */), /* default partition */ PARTITION /* default name */ sales_other /* default */ DEFAULT /* list right */) /* semicolon */; + +ALTER TABLE options_test ALTER COLUMN metadata SET (json = constraint); + +ALTER /* table */ TABLE /* relation */ options_test /* alter */ ALTER /* column */ COLUMN /* name */ metadata /* set */ SET /* left paren */ (/* option */ json /* equals */ = /* value */ constraint /* right paren */) /* semicolon */; diff --git a/crates/squawk_fmt/tests/before/compound_select.sql b/crates/squawk_fmt/tests/before/compound_select.sql index ddee7df7f..f37cabedf 100644 --- a/crates/squawk_fmt/tests/before/compound_select.sql +++ b/crates/squawk_fmt/tests/before/compound_select.sql @@ -22,3 +22,6 @@ select 1 /* before operator */ union /* before quantifier */ distinct /* before select 1 -- before operator union all -- before right select select 2; + +select 1 union select 2 offset 1 limit 2; +select 1 union select 2 limit 1 for update; diff --git a/crates/squawk_fmt/tests/before/create_aggregate.sql b/crates/squawk_fmt/tests/before/create_aggregate.sql index be41a3f95..ffb0157e7 100644 --- a/crates/squawk_fmt/tests/before/create_aggregate.sql +++ b/crates/squawk_fmt/tests/before/create_aggregate.sql @@ -11,3 +11,11 @@ create /* or replace */ or /* replace keyword */ replace /* aggregate keyword */ create aggregate public.ordered_set_aggregate(/* direct parameter */ double precision /* order keyword */ order /* by keyword */ by /* first ordered parameter */ anyelement, /* second ordered parameter */ text /* closing parenthesis */) (sfunc = public.ordered_set_state, stype = internal); create aggregate extraordinarily_long_schema_name.extraordinarily_long_ordered_set_aggregate_name(extraordinarily_long_schema_name.extraordinarily_long_direct_argument_type order by extraordinarily_long_schema_name.extraordinarily_long_first_ordered_argument_type, extraordinarily_long_schema_name.extraordinarily_long_second_ordered_argument_type) (sfunc = extraordinarily_long_schema_name.extraordinarily_long_transition_function_name, stype = internal); + +create aggregate alt_agg2 (sfunc1 = int4mi, basetype = int4, stype1 = int4, initcond = -100); + +create aggregate commented_negative (sfunc = transition_function, stype = bigint, initcond /* before equals */ = /* before minus */ - /* after minus */ 100 /* after value */); + +create aggregate public.old_style_sort_operator (sfunc = public.sort_state, basetype = integer, stype = bigint, sortop = <); + +create aggregate public.commented_sort_operator(integer) (sfunc = public.sort_state, stype = bigint, /* sort operator */ sortop /* equals */ = /* less-than operator */ < /* closing parenthesis */); diff --git a/crates/squawk_fmt/tests/before/create_function.sql b/crates/squawk_fmt/tests/before/create_function.sql index e8fb6bc2b..65d52b754 100644 --- a/crates/squawk_fmt/tests/before/create_function.sql +++ b/crates/squawk_fmt/tests/before/create_function.sql @@ -6,7 +6,9 @@ create function get_users(p_active boolean) returns table (id bigint, name text) create function a_function_with_a_very_long_name(a_parameter_with_a_very_long_name numeric, another_parameter_with_a_very_long_name text default 'a long default value') returns table (a_column_with_a_very_long_name numeric, another_column_with_a_very_long_name text) language sql as $$ select $1, $2 $$; -create function option_examples(in first integer, out second text, inout third bigint, variadic rest text[]) returns text external security invoker called on null input returns null on null input not leakproof stable window support public.support_fn transform for type integer, for type text set work_mem from current reset all language 'sql' as $$ select null::text $$; +create function option_examples(in first integer, out second text, inout third bigint, variadic rest text[]) returns text external security invoker called on null input not leakproof stable window support public.support_fn transform for type integer, for type text set work_mem from current reset all language 'sql' as $$ select null::text $$; + +create function returns_null_on_null_input_example() returns text returns null on null input language sql as $$ select null::text $$; -- comments in every position create /*a*/ or /*b*/ replace /*c*/ function /*d*/ app /*e*/. /*f*/ commented @@ -32,6 +34,10 @@ create function commented_body(value integer) returns integer language sql /*bb* create function external_add(integer, integer) returns integer as '$libdir/example', 'external_add' language c; +create function test_enc_conversion(bytea, name, name, bool, validlen OUT int, result OUT bytea) as 'regresslib', 'test_enc_conversion' language C strict; + +create function mode_after_name(input /*bp1*/ in /*bp2*/ bytea, result /*bp3*/ out /*bp4*/ bytea, error /*bp5*/ out /*bp6*/ text) returns record language sql as $$ select null $$; + create function commented_external() returns integer as /*bo*/ '$libdir/example' /*bp*/, /*bq*/ 'commented_external' /*br*/ language c; create function function_with_a_very_long_external_definition() returns integer as '$libdir/a_very_long_object_file_name_that_does_not_fit_on_the_same_line', 'a_very_long_link_symbol_name_that_does_not_fit_on_the_same_line' language c; diff --git a/crates/squawk_fmt/tests/before/create_index.sql b/crates/squawk_fmt/tests/before/create_index.sql index 969d5ad96..36769db9c 100644 --- a/crates/squawk_fmt/tests/before/create_index.sql +++ b/crates/squawk_fmt/tests/before/create_index.sql @@ -17,3 +17,11 @@ create /*a*/ unique /*b*/ index /*c*/ concurrently /*d*/ if /*e*/ not /*f*/ exis /*aj*/ with /*ak*/ (/*al*/ fillfactor /*am*/ = /*an*/ 70 /*ao*/, /*ap*/ deduplicate_items /*aq*/ = /*ar*/ on /*as*/) /*at*/ tablespace /*au*/ fastspace /*av*/ where /*aw*/ active /*ax*/ and email is not null /*ay*/; + +create index on t (a text_pattern_ops (strength = primary, variant = default), b text_pattern_ops (strength = primary)); + +-- operator class parameters with comments +create index on t ( + a /*ba*/ text_pattern_ops /*bb*/ (/*bc*/ strength /*bd*/ = /*be*/ primary /*bf*/, /*bg*/ variant /*bh*/ = /*bi*/ default /*bj*/), + /*bk*/ b /*bl*/ text_pattern_ops /*bm*/ (/*bn*/ strength /*bo*/ = /*bp*/ primary /*bq*/) +); diff --git a/crates/squawk_fmt/tests/before/create_operator.sql b/crates/squawk_fmt/tests/before/create_operator.sql index bcb9480a0..452c8f9c0 100644 --- a/crates/squawk_fmt/tests/before/create_operator.sql +++ b/crates/squawk_fmt/tests/before/create_operator.sql @@ -1,6 +1,24 @@ create operator > (function = greater_than); +create operator === (leftarg = integer, rightarg = integer, procedure = int4eq, commutator = =); + +create operator === (leftarg = integer, rightarg = integer, procedure = int4eq, negator = <>); + +create operator = (commutator /* assignment */ = /* equal operator */ = /* comma */, negator = /* not equal operator */ <> /* closing parenthesis */); + +create operator = (join = eqjoinsel); + +create operator <> (/* join option */ join /* equals */ = /* selectivity function */ eqjoinsel /* closing parenthesis */); + create operator extraordinarily_long_schema_name.## (function = extraordinarily_long_schema_name.extraordinarily_long_operator_function_name, leftarg = extraordinarily_long_schema_name.extraordinarily_long_left_argument_type, rightarg = extraordinarily_long_schema_name.extraordinarily_long_right_argument_type, commutator = &&&&, hashes, merges); -- comments in every position create /* operator */ operator /* schema */ app /* dot */ . /* symbol */ # /* attributes */ (/* function name */ function /* equals */ = /* function value */ app.compare_values /* comma */, /* left argument */ leftarg /* second equals */ = /* type */ integer /* comma */, /* marker */ hashes /* closing parenthesis */) /* semicolon */; + +create operator + (commutator = +, negator = -, restrict = *, join = %); + +create operator + (commutator = >, negator = >=, restrict = <=, join = !=); + +create operator + (commutator = operator(pg_catalog.+)); + +create operator + (commutator /* before qualified operator */ = /* before operator keyword */ operator /* before open paren */ (/* before operator */ pg_catalog.+ /* before close paren */) /* before semicolon */); diff --git a/crates/squawk_fmt/tests/before/create_property_graph.sql b/crates/squawk_fmt/tests/before/create_property_graph.sql index 15cea54da..951b8b2b2 100644 --- a/crates/squawk_fmt/tests/before/create_property_graph.sql +++ b/crates/squawk_fmt/tests/before/create_property_graph.sql @@ -1,5 +1,7 @@ create property graph social vertex tables (people key (id)) edge tables (follows source key (follower_id) references people (id) destination key (followed_id) references people (id)); +create property graph social vertex tables (people, organizations) edge tables (employment /* source */ source /* source table */ people /* destination */ destination /* destination table */ organizations); + create unlogged property graph extraordinarily_long_social_relationship_property_graph vertex tables (extraordinarily_long_people_table key (extraordinarily_long_person_identifier) label person properties (display_name as public_name, date_of_birth), extraordinarily_long_organizations_table key (extraordinarily_long_organization_identifier) no properties) edge tables (extraordinarily_long_employment_relationship_table source key (employee_identifier) references extraordinarily_long_people_table (extraordinarily_long_person_identifier) destination key (employer_identifier) references extraordinarily_long_organizations_table (extraordinarily_long_organization_identifier) properties all columns); -- comments in every position diff --git a/crates/squawk_fmt/tests/before/create_publication.sql b/crates/squawk_fmt/tests/before/create_publication.sql index f66a7c9b8..d29aea88f 100644 --- a/crates/squawk_fmt/tests/before/create_publication.sql +++ b/crates/squawk_fmt/tests/before/create_publication.sql @@ -4,6 +4,10 @@ create publication selected_tables for table only public.accounts (id, email) wh create publication no_tables with (publish = 'insert'); +create publication omitted_table_keywords for table first_table, /* before omitted table */ public.second_table where (id > 100), /* before omitted only */ only third_table; + +create publication omitted_schema_keywords for tables in schema first_schema, /* before omitted schema */ second_schema; + create /* after create */ publication /* after publication */ commented_pub for /* after for */ table /* after table */ only /* after only */ (/* before table name */ public.commented /* before close */) /* before star */ * /* before columns */ (/* before column */ id /* before comma */, /* after comma */ payload /* before columns close */) /* before where */ where /* before where open */ (/* before expression */ id > 0 /* before where close */), /* after object comma */ tables /* after tables */ in /* after in */ schema /* after schema */ current_schema with /* after with */ (/* before option */ publish /* before equals */ = /* before value */ 'insert' /* before options close */) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/create_schema.sql b/crates/squawk_fmt/tests/before/create_schema.sql index 57866c93d..fb68c20e0 100644 --- a/crates/squawk_fmt/tests/before/create_schema.sql +++ b/crates/squawk_fmt/tests/before/create_schema.sql @@ -8,3 +8,15 @@ create schema authorization current_user; create /* schema */ schema /* if */ if /* not */ not /* exists */ exists /* schema name */ reporting /* authorization */ authorization /* role */ reporting_owner /* end */; create /* schema */ schema /* authorization */ authorization /* role */ schema_owner /* end */; + +create schema authorization regress_create_schema_role + create function schema_not_existing.func(int) returns int + as 'SELECT $1' language sql; + +create schema extraordinarily_long_application_reporting_and_data_warehouse_schema authorization extraordinarily_long_application_reporting_and_data_warehouse_owner_role + create function extraordinarily_long_application_reporting_and_data_warehouse_schema.calculate_an_extraordinarily_long_result(an_extraordinarily_long_input_parameter integer) returns an_extraordinarily_long_result_type + language sql immutable as 'SELECT $1'; + +create /* schema keyword */ schema /* authorization keyword */ authorization /* role */ schema_owner + /* schema element */ create /* function keyword */ function /* function name */ app /* separator */ . /* local name */ calculate(/* parameter */ value integer) /* return type */ returns integer + /* language option */ language /* language */ sql /* as option */ as /* body */ 'SELECT $1' /* end */; diff --git a/crates/squawk_fmt/tests/before/create_sequence.sql b/crates/squawk_fmt/tests/before/create_sequence.sql index 95930d698..b8054a406 100644 --- a/crates/squawk_fmt/tests/before/create_sequence.sql +++ b/crates/squawk_fmt/tests/before/create_sequence.sql @@ -1,5 +1,9 @@ create sequence invoice_number_seq; +create sequence descending_seq increment by -1 minvalue -100000 maxvalue +20 start with -10 cache +5; + +create sequence commented_descending_seq increment by /* before sign */ - /* before value */ 1 minvalue /* before minimum sign */ - /* before minimum */ 100000 maxvalue /* before maximum sign */ + /* before maximum */ 20 start with /* before start sign */ - /* before start */ 10 cache /* before cache sign */ + /* before cache */ 5; + create temporary sequence if not exists extraordinarily_long_schema_name.extraordinarily_long_invoice_number_sequence as bigint increment by 5 minvalue 100 maxvalue 999999999 start with 100 cache 50 cycle owned by extraordinarily_long_schema_name.extraordinarily_long_table_name.extraordinarily_long_column_name; -- comments in every position diff --git a/crates/squawk_fmt/tests/before/create_subscription.sql b/crates/squawk_fmt/tests/before/create_subscription.sql index 0b399c0dc..34193fa93 100644 --- a/crates/squawk_fmt/tests/before/create_subscription.sql +++ b/crates/squawk_fmt/tests/before/create_subscription.sql @@ -2,4 +2,8 @@ create subscription local_sub connection 'host=localhost port=5432 dbname=publis create subscription server_sub server publisher_server publication all_changes; +create subscription regress_testsub connection 'dbname=regress_doesnotexist' publication testpub with (connect = false, max_retention_duration = -1); + +create subscription regress_testsub connection 'dbname=regress_doesnotexist' publication testpub with (connect = false, max_retention_duration /* before equals */ = /* before minus */ - /* before one */ 1 /* after one */); + create /* after create */ subscription /* before name */ commented_sub connection /* before connection */ 'host=localhost' publication /* before publication */ all_changes, /* after comma */ selected_tables with /* before params */ (enabled = true) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/create_table.sql b/crates/squawk_fmt/tests/before/create_table.sql index cbcb5922b..11e4402b5 100644 --- a/crates/squawk_fmt/tests/before/create_table.sql +++ b/crates/squawk_fmt/tests/before/create_table.sql @@ -56,3 +56,15 @@ create table column_option_comments ( /*z*/ constraint /*aa*/ payload_required /*ab*/ not /*ac*/ null /*ad*/ deferrable, /*ae*/ id /*af*/ bigint /*ag*/ generated /*ah*/ by /*ai*/ default /*aj*/ as /*ak*/ identity /*al*/ (/*am*/ increment /*an*/ by /*ao*/ 2 /*ap*/ start /*aq*/ with /*ar*/ 3 /*as*/ restart /*at*/ with /*au*/ 4 /*av*/ owned /*aw*/ by /*ax*/ none /*ay*/ sequence /*az*/ name /*ba*/ public.seq /*bb*/) ); + +-- preserve column clause source order when collate follows constraints +create table collate_after_primary_key (id VARCHAR PRIMARY KEY COLLATE "C"); + +create table collate_between_constraints (id varchar NOT NULL COLLATE "C" UNIQUE); + +-- preserve comments when collate follows or occurs between constraints +create table extraordinarily_long_collated_column_clause_order ( + first_identifier /* before type */ varchar /* before primary */ primary /* before key */ key /* before collate */ collate /* before collation */ "C", + second_identifier varchar /* before not */ not /* before null */ null /* before second collate */ collate /* before second collation */ "POSIX" /* before unique */ unique +); + diff --git a/crates/squawk_fmt/tests/before/create_trigger.sql b/crates/squawk_fmt/tests/before/create_trigger.sql index 8ee2d3495..dab3e8a72 100644 --- a/crates/squawk_fmt/tests/before/create_trigger.sql +++ b/crates/squawk_fmt/tests/before/create_trigger.sql @@ -8,5 +8,9 @@ create trigger bar after update of a, b, c on foo referencing new table bar old create trigger a_trigger_with_a_very_long_name before update of a_column_with_a_very_long_name or insert or delete on a_schema_with_a_very_long_name.a_table_with_a_very_long_name for each statement execute function a_schema_with_a_very_long_name.a_function_with_a_very_long_name('a long argument value'); +create constraint trigger constraint_options after insert on foo not valid no inherit not enforced enforced for each row execute function foo(); + +create constraint trigger commented_constraint_options after insert on foo /*a*/ not /*b*/ valid /*c*/ no /*d*/ inherit /*e*/ not /*f*/ enforced /*g*/ enforced /*h*/ for each row execute function foo(); + -- comments in every position create /*a*/ or /*b*/ replace /*c*/ constraint /*d*/ trigger /*e*/ commented_trigger /*f*/ instead /*g*/ of /*h*/ update /*i*/ of /*j*/ first_column /*k*/, /*l*/ second_column /*m*/ or /*n*/ delete /*o*/ on /*p*/ app /*q*/. /*r*/ records /*s*/ from /*t*/ app /*u*/. /*v*/ source_records /*w*/ deferrable /*x*/ initially /*y*/ deferred /*z*/ referencing /*aa*/ old /*ab*/ table /*ac*/ as /*ad*/ old_rows /*ae*/ new /*af*/ table /*ag*/ new_rows /*ah*/ for /*ai*/ each /*aj*/ row /*ak*/ when /*al*/ (/*am*/ old_rows.first_column /*an*/ > /*ao*/ 1 /*ap*/) /*aq*/ execute /*ar*/ function /*as*/ app /*at*/. /*au*/ handle_records(/*av*/ 1 /*aw*/, /*ax*/ 'two' /*ay*/) /*az*/; diff --git a/crates/squawk_fmt/tests/before/create_type.sql b/crates/squawk_fmt/tests/before/create_type.sql index a49ee1f80..1184cba0b 100644 --- a/crates/squawk_fmt/tests/before/create_type.sql +++ b/crates/squawk_fmt/tests/before/create_type.sql @@ -8,6 +8,10 @@ create type float8_range as range (subtype = float8, subtype_diff = float8mi); create type complex_base (input = complex_in, output = complex_out, internallength = 16); +create type null_default_base (default = null); + +create type commented_null_default_base (default /* equals */ = /* null value */ null /* close */); + -- comments in every composite type position create /* type */ type /* name */ commented_composite /* as */ as /* open */ (/* field */ first_field /* type */ text /* collate */ collate /* collation */ "C" /* comma */, /* next field */ second_field /* next type */ integer /* close */) /* end */; diff --git a/crates/squawk_fmt/tests/before/create_user_mapping.sql b/crates/squawk_fmt/tests/before/create_user_mapping.sql index 44cb6e020..40f5a5f89 100644 --- a/crates/squawk_fmt/tests/before/create_user_mapping.sql +++ b/crates/squawk_fmt/tests/before/create_user_mapping.sql @@ -1,5 +1,7 @@ create user mapping for app_user server app_server; +create user mapping for /* special user target */ user /* server */ server app_server; + create user mapping if not exists for extraordinarily_long_application_reporting_user server extraordinarily_long_foreign_data_server options (user 'extraordinarily_long_remote_user_name', password 'an-extraordinarily-long-secret-value'); -- comments in every position diff --git a/crates/squawk_fmt/tests/before/delete.sql b/crates/squawk_fmt/tests/before/delete.sql index ea5bc1f32..6b39baa2b 100644 --- a/crates/squawk_fmt/tests/before/delete.sql +++ b/crates/squawk_fmt/tests/before/delete.sql @@ -16,4 +16,8 @@ WITH doomed AS (SELECT id FROM foo) DELETE FROM foo USING doomed WHERE foo.id = WITH deleted AS (DELETE FROM foo WHERE id = 1 RETURNING id) SELECT * FROM deleted; +DELETE FROM t3 USING t1 JOIN t2 USING (a) WHERE t3.x > t1.a; + +DELETE FROM t3 /*before using*/ USING /*before t1*/ t1 /*before join*/ JOIN /*before t2*/ t2 /*before join using*/ USING /*before columns*/ (/*before a*/ a /*after a*/) /*before where*/ WHERE t3.x > t1.a; + /*before*/ DELETE /*a*/ FROM /*b*/ foo /*c*/ FOR /*d*/ PORTION /*e*/ OF /*f*/ valid_at /*g*/ FROM /*h*/ 1 /*i*/ TO /*j*/ 2 /*k*/ AS /*l*/ f /*m*/ USING /*n*/ bar /*o*/ b /*p*/, /*q*/ baz /*r*/ WHERE /*s*/ f.id = b.id /*t*/ RETURNING /*u*/ WITH /*v*/ (/*w*/ OLD /*x*/ AS /*y*/ o /*z*/, /*aa*/ NEW /*ab*/ AS /*ac*/ n /*ad*/) /*ae*/ o.id /*af*/, /*ag*/ n.id /*ah*/; diff --git a/crates/squawk_fmt/tests/before/drop_user_mapping.sql b/crates/squawk_fmt/tests/before/drop_user_mapping.sql index f141c05eb..ea5350450 100644 --- a/crates/squawk_fmt/tests/before/drop_user_mapping.sql +++ b/crates/squawk_fmt/tests/before/drop_user_mapping.sql @@ -1,5 +1,7 @@ drop user mapping for app_user server app_server; +drop user mapping for /* special user target */ user /* server */ server app_server; + drop user mapping if exists for extraordinarily_long_application_reporting_user_name server extraordinarily_long_foreign_data_server_name; -- comments in every position diff --git a/crates/squawk_fmt/tests/before/from.sql b/crates/squawk_fmt/tests/before/from.sql index d0c5afd1c..d20d45456 100644 --- a/crates/squawk_fmt/tests/before/from.sql +++ b/crates/squawk_fmt/tests/before/from.sql @@ -8,6 +8,8 @@ select * from foo /* after comma */ display_name /* before close paren */ ); select * from users tablesample bernoulli(10) repeatable (42); +select t.id from test_tablesample as t tablesample system (50) repeatable (0); +select * from users /* before alias */ as /* before alias name */ u /* before tablesample */ tablesample /* before method */ bernoulli /* before sample opening paren */ ( /* before percentage */ 10 /* before sample closing paren */ ) /* before repeatable */ repeatable /* before seed opening paren */ ( /* before seed */ 42 /* before seed closing paren */ ); select * from generate_series(1, 3); select * from lateral generate_series(1, 3) with ordinality as g(n, ord); select * from /* before lateral */ lateral /* before call */ generate_series /* before opening paren */ ( /* before first argument */ 1 /* before comma */, /* before second argument */ 3 /* before closing paren */ ) /* before with */ with /* before ordinality */ ordinality /* before alias */ as /* before alias name */ g /* before alias opening paren */ ( /* before first column */ n /* before column comma */, /* before second column */ ord /* before alias closing paren */ ) /* after function item */, other; @@ -45,6 +47,10 @@ select * from users join profiles on users.id = profiles.user_id; select * from users left outer join profiles using (user_id) as matched_users; +select relname, pg_get_indexdef from pg_class left join pg_inherits on inhrelid = oid, lateral pg_get_indexdef(pg_class.oid); + +select * from /* before joined relation */ pg_class /* before join */ left /* before outer */ outer /* before join keyword */ join /* before joined table */ pg_inherits /* before on */ on /* before left operand */ inhrelid /* before equals */ = /* before right operand */ oid /* before comma */, /* before lateral item */ lateral /* before function */ pg_get_indexdef /* before opening paren */ ( /* before argument */ pg_class.oid /* before closing paren */ ); + select * from a right join b on true, c full outer join d on true, e natural inner join f, g cross join h; select * from first_really_long_table_name join second_really_long_table_name on first_really_long_table_name.id = second_really_long_table_name.first_id join third_really_long_table_name on second_really_long_table_name.id = third_really_long_table_name.second_id; @@ -54,3 +60,7 @@ select * from (a join b on a.id = b.id) as joined_tables; select * from a /*ja*/ left /*jb*/ outer /*jc*/ join /*jd*/ b /*je*/ on /*jf*/ a /*jg*/. /*jh*/ id /*ji*/ = /*jj*/ b /*jk*/. /*jl*/ id, /*jm*/ c /*jn*/ join /*jo*/ d /*jp*/ using /*jq*/ (/*jr*/ first_id /*js*/, /*jt*/ second_id /*ju*/) /*jv*/ as /*jw*/ ids /*jx*/; select a_very_long_column_name from a_very_long_schema_name.a_very_long_table_name left outer join another_very_long_schema_name.another_very_long_table_name on a_very_long_schema_name.a_very_long_table_name.a_very_long_column_name = another_very_long_schema_name.another_very_long_table_name.another_very_long_column_name; + +select s.stxrelid::regclass as relation, array_agg(a.attname order by u.ord) as stats_columns from pg_statistic_ext s cross join lateral unnest(s.stxkeys::int2[]) with ordinality as u(attnum, ord) join pg_attribute a on a.attrelid = s.stxrelid and a.attnum = u.attnum; + +select * from statistics /* before base alias */ s /* before cross */ cross /* before cross join */ join /* before lateral */ lateral /* before function */ unnest /* before function opening paren */ ( /* before argument */ s.keys /* before cast */ ::int2[] /* before function closing paren */ ) /* before with */ with /* before ordinality */ ordinality /* before function alias */ as /* before function alias name */ u /* before alias opening paren */ ( /* before first alias column */ attnum /* before alias comma */, /* before second alias column */ ord /* before alias closing paren */ ) /* before nested join */ join /* before nested relation */ attributes /* before nested alias */ a /* before on */ on /* before left operand */ a.attnum /* before equals */ = /* before right operand */ u.attnum; diff --git a/crates/squawk_fmt/tests/before/merge.sql b/crates/squawk_fmt/tests/before/merge.sql index d30bb1229..c245187d7 100644 --- a/crates/squawk_fmt/tests/before/merge.sql +++ b/crates/squawk_fmt/tests/before/merge.sql @@ -4,4 +4,8 @@ WITH merged AS (MERGE INTO target USING source ON target.id = source.id WHEN MAT MERGE INTO a_very_long_schema_name.a_very_long_target_table_name AS an_extremely_long_target_alias USING a_very_long_schema_name.a_very_long_source_table_name AS an_extremely_long_source_alias ON an_extremely_long_target_alias.organization_identifier = an_extremely_long_source_alias.organization_identifier WHEN MATCHED AND an_extremely_long_source_alias.should_update_the_existing_record THEN UPDATE SET extremely_long_descriptive_column_name = an_extremely_long_source_alias.extremely_long_descriptive_column_name WHEN NOT MATCHED BY TARGET THEN INSERT (organization_identifier, extremely_long_descriptive_column_name) OVERRIDING SYSTEM VALUE VALUES (an_extremely_long_source_alias.organization_identifier, an_extremely_long_source_alias.extremely_long_descriptive_column_name) RETURNING an_extremely_long_target_alias.organization_identifier; +MERGE INTO target t USING source1 s1 INNER JOIN source2 s2 ON s1.id = s2.id ON t.id = s1.id WHEN NOT MATCHED THEN INSERT VALUES (s1.id, s2.value); + +MERGE INTO target t USING /* before left source */ source1 /* before left alias */ s1 /* before inner */ INNER /* before join */ JOIN /* before right source */ source2 /* before right alias */ s2 /* before join on */ ON /* before join condition */ s1.id = s2.id /* before merge on */ ON /* before merge condition */ t.id = s1.id WHEN MATCHED THEN DELETE; + /* before merge */ MERGE /* before into */ INTO /* before target */ ONLY /* before target open */ (/* before schema */ public /* before dot */ . /* before table */ target /* before target close */) /* before as */ AS /* before target alias */ t /* before using */ USING /* before source */ source /* before source as */ AS /* before source alias */ s /* before on */ ON /* before condition */ t.id = s.id /* before first when */ WHEN /* before matched */ MATCHED /* before and */ AND /* before predicate */ s.deleted /* before then */ THEN /* before delete */ DELETE /* before second when */ WHEN /* before second matched */ MATCHED /* before second then */ THEN /* before update */ UPDATE /* before set */ SET /* before column */ value /* before equals */ = /* before value */ s.value /* before third when */ WHEN /* before not */ NOT /* before third matched */ MATCHED /* before by */ BY /* before source keyword */ SOURCE /* before source and */ AND /* before source predicate */ t.active /* before source then */ THEN /* before do */ DO /* before nothing */ NOTHING /* before fourth when */ WHEN /* before fourth not */ NOT /* before fourth matched */ MATCHED /* before target by */ BY /* before target keyword */ TARGET /* before target then */ THEN /* before insert */ INSERT /* before columns */ (/* before id */ id /* before comma */, /* before value column */ value /* before columns close */) /* before overriding */ OVERRIDING /* before user */ USER /* before overriding value */ VALUE /* before values */ VALUES /* before row */ (/* before source id */ s.id /* before values comma */, /* before source value */ s.value /* before row close */) /* before fifth when */ WHEN NOT MATCHED THEN INSERT /* before default */ DEFAULT /* before default values */ VALUES /* before returning */ RETURNING /* before return target */ t.id /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/paren_select.sql b/crates/squawk_fmt/tests/before/paren_select.sql index e4e7bdb99..4611597af 100644 --- a/crates/squawk_fmt/tests/before/paren_select.sql +++ b/crates/squawk_fmt/tests/before/paren_select.sql @@ -7,3 +7,10 @@ with cte as (select 1) (select x from cte); with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* before query open */ (/* before query */ select 1 /* before query close */) /* before outer open */ (/* before select */ select /* before target */ x /* before from */ from /* before relation */ cte /* before outer close */) /* before order */ order /* before order by */ by /* before order expression */ x /* before desc */ desc /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ cte /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows /* before semicolon */; (/* before select */ select 1 /* before close */) /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties; + +select * from (((select 1) union select 2) limit 1) as t; + +select * from (/* after outer open */ ((/* before left select */ select 1 /* before left close */) /* before union */ union /* before right select */ select 2 /* before compound close */) /* before inner limit */ limit /* before limit value */ 1 /* before outer close */) /* before alias */ as t; + +((select 1) limit 2) order by 3; +(select 1) offset 1 limit 2; diff --git a/crates/squawk_fmt/tests/before/select_clauses.sql b/crates/squawk_fmt/tests/before/select_clauses.sql index cee7464cc..e0a578690 100644 --- a/crates/squawk_fmt/tests/before/select_clauses.sql +++ b/crates/squawk_fmt/tests/before/select_clauses.sql @@ -7,3 +7,12 @@ with a_very_long_common_table_expression_name as (select a_very_long_source_colu with /* before recursive */ recursive /* before cte */ cte /* before columns */ (/* before column */ x /* before columns close */) /* before as */ as /* before materialized */ materialized /* before query open */ (/* before query */ select 1 /* before query close */) /* before outer select */ select /* before target */ x /* before from */ from /* before relation */ cte /* before where */ where /* before where expression */ x > 0 /* before group */ group /* before by */ by /* before group expression */ x /* before having */ having /* before having expression */ count(*) > 0 /* before window */ window /* before window name */ win /* before window as */ as /* before window open */ (/* before partition */ partition /* before partition by */ by /* before partition expression */ x /* before window close */) /* before order */ order /* before order by */ by /* before order expression */ x /* before desc */ desc /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ cte /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows /* before semicolon */; select x /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties; + +select ''::text as five, unique1, unique2, stringu1 from onek order by unique1 /* before offset */ offset 990 /* before limit */ limit 5; + +select thousand from onek where thousand < 5 order by thousand /* before fetch */ fetch first 1 row with ties /* before locking */ for update skip locked; + +select thousand from onek where thousand < 995 order by thousand /* before offset */ offset 10 /* before fetch */ fetch first 5 rows only; + +select x from foo offset 1 limit 2; +select x from foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/before/select_expr.sql b/crates/squawk_fmt/tests/before/select_expr.sql index dd124f5bb..60fe2139c 100644 --- a/crates/squawk_fmt/tests/before/select_expr.sql +++ b/crates/squawk_fmt/tests/before/select_expr.sql @@ -2,12 +2,18 @@ select -- array expr array[1,2], array(select 1), + array(select '1 4'::int2vector union all select '1 2'::int2vector), + /* before array query */ ARRAY /* before opening paren */ (/* before left select */ SELECT /* before left target */ 1 /* before union */ UNION /* before all */ ALL /* before right select */ SELECT /* before right target */ 2 /* before closing paren */) /* after array query */, + array(select a_very_long_first_array_select_expression from a_very_long_first_array_select_relation union all select a_very_long_second_array_select_expression from a_very_long_second_array_select_relation), array[[1,2],[3,4]], /* before array */ ARRAY /* before opening bracket */ [ /* before first */ 1 /* before comma */, /* before second */ 2 /* before closing bracket */ ] /* after array */, array[a_very_long_first_array_expression, a_very_long_second_array_expression, a_very_long_third_array_expression], array(select a_very_long_array_select_expression from a_very_long_array_select_relation_name), array[[a_very_long_first_nested_array_expression, a_very_long_second_nested_array_expression], [a_very_long_third_nested_array_expression, a_very_long_fourth_nested_array_expression]], array[[a_very_long_first, a_very_long_second], [a_very_long_third, a_very_long_fourth]], + array(((select 1))), + array(((select a_very_long_array_select_expression from a_very_long_array_select_relation_name))), + /* before nested array */ array /* before outer opening paren */ (/* after outer opening paren */ (/* after middle opening paren */ (/* before select */ select /* before target */ 1 /* before inner closing paren */) /* before middle closing paren */) /* before outer closing paren */), -- between expr 2 between 1 and 3, 2 not between 1 and 3, @@ -120,6 +126,9 @@ select foo ( /* before distinct */ DISTINCT /* after distinct */ 1 ), foo ( VARIADIC xs ), foo ( /* before variadic */ VARIADIC /* after variadic */ xs ), + foo(variadic arr => array[2,3]), + foo(/* before variadic */ VARIADIC /* before name */ arr /* before arrow */ => /* before value */ array[2,3]), + a_very_long_function_name(VARIADIC a_very_long_named_argument => a_very_long_variadic_argument_expression_that_forces_wrapping), foo(a => 1, b := 2), foo(/* before name */ a /* before arrow */ => /* before value */ 1 /* before comma */, /* before arg */ b /* before assign */ := /* before value 2 */ 2), array_agg(x order by y desc nulls last, z asc), @@ -135,6 +144,9 @@ select JSON_ARRAYAGG(v FORMAT JSON ORDER BY sort_col DESC NULL ON NULL RETURNING jsonb FORMAT JSON), JSON_ARRAYAGG(a_very_long_json_arrayagg_value FORMAT JSON ENCODING UTF8 ORDER BY a_very_long_json_arrayagg_sort_expression DESC ABSENT ON NULL RETURNING a_very_long_json_arrayagg_return_type FORMAT JSON), JSON_ARRAYAGG /* before opening paren */ (/* before value */ v /* before value format */ FORMAT /* before value json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before order */ ORDER /* before by */ BY /* before sort */ x /* before absent */ ABSENT /* before on */ ON /* before null */ NULL /* before returning */ RETURNING /* before type */ jsonb /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), + JSON_ARRAYAGG(v) FILTER (WHERE v > 0), + JSON_OBJECTAGG(k: v) FILTER (WHERE v IS NOT NULL) OVER (PARTITION BY category ORDER BY k), + JSON_ARRAYAGG(v) /* before filter */ FILTER /* before filter opening */ (/* before where */ WHERE /* before condition */ v > 0 /* before filter closing */) /* before over */ OVER /* before over opening */ (/* before partition */ PARTITION /* before by */ BY /* before partition expr */ category /* before order */ ORDER /* before order by */ BY /* before sort */ v /* before over closing */), JSON_ARRAY(), JSON_ARRAY(1, 2 FORMAT JSON RETURNING jsonb), JSON_ARRAY(first_very_long_json_array_expression, second_very_long_json_array_expression, third_very_long_json_array_expression RETURNING a_very_long_json_array_return_type), @@ -253,6 +265,9 @@ select ( ( 1 ) ), ( /* before expr */ 1 /* before closing paren */ ), ( select 1 ), + ((select 2) union select 2), + (((select 2)) union select 2), + /* before compound opening paren */ (/* after compound opening paren */ (/* before inner select */ select /* before inner target */ 2 /* before inner closing paren */) /* before union */ union /* before right select */ select /* before right target */ 2 /* before compound closing paren */), /* before opening paren */ ( /* before select */ select /* before target */ x /* before from */ from /* before relation */ things /* before closing paren */ ) /* after paren */, (select a_very_long_parenthesized_select_expression from a_very_long_parenthesized_select_relation_name), ( table foo ), @@ -284,6 +299,8 @@ select x is not normalized, x is not nfkd normalized, x is /* before not */ not /* before json */ json /* before array */ array /* before with */ with /* before unique */ unique /* before keys */ keys, + null::json is json, + null::json /* before is */ is /* before json */ json /* before with */ with /* before unique */ unique /* before keys */ keys, x is /* before normalized not */ not /* before form */ nfkd /* before normalized */ normalized, a_very_long_json_postfix_input_expression_that_forces_wrapping is json array with unique keys, a_very_long_normalized_postfix_input_expression_that_forces_wrapping is not nfkd normalized, diff --git a/crates/squawk_fmt/tests/before/select_into.sql b/crates/squawk_fmt/tests/before/select_into.sql index ba7441ded..0f836bf31 100644 --- a/crates/squawk_fmt/tests/before/select_into.sql +++ b/crates/squawk_fmt/tests/before/select_into.sql @@ -7,3 +7,6 @@ select a_very_long_first_select_into_expression, a_very_long_second_select_into_ with recursive first_cte(a, b) as not materialized (select 1, 2) search depth first by a, b set traversal_order cycle a, b set is_cycle to true default false using traversal_path, second_cte as (values (3, 4)) select distinct on (a, b) a, count(*) into result from source where a > 1 group by a having count(*) > 1 window named_window as (partition by a order by b) order by a for no key update of source skip locked limit 10 offset 2 rows; with /* before recursive */ recursive /* before cte */ cte /* before columns */ (/* before column */ a /* before column comma */, /* before second column */ b /* before columns close */) /* before as */ as /* before not */ not /* before materialized */ materialized /* before query open */ (/* before query */ select 1 /* before query close */) /* before search */ search /* before depth */ depth /* before first */ first /* before search by */ by /* before search column */ a /* before search comma */, /* before second search column */ b /* before search set */ set /* before search set column */ traversal_order /* before cycle */ cycle /* before cycle column */ a /* before cycle comma */, /* before second cycle column */ b /* before cycle set */ set /* before cycle set column */ is_cycle /* before to */ to /* before cycle value */ true /* before default */ default /* before default value */ false /* before using */ using /* before path column */ traversal_path /* before outer select */ select /* before distinct */ distinct /* before on */ on /* before distinct open */ (/* before distinct expression */ a /* before distinct comma */, /* before second distinct expression */ b /* before distinct close */) a into result from source /* before where */ where /* before where expression */ a > 1 /* before having */ having /* before having expression */ count(*) > 1 /* before window */ window /* before window name */ named_window /* before window as */ as /* before window open */ (/* before partition */ partition /* before partition by */ by a /* before window close */) /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ source /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows; + +select 1 into foo offset 1 limit 2; +select 1 into foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/before/set.sql b/crates/squawk_fmt/tests/before/set.sql index 48c6636d1..c80c46655 100644 --- a/crates/squawk_fmt/tests/before/set.sql +++ b/crates/squawk_fmt/tests/before/set.sql @@ -8,7 +8,11 @@ set foo from current; set foo = default; -set foo to null; +set foo to off; + +SET search_path = myschema, public; + +SET /* before list parameter */ search_path /* before list equals */ = /* before first value */ myschema /* before list comma */, /* before second value */ public /* before list semicolon */; set foo to a, 10.0, 1, 'foo', true, false; @@ -26,6 +30,18 @@ set time zone default; set time zone local; +SET LOCAL TIME ZONE -8; + +SET TIME ZONE INTERVAL '-08:00' HOUR TO MINUTE; + +SET TIME ZONE INTERVAL(2) '-08:00'; + +SET extra_float_digits = -1; + +SET /* before negative config parameter */ extra_float_digits /* before negative config equals */ = /* before config minus */ - /* before config number */ 1 /* before negative config semicolon */; + +SET /* before negative scope */ LOCAL /* before negative time */ TIME /* before negative zone */ ZONE /* before minus */ - /* before timezone number */ 8 /* before negative timezone semicolon */; + set an_intentionally_long_config_namespace.an_intentionally_long_config_group.an_intentionally_long_parameter_name to an_intentionally_long_value_name, another_intentionally_long_value_name; /* before set */ SET /* before scope */ LOCAL /* before parameter */ custom /* before dot */ . /* after dot */ parameter /* before equals */ = /* before first value */ first_value /* before comma */, /* after comma */ 'second value' /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/table.sql b/crates/squawk_fmt/tests/before/table.sql index a9692d1cf..7bdc926b5 100644 --- a/crates/squawk_fmt/tests/before/table.sql +++ b/crates/squawk_fmt/tests/before/table.sql @@ -13,3 +13,6 @@ table a_very_long_schema_name.a_very_long_relation_name_that_forces_the_table_st with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* before query open */ (/* before query */ select 1 /* before query close */) /* before table */ table /* before relation */ cte /* before order */ order /* before by */ by /* before order expression */ a /* before desc */ desc /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ cte /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows /* before semicolon */; table /* before relation */ foo /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties /* before semicolon */; + +table foo offset 1 limit 2; +table foo limit 2 for update; diff --git a/crates/squawk_fmt/tests/before/table_constraints.sql b/crates/squawk_fmt/tests/before/table_constraints.sql index 560dfe16f..819af5f35 100644 --- a/crates/squawk_fmt/tests/before/table_constraints.sql +++ b/crates/squawk_fmt/tests/before/table_constraints.sql @@ -19,6 +19,33 @@ create table named_constraints ( CONSTRAINT no_overlap EXCLUDE USING gist (first_very_long_exclusion_expression WITH =, second_very_long_exclusion_expression WITH &&) INCLUDE (first_very_long_excluded_column_name, second_very_long_excluded_column_name) WITH (fillfactor = 80, an_extremely_long_exclusion_storage_parameter_name_that_forces_wrapping = false) USING INDEX TABLESPACE fast WHERE (a_very_long_exclusion_predicate_expression > an_extremely_long_exclusion_predicate_value_that_forces_wrapping) DEFERRABLE ); +create table inherited_not_null_constraints ( + inherited_column bigint, + NOT NULL inherited_column NO INHERIT, + CONSTRAINT inherited_not_null NOT NULL inherited_column +); + +create table commented_inherited_not_null_constraint ( + inherited_column bigint, + /* constraint */ CONSTRAINT /* name */ inherited_not_null /* not */ NOT /* null */ NULL /* column */ inherited_column /* no */ NO /* inherit */ INHERIT +); + +create table temporal_constraints ( + id bigint, + parent_id bigint, + valid_at daterange, + PRIMARY KEY (id, valid_at WITHOUT OVERLAPS), + FOREIGN KEY (parent_id, PERIOD valid_at) REFERENCES temporal_parents (id, PERIOD valid_at) +); + +create table commented_temporal_constraints ( + id bigint, + parent_id bigint, + valid_at daterange, + PRIMARY /* before primary key */ KEY /* before primary opening paren */ (id /* before primary comma */, /* before valid column */ valid_at /* before without */ WITHOUT /* before overlaps */ OVERLAPS /* before primary closing paren */), + FOREIGN /* before foreign key */ KEY /* before from opening paren */ (parent_id /* before period comma */, /* before from period */ PERIOD /* before from period column */ valid_at /* before from closing paren */) /* before references */ REFERENCES /* before referenced table */ temporal_parents /* before to opening paren */ (id /* before referenced period comma */, /* before referenced period */ PERIOD /* before referenced period column */ valid_at /* before to closing paren */) +); + create table using_indexes ( id bigint, UNIQUE USING INDEX existing_unique, @@ -36,3 +63,9 @@ create table commented_constraints ( CONSTRAINT named_fk /* before foreign */ FOREIGN /* before key */ KEY /* before from opening paren */ ( /* before from column */ parent_id /* before from closing paren */ ) /* before references */ REFERENCES /* before table */ public /* before dot */ . /* before table name */ parents /* before to opening paren */ ( /* before to column */ id /* before to closing paren */ ) /* before match */ MATCH /* before simple */ SIMPLE /* before on delete */ ON /* before delete */ DELETE /* before set */ SET /* before null */ NULL /* before set columns */ (parent_id) /* before on update */ ON /* before update */ UPDATE /* before cascade */ CASCADE /* before enforced */ ENFORCED, CONSTRAINT named_exclude /* before exclude */ EXCLUDE /* before using */ USING /* before method */ gist /* before exclusion opening paren */ ( /* before exclusion expression */ id /* before exclusion with */ WITH /* before exclusion op */ = /* before exclusion comma */, /* before second exclusion */ valid_at WITH /* before operator */ OPERATOR /* before operator opening paren */ ( /* before operator name */ public /* before operator dot */ . /* before operator op */ && /* before operator closing paren */ ) /* before exclusion closing paren */ ) /* before include */ INCLUDE /* before include opening paren */ (id /* before include closing paren */ ) /* before with params */ WITH /* before params opening paren */ ( /* before param */ fillfactor /* before equals */ = /* before value */ 80 /* before params closing paren */ ) /* before tablespace using */ USING /* before index */ INDEX /* before tablespace */ TABLESPACE /* before tablespace name */ fast /* before where */ WHERE /* before where opening paren */ ( /* before where expression */ id > 0 /* before where closing paren */ ) /* before initially */ INITIALLY /* before immediate */ IMMEDIATE ); + +create table source_ordered_referential_actions ( + id bigint, + parent_id bigint REFERENCES parents /* before update on */ ON /* before update */ UPDATE /* before update action */ CASCADE /* before delete on */ ON /* before delete */ DELETE /* before delete action */ RESTRICT, + FOREIGN KEY (parent_id) REFERENCES parents (id) /* before update on */ ON /* before update */ UPDATE /* before update action */ SET /* before update null */ NULL /* before delete on */ ON /* before delete */ DELETE /* before delete action */ NO /* before action */ ACTION +); diff --git a/crates/squawk_fmt/tests/before/transaction_control.sql b/crates/squawk_fmt/tests/before/transaction_control.sql index 8bfcef94c..c2dd40cd5 100644 --- a/crates/squawk_fmt/tests/before/transaction_control.sql +++ b/crates/squawk_fmt/tests/before/transaction_control.sql @@ -6,6 +6,22 @@ start transaction isolation level serializable, read write, deferrable; begin transaction isolation level repeatable read, read only, not deferrable; +begin + isolation level read committed + read only + read write + deferrable + not deferrable; + +start transaction + isolation level read committed + read only + read write + deferrable + not deferrable; + +begin /* before first mode */ isolation /* before level */ level /* before read */ read /* before committed */ committed /* before read only */ read /* before only */ only /* before read write */ read /* before write */ write /* before deferrable */ deferrable /* before not */ not /* before final deferrable */ deferrable /* before semicolon */; + commit; end work; diff --git a/crates/squawk_fmt/tests/before/values.sql b/crates/squawk_fmt/tests/before/values.sql index 69a6f09a7..6a01b32cc 100644 --- a/crates/squawk_fmt/tests/before/values.sql +++ b/crates/squawk_fmt/tests/before/values.sql @@ -11,3 +11,6 @@ values (a_very_long_first_expression, a_very_long_second_expression, a_very_long with /* before recursive */ recursive /* before cte */ cte /* before as */ as /* before query open */ (/* before query */ select 1 /* before query close */) /* before values */ values /* before row */ (/* before expression */ 1 /* before row close */) /* before order */ order /* before by */ by /* before order expression */ 1 /* before desc */ desc /* before locking */ for /* before lock strength */ update /* before locking of */ of /* before locked relation */ cte /* before lock wait */ nowait /* before limit */ limit /* before limit value */ 10 /* before offset */ offset /* before offset value */ 2 /* before rows */ rows /* before semicolon */; values /* before row */ (/* before expression */ 1 /* before row close */) /* before fetch */ fetch /* before first */ first /* before quantity */ 5 /* before rows */ rows /* before with ties */ with /* before ties */ ties /* before semicolon */; + +values (1) offset 1 limit 2; +values (1) limit 2 for update; diff --git a/crates/squawk_ide/src/ast_nav.rs b/crates/squawk_ide/src/ast_nav.rs index 24e7b0d45..da4900b92 100644 --- a/crates/squawk_ide/src/ast_nav.rs +++ b/crates/squawk_ide/src/ast_nav.rs @@ -308,76 +308,50 @@ pub(crate) fn unwrap_paren_expr(expr: ast::Expr) -> impl Iterator Option { + match merge.using_on_clause()?.from_list_item()? { + ast::FromListItem::FromItem(from_item) => Some(from_item), + ast::FromListItem::JoinExpr(_) => None, + } +} + pub(crate) fn iter_from_clause( from_clause: &ast::FromClause, ) -> impl Iterator { - from_clause.from_items().chain( - from_clause - .join_exprs() - .flat_map(|join_expr| JoinExprIter::new(&join_expr)), - ) + iter_from_items(from_clause.items()) } pub(crate) fn iter_join_expr(join_expr: &ast::JoinExpr) -> impl Iterator { - JoinExprIter::new(join_expr) -} - -struct JoinExprIter { - stack: Vec, + iter_from_items(std::iter::once(ast::FromListItem::JoinExpr( + join_expr.clone(), + ))) } -impl JoinExprIter { - fn new(join_expr: &ast::JoinExpr) -> Self { - Self { - stack: vec![JoinExprIterFrame { - join_expr: join_expr.clone(), - state: JoinExprIterState::JoinExpr, - }], - } - } -} - -struct JoinExprIterFrame { - join_expr: ast::JoinExpr, - state: JoinExprIterState, +pub(crate) fn iter_from_items( + items: impl Iterator, +) -> impl Iterator { + let mut stack = items.collect::>(); + stack.reverse(); + FromItemIter { stack } } -#[derive(Clone, Copy)] -enum JoinExprIterState { - FromItem, - Join, - JoinExpr, +struct FromItemIter { + stack: Vec, } -impl Iterator for JoinExprIter { +impl Iterator for FromItemIter { type Item = ast::FromItem; fn next(&mut self) -> Option { - while let Some(frame) = self.stack.last_mut() { - match frame.state { - JoinExprIterState::JoinExpr => { - frame.state = JoinExprIterState::FromItem; - - if let Some(nested_join) = frame.join_expr.join_expr() { - self.stack.push(JoinExprIterFrame { - join_expr: nested_join, - state: JoinExprIterState::JoinExpr, - }); + while let Some(item) = self.stack.pop() { + match item { + ast::FromListItem::FromItem(from_item) => return Some(from_item), + ast::FromListItem::JoinExpr(join_expr) => { + if let Some(rhs) = join_expr.join().and_then(|join| join.from_list_item()) { + self.stack.push(rhs); } - } - JoinExprIterState::FromItem => { - frame.state = JoinExprIterState::Join; - - if let Some(from_item) = frame.join_expr.from_item() { - return Some(from_item); - } - } - JoinExprIterState::Join => { - let from_item = frame.join_expr.join().and_then(|join| join.from_item()); - self.stack.pop(); - - if from_item.is_some() { - return from_item; + if let Some(lhs) = join_expr.from_list_item() { + self.stack.push(lhs); } } } diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 624a31bf7..72552a568 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -450,8 +450,10 @@ fn bind_create_table_constraints( for arg in table_arg_list.args() { match arg { ast::TableArg::Column(column) => { - for constraint in column.constraints() { - if let Some(constraint_name) = constraint.constraint_name() { + for clause in column.clauses() { + if let ast::ColumnClause::ColumnConstraint(constraint) = clause + && let Some(constraint_name) = constraint.constraint_name() + { bind_constraint_name_node(b, constraint_name, schema, table_name); } } @@ -599,7 +601,7 @@ fn bind_create_function(b: &mut Binder, create_function: ast::CreateFunction) { b.scope.insert(function_name, function_id); - bind_routine_body_search_path(b, create_function.option_list()); + bind_routine_body_search_path(b, create_function.option_list(), create_function.body()); } fn bind_create_aggregate(b: &mut Binder, create_aggregate: ast::CreateAggregate) { @@ -660,7 +662,7 @@ fn bind_create_procedure(b: &mut Binder, create_procedure: ast::CreateProcedure) b.scope.insert(procedure_name, procedure_id); - bind_routine_body_search_path(b, create_procedure.option_list()); + bind_routine_body_search_path(b, create_procedure.option_list(), create_procedure.body()); } fn bind_create_schema(b: &mut Binder, create_schema: ast::CreateSchema) { @@ -694,13 +696,28 @@ fn bind_create_schema(b: &mut Binder, create_schema: ast::CreateSchema) { fn bind_schema_element(b: &mut Binder, element: ast::SchemaElement) { match element { - ast::SchemaElement::CreateIndex(create_index) => bind_create_index(b, create_index), - ast::SchemaElement::CreateSequence(create_sequence) => { - bind_create_sequence(b, create_sequence) + ast::SchemaElement::CreateAggregate(stmt) => bind_create_aggregate(b, stmt), + ast::SchemaElement::CreateCollation(stmt) => bind_create_collation(b, stmt), + ast::SchemaElement::CreateDomain(stmt) => bind_create_domain(b, stmt), + ast::SchemaElement::CreateFunction(stmt) => bind_create_function(b, stmt), + ast::SchemaElement::CreateIndex(stmt) => bind_create_index(b, stmt), + ast::SchemaElement::CreateOperator(stmt) => bind_create_operator(b, stmt), + ast::SchemaElement::CreateProcedure(stmt) => bind_create_procedure(b, stmt), + ast::SchemaElement::CreateSequence(stmt) => bind_create_sequence(b, stmt), + ast::SchemaElement::CreateTable(stmt) => bind_create_table(b, stmt), + ast::SchemaElement::CreateTextSearchConfiguration(stmt) => { + bind_create_text_search_configuration(b, stmt) + } + ast::SchemaElement::CreateTextSearchDictionary(stmt) => { + bind_create_text_search_dictionary(b, stmt) + } + ast::SchemaElement::CreateTextSearchParser(stmt) => bind_create_text_search_parser(b, stmt), + ast::SchemaElement::CreateTextSearchTemplate(stmt) => { + bind_create_text_search_template(b, stmt) } - ast::SchemaElement::CreateTable(create_table) => bind_create_table(b, create_table), - ast::SchemaElement::CreateTrigger(create_trigger) => bind_create_trigger(b, create_trigger), - ast::SchemaElement::CreateView(create_view) => bind_create_view(b, create_view), + ast::SchemaElement::CreateTrigger(stmt) => bind_create_trigger(b, stmt), + ast::SchemaElement::CreateType(stmt) => bind_create_type(b, stmt), + ast::SchemaElement::CreateView(stmt) => bind_create_view(b, stmt), ast::SchemaElement::Grant(_) => (), } } @@ -1834,28 +1851,29 @@ fn schema_name_from_qualifier( b.default_schema() } -fn bind_routine_body_search_path(b: &mut Binder, option_list: Option) { +fn bind_routine_body_search_path( + b: &mut Binder, + option_list: Option, + body: Option, +) { let Some(option_list) = option_list else { return; }; + let Some(ast::RoutineBody::AtomicBody(atomic_body)) = body else { + return; + }; let mut search_path = None; - let mut body_range = None; for option in option_list.options() { - match option { - ast::FuncOption::SetFuncOption(set_func_option) => { - if let Some(set_config_param) = set_func_option.set_config_param() { - search_path = search_path_from_set_config_param(&set_config_param); - } + if let ast::FuncOption::SetFuncOption(set_func_option) = option { + if let Some(set_config_param) = set_func_option.set_config_param() { + search_path = search_path_from_set_config_param(&set_config_param); } - ast::FuncOption::BeginFuncOptionList(begin_func_option_list) => { - body_range = Some(begin_func_option_list.syntax().text_range()); - } - _ => (), } } - let (Some(search_path), Some(body_range)) = (search_path, body_range) else { + let body_range = atomic_body.syntax().text_range(); + let Some(search_path) = search_path else { return; }; @@ -1925,6 +1943,7 @@ fn search_path_from_config_value(to_config_value: &ast::ToConfigValue) -> Vec { search_path.push(Schema::new(config_value_name.syntax().text().to_string())); } + ast::ConfigValue::PrefixExpr(_) => {} } } search_path diff --git a/crates/squawk_ide/src/classify.rs b/crates/squawk_ide/src/classify.rs index 03d39c40e..38366fa83 100644 --- a/crates/squawk_ide/src/classify.rs +++ b/crates/squawk_ide/src/classify.rs @@ -335,7 +335,7 @@ fn classify_call_expr_name_ref( if ast::Select::can_cast(ancestor.kind()) || ast::SelectInto::can_cast(ancestor.kind()) || ast::Insert::can_cast(ancestor.kind()) - || ast::ReturnFuncOption::can_cast(ancestor.kind()) + || ast::ReturnStmt::can_cast(ancestor.kind()) { return Some(NameRefClass::SelectFunctionCall); } @@ -714,7 +714,7 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { return Some(NameRefClass::PolicyColumn); } } - if ast::ReturnFuncOption::can_cast(ancestor.kind()) { + if ast::ReturnStmt::can_cast(ancestor.kind()) { if let Some(ast::Expr::NameRef(base)) = field_expr.base() && enclosing_routine_name(&ancestor) .is_some_and(|routine_name| Name::from_node(&base) == routine_name) @@ -1092,7 +1092,7 @@ pub(crate) fn classify_name_ref(node: &SyntaxNode) -> Option { break; } // SQL-body function: `create function f(x int) returns int language sql return x + 1;` - if ast::ReturnFuncOption::can_cast(ancestor.kind()) { + if ast::ReturnStmt::can_cast(ancestor.kind()) { return Some(NameRefClass::SelectColumn); } } diff --git a/crates/squawk_ide/src/code_actions/rewrite_select_as_table.rs b/crates/squawk_ide/src/code_actions/rewrite_select_as_table.rs index fb861438c..629878ad8 100644 --- a/crates/squawk_ide/src/code_actions/rewrite_select_as_table.rs +++ b/crates/squawk_ide/src/code_actions/rewrite_select_as_table.rs @@ -20,9 +20,9 @@ pub(super) fn rewrite_select_as_table( } let from_clause = select.from_clause()?; - let from_item = from_clause.from_items().next()?; - - let ast::FromItem::RelationFromItem(relation) = from_item else { + let ast::FromListItem::FromItem(ast::FromItem::RelationFromItem(relation)) = + from_clause.items().next()? + else { return None; }; let table_name = relation.relation_name_ref()?.syntax().text().to_string(); @@ -47,12 +47,8 @@ fn can_transform_select_to_table(select: &ast::Select) -> bool { || select.group_by_clause().is_some() || select.having_clause().is_some() || select.window_clause().is_some() - || select.order_by_clause().is_some() - || select.limit_clause().is_some() - || select.fetch_clause().is_some() - || select.offset_clause().is_some() || select.filter_clause().is_some() - || select.locking_clauses().next().is_some() + || select.tail_clauses().next().is_some() { return false; } @@ -90,17 +86,17 @@ fn can_transform_select_to_table(select: &ast::Select) -> bool { return false; }; - let mut from_items = from_clause.from_items(); - let Some(from_item) = from_items.next() else { + let mut items = from_clause.items(); + let Some(item) = items.next() else { return false; }; // only can have one from item & no join exprs - if from_items.next().is_some() || from_clause.join_exprs().next().is_some() { + if items.next().is_some() { return false; } - let ast::FromItem::RelationFromItem(relation) = from_item else { + let ast::FromListItem::FromItem(ast::FromItem::RelationFromItem(relation)) = item else { return false; }; if relation.alias().is_some() diff --git a/crates/squawk_ide/src/completion.rs b/crates/squawk_ide/src/completion.rs index c3ee0fda3..f65587881 100644 --- a/crates/squawk_ide/src/completion.rs +++ b/crates/squawk_ide/src/completion.rs @@ -1,6 +1,6 @@ use rowan::TextSize; use salsa::Database as Db; -use squawk_syntax::ast::{self, AstNode}; +use squawk_syntax::ast::{self, AstNode, HasSelectTail}; use squawk_syntax::{SyntaxKind, SyntaxNode, SyntaxToken}; use crate::ast_nav; @@ -297,7 +297,7 @@ fn select_expr_completions( completions.extend(function_completions(db, file, schema.as_ref(), position)); if let Some(from_clause) = select.from_clause() { - for from_item in from_clause.from_items() { + for from_item in ast_nav::iter_from_clause(&from_clause) { if let Some(table_name) = table_name_from_from_item(&from_item) { completions.push(CompletionItem { label: table_name.to_string(), @@ -1229,6 +1229,22 @@ select $0 from child; "); } + #[test] + fn completion_in_where_clause_with_join() { + assert_snapshot!(completions(" +create table t (a int); +create table u (b int); +select * from t join u on t.a = u.b where $0; +"), @" + label | kind | detail + -------+--------+-------- + a | Column | int + b | Column | int + t | Table | + u | Table | + "); + } + #[test] fn completion_after_select_select_into() { assert_snapshot!(completions(" diff --git a/crates/squawk_ide/src/expand_selection.rs b/crates/squawk_ide/src/expand_selection.rs index 97ddcab91..b0127d4f1 100644 --- a/crates/squawk_ide/src/expand_selection.rs +++ b/crates/squawk_ide/src/expand_selection.rs @@ -41,7 +41,7 @@ const DELIMITED_LIST_KINDS: &[SyntaxKind] = &[ SyntaxKind::ALTER_TYPE_ATTRIBUTE_ACTION_LIST, SyntaxKind::ARG_LIST, SyntaxKind::ATTRIBUTE_LIST, - SyntaxKind::BEGIN_FUNC_OPTION_LIST, + SyntaxKind::ATOMIC_BODY, SyntaxKind::CHECKPOINT_OPTION_LIST, SyntaxKind::COLUMN_DEF_LIST, SyntaxKind::COLUMN_LIST, diff --git a/crates/squawk_ide/src/folding_ranges.rs b/crates/squawk_ide/src/folding_ranges.rs index d8c9b00e4..f0d8f9d5b 100644 --- a/crates/squawk_ide/src/folding_ranges.rs +++ b/crates/squawk_ide/src/folding_ranges.rs @@ -138,7 +138,7 @@ fn fold_kind(kind: SyntaxKind) -> Option { | SyntaxKind::ALTER_OPTION_LIST | SyntaxKind::ALTER_TYPE_ATTRIBUTE_ACTION_LIST | SyntaxKind::ATTRIBUTE_LIST - | SyntaxKind::BEGIN_FUNC_OPTION_LIST + | SyntaxKind::ATOMIC_BODY | SyntaxKind::CHECKPOINT_OPTION_LIST | SyntaxKind::COLUMN_DEF_LIST | SyntaxKind::COLUMN_LIST diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 4ef1a39e4..12abdd95e 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -881,7 +881,7 @@ fn target_has_schema_qualified_from_item(target: &ast::Target) -> bool { return false; }; - for from_item in from_clause.from_items() { + for from_item in ast_nav::iter_from_clause(&from_clause) { if let ast::FromItem::RelationFromItem(relation) = from_item && relation .path_ref() diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 6779f481b..81f4a02dd 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -2169,9 +2169,7 @@ fn resolve_select_qualified_column_ptr( // relation name), resolve the column against that source. This // handles subquery and VALUES sources, where the qualifier is not // a real table name. - if let Some(using_on) = merge.using_on_clause() - && let Some(from_item) = using_on.from_item() - { + if let Some(from_item) = ast_nav::merge_using_from_item(&merge) { let matches_source = if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) { @@ -2318,11 +2316,10 @@ pub(crate) fn resolve_table_name( } fn resolve_merge_alias(name_ref: &impl ast::NameLike, table_name: &Name) -> Option { - let from_item = name_ref.syntax().ancestors().find_map(|x| { - ast::Merge::cast(x)? - .using_on_clause() - .and_then(|c| c.from_item()) - })?; + let from_item = name_ref + .syntax() + .ancestors() + .find_map(|x| ast_nav::merge_using_from_item(&ast::Merge::cast(x)?))?; if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) && Name::from_node(&alias_name) == *table_name && let ast::FromItem::RelationFromItem(relation) = &from_item @@ -3104,15 +3101,18 @@ fn find_from_item_matching_qualifier( && let ast::FromItem::ParenFromItem(paren) = from_item && let Some(paren_expr) = paren.paren_expr() { - if let Some(join_expr) = paren_expr.join_expr() { - for inner in ast_nav::iter_join_expr(&join_expr) { - if let Some(found) = find_from_item_matching_qualifier(&inner, qualifier) { - return Some(found); + match paren_expr.from_list_item() { + Some(ast::FromListItem::JoinExpr(join_expr)) => { + for inner in ast_nav::iter_join_expr(&join_expr) { + if let Some(found) = find_from_item_matching_qualifier(&inner, qualifier) { + return Some(found); + } } } - } - if let Some(inner) = paren_expr.from_item() { - return find_from_item_matching_qualifier(&inner, qualifier); + Some(ast::FromListItem::FromItem(inner)) => { + return find_from_item_matching_qualifier(&inner, qualifier); + } + None => (), } } @@ -3139,7 +3139,10 @@ fn find_join_expr_by_using_alias( { return Some(join_expr.clone()); } - find_join_expr_by_using_alias(&join_expr.join_expr()?, qualifier) + let ast::FromListItem::JoinExpr(lhs) = join_expr.from_list_item()? else { + return None; + }; + find_join_expr_by_using_alias(&lhs, qualifier) } fn find_using_alias_join_expr_for_name_ref( @@ -3152,7 +3155,11 @@ fn find_using_alias_join_expr_for_name_ref( .find(|a| ast::Select::can_cast(a.kind()) || ast::SelectInto::can_cast(a.kind()))?; let from_clause = select_like_from_clause(&select)?; from_clause - .join_exprs() + .items() + .filter_map(|item| match item { + ast::FromListItem::JoinExpr(join_expr) => Some(join_expr), + ast::FromListItem::FromItem(_) => None, + }) .find_map(|join_expr| find_join_expr_by_using_alias(&join_expr, qualifier)) } @@ -4572,7 +4579,7 @@ fn table_ptr_from_paren_expr( ) -> Option { let file = paren_expr.file_id; let paren_expr = paren_expr.value; - if let Some(from_item) = paren_expr.from_item() { + if let Some(ast::FromListItem::FromItem(from_item)) = paren_expr.from_list_item() { return table_ptr_from_from_item(db, InFile::new(file, &from_item)); } if let Some(ast::Expr::ParenExpr(inner)) = paren_expr.expr() { @@ -4784,28 +4791,30 @@ fn resolve_column_from_paren_expr_with_skip( } } - if let Some(from_item) = paren_expr.from_item() { - return resolve_from_item_column_by_name_after_index( - db, - InFile::new(file, &from_item), - name_ref, - column_name, - skip_column_count, - ); - } - - if let Some(join_expr) = paren_expr.join_expr() { - for from_item in ast_nav::iter_join_expr(&join_expr) { - if let Some(ptr) = resolve_from_item_column_by_name_after_index( + match paren_expr.from_list_item() { + Some(ast::FromListItem::FromItem(from_item)) => { + return resolve_from_item_column_by_name_after_index( db, InFile::new(file, &from_item), name_ref, column_name, skip_column_count, - ) { - return Some(ptr); + ); + } + Some(ast::FromListItem::JoinExpr(join_expr)) => { + for from_item in ast_nav::iter_join_expr(&join_expr) { + if let Some(ptr) = resolve_from_item_column_by_name_after_index( + db, + InFile::new(file, &from_item), + name_ref, + column_name, + skip_column_count, + ) { + return Some(ptr); + } } } + None => (), } None @@ -5503,7 +5512,7 @@ fn resolve_delete_column_ptr( .find_map(ast::Delete::cast)?; if let Some(using_clause) = delete.using_clause() { - for from_item in using_clause.from_items() { + for from_item in ast_nav::iter_from_items(using_clause.items()) { if let Some(ptr) = resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) { @@ -5540,7 +5549,7 @@ fn resolve_delete_table_name_ptr( .find_map(ast::Delete::cast)?; if let Some(using_clause) = delete.using_clause() { - for from_item in using_clause.from_items() { + for from_item in ast_nav::iter_from_items(using_clause.items()) { if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) { if Name::from_node(&alias_name) == table_name { return Some(smallvec![Location::new( @@ -5604,7 +5613,7 @@ fn resolve_merge_column_ptr( if !is_set_target && !in_insert_column_list - && let Some(from_item) = merge.using_on_clause().and_then(|x| x.from_item()) + && let Some(from_item) = ast_nav::merge_using_from_item(&merge) && let Some(ptr) = resolve_from_item_column_ptr(db, InFile::new(file, &from_item), column_name_ref) { @@ -5685,7 +5694,7 @@ fn resolve_merge_table_name_ptr( // Check USING clause for the source table - MERGE-specific. // A source alias hides the underlying table name. - if let Some(from_item) = merge.using_on_clause().and_then(|x| x.from_item()) { + if let Some(from_item) = ast_nav::merge_using_from_item(&merge) { if let Some(alias_name) = from_item.alias().and_then(|alias| alias.name()) { if Name::from_node(&alias_name) == table_name { return Some(smallvec![Location::new( diff --git a/crates/squawk_linter/src/analyze.rs b/crates/squawk_linter/src/analyze.rs index fe97db2c4..c2a1a23f5 100644 --- a/crates/squawk_linter/src/analyze.rs +++ b/crates/squawk_linter/src/analyze.rs @@ -8,10 +8,14 @@ fn has_foreign_key_constraint(create_table: &ast::CreateTable) -> bool { return true; } ast::TableArg::Column(column) => { - if column - .constraints() - .any(|c| matches!(c, ast::ColumnConstraint::ReferencesConstraint(_))) - { + if column.clauses().any(|c| { + matches!( + c, + ast::ColumnClause::ColumnConstraint( + ast::ColumnConstraint::ReferencesConstraint(_) + ) + ) + }) { return true; } } diff --git a/crates/squawk_linter/src/rules/require_timeout_settings.rs b/crates/squawk_linter/src/rules/require_timeout_settings.rs index 1c83fe945..96ff4ac33 100644 --- a/crates/squawk_linter/src/rules/require_timeout_settings.rs +++ b/crates/squawk_linter/src/rules/require_timeout_settings.rs @@ -185,6 +185,7 @@ impl LockKind { }, ast::AlterTableAction::AddColumn(_) | ast::AlterTableAction::AlterConstraint(_) + | ast::AlterTableAction::AlterOptionList(_) | ast::AlterTableAction::AttachPartition(_) | ast::AlterTableAction::DisableRls(_) | ast::AlterTableAction::DisableRule(_) diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index b6aa88df9..60a99acc7 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -581,6 +581,7 @@ pub enum SyntaxKind { ADD_ATTRIBUTE, ADD_COLUMN, ADD_CONSTRAINT, + ADD_EDGE_TABLES, ADD_FOREIGN_OPTION, ADD_GENERATED, ADD_LABEL, @@ -592,6 +593,7 @@ pub enum SyntaxKind { ADD_VALUE, ADD_VERTEX_EDGE_LABEL_PROPERTIES, ADD_VERTEX_EDGE_TABLES, + ADD_VERTEX_TABLES, ADMIN_OPTION_FOR, AFTER_VALUE, AGGREGATE, @@ -679,6 +681,7 @@ pub enum SyntaxKind { AS_NAME, AS_OBJ_FILE, AS_POLICY_TYPE, + ATOMIC_BODY, ATTACH_INDEX_PARTITION, ATTACH_PARTITION, ATTRIBUTE_LIST, @@ -693,7 +696,6 @@ pub enum SyntaxKind { BASE_TYPE, BEFORE_VALUE, BEGIN, - BEGIN_FUNC_OPTION_LIST, BETWEEN_EXPR, BIND_PARAM_NAME_REF, BIN_EXPR, @@ -1590,7 +1592,7 @@ pub enum SyntaxKind { RETURNING_OLD, RETURNING_OPTION_LIST, RETURNS_NULL_ON_NULL_INPUT_FUNC_OPTION, - RETURN_FUNC_OPTION, + RETURN_STMT, RET_TYPE, REVOKE, REVOKE_COMMAND, @@ -1835,7 +1837,9 @@ pub enum SyntaxKind { UPDATE, UPDATE_TO, USER, + USER_MAPPING_ROLE, USING_CLAUSE, + USING_EXPR, USING_EXPR_CLAUSE, USING_INDEX, USING_INDEX_NAME, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index bd4e8238a..f052780cf 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -206,6 +206,9 @@ fn tuple_expr(p: &mut Parser<'_>) -> (CompletedMarker, ExprKind) { if !p.at(R_PAREN) { saw_comma = true; p.expect(COMMA); + if p.at(R_PAREN) { + p.error("expected expression after comma"); + } } } p.expect(R_PAREN); @@ -888,7 +891,7 @@ fn opt_json_table_column(p: &mut Parser<'_>) -> bool { // name type EXISTS [ PATH path_expression ] if p.eat(EXISTS_KW) { opt_json_path_clause(p); - opt_json_behavior_clause(p); + opt_json_on_error_clause(p); JSON_TABLE_EXISTS_COLUMN } else { // [ FORMAT JSON [ENCODING UTF8]] @@ -1011,7 +1014,6 @@ fn atom_expr(p: &mut Parser<'_>) -> Option<(CompletedMarker, ExprKind)> { expr(p); p.expect(AS_KW); type_name(p); - opt_collate(p); p.expect(R_PAREN); m.complete(p, CAST_EXPR) } @@ -1859,91 +1861,91 @@ fn postfix_expr(p: &mut Parser<'_>, mut lhs: CompletedMarker) -> CompletedMarker L_BRACK => index_expr(p, lhs), DOT => postfix_dot_expr(p, lhs), AT_KW if p.at(AT_LOCAL) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(AT_LOCAL); lhs = m.complete(p, POSTFIX_EXPR); break; } ISNULL_KW => { - let m = p.start(); + let m = lhs.precede(p); p.bump(ISNULL_KW); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_NORMALIZED) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_NORMALIZED); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NORMALIZED) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NORMALIZED); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_JSON_OBJECT) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_JSON_OBJECT); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_JSON_ARRAY) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_JSON_ARRAY); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_JSON_VALUE) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_JSON_VALUE); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_JSON_SCALAR) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_JSON_SCALAR); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_NOT_JSON) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_NOT_JSON); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_JSON_OBJECT) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_JSON_OBJECT); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_JSON_ARRAY) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_JSON_ARRAY); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_JSON_VALUE) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_JSON_VALUE); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_JSON_SCALAR) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_JSON_SCALAR); lhs = m.complete(p, POSTFIX_EXPR); break; } IS_KW if p.at(IS_JSON) => { - let m = p.start(); + let m = lhs.precede(p); p.bump(IS_JSON); lhs = m.complete(p, POSTFIX_EXPR); break; } NOTNULL_KW => { - let m = p.start(); + let m = lhs.precede(p); p.bump(NOTNULL_KW); lhs = m.complete(p, POSTFIX_EXPR); break; @@ -3790,14 +3792,14 @@ struct DataSource { paren_select: Option, } -fn data_source(p: &mut Parser<'_>) -> DataSource { +fn data_source(p: &mut Parser<'_>, in_parens: bool) -> DataSource { let only = p.eat(ONLY_KW); let lateral = p.eat(LATERAL_KW); let mut expr_kind = ExprKind::Other; let mut paren_select = None; let kind = match p.current() { L_PAREN => { - let cm = paren_data_source(p); + let cm = paren_data_source(p, in_parens); expr_kind = cm.as_ref().map_or(ExprKind::Other, |(_, kind)| *kind); let alias = opt_from_alias(p); if !only && !lateral && alias.is_none() { @@ -4022,11 +4024,14 @@ fn xml_namespace_prefix(p: &mut Parser<'_>) { m.complete(p, XML_NAMESPACE_PREFIX); } -fn paren_data_source(p: &mut Parser<'_>) -> Option<(CompletedMarker, ExprKind)> { +fn paren_data_source(p: &mut Parser<'_>, in_parens: bool) -> Option<(CompletedMarker, ExprKind)> { assert!(p.at(L_PAREN)); if p.at(L_PAREN) && p.nth_at_ts(1, SELECT_FIRST) { - return opt_paren_select(p, None, &SelectRestrictions::default()) - .map(|cm| (cm, ExprKind::Select)); + let r = SelectRestrictions { + trailing_clauses: in_parens, + ..SelectRestrictions::default() + }; + return opt_paren_select(p, None, &r).map(|cm| (cm, ExprKind::Select)); } let m = p.start(); p.bump(L_PAREN); @@ -4037,14 +4042,15 @@ fn paren_data_source(p: &mut Parser<'_>) -> Option<(CompletedMarker, ExprKind)> p.error("expected select before compound select operator"); } compound_select(p, lhs, &SelectRestrictions::default()); - } else if lhs_kind == ExprKind::Select { - opt_select_trailing_clauses(p); } p.expect(R_PAREN); let kind = match lhs_kind { ExprKind::Select => PAREN_SELECT, ExprKind::Other => PAREN_EXPR, }; + if in_parens && lhs_kind == ExprKind::Select { + opt_select_trailing_clauses(p); + } return Some((m.complete(p, kind), lhs_kind)); } else { p.error("expected table name or SELECT"); @@ -4118,7 +4124,7 @@ fn from_item(p: &mut Parser<'_>, in_parens: bool) -> Option<(CompletedMarker, Ex kind, mut expr_kind, paren_select, - } = data_source(p); + } = data_source(p, in_parens); if in_parens && let Some(select) = paren_select && !p.at_ts(JOIN_FIRST) @@ -7565,6 +7571,16 @@ fn opt_utility_option_arg(p: &mut Parser<'_>) -> bool { false } +fn opt_null_literal(p: &mut Parser<'_>) -> bool { + if !p.at(NULL_KW) { + return false; + } + let m = p.start(); + p.bump(NULL_KW); + m.complete(p, LITERAL); + true +} + fn opt_bool_literal(p: &mut Parser<'_>) -> bool { let m = p.start(); // TOOD: add validation to check for `1` or `0` inside the INT_NUMBER @@ -8183,7 +8199,7 @@ fn alter_routine(p: &mut Parser<'_>) -> CompletedMarker { depends_on_extension(p); } _ => { - func_option_list(p); + func_option_list(p, false); } } p.eat(RESTRICT_KW); @@ -8361,7 +8377,7 @@ fn alter_procedure(p: &mut Parser<'_>) -> CompletedMarker { depends_on_extension(p); } _ => { - func_option_list(p); + func_option_list(p, false); p.eat(RESTRICT_KW); } } @@ -8859,7 +8875,7 @@ fn alter_function(p: &mut Parser<'_>) -> CompletedMarker { depends_on_extension(p); } _ => { - func_option_list(p); + func_option_list(p, false); } } p.eat(RESTRICT_KW); @@ -11340,10 +11356,17 @@ fn drop_vertex_tables(p: &mut Parser<'_>) { fn add_vertex_edge_tables(p: &mut Parser<'_>) { assert!(p.at(ADD_KW)); let m = p.start(); - p.bump(ADD_KW); - let is_vertex = opt_vertex_tables(p); - if p.eat(ADD_KW) || !is_vertex { + if p.nth_at_ts(1, VERTEX) { + let m = p.start(); + p.bump(ADD_KW); + opt_vertex_tables(p); + m.complete(p, ADD_VERTEX_TABLES); + } + if p.at(ADD_KW) { + let m = p.start(); + p.bump(ADD_KW); opt_edge_tables(p); + m.complete(p, ADD_EDGE_TABLES); } m.complete(p, ADD_VERTEX_EDGE_TABLES); } @@ -12511,7 +12534,8 @@ fn create_procedure(p: &mut Parser<'_>) -> CompletedMarker { p.expect(PROCEDURE_KW); procedure_name(p); param_list(p, ParamKind::All); - func_option_list(p); + func_option_list(p, true); + opt_routine_body(p); p.eat(SEMICOLON); m.complete(p, CREATE_PROCEDURE) } @@ -13029,10 +13053,7 @@ fn create_user_mapping(p: &mut Parser<'_>) -> CompletedMarker { p.bump(MAPPING_KW); opt_if_not_exists(p); p.expect(FOR_KW); - // role | USER - if !p.eat(USER_KW) { - role_ref(p); - } + user_mapping_role(p); server_clause(p); opt_alter_option_list(p); p.eat(SEMICOLON); @@ -13823,10 +13844,7 @@ fn drop_user_mapping(p: &mut Parser<'_>) -> CompletedMarker { p.bump(MAPPING_KW); opt_if_exists(p); p.expect(FOR_KW); - // role | USER - if !p.eat(USER_KW) { - role_ref(p); - } + user_mapping_role(p); server_clause(p); p.eat(SEMICOLON); m.complete(p, DROP_USER_MAPPING) @@ -14742,6 +14760,16 @@ fn role_ref(p: &mut Parser<'_>) { } } +fn user_mapping_role(p: &mut Parser<'_>) { + let m = p.start(); + if p.at(USER_KW) { + p.bump(USER_KW); + } else { + role_ref(p); + } + m.complete(p, USER_MAPPING_ROLE); +} + fn opt_role_ref(p: &mut Parser<'_>) -> bool { opt_role_(p, ROLE_REF) } @@ -17605,29 +17633,6 @@ fn opt_function_option(p: &mut Parser<'_>) -> bool { m.complete(p, kind); AS_FUNC_OPTION } - // RETURN expression - RETURN_KW => { - p.bump(RETURN_KW); - if expr(p).is_none() { - p.error("expected expression for return"); - } - RETURN_FUNC_OPTION - } - // BEGIN ATOMIC - // statement; - // statement; - // ... - // statement; - // END - BEGIN_KW => { - p.bump(BEGIN_KW); - p.expect(ATOMIC_KW); - while !p.at(EOF) && !p.at(END_KW) { - begin_func_option(p); - } - p.expect(END_KW); - BEGIN_FUNC_OPTION_LIST - } _ => { m.abandon(p); return false; @@ -17643,7 +17648,7 @@ fn config_parameter_ref(p: &mut Parser<'_>) { m.complete(p, CONFIG_PARAMETER_REF); } -fn begin_func_option(p: &mut Parser<'_>) { +fn routine_body_stmt(p: &mut Parser<'_>) { if p.at(RETURN_KW) { let m = p.start(); p.bump(RETURN_KW); @@ -17651,7 +17656,7 @@ fn begin_func_option(p: &mut Parser<'_>) { p.error("expected expr") } p.expect(SEMICOLON); - m.complete(p, RETURN_FUNC_OPTION); + m.complete(p, RETURN_STMT); } else { stmt( p, @@ -17708,12 +17713,39 @@ fn opt_ret_type(p: &mut Parser<'_>) { } } -fn func_option_list(p: &mut Parser<'_>) { +fn opt_routine_body(p: &mut Parser<'_>) { + match p.current() { + RETURN_KW => { + let m = p.start(); + p.bump(RETURN_KW); + if expr(p).is_none() { + p.error("expected expression for return"); + } + m.complete(p, RETURN_STMT); + } + BEGIN_KW => { + let m = p.start(); + p.bump(BEGIN_KW); + p.expect(ATOMIC_KW); + while !p.at(EOF) && !p.at(END_KW) { + routine_body_stmt(p); + } + p.expect(END_KW); + m.complete(p, ATOMIC_BODY); + } + _ => (), + } +} + +const ROUTINE_BODY_FIRST: TokenSet = TokenSet::new(&[RETURN_KW, BEGIN_KW]); + +fn func_option_list(p: &mut Parser<'_>, allow_routine_body: bool) { let m = p.start(); let mut seen_func_option = false; while !p.at(EOF) { if !opt_function_option(p) { - if !seen_func_option { + let at_routine_body = allow_routine_body && p.at_ts(ROUTINE_BODY_FIRST); + if !seen_func_option && !at_routine_body { p.error("expected function option"); } break; @@ -17794,7 +17826,8 @@ fn create_function(p: &mut Parser<'_>) -> CompletedMarker { function_name(p); param_list(p, ParamKind::All); opt_ret_type(p); - func_option_list(p); + func_option_list(p, true); + opt_routine_body(p); p.eat(SEMICOLON); m.complete(p, CREATE_FUNCTION) } @@ -18011,7 +18044,7 @@ fn config_value(p: &mut Parser<'_>) -> bool { && opt_numeric_literal(p).is_none() && !opt_config_value_name(p) && !opt_bool_literal(p) - && !p.eat(NULL_KW) + && !opt_null_literal(p) { break; } @@ -18023,6 +18056,39 @@ fn config_value(p: &mut Parser<'_>) -> bool { found_value } +fn zone_value(p: &mut Parser<'_>) -> bool { + match p.current() { + DEFAULT_KW | LOCAL_KW => { + p.bump_any(); + true + } + INTERVAL_KW => { + interval_value(p); + true + } + _ => { + opt_string_literal(p).is_some() + || opt_numeric_literal(p).is_some() + || opt_config_value_name(p) + } + } +} + +fn interval_value(p: &mut Parser<'_>) -> CompletedMarker { + assert!(p.at(INTERVAL_KW)); + let m = p.start(); + let has_precision = { + let m = p.start(); + p.bump(INTERVAL_KW); + let has_precision = opt_interval_precision(p); + m.complete(p, INTERVAL_TYPE); + has_precision + }; + string_literal(p); + opt_interval_trailing(p, has_precision); + m.complete(p, CAST_EXPR) +} + // SET [ SESSION | LOCAL ] configuration_parameter { TO | = } { value | 'value' | DEFAULT } // SET [ SESSION | LOCAL ] TIME ZONE { value | 'value' | LOCAL | DEFAULT } // @@ -18046,8 +18112,8 @@ fn set(p: &mut Parser<'_>) -> CompletedMarker { let m = p.start(); p.bump(TIME_KW); p.expect(ZONE_KW); - if !p.eat(LOCAL_KW) && !config_value(p) { - p.error(format!("expected config value, got {:?}", p.current())); + if !zone_value(p) { + p.error(format!("expected time zone value, got {:?}", p.current())); } m.complete(p, SET_TIME_ZONE); } else if p.at(CATALOG_KW) || p.at(SCHEMA_KW) { @@ -18759,9 +18825,11 @@ const ATTRIBUTE_VALUE_RECOVERY: TokenSet = TokenSet::new(&[COMMA]).union(EXPR_RE fn def_arg(p: &mut Parser<'_>) { let m = p.start(); + let at_numeric = + p.at_ts(NUMERIC_FIRST) || ((p.at(MINUS) || p.at(PLUS)) && p.nth_at_ts(1, NUMERIC_FIRST)); if opt_bool_literal(p) || opt_string_literal(p).is_some() - || opt_numeric_literal(p).is_some() + || (at_numeric && opt_numeric_literal(p).is_some()) || opt_op_or_opcall(p) || p.eat(NONE_KW) { @@ -19004,8 +19072,13 @@ fn set_data_type(p: &mut Parser<'_>) { p.expect(TYPE_KW); type_name(p); opt_collate(p); - if p.eat(USING_KW) && expr(p).is_none() { - p.error("expected expression"); + if p.at(USING_KW) { + let m = p.start(); + p.bump(USING_KW); + if expr(p).is_none() { + p.error("expected expression"); + } + m.complete(p, USING_EXPR); } } diff --git a/crates/squawk_parser/tests/data/err/create_table.sql b/crates/squawk_parser/tests/data/err/create_table.sql index ab82e01cd..75b33e08d 100644 --- a/crates/squawk_parser/tests/data/err/create_table.sql +++ b/crates/squawk_parser/tests/data/err/create_table.sql @@ -79,3 +79,6 @@ create table t (a int, foreign key (a without overlaps) references u (c)); -- PERIOD must be the last column in a FOREIGN KEY column list create table t (a int, b daterange, foreign key (period b, a) references u (c, period d)); + +-- trailing comma in a parenthesized default expression +create table error_tbl (i int default (100, )); diff --git a/crates/squawk_parser/tests/data/err/routine_body.sql b/crates/squawk_parser/tests/data/err/routine_body.sql new file mode 100644 index 000000000..911e5737e --- /dev/null +++ b/crates/squawk_parser/tests/data/err/routine_body.sql @@ -0,0 +1,11 @@ +-- the routine body must come after the options +create function f() returns int return 1 language sql; +create function f() returns int begin atomic select 1; end language sql; + +-- only one routine body +create function f() returns int language sql return 1 return 2; + +-- alter never takes a routine body +alter function f() return 1; +alter procedure p() begin atomic select 1; end; +alter routine r() return 1; diff --git a/crates/squawk_parser/tests/data/err/select.sql b/crates/squawk_parser/tests/data/err/select.sql index c88e88410..7f80cfc80 100644 --- a/crates/squawk_parser/tests/data/err/select.sql +++ b/crates/squawk_parser/tests/data/err/select.sql @@ -156,3 +156,6 @@ select * from rows from (f() as (a)); -- trailing comma at EOF select 1, + +-- collate must follow cast +select cast('42' as text collate "C"); diff --git a/crates/squawk_parser/tests/data/err/set.sql b/crates/squawk_parser/tests/data/err/set.sql new file mode 100644 index 000000000..aa9a67857 --- /dev/null +++ b/crates/squawk_parser/tests/data/err/set.sql @@ -0,0 +1,8 @@ +-- zone_value is a single value, not a list +set time zone 'a', 'b'; + +-- zone_value doesn't allow arbitrary expressions +set time zone now(); + +-- zone_value doesn't accept NULL +set time zone null; diff --git a/crates/squawk_parser/tests/data/ok/alter_function.sql b/crates/squawk_parser/tests/data/ok/alter_function.sql index a6dc84037..c5e94c707 100644 --- a/crates/squawk_parser/tests/data/ok/alter_function.sql +++ b/crates/squawk_parser/tests/data/ok/alter_function.sql @@ -17,20 +17,10 @@ alter function f set schema s; -- multiple_actions alter function f called on null input - returns null on null input - strict immutable - stable - volatile not leakproof - leakproof external security invoker - security invoker - external security definer - security definer parallel unsafe - parallel restricted - parallel safe cost 100 rows 10 support f @@ -42,6 +32,26 @@ alter function f reset all restrict; +-- strict_variants +alter function f returns null on null input; +alter function f strict; + +-- volatility_variants +alter function f stable; +alter function f volatile; + +-- leakproof_variants +alter function f leakproof; + +-- security_variants +alter function f security invoker; +alter function f external security definer; +alter function f security definer; + +-- parallel_variants +alter function f parallel restricted; +alter function f parallel safe; + -- depends alter function f depends on extension e; alter function f no depends on extension e; diff --git a/crates/squawk_parser/tests/data/ok/alter_procedure.sql b/crates/squawk_parser/tests/data/ok/alter_procedure.sql index ddc17934e..86bec3d92 100644 --- a/crates/squawk_parser/tests/data/ok/alter_procedure.sql +++ b/crates/squawk_parser/tests/data/ok/alter_procedure.sql @@ -17,8 +17,14 @@ alter procedure p -- security alter procedure p security invoker + restrict; +alter procedure p external security invoker + restrict; +alter procedure p security definer + restrict; +alter procedure p external security definer restrict; diff --git a/crates/squawk_parser/tests/data/ok/alter_routine.sql b/crates/squawk_parser/tests/data/ok/alter_routine.sql index 870c0dbe1..9bba3043a 100644 --- a/crates/squawk_parser/tests/data/ok/alter_routine.sql +++ b/crates/squawk_parser/tests/data/ok/alter_routine.sql @@ -32,17 +32,9 @@ alter routine f(in a text, out b int) -- all_actions alter routine r immutable - stable - volatile not leakproof - leakproof external security invoker - security invoker - security definer - external security definer parallel unsafe - parallel restricted - parallel safe cost 10 rows 10 set c = 1 @@ -52,3 +44,19 @@ alter routine r reset all restrict; +-- volatility_variants +alter routine r stable; +alter routine r volatile; + +-- leakproof_variants +alter routine r leakproof; + +-- security_variants +alter routine r security invoker; +alter routine r security definer; +alter routine r external security definer; + +-- parallel_variants +alter routine r parallel restricted; +alter routine r parallel safe; + diff --git a/crates/squawk_parser/tests/data/ok/create_collation.sql b/crates/squawk_parser/tests/data/ok/create_collation.sql index 401a5f6af..0b03b7a81 100644 --- a/crates/squawk_parser/tests/data/ok/create_collation.sql +++ b/crates/squawk_parser/tests/data/ok/create_collation.sql @@ -4,6 +4,11 @@ create collation c from d; -- from_full create collation if not exists foo.c from bar.d; +-- from_option +create collation c ( + from = "C" +); + -- with_options create collation if not exists foo.c ( locale = 'foo', diff --git a/crates/squawk_parser/tests/data/ok/create_function.sql b/crates/squawk_parser/tests/data/ok/create_function.sql index a44562d58..f43211334 100644 --- a/crates/squawk_parser/tests/data/ok/create_function.sql +++ b/crates/squawk_parser/tests/data/ok/create_function.sql @@ -291,10 +291,14 @@ create function f() rows 1000 support foo.bar set a.b = 10 - as '' - as 'foo', 'bar' return (select 1); +-- as_with_two_strings +create function f() + returns void + language c + as 'foo', 'bar'; + -- regression create function foo(int8) returns int diff --git a/crates/squawk_parser/tests/data/ok/create_operator.sql b/crates/squawk_parser/tests/data/ok/create_operator.sql index 237b31abf..0d618acc2 100644 --- a/crates/squawk_parser/tests/data/ok/create_operator.sql +++ b/crates/squawk_parser/tests/data/ok/create_operator.sql @@ -21,3 +21,35 @@ create operator foo.bar.>>-# ( merges ); + +-- operator names as values +create operator + ( + commutator = +, + negator = -, + restrict = * +); + +create operator + ( + commutator = >, + negator = <, + restrict = >=, + join = <= +); + +create operator + ( + commutator = <>, + negator = !=, + restrict = /, + join = % +); + +create operator + ( + commutator = ^, + negator = -> +); + +-- qualified operator names +create operator + ( + commutator = operator(pg_catalog.+), + negator = operator(-) +); diff --git a/crates/squawk_parser/tests/data/ok/create_procedure.sql b/crates/squawk_parser/tests/data/ok/create_procedure.sql index fc0c32cac..1a2fc034a 100644 --- a/crates/squawk_parser/tests/data/ok/create_procedure.sql +++ b/crates/squawk_parser/tests/data/ok/create_procedure.sql @@ -11,16 +11,19 @@ create or replace procedure p( language sql transform for type foo.t, for type text external security invoker -security invoker -external security definer -security definer set foo to true set bar = false set buzz from current -as 'foo' -as 'foo', 'bar' return 10 + 1; +-- security_variants +create procedure p() language sql security invoker as 'foo'; +create procedure p() language sql external security definer as 'foo'; +create procedure p() language sql security definer as 'foo'; + +-- as_with_two_strings +create procedure p() language c as 'foo', 'bar'; + -- with_select_body create or replace procedure p() language sql diff --git a/crates/squawk_parser/tests/data/ok/create_text_search_config.sql b/crates/squawk_parser/tests/data/ok/create_text_search_config.sql index d964557c6..1996808b2 100644 --- a/crates/squawk_parser/tests/data/ok/create_text_search_config.sql +++ b/crates/squawk_parser/tests/data/ok/create_text_search_config.sql @@ -8,3 +8,8 @@ create text search configuration foo.name ( config = source_config ); +-- copy +create text search configuration foo.name ( + copy = other_config +); + diff --git a/crates/squawk_parser/tests/data/ok/create_type.sql b/crates/squawk_parser/tests/data/ok/create_type.sql index aee991be5..577dc95c1 100644 --- a/crates/squawk_parser/tests/data/ok/create_type.sql +++ b/crates/squawk_parser/tests/data/ok/create_type.sql @@ -21,6 +21,15 @@ create type t as range ( subtype = bigint ); +create type t as range ( + subtype = bigint, + subtype_opclass = o, + collation = "C", + canonical = c, + subtype_diff = d, + multirange_type_name = m +); + -- schema @@ -43,6 +52,7 @@ create type t ( typmod_in = type_modifier_input_function, typmod_out = type_modifier_output_function, analyze = analyze_function, + analyse = analyze_function, subscript = subscript_function, internallength = variable, passedbyvalue, diff --git a/crates/squawk_parser/tests/data/ok/schemas.sql b/crates/squawk_parser/tests/data/ok/schemas.sql index 02fa570db..911a04db0 100644 --- a/crates/squawk_parser/tests/data/ok/schemas.sql +++ b/crates/squawk_parser/tests/data/ok/schemas.sql @@ -54,6 +54,10 @@ set role foo; set time zone 'America/Los_Angeles'; set time zone default; set time zone local; +set time zone utc; +set time zone -8; +set time zone interval '-08:00' hour to minute; +set time zone interval(2) '-08:00'; set foo from current; set foo.bar from current; diff --git a/crates/squawk_parser/tests/data/ok/select.sql b/crates/squawk_parser/tests/data/ok/select.sql index 92343893a..199bed83b 100644 --- a/crates/squawk_parser/tests/data/ok/select.sql +++ b/crates/squawk_parser/tests/data/ok/select.sql @@ -536,3 +536,10 @@ from varchar_tbl; select (select ((((1)))) union select 1); select ((((select (1)))) union select 1); select (select (select 1) union select 1); + +-- trailing clauses after a from-item subquery belong to the outer select +select * from (select 1) limit 5; +select * from (select 1) t limit 5; + +-- but inside the parens they belong to the subquery +select * from ((select 1) limit 1) t; diff --git a/crates/squawk_parser/tests/data/ok/select_operators.sql b/crates/squawk_parser/tests/data/ok/select_operators.sql index 1404eddce..f6030324c 100644 --- a/crates/squawk_parser/tests/data/ok/select_operators.sql +++ b/crates/squawk_parser/tests/data/ok/select_operators.sql @@ -287,6 +287,9 @@ select 1 isnull; -- notnull (non-standard syntax) select 'foo' notnull; +-- postfix expression wrapping another expression +select null::json is json; + -- is true select true is true; diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_function_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_function_ok.snap index f8d2b82b0..8d5a7dff9 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_function_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_function_ok.snap @@ -171,37 +171,14 @@ SOURCE_FILE WHITESPACE " " INPUT_KW "input" WHITESPACE "\n " - RETURNS_NULL_ON_NULL_INPUT_FUNC_OPTION - RETURNS_KW "returns" - WHITESPACE " " - NULL_KW "null" - WHITESPACE " " - ON_KW "on" - WHITESPACE " " - NULL_KW "null" - WHITESPACE " " - INPUT_KW "input" - WHITESPACE "\n " - STRICT_FUNC_OPTION - STRICT_KW "strict" - WHITESPACE "\n " IMMUTABLE IMMUTABLE_KW "immutable" WHITESPACE "\n " - STABLE - STABLE_KW "stable" - WHITESPACE "\n " - VOLATILE - VOLATILE_KW "volatile" - WHITESPACE "\n " NOT_LEAKPROOF_FUNC_OPTION NOT_KW "not" WHITESPACE " " LEAKPROOF_KW "leakproof" WHITESPACE "\n " - LEAKPROOF_FUNC_OPTION - LEAKPROOF_KW "leakproof" - WHITESPACE "\n " SECURITY_INVOKER_FUNC_OPTION EXTERNAL_KW "external" WHITESPACE " " @@ -209,38 +186,11 @@ SOURCE_FILE WHITESPACE " " INVOKER_KW "invoker" WHITESPACE "\n " - SECURITY_INVOKER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - INVOKER_KW "invoker" - WHITESPACE "\n " - SECURITY_DEFINER_FUNC_OPTION - EXTERNAL_KW "external" - WHITESPACE " " - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n " - SECURITY_DEFINER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n " PARALLEL_FUNC_OPTION PARALLEL_KW "parallel" WHITESPACE " " IDENT "unsafe" WHITESPACE "\n " - PARALLEL_FUNC_OPTION - PARALLEL_KW "parallel" - WHITESPACE " " - IDENT "restricted" - WHITESPACE "\n " - PARALLEL_FUNC_OPTION - PARALLEL_KW "parallel" - WHITESPACE " " - IDENT "safe" - WHITESPACE "\n " COST_FUNC_OPTION COST_KW "cost" WHITESPACE " " @@ -335,6 +285,196 @@ SOURCE_FILE RESTRICT_KW "restrict" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- strict_variants" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + RETURNS_NULL_ON_NULL_INPUT_FUNC_OPTION + RETURNS_KW "returns" + WHITESPACE " " + NULL_KW "null" + WHITESPACE " " + ON_KW "on" + WHITESPACE " " + NULL_KW "null" + WHITESPACE " " + INPUT_KW "input" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + STRICT_FUNC_OPTION + STRICT_KW "strict" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- volatility_variants" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + STABLE + STABLE_KW "stable" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + VOLATILE + VOLATILE_KW "volatile" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- leakproof_variants" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + LEAKPROOF_FUNC_OPTION + LEAKPROOF_KW "leakproof" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- security_variants" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_INVOKER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + INVOKER_KW "invoker" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_DEFINER_FUNC_OPTION + EXTERNAL_KW "external" + WHITESPACE " " + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_DEFINER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- parallel_variants" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + PARALLEL_FUNC_OPTION + PARALLEL_KW "parallel" + WHITESPACE " " + IDENT "restricted" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + WHITESPACE " " + FUNC_OPTION_LIST + PARALLEL_FUNC_OPTION + PARALLEL_KW "parallel" + WHITESPACE " " + IDENT "safe" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- depends" WHITESPACE "\n" ALTER_FUNCTION diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_procedure_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_procedure_ok.snap index 4cc5b6a20..1e78e2716 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_procedure_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_procedure_ok.snap @@ -150,19 +150,64 @@ SOURCE_FILE SECURITY_KW "security" WHITESPACE " " INVOKER_KW "invoker" - WHITESPACE "\n " + WHITESPACE "\n " + RESTRICT_KW "restrict" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_PROCEDURE + ALTER_KW "alter" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_SIG + PROCEDURE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "p" + WHITESPACE "\n " + FUNC_OPTION_LIST SECURITY_INVOKER_FUNC_OPTION EXTERNAL_KW "external" WHITESPACE " " SECURITY_KW "security" WHITESPACE " " INVOKER_KW "invoker" - WHITESPACE "\n " + WHITESPACE "\n " + RESTRICT_KW "restrict" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_PROCEDURE + ALTER_KW "alter" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_SIG + PROCEDURE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "p" + WHITESPACE "\n " + FUNC_OPTION_LIST SECURITY_DEFINER_FUNC_OPTION SECURITY_KW "security" WHITESPACE " " DEFINER_KW "definer" - WHITESPACE "\n " + WHITESPACE "\n " + RESTRICT_KW "restrict" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_PROCEDURE + ALTER_KW "alter" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_SIG + PROCEDURE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "p" + WHITESPACE "\n " + FUNC_OPTION_LIST SECURITY_DEFINER_FUNC_OPTION EXTERNAL_KW "external" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_property_graph_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_property_graph_ok.snap index 1d0b83091..f47ea9991 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_property_graph_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_property_graph_ok.snap @@ -125,192 +125,194 @@ SOURCE_FILE IDENT "bar" WHITESPACE "\n " ADD_VERTEX_EDGE_TABLES - ADD_KW "add" - WHITESPACE " " - VERTEX_TABLES - VERTEX_KW "vertex" - WHITESPACE " " - TABLES_KW "tables" + ADD_VERTEX_TABLES + ADD_KW "add" WHITESPACE " " - L_PAREN "(" - WHITESPACE "\n " - VERTEX_TABLE_DEF - TABLE_NAME_REF - PATH_REF - PATH_SEGMENT_REF - IDENT "a" + VERTEX_TABLES + VERTEX_KW "vertex" WHITESPACE " " - ELEMENT_TABLE_KEY_CLAUSE - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "c" - COMMA "," - WHITESPACE " " - COLUMN_NAME_REF - IDENT "k" - R_PAREN ")" - COMMA "," - WHITESPACE "\n " - VERTEX_TABLE_DEF - TABLE_NAME_REF - PATH_REF - PATH_SEGMENT_REF - IDENT "d" + TABLES_KW "tables" WHITESPACE " " - ELEMENT_TABLE_KEY_CLAUSE - KEY_KW "key" + L_PAREN "(" + WHITESPACE "\n " + VERTEX_TABLE_DEF + TABLE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "a" WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "l" - COMMA "," + ELEMENT_TABLE_KEY_CLAUSE + KEY_KW "key" WHITESPACE " " - COLUMN_NAME_REF - IDENT "u" - R_PAREN ")" - WHITESPACE " \n " - NO_PROPERTIES - NO_KW "no" + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "c" + COMMA "," + WHITESPACE " " + COLUMN_NAME_REF + IDENT "k" + R_PAREN ")" + COMMA "," + WHITESPACE "\n " + VERTEX_TABLE_DEF + TABLE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "d" WHITESPACE " " - PROPERTIES_KW "properties" - WHITESPACE "\n " - R_PAREN ")" + ELEMENT_TABLE_KEY_CLAUSE + KEY_KW "key" + WHITESPACE " " + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "l" + COMMA "," + WHITESPACE " " + COLUMN_NAME_REF + IDENT "u" + R_PAREN ")" + WHITESPACE " \n " + NO_PROPERTIES + NO_KW "no" + WHITESPACE " " + PROPERTIES_KW "properties" + WHITESPACE "\n " + R_PAREN ")" WHITESPACE "\n " - ADD_KW "add" - WHITESPACE " " - EDGE_TABLES - EDGE_KW "edge" - WHITESPACE " " - TABLES_KW "tables" + ADD_EDGE_TABLES + ADD_KW "add" WHITESPACE " " - L_PAREN "(" - WHITESPACE "\n " - EDGE_TABLE_DEF - TABLE_NAME_REF - PATH_REF - PATH_SEGMENT_REF - IDENT "a" + EDGE_TABLES + EDGE_KW "edge" WHITESPACE " " - ELEMENT_TABLE_KEY_CLAUSE - KEY_KW "key" + TABLES_KW "tables" + WHITESPACE " " + L_PAREN "(" + WHITESPACE "\n " + EDGE_TABLE_DEF + TABLE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "a" WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "x" - COMMA "," + ELEMENT_TABLE_KEY_CLAUSE + KEY_KW "key" WHITESPACE " " - COLUMN_NAME_REF - IDENT "y" - R_PAREN ")" - WHITESPACE "\n " - SOURCE_VERTEX_TABLE - SOURCE_KW "source" - WHITESPACE " " - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "s" - R_PAREN ")" - WHITESPACE " " - REFERENCES_TABLE - REFERENCES_KW "references" + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "x" + COMMA "," + WHITESPACE " " + COLUMN_NAME_REF + IDENT "y" + R_PAREN ")" + WHITESPACE "\n " + SOURCE_VERTEX_TABLE + SOURCE_KW "source" WHITESPACE " " - VERTEX_TABLE_REF - IDENT "k" + KEY_KW "key" WHITESPACE " " COLUMN_REF_LIST L_PAREN "(" COLUMN_NAME_REF - IDENT "id" + IDENT "s" R_PAREN ")" - WHITESPACE "\n " - DEST_VERTEX_TABLE - DESTINATION_KW "destination" - WHITESPACE " " - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "d" - R_PAREN ")" - WHITESPACE " " - REFERENCES_TABLE - REFERENCES_KW "references" WHITESPACE " " - VERTEX_TABLE_REF - IDENT "k" + REFERENCES_TABLE + REFERENCES_KW "references" + WHITESPACE " " + VERTEX_TABLE_REF + IDENT "k" + WHITESPACE " " + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "id" + R_PAREN ")" + WHITESPACE "\n " + DEST_VERTEX_TABLE + DESTINATION_KW "destination" + WHITESPACE " " + KEY_KW "key" WHITESPACE " " COLUMN_REF_LIST L_PAREN "(" COLUMN_NAME_REF - IDENT "id" + IDENT "d" R_PAREN ")" - WHITESPACE "\n " - LABEL_AND_PROPERTIES_LIST - LABEL_AND_PROPERTIES - NAMED_LABEL - LABEL_KW "label" - WHITESPACE " " - LABEL - IDENT "q" WHITESPACE " " - PROPERTIES - PROPERTIES_KW "properties" + REFERENCES_TABLE + REFERENCES_KW "references" + WHITESPACE " " + VERTEX_TABLE_REF + IDENT "k" WHITESPACE " " - EXPR_AS_PROPERTY_NAME_LIST + COLUMN_REF_LIST L_PAREN "(" - EXPR_AS_PROPERTY_NAME - NAME_REF - IDENT "o" - COMMA "," + COLUMN_NAME_REF + IDENT "id" + R_PAREN ")" + WHITESPACE "\n " + LABEL_AND_PROPERTIES_LIST + LABEL_AND_PROPERTIES + NAMED_LABEL + LABEL_KW "label" + WHITESPACE " " + LABEL + IDENT "q" + WHITESPACE " " + PROPERTIES + PROPERTIES_KW "properties" WHITESPACE " " - EXPR_AS_PROPERTY_NAME - BIN_EXPR + EXPR_AS_PROPERTY_NAME_LIST + L_PAREN "(" + EXPR_AS_PROPERTY_NAME NAME_REF - IDENT "f" + IDENT "o" + COMMA "," + WHITESPACE " " + EXPR_AS_PROPERTY_NAME + BIN_EXPR + NAME_REF + IDENT "f" + WHITESPACE " " + STAR "*" + WHITESPACE " " + LITERAL + INT_NUMBER "10" WHITESPACE " " - STAR "*" + AS_KW "as" WHITESPACE " " - LITERAL - INT_NUMBER "10" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PROPERTY_NAME - IDENT "p" - R_PAREN ")" - WHITESPACE "\n " - LABEL_AND_PROPERTIES - NAMED_LABEL - LABEL_KW "label" - WHITESPACE " " - LABEL - IDENT "q" - WHITESPACE " " - PROPERTIES - PROPERTIES_KW "properties" + PROPERTY_NAME + IDENT "p" + R_PAREN ")" + WHITESPACE "\n " + LABEL_AND_PROPERTIES + NAMED_LABEL + LABEL_KW "label" + WHITESPACE " " + LABEL + IDENT "q" WHITESPACE " " - EXPR_AS_PROPERTY_NAME_LIST - L_PAREN "(" - EXPR_AS_PROPERTY_NAME - NAME_REF - IDENT "i" - WHITESPACE " " - AS_KW "as" - WHITESPACE " " - PROPERTY_NAME - IDENT "x" - R_PAREN ")" - WHITESPACE "\n " - R_PAREN ")" + PROPERTIES + PROPERTIES_KW "properties" + WHITESPACE " " + EXPR_AS_PROPERTY_NAME_LIST + L_PAREN "(" + EXPR_AS_PROPERTY_NAME + NAME_REF + IDENT "i" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PROPERTY_NAME + IDENT "x" + R_PAREN ")" + WHITESPACE "\n " + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- add vertex/edge tables part 2" @@ -333,114 +335,116 @@ SOURCE_FILE IDENT "g" WHITESPACE "\n " ADD_VERTEX_EDGE_TABLES - ADD_KW "add" - WHITESPACE " " - VERTEX_TABLES - VERTEX_KW "vertex" - WHITESPACE " " - TABLES_KW "tables" + ADD_VERTEX_TABLES + ADD_KW "add" WHITESPACE " " - L_PAREN "(" - WHITESPACE "\n " - VERTEX_TABLE_DEF - TABLE_NAME_REF - PATH_REF - PATH_SEGMENT_REF - IDENT "d" + VERTEX_TABLES + VERTEX_KW "vertex" WHITESPACE " " - ELEMENT_TABLE_KEY_CLAUSE - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "l" - R_PAREN ")" - WHITESPACE " \n " - ALL_PROPERTIES - PROPERTIES_KW "properties" - WHITESPACE " " - ALL_KW "all" + TABLES_KW "tables" + WHITESPACE " " + L_PAREN "(" + WHITESPACE "\n " + VERTEX_TABLE_DEF + TABLE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "d" WHITESPACE " " - COLUMNS_KW "columns" - WHITESPACE "\n " - R_PAREN ")" + ELEMENT_TABLE_KEY_CLAUSE + KEY_KW "key" + WHITESPACE " " + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "l" + R_PAREN ")" + WHITESPACE " \n " + ALL_PROPERTIES + PROPERTIES_KW "properties" + WHITESPACE " " + ALL_KW "all" + WHITESPACE " " + COLUMNS_KW "columns" + WHITESPACE "\n " + R_PAREN ")" WHITESPACE "\n " - ADD_KW "add" - WHITESPACE " " - EDGE_TABLES - EDGE_KW "edge" - WHITESPACE " " - TABLES_KW "tables" + ADD_EDGE_TABLES + ADD_KW "add" WHITESPACE " " - L_PAREN "(" - WHITESPACE "\n " - EDGE_TABLE_DEF - TABLE_NAME_REF - PATH_REF - PATH_SEGMENT_REF - IDENT "a" + EDGE_TABLES + EDGE_KW "edge" + WHITESPACE " " + TABLES_KW "tables" WHITESPACE " " - ELEMENT_TABLE_KEY_CLAUSE - KEY_KW "key" + L_PAREN "(" + WHITESPACE "\n " + EDGE_TABLE_DEF + TABLE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "a" WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "x" - COMMA "," + ELEMENT_TABLE_KEY_CLAUSE + KEY_KW "key" WHITESPACE " " - COLUMN_NAME_REF - IDENT "y" - R_PAREN ")" - WHITESPACE "\n " - SOURCE_VERTEX_TABLE - SOURCE_KW "source" - WHITESPACE " " - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "s" - R_PAREN ")" - WHITESPACE " " - REFERENCES_TABLE - REFERENCES_KW "references" + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "x" + COMMA "," + WHITESPACE " " + COLUMN_NAME_REF + IDENT "y" + R_PAREN ")" + WHITESPACE "\n " + SOURCE_VERTEX_TABLE + SOURCE_KW "source" WHITESPACE " " - VERTEX_TABLE_REF - IDENT "k" + KEY_KW "key" WHITESPACE " " COLUMN_REF_LIST L_PAREN "(" COLUMN_NAME_REF - IDENT "id" + IDENT "s" R_PAREN ")" - WHITESPACE "\n " - DEST_VERTEX_TABLE - DESTINATION_KW "destination" - WHITESPACE " " - KEY_KW "key" - WHITESPACE " " - COLUMN_REF_LIST - L_PAREN "(" - COLUMN_NAME_REF - IDENT "d" - R_PAREN ")" - WHITESPACE " " - REFERENCES_TABLE - REFERENCES_KW "references" WHITESPACE " " - VERTEX_TABLE_REF - IDENT "k" + REFERENCES_TABLE + REFERENCES_KW "references" + WHITESPACE " " + VERTEX_TABLE_REF + IDENT "k" + WHITESPACE " " + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "id" + R_PAREN ")" + WHITESPACE "\n " + DEST_VERTEX_TABLE + DESTINATION_KW "destination" + WHITESPACE " " + KEY_KW "key" WHITESPACE " " COLUMN_REF_LIST L_PAREN "(" COLUMN_NAME_REF - IDENT "id" + IDENT "d" R_PAREN ")" - WHITESPACE "\n " - R_PAREN ")" + WHITESPACE " " + REFERENCES_TABLE + REFERENCES_KW "references" + WHITESPACE " " + VERTEX_TABLE_REF + IDENT "k" + WHITESPACE " " + COLUMN_REF_LIST + L_PAREN "(" + COLUMN_NAME_REF + IDENT "id" + R_PAREN ")" + WHITESPACE "\n " + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- alter element tables" diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_routine_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_routine_ok.snap index a41716ba5..0402ce2d3 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_routine_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_routine_ok.snap @@ -227,20 +227,11 @@ SOURCE_FILE IMMUTABLE IMMUTABLE_KW "immutable" WHITESPACE "\n " - STABLE - STABLE_KW "stable" - WHITESPACE "\n " - VOLATILE - VOLATILE_KW "volatile" - WHITESPACE "\n " NOT_LEAKPROOF_FUNC_OPTION NOT_KW "not" WHITESPACE " " LEAKPROOF_KW "leakproof" WHITESPACE "\n " - LEAKPROOF_FUNC_OPTION - LEAKPROOF_KW "leakproof" - WHITESPACE "\n " SECURITY_INVOKER_FUNC_OPTION EXTERNAL_KW "external" WHITESPACE " " @@ -248,38 +239,11 @@ SOURCE_FILE WHITESPACE " " INVOKER_KW "invoker" WHITESPACE "\n " - SECURITY_INVOKER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - INVOKER_KW "invoker" - WHITESPACE "\n " - SECURITY_DEFINER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n " - SECURITY_DEFINER_FUNC_OPTION - EXTERNAL_KW "external" - WHITESPACE " " - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n " PARALLEL_FUNC_OPTION PARALLEL_KW "parallel" WHITESPACE " " IDENT "unsafe" WHITESPACE "\n " - PARALLEL_FUNC_OPTION - PARALLEL_KW "parallel" - WHITESPACE " " - IDENT "restricted" - WHITESPACE "\n " - PARALLEL_FUNC_OPTION - PARALLEL_KW "parallel" - WHITESPACE " " - IDENT "safe" - WHITESPACE "\n " COST_FUNC_OPTION COST_KW "cost" WHITESPACE " " @@ -352,3 +316,151 @@ SOURCE_FILE RESTRICT_KW "restrict" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- volatility_variants" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + STABLE + STABLE_KW "stable" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + VOLATILE + VOLATILE_KW "volatile" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- leakproof_variants" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + LEAKPROOF_FUNC_OPTION + LEAKPROOF_KW "leakproof" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- security_variants" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_INVOKER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + INVOKER_KW "invoker" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_DEFINER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + SECURITY_DEFINER_FUNC_OPTION + EXTERNAL_KW "external" + WHITESPACE " " + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- parallel_variants" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + PARALLEL_FUNC_OPTION + PARALLEL_KW "parallel" + WHITESPACE " " + IDENT "restricted" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + WHITESPACE " " + FUNC_OPTION_LIST + PARALLEL_FUNC_OPTION + PARALLEL_KW "parallel" + WHITESPACE " " + IDENT "safe" + SEMICOLON ";" + WHITESPACE "\n\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap index b72fd9d3c..78660f463 100644 --- a/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__alter_table_ok.snap @@ -329,16 +329,17 @@ SOURCE_FILE PATH_SEGMENT_REF IDENT "\"foo\"" WHITESPACE " " - USING_KW "using" - WHITESPACE " " - BIN_EXPR - NAME_REF - IDENT "a" - WHITESPACE " " - R_ANGLE ">" + USING_EXPR + USING_KW "using" WHITESPACE " " - NAME_REF - IDENT "b" + BIN_EXPR + NAME_REF + IDENT "a" + WHITESPACE " " + R_ANGLE ">" + WHITESPACE " " + NAME_REF + IDENT "b" SEMICOLON ";" WHITESPACE "\n" ALTER_TABLE @@ -2182,21 +2183,22 @@ SOURCE_FILE PATH_SEGMENT_REF IDENT "timestamptz" WHITESPACE " " - USING_KW "using" - WHITESPACE " " - BIN_EXPR - NAME_REF - IDENT "c" + USING_EXPR + USING_KW "using" WHITESPACE " " - AT_TIME_ZONE - AT_KW "at" + BIN_EXPR + NAME_REF + IDENT "c" WHITESPACE " " - TIME_KW "time" + AT_TIME_ZONE + AT_KW "at" + WHITESPACE " " + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" WHITESPACE " " - ZONE_KW "zone" - WHITESPACE " " - LITERAL - STRING "'UTC'" + LITERAL + STRING "'UTC'" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- remove_column" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_collation_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_collation_ok.snap index dfda29d62..c921a8b3c 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_collation_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_collation_ok.snap @@ -60,6 +60,36 @@ SOURCE_FILE IDENT "d" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- from_option" + WHITESPACE "\n" + CREATE_COLLATION + CREATE_KW "create" + WHITESPACE " " + COLLATION_KW "collation" + WHITESPACE " " + COLLATION + PATH + PATH_SEGMENT + IDENT "c" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + FROM_KW "from" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "\"C\"" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- with_options" WHITESPACE "\n" CREATE_COLLATION diff --git a/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap index 3590b7239..f43c247e1 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap @@ -1807,21 +1807,21 @@ SOURCE_FILE WHITESPACE " " LANGUAGE_REF SQL_KW "sql" - WHITESPACE "\n" - RETURN_FUNC_OPTION - RETURN_KW "return" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - R_PAREN ")" + WHITESPACE "\n" + RETURN_STMT + RETURN_KW "return" + WHITESPACE " " + PAREN_EXPR + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- begin atomic" @@ -1853,33 +1853,33 @@ SOURCE_FILE WHITESPACE " " LANGUAGE_REF SQL_KW "sql" + WHITESPACE "\n" + ATOMIC_BODY + BEGIN_KW "begin" + WHITESPACE " " + ATOMIC_KW "atomic" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "2" + SEMICOLON ";" WHITESPACE "\n" - BEGIN_FUNC_OPTION_LIST - BEGIN_KW "begin" - WHITESPACE " " - ATOMIC_KW "atomic" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - SEMICOLON ";" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "2" - SEMICOLON ";" - WHITESPACE "\n" - END_KW "end" + END_KW "end" SEMICOLON ";" WHITESPACE "\n\n" CREATE_FUNCTION @@ -1909,23 +1909,23 @@ SOURCE_FILE WHITESPACE " " LANGUAGE_REF SQL_KW "sql" - WHITESPACE "\n" - BEGIN_FUNC_OPTION_LIST - BEGIN_KW "begin" - WHITESPACE " " - ATOMIC_KW "atomic" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - SEMICOLON ";" - WHITESPACE "\n " - END_KW "end" + WHITESPACE "\n" + ATOMIC_BODY + BEGIN_KW "begin" + WHITESPACE " " + ATOMIC_KW "atomic" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n " + END_KW "end" SEMICOLON ";" WHITESPACE "\n" COMMIT_TRANSACTION @@ -1961,46 +1961,46 @@ SOURCE_FILE WHITESPACE " " LANGUAGE_REF SQL_KW "sql" + WHITESPACE "\n" + ATOMIC_BODY + BEGIN_KW "begin" + WHITESPACE " " + ATOMIC_KW "atomic" WHITESPACE "\n" - BEGIN_FUNC_OPTION_LIST - BEGIN_KW "begin" - WHITESPACE " " - ATOMIC_KW "atomic" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - EMPTY_STMT - SEMICOLON ";" - EMPTY_STMT - SEMICOLON ";" - WHITESPACE "\n" - END_KW "end" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + EMPTY_STMT + SEMICOLON ";" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + END_KW "end" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- all options" @@ -2114,13 +2114,52 @@ SOURCE_FILE WHITESPACE " " LITERAL INT_NUMBER "10" - WHITESPACE "\n " - AS_FUNC_OPTION - AS_KW "as" + WHITESPACE "\n " + RETURN_STMT + RETURN_KW "return" + WHITESPACE " " + PAREN_EXPR + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- as_with_two_strings" + WHITESPACE "\n" + CREATE_FUNCTION + CREATE_KW "create" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_NAME + PATH + PATH_SEGMENT + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE "\n " + RET_TYPE + RETURNS_KW "returns" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "void" + WHITESPACE "\n " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" WHITESPACE " " - AS_DEFINITION - LITERAL - STRING "''" + LANGUAGE_REF + IDENT "c" WHITESPACE "\n " AS_FUNC_OPTION AS_KW "as" @@ -2132,21 +2171,6 @@ SOURCE_FILE WHITESPACE " " LITERAL STRING "'bar'" - WHITESPACE "\n " - RETURN_FUNC_OPTION - RETURN_KW "return" - WHITESPACE " " - PAREN_EXPR - L_PAREN "(" - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- regression" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_operator_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_operator_ok.snap index e3a6e8438..31e05ab9c 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_operator_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_operator_ok.snap @@ -206,4 +206,241 @@ SOURCE_FILE WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n\n" + COMMENT "-- operator names as values" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PLUS "+" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "negator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + MINUS "-" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + RESTRICT_KW "restrict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + STAR "*" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + R_ANGLE ">" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "negator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + L_ANGLE "<" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + RESTRICT_KW "restrict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + GTEQ ">=" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + JOIN_KW "join" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + LTEQ "<=" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + NEQB "<>" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "negator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + NEQ "!=" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + RESTRICT_KW "restrict" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + SLASH "/" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + JOIN_KW "join" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PERCENT "%" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + CARET "^" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "negator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + CUSTOM_OP + MINUS "-" + R_ANGLE ">" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- qualified operator names" + WHITESPACE "\n" + CREATE_OPERATOR + CREATE_KW "create" + WHITESPACE " " + OPERATOR_KW "operator" + WHITESPACE " " + OP + PLUS "+" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "commutator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "pg_catalog" + DOT "." + PATH_SEGMENT_REF + PLUS "+" + R_PAREN ")" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "negator" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + OPERATOR_CALL + OPERATOR_KW "operator" + L_PAREN "(" + OP + MINUS "-" + R_PAREN ")" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_procedure_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_procedure_ok.snap index 1fbfdefcc..28696b641 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_procedure_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_procedure_ok.snap @@ -139,23 +139,6 @@ SOURCE_FILE WHITESPACE " " INVOKER_KW "invoker" WHITESPACE "\n" - SECURITY_INVOKER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - INVOKER_KW "invoker" - WHITESPACE "\n" - SECURITY_DEFINER_FUNC_OPTION - EXTERNAL_KW "external" - WHITESPACE " " - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n" - SECURITY_DEFINER_FUNC_OPTION - SECURITY_KW "security" - WHITESPACE " " - DEFINER_KW "definer" - WHITESPACE "\n" SET_FUNC_OPTION SET_CONFIG_PARAM SET_KW "set" @@ -199,36 +182,155 @@ SOURCE_FILE FROM_KW "from" WHITESPACE " " CURRENT_KW "current" - WHITESPACE "\n" + WHITESPACE "\n" + RETURN_STMT + RETURN_KW "return" + WHITESPACE " " + BIN_EXPR + LITERAL + INT_NUMBER "10" + WHITESPACE " " + PLUS "+" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- security_variants" + WHITESPACE "\n" + CREATE_PROCEDURE + CREATE_KW "create" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_NAME + PATH + PATH_SEGMENT + IDENT "p" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" + WHITESPACE " " + LANGUAGE_REF + SQL_KW "sql" + WHITESPACE " " + SECURITY_INVOKER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + INVOKER_KW "invoker" + WHITESPACE " " AS_FUNC_OPTION AS_KW "as" WHITESPACE " " AS_DEFINITION LITERAL STRING "'foo'" - WHITESPACE "\n" + SEMICOLON ";" + WHITESPACE "\n" + CREATE_PROCEDURE + CREATE_KW "create" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_NAME + PATH + PATH_SEGMENT + IDENT "p" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" + WHITESPACE " " + LANGUAGE_REF + SQL_KW "sql" + WHITESPACE " " + SECURITY_DEFINER_FUNC_OPTION + EXTERNAL_KW "external" + WHITESPACE " " + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + WHITESPACE " " AS_FUNC_OPTION AS_KW "as" WHITESPACE " " - AS_OBJ_FILE + AS_DEFINITION LITERAL STRING "'foo'" - COMMA "," - WHITESPACE " " + SEMICOLON ";" + WHITESPACE "\n" + CREATE_PROCEDURE + CREATE_KW "create" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_NAME + PATH + PATH_SEGMENT + IDENT "p" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" + WHITESPACE " " + LANGUAGE_REF + SQL_KW "sql" + WHITESPACE " " + SECURITY_DEFINER_FUNC_OPTION + SECURITY_KW "security" + WHITESPACE " " + DEFINER_KW "definer" + WHITESPACE " " + AS_FUNC_OPTION + AS_KW "as" + WHITESPACE " " + AS_DEFINITION LITERAL - STRING "'bar'" - WHITESPACE "\n" - RETURN_FUNC_OPTION - RETURN_KW "return" + STRING "'foo'" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- as_with_two_strings" + WHITESPACE "\n" + CREATE_PROCEDURE + CREATE_KW "create" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_NAME + PATH + PATH_SEGMENT + IDENT "p" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" WHITESPACE " " - BIN_EXPR + LANGUAGE_REF + IDENT "c" + WHITESPACE " " + AS_FUNC_OPTION + AS_KW "as" + WHITESPACE " " + AS_OBJ_FILE LITERAL - INT_NUMBER "10" - WHITESPACE " " - PLUS "+" + STRING "'foo'" + COMMA "," WHITESPACE " " LITERAL - INT_NUMBER "1" + STRING "'bar'" SEMICOLON ";" WHITESPACE "\n\n" COMMENT "-- with_select_body" @@ -257,32 +359,32 @@ SOURCE_FILE WHITESPACE " " LANGUAGE_REF SQL_KW "sql" + WHITESPACE "\n" + ATOMIC_BODY + BEGIN_KW "begin" + WHITESPACE " " + ATOMIC_KW "atomic" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE "\n " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "2" + SEMICOLON ";" WHITESPACE "\n" - BEGIN_FUNC_OPTION_LIST - BEGIN_KW "begin" - WHITESPACE " " - ATOMIC_KW "atomic" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "1" - SEMICOLON ";" - WHITESPACE "\n " - SELECT - SELECT_CLAUSE - SELECT_KW "select" - WHITESPACE " " - TARGET_LIST - TARGET - LITERAL - INT_NUMBER "2" - SEMICOLON ";" - WHITESPACE "\n" - END_KW "end" + END_KW "end" SEMICOLON ";" WHITESPACE "\n\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap b/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap index d3a137ed2..53f766b71 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_table_err.snap @@ -838,6 +838,42 @@ SOURCE_FILE R_PAREN ")" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- trailing comma in a parenthesized default expression" + WHITESPACE "\n" + CREATE_TABLE + CREATE_KW "create" + WHITESPACE " " + TABLE_KW "table" + WHITESPACE " " + TABLE_NAME + PATH + PATH_SEGMENT + IDENT "error_tbl" + WHITESPACE " " + TABLE_ARG_LIST + L_PAREN "(" + COLUMN + COLUMN_NAME + IDENT "i" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + WHITESPACE " " + DEFAULT_CONSTRAINT + DEFAULT_KW "default" + WHITESPACE " " + TUPLE_EXPR + L_PAREN "(" + LITERAL + INT_NUMBER "100" + COMMA "," + WHITESPACE " " + R_PAREN ")" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n" --- error[syntax-error]: expected path name @@ -904,3 +940,7 @@ error[syntax-error]: PERIOD must be the last column in the list ╭▸ 81 │ create table t (a int, b daterange, foreign key (period b, a) references u (c, period d)); ╰╴ ━ +error[syntax-error]: expected expression after comma + ╭▸ +84 │ create table error_tbl (i int default (100, )); + ╰╴ ━ diff --git a/crates/squawk_parser/tests/snapshots/tests__create_text_search_config_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_text_search_config_ok.snap index e11c4187f..f301ba56a 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_text_search_config_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_text_search_config_ok.snap @@ -75,3 +75,41 @@ SOURCE_FILE R_PAREN ")" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- copy" + WHITESPACE "\n" + CREATE_TEXT_SEARCH_CONFIGURATION + CREATE_KW "create" + WHITESPACE " " + TEXT_KW "text" + WHITESPACE " " + SEARCH_KW "search" + WHITESPACE " " + CONFIGURATION_KW "configuration" + WHITESPACE " " + TEXT_SEARCH_CONFIGURATION + PATH + PATH_REF + PATH_SEGMENT_REF + IDENT "foo" + DOT "." + PATH_SEGMENT + NAME_KW "name" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + COPY_KW "copy" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "other_config" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__create_type_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_type_ok.snap index d15251113..2a9e6de3f 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_type_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_type_ok.snap @@ -227,6 +227,104 @@ SOURCE_FILE WHITESPACE "\n" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n" + CREATE_TYPE + CREATE_KW "create" + WHITESPACE " " + TYPE_KW "type" + WHITESPACE " " + TYPE_NAME + PATH + PATH_SEGMENT + IDENT "t" + WHITESPACE " " + RANGE_TYPE + AS_KW "as" + WHITESPACE " " + RANGE_KW "range" + WHITESPACE " " + ATTRIBUTE_LIST + L_PAREN "(" + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "subtype" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + BIGINT_KW "bigint" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "subtype_opclass" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "o" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + COLLATION_KW "collation" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "\"C\"" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "canonical" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "c" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "subtype_diff" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "d" + COMMA "," + WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + IDENT "multirange_type_name" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "m" + WHITESPACE "\n" + R_PAREN ")" + SEMICOLON ";" WHITESPACE "\n\n\n\n" COMMENT "-- schema" WHITESPACE "\n" @@ -444,6 +542,19 @@ SOURCE_FILE IDENT "analyze_function" COMMA "," WHITESPACE "\n " + ATTRIBUTE_OPTION + ATTRIBUTE_NAME + ANALYSE_KW "analyse" + WHITESPACE " " + EQ "=" + WHITESPACE " " + ATTRIBUTE_VALUE + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + IDENT "analyze_function" + COMMA "," + WHITESPACE "\n " ATTRIBUTE_OPTION ATTRIBUTE_NAME IDENT "subscript" diff --git a/crates/squawk_parser/tests/snapshots/tests__drop_user_mapping_ok.snap b/crates/squawk_parser/tests/snapshots/tests__drop_user_mapping_ok.snap index 9f536bdba..7450f717d 100644 --- a/crates/squawk_parser/tests/snapshots/tests__drop_user_mapping_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__drop_user_mapping_ok.snap @@ -14,7 +14,8 @@ SOURCE_FILE WHITESPACE " " FOR_KW "for" WHITESPACE " " - USER_KW "user" + USER_MAPPING_ROLE + USER_KW "user" WHITESPACE " " SERVER_CLAUSE SERVER_KW "server" @@ -39,8 +40,9 @@ SOURCE_FILE WHITESPACE " " FOR_KW "for" WHITESPACE " " - ROLE_REF - CURRENT_ROLE_KW "current_role" + USER_MAPPING_ROLE + ROLE_REF + CURRENT_ROLE_KW "current_role" WHITESPACE " " SERVER_CLAUSE SERVER_KW "server" diff --git a/crates/squawk_parser/tests/snapshots/tests__routine_body_err.snap b/crates/squawk_parser/tests/snapshots/tests__routine_body_err.snap new file mode 100644 index 000000000..f20c24b40 --- /dev/null +++ b/crates/squawk_parser/tests/snapshots/tests__routine_body_err.snap @@ -0,0 +1,269 @@ +--- +source: crates/squawk_parser/tests/tests.rs +input_file: crates/squawk_parser/tests/data/err/routine_body.sql +--- +SOURCE_FILE + COMMENT "-- the routine body must come after the options" + WHITESPACE "\n" + CREATE_FUNCTION + CREATE_KW "create" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_NAME + PATH + PATH_SEGMENT + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + RETURNS_KW "returns" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + WHITESPACE " " + RETURN_STMT + RETURN_KW "return" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + ERROR + LANGUAGE_KW "language" + WHITESPACE " " + ERROR + SQL_KW "sql" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + CREATE_FUNCTION + CREATE_KW "create" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_NAME + PATH + PATH_SEGMENT + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + RETURNS_KW "returns" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + WHITESPACE " " + ATOMIC_BODY + BEGIN_KW "begin" + WHITESPACE " " + ATOMIC_KW "atomic" + WHITESPACE " " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + END_KW "end" + WHITESPACE " " + ERROR + LANGUAGE_KW "language" + WHITESPACE " " + ERROR + SQL_KW "sql" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- only one routine body" + WHITESPACE "\n" + CREATE_FUNCTION + CREATE_KW "create" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_NAME + PATH + PATH_SEGMENT + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + RET_TYPE + RETURNS_KW "returns" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + INT_KW "int" + WHITESPACE " " + FUNC_OPTION_LIST + LANGUAGE_FUNC_OPTION + LANGUAGE_KW "language" + WHITESPACE " " + LANGUAGE_REF + SQL_KW "sql" + WHITESPACE " " + RETURN_STMT + RETURN_KW "return" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + WHITESPACE " " + ERROR + RETURN_KW "return" + WHITESPACE " " + ERROR + INT_NUMBER "2" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- alter never takes a routine body" + WHITESPACE "\n" + ALTER_FUNCTION + ALTER_KW "alter" + WHITESPACE " " + FUNCTION_KW "function" + WHITESPACE " " + FUNCTION_SIG + FUNCTION_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "f" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + ERROR + RETURN_KW "return" + WHITESPACE " " + ERROR + INT_NUMBER "1" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" + ALTER_PROCEDURE + ALTER_KW "alter" + WHITESPACE " " + PROCEDURE_KW "procedure" + WHITESPACE " " + PROCEDURE_SIG + PROCEDURE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "p" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + BEGIN + BEGIN_KW "begin" + WHITESPACE " " + ERROR + ATOMIC_KW "atomic" + WHITESPACE " " + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + SEMICOLON ";" + WHITESPACE " " + COMMIT_TRANSACTION + END_KW "end" + SEMICOLON ";" + WHITESPACE "\n" + ALTER_ROUTINE + ALTER_KW "alter" + WHITESPACE " " + ROUTINE_KW "routine" + WHITESPACE " " + ROUTINE_SIG + ROUTINE_NAME_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "r" + PARAM_LIST + L_PAREN "(" + R_PAREN ")" + WHITESPACE " " + ERROR + RETURN_KW "return" + WHITESPACE " " + ERROR + INT_NUMBER "1" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" +--- +error[syntax-error]: expected command, found LANGUAGE_KW + ╭▸ +2 │ create function f() returns int return 1 language sql; + ╰╴ ━ +error[syntax-error]: expected command, found SQL_KW + ╭▸ +2 │ create function f() returns int return 1 language sql; + ╰╴ ━ +error[syntax-error]: expected command, found LANGUAGE_KW + ╭▸ +3 │ create function f() returns int begin atomic select 1; end language sql; + ╰╴ ━ +error[syntax-error]: expected command, found SQL_KW + ╭▸ +3 │ create function f() returns int begin atomic select 1; end language sql; + ╰╴ ━ +error[syntax-error]: expected command, found RETURN_KW + ╭▸ +6 │ create function f() returns int language sql return 1 return 2; + ╰╴ ━ +error[syntax-error]: expected command, found INT_NUMBER + ╭▸ +6 │ create function f() returns int language sql return 1 return 2; + ╰╴ ━ +error[syntax-error]: expected function option + ╭▸ +9 │ alter function f() return 1; + ╰╴ ━ +error[syntax-error]: expected command, found RETURN_KW + ╭▸ +9 │ alter function f() return 1; + ╰╴ ━ +error[syntax-error]: expected command, found INT_NUMBER + ╭▸ +9 │ alter function f() return 1; + ╰╴ ━ +error[syntax-error]: expected function option + ╭▸ +10 │ alter procedure p() begin atomic select 1; end; + ╰╴ ━ +error[syntax-error]: expected command, found ATOMIC_KW + ╭▸ +10 │ alter procedure p() begin atomic select 1; end; + ╰╴ ━ +error[syntax-error]: expected function option + ╭▸ +11 │ alter routine r() return 1; + ╰╴ ━ +error[syntax-error]: expected command, found RETURN_KW + ╭▸ +11 │ alter routine r() return 1; + ╰╴ ━ +error[syntax-error]: expected command, found INT_NUMBER + ╭▸ +11 │ alter routine r() return 1; + ╰╴ ━ diff --git a/crates/squawk_parser/tests/snapshots/tests__schemas_ok.snap b/crates/squawk_parser/tests/snapshots/tests__schemas_ok.snap index 82de824df..068a46d15 100644 --- a/crates/squawk_parser/tests/snapshots/tests__schemas_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__schemas_ok.snap @@ -522,6 +522,75 @@ SOURCE_FILE WHITESPACE " " LOCAL_KW "local" SEMICOLON ";" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + CONFIG_VALUE_NAME + IDENT "utc" + SEMICOLON ";" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + PREFIX_EXPR + MINUS "-" + LITERAL + INT_NUMBER "8" + SEMICOLON ";" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + CAST_EXPR + INTERVAL_TYPE + INTERVAL_KW "interval" + WHITESPACE " " + LITERAL + STRING "'-08:00'" + WHITESPACE " " + INTERVAL_MINUTE + HOUR_KW "hour" + WHITESPACE " " + TO_KW "to" + WHITESPACE " " + MINUTE_KW "minute" + SEMICOLON ";" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + CAST_EXPR + INTERVAL_TYPE + INTERVAL_KW "interval" + L_PAREN "(" + LITERAL + INT_NUMBER "2" + R_PAREN ")" + WHITESPACE " " + LITERAL + STRING "'-08:00'" + SEMICOLON ";" WHITESPACE "\n\n" SET SET_KW "set" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap index 4f5d96050..cf0082f66 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_compound_union_select_ok.snap @@ -614,12 +614,12 @@ SOURCE_FILE LITERAL INT_NUMBER "2" R_PAREN ")" - WHITESPACE " " - LIMIT_CLAUSE - LIMIT_KW "limit" WHITESPACE " " - LITERAL - INT_NUMBER "1" + LIMIT_CLAUSE + LIMIT_KW "limit" + WHITESPACE " " + LITERAL + INT_NUMBER "1" R_PAREN ")" WHITESPACE " " FROM_ALIAS diff --git a/crates/squawk_parser/tests/snapshots/tests__select_err.snap b/crates/squawk_parser/tests/snapshots/tests__select_err.snap index 0b8b2c794..a19bf5321 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_err.snap @@ -1732,6 +1732,39 @@ SOURCE_FILE INT_NUMBER "1" ERROR COMMA "," + WHITESPACE "\n\n" + COMMENT "-- collate must follow cast" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + COLLATE + CAST_EXPR + CAST_KW "cast" + L_PAREN "(" + LITERAL + STRING "'42'" + WHITESPACE " " + AS_KW "as" + WHITESPACE " " + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + TEXT_KW "text" + WHITESPACE " " + COLLATE_KW "collate" + WHITESPACE " " + COLLATION_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "\"C\"" + ERROR + R_PAREN ")" + EMPTY_STMT + SEMICOLON ";" WHITESPACE "\n" --- error[syntax-error]: unexpected trailing comma @@ -1966,3 +1999,11 @@ error[syntax-error]: unexpected trailing comma ╭▸ 158 │ select 1, ╰╴ ━ +error[syntax-error]: expected R_PAREN + ╭▸ +161 │ select cast('42' as text collate "C"); + ╰╴ ━ +error[syntax-error]: expected command, found R_PAREN + ╭▸ +161 │ select cast('42' as text collate "C"); + ╰╴ ━ diff --git a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap index 409e6d1e6..2786f88a7 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_ok.snap @@ -7035,4 +7035,112 @@ SOURCE_FILE INT_NUMBER "1" R_PAREN ")" SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- trailing clauses after a from-item subquery belong to the outer select" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + PAREN_FROM_ITEM + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + LIMIT_CLAUSE + LIMIT_KW "limit" + WHITESPACE " " + LITERAL + INT_NUMBER "5" + SEMICOLON ";" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + PAREN_FROM_ITEM + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + FROM_ALIAS + TABLE_ALIAS + IDENT "t" + WHITESPACE " " + LIMIT_CLAUSE + LIMIT_KW "limit" + WHITESPACE " " + LITERAL + INT_NUMBER "5" + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- but inside the parens they belong to the subquery" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + STAR "*" + WHITESPACE " " + FROM_CLAUSE + FROM_KW "from" + WHITESPACE " " + PAREN_FROM_ITEM + PAREN_SELECT + L_PAREN "(" + PAREN_SELECT + L_PAREN "(" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + LIMIT_CLAUSE + LIMIT_KW "limit" + WHITESPACE " " + LITERAL + INT_NUMBER "1" + R_PAREN ")" + WHITESPACE " " + FROM_ALIAS + TABLE_ALIAS + IDENT "t" + SEMICOLON ";" WHITESPACE "\n" diff --git a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap index efd0cda64..183e0ebd1 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_operators_ok.snap @@ -2739,6 +2739,32 @@ SOURCE_FILE NOTNULL_KW "notnull" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- postfix expression wrapping another expression" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + POSTFIX_EXPR + CAST_EXPR + LITERAL + NULL_KW "null" + COLON_COLON + COLON ":" + COLON ":" + PATH_TYPE + PATH_REF + PATH_SEGMENT_REF + JSON_KW "json" + WHITESPACE " " + IS_JSON + IS_KW "is" + WHITESPACE " " + JSON_KW "json" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- is true" WHITESPACE "\n" SELECT diff --git a/crates/squawk_parser/tests/snapshots/tests__set_err.snap b/crates/squawk_parser/tests/snapshots/tests__set_err.snap new file mode 100644 index 000000000..751bc5e83 --- /dev/null +++ b/crates/squawk_parser/tests/snapshots/tests__set_err.snap @@ -0,0 +1,84 @@ +--- +source: crates/squawk_parser/tests/tests.rs +input_file: crates/squawk_parser/tests/data/err/set.sql +--- +SOURCE_FILE + COMMENT "-- zone_value is a single value, not a list" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + LITERAL + STRING "'a'" + ERROR + COMMA "," + WHITESPACE " " + ERROR + STRING "'b'" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- zone_value doesn't allow arbitrary expressions" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + CONFIG_VALUE_NAME + IDENT "now" + ERROR + L_PAREN "(" + ERROR + R_PAREN ")" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n\n" + COMMENT "-- zone_value doesn't accept NULL" + WHITESPACE "\n" + SET + SET_KW "set" + WHITESPACE " " + SET_TIME_ZONE + TIME_KW "time" + WHITESPACE " " + ZONE_KW "zone" + WHITESPACE " " + ERROR + NULL_KW "null" + EMPTY_STMT + SEMICOLON ";" + WHITESPACE "\n" +--- +error[syntax-error]: expected command, found COMMA + ╭▸ +2 │ set time zone 'a', 'b'; + ╰╴ ━ +error[syntax-error]: expected command, found STRING + ╭▸ +2 │ set time zone 'a', 'b'; + ╰╴ ━ +error[syntax-error]: expected command, found L_PAREN + ╭▸ +5 │ set time zone now(); + ╰╴ ━ +error[syntax-error]: expected command, found R_PAREN + ╭▸ +5 │ set time zone now(); + ╰╴ ━ +error[syntax-error]: expected time zone value, got NULL_KW + ╭▸ +8 │ set time zone null; + ╰╴ ━ +error[syntax-error]: expected command, found NULL_KW + ╭▸ +8 │ set time zone null; + ╰╴ ━ diff --git a/crates/squawk_syntax/src/ast.rs b/crates/squawk_syntax/src/ast.rs index 48a9c6efb..74a324690 100644 --- a/crates/squawk_syntax/src/ast.rs +++ b/crates/squawk_syntax/src/ast.rs @@ -41,7 +41,7 @@ pub use self::{ generated::tokens::*, node_ext::{BinOp, LitKind, PostfixOp, PrefixOp}, nodes::*, - traits::{HasCreateTable, HasPathRef, HasWithClause, NameLike}, + traits::{HasCreateTable, HasPathRef, HasSelectTail, HasWithClause, NameLike}, }; /// The main trait to go from untyped `SyntaxNode` to a typed ast. The diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index 6e9d3cc7b..555270676 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -181,6 +181,21 @@ impl AddConstraint { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AddEdgeTables { + pub(crate) syntax: SyntaxNode, +} +impl AddEdgeTables { + #[inline] + pub fn edge_tables(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn add_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::ADD_KW) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AddForeignOption { pub(crate) syntax: SyntaxNode, @@ -445,9 +460,20 @@ pub struct AddVertexEdgeTables { } impl AddVertexEdgeTables { #[inline] - pub fn edge_tables(&self) -> Option { + pub fn add_edge_tables(&self) -> Option { support::child(&self.syntax) } + #[inline] + pub fn add_vertex_tables(&self) -> Option { + support::child(&self.syntax) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AddVertexTables { + pub(crate) syntax: SyntaxNode, +} +impl AddVertexTables { #[inline] pub fn vertex_tables(&self) -> Option { support::child(&self.syntax) @@ -827,24 +853,8 @@ impl AlterConstraint { support::child(&self.syntax) } #[inline] - pub fn deferrable_constraint_option(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn initially_deferred_constraint_option( - &self, - ) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn initially_immediate_constraint_option( - &self, - ) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn not_deferrable_constraint_option(&self) -> Option { - support::child(&self.syntax) + pub fn constraint_options(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] pub fn alter_token(&self) -> Option { @@ -2548,7 +2558,7 @@ impl ArrayExpr { support::children(&self.syntax) } #[inline] - pub fn select(&self) -> Option