diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index 85304496..29aca858 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -17,15 +17,7 @@ fn build_source_file(source_file: &ast::SourceFile) -> Doc<'_> { match el { rowan::NodeOrToken::Node(node) => { if let Some(stmt) = ast::Stmt::cast(node) { - match stmt { - ast::Stmt::Select(select) => { - doc = doc.append(build_select_doc(&select)); - } - ast::Stmt::CreateTable(create_table) => { - doc = doc.append(build_create_table(&create_table)); - } - _ => (), - } + doc = doc.append(build_stmt(stmt)); } } rowan::NodeOrToken::Token(token) => { @@ -46,899 +38,10087 @@ fn build_source_file(source_file: &ast::SourceFile) -> Doc<'_> { doc } -fn build_create_table<'a>(create_table: &ast::CreateTable) -> Doc<'a> { - let table_name = create_table.table_name().unwrap(); - let arg_list = create_table.table_arg_list().unwrap(); - let mut doc = Doc::text("create") - .append(Doc::space()) - .append(Doc::text("table")) - .append(Doc::space()) - .append(leading_comments(table_name.syntax())) - .append(build_path(&table_name.path().unwrap())); - if let Some(l_paren) = arg_list.l_paren_token() { - if comment_tokens_before(l_paren.clone()).is_empty() { - doc = doc.append(Doc::space()); - } else { - doc = doc.append(comments_before(l_paren)); - } - } - doc = doc - .append(Doc::text("(")) - .append( - wrap_body(Doc::list( - Itertools::intersperse( - arg_list.args().map(build_table_arg), - Doc::text(",").append(Doc::hard_line()), - ) - .collect(), - )) - .group(), - ) - .append(Doc::text(")")); - - doc = doc.append(build_semicolon(create_table.semicolon_token())); - - doc -} - -fn build_path<'a>(path: &ast::Path) -> Doc<'a> { - build_path_parts(path.qualifier(), path.dot_token(), path.segment()) +fn build_empty_stmt<'a>(empty_stmt: &ast::EmptyStmt) -> Doc<'a> { + build_semicolon(empty_stmt.semicolon_token()) } -fn build_path_ref<'a>(path: &ast::PathRef) -> Doc<'a> { - build_path_parts(path.qualifier(), path.dot_token(), path.segment()) -} +fn build_begin<'a>(begin: &ast::Begin) -> Doc<'a> { + let mut doc = if begin.start_token().is_some() { + Doc::text("start") + } else { + Doc::text("begin") + }; -fn build_path_parts<'a>( - qualifier: Option, - dot: Option, - segment: Option, -) -> Doc<'a> { - let mut doc = Doc::nil(); - if let Some(qualifier) = qualifier { + if let Some(token) = begin.work_token().or_else(|| begin.transaction_token()) { + let keyword = if token.kind() == SyntaxKind::WORK_KW { + "work" + } else { + "transaction" + }; doc = doc - .append(build_path_ref(&qualifier)) - .append(trailing_comments(qualifier.syntax())); - } - if dot.is_some() { - doc = doc.append(Doc::text(".")); + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); } - if let Some(segment) = segment { - doc = doc - .append(leading_comments(segment.syntax())) - .append(build_name(segment.syntax())); + 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), + ); + } } - doc -} - -fn build_name<'a>(node: &SyntaxNode) -> Doc<'a> { - let mut tokens = node - .children_with_tokens() - .filter_map(|el| el.into_token()) - .filter(|token| token.kind() != SyntaxKind::WHITESPACE); - let Some(ident) = tokens.next() else { - return Doc::nil(); - }; + doc.group().append(build_semicolon(begin.semicolon_token())) +} - if is_unicode_escape(ident.text()) { - let mut doc = Doc::text(ident.text().to_string()); - for token in tokens { - let text = match token.kind() { - SyntaxKind::STRING | SyntaxKind::COMMENT => token.text().to_string(), - _ => token.text().to_ascii_lowercase(), +fn build_commit<'a>(commit: ast::Commit) -> Doc<'a> { + match commit { + ast::Commit::CommitPrepared(commit) => { + let mut doc = Doc::text("commit"); + if let Some(prepared) = commit.prepared_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&prepared)) + .append(Doc::text("prepared")); + } + if let Some(literal) = commit.literal() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(commit.semicolon_token())) + } + ast::Commit::CommitTransaction(commit) => { + let mut doc = if commit.end_token().is_some() { + Doc::text("end") + } else { + Doc::text("commit") }; - doc = doc.append(Doc::space()).append(Doc::text(text)); - if is_line_comment(&token) { - doc = doc.append(Doc::hard_line()); + if let Some(token) = commit.work_token().or_else(|| commit.transaction_token()) { + let keyword = if token.kind() == SyntaxKind::WORK_KW { + "work" + } else { + "transaction" + }; + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + if let Some(chain) = commit.chain_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(chain.syntax())) + .append(build_keyword_node(chain.syntax())); } + doc.append(build_semicolon(commit.semicolon_token())) } - return doc; } - - Doc::text(quote_ident(&normalize_name_node(node))) -} - -fn is_unicode_escape(text: &str) -> bool { - text.strip_prefix(['u', 'U']) - .is_some_and(|text| text.starts_with("&\"")) } -fn build_table_arg<'a>(arg: ast::TableArg) -> Doc<'a> { - let doc = leading_comments(arg.syntax()); - let doc = doc.append(match &arg { - ast::TableArg::Column(column) => { - let mut doc = build_name(column.name().unwrap().syntax()); - if let Some(ty) = column.ty() { +fn build_rollback<'a>(rollback: ast::Rollback) -> Doc<'a> { + match rollback { + ast::Rollback::RollbackPrepared(rollback) => { + let mut doc = Doc::text("rollback"); + if let Some(prepared) = rollback.prepared_token() { doc = doc .append(Doc::space()) - .append(leading_comments(ty.syntax())) - .append(build_type(ty)); + .append(leading_comments_token(&prepared)) + .append(Doc::text("prepared")); } - doc + if let Some(literal) = rollback.literal() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(rollback.semicolon_token())) + } + ast::Rollback::RollbackToSavepoint(rollback) => { + let mut doc = Doc::text("rollback"); + if let Some(token) = rollback + .work_token() + .or_else(|| rollback.transaction_token()) + { + let keyword = if token.kind() == SyntaxKind::WORK_KW { + "work" + } else { + "transaction" + }; + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + if let Some(to) = rollback.to_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&to)) + .append(Doc::text("to")); + } + if let Some(savepoint) = rollback.savepoint_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&savepoint)) + .append(Doc::text("savepoint")); + } + if let Some(savepoint) = rollback.savepoint_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(savepoint.syntax())) + .append(build_name(savepoint.syntax())); + } + doc.append(build_semicolon(rollback.semicolon_token())) } - ast::TableArg::LikeClause(like_clause) => build_like_clause(like_clause), - ast::TableArg::TableConstraint(table_constraint) => { - build_table_constraint(table_constraint.clone()) + ast::Rollback::RollbackTransaction(rollback) => { + let mut doc = if rollback.abort_token().is_some() { + Doc::text("abort") + } else { + Doc::text("rollback") + }; + if let Some(token) = rollback + .work_token() + .or_else(|| rollback.transaction_token()) + { + let keyword = if token.kind() == SyntaxKind::WORK_KW { + "work" + } else { + "transaction" + }; + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + if let Some(chain) = rollback.chain_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(chain.syntax())) + .append(build_keyword_node(chain.syntax())); + } + doc.append(build_semicolon(rollback.semicolon_token())) } - }); - doc.append(trailing_comments(arg.syntax())) + } } -fn build_table_constraint<'a>(constraint: ast::TableConstraint) -> Doc<'a> { - match constraint { - ast::TableConstraint::CheckConstraint(constraint) => build_check_constraint(constraint), - ast::TableConstraint::ExcludeConstraint(constraint) => build_exclude_constraint(constraint), - ast::TableConstraint::ForeignKeyConstraint(constraint) => { - build_foreign_key_constraint(constraint) - } - ast::TableConstraint::PrimaryKeyConstraint(constraint) => { - build_primary_key_constraint(constraint) +fn build_prepare<'a>(prepare: &ast::Prepare) -> Doc<'a> { + let mut header_body = Doc::nil(); + let mut has_header_body = false; + if let Some(name) = prepare.name() { + has_header_body = true; + header_body = header_body + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + if let Some(params) = prepare.param_list() { + has_header_body = true; + header_body = header_body + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + if let Some(as_token) = prepare.as_token() { + if has_header_body { + header_body = header_body.append(Doc::line_or_space()); } - ast::TableConstraint::UniqueConstraint(constraint) => build_unique_constraint(constraint), + has_header_body = true; + header_body = header_body + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + + let mut header = Doc::text("prepare"); + if has_header_body { + header = header.append(Doc::line_or_space().append(header_body).nest(2)); + } + let mut doc = header.group(); + if let Some(stmt) = prepare.stmt() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(stmt.syntax())) + .append(build_preparable_stmt(stmt)) + .nest(2), + ); } + doc.append(build_semicolon(prepare.semicolon_token())) } -fn build_constraint_name_clause<'a>(clause: Option) -> Doc<'a> { - let Some(clause) = clause else { - return Doc::nil(); - }; - let mut doc = Doc::text("constraint"); - if let Some(name) = clause.constraint_name() { +fn build_prepare_transaction<'a>(prepare: &ast::PrepareTransaction) -> Doc<'a> { + let mut doc = Doc::text("prepare"); + if let Some(transaction) = prepare.transaction_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&transaction)) + .append(Doc::text("transaction")); + } + if let Some(literal) = prepare.literal() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(prepare.semicolon_token())) +} + +fn build_savepoint_create<'a>(savepoint: &ast::SavepointCreate) -> Doc<'a> { + let mut doc = Doc::text("savepoint"); + if let Some(name) = savepoint.savepoint() { doc = doc .append(Doc::space()) .append(leading_comments(name.syntax())) .append(build_name(name.syntax())); } - doc.append(Doc::space()) + doc.append(build_semicolon(savepoint.semicolon_token())) } -fn build_check_constraint<'a>(constraint: ast::CheckConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); - if let Some(check) = constraint.check_token() { +fn build_release_savepoint<'a>(release: &ast::ReleaseSavepoint) -> Doc<'a> { + let mut doc = Doc::text("release"); + if let Some(savepoint) = release.savepoint_token() { doc = doc - .append(leading_comments_token(&check)) - .append(Doc::text("check")); + .append(Doc::space()) + .append(leading_comments_token(&savepoint)) + .append(Doc::text("savepoint")); } - if let Some(l_paren) = constraint.l_paren_token() { + if let Some(name) = release.savepoint_ref() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&l_paren)); + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } - doc = doc.append(Doc::text("(")); + doc.append(build_semicolon(release.semicolon_token())) +} - let mut body = Doc::nil(); - if let Some(expr) = constraint.expr() { - body = body - .append(leading_comments(expr.syntax())) - .append(build_expr(expr)); - } - if let Some(r_paren) = constraint.r_paren_token() { - body = body.append(comments_before(r_paren)); +fn build_insert<'a>(insert: &ast::Insert) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = insert.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(insert_token) = insert.insert_token() { + doc = doc.append(leading_comments_token(&insert_token)); + } } - doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); - let mut options = Doc::nil(); - for option in constraint.constraint_options() { - options = options - .append(Doc::line_or_space()) - .append(leading_comments(option.syntax())) - .append(build_keyword_node(option.syntax())); + doc = doc.append(Doc::text("insert")); + if let Some(into_token) = insert.into_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&into_token)) + .append(Doc::text("into")); } - doc.append(options.nest(2)).group() -} - -fn build_primary_key_constraint<'a>(constraint: ast::PrimaryKeyConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); - if let Some(primary) = constraint.primary_token() { + if let Some(relation) = insert.relation_name_ref() { doc = doc - .append(leading_comments_token(&primary)) - .append(Doc::text("primary")); + .append(Doc::space()) + .append(leading_comments(relation.syntax())); + if let Some(path) = relation.path_ref() { + doc = doc.append(build_path_ref(&path)); + } } - if let Some(key) = constraint.key_token() { + if let Some(alias) = insert.alias() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&key)) - .append(Doc::text("key")); + .append(leading_comments(alias.syntax())) + .append(build_required_as_alias(alias)); } - if let Some(using_index) = constraint.using_index() { + if let Some(columns) = insert.column_target_list() { doc = doc .append(Doc::space()) - .append(leading_comments(using_index.syntax())) - .append(build_using_index_name(using_index)); - } else if let Some(parameters) = constraint.index_parameters() { + .append(leading_comments(columns.syntax())) + .append(build_column_target_list(columns)); + } + if let Some(overriding) = insert.overriding_clause() { doc = doc - .append(leading_comments(parameters.syntax())) - .append(build_index_parameters(parameters)); + .append(Doc::line_or_space()) + .append(leading_comments(overriding.syntax())) + .append(build_overriding_clause(overriding)); } - append_constraint_options(doc, constraint.constraint_options()) - .nest(2) - .group() -} - -fn build_unique_constraint<'a>(constraint: ast::UniqueConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); - if let Some(unique) = constraint.unique_token() { + if let Some(source) = insert.insert_source() { doc = doc - .append(leading_comments_token(&unique)) - .append(Doc::text("unique")); + .append(Doc::line_or_space()) + .append(leading_comments(source.syntax())) + .append(build_insert_source(source)); } - if let Some(using_index) = constraint.using_index() { + if let Some(on_conflict) = insert.on_conflict_clause() { doc = doc - .append(Doc::space()) - .append(leading_comments(using_index.syntax())) - .append(build_using_index_name(using_index)); - } else if let Some(parameters) = constraint.index_parameters() { + .append(Doc::line_or_space()) + .append(leading_comments(on_conflict.syntax())) + .append(build_on_conflict_clause(on_conflict)); + } + if let Some(returning) = insert.returning_clause() { doc = doc - .append(leading_comments(parameters.syntax())) - .append(build_index_parameters(parameters)); + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_returning_clause(returning)); } - append_constraint_options(doc, constraint.constraint_options()) - .nest(2) + + doc.append(build_semicolon(insert.semicolon_token())) .group() } -fn build_using_index_name<'a>(using_index: ast::UsingIndexName) -> Doc<'a> { - let mut doc = Doc::text("using"); - if let Some(index) = using_index.index_token() { +fn build_required_as_alias<'a>(alias: ast::RequiredAsAlias) -> Doc<'a> { + let mut doc = alias + .as_token() + .map(|token| leading_comments_token(&token).append(Doc::text("as"))) + .unwrap_or_else(Doc::nil); + if let Some(name) = alias.name() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&index)) - .append(Doc::text("index")); - } - if let Some(index) = using_index.index_ref() { - if let Some(path) = index.path_ref() { - doc = doc - .append(Doc::space()) - .append(leading_comments(index.syntax())) - .append(build_path_ref(&path)); - } + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } doc } -fn build_index_parameters<'a>(parameters: ast::IndexParameters) -> Doc<'a> { - let mut doc = Doc::nil(); - if let Some(nulls) = parameters.nulls_distinct_option() { +fn build_overriding_clause<'a>(overriding: ast::OverridingClause) -> Doc<'a> { + let (middle, middle_token, value_token) = match overriding { + ast::OverridingClause::OverridingSystemValue(value) => { + ("system", value.system_token(), value.value_token()) + } + ast::OverridingClause::OverridingUserValue(value) => { + ("user", value.user_token(), value.value_token()) + } + }; + let mut doc = Doc::text("overriding"); + if let Some(token) = middle_token { doc = doc .append(Doc::space()) - .append(leading_comments(nulls.syntax())) - .append(build_keyword_node(nulls.syntax())); + .append(leading_comments_token(&token)) + .append(Doc::text(middle)); } - if let Some(columns) = parameters.column_list() { + if let Some(token) = value_token { doc = doc .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_constraint_column_ref_list(columns)); + .append(leading_comments_token(&token)) + .append(Doc::text("value")); } - if let Some(include) = parameters.constraint_include_clause() { - doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(include.syntax())) - .append(build_constraint_include_clause(include)); + doc +} + +fn build_insert_source<'a>(source: ast::InsertSource) -> Doc<'a> { + match source { + ast::InsertSource::DefaultValues(default_values) => { + let mut doc = Doc::text("default"); + if let Some(values_token) = default_values.values_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&values_token)) + .append(Doc::text("values")); + } + doc + } + ast::InsertSource::SelectVariant(select) => build_select_variant(select), } - if let Some(with_params) = parameters.with_params() { +} + +fn build_on_conflict_clause<'a>(on_conflict: ast::OnConflictClause) -> Doc<'a> { + let mut doc = Doc::text("on"); + if let Some(conflict_token) = on_conflict.conflict_token() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(with_params.syntax())) - .append(build_with_params(with_params)); + .append(Doc::space()) + .append(leading_comments_token(&conflict_token)) + .append(Doc::text("conflict")); } - if let Some(tablespace) = parameters.constraint_index_tablespace() { + if let Some(target) = on_conflict.conflict_target() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(tablespace.syntax())) - .append(build_constraint_index_tablespace(tablespace)); + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(build_conflict_target(target)); } - doc -} - -fn build_constraint_column_ref_list<'a>(list: ast::ConstraintColumnRefList) -> Doc<'a> { - let suffix = list.without_overlaps().map(|overlaps| { - Doc::space() - .append(leading_comments(overlaps.syntax())) - .append(build_keyword_node(overlaps.syntax())) - }); - build_column_names( - list.l_paren_token(), - list.column_name_refs(), - suffix, - list.r_paren_token(), - ) + if let Some(action) = on_conflict.conflict_action() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(action.syntax())) + .append(build_conflict_action(action)) + .nest(2), + ); + } + doc.group() } -fn build_column_ref_list<'a>(list: ast::ColumnRefList) -> Doc<'a> { - build_column_names( - list.l_paren_token(), - list.column_name_refs(), - None, - list.r_paren_token(), - ) +fn build_conflict_target<'a>(target: ast::ConflictTarget) -> Doc<'a> { + match target { + ast::ConflictTarget::ConflictOnConstraint(constraint) => { + let mut doc = Doc::text("on"); + if let Some(token) = constraint.constraint_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("constraint")); + } + if let Some(name) = constraint.constraint_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())); + if let Some(path) = name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + doc + } + ast::ConflictTarget::ConflictOnIndex(index) => { + let mut doc = index + .conflict_index_item_list() + .map(build_conflict_index_item_list) + .unwrap_or_else(Doc::nil); + if let Some(where_clause) = index.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + doc + } + } } -fn build_column_names<'a>( - l_paren: Option, - names: impl Iterator, - suffix: Option>, - r_paren: Option, -) -> Doc<'a> { - let doc = l_paren +fn build_conflict_index_item_list<'a>(items: ast::ConflictIndexItemList) -> Doc<'a> { + let mut doc = items + .l_paren_token() .map(comments_before) .unwrap_or_else(Doc::nil) .append(Doc::text("(")); - let items = names.map(|name| { + let item_docs = items.conflict_index_items().map(|item| { + let syntax = item.syntax().clone(); ( - leading_comments(name.syntax()).append(build_name(name.syntax())), - name.syntax().clone(), + leading_comments(item.syntax()).append(build_conflict_index_item(item)), + syntax, ) }); - let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); - if let Some(suffix) = suffix { - body = body.append(suffix); - } - if let Some(r_paren) = r_paren { + let mut body = build_comma_separated_docs(item_docs).unwrap_or_else(Doc::nil); + if let Some(r_paren) = items.r_paren_token() { body = body.append(comments_before(r_paren)); } - doc.append(wrap_body(body)).append(Doc::text(")")).group() -} - -fn build_constraint_include_clause<'a>(include: ast::ConstraintIncludeClause) -> Doc<'a> { - let mut doc = Doc::text("include"); - if let Some(columns) = include.column_ref_list() { - doc = doc - .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_column_ref_list(columns)); - } - doc + doc = doc.append(wrap_body(body)).append(Doc::text(")")); + doc.group() } -fn build_with_params<'a>(with_params: ast::WithParams) -> Doc<'a> { - let mut doc = Doc::text("with"); - if let Some(attributes) = with_params.attribute_list() { +fn build_conflict_index_item<'a>(item: ast::ConflictIndexItem) -> Doc<'a> { + let mut doc = if let Some(collate) = item.collate() { + build_collate_expr(collate) + } else { + item.expr().map(build_expr).unwrap_or_else(Doc::nil) + }; + if let Some(op_class) = item.op_class_ref() { doc = doc .append(Doc::space()) - .append(leading_comments(attributes.syntax())) - .append(build_attribute_list(attributes)); + .append(leading_comments(op_class.syntax())); + if let Some(path) = op_class.path_ref() { + doc = doc.append(build_path_ref(&path)); + } } doc } -fn build_attribute_list<'a>(list: ast::AttributeList) -> Doc<'a> { - let doc = list - .l_paren_token() - .map(comments_before) - .unwrap_or_else(Doc::nil) - .append(Doc::text("(")); - let items = list.attribute_options().map(|option| { - let mut item = option - .namespace() - .map(|namespace| build_name(namespace.syntax())) - .unwrap_or_else(Doc::nil); - if let Some(dot) = option.dot_token() { - item = item.append(comments_before(dot)).append(Doc::text(".")); - } - if let Some(name) = option.name() { - item = item - .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); +fn build_conflict_action<'a>(action: ast::ConflictAction) -> Doc<'a> { + match action { + ast::ConflictAction::ConflictDoNothing(action) => { + let mut doc = Doc::text("do"); + if let Some(nothing_token) = action.nothing_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(¬hing_token)) + .append(Doc::text("nothing")); + } + doc } - if let Some(eq) = option.eq_token() { - item = item - .append(Doc::space()) - .append(leading_comments_token(&eq)) - .append(Doc::text("=")); + ast::ConflictAction::ConflictDoUpdateSet(action) => { + let mut doc = Doc::text("do"); + if let Some(update_token) = action.update_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&update_token)) + .append(Doc::text("update")); + } + if let Some(set_clause) = action.set_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(set_clause.syntax())) + .append(build_set_clause(set_clause)); + } + if let Some(where_clause) = action.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + doc } - if let Some(value) = option.attribute_value() { - item = item - .append(Doc::space()) - .append(leading_comments(value.syntax())) - .append(build_attribute_value(value)); + ast::ConflictAction::ConflictDoSelect(action) => { + let mut doc = Doc::text("do"); + if let Some(select_token) = action.select_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&select_token)) + .append(Doc::text("select")); + } + if let Some(locking) = action.locking_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(locking.syntax())) + .append(build_locking_clause(locking)); + } + if let Some(where_clause) = action.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + doc } - ( - leading_comments(option.syntax()).append(item), - option.syntax().clone(), - ) - }); - let mut body = build_comma_separated_docs(items).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_attribute_value<'a>(value: ast::AttributeValue) -> Doc<'a> { - if let Some(literal) = value.literal() { - build_literal(literal) - } else if let Some(ty) = value.ty() { - build_type(ty) - } 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) +fn build_delete<'a>(delete: &ast::Delete) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = delete.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(delete_token) = delete.delete_token() { + doc = doc.append(leading_comments_token(&delete_token)); } - } else { - Doc::nil() } -} -fn build_constraint_index_tablespace<'a>(tablespace: ast::ConstraintIndexTablespace) -> Doc<'a> { - let mut doc = Doc::text("using"); - if let Some(index) = tablespace.index_token() { + doc = doc.append(Doc::text("delete")); + if let Some(from_token) = delete.from_token() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&index)) - .append(Doc::text("index")); + .append(leading_comments_token(&from_token)) + .append(Doc::text("from")); } - if let Some(token) = tablespace.tablespace_token() { + if let Some(relation) = delete.relation_name() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&token)) - .append(Doc::text("tablespace")); + .append(leading_comments(relation.syntax())) + .append(build_relation_name(relation)); } - if let Some(name) = tablespace.tablespace_ref() { + if let Some(for_portion_of) = delete.for_portion_of() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(for_portion_of.syntax())) + .append(build_for_portion_of(for_portion_of)); + } + if let Some(alias) = delete.alias() { doc = doc .append(Doc::space()) - .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); + .append(leading_comments(alias.syntax())) + .append(build_optional_as_alias(alias)); } - doc -} - -fn append_constraint_options<'a>( - mut doc: Doc<'a>, - options: impl Iterator, -) -> Doc<'a> { - for option in options { + if let Some(using_clause) = delete.using_clause() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(option.syntax())) - .append(build_keyword_node(option.syntax())); + .append(leading_comments(using_clause.syntax())) + .append(build_using_clause(using_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())) + .append(build_where_clause_or_current_of(where_clause)); + } + if let Some(returning_clause) = delete.returning_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(returning_clause.syntax())) + .append(build_returning_clause(returning_clause)); + } + + doc.append(build_semicolon(delete.semicolon_token())) + .group() +} + +fn build_optional_as_alias<'a>(alias: ast::OptionalAsAlias) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(as_token) = alias.as_token() { + doc = doc + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")) + .append(Doc::space()); + } + if let Some(name) = alias.name() { + doc = doc + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } doc } -fn build_foreign_key_constraint<'a>(constraint: ast::ForeignKeyConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); - if let Some(foreign) = constraint.foreign_token() { +fn build_using_clause<'a>(using_clause: ast::UsingClause) -> Doc<'a> { + let mut doc = using_clause + .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| { + ( + leading_comments(item.syntax()).append(build_from_item(item.clone())), + item.syntax().clone(), + ) + }); + if let Some(items) = build_comma_separated_docs(items) { + doc = doc.append(Doc::space()).append(items.nest(2)); + } + doc +} + +fn build_where_clause_or_current_of<'a>(clause: ast::WhereClauseOrCurrentOf) -> Doc<'a> { + match clause { + ast::WhereClauseOrCurrentOf::WhereClause(clause) => build_where_clause(clause), + ast::WhereClauseOrCurrentOf::WhereCurrentOf(current_of) => { + build_where_current_of(current_of) + } + } +} + +fn build_where_current_of<'a>(current_of: ast::WhereCurrentOf) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(where_token) = current_of.where_token() { doc = doc - .append(leading_comments_token(&foreign)) - .append(Doc::text("foreign")); + .append(leading_comments_token(&where_token)) + .append(Doc::text("where")); } - if let Some(key) = constraint.key_token() { + if let Some(current_token) = current_of.current_token() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&key)) - .append(Doc::text("key")); + .append(leading_comments_token(¤t_token)) + .append(Doc::text("current")); } - if let Some(columns) = constraint.from_columns() { + if let Some(of_token) = current_of.of_token() { doc = doc .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_foreign_key_column_list(columns)); + .append(leading_comments_token(&of_token)) + .append(Doc::text("of")); } - if let Some(references) = constraint.references_token() { + if let Some(cursor) = current_of.cursor_ref() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments_token(&references)) - .append(Doc::text("references")); - } - if let Some(table) = constraint.table_name_ref() { - if let Some(path) = table.path_ref() { - doc = doc - .append(Doc::space()) - .append(leading_comments(table.syntax())) - .append(build_path_ref(&path)); - } + .append(Doc::space()) + .append(leading_comments(cursor.syntax())) + .append(build_name(cursor.syntax())); } - if let Some(columns) = constraint.to_columns() { + doc +} + +fn build_returning_clause<'a>(returning: ast::ReturningClause) -> Doc<'a> { + let mut doc = returning + .returning_token() + .map(|token| leading_comments_token(&token).append(Doc::text("returning"))) + .unwrap_or_else(Doc::nil); + if let Some(options) = returning.returning_option_list() { doc = doc .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_foreign_key_column_list(columns)); + .append(leading_comments(options.syntax())) + .append(build_returning_option_list(options)); } - if let Some(match_type) = constraint.match_type() { + if let Some(target_list) = returning.target_list() { + let targets = Doc::list( + Itertools::intersperse( + target_list.targets().flat_map(build_target), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ); doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(match_type.syntax())) - .append(build_keyword_node(match_type.syntax())); + .append(Doc::space()) + .append(leading_comments(target_list.syntax())) + .append(targets.nest(2).group()); } - if let Some(action) = constraint.on_delete_action() { + doc +} + +fn build_returning_option_list<'a>(options: ast::ReturningOptionList) -> Doc<'a> { + let mut doc = options + .with_token() + .map(|token| leading_comments_token(&token).append(Doc::text("with"))) + .unwrap_or_else(Doc::nil); + if let Some(l_paren) = options.l_paren_token() { 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(), - )); + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); } - 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(), - )); + let items = options.returning_options().map(|option| { + let syntax = option.syntax().clone(); + ( + leading_comments(option.syntax()).append(build_returning_option(option)), + syntax, + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(r_paren) = options.r_paren_token() { + body = body.append(comments_before(r_paren)); } - append_constraint_options(doc, constraint.constraint_options()) - .nest(2) - .group() + doc.append(wrap_body(body)).append(Doc::text(")")).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")); - if let Some(name) = period.name() { - doc = 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(), - ) +fn build_returning_option<'a>(option: ast::ReturningOption) -> Doc<'a> { + let (keyword, as_token, name) = match option { + ast::ReturningOption::ReturningOld(old) => ("old", old.as_token(), old.name()), + ast::ReturningOption::ReturningNew(new) => ("new", new.as_token(), new.name()), + }; + let mut doc = Doc::text(keyword); + if let Some(as_token) = as_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(name) = name { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc } -fn build_reference_action<'a>( - on: Option, - kind_token: Option, - kind: &'static str, - action: Option, -) -> Doc<'a> { - let mut doc = on - .map(|token| leading_comments_token(&token).append(Doc::text("on"))) - .unwrap_or_else(Doc::nil); - if let Some(token) = kind_token { +fn build_for_portion_of<'a>(portion: ast::ForPortionOf) -> Doc<'a> { + let mut doc = Doc::text("for"); + if let Some(portion_token) = portion.portion_token() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&token)) - .append(Doc::text(kind)); + .append(leading_comments_token(&portion_token)) + .append(Doc::text("portion")); } - let Some(action) = action else { - return doc; - }; - doc = doc - .append(Doc::space()) - .append(leading_comments(action.syntax())); - match action { - ast::RefAction::SetNullColumns(action) => { - if let Some(set) = action.set_token() { + if let Some(of_token) = portion.of_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&of_token)) + .append(Doc::text("of")); + } + if let Some(column) = portion.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } + if let Some(range) = portion.range() { + doc = doc + .append(Doc::space()) + .append(leading_comments(range.syntax())) + .append(build_portion_target(range)); + } + doc +} + +fn build_portion_target<'a>(target: ast::PortionTarget) -> Doc<'a> { + match target { + ast::PortionTarget::PortionFromTo(range) => { + let mut doc = Doc::text("from"); + if let Some(from) = range.from() { doc = doc - .append(leading_comments_token(&set)) - .append(Doc::text("set")); + .append(Doc::space()) + .append(leading_comments(from.syntax())) + .append(build_expr(from)); } - if let Some(null) = action.null_token() { + if let Some(to_token) = range.to_token() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&null)) - .append(Doc::text("null")); + .append(leading_comments_token(&to_token)) + .append(Doc::text("to")); } - if let Some(columns) = action.column_ref_list() { + if let Some(to) = range.to() { doc = doc .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_column_ref_list(columns)); + .append(leading_comments(to.syntax())) + .append(build_expr(to)); } doc } - ast::RefAction::SetDefaultColumns(action) => { - if let Some(set) = action.set_token() { - doc = doc - .append(leading_comments_token(&set)) - .append(Doc::text("set")); - } - if let Some(default) = action.default_token() { + ast::PortionTarget::PortionRange(range) => { + let mut doc = range + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + if let Some(expr) = range.expr() { doc = doc - .append(Doc::space()) - .append(leading_comments_token(&default)) - .append(Doc::text("default")); + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); } - if let Some(columns) = action.column_ref_list() { - doc = doc - .append(Doc::space()) - .append(leading_comments(columns.syntax())) - .append(build_column_ref_list(columns)); + if let Some(r_paren) = range.r_paren_token() { + doc = doc.append(comments_before(r_paren)); } - doc + doc.append(Doc::text(")")) } - action => doc.append(build_keyword_node(action.syntax())), } } -fn build_exclude_constraint<'a>(constraint: ast::ExcludeConstraint) -> Doc<'a> { - let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); - if let Some(exclude) = constraint.exclude_token() { +fn build_merge<'a>(merge: &ast::Merge) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = merge.with_clause() { doc = doc - .append(leading_comments_token(&exclude)) - .append(Doc::text("exclude")); + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(merge_token) = merge.merge_token() { + doc = doc.append(leading_comments_token(&merge_token)); + } } - if let Some(method) = constraint.constraint_index_method() { + + doc = doc.append(Doc::text("merge")); + if let Some(into_token) = merge.into_token() { doc = doc .append(Doc::space()) - .append(leading_comments(method.syntax())) - .append(Doc::text("using")); - if let Some(name) = method.access_method_ref() { - doc = doc - .append(Doc::space()) - .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); + .append(leading_comments_token(&into_token)) + .append(Doc::text("into")); + } + if let Some(relation) = merge.table_relation_name() { + let trailing = merge + .alias() + .is_some() + .then(|| trailing_comments(relation.syntax())); + doc = doc + .append(Doc::space()) + .append(build_table_relation_name(relation)); + if let Some(trailing) = trailing { + doc = doc.append(trailing); } } - if let Some(list) = constraint.constraint_exclusion_list() { + if let Some(alias) = merge.alias() { doc = doc .append(Doc::space()) - .append(leading_comments(list.syntax())) - .append(build_constraint_exclusion_list(list)); + .append(build_optional_as_alias(alias)); } - if let Some(include) = constraint.constraint_include_clause() { + if let Some(using) = merge.using_on_clause() { doc = doc .append(Doc::line_or_space()) - .append(leading_comments(include.syntax())) - .append(build_constraint_include_clause(include)); + .append(leading_comments(using.syntax())) + .append(build_using_on_clause(using)); } - if let Some(with_params) = constraint.with_params() { + for when_clause in merge.merge_when_clauses() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(with_params.syntax())) - .append(build_with_params(with_params)); + .append(Doc::hard_line()) + .append(leading_comments(when_clause.syntax())) + .append(build_merge_when_clause(when_clause)); } - if let Some(tablespace) = constraint.constraint_index_tablespace() { + if let Some(returning) = merge.returning_clause() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(tablespace.syntax())) - .append(build_constraint_index_tablespace(tablespace)); + .append(Doc::hard_line()) + .append(leading_comments(returning.syntax())) + .append(build_returning_clause(returning)); } - if let Some(where_clause) = constraint.where_condition_clause() { + + doc.append(build_semicolon(merge.semicolon_token())).group() +} + +fn build_using_on_clause<'a>(using: ast::UsingOnClause) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(item) = using.from_item() { doc = doc - .append(Doc::line_or_space()) - .append(leading_comments(where_clause.syntax())) - .append(build_where_condition_clause(where_clause)); + .append(Doc::space()) + .append(leading_comments(item.syntax())) + .append(build_from_item(item)); } - append_constraint_options(doc, constraint.constraint_options()) - .nest(2) + if let Some(on_clause) = using.on_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(on_clause.syntax())) + .append(build_on_clause(on_clause)); + } + doc.group() +} + +fn build_on_clause<'a>(on_clause: ast::OnClause) -> Doc<'a> { + let mut doc = Doc::text("on"); + if let Some(expr) = on_clause.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc +} + +fn build_merge_when_clause<'a>(clause: ast::MergeWhenClause) -> Doc<'a> { + let (mut doc, condition, then_token, action) = match clause { + ast::MergeWhenClause::MergeWhenMatched(clause) => { + let mut doc = Doc::text("when"); + if let Some(token) = clause.matched_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("matched")); + } + ( + doc, + clause.merge_condition(), + clause.then_token(), + clause.merge_action(), + ) + } + ast::MergeWhenClause::MergeWhenNotMatchedSource(clause) => { + let mut doc = + build_merge_when_not_matched_prefix(clause.not_token(), clause.matched_token()); + if let Some(token) = clause.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("by")); + } + if let Some(token) = clause.source_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("source")); + } + ( + doc, + clause.merge_condition(), + clause.then_token(), + clause.merge_action(), + ) + } + ast::MergeWhenClause::MergeWhenNotMatchedTarget(clause) => { + let mut doc = + build_merge_when_not_matched_prefix(clause.not_token(), clause.matched_token()); + if let Some(by_target) = clause.by_target() { + doc = doc + .append(Doc::space()) + .append(leading_comments(by_target.syntax())); + if let Some(token) = by_target.by_token() { + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text("by")); + } + if let Some(token) = by_target.target_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("target")); + } + } + ( + doc, + clause.merge_condition(), + clause.then_token(), + clause.merge_action(), + ) + } + }; + + if let Some(condition) = condition { + doc = doc + .append(Doc::space()) + .append(leading_comments(condition.syntax())) + .append(build_merge_condition(condition)); + } + if let Some(then_token) = then_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&then_token)) + .append(Doc::text("then")); + } + doc = doc.group(); + if let Some(action) = action { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(action.syntax())) + .append(build_merge_action(action)) + .nest(2), + ); + } + doc +} + +fn build_merge_when_not_matched_prefix<'a>( + not_token: Option, + matched_token: Option, +) -> Doc<'a> { + let mut doc = Doc::text("when"); + if let Some(token) = not_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("not")); + } + if let Some(token) = matched_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("matched")); + } + doc +} + +fn build_merge_condition<'a>(condition: ast::MergeCondition) -> Doc<'a> { + let mut doc = Doc::text("and"); + if let Some(expr) = condition.expr() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)) + .nest(2), + ); + } + doc +} + +fn build_merge_action<'a>(action: ast::MergeAction) -> Doc<'a> { + match action { + ast::MergeAction::MergeDelete(_) => Doc::text("delete"), + ast::MergeAction::MergeDoNothing(action) => { + let mut doc = Doc::text("do"); + if let Some(token) = action.nothing_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("nothing")); + } + doc + } + ast::MergeAction::MergeUpdate(action) => { + let mut doc = Doc::text("update"); + if let Some(set_clause) = action.set_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(set_clause.syntax())) + .append(build_set_clause(set_clause)); + } + doc.group() + } + ast::MergeAction::MergeInsert(action) => { + let mut doc = Doc::text("insert"); + if let Some(columns) = action.column_target_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_target_list(columns)); + } + if let Some(overriding) = action.overriding_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(overriding.syntax())) + .append(build_overriding_clause(overriding)); + } + if let Some(values) = action.values() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(values.syntax())) + .append(build_values(&values)); + } else if let Some(default_values) = action.default_values() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(default_values.syntax())) + .append(build_default_values(default_values)); + } + doc.group() + } + } +} + +fn build_default_values<'a>(default_values: ast::DefaultValues) -> Doc<'a> { + let mut doc = Doc::text("default"); + if let Some(token) = default_values.values_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("values")); + } + doc +} + +fn build_update<'a>(update: &ast::Update) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = update.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(update_token) = update.update_token() { + doc = doc.append(leading_comments_token(&update_token)); + } + } + + doc = doc.append(Doc::text("update")); + if let Some(relation) = update.relation_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(relation.syntax())) + .append(build_relation_name(relation)); + } + if let Some(for_portion_of) = update.for_portion_of() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(for_portion_of.syntax())) + .append(build_for_portion_of(for_portion_of)); + } + if let Some(alias) = update.alias() { + doc = doc + .append(Doc::space()) + .append(leading_comments(alias.syntax())) + .append(build_optional_as_alias(alias)); + } + if let Some(set_clause) = update.set_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(set_clause.syntax())) + .append(build_set_clause(set_clause)); + } + if let Some(from_clause) = update.from_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(from_clause.syntax())) + .append(build_from_clause(from_clause)); + } + if let Some(where_clause) = update.where_clause_or_current_of() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause_or_current_of(where_clause)); + } + if let Some(returning_clause) = update.returning_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(returning_clause.syntax())) + .append(build_returning_clause(returning_clause)); + } + + doc.append(build_semicolon(update.semicolon_token())) .group() } -fn build_constraint_exclusion_list<'a>(list: ast::ConstraintExclusionList) -> Doc<'a> { - let doc = list +fn build_set_clause<'a>(set_clause: ast::SetClause) -> Doc<'a> { + let mut doc = set_clause + .set_token() + .map(|token| leading_comments_token(&token).append(Doc::text("set"))) + .unwrap_or_else(Doc::nil); + if let Some(columns) = set_clause.set_column_list() { + let items = columns.set_columns().map(|column| { + let syntax = column.syntax().clone(); + ( + leading_comments(column.syntax()).append(build_set_column(column)), + syntax, + ) + }); + if let Some(items) = build_comma_separated_docs(items) { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(columns.syntax())) + .append(items) + .nest(2) + .group(), + ); + } + } + doc +} + +fn build_set_column<'a>(column: ast::SetColumn) -> Doc<'a> { + match column { + ast::SetColumn::SetSingleColumn(column) => { + let mut doc = column + .column_target() + .map(build_column_target) + .unwrap_or_else(Doc::nil); + if let Some(eq_token) = column.eq_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&eq_token)) + .append(Doc::text("=")) + .nest(2), + ); + } + if let Some(expr) = column.set_expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_set_expr(expr)); + } + doc.group() + } + ast::SetColumn::SetMultipleColumns(columns) => { + let mut doc = columns + .column_target_list() + .map(build_column_target_list) + .unwrap_or_else(Doc::nil); + if let Some(eq_token) = columns.eq_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&eq_token)) + .append(Doc::text("=")); + } + if let Some(exprs) = columns.set_expr_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(exprs.syntax())) + .append(build_set_expr_list(exprs)); + } else if let Some(select) = columns.paren_select() { + doc = doc + .append(Doc::space()) + .append(leading_comments(select.syntax())) + .append(build_paren_select(select)); + } + doc + } + } +} + +fn build_column_target_list<'a>(targets: ast::ColumnTargetList) -> Doc<'a> { + let mut doc = targets .l_paren_token() .map(comments_before) .unwrap_or_else(Doc::nil) .append(Doc::text("(")); - let items = list.constraint_exclusions().map(|exclusion| { - let mut item = exclusion.expr().map(build_expr).unwrap_or_else(Doc::nil); - if let Some(with) = exclusion.with_token() { - item = item - .append(Doc::line_or_space()) - .append(leading_comments_token(&with)) - .append(Doc::text("with")); + let items = targets.column_targets().map(|target| { + let syntax = target.syntax().clone(); + ( + leading_comments(target.syntax()).append(build_column_target(target)), + syntax, + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(r_paren) = targets.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")); + doc.group() +} + +fn build_column_target<'a>(target: ast::ColumnTarget) -> Doc<'a> { + let mut doc = target + .name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + for accessor in target.accessors() { + doc = doc + .append(leading_comments(accessor.syntax())) + .append(build_accessor(accessor)); + } + doc +} + +fn build_accessor<'a>(accessor: ast::Accessor) -> Doc<'a> { + match accessor { + ast::Accessor::FieldAccessor(field) => { + let mut doc = field + .dot_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text(".")); + if let Some(star) = field.star_token() { + doc = doc + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } else if let Some(name) = field.composite_field_ref() { + doc = doc + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc } - if let Some(op) = exclusion.op() { - item = item - .append(Doc::space()) - .append(leading_comments(op.syntax())) - .append(build_operator(&op)); - } else if let Some(op) = exclusion.operator_call() { - item = item - .append(Doc::space()) - .append(leading_comments(op.syntax())) - .append(build_operator_call(&op)); + ast::Accessor::IndexAccessor(index) => { + let mut body = index + .index() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil); + if let Some(r_brack) = index.r_brack_token() { + body = body.append(comments_before(r_brack)); + } + index + .l_brack_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("[")) + .append(wrap_body(body)) + .append(Doc::text("]")) + .group() + } + ast::Accessor::SliceAccessor(slice) => { + let mut body = slice + .start() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil); + if let Some(colon) = slice.colon_token() { + body = body.append(comments_before(colon)); + } + body = body.append(Doc::text(":")); + if let Some(end) = slice.end() { + body = body + .append(leading_comments(end.syntax())) + .append(build_expr(end)); + } + if let Some(r_brack) = slice.r_brack_token() { + body = body.append(comments_before(r_brack)); + } + slice + .l_brack_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("[")) + .append(wrap_body(body)) + .append(Doc::text("]")) + .group() } + } +} + +fn build_set_expr_list<'a>(exprs: ast::SetExprList) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(row_token) = exprs.row_token() { + doc = doc + .append(leading_comments_token(&row_token)) + .append(Doc::text("row")); + } + if let Some(l_paren) = exprs.l_paren_token() { + doc = doc.append(comments_before(l_paren)).append(Doc::text("(")); + } + let items = exprs.set_exprs().map(|expr| { + let syntax = expr.syntax().clone(); + ( + leading_comments(expr.syntax()).append(build_set_expr(expr)), + syntax, + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(r_paren) = exprs.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_set_expr<'a>(expr: ast::SetExpr) -> Doc<'a> { + if let Some(expr) = expr.expr() { + build_expr(expr) + } else if let Some(default) = expr.default_token() { + leading_comments_token(&default).append(Doc::text("default")) + } else { + Doc::nil() + } +} + +fn build_truncate<'a>(truncate: &ast::Truncate) -> Doc<'a> { + let mut doc = Doc::text("truncate"); + + if let Some(table_token) = truncate.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table_token)) + .append(Doc::text("table")); + } + + if let Some(table_list) = truncate.table_list() { + let tables = Doc::list( + Itertools::intersperse( + table_list.table_relation_names().map(|relation| { + let trailing = trailing_comments(relation.syntax()); + build_table_relation_name(relation).append(trailing) + }), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ); + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(table_list.syntax())) + .append(tables) + .nest(2), + ); + } + + if let Some(identity_action) = truncate.identity_action() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(identity_action.syntax())) + .append(build_keyword_node(identity_action.syntax())); + } + + if let Some(drop_behavior) = truncate.drop_behavior() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(drop_behavior.syntax())) + .append(build_keyword_node(drop_behavior.syntax())); + } + + doc.append(build_semicolon(truncate.semicolon_token())) + .group() +} + +fn build_table_relation_name<'a>(relation: ast::TableRelationName) -> Doc<'a> { + let mut doc = leading_comments(relation.syntax()); + let has_only = relation.only_token().is_some(); + + if let Some(only_token) = relation.only_token() { + doc = doc + .append(leading_comments_token(&only_token)) + .append(Doc::text("only")); + } + if let Some(l_paren) = relation.l_paren_token() { + if has_only && comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } + doc = doc.append(comments_before(l_paren)).append(Doc::text("(")); + } + if let Some(table_name) = relation.table_name_ref() { + if has_only && relation.l_paren_token().is_none() { + doc = doc.append(Doc::space()); + } + doc = doc.append(leading_comments(table_name.syntax())); + if let Some(path) = table_name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(r_paren) = relation.r_paren_token() { + doc = doc.append(comments_before(r_paren)).append(Doc::text(")")); + } + if let Some(star) = relation.star_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } + + doc +} + +fn build_create_trigger<'a>(stmt: &ast::CreateTrigger) -> Doc<'a> { + let mut doc = Doc::text("create"); + if let Some(or_replace) = stmt.or_replace() { + doc = doc + .append(Doc::space()) + .append(leading_comments(or_replace.syntax())) + .append(build_keyword_node(or_replace.syntax())); + } + if let Some(constraint) = stmt.constraint_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&constraint)) + .append(Doc::text("constraint")); + } + if let Some(trigger) = stmt.trigger_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&trigger)) + .append(Doc::text("trigger")); + } + if let Some(trigger) = stmt.trigger() { + doc = doc + .append(Doc::space()) + .append(leading_comments(trigger.syntax())) + .append(build_name(trigger.syntax())); + } + doc = doc.group(); + + if let Some(timing) = stmt.timing() { + let mut clause = + leading_comments(timing.syntax()).append(build_keyword_node(timing.syntax())); + if let Some(events) = stmt.trigger_event_list() { + clause = clause + .append(Doc::line_or_space()) + .append(leading_comments(events.syntax())) + .append(build_trigger_event_list(events)); + } + doc = doc.append(Doc::hard_line().append(clause.group()).nest(2)); + } + if let Some(on_relation) = stmt.on_relation() { + let mut clause = Doc::text("on"); + if let Some(relation) = on_relation.relation_name_ref() { + clause = clause + .append(Doc::line_or_space()) + .append(leading_comments(relation.syntax())); + if let Some(path) = relation.path_ref() { + clause = clause.append(build_path_ref(&path)); + } + } + doc = doc.append( + Doc::hard_line() + .append(leading_comments(on_relation.syntax())) + .append(clause.group()) + .nest(2), + ); + } + if let Some(from_table) = stmt.from_table() { + let mut clause = Doc::text("from"); + if let Some(table) = from_table.table_name_ref() { + clause = clause + .append(Doc::line_or_space()) + .append(leading_comments(table.syntax())); + if let Some(path) = table.path_ref() { + clause = clause.append(build_path_ref(&path)); + } + } + doc = doc.append( + Doc::hard_line() + .append(leading_comments(from_table.syntax())) + .append(clause.group()) + .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() + { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(&option)) + .append(build_keyword_node(&option)) + .nest(2), + ); + } + if let Some(referencing) = stmt.referencing() { + let mut clause = Doc::text("referencing"); + for table in referencing.referencing_tables() { + clause = clause.append( + Doc::line_or_space() + .append(leading_comments(table.syntax())) + .append(build_referencing_table(table)) + .nest(2), + ); + } + doc = doc.append( + Doc::hard_line() + .append(leading_comments(referencing.syntax())) + .append(clause.group()) + .nest(2), + ); + } + if let Some(level) = stmt.trigger_level() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(level.syntax())) + .append(build_keyword_node(level.syntax())) + .nest(2), + ); + } + if let Some(condition) = stmt.when_condition() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(condition.syntax())) + .append(build_trigger_when_condition(condition)) + .nest(2), + ); + } + if let Some(call) = stmt.call_expr() { + let mut clause = stmt + .execute_token() + .map(|execute| leading_comments_token(&execute).append(Doc::text("execute"))) + .unwrap_or_else(|| Doc::text("execute")); + if let Some(function) = stmt.function_token() { + clause = clause + .append(Doc::space()) + .append(leading_comments_token(&function)) + .append(Doc::text("function")); + } else if let Some(procedure) = stmt.procedure_token() { + clause = clause + .append(Doc::space()) + .append(leading_comments_token(&procedure)) + .append(Doc::text("procedure")); + } + clause = clause + .append(Doc::line_or_space()) + .append(leading_comments(call.syntax())) + .append(build_call_expr(call)) + .nest(2) + .group(); + doc = doc.append(Doc::hard_line().append(clause).nest(2)); + } + + doc.append(build_semicolon(stmt.semicolon_token())) +} + +fn build_trigger_event_list<'a>(events: ast::TriggerEventList) -> Doc<'a> { + let mut events = events.trigger_events(); + let Some(first) = events.next() else { + return Doc::nil(); + }; + let mut previous_syntax = first.syntax().clone(); + let mut doc = build_trigger_event(first); + for event in events { + doc = doc + .append(trailing_comments(&previous_syntax)) + .append(Doc::line_or_space()) + .append(Doc::text("or")) + .append(Doc::line_or_space()) + .append(leading_comments(event.syntax())) + .append(build_trigger_event(event.clone())); + previous_syntax = event.syntax().clone(); + } + doc.group() +} + +fn build_trigger_event<'a>(event: ast::TriggerEvent) -> Doc<'a> { + match event { + ast::TriggerEvent::TriggerEventDelete(event) => build_keyword_node(event.syntax()), + ast::TriggerEvent::TriggerEventInsert(event) => build_keyword_node(event.syntax()), + ast::TriggerEvent::TriggerEventTruncate(event) => build_keyword_node(event.syntax()), + ast::TriggerEvent::TriggerEventUpdate(event) => { + let mut doc = Doc::text("update"); + if let Some(of) = event.of_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&of)) + .append(Doc::text("of")); + } + let columns = event.column_name_refs().map(|column| { + let syntax = column.syntax().clone(); + ( + leading_comments(&syntax).append(build_name(&syntax)), + syntax, + ) + }); + if let Some(columns) = build_comma_separated_docs(columns) { + doc = doc.append(Doc::line_or_space().append(columns).nest(2)); + } + doc.group() + } + } +} + +fn build_referencing_table<'a>(table: ast::ReferencingTable) -> Doc<'a> { + match table { + ast::ReferencingTable::OldTable(table) => { + let mut doc = build_keyword_tokens([ + (table.old_token(), "old"), + (table.table_token(), "table"), + (table.as_token(), "as"), + ]); + if let Some(name) = table.transition_relation_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc + } + ast::ReferencingTable::NewTable(table) => { + let mut doc = build_keyword_tokens([ + (table.new_token(), "new"), + (table.table_token(), "table"), + (table.as_token(), "as"), + ]); + if let Some(name) = table.transition_relation_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc + } + } +} + +fn build_trigger_when_condition<'a>(condition: ast::WhenCondition) -> Doc<'a> { + let mut doc = Doc::text("when"); + if let Some(l_paren) = condition.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + let mut body = condition + .expr() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = condition.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::space()) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_create_transform<'a>(stmt: &ast::CreateTransform) -> Doc<'a> { + let mut doc = Doc::text("create"); + if let Some(or_replace) = stmt.or_replace() { + doc = doc + .append(Doc::space()) + .append(leading_comments(or_replace.syntax())) + .append(build_keyword_node(or_replace.syntax())); + } + if let Some(transform) = stmt.transform_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&transform)) + .append(Doc::text("transform")); + } + if let Some(for_token) = stmt.for_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&for_token)) + .append(Doc::text("for")); + } + if let Some(ty) = stmt.ty() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(ty.syntax())) + .append(build_type(ty)) + .nest(2), + ); + } + if let Some(language) = stmt.language_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&language)) + .append(Doc::text("language")) + .nest(2), + ); + } + if let Some(language) = stmt.language_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(language.syntax())) + .append(build_name(language.syntax())); + } + doc = doc.group(); + + if let Some(l_paren) = stmt.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + let funcs = stmt.transform_funcs().map(|func| { + let syntax = func.syntax().clone(); + ( + leading_comments(&syntax).append(build_transform_func(func)), + syntax, + ) + }); + let mut body = build_comma_separated_docs(funcs).unwrap_or_else(Doc::nil); + if let Some(r_paren) = stmt.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::space()) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() + .append(build_semicolon(stmt.semicolon_token())) +} + +fn build_transform_func<'a>(func: ast::TransformFunc) -> Doc<'a> { + let (prefix, sig) = match func { + ast::TransformFunc::TransformFromFunc(func) => ( + build_keyword_tokens([ + (func.from_token(), "from"), + (func.sql_token(), "sql"), + (func.with_token(), "with"), + (func.function_token(), "function"), + ]), + func.function_sig(), + ), + ast::TransformFunc::TransformToFunc(func) => ( + build_keyword_tokens([ + (func.to_token(), "to"), + (func.sql_token(), "sql"), + (func.with_token(), "with"), + (func.function_token(), "function"), + ]), + func.function_sig(), + ), + }; + if let Some(sig) = sig { + prefix + .append(Doc::line_or_space()) + .append(leading_comments(sig.syntax())) + .append(build_function_sig(sig)) + .nest(2) + .group() + } else { + prefix + } +} + +fn build_create_function<'a>(create_function: &ast::CreateFunction) -> Doc<'a> { + let mut doc = Doc::text("create"); + if let Some(or_replace) = create_function.or_replace() { + doc = doc + .append(Doc::space()) + .append(leading_comments(or_replace.syntax())) + .append(build_keyword_node(or_replace.syntax())); + } + if let Some(function_token) = create_function.function_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&function_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("function")); + if let Some(name) = create_function.name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())); + if let Some(path) = name.path() { + doc = doc.append(build_path(&path)); + } + } + if let Some(params) = create_function.param_list() { + doc = doc + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + if let Some(ret_type) = create_function.ret_type() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ret_type.syntax())) + .append(build_function_ret_type(ret_type.clone())) + .append(trailing_comments(ret_type.syntax())); + } + doc = doc.group(); + + if let Some(options) = create_function.option_list() { + for option in options.options() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(option.syntax())) + .append(build_function_option(option)) + .nest(2), + ); + } + } + doc.append(build_semicolon(create_function.semicolon_token())) +} + +fn build_function_param_list<'a>(params: ast::ParamList) -> Doc<'a> { + let doc = params + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let mut body = if let Some(star) = params.star_token() { + leading_comments_token(&star).append(Doc::text("*")) + } else { + let param_docs = params.params().map(|param| { + let syntax = param.syntax().clone(); + ( + leading_comments(param.syntax()).append(build_function_param(param)), + syntax, + ) + }); + build_comma_separated_docs(param_docs).unwrap_or_else(Doc::nil) + }; + if let Some(r_paren) = params.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")) +} + +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() { + doc = doc.append(Doc::space()); + } + doc = doc + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + if let Some(ty) = param.ty() { + if param.mode().is_some() || param.name().is_some() { + doc = doc.append(Doc::space()); + } + doc = doc + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + if let Some(default) = param.param_default() { + doc = doc + .append(Doc::space()) + .append(leading_comments(default.syntax())); + if let Some(default_token) = default.default_token() { + doc = doc + .append(leading_comments_token(&default_token)) + .append(Doc::text("default")); + } else if let Some(eq_token) = default.eq_token() { + doc = doc + .append(leading_comments_token(&eq_token)) + .append(Doc::text("=")); + } + if let Some(expr) = default.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + } + doc +} + +fn build_function_ret_type<'a>(ret_type: ast::RetType) -> Doc<'a> { + let mut doc = Doc::text("returns"); + if let Some(table_token) = ret_type.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table_token)) + .append(Doc::text("table")); + } + if let Some(args) = ret_type.table_arg_list() { + let mut body = Doc::list( + Itertools::intersperse( + args.args().map(build_table_arg), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ); + if args.args().next().is_none() { + if let Some(r_paren) = args.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + } + let args_doc = args + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group(); + doc = doc + .append(Doc::space()) + .append(leading_comments(args.syntax())) + .append(args_doc); + } else if let Some(ty) = ret_type.ty() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + doc +} + +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()) + } + ast::FuncOption::LanguageFuncOption(option) => { + let mut doc = Doc::text("language"); + if let Some(language) = option.language_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(language.syntax())) + .append(build_name(language.syntax())); + } else if let Some(literal) = option.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc + } + ast::FuncOption::ResetFuncOption(option) => { + let mut doc = Doc::text("reset"); + if let Some(all) = option.all_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&all)) + .append(Doc::text("all")); + } else if let Some(parameter) = option.config_parameter_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(parameter.syntax())); + if let Some(path) = parameter.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + doc + } + ast::FuncOption::ReturnFuncOption(option) => build_return_function_option(option), + ast::FuncOption::RowsFuncOption(option) => { + build_literal_function_option("rows", option.literal()) + } + ast::FuncOption::SetFuncOption(option) => option + .set_config_param() + .map(build_set_config_param) + .unwrap_or_else(Doc::nil), + ast::FuncOption::SupportFuncOption(option) => { + let mut doc = Doc::text("support"); + if let Some(function) = option.function_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(function.syntax())); + if let Some(path) = function.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + doc + } + ast::FuncOption::TransformFuncOption(option) => { + let transforms = option.transform_for_types().map(|transform| { + let syntax = transform.syntax().clone(); + let mut doc = leading_comments(transform.syntax()).append(Doc::text("for")); + if let Some(type_token) = transform.type_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&type_token)) + .append(Doc::text("type")); + } + if let Some(ty) = transform.ty() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + (doc, syntax) + }); + Doc::text("transform").append( + Doc::space() + .append(build_comma_separated_docs(transforms).unwrap_or_else(Doc::nil)), + ) + } + ast::FuncOption::CalledOnNullInputFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::LeakproofFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::NotLeakproofFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::ParallelFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::ReturnsNullOnNullInputFuncOption(option) => { + build_keyword_node(option.syntax()) + } + ast::FuncOption::SecurityDefinerFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::SecurityInvokerFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::StrictFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::VolatilityFuncOption(option) => build_keyword_node(option.syntax()), + ast::FuncOption::WindowFuncOption(option) => build_keyword_node(option.syntax()), + } +} + +fn build_begin_function_option_list<'a>(options: ast::BeginFuncOptionList) -> Doc<'a> { + let mut doc = Doc::text("begin"); + if let Some(atomic) = options.atomic_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&atomic)) + .append(Doc::text("atomic")); + } + + for option in options.begin_func_options() { + 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), + }; + doc = doc.append( + Doc::hard_line() + .append(option_comments) + .append(option_doc) + .nest(2), + ); + } + + let end_doc = options + .end_token() + .map(|end| leading_comments_token(&end)) + .unwrap_or_else(Doc::nil) + .append(Doc::text("end")); + doc.append(Doc::hard_line()).append(end_doc) +} + +fn build_call<'a>(call: &ast::Call) -> Doc<'a> { + let mut doc = Doc::text("call"); + if let Some(procedure) = call.procedure_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(procedure.syntax())); + if let Some(path) = procedure.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(args) = call.arg_list() { + doc = doc + .append(comments_before(args.syntax().clone())) + .append(build_call_arg_list(args)); + } + doc.group().append(build_semicolon(call.semicolon_token())) +} + +fn build_checkpoint<'a>(checkpoint: &ast::Checkpoint) -> Doc<'a> { + let mut doc = Doc::text("checkpoint"); + if let Some(options) = checkpoint.checkpoint_option_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(options.syntax())) + .append(build_checkpoint_option_list(options)); + } + doc.group() + .append(build_semicolon(checkpoint.semicolon_token())) +} + +fn build_checkpoint_option_list<'a>(list: ast::CheckpointOptionList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.checkpoint_options().map(|option| { + ( + leading_comments(option.syntax()).append(build_checkpoint_option(option.clone())), + option.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_checkpoint_option<'a>(option: ast::CheckpointOption) -> Doc<'a> { + let mut doc = option + .checkpoint_option_name() + .map(|name| build_keyword_node(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(expr) = option.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc.group() +} + +fn build_deallocate<'a>(deallocate: &ast::Deallocate) -> Doc<'a> { + let mut doc = Doc::text("deallocate"); + + if let Some(prepare) = deallocate.prepare_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&prepare)) + .append(Doc::text("prepare")) + .nest(2), + ); + } + + let target = if let Some(statement) = deallocate.prepared_statement_ref() { + Some(leading_comments(statement.syntax()).append(build_name(statement.syntax()))) + } else { + deallocate + .all_token() + .map(|all| leading_comments_token(&all).append(Doc::text("all"))) + }; + if let Some(target) = target { + doc = doc.append(Doc::line_or_space().append(target).nest(2)); + } + + doc.group() + .append(build_semicolon(deallocate.semicolon_token())) +} + +fn build_declare<'a>(declare: &ast::Declare) -> Doc<'a> { + let mut doc = Doc::text("declare"); + + if let Some(cursor) = declare.cursor() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(cursor.syntax())) + .append(build_name(cursor.syntax())) + .nest(2), + ); + } + if let Some(binary) = declare.binary_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&binary)) + .append(Doc::text("binary")) + .nest(2), + ); + } + if let Some(sensitivity) = declare.cursor_sensitivity() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(sensitivity.syntax())) + .append(build_keyword_node(sensitivity.syntax())) + .nest(2), + ); + } + if let Some(scroll) = declare.cursor_scroll() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(scroll.syntax())) + .append(build_keyword_node(scroll.syntax())) + .nest(2), + ); + } + if let Some(cursor) = declare.cursor_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&cursor)) + .append(Doc::text("cursor")) + .nest(2), + ); + } + if let Some(hold) = declare.cursor_hold() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(hold.syntax())) + .append(build_keyword_node(hold.syntax())) + .nest(2), + ); + } + doc = doc.group(); + + let has_for = if let Some(for_token) = declare.for_token() { + doc = doc + .append(Doc::hard_line()) + .append(leading_comments_token(&for_token)) + .append(Doc::text("for")); + true + } else { + false + }; + if let Some(query) = declare.query() { + doc = doc + .append(if has_for { + Doc::space() + } else { + Doc::hard_line() + }) + .append(leading_comments(query.syntax())) + .append(build_select_variant(query)); + } + + doc.append(build_semicolon(declare.semicolon_token())) +} + +fn build_lock<'a>(lock: &ast::Lock) -> Doc<'a> { + let mut doc = Doc::text("lock"); + if let Some(table) = lock.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table)) + .append(Doc::text("table")); + } + if let Some(relations) = lock.relation_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(relations.syntax())) + .append(build_lock_relation_list(relations)) + .nest(2), + ); + } + if let Some(mode) = lock.lock_mode_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(mode.syntax())) + .append(build_lock_mode_clause(mode)) + .nest(2), + ); + } + if let Some(nowait) = lock.nowait() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(nowait.syntax())) + .append(build_keyword_node(nowait.syntax())) + .nest(2), + ); + } + doc.group().append(build_semicolon(lock.semicolon_token())) +} + +fn build_lock_relation_list<'a>(list: ast::RelationList) -> Doc<'a> { + build_comma_separated_docs(list.relation_names().map(|relation| { + ( + leading_comments(relation.syntax()).append(build_relation_name(relation.clone())), + relation.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil) +} + +fn build_lock_mode_clause<'a>(clause: ast::LockModeClause) -> Doc<'a> { + let mut doc = Doc::text("in"); + if let Some(mode) = clause.lock_mode() { + doc = doc + .append(Doc::space()) + .append(leading_comments(mode.syntax())) + .append(build_keyword_node(mode.syntax())); + } + if let Some(mode_token) = clause.mode_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&mode_token)) + .append(Doc::text("mode")); + } + doc.group() +} + +fn build_reindex<'a>(reindex: &ast::Reindex) -> Doc<'a> { + let mut doc = Doc::text("reindex"); + if let Some(options) = reindex.reindex_option_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(options.syntax())) + .append(build_reindex_option_list(options)); + } + if let Some(target) = reindex.reindex_target() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(target.syntax())) + .append(build_reindex_target(target)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(reindex.semicolon_token())) +} + +fn build_reindex_option_list<'a>(list: ast::ReindexOptionList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.reindex_options().map(|option| { + ( + leading_comments(option.syntax()).append(build_reindex_option(option.clone())), + option.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_reindex_option<'a>(option: ast::ReindexOption) -> Doc<'a> { + match option { + ast::ReindexOption::ReindexOptionConcurrently(option) => { + let doc = Doc::text("concurrently"); + build_reindex_boolean_option_value( + doc, + option.literal(), + option.ident_token(), + option.no_token(), + option.yes_token(), + ) + } + ast::ReindexOption::ReindexOptionVerbose(option) => { + let doc = Doc::text("verbose"); + build_reindex_boolean_option_value( + doc, + option.literal(), + option.ident_token(), + option.no_token(), + option.yes_token(), + ) + } + ast::ReindexOption::ReindexOptionTablespace(option) => { + let mut doc = Doc::text("tablespace"); + if let Some(tablespace) = option.tablespace_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(tablespace.syntax())) + .append(build_name(tablespace.syntax())); + } + doc.group() + } + } +} + +fn build_reindex_boolean_option_value<'a>( + mut doc: Doc<'a>, + literal: Option, + ident: Option, + no: Option, + yes: Option, +) -> Doc<'a> { + let value = if let Some(literal) = literal { + Some(leading_comments(literal.syntax()).append(build_literal(literal))) + } else if let Some(ident) = ident { + let text = if ident.text().starts_with('"') { + ident.text().to_string() + } else { + ident.text().to_ascii_lowercase() + }; + Some(leading_comments_token(&ident).append(Doc::text(text))) + } else if let Some(no) = no { + Some(leading_comments_token(&no).append(Doc::text("no"))) + } else { + yes.map(|yes| leading_comments_token(&yes).append(Doc::text("yes"))) + }; + if let Some(value) = value { + doc = doc.append(Doc::space()).append(value); + } + doc.group() +} + +fn build_reindex_target<'a>(target: ast::ReindexTarget) -> Doc<'a> { + match target { + ast::ReindexTarget::ReindexTargetDatabase(target) => { + let name = target + .database_ref() + .map(|name| leading_comments(name.syntax()).append(build_name(name.syntax()))); + build_reindex_target_parts("database", target.concurrently_token(), name) + } + ast::ReindexTarget::ReindexTargetIndex(target) => { + let name = target.index_ref().map(|name| { + let mut doc = leading_comments(name.syntax()); + if let Some(path) = name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + doc + }); + build_reindex_target_parts("index", target.concurrently_token(), name) + } + ast::ReindexTarget::ReindexTargetSchema(target) => { + let name = target + .schema_ref() + .map(|name| leading_comments(name.syntax()).append(build_name(name.syntax()))); + build_reindex_target_parts("schema", target.concurrently_token(), name) + } + ast::ReindexTarget::ReindexTargetSystem(target) => { + let name = target + .database_ref() + .map(|name| leading_comments(name.syntax()).append(build_name(name.syntax()))); + build_reindex_target_parts("system", target.concurrently_token(), name) + } + ast::ReindexTarget::ReindexTargetTable(target) => { + let name = target.table_name_ref().map(|name| { + let mut doc = leading_comments(name.syntax()); + if let Some(path) = name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + doc + }); + build_reindex_target_parts("table", target.concurrently_token(), name) + } + } +} + +fn build_reindex_target_parts<'a>( + keyword: &'static str, + concurrently: Option, + name: Option>, +) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(concurrently) = concurrently { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&concurrently)) + .append(Doc::text("concurrently")); + } + if let Some(name) = name { + doc = doc.append(Doc::space()).append(name); + } + doc.group() +} + +fn build_reset<'a>(reset: &ast::Reset) -> Doc<'a> { + let mut doc = Doc::text("reset"); + if let Some(target) = reset.reset_target() { + let target_doc = leading_comments(target.syntax()).append(match target { + ast::ResetTarget::All(target) => build_keyword_node(target.syntax()), + ast::ResetTarget::ConfigParameterRef(target) => { + if let Some(path) = target.path_ref() { + build_path_ref(&path) + } else { + Doc::nil() + } + } + ast::ResetTarget::ResetTimeZone(target) => build_keyword_node(target.syntax()), + ast::ResetTarget::ResetTransactionIsolation(target) => { + build_keyword_node(target.syntax()) + } + }); + doc = doc.append(Doc::line_or_space().append(target_doc).nest(2)); + } + doc.group().append(build_semicolon(reset.semicolon_token())) +} + +fn build_load<'a>(load: &ast::Load) -> Doc<'a> { + let mut doc = Doc::text("load"); + if let Some(literal) = load.literal() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + .nest(2), + ); + } + doc.group().append(build_semicolon(load.semicolon_token())) +} + +fn build_discard<'a>(discard: &ast::Discard) -> Doc<'a> { + let mut doc = Doc::text("discard"); + if let Some(target) = discard.discard_target() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(target.syntax())) + .append(build_keyword_node(target.syntax())) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(discard.semicolon_token())) +} + +fn build_fetch<'a>(fetch: &ast::Fetch) -> Doc<'a> { + let mut doc = Doc::text("fetch"); + if let Some(action) = fetch.cursor_action() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(action.syntax())) + .append(build_cursor_action(action)) + .nest(2), + ); + } + if let Some(token) = fetch.from_token().or_else(|| fetch.in_token()) { + let keyword = if token.kind() == SyntaxKind::FROM_KW { + "from" + } else { + "in" + }; + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)) + .nest(2), + ); + } + if let Some(cursor) = fetch.cursor_ref() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(cursor.syntax())) + .append(build_name(cursor.syntax())) + .nest(2), + ); + } + doc.group().append(build_semicolon(fetch.semicolon_token())) +} + +fn build_cursor_action<'a>(action: ast::CursorAction) -> Doc<'a> { + match action { + ast::CursorAction::Absolute(action) => build_cursor_action_expr("absolute", action.expr()), + ast::CursorAction::Relative(action) => build_cursor_action_expr("relative", action.expr()), + ast::CursorAction::Backward(action) => { + build_cursor_action_optional_value("backward", action.all_token(), action.expr()) + } + ast::CursorAction::Forward(action) => { + build_cursor_action_optional_value("forward", action.all_token(), action.expr()) + } + ast::CursorAction::All(action) => build_keyword_node(action.syntax()), + ast::CursorAction::First(action) => build_keyword_node(action.syntax()), + ast::CursorAction::Last(action) => build_keyword_node(action.syntax()), + ast::CursorAction::Next(action) => build_keyword_node(action.syntax()), + ast::CursorAction::Prior(action) => build_keyword_node(action.syntax()), + ast::CursorAction::Expr(expr) => build_expr(expr), + } +} + +fn build_cursor_action_expr<'a>(keyword: &'static str, expr: Option) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(expr) = expr { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc.group() +} + +fn build_cursor_action_optional_value<'a>( + keyword: &'static str, + all_token: Option, + expr: Option, +) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(all) = all_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&all)) + .append(Doc::text("all")); + } else if let Some(expr) = expr { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc.group() +} + +fn build_close<'a>(close: &ast::Close) -> Doc<'a> { + let target = if let Some(all) = close.all_token() { + Some(leading_comments_token(&all).append(Doc::text("all"))) + } else { + close + .cursor_ref() + .map(|cursor| leading_comments(cursor.syntax()).append(build_name(cursor.syntax()))) + }; + + let mut doc = Doc::text("close"); + if let Some(target) = target { + doc = doc.append(Doc::line_or_space().append(target).nest(2)); + } + doc.group().append(build_semicolon(close.semicolon_token())) +} + +fn build_cluster<'a>(cluster: &ast::Cluster) -> Doc<'a> { + let mut doc = Doc::text("cluster"); + + if let Some(verbose) = cluster.verbose_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&verbose)) + .append(Doc::text("verbose")); + } else if let Some(options) = cluster.option_item_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(options.syntax())) + .append(build_option_item_list(options)) + .nest(2), + ); + } + + if let Some(table) = cluster.table_name_ref() { + let mut table_doc = leading_comments(table.syntax()); + if let Some(path) = table.path_ref() { + table_doc = table_doc.append(build_path_ref(&path)); + } + doc = doc.append(Doc::line_or_space().append(table_doc).nest(2)); + if let Some(using_index) = cluster.cluster_using_index() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(using_index.syntax())) + .append(build_cluster_using_index(using_index)); + } + } else if let Some(legacy) = cluster.cluster_legacy() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(legacy.syntax())) + .append(build_cluster_legacy(legacy)) + .nest(2), + ); + } + + doc.group() + .append(build_semicolon(cluster.semicolon_token())) +} + +fn build_cluster_using_index<'a>(using_index: ast::ClusterUsingIndex) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(index) = using_index.index_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(index.syntax())); + if let Some(path) = index.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + doc +} + +fn build_cluster_legacy<'a>(legacy: ast::ClusterLegacy) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(index) = legacy.index_ref() { + doc = doc.append(leading_comments(index.syntax())); + if let Some(path) = index.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(on_path) = legacy.on_path() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(on_path.syntax())) + .append(Doc::text("on")); + if let Some(table) = on_path.table_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table.syntax())); + if let Some(path) = table.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + } + doc.group() +} + +fn build_option_item_list<'a>(list: ast::OptionItemList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.option_items().map(|option| { + ( + leading_comments(option.syntax()).append(build_option_item(option.clone())), + option.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_option_item<'a>(option: ast::OptionItem) -> Doc<'a> { + let mut doc = option + .option_item_key() + .map(|key| build_keyword_node(key.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(value) = option.option_item_value() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_option_item_value(value)); + } + doc.group() +} + +fn build_option_item_value<'a>(value: ast::OptionItemValue) -> Doc<'a> { + if let Some(expr) = value.expr() { + build_expr(expr) + } else if let Some(name) = value.option_item_value_name() { + build_keyword_node(name.syntax()) + } else { + Doc::text("default") + } +} + +fn build_analyze_option_list<'a>(list: ast::OptionItemList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.option_items().map(|option| { + let syntax = option.syntax().clone(); + ( + leading_comments(&syntax).append(build_option_item(option)), + syntax, + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(Doc::hard_line().append(body.group()).nest(2)) + .append(Doc::hard_line()) + .append(Doc::text(")")) +} + +fn build_analyze<'a>(analyze: &ast::Analyze) -> Doc<'a> { + let mut doc = if analyze.analyse_token().is_some() { + Doc::text("analyse") + } else { + Doc::text("analyze") + }; + + if let Some(verbose) = analyze.verbose_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&verbose)) + .append(Doc::text("verbose")); + } + if let Some(options) = analyze.option_item_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(options.syntax())) + .append(build_analyze_option_list(options)); + } + if let Some(tables) = analyze.table_and_columns_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(tables.syntax())) + .append(build_table_and_columns_list(tables)) + .nest(2), + ); + } + + doc.group() + .append(build_semicolon(analyze.semicolon_token())) +} + +fn build_vacuum<'a>(vacuum: &ast::Vacuum) -> Doc<'a> { + let mut doc = Doc::text("vacuum"); + + for (token, keyword) in [ + (vacuum.full_token(), "full"), + (vacuum.freeze_token(), "freeze"), + (vacuum.verbose_token(), "verbose"), + (vacuum.analyze_token(), "analyze"), + (vacuum.analyse_token(), "analyse"), + ] { + if let Some(token) = token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + } + + if let Some(options) = vacuum.vacuum_option_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(options.syntax())) + .append(build_vacuum_option_list(options)); + } + + if let Some(tables) = vacuum.table_and_columns_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(tables.syntax())) + .append(build_table_and_columns_list(tables)) + .nest(2), + ); + } + + doc.group() + .append(build_semicolon(vacuum.semicolon_token())) +} + +fn build_vacuum_option_list<'a>(list: ast::VacuumOptionList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.vacuum_options().map(|option| { + ( + leading_comments(option.syntax()).append(build_vacuum_option(option.clone())), + option.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_vacuum_option<'a>(option: ast::VacuumOption) -> Doc<'a> { + let mut doc = option + .vacuum_option_name() + .map(|name| build_keyword_node(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(value) = option.vacuum_option_value() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_vacuum_option_value(value)); + } + doc.group() +} + +fn build_vacuum_option_value<'a>(value: ast::VacuumOptionValue) -> Doc<'a> { + if let Some(expr) = value.expr() { + build_expr(expr) + } else if let Some(name) = value.vacuum_option_value_name() { + build_keyword_node(name.syntax()) + } else if value.no_token().is_some() { + Doc::text("no") + } else if value.yes_token().is_some() { + Doc::text("yes") + } else { + unreachable!("vacuum option value must have a value") + } +} + +fn build_table_and_columns_list<'a>(list: ast::TableAndColumnsList) -> Doc<'a> { + build_comma_separated_docs(list.table_and_columnss().map(|table| { + ( + leading_comments(table.syntax()).append(build_table_and_columns(table.clone())), + table.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil) +} + +fn build_table_and_columns<'a>(table: ast::TableAndColumns) -> Doc<'a> { + let mut doc = table + .table_relation_name() + .map(build_table_relation_name) + .unwrap_or_else(Doc::nil); + if let Some(columns) = table.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc +} + +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), + ); + } + } + + doc.group() + .append(build_semicolon(do_stmt.semicolon_token())) +} + +fn build_do_language<'a>(language: ast::DoLanguage) -> Doc<'a> { + let mut doc = Doc::text("language"); + if let Some(name) = language.language_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } else if let Some(literal) = language.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc +} + +fn build_copy<'a>(copy: &ast::Copy) -> Doc<'a> { + let mut doc = Doc::text("copy"); + + if let Some(query) = copy.copy_query() { + doc = doc + .append(Doc::space()) + .append(leading_comments(query.syntax())) + .append(build_copy_query(query)); + } else if let Some(table) = copy.copy_table() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table.syntax())) + .append(build_copy_table(table)); + } + + if let Some(direction) = copy.copy_direction() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(direction.syntax())) + .append(build_copy_direction(direction)), + ); + } + + let has_with = copy.with_token().is_some(); + if let Some(with) = copy.with_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&with)) + .append(Doc::text("with")); + } + + if let Some(options) = copy.copy_option_list() { + doc = doc + .append(if has_with { + Doc::space() + } else { + Doc::line_or_space() + }) + .append(leading_comments(options.syntax())) + .append(build_copy_option_list(options)); + } else { + for option in copy.copy_legacy_options() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(option.syntax())) + .append(build_copy_legacy_option(option)); + } + } + + if let Some(where_clause) = copy.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + + doc.group().append(build_semicolon(copy.semicolon_token())) +} + +fn build_copy_query<'a>(query: ast::CopyQuery) -> Doc<'a> { + let mut body = Doc::nil(); + if let Some(stmt) = query.preparable_stmt() { + body = body + .append(leading_comments(stmt.syntax())) + .append(build_preparable_stmt(stmt)); + } + if let Some(r_paren) = query.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + query + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_preparable_stmt<'a>(stmt: ast::PreparableStmt) -> Doc<'a> { + match stmt { + ast::PreparableStmt::CompoundSelect(stmt) => build_compound_select(&stmt), + ast::PreparableStmt::Delete(stmt) => build_delete(&stmt), + ast::PreparableStmt::Insert(stmt) => build_insert(&stmt), + ast::PreparableStmt::Merge(stmt) => build_merge(&stmt), + ast::PreparableStmt::Select(stmt) => build_select_doc(&stmt), + ast::PreparableStmt::SelectInto(stmt) => build_select_into(&stmt), + ast::PreparableStmt::Table(stmt) => build_table(&stmt), + ast::PreparableStmt::Update(stmt) => build_update(&stmt), + ast::PreparableStmt::Values(stmt) => build_values(&stmt), + } +} + +fn build_copy_table<'a>(table: ast::CopyTable) -> Doc<'a> { + let mut doc = Doc::nil(); + if table.binary_token().is_some() { + doc = doc.append(Doc::text("binary")).append(Doc::space()); + } + if let Some(name) = table.table_name_ref() { + doc = doc.append(leading_comments(name.syntax())); + if let Some(path) = name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(columns) = table.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc +} + +fn build_copy_direction<'a>(direction: ast::CopyDirection) -> Doc<'a> { + match direction { + ast::CopyDirection::CopyFrom(from) => { + let mut doc = Doc::text("from"); + if let Some(source) = from.copy_source() { + doc = doc + .append(Doc::space()) + .append(leading_comments(source.syntax())) + .append(build_copy_source(source)); + } + doc + } + ast::CopyDirection::CopyTo(to) => { + let mut doc = Doc::text("to"); + if let Some(target) = to.copy_target() { + doc = doc + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(build_copy_target(target)); + } + doc + } + } +} + +fn build_copy_source<'a>(source: ast::CopySource) -> Doc<'a> { + match source { + ast::CopySource::CopyProgram(program) => build_copy_program(program), + ast::CopySource::CopyStdin(_) => Doc::text("stdin"), + ast::CopySource::CopyStdout(_) => Doc::text("stdout"), + } +} + +fn build_copy_target<'a>(target: ast::CopyTarget) -> Doc<'a> { + match target { + ast::CopyTarget::CopyProgram(program) => build_copy_program(program), + ast::CopyTarget::CopyStdout(_) => Doc::text("stdout"), + } +} + +fn build_copy_program<'a>(program: ast::CopyProgram) -> Doc<'a> { + let mut doc = Doc::nil(); + if program.program_token().is_some() { + doc = doc.append(Doc::text("program")); + } + if let Some(literal) = program.literal() { + if program.program_token().is_some() { + doc = doc.append(Doc::space()); + } + doc = doc + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc +} + +fn build_copy_option_list<'a>(list: ast::CopyOptionList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.copy_options().map(|option| { + ( + leading_comments(option.syntax()).append(build_copy_option(option.clone())), + option.syntax().clone(), + ) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_copy_option<'a>(option: ast::CopyOption) -> Doc<'a> { + let mut doc = option + .copy_option_key() + .map(|key| build_keyword_node(key.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(value) = option.copy_option_value() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_copy_option_value(value)); + } + doc.group() +} + +fn build_copy_option_value<'a>(value: ast::CopyOptionValue) -> Doc<'a> { + if let Some(list) = value.copy_option_list() { + build_copy_option_list(list) + } else if let Some(name) = value.copy_option_value_name() { + build_keyword_node(name.syntax()) + } else if let Some(expr) = value.expr() { + build_expr(expr) + } else if value.star_token().is_some() { + Doc::text("*") + } else if value.default_token().is_some() { + Doc::text("default") + } else if value.on_token().is_some() { + Doc::text("on") + } else if value.off_token().is_some() { + Doc::text("off") + } else { + unreachable!("copy option value must have a value") + } +} + +fn build_copy_legacy_option<'a>(option: ast::CopyLegacyOption) -> Doc<'a> { + let keyword = if option.binary_token().is_some() { + "binary" + } else if option.freeze_token().is_some() { + "freeze" + } else if option.csv_token().is_some() { + "csv" + } else if option.header_token().is_some() { + "header" + } else if option.json_token().is_some() { + "json" + } else if option.delimiter_token().is_some() { + "delimiter" + } else if option.null_token().is_some() { + "null" + } else if option.quote_token().is_some() { + "quote" + } else if option.escape_token().is_some() { + "escape" + } else if option.encoding_token().is_some() { + "encoding" + } else if option.force_token().is_some() { + "force" + } else { + unreachable!("copy legacy option must have a keyword") + }; + let mut doc = Doc::text(keyword); + + if let Some(as_token) = option.as_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(literal) = option.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + if let Some(kind) = option.copy_force_kind() { + doc = doc + .append(Doc::space()) + .append(leading_comments(kind.syntax())) + .append(build_keyword_node(kind.syntax())); + } + if let Some(star) = option.star_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } else { + let names = option.column_name_refs().map(|name| { + ( + leading_comments(name.syntax()).append(build_name(name.syntax())), + name.syntax().clone(), + ) + }); + if let Some(names) = build_comma_separated_docs(names) { + doc = doc.append(Doc::space()).append(names); + } + } + doc.group() +} + +fn build_stmt<'a>(stmt: ast::Stmt) -> Doc<'a> { + match stmt { + ast::Stmt::AlterPublication(stmt) => build_alter_publication(&stmt), + ast::Stmt::AlterSubscription(stmt) => build_alter_subscription(&stmt), + ast::Stmt::Begin(stmt) => build_begin(&stmt), + ast::Stmt::Commit(stmt) => build_commit(stmt), + ast::Stmt::CompoundSelect(stmt) => build_compound_select(&stmt), + ast::Stmt::CreateFunction(stmt) => build_create_function(&stmt), + ast::Stmt::CreateIndex(stmt) => build_create_index(&stmt), + ast::Stmt::CreatePublication(stmt) => build_create_publication(&stmt), + ast::Stmt::CreateSubscription(stmt) => build_create_subscription(&stmt), + ast::Stmt::CreateTable(stmt) => build_create_table(&stmt), + ast::Stmt::CreateTableAs(stmt) => build_create_table_as(&stmt), + ast::Stmt::CreateView(stmt) => build_create_view(&stmt), + ast::Stmt::Delete(stmt) => build_delete(&stmt), + ast::Stmt::DropPublication(stmt) => build_drop_publication(&stmt), + ast::Stmt::DropSubscription(stmt) => build_drop_subscription(&stmt), + ast::Stmt::EmptyStmt(stmt) => build_empty_stmt(&stmt), + ast::Stmt::Insert(stmt) => build_insert(&stmt), + ast::Stmt::Merge(stmt) => build_merge(&stmt), + ast::Stmt::ParenSelect(stmt) => build_paren_select(stmt), + ast::Stmt::PrepareTransaction(stmt) => build_prepare_transaction(&stmt), + ast::Stmt::ReleaseSavepoint(stmt) => build_release_savepoint(&stmt), + ast::Stmt::Rollback(stmt) => build_rollback(stmt), + ast::Stmt::SavepointCreate(stmt) => build_savepoint_create(&stmt), + ast::Stmt::Select(stmt) => build_select_doc(&stmt), + ast::Stmt::SelectInto(stmt) => build_select_into(&stmt), + ast::Stmt::Table(stmt) => build_table(&stmt), + ast::Stmt::Truncate(stmt) => build_truncate(&stmt), + ast::Stmt::Update(stmt) => build_update(&stmt), + ast::Stmt::Values(stmt) => build_values(&stmt), + ast::Stmt::AlterAggregate(_) => todo!(), + ast::Stmt::AlterCollation(_) => todo!(), + ast::Stmt::AlterConversion(_) => todo!(), + ast::Stmt::AlterDatabase(_) => todo!(), + ast::Stmt::AlterDefaultPrivileges(_) => todo!(), + ast::Stmt::AlterDomain(_) => todo!(), + ast::Stmt::AlterEventTrigger(_) => todo!(), + ast::Stmt::AlterExtension(_) => todo!(), + ast::Stmt::AlterForeignDataWrapper(_) => todo!(), + ast::Stmt::AlterForeignTable(_) => todo!(), + ast::Stmt::AlterFunction(_) => todo!(), + ast::Stmt::AlterGroup(_) => todo!(), + ast::Stmt::AlterIndex(_) => todo!(), + ast::Stmt::AlterLanguage(_) => todo!(), + ast::Stmt::AlterLargeObject(_) => todo!(), + ast::Stmt::AlterMaterializedView(_) => todo!(), + ast::Stmt::AlterOperator(_) => todo!(), + ast::Stmt::AlterOperatorClass(_) => todo!(), + ast::Stmt::AlterOperatorFamily(_) => todo!(), + ast::Stmt::AlterPolicy(_) => todo!(), + ast::Stmt::AlterProcedure(_) => todo!(), + ast::Stmt::AlterPropertyGraph(_) => todo!(), + ast::Stmt::AlterRole(_) => todo!(), + ast::Stmt::AlterRoutine(_) => todo!(), + ast::Stmt::AlterRule(_) => todo!(), + ast::Stmt::AlterSchema(_) => todo!(), + ast::Stmt::AlterSequence(_) => todo!(), + ast::Stmt::AlterServer(_) => todo!(), + ast::Stmt::AlterStatistics(_) => todo!(), + ast::Stmt::AlterSystem(_) => todo!(), + ast::Stmt::AlterTable(_) => todo!(), + ast::Stmt::AlterTablespace(_) => todo!(), + ast::Stmt::AlterTextSearchConfiguration(_) => todo!(), + ast::Stmt::AlterTextSearchDictionary(_) => todo!(), + ast::Stmt::AlterTextSearchParser(_) => todo!(), + ast::Stmt::AlterTextSearchTemplate(_) => todo!(), + ast::Stmt::AlterTrigger(_) => todo!(), + ast::Stmt::AlterType(_) => todo!(), + ast::Stmt::AlterUser(_) => todo!(), + ast::Stmt::AlterUserMapping(_) => todo!(), + ast::Stmt::AlterView(_) => todo!(), + ast::Stmt::Analyze(stmt) => build_analyze(&stmt), + ast::Stmt::Call(stmt) => build_call(&stmt), + ast::Stmt::Checkpoint(stmt) => build_checkpoint(&stmt), + ast::Stmt::Close(stmt) => build_close(&stmt), + ast::Stmt::Cluster(stmt) => build_cluster(&stmt), + ast::Stmt::CommentOn(_) => todo!(), + ast::Stmt::Copy(stmt) => build_copy(&stmt), + ast::Stmt::CreateAccessMethod(_) => todo!(), + ast::Stmt::CreateAggregate(_) => todo!(), + ast::Stmt::CreateCast(_) => todo!(), + ast::Stmt::CreateCollation(_) => todo!(), + ast::Stmt::CreateConversion(_) => todo!(), + ast::Stmt::CreateDatabase(_) => todo!(), + ast::Stmt::CreateDomain(_) => todo!(), + ast::Stmt::CreateEventTrigger(_) => todo!(), + ast::Stmt::CreateExtension(_) => todo!(), + ast::Stmt::CreateForeignDataWrapper(_) => todo!(), + ast::Stmt::CreateForeignTable(stmt) => build_create_foreign_table(&stmt), + ast::Stmt::CreateGroup(_) => todo!(), + ast::Stmt::CreateLanguage(_) => todo!(), + ast::Stmt::CreateMaterializedView(_) => todo!(), + ast::Stmt::CreateOperator(_) => todo!(), + ast::Stmt::CreateOperatorClass(_) => todo!(), + ast::Stmt::CreateOperatorFamily(_) => todo!(), + ast::Stmt::CreatePolicy(_) => todo!(), + ast::Stmt::CreateProcedure(_) => todo!(), + ast::Stmt::CreatePropertyGraph(_) => todo!(), + ast::Stmt::CreateRole(_) => todo!(), + ast::Stmt::CreateRule(_) => todo!(), + ast::Stmt::CreateSchema(_) => todo!(), + ast::Stmt::CreateSequence(_) => todo!(), + ast::Stmt::CreateServer(_) => todo!(), + ast::Stmt::CreateStatistics(_) => todo!(), + ast::Stmt::CreateTablespace(_) => todo!(), + ast::Stmt::CreateTextSearchConfiguration(_) => todo!(), + ast::Stmt::CreateTextSearchDictionary(_) => todo!(), + ast::Stmt::CreateTextSearchParser(_) => todo!(), + ast::Stmt::CreateTextSearchTemplate(_) => todo!(), + ast::Stmt::CreateTransform(stmt) => build_create_transform(&stmt), + ast::Stmt::CreateTrigger(stmt) => build_create_trigger(&stmt), + ast::Stmt::CreateType(_) => todo!(), + ast::Stmt::CreateUser(_) => todo!(), + ast::Stmt::CreateUserMapping(_) => todo!(), + ast::Stmt::Deallocate(stmt) => build_deallocate(&stmt), + ast::Stmt::Declare(stmt) => build_declare(&stmt), + ast::Stmt::Discard(stmt) => build_discard(&stmt), + ast::Stmt::Do(stmt) => build_do(&stmt), + ast::Stmt::DropAccessMethod(_) => todo!(), + ast::Stmt::DropAggregate(_) => todo!(), + ast::Stmt::DropCast(_) => todo!(), + ast::Stmt::DropCollation(_) => todo!(), + ast::Stmt::DropConversion(_) => todo!(), + ast::Stmt::DropDatabase(_) => todo!(), + ast::Stmt::DropDomain(_) => todo!(), + ast::Stmt::DropEventTrigger(_) => todo!(), + ast::Stmt::DropExtension(_) => todo!(), + ast::Stmt::DropForeignDataWrapper(_) => todo!(), + ast::Stmt::DropForeignTable(_) => todo!(), + ast::Stmt::DropFunction(_) => todo!(), + ast::Stmt::DropGroup(_) => todo!(), + ast::Stmt::DropIndex(_) => todo!(), + ast::Stmt::DropLanguage(_) => todo!(), + ast::Stmt::DropMaterializedView(_) => todo!(), + ast::Stmt::DropOperator(_) => todo!(), + ast::Stmt::DropOperatorClass(_) => todo!(), + ast::Stmt::DropOperatorFamily(_) => todo!(), + ast::Stmt::DropOwned(_) => todo!(), + ast::Stmt::DropPolicy(_) => todo!(), + ast::Stmt::DropProcedure(_) => todo!(), + ast::Stmt::DropPropertyGraph(_) => todo!(), + ast::Stmt::DropRole(_) => todo!(), + ast::Stmt::DropRoutine(_) => todo!(), + ast::Stmt::DropRule(_) => todo!(), + ast::Stmt::DropSchema(_) => todo!(), + ast::Stmt::DropSequence(_) => todo!(), + ast::Stmt::DropServer(_) => todo!(), + ast::Stmt::DropStatistics(_) => todo!(), + ast::Stmt::DropTable(_) => todo!(), + ast::Stmt::DropTablespace(_) => todo!(), + ast::Stmt::DropTextSearchConfig(_) => todo!(), + ast::Stmt::DropTextSearchDict(_) => todo!(), + ast::Stmt::DropTextSearchParser(_) => todo!(), + ast::Stmt::DropTextSearchTemplate(_) => todo!(), + ast::Stmt::DropTransform(_) => todo!(), + ast::Stmt::DropTrigger(_) => todo!(), + ast::Stmt::DropType(_) => todo!(), + ast::Stmt::DropUser(_) => todo!(), + ast::Stmt::DropUserMapping(_) => todo!(), + ast::Stmt::DropView(_) => todo!(), + ast::Stmt::Execute(stmt) => build_execute(stmt), + ast::Stmt::Explain(stmt) => build_explain(&stmt), + ast::Stmt::Fetch(stmt) => build_fetch(&stmt), + ast::Stmt::Grant(stmt) => build_grant(&stmt), + ast::Stmt::ImportForeignSchema(stmt) => build_import_foreign_schema(&stmt), + ast::Stmt::Listen(stmt) => build_listen(&stmt), + ast::Stmt::Load(stmt) => build_load(&stmt), + ast::Stmt::Lock(stmt) => build_lock(&stmt), + ast::Stmt::Move(stmt) => build_move(&stmt), + ast::Stmt::Notify(stmt) => build_notify(&stmt), + ast::Stmt::Prepare(stmt) => build_prepare(&stmt), + ast::Stmt::Reassign(stmt) => build_reassign(&stmt), + ast::Stmt::Refresh(stmt) => build_refresh(&stmt), + ast::Stmt::Reindex(stmt) => build_reindex(&stmt), + ast::Stmt::Repack(stmt) => build_repack(&stmt), + ast::Stmt::Reset(stmt) => build_reset(&stmt), + ast::Stmt::ResetRole(stmt) => build_reset_role(&stmt), + ast::Stmt::ResetSessionAuth(stmt) => build_reset_session_auth(&stmt), + ast::Stmt::Revoke(stmt) => build_revoke(&stmt), + ast::Stmt::SecurityLabel(stmt) => build_security_label(&stmt), + ast::Stmt::Set(stmt) => build_set(&stmt), + ast::Stmt::SetConstraints(stmt) => build_set_constraints(&stmt), + ast::Stmt::SetRole(stmt) => build_set_role(&stmt), + ast::Stmt::SetSessionAuth(stmt) => build_set_session_auth(&stmt), + ast::Stmt::SetTransaction(stmt) => build_set_transaction(&stmt), + ast::Stmt::Show(stmt) => build_show(&stmt), + ast::Stmt::Unlisten(stmt) => build_unlisten(&stmt), + ast::Stmt::Vacuum(stmt) => build_vacuum(&stmt), + } +} + +fn build_return_function_option<'a>(option: ast::ReturnFuncOption) -> Doc<'a> { + let mut doc = Doc::text("return"); + if let Some(expr) = option.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc.append(build_semicolon(option.semicolon_token())) +} + +fn build_literal_function_option<'a>( + keyword: &'static str, + literal: Option, +) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(literal) = literal { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc +} + +fn build_as_function_option<'a>(option: ast::AsFuncOption) -> Doc<'a> { + let mut doc = Doc::text("as"); + if let Some(target) = option.as_func_target() { + match target { + ast::AsFuncTarget::AsDefinition(definition) => { + if let Some(literal) = definition.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(definition.syntax())) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + } + ast::AsFuncTarget::AsObjFile(obj_file) => { + doc = doc + .append(Doc::space()) + .append(leading_comments(obj_file.syntax())); + if let Some(literal) = obj_file.obj_file() { + doc = doc + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + if let Some(comma) = obj_file.comma_token() { + doc = doc.append(comments_before(comma)).append(Doc::text(",")); + } + if let Some(literal) = obj_file.link_symbol() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc = doc.group(); + } + } + } + doc +} + +fn build_explain<'a>(explain: &ast::Explain) -> Doc<'a> { + let mut doc = Doc::text("explain"); + if let Some(mode) = explain.explain_mode() { + let mode_syntax = mode.syntax().clone(); + let parenthesized = matches!(&mode, ast::ExplainMode::ExplainOptionList(_)); + let mode_doc = match mode { + ast::ExplainMode::ExplainAnalyze(analyze) => { + let mut doc = if analyze.analyse_token().is_some() { + Doc::text("analyse") + } else { + Doc::text("analyze") + }; + if let Some(verbose) = analyze.explain_verbose() { + doc = doc + .append(Doc::space()) + .append(leading_comments(verbose.syntax())) + .append(build_keyword_node(verbose.syntax())); + } + doc + } + ast::ExplainMode::ExplainVerbose(verbose) => build_keyword_node(verbose.syntax()), + ast::ExplainMode::ExplainOptionList(options) => build_explain_option_list(options), + }; + let mode_doc = leading_comments(&mode_syntax).append(mode_doc); + doc = if parenthesized { + doc.append(Doc::space()).append(mode_doc) + } else { + doc.append(Doc::line_or_space().append(mode_doc).nest(2)) + }; + } + if let Some(stmt) = explain.explain_stmt() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(stmt.syntax())) + .append(build_explain_stmt(stmt)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(explain.semicolon_token())) +} + +fn build_explain_option_list<'a>(list: ast::ExplainOptionList) -> Doc<'a> { + let mut body = build_comma_separated_docs(list.explain_options().map(|option| { + let syntax = option.syntax().clone(); + let mut doc = option + .explain_option_name() + .map(|name| build_keyword_node(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(value) = option.explain_option_value() { + let value_doc = value + .expr() + .map(build_expr) + .unwrap_or_else(|| build_keyword_node(value.syntax())); + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(value_doc); + } + (leading_comments(&syntax).append(doc), syntax) + })) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + list.l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(Doc::hard_line().append(body.group()).nest(2)) + .append(Doc::hard_line()) + .append(Doc::text(")")) +} + +fn build_create_materialized_view<'a>(view: &ast::CreateMaterializedView) -> Doc<'a> { + let mut doc = Doc::text("create"); + for (token, keyword) in [ + (view.materialized_token(), "materialized"), + (view.view_token(), "view"), + ] { + if let Some(token) = token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + } + if let Some(if_not_exists) = view.if_not_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_not_exists.syntax())) + .append(build_keyword_node(if_not_exists.syntax())); + } + if let Some(name) = view.view() { + if let Some(path) = name.path() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_path(&path)); + } + } + if let Some(columns) = view.column_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_cte_column_list(columns)); + } + if let Some(using) = view.using_method() { + let mut option = Doc::text("using"); + if let Some(method) = using.access_method_ref() { + option = option + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(build_name(method.syntax())); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(using.syntax())) + .append(option) + .nest(2), + ); + } + if let Some(params) = view.with_params() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(params.syntax())) + .append(build_with_params(params)) + .nest(2), + ); + } + if let Some(tablespace) = view.tablespace_clause() { + let mut option = Doc::text("tablespace"); + if let Some(name) = tablespace.tablespace_ref() { + option = option + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(tablespace.syntax())) + .append(option) + .nest(2), + ); + } + if let Some(as_token) = view.as_token() { + doc = doc + .append(Doc::hard_line()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(query) = view.query() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(query.syntax())) + .append(build_select_variant(query)) + .nest(2), + ); + } + if let Some(data) = view.data_option() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(data.syntax())) + .append(build_keyword_node(data.syntax())) + .nest(2), + ); + } + doc.group().append(build_semicolon(view.semicolon_token())) +} + +fn build_explain_stmt<'a>(stmt: ast::ExplainStmt) -> Doc<'a> { + match stmt { + ast::ExplainStmt::CompoundSelect(stmt) => build_compound_select(&stmt), + ast::ExplainStmt::CreateTableAs(stmt) => build_create_table_as(&stmt), + ast::ExplainStmt::Declare(stmt) => build_declare(&stmt), + ast::ExplainStmt::Delete(stmt) => build_delete(&stmt), + ast::ExplainStmt::Execute(stmt) => build_execute(stmt), + ast::ExplainStmt::Insert(stmt) => build_insert(&stmt), + ast::ExplainStmt::Merge(stmt) => build_merge(&stmt), + ast::ExplainStmt::ParenSelect(stmt) => build_paren_select(stmt), + ast::ExplainStmt::Select(stmt) => build_select_doc(&stmt), + ast::ExplainStmt::SelectInto(stmt) => build_select_into(&stmt), + ast::ExplainStmt::Table(stmt) => build_table(&stmt), + ast::ExplainStmt::Update(stmt) => build_update(&stmt), + ast::ExplainStmt::Values(stmt) => build_values(&stmt), + ast::ExplainStmt::CreateMaterializedView(stmt) => build_create_materialized_view(&stmt), + } +} + +fn build_role_ref_list<'a>(list: ast::RoleRefList) -> Doc<'a> { + build_comma_separated_docs(list.role_refs().map(|role| { + let syntax = role.syntax().clone(); + ( + leading_comments(&syntax).append(build_role_ref(&role)), + syntax, + ) + })) + .unwrap_or_else(Doc::nil) +} + +fn build_keyword_tokens<'a, const N: usize>( + tokens: [(Option, &'static str); N], +) -> Doc<'a> { + let mut doc = Doc::nil(); + let mut has_keyword = false; + for (token, keyword) in tokens { + if let Some(token) = token { + if has_keyword { + doc = doc.append(Doc::space()); + } + has_keyword = true; + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + } + doc +} + +fn build_revoke_command<'a>(command: ast::RevokeCommand) -> Doc<'a> { + let mut doc = if let Some(role) = command.role_ref() { + build_role_ref(&role) + } else if command.ident_token().is_some() { + build_name(command.syntax()) + } else { + let tokens = [ + (command.all_token(), "all"), + (command.alter_token(), "alter"), + (command.create_token(), "create"), + (command.delete_token(), "delete"), + (command.execute_token(), "execute"), + (command.insert_token(), "insert"), + (command.references_token(), "references"), + (command.select_token(), "select"), + (command.system_token(), "system"), + (command.temp_token(), "temp"), + (command.temporary_token(), "temporary"), + (command.trigger_token(), "trigger"), + (command.truncate_token(), "truncate"), + (command.update_token(), "update"), + ]; + build_keyword_tokens(tokens) + }; + if let Some(columns) = command.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc.group() +} + +fn build_privileges<'a>(privileges: ast::Privileges) -> Doc<'a> { + match privileges { + ast::Privileges::AllPrivileges(all) => { + let mut doc = build_keyword_tokens([ + (all.all_token(), "all"), + (all.privileges_token(), "privileges"), + ]); + if let Some(columns) = all.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc + } + ast::Privileges::RevokeCommandList(commands) => { + build_comma_separated_docs(commands.revoke_commands().map(|command| { + let syntax = command.syntax().clone(); + ( + leading_comments(&syntax).append(build_revoke_command(command)), + syntax, + ) + })) + .unwrap_or_else(Doc::nil) + } + } +} + +fn build_path_items<'a>(items: Vec<(SyntaxNode, ast::PathRef)>) -> Doc<'a> { + build_comma_separated_docs(items.into_iter().map(|(syntax, path)| { + ( + leading_comments(&syntax).append(build_path_ref(&path)), + syntax, + ) + })) + .unwrap_or_else(Doc::nil) +} + +fn build_name_items<'a>(items: Vec) -> Doc<'a> { + build_comma_separated_docs(items.into_iter().map(|syntax| { + let doc = leading_comments(&syntax).append(build_name(&syntax)); + (doc, syntax) + })) + .unwrap_or_else(Doc::nil) +} + +fn append_privilege_items<'a>(prefix: Doc<'a>, items: Doc<'a>) -> Doc<'a> { + prefix + .append(Doc::line_or_space().append(items).nest(2)) + .group() +} + +fn build_function_sig<'a>(sig: ast::FunctionSig) -> Doc<'a> { + let mut doc = sig + .function_name_ref() + .and_then(|name| name.path_ref()) + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + if let Some(params) = sig.param_list() { + doc = doc + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + doc +} + +fn build_procedure_sig<'a>(sig: ast::ProcedureSig) -> Doc<'a> { + let mut doc = sig + .procedure_name_ref() + .and_then(|name| name.path_ref()) + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + if let Some(params) = sig.param_list() { + doc = doc + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + doc +} + +fn build_routine_sig<'a>(sig: ast::RoutineSig) -> Doc<'a> { + let mut doc = sig + .routine_name_ref() + .and_then(|name| name.path_ref()) + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + if let Some(params) = sig.param_list() { + doc = doc + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + doc +} + +fn build_privilege_objects<'a>(objects: ast::PrivilegeObjects) -> Doc<'a> { + macro_rules! direct_items { + ($node:ident, $method:ident, $tokens:expr) => {{ + let items = + build_name_items($node.$method().map(|item| item.syntax().clone()).collect()); + append_privilege_items(build_keyword_tokens($tokens), items) + }}; + } + match objects { + ast::PrivilegeObjects::PrivilegeAllFunctionsInSchema(node) => direct_items!( + node, + schema_refs, + [ + (node.all_token(), "all"), + (node.functions_token(), "functions"), + (node.in_token(), "in"), + (node.schema_token(), "schema") + ] + ), + ast::PrivilegeObjects::PrivilegeAllProceduresInSchema(node) => direct_items!( + node, + schema_refs, + [ + (node.all_token(), "all"), + (node.procedures_token(), "procedures"), + (node.in_token(), "in"), + (node.schema_token(), "schema") + ] + ), + ast::PrivilegeObjects::PrivilegeAllRoutinesInSchema(node) => direct_items!( + node, + schema_refs, + [ + (node.all_token(), "all"), + (node.routines_token(), "routines"), + (node.in_token(), "in"), + (node.schema_token(), "schema") + ] + ), + ast::PrivilegeObjects::PrivilegeAllSequencesInSchema(node) => direct_items!( + node, + schema_refs, + [ + (node.all_token(), "all"), + (node.sequences_token(), "sequences"), + (node.in_token(), "in"), + (node.schema_token(), "schema") + ] + ), + ast::PrivilegeObjects::PrivilegeAllTablesInSchema(node) => direct_items!( + node, + schema_refs, + [ + (node.all_token(), "all"), + (node.tables_token(), "tables"), + (node.in_token(), "in"), + (node.schema_token(), "schema") + ] + ), + ast::PrivilegeObjects::PrivilegeDatabase(node) => { + direct_items!(node, database_refs, [(node.database_token(), "database")]) + } + ast::PrivilegeObjects::PrivilegeDefault(node) => build_path_items( + node.relation_name_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ), + ast::PrivilegeObjects::PrivilegeDomain(node) => { + let items = build_path_items( + node.domain_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ); + append_privilege_items( + build_keyword_tokens([(node.domain_token(), "domain")]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeForeignDataWrapper(node) => direct_items!( + node, + foreign_data_wrapper_refs, + [ + (node.foreign_token(), "foreign"), + (node.data_token(), "data"), + (node.wrapper_token(), "wrapper") + ] + ), + ast::PrivilegeObjects::PrivilegeForeignServer(node) => direct_items!( + node, + server_refs, + [ + (node.foreign_token(), "foreign"), + (node.server_token(), "server") + ] + ), + ast::PrivilegeObjects::PrivilegeLanguage(node) => { + direct_items!(node, language_refs, [(node.language_token(), "language")]) + } + ast::PrivilegeObjects::PrivilegeParameter(node) => { + let items = + build_comma_separated_docs(node.config_parameter_refs().filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| { + ( + leading_comments(&syntax).append(build_path_ref(&path)), + syntax, + ) + }) + })) + .unwrap_or_else(Doc::nil); + append_privilege_items( + build_keyword_tokens([(node.parameter_token(), "parameter")]), + items, + ) + } + ast::PrivilegeObjects::PrivilegePropertyGraph(node) => { + let items = build_path_items( + node.property_graph_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ); + append_privilege_items( + build_keyword_tokens([ + (node.property_token(), "property"), + (node.graph_token(), "graph"), + ]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeSchema(node) => { + direct_items!(node, schema_refs, [(node.schema_token(), "schema")]) + } + ast::PrivilegeObjects::PrivilegeSequence(node) => { + let items = build_path_items( + node.sequence_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ); + append_privilege_items( + build_keyword_tokens([(node.sequence_token(), "sequence")]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeTable(node) => { + let items = build_path_items( + node.relation_name_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ); + append_privilege_items(build_keyword_tokens([(node.table_token(), "table")]), items) + } + ast::PrivilegeObjects::PrivilegeTablespace(node) => direct_items!( + node, + tablespace_refs, + [(node.tablespace_token(), "tablespace")] + ), + ast::PrivilegeObjects::PrivilegeType(node) => { + let items = build_path_items( + node.type_name_refs() + .filter_map(|item| { + let syntax = item.syntax().clone(); + item.path_ref().map(|path| (syntax, path)) + }) + .collect(), + ); + append_privilege_items(build_keyword_tokens([(node.type_token(), "type")]), items) + } + ast::PrivilegeObjects::PrivilegeLargeObject(node) => { + let items = build_comma_separated_docs(node.literals().map(|item| { + let syntax = item.syntax().clone(); + ( + leading_comments(&syntax).append(build_literal(item)), + syntax, + ) + })) + .unwrap_or_else(Doc::nil); + append_privilege_items( + build_keyword_tokens([ + (node.large_token(), "large"), + (node.object_token(), "object"), + ]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeFunction(node) => { + let items = node + .function_sig_list() + .and_then(|list| { + build_comma_separated_docs(list.function_sigs().map(|sig| { + let syntax = sig.syntax().clone(); + ( + leading_comments(&syntax).append(build_function_sig(sig)), + syntax, + ) + })) + }) + .unwrap_or_else(Doc::nil); + append_privilege_items( + build_keyword_tokens([(node.function_token(), "function")]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeProcedure(node) => { + let items = node + .procedure_sig_list() + .and_then(|list| { + build_comma_separated_docs(list.procedure_sigs().map(|sig| { + let syntax = sig.syntax().clone(); + ( + leading_comments(&syntax).append(build_procedure_sig(sig)), + syntax, + ) + })) + }) + .unwrap_or_else(Doc::nil); + append_privilege_items( + build_keyword_tokens([(node.procedure_token(), "procedure")]), + items, + ) + } + ast::PrivilegeObjects::PrivilegeRoutine(node) => { + let items = node + .routine_sig_list() + .and_then(|list| { + build_comma_separated_docs(list.routine_sigs().map(|sig| { + let syntax = sig.syntax().clone(); + ( + leading_comments(&syntax).append(build_routine_sig(sig)), + syntax, + ) + })) + }) + .unwrap_or_else(Doc::nil); + append_privilege_items( + build_keyword_tokens([(node.routine_token(), "routine")]), + items, + ) + } + } +} + +fn build_grant_with_clause<'a>(with: ast::GrantWithClause) -> Doc<'a> { + let mut doc = Doc::text("with"); + if let Some(option) = with.grant_option() { + doc = doc + .append(Doc::space()) + .append(leading_comments(option.syntax())) + .append(build_keyword_node(option.syntax())); + } else if let Some(options) = with.grant_role_option_list() { + let options_doc = build_comma_separated_docs(options.grant_role_options().map(|option| { + let syntax = option.syntax().clone(); + let mut option_doc = option + .grant_role_option_name() + .map(|name| build_keyword_node(name.syntax())) + .unwrap_or_else(Doc::nil); + let value = option + .option_token() + .map(|token| (token, "option")) + .or_else(|| option.true_token().map(|token| (token, "true"))) + .or_else(|| option.false_token().map(|token| (token, "false"))); + if let Some((token, keyword)) = value { + option_doc = option_doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + (leading_comments(&syntax).append(option_doc), syntax) + })) + .unwrap_or_else(Doc::nil); + doc = doc.append(Doc::line_or_space().append(options_doc).nest(2)); + } + doc.group() +} + +fn build_granted_by_clause<'a>(granted: ast::GrantedByClause) -> Doc<'a> { + let mut doc = Doc::text("granted"); + if let Some(by) = granted.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&by)) + .append(Doc::text("by")); + } + if let Some(role) = granted.role_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(role.syntax())) + .append(build_role_ref(&role)); + } + doc.group() +} + +fn build_grant<'a>(grant: &ast::Grant) -> Doc<'a> { + 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_privilege_objects_clause() { + let mut on_doc = Doc::text("on"); + if let Some(objects) = on.privilege_objects() { + on_doc = on_doc.append( + Doc::line_or_space() + .append(leading_comments(objects.syntax())) + .append(build_privilege_objects(objects)) + .nest(2), + ); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(on.syntax())) + .append(on_doc) + .nest(2), + ); + } + if let Some(to) = grant.to_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&to)) + .append(Doc::text("to")) + .nest(2), + ); + } + if let Some(roles) = grant.role_ref_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(roles.syntax())) + .append(build_role_ref_list(roles)) + .nest(2), + ); + } + if let Some(with) = grant.grant_with_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(with.syntax())) + .append(build_grant_with_clause(with)) + .nest(2), + ); + } + if let Some(granted) = grant.granted_by_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(granted.syntax())) + .append(build_granted_by_clause(granted)) + .nest(2), + ); + } + doc.group().append(build_semicolon(grant.semicolon_token())) +} + +fn build_revoke_option_for<'a>(option: ast::RevokeOptionFor) -> Doc<'a> { + match option { + ast::RevokeOptionFor::AdminOptionFor(option) => build_keyword_node(option.syntax()), + ast::RevokeOptionFor::GrantOptionFor(option) => build_keyword_node(option.syntax()), + ast::RevokeOptionFor::InheritOptionFor(option) => build_keyword_node(option.syntax()), + ast::RevokeOptionFor::SetOptionFor(option) => build_keyword_node(option.syntax()), + } +} + +fn build_revoke<'a>(revoke: &ast::Revoke) -> Doc<'a> { + let mut doc = Doc::text("revoke"); + if let Some(option) = revoke.revoke_option_for() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(option.syntax())) + .append(build_revoke_option_for(option)) + .nest(2), + ); + } + 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_privilege_objects_clause() { + let mut on_doc = Doc::text("on"); + if let Some(objects) = on.privilege_objects() { + on_doc = on_doc.append( + Doc::line_or_space() + .append(leading_comments(objects.syntax())) + .append(build_privilege_objects(objects)) + .nest(2), + ); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(on.syntax())) + .append(on_doc) + .nest(2), + ); + } + if let Some(from) = revoke.from_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&from)) + .append(Doc::text("from")) + .nest(2), + ); + } + if let Some(roles) = revoke.role_ref_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(roles.syntax())) + .append(build_role_ref_list(roles)) + .nest(2), + ); + } + if let Some(granted) = revoke.granted_by_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(granted.syntax())) + .append(build_granted_by_clause(granted)) + .nest(2), + ); + } + if let Some(behavior) = revoke.drop_behavior() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(behavior.syntax())) + .append(build_drop_behavior(behavior)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(revoke.semicolon_token())) +} + +fn build_import_table_filter<'a>(filter: ast::ImportTableFilter) -> Doc<'a> { + let (mut doc, names, l_paren, r_paren) = match filter { + ast::ImportTableFilter::ExceptTables(filter) => ( + Doc::text("except"), + filter + .remote_table_name_refs() + .map(|name| (name.syntax().clone(), build_name(name.syntax()))) + .collect::>(), + filter.l_paren_token(), + filter.r_paren_token(), + ), + ast::ImportTableFilter::LimitToTables(filter) => { + let mut doc = Doc::text("limit"); + if let Some(to) = filter.to_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&to)) + .append(Doc::text("to")); + } + ( + doc, + filter + .remote_table_name_refs() + .map(|name| (name.syntax().clone(), build_name(name.syntax()))) + .collect::>(), + filter.l_paren_token(), + filter.r_paren_token(), + ) + } + }; + let mut body = build_comma_separated_docs( + names + .into_iter() + .map(|(syntax, name)| (leading_comments(&syntax).append(name), syntax)), + ) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = r_paren { + body = body.append(comments_before(r_paren)); + } + doc = doc + .append(Doc::space()) + .append(l_paren.map(comments_before).unwrap_or_else(Doc::nil)) + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")); + doc.group() +} + +fn build_import_foreign_schema<'a>(import: &ast::ImportForeignSchema) -> Doc<'a> { + let mut doc = Doc::text("import"); + for (token, keyword) in [ + (import.foreign_token(), "foreign"), + (import.schema_token(), "schema"), + ] { + if let Some(token) = token { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)) + .nest(2), + ); + } + } + if let Some(schema) = import.schema_ref() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(schema.syntax())) + .append(build_name(schema.syntax())) + .nest(2), + ); + } + if let Some(filter) = import.import_table_filter() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(filter.syntax())) + .append(build_import_table_filter(filter)) + .nest(2), + ); + } + if let Some(from) = import.from_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&from)) + .append(Doc::text("from")) + .nest(2), + ); + } + if let Some(server) = import.server_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(server.syntax())) + .append(build_server_clause(server)) + .nest(2), + ); + } + if let Some(into) = import.into_schema() { + let mut into_doc = Doc::text("into"); + if let Some(schema) = into.schema_ref() { + into_doc = into_doc + .append(Doc::space()) + .append(leading_comments(schema.syntax())) + .append(build_name(schema.syntax())); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(into.syntax())) + .append(into_doc) + .nest(2), + ); + } + if let Some(options) = import.alter_option_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(options.syntax())) + .append(build_alter_option_list(options)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(import.semicolon_token())) +} + +fn build_listen<'a>(listen: &ast::Listen) -> Doc<'a> { + let mut doc = Doc::text("listen"); + if let Some(channel) = listen.channel() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(channel.syntax())) + .append(build_name(channel.syntax())) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(listen.semicolon_token())) +} + +fn build_move<'a>(move_stmt: &ast::Move) -> Doc<'a> { + let mut doc = Doc::text("move"); + if let Some(action) = move_stmt.cursor_action() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(action.syntax())) + .append(build_cursor_action(action)) + .nest(2), + ); + } + if let Some(token) = move_stmt.from_token().or_else(|| move_stmt.in_token()) { + let keyword = if token.kind() == SyntaxKind::FROM_KW { + "from" + } else { + "in" + }; + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)) + .nest(2), + ); + } + if let Some(cursor) = move_stmt.cursor_ref() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(cursor.syntax())) + .append(build_name(cursor.syntax())) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(move_stmt.semicolon_token())) +} + +fn build_notify<'a>(notify: &ast::Notify) -> Doc<'a> { + let mut doc = Doc::text("notify"); + if let Some(channel) = notify.channel_ref() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(channel.syntax())) + .append(build_name(channel.syntax())) + .nest(2), + ); + } + if let Some(comma) = notify.comma_token() { + doc = doc.append(comments_before(comma)).append(Doc::text(",")); + } + if let Some(payload) = notify.literal() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(payload.syntax())) + .append(build_literal(payload)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(notify.semicolon_token())) +} + +fn build_reassign<'a>(reassign: &ast::Reassign) -> Doc<'a> { + let mut doc = Doc::text("reassign"); + for (token, keyword) in [ + (reassign.owned_token(), "owned"), + (reassign.by_token(), "by"), + ] { + if let Some(token) = token { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)) + .nest(2), + ); + } + } + if let Some(roles) = reassign.before() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(roles.syntax())) + .append(build_role_ref_list(roles)) + .nest(2), + ); + } + if let Some(to) = reassign.to_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&to)) + .append(Doc::text("to")) + .nest(2), + ); + } + if let Some(roles) = reassign.after() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(roles.syntax())) + .append(build_role_ref_list(roles)) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(reassign.semicolon_token())) +} + +fn build_refresh<'a>(refresh: &ast::Refresh) -> Doc<'a> { + let mut doc = Doc::text("refresh"); + for (token, keyword) in [ + (refresh.materialized_token(), "materialized"), + (refresh.view_token(), "view"), + (refresh.concurrently_token(), "concurrently"), + ] { + if let Some(token) = token { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)) + .nest(2), + ); + } + } + if let Some(view) = refresh.view_ref() { + let view_doc = view + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(view.syntax())) + .append(view_doc) + .nest(2), + ); + } + if let Some(data) = refresh.data_option() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(data.syntax())) + .append(build_keyword_node(data.syntax())) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(refresh.semicolon_token())) +} + +fn build_repack<'a>(repack: &ast::Repack) -> Doc<'a> { + let mut doc = Doc::text("repack"); + if let Some(options) = repack.option_item_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(options.syntax())) + .append(build_option_item_list(options)) + .nest(2), + ); + } + if let Some(tables) = repack.table_and_columns_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(tables.syntax())) + .append(build_table_and_columns_list(tables)) + .nest(2), + ); + } + if let Some(using_index) = repack.using_index() { + let mut using_doc = Doc::text("using"); + if let Some(index) = using_index.index_token() { + using_doc = using_doc + .append(Doc::space()) + .append(leading_comments_token(&index)) + .append(Doc::text("index")); + } + if let Some(index) = using_index.index_ref() { + let name = index + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + using_doc = using_doc + .append(Doc::space()) + .append(leading_comments(index.syntax())) + .append(name); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(using_index.syntax())) + .append(using_doc) + .nest(2), + ); + } + doc.group() + .append(build_semicolon(repack.semicolon_token())) +} + +fn build_reset_role<'a>(reset: &ast::ResetRole) -> Doc<'a> { + let mut doc = Doc::text("reset"); + if let Some(role) = reset.role_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&role)) + .append(Doc::text("role")); + } + doc.append(build_semicolon(reset.semicolon_token())) +} + +fn build_reset_session_auth<'a>(reset: &ast::ResetSessionAuth) -> Doc<'a> { + let mut doc = Doc::text("reset"); + for (token, keyword) in [ + (reset.session_token(), "session"), + (reset.authorization_token(), "authorization"), + ] { + if let Some(token) = token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + } + doc.append(build_semicolon(reset.semicolon_token())) +} + +fn append_security_object_value<'a>(prefix: Doc<'a>, value: Doc<'a>) -> Doc<'a> { + prefix + .append(Doc::line_or_space().append(value).nest(2)) + .group() +} + +fn build_security_object_value<'a>(node: &impl AstNode, value: Doc<'a>) -> Doc<'a> { + leading_comments(node.syntax()).append(value) +} + +fn build_aggregate_sig<'a>(aggregate: ast::Aggregate) -> Doc<'a> { + let mut doc = aggregate + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + if let Some(params) = aggregate.param_list() { + doc = doc + .append(leading_comments(params.syntax())) + .append(build_function_param_list(params)); + } + doc +} + +fn build_security_label_object<'a>(object: ast::SecurityLabelObject) -> Doc<'a> { + match object { + ast::SecurityLabelObject::ObjectAggregate(node) => { + let value = node + .aggregate() + .map(|value| { + let doc = build_aggregate_sig(value.clone()); + build_security_object_value(&value, doc) + }) + .unwrap_or_else(Doc::nil); + append_security_object_value( + build_keyword_tokens([(node.aggregate_token(), "aggregate")]), + value, + ) + } + ast::SecurityLabelObject::ObjectColumn(node) => { + let value = node + .name() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil); + append_security_object_value( + build_keyword_tokens([(node.column_token(), "column")]), + value, + ) + } + ast::SecurityLabelObject::ObjectDatabase(node) => append_security_object_value( + build_keyword_tokens([(node.database_token(), "database")]), + node.database_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectDomain(node) => append_security_object_value( + build_keyword_tokens([(node.domain_token(), "domain")]), + node.domain_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectEventTrigger(node) => append_security_object_value( + build_keyword_tokens([ + (node.event_token(), "event"), + (node.trigger_token(), "trigger"), + ]), + node.event_trigger_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectForeignTable(node) => append_security_object_value( + build_keyword_tokens([ + (node.foreign_token(), "foreign"), + (node.table_token(), "table"), + ]), + node.table_name_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectFunction(node) => append_security_object_value( + build_keyword_tokens([(node.function_token(), "function")]), + node.function_sig() + .map(|value| { + let doc = build_function_sig(value.clone()); + build_security_object_value(&value, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectLanguage(node) => append_security_object_value( + build_keyword_tokens([ + (node.procedural_token(), "procedural"), + (node.language_token(), "language"), + ]), + node.language_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectLargeObject(node) => append_security_object_value( + build_keyword_tokens([ + (node.large_token(), "large"), + (node.object_token(), "object"), + ]), + node.literal() + .map(|value| { + let doc = build_literal(value.clone()); + build_security_object_value(&value, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectMaterializedView(node) => append_security_object_value( + build_keyword_tokens([ + (node.materialized_token(), "materialized"), + (node.view_token(), "view"), + ]), + node.view_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectProcedure(node) => append_security_object_value( + build_keyword_tokens([(node.procedure_token(), "procedure")]), + node.procedure_sig() + .map(|value| { + let doc = build_procedure_sig(value.clone()); + build_security_object_value(&value, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectPublication(node) => append_security_object_value( + build_keyword_tokens([(node.publication_token(), "publication")]), + node.publication_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectRole(node) => append_security_object_value( + build_keyword_tokens([(node.role_token(), "role")]), + node.role_ref() + .map(|role| { + let doc = build_role_ref(&role); + build_security_object_value(&role, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectRoutine(node) => append_security_object_value( + build_keyword_tokens([(node.routine_token(), "routine")]), + node.routine_sig() + .map(|value| { + let doc = build_routine_sig(value.clone()); + build_security_object_value(&value, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectSchema(node) => append_security_object_value( + build_keyword_tokens([(node.schema_token(), "schema")]), + node.schema_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectSequence(node) => append_security_object_value( + build_keyword_tokens([(node.sequence_token(), "sequence")]), + node.sequence_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectSubscription(node) => append_security_object_value( + build_keyword_tokens([(node.subscription_token(), "subscription")]), + node.subscription_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectTable(node) => append_security_object_value( + build_keyword_tokens([(node.table_token(), "table")]), + node.table_name_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectTablespace(node) => append_security_object_value( + build_keyword_tokens([(node.tablespace_token(), "tablespace")]), + node.tablespace_ref() + .map(|name| build_security_object_value(&name, build_name(name.syntax()))) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectType(node) => append_security_object_value( + build_keyword_tokens([(node.type_token(), "type")]), + node.type_name_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + ast::SecurityLabelObject::ObjectView(node) => append_security_object_value( + build_keyword_tokens([(node.view_token(), "view")]), + node.view_ref() + .map(|name| { + let doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + build_security_object_value(&name, doc) + }) + .unwrap_or_else(Doc::nil), + ), + } +} + +fn build_security_label<'a>(label: &ast::SecurityLabel) -> Doc<'a> { + let mut doc = Doc::text("security"); + if let Some(token) = label.label_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("label")); + } + if let Some(provider) = label.for_provider() { + let mut provider_doc = Doc::text("for"); + if let Some(name) = provider.security_label_provider() { + provider_doc = provider_doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } else if let Some(literal) = provider.literal() { + provider_doc = provider_doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(provider.syntax())) + .append(provider_doc) + .nest(2), + ); + } + let object = label.security_label_object(); + if let Some(on) = label.on_token() { + let mut on_doc = leading_comments_token(&on).append(Doc::text("on")); + if let Some(object) = object { + on_doc = on_doc + .append(Doc::space()) + .append(leading_comments(object.syntax())) + .append(build_security_label_object(object)); + } + doc = doc.append(Doc::line_or_space().append(on_doc).nest(2)); + } else if let Some(object) = object { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(object.syntax())) + .append(build_security_label_object(object)) + .nest(2), + ); + } + + let value = if let Some(literal) = label.literal() { + Some(leading_comments(literal.syntax()).append(build_literal(literal))) + } else { + label + .null_token() + .map(|null| leading_comments_token(&null).append(Doc::text("null"))) + }; + if let Some(is) = label.is_token() { + let mut is_doc = leading_comments_token(&is).append(Doc::text("is")); + if let Some(value) = value { + is_doc = is_doc.append(Doc::space()).append(value); + } + doc = doc.append(Doc::line_or_space().append(is_doc).nest(2)); + } else if let Some(value) = value { + doc = doc.append(Doc::line_or_space().append(value).nest(2)); + } + doc.group().append(build_semicolon(label.semicolon_token())) +} + +fn build_set_constraints<'a>(set: &ast::SetConstraints) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(constraints) = set.constraints_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&constraints)) + .append(Doc::text("constraints")) + .nest(2), + ); + } + + let names = set.constraint_name_refs().map(|name| { + let syntax = name.syntax().clone(); + let name_doc = name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil); + (leading_comments(&syntax).append(name_doc), syntax) + }); + let target = if let Some(names) = build_comma_separated_docs(names) { + Some(names) + } else { + set.all_token() + .map(|all| leading_comments_token(&all).append(Doc::text("all"))) + }; + if let Some(target) = target { + doc = doc.append(Doc::line_or_space().append(target).nest(2)); + } + if let Some(timing) = set.constraint_timing() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(timing.syntax())) + .append(build_keyword_node(timing.syntax())) + .nest(2), + ); + } + doc.group().append(build_semicolon(set.semicolon_token())) +} + +fn build_role_ref<'a>(role: &ast::RoleRef) -> Doc<'a> { + if let Some(group) = role.group_token() { + let mut doc = leading_comments_token(&group).append(Doc::text("group")); + if let Some(ident) = role.ident_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&ident)) + .append(build_name(role.syntax())); + } + doc + } else if role.ident_token().is_some() { + build_name(role.syntax()) + } else { + build_keyword_node(role.syntax()) + } +} + +fn build_set_role_target<'a>(target: ast::SetRoleTarget) -> Doc<'a> { + match target { + ast::SetRoleTarget::Literal(literal) => build_literal(literal), + ast::SetRoleTarget::RoleRef(role) => build_role_ref(&role), + ast::SetRoleTarget::SetRoleNone(none) => build_keyword_node(none.syntax()), + } +} + +fn build_set_role<'a>(set: &ast::SetRole) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(scope) = set.set_scope() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(scope.syntax())) + .append(build_keyword_node(scope.syntax())) + .nest(2), + ); + } + if let Some(role) = set.role_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&role)) + .append(Doc::text("role")) + .nest(2), + ); + } + if let Some(target) = set.set_role_target() { + let comments = leading_comments(target.syntax()); + doc = doc.append( + Doc::line_or_space() + .append(comments) + .append(build_set_role_target(target)) + .nest(2), + ); + } + doc.group().append(build_semicolon(set.semicolon_token())) +} + +fn build_set_session_auth_target<'a>(target: ast::SetSessionAuthTarget) -> Doc<'a> { + match target { + ast::SetSessionAuthTarget::Literal(literal) => build_literal(literal), + ast::SetSessionAuthTarget::RoleRef(role) => build_role_ref(&role), + ast::SetSessionAuthTarget::SetSessionAuthDefault(default) => { + build_keyword_node(default.syntax()) + } + } +} + +fn build_set_session_auth<'a>(set: &ast::SetSessionAuth) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(scope) = set.set_scope() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(scope.syntax())) + .append(build_keyword_node(scope.syntax())) + .nest(2), + ); + } + if let Some(session) = set.session_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&session)) + .append(Doc::text("session")) + .nest(2), + ); + } + if let Some(authorization) = set.authorization_token() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments_token(&authorization)) + .append(Doc::text("authorization")) + .nest(2), + ); + } + if let Some(target) = set.set_session_auth_target() { + let comments = leading_comments(target.syntax()); + doc = doc.append( + Doc::line_or_space() + .append(comments) + .append(build_set_session_auth_target(target)) + .nest(2), + ); + } + doc.group().append(build_semicolon(set.semicolon_token())) +} + +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) +} + +fn build_set_transaction<'a>(set: &ast::SetTransaction) -> Doc<'a> { + let mut doc = Doc::text("set"); + let body = if let Some(characteristics) = set.session_characteristics() { + let mut body = Doc::text("session"); + for (token, keyword) in [ + (characteristics.characteristics_token(), "characteristics"), + (characteristics.as_token(), "as"), + (characteristics.transaction_token(), "transaction"), + ] { + if let Some(token) = token { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); + } + } + if let Some(modes) = characteristics.transaction_mode_list() { + body = body.append( + Doc::line_or_space() + .append(leading_comments(modes.syntax())) + .append(build_transaction_mode_list(modes)) + .nest(2), + ); + } + Some((leading_comments(characteristics.syntax()), body)) + } else if let Some(modes) = set.transaction_modes() { + let mut body = Doc::text("transaction"); + if let Some(list) = modes.transaction_mode_list() { + body = body.append( + Doc::line_or_space() + .append(leading_comments(list.syntax())) + .append(build_transaction_mode_list(list)) + .nest(2), + ); + } + Some((leading_comments(modes.syntax()), body)) + } else { + set.transaction_snapshot().map(|snapshot| { + let mut body = Doc::text("transaction"); + if let Some(token) = snapshot.snapshot_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&token)) + .append(Doc::text("snapshot")); + } + if let Some(literal) = snapshot.literal() { + body = body.append( + Doc::line_or_space() + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + .nest(2), + ); + } + (leading_comments(snapshot.syntax()), body) + }) + }; + if let Some((comments, body)) = body { + doc = doc.append(Doc::line_or_space().append(comments).append(body).nest(2)); + } + doc.group().append(build_semicolon(set.semicolon_token())) +} + +fn build_show<'a>(show: &ast::Show) -> Doc<'a> { + let mut doc = Doc::text("show"); + if let Some(action) = show.show_action() { + let comments = leading_comments(action.syntax()); + let action_doc = match action { + ast::ShowAction::ConfigParameterRef(parameter) => parameter + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil), + ast::ShowAction::All(action) => build_keyword_node(action.syntax()), + ast::ShowAction::SessionAuthorization(action) => build_keyword_node(action.syntax()), + ast::ShowAction::TimeZone(action) => build_keyword_node(action.syntax()), + ast::ShowAction::TransactionIsolationLevel(action) => { + build_keyword_node(action.syntax()) + } + }; + doc = doc.append( + Doc::line_or_space() + .append(comments) + .append(action_doc) + .nest(2), + ); + } + doc.group().append(build_semicolon(show.semicolon_token())) +} + +fn build_unlisten<'a>(unlisten: &ast::Unlisten) -> Doc<'a> { + let mut doc = Doc::text("unlisten"); + let target = if let Some(channel) = unlisten.channel_ref() { + Some(leading_comments(channel.syntax()).append(build_name(channel.syntax()))) + } else { + unlisten + .star_token() + .map(|star| leading_comments_token(&star).append(Doc::text("*"))) + }; + if let Some(target) = target { + doc = doc.append(Doc::line_or_space().append(target).nest(2)); + } + doc.group() + .append(build_semicolon(unlisten.semicolon_token())) +} + +fn build_set<'a>(set: &ast::Set) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(scope) = set.set_scope() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(scope.syntax())) + .append(build_keyword_node(scope.syntax())) + .nest(2), + ); + } + if let Some(target) = set.set_target() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(target.syntax())) + .append(build_set_target(target)) + .nest(2), + ); + } + doc.group().append(build_semicolon(set.semicolon_token())) +} + +fn build_set_target<'a>(target: ast::SetTarget) -> Doc<'a> { + match target { + ast::SetTarget::SetCatalog(target) => build_set_literal_target("catalog", target.literal()), + ast::SetTarget::SetSchemaValue(target) => { + build_set_literal_target("schema", target.literal()) + } + ast::SetTarget::SetConfig(target) => build_set_config(target), + ast::SetTarget::SetTimeZone(target) => build_set_time_zone(target), + ast::SetTarget::SetXmlOption(target) => build_set_xml_option(target), + } +} + +fn build_set_literal_target<'a>(keyword: &'static str, literal: Option) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(literal) = literal { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc.group() +} + +fn build_set_xml_option<'a>(target: ast::SetXmlOption) -> Doc<'a> { + let mut doc = Doc::text("xml"); + if let Some(option) = target.option_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&option)) + .append(Doc::text("option")); + } + if let Some(value) = target.xml_document_or_content() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_keyword_node(value.syntax())); + } + doc.group() +} + +fn build_set_time_zone<'a>(target: ast::SetTimeZone) -> Doc<'a> { + let mut doc = Doc::text("time"); + if let Some(zone) = target.zone_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&zone)) + .append(Doc::text("zone")); + } + let value = if let Some(value) = target.config_value() { + let value_doc = match value.clone() { + ast::ConfigValue::ConfigValueName(name) => build_name(name.syntax()), + ast::ConfigValue::Literal(literal) => build_literal(literal), + }; + Some(leading_comments(value.syntax()).append(value_doc)) + } else if let Some(default) = target.default_token() { + Some(leading_comments_token(&default).append(Doc::text("default"))) + } else { + target + .local_token() + .map(|local| leading_comments_token(&local).append(Doc::text("local"))) + }; + if let Some(value) = value { + doc = doc.append(Doc::space()).append(value); + } + doc.group() +} + +fn build_set_config<'a>(set: ast::SetConfig) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(parameter) = set.config_parameter_ref() { + doc = doc.append(leading_comments(parameter.syntax())); + if let Some(path) = parameter.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(assignment) = set.config_assignment() { + let comments = leading_comments(assignment.syntax()); + doc = doc + .append(Doc::line_or_space()) + .append(comments) + .append(build_config_assignment(assignment)); + } + doc.group() +} + +fn build_set_config_param<'a>(set: ast::SetConfigParam) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(parameter) = set.config_parameter_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(parameter.syntax())); + if let Some(path) = parameter.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(assignment) = set.config_assignment() { + let comments = leading_comments(assignment.syntax()); + doc = doc + .append(Doc::space()) + .append(comments) + .append(build_config_assignment(assignment)); + } + doc.group() +} + +fn build_config_assignment<'a>(assignment: ast::ConfigAssignment) -> Doc<'a> { + match assignment { + ast::ConfigAssignment::FromCurrent(current) => build_keyword_node(current.syntax()), + ast::ConfigAssignment::ToConfigValue(values) => { + let mut doc = if let Some(eq) = values.eq_token() { + leading_comments_token(&eq).append(Doc::text("=")) + } else if let Some(to) = values.to_token() { + leading_comments_token(&to).append(Doc::text("to")) + } else { + Doc::nil() + }; + let value_docs = values.config_values().map(|value| { + let syntax = value.syntax().clone(); + let value_doc = match value { + ast::ConfigValue::ConfigValueName(name) => build_name(name.syntax()), + ast::ConfigValue::Literal(literal) => build_literal(literal), + }; + (leading_comments(&syntax).append(value_doc), syntax) + }); + if let Some(values_doc) = build_comma_separated_docs(value_docs) { + doc = doc.append(Doc::space()).append(values_doc); + } else if let Some(default) = values.default_token() { + doc = doc + .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() + } + } +} + +fn build_create_index<'a>(create_index: &ast::CreateIndex) -> Doc<'a> { + let mut doc = Doc::text("create"); + + if let Some(unique_token) = create_index.unique_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&unique_token)) + .append(Doc::text("unique")); + } + if let Some(index_token) = create_index.index_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&index_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("index")); + if let Some(concurrently_token) = create_index.concurrently_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&concurrently_token)) + .append(Doc::text("concurrently")); + } + if let Some(if_not_exists) = create_index.if_not_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_not_exists.syntax())) + .append(build_keyword_node(if_not_exists.syntax())); + } + if let Some(index) = create_index.index() { + doc = doc + .append(Doc::space()) + .append(leading_comments(index.syntax())); + if let Some(path) = index.path() { + doc = doc.append(build_path(&path)); + } + } + let mut on_doc = Doc::nil(); + if let Some(on_token) = create_index.on_token() { + on_doc = on_doc + .append(leading_comments_token(&on_token)) + .append(Doc::text("on")); + } + if let Some(table) = create_index.table_relation_name() { + on_doc = on_doc + .append(Doc::space()) + .append(build_table_relation_name(table)); + } + if let Some(using_method) = create_index.using_method() { + let mut using_doc = leading_comments(using_method.syntax()).append(Doc::text("using")); + if let Some(method) = using_method.access_method_ref() { + using_doc = using_doc + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(build_name(method.syntax())); + } + if let Some(items) = create_index.partition_item_list() { + using_doc = using_doc + .append(Doc::space()) + .append(leading_comments(items.syntax())) + .append(build_create_table_partition_items(items)); + } + 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 { + if let Some(items) = create_index.partition_item_list() { + on_doc = on_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")); + if let Some(items) = include.partition_item_list() { + include_doc = include_doc + .append(Doc::space()) + .append(leading_comments(items.syntax())) + .append(build_create_table_partition_items(items)); + } + doc = doc.append(Doc::line_or_space().append(include_doc).nest(2)); + } + if let Some(nulls) = create_index.nulls_distinct_option() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(nulls.syntax())) + .append(build_keyword_node(nulls.syntax())) + .nest(2), + ); + } + if let Some(params) = create_index.with_params() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(params.syntax())) + .append(build_with_params(params)) + .nest(2), + ); + } + if let Some(tablespace) = create_index.tablespace_clause() { + let mut tablespace_doc = + leading_comments(tablespace.syntax()).append(Doc::text("tablespace")); + if let Some(tablespace_ref) = tablespace.tablespace_ref() { + tablespace_doc = tablespace_doc + .append(Doc::space()) + .append(leading_comments(tablespace_ref.syntax())) + .append(build_name(tablespace_ref.syntax())); + } + doc = doc.append(Doc::line_or_space().append(tablespace_doc).nest(2)); + } + if let Some(where_clause) = create_index.where_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)) + .nest(2), + ); + } + + doc.group() + .append(build_semicolon(create_index.semicolon_token())) +} + +fn build_create_view<'a>(create_view: &ast::CreateView) -> Doc<'a> { + let mut doc = Doc::text("create"); + + if let Some(or_replace) = create_view.or_replace() { + doc = doc + .append(Doc::space()) + .append(leading_comments(or_replace.syntax())) + .append(build_keyword_node(or_replace.syntax())); + } + if let Some(persistence) = create_view.persistence() { + doc = doc + .append(Doc::space()) + .append(leading_comments(persistence.syntax())) + .append(build_keyword_node(persistence.syntax())); + } + if let Some(recursive) = create_view.recursive_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&recursive)) + .append(Doc::text("recursive")); + } + if let Some(view_token) = create_view.view_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&view_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("view")); + + if let Some(view) = create_view.view() { + doc = doc + .append(Doc::space()) + .append(leading_comments(view.syntax())); + if let Some(path) = view.path() { + doc = doc.append(build_path(&path)); + } + } + if let Some(columns) = create_view.column_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_cte_column_list(columns)); + } + let has_with_params = if let Some(with_params) = create_view.with_params() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(with_params.syntax())) + .append(build_with_params(with_params)) + .nest(2), + ); + true + } else { + false + }; + if let Some(as_token) = create_view.as_token() { + let as_doc = leading_comments_token(&as_token).append(Doc::text("as")); + doc = if has_with_params { + doc.append(Doc::hard_line().append(as_doc).nest(2)) + } else { + doc.append(Doc::space()).append(as_doc) + }; + } + if let Some(query) = create_view.query() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(query.syntax())) + .append(build_select_variant(query)) + .nest(2), + ); + } + if let Some(check_option) = create_view.with_check_option() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(check_option.syntax())) + .append(build_with_check_option(check_option)) + .nest(2), + ); + } + + doc.append(build_semicolon(create_view.semicolon_token())) + .group() +} + +fn build_create_table_as<'a>(create_table_as: &ast::CreateTableAs) -> Doc<'a> { + let mut doc = Doc::text("create"); + + if let Some(persistence) = create_table_as.persistence() { + doc = doc + .append(Doc::space()) + .append(leading_comments(persistence.syntax())) + .append(build_keyword_node(persistence.syntax())); + } + if let Some(table_token) = create_table_as.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("table")); + + if let Some(if_not_exists) = create_table_as.if_not_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_not_exists.syntax())) + .append(build_keyword_node(if_not_exists.syntax())); + } + if let Some(table_name) = create_table_as.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)); + } + } + if let Some(arg_list) = create_table_as.table_arg_list() { + let comments = comments_before(arg_list.syntax().clone()); + if comment_tokens_before(arg_list.syntax().clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments).append(Doc::space()); + } + let body = Doc::list( + Itertools::intersperse( + arg_list.args().map(build_table_arg), + Doc::text(",").append(Doc::hard_line()), + ) + .collect(), + ); + doc = doc + .append(Doc::text("(")) + .append(wrap_body(body).group()) + .append(Doc::text(")")); + } + + let mut has_table_option = false; + if let Some(using_method) = create_table_as.using_method() { + has_table_option = true; + let mut option = leading_comments(using_method.syntax()).append(Doc::text("using")); + if let Some(method) = using_method.access_method_ref() { + option = option + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(build_name(method.syntax())); + } + doc = doc.append(Doc::line_or_space().append(option).nest(2)); + } + if let Some(params) = create_table_as.table_params() { + has_table_option = true; + let option = match params { + ast::TableParams::WithParams(params) => { + leading_comments(params.syntax()).append(build_with_params(params)) + } + ast::TableParams::WithoutOids(without_oids) => leading_comments(without_oids.syntax()) + .append(build_keyword_node(without_oids.syntax())), + }; + doc = doc.append(Doc::line_or_space().append(option).nest(2)); + } + if let Some(on_commit) = create_table_as.on_commit() { + has_table_option = true; + let mut option = leading_comments(on_commit.syntax()).append(Doc::text("on")); + if let Some(commit_token) = on_commit.commit_token() { + option = option + .append(Doc::space()) + .append(leading_comments_token(&commit_token)); + } else { + option = option.append(Doc::space()); + } + option = option.append(Doc::text("commit")); + if let Some(action) = on_commit.on_commit_action() { + option = option + .append(Doc::space()) + .append(leading_comments(action.syntax())) + .append(build_keyword_node(action.syntax())); + } + doc = doc.append(Doc::line_or_space().append(option).nest(2)); + } + if let Some(tablespace) = create_table_as.tablespace_clause() { + has_table_option = true; + let mut option = leading_comments(tablespace.syntax()).append(Doc::text("tablespace")); + if let Some(name) = tablespace.tablespace_ref() { + option = option + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc = doc.append(Doc::line_or_space().append(option).nest(2)); + } + + if let Some(as_token) = create_table_as.as_token() { + let as_doc = leading_comments_token(&as_token).append(Doc::text("as")); + doc = if has_table_option { + doc.append(Doc::hard_line().append(as_doc).nest(2)) + } else { + doc.append(Doc::space()).append(as_doc) + }; + } + if let Some(query) = create_table_as.query() { + let query_comments = leading_comments(query.syntax()); + let query_doc = match query { + ast::CreateTableAsQuery::SelectVariant(select) => build_select_variant(select), + ast::CreateTableAsQuery::Execute(execute) => build_execute(execute), + }; + doc = doc.append( + Doc::hard_line() + .append(query_comments) + .append(query_doc) + .nest(2), + ); + } + if let Some(data_option) = create_table_as.data_option() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(data_option.syntax())) + .append(build_keyword_node(data_option.syntax())) + .nest(2), + ); + } + + doc.append(build_semicolon(create_table_as.semicolon_token())) + .group() +} + +fn build_execute<'a>(execute: ast::Execute) -> Doc<'a> { + let mut doc = Doc::text("execute"); + if let Some(statement) = execute.prepared_statement_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(statement.syntax())) + .append(build_name(statement.syntax())); + } + if let Some(args) = execute.arg_list() { + doc = doc + .append(comments_before(args.syntax().clone())) + .append(build_call_arg_list(args)); + } + doc.group() + .append(build_semicolon(execute.semicolon_token())) +} + +fn build_with_check_option<'a>(check_option: ast::WithCheckOption) -> Doc<'a> { + let mut doc = Doc::text("with"); + if let Some(level) = check_option.check_option_level() { + let syntax = match &level { + ast::CheckOptionLevel::CascadedCheckOption(level) => level.syntax(), + ast::CheckOptionLevel::LocalCheckOption(level) => level.syntax(), + }; + doc = doc + .append(Doc::space()) + .append(leading_comments(syntax)) + .append(build_keyword_node(syntax)); + } + if let Some(check_token) = check_option.check_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&check_token)) + .append(Doc::text("check")); + } + if let Some(option_token) = check_option.option_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&option_token)) + .append(Doc::text("option")); + } + doc +} + +fn build_create_foreign_table<'a>(create_table: &ast::CreateForeignTable) -> Doc<'a> { + let mut doc = Doc::text("create"); + + if let Some(foreign) = create_table.foreign_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&foreign)) + .append(Doc::text("foreign")); + } + if let Some(table) = create_table.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table)) + .append(Doc::text("table")); + } + if let Some(if_not_exists) = create_table.if_not_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_not_exists.syntax())) + .append(build_keyword_node(if_not_exists.syntax())); + } + if let Some(table_name) = create_table.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)); + } + } + + if let Some(partition_of) = create_table.partition_of() { + let mut partition_doc = leading_comments(partition_of.syntax()) + .append(build_create_table_partition_of(partition_of)); + if let Some(arg_list) = create_table.table_arg_list() { + partition_doc = append_table_arg_list(partition_doc, arg_list); + } + if let Some(partition_type) = create_table.partition_type() { + let separator = if matches!(&partition_type, ast::PartitionType::PartitionDefault(_)) { + Doc::space() + } else { + Doc::line_or_space() + }; + partition_doc = partition_doc + .append(separator) + .append(leading_comments(partition_type.syntax())) + .append(build_create_table_partition_type(partition_type)); + } + doc = doc.append(Doc::hard_line().append(partition_doc).nest(2)); + } else { + if let Some(arg_list) = create_table.table_arg_list() { + doc = append_table_arg_list(doc, arg_list); + } + if let Some(inherits) = create_table.inherits() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(inherits.syntax())) + .append(build_create_table_inherits(inherits)) + .nest(2), + ); + } + if let Some(partition_type) = create_table.partition_type() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(partition_type.syntax())) + .append(build_create_table_partition_type(partition_type)) + .nest(2), + ); + } + } + if let Some(server) = create_table.server_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(server.syntax())) + .append(build_server_clause(server)) + .nest(2), + ); + } + if let Some(options) = create_table.alter_option_list() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(options.syntax())) + .append(build_alter_option_list(options)) + .nest(2), + ); + } + + doc.group() + .append(build_semicolon(create_table.semicolon_token())) +} + +fn append_table_arg_list<'a>(mut doc: Doc<'a>, arg_list: ast::TableArgList) -> Doc<'a> { + doc = doc.append(leading_comments(arg_list.syntax())); + if let Some(l_paren) = arg_list.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + let body = Doc::list( + Itertools::intersperse( + arg_list.args().map(build_table_arg), + Doc::text(",").append(Doc::hard_line()), + ) + .collect(), + ); + doc.append(Doc::text("(")) + .append(wrap_body(body).group()) + .append(Doc::text(")")) +} + +fn build_server_clause<'a>(server: ast::ServerClause) -> Doc<'a> { + let mut doc = Doc::text("server"); + if let Some(server_ref) = server.server_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(server_ref.syntax())) + .append(build_name(server_ref.syntax())); + } + doc +} + +fn build_create_table<'a>(create_table: &ast::CreateTable) -> Doc<'a> { + let mut doc = Doc::text("create"); + + if let Some(persistence) = create_table.persistence() { + doc = doc + .append(Doc::space()) + .append(leading_comments(persistence.syntax())) + .append(build_keyword_node(persistence.syntax())); + } + + if let Some(table_token) = create_table.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("table")); + + if let Some(if_not_exists) = create_table.if_not_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_not_exists.syntax())) + .append(build_keyword_node(if_not_exists.syntax())); + } + + if let Some(table_name) = create_table.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)); + } + } + + if let Some(partition_of) = create_table.partition_of() { + doc = doc.append( + Doc::hard_line() + .append(leading_comments(partition_of.syntax())) + .append(build_create_table_partition_of(partition_of)) + .nest(2), + ); + } + + if let Some(of_type) = create_table.of_type() { + let mut of_type_doc = leading_comments(of_type.syntax()).append(Doc::text("of")); + if let Some(ty) = of_type.ty() { + of_type_doc = of_type_doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + doc = doc.append(Doc::hard_line().append(of_type_doc).nest(2)); + } + + if let Some(arg_list) = create_table.table_arg_list() { + if let Some(l_paren) = arg_list.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + let body = Doc::list( + Itertools::intersperse( + arg_list.args().map(build_table_arg), + Doc::text(",").append(Doc::hard_line()), + ) + .collect(), + ); + doc = doc + .append(Doc::text("(")) + .append(wrap_body(body).group()) + .append(Doc::text(")")); + } + + if let Some(partition_type) = create_table.partition_type() { + let separator = if matches!(&partition_type, ast::PartitionType::PartitionDefault(_)) { + Doc::space() + } else { + Doc::line_or_space() + }; + doc = doc.append( + separator + .append(leading_comments(partition_type.syntax())) + .append(build_create_table_partition_type(partition_type)) + .nest(2), + ); + } + + if let Some(inherits) = create_table.inherits() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(inherits.syntax())) + .append(build_create_table_inherits(inherits)) + .nest(2), + ); + } + + if let Some(partition_by) = create_table.partition_by() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(partition_by.syntax())) + .append(build_create_table_partition_by(partition_by)) + .nest(2), + ); + } + + if let Some(using_method) = create_table.using_method() { + let mut using_doc = leading_comments(using_method.syntax()).append(Doc::text("using")); + if let Some(method) = using_method.access_method_ref() { + using_doc = using_doc + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(build_name(method.syntax())); + } + doc = doc.append(Doc::line_or_space().append(using_doc).nest(2)); + } + + if let Some(params) = create_table.table_params() { + let (separator, params_doc) = match params { + ast::TableParams::WithParams(params) => ( + Doc::line_or_space(), + leading_comments(params.syntax()).append(build_with_params(params)), + ), + ast::TableParams::WithoutOids(without_oids) => ( + Doc::hard_line(), + leading_comments(without_oids.syntax()) + .append(build_keyword_node(without_oids.syntax())), + ), + }; + doc = doc.append(separator.append(params_doc).nest(2)); + } + + if let Some(on_commit) = create_table.on_commit() { + let mut on_commit_doc = leading_comments(on_commit.syntax()).append(Doc::text("on")); + if let Some(commit_token) = on_commit.commit_token() { + on_commit_doc = on_commit_doc + .append(Doc::space()) + .append(leading_comments_token(&commit_token)); + } else { + on_commit_doc = on_commit_doc.append(Doc::space()); + } + on_commit_doc = on_commit_doc.append(Doc::text("commit")); + if let Some(action) = on_commit.on_commit_action() { + on_commit_doc = on_commit_doc + .append(Doc::space()) + .append(leading_comments(action.syntax())) + .append(build_keyword_node(action.syntax())); + } + doc = doc.append(Doc::hard_line().append(on_commit_doc).nest(2)); + } + + if let Some(tablespace) = create_table.tablespace_clause() { + let mut tablespace_doc = + leading_comments(tablespace.syntax()).append(Doc::text("tablespace")); + if let Some(tablespace_ref) = tablespace.tablespace_ref() { + tablespace_doc = tablespace_doc + .append(Doc::space()) + .append(leading_comments(tablespace_ref.syntax())) + .append(build_name(tablespace_ref.syntax())); + } + doc = doc.append(Doc::line_or_space().append(tablespace_doc).nest(2)); + } + + doc.group() + .append(build_semicolon(create_table.semicolon_token())) +} + +fn build_create_table_partition_of<'a>(partition_of: ast::PartitionOf) -> Doc<'a> { + let mut doc = Doc::text("partition"); + if let Some(of_token) = partition_of.of_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&of_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("of")); + if let Some(table) = partition_of.table_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table.syntax())); + if let Some(path) = table.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + doc +} + +fn build_create_table_inherits<'a>(inherits: ast::Inherits) -> Doc<'a> { + let mut doc = Doc::text("inherits"); + if let Some(l_paren) = inherits.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + let tables = inherits.table_name_refs().map(|table| { + let mut item = leading_comments(table.syntax()); + if let Some(path) = table.path_ref() { + item = item.append(build_path_ref(&path)); + } + (item, table.syntax().clone()) + }); + let mut body = build_comma_separated_docs(tables).unwrap_or_else(Doc::nil); + if let Some(r_paren) = inherits.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() +} + +fn build_create_table_partition_by<'a>(partition_by: ast::PartitionBy) -> Doc<'a> { + let mut doc = Doc::text("partition"); + if let Some(by_token) = partition_by.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&by_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("by")); + if let Some(strategy) = partition_by.partition_strategy() { + doc = doc + .append(Doc::space()) + .append(leading_comments(strategy.syntax())) + .append(build_keyword_node(strategy.syntax())); + } + if let Some(items) = partition_by.partition_item_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(items.syntax())) + .append(build_create_table_partition_items(items)); + } + doc +} + +fn build_create_table_partition_items<'a>(items: ast::PartitionItemList) -> Doc<'a> { + let doc = items + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let item_docs = items.partition_items().map(|item| { + let mut item_doc = leading_comments(item.syntax()); + if let Some(expr) = item.expr() { + item_doc = item_doc.append(build_expr(expr)); + } + if item.expr().is_none() { + if let Some(collate) = item.collate() { + item_doc = item_doc.append(build_collate_expr(collate)); + } + } + if let Some(op_class) = item.op_class_ref() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(op_class.syntax())); + if let Some(path) = op_class.path_ref() { + item_doc = item_doc.append(build_path_ref(&path)); + } + } + if let Some(attributes) = item.attribute_list() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(attributes.syntax())) + .append(build_attribute_list(attributes)); + } + if let Some(order) = item.sort_order() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(order.syntax())) + .append(build_keyword_node(order.syntax())); + } + if let Some(nulls) = item.nulls_order() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(nulls.syntax())) + .append(build_keyword_node(nulls.syntax())); + } + (item_doc, item.syntax().clone()) + }); + let mut body = build_comma_separated_docs(item_docs).unwrap_or_else(Doc::nil); + if let Some(r_paren) = items.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_create_table_partition_type<'a>(partition_type: ast::PartitionType) -> Doc<'a> { + match partition_type { + ast::PartitionType::PartitionDefault(_) => Doc::text("default"), + ast::PartitionType::PartitionForValuesIn(values) => { + let mut doc = build_partition_for_values_prefix(values.values_token()); + if let Some(in_token) = values.in_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&in_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("in")); + if let Some(l_paren) = values.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + let body = build_comma_separated_exprs(values.exprs()).unwrap_or_else(Doc::nil); + doc.append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() + } + ast::PartitionType::PartitionForValuesFrom(values) => { + let mut doc = build_partition_for_values_prefix(values.values_token()); + if let Some(from_token) = values.from_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&from_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("from")); + if let Some(from) = values.from() { + doc = doc + .append(Doc::space()) + .append(leading_comments(from.syntax())) + .append(build_create_table_partition_values( + from.l_paren_token(), + from.exprs(), + from.r_paren_token(), + )); + } + doc = doc.append(Doc::line_or_space()); + if let Some(to_token) = values.to_token() { + doc = doc.append(leading_comments_token(&to_token)); + } + doc = doc.append(Doc::text("to")); + if let Some(to) = values.to() { + doc = doc + .append(Doc::space()) + .append(leading_comments(to.syntax())) + .append(build_create_table_partition_values( + to.l_paren_token(), + to.exprs(), + to.r_paren_token(), + )); + } + doc.group() + } + ast::PartitionType::PartitionForValuesWith(values) => { + let mut doc = build_partition_for_values_prefix(values.values_token()); + if let Some(with_token) = values.with_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&with_token)); + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("with")); + if let Some(l_paren) = values.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + let mut parts = Vec::new(); + if let Some(modulus) = values.modulus() { + parts.push( + leading_comments(modulus.syntax()) + .append(build_keyword_node(modulus.syntax())) + .append(trailing_comments(modulus.syntax())), + ); + } + if let Some(remainder) = values.remainder() { + parts.push( + leading_comments(remainder.syntax()) + .append(build_keyword_node(remainder.syntax())) + .append(trailing_comments(remainder.syntax())), + ); + } + let body = Doc::list( + Itertools::intersperse( + parts.into_iter(), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ); + doc.append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() + } + } +} + +fn build_partition_for_values_prefix<'a>(values_token: Option) -> Doc<'a> { + let mut doc = Doc::text("for"); + if let Some(values_token) = values_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&values_token)); + } else { + doc = doc.append(Doc::space()); + } + doc.append(Doc::text("values")) +} + +fn build_create_table_partition_values<'a>( + l_paren: Option, + exprs: impl Iterator, + _r_paren: Option, +) -> Doc<'a> { + let doc = l_paren + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let body = build_comma_separated_exprs(exprs).unwrap_or_else(Doc::nil); + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_path<'a>(path: &ast::Path) -> Doc<'a> { + build_path_parts(path.qualifier(), path.dot_token(), path.segment()) +} + +fn build_path_ref<'a>(path: &ast::PathRef) -> Doc<'a> { + build_path_parts(path.qualifier(), path.dot_token(), path.segment()) +} + +fn build_path_parts<'a>( + qualifier: Option, + dot: Option, + segment: Option, +) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(qualifier) = qualifier { + doc = doc + .append(build_path_ref(&qualifier)) + .append(trailing_comments(qualifier.syntax())); + } + if dot.is_some() { + doc = doc.append(Doc::text(".")); + } + if let Some(segment) = segment { + doc = doc + .append(leading_comments(segment.syntax())) + .append(build_name(segment.syntax())); + } + doc +} + +fn build_name<'a>(node: &SyntaxNode) -> Doc<'a> { + let mut tokens = node + .children_with_tokens() + .filter_map(|el| el.into_token()) + .filter(|token| token.kind() != SyntaxKind::WHITESPACE); + + let Some(ident) = tokens.next() else { + return Doc::nil(); + }; + + if is_unicode_escape(ident.text()) { + let mut doc = Doc::text(ident.text().to_string()); + for token in tokens { + let text = match token.kind() { + SyntaxKind::STRING | SyntaxKind::COMMENT => token.text().to_string(), + _ => token.text().to_ascii_lowercase(), + }; + doc = doc.append(Doc::space()).append(Doc::text(text)); + if is_line_comment(&token) { + doc = doc.append(Doc::hard_line()); + } + } + return doc; + } + + Doc::text(quote_ident(&normalize_name_node(node))) +} + +fn is_unicode_escape(text: &str) -> bool { + text.strip_prefix(['u', 'U']) + .is_some_and(|text| text.starts_with("&\"")) +} + +fn build_table_arg<'a>(arg: ast::TableArg) -> Doc<'a> { + let doc = leading_comments(arg.syntax()); + let doc = doc.append(match &arg { + ast::TableArg::Column(column) => build_column(column), + ast::TableArg::LikeClause(like_clause) => build_like_clause(like_clause), + ast::TableArg::TableConstraint(table_constraint) => { + build_table_constraint(table_constraint.clone()) + } + }); + doc.append(trailing_comments(arg.syntax())) +} + +fn build_column<'a>(column: &ast::Column) -> Doc<'a> { + let mut doc = column + .name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(ty) = column.ty() { + doc = doc + .append(Doc::space()) + .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)); + } + doc.group() +} + +fn append_column_clause<'a>(doc: Doc<'a>, syntax: &SyntaxNode, clause: Doc<'a>) -> Doc<'a> { + doc.append( + Doc::line_or_space() + .append(leading_comments(syntax)) + .append(clause) + .nest(2), + ) +} + +fn build_alter_option_list<'a>(list: ast::AlterOptionList) -> Doc<'a> { + let mut doc = Doc::text("options"); + if let Some(l_paren) = list.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } else { + doc = doc.append(Doc::space()); + } + let items = list.alter_options().map(|option| { + let item = leading_comments(option.syntax()).append(build_alter_option(&option)); + (item, option.syntax().clone()) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::text("(")) + .append(Doc::hard_line().append(body).nest(2)) + .append(Doc::hard_line()) + .append(Doc::text(")")) +} + +fn build_alter_option<'a>(option: &ast::AlterOption) -> Doc<'a> { + match option { + ast::AlterOption::AddForeignOption(option) => { + let mut doc = option + .add_token() + .map(|token| leading_comments_token(&token).append(Doc::text("add"))) + .unwrap_or_else(Doc::nil); + if let Some(name) = option.foreign_option_name() { + if option.add_token().is_some() { + doc = doc.append(Doc::space()); + } + doc = doc + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + if let Some(value) = option.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_literal(value)); + } + doc + } + ast::AlterOption::SetForeignOption(option) => { + let mut doc = Doc::text("set"); + if let Some(name) = option.foreign_option_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + if let Some(value) = option.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_literal(value)); + } + doc + } + ast::AlterOption::DropForeignOption(option) => { + let mut doc = Doc::text("drop"); + if let Some(name) = option.foreign_option_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc + } + } +} + +fn build_column_constraint<'a>(constraint: ast::ColumnConstraint) -> Doc<'a> { + match constraint { + ast::ColumnConstraint::CheckConstraint(constraint) => build_check_constraint(constraint), + ast::ColumnConstraint::DefaultConstraint(constraint) => { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(default) = constraint.default_token() { + doc = doc + .append(leading_comments_token(&default)) + .append(Doc::text("default")); + } + if let Some(expr) = constraint.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() + } + ast::ColumnConstraint::ExcludeConstraint(constraint) => { + build_exclude_constraint(constraint) + } + ast::ColumnConstraint::GeneratedConstraint(constraint) => { + build_generated_constraint(constraint) + } + ast::ColumnConstraint::NotNullConstraint(constraint) => { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(not) = constraint.not_token() { + doc = doc + .append(leading_comments_token(¬)) + .append(Doc::text("not")); + } + if let Some(null) = constraint.null_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&null)) + .append(Doc::text("null")); + } + if let Some(column) = constraint.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() + } + ast::ColumnConstraint::NullConstraint(constraint) => { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(null) = constraint.null_token() { + doc = doc + .append(leading_comments_token(&null)) + .append(Doc::text("null")); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() + } + ast::ColumnConstraint::PrimaryKeyConstraint(constraint) => { + build_primary_key_constraint(constraint) + } + ast::ColumnConstraint::ReferencesConstraint(constraint) => { + build_references_constraint(constraint) + } + ast::ColumnConstraint::UniqueConstraint(constraint) => build_unique_constraint(constraint), + } +} + +fn build_references_constraint<'a>(constraint: ast::ReferencesConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(references) = constraint.references_token() { + doc = doc + .append(leading_comments_token(&references)) + .append(Doc::text("references")); + } + if let Some(table) = constraint.table() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table.syntax())); + if let Some(path) = table.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(column) = constraint.column() { + if let Some(l_paren) = constraint.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc + .append(Doc::text("(")) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + if let Some(r_paren) = constraint.r_paren_token() { + doc = doc.append(comments_before(r_paren)); + } + doc = doc.append(Doc::text(")")); + } + 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())); + } + 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() { + 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_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() +} + +fn build_generated_constraint<'a>(constraint: ast::GeneratedConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(generated) = constraint.generated_token() { + doc = doc + .append(leading_comments_token(&generated)) + .append(Doc::text("generated")); + } + if let Some(generated_as) = constraint.generated_as() { + doc = doc + .append(Doc::space()) + .append(leading_comments(generated_as.syntax())) + .append(match generated_as { + ast::GeneratedAs::GeneratedIdentity(identity) => { + let mut body = identity + .generated_when() + .map(build_generated_when) + .unwrap_or_else(Doc::nil); + if let Some(as_token) = identity.as_token() { + body = body + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(identity_token) = identity.identity_token() { + body = body + .append(Doc::space()) + .append(leading_comments_token(&identity_token)) + .append(Doc::text("identity")); + } + if let Some(options) = identity.sequence_option_list() { + body = body + .append(Doc::space()) + .append(leading_comments(options.syntax())) + .append(build_sequence_option_list(options)); + } + body + } + ast::GeneratedAs::GeneratedStored(stored) => { + let mut body = stored + .generated_when() + .map(build_generated_when) + .unwrap_or_else(Doc::nil); + if let Some(as_token) = stored.as_token() { + body = body + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(l_paren) = stored.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + body = body.append(Doc::space()); + } else { + body = body.append(comments_before(l_paren)); + } + } + let mut expr = stored + .expr() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = stored.r_paren_token() { + expr = expr.append(comments_before(r_paren)); + } + body = body + .append(Doc::text("(")) + .append(wrap_body(expr)) + .append(Doc::text(")")); + if let Some(kind) = stored.generated_kind() { + body = body + .append(Doc::space()) + .append(leading_comments(kind.syntax())) + .append(build_keyword_node(kind.syntax())); + } + body.group() + } + }); + } + doc = doc.group(); + for option in constraint.constraint_options() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(option.syntax())) + .append(build_keyword_node(option.syntax())) + .nest(2), + ); + } + doc.group() +} + +fn build_generated_when<'a>(when: ast::GeneratedWhen) -> Doc<'a> { + match when { + ast::GeneratedWhen::GeneratedAlways(always) => build_keyword_node(always.syntax()), + ast::GeneratedWhen::GeneratedByDefault(by_default) => { + build_keyword_node(by_default.syntax()) + } + } +} + +fn build_sequence_option_list<'a>(list: ast::SequenceOptionList) -> Doc<'a> { + let doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let options = list + .sequence_options() + .map(|option| leading_comments(option.syntax()).append(build_sequence_option(option))); + let mut body = Doc::list(Itertools::intersperse(options, Doc::line_or_space()).collect()); + if let Some(r_paren) = list.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::hard_line().append(body).nest(2)) + .append(Doc::hard_line()) + .append(Doc::text(")")) +} + +fn build_sequence_option<'a>(option: ast::SequenceOption) -> Doc<'a> { + match option { + ast::SequenceOption::OptionAsType(option) => { + let mut doc = Doc::text("as"); + if let Some(ty) = option.ty() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + doc + } + ast::SequenceOption::OptionCache(option) => { + append_optional_literal(Doc::text("cache"), option.literal()) + } + ast::SequenceOption::OptionIncrement(option) => { + let mut doc = option + .increment_token() + .map(|token| leading_comments_token(&token).append(Doc::text("increment"))) + .unwrap_or_else(Doc::nil); + if let Some(by) = option.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&by)) + .append(Doc::text("by")); + } + append_optional_literal(doc, option.literal()) + } + ast::SequenceOption::OptionMaxValue(option) => { + append_optional_literal(Doc::text("maxvalue"), option.literal()) + } + ast::SequenceOption::OptionMinValue(option) => { + append_optional_literal(Doc::text("minvalue"), option.literal()) + } + ast::SequenceOption::OptionRestart(option) => { + let mut doc = option + .restart_token() + .map(|token| leading_comments_token(&token).append(Doc::text("restart"))) + .unwrap_or_else(Doc::nil); + if let Some(with) = option.with_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&with)) + .append(Doc::text("with")); + } + append_optional_literal(doc, option.literal()) + } + ast::SequenceOption::OptionStart(option) => { + let mut doc = option + .start_token() + .map(|token| leading_comments_token(&token).append(Doc::text("start"))) + .unwrap_or_else(Doc::nil); + if let Some(with) = option.with_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&with)) + .append(Doc::text("with")); + } + append_optional_literal(doc, option.literal()) + } + ast::SequenceOption::OptionOwnedBy(option) => { + let mut doc = option + .owned_token() + .map(|token| leading_comments_token(&token).append(Doc::text("owned"))) + .unwrap_or_else(Doc::nil); + if let Some(by) = option.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&by)) + .append(Doc::text("by")); + } + if let Some(target) = option.owned_by_target() { + doc = doc + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(match target { + ast::OwnedByTarget::OwnedByNone(_) => Doc::text("none"), + ast::OwnedByTarget::QualifiedColumnNameRef(name) => name + .path_ref() + .map(|path| build_path_ref(&path)) + .unwrap_or_else(Doc::nil), + }); + } + doc + } + ast::SequenceOption::OptionSequenceName(option) => { + let mut doc = option + .sequence_token() + .map(|token| leading_comments_token(&token).append(Doc::text("sequence"))) + .unwrap_or_else(Doc::nil); + if let Some(name) = option.name_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&name)) + .append(Doc::text("name")); + } + if let Some(sequence) = option.sequence() { + doc = doc + .append(Doc::space()) + .append(leading_comments(sequence.syntax())); + if let Some(path) = sequence.path() { + doc = doc.append(build_path(&path)); + } + } + doc + } + ast::SequenceOption::OptionCycle(option) => build_keyword_node(option.syntax()), + ast::SequenceOption::OptionLogged(option) => build_keyword_node(option.syntax()), + ast::SequenceOption::OptionNoCycle(option) => build_keyword_node(option.syntax()), + ast::SequenceOption::OptionNoMaxValue(option) => build_keyword_node(option.syntax()), + ast::SequenceOption::OptionNoMinValue(option) => build_keyword_node(option.syntax()), + ast::SequenceOption::OptionUnlogged(option) => build_keyword_node(option.syntax()), + } +} + +fn append_optional_literal<'a>(doc: Doc<'a>, literal: Option) -> Doc<'a> { + literal.map_or(doc.clone(), |literal| { + doc.append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)) + }) +} + +fn build_table_constraint<'a>(constraint: ast::TableConstraint) -> Doc<'a> { + match constraint { + ast::TableConstraint::CheckConstraint(constraint) => build_check_constraint(constraint), + ast::TableConstraint::ExcludeConstraint(constraint) => build_exclude_constraint(constraint), + ast::TableConstraint::ForeignKeyConstraint(constraint) => { + build_foreign_key_constraint(constraint) + } + ast::TableConstraint::PrimaryKeyConstraint(constraint) => { + build_primary_key_constraint(constraint) + } + ast::TableConstraint::UniqueConstraint(constraint) => build_unique_constraint(constraint), + } +} + +fn build_constraint_name_clause<'a>(clause: Option) -> Doc<'a> { + let Some(clause) = clause else { + return Doc::nil(); + }; + let mut doc = Doc::text("constraint"); + if let Some(name) = clause.constraint_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc.append(Doc::space()) +} + +fn build_check_constraint<'a>(constraint: ast::CheckConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(check) = constraint.check_token() { + doc = doc + .append(leading_comments_token(&check)) + .append(Doc::text("check")); + } + if let Some(l_paren) = constraint.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(expr) = constraint.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(r_paren) = constraint.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + + let mut options = Doc::nil(); + for option in constraint.constraint_options() { + options = options + .append(Doc::line_or_space()) + .append(leading_comments(option.syntax())) + .append(build_keyword_node(option.syntax())); + } + doc.append(options.nest(2)).group() +} + +fn build_primary_key_constraint<'a>(constraint: ast::PrimaryKeyConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(primary) = constraint.primary_token() { + doc = doc + .append(leading_comments_token(&primary)) + .append(Doc::text("primary")); + } + if let Some(key) = constraint.key_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&key)) + .append(Doc::text("key")); + } + if let Some(using_index) = constraint.using_index() { + doc = doc + .append(Doc::space()) + .append(leading_comments(using_index.syntax())) + .append(build_using_index_name(using_index)); + } else if let Some(parameters) = constraint.index_parameters() { + doc = doc + .append(leading_comments(parameters.syntax())) + .append(build_index_parameters(parameters)); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() +} + +fn build_unique_constraint<'a>(constraint: ast::UniqueConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(unique) = constraint.unique_token() { + doc = doc + .append(leading_comments_token(&unique)) + .append(Doc::text("unique")); + } + if let Some(using_index) = constraint.using_index() { + doc = doc + .append(Doc::space()) + .append(leading_comments(using_index.syntax())) + .append(build_using_index_name(using_index)); + } else if let Some(parameters) = constraint.index_parameters() { + doc = doc + .append(leading_comments(parameters.syntax())) + .append(build_index_parameters(parameters)); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() +} + +fn build_using_index_name<'a>(using_index: ast::UsingIndexName) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(index) = using_index.index_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&index)) + .append(Doc::text("index")); + } + if let Some(index) = using_index.index_ref() { + if let Some(path) = index.path_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(index.syntax())) + .append(build_path_ref(&path)); + } + } + doc +} + +fn build_index_parameters<'a>(parameters: ast::IndexParameters) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(nulls) = parameters.nulls_distinct_option() { + doc = doc + .append(Doc::space()) + .append(leading_comments(nulls.syntax())) + .append(build_keyword_node(nulls.syntax())); + } + if let Some(columns) = parameters.column_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_constraint_column_ref_list(columns)); + } + if let Some(include) = parameters.constraint_include_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(include.syntax())) + .append(build_constraint_include_clause(include)); + } + if let Some(with_params) = parameters.with_params() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(with_params.syntax())) + .append(build_with_params(with_params)); + } + if let Some(tablespace) = parameters.constraint_index_tablespace() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(tablespace.syntax())) + .append(build_constraint_index_tablespace(tablespace)); + } + doc +} + +fn build_constraint_column_ref_list<'a>(list: ast::ConstraintColumnRefList) -> Doc<'a> { + let suffix = list.without_overlaps().map(|overlaps| { + Doc::space() + .append(leading_comments(overlaps.syntax())) + .append(build_keyword_node(overlaps.syntax())) + }); + build_column_names( + list.l_paren_token(), + list.column_name_refs(), + suffix, + list.r_paren_token(), + ) +} + +fn build_column_ref_list<'a>(list: ast::ColumnRefList) -> Doc<'a> { + build_column_names( + list.l_paren_token(), + list.column_name_refs(), + None, + list.r_paren_token(), + ) +} + +fn build_column_names<'a>( + l_paren: Option, + names: impl Iterator, + suffix: Option>, + r_paren: Option, +) -> Doc<'a> { + let doc = l_paren + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let items = names.map(|name| { + ( + leading_comments(name.syntax()).append(build_name(name.syntax())), + name.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(suffix) = suffix { + body = body.append(suffix); + } + if let Some(r_paren) = r_paren { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_constraint_include_clause<'a>(include: ast::ConstraintIncludeClause) -> Doc<'a> { + let mut doc = Doc::text("include"); + if let Some(columns) = include.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc +} + +fn build_with_params<'a>(with_params: ast::WithParams) -> Doc<'a> { + let mut doc = Doc::text("with"); + if let Some(attributes) = with_params.attribute_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(attributes.syntax())) + .append(build_attribute_list(attributes)); + } + doc +} + +fn build_attribute_list<'a>(list: ast::AttributeList) -> Doc<'a> { + let doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let items = list.attribute_options().map(|option| { + let mut item = option + .namespace() + .map(|namespace| build_name(namespace.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(dot) = option.dot_token() { + item = item.append(comments_before(dot)).append(Doc::text(".")); + } + if let Some(name) = option.name() { + item = item + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + if let Some(eq) = option.eq_token() { + item = item + .append(Doc::space()) + .append(leading_comments_token(&eq)) + .append(Doc::text("=")); + } + if let Some(value) = option.attribute_value() { + item = item + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_attribute_value(value)); + } + ( + leading_comments(option.syntax()).append(item), + option.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(items).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_attribute_value<'a>(value: ast::AttributeValue) -> Doc<'a> { + if let Some(literal) = value.literal() { + build_literal(literal) + } else if let Some(ty) = value.ty() { + build_type(ty) + } 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 { + Doc::nil() + } +} + +fn build_constraint_index_tablespace<'a>(tablespace: ast::ConstraintIndexTablespace) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(index) = tablespace.index_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&index)) + .append(Doc::text("index")); + } + if let Some(token) = tablespace.tablespace_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("tablespace")); + } + if let Some(name) = tablespace.tablespace_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc +} + +fn append_constraint_options<'a>( + mut doc: Doc<'a>, + options: impl Iterator, +) -> Doc<'a> { + for option in options { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(option.syntax())) + .append(build_keyword_node(option.syntax())); + } + doc +} + +fn build_foreign_key_constraint<'a>(constraint: ast::ForeignKeyConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(foreign) = constraint.foreign_token() { + doc = doc + .append(leading_comments_token(&foreign)) + .append(Doc::text("foreign")); + } + if let Some(key) = constraint.key_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&key)) + .append(Doc::text("key")); + } + if let Some(columns) = constraint.from_columns() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_foreign_key_column_list(columns)); + } + if let Some(references) = constraint.references_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&references)) + .append(Doc::text("references")); + } + if let Some(table) = constraint.table_name_ref() { + if let Some(path) = table.path_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(table.syntax())) + .append(build_path_ref(&path)); + } + } + if let Some(columns) = constraint.to_columns() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_foreign_key_column_list(columns)); + } + 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())); + } + 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() { + 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_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .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")); + if let Some(name) = period.name() { + doc = 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(), + ) +} + +fn build_reference_action<'a>( + on: Option, + kind_token: Option, + kind: &'static str, + action: Option, +) -> Doc<'a> { + let mut doc = on + .map(|token| leading_comments_token(&token).append(Doc::text("on"))) + .unwrap_or_else(Doc::nil); + if let Some(token) = kind_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(kind)); + } + let Some(action) = action else { + return doc; + }; + doc = doc + .append(Doc::space()) + .append(leading_comments(action.syntax())); + match action { + ast::RefAction::SetNullColumns(action) => { + if let Some(set) = action.set_token() { + doc = doc + .append(leading_comments_token(&set)) + .append(Doc::text("set")); + } + if let Some(null) = action.null_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&null)) + .append(Doc::text("null")); + } + if let Some(columns) = action.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc + } + ast::RefAction::SetDefaultColumns(action) => { + if let Some(set) = action.set_token() { + doc = doc + .append(leading_comments_token(&set)) + .append(Doc::text("set")); + } + if let Some(default) = action.default_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&default)) + .append(Doc::text("default")); + } + if let Some(columns) = action.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + doc + } + action => doc.append(build_keyword_node(action.syntax())), + } +} + +fn build_exclude_constraint<'a>(constraint: ast::ExcludeConstraint) -> Doc<'a> { + let mut doc = build_constraint_name_clause(constraint.constraint_name_clause()); + if let Some(exclude) = constraint.exclude_token() { + doc = doc + .append(leading_comments_token(&exclude)) + .append(Doc::text("exclude")); + } + if let Some(method) = constraint.constraint_index_method() { + doc = doc + .append(Doc::space()) + .append(leading_comments(method.syntax())) + .append(Doc::text("using")); + if let Some(name) = method.access_method_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + } + if let Some(list) = constraint.constraint_exclusion_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(list.syntax())) + .append(build_constraint_exclusion_list(list)); + } + if let Some(include) = constraint.constraint_include_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(include.syntax())) + .append(build_constraint_include_clause(include)); + } + if let Some(with_params) = constraint.with_params() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(with_params.syntax())) + .append(build_with_params(with_params)); + } + if let Some(tablespace) = constraint.constraint_index_tablespace() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(tablespace.syntax())) + .append(build_constraint_index_tablespace(tablespace)); + } + if let Some(where_clause) = constraint.where_condition_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_condition_clause(where_clause)); + } + append_constraint_options(doc, constraint.constraint_options()) + .nest(2) + .group() +} + +fn build_constraint_exclusion_list<'a>(list: ast::ConstraintExclusionList) -> Doc<'a> { + let doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let items = list.constraint_exclusions().map(|exclusion| { + let mut item = exclusion.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(with) = exclusion.with_token() { + item = item + .append(Doc::line_or_space()) + .append(leading_comments_token(&with)) + .append(Doc::text("with")); + } + if let Some(op) = exclusion.op() { + item = item + .append(Doc::space()) + .append(leading_comments(op.syntax())) + .append(build_operator(&op)); + } else if let Some(op) = exclusion.operator_call() { + item = item + .append(Doc::space()) + .append(leading_comments(op.syntax())) + .append(build_operator_call(&op)); + } + ( + leading_comments(exclusion.syntax()).append(item.nest(2).group()), + exclusion.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(items).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_where_condition_clause<'a>(where_clause: ast::WhereConditionClause) -> Doc<'a> { + let mut doc = Doc::text("where"); + if let Some(l_paren) = where_clause.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(expr) = where_clause.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(r_paren) = where_clause.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_like_clause<'a>(like_clause: &ast::LikeClause) -> Doc<'a> { + let mut doc = Doc::text("like"); + + if let Some(relation_name) = like_clause.relation_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(relation_name.syntax())); + if let Some(path) = relation_name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + + let options: Vec> = like_clause + .like_options() + .map(|option| { + Doc::line_or_space() + .append(leading_comments(option.syntax())) + .append(build_like_option(&option)) + }) + .collect(); + if !options.is_empty() { + doc = doc.append(Doc::list(options).nest(2).group()); + } + + doc +} + +fn build_like_option<'a>(option: &ast::LikeOption) -> Doc<'a> { + let (keyword, property) = match option { + ast::LikeOption::ExcludingProperty(n) => ("excluding", n.table_property()), + ast::LikeOption::IncludingProperty(n) => ("including", n.table_property()), + }; + + let mut doc = Doc::text(keyword); + if let Some(property) = property { + doc = doc + .append(Doc::space()) + .append(leading_comments(property.syntax())) + .append(build_keyword_node(property.syntax())); + } + doc +} + +fn build_values<'a>(values: &ast::Values) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = values.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(values_token) = values.values_token() { + doc = doc.append(leading_comments_token(&values_token)); + } + } + + let mut values_doc = Doc::text("values"); + if let Some(row_list) = values.row_list() { + let rows = row_list.rows().map(|row| { + ( + leading_comments(row.syntax()).append(build_row(row.clone())), + row.syntax().clone(), + ) + }); + if let Some(rows) = build_comma_separated_docs(rows) { + values_doc = values_doc.append( + Doc::space() + .append(leading_comments(row_list.syntax())) + .append(rows), + ); + } + } + 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() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(offset.syntax())) + .append(build_offset_clause(offset)); + } + + doc.append(build_semicolon(values.semicolon_token())) + .group() +} + +fn build_row<'a>(row: ast::Row) -> Doc<'a> { + let mut doc = row + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let exprs = build_comma_separated_exprs(row.exprs()); + let has_exprs = exprs.is_some(); + let mut body = exprs.unwrap_or_else(Doc::nil); + if !has_exprs { + if let Some(r_paren) = row.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + doc +} + +fn build_table<'a>(table: &ast::Table) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = table.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(table_token) = table.table_token() { + doc = doc.append(leading_comments_token(&table_token)); + } + } + + let mut table_doc = Doc::text("table"); + if let Some(relation) = table.relation_name() { + table_doc = table_doc.append( + Doc::line_or_space() + .append(leading_comments(relation.syntax())) + .append(build_relation_name(relation)) + .nest(2), + ); + } + 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() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(offset.syntax())) + .append(build_offset_clause(offset)); + } + + doc.append(build_semicolon(table.semicolon_token())).group() +} + +fn build_relation_name<'a>(relation: ast::RelationName) -> Doc<'a> { + let mut doc = Doc::nil(); + let has_only = relation.only_token().is_some(); + if let Some(only) = relation.only_token() { + doc = doc + .append(leading_comments_token(&only)) + .append(Doc::text("only")); + } + if let Some(l_paren) = relation.l_paren_token() { + if has_only && comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } + doc = doc.append(comments_before(l_paren)); + doc = doc.append(Doc::text("(")); + } + if let Some(name) = relation.relation_name_ref() { + if has_only && relation.l_paren_token().is_none() { + doc = doc.append(Doc::space()); + } + doc = doc.append(leading_comments(name.syntax())); + if let Some(path) = name.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(r_paren) = relation.r_paren_token() { + doc = doc.append(comments_before(r_paren)); + doc = doc.append(Doc::text(")")); + } + if let Some(star) = relation.star_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } + doc +} + +fn build_select_into<'a>(select_into: &ast::SelectInto) -> Doc<'a> { + let mut select_body = Doc::nil(); + if let Some(select_clause) = select_into.select_clause() { + match select_clause.select_quantifier() { + Some(ast::SelectQuantifier::DistinctClause(distinct_clause)) => { + select_body = select_body + .append(leading_comments(distinct_clause.syntax())) + .append(Doc::text("distinct")); + if let Some(distinct_on) = distinct_clause.distinct_on() { + select_body = select_body + .append(Doc::space()) + .append(leading_comments(distinct_on.syntax())) + .append(build_distinct_on(distinct_on)); + } + select_body = select_body.append(Doc::space()); + } + Some(ast::SelectQuantifier::All(all)) => { + select_body = select_body + .append(leading_comments(all.syntax())) + .append(Doc::text("all")) + .append(Doc::space()); + } + None => (), + } + if let Some(target_list) = select_clause.target_list() { + select_body = select_body + .append(leading_comments(target_list.syntax())) + .append(Doc::list( + Itertools::intersperse( + target_list.targets().flat_map(build_target), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + )); + } + } + let mut doc = Doc::nil(); + if let Some(with_clause) = select_into.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + } + if select_into.with_clause().is_some() { + if let Some(select_clause) = select_into.select_clause() { + doc = doc.append(leading_comments(select_clause.syntax())); + } + } + doc = doc.append( + Doc::text("select") + .append(Doc::line_or_space().append(select_body).nest(2)) + .group(), + ); + + if let Some(into) = select_into.into_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(into.syntax())) + .append(build_into_clause(into)); + } + if let Some(from) = select_into.from_clause() { + doc = doc + .group() + .append(Doc::line_or_space()) + .append(leading_comments(from.syntax())) + .append(build_from_clause(from)); + } + if let Some(where_clause) = select_into.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + if let Some(group) = select_into.group_by_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(group.syntax())) + .append(build_select_group_by_clause(group)); + } + if let Some(having) = select_into.having_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(having.syntax())) + .append(build_having_clause(having)); + } + if let Some(window) = select_into.window_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(window.syntax())) + .append(build_window_clause(window)); + } + if let Some(order_by) = select_into.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_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)); + } + if let Some(filter) = select_into.filter_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(filter.syntax())) + .append(build_filter_clause(filter)); + } + doc.append(build_semicolon(select_into.semicolon_token())) + .group() +} + +fn build_with_clause<'a>(with_clause: ast::WithClause) -> Doc<'a> { + let mut doc = Doc::text("with"); + if let Some(recursive) = with_clause.recursive_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&recursive)) + .append(Doc::text("recursive")); + } + let tables = with_clause.with_tables().map(|table| { + ( + leading_comments(table.syntax()).append(build_with_table(table.clone())), + table.syntax().clone(), + ) + }); + if let Some(tables) = build_comma_separated_docs(tables) { + doc = doc.append(Doc::space()).append(tables); + } + doc +} + +fn build_with_table<'a>(table: ast::WithTable) -> Doc<'a> { + let mut doc = table + .name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(columns) = table.column_list() { + doc = doc + .append(leading_comments(columns.syntax())) + .append(build_cte_column_list(columns)); + } + if let Some(as_token) = table.as_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(materialized) = table.materialized_option() { + doc = doc + .append(Doc::space()) + .append(leading_comments(materialized.syntax())) + .append(match materialized { + ast::MaterializedOption::Materialized(_) => Doc::text("materialized"), + ast::MaterializedOption::NotMaterialized(not_materialized) => Doc::text("not") + .append(Doc::space()) + .append( + not_materialized + .materialized_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("materialized")), + }); + } + if let Some(l_paren) = table.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); + } + let mut body = table + .query() + .map(|query| leading_comments(query.syntax()).append(build_with_query(query))) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = table.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc + .append(Doc::hard_line().append(body).nest(2)) + .append(Doc::hard_line()) + .append(Doc::text(")")); + if let Some(search) = table.search_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(search.syntax())) + .append(build_search_clause(search)); + } + if let Some(cycle) = table.cycle_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(cycle.syntax())) + .append(build_cycle_clause(cycle)); + } + doc.group() +} + +fn build_search_clause<'a>(search: ast::SearchClause) -> Doc<'a> { + let mut doc = Doc::text("search"); + if let Some(order) = search.search_order() { + doc = doc + .append(Doc::space()) + .append(leading_comments(order.syntax())) + .append(match order { + ast::SearchOrder::BreadthFirst(first) => Doc::text("breadth") + .append(Doc::space()) + .append( + first + .first_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("first")), + ast::SearchOrder::DepthFirst(first) => Doc::text("depth") + .append(Doc::space()) + .append( + first + .first_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("first")), + }); + } + if let Some(by_token) = search.by_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&by_token)) + .append(Doc::text("by")); + } + if let Some(columns) = search.columns() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_name_refs(columns.column_name_refs())); + } + if let Some(set_column) = search.set_column() { + doc = doc + .append(Doc::space()) + .append(leading_comments(set_column.syntax())) + .append(build_search_set_column(set_column)); + } + doc.group() +} + +fn build_search_set_column<'a>(set_column: ast::SearchSetColumn) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(column) = set_column.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } + doc +} + +fn build_cycle_clause<'a>(cycle: ast::CycleClause) -> Doc<'a> { + let mut doc = Doc::text("cycle"); + if let Some(columns) = cycle.columns() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_name_refs(columns.column_name_refs())); + } + if let Some(set_column) = cycle.set_column() { + doc = doc + .append(Doc::space()) + .append(leading_comments(set_column.syntax())) + .append(build_cycle_set_column(set_column)); + } + if let Some(path) = cycle.path() { + doc = doc + .append(Doc::space()) + .append(leading_comments(path.syntax())) + .append(build_cycle_path(path)); + } + doc.group() +} + +fn build_cycle_set_column<'a>(set_column: ast::CycleSetColumn) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(column) = set_column.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } + if let Some(column_to) = set_column.column_to() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column_to.syntax())) + .append(build_cycle_column_to(column_to)); + } + doc +} + +fn build_cycle_column_to<'a>(column_to: ast::CycleColumnTo) -> Doc<'a> { + let mut doc = Doc::text("to"); + if let Some(expr) = column_to.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(default) = column_to.default() { + doc = doc + .append(Doc::space()) + .append(leading_comments(default.syntax())) + .append(Doc::text("default")); + if let Some(expr) = default.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + } + doc +} + +fn build_cycle_path<'a>(path: ast::CyclePath) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(column) = path.column_name_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(column.syntax())) + .append(build_name(column.syntax())); + } + doc +} + +fn build_column_name_refs<'a>(columns: impl Iterator) -> Doc<'a> { + let columns = columns.map(|column| { + ( + leading_comments(column.syntax()).append(build_name(column.syntax())), + column.syntax().clone(), + ) + }); + build_comma_separated_docs(columns).unwrap_or_else(Doc::nil) +} + +fn build_cte_column_list<'a>(columns: ast::ColumnList) -> Doc<'a> { + let mut doc = columns + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let names = columns.column_names().map(|name| { + ( + leading_comments(name.syntax()).append(build_name(name.syntax())), + name.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(names).unwrap_or_else(Doc::nil); + if let Some(r_paren) = columns.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + doc +} + +fn build_with_query<'a>(query: ast::WithQuery) -> Doc<'a> { + match query { + ast::WithQuery::CompoundSelect(select) => build_compound_select(&select), + ast::WithQuery::ParenSelect(select) => build_paren_select(select), + ast::WithQuery::Select(select) => build_select_doc(&select), + ast::WithQuery::Table(table) => build_table(&table), + ast::WithQuery::Values(values) => build_values(&values), + ast::WithQuery::Delete(delete) => build_delete(&delete), + ast::WithQuery::Insert(insert) => build_insert(&insert), + ast::WithQuery::Merge(merge) => build_merge(&merge), + ast::WithQuery::Update(update) => build_update(&update), + } +} + +fn build_distinct_on<'a>(distinct_on: ast::DistinctOn) -> Doc<'a> { + let mut doc = Doc::text("on"); + if let Some(l_paren) = distinct_on.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); + } + let exprs: Vec<_> = distinct_on.exprs().collect(); + let has_exprs = !exprs.is_empty(); + let mut body = build_comma_separated_exprs(exprs.into_iter()).unwrap_or_else(Doc::nil); + if !has_exprs { + if let Some(r_paren) = distinct_on.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_having_clause<'a>(having: ast::HavingClause) -> Doc<'a> { + let mut doc = Doc::text("having"); + if let Some(expr) = having.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc +} + +fn build_window_clause<'a>(window: ast::WindowClause) -> Doc<'a> { + let defs = window.window_defs().map(|def| { + ( + leading_comments(def.syntax()).append(build_window_def(def.clone())), + def.syntax().clone(), + ) + }); + let mut doc = Doc::text("window"); + if let Some(defs) = build_comma_separated_docs(defs) { + doc = doc.append(Doc::space()).append(defs.nest(2)); + } + doc.group() +} + +fn build_window_def<'a>(def: ast::WindowDef) -> Doc<'a> { + let mut doc = def + .window() + .map(|window| build_name(window.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(as_token) = def.as_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(l_paren) = def.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); + } + let mut body = def + .window_spec() + .map(|spec| leading_comments(spec.syntax()).append(build_window_spec(spec))) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = def.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_into_clause<'a>(into: ast::IntoClause) -> Doc<'a> { + let mut doc = Doc::text("into"); + if let Some(persistence) = into.persistence() { + doc = doc + .append(Doc::space()) + .append(leading_comments(persistence.syntax())) + .append(build_keyword_node(persistence.syntax())); + } + if let Some(table_token) = into.table_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&table_token)) + .append(Doc::text("table")); + } + if let Some(table_name) = into.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 +} + +fn build_select_group_by_clause<'a>(group: ast::GroupByClause) -> Doc<'a> { + let mut doc = Doc::text("group").append(Doc::space()); + if let Some(by_token) = group.by_token() { + doc = doc.append(leading_comments_token(&by_token)); + } + doc = doc.append(Doc::text("by")).append(Doc::space()); + if let Some(quantifier) = group.all_or_distinct() { + doc = doc + .append(leading_comments(quantifier.syntax())) + .append(match quantifier { + ast::AllOrDistinct::All(_) => Doc::text("all"), + ast::AllOrDistinct::Distinct(_) => Doc::text("distinct"), + }) + .append(Doc::space()); + } + if let Some(list) = group.group_by_list() { + doc = doc.append(build_group_by_list(list)); + } + doc +} + +fn build_create_publication<'a>(stmt: &ast::CreatePublication) -> Doc<'a> { + let mut doc = Doc::text("create"); + if let Some(token) = stmt.publication_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(publication) = stmt.publication() { + doc = doc + .append(Doc::space()) + .append(leading_comments(publication.syntax())) + .append(build_name(publication.syntax())); + } + if let Some(clause) = stmt.publication_for_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(clause.syntax())) + .append(build_publication_for_clause(clause)); + } + if let Some(params) = stmt.with_params() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(params.syntax())) + .append(build_with_params(params)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_publication_for_clause<'a>(clause: ast::PublicationForClause) -> Doc<'a> { + match clause { + ast::PublicationForClause::ForAllPublicationObjects(clause) => { + let mut doc = Doc::text("for"); + if let Some(objects) = build_all_publication_objects(clause.all_publication_objects()) { + doc = doc.append(Doc::line_or_space().append(objects).nest(2)); + } + if let Some(except) = clause.except_table_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(except.syntax())) + .append(build_except_table_clause(except)); + } + doc.group() + } + ast::PublicationForClause::ForPublicationObjects(clause) => { + let mut doc = Doc::text("for"); + if let Some(objects) = build_publication_objects(clause.publication_objects()) { + doc = doc.append(Doc::line_or_space().append(objects).nest(2)); + } + doc.group() + } + } +} + +fn build_all_publication_objects<'a>( + objects: impl Iterator, +) -> Option> { + build_comma_separated_docs(objects.map(|object| { + let syntax = object.syntax().clone(); + let doc = leading_comments(object.syntax()).append(match object { + ast::AllPublicationObject::AllPublicationTables(object) => { + let mut doc = Doc::text("all"); + if let Some(token) = object.tables_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("tables")); + } + doc + } + ast::AllPublicationObject::AllPublicationSequences(object) => { + let mut doc = Doc::text("all"); + if let Some(token) = object.sequences_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("sequences")); + } + doc + } + }); + (doc, syntax) + })) +} + +fn build_publication_objects<'a>( + objects: impl Iterator, +) -> Option> { + build_comma_separated_docs(objects.map(|object| { + let syntax = object.syntax().clone(); + ( + leading_comments(object.syntax()).append(build_publication_object(object)), + syntax, + ) + })) +} + +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"); + if let Some(token) = object.only_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("only")); + } + 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 let Some(table) = object.table_name_ref() { + if !parenthesized { + doc = doc.append(Doc::space()); + } + doc = doc.append(leading_comments(table.syntax())); + if let Some(path) = table.path_ref() { + doc = doc.append(build_path_ref(&path)); + } + } + if let Some(r_paren) = object.r_paren_token() { + doc = doc.append(comments_before(r_paren)).append(Doc::text(")")); + } + if let Some(star) = object.star_token() { + doc = doc + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } + if let Some(columns) = object.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + if let Some(where_clause) = object.where_condition_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_condition_clause(where_clause)); + } + doc.group() + } + ast::PublicationObject::PublicationObjectTablesInSchema(object) => { + let mut doc = Doc::text("tables"); + if let Some(token) = object.in_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("in")); + } + if let Some(token) = object.schema_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("schema")); + } + if let Some(token) = object.current_schema_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("current_schema")); + } else if let Some(schema) = object.schema_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(schema.syntax())) + .append(build_name(schema.syntax())); + } + if let Some(where_clause) = object.where_condition_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_condition_clause(where_clause)); + } + doc.group() + } + } +} + +fn build_except_table_clause<'a>(clause: ast::ExceptTableClause) -> Doc<'a> { + let mut doc = Doc::text("except"); + if let Some(l_paren) = clause.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(comments_before(l_paren)) + .append(Doc::text("(")); + } + let items = clause.except_table_names().map(|name| { + let mut item = Doc::nil(); + if let Some(table_token) = name.table_token() { + item = item + .append(leading_comments_token(&table_token)) + .append(Doc::text("table")) + .append(Doc::space()); + } + if let Some(table) = name.table_relation_name() { + item = item + .append(leading_comments(table.syntax())) + .append(build_table_relation_name(table)); + } + ( + leading_comments(name.syntax()).append(item), + name.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + if let Some(r_paren) = clause.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_alter_publication<'a>(stmt: &ast::AlterPublication) -> Doc<'a> { + let mut doc = Doc::text("alter"); + if let Some(token) = stmt.publication_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(publication) = stmt.publication_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(publication.syntax())) + .append(build_name(publication.syntax())); + } + if let Some(action) = stmt.action() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(action.syntax())) + .append(build_alter_publication_action(action)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_alter_publication_action<'a>(action: ast::AlterPublicationAction) -> Doc<'a> { + match action { + ast::AlterPublicationAction::AddPublicationObjects(action) => { + build_publication_object_action("add", action.publication_objects()) + } + ast::AlterPublicationAction::DropPublicationObjects(action) => { + build_publication_object_action("drop", action.publication_objects()) + } + ast::AlterPublicationAction::SetPublicationObjects(action) => { + build_publication_object_action("set", action.publication_objects()) + } + ast::AlterPublicationAction::SetAllPublicationObjectList(action) => { + let mut doc = Doc::text("set"); + if let Some(objects) = build_all_publication_objects(action.all_publication_objects()) { + doc = doc.append(Doc::line_or_space().append(objects).nest(2)); + } + if let Some(except) = action.except_table_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(except.syntax())) + .append(build_except_table_clause(except)); + } + doc.group() + } + ast::AlterPublicationAction::SetOptions(action) => build_set_options(action), + ast::AlterPublicationAction::OwnerTo(action) => build_owner_to(action), + ast::AlterPublicationAction::PublicationRenameTo(action) => { + let mut doc = Doc::text("rename"); + if let Some(token) = action.to_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("to")); + } + if let Some(publication) = action.publication() { + doc = doc + .append(Doc::space()) + .append(leading_comments(publication.syntax())) + .append(build_name(publication.syntax())); + } + doc + } + } +} + +fn build_publication_object_action<'a>( + keyword: &'static str, + objects: impl Iterator, +) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(objects) = build_publication_objects(objects) { + doc = doc.append(Doc::line_or_space().append(objects).nest(2)); + } + doc.group() +} + +fn build_set_options<'a>(options: ast::SetOptions) -> Doc<'a> { + let mut doc = Doc::text("set"); + if let Some(attributes) = options.attribute_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(attributes.syntax())) + .append(build_attribute_list(attributes)); + } + doc +} + +fn build_owner_to<'a>(owner: ast::OwnerTo) -> Doc<'a> { + let mut doc = Doc::text("owner"); + if let Some(token) = owner.to_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("to")); + } + if let Some(role) = owner.role_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(role.syntax())) + .append(build_name(role.syntax())); + } + doc +} + +fn build_create_subscription<'a>(stmt: &ast::CreateSubscription) -> Doc<'a> { + let mut doc = Doc::text("create"); + if let Some(token) = stmt.subscription_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("subscription")); + } + if let Some(subscription) = stmt.subscription() { + doc = doc + .append(Doc::space()) + .append(leading_comments(subscription.syntax())) + .append(build_name(subscription.syntax())); + } + if let Some(source) = stmt.source() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(source.syntax())) + .append(build_subscription_source(source)); + } + if let Some(token) = stmt.publication_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(publications) = build_publication_refs(stmt.publication_refs()) { + doc = doc.append(Doc::space()).append(publications.nest(2)); + } + if let Some(params) = stmt.with_params() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(params.syntax())) + .append(build_with_params(params)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_subscription_source<'a>(source: ast::SubscriptionSource) -> Doc<'a> { + match source { + ast::SubscriptionSource::ConnectionClause(source) => { + let mut doc = Doc::text("connection"); + if let Some(literal) = source.literal() { + doc = doc + .append(Doc::space()) + .append(leading_comments(literal.syntax())) + .append(build_literal(literal)); + } + doc + } + ast::SubscriptionSource::ServerClause(source) => { + let mut doc = Doc::text("server"); + if let Some(server) = source.server_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(server.syntax())) + .append(build_name(server.syntax())); + } + doc + } + } +} + +fn build_publication_refs<'a>( + publications: impl Iterator, +) -> Option> { + build_comma_separated_docs(publications.map(|publication| { + ( + leading_comments(publication.syntax()).append(build_name(publication.syntax())), + publication.syntax().clone(), + ) + })) +} + +fn build_alter_subscription<'a>(stmt: &ast::AlterSubscription) -> Doc<'a> { + let mut doc = Doc::text("alter"); + if let Some(token) = stmt.subscription_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("subscription")); + } + if let Some(subscription) = stmt.subscription_ref() { + doc = doc + .append(Doc::space()) + .append(leading_comments(subscription.syntax())) + .append(build_name(subscription.syntax())); + } + if let Some(action) = stmt.action() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(action.syntax())) + .append(build_alter_subscription_action(action)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_alter_subscription_action<'a>(action: ast::AlterSubscriptionAction) -> Doc<'a> { + match action { + ast::AlterSubscriptionAction::ConnectionClause(action) => { + build_subscription_source(ast::SubscriptionSource::ConnectionClause(action)) + } + ast::AlterSubscriptionAction::ServerClause(action) => { + build_subscription_source(ast::SubscriptionSource::ServerClause(action)) + } + ast::AlterSubscriptionAction::SetOptions(action) => build_set_options(action), + ast::AlterSubscriptionAction::AddPublication(action) => { + build_subscription_publication_action( + "add", + action.publication_token(), + action.publication_refs(), + action.with_params(), + ) + } + ast::AlterSubscriptionAction::SetPublication(action) => { + build_subscription_publication_action( + "set", + action.publication_token(), + action.publication_refs(), + action.with_params(), + ) + } + ast::AlterSubscriptionAction::DropSubscriptionPublication(action) => { + build_subscription_publication_action( + "drop", + action.publication_token(), + action.publication_refs(), + action.with_params(), + ) + } + ast::AlterSubscriptionAction::RefreshPublication(action) => { + let mut doc = Doc::text("refresh").append(Doc::space()); + if let Some(token) = action.publication_token() { + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(params) = action.with_params() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(params.syntax())) + .append(build_with_params(params)); + } + doc.group() + } + ast::AlterSubscriptionAction::EnableSubscription(_) => Doc::text("enable"), + ast::AlterSubscriptionAction::DisableSubscription(_) => Doc::text("disable"), + ast::AlterSubscriptionAction::SkipSubscription(action) => { + let mut doc = Doc::text("skip"); + if let Some(attributes) = action.attribute_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(attributes.syntax())) + .append(build_attribute_list(attributes)); + } + doc + } + ast::AlterSubscriptionAction::OwnerTo(action) => build_owner_to(action), + ast::AlterSubscriptionAction::SubscriptionRenameTo(action) => { + let mut doc = Doc::text("rename"); + if let Some(token) = action.to_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("to")); + } + if let Some(subscription) = action.subscription() { + doc = doc + .append(Doc::space()) + .append(leading_comments(subscription.syntax())) + .append(build_name(subscription.syntax())); + } + doc + } + } +} + +fn build_subscription_publication_action<'a>( + keyword: &'static str, + publication_token: Option, + publications: impl Iterator, + params: Option, +) -> Doc<'a> { + let mut doc = Doc::text(keyword).append(Doc::space()); + if let Some(token) = publication_token { + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(publications) = build_publication_refs(publications) { + doc = doc.append(Doc::space()).append(publications.nest(2)); + } + if let Some(params) = params { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(params.syntax())) + .append(build_with_params(params)); + } + doc.group() +} + +fn build_drop_publication<'a>(stmt: &ast::DropPublication) -> Doc<'a> { + let mut doc = Doc::text("drop"); + if let Some(token) = stmt.publication_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("publication")); + } + if let Some(if_exists) = stmt.if_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_exists.syntax())) + .append(build_if_exists(if_exists)); + } + if let Some(publications) = build_publication_refs(stmt.publication_refs()) { + doc = doc + .append(Doc::line_or_space()) + .append(publications.nest(2)); + } + if let Some(behavior) = stmt.drop_behavior() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(behavior.syntax())) + .append(build_drop_behavior(behavior)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_drop_subscription<'a>(stmt: &ast::DropSubscription) -> Doc<'a> { + let mut doc = Doc::text("drop"); + if let Some(token) = stmt.subscription_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("subscription")); + } + if let Some(if_exists) = stmt.if_exists() { + doc = doc + .append(Doc::space()) + .append(leading_comments(if_exists.syntax())) + .append(build_if_exists(if_exists)); + } + if let Some(subscription) = stmt.subscription_ref() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(subscription.syntax())) + .append(build_name(subscription.syntax())); + } + if let Some(behavior) = stmt.drop_behavior() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(behavior.syntax())) + .append(build_drop_behavior(behavior)); + } + doc.append(build_semicolon(stmt.semicolon_token())).group() +} + +fn build_if_exists<'a>(if_exists: ast::IfExists) -> Doc<'a> { + let mut doc = Doc::text("if"); + if let Some(token) = if_exists.exists_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("exists")); + } + doc +} + +fn build_drop_behavior<'a>(behavior: ast::DropBehavior) -> Doc<'a> { + match behavior { + ast::DropBehavior::Cascade(_) => Doc::text("cascade"), + ast::DropBehavior::Restrict(_) => Doc::text("restrict"), + } +} + +fn build_select_doc<'a>(select: &ast::Select) -> Doc<'a> { + build_select_doc_ungrouped(select).group() +} + +fn has_single_call_target(select: &ast::Select) -> bool { + let Some(select_clause) = select.select_clause() else { + return false; + }; + if select_clause.select_quantifier().is_some() { + return false; + } + let Some(target_list) = select_clause.target_list() else { + return false; + }; + let mut targets = target_list.targets(); + let Some(target) = targets.next() else { + return false; + }; + if targets.next().is_some() { + return false; + } + matches!(target.expr(), Some(ast::Expr::CallExpr(_))) +} + +fn build_select_doc_ungrouped<'a>(select: &ast::Select) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = select.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + if let Some(select_clause) = select.select_clause() { + doc = doc.append(leading_comments(select_clause.syntax())); + } + } + let mut select_doc = Doc::text("select"); + let mut select_body = Doc::nil(); + if let Some(select_clause) = select.select_clause() { + match select_clause.select_quantifier() { + Some(ast::SelectQuantifier::DistinctClause(distinct_clause)) => { + select_body = select_body + .append(leading_comments(distinct_clause.syntax())) + .append(Doc::text("distinct")); + if let Some(distinct_on) = distinct_clause.distinct_on() { + select_body = select_body + .append(Doc::space()) + .append(leading_comments(distinct_on.syntax())) + .append(build_distinct_on(distinct_on)); + } + select_body = select_body.append(Doc::space()); + } + Some(ast::SelectQuantifier::All(all)) => { + select_body = select_body + .append(leading_comments(all.syntax())) + .append(Doc::text("all")) + .append(Doc::space()); + } + None => (), + } + if let Some(target_list) = select_clause.target_list() { + select_body = select_body + .append(leading_comments(target_list.syntax())) + .append(Doc::list( + Itertools::intersperse( + target_list.targets().flat_map(build_target), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + )); + } + } + let has_distinct_on = matches!( + select + .select_clause() + .and_then(|clause| clause.select_quantifier()), + Some(ast::SelectQuantifier::DistinctClause(clause)) + if clause.distinct_on().is_some() + ); + select_doc = if has_single_call_target(select) { + select_doc.append(Doc::space()).append(select_body) + } else if has_distinct_on { + select_doc.append(Doc::space()).append(select_body.nest(2)) + } else { + select_doc.append(Doc::line_or_space().append(select_body).nest(2)) + }; + doc = if select.with_clause().is_some() { + doc.append(select_doc.group()) + } else { + doc.append(select_doc) + }; + if select.from_clause().is_some() { + doc = doc.group(); + } + + if let Some(from) = select.from_clause() { + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(from.syntax())) + .append(build_from_clause(from)), + ); + } + + if let Some(where_clause) = select.where_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + if let Some(group) = select.group_by_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(group.syntax())) + .append(build_select_group_by_clause(group)); + } + if let Some(having) = select.having_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(having.syntax())) + .append(build_having_clause(having)); + } + if let Some(window) = select.window_clause() { + doc = doc + .append(Doc::line_or_space()) + .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() { + 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)); + } + if let Some(filter) = select.filter_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(filter.syntax())) + .append(build_filter_clause(filter)); + } + + doc = doc.append(build_semicolon(select.semicolon_token())); + + doc +} + +fn build_from_clause<'a>(from: ast::FromClause) -> Doc<'a> { + let from_items = from.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)), + syntax, + ) + }); + let body = build_comma_separated_docs(from_items.chain(join_exprs)).unwrap_or_else(Doc::nil); + + Doc::text("from").append(Doc::space()).append(body.nest(2)) +} + +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)) + } else { + Doc::nil() + }; + + if let Some(join) = join_expr.join() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(join.syntax())) + .append(build_join(join)); + } + doc.group() +} + +fn build_join<'a>(join: ast::Join) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(natural) = join.natural_token() { + doc = doc + .append(leading_comments_token(&natural)) + .append(Doc::text("natural")) + .append(Doc::space()); + } + if let Some(join_type) = join.join_type() { + doc = doc + .append(leading_comments(join_type.syntax())) + .append(build_keyword_node(join_type.syntax())); + } + if let Some(item) = join.from_item() { + doc = doc + .append(Doc::space()) + .append(leading_comments(item.syntax())) + .append(build_from_item(item)); + } + if let Some(condition) = join.join_condition() { + match condition { + ast::JoinCondition::OnClause(on_clause) => { + doc = doc + .append(Doc::space()) + .append(leading_comments(on_clause.syntax())) + .append(build_join_on_clause(on_clause)); + } + ast::JoinCondition::JoinUsingClause(using) => { + let condition_doc = + leading_comments(using.syntax()).append(build_join_using_clause(using)); + doc = doc.append(Doc::line_or_space().append(condition_doc).nest(2)); + } + } + } + doc.group() +} + +fn build_join_on_clause<'a>(on_clause: ast::OnClause) -> Doc<'a> { + let mut doc = Doc::text("on"); + if let Some(expr) = on_clause.expr() { + let expr_doc = leading_comments(expr.syntax()).append(build_expr(expr)); + doc = doc.append(Doc::line_or_space().append(expr_doc).nest(2)); + } + doc +} + +fn build_join_using_clause<'a>(using: ast::JoinUsingClause) -> Doc<'a> { + let mut doc = Doc::text("using"); + if let Some(columns) = using.column_ref_list() { + doc = doc + .append(Doc::space()) + .append(leading_comments(columns.syntax())) + .append(build_column_ref_list(columns)); + } + if let Some(alias) = using.alias() { + doc = doc + .append(Doc::space()) + .append(leading_comments(alias.syntax())) + .append(build_required_as_alias(alias)); + } + doc +} + +fn build_from_item<'a>(item: ast::FromItem) -> Doc<'a> { + match item { + ast::FromItem::RelationFromItem(relation) => build_relation_from_item(relation), + ast::FromItem::FunctionFromItem(function) => build_function_from_item(function), + ast::FromItem::ExprFromItem(expr) => build_expr_from_item(expr), + ast::FromItem::ParenFromItem(paren) => build_paren_from_item(paren), + ast::FromItem::RowsFromItem(rows) => build_rows_from_item(rows), + ast::FromItem::GraphTableFromItem(graph_table) => build_graph_table_from_item(graph_table), + ast::FromItem::JsonTableFromItem(json_table) => build_json_table_from_item(json_table), + ast::FromItem::XmlTableFromItem(xml_table) => build_xml_table_from_item(xml_table), + } +} + +fn build_function_from_item<'a>(item: ast::FunctionFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(call) = item.call_expr() { + doc = doc + .append(leading_comments(call.syntax())) + .append(build_call_expr(call)); + } + if let Some(ordinality) = item.with_ordinality() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ordinality.syntax())) + .append(build_with_ordinality(ordinality)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_with_ordinality<'a>(ordinality: ast::WithOrdinality) -> Doc<'a> { + let mut doc = Doc::text("with"); + if let Some(ordinality_token) = ordinality.ordinality_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&ordinality_token)) + .append(Doc::text("ordinality")); + } + doc +} + +fn build_expr_from_item<'a>(item: ast::ExprFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(cast) = item.cast_expr() { + doc = doc + .append(leading_comments(cast.syntax())) + .append(build_cast_expr(cast)); + } else if let Some(call) = item.call_expr() { + doc = doc + .append(leading_comments(call.syntax())) + .append(build_call_expr(call)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_paren_from_item<'a>(item: ast::ParenFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(only) = item.only_token() { + doc = doc + .append(leading_comments_token(&only)) + .append(Doc::text("only")) + .append(Doc::space()); + } + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(select) = item.paren_select() { + doc = doc + .append(leading_comments(select.syntax())) + .append(build_paren_select(select)); + } else if let Some(expr) = item.paren_expr() { + doc = doc + .append(leading_comments(expr.syntax())) + .append(build_paren_expr(expr)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_select_variant<'a>(select: ast::SelectVariant) -> Doc<'a> { + match select { + ast::SelectVariant::CompoundSelect(compound_select) => { + build_compound_select(&compound_select) + } + ast::SelectVariant::ParenSelect(select) => build_paren_select(select), + ast::SelectVariant::Select(select) => build_select_doc(&select), + ast::SelectVariant::SelectInto(select_into) => build_select_into(&select_into), + ast::SelectVariant::Table(table) => build_table(&table), + ast::SelectVariant::Values(values) => build_values(&values), + } +} + +fn build_compound_select<'a>(select: &ast::CompoundSelect) -> Doc<'a> { + let mut doc = select + .lhs() + .map(build_select_variant) + .unwrap_or_else(Doc::nil); + + if let Some(op) = select.op() { + let (syntax, keyword, quantifier) = match op { + ast::CompoundOp::Union(op) => (op.syntax().clone(), "union", op.all_or_distinct()), + ast::CompoundOp::Intersect(op) => { + (op.syntax().clone(), "intersect", op.all_or_distinct()) + } + ast::CompoundOp::Except(op) => (op.syntax().clone(), "except", op.all_or_distinct()), + }; + let mut op_doc = leading_comments(&syntax).append(Doc::text(keyword)); + if let Some(quantifier) = quantifier { + op_doc = op_doc + .append(Doc::space()) + .append(leading_comments(quantifier.syntax())) + .append(match quantifier { + ast::AllOrDistinct::All(_) => Doc::text("all"), + ast::AllOrDistinct::Distinct(_) => Doc::text("distinct"), + }); + } + doc = doc.append(Doc::line_or_space()).append(op_doc); + } + + if let Some(rhs) = select.rhs() { + 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)); + } + if let Some(offset) = select.offset_clause() { + doc = doc + .append(Doc::hard_line()) + .append(leading_comments(offset.syntax())) + .append(build_offset_clause(offset)); + } + + doc.append(build_semicolon(select.semicolon_token())) + .group() +} + +fn build_locking_clause<'a>(locking: ast::LockingClause) -> Doc<'a> { + let mut doc = Doc::text("for"); + if let Some(strength) = locking.lock_strength() { + doc = doc + .append(Doc::space()) + .append(leading_comments(strength.syntax())) + .append(build_keyword_node(strength.syntax())); + } + if let Some(of) = locking.locking_of() { + doc = doc + .append(Doc::space()) + .append(leading_comments(of.syntax())) + .append(Doc::text("of")); + if let Some(exprs) = build_comma_separated_exprs(of.exprs()) { + doc = doc.append(Doc::space()).append(exprs); + } + } + if let Some(wait) = locking.lock_wait() { + doc = doc + .append(Doc::space()) + .append(leading_comments(wait.syntax())) + .append(build_keyword_node(wait.syntax())); + } + doc.group() +} + +fn build_limit_clause<'a>(limit: ast::LimitClause) -> Doc<'a> { + let mut doc = Doc::text("limit"); + if let Some(value) = limit.limit_value() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(match value { + ast::LimitValue::All(_) => Doc::text("all"), + ast::LimitValue::Expr(expr) => build_expr(expr), + }); + } + doc +} + +fn build_fetch_clause<'a>(fetch: ast::FetchClause) -> Doc<'a> { + let mut doc = Doc::text("fetch"); + if let Some(token) = fetch.first_token().or_else(|| fetch.next_token()) { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(token.text().to_ascii_lowercase())); + } + if let Some(expr) = fetch.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(token) = fetch.row_token().or_else(|| fetch.rows_token()) { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(token.text().to_ascii_lowercase())); + } + if let Some(quantity) = fetch.fetch_quantity() { + doc = doc + .append(Doc::space()) + .append(leading_comments(quantity.syntax())) + .append(build_keyword_node(quantity.syntax())); + } + doc +} + +fn build_offset_clause<'a>(offset: ast::OffsetClause) -> Doc<'a> { + let mut doc = Doc::text("offset"); + if let Some(expr) = offset.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(token) = offset.row_token().or_else(|| offset.rows_token()) { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(token.text().to_ascii_lowercase())); + } + doc +} + +fn build_paren_select<'a>(select: ast::ParenSelect) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(with_clause) = select.with_clause() { + doc = doc + .append(leading_comments(with_clause.syntax())) + .append(build_with_clause(with_clause)) + .append(Doc::hard_line()); + } + + let has_with_clause = select.with_clause().is_some(); + let mut paren_doc = select + .l_paren_token() + .map(|token| { + if has_with_clause { + leading_comments_token(&token) + } else { + comments_before(token) + } + }) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let mut body = select + .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)); + } + paren_doc = paren_doc + .append(wrap_body(body)) + .append(Doc::text(")")) + .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() { + 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)); + } + + doc.append(build_semicolon(select.semicolon_token())) + .group() +} + +fn build_rows_from_item<'a>(item: ast::RowsFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(rows) = item.rows_token() { + doc = doc + .append(leading_comments_token(&rows)) + .append(Doc::text("rows")); + } + if let Some(from) = item.from_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&from)) + .append(Doc::text("from")); + } + if let Some(l_paren) = item.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + doc = doc.append(Doc::text("(")); + + let args = item.rows_from_args().map(|arg| { + ( + leading_comments(arg.syntax()).append(build_rows_from_arg(arg.clone())), + arg.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(args).unwrap_or_else(Doc::nil); + if let Some(r_paren) = item.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + + if let Some(ordinality) = item.with_ordinality() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ordinality.syntax())) + .append(build_with_ordinality(ordinality)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_rows_from_arg<'a>(arg: ast::RowsFromArg) -> Doc<'a> { + let mut doc = arg + .call_expr() + .map(build_call_expr) + .unwrap_or_else(Doc::nil); + if let Some(as_token) = arg.as_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(columns) = arg.column_def_list() { + doc = doc.append(build_from_alias_columns(columns.into())); + } + doc +} + +fn build_json_table_from_item<'a>(item: ast::JsonTableFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(json_table) = item.json_table() { + doc = doc + .append(leading_comments(json_table.syntax())) + .append(build_json_table(json_table)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_json_table<'a>(json_table: ast::JsonTable) -> Doc<'a> { + let mut doc = Doc::text("json_table"); + if let Some(l_paren) = json_table.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(document) = json_table.document_expr() { + body = body + .append(leading_comments(document.syntax())) + .append(build_expr(document)); + } + if let Some(format) = json_table.json_format_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); + } + if let Some(comma) = json_table.comma_token() { + body = body + .append(comments_before(comma)) + .append(Doc::text(",")) + .append(Doc::line_or_space()); + } + if let Some(path) = json_table.path_expr() { + body = body + .append(leading_comments(path.syntax())) + .append(build_expr(path)); + } + if let Some(name) = json_table.json_path_name_clause() { + body = body + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_json_path_name_clause(name)); + } + if let Some(passing) = json_table.json_passing_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(passing.syntax())) + .append(build_json_passing_clause(passing)); + } + if let Some(columns) = json_table.json_table_column_list() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(columns.syntax())) + .append(build_json_table_column_list(columns)); + } + if let Some(plan) = json_table.json_table_plan_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(plan.syntax())) + .append(build_json_table_plan_clause(plan)); + } + if let Some(on_error) = json_table.json_on_error_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(on_error.syntax())) + .append(build_json_on_error_clause(on_error)); + } + if let Some(r_paren) = json_table.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_json_path_name_clause<'a>(clause: ast::JsonPathNameClause) -> Doc<'a> { + let mut doc = Doc::text("as"); + if let Some(name) = clause.json_path_name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc +} + +fn build_json_path_clause<'a>(clause: ast::JsonPathClause) -> Doc<'a> { + let mut doc = Doc::text("path"); + if let Some(expr) = clause.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc +} + +fn build_json_table_column_list<'a>(list: ast::JsonTableColumnList) -> Doc<'a> { + let mut doc = Doc::text("columns"); + if let Some(l_paren) = list.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + doc = doc.append(Doc::text("(")); + let columns = list.json_table_columns().map(|column| { + ( + leading_comments(column.syntax()).append(build_json_table_column(column.clone())), + column.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(columns).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_json_table_column<'a>(column: ast::JsonTableColumn) -> Doc<'a> { + match column { + ast::JsonTableColumn::JsonTableOrdinalityColumn(column) => { + let mut doc = column + .column_name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(for_token) = column.for_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&for_token)) + .append(Doc::text("for")); + } + if let Some(ordinality) = column.ordinality_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&ordinality)) + .append(Doc::text("ordinality")); + } + doc + } + ast::JsonTableColumn::JsonTableValueColumn(column) => { + let mut doc = build_json_table_typed_column(column.column_name(), column.ty()); + if let Some(format) = column.json_format_clause() { + doc = append_json_table_column_clause(doc, format, build_json_format_clause); + } + if let Some(path) = column.json_path_clause() { + doc = append_json_table_column_clause(doc, path, build_json_path_clause); + } + if let Some(wrapper) = column.json_wrapper_behavior_clause() { + doc = append_json_table_column_clause( + doc, + wrapper, + build_json_wrapper_behavior_clause, + ); + } + if let Some(quotes) = column.json_quotes_clause() { + doc = append_json_table_column_clause(doc, quotes, build_json_quotes_clause); + } + if let Some(on_empty) = column.json_on_empty_clause() { + doc = append_json_table_column_clause(doc, on_empty, build_json_on_empty_clause); + } + if let Some(on_error) = column.json_on_error_clause() { + doc = append_json_table_column_clause(doc, on_error, build_json_on_error_clause); + } + doc.group() + } + ast::JsonTableColumn::JsonTableExistsColumn(column) => { + let mut doc = build_json_table_typed_column(column.column_name(), column.ty()); + if let Some(exists) = column.exists_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&exists)) + .append(Doc::text("exists")); + } + if let Some(path) = column.json_path_clause() { + doc = append_json_table_column_clause(doc, path, build_json_path_clause); + } + if let Some(on_error) = column.json_on_error_clause() { + doc = append_json_table_column_clause(doc, on_error, build_json_on_error_clause); + } + doc.group() + } + ast::JsonTableColumn::JsonTableNestedColumn(column) => { + let mut doc = Doc::text("nested"); + if let Some(path_token) = column.path_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&path_token)) + .append(Doc::text("path")); + } + if let Some(expr) = column.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(name) = column.json_path_name_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_json_path_name_clause(name)); + } + if let Some(columns) = column.json_table_column_list() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(columns.syntax())) + .append(build_json_table_column_list(columns)); + } + doc.group() + } + } +} + +fn build_json_table_typed_column<'a>( + name: Option, + ty: Option, +) -> Doc<'a> { + let mut doc = name + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(ty) = ty { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + doc +} + +fn append_json_table_column_clause<'a, T: AstNode>( + doc: Doc<'a>, + clause: T, + build: impl FnOnce(T) -> Doc<'a>, +) -> Doc<'a> { + doc.append(Doc::line_or_space()) + .append(leading_comments(clause.syntax())) + .append(build(clause)) +} + +fn build_json_table_plan_clause<'a>(clause: ast::JsonTablePlanClause) -> Doc<'a> { + let mut doc = Doc::text("plan"); + if let Some(default) = clause.default_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&default)) + .append(Doc::text("default")); + } + if let Some(l_paren) = clause.l_paren_token() { + if comment_tokens_before(l_paren.clone()).is_empty() { + doc = doc.append(Doc::space()); + } else { + doc = doc.append(comments_before(l_paren)); + } + } + doc = doc.append(Doc::text("(")); + let plans = clause.json_table_plans().map(|plan| { + ( + leading_comments(plan.syntax()).append(build_json_table_plan(plan.clone())), + plan.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(plans).unwrap_or_else(Doc::nil); + if let Some(r_paren) = clause.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_json_table_plan<'a>(plan: ast::JsonTablePlan) -> Doc<'a> { + match plan { + ast::JsonTablePlan::JsonPathNameRef(name) => build_name(name.syntax()), + ast::JsonTablePlan::JsonTablePlanChoice(choice) => choice + .json_table_plan_operator() + .map(build_json_table_plan_operator) + .unwrap_or_else(Doc::nil), + ast::JsonTablePlan::JsonTablePlanJoin(join) => { + let mut doc = join + .lhs() + .map(build_json_table_plan) + .unwrap_or_else(Doc::nil); + if let Some(operator) = join.json_table_plan_operator() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(operator.syntax())) + .append(build_json_table_plan_operator(operator)); + } + if let Some(rhs) = join.rhs() { + doc = doc + .append(Doc::space()) + .append(leading_comments(rhs.syntax())) + .append(build_json_table_plan(rhs)); + } + doc.group() + } + ast::JsonTablePlan::ParenJsonTablePlan(plan) => { + let mut doc = plan + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let mut body = plan + .json_table_plan() + .map(|plan| leading_comments(plan.syntax()).append(build_json_table_plan(plan))) + .unwrap_or_else(Doc::nil); + if let Some(r_paren) = plan.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + doc + } + } +} + +fn build_json_table_plan_operator<'a>(operator: ast::JsonTablePlanOperator) -> Doc<'a> { + match operator { + ast::JsonTablePlanOperator::JsonTablePlanCross(_) => Doc::text("cross"), + ast::JsonTablePlanOperator::JsonTablePlanInner(_) => Doc::text("inner"), + ast::JsonTablePlanOperator::JsonTablePlanOuter(_) => Doc::text("outer"), + ast::JsonTablePlanOperator::JsonTablePlanUnion(_) => Doc::text("union"), + } +} + +fn build_xml_table_from_item<'a>(item: ast::XmlTableFromItem) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(lateral) = item.lateral_token() { + doc = doc + .append(leading_comments_token(&lateral)) + .append(Doc::text("lateral")) + .append(Doc::space()); + } + if let Some(xml_table) = item.xml_table() { + doc = doc + .append(leading_comments(xml_table.syntax())) + .append(build_xml_table(xml_table)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_xml_table<'a>(xml_table: ast::XmlTable) -> Doc<'a> { + let mut doc = Doc::text("xmltable"); + if let Some(l_paren) = xml_table.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(namespaces) = xml_table.xml_namespace_list() { + if let Some(xmlnamespaces) = xml_table.xmlnamespaces_token() { + body = body + .append(leading_comments_token(&xmlnamespaces)) + .append(Doc::text("xmlnamespaces")); + } + body = body + .append(comments_before(namespaces.syntax().clone())) + .append(build_xml_namespace_list(namespaces)); + if let Some(comma) = xml_table.comma_token() { + body = body + .append(comments_before(comma)) + .append(Doc::text(",")) + .append(Doc::line_or_space()); + } + } + if let Some(passing) = xml_table.xml_row_passing_clause() { + body = body + .append(leading_comments(passing.syntax())) + .append(build_xml_row_passing_clause(passing)); + } + if let Some(columns) = xml_table.xml_table_column_list() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(columns.syntax())) + .append(build_xml_table_column_list(columns)); + } + if let Some(r_paren) = xml_table.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_namespace_list<'a>(list: ast::XmlNamespaceList) -> Doc<'a> { + let mut doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let namespaces = list.xml_namespaces().map(|namespace| { ( - leading_comments(exclusion.syntax()).append(item.nest(2).group()), - exclusion.syntax().clone(), + leading_comments(namespace.syntax()).append(build_xml_namespace(namespace.clone())), + namespace.syntax().clone(), ) }); - let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + let mut body = build_comma_separated_docs(namespaces).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() + doc = doc.append(wrap_body(body)).append(Doc::text(")")).group(); + doc } -fn build_where_condition_clause<'a>(where_clause: ast::WhereConditionClause) -> Doc<'a> { - let mut doc = Doc::text("where"); - if let Some(l_paren) = where_clause.l_paren_token() { - if comment_tokens_before(l_paren.clone()).is_empty() { - doc = doc.append(Doc::space()); - } else { - doc = doc.append(comments_before(l_paren)); - } +fn build_xml_namespace<'a>(namespace: ast::XmlNamespace) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(default) = namespace.default_token() { + doc = doc + .append(leading_comments_token(&default)) + .append(Doc::text("default")) + .append(Doc::space()); } - doc = doc.append(Doc::text("(")); - - let mut body = Doc::nil(); - if let Some(expr) = where_clause.expr() { - body = body + if let Some(expr) = namespace.expr() { + doc = doc .append(leading_comments(expr.syntax())) .append(build_expr(expr)); } - if let Some(r_paren) = where_clause.r_paren_token() { - body = body.append(comments_before(r_paren)); - } - doc.append(wrap_body(body)).append(Doc::text(")")).group() -} - -fn build_like_clause<'a>(like_clause: &ast::LikeClause) -> Doc<'a> { - let mut doc = Doc::text("like"); - - if let Some(relation_name) = like_clause.relation_name_ref() { + if let Some(as_token) = namespace.as_token() { doc = doc .append(Doc::space()) - .append(leading_comments(relation_name.syntax())); - if let Some(path) = relation_name.path_ref() { - doc = doc.append(build_path_ref(&path)); - } + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); } - - let options: Vec> = like_clause - .like_options() - .map(|option| { - Doc::line_or_space() - .append(leading_comments(option.syntax())) - .append(build_like_option(&option)) - }) - .collect(); - if !options.is_empty() { - doc = doc.append(Doc::list(options).nest(2).group()); + if let Some(prefix) = namespace.prefix() { + doc = doc + .append(Doc::space()) + .append(leading_comments(prefix.syntax())) + .append(build_name(prefix.syntax())); } - doc } -fn build_like_option<'a>(option: &ast::LikeOption) -> Doc<'a> { - let (keyword, property) = match option { - ast::LikeOption::ExcludingProperty(n) => ("excluding", n.table_property()), - ast::LikeOption::IncludingProperty(n) => ("including", n.table_property()), - }; - - let mut doc = Doc::text(keyword); - if let Some(property) = property { +fn build_xml_row_passing_clause<'a>(clause: ast::XmlRowPassingClause) -> Doc<'a> { + let mut doc = clause.row().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(passing) = clause.passing_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&passing)) + .append(Doc::text("passing")); + } + if let Some(mech) = clause.xml_passing_mech() { doc = doc .append(Doc::space()) - .append(leading_comments(property.syntax())) - .append(build_keyword_node(property.syntax())); + .append(leading_comments(mech.syntax())) + .append(build_xml_passing_mech(mech)); + } + doc = doc.group(); + if let Some(passing_doc) = clause.xml_passing_doc() { + let mut passing_doc_doc = leading_comments(passing_doc.syntax()); + if let Some(expr) = passing_doc.expr() { + passing_doc_doc = passing_doc_doc + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(mech) = passing_doc.xml_passing_mech() { + passing_doc_doc = passing_doc_doc + .append(Doc::space()) + .append(leading_comments(mech.syntax())) + .append(build_xml_passing_mech(mech)); + } + doc = doc.append(Doc::line_or_space().append(passing_doc_doc).nest(2)); } - doc + doc.group() } -fn build_select_doc<'a>(select: &ast::Select) -> Doc<'a> { - build_select_doc_ungrouped(select).group() +fn build_xml_table_column_list<'a>(list: ast::XmlTableColumnList) -> Doc<'a> { + let mut doc = Doc::text("columns"); + let columns = list.xml_table_columns().map(|column| { + ( + leading_comments(column.syntax()).append(build_xml_table_column(column.clone())), + column.syntax().clone(), + ) + }); + if let Some(columns) = build_comma_separated_docs(columns) { + doc = doc.append(Doc::hard_line().append(columns).nest(2)); + } + doc } -fn build_select_doc_ungrouped<'a>(select: &ast::Select) -> Doc<'a> { - let mut doc = Doc::text("select").append(Doc::line_or_space()); - - if let Some(select_clause) = select.select_clause() { - match select_clause.select_quantifier() { - Some(ast::SelectQuantifier::DistinctClause(distinct_clause)) => { - doc = doc.append(leading_comments(distinct_clause.syntax())); - doc = doc.append(Doc::text("distinct")).append(Doc::space()); - } - Some(ast::SelectQuantifier::All(all)) => { - doc = doc.append(leading_comments(all.syntax())); - doc = doc.append(Doc::text("all")).append(Doc::space()); - } - None => (), +fn build_xml_table_column<'a>(column: ast::XmlTableColumn) -> Doc<'a> { + let mut doc = column + .column_name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(ty) = column.ty() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } else { + if let Some(for_token) = column.for_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&for_token)) + .append(Doc::text("for")); } - if let Some(target_list) = select_clause.target_list() { - doc = doc.append(leading_comments(target_list.syntax())); + if let Some(ordinality) = column.ordinality_token() { doc = doc - .append(Doc::list( - Itertools::intersperse( - target_list.targets().flat_map(build_target), - Doc::text(",").append(Doc::line_or_space()), - ) - .collect(), - )) - .nest(2); + .append(Doc::space()) + .append(leading_comments_token(&ordinality)) + .append(Doc::text("ordinality")); } } - if select.from_clause().is_some() { - doc = doc.group(); - } - - if let Some(from) = select.from_clause() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(from.syntax())) - .append(build_from_clause(from)), - ); - } - - if let Some(group) = &select.group_by_clause() { - let mut group_doc = Doc::line_or_space().append(leading_comments(group.syntax())); - group_doc = group_doc.append(Doc::text("group")).append(Doc::space()); - if let Some(by_token) = group.by_token() { - group_doc = group_doc.append(leading_comments_token(&by_token)); - } - group_doc = group_doc.append(Doc::text("by")).append(Doc::space()); - if let Some(quantifier) = group.all_or_distinct() { - group_doc = group_doc - .append(leading_comments(quantifier.syntax())) - .append(match quantifier { - ast::AllOrDistinct::All(_) => Doc::text("all"), - ast::AllOrDistinct::Distinct(_) => Doc::text("distinct"), - }) - .append(Doc::space()); - } - if let Some(list) = group.group_by_list() { - group_doc = group_doc.append(build_group_by_list(list)); + if let Some(options) = column.xml_column_option_list() { + let mut first = true; + for option in options.xml_column_options() { + doc = doc.append(Doc::line_or_space()); + if first { + doc = doc.append(leading_comments(options.syntax())); + first = false; + } + doc = doc + .append(leading_comments(option.syntax())) + .append(build_xml_column_option(option)); } - doc = doc.append(group_doc); } - - doc = doc.append(build_semicolon(select.semicolon_token())); - - doc + doc.group() } -fn build_from_clause<'a>(from: ast::FromClause) -> Doc<'a> { - if from.join_exprs().next().is_some() { - todo!("joins are not supported yet") - } - - let from_items: Vec<_> = from - .from_items() - .map(|item| { - let leading = leading_comments(item.syntax()); - let trailing = trailing_comments(item.syntax()); - leading.append(build_from_item(item)).append(trailing) - }) - .collect(); - - Doc::text("from").append(Doc::space()).append( - Doc::list( - Itertools::intersperse( - from_items.into_iter(), - Doc::text(",").append(Doc::line_or_space()), +fn build_xml_column_option<'a>(option: ast::XmlColumnOption) -> Doc<'a> { + match option { + ast::XmlColumnOption::OptionDefault(option) => { + Doc::text("default").append(Doc::space()).append( + option + .expr() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil), ) - .collect(), - ) - .nest(2), - ) -} - -fn build_from_item<'a>(item: ast::FromItem) -> Doc<'a> { - match item { - ast::FromItem::RelationFromItem(relation) => build_relation_from_item(relation), - ast::FromItem::FunctionFromItem(_) => { - todo!("function from items are not supported yet") } - ast::FromItem::ExprFromItem(_) => todo!("expression from items are not supported yet"), - ast::FromItem::ParenFromItem(_) => { - todo!("parenthesized from items are not supported yet") - } - ast::FromItem::RowsFromItem(_) => todo!("rows from items are not supported yet"), - ast::FromItem::GraphTableFromItem(graph_table) => build_graph_table_from_item(graph_table), - ast::FromItem::JsonTableFromItem(_) => { - todo!("json_table from items are not supported yet") + ast::XmlColumnOption::OptionIdent(option) => { + let mut doc = build_name(option.syntax()); + if let Some(expr) = option.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc } - ast::FromItem::XmlTableFromItem(_) => { - todo!("xmltable from items are not supported yet") + ast::XmlColumnOption::OptionNotNull(option) => { + build_two_keywords(option.not_token(), "not", option.null_token(), "null") } + ast::XmlColumnOption::OptionNull(_) => Doc::text("null"), + ast::XmlColumnOption::OptionPath(option) => Doc::text("path").append(Doc::space()).append( + option + .expr() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil), + ), } } @@ -1355,7 +10535,7 @@ fn build_index_expr<'a>(index_expr: ast::IndexExpr) -> Doc<'a> { body = body .append(leading_comments(index.syntax())) .append(match index { - ast::Expr::BinExpr(binary) => build_bin_expr_doc(binary, false), + ast::Expr::BinExpr(binary) => build_bin_expr(binary), expression => build_expr(expression), }); } @@ -1811,12 +10991,24 @@ fn build_where_clause<'a>(where_clause: ast::WhereClause) -> Doc<'a> { .map(|token| leading_comments_token(&token).append(Doc::text("where"))) .unwrap_or_else(Doc::nil); if let Some(expr) = where_clause.expr() { - doc = doc - .append(Doc::space()) - .append(leading_comments(expr.syntax())) - .append(build_expr(expr)); + let expr_doc = match expr.clone() { + ast::Expr::BinExpr(bin_expr) => { + if let Some(logical) = bin_expr.op().as_ref().and_then(logical_op) { + build_logical_expr(bin_expr, logical) + } else { + build_expr(expr.clone()) + } + } + _ => build_expr(expr.clone()), + }; + doc = doc.append( + Doc::line_or_space() + .append(leading_comments(expr.syntax())) + .append(expr_doc) + .nest(2), + ); } - doc + doc.group() } fn build_paren_graph_pattern<'a>(pattern: ast::ParenGraphPattern) -> Doc<'a> { @@ -2992,10 +12184,7 @@ fn build_json_expr_format<'a>(value: ast::JsonExprFormat) -> Doc<'a> { fn build_json_select_format<'a>(select: ast::JsonSelectFormat) -> Doc<'a> { let mut doc = select .select_variant() - .map(|select| match select { - ast::SelectVariant::Select(select) => build_select_doc(&select), - _ => todo!("this select variant is not supported yet"), - }) + .map(build_select_variant) .unwrap_or_else(Doc::nil); if let Some(format) = select.json_format_clause() { doc = doc @@ -3470,7 +12659,7 @@ fn build_parenthesized_expr_or_select_fn<'a>( .append(leading_comments(select.syntax())) .append(match select { ast::SelectVariant::Select(select) => build_select_doc_ungrouped(&select), - _ => todo!("this select variant is not supported yet"), + select => build_select_variant(select), }); } @@ -3561,14 +12750,12 @@ fn build_order_by_clause<'a>(clause: ast::OrderByClause) -> Doc<'a> { let items = list .sort_bys() .map(|sort_by| leading_comments(sort_by.syntax()).append(build_sort_by(sort_by))); - doc = doc - .append(Doc::space()) - .append(leading_comments(list.syntax())) - .append(Doc::list( - Itertools::intersperse(items, Doc::text(",").append(Doc::space())).collect(), - )); + let body = leading_comments(list.syntax()).append(Doc::list( + Itertools::intersperse(items, Doc::text(",").append(Doc::line_or_space())).collect(), + )); + doc = doc.append(Doc::line_or_space().append(body).nest(2)); } - doc + doc.group() } fn build_sort_by<'a>(sort_by: ast::SortBy) -> Doc<'a> { @@ -3829,21 +13016,33 @@ fn build_paren_expr<'a>(paren_expr: ast::ParenExpr) -> Doc<'a> { body = body .append(leading_comments(expr.syntax())) .append(match expr { - ast::Expr::BinExpr(binary) => build_bin_expr_doc(binary, false), + ast::Expr::BinExpr(binary) => build_bin_expr(binary), expression => build_expr(expression), }); - } else if let Some(_compound_select) = paren_expr.compound_select() { - todo!("parenthesized compound select nodes are not supported yet") - } else if let Some(_from_item) = paren_expr.from_item() { - todo!("parenthesized from item nodes are not supported yet") - } else if let Some(_join_expr) = paren_expr.join_expr() { - todo!("parenthesized join expression nodes are not supported yet") - } else if let Some(_select) = paren_expr.select() { - todo!("parenthesized select nodes are not supported yet") - } else if let Some(_table) = paren_expr.table() { - todo!("parenthesized table nodes are not supported yet") - } else if let Some(_values) = paren_expr.values() { - todo!("parenthesized values nodes are not supported yet") + } else if let Some(compound_select) = paren_expr.compound_select() { + body = body + .append(leading_comments(compound_select.syntax())) + .append(build_compound_select(&compound_select)); + } else if let Some(from_item) = paren_expr.from_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)); + } else if let Some(select) = paren_expr.select() { + body = body + .append(leading_comments(select.syntax())) + .append(build_select_doc(&select)); + } else if let Some(table) = paren_expr.table() { + body = body + .append(leading_comments(table.syntax())) + .append(build_table(&table)); + } else if let Some(values) = paren_expr.values() { + body = body + .append(leading_comments(values.syntax())) + .append(build_values(&values)); } else { unreachable!("a parenthesized expression should contain a node") } @@ -3857,76 +13056,198 @@ 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() { - ast::PostfixOp::AtLocal(_) => Doc::text("at local"), + ast::PostfixOp::AtLocal(n) => { + build_two_keywords(n.at_token(), "at", n.local_token(), "local") + } ast::PostfixOp::IsNull(_) => Doc::text("isnull"), ast::PostfixOp::NotNull(_) => Doc::text("notnull"), - ast::PostfixOp::IsJson(n) => build_json_postfix("is json", n.json_keys_unique_clause()), - ast::PostfixOp::IsJsonArray(n) => { - build_json_postfix("is json array", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsJsonObject(n) => { - build_json_postfix("is json object", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsJsonScalar(n) => { - build_json_postfix("is json scalar", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsJsonValue(n) => { - build_json_postfix("is json value", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNormalized(n) => build_normalized_postfix("is", n.unicode_normal_form()), - ast::PostfixOp::IsNotJson(n) => { - build_json_postfix("is not json", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNotJsonArray(n) => { - build_json_postfix("is not json array", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNotJsonObject(n) => { - build_json_postfix("is not json object", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNotJsonScalar(n) => { - build_json_postfix("is not json scalar", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNotJsonValue(n) => { - build_json_postfix("is not json value", n.json_keys_unique_clause()) - } - ast::PostfixOp::IsNotNormalized(n) => { - build_normalized_postfix("is not", n.unicode_normal_form()) - } + ast::PostfixOp::IsJson(n) => build_json_postfix( + [(n.is_token(), "is"), (n.json_token(), "json")], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsJsonArray(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.json_token(), "json"), + (n.array_token(), "array"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsJsonObject(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.json_token(), "json"), + (n.object_token(), "object"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsJsonScalar(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.json_token(), "json"), + (n.scalar_token(), "scalar"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsJsonValue(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.json_token(), "json"), + (n.value_token(), "value"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNormalized(n) => build_normalized_postfix( + [(n.is_token(), "is")], + n.unicode_normal_form(), + n.normalized_token(), + ), + ast::PostfixOp::IsNotJson(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.not_token(), "not"), + (n.json_token(), "json"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNotJsonArray(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.not_token(), "not"), + (n.json_token(), "json"), + (n.array_token(), "array"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNotJsonObject(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.not_token(), "not"), + (n.json_token(), "json"), + (n.object_token(), "object"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNotJsonScalar(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.not_token(), "not"), + (n.json_token(), "json"), + (n.scalar_token(), "scalar"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNotJsonValue(n) => build_json_postfix( + [ + (n.is_token(), "is"), + (n.not_token(), "not"), + (n.json_token(), "json"), + (n.value_token(), "value"), + ], + n.json_keys_unique_clause(), + ), + ast::PostfixOp::IsNotNormalized(n) => build_normalized_postfix( + [(n.is_token(), "is"), (n.not_token(), "not")], + n.unicode_normal_form(), + n.normalized_token(), + ), }; expr.append(Doc::space()).append(op) } +fn build_postfix_keywords<'a>( + keywords: impl IntoIterator, &'static str)>, +) -> Doc<'a> { + let mut doc = Doc::nil(); + let mut has_keyword = false; + for (token, text) in keywords { + let Some(token) = token else { + continue; + }; + if has_keyword { + doc = doc.append(Doc::space()); + } + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text(text)); + has_keyword = true; + } + doc +} + fn build_json_postfix<'a>( - prefix: &'static str, + keywords: impl IntoIterator, &'static str)>, clause: Option, ) -> Doc<'a> { - let mut doc = Doc::text(prefix); + let mut doc = build_postfix_keywords(keywords); if let Some(clause) = clause { doc = doc .append(Doc::space()) + .append(leading_comments(clause.syntax())) .append(build_json_keys_unique_clause(clause)); } doc } fn build_normalized_postfix<'a>( - prefix: &'static str, + keywords: impl IntoIterator, &'static str)>, form: Option, + normalized_token: Option, ) -> Doc<'a> { - let mut doc = Doc::text(prefix); + let mut doc = build_postfix_keywords(keywords); if let Some(form) = form { doc = doc .append(Doc::space()) + .append(leading_comments(form.syntax())) .append(build_unicode_normal_form(form)); } - doc.append(Doc::space()).append(Doc::text("normalized")) + append_keyword_token(doc, normalized_token, "normalized") } -fn build_bin_expr<'a>(bin_expr: ast::BinExpr) -> Doc<'a> { - build_bin_expr_doc(bin_expr, true) +#[derive(Clone, Copy, PartialEq)] +enum LogicalOp { + And, + Or, +} + +fn logical_op(op: &ast::BinOp) -> Option { + match op { + ast::BinOp::And(_) => Some(LogicalOp::And), + ast::BinOp::Or(_) => Some(LogicalOp::Or), + _ => None, + } +} + +fn build_logical_expr<'a>(bin_expr: ast::BinExpr, logical: LogicalOp) -> Doc<'a> { + let lhs = bin_expr.lhs().unwrap(); + let rhs = bin_expr.rhs().unwrap(); + let lhs_doc = match lhs.clone() { + ast::Expr::BinExpr(inner) if inner.op().as_ref().and_then(logical_op) == Some(logical) => { + build_logical_expr(inner, logical) + } + lhs => build_expr(lhs), + }; + let rhs_doc = match rhs.clone() { + ast::Expr::BinExpr(inner) if inner.op().as_ref().and_then(logical_op) == Some(logical) => { + build_logical_expr(inner, logical) + } + rhs => build_expr(rhs), + }; + + lhs_doc + .append(trailing_comments(lhs.syntax())) + .append(Doc::line_or_space()) + .append(build_op(bin_expr.op().unwrap())) + .append(Doc::space()) + .append(leading_comments(rhs.syntax())) + .append(rhs_doc) } -fn build_bin_expr_doc<'a>(bin_expr: ast::BinExpr, wrap: bool) -> Doc<'a> { +fn build_bin_expr<'a>(bin_expr: ast::BinExpr) -> Doc<'a> { + if let Some(logical) = bin_expr.op().as_ref().and_then(logical_op) { + return build_logical_expr(bin_expr, logical).nest(2).group(); + } + let lhs = bin_expr.lhs().unwrap(); let rhs = bin_expr.rhs().unwrap(); let before_op = trailing_comments(lhs.syntax()); @@ -3950,7 +13271,7 @@ fn build_bin_expr_doc<'a>(bin_expr: ast::BinExpr, wrap: bool) -> Doc<'a> { .append(Doc::space()) .append(after_op) .append(build_expr(rhs)); - if rhs_is_uncommented_quantifier || !wrap { + if rhs_is_uncommented_quantifier { doc } else { doc.nest(2).group() @@ -4426,6 +13747,8 @@ fn build_literal<'a>(lit: ast::Literal) -> Doc<'a> { LitKind::IntNumber(t) => Doc::text(t.text().to_string()), LitKind::Null(_) => Doc::text("null"), LitKind::NumericNumber(t) => Doc::text(t.text().to_string()), + LitKind::Off(_) => Doc::text("off"), + LitKind::On(_) => Doc::text("on"), LitKind::PositionalParam(t) => Doc::text(t.text().to_string()), LitKind::True(_) => Doc::text("true"), LitKind::BitString(_) diff --git a/crates/squawk_fmt/tests/after/alter_publication.snap b/crates/squawk_fmt/tests/after/alter_publication.snap new file mode 100644 index 00000000..37d33102 --- /dev/null +++ b/crates/squawk_fmt/tests/after/alter_publication.snap @@ -0,0 +1,25 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/alter_publication.sql +--- +alter publication selected_tables +add table public.new_accounts, tables in schema archive; + +alter publication selected_tables drop table public.old_accounts; + +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); + +alter publication everything set (publish = 'insert, update'); + +alter publication everything owner to replication_admin; + +alter publication everything rename to all_changes; + +alter /* after alter */ publication /* before name */ commented_pub +set + /* before object */ table /* before table name */ public.commented, + /* after comma */ current_schema/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/alter_subscription.snap b/crates/squawk_fmt/tests/after/alter_subscription.snap new file mode 100644 index 00000000..a73efe5d --- /dev/null +++ b/crates/squawk_fmt/tests/after/alter_subscription.snap @@ -0,0 +1,35 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/alter_subscription.sql +--- +alter subscription local_sub connection 'host=otherhost dbname=publisher'; + +alter subscription local_sub server publisher_server; + +alter subscription local_sub +set (slot_name = new_slot, synchronous_commit = local); + +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); + +alter subscription local_sub +drop publication selected_tables with (refresh = false); + +alter subscription local_sub refresh publication with (copy_data = true); + +alter subscription local_sub enable; + +alter subscription local_sub disable; + +alter subscription local_sub skip (lsn = '0/16B6C50'); + +alter subscription local_sub owner to replication_admin; + +alter subscription local_sub rename to renamed_sub; + +alter /* after alter */ subscription /* after subscription */ renamed_sub +add /* after add */ publication /* before publication name */ commented_pub, + /* after publication comma */ selected_tables +with /* before params */ (copy_data = true)/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/analyze.snap b/crates/squawk_fmt/tests/after/analyze.snap new file mode 100644 index 00000000..ab1dc2d2 --- /dev/null +++ b/crates/squawk_fmt/tests/after/analyze.snap @@ -0,0 +1,30 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/analyze.sql +--- +analyze; + +analyse verbose records; + +analyze public.records (id, payload), public.archived_records; + +analyze ( + verbose true, skip_locked false, buffer_usage_limit '4MB' +) + public.records; + +analyze + an_intentionally_long_schema_name.an_intentionally_long_table_name_that_makes_this_statement_exceed_eighty_characters ( + an_intentionally_long_column_name + ); + +/* before analyze */ +analyze /* before options */ ( + /* before verbose */ verbose /* before true */ true /* before comma */, + /* before skip locked */ skip_locked /* before false */ false /* before close */ +) + /* before table */ public /* before dot */./* after dot */ records /* before columns */ ( + /* before id */ id /* before column comma */, + /* before payload */ payload /* before columns close */ + ) /* before table comma */, + /* before second table */ archived_records/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/call.snap b/crates/squawk_fmt/tests/after/call.snap new file mode 100644 index 00000000..2cc0f876 --- /dev/null +++ b/crates/squawk_fmt/tests/after/call.snap @@ -0,0 +1,22 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/call.sql +--- +call refresh_materialized_data(); + +call public.process_record(1, 'record', enabled => true); + +call process_a_record_with_an_intentionally_long_procedure_name( + an_intentionally_long_argument_name => 'an intentionally long argument value', + another_intentionally_long_argument_name => 12345 +); + +/* before */ +call /* after call */ public /* before dot */./* after dot */ process_record /* before left paren */( + /* after left paren */ 1 /* before comma */, + /* after comma */ argument_name /* before arrow */ => /* after arrow */ 'value' /* before second comma */, + /* after second comma */ variadic /* after variadic */ array[ + 1, + 2 + ] /* before right paren */ +)/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/checkpoint.snap b/crates/squawk_fmt/tests/after/checkpoint.snap new file mode 100644 index 00000000..862bff79 --- /dev/null +++ b/crates/squawk_fmt/tests/after/checkpoint.snap @@ -0,0 +1,24 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/checkpoint.sql +--- +checkpoint; + +checkpoint (mode fast); + +checkpoint (mode spread, flush_unlogged true); + +checkpoint (flush_unlogged false); + +checkpoint (flush_unlogged); + +checkpoint ( + an_intentionally_long_checkpoint_option_name an_intentionally_long_value_name, + another_intentionally_long_checkpoint_option_name another_intentionally_long_value_name +); + +/* before checkpoint */ +checkpoint /* before left paren */ ( + /* after left paren */ mode /* before value */ fast /* before comma */, + /* after comma */ flush_unlogged /* before second value */ false /* before right paren */ +)/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/close.snap b/crates/squawk_fmt/tests/after/close.snap new file mode 100644 index 00000000..59cd43db --- /dev/null +++ b/crates/squawk_fmt/tests/after/close.snap @@ -0,0 +1,17 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/close.sql +--- +close all; + +close active_cursor; + +close "Case-Sensitive Cursor"; + +close + cursor_with_an_intentionally_long_name_that_makes_this_close_statement_longer_than_eighty_characters; + +/* before */ +close /* after close */ all/* before semicolon */; + +close /* before cursor */ cursor_name/* before cursor semicolon */; diff --git a/crates/squawk_fmt/tests/after/cluster.snap b/crates/squawk_fmt/tests/after/cluster.snap new file mode 100644 index 00000000..028a6afb --- /dev/null +++ b/crates/squawk_fmt/tests/after/cluster.snap @@ -0,0 +1,31 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/cluster.sql +--- +cluster; + +cluster verbose; + +cluster records; + +cluster verbose public.records using records_created_at_idx; + +cluster records_created_at_idx on public.records; + +cluster + (verbose true, analyze false) + a_very_long_schema_name.an_intentionally_long_table_name +using a_very_long_schema_name.an_intentionally_long_index_name; + +/* before */ +cluster + /* after cluster */ ( + /* after left paren */ verbose /* before comma */, + /* after comma */ analyze /* before value */ true /* before right paren */ + ) + /* before table */ public /* before table dot */./* after table dot */ records +/* before using */ using /* after using */ public /* before index dot */./* after index dot */ records_idx/* before semicolon */; + +cluster + /* before legacy index */ public /* before legacy index dot */./* after legacy index dot */ records_idx + /* before on */ on /* after on */ public /* before legacy table dot */./* after legacy table dot */ records/* before legacy semicolon */; diff --git a/crates/squawk_fmt/tests/after/compound_select.snap b/crates/squawk_fmt/tests/after/compound_select.snap new file mode 100644 index 00000000..60fda801 --- /dev/null +++ b/crates/squawk_fmt/tests/after/compound_select.snap @@ -0,0 +1,53 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/compound_select.sql +--- +select 1 union select 2; + +select 1 union all select 2 intersect distinct select 3 except select 4; + +(select 1) +except +(select 2) +order by 1; + +(select 1); + +select 1 +union +select 2 +order by 1 +for update +limit 10 +offset 2 rows; + +select 1 +union +select 2 +fetch first 5 rows with ties; + +select 1 +union +select 2 +for no key update of foo, bar skip locked; + +table foo union values (1), (2); + +select + /* after select */ a_very_long_first_column_name, + a_very_long_second_column_name +from a_very_long_first_table_name +/* before union */ union /* before all */ all +/* before rhs */ select + /* rhs select */ a_very_long_first_column_name, + a_very_long_second_column_name +from a_very_long_second_table_name/* before semicolon */; + +select 1 +/* before operator */ union /* before quantifier */ distinct +/* before right select */ select 2; +select 1 +-- before operator +union all +-- before right select +select 2; diff --git a/crates/squawk_fmt/tests/after/copy.snap b/crates/squawk_fmt/tests/after/copy.snap new file mode 100644 index 00000000..80ee8d87 --- /dev/null +++ b/crates/squawk_fmt/tests/after/copy.snap @@ -0,0 +1,55 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/copy.sql +--- +copy foo from '/tmp/foo.csv'; + +copy foo (id, name) +to stdout +with (format csv, header true, delimiter ',', null '', encoding 'UTF8'); + +copy ( + select id, a_very_long_column_name, another_very_long_column_name + from a_very_long_schema_name.a_very_long_table_name +) +to program 'gzip > /tmp/a_very_long_output_file_name.csv' +with (format csv, header on); + +copy binary foo +from stdin +binary +freeze +csv +header +json +delimiter as ',' +null as '' +quote as '"' +escape as '\\' +encoding 'UTF8' +force not null id, name +force quote * +force null description +where id > 0; + +/* before */ +copy /* after copy */ binary /* after binary */ public /* before dot */./* after dot */ records /* before columns */ ( + /* after left paren */ id /* before comma */, + /* after comma */ description /* before right paren */ +) +/* before from */ from /* after from */ program /* after program */ 'cat /tmp/records' +/* before with */ with /* before options */ ( + /* after options left paren */ format /* before format value */ csv /* before option comma */, + /* after option comma */ header /* before header value */ on /* before second option comma */, + /* after second option comma */ force_null /* before nested options */ ( + /* after nested left paren */ id /* before nested comma */, + /* after nested comma */ description /* before nested right paren */ + ) /* before options right paren */ +) +/* before where */ where /* after where */ id > 0/* before semicolon */; + +copy ( + /* after query left paren */ select /* after select */ id + from records /* before query right paren */ +) +to /* before stdout */ stdout; diff --git a/crates/squawk_fmt/tests/after/create_foreign_table.snap b/crates/squawk_fmt/tests/after/create_foreign_table.snap new file mode 100644 index 00000000..1511c878 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_foreign_table.snap @@ -0,0 +1,53 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_foreign_table.sql +--- +create foreign table t () server s; + +create foreign table if not exists public.remote_records ( + id bigint not null, + name text, + constraint remote_records_pkey primary key (id) +) + inherits (public.base_records) + server foreign_server + options ( + schema_name 'public', + table_name 'records' + ); + +create foreign table partition_records + partition of public.records ( + id with options not null, + name + ) default + server foreign_server; + +create foreign table ranged_records + partition of public.records (id) + for values from (1) to (100) + server foreign_server + options ( + table_name 'ranged_records' + ); + +create foreign table an_intentionally_long_schema_name.an_intentionally_long_foreign_table_name ( + an_intentionally_long_column_name character varying, + another_intentionally_long_column_name timestamp with time zone +) + server an_intentionally_long_foreign_server_name + options ( + schema_name 'an_intentionally_long_schema_name', + table_name 'an_intentionally_long_foreign_table_name' + ); + +/* before create */ +create /* before foreign */ foreign /* before table */ table /* before if */ if /* before not */ not /* before exists */ exists /* before table name */ public /* before dot */./* after dot */ remote_records/* before left paren */ ( + /* after left paren */ id /* before type */ bigint /* before comma */, + /* after comma */ name /* before second type */ text /* before right paren */ +) + /* before server */ server /* before server name */ foreign_server + /* before options */ options /* before options left paren */( + /* after options left paren */ schema_name /* before option value */ 'public' /* before option comma */, + /* after option comma */ table_name /* before second option value */ 'records' /* before options right paren */ + )/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/create_function.snap b/crates/squawk_fmt/tests/after/create_function.snap new file mode 100644 index 00000000..c64a9f7e --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_function.snap @@ -0,0 +1,118 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_function.sql +--- +create function add(integer, integer) returns integer + language sql + as $$ select $1 + $2 $$; + +create or replace function public.add( + a integer, + b integer default 1 +) returns integer + language sql + immutable + strict + parallel safe + cost 10 + rows 1 + security definer + set search_path to public + as $$ select a + b $$; + +create function get_users(p_active boolean) returns table (id bigint, name text) + language plpgsql + security definer + set search_path = public, pg_temp + as $body$ begin return query select id, name from users where active = p_active; end; $body$; + +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 $$; + +-- 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*/, + /*m*/ out /*n*/ result /*o*/ text /*p*/ +) /*q*/ returns /*r*/ table /*s*/ ( + /*t*/ id /*u*/ bigint /*v*/, + /*w*/ label /*x*/ text /*y*/ +) /*z*/ + language /*aa*/ sql + /*ab*/ immutable + /*ac*/ strict + /*ad*/ parallel /*ae*/ safe + /*af*/ cost /*ag*/ 10 + /*ah*/ rows /*ai*/ 1 + /*aj*/ security /*ak*/ definer + /*al*/ set /*am*/ search_path /*an*/ to /*ao*/ public /*ap*/, /*aq*/ pg_temp + /*ar*/ /*as*/ reset /*at*/ all + /*au*/ support /*av*/ public /*aw*/./*ax*/ support_fn + /*ay*/ as /*az*/ $$ select value::text $$/*ba*/; + +create function increment(value integer) returns integer + language sql + begin atomic + return value + 1; + end; + +create function record_and_calculate( + a_very_long_input_parameter_name integer, + another_very_long_input_parameter_name integer +) returns integer + language sql + begin atomic + insert into function_audit_log (first_recorded_value, second_recorded_value) + values ( + a_very_long_input_parameter_name, another_very_long_input_parameter_name + ); + return a_very_long_input_parameter_name + + another_very_long_input_parameter_name; + end; + +create function commented_body(value integer) returns integer + language sql + /*bb*/ begin /*bc*/ atomic + /*bd*/ insert /*be*/ into function_log /*bf*/ (value) + /*bg*/ values /*bh*/ (value)/*bi*/; + /*bj*/ return /*bk*/ value + 1/*bl*/; + /*bm*/ end/*bn*/; + +create function external_add(integer, integer) returns integer + as '$libdir/example', 'external_add' + language c; + +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/after/create_index.snap b/crates/squawk_fmt/tests/after/create_index.snap new file mode 100644 index 00000000..6c7cce11 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_index.snap @@ -0,0 +1,50 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_index.sql +--- +create index users_email_idx on users (email); + +create unique index concurrently if not exists idx_users_email + on only public.users + using btree ( + email collate public."C" text_pattern_ops desc nulls last, + (lower(display_name)) asc + ) + include (id, created_at) + nulls not distinct + with (fillfactor = 70, deduplicate_items = off) + tablespace fastspace + where active and email is not null; + +create index reservations_during_idx + on reservations using gist (during); + +create index documents_search_idx + on documents using gin (search_vector); + +create index long_index_name_for_testing_line_wrapping + on long_schema_name.a_very_long_table_name + using btree ( + a_very_long_column_name, + another_very_long_column_name, + a_third_very_long_column_name + ) + include (a_very_long_included_column_name) + nulls distinct + where a_very_long_column_name is not null; + +-- comments in every position +create /*a*/ unique /*b*/ index /*c*/ concurrently /*d*/ if /*e*/ not /*f*/ exists /*g*/ idx + /*h*/ on /*i*/ only /*j*/ app /*k*/./*l*/ users + /*m*/ using /*n*/ btree /*o*/ ( + /*p*/ email /*q*/ collate /*r*/ "C" /*s*/ text_pattern_ops /*t*/ desc /*u*/ nulls /*v*/ last /*w*/, + /*x*/ (lower(name)) /*y*/ asc /*z*/ + ) + /*aa*/ include /*ab*/ (/*ac*/ id /*ad*/, /*ae*/ created_at /*af*/) + /*ag*/ nulls /*ah*/ not /*ai*/ distinct + /*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*/; diff --git a/crates/squawk_fmt/tests/after/create_publication.snap b/crates/squawk_fmt/tests/after/create_publication.snap new file mode 100644 index 00000000..40a46467 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_publication.snap @@ -0,0 +1,33 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_publication.sql +--- +create publication everything +for all tables, all sequences except (table audit.secret_events, internal_jobs) +with ( + publish = 'insert, update, delete, truncate', + publish_via_partition_root = true +); + +create publication selected_tables +for + table only public.accounts (id, email) where (id > 100), + table (public.orders)*, + tables in schema reporting where (tenant_id = 42), + current_schema; + +create publication no_tables with (publish = 'insert'); + +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/after/create_subscription.snap b/crates/squawk_fmt/tests/after/create_subscription.snap new file mode 100644 index 00000000..21628651 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_subscription.snap @@ -0,0 +1,17 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_subscription.sql +--- +create subscription local_sub +connection 'host=localhost port=5432 dbname=publisher user=replicator password=very_long_password' +publication all_changes, + selected_tables +with (copy_data = true, enabled = false, streaming = parallel); + +create subscription server_sub server publisher_server publication all_changes; + +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/after/create_table.snap b/crates/squawk_fmt/tests/after/create_table.snap index 6414cfd6..24c7addc 100644 --- a/crates/squawk_fmt/tests/after/create_table.snap +++ b/crates/squawk_fmt/tests/after/create_table.snap @@ -1,5 +1,6 @@ --- source: crates/squawk_fmt/tests/tests.rs +assertion_line: 28 input_file: crates/squawk_fmt/tests/before/create_table.sql --- create table u (); @@ -75,3 +76,62 @@ create table a_very_long_schema_name.a_very_long_table_name ( a_very_long_qualified_type_column_name a_very_long_type_schema_name.a_very_long_type_name, U&"c!006fl" uescape '!' a_very_long_type_schema_name.a_very_long_type_name ); + +-- column options and constraints +create table column_features ( + payload text + storage external + compression lz4 + with options + options ( + formatter 'plain', + set compression 'fast', + drop obsolete + ) + collate public."C" + constraint payload_required not null + default 'missing' + check (length(payload) > 0) + unique, + nullable text null, + id bigint + generated by default as identity ( + increment by 2 + minvalue 1 + no maxvalue + start with 10 + cache 20 + cycle + ) + primary key, + computed bigint generated always as (id + 1) stored, + parent_id bigint + references public.parents(id) + match full + on delete cascade + on update restrict +); + +-- comments in every column option position +create table column_option_comments ( + /*a*/ payload /*b*/ text + /*c*/ storage /*d*/ external + /*e*/ compression /*f*/ lz4 + /*g*/ with /*h*/ options + /*i*/ options /*j*/( + /*k*/ add /*l*/ formatter /*m*/ 'x' /*n*/, + /*o*/ set /*p*/ formatter /*q*/ 'y' /*r*/, + /*s*/ drop /*t*/ formatter /*u*/ + ) + /*v*/ collate /*w*/ public /*x*/./*y*/ "C" + /*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*/ + ) +); diff --git a/crates/squawk_fmt/tests/after/create_table_as.snap b/crates/squawk_fmt/tests/after/create_table_as.snap new file mode 100644 index 00000000..75782efd --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_table_as.snap @@ -0,0 +1,40 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_table_as.sql +--- +create table active_users as + select id, name from users where active = true; + +create temporary table if not exists reporting.a_very_long_destination_table_name ( + a_very_long_identifier_column, + another_very_long_identifier_column +) + using heap + with (fillfactor = 70, autovacuum_enabled = true) + on commit preserve rows + tablespace fast + as + select a_very_long_identifier_column, another_very_long_identifier_column + from a_very_long_source_schema_name.a_very_long_source_table_name + with no data; + +create table cached_result as + execute refresh_cached_result(1, 'full') + with data; + +-- comments in every position +create /*a*/ temp /*b*/ table /*c*/ if /*d*/ not /*e*/ exists /*f*/ app /*g*/./*h*/ report /*i*/ ( + /*j*/ id /*k*/, + /*l*/ total /*m*/ +) + /*n*/ using /*o*/ heap + /*p*/ with /*q*/ (/*r*/ fillfactor /*s*/ = /*t*/ 70 /*u*/) + /*v*/ on /*w*/ commit /*x*/ preserve /*y*/ rows + /*z*/ tablespace /*aa*/ fast + /*ab*/ as + /*ac*/ select id, total from summaries + /*ad*/ with /*ae*/ no /*af*/ data/*ag*/; + +create table executed /*a*/ as + /*b*/ execute /*c*/ refresh_report /*d*/(/*e*/ 1 /*f*/, /*g*/ 'full' /*h*/) + /*i*/ with /*j*/ data/*k*/; diff --git a/crates/squawk_fmt/tests/after/create_table_options.snap b/crates/squawk_fmt/tests/after/create_table_options.snap new file mode 100644 index 00000000..658d408e --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_table_options.snap @@ -0,0 +1,77 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +assertion_line: 28 +input_file: crates/squawk_fmt/tests/before/create_table_options.sql +--- +create temporary table if not exists t (id int) + inherits (parent, archive.parent) + partition by hash (id) + using heap + with (fillfactor = 70) + on commit delete rows + tablespace fast; + +create global temporary table global_temp (id int) + on commit preserve rows; + +create local temp table local_temp (id int) + on commit drop; + +create unlogged table events (id int) + without oids; + +create table typed_table + of public.record_type; + +create table measurements + partition of events + for values from (minvalue, 1) to (maxvalue, 100); + +create table statuses + partition of events + for values in ( + 'new', + 'ready', + 'a status value which makes this create table statement longer than eighty characters' + ); + +create table hash_part + partition of events + for values with (modulus 4, remainder 0); + +create table default_part + partition of events default; + +create table partitioned ( + a_very_long_region_column_name text, + a_very_long_created_at_column_name timestamptz +) + partition by range ( + a_very_long_region_column_name collate "C", + a_very_long_created_at_column_name + ); + +-- comments in every clause position +create /*a*/ local /*b*/ temporary /*c*/ table /*d*/ if /*e*/ not /*f*/ exists /*g*/ commented ( + /*h*/ id int /*i*/ +) + /*j*/ inherits /*k*/(/*l*/ parent /*m*/, /*n*/ archive.parent /*o*/) + /*p*/ partition /*q*/ by /*r*/ hash /*s*/ (/*t*/ id /*u*/) + /*v*/ using /*w*/ heap + /*x*/ with /*y*/ (fillfactor /*z*/ = /*aa*/ 70 /*ab*/) + /*ac*/ on /*ad*/ commit /*ae*/ preserve /*af*/ rows + /*ag*/ tablespace /*ah*/ fast; + +create table child + /*a*/ partition /*b*/ of /*c*/ parent + /*d*/ for /*e*/ values /*f*/ from /*g*/ (/*h*/ minvalue /*i*/, /*j*/ 1 /*k*/) + /*l*/ to /*m*/ (/*n*/ maxvalue /*o*/, /*p*/ 100 /*q*/); +create table child_in + partition of parent + for /*a*/ values /*b*/ in /*c*/(/*d*/ 1 /*e*/, /*f*/ 2 /*g*/); +create table child_hash + partition of parent + for /*a*/ values /*b*/ with /*c*/( + /*d*/ modulus /*e*/ 4 /*f*/, + /*g*/ remainder /*h*/ 0 /*i*/ + ); diff --git a/crates/squawk_fmt/tests/after/create_transform.snap b/crates/squawk_fmt/tests/after/create_transform.snap new file mode 100644 index 00000000..18098594 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_transform.snap @@ -0,0 +1,37 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_transform.sql +--- +create transform for t language l ( + from sql with function t, + to sql with function g +); + +create or replace transform for foo.t(10231) language l ( + from sql with function bar.foo.f(a text), + to sql with function g +); + +create transform for + a_transform_type_with_a_very_long_name + language a_language_with_a_very_long_name ( + from sql with function + a_schema_with_a_long_name.a_function_with_a_very_long_name( + a_parameter_with_a_long_name text + ), + to sql with function another_function_with_a_very_long_name +); + +-- comments in every position +create /*a*/ or /*b*/ replace /*c*/ transform /*d*/ for + /*e*/ app /*f*/./*g*/ custom_type + /*h*/ language /*i*/ plpgsql /*j*/ ( + /*k*/ from /*l*/ sql /*m*/ with /*n*/ function + /*o*/ app /*p*/./*q*/ from_sql( + /*r*/ integer /*s*/ + ) /*t*/, + /*u*/ to /*v*/ sql /*w*/ with /*x*/ function + /*y*/ app /*z*/./*aa*/ to_sql( + /*ab*/ integer /*ac*/ + ) /*ad*/ +)/*ae*/; diff --git a/crates/squawk_fmt/tests/after/create_trigger.snap b/crates/squawk_fmt/tests/after/create_trigger.snap new file mode 100644 index 00000000..22702a3e --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_trigger.snap @@ -0,0 +1,63 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_trigger.sql +--- +create trigger update_foo_column + before insert + on core_recipe + for each row + execute procedure foo_update_trigger(); + +create or replace trigger buzz + instead of insert or delete + on foo.bar.buzz + referencing old table as foo new table as bar + for each statement + when (x > 10 and b is not null) + execute function x.y.z(1, 2, '3'); + +create constraint trigger t + after insert or delete + on f + from other_f + deferrable + initially deferred + for each row + execute function f(); + +create trigger bar + after update of a, b, c + on foo + referencing new table bar old table foo + for row + execute procedure foo('bar'); + +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' + ); + +-- 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/after/create_view.snap b/crates/squawk_fmt/tests/after/create_view.snap new file mode 100644 index 00000000..21768021 --- /dev/null +++ b/crates/squawk_fmt/tests/after/create_view.snap @@ -0,0 +1,44 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/create_view.sql +--- +create view active_users as + select id, name from users where active = true; + +create or replace temporary recursive view public.user_summary ( + user_id, + display_name +) + with (security_barrier = true, check_option = local) + as + select id, name from public.users + with local check option; + +create view a_very_long_schema_name.a_very_long_view_name ( + a_very_long_identifier_column, + another_very_long_identifier_column, + a_third_very_long_identifier_column +) as + select + a_very_long_identifier_column, + another_very_long_identifier_column, + a_third_very_long_identifier_column + from a_very_long_schema_name.a_very_long_table_name + with cascaded check option; + +-- comments in every position +create /*a*/ or /*b*/ replace /*c*/ temp /*d*/ recursive /*e*/ view /*f*/ app /*g*/./*h*/ dashboard /*i*/ ( + /*j*/ account_id /*k*/, + /*l*/ total /*m*/ +) + /*n*/ with /*o*/ ( + /*p*/ security_barrier /*q*/ = /*r*/ true /*s*/, + /*t*/ check_option /*u*/ = /*v*/ local /*w*/ + ) + /*x*/ as + /*y*/ select /*z*/ account_id, total from summaries + /*aa*/ with /*ab*/ cascaded /*ac*/ check /*ad*/ option/*ae*/; + +create view plain_check as + select 1 + with check option; diff --git a/crates/squawk_fmt/tests/after/deallocate.snap b/crates/squawk_fmt/tests/after/deallocate.snap new file mode 100644 index 00000000..2b368420 --- /dev/null +++ b/crates/squawk_fmt/tests/after/deallocate.snap @@ -0,0 +1,24 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/deallocate.sql +--- +deallocate all; + +deallocate prepare all; + +deallocate statement_name; + +deallocate prepare statement_name; + +deallocate "Case-Sensitive Statement"; + +deallocate + prepare + prepared_statement_with_an_intentionally_long_name_that_makes_the_statement_longer_than_eighty_characters; + +/* before deallocate */ +deallocate + /* before prepare */ prepare + /* before target */ statement_name/* before semicolon */; + +deallocate /* before all */ all/* before all semicolon */; diff --git a/crates/squawk_fmt/tests/after/declare.snap b/crates/squawk_fmt/tests/after/declare.snap new file mode 100644 index 00000000..c72c74cd --- /dev/null +++ b/crates/squawk_fmt/tests/after/declare.snap @@ -0,0 +1,38 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/declare.sql +--- +declare cursor_name cursor +for select * from t; + +declare c binary insensitive no scroll cursor without hold +for select 1; + +declare c binary asensitive scroll cursor with hold +for select 2; + +declare c cursor +for (values (1) union values (2)); + +declare + cursor_with_an_intentionally_long_name + binary + insensitive + no scroll + cursor + without hold +for select + an_intentionally_long_column_name, + another_intentionally_long_column_name +from an_intentionally_long_table_name; + +/* before declare */ +declare + /* before cursor name */ c + /* before binary */ binary + /* before sensitivity */ insensitive + /* before no */ no /* before scroll */ scroll + /* before cursor keyword */ cursor + /* before without */ without /* before hold */ hold +/* before for */ for /* before query */ select + /* before value */ 1/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/delete.snap b/crates/squawk_fmt/tests/after/delete.snap new file mode 100644 index 00000000..4d4a163a --- /dev/null +++ b/crates/squawk_fmt/tests/after/delete.snap @@ -0,0 +1,56 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/delete.sql +--- +delete from foo; + +delete from foo as f +using bar b, + another_extremely_long_table_name_with_many_characters baz +where f.id = b.id +returning f.id, f.name, f.created_at; + +delete from foo * f +where current of delete_cursor +returning with ( + old as the_extremely_long_previous_row_value, + new as the_extremely_long_updated_row_value +) the_extremely_long_previous_row_value.*; + +delete from only (foo) f; + +delete from foo for portion of valid_at from 1 to 2; + +delete from foo +for portion of valid_at from 1 to 2 +where + organization_id = 12345 + and status = 'inactive' + and archived_at is not null +returning id, valid_at; + +delete from foo for portion of valid_at (1 + 2); + +with doomed as ( + select id from foo +) +delete from foo +using doomed +where foo.id = doomed.id; + +with deleted as ( + delete from foo where id = 1 returning id +) +select * +from deleted; + +/*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/after/discard.snap b/crates/squawk_fmt/tests/after/discard.snap new file mode 100644 index 00000000..f4b061b8 --- /dev/null +++ b/crates/squawk_fmt/tests/after/discard.snap @@ -0,0 +1,17 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/discard.sql +--- +discard all; + +discard plans; + +discard sequences; + +discard temporary; + +discard temp; + +/* before discard */ +discard + /* first comment before target */ /* second comment before target */ /* third comment before target */ all/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/distinct_on.snap b/crates/squawk_fmt/tests/after/distinct_on.snap new file mode 100644 index 00000000..1a55fc7e --- /dev/null +++ b/crates/squawk_fmt/tests/after/distinct_on.snap @@ -0,0 +1,22 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/distinct_on.sql +--- +select distinct on (a, b) a, b from foo; +select distinct on (b) a, b from foo; + +select distinct on ( + a_very_long_first_distinct_expression, + a_very_long_second_distinct_expression, + a_very_long_third_distinct_expression + ) a_very_long_first_target_expression, + a_very_long_second_target_expression +from a_very_long_source_relation_name; + +select /* before distinct */ distinct /* before on */ on /* before opening paren */ ( + /* before first expression */ a /* before comma */, + /* before second expression */ b /* before closing paren */ + ) /* before target */ a +/* before from */ from /* before relation */ foo; + +select distinct on ( /* before empty closing paren */) 1; diff --git a/crates/squawk_fmt/tests/after/do.snap b/crates/squawk_fmt/tests/after/do.snap new file mode 100644 index 00000000..24f50fec --- /dev/null +++ b/crates/squawk_fmt/tests/after/do.snap @@ -0,0 +1,28 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/do.sql +--- +do 'BEGIN NULL; END'; + +do $$BEGIN RAISE NOTICE 'hello'; END$$; + +do + language plpgsql + $$begin perform refresh_materialized_view_with_an_intentionally_long_name(); end$$; + +do $$begin null; end$$ language 'plpgsql'; + +do $body$ +begin + raise notice 'hello'; +end +$body$; + +/* before */ +do + /* after do */ language /* after language */ plpgsql + /* before body */ $body$BEGIN NULL; END$body$/* before semicolon */; + +do + /* before trailing body */ $body$BEGIN NULL; END$body$ + /* before trailing language */ language /* before language literal */ 'plpgsql'/* before trailing semicolon */; diff --git a/crates/squawk_fmt/tests/after/drop_publication.snap b/crates/squawk_fmt/tests/after/drop_publication.snap new file mode 100644 index 00000000..af82f46f --- /dev/null +++ b/crates/squawk_fmt/tests/after/drop_publication.snap @@ -0,0 +1,10 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/drop_publication.sql +--- +drop publication if exists all_changes, selected_tables cascade; + +drop /* after drop */ publication /* before if */ if /* before exists */ exists +/* before name */ commented_pub, + /* after comma */ selected_tables +/* before behavior */ restrict/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/drop_subscription.snap b/crates/squawk_fmt/tests/after/drop_subscription.snap new file mode 100644 index 00000000..5335f46e --- /dev/null +++ b/crates/squawk_fmt/tests/after/drop_subscription.snap @@ -0,0 +1,9 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/drop_subscription.sql +--- +drop subscription if exists renamed_sub restrict; + +drop /* after drop */ subscription /* before if */ if /* before exists */ exists +/* before name */ commented_sub +/* before behavior */ cascade/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/empty_stmt.snap b/crates/squawk_fmt/tests/after/empty_stmt.snap new file mode 100644 index 00000000..cd19e129 --- /dev/null +++ b/crates/squawk_fmt/tests/after/empty_stmt.snap @@ -0,0 +1,14 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/empty_stmt.sql +--- +; + +;; + +/* before empty statement */ +; + +; +/* between empty statements */ +; diff --git a/crates/squawk_fmt/tests/after/execute.snap b/crates/squawk_fmt/tests/after/execute.snap new file mode 100644 index 00000000..9b4aaaeb --- /dev/null +++ b/crates/squawk_fmt/tests/after/execute.snap @@ -0,0 +1,21 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/execute.sql +--- +execute statement_name; + +execute statement_name(1, true, some_value); + +execute "Case-Sensitive Statement"; + +execute statement_name( + an_intentionally_long_argument_name, + another_intentionally_long_argument_name, + a_third_intentionally_long_argument_name +); + +/* before execute */ +execute /* before statement */ statement_name /* before left paren */( + /* after left paren */ 1 /* before comma */, + /* after comma */ true /* before right paren */ +)/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/explain.snap b/crates/squawk_fmt/tests/after/explain.snap new file mode 100644 index 00000000..a22ac8bb --- /dev/null +++ b/crates/squawk_fmt/tests/after/explain.snap @@ -0,0 +1,24 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/explain.sql +--- +explain + select * from records; + +explain + analyze verbose + update records set value = 1; + +explain ( + analyze true, verbose, costs false, format json +) + select an_intentionally_long_column_name + from an_intentionally_long_table_name + where an_intentionally_long_column_name > 0; + +/* before explain */ +explain /* before options */ ( + /* before analyze */ analyze /* before value */ true /* before comma */, + /* before format */ format /* before json */ json /* before close */ +) + /* before select */ select /* before target */ 1/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/fetch.snap b/crates/squawk_fmt/tests/after/fetch.snap new file mode 100644 index 00000000..6211b93f --- /dev/null +++ b/crates/squawk_fmt/tests/after/fetch.snap @@ -0,0 +1,49 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/fetch.sql +--- +fetch next from cursor_name; + +fetch prior in cursor_name; + +fetch first from cursor_name; + +fetch last from cursor_name; + +fetch absolute 10 from cursor_name; + +fetch relative -3 from cursor_name; + +fetch 10 from cursor_name; + +fetch all from cursor_name; + +fetch forward from cursor_name; + +fetch forward 10 in cursor_name; + +fetch forward all from cursor_name; + +fetch backward from cursor_name; + +fetch backward 10 from cursor_name; + +fetch backward all from cursor_name; + +fetch prior cursor_name; + +fetch + next + from + cursor_with_an_intentionally_long_name_that_makes_this_fetch_statement_longer_than_eighty_characters; + +/* before fetch */ +fetch + /* before action */ forward /* before all */ all + /* before from */ from + /* before cursor */ cursor_name/* before semicolon */; + +fetch + /* before absolute */ absolute /* before count */ 10 + /* before in */ in + /* before second cursor */ cursor_name/* before second semicolon */; diff --git a/crates/squawk_fmt/tests/after/from.snap b/crates/squawk_fmt/tests/after/from.snap index 7f6cf144..51605684 100644 --- a/crates/squawk_fmt/tests/after/from.snap +++ b/crates/squawk_fmt/tests/after/from.snap @@ -12,11 +12,184 @@ 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 * 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; +select * +from lateral cast( + a_very_long_expression_name as a_very_long_schema_name.a_very_long_type_name + ) as converted; +select * from collation for (foo) as collation_name; +select * +from /* before lateral */ lateral /* before cast */ cast /* before opening paren */( + /* before expression */ value + /* before as */ as + /* before type */ int8 /* before closing paren */ + ) /* before alias */ as /* before alias name */ converted; +select * from (select 1) as selected; +select * +from only lateral ( + select a_very_long_parenthesized_select_expression + from a_very_long_parenthesized_select_relation_name + ) as a_very_long_parenthesized_select_alias; +select * +from /* before only */ only /* before lateral */ lateral /* before opening paren */ ( + /* before select */ select + /* before target */ value /* before closing paren */ + ) /* before alias */ as /* before alias name */ selected; +select * +from ( + /* before relation */ foo /* before closing paren */ + ) as parenthesized_relation; +select * +from rows from ( + generate_series(1, 3), + unnest(array[1, 2]) as (value int8) + ) with ordinality as generated (first_value, second_value, ordinality); +select * +from /* before lateral */ lateral /* before rows */ rows /* before from */ from /* before opening paren */( + /* before first argument */ generate_series( + 1, + 3 + ) /* before argument comma */, + /* before second argument */ unnest( + array[1, 2] + ) /* before as */ as /* before column list */ ( + /* before column */ value /* before type */ int8 /* before column list close */ + ) /* before rows close */ + ) /* before with */ with /* before ordinality */ ordinality /* before alias */ as /* before alias name */ generated ( + value, + ordinality + ); +select * +from xmltable( + '/rows/row' passing doc + columns + id int8 path '@id' not null, + ord for ordinality, + value text default 'unknown' null + ) as parsed; +select * +from lateral xmltable( + xmlnamespaces('urn:a' as a, default 'urn:default'), + a_very_long_xml_row_expression passing by ref + a_very_long_xml_document_expression by value + columns + a_very_long_first_xml_column_name a_very_long_xml_column_type + path a_very_long_xml_path_expression, + a_very_long_ordinality_column_name for ordinality + ) as a_very_long_xml_table_alias; +select * +from /* before lateral */ lateral /* before xmltable */ xmltable /* before opening paren */( + /* before namespaces */ xmlnamespaces /* before namespace opening paren */( + /* before namespace expression */ 'urn:a' /* before as */ as /* before prefix */ a /* before namespace comma */, + /* before default */ default /* before default expression */ 'urn:default' /* before namespace closing paren */ + ) /* before outer comma */, + /* before row */ '/rows/row' + /* before passing */ passing /* before first by */ by /* before ref */ ref + /* before document */ doc /* before second by */ by /* before value */ value + /* before columns */ columns + /* before first column */ id /* before type */ int8 + /* before path */ path /* before path expression */ '@id' + /* before not */ not /* before null */ null /* before column comma */, + /* before ordinality column */ ord /* before for */ for /* before ordinality */ ordinality /* before closing paren */ + ) /* 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; +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; +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 + 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' + ) + ) + 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; 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; select * +from lateral a_very_long_function_name( + a_very_long_first_argument_name, + a_very_long_second_argument_name, + a_very_long_third_argument_name + ) with ordinality as a_very_long_function_alias ( + a_very_long_value_column_alias, + a_very_long_ordinality_column_alias + ); +select * from a_very_long_schema_name.a_very_long_relation_name as a_very_long_relation_alias ( a_very_long_first_column_alias, a_very_long_second_column_alias, @@ -34,3 +207,39 @@ from a_very_long_relation_name tablesample bernoulli ( select * from only a_very_long_schema_name.a_very_long_first_relation_name * as a_very_long_first_alias, a_very_long_schema_name.a_very_long_second_relation_name as a_very_long_second_alias; + +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 * +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; + +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; diff --git a/crates/squawk_fmt/tests/after/grant.snap b/crates/squawk_fmt/tests/after/grant.snap new file mode 100644 index 00000000..e092ee6a --- /dev/null +++ b/crates/squawk_fmt/tests/after/grant.snap @@ -0,0 +1,51 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/grant.sql +--- +grant + select, + update (payload) + on + table public.records, archived_records + to + app_user; + +grant select on public.records to app_user; + +grant + all privileges + on + all tables in schema public, audit + to + app_user + with grant option + granted by current_user; + +grant + app_reader, + app_writer + to + app_user + with admin option, inherit true + granted by current_user; + +grant + usage + on + sequence + public.an_intentionally_long_sequence_name_that_makes_this_statement_exceed_eighty_characters + to + an_intentionally_long_role_name; + +/* before grant */ +grant + /* before select */ select /* before columns */ ( + /* before column */ payload /* before close */ + ) + /* before on */ on + /* before table */ table + /* before object */ public /* before dot */./* after dot */ records + /* before to */ to + /* before role */ app_user + /* before with */ with /* before grant option */ grant /* before option */ option + /* before granted */ granted /* before by */ by /* before grantor */ current_user/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/graph_table.snap b/crates/squawk_fmt/tests/after/graph_table.snap index 01c1852c..32e0ad35 100644 --- a/crates/squawk_fmt/tests/after/graph_table.snap +++ b/crates/squawk_fmt/tests/after/graph_table.snap @@ -56,10 +56,12 @@ from /* before graph table */ graph_table /* before outer opening paren */( /* before match */ match /* before first vertex opening paren */ (/* before first variable */ a /* before is */ is /* before first label */ person - /* before vertex where */ where /* before vertex expression */ a.active /* before first vertex closing paren */) + /* before vertex where */ where + /* before vertex expression */ a.active /* before first vertex closing paren */) /* before first edge minus */ - /* before edge opening bracket */[/* before edge variable */ e /* before edge is */ is /* before edge label */ knows - /* before edge where */ where /* before edge expression */ e.active /* before edge closing bracket */] /* before edge ending minus */- /* before right angle */> + /* before edge where */ where + /* before edge expression */ e.active /* before edge closing bracket */] /* before edge ending minus */- /* before right angle */> /* before second vertex */ (b)/* before qualifier opening curly */ {/* before qualifier min */ 1 /* before qualifier comma */, /* before qualifier max */ 3 /* before qualifier closing curly */} /* before pattern comma */, /* before second pattern */ /* before left angle */ < /* before left minus */- /* before left opening bracket */[left_edge /* before left closing bracket */] /* before left ending minus */- (c), @@ -68,7 +70,8 @@ from /* before graph table */ graph_table /* before outer opening paren */( (e), /* before nested opening paren */ (/* before nested pattern */ (x) /* before simple edge */ - /* before simple right angle */> - (y) /* before nested where */ where /* before nested expression */ x.active /* before nested closing paren */) + (y) /* before nested where */ where + /* before nested expression */ x.active /* before nested closing paren */) /* before graph where */ where /* before graph expression */ b.active /* before columns */ columns /* before columns opening paren */( /* before first column */ a.name /* before column as */ as /* before column name */ source /* before column comma */, diff --git a/crates/squawk_fmt/tests/after/import_foreign_schema.snap b/crates/squawk_fmt/tests/after/import_foreign_schema.snap new file mode 100644 index 00000000..777f1f38 --- /dev/null +++ b/crates/squawk_fmt/tests/after/import_foreign_schema.snap @@ -0,0 +1,39 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/import_foreign_schema.sql +--- +import foreign schema remote from server foreign_server into local; + +import + foreign + schema + remote + limit to (records, users) + from + server foreign_server + into local + options ( + schema_name 'public' + ); + +import + foreign + schema + remote + except (ignored_records) + from + server foreign_server + into an_intentionally_long_local_schema_name_that_makes_the_statement_exceed_eighty_characters; + +/* before import */ +import + /* before foreign */ foreign + /* before schema */ schema + /* before remote */ remote + /* before limit */ limit /* before to */ to /* before open */( + /* before table */ records /* before comma */, + /* before second */ users /* before close */ + ) + /* before from */ from + /* before server */ server /* before server name */ foreign_server + /* before into */ into /* before local */ local/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/insert.snap b/crates/squawk_fmt/tests/after/insert.snap new file mode 100644 index 00000000..8e24cd1f --- /dev/null +++ b/crates/squawk_fmt/tests/after/insert.snap @@ -0,0 +1,68 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/insert.sql +--- +insert into foo (id, name) values (1, 'one') returning id; + +with inserted as ( + insert into foo default values returning id +) +select * +from inserted; + +with inserted as ( + insert into foo as f (id, name) + overriding system value + select id, name from incoming + on conflict on constraint foo_pkey do nothing + returning id +) +select * +from inserted; + +with inserted as ( + insert into a_very_long_schema_name.a_very_long_table_name ( + organization_identifier, + extremely_long_descriptive_column_name + ) + values ( + 123456789, + 'an extremely long value that forces the insert statement to wrap across lines' + ) + on conflict (organization_identifier) + do update + set + extremely_long_descriptive_column_name + = excluded.extremely_long_descriptive_column_name + where + a_very_long_table_name.organization_identifier + = excluded.organization_identifier + returning organization_identifier, extremely_long_descriptive_column_name +) +select organization_identifier, extremely_long_descriptive_column_name +from inserted; + +/*before*/ +with /*a*/ inserted/*b*/ ( + /*c*/ result_id /*d*/ +) /*e*/ as /*f*/ not /*g*/ materialized /*h*/ ( + /*i*/ insert /*j*/ into /*k*/ public /*l*/./*m*/ foo /*n*/ as /*o*/ f /*p*/ ( + /*q*/ id /*r*/, + /*s*/ payload /*t*/ + ) + /*u*/ overriding /*v*/ user /*w*/ value + /*x*/ values /*y*/ (/*z*/ 1 /*aa*/, /*ab*/ 'new' /*ac*/) + /*ad*/ on /*ae*/ conflict /*af*/ ( + /*ag*/ id /*ah*/ collate /*ai*/ "C" /*aj*/ text_ops /*ak*/ + ) + /*al*/ where /*am*/ id > 0 + /*an*/ do /*ao*/ update + /*ap*/ set /*aq*/ payload /*ar*/ = /*as*/ excluded.payload + /*at*/ where /*au*/ foo.id = excluded.id + /*av*/ returning /*aw*/ with /*ax*/ ( + /*ay*/ old /*az*/ as /*ba*/ old_row /*bb*/, + /*bc*/ new /*bd*/ as /*be*/ new_row /*bf*/ + ) /*bg*/ new_row.id /*bh*/ +) +/*bi*/ select /*bj*/ result_id +/*bk*/ from /*bl*/ inserted/*bm*/; diff --git a/crates/squawk_fmt/tests/after/listen.snap b/crates/squawk_fmt/tests/after/listen.snap new file mode 100644 index 00000000..f3246856 --- /dev/null +++ b/crates/squawk_fmt/tests/after/listen.snap @@ -0,0 +1,11 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/listen.sql +--- +listen events; + +listen + an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before listen */ +listen /* before channel */ event_channel/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/load.snap b/crates/squawk_fmt/tests/after/load.snap new file mode 100644 index 00000000..749e5128 --- /dev/null +++ b/crates/squawk_fmt/tests/after/load.snap @@ -0,0 +1,13 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/load.sql +--- +load 'foo'; + +load '$libdir/extension'; + +load + 'an/intentionally/long/path/to/a/postgresql/shared/library/that/makes/this/load/statement/longer/than/eighty/characters'; + +/* before load */ +load /* before filename */ 'filename'/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/lock.snap b/crates/squawk_fmt/tests/after/lock.snap new file mode 100644 index 00000000..693a2898 --- /dev/null +++ b/crates/squawk_fmt/tests/after/lock.snap @@ -0,0 +1,38 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/lock.sql +--- +lock t; + +lock table t, only b, c *; + +lock t in access share mode; + +lock t in row share mode; + +lock t in row exclusive mode; + +lock t in share update exclusive mode; + +lock t in share mode; + +lock t in share row exclusive mode; + +lock t in exclusive mode; + +lock t in access exclusive mode; + +lock table t, a *, only c in row exclusive mode nowait; + +lock table + an_intentionally_long_schema_name.an_intentionally_long_table_name, + another_intentionally_long_schema_name.another_intentionally_long_table_name + in access exclusive mode + nowait; + +/* before lock */ +lock /* before table */ table + /* before first relation */ only /* before first name */ public /* before dot */./* after dot */ records /* before comma */, + /* after comma */ archived_records + /* before in */ in /* before access */ access /* before exclusive */ exclusive /* before mode */ mode + /* before nowait */ nowait/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/merge.snap b/crates/squawk_fmt/tests/after/merge.snap new file mode 100644 index 00000000..152a83d8 --- /dev/null +++ b/crates/squawk_fmt/tests/after/merge.snap @@ -0,0 +1,72 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/merge.sql +--- +merge into target as t +using source as s on t.id = s.id +when matched then + update set value = s.value +when not matched then + insert (id, value) values (s.id, s.value) +returning t.id; + +with merged as ( + merge into target + using source on target.id = source.id + when matched and source.deleted then + delete + when not matched by source then + do nothing + when not matched by target then + insert default values + returning target.id +) +select * +from merged; + +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; + +/* 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/after/move.snap b/crates/squawk_fmt/tests/after/move.snap new file mode 100644 index 00000000..7b122227 --- /dev/null +++ b/crates/squawk_fmt/tests/after/move.snap @@ -0,0 +1,18 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/move.sql +--- +move next from cursor_name; + +move forward 10 in cursor_name; + +move + absolute 100000000000000000000000000000000000000000000000000000000000000 + from + an_intentionally_long_cursor_name; + +/* before move */ +move + /* before backward */ backward /* before all */ all + /* before from */ from + /* before cursor */ cursor_name/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/notify.snap b/crates/squawk_fmt/tests/after/notify.snap new file mode 100644 index 00000000..2d0fe7af --- /dev/null +++ b/crates/squawk_fmt/tests/after/notify.snap @@ -0,0 +1,16 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/notify.sql +--- +notify events; + +notify events, 'payload'; + +notify + an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters, + 'an intentionally long payload that also wraps'; + +/* before notify */ +notify + /* before channel */ events /* before comma */, + /* before payload */ 'payload'/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/paren_select.snap b/crates/squawk_fmt/tests/after/paren_select.snap new file mode 100644 index 00000000..ff168d83 --- /dev/null +++ b/crates/squawk_fmt/tests/after/paren_select.snap @@ -0,0 +1,34 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/paren_select.sql +--- +(select 1) order by 1 for update limit 10 offset 2 rows; +(select 1) fetch first 5 rows with ties; +with cte as ( + select 1 +) +(select x from cte); + +(select a_very_long_result_expression from a_very_long_source_relation_name) +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc +for no key update of a_very_long_source_relation_name skip locked +limit a_very_long_limit_expression +offset a_very_long_offset_expression rows; + +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; diff --git a/crates/squawk_fmt/tests/after/prepare.snap b/crates/squawk_fmt/tests/after/prepare.snap new file mode 100644 index 00000000..2300684e --- /dev/null +++ b/crates/squawk_fmt/tests/after/prepare.snap @@ -0,0 +1,38 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/prepare.sql +--- +prepare statement_name as + select 1; + +prepare statement_name(int, text) as + insert into t values ($1, $2); + +prepare statement_name as + update t set value = 1 where id = $1; + +prepare statement_name as + delete from t where id = $1; + +prepare statement_name as + values (1, 'one'), (2, 'two'); + +prepare + prepared_statement_with_an_intentionally_long_name( + integer, + character varying, + timestamp with time zone, + double precision + ) + as + select an_intentionally_long_column_name + from an_intentionally_long_table_name; + +/* before prepare */ +prepare + /* before name */ statement_name/* before left paren */ ( + /* after left paren */ int /* before comma */, + /* after comma */ text /* before right paren */ + ) + /* before as */ as + /* before statement */ select /* before value */ $1/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/reassign.snap b/crates/squawk_fmt/tests/after/reassign.snap new file mode 100644 index 00000000..a262fb94 --- /dev/null +++ b/crates/squawk_fmt/tests/after/reassign.snap @@ -0,0 +1,23 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/reassign.sql +--- +reassign owned by alice to bob; + +reassign + owned + by + alice, + group legacy_owner, + current_user + to + an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before reassign */ +reassign + /* before owned */ owned + /* before by */ by + /* before first */ alice /* before comma */, + /* before second */ bob + /* before to */ to + /* before new owner */ carol/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/refresh.snap b/crates/squawk_fmt/tests/after/refresh.snap new file mode 100644 index 00000000..a5b1435f --- /dev/null +++ b/crates/squawk_fmt/tests/after/refresh.snap @@ -0,0 +1,20 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/refresh.sql +--- +refresh materialized view public.summary; + +refresh + materialized + view + concurrently + public.an_intentionally_long_materialized_view_name_that_makes_this_statement_longer_than_eighty_characters + with no data; + +/* before refresh */ +refresh + /* before materialized */ materialized + /* before view */ view + /* before concurrently */ concurrently + /* before name */ public /* before dot */./* after dot */ summary + /* before with */ with /* before no */ no /* before data */ data/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/reindex.snap b/crates/squawk_fmt/tests/after/reindex.snap new file mode 100644 index 00000000..9bf85ddb --- /dev/null +++ b/crates/squawk_fmt/tests/after/reindex.snap @@ -0,0 +1,41 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/reindex.sql +--- +reindex index my_index; + +reindex table my_table; + +reindex table concurrently my_broken_table; + +reindex database my_database; + +reindex system my_database; + +reindex schema my_schema; + +reindex (concurrently true, tablespace new_tablespace, verbose false) + database concurrently my_database; + +reindex (concurrently 'off', verbose yes) table public.my_table; + +reindex (concurrently no, verbose auto) index public.my_index; + +reindex (concurrently, verbose) table public.my_table; + +reindex () table my_table; + +reindex ( + concurrently true, + tablespace an_intentionally_long_tablespace_name, + verbose false +) + table concurrently an_intentionally_long_schema_name.an_intentionally_long_table_name; + +/* before reindex */ +reindex /* before left paren */ ( + /* after left paren */ concurrently /* before option value */ true /* before comma */, + /* after comma */ tablespace /* before tablespace */ new_tablespace /* before second comma */, + /* after second comma */ verbose /* before verbose value */ no /* before right paren */ +) + /* before target */ table /* before concurrently */ concurrently /* before table name */ public /* before dot */./* after dot */ records/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/repack.snap b/crates/squawk_fmt/tests/after/repack.snap new file mode 100644 index 00000000..43b29415 --- /dev/null +++ b/crates/squawk_fmt/tests/after/repack.snap @@ -0,0 +1,24 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/repack.sql +--- +repack public.records; + +repack + (verbose true, analyze false) + public.records (id, payload), + public.archived_records + using index public.records_idx; + +repack + an_intentionally_long_schema_name.an_intentionally_long_table_name_that_makes_this_statement_exceed_eighty_characters; + +/* before repack */ +repack + /* before options */ ( + /* before verbose */ verbose /* before true */ true /* before close */ + ) + /* before table */ public /* before dot */./* after dot */ records /* before columns */ ( + /* before column */ id /* before close */ + ) + /* before using */ using /* before index */ index /* before index name */ public /* before index dot */./* after index dot */ records_idx/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/reset.snap b/crates/squawk_fmt/tests/after/reset.snap new file mode 100644 index 00000000..0fe8677d --- /dev/null +++ b/crates/squawk_fmt/tests/after/reset.snap @@ -0,0 +1,25 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/reset.sql +--- +reset all; + +reset some_config_param; + +reset foo.bar.buzz; + +reset time zone; + +reset transaction isolation level; + +reset + an_intentionally_long_config_namespace.an_intentionally_long_config_group.an_intentionally_long_config_parameter_name; + +/* before reset */ +reset + /* before parameter */ custom /* before first dot */./* after first dot */ group_name /* before second dot */./* after second dot */ parameter_name/* before semicolon */; + +reset + /* before transaction */ transaction /* before isolation */ isolation /* before level */ level/* before transaction semicolon */; + +reset /* before time */ time /* before zone */ zone/* before time semicolon */; diff --git a/crates/squawk_fmt/tests/after/reset_role.snap b/crates/squawk_fmt/tests/after/reset_role.snap new file mode 100644 index 00000000..201fac37 --- /dev/null +++ b/crates/squawk_fmt/tests/after/reset_role.snap @@ -0,0 +1,8 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/reset_role.sql +--- +reset role; + +/* before reset */ +reset /* before role */ role/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/reset_session_auth.snap b/crates/squawk_fmt/tests/after/reset_session_auth.snap new file mode 100644 index 00000000..0b8c313b --- /dev/null +++ b/crates/squawk_fmt/tests/after/reset_session_auth.snap @@ -0,0 +1,8 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/reset_session_auth.sql +--- +reset session authorization; + +/* before reset */ +reset /* before session */ session /* before authorization */ authorization/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/revoke.snap b/crates/squawk_fmt/tests/after/revoke.snap new file mode 100644 index 00000000..f6362452 --- /dev/null +++ b/crates/squawk_fmt/tests/after/revoke.snap @@ -0,0 +1,35 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/revoke.sql +--- +revoke select, update (payload) on table public.records from app_user; + +revoke + grant option for + all privileges + on + all tables in schema public, audit + from + app_user + granted by current_user + cascade; + +revoke + admin option for + app_reader, + app_writer + from + an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters + restrict; + +/* before revoke */ +revoke + /* before grant */ grant /* before option */ option /* before for */ for + /* before select */ select + /* before on */ on + /* before table */ table + /* before object */ public /* before dot */./* after dot */ records + /* before from */ from + /* before role */ app_user + /* before granted */ granted /* before by */ by /* before grantor */ current_user + /* before cascade */ cascade/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/security_label.snap b/crates/squawk_fmt/tests/after/security_label.snap new file mode 100644 index 00000000..67943492 --- /dev/null +++ b/crates/squawk_fmt/tests/after/security_label.snap @@ -0,0 +1,22 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/security_label.sql +--- +security label + on table public.records + is 'system_u:object_r:postgresql_db_t:s0'; + +security label + for selinux + on materialized view + public.an_intentionally_long_materialized_view_name_that_makes_this_statement_exceed_eighty_characters + is null; + +security label on function public.process_record(bigint, text) is 'trusted'; + +/* before security */ +security /* before label */ label + /* before for */ for /* before provider */ selinux + /* before on */ on /* before foreign */ foreign /* before table */ table + /* before name */ public /* before dot */./* after dot */ records + /* before is */ is /* before value */ 'trusted'/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/select_clauses.snap b/crates/squawk_fmt/tests/after/select_clauses.snap new file mode 100644 index 00000000..89c1e704 --- /dev/null +++ b/crates/squawk_fmt/tests/after/select_clauses.snap @@ -0,0 +1,59 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/select_clauses.sql +--- +with cte as ( + select 1 +) +select x +from cte +where x > 0 +group by x +having count(*) > 0 +window win as (partition by x) +order by x +for update +limit 10 +offset 2 rows; + +select x from foo fetch first 5 rows with ties; + +with a_very_long_common_table_expression_name as ( + select a_very_long_source_column_name from a_very_long_source_relation_name +) +select a_very_long_result_column_name +from a_very_long_common_table_expression_name +where a_very_long_filter_column_name > a_very_long_filter_threshold_value +group by a_very_long_result_column_name +having count(*) > a_very_long_having_threshold_value +window a_very_long_window_name as ( + partition by a_very_long_partition_column_name + order by a_very_long_order_column_name + ) +order by a_very_long_result_column_name desc +for no key update of a_very_long_common_table_expression_name skip locked +limit a_very_long_limit_expression +offset a_very_long_offset_expression rows; + +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; diff --git a/crates/squawk_fmt/tests/after/select_expr.snap b/crates/squawk_fmt/tests/after/select_expr.snap index 4fbaa901..281a4c3f 100644 --- a/crates/squawk_fmt/tests/after/select_expr.snap +++ b/crates/squawk_fmt/tests/after/select_expr.snap @@ -96,6 +96,9 @@ select /* before expr */ array[4] /* before closing paren */ ) /* after any */, exists(select 1 from things), + exists((select 1)), + exists(table things), + 5 = any(values (1), (5)), a_very_long_any_comparison_expression = any( a_very_long_any_input_expression_that_forces_wrapping ), @@ -271,10 +274,14 @@ select a_very_long_second_argument_name := a_very_long_second_argument_expression ), a_very_long_aggregate_name( - a_very_long_aggregate_argument_expression order by a_very_long_first_order_expression desc nulls last, a_very_long_second_order_expression using > nulls first + a_very_long_aggregate_argument_expression order by + a_very_long_first_order_expression desc nulls last, + a_very_long_second_order_expression using > nulls first ), array_agg( - x /* before order */ order /* before by */ by /* before first */ y /* before desc */ desc /* before nulls */ nulls /* before last */ last /* before comma */, /* before second */ z /* before asc */ asc + x /* before order */ order /* before by */ by + /* before first */ y /* before desc */ desc /* before nulls */ nulls /* before last */ last /* before comma */, + /* before second */ z /* before asc */ asc ), json_arrayagg(v), json_arrayagg( @@ -313,6 +320,9 @@ select absent on null returning a_very_long_json_select_return_type ), + json_array((select 1)), + json_array(table things), + json_array(values (1), (2)), json_array /* before opening paren */( /* before first */ 1 /* before comma */, /* before second */ 2 @@ -494,7 +504,8 @@ select percentile_disc( 0.5 ) /* before within */ within /* before group */ group /* before opening paren */ ( - /* before order */ order /* before by */ by /* before sort */ x /* before closing paren */ + /* before order */ order /* before by */ by + /* before sort */ x /* before closing paren */ ) /* after within */, count(*) filter (where x > 1), sum(x) /* before filter */ filter /* before opening paren */ ( @@ -603,8 +614,7 @@ select a[1 + 2], a /* before bracket */[/* before index */ 1 /* before closing bracket */], a_very_long_indexed_expression[a_very_long_first_index_expression][ - a_very_long_second_index_expression - + a_very_long_index_offset_expression + a_very_long_second_index_expression + a_very_long_index_offset_expression ], -- literal 42, @@ -621,11 +631,42 @@ select (1 + 2), ((1)), (/* before expr */ 1 /* before closing paren */), + (select 1), + /* 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), + (table foo order by a desc, b asc), + ( + /* before table */ table + /* before only */ only /* before relation opening paren */(/* before relation */ public /* before dot */./* before name */ foo /* before relation closing paren */) /* before outer closing paren */ + ), + ( + table + a_very_long_schema_name.a_very_long_relation_name_that_forces_parenthesized_table_wrapping + ), + (values (1, 2), (3, 4)), + ( + values ( + a_very_long_first_parenthesized_value_expression, + a_very_long_second_parenthesized_value_expression + ), + ( + a_very_long_third_parenthesized_value_expression, + a_very_long_fourth_parenthesized_value_expression + ) + ), ( a_very_long_parenthesized_expression - + a_second_very_long_parenthesized_expression + + a_second_very_long_parenthesized_expression ), -- postfix expr + 'x' at /* between at and local */ local, 1 isnull, 2 notnull, x is json, @@ -645,6 +686,8 @@ select x is not json value, 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, + 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, -- prefix expr @@ -684,9 +727,16 @@ select /* before second */ 2 /* before closing paren */ ); -select - a_very_long_function_name( - first_very_long_argument_name, - second_very_long_argument_name, - third_very_long_argument_name - ); +select a_very_long_function_name( + first_very_long_argument_name, + second_very_long_argument_name, + third_very_long_argument_name +); + +select json_object( + 'a': 1, + 'b' value 2 format json + null on null + with unique keys + returning jsonb format json +); diff --git a/crates/squawk_fmt/tests/after/select_into.snap b/crates/squawk_fmt/tests/after/select_into.snap new file mode 100644 index 00000000..c85cb228 --- /dev/null +++ b/crates/squawk_fmt/tests/after/select_into.snap @@ -0,0 +1,71 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/select_into.sql +--- +select 1 into foo; +select a, b into temporary table public.foo from bar; +select 1 into unlogged foo; +/* before select */ +select + /* before distinct */ distinct /* before first target */ a /* before target comma */, + /* before second target */ b +/* before into */ into /* before local */ local /* before temporary */ temporary /* before table */ table /* before schema */ public /* before dot */./* before table name */ foo +/* before from */ from /* before relation */ bar +/* before group */ group /* before by */ by /* before group expression */ a +/* before order */ order /* before order by */ by + /* before order expression */ b /* before desc */ desc/* before semicolon */; +select + a_very_long_first_select_into_expression, + a_very_long_second_select_into_expression, + a_very_long_third_select_into_expression +into unlogged a_very_long_schema_name.a_very_long_select_into_table_name +from a_very_long_select_into_source_relation +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc; + +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; diff --git a/crates/squawk_fmt/tests/after/set.snap b/crates/squawk_fmt/tests/after/set.snap new file mode 100644 index 00000000..cdea8b83 --- /dev/null +++ b/crates/squawk_fmt/tests/after/set.snap @@ -0,0 +1,48 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/set.sql +--- +set search_path to myschema, public; + +set session search_path = public; + +set local work_mem to '64MB'; + +set foo from current; + +set foo = default; + +set foo to null; + +set foo to a, 10.0, 1, 'foo', true, false; + +set schema 'my_schema'; + +set catalog 'my_database'; + +set xml option document; + +set xml option content; + +set time zone 'America/Los_Angeles'; + +set time zone default; + +set time zone local; + +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 */; + +set + /* before time */ time /* before zone */ zone /* before timezone value */ default/* before timezone semicolon */; + +set + /* before xml */ xml /* before option */ option /* before document */ document/* before xml semicolon */; diff --git a/crates/squawk_fmt/tests/after/set_constraints.snap b/crates/squawk_fmt/tests/after/set_constraints.snap new file mode 100644 index 00000000..60e6e08c --- /dev/null +++ b/crates/squawk_fmt/tests/after/set_constraints.snap @@ -0,0 +1,25 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/set_constraints.sql +--- +set constraints all deferred; + +set constraints first_constraint, public.second_constraint immediate; + +set + constraints + an_intentionally_long_schema_name.an_intentionally_long_constraint_name, + another_intentionally_long_constraint_name + deferred; + +/* before set */ +set + /* before constraints */ constraints + /* before first name */ first_constraint /* before comma */, + /* before second name */ public /* before dot */./* after dot */ second_constraint + /* before timing */ immediate/* before semicolon */; + +set + /* before constraints */ constraints + /* before all */ all + /* before deferred */ deferred/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/set_role.snap b/crates/squawk_fmt/tests/after/set_role.snap new file mode 100644 index 00000000..9595bcf8 --- /dev/null +++ b/crates/squawk_fmt/tests/after/set_role.snap @@ -0,0 +1,24 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/set_role.sql +--- +set role app_user; + +set local role none; + +set session role current_user; + +set role group legacy_user; + +set role 'literal role'; + +set + session + role + an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before set */ +set + /* before scope */ local + /* before role */ role + /* before target */ group /* before role name */ legacy_user/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/set_session_auth.snap b/crates/squawk_fmt/tests/after/set_session_auth.snap new file mode 100644 index 00000000..ee4c6dde --- /dev/null +++ b/crates/squawk_fmt/tests/after/set_session_auth.snap @@ -0,0 +1,23 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/set_session_auth.sql +--- +set session authorization app_user; + +set local session authorization default; + +set session session authorization current_role; + +set session authorization 'literal role'; + +set + session + authorization + an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before set */ +set + /* before scope */ local + /* before session */ session + /* before authorization */ authorization + /* before target */ group /* before role name */ legacy_user/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/set_transaction.snap b/crates/squawk_fmt/tests/after/set_transaction.snap new file mode 100644 index 00000000..7a28f7f4 --- /dev/null +++ b/crates/squawk_fmt/tests/after/set_transaction.snap @@ -0,0 +1,46 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/set_transaction.sql +--- +set transaction isolation level serializable; + +set transaction isolation level repeatable read, read write, not deferrable; + +set + session + characteristics + as + transaction + isolation level read committed, + read only, + deferrable; + +set transaction snapshot '00000003-0000001B-1'; + +set + session + characteristics + as + transaction + isolation level serializable, + read write, + not deferrable; + +/* before set */ +set + /* before transaction */ transaction + /* before first mode */ isolation /* before level */ level /* before serializable */ serializable /* before comma */, + /* before read */ read /* before only */ only /* before second comma */, + /* before not */ not /* before deferrable */ deferrable/* before semicolon */; + +set + /* before session */ session + /* before characteristics */ characteristics + /* before as */ as + /* before transaction */ transaction + /* before mode */ read /* before write */ write/* before semicolon */; + +set + /* before transaction */ transaction + /* before snapshot */ snapshot + /* before literal */ '00000003-0000001B-1'/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/show.snap b/crates/squawk_fmt/tests/after/show.snap new file mode 100644 index 00000000..e3bbf966 --- /dev/null +++ b/crates/squawk_fmt/tests/after/show.snap @@ -0,0 +1,23 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/show.sql +--- +show all; + +show work_mem; + +show + custom.an_intentionally_long_config_group.an_intentionally_long_parameter_name_that_exceeds_eighty_characters; + +show time zone; + +show transaction isolation level; + +show session authorization; + +/* before show */ +show + /* before parameter */ custom /* before dot */./* after dot */ parameter/* before semicolon */; + +show + /* before transaction */ transaction /* before isolation */ isolation /* before level */ level/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/table.snap b/crates/squawk_fmt/tests/after/table.snap new file mode 100644 index 00000000..d1ea402d --- /dev/null +++ b/crates/squawk_fmt/tests/after/table.snap @@ -0,0 +1,51 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/table.sql +--- +table foo; +table public.foo *; +table only (public.foo); +table foo order by a desc, b asc; +/* before table */ +table + /* before only */ only /* before relation opening paren */(/* before relation */ public /* before dot */./* before name */ foo /* before relation closing paren */) +/* before order */ order /* before by */ by + /* before first expression */ a /* before desc */ desc /* before comma */, + /* before second expression */ b /* before asc */ asc/* before semicolon */; +table + a_very_long_schema_name.a_very_long_relation_name_that_forces_the_table_statement_to_wrap +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc; + +with cte as ( + select 1 +) +table cte +order by a +for update +limit 10 +offset 2 rows; +table foo fetch first 5 rows with ties; + +table + a_very_long_schema_name.a_very_long_relation_name_that_forces_the_table_statement_to_wrap +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc +for no key update of a_very_long_schema_name.a_very_long_relation_name_that_forces_the_locking_clause_to_wrap skip locked +limit a_very_long_limit_expression +offset a_very_long_offset_expression rows; + +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 */; diff --git a/crates/squawk_fmt/tests/after/transaction_control.snap b/crates/squawk_fmt/tests/after/transaction_control.snap new file mode 100644 index 00000000..3da4ede6 --- /dev/null +++ b/crates/squawk_fmt/tests/after/transaction_control.snap @@ -0,0 +1,62 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/transaction_control.sql +--- +begin; + +begin work; + +start transaction isolation level serializable, read write, deferrable; + +begin transaction isolation level repeatable read, read only, not deferrable; + +commit; + +end work; + +commit transaction and chain; + +commit and no chain; + +prepare transaction + 'a very long prepared transaction identifier used to test transaction statement line length'; + +commit prepared 'prepared_transaction'; + +rollback; + +abort work; + +rollback transaction and chain; + +rollback and no chain; + +rollback to savepoint before_changes; + +rollback work to savepoint before_changes; + +rollback prepared 'prepared_transaction'; + +savepoint before_changes; + +release savepoint before_changes; + +-- comments in every position +begin /*a*/ transaction + /*b*/ isolation /*c*/ level /*d*/ serializable /*e*/, + /*f*/ read /*g*/ write /*h*/, + /*i*/ not /*j*/ deferrable/*k*/; + +commit /*a*/ transaction /*b*/ and /*c*/ no /*d*/ chain/*e*/; + +prepare /*a*/ transaction /*b*/ 'prepared_transaction'/*c*/; + +commit /*a*/ prepared /*b*/ 'prepared_transaction'/*c*/; + +rollback /*a*/ work /*b*/ to /*c*/ savepoint /*d*/ before_changes/*e*/; + +rollback /*a*/ prepared /*b*/ 'prepared_transaction'/*c*/; + +savepoint /*a*/ before_changes/*b*/; + +release /*a*/ savepoint /*b*/ before_changes/*c*/; diff --git a/crates/squawk_fmt/tests/after/truncate.snap b/crates/squawk_fmt/tests/after/truncate.snap new file mode 100644 index 00000000..3d106bcf --- /dev/null +++ b/crates/squawk_fmt/tests/after/truncate.snap @@ -0,0 +1,21 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +assertion_line: 28 +input_file: crates/squawk_fmt/tests/before/truncate.sql +--- +truncate foo; +truncate table only foo continue identity restrict; +truncate foo *, bar restart identity cascade; + +/*before*/ +truncate /*a*/ table + /*b*/ public /*c*/./*d*/ foo /*e*/ * /*f*/, + /*g*/ bar +/*h*/ continue /*i*/ identity +/*j*/ restrict/*k*/; + +truncate table + a_very_long_schema_name.a_very_long_table_name, + another_very_long_schema_name.another_very_long_table_name +restart identity +cascade; diff --git a/crates/squawk_fmt/tests/after/unlisten.snap b/crates/squawk_fmt/tests/after/unlisten.snap new file mode 100644 index 00000000..da1333ce --- /dev/null +++ b/crates/squawk_fmt/tests/after/unlisten.snap @@ -0,0 +1,15 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/unlisten.sql +--- +unlisten events; + +unlisten *; + +unlisten + an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before unlisten */ +unlisten /* before channel */ event_channel/* before semicolon */; + +unlisten /* before star */ */* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/update.snap b/crates/squawk_fmt/tests/after/update.snap new file mode 100644 index 00000000..3c8093af --- /dev/null +++ b/crates/squawk_fmt/tests/after/update.snap @@ -0,0 +1,53 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/update.sql +--- +update foo set a = 1; + +update only (foo) as f +set a = 1, b = default +from bar +where f.id = bar.id +returning f.*; + +update foo +set + (a, b) = row(1, default), + (c, d) = (select x, y from bar), + payload.field[1][2:3] = 4; + +update a_very_long_schema_name.a_very_long_table_name +set + a_very_long_first_column_name = 'a very long replacement value', + a_very_long_second_column_name = 'another very long replacement value' +where organization_id = 12345 and status = 'active' +returning id, a_very_long_first_column_name; + +with changed as ( + update foo set a = 1 where id = 2 returning id +) +select * +from changed; + +with source as ( + select id, value from incoming +) +update foo +set value = source.value +from source +where foo.id = source.id +returning foo.id; + +/*before*/ +update /*a*/ only /*b*/(/*c*/ public /*d*/./*e*/ foo /*f*/) +/*g*/ for /*h*/ portion /*i*/ of /*j*/ valid_at /*k*/ from /*l*/ 1 /*m*/ to /*n*/ 2 /*o*/ as /*p*/ f +/*q*/ set + /*r*/ payload/*s*/ ./*t*/ field/*u*/ [/*v*/ 1 /*w*/:/*x*/ 2 /*y*/] + /*z*/ = /*aa*/ 'new' /*ab*/, + /*ac*/ (/*ad*/ a /*ae*/, /*af*/ b /*ag*/) /*ah*/ = /*ai*/ row /*aj*/( + /*ak*/ 1 /*al*/, + /*am*/ default /*an*/ + ) +/*ao*/ from /*ap*/ bar +/*aq*/ where /*ar*/ f.id = bar.id +/*as*/ returning /*at*/ f.id /*au*/, /*av*/ f.payload/*aw*/; diff --git a/crates/squawk_fmt/tests/after/vacuum.snap b/crates/squawk_fmt/tests/after/vacuum.snap new file mode 100644 index 00000000..49b0cceb --- /dev/null +++ b/crates/squawk_fmt/tests/after/vacuum.snap @@ -0,0 +1,48 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/vacuum.sql +--- +vacuum; + +vacuum records; + +vacuum full freeze verbose analyze public.records; + +vacuum full freeze verbose analyse records; + +vacuum ( + full, + freeze, + verbose, + analyze, + disable_page_skipping true, + skip_locked on, + index_cleanup auto, + truncate no, + process_main yes, + parallel 2 +) + public.records (id, name), + public.archived_records; + +vacuum (analyze, verbose, index_cleanup auto, parallel 4) + a_very_long_schema_name.an_intentionally_long_table_name ( + an_intentionally_long_column_name, + another_intentionally_long_column_name + ), + another_very_long_schema_name.another_intentionally_long_table_name; + +/* before */ +vacuum /* after vacuum */ ( + /* after left paren */ full /* before comma */, + /* after comma */ analyze /* before value */ true /* before second comma */, + /* after second comma */ index_cleanup /* before name value */ auto /* before right paren */ +) + /* before tables */ public /* before table dot */./* after table dot */ records /* before columns */ ( + /* after columns left paren */ id /* before column comma */, + /* after column comma */ name /* before columns right paren */ + ) /* before table comma */, + /* after table comma */ archived_records/* before semicolon */; + +vacuum /* before full */ full /* before freeze */ freeze /* before verbose */ verbose /* before analyse */ analyse + /* before legacy table */ records/* before legacy semicolon */; diff --git a/crates/squawk_fmt/tests/after/values.snap b/crates/squawk_fmt/tests/after/values.snap new file mode 100644 index 00000000..e310f3f6 --- /dev/null +++ b/crates/squawk_fmt/tests/after/values.snap @@ -0,0 +1,71 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/values.sql +--- +values (1, 2), (3, 4); +values (1), (2) order by column1 desc, column2 asc; +/* before values */ +values /* before first row */ ( + /* before first expression */ 1 /* before expression comma */, + /* before second expression */ 2 /* before first row closing paren */ +) /* before row comma */, +/* before second row */ ( + /* before third expression */ 3 /* before second row closing paren */ +) +/* before order */ order /* before by */ by + /* before order expression */ column1 /* before desc */ desc/* before semicolon */; +values ( + a_very_long_first_expression, + a_very_long_second_expression, + a_very_long_third_expression +), +( + a_very_long_fourth_expression, + a_very_long_fifth_expression, + a_very_long_sixth_expression +) +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc; + +with cte as ( + select 1 +) +values (1), (2) +order by 1 +for update +limit 10 +offset 2 rows; +values (1) fetch first 5 rows with ties; + +values ( + a_very_long_first_expression, + a_very_long_second_expression, + a_very_long_third_expression +), +( + a_very_long_fourth_expression, + a_very_long_fifth_expression, + a_very_long_sixth_expression +) +order by + a_very_long_first_order_expression desc, + a_very_long_second_order_expression asc +for no key update of a_very_long_relation_name skip locked +limit a_very_long_limit_expression +offset a_very_long_offset_expression rows; + +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 */; diff --git a/crates/squawk_fmt/tests/before/alter_publication.sql b/crates/squawk_fmt/tests/before/alter_publication.sql new file mode 100644 index 00000000..8634ab16 --- /dev/null +++ b/crates/squawk_fmt/tests/before/alter_publication.sql @@ -0,0 +1,15 @@ +alter publication selected_tables add table public.new_accounts, tables in schema archive; + +alter publication selected_tables drop table public.old_accounts; + +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); + +alter publication everything set (publish = 'insert, update'); + +alter publication everything owner to replication_admin; + +alter publication everything rename to all_changes; + +alter /* after alter */ publication /* before name */ commented_pub set /* before object */ table /* before table name */ public.commented, /* after comma */ current_schema /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/alter_subscription.sql b/crates/squawk_fmt/tests/before/alter_subscription.sql new file mode 100644 index 00000000..78f5bdec --- /dev/null +++ b/crates/squawk_fmt/tests/before/alter_subscription.sql @@ -0,0 +1,25 @@ +alter subscription local_sub connection 'host=otherhost dbname=publisher'; + +alter subscription local_sub server publisher_server; + +alter subscription local_sub set (slot_name = new_slot, synchronous_commit = local); + +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); + +alter subscription local_sub drop publication selected_tables with (refresh = false); + +alter subscription local_sub refresh publication with (copy_data = true); + +alter subscription local_sub enable; + +alter subscription local_sub disable; + +alter subscription local_sub skip (lsn = '0/16B6C50'); + +alter subscription local_sub owner to replication_admin; + +alter subscription local_sub rename to renamed_sub; + +alter /* after alter */ subscription /* after subscription */ renamed_sub add /* after add */ publication /* before publication name */ commented_pub, /* after publication comma */ selected_tables with /* before params */ (copy_data = true) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/analyze.sql b/crates/squawk_fmt/tests/before/analyze.sql new file mode 100644 index 00000000..713669df --- /dev/null +++ b/crates/squawk_fmt/tests/before/analyze.sql @@ -0,0 +1,11 @@ +analyze; + +analyse verbose records; + +analyze public.records (id, payload), public.archived_records; + +analyze (verbose true, skip_locked false, buffer_usage_limit '4MB') public.records; + +analyze an_intentionally_long_schema_name.an_intentionally_long_table_name_that_makes_this_statement_exceed_eighty_characters (an_intentionally_long_column_name); + +/* before analyze */ ANALYZE /* before options */ (/* before verbose */ VERBOSE /* before true */ TRUE /* before comma */, /* before skip locked */ SKIP_LOCKED /* before false */ FALSE /* before close */) /* before table */ public /* before dot */ . /* after dot */ records /* before columns */ (/* before id */ id /* before column comma */, /* before payload */ payload /* before columns close */) /* before table comma */, /* before second table */ archived_records /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/call.sql b/crates/squawk_fmt/tests/before/call.sql new file mode 100644 index 00000000..f42e4305 --- /dev/null +++ b/crates/squawk_fmt/tests/before/call.sql @@ -0,0 +1,7 @@ +CALL refresh_materialized_data(); + +call public.process_record(1, 'record', enabled => true); + +call process_a_record_with_an_intentionally_long_procedure_name(an_intentionally_long_argument_name => 'an intentionally long argument value', another_intentionally_long_argument_name => 12345); + +/* before */ CALL /* after call */ public /* before dot */ . /* after dot */ process_record /* before left paren */ (/* after left paren */ 1 /* before comma */, /* after comma */ argument_name /* before arrow */ => /* after arrow */ 'value' /* before second comma */, /* after second comma */ VARIADIC /* after variadic */ ARRAY[1, 2] /* before right paren */) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/checkpoint.sql b/crates/squawk_fmt/tests/before/checkpoint.sql new file mode 100644 index 00000000..e71613d2 --- /dev/null +++ b/crates/squawk_fmt/tests/before/checkpoint.sql @@ -0,0 +1,13 @@ +CHECKPOINT; + +checkpoint (mode fast); + +checkpoint (mode spread, flush_unlogged true); + +checkpoint (flush_unlogged false); + +checkpoint (flush_unlogged); + +checkpoint (an_intentionally_long_checkpoint_option_name an_intentionally_long_value_name, another_intentionally_long_checkpoint_option_name another_intentionally_long_value_name); + +/* before checkpoint */ CHECKPOINT /* before left paren */ (/* after left paren */ MODE /* before value */ FAST /* before comma */, /* after comma */ FLUSH_UNLOGGED /* before second value */ FALSE /* before right paren */) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/close.sql b/crates/squawk_fmt/tests/before/close.sql new file mode 100644 index 00000000..f1b4b6d3 --- /dev/null +++ b/crates/squawk_fmt/tests/before/close.sql @@ -0,0 +1,11 @@ +CLOSE ALL; + +close active_cursor; + +close "Case-Sensitive Cursor"; + +close cursor_with_an_intentionally_long_name_that_makes_this_close_statement_longer_than_eighty_characters; + +/* before */ CLOSE /* after close */ ALL /* before semicolon */; + +CLOSE /* before cursor */ cursor_name /* before cursor semicolon */; diff --git a/crates/squawk_fmt/tests/before/cluster.sql b/crates/squawk_fmt/tests/before/cluster.sql new file mode 100644 index 00000000..bcdb46e1 --- /dev/null +++ b/crates/squawk_fmt/tests/before/cluster.sql @@ -0,0 +1,15 @@ +CLUSTER; + +cluster verbose; + +cluster records; + +cluster verbose public.records using records_created_at_idx; + +cluster records_created_at_idx on public.records; + +cluster (verbose true, analyze false) a_very_long_schema_name.an_intentionally_long_table_name using a_very_long_schema_name.an_intentionally_long_index_name; + +/* before */ CLUSTER /* after cluster */ (/* after left paren */ VERBOSE /* before comma */, /* after comma */ ANALYZE /* before value */ TRUE /* before right paren */) /* before table */ public /* before table dot */ . /* after table dot */ records /* before using */ USING /* after using */ public /* before index dot */ . /* after index dot */ records_idx /* before semicolon */; + +CLUSTER /* before legacy index */ public /* before legacy index dot */ . /* after legacy index dot */ records_idx /* before on */ ON /* after on */ public /* before legacy table dot */ . /* after legacy table dot */ records /* before legacy semicolon */; diff --git a/crates/squawk_fmt/tests/before/compound_select.sql b/crates/squawk_fmt/tests/before/compound_select.sql new file mode 100644 index 00000000..ddee7df7 --- /dev/null +++ b/crates/squawk_fmt/tests/before/compound_select.sql @@ -0,0 +1,24 @@ +select 1 union select 2; + +select 1 UNION ALL select 2 INTERSECT DISTINCT select 3 EXCEPT select 4; + +(select 1) except (select 2) order by 1; + +(select 1); + +select 1 union select 2 order by 1 for update limit 10 offset 2 rows; + +select 1 union select 2 fetch first 5 rows with ties; + +select 1 union select 2 for no key update of foo, bar skip locked; + +table foo union values (1), (2); + +select /* after select */ a_very_long_first_column_name, a_very_long_second_column_name from a_very_long_first_table_name +/* before union */ union /* before all */ all +/* before rhs */ select /* rhs select */ a_very_long_first_column_name, a_very_long_second_column_name from a_very_long_second_table_name /* before semicolon */; + +select 1 /* before operator */ union /* before quantifier */ distinct /* before right select */ select 2; +select 1 -- before operator +union all -- before right select +select 2; diff --git a/crates/squawk_fmt/tests/before/copy.sql b/crates/squawk_fmt/tests/before/copy.sql new file mode 100644 index 00000000..555cdd0c --- /dev/null +++ b/crates/squawk_fmt/tests/before/copy.sql @@ -0,0 +1,11 @@ +COPY foo FROM '/tmp/foo.csv'; + +copy foo (id, name) to stdout with (format csv, header true, delimiter ',', null '', encoding 'UTF8'); + +copy (select id, a_very_long_column_name, another_very_long_column_name from a_very_long_schema_name.a_very_long_table_name) to program 'gzip > /tmp/a_very_long_output_file_name.csv' with (format csv, header on); + +copy binary foo from stdin binary freeze csv header json delimiter as ',' null as '' quote as '"' escape as '\\' encoding 'UTF8' force not null id,name force quote * force null description where id > 0; + +/* before */ COPY /* after copy */ BINARY /* after binary */ public /* before dot */ . /* after dot */ records /* before columns */ (/* after left paren */ id /* before comma */, /* after comma */ description /* before right paren */) /* before from */ FROM /* after from */ PROGRAM /* after program */ 'cat /tmp/records' /* before with */ WITH /* before options */ (/* after options left paren */ FORMAT /* before format value */ CSV /* before option comma */, /* after option comma */ HEADER /* before header value */ ON /* before second option comma */, /* after second option comma */ FORCE_NULL /* before nested options */ (/* after nested left paren */ id /* before nested comma */, /* after nested comma */ description /* before nested right paren */) /* before options right paren */) /* before where */ WHERE /* after where */ id > 0 /* before semicolon */; + +copy (/* after query left paren */ select /* after select */ id from records /* before query right paren */) to /* before stdout */ stdout; diff --git a/crates/squawk_fmt/tests/before/create_foreign_table.sql b/crates/squawk_fmt/tests/before/create_foreign_table.sql new file mode 100644 index 00000000..2136bb3b --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_foreign_table.sql @@ -0,0 +1,15 @@ +CREATE FOREIGN TABLE t() SERVER s; + +create foreign table if not exists public.remote_records ( + id bigint not null, + name text, + constraint remote_records_pkey primary key (id) +) inherits (public.base_records) server foreign_server options (schema_name 'public', table_name 'records'); + +create foreign table partition_records partition of public.records (id with options not null, name) default server foreign_server; + +create foreign table ranged_records partition of public.records (id) for values from (1) to (100) server foreign_server options (table_name 'ranged_records'); + +create foreign table an_intentionally_long_schema_name.an_intentionally_long_foreign_table_name (an_intentionally_long_column_name character varying, another_intentionally_long_column_name timestamp with time zone) server an_intentionally_long_foreign_server_name options (schema_name 'an_intentionally_long_schema_name', table_name 'an_intentionally_long_foreign_table_name'); + +/* before create */ CREATE /* before foreign */ FOREIGN /* before table */ TABLE /* before if */ IF /* before not */ NOT /* before exists */ EXISTS /* before table name */ public /* before dot */ . /* after dot */ remote_records /* before left paren */ (/* after left paren */ id /* before type */ BIGINT /* before comma */, /* after comma */ name /* before second type */ TEXT /* before right paren */) /* before server */ SERVER /* before server name */ foreign_server /* before options */ OPTIONS /* before options left paren */ (/* after options left paren */ schema_name /* before option value */ 'public' /* before option comma */, /* after option comma */ table_name /* before second option value */ 'records' /* before options right paren */) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/create_function.sql b/crates/squawk_fmt/tests/before/create_function.sql new file mode 100644 index 00000000..e8fb6bc2 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_function.sql @@ -0,0 +1,37 @@ +create function add(integer, integer) returns integer language sql as $$ select $1 + $2 $$; + +create or replace function public.add(a integer, b integer default 1) returns integer language sql immutable strict parallel safe cost 10 rows 1 security definer set search_path to public as $$ select a + b $$; + +create function get_users(p_active boolean) returns table (id bigint, name text) language plpgsql security definer set search_path = public, pg_temp as $body$ begin return query select id, name from users where active = p_active; end; $body$; + +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 $$; + +-- 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*/, /*m*/ out /*n*/ result /*o*/ text /*p*/) +/*q*/ returns /*r*/ table /*s*/ (/*t*/ id /*u*/ bigint /*v*/, /*w*/ label /*x*/ text /*y*/) +/*z*/ language /*aa*/ sql +/*ab*/ immutable +/*ac*/ strict +/*ad*/ parallel /*ae*/ safe +/*af*/ cost /*ag*/ 10 +/*ah*/ rows /*ai*/ 1 +/*aj*/ security /*ak*/ definer +/*al*/ set /*am*/ search_path /*an*/ to /*ao*/ public /*ap*/, /*aq*/ pg_temp /*ar*/ +/*as*/ reset /*at*/ all +/*au*/ support /*av*/ public /*aw*/. /*ax*/ support_fn +/*ay*/ as /*az*/ $$ select value::text $$ /*ba*/; + +create function increment(value integer) returns integer language sql begin atomic return value + 1; end; + +create function record_and_calculate(a_very_long_input_parameter_name integer, another_very_long_input_parameter_name integer) returns integer language sql begin atomic insert into function_audit_log (first_recorded_value, second_recorded_value) values (a_very_long_input_parameter_name, another_very_long_input_parameter_name); return a_very_long_input_parameter_name + another_very_long_input_parameter_name; end; + +create function commented_body(value integer) returns integer language sql /*bb*/ begin /*bc*/ atomic /*bd*/ insert /*be*/ into function_log /*bf*/ (value) /*bg*/ values /*bh*/ (value) /*bi*/; /*bj*/ return /*bk*/ value + 1 /*bl*/; /*bm*/ end /*bn*/; + +create function external_add(integer, integer) returns integer as '$libdir/example', 'external_add' language c; + +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 new file mode 100644 index 00000000..969d5ad9 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_index.sql @@ -0,0 +1,19 @@ +create index users_email_idx on users (email); + +create unique index concurrently if not exists idx_users_email on only public.users using btree (email collate public."C" text_pattern_ops desc nulls last, (lower(display_name)) asc) include (id, created_at) nulls not distinct with (fillfactor = 70, deduplicate_items = off) tablespace fastspace where active and email is not null; + +create index reservations_during_idx on reservations using gist (during); + +create index documents_search_idx on documents using gin (search_vector); + +create index long_index_name_for_testing_line_wrapping on long_schema_name.a_very_long_table_name using btree (a_very_long_column_name, another_very_long_column_name, a_third_very_long_column_name) include (a_very_long_included_column_name) nulls distinct where a_very_long_column_name is not null; + +-- comments in every position +create /*a*/ unique /*b*/ index /*c*/ concurrently /*d*/ if /*e*/ not /*f*/ exists /*g*/ idx /*h*/ on /*i*/ only /*j*/ app /*k*/. /*l*/ users +/*m*/ using /*n*/ btree +/*o*/ (/*p*/ email /*q*/ collate /*r*/ "C" /*s*/ text_pattern_ops /*t*/ desc /*u*/ nulls /*v*/ last /*w*/, /*x*/ (lower(name)) /*y*/ asc /*z*/) +/*aa*/ include /*ab*/ (/*ac*/ id /*ad*/, /*ae*/ created_at /*af*/) +/*ag*/ nulls /*ah*/ not /*ai*/ distinct +/*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*/; diff --git a/crates/squawk_fmt/tests/before/create_publication.sql b/crates/squawk_fmt/tests/before/create_publication.sql new file mode 100644 index 00000000..f66a7c9b --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_publication.sql @@ -0,0 +1,9 @@ +create publication everything for all tables, all sequences except (table audit.secret_events, internal_jobs) with (publish = 'insert, update, delete, truncate', publish_via_partition_root = true); + +create publication selected_tables for table only public.accounts (id, email) where (id > 100), table (public.orders)*, tables in schema reporting where (tenant_id = 42), current_schema; + +create publication no_tables with (publish = 'insert'); + +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_subscription.sql b/crates/squawk_fmt/tests/before/create_subscription.sql new file mode 100644 index 00000000..0b399c0d --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_subscription.sql @@ -0,0 +1,5 @@ +create subscription local_sub connection 'host=localhost port=5432 dbname=publisher user=replicator password=very_long_password' publication all_changes, selected_tables with (copy_data = true, enabled = false, streaming = parallel); + +create subscription server_sub server publisher_server publication all_changes; + +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 d7e1dd74..cbcb5922 100644 --- a/crates/squawk_fmt/tests/before/create_table.sql +++ b/crates/squawk_fmt/tests/before/create_table.sql @@ -41,3 +41,18 @@ create table t (a int) -- one ; create table a_very_long_schema_name.a_very_long_table_name (a_very_long_integer_column_name int, a_very_long_text_column_name text, a_very_long_qualified_type_column_name a_very_long_type_schema_name.a_very_long_type_name, U&"c!006fl" uescape '!' a_very_long_type_schema_name.a_very_long_type_name); + +-- column options and constraints +create table column_features (payload text storage external compression lz4 with options options (formatter 'plain', set compression 'fast', drop obsolete) collate public."C" constraint payload_required not null default 'missing' check (length(payload) > 0) unique, nullable text null, id bigint generated by default as identity (increment by 2 minvalue 1 no maxvalue start with 10 cache 20 cycle) primary key, computed bigint generated always as (id + 1) stored, parent_id bigint references public.parents(id) match full on delete cascade on update restrict); + +-- comments in every column option position +create table column_option_comments ( +/*a*/ payload /*b*/ text +/*c*/ storage /*d*/ external +/*e*/ compression /*f*/ lz4 +/*g*/ with /*h*/ options +/*i*/ options /*j*/ (/*k*/ add /*l*/ formatter /*m*/ 'x' /*n*/, /*o*/ set /*p*/ formatter /*q*/ 'y' /*r*/, /*s*/ drop /*t*/ formatter /*u*/) +/*v*/ collate /*w*/ public /*x*/ . /*y*/ "C" +/*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*/) +); diff --git a/crates/squawk_fmt/tests/before/create_table_as.sql b/crates/squawk_fmt/tests/before/create_table_as.sql new file mode 100644 index 00000000..5eed2cfb --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_table_as.sql @@ -0,0 +1,10 @@ +create table active_users as select id, name from users where active = true; + +create temporary table if not exists reporting.a_very_long_destination_table_name (a_very_long_identifier_column, another_very_long_identifier_column) using heap with (fillfactor = 70, autovacuum_enabled = true) on commit preserve rows tablespace fast as select a_very_long_identifier_column, another_very_long_identifier_column from a_very_long_source_schema_name.a_very_long_source_table_name with no data; + +create table cached_result as execute refresh_cached_result(1, 'full') with data; + +-- comments in every position +create /*a*/ temp /*b*/ table /*c*/ if /*d*/ not /*e*/ exists /*f*/ app /*g*/ . /*h*/ report /*i*/ (/*j*/ id /*k*/, /*l*/ total /*m*/) /*n*/ using /*o*/ heap /*p*/ with /*q*/ (/*r*/ fillfactor /*s*/ = /*t*/ 70 /*u*/) /*v*/ on /*w*/ commit /*x*/ preserve /*y*/ rows /*z*/ tablespace /*aa*/ fast /*ab*/ as /*ac*/ select id, total from summaries /*ad*/ with /*ae*/ no /*af*/ data /*ag*/; + +create table executed /*a*/ as /*b*/ execute /*c*/ refresh_report /*d*/ (/*e*/ 1 /*f*/, /*g*/ 'full' /*h*/) /*i*/ with /*j*/ data /*k*/; diff --git a/crates/squawk_fmt/tests/before/create_table_options.sql b/crates/squawk_fmt/tests/before/create_table_options.sql new file mode 100644 index 00000000..25def3d3 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_table_options.sql @@ -0,0 +1,35 @@ +create temporary table if not exists t (id int) inherits (parent, archive.parent) partition by hash (id) using heap with (fillfactor=70) on commit delete rows tablespace fast; + +create global temporary table global_temp (id int) on commit preserve rows; + +create local temp table local_temp (id int) on commit drop; + +create unlogged table events (id int) without oids; + +create table typed_table of public.record_type; + +create table measurements partition of events for values from (minvalue, 1) to (maxvalue, 100); + +create table statuses partition of events for values in ('new', 'ready', 'a status value which makes this create table statement longer than eighty characters'); + +create table hash_part partition of events for values with (modulus 4, remainder 0); + +create table default_part partition of events default; + +create table partitioned (a_very_long_region_column_name text, a_very_long_created_at_column_name timestamptz) partition by range (a_very_long_region_column_name collate "C", a_very_long_created_at_column_name); + +-- comments in every clause position +create /*a*/ local /*b*/ temporary /*c*/ table /*d*/ if /*e*/ not /*f*/ exists /*g*/ commented +(/*h*/ id int /*i*/) +/*j*/ inherits /*k*/ (/*l*/ parent /*m*/, /*n*/ archive.parent /*o*/) +/*p*/ partition /*q*/ by /*r*/ hash /*s*/ (/*t*/ id /*u*/) +/*v*/ using /*w*/ heap +/*x*/ with /*y*/ (fillfactor /*z*/ = /*aa*/ 70 /*ab*/) +/*ac*/ on /*ad*/ commit /*ae*/ preserve /*af*/ rows +/*ag*/ tablespace /*ah*/ fast; + +create table child /*a*/ partition /*b*/ of /*c*/ parent +/*d*/ for /*e*/ values /*f*/ from /*g*/ (/*h*/ minvalue /*i*/, /*j*/ 1 /*k*/) +/*l*/ to /*m*/ (/*n*/ maxvalue /*o*/, /*p*/ 100 /*q*/); +create table child_in partition of parent for /*a*/ values /*b*/ in /*c*/ (/*d*/ 1 /*e*/, /*f*/ 2 /*g*/); +create table child_hash partition of parent for /*a*/ values /*b*/ with /*c*/ (/*d*/ modulus /*e*/ 4 /*f*/, /*g*/ remainder /*h*/ 0 /*i*/); diff --git a/crates/squawk_fmt/tests/before/create_transform.sql b/crates/squawk_fmt/tests/before/create_transform.sql new file mode 100644 index 00000000..971b5387 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_transform.sql @@ -0,0 +1,8 @@ +create transform for t language l (from sql with function t, to sql with function g); + +create or replace transform for foo.t(10231) language l (from sql with function bar.foo.f(a text), to sql with function g); + +create transform for a_transform_type_with_a_very_long_name language a_language_with_a_very_long_name (from sql with function a_schema_with_a_long_name.a_function_with_a_very_long_name(a_parameter_with_a_long_name text), to sql with function another_function_with_a_very_long_name); + +-- comments in every position +create /*a*/ or /*b*/ replace /*c*/ transform /*d*/ for /*e*/ app /*f*/. /*g*/ custom_type /*h*/ language /*i*/ plpgsql /*j*/ (/*k*/ from /*l*/ sql /*m*/ with /*n*/ function /*o*/ app /*p*/. /*q*/ from_sql(/*r*/ integer /*s*/) /*t*/, /*u*/ to /*v*/ sql /*w*/ with /*x*/ function /*y*/ app /*z*/. /*aa*/ to_sql(/*ab*/ integer /*ac*/) /*ad*/) /*ae*/; diff --git a/crates/squawk_fmt/tests/before/create_trigger.sql b/crates/squawk_fmt/tests/before/create_trigger.sql new file mode 100644 index 00000000..8ee2d349 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_trigger.sql @@ -0,0 +1,12 @@ +create trigger update_foo_column before insert on core_recipe for each row execute procedure foo_update_trigger(); + +create or replace trigger buzz instead of insert or delete on foo.bar.buzz referencing old table as foo new table as bar for each statement when (x > 10 and b is not null) execute function x.y.z(1,2,'3'); + +create constraint trigger t after insert or delete on f from other_f deferrable initially deferred for each row execute function f(); + +create trigger bar after update of a, b, c on foo referencing new table bar old table foo for row execute procedure foo('bar'); + +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'); + +-- 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_view.sql b/crates/squawk_fmt/tests/before/create_view.sql new file mode 100644 index 00000000..6471bb26 --- /dev/null +++ b/crates/squawk_fmt/tests/before/create_view.sql @@ -0,0 +1,10 @@ +create view active_users as select id, name from users where active = true; + +create or replace temporary recursive view public.user_summary (user_id, display_name) with (security_barrier = true, check_option = local) as select id, name from public.users with local check option; + +create view a_very_long_schema_name.a_very_long_view_name (a_very_long_identifier_column, another_very_long_identifier_column, a_third_very_long_identifier_column) as select a_very_long_identifier_column, another_very_long_identifier_column, a_third_very_long_identifier_column from a_very_long_schema_name.a_very_long_table_name with cascaded check option; + +-- comments in every position +create /*a*/ or /*b*/ replace /*c*/ temp /*d*/ recursive /*e*/ view /*f*/ app /*g*/ . /*h*/ dashboard /*i*/ (/*j*/ account_id /*k*/, /*l*/ total /*m*/) /*n*/ with /*o*/ (/*p*/ security_barrier /*q*/ = /*r*/ true /*s*/, /*t*/ check_option /*u*/ = /*v*/ local /*w*/) /*x*/ as /*y*/ select /*z*/ account_id, total from summaries /*aa*/ with /*ab*/ cascaded /*ac*/ check /*ad*/ option /*ae*/; + +create view plain_check as select 1 with check option; diff --git a/crates/squawk_fmt/tests/before/deallocate.sql b/crates/squawk_fmt/tests/before/deallocate.sql new file mode 100644 index 00000000..7e9bc133 --- /dev/null +++ b/crates/squawk_fmt/tests/before/deallocate.sql @@ -0,0 +1,15 @@ +DEALLOCATE ALL; + +DEALLOCATE PREPARE ALL; + +deallocate statement_name; + +deallocate prepare statement_name; + +deallocate "Case-Sensitive Statement"; + +deallocate prepare prepared_statement_with_an_intentionally_long_name_that_makes_the_statement_longer_than_eighty_characters; + +/* before deallocate */ DEALLOCATE /* before prepare */ PREPARE /* before target */ statement_name /* before semicolon */; + +DEALLOCATE /* before all */ ALL /* before all semicolon */; diff --git a/crates/squawk_fmt/tests/before/declare.sql b/crates/squawk_fmt/tests/before/declare.sql new file mode 100644 index 00000000..29ba0dce --- /dev/null +++ b/crates/squawk_fmt/tests/before/declare.sql @@ -0,0 +1,11 @@ +DECLARE cursor_name CURSOR FOR SELECT * FROM t; + +declare c binary insensitive no scroll cursor without hold for select 1; + +declare c binary asensitive scroll cursor with hold for select 2; + +declare c cursor for (values (1) union values (2)); + +declare cursor_with_an_intentionally_long_name binary insensitive no scroll cursor without hold for select an_intentionally_long_column_name, another_intentionally_long_column_name from an_intentionally_long_table_name; + +/* before declare */ DECLARE /* before cursor name */ c /* before binary */ BINARY /* before sensitivity */ INSENSITIVE /* before no */ NO /* before scroll */ SCROLL /* before cursor keyword */ CURSOR /* before without */ WITHOUT /* before hold */ HOLD /* before for */ FOR /* before query */ SELECT /* before value */ 1 /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/delete.sql b/crates/squawk_fmt/tests/before/delete.sql new file mode 100644 index 00000000..ea5bc1f3 --- /dev/null +++ b/crates/squawk_fmt/tests/before/delete.sql @@ -0,0 +1,19 @@ +delete from foo; + +DELETE FROM foo AS f USING bar b, another_extremely_long_table_name_with_many_characters baz WHERE f.id = b.id RETURNING f.id, f.name, f.created_at; + +DELETE FROM foo * f WHERE CURRENT OF delete_cursor RETURNING WITH (OLD AS the_extremely_long_previous_row_value, NEW AS the_extremely_long_updated_row_value) the_extremely_long_previous_row_value.*; + +DELETE FROM ONLY (foo) f; + +DELETE FROM foo FOR PORTION OF valid_at FROM 1 TO 2; + +DELETE FROM foo FOR PORTION OF valid_at FROM 1 TO 2 WHERE organization_id = 12345 AND status = 'inactive' AND archived_at IS NOT NULL RETURNING id, valid_at; + +DELETE FROM foo FOR PORTION OF valid_at (1 + 2); + +WITH doomed AS (SELECT id FROM foo) DELETE FROM foo USING doomed WHERE foo.id = doomed.id; + +WITH deleted AS (DELETE FROM foo WHERE id = 1 RETURNING id) SELECT * FROM deleted; + +/*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/discard.sql b/crates/squawk_fmt/tests/before/discard.sql new file mode 100644 index 00000000..1fc75256 --- /dev/null +++ b/crates/squawk_fmt/tests/before/discard.sql @@ -0,0 +1,11 @@ +DISCARD ALL; + +DISCARD PLANS; + +DISCARD SEQUENCES; + +DISCARD TEMPORARY; + +DISCARD TEMP; + +/* before discard */ DISCARD /* first comment before target */ /* second comment before target */ /* third comment before target */ ALL /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/distinct_on.sql b/crates/squawk_fmt/tests/before/distinct_on.sql new file mode 100644 index 00000000..e3d81f5d --- /dev/null +++ b/crates/squawk_fmt/tests/before/distinct_on.sql @@ -0,0 +1,8 @@ +select distinct on (a, b) a, b from foo; +select distinct on (b) a, b from foo; + +select distinct on (a_very_long_first_distinct_expression, a_very_long_second_distinct_expression, a_very_long_third_distinct_expression) a_very_long_first_target_expression, a_very_long_second_target_expression from a_very_long_source_relation_name; + +select /* before distinct */ distinct /* before on */ on /* before opening paren */ (/* before first expression */ a /* before comma */, /* before second expression */ b /* before closing paren */) /* before target */ a /* before from */ from /* before relation */ foo; + +select distinct on (/* before empty closing paren */) 1; diff --git a/crates/squawk_fmt/tests/before/do.sql b/crates/squawk_fmt/tests/before/do.sql new file mode 100644 index 00000000..54076b10 --- /dev/null +++ b/crates/squawk_fmt/tests/before/do.sql @@ -0,0 +1,17 @@ +DO 'BEGIN NULL; END'; + +DO $$BEGIN RAISE NOTICE 'hello'; END$$; + +do language plpgsql $$begin perform refresh_materialized_view_with_an_intentionally_long_name(); end$$; + +do $$begin null; end$$ language 'plpgsql'; + +do $body$ +begin + raise notice 'hello'; +end +$body$; + +/* before */ DO /* after do */ LANGUAGE /* after language */ plpgsql /* before body */ $body$BEGIN NULL; END$body$ /* before semicolon */; + +DO /* before trailing body */ $body$BEGIN NULL; END$body$ /* before trailing language */ LANGUAGE /* before language literal */ 'plpgsql' /* before trailing semicolon */; diff --git a/crates/squawk_fmt/tests/before/drop_publication.sql b/crates/squawk_fmt/tests/before/drop_publication.sql new file mode 100644 index 00000000..37205cef --- /dev/null +++ b/crates/squawk_fmt/tests/before/drop_publication.sql @@ -0,0 +1,3 @@ +drop publication if exists all_changes, selected_tables cascade; + +drop /* after drop */ publication /* before if */ if /* before exists */ exists /* before name */ commented_pub, /* after comma */ selected_tables /* before behavior */ restrict /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/drop_subscription.sql b/crates/squawk_fmt/tests/before/drop_subscription.sql new file mode 100644 index 00000000..512c6d09 --- /dev/null +++ b/crates/squawk_fmt/tests/before/drop_subscription.sql @@ -0,0 +1,3 @@ +drop subscription if exists renamed_sub restrict; + +drop /* after drop */ subscription /* before if */ if /* before exists */ exists /* before name */ commented_sub /* before behavior */ cascade /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/empty_stmt.sql b/crates/squawk_fmt/tests/before/empty_stmt.sql new file mode 100644 index 00000000..3c4c3f9a --- /dev/null +++ b/crates/squawk_fmt/tests/before/empty_stmt.sql @@ -0,0 +1,7 @@ +; + +;; + +/* before empty statement */ ; + +; /* between empty statements */ ; diff --git a/crates/squawk_fmt/tests/before/execute.sql b/crates/squawk_fmt/tests/before/execute.sql new file mode 100644 index 00000000..39fba319 --- /dev/null +++ b/crates/squawk_fmt/tests/before/execute.sql @@ -0,0 +1,9 @@ +EXECUTE statement_name; + +execute statement_name(1, true, some_value); + +execute "Case-Sensitive Statement"; + +execute statement_name(an_intentionally_long_argument_name, another_intentionally_long_argument_name, a_third_intentionally_long_argument_name); + +/* before execute */ EXECUTE /* before statement */ statement_name /* before left paren */ (/* after left paren */ 1 /* before comma */, /* after comma */ TRUE /* before right paren */) /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/explain.sql b/crates/squawk_fmt/tests/before/explain.sql new file mode 100644 index 00000000..e3518f75 --- /dev/null +++ b/crates/squawk_fmt/tests/before/explain.sql @@ -0,0 +1,7 @@ +explain select * from records; + +explain analyze verbose update records set value = 1; + +explain (analyze true, verbose, costs false, format json) select an_intentionally_long_column_name from an_intentionally_long_table_name where an_intentionally_long_column_name > 0; + +/* before explain */ EXPLAIN /* before options */ (/* before analyze */ ANALYZE /* before value */ TRUE /* before comma */, /* before format */ FORMAT /* before json */ JSON /* before close */) /* before select */ SELECT /* before target */ 1 /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/fetch.sql b/crates/squawk_fmt/tests/before/fetch.sql new file mode 100644 index 00000000..a828afdf --- /dev/null +++ b/crates/squawk_fmt/tests/before/fetch.sql @@ -0,0 +1,35 @@ +FETCH NEXT FROM cursor_name; + +fetch prior in cursor_name; + +fetch first from cursor_name; + +fetch last from cursor_name; + +fetch absolute 10 from cursor_name; + +fetch relative -3 from cursor_name; + +fetch 10 from cursor_name; + +fetch all from cursor_name; + +fetch forward from cursor_name; + +fetch forward 10 in cursor_name; + +fetch forward all from cursor_name; + +fetch backward from cursor_name; + +fetch backward 10 from cursor_name; + +fetch backward all from cursor_name; + +fetch prior cursor_name; + +fetch next from cursor_with_an_intentionally_long_name_that_makes_this_fetch_statement_longer_than_eighty_characters; + +/* before fetch */ FETCH /* before action */ FORWARD /* before all */ ALL /* before from */ FROM /* before cursor */ cursor_name /* before semicolon */; + +FETCH /* before absolute */ ABSOLUTE /* before count */ 10 /* before in */ IN /* before second cursor */ cursor_name /* before second semicolon */; diff --git a/crates/squawk_fmt/tests/before/from.sql b/crates/squawk_fmt/tests/before/from.sql index 42856827..d0c5afd1 100644 --- a/crates/squawk_fmt/tests/before/from.sql +++ b/crates/squawk_fmt/tests/before/from.sql @@ -8,6 +8,26 @@ select * from foo /* after comma */ display_name /* before close paren */ ); select * from users tablesample bernoulli(10) repeatable (42); +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; +select * from lateral cast(a_very_long_expression_name as a_very_long_schema_name.a_very_long_type_name) as converted; +select * from collation for (foo) as collation_name; +select * from /* before lateral */ lateral /* before cast */ cast /* before opening paren */ ( /* before expression */ value /* before as */ as /* before type */ int8 /* before closing paren */ ) /* before alias */ as /* before alias name */ converted; +select * from (select 1) as selected; +select * from only lateral (select a_very_long_parenthesized_select_expression from a_very_long_parenthesized_select_relation_name) as a_very_long_parenthesized_select_alias; +select * from /* before only */ only /* before lateral */ lateral /* before opening paren */ ( /* before select */ select /* before target */ value /* before closing paren */ ) /* before alias */ as /* before alias name */ selected; +select * from (/* before relation */ foo /* before closing paren */) as parenthesized_relation; +select * from rows from (generate_series(1, 3), unnest(array[1, 2]) as (value int8)) with ordinality as generated(first_value, second_value, ordinality); +select * from /* before lateral */ lateral /* before rows */ rows /* before from */ from /* before opening paren */ ( /* before first argument */ generate_series(1, 3) /* before argument comma */, /* before second argument */ unnest(array[1, 2]) /* before as */ as /* before column list */ ( /* before column */ value /* before type */ int8 /* before column list close */ ) /* before rows close */ ) /* before with */ with /* before ordinality */ ordinality /* before alias */ as /* before alias name */ generated(value, ordinality); +select * from xmltable('/rows/row' passing doc columns id int8 path '@id' not null, ord for ordinality, value text default 'unknown' null) as parsed; +select * from lateral xmltable(xmlnamespaces('urn:a' as a, default 'urn:default'), a_very_long_xml_row_expression passing by ref a_very_long_xml_document_expression by value columns a_very_long_first_xml_column_name a_very_long_xml_column_type path a_very_long_xml_path_expression, a_very_long_ordinality_column_name for ordinality) as a_very_long_xml_table_alias; +select * from /* before lateral */ lateral /* before xmltable */ xmltable /* before opening paren */ ( /* before namespaces */ xmlnamespaces /* before namespace opening paren */ ( /* before namespace expression */ 'urn:a' /* before as */ as /* before prefix */ a /* before namespace comma */, /* before default */ default /* before default expression */ 'urn:default' /* before namespace closing paren */ ) /* before outer comma */, /* before row */ '/rows/row' /* before passing */ passing /* before first by */ by /* before ref */ ref /* before document */ doc /* before second by */ by /* before value */ value /* before columns */ columns /* before first column */ id /* before type */ int8 /* before path */ path /* before path expression */ '@id' /* before not */ not /* before null */ null /* before column comma */, /* before ordinality column */ ord /* before for */ for /* before ordinality */ ordinality /* before closing paren */ ) /* 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; +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; +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 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')) 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; select * /* before from */ from /* before item */ only /* before relation */ public /* before dot */ . /* before table */ foo @@ -15,7 +35,22 @@ select * /* before alias */ as /* before alias name */ f /* before item comma */, /* before second item */ other /* before second alias */ o; +select * from lateral a_very_long_function_name(a_very_long_first_argument_name, a_very_long_second_argument_name, a_very_long_third_argument_name) with ordinality as a_very_long_function_alias(a_very_long_value_column_alias, a_very_long_ordinality_column_alias); select * from a_very_long_schema_name.a_very_long_relation_name as a_very_long_relation_alias (a_very_long_first_column_alias, a_very_long_second_column_alias, a_very_long_third_column_alias); select * from a_very_long_relation_name a_very_long_relation_alias (a_very_long_first_column_name a_very_long_type_schema.a_very_long_first_type_name, a_very_long_second_column_name a_very_long_type_schema.a_very_long_second_type_name collate a_very_long_collation_name); select * from a_very_long_relation_name tablesample bernoulli(a_very_long_sampling_percentage_expression) repeatable (a_very_long_repeatable_seed_expression); select * from only a_very_long_schema_name.a_very_long_first_relation_name * as a_very_long_first_alias, a_very_long_schema_name.a_very_long_second_relation_name as a_very_long_second_alias; + +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 * 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; + +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; diff --git a/crates/squawk_fmt/tests/before/grant.sql b/crates/squawk_fmt/tests/before/grant.sql new file mode 100644 index 00000000..a2a1302e --- /dev/null +++ b/crates/squawk_fmt/tests/before/grant.sql @@ -0,0 +1,11 @@ +grant select, update (payload) on table public.records, archived_records to app_user; + +grant select on public.records to app_user; + +grant all privileges on all tables in schema public, audit to app_user with grant option granted by current_user; + +grant app_reader, app_writer to app_user with admin option, inherit true granted by current_user; + +grant usage on sequence public.an_intentionally_long_sequence_name_that_makes_this_statement_exceed_eighty_characters to an_intentionally_long_role_name; + +/* before grant */ GRANT /* before select */ SELECT /* before columns */ (/* before column */ payload /* before close */) /* before on */ ON /* before table */ TABLE /* before object */ public /* before dot */ . /* after dot */ records /* before to */ TO /* before role */ app_user /* before with */ WITH /* before grant option */ GRANT /* before option */ OPTION /* before granted */ GRANTED /* before by */ BY /* before grantor */ current_user /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/import_foreign_schema.sql b/crates/squawk_fmt/tests/before/import_foreign_schema.sql new file mode 100644 index 00000000..da17d695 --- /dev/null +++ b/crates/squawk_fmt/tests/before/import_foreign_schema.sql @@ -0,0 +1,7 @@ +import foreign schema remote from server foreign_server into local; + +import foreign schema remote limit to (records, users) from server foreign_server into local options (schema_name 'public'); + +import foreign schema remote except (ignored_records) from server foreign_server into an_intentionally_long_local_schema_name_that_makes_the_statement_exceed_eighty_characters; + +/* before import */ IMPORT /* before foreign */ FOREIGN /* before schema */ SCHEMA /* before remote */ remote /* before limit */ LIMIT /* before to */ TO /* before open */ (/* before table */ records /* before comma */, /* before second */ users /* before close */) /* before from */ FROM /* before server */ SERVER /* before server name */ foreign_server /* before into */ INTO /* before local */ local /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/insert.sql b/crates/squawk_fmt/tests/before/insert.sql new file mode 100644 index 00000000..f2a1e927 --- /dev/null +++ b/crates/squawk_fmt/tests/before/insert.sql @@ -0,0 +1,9 @@ +INSERT INTO foo (id, name) VALUES (1, 'one') RETURNING id; + +WITH inserted AS (INSERT INTO foo DEFAULT VALUES RETURNING id) SELECT * FROM inserted; + +WITH inserted AS (INSERT INTO foo AS f (id, name) OVERRIDING SYSTEM VALUE SELECT id, name FROM incoming ON CONFLICT ON CONSTRAINT foo_pkey DO NOTHING RETURNING id) SELECT * FROM inserted; + +WITH inserted AS (INSERT INTO a_very_long_schema_name.a_very_long_table_name (organization_identifier, extremely_long_descriptive_column_name) VALUES (123456789, 'an extremely long value that forces the insert statement to wrap across lines') ON CONFLICT (organization_identifier) DO UPDATE SET extremely_long_descriptive_column_name = excluded.extremely_long_descriptive_column_name WHERE a_very_long_table_name.organization_identifier = excluded.organization_identifier RETURNING organization_identifier, extremely_long_descriptive_column_name) SELECT organization_identifier, extremely_long_descriptive_column_name FROM inserted; + +/*before*/ WITH /*a*/ inserted /*b*/ (/*c*/ result_id /*d*/) /*e*/ AS /*f*/ NOT /*g*/ MATERIALIZED /*h*/ (/*i*/ INSERT /*j*/ INTO /*k*/ public /*l*/ . /*m*/ foo /*n*/ AS /*o*/ f /*p*/ (/*q*/ id /*r*/, /*s*/ payload /*t*/) /*u*/ OVERRIDING /*v*/ USER /*w*/ VALUE /*x*/ VALUES /*y*/ (/*z*/ 1 /*aa*/, /*ab*/ 'new' /*ac*/) /*ad*/ ON /*ae*/ CONFLICT /*af*/ (/*ag*/ id /*ah*/ COLLATE /*ai*/ "C" /*aj*/ text_ops /*ak*/) /*al*/ WHERE /*am*/ id > 0 /*an*/ DO /*ao*/ UPDATE /*ap*/ SET /*aq*/ payload /*ar*/ = /*as*/ excluded.payload /*at*/ WHERE /*au*/ foo.id = excluded.id /*av*/ RETURNING /*aw*/ WITH /*ax*/ (/*ay*/ OLD /*az*/ AS /*ba*/ old_row /*bb*/, /*bc*/ NEW /*bd*/ AS /*be*/ new_row /*bf*/) /*bg*/ new_row.id /*bh*/) /*bi*/ SELECT /*bj*/ result_id /*bk*/ FROM /*bl*/ inserted /*bm*/; diff --git a/crates/squawk_fmt/tests/before/listen.sql b/crates/squawk_fmt/tests/before/listen.sql new file mode 100644 index 00000000..93e483b6 --- /dev/null +++ b/crates/squawk_fmt/tests/before/listen.sql @@ -0,0 +1,5 @@ +listen events; + +listen an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before listen */ LISTEN /* before channel */ event_channel /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/load.sql b/crates/squawk_fmt/tests/before/load.sql new file mode 100644 index 00000000..d5927c0e --- /dev/null +++ b/crates/squawk_fmt/tests/before/load.sql @@ -0,0 +1,7 @@ +LOAD 'foo'; + +load '$libdir/extension'; + +load 'an/intentionally/long/path/to/a/postgresql/shared/library/that/makes/this/load/statement/longer/than/eighty/characters'; + +/* before load */ LOAD /* before filename */ 'filename' /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/lock.sql b/crates/squawk_fmt/tests/before/lock.sql new file mode 100644 index 00000000..9a87a3b4 --- /dev/null +++ b/crates/squawk_fmt/tests/before/lock.sql @@ -0,0 +1,25 @@ +LOCK t; + +lock table t, only b, c *; + +lock t in access share mode; + +lock t in row share mode; + +lock t in row exclusive mode; + +lock t in share update exclusive mode; + +lock t in share mode; + +lock t in share row exclusive mode; + +lock t in exclusive mode; + +lock t in access exclusive mode; + +lock table t, a *, only c in row exclusive mode nowait; + +lock table an_intentionally_long_schema_name.an_intentionally_long_table_name, another_intentionally_long_schema_name.another_intentionally_long_table_name in access exclusive mode nowait; + +/* before lock */ LOCK /* before table */ TABLE /* before first relation */ ONLY /* before first name */ public /* before dot */ . /* after dot */ records /* before comma */, /* after comma */ archived_records /* before in */ IN /* before access */ ACCESS /* before exclusive */ EXCLUSIVE /* before mode */ MODE /* before nowait */ NOWAIT /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/merge.sql b/crates/squawk_fmt/tests/before/merge.sql new file mode 100644 index 00000000..d30bb122 --- /dev/null +++ b/crates/squawk_fmt/tests/before/merge.sql @@ -0,0 +1,7 @@ +MERGE INTO target AS t USING source AS s ON t.id = s.id WHEN MATCHED THEN UPDATE SET value = s.value WHEN NOT MATCHED THEN INSERT (id, value) VALUES (s.id, s.value) RETURNING t.id; + +WITH merged AS (MERGE INTO target USING source ON target.id = source.id WHEN MATCHED AND source.deleted THEN DELETE WHEN NOT MATCHED BY SOURCE THEN DO NOTHING WHEN NOT MATCHED BY TARGET THEN INSERT DEFAULT VALUES RETURNING target.id) SELECT * FROM merged; + +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; + +/* 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/move.sql b/crates/squawk_fmt/tests/before/move.sql new file mode 100644 index 00000000..f6f153a1 --- /dev/null +++ b/crates/squawk_fmt/tests/before/move.sql @@ -0,0 +1,7 @@ +move next from cursor_name; + +move forward 10 in cursor_name; + +move absolute 100000000000000000000000000000000000000000000000000000000000000 from an_intentionally_long_cursor_name; + +/* before move */ MOVE /* before backward */ BACKWARD /* before all */ ALL /* before from */ FROM /* before cursor */ cursor_name /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/notify.sql b/crates/squawk_fmt/tests/before/notify.sql new file mode 100644 index 00000000..c85273b9 --- /dev/null +++ b/crates/squawk_fmt/tests/before/notify.sql @@ -0,0 +1,7 @@ +notify events; + +notify events, 'payload'; + +notify an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters, 'an intentionally long payload that also wraps'; + +/* before notify */ NOTIFY /* before channel */ events /* before comma */, /* before payload */ 'payload' /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/paren_select.sql b/crates/squawk_fmt/tests/before/paren_select.sql new file mode 100644 index 00000000..e4e7bdb9 --- /dev/null +++ b/crates/squawk_fmt/tests/before/paren_select.sql @@ -0,0 +1,9 @@ +(select 1) order by 1 for update limit 10 offset 2 rows; +(select 1) fetch first 5 rows with ties; +with cte as (select 1) (select x from cte); + +(select a_very_long_result_expression from a_very_long_source_relation_name) order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc for no key update of a_very_long_source_relation_name skip locked limit a_very_long_limit_expression offset a_very_long_offset_expression rows; + +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; diff --git a/crates/squawk_fmt/tests/before/prepare.sql b/crates/squawk_fmt/tests/before/prepare.sql new file mode 100644 index 00000000..43b1536a --- /dev/null +++ b/crates/squawk_fmt/tests/before/prepare.sql @@ -0,0 +1,13 @@ +PREPARE statement_name AS SELECT 1; + +prepare statement_name(int, text) as insert into t values ($1, $2); + +prepare statement_name as update t set value = 1 where id = $1; + +prepare statement_name as delete from t where id = $1; + +prepare statement_name as values (1, 'one'), (2, 'two'); + +prepare prepared_statement_with_an_intentionally_long_name(integer, character varying, timestamp with time zone, double precision) as select an_intentionally_long_column_name from an_intentionally_long_table_name; + +/* before prepare */ PREPARE /* before name */ statement_name /* before left paren */ (/* after left paren */ INT /* before comma */, /* after comma */ TEXT /* before right paren */) /* before as */ AS /* before statement */ SELECT /* before value */ $1 /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/reassign.sql b/crates/squawk_fmt/tests/before/reassign.sql new file mode 100644 index 00000000..35981f6c --- /dev/null +++ b/crates/squawk_fmt/tests/before/reassign.sql @@ -0,0 +1,5 @@ +reassign owned by alice to bob; + +reassign owned by alice, group legacy_owner, current_user to an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before reassign */ REASSIGN /* before owned */ OWNED /* before by */ BY /* before first */ alice /* before comma */, /* before second */ bob /* before to */ TO /* before new owner */ carol /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/refresh.sql b/crates/squawk_fmt/tests/before/refresh.sql new file mode 100644 index 00000000..91255d69 --- /dev/null +++ b/crates/squawk_fmt/tests/before/refresh.sql @@ -0,0 +1,5 @@ +refresh materialized view public.summary; + +refresh materialized view concurrently public.an_intentionally_long_materialized_view_name_that_makes_this_statement_longer_than_eighty_characters with no data; + +/* before refresh */ REFRESH /* before materialized */ MATERIALIZED /* before view */ VIEW /* before concurrently */ CONCURRENTLY /* before name */ public /* before dot */ . /* after dot */ summary /* before with */ WITH /* before no */ NO /* before data */ DATA /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/reindex.sql b/crates/squawk_fmt/tests/before/reindex.sql new file mode 100644 index 00000000..b176d8cb --- /dev/null +++ b/crates/squawk_fmt/tests/before/reindex.sql @@ -0,0 +1,25 @@ +REINDEX INDEX my_index; + +REINDEX TABLE my_table; + +REINDEX TABLE CONCURRENTLY my_broken_table; + +reindex database my_database; + +reindex system my_database; + +reindex schema my_schema; + +reindex (concurrently true, tablespace new_tablespace, verbose false) database concurrently my_database; + +reindex (concurrently 'off', verbose yes) table public.my_table; + +reindex (concurrently no, verbose auto) index public.my_index; + +reindex (concurrently, verbose) table public.my_table; + +reindex () table my_table; + +reindex (concurrently true, tablespace an_intentionally_long_tablespace_name, verbose false) table concurrently an_intentionally_long_schema_name.an_intentionally_long_table_name; + +/* before reindex */ REINDEX /* before left paren */ (/* after left paren */ CONCURRENTLY /* before option value */ TRUE /* before comma */, /* after comma */ TABLESPACE /* before tablespace */ new_tablespace /* before second comma */, /* after second comma */ VERBOSE /* before verbose value */ NO /* before right paren */) /* before target */ TABLE /* before concurrently */ CONCURRENTLY /* before table name */ public /* before dot */ . /* after dot */ records /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/repack.sql b/crates/squawk_fmt/tests/before/repack.sql new file mode 100644 index 00000000..1918df99 --- /dev/null +++ b/crates/squawk_fmt/tests/before/repack.sql @@ -0,0 +1,7 @@ +repack public.records; + +repack (verbose true, analyze false) public.records (id, payload), public.archived_records using index public.records_idx; + +repack an_intentionally_long_schema_name.an_intentionally_long_table_name_that_makes_this_statement_exceed_eighty_characters; + +/* before repack */ REPACK /* before options */ (/* before verbose */ VERBOSE /* before true */ TRUE /* before close */) /* before table */ public /* before dot */ . /* after dot */ records /* before columns */ (/* before column */ id /* before close */) /* before using */ USING /* before index */ INDEX /* before index name */ public /* before index dot */ . /* after index dot */ records_idx /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/reset.sql b/crates/squawk_fmt/tests/before/reset.sql new file mode 100644 index 00000000..da1d0a9e --- /dev/null +++ b/crates/squawk_fmt/tests/before/reset.sql @@ -0,0 +1,17 @@ +RESET ALL; + +reset some_config_param; + +reset foo.bar.buzz; + +reset time zone; + +reset transaction isolation level; + +reset an_intentionally_long_config_namespace.an_intentionally_long_config_group.an_intentionally_long_config_parameter_name; + +/* before reset */ RESET /* before parameter */ custom /* before first dot */ . /* after first dot */ group_name /* before second dot */ . /* after second dot */ parameter_name /* before semicolon */; + +RESET /* before transaction */ TRANSACTION /* before isolation */ ISOLATION /* before level */ LEVEL /* before transaction semicolon */; + +RESET /* before time */ TIME /* before zone */ ZONE /* before time semicolon */; diff --git a/crates/squawk_fmt/tests/before/reset_role.sql b/crates/squawk_fmt/tests/before/reset_role.sql new file mode 100644 index 00000000..dbf716e1 --- /dev/null +++ b/crates/squawk_fmt/tests/before/reset_role.sql @@ -0,0 +1,3 @@ +reset role; + +/* before reset */ RESET /* before role */ ROLE /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/reset_session_auth.sql b/crates/squawk_fmt/tests/before/reset_session_auth.sql new file mode 100644 index 00000000..dc1c9f1b --- /dev/null +++ b/crates/squawk_fmt/tests/before/reset_session_auth.sql @@ -0,0 +1,3 @@ +reset session authorization; + +/* before reset */ RESET /* before session */ SESSION /* before authorization */ AUTHORIZATION /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/revoke.sql b/crates/squawk_fmt/tests/before/revoke.sql new file mode 100644 index 00000000..e40ea28b --- /dev/null +++ b/crates/squawk_fmt/tests/before/revoke.sql @@ -0,0 +1,7 @@ +revoke select, update (payload) on table public.records from app_user; + +revoke grant option for all privileges on all tables in schema public, audit from app_user granted by current_user cascade; + +revoke admin option for app_reader, app_writer from an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters restrict; + +/* before revoke */ REVOKE /* before grant */ GRANT /* before option */ OPTION /* before for */ FOR /* before select */ SELECT /* before on */ ON /* before table */ TABLE /* before object */ public /* before dot */ . /* after dot */ records /* before from */ FROM /* before role */ app_user /* before granted */ GRANTED /* before by */ BY /* before grantor */ current_user /* before cascade */ CASCADE /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/security_label.sql b/crates/squawk_fmt/tests/before/security_label.sql new file mode 100644 index 00000000..07354e5d --- /dev/null +++ b/crates/squawk_fmt/tests/before/security_label.sql @@ -0,0 +1,7 @@ +security label on table public.records is 'system_u:object_r:postgresql_db_t:s0'; + +security label for selinux on materialized view public.an_intentionally_long_materialized_view_name_that_makes_this_statement_exceed_eighty_characters is null; + +security label on function public.process_record(bigint, text) is 'trusted'; + +/* before security */ SECURITY /* before label */ LABEL /* before for */ FOR /* before provider */ selinux /* before on */ ON /* before foreign */ FOREIGN /* before table */ TABLE /* before name */ public /* before dot */ . /* after dot */ records /* before is */ IS /* before value */ 'trusted' /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/select_clauses.sql b/crates/squawk_fmt/tests/before/select_clauses.sql new file mode 100644 index 00000000..cee7464c --- /dev/null +++ b/crates/squawk_fmt/tests/before/select_clauses.sql @@ -0,0 +1,9 @@ +with cte as (select 1) select x from cte where x > 0 group by x having count(*) > 0 window win as (partition by x) order by x for update limit 10 offset 2 rows; + +select x from foo fetch first 5 rows with ties; + +with a_very_long_common_table_expression_name as (select a_very_long_source_column_name from a_very_long_source_relation_name) select a_very_long_result_column_name from a_very_long_common_table_expression_name where a_very_long_filter_column_name > a_very_long_filter_threshold_value group by a_very_long_result_column_name having count(*) > a_very_long_having_threshold_value window a_very_long_window_name as (partition by a_very_long_partition_column_name order by a_very_long_order_column_name) order by a_very_long_result_column_name desc for no key update of a_very_long_common_table_expression_name skip locked limit a_very_long_limit_expression offset a_very_long_offset_expression rows; + +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; diff --git a/crates/squawk_fmt/tests/before/select_expr.sql b/crates/squawk_fmt/tests/before/select_expr.sql index 6f2c5340..dd124f5b 100644 --- a/crates/squawk_fmt/tests/before/select_expr.sql +++ b/crates/squawk_fmt/tests/before/select_expr.sql @@ -60,6 +60,9 @@ select 3 = SOME ( ARRAY [ 3 ] ), 4 /* before op */ = /* before any */ ANY /* before opening paren */ ( /* before expr */ ARRAY [ 4 ] /* before closing paren */ ) /* after any */, EXISTS ( SELECT 1 FROM things ), + EXISTS((SELECT 1)), + EXISTS(TABLE things), + 5 = ANY(VALUES (1), (5)), a_very_long_any_comparison_expression = ANY(a_very_long_any_input_expression_that_forces_wrapping), a_very_long_all_comparison_expression = ALL(SELECT a_very_long_all_select_expression FROM a_very_long_all_relation_name), a_very_long_some_comparison_expression = SOME(a_very_long_some_input_expression), @@ -136,6 +139,9 @@ select 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), JSON_ARRAY(SELECT a_very_long_json_select_expression_that_forces_the_json_select_format_node_to_wrap FORMAT JSON ABSENT ON NULL RETURNING a_very_long_json_select_return_type), + JSON_ARRAY((SELECT 1)), + JSON_ARRAY(TABLE things), + JSON_ARRAY(VALUES (1), (2)), JSON_ARRAY /* before opening paren */ (/* before first */ 1 /* before comma */, /* before second */ 2 /* before format */ FORMAT /* before json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before returning */ RETURNING /* before type */ jsonb /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), JSON_ARRAY(/* before select */ SELECT /* before target */ x /* before format */ FORMAT /* before json */ JSON /* before absent */ ABSENT /* before on */ ON /* before null */ NULL /* before returning */ RETURNING /* before type */ jsonb /* before closing paren */), JSON_EXISTS(doc, '$.a'), @@ -246,8 +252,18 @@ select ( 1 + 2 ), ( ( 1 ) ), ( /* before expr */ 1 /* before closing paren */ ), + ( select 1 ), + /* 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 ), + ( table foo order by a desc, b asc ), + ( /* before table */ table /* before only */ only /* before relation opening paren */ ( /* before relation */ public /* before dot */ . /* before name */ foo /* before relation closing paren */ ) /* before outer closing paren */ ), + (table a_very_long_schema_name.a_very_long_relation_name_that_forces_parenthesized_table_wrapping), + ( values (1, 2), (3, 4) ), + (values (a_very_long_first_parenthesized_value_expression, a_very_long_second_parenthesized_value_expression), (a_very_long_third_parenthesized_value_expression, a_very_long_fourth_parenthesized_value_expression)), (a_very_long_parenthesized_expression + a_second_very_long_parenthesized_expression), -- postfix expr + 'x' at /* between at and local */ local, 1 isnull, 2 notnull, x is json, @@ -267,6 +283,8 @@ select x is not json value, 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, + 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, -- prefix expr @@ -296,3 +314,8 @@ select ( /* before first */ 1 /* before comma */ , /* before second */ 2 /* before closing paren */ ); select a_very_long_function_name(first_very_long_argument_name, second_very_long_argument_name, third_very_long_argument_name); + +select +json_object( + 'a': 1, 'b' value 2 format json + null on null with unique keys returning jsonb format json); diff --git a/crates/squawk_fmt/tests/before/select_into.sql b/crates/squawk_fmt/tests/before/select_into.sql new file mode 100644 index 00000000..ba7441de --- /dev/null +++ b/crates/squawk_fmt/tests/before/select_into.sql @@ -0,0 +1,9 @@ +select 1 into foo; +select a, b into temporary table public.foo from bar; +select 1 into unlogged foo; +/* before select */ select /* before distinct */ distinct /* before first target */ a /* before target comma */, /* before second target */ b /* before into */ into /* before local */ local /* before temporary */ temporary /* before table */ table /* before schema */ public /* before dot */ . /* before table name */ foo /* before from */ from /* before relation */ bar /* before group */ group /* before by */ by /* before group expression */ a /* before order */ order /* before order by */ by /* before order expression */ b /* before desc */ desc /* before semicolon */; +select a_very_long_first_select_into_expression, a_very_long_second_select_into_expression, a_very_long_third_select_into_expression into unlogged a_very_long_schema_name.a_very_long_select_into_table_name from a_very_long_select_into_source_relation order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc; + +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; diff --git a/crates/squawk_fmt/tests/before/set.sql b/crates/squawk_fmt/tests/before/set.sql new file mode 100644 index 00000000..48c6636d --- /dev/null +++ b/crates/squawk_fmt/tests/before/set.sql @@ -0,0 +1,35 @@ +SET search_path TO myschema, public; + +set session search_path = public; + +set local work_mem to '64MB'; + +set foo from current; + +set foo = default; + +set foo to null; + +set foo to a, 10.0, 1, 'foo', true, false; + +set schema 'my_schema'; + +set catalog 'my_database'; + +set xml option document; + +set xml option content; + +set time zone 'America/Los_Angeles'; + +set time zone default; + +set time zone local; + +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 */; + +SET /* before time */ TIME /* before zone */ ZONE /* before timezone value */ DEFAULT /* before timezone semicolon */; + +SET /* before xml */ XML /* before option */ OPTION /* before document */ DOCUMENT /* before xml semicolon */; diff --git a/crates/squawk_fmt/tests/before/set_constraints.sql b/crates/squawk_fmt/tests/before/set_constraints.sql new file mode 100644 index 00000000..6b734069 --- /dev/null +++ b/crates/squawk_fmt/tests/before/set_constraints.sql @@ -0,0 +1,9 @@ +set constraints all deferred; + +set constraints first_constraint, public.second_constraint immediate; + +set constraints an_intentionally_long_schema_name.an_intentionally_long_constraint_name, another_intentionally_long_constraint_name deferred; + +/* before set */ SET /* before constraints */ CONSTRAINTS /* before first name */ first_constraint /* before comma */, /* before second name */ public /* before dot */ . /* after dot */ second_constraint /* before timing */ IMMEDIATE /* before semicolon */; + +SET /* before constraints */ CONSTRAINTS /* before all */ ALL /* before deferred */ DEFERRED /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/set_role.sql b/crates/squawk_fmt/tests/before/set_role.sql new file mode 100644 index 00000000..fd541b63 --- /dev/null +++ b/crates/squawk_fmt/tests/before/set_role.sql @@ -0,0 +1,13 @@ +set role app_user; + +set local role none; + +set session role current_user; + +set role group legacy_user; + +set role 'literal role'; + +set session role an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before set */ SET /* before scope */ LOCAL /* before role */ ROLE /* before target */ GROUP /* before role name */ legacy_user /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/set_session_auth.sql b/crates/squawk_fmt/tests/before/set_session_auth.sql new file mode 100644 index 00000000..eccadfa7 --- /dev/null +++ b/crates/squawk_fmt/tests/before/set_session_auth.sql @@ -0,0 +1,11 @@ +set session authorization app_user; + +set local session authorization default; + +set session session authorization current_role; + +set session authorization 'literal role'; + +set session authorization an_intentionally_long_role_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before set */ SET /* before scope */ LOCAL /* before session */ SESSION /* before authorization */ AUTHORIZATION /* before target */ GROUP /* before role name */ legacy_user /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/set_transaction.sql b/crates/squawk_fmt/tests/before/set_transaction.sql new file mode 100644 index 00000000..b540809d --- /dev/null +++ b/crates/squawk_fmt/tests/before/set_transaction.sql @@ -0,0 +1,15 @@ +set transaction isolation level serializable; + +set transaction isolation level repeatable read, read write, not deferrable; + +set session characteristics as transaction isolation level read committed, read only, deferrable; + +set transaction snapshot '00000003-0000001B-1'; + +set session characteristics as transaction isolation level serializable, read write, not deferrable; + +/* before set */ SET /* before transaction */ TRANSACTION /* before first mode */ ISOLATION /* before level */ LEVEL /* before serializable */ SERIALIZABLE /* before comma */, /* before read */ READ /* before only */ ONLY /* before second comma */, /* before not */ NOT /* before deferrable */ DEFERRABLE /* before semicolon */; + +SET /* before session */ SESSION /* before characteristics */ CHARACTERISTICS /* before as */ AS /* before transaction */ TRANSACTION /* before mode */ READ /* before write */ WRITE /* before semicolon */; + +SET /* before transaction */ TRANSACTION /* before snapshot */ SNAPSHOT /* before literal */ '00000003-0000001B-1' /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/show.sql b/crates/squawk_fmt/tests/before/show.sql new file mode 100644 index 00000000..14e81d3c --- /dev/null +++ b/crates/squawk_fmt/tests/before/show.sql @@ -0,0 +1,15 @@ +show all; + +show work_mem; + +show custom.an_intentionally_long_config_group.an_intentionally_long_parameter_name_that_exceeds_eighty_characters; + +show time zone; + +show transaction isolation level; + +show session authorization; + +/* before show */ SHOW /* before parameter */ custom /* before dot */ . /* after dot */ parameter /* before semicolon */; + +SHOW /* before transaction */ TRANSACTION /* before isolation */ ISOLATION /* before level */ LEVEL /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/table.sql b/crates/squawk_fmt/tests/before/table.sql new file mode 100644 index 00000000..a9692d1c --- /dev/null +++ b/crates/squawk_fmt/tests/before/table.sql @@ -0,0 +1,15 @@ +table foo; +table public.foo *; +table only (public.foo); +table foo order by a desc, b asc; +/* before table */ table /* before only */ only /* before relation opening paren */ ( /* before relation */ public /* before dot */ . /* before name */ foo /* before relation closing paren */ ) /* before order */ order /* before by */ by /* before first expression */ a /* before desc */ desc /* before comma */, /* before second expression */ b /* before asc */ asc /* before semicolon */; +table a_very_long_schema_name.a_very_long_relation_name_that_forces_the_table_statement_to_wrap order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc; + +with cte as (select 1) table cte order by a for update limit 10 offset 2 rows; +table foo fetch first 5 rows with ties; + +table a_very_long_schema_name.a_very_long_relation_name_that_forces_the_table_statement_to_wrap order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc for no key update of a_very_long_schema_name.a_very_long_relation_name_that_forces_the_locking_clause_to_wrap skip locked limit a_very_long_limit_expression offset a_very_long_offset_expression rows; + +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 */; diff --git a/crates/squawk_fmt/tests/before/transaction_control.sql b/crates/squawk_fmt/tests/before/transaction_control.sql new file mode 100644 index 00000000..8bfcef94 --- /dev/null +++ b/crates/squawk_fmt/tests/before/transaction_control.sql @@ -0,0 +1,54 @@ +begin; + +begin work; + +start transaction isolation level serializable, read write, deferrable; + +begin transaction isolation level repeatable read, read only, not deferrable; + +commit; + +end work; + +commit transaction and chain; + +commit and no chain; + +prepare transaction 'a very long prepared transaction identifier used to test transaction statement line length'; + +commit prepared 'prepared_transaction'; + +rollback; + +abort work; + +rollback transaction and chain; + +rollback and no chain; + +rollback to savepoint before_changes; + +rollback work to savepoint before_changes; + +rollback prepared 'prepared_transaction'; + +savepoint before_changes; + +release savepoint before_changes; + +-- comments in every position +begin /*a*/ transaction /*b*/ isolation /*c*/ level /*d*/ serializable /*e*/, /*f*/ read /*g*/ write /*h*/, /*i*/ not /*j*/ deferrable /*k*/; + +commit /*a*/ transaction /*b*/ and /*c*/ no /*d*/ chain /*e*/; + +prepare /*a*/ transaction /*b*/ 'prepared_transaction' /*c*/; + +commit /*a*/ prepared /*b*/ 'prepared_transaction' /*c*/; + +rollback /*a*/ work /*b*/ to /*c*/ savepoint /*d*/ before_changes /*e*/; + +rollback /*a*/ prepared /*b*/ 'prepared_transaction' /*c*/; + +savepoint /*a*/ before_changes /*b*/; + +release /*a*/ savepoint /*b*/ before_changes /*c*/; diff --git a/crates/squawk_fmt/tests/before/truncate.sql b/crates/squawk_fmt/tests/before/truncate.sql new file mode 100644 index 00000000..5c6985ae --- /dev/null +++ b/crates/squawk_fmt/tests/before/truncate.sql @@ -0,0 +1,7 @@ +truncate foo; +TRUNCATE TABLE ONLY foo CONTINUE IDENTITY RESTRICT; +truncate foo *, bar RESTART IDENTITY CASCADE; + +/*before*/ TRUNCATE /*a*/ TABLE /*b*/ public /*c*/ . /*d*/ foo /*e*/ * /*f*/, /*g*/ bar /*h*/ CONTINUE /*i*/ IDENTITY /*j*/ RESTRICT /*k*/; + +TRUNCATE TABLE a_very_long_schema_name.a_very_long_table_name, another_very_long_schema_name.another_very_long_table_name RESTART IDENTITY CASCADE; diff --git a/crates/squawk_fmt/tests/before/unlisten.sql b/crates/squawk_fmt/tests/before/unlisten.sql new file mode 100644 index 00000000..60c6378e --- /dev/null +++ b/crates/squawk_fmt/tests/before/unlisten.sql @@ -0,0 +1,9 @@ +unlisten events; + +unlisten *; + +unlisten an_intentionally_long_channel_name_that_makes_this_statement_longer_than_eighty_characters; + +/* before unlisten */ UNLISTEN /* before channel */ event_channel /* before semicolon */; + +UNLISTEN /* before star */ * /* before semicolon */; diff --git a/crates/squawk_fmt/tests/before/update.sql b/crates/squawk_fmt/tests/before/update.sql new file mode 100644 index 00000000..5e0561b1 --- /dev/null +++ b/crates/squawk_fmt/tests/before/update.sql @@ -0,0 +1,13 @@ +update foo set a = 1; + +UPDATE ONLY (foo) AS f SET a = 1, b = DEFAULT FROM bar WHERE f.id = bar.id RETURNING f.*; + +UPDATE foo SET (a, b) = ROW (1, DEFAULT), (c, d) = (SELECT x, y FROM bar), payload.field[1][2:3] = 4; + +UPDATE a_very_long_schema_name.a_very_long_table_name SET a_very_long_first_column_name = 'a very long replacement value', a_very_long_second_column_name = 'another very long replacement value' WHERE organization_id = 12345 AND status = 'active' RETURNING id, a_very_long_first_column_name; + +WITH changed AS (UPDATE foo SET a = 1 WHERE id = 2 RETURNING id) SELECT * FROM changed; + +WITH source AS (SELECT id, value FROM incoming) UPDATE foo SET value = source.value FROM source WHERE foo.id = source.id RETURNING foo.id; + +/*before*/ UPDATE /*a*/ ONLY /*b*/ (/*c*/ public /*d*/ . /*e*/ foo /*f*/) /*g*/ FOR /*h*/ PORTION /*i*/ OF /*j*/ valid_at /*k*/ FROM /*l*/ 1 /*m*/ TO /*n*/ 2 /*o*/ AS /*p*/ f /*q*/ SET /*r*/ payload /*s*/ . /*t*/ field /*u*/ [/*v*/ 1 /*w*/ : /*x*/ 2 /*y*/] /*z*/ = /*aa*/ 'new' /*ab*/, /*ac*/ (/*ad*/ a /*ae*/, /*af*/ b /*ag*/) /*ah*/ = /*ai*/ ROW /*aj*/ (/*ak*/ 1 /*al*/, /*am*/ DEFAULT /*an*/) /*ao*/ FROM /*ap*/ bar /*aq*/ WHERE /*ar*/ f.id = bar.id /*as*/ RETURNING /*at*/ f.id /*au*/, /*av*/ f.payload /*aw*/; diff --git a/crates/squawk_fmt/tests/before/vacuum.sql b/crates/squawk_fmt/tests/before/vacuum.sql new file mode 100644 index 00000000..6cb059b8 --- /dev/null +++ b/crates/squawk_fmt/tests/before/vacuum.sql @@ -0,0 +1,15 @@ +VACUUM; + +vacuum records; + +vacuum full freeze verbose analyze public.records; + +vacuum full freeze verbose analyse records; + +vacuum (full, freeze, verbose, analyze, disable_page_skipping true, skip_locked on, index_cleanup auto, truncate no, process_main yes, parallel 2) public.records (id, name), public.archived_records; + +vacuum (analyze, verbose, index_cleanup auto, parallel 4) a_very_long_schema_name.an_intentionally_long_table_name (an_intentionally_long_column_name, another_intentionally_long_column_name), another_very_long_schema_name.another_intentionally_long_table_name; + +/* before */ VACUUM /* after vacuum */ (/* after left paren */ FULL /* before comma */, /* after comma */ ANALYZE /* before value */ TRUE /* before second comma */, /* after second comma */ INDEX_CLEANUP /* before name value */ AUTO /* before right paren */) /* before tables */ public /* before table dot */ . /* after table dot */ records /* before columns */ (/* after columns left paren */ id /* before column comma */, /* after column comma */ name /* before columns right paren */) /* before table comma */, /* after table comma */ archived_records /* before semicolon */; + +VACUUM /* before full */ FULL /* before freeze */ FREEZE /* before verbose */ VERBOSE /* before analyse */ ANALYSE /* before legacy table */ records /* before legacy semicolon */; diff --git a/crates/squawk_fmt/tests/before/values.sql b/crates/squawk_fmt/tests/before/values.sql new file mode 100644 index 00000000..69a6f09a --- /dev/null +++ b/crates/squawk_fmt/tests/before/values.sql @@ -0,0 +1,13 @@ +values (1, 2), (3, 4); +values (1), (2) order by column1 desc, column2 asc; +/* before values */ values /* before first row */ ( /* before first expression */ 1 /* before expression comma */, /* before second expression */ 2 /* before first row closing paren */ ) /* before row comma */, /* before second row */ ( /* before third expression */ 3 /* before second row closing paren */ ) /* before order */ order /* before by */ by /* before order expression */ column1 /* before desc */ desc /* before semicolon */; +values (a_very_long_first_expression, a_very_long_second_expression, a_very_long_third_expression), (a_very_long_fourth_expression, a_very_long_fifth_expression, a_very_long_sixth_expression) order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc; + +with cte as (select 1) values (1), (2) order by 1 for update limit 10 offset 2 rows; +values (1) fetch first 5 rows with ties; + +values (a_very_long_first_expression, a_very_long_second_expression, a_very_long_third_expression), (a_very_long_fourth_expression, a_very_long_fifth_expression, a_very_long_sixth_expression) order by a_very_long_first_order_expression desc, a_very_long_second_order_expression asc for no key update of a_very_long_relation_name skip locked limit a_very_long_limit_expression offset a_very_long_offset_expression rows; + +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 */; diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 572d5d3a..4ef1a39e 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -300,6 +300,8 @@ fn hover_literal(literal: &ast::Literal) -> Option { LitKind::IntNumber(_) => return None, LitKind::Null(_) => return None, LitKind::NumericNumber(_) => return None, + LitKind::Off(_) => return None, + LitKind::On(_) => return None, LitKind::PositionalParam(_) => return None, LitKind::True(_) => return None, }; diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index dd19d9ae..96d92770 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -10902,11 +10902,11 @@ impl ExprFromItem { support::child(&self.syntax) } #[inline] - pub fn cast_expr(&self) -> Option { + pub fn call_expr(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn collation_for_fn(&self) -> Option { + pub fn cast_expr(&self) -> Option { support::child(&self.syntax) } #[inline] diff --git a/crates/squawk_syntax/src/ast/node_ext.rs b/crates/squawk_syntax/src/ast/node_ext.rs index 1ec02abb..bd259d47 100644 --- a/crates/squawk_syntax/src/ast/node_ext.rs +++ b/crates/squawk_syntax/src/ast/node_ext.rs @@ -55,6 +55,8 @@ pub enum LitKind { NationalString(SyntaxToken), Null(SyntaxToken), NumericNumber(SyntaxToken), + Off(SyntaxToken), + On(SyntaxToken), PositionalParam(SyntaxToken), String(SyntaxToken), True(SyntaxToken), @@ -83,6 +85,8 @@ impl ast::Literal { SyntaxKind::NATIONAL_STRING => LitKind::NationalString(token), SyntaxKind::NULL_KW => LitKind::Null(token), SyntaxKind::NUMERIC_NUMBER => LitKind::NumericNumber(token), + SyntaxKind::OFF_KW => LitKind::Off(token), + SyntaxKind::ON_KW => LitKind::On(token), SyntaxKind::POSITIONAL_PARAM => LitKind::PositionalParam(token), SyntaxKind::STRING => LitKind::String(token), SyntaxKind::TRUE_KW => LitKind::True(token), @@ -434,6 +438,29 @@ impl ast::FieldExpr { } } +impl ast::IndexAccessor { + #[inline] + pub fn index(&self) -> Option { + support::child(self.syntax()) + } +} + +impl ast::SliceAccessor { + #[inline] + pub fn start(&self) -> Option { + let colon = self.colon_token()?; + support::children(self.syntax()) + .find(|expr: &ast::Expr| expr.syntax().text_range().end() <= colon.text_range().start()) + } + + #[inline] + pub fn end(&self) -> Option { + let colon = self.colon_token()?; + support::children(self.syntax()) + .find(|expr: &ast::Expr| expr.syntax().text_range().start() >= colon.text_range().end()) + } +} + impl ast::IndexExpr { #[inline] pub fn base(&self) -> Option { @@ -882,6 +909,26 @@ impl ast::JsonNullOnNull { } } +impl ast::JsonTable { + pub fn document_expr(&self) -> Option { + support::children(self.syntax()).next() + } + + pub fn path_expr(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + +impl ast::JsonTablePlanJoin { + pub fn lhs(&self) -> Option { + support::children(self.syntax()).next() + } + + pub fn rhs(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + impl ast::JsonExistsFn { #[inline] pub fn document(&self) -> Option { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index a45ee6a3..4b345993 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -2968,7 +2968,7 @@ XmlTableFromItem = 'lateral'? XmlTable alias:FromAlias? ExprFromItem = - 'lateral'? (CastExpr | CollationForFn) alias:FromAlias? + 'lateral'? (CastExpr | CallExpr) alias:FromAlias? RowsFromItem = 'lateral'? 'rows' 'from' '(' (RowsFromArg (',' RowsFromArg)*) ')' WithOrdinality? alias:FromAlias? diff --git a/playground/src/App.tsx b/playground/src/App.tsx index da8b27b4..6ec8d83a 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -51,6 +51,8 @@ const SETTINGS = { value: DEFAULT_CONTENT, language: "pgsql", tabSize: 2, + insertSpaces: true, + detectIndentation: false, theme: "squawk-dark", minimap: { enabled: false }, automaticLayout: true,