diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index bfdd5c2b..85304496 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -48,29 +48,31 @@ fn build_source_file(source_file: &ast::SourceFile) -> 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())) + .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( - Doc::line_or_nil() - .append(Doc::list( - Itertools::intersperse( - create_table - .table_arg_list() - .unwrap() - .args() - .map(build_table_arg), - Doc::text(",").append(Doc::hard_line()), - ) - .collect(), - )) - .nest(2) - .append(Doc::line_or_nil()) - .group(), + wrap_body(Doc::list( + Itertools::intersperse( + arg_list.args().map(build_table_arg), + Doc::text(",").append(Doc::hard_line()), + ) + .collect(), + )) + .group(), ) .append(Doc::text(")")); @@ -156,506 +158,3165 @@ fn build_table_arg<'a>(arg: ast::TableArg) -> Doc<'a> { doc } ast::TableArg::LikeClause(like_clause) => build_like_clause(like_clause), - ast::TableArg::TableConstraint(_table_constraint) => todo!(), + ast::TableArg::TableConstraint(table_constraint) => { + build_table_constraint(table_constraint.clone()) + } }); doc.append(trailing_comments(arg.syntax())) } -fn build_like_clause<'a>(like_clause: &ast::LikeClause) -> Doc<'a> { - let mut doc = Doc::text("like"); +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), + } +} - if let Some(relation_name) = like_clause.relation_name_ref() { +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(relation_name.syntax())); - if let Some(path) = relation_name.path_ref() { - doc = doc.append(build_path_ref(&path)); - } + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } + doc.append(Doc::space()) +} - 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()); +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("(")); - doc + 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_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_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(property.syntax())) - .append(build_keyword_node(property.syntax())); + .append(leading_comments_token(&key)) + .append(Doc::text("key")); } - doc + 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_select_doc<'a>(select: &ast::Select) -> Doc<'a> { - let mut doc = Doc::text("select").append(Doc::line_or_space()); +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() +} - 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 => (), - } - if let Some(target_list) = select_clause.target_list() { - doc = doc.append(leading_comments(target_list.syntax())); +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::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(index.syntax())) + .append(build_path_ref(&path)); } } + doc +} - if let Some(from) = select.from_clause() { - doc = doc.append( - Doc::line_or_space() - .append(leading_comments(from.syntax())) - .append(build_from_clause(from)), - ); +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(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)); - } - doc = doc.append(group_doc); + 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 +} - doc = doc.append(build_semicolon(select.semicolon_token())); +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(), + ) +} - 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_from_clause<'a>(from: ast::FromClause) -> Doc<'a> { - if from.join_exprs().next().is_some() { - todo!("joins are not supported yet") +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() +} - 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(); +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::text("from").append(Doc::space()).append( - Doc::list( - Itertools::intersperse( - from_items.into_iter(), - Doc::text(",").append(Doc::line_or_space()), - ) - .collect(), - ) - .nest(2), - ) +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_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") +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(".")); } - ast::FromItem::RowsFromItem(_) => todo!("rows from items are not supported yet"), - ast::FromItem::GraphTableFromItem(_) => { - todo!("graph_table from items are not supported yet") + if let Some(name) = option.name() { + item = item + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } - ast::FromItem::JsonTableFromItem(_) => { - todo!("json_table from items are not supported yet") + if let Some(eq) = option.eq_token() { + item = item + .append(Doc::space()) + .append(leading_comments_token(&eq)) + .append(Doc::text("=")); } - ast::FromItem::XmlTableFromItem(_) => { - todo!("xmltable from items are not supported yet") + 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_relation_from_item<'a>(relation: ast::RelationFromItem) -> Doc<'a> { - let mut doc = if relation.only_token().is_some() { - Doc::text("only").append(Doc::space()) +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() - }; + } +} - if let Some(name) = relation.relation_name_ref() { - doc = doc.append(leading_comments(name.syntax())); - if let Some(path) = name.path_ref() { - doc = doc.append(build_path_ref(&path)); - } +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(star) = relation.star_token() { + if let Some(token) = tablespace.tablespace_token() { doc = doc .append(Doc::space()) - .append(leading_comments_token(&star)) - .append(Doc::text("*")); + .append(leading_comments_token(&token)) + .append(Doc::text("tablespace")); } - if let Some(tablesample) = relation.tablesample_clause() { + if let Some(name) = tablespace.tablespace_ref() { doc = doc .append(Doc::space()) - .append(leading_comments(tablesample.syntax())) - .append(build_tablesample_clause(tablesample)); + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); } - doc.append(build_from_alias(relation.alias())) + doc } -fn build_tablesample_clause<'a>(tablesample: ast::TablesampleClause) -> Doc<'a> { - let mut doc = Doc::text("tablesample").append(Doc::space()); - if let Some(call) = tablesample.call_expr() { +fn append_constraint_options<'a>( + mut doc: Doc<'a>, + options: impl Iterator, +) -> Doc<'a> { + for option in options { doc = doc - .append(leading_comments(call.syntax())) - .append(build_call_expr(call)); + .append(Doc::line_or_space()) + .append(leading_comments(option.syntax())) + .append(build_keyword_node(option.syntax())); } - if let Some(repeatable) = tablesample.repeatable_clause() { + 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(repeatable.syntax())) - .append(Doc::text("repeatable")); - if let Some(l_paren) = repeatable.l_paren_token() { - doc = doc.append(comments_before(l_paren)); - } - doc = doc.append(Doc::text("(")); - if let Some(expr) = repeatable.expr() { + .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_select_doc<'a>(select: &ast::Select) -> Doc<'a> { + build_select_doc_ungrouped(select).group() +} + +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 => (), + } + if let Some(target_list) = select_clause.target_list() { + doc = doc.append(leading_comments(target_list.syntax())); + doc = doc + .append(Doc::list( + Itertools::intersperse( + target_list.targets().flat_map(build_target), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + )) + .nest(2); + } + } + 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)); + } + doc = doc.append(group_doc); + } + + doc = doc.append(build_semicolon(select.semicolon_token())); + + doc +} + +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()), + ) + .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::FromItem::XmlTableFromItem(_) => { + todo!("xmltable from items are not supported yet") + } + } +} + +fn build_graph_table_from_item<'a>(item: ast::GraphTableFromItem) -> 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(graph_table) = item.graph_table_fn() { + doc = doc + .append(leading_comments(graph_table.syntax())) + .append(build_graph_table_fn(graph_table)); + } + doc.append(build_from_alias(item.alias())) +} + +fn build_relation_from_item<'a>(relation: ast::RelationFromItem) -> Doc<'a> { + let mut doc = if relation.only_token().is_some() { + Doc::text("only").append(Doc::space()) + } else { + Doc::nil() + }; + + if let Some(name) = relation.relation_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(star) = relation.star_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } + if let Some(tablesample) = relation.tablesample_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(tablesample.syntax())) + .append(build_tablesample_clause(tablesample)); + } + doc.append(build_from_alias(relation.alias())) +} + +fn build_tablesample_clause<'a>(tablesample: ast::TablesampleClause) -> Doc<'a> { + let mut doc = Doc::text("tablesample").append(Doc::space()); + if let Some(call) = tablesample.call_expr() { + doc = doc + .append(leading_comments(call.syntax())) + .append(build_call_expr_with_spacing(call, true)); + } + if let Some(repeatable) = tablesample.repeatable_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(repeatable.syntax())) + .append(Doc::text("repeatable")); + if let Some(l_paren) = repeatable.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("(")); + if let Some(expr) = repeatable.expr() { + doc = doc + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(r_paren) = repeatable.r_paren_token() { + doc = doc.append(comments_before(r_paren)); + } + doc = doc.append(Doc::text(")")); + } + doc +} + +fn build_from_alias<'a>(alias: Option) -> Doc<'a> { + let Some(alias) = alias else { + return Doc::nil(); + }; + let mut doc = Doc::space().append(leading_comments(alias.syntax())); + if alias.as_token().is_some() { + doc = doc.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())); + } + if let Some(columns) = alias.columns() { + doc = doc.append(build_from_alias_columns(columns)); + } + doc +} + +fn build_from_alias_columns<'a>(columns: ast::FromAliasColumns) -> Doc<'a> { + match columns { + ast::FromAliasColumns::ColumnList(list) => { + let items = list + .column_names() + .map(|name| { + leading_comments(name.syntax()) + .append(build_name(name.syntax())) + .append(trailing_comments(name.syntax())) + }) + .collect(); + comments_before(list.syntax().clone()).append(build_from_alias_column_list( + list.l_paren_token(), + items, + list.r_paren_token(), + )) + } + ast::FromAliasColumns::ColumnDefList(list) => { + let items = list + .column_defs() + .map(|column| { + let mut doc = leading_comments(column.syntax()); + if let Some(name) = column.name() { + doc = doc.append(build_name(name.syntax())); + } + if let Some(ty) = column.ty() { + doc = doc + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + if let Some(collate) = column.collate() { + doc = doc + .append(Doc::space()) + .append(leading_comments(collate.syntax())) + .append(build_collate_expr(collate)); + } + doc.append(trailing_comments(column.syntax())) + }) + .collect(); + comments_before(list.syntax().clone()).append(build_from_alias_column_list( + list.l_paren_token(), + items, + list.r_paren_token(), + )) + } + } +} + +fn build_from_alias_column_list<'a>( + l_paren: Option, + items: Vec>, + r_paren: Option, +) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(l_paren) = l_paren { + 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 has_items = !items.is_empty(); + let mut body = if has_items { + Doc::list( + Itertools::intersperse( + items.into_iter(), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ) + } else { + Doc::nil() + }; + if !has_items { + 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_group_by_list<'a>(list: ast::GroupByList) -> Doc<'a> { + leading_comments(list.syntax()).append(build_group_bys(list.group_bys())) +} + +fn build_group_bys<'a>(group_bys: impl Iterator) -> Doc<'a> { + Doc::list( + Itertools::intersperse( + group_bys.map(|group_by| { + let leading = leading_comments(group_by.syntax()); + let trailing = trailing_comments(group_by.syntax()); + leading.append(build_group_by(group_by)).append(trailing) + }), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ) + .nest(2) +} + +fn build_group_by<'a>(group_by: ast::GroupBy) -> Doc<'a> { + match group_by { + ast::GroupBy::GroupingExpr(grouping_expr) => grouping_expr + .expr() + .map(build_expr) + .unwrap_or_else(Doc::nil), + ast::GroupBy::GroupingRollup(rollup) => Doc::text("rollup").append(build_grouping_exprs( + rollup.l_paren_token(), + rollup.exprs(), + rollup.r_paren_token(), + )), + ast::GroupBy::GroupingCube(cube) => Doc::text("cube").append(build_grouping_exprs( + cube.l_paren_token(), + cube.exprs(), + cube.r_paren_token(), + )), + ast::GroupBy::GroupingSets(sets) => { + let mut doc = Doc::text("grouping").append(Doc::space()); + if let Some(sets_token) = sets.sets_token() { + doc = doc.append(leading_comments_token(&sets_token)); + } + doc.append(Doc::text("sets")) + .append(build_grouping_group_bys( + sets.l_paren_token(), + sets.group_bys(), + sets.r_paren_token(), + )) + } + } +} + +fn build_grouping_exprs<'a>( + l_paren: Option, + exprs: impl Iterator, + r_paren: Option, +) -> Doc<'a> { + let exprs: Vec<_> = exprs + .map(|expr| { + let leading = leading_comments(expr.syntax()); + let trailing = trailing_comments(expr.syntax()); + leading.append(build_expr(expr)).append(trailing) + }) + .collect(); + build_grouping_list(l_paren, exprs, r_paren) +} + +fn build_grouping_group_bys<'a>( + l_paren: Option, + group_bys: impl Iterator, + r_paren: Option, +) -> Doc<'a> { + let group_bys = group_bys + .map(|group_by| { + let leading = leading_comments(group_by.syntax()); + let trailing = trailing_comments(group_by.syntax()); + leading.append(build_group_by(group_by)).append(trailing) + }) + .collect(); + build_grouping_list(l_paren, group_bys, r_paren) +} + +fn build_grouping_list<'a>( + l_paren: Option, + items: Vec>, + r_paren: Option, +) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(l_paren) = l_paren { + 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("(")); + + if items.is_empty() { + if let Some(r_paren) = r_paren { + doc = doc.append(comments_before(r_paren)); + } + } else { + doc = doc.append(wrap_body(Doc::list( + Itertools::intersperse( + items.into_iter(), + Doc::text(",").append(Doc::line_or_space()), + ) + .collect(), + ))); + } + + doc.append(Doc::text(")")).group() +} + +fn wrap_body<'a>(body: Doc<'a>) -> Doc<'a> { + Doc::line_or_nil() + .append(body) + .nest(2) + .append(Doc::line_or_nil()) +} + +fn build_semicolon<'a>(semi: Option) -> Doc<'a> { + let Some(semi) = semi else { + return Doc::nil(); + }; + let mut doc = Doc::nil(); + for comment in comment_tokens_before(semi) { + doc = doc.append(Doc::text(comment.text().to_string())); + if is_line_comment(&comment) { + doc = doc.append(Doc::hard_line()); + } + } + doc.append(Doc::text(";")) +} + +fn build_expr<'a>(expr: ast::Expr) -> Doc<'a> { + match expr { + ast::Expr::ArrayExpr(array_expr) => build_array_expr(array_expr), + ast::Expr::BetweenExpr(between_expr) => build_between_expr(between_expr), + ast::Expr::BinExpr(bin_expr) => build_bin_expr(bin_expr), + ast::Expr::CallExpr(call_expr) => build_call_expr(call_expr), + ast::Expr::CaseExpr(case_expr) => build_case_expr(case_expr), + ast::Expr::CastExpr(cast_expr) => build_cast_expr(cast_expr), + ast::Expr::Collate(collate) => build_collate_expr(collate), + ast::Expr::FieldExpr(field_expr) => build_field_expr(field_expr), + ast::Expr::IndexExpr(index_expr) => build_index_expr(index_expr), + ast::Expr::Literal(literal) => build_literal(literal), + ast::Expr::NameRef(name_ref) => build_name(name_ref.syntax()), + ast::Expr::ParenExpr(paren_expr) => build_paren_expr(paren_expr), + ast::Expr::PostfixExpr(postfix_expr) => build_postfix_expr(postfix_expr), + ast::Expr::PrefixExpr(prefix_expr) => build_prefix_expr(prefix_expr), + ast::Expr::SliceExpr(slice_expr) => build_slice_expr(slice_expr), + ast::Expr::TupleExpr(tuple_expr) => build_tuple_expr(tuple_expr), + } +} + +fn build_array_expr<'a>(array_expr: ast::ArrayExpr) -> Doc<'a> { + let mut doc = Doc::nil(); + + // nested parts of array expressions don't require the array token + if array_expr.array_token().is_some() { + doc = doc.append(Doc::text("array")); + }; + + if let Some(select) = array_expr.select() { + if let Some(l_paren) = array_expr.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + let mut body = leading_comments(select.syntax()).append(build_select_doc(&select)); + if let Some(r_paren) = array_expr.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) + .group() + } else { + if let Some(l_brack) = array_expr.l_brack_token() { + doc = doc.append(comments_before(l_brack)); + } + doc = doc.append(Doc::text("[")); + + let exprs = array_expr.exprs().map(|expr| { + let syntax = expr.syntax().clone(); + let doc = leading_comments(expr.syntax()).append(build_expr(expr)); + (doc, syntax) + }); + let mut body = build_comma_separated_docs(exprs).unwrap_or_else(Doc::nil); + if let Some(r_brack) = array_expr.r_brack_token() { + body = body.append(comments_before(r_brack)); + } + doc.append(wrap_body(body)).append(Doc::text("]")).group() + } +} + +fn build_field_expr<'a>(field_expr: ast::FieldExpr) -> Doc<'a> { + let mut doc = match field_expr.base() { + Some(base) => build_expr(base), + None => Doc::nil(), + }; + + if let Some(dot) = field_expr.dot_token() { + doc = doc.append(comments_before(dot)); + } + doc = doc.append(Doc::text(".")); + + if let Some(star) = field_expr.star_token() { + doc = doc + .append(leading_comments_token(&star)) + .append(Doc::text("*")); + } else if let Some(field) = field_expr.field() { + doc = doc + .append(leading_comments(field.syntax())) + .append(build_name(field.syntax())); + } + + doc +} + +fn build_index_expr<'a>(index_expr: ast::IndexExpr) -> Doc<'a> { + let mut doc = match index_expr.base() { + Some(base) => build_expr(base), + None => Doc::nil(), + }; + + if let Some(l_brack) = index_expr.l_brack_token() { + doc = doc.append(comments_before(l_brack)); + } + doc = doc.append(Doc::text("[")); + + let mut body = Doc::nil(); + if let Some(index) = index_expr.index() { + body = body + .append(leading_comments(index.syntax())) + .append(match index { + ast::Expr::BinExpr(binary) => build_bin_expr_doc(binary, false), + expression => build_expr(expression), + }); + } + if let Some(r_brack) = index_expr.r_brack_token() { + body = body.append(comments_before(r_brack)); + } + doc.append(wrap_body(body)).append(Doc::text("]")).group() +} + +fn build_slice_expr<'a>(slice_expr: ast::SliceExpr) -> Doc<'a> { + let mut doc = match slice_expr.base() { + Some(base) => build_expr(base), + None => Doc::nil(), + }; + + if let Some(l_brack) = slice_expr.l_brack_token() { + doc = doc.append(comments_before(l_brack)); + } + doc = doc.append(Doc::text("[")); + + if let Some(start) = slice_expr.start() { + doc = doc + .append(leading_comments(start.syntax())) + .append(build_expr(start)); + } + if let Some(colon) = slice_expr.colon_token() { + doc = doc.append(comments_before(colon)); + } + doc = doc.append(Doc::text(":")); + + if let Some(end) = slice_expr.end() { + doc = doc + .append(leading_comments(end.syntax())) + .append(build_expr(end)); + } + if let Some(r_brack) = slice_expr.r_brack_token() { + doc = doc.append(comments_before(r_brack)); + } + doc.append(Doc::text("]")) +} + +fn build_tuple_expr<'a>(tuple_expr: ast::TupleExpr) -> Doc<'a> { + let mut doc = if tuple_expr.row_token().is_some() { + Doc::text("row") + } else { + Doc::nil() + }; + + if let Some(l_paren) = tuple_expr.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let exprs = build_comma_separated_exprs(tuple_expr.exprs()); + let has_exprs = exprs.is_some(); + let mut body = exprs.unwrap_or_else(Doc::nil); + if !has_exprs { + if let Some(r_paren) = tuple_expr.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + } + + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_between_expr<'a>(between_expr: ast::BetweenExpr) -> Doc<'a> { + let mut doc = build_expr(between_expr.target().unwrap()).append(Doc::line_or_space()); + if between_expr.not_token().is_some() { + doc = doc.append(Doc::text("not")).append(Doc::space()); + } + doc = doc.append(Doc::text("between")); + match between_expr.between_symmetry() { + Some(ast::BetweenSymmetry::Asymmetric(_)) => { + doc = doc.append(Doc::space()).append(Doc::text("asymmetric")); + } + Some(ast::BetweenSymmetry::Symmetric(_)) => { + doc = doc.append(Doc::space()).append(Doc::text("symmetric")); + } + None => (), + } + doc.append(Doc::space()) + .append(build_expr(between_expr.start().unwrap())) + .append(Doc::line_or_space()) + .append(Doc::text("and")) + .append(Doc::space()) + .append(build_expr(between_expr.end().unwrap())) + .nest(2) + .group() +} + +fn build_call_expr<'a>(call_expr: ast::CallExpr) -> Doc<'a> { + build_call_expr_with_spacing(call_expr, false) +} + +fn build_call_expr_with_spacing<'a>(call_expr: ast::CallExpr, space_before_paren: bool) -> Doc<'a> { + if let (Some(expr), Some(arg_list)) = (call_expr.expr(), call_expr.arg_list()) { + let mut doc = build_expr(expr); + if space_before_paren && comment_tokens_before(arg_list.syntax().clone()).is_empty() { + doc = doc.append(Doc::space()); + } + doc = doc + .append(comments_before(arg_list.syntax().clone())) + .append(build_call_arg_list(arg_list)); + if let Some(within_clause) = call_expr.within_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(within_clause.syntax())) + .append(build_within_clause(within_clause)); + } + if let Some(filter_clause) = call_expr.filter_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(filter_clause.syntax())) + .append(build_filter_clause(filter_clause)); + } + if let Some(null_treatment) = call_expr.null_treatment() { + doc = doc + .append(Doc::space()) + .append(leading_comments(null_treatment.syntax())) + .append(build_null_treatment(null_treatment)); + } + if let Some(over_clause) = call_expr.over_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(over_clause.syntax())) + .append(build_over_clause(over_clause)); + } + doc + } else if let Some(all_fn) = call_expr.all_fn() { + build_parenthesized_expr_or_select_fn( + "all", + all_fn.l_paren_token(), + all_fn.expr(), + all_fn.select_variant(), + all_fn.r_paren_token(), + ) + } else if let Some(any_fn) = call_expr.any_fn() { + build_parenthesized_expr_or_select_fn( + "any", + any_fn.l_paren_token(), + any_fn.expr(), + any_fn.select_variant(), + any_fn.r_paren_token(), + ) + } else if let Some(collation_for_fn) = call_expr.collation_for_fn() { + build_collation_for_fn(collation_for_fn) + } else if let Some(exists_fn) = call_expr.exists_fn() { + build_parenthesized_expr_or_select_fn( + "exists", + exists_fn.l_paren_token(), + None, + exists_fn.select_variant(), + exists_fn.r_paren_token(), + ) + } else if let Some(extract_fn) = call_expr.extract_fn() { + build_extract_fn(extract_fn) + } else if let Some(graph_table_fn) = call_expr.graph_table_fn() { + build_graph_table_fn(graph_table_fn) + } else if let Some(json_array_agg_fn) = call_expr.json_array_agg_fn() { + build_json_array_agg_fn(json_array_agg_fn) + } else if let Some(json_array_fn) = call_expr.json_array_fn() { + build_json_array_fn(json_array_fn) + } else if let Some(json_exists_fn) = call_expr.json_exists_fn() { + build_json_exists_fn(json_exists_fn) + } else if let Some(json_fn) = call_expr.json_fn() { + build_json_fn(json_fn) + } else if let Some(json_object_agg_fn) = call_expr.json_object_agg_fn() { + build_json_object_agg_fn(json_object_agg_fn) + } else if let Some(json_object_fn) = call_expr.json_object_fn() { + build_json_object_fn(json_object_fn) + } else if let Some(json_query_fn) = call_expr.json_query_fn() { + build_json_query_fn(json_query_fn) + } else if let Some(json_scalar_fn) = call_expr.json_scalar_fn() { + build_json_scalar_fn(json_scalar_fn) + } else if let Some(json_serialize_fn) = call_expr.json_serialize_fn() { + build_json_serialize_fn(json_serialize_fn) + } else if let Some(json_value_fn) = call_expr.json_value_fn() { + build_json_value_fn(json_value_fn) + } else if let Some(overlay_fn) = call_expr.overlay_fn() { + build_overlay_fn(overlay_fn) + } else if let Some(position_fn) = call_expr.position_fn() { + build_position_fn(position_fn) + } else if let Some(some_fn) = call_expr.some_fn() { + build_parenthesized_expr_or_select_fn( + "some", + some_fn.l_paren_token(), + some_fn.expr(), + some_fn.select_variant(), + some_fn.r_paren_token(), + ) + } else if let Some(substring_fn) = call_expr.substring_fn() { + build_substring_fn(substring_fn) + } else if let Some(trim_fn) = call_expr.trim_fn() { + build_trim_fn(trim_fn) + } else if let Some(xml_element_fn) = call_expr.xml_element_fn() { + build_xml_element_fn(xml_element_fn) + } else if let Some(xml_exists_fn) = call_expr.xml_exists_fn() { + build_xml_exists_fn(xml_exists_fn) + } else if let Some(xml_forest_fn) = call_expr.xml_forest_fn() { + build_xml_forest_fn(xml_forest_fn) + } else if let Some(xml_parse_fn) = call_expr.xml_parse_fn() { + build_xml_parse_fn(xml_parse_fn) + } else if let Some(xml_pi_fn) = call_expr.xml_pi_fn() { + build_xml_pi_fn(xml_pi_fn) + } else if let Some(xml_root_fn) = call_expr.xml_root_fn() { + build_xml_root_fn(xml_root_fn) + } else if let Some(xml_serialize_fn) = call_expr.xml_serialize_fn() { + build_xml_serialize_fn(xml_serialize_fn) + } else { + unreachable!("a call expression should contain a supported function node") + } +} + +fn build_graph_table_fn<'a>(graph_table_fn: ast::GraphTableFn) -> Doc<'a> { + let mut doc = Doc::text("graph_table"); + if let Some(l_paren) = graph_table_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(graph) = graph_table_fn.property_graph_ref() { + if let Some(path) = graph.path_ref() { + body = body + .append(leading_comments(graph.syntax())) + .append(build_path_ref(&path)); + } + } + if let Some(match_token) = graph_table_fn.match_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&match_token)) + .append(Doc::text("match")); + } + if let Some(patterns) = graph_table_fn.path_pattern_list() { + body = body.append( + Doc::line_or_space() + .append(leading_comments(patterns.syntax())) + .append(build_path_pattern_list(patterns)) + .nest(2), + ); + } + if let Some(where_clause) = graph_table_fn.where_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + if let Some(columns) = graph_table_fn.columns_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&columns)) + .append(Doc::text("columns")); + } + if let Some(columns) = graph_table_fn.expr_as_column_name_list() { + if comment_tokens_before(columns.syntax().clone()).is_empty() { + body = body.append(Doc::space()); + } else { + body = body.append(comments_before(columns.syntax().clone())); + } + body = body.append(build_expr_as_column_name_list(columns)); + } + + if let Some(r_paren) = graph_table_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body).group()).append(Doc::text(")")) +} + +fn build_path_pattern_list<'a>(patterns: ast::PathPatternList) -> Doc<'a> { + let items = patterns.path_patterns().map(|pattern| { + ( + leading_comments(pattern.syntax()).append(build_path_pattern(pattern.clone())), + pattern.syntax().clone(), + ) + }); + build_comma_separated_docs(items).unwrap_or_else(Doc::nil) +} + +fn build_path_pattern<'a>(pattern: ast::PathPattern) -> Doc<'a> { + Doc::list( + Itertools::intersperse( + pattern + .path_factors() + .map(|factor| leading_comments(factor.syntax()).append(build_path_factor(factor))), + Doc::line_or_nil(), + ) + .collect(), + ) + .nest(2) + .group() +} + +fn build_path_factor<'a>(factor: ast::PathFactor) -> Doc<'a> { + let mut doc = factor + .path_primary() + .map(build_path_primary) + .unwrap_or_else(Doc::nil); + if let Some(qualifier) = factor.graph_pattern_qualifier() { + doc = doc + .append(leading_comments(qualifier.syntax())) + .append(build_graph_pattern_qualifier(qualifier)); + } + doc +} + +fn build_path_primary<'a>(primary: ast::PathPrimary) -> Doc<'a> { + match primary { + ast::PathPrimary::VertexPattern(pattern) => build_vertex_pattern(pattern), + ast::PathPrimary::EdgeLeft(edge) => build_edge_left(edge), + ast::PathPrimary::EdgeRight(edge) => build_edge_right(edge), + ast::PathPrimary::EdgeAny(edge) => build_edge_any(edge), + ast::PathPrimary::ParenGraphPattern(pattern) => build_paren_graph_pattern(pattern), + } +} + +fn build_vertex_pattern<'a>(pattern: ast::VertexPattern) -> Doc<'a> { + let mut doc = pattern + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")) + .append(build_graph_pattern_inner( + pattern.element_variable(), + pattern.is_label(), + pattern.where_clause(), + )); + if let Some(r_paren) = pattern.r_paren_token() { + doc = doc.append(comments_before(r_paren)); + } + doc.append(Doc::text(")")) +} + +fn build_edge_left<'a>(edge: ast::EdgeLeft) -> Doc<'a> { + let mut doc = Doc::text("<"); + if let Some(minus) = edge.minus_token() { + doc = doc.append(comments_before(minus)); + } + doc = doc.append(Doc::text("-")); + if let Some(l_brack) = edge.l_brack_token() { + doc = doc + .append(comments_before(l_brack)) + .append(Doc::text("[")) + .append(build_graph_pattern_inner( + edge.element_variable(), + edge.is_label(), + edge.where_clause(), + )); + if let Some(r_brack) = edge.r_brack_token() { + doc = doc.append(comments_before(r_brack)); + } + doc = doc.append(Doc::text("]")); + if let Some(minus) = edge.end_minus_token() { + doc = doc.append(comments_before(minus)); + } + doc = doc.append(Doc::text("-")); + } + doc +} + +fn build_edge_right<'a>(edge: ast::EdgeRight) -> Doc<'a> { + let mut doc = Doc::text("-"); + if let Some(l_brack) = edge.l_brack_token() { + doc = doc + .append(comments_before(l_brack)) + .append(Doc::text("[")) + .append(build_graph_pattern_inner( + edge.element_variable(), + edge.is_label(), + edge.where_clause(), + )); + if let Some(r_brack) = edge.r_brack_token() { + doc = doc.append(comments_before(r_brack)); + } + doc = doc.append(Doc::text("]")); + if let Some(minus) = edge.end_minus_token() { + doc = doc.append(comments_before(minus)); + } + doc = doc.append(Doc::text("-")); + } + if let Some(r_angle) = edge.r_angle_token() { + doc = doc.append(comments_before(r_angle)); + } + doc.append(Doc::text(">")) +} + +fn build_edge_any<'a>(edge: ast::EdgeAny) -> Doc<'a> { + let mut doc = Doc::text("-"); + if let Some(l_brack) = edge.l_brack_token() { + doc = doc + .append(comments_before(l_brack)) + .append(Doc::text("[")) + .append(build_graph_pattern_inner( + edge.element_variable(), + edge.is_label(), + edge.where_clause(), + )); + if let Some(r_brack) = edge.r_brack_token() { + doc = doc.append(comments_before(r_brack)); + } + doc = doc.append(Doc::text("]")); + if let Some(minus) = edge.end_minus_token() { + doc = doc.append(comments_before(minus)); + } + doc = doc.append(Doc::text("-")); + } + doc +} + +fn build_graph_pattern_inner<'a>( + variable: Option, + label: Option, + where_clause: Option, +) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(variable) = variable { + doc = doc + .append(leading_comments(variable.syntax())) + .append(build_name(variable.syntax())); + } + if let Some(label) = label { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(label.syntax())) + .append(build_is_label(label)); + } + if let Some(where_clause) = where_clause { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + doc.nest(2).group() +} + +fn build_is_label<'a>(label: ast::IsLabel) -> Doc<'a> { + let mut doc = label + .is_token() + .map(|token| leading_comments_token(&token).append(Doc::text("is"))) + .unwrap_or_else(Doc::nil); + if let Some(expr) = label.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc +} + +fn build_where_clause<'a>(where_clause: ast::WhereClause) -> Doc<'a> { + let mut doc = where_clause + .where_token() + .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)); + } + doc +} + +fn build_paren_graph_pattern<'a>(pattern: ast::ParenGraphPattern) -> Doc<'a> { + let mut doc = pattern + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + if let Some(inner) = pattern.path_pattern() { + doc = doc + .append(leading_comments(inner.syntax())) + .append(build_path_pattern(inner)); + } + if let Some(where_clause) = pattern.where_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(where_clause.syntax())) + .append(build_where_clause(where_clause)); + } + if let Some(r_paren) = pattern.r_paren_token() { + doc = doc.append(comments_before(r_paren)); + } + doc.append(Doc::text(")")) +} + +fn build_graph_pattern_qualifier<'a>(qualifier: ast::GraphPatternQualifier) -> Doc<'a> { + let mut doc = qualifier + .l_curly_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("{")); + if let Some(min) = qualifier.min() { + if let Some(literal) = min.literal() { doc = doc - .append(leading_comments(expr.syntax())) - .append(build_expr(expr)); + .append(leading_comments(min.syntax())) + .append(build_literal(literal)); + } + } + if let Some(comma) = qualifier.comma_token() { + doc = doc.append(comments_before(comma)).append(Doc::text(",")); + } + if let Some(max) = qualifier.max() { + if qualifier.comma_token().is_some() { + doc = doc.append(Doc::space()); + } + if let Some(literal) = max.literal() { + doc = doc + .append(leading_comments(max.syntax())) + .append(build_literal(literal)); + } + } + if let Some(r_curly) = qualifier.r_curly_token() { + doc = doc.append(comments_before(r_curly)); + } + doc.append(Doc::text("}")) +} + +fn build_expr_as_column_name_list<'a>(list: ast::ExprAsColumnNameList) -> Doc<'a> { + let doc = list + .l_paren_token() + .map(comments_before) + .unwrap_or_else(Doc::nil) + .append(Doc::text("(")); + let items = list.expr_as_column_names().map(|item| { + let mut item_doc = item.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(as_token) = item.as_token() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(name) = item.column_name() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + ( + leading_comments(item.syntax()).append(item_doc), + item.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_xml_element_fn<'a>(xml_element_fn: ast::XmlElementFn) -> Doc<'a> { + let mut doc = Doc::text("xmlelement"); + if let Some(l_paren) = xml_element_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(name) = xml_element_fn.name_token() { + body = body + .append(leading_comments_token(&name)) + .append(Doc::text("name")); + } + + let Some(tag) = xml_element_fn.tag() else { + return doc.append(Doc::text(")")); + }; + body = body + .append(Doc::space()) + .append(leading_comments(tag.syntax())) + .append(build_name(tag.syntax())); + + let mut items = Vec::new(); + if let Some(attrs) = xml_element_fn.expr_as_xml_attr_list() { + let attrs_doc = xml_element_fn + .xmlattributes_token() + .map(|token| { + leading_comments_token(&token) + .append(Doc::text("xmlattributes")) + .append(comments_before(attrs.syntax().clone())) + }) + .unwrap_or_else(Doc::nil) + .append(build_expr_as_xml_attr_list(attrs.clone())); + items.push((attrs_doc, attrs.syntax().clone())); + } + items.extend(xml_element_fn.exprs().map(|expr| { + ( + leading_comments(expr.syntax()).append(build_expr(expr.clone())), + expr.syntax().clone(), + ) + })); + + let mut previous = tag.syntax().clone(); + for (item, syntax) in items { + body = body + .append(trailing_comments(&previous)) + .append(Doc::text(",")) + .append(Doc::line_or_space()) + .append(item); + previous = syntax; + } + + if let Some(r_paren) = xml_element_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_expr_as_xml_attr_list<'a>(attrs: ast::ExprAsXmlAttrList) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(l_paren) = attrs.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let items = attrs.expr_as_xml_attrs().map(|attr| { + let mut item = attr.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(as_token) = attr.as_token() { + item = item + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(name) = attr.attr() { + item = item + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + ( + leading_comments(attr.syntax()).append(item), + attr.syntax().clone(), + ) + }); + let mut body = build_comma_separated_docs(items).unwrap_or_else(Doc::nil); + + if let Some(r_paren) = attrs.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_exists_fn<'a>(xml_exists_fn: ast::XmlExistsFn) -> Doc<'a> { + let mut doc = Doc::text("xmlexists"); + if let Some(l_paren) = xml_exists_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(passing) = xml_exists_fn.xml_row_passing_clause() { + if let Some(row) = passing.row() { + body = body + .append(leading_comments(passing.syntax())) + .append(build_expr(row)); + } + if let Some(passing_token) = passing.passing_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&passing_token)) + .append(Doc::text("passing")); + } + if let Some(mech) = passing.xml_passing_mech() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(mech.syntax())) + .append(build_xml_passing_mech(mech)); + } + if let Some(passing_doc) = passing.xml_passing_doc() { + if let Some(expr) = passing_doc.expr() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(passing_doc.syntax())) + .append(build_expr(expr)); + } + if let Some(mech) = passing_doc.xml_passing_mech() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(mech.syntax())) + .append(build_xml_passing_mech(mech)); + } + } + } + + if let Some(r_paren) = xml_exists_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_forest_fn<'a>(xml_forest_fn: ast::XmlForestFn) -> Doc<'a> { + Doc::text("xmlforest") + .append( + xml_forest_fn + .expr_as_element_tag_list() + .map(|list| { + comments_before(list.syntax().clone()) + .append(build_expr_as_element_tag_list(list)) + }) + .unwrap_or_else(Doc::nil), + ) + .group() +} + +fn build_expr_as_element_tag_list<'a>(list: ast::ExprAsElementTagList) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(l_paren) = list.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let items = list.expr_as_element_tags().map(|item| { + let mut item_doc = item.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(as_token) = item.as_token() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(tag) = item.tag() { + item_doc = item_doc + .append(Doc::space()) + .append(leading_comments(tag.syntax())) + .append(build_name(tag.syntax())); + } + ( + leading_comments(item.syntax()).append(item_doc), + item.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_xml_parse_fn<'a>(xml_parse_fn: ast::XmlParseFn) -> Doc<'a> { + let mut doc = Doc::text("xmlparse"); + if let Some(l_paren) = xml_parse_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(kind) = xml_parse_fn.xml_document_or_content() { + body = body + .append(leading_comments(kind.syntax())) + .append(build_xml_document_or_content(kind)); + } + if let Some(expr) = xml_parse_fn.expr() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(whitespace) = xml_parse_fn.xml_whitespace() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(whitespace.syntax())) + .append(build_xml_whitespace(whitespace)); + } + + if let Some(r_paren) = xml_parse_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_pi_fn<'a>(xml_pi_fn: ast::XmlPiFn) -> Doc<'a> { + let mut doc = Doc::text("xmlpi"); + if let Some(l_paren) = xml_pi_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(name) = xml_pi_fn.name_token() { + body = body + .append(leading_comments_token(&name)) + .append(Doc::text("name")); + } + if let Some(target) = xml_pi_fn.target() { + body = body + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(build_name(target.syntax())); + } + if let Some(expr) = xml_pi_fn.expr() { + if let Some(comma) = xml_pi_fn.comma_token() { + body = body.append(comments_before(comma)); + } + body = body + .append(Doc::text(",")) + .append(Doc::line_or_space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + + if let Some(r_paren) = xml_pi_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_root_fn<'a>(xml_root_fn: ast::XmlRootFn) -> Doc<'a> { + let mut doc = Doc::text("xmlroot"); + if let Some(l_paren) = xml_root_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(expr) = xml_root_fn.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(comma) = xml_root_fn.comma_token() { + body = body.append(comments_before(comma)); + } + body = body.append(Doc::text(",")).append(Doc::line_or_space()); + if let Some(version) = xml_root_fn.xml_root_version() { + body = body + .append(leading_comments(version.syntax())) + .append(build_xml_root_version(version)); + } + if let Some(standalone) = xml_root_fn.xml_standalone() { + body = body + .append(leading_comments(standalone.syntax())) + .append(build_xml_standalone(standalone)); + } + + if let Some(r_paren) = xml_root_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_root_version<'a>(version: ast::XmlRootVersion) -> Doc<'a> { + match version { + ast::XmlRootVersion::XmlVersionExpr(version) => { + let mut doc = version + .version_token() + .map(|token| leading_comments_token(&token).append(Doc::text("version"))) + .unwrap_or_else(Doc::nil); + if let Some(expr) = version.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc + } + ast::XmlRootVersion::XmlVersionNoValue(version) => { + let mut doc = version + .version_token() + .map(|token| leading_comments_token(&token).append(Doc::text("version"))) + .unwrap_or_else(Doc::nil); + if let Some(no) = version.no_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&no)) + .append(Doc::text("no")); + } + if let Some(value) = version.value_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&value)) + .append(Doc::text("value")); + } + doc + } + } +} + +fn build_xml_standalone<'a>(standalone: ast::XmlStandalone) -> Doc<'a> { + let (comma, standalone_token, no_or_yes, value, text) = match standalone { + ast::XmlStandalone::StandaloneYes(node) => ( + node.comma_token(), + node.standalone_token(), + node.yes_token(), + None, + "yes", + ), + ast::XmlStandalone::StandaloneNo(node) => ( + node.comma_token(), + node.standalone_token(), + node.no_token(), + None, + "no", + ), + ast::XmlStandalone::StandaloneNoValue(node) => ( + node.comma_token(), + node.standalone_token(), + node.no_token(), + node.value_token(), + "no", + ), + }; + + let mut doc = comma.map(comments_before).unwrap_or_else(Doc::nil); + doc = doc.append(Doc::text(",")).append(Doc::line_or_space()); + if let Some(token) = standalone_token { + doc = doc + .append(leading_comments_token(&token)) + .append(Doc::text("standalone")); + } + if let Some(token) = no_or_yes { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(text)); + } + if let Some(token) = value { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text("value")); + } + doc +} + +fn build_xml_serialize_fn<'a>(xml_serialize_fn: ast::XmlSerializeFn) -> Doc<'a> { + let mut doc = Doc::text("xmlserialize"); + if let Some(l_paren) = xml_serialize_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(kind) = xml_serialize_fn.xml_document_or_content() { + body = body + .append(leading_comments(kind.syntax())) + .append(build_xml_document_or_content(kind)); + } + if let Some(expr) = xml_serialize_fn.expr() { + body = body + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(as_token) = xml_serialize_fn.as_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&as_token)) + .append(Doc::text("as")); + } + if let Some(ty) = xml_serialize_fn.ty() { + body = body + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); + } + if let Some(indent) = xml_serialize_fn.xml_indent() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(indent.syntax())) + .append(build_xml_indent(indent)); + } + + if let Some(r_paren) = xml_serialize_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_xml_document_or_content<'a>(kind: ast::XmlDocumentOrContent) -> Doc<'a> { + match kind { + ast::XmlDocumentOrContent::XmlDocument(_) => Doc::text("document"), + ast::XmlDocumentOrContent::XmlContent(_) => Doc::text("content"), + } +} + +fn build_xml_whitespace<'a>(whitespace: ast::XmlWhitespace) -> Doc<'a> { + let (first, second, text) = match whitespace { + ast::XmlWhitespace::PreserveWhitespace(node) => { + (node.preserve_token(), node.whitespace_token(), "preserve") + } + ast::XmlWhitespace::StripWhitespace(node) => { + (node.strip_token(), node.whitespace_token(), "strip") + } + }; + build_two_keywords(first, text, second, "whitespace") +} + +fn build_xml_indent<'a>(indent: ast::XmlIndent) -> Doc<'a> { + match indent { + ast::XmlIndent::Indent(_) => Doc::text("indent"), + ast::XmlIndent::NoIndent(node) => { + build_two_keywords(node.no_token(), "no", node.indent_token(), "indent") + } + } +} + +fn build_two_keywords<'a>( + first: Option, + first_text: &'static str, + second: Option, + second_text: &'static str, +) -> Doc<'a> { + let mut doc = first + .map(|token| leading_comments_token(&token).append(Doc::text(first_text))) + .unwrap_or_else(Doc::nil); + if let Some(token) = second { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(second_text)); + } + doc +} + +fn build_xml_passing_mech<'a>(mech: ast::XmlPassingMech) -> Doc<'a> { + let (by, end, text) = match mech { + ast::XmlPassingMech::XmlPassingMechByRef(mech) => { + (mech.by_token(), mech.ref_token(), "ref") + } + ast::XmlPassingMech::XmlPassingMechByValue(mech) => { + (mech.by_token(), mech.value_token(), "value") + } + }; + let mut doc = by + .map(|token| leading_comments_token(&token).append(Doc::text("by"))) + .unwrap_or_else(Doc::nil); + if let Some(end) = end { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&end)) + .append(Doc::text(text)); + } + doc +} + +fn build_json_object_fn<'a>(json_object_fn: ast::JsonObjectFn) -> Doc<'a> { + let mut doc = Doc::text("json_object"); + if let Some(l_paren) = json_object_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + let exprs = json_object_fn.exprs().map(|expr| { + ( + leading_comments(expr.syntax()).append(build_expr(expr.clone())), + expr.syntax().clone(), + ) + }); + let key_values = json_object_fn.json_key_values().map(|key_value| { + ( + leading_comments(key_value.syntax()).append(build_json_key_value(key_value.clone())), + key_value.syntax().clone(), + ) + }); + let items = build_comma_separated_docs(exprs.chain(key_values)); + let mut has_content = items.is_some(); + if let Some(items) = items { + body = body.append(items); + } + + if let Some(null_clause) = json_object_fn.json_null_clause() { + if has_content { + body = body.append(Doc::line_or_space()); } - if let Some(r_paren) = repeatable.r_paren_token() { - doc = doc.append(comments_before(r_paren)); + body = body + .append(leading_comments(null_clause.syntax())) + .append(build_json_null_clause(null_clause)); + has_content = true; + } + if let Some(unique) = json_object_fn.json_keys_unique_clause() { + if has_content { + body = body.append(Doc::line_or_space()); } - doc = doc.append(Doc::text(")")); + body = body + .append(leading_comments(unique.syntax())) + .append(build_json_keys_unique_clause(unique)); + has_content = true; } - doc + if let Some(returning) = json_object_fn.json_returning_clause() { + if has_content { + body = body.append(Doc::line_or_space()); + } + body = body + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(r_paren) = json_object_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_from_alias<'a>(alias: Option) -> Doc<'a> { - let Some(alias) = alias else { - return Doc::nil(); - }; - let mut doc = Doc::space().append(leading_comments(alias.syntax())); - if alias.as_token().is_some() { - doc = doc.append(Doc::text("as")).append(Doc::space()); +fn build_json_object_agg_fn<'a>(json_object_agg_fn: ast::JsonObjectAggFn) -> Doc<'a> { + let mut doc = Doc::text("json_objectagg"); + if let Some(l_paren) = json_object_agg_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); } - if let Some(name) = alias.name() { + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(key_value) = json_object_agg_fn.json_key_value() { + body = body + .append(leading_comments(key_value.syntax())) + .append(build_json_key_value(key_value)); + } + if let Some(null_clause) = json_object_agg_fn.json_null_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(null_clause.syntax())) + .append(build_json_null_clause(null_clause)); + } + if let Some(unique) = json_object_agg_fn.json_keys_unique_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(unique.syntax())) + .append(build_json_keys_unique_clause(unique)); + } + if let Some(returning) = json_object_agg_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(r_paren) = json_object_agg_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_json_key_value<'a>(key_value: ast::JsonKeyValue) -> Doc<'a> { + let mut doc = key_value.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(colon) = key_value.colon_token() { + doc = doc.append(comments_before(colon)).append(Doc::text(":")); + } else if let Some(value_token) = key_value.value_token() { doc = doc - .append(leading_comments(name.syntax())) - .append(build_name(name.syntax())); + .append(Doc::space()) + .append(leading_comments_token(&value_token)) + .append(Doc::text("value")); } - if alias.columns().is_some() { - todo!("columns in from aliases are not supported yet") + if let Some(value) = key_value.json_value_expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(value.syntax())) + .append(build_json_value_expr(value)); } doc } -fn build_group_by_list<'a>(list: ast::GroupByList) -> Doc<'a> { - leading_comments(list.syntax()).append(build_group_bys(list.group_bys())) +fn build_json_fn<'a>(json_fn: ast::JsonFn) -> Doc<'a> { + let mut doc = Doc::text("json"); + if let Some(l_paren) = json_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(expr) = json_fn.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(format) = json_fn.json_format_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); + } + if let Some(unique) = json_fn.json_keys_unique_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(unique.syntax())) + .append(build_json_keys_unique_clause(unique)); + } + if let Some(r_paren) = json_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_group_bys<'a>(group_bys: impl Iterator) -> Doc<'a> { - Doc::list( - Itertools::intersperse( - group_bys.map(|group_by| { - let leading = leading_comments(group_by.syntax()); - let trailing = trailing_comments(group_by.syntax()); - leading.append(build_group_by(group_by)).append(trailing) - }), - Doc::text(",").append(Doc::line_or_space()), - ) - .collect(), +fn build_json_scalar_fn<'a>(json_scalar_fn: ast::JsonScalarFn) -> Doc<'a> { + build_parenthesized_expr_or_select_fn( + "json_scalar", + json_scalar_fn.l_paren_token(), + json_scalar_fn.expr(), + None, + json_scalar_fn.r_paren_token(), ) - .nest(2) } -fn build_group_by<'a>(group_by: ast::GroupBy) -> Doc<'a> { - match group_by { - ast::GroupBy::GroupingExpr(grouping_expr) => grouping_expr - .expr() - .map(build_expr) - .unwrap_or_else(Doc::nil), - ast::GroupBy::GroupingRollup(rollup) => Doc::text("rollup").append(build_grouping_exprs( - rollup.l_paren_token(), - rollup.exprs(), - rollup.r_paren_token(), - )), - ast::GroupBy::GroupingCube(cube) => Doc::text("cube").append(build_grouping_exprs( - cube.l_paren_token(), - cube.exprs(), - cube.r_paren_token(), - )), - ast::GroupBy::GroupingSets(sets) => { - let mut doc = Doc::text("grouping").append(Doc::space()); - if let Some(sets_token) = sets.sets_token() { - doc = doc.append(leading_comments_token(&sets_token)); - } - doc.append(Doc::text("sets")) - .append(build_grouping_group_bys( - sets.l_paren_token(), - sets.group_bys(), - sets.r_paren_token(), - )) - } +fn build_json_serialize_fn<'a>(json_serialize_fn: ast::JsonSerializeFn) -> Doc<'a> { + let mut doc = Doc::text("json_serialize"); + if let Some(l_paren) = json_serialize_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(expr) = json_serialize_fn.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); } + if let Some(format) = json_serialize_fn.json_format_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); + } + if let Some(returning) = json_serialize_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(r_paren) = json_serialize_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_grouping_exprs<'a>( - l_paren: Option, - exprs: impl Iterator, - r_paren: Option, -) -> Doc<'a> { - let exprs: Vec<_> = exprs - .map(|expr| { - let leading = leading_comments(expr.syntax()); - let trailing = trailing_comments(expr.syntax()); - leading.append(build_expr(expr)).append(trailing) - }) - .collect(); - build_grouping_list(l_paren, exprs, r_paren) +fn build_json_query_fn<'a>(json_query_fn: ast::JsonQueryFn) -> Doc<'a> { + let (doc, mut body) = build_json_document_path_fn( + "json_query", + json_query_fn.l_paren_token(), + json_query_fn.document(), + json_query_fn.json_format_clause(), + json_query_fn.comma_token(), + json_query_fn.path(), + ); + if let Some(passing) = json_query_fn.json_passing_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(passing.syntax())) + .append(build_json_passing_clause(passing)); + } + if let Some(returning) = json_query_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(wrapper) = json_query_fn.json_wrapper_behavior_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(wrapper.syntax())) + .append(build_json_wrapper_behavior_clause(wrapper)); + } + if let Some(quotes) = json_query_fn.json_quotes_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(quotes.syntax())) + .append(build_json_quotes_clause(quotes)); + } + if let Some(on_empty) = json_query_fn.json_on_empty_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(on_empty.syntax())) + .append(build_json_on_empty_clause(on_empty)); + } + if let Some(on_error) = json_query_fn.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_query_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_grouping_group_bys<'a>( - l_paren: Option, - group_bys: impl Iterator, - r_paren: Option, -) -> Doc<'a> { - let group_bys = group_bys - .map(|group_by| { - let leading = leading_comments(group_by.syntax()); - let trailing = trailing_comments(group_by.syntax()); - leading.append(build_group_by(group_by)).append(trailing) - }) - .collect(); - build_grouping_list(l_paren, group_bys, r_paren) +fn build_json_value_fn<'a>(json_value_fn: ast::JsonValueFn) -> Doc<'a> { + let (doc, mut body) = build_json_document_path_fn( + "json_value", + json_value_fn.l_paren_token(), + json_value_fn.document(), + json_value_fn.json_format_clause(), + json_value_fn.comma_token(), + json_value_fn.path(), + ); + if let Some(passing) = json_value_fn.json_passing_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(passing.syntax())) + .append(build_json_passing_clause(passing)); + } + if let Some(returning) = json_value_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(on_empty) = json_value_fn.json_on_empty_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(on_empty.syntax())) + .append(build_json_on_empty_clause(on_empty)); + } + if let Some(on_error) = json_value_fn.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_value_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_grouping_list<'a>( +fn build_json_document_path_fn<'a>( + keyword: &'static str, l_paren: Option, - items: Vec>, - r_paren: Option, -) -> Doc<'a> { - let mut doc = Doc::nil(); + document: Option, + format: Option, + comma: Option, + path: Option, +) -> (Doc<'a>, Doc<'a>) { + let mut doc = Doc::text(keyword); if let Some(l_paren) = l_paren { doc = doc.append(comments_before(l_paren)); } doc = doc.append(Doc::text("(")); - if items.is_empty() { - if let Some(r_paren) = r_paren { - doc = doc.append(comments_before(r_paren)); + let mut body = Doc::nil(); + if let Some(document) = document { + body = body + .append(leading_comments(document.syntax())) + .append(build_expr(document)); + } + if let Some(format) = format { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); + } + if let Some(comma) = comma { + body = body + .append(comments_before(comma)) + .append(Doc::text(",")) + .append(Doc::line_or_space()); + } + if let Some(path) = path { + body = body + .append(leading_comments(path.syntax())) + .append(build_expr(path)); + } + (doc, body) +} + +fn build_json_exists_fn<'a>(json_exists_fn: ast::JsonExistsFn) -> Doc<'a> { + let mut doc = Doc::text("json_exists"); + if let Some(l_paren) = json_exists_fn.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_exists_fn.document() { + body = body + .append(leading_comments(document.syntax())) + .append(build_expr(document)); + } + if let Some(format) = json_exists_fn.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_exists_fn.comma_token() { + body = body + .append(comments_before(comma)) + .append(Doc::text(",")) + .append(Doc::line_or_space()); + } + if let Some(path) = json_exists_fn.path() { + body = body + .append(leading_comments(path.syntax())) + .append(build_expr(path)); + } + if let Some(passing) = json_exists_fn.json_passing_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(passing.syntax())) + .append(build_json_passing_clause(passing)); + } + if let Some(on_error) = json_exists_fn.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_exists_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_json_passing_clause<'a>(passing: ast::JsonPassingClause) -> Doc<'a> { + let mut doc = Doc::text("passing"); + let mut args = passing.json_passing_args(); + if let Some(first) = args.next() { + let mut previous_syntax = first.syntax().clone(); + doc = doc + .append(Doc::space()) + .append(leading_comments(first.syntax())) + .append(build_json_passing_arg(first)); + for arg in args { + doc = doc + .append(trailing_comments(&previous_syntax)) + .append(Doc::text(",")) + .append(Doc::line_or_space()) + .append(leading_comments(arg.syntax())) + .append(build_json_passing_arg(arg.clone())); + previous_syntax = arg.syntax().clone(); } - } else { - doc = doc.append( - Doc::list( - Itertools::intersperse( - items.into_iter(), - Doc::text(",").append(Doc::line_or_space()), - ) - .collect(), - ) - .nest(2), - ); + } + doc.nest(2).group() +} + +fn build_json_passing_arg<'a>(arg: ast::JsonPassingArg) -> Doc<'a> { + let mut doc = arg.expr().map(build_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(name) = arg.name() { + doc = doc + .append(Doc::space()) + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + doc +} + +fn build_json_wrapper_behavior_clause<'a>(clause: ast::JsonWrapperBehaviorClause) -> Doc<'a> { + match clause { + ast::JsonWrapperBehaviorClause::JsonWithConditionalWrapper(clause) => { + let mut doc = Doc::text("with"); + doc = append_keyword_token(doc, clause.conditional_token(), "conditional"); + doc = append_keyword_token(doc, clause.array_token(), "array"); + append_keyword_token(doc, clause.wrapper_token(), "wrapper") + } + ast::JsonWrapperBehaviorClause::JsonWithUnconditionalWrapper(clause) => { + let mut doc = Doc::text("with"); + doc = append_keyword_token(doc, clause.unconditional_token(), "unconditional"); + doc = append_keyword_token(doc, clause.array_token(), "array"); + append_keyword_token(doc, clause.wrapper_token(), "wrapper") + } + ast::JsonWrapperBehaviorClause::JsonWithoutWrapper(clause) => { + let mut doc = Doc::text("without"); + doc = append_keyword_token(doc, clause.array_token(), "array"); + append_keyword_token(doc, clause.wrapper_token(), "wrapper") + } + } +} + +fn build_json_quotes_clause<'a>(clause: ast::JsonQuotesClause) -> Doc<'a> { + let mut doc = clause + .quotes_behavior() + .map(|behavior| match behavior { + ast::QuotesBehavior::KeepQuotes(behavior) => { + append_keyword_token(Doc::text("keep"), behavior.quotes_token(), "quotes") + } + ast::QuotesBehavior::OmitQuotes(behavior) => { + append_keyword_token(Doc::text("omit"), behavior.quotes_token(), "quotes") + } + }) + .unwrap_or_else(Doc::nil); + if let Some(on_scalar) = clause.on_scalar_string() { + doc = doc + .append(Doc::space()) + .append(leading_comments(on_scalar.syntax())) + .append(Doc::text("on")); + doc = append_keyword_token(doc, on_scalar.scalar_token(), "scalar"); + doc = append_keyword_token(doc, on_scalar.string_token(), "string"); + } + doc +} + +fn build_json_on_empty_clause<'a>(clause: ast::JsonOnEmptyClause) -> Doc<'a> { + let mut doc = clause + .json_behavior() + .map(build_json_behavior) + .unwrap_or_else(Doc::nil); + doc = append_keyword_token(doc, clause.on_token(), "on"); + append_keyword_token(doc, clause.empty_token(), "empty") +} + +fn build_json_on_error_clause<'a>(clause: ast::JsonOnErrorClause) -> Doc<'a> { + let mut doc = clause + .json_behavior() + .map(build_json_behavior) + .unwrap_or_else(Doc::nil); + if let Some(on_token) = clause.on_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&on_token)) + .append(Doc::text("on")); + } + if let Some(error_token) = clause.error_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&error_token)) + .append(Doc::text("error")); + } + doc +} + +fn build_json_behavior<'a>(behavior: ast::JsonBehavior) -> Doc<'a> { + match behavior { + ast::JsonBehavior::JsonBehaviorDefault(behavior) => { + let mut doc = Doc::text("default"); + if let Some(expr) = behavior.expr() { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc + } + ast::JsonBehavior::JsonBehaviorEmptyArray(behavior) => { + let mut doc = Doc::text("empty"); + if let Some(array_token) = behavior.array_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&array_token)) + .append(Doc::text("array")); + } + doc + } + ast::JsonBehavior::JsonBehaviorEmptyObject(behavior) => { + let mut doc = Doc::text("empty"); + if let Some(object_token) = behavior.object_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&object_token)) + .append(Doc::text("object")); + } + doc + } + ast::JsonBehavior::JsonBehaviorError(_) => Doc::text("error"), + ast::JsonBehavior::JsonBehaviorFalse(_) => Doc::text("false"), + ast::JsonBehavior::JsonBehaviorNull(_) => Doc::text("null"), + ast::JsonBehavior::JsonBehaviorTrue(_) => Doc::text("true"), + ast::JsonBehavior::JsonBehaviorUnknown(_) => Doc::text("unknown"), + } +} + +fn build_json_array_fn<'a>(json_array_fn: ast::JsonArrayFn) -> Doc<'a> { + let mut doc = Doc::text("json_array"); + if let Some(l_paren) = json_array_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + let exprs = json_array_fn.json_expr_formats().map(|value| { + ( + leading_comments(value.syntax()).append(build_json_expr_format(value.clone())), + value.syntax().clone(), + ) + }); + let selects = json_array_fn.json_select_formats().map(|select| { + ( + leading_comments(select.syntax()).append(build_json_select_format(select.clone())), + select.syntax().clone(), + ) + }); + if let Some(items) = build_comma_separated_docs(exprs.chain(selects)) { + body = body.append(items); } - doc.append(Doc::text(")")).group() + if let Some(null_clause) = json_array_fn.json_null_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(null_clause.syntax())) + .append(build_json_null_clause(null_clause)); + } + if let Some(returning) = json_array_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(r_paren) = json_array_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_semicolon<'a>(semi: Option) -> Doc<'a> { - let Some(semi) = semi else { - return Doc::nil(); - }; - let mut doc = Doc::nil(); - for comment in comment_tokens_before(semi) { - doc = doc.append(Doc::text(comment.text().to_string())); - if is_line_comment(&comment) { - doc = doc.append(Doc::hard_line()); - } +fn build_comma_separated_docs<'a>( + mut items: impl Iterator, SyntaxNode)>, +) -> Option> { + let (first, mut previous_syntax) = items.next()?; + let mut docs = vec![first]; + for (item, syntax) in items { + docs.push( + trailing_comments(&previous_syntax) + .append(Doc::text(",")) + .append(Doc::line_or_space()) + .append(item), + ); + previous_syntax = syntax; } - doc.append(Doc::text(";")) + Some(Doc::list(docs)) } -fn build_expr<'a>(expr: ast::Expr) -> Doc<'a> { - match expr { - ast::Expr::ArrayExpr(array_expr) => build_array_expr(array_expr), - ast::Expr::BetweenExpr(between_expr) => build_between_expr(between_expr), - ast::Expr::BinExpr(bin_expr) => build_bin_expr(bin_expr), - ast::Expr::CallExpr(call_expr) => build_call_expr(call_expr), - ast::Expr::CaseExpr(case_expr) => build_case_expr(case_expr), - ast::Expr::CastExpr(cast_expr) => build_cast_expr(cast_expr), - ast::Expr::Collate(collate) => build_collate_expr(collate), - ast::Expr::FieldExpr(field_expr) => build_field_expr(field_expr), - ast::Expr::IndexExpr(index_expr) => build_index_expr(index_expr), - ast::Expr::Literal(literal) => build_literal(literal), - ast::Expr::NameRef(name_ref) => build_name(name_ref.syntax()), - ast::Expr::ParenExpr(paren_expr) => build_paren_expr(paren_expr), - ast::Expr::PostfixExpr(postfix_expr) => build_postfix_expr(postfix_expr), - ast::Expr::PrefixExpr(prefix_expr) => build_prefix_expr(prefix_expr), - ast::Expr::SliceExpr(slice_expr) => build_slice_expr(slice_expr), - ast::Expr::TupleExpr(tuple_expr) => build_tuple_expr(tuple_expr), +fn build_json_expr_format<'a>(value: ast::JsonExprFormat) -> Doc<'a> { + let mut doc = value.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(format) = value.json_format_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); } + doc.group() } -fn build_array_expr<'a>(array_expr: ast::ArrayExpr) -> Doc<'a> { - let mut doc = Doc::nil(); +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"), + }) + .unwrap_or_else(Doc::nil); + if let Some(format) = select.json_format_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); + } + doc.group() +} - // nested parts of array expressions don't require the array token - if array_expr.array_token().is_some() { - doc = doc.append(Doc::text("array")); - }; +fn build_json_array_agg_fn<'a>(json_array_agg_fn: ast::JsonArrayAggFn) -> Doc<'a> { + let mut doc = Doc::text("json_arrayagg"); + if let Some(l_paren) = json_array_agg_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); - if let Some(select) = array_expr.select() { - doc.append(Doc::text("(")) - .append(build_select_doc(&select)) - .append(Doc::text(")")) - } else { - doc.append(Doc::text("[")) - .append(Doc::list( - Itertools::intersperse( - array_expr.exprs().map(build_expr), - Doc::text(",").append(Doc::space()), - ) - .collect(), - )) - .append(Doc::text("]")) + let mut body = Doc::nil(); + + if let Some(value) = json_array_agg_fn.json_value_expr() { + body = body + .append(leading_comments(value.syntax())) + .append(build_json_value_expr(value)); + } + if let Some(order_by) = json_array_agg_fn.order_by_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(order_by.syntax())) + .append(build_order_by_clause(order_by)); + } + if let Some(null_clause) = json_array_agg_fn.json_null_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(null_clause.syntax())) + .append(build_json_null_clause(null_clause)); + } + if let Some(returning) = json_array_agg_fn.json_returning_clause() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(returning.syntax())) + .append(build_json_returning_clause(returning)); + } + if let Some(r_paren) = json_array_agg_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_field_expr<'a>(field_expr: ast::FieldExpr) -> Doc<'a> { - let mut doc = match field_expr.base() { - Some(base) => build_expr(base), - None => Doc::nil(), - }; - - if let Some(dot) = field_expr.dot_token() { - doc = doc.append(comments_before(dot)); +fn build_json_value_expr<'a>(value: ast::JsonValueExpr) -> Doc<'a> { + let mut doc = value.expr().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(format) = value.json_format_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); } - doc = doc.append(Doc::text(".")); + doc.group() +} - if let Some(star) = field_expr.star_token() { +fn build_json_format_clause<'a>(format: ast::JsonFormatClause) -> Doc<'a> { + let mut doc = Doc::text("format"); + if let Some(json_token) = format.json_token() { doc = doc - .append(leading_comments_token(&star)) - .append(Doc::text("*")); - } else if let Some(field) = field_expr.field() { + .append(Doc::space()) + .append(leading_comments_token(&json_token)) + .append(Doc::text("json")); + } + if let Some(encoding) = format.json_encoding_clause() { doc = doc - .append(leading_comments(field.syntax())) - .append(build_name(field.syntax())); + .append(Doc::line_or_space()) + .append(leading_comments(encoding.syntax())) + .append(build_json_encoding_clause(encoding)); } + doc.group() +} +fn build_json_encoding_clause<'a>(clause: ast::JsonEncodingClause) -> Doc<'a> { + let mut doc = Doc::text("encoding"); + if let Some(encoding) = clause.json_encoding() { + doc = doc + .append(Doc::space()) + .append(leading_comments(encoding.syntax())) + .append(build_name(encoding.syntax())); + } doc } -fn build_index_expr<'a>(index_expr: ast::IndexExpr) -> Doc<'a> { - let mut doc = match index_expr.base() { - Some(base) => build_expr(base), - None => Doc::nil(), +fn build_json_null_clause<'a>(clause: ast::JsonNullClause) -> Doc<'a> { + let (prefix, on_token, null_token) = match clause { + ast::JsonNullClause::JsonAbsentOnNull(clause) => { + ("absent", clause.on_token(), clause.null_token()) + } + ast::JsonNullClause::JsonNullOnNull(clause) => { + ("null", clause.on_token(), clause.on_null_token()) + } }; - if let Some(l_brack) = index_expr.l_brack_token() { - doc = doc.append(comments_before(l_brack)); + let mut doc = Doc::text(prefix); + if let Some(on_token) = on_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&on_token)) + .append(Doc::text("on")); } - doc = doc.append(Doc::text("[")); + if let Some(null_token) = null_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&null_token)) + .append(Doc::text("null")); + } + doc +} - if let Some(index) = index_expr.index() { +fn build_json_returning_clause<'a>(returning: ast::JsonReturningClause) -> Doc<'a> { + let mut doc = Doc::text("returning"); + if let Some(ty) = returning.ty() { doc = doc - .append(leading_comments(index.syntax())) - .append(build_expr(index)); + .append(Doc::space()) + .append(leading_comments(ty.syntax())) + .append(build_type(ty)); } - if let Some(r_brack) = index_expr.r_brack_token() { - doc = doc.append(comments_before(r_brack)); + if let Some(format) = returning.json_format_clause() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(format.syntax())) + .append(build_json_format_clause(format)); } - doc.append(Doc::text("]")) + doc.nest(2).group() } -fn build_slice_expr<'a>(slice_expr: ast::SliceExpr) -> Doc<'a> { - let mut doc = match slice_expr.base() { - Some(base) => build_expr(base), - None => Doc::nil(), - }; +fn build_overlay_fn<'a>(overlay_fn: ast::OverlayFn) -> Doc<'a> { + let mut doc = Doc::text("overlay"); + if let Some(l_paren) = overlay_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); - if let Some(l_brack) = slice_expr.l_brack_token() { - doc = doc.append(comments_before(l_brack)); + let mut body = Doc::nil(); + if let Some(args) = overlay_fn.overlay_args() { + body = body + .append(leading_comments(args.syntax())) + .append(match args { + ast::OverlayArgs::OverlayPlacing(args) => { + let mut doc = args + .string() + .map(|expr| leading_comments(expr.syntax()).append(build_expr(expr))) + .unwrap_or_else(Doc::nil); + doc = append_line_keyword_expr( + doc, + args.placing_token(), + "placing", + args.placing(), + ); + doc = append_line_keyword_expr(doc, args.from_token(), "from", args.from()); + append_line_keyword_expr(doc, args.for_token(), "for", args.for_()).group() + } + ast::OverlayArgs::OverlayExprs(args) => { + let items = args.overlay_exprs().map(|arg| { + let syntax = arg.syntax().clone(); + let doc = leading_comments(arg.syntax()).append(match arg { + ast::OverlayExpr::Expr(expr) => build_expr(expr), + ast::OverlayExpr::NamedArg(arg) => build_named_call_arg(arg), + }); + (doc, syntax) + }); + build_comma_separated_docs(items).unwrap_or_else(Doc::nil) + } + }); } - doc = doc.append(Doc::text("[")); - if let Some(start) = slice_expr.start() { - doc = doc - .append(leading_comments(start.syntax())) - .append(build_expr(start)); + if let Some(r_paren) = overlay_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); } - if let Some(colon) = slice_expr.colon_token() { - doc = doc.append(comments_before(colon)); + doc = doc.append(wrap_body(body)); + doc.append(Doc::text(")")).group() +} + +fn build_substring_fn<'a>(substring_fn: ast::SubstringFn) -> Doc<'a> { + let mut doc = Doc::text("substring"); + if let Some(l_paren) = substring_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); } - doc = doc.append(Doc::text(":")); + doc = doc.append(Doc::text("(")); - if let Some(end) = slice_expr.end() { + let mut body = Doc::nil(); + + if let Some(args) = substring_fn.substring_args() { + body = body + .append(leading_comments(args.syntax())) + .append(match args { + ast::SubstringArgs::SubstringForFrom(args) => { + let mut body = args.string().map(build_expr).unwrap_or_else(Doc::nil); + body = append_line_keyword_expr(body, args.for_token(), "for", args.count()); + append_line_keyword_expr(body, args.from_token(), "from", args.start()) + } + ast::SubstringArgs::SubstringFromFor(args) => { + let mut body = args.string().map(build_expr).unwrap_or_else(Doc::nil); + body = append_line_keyword_expr(body, args.from_token(), "from", args.start()); + append_line_keyword_expr(body, args.for_token(), "for", args.count()) + } + ast::SubstringArgs::SubstringSimilarEscape(args) => { + let mut body = args.string().map(build_expr).unwrap_or_else(Doc::nil); + body = append_line_keyword_expr( + body, + args.similar_token(), + "similar", + args.pattern(), + ); + append_line_keyword_expr(body, args.escape_token(), "escape", args.escape()) + } + ast::SubstringArgs::SubstringExprs(args) => { + build_comma_separated_exprs(args.exprs()).unwrap_or_else(Doc::nil) + } + }); + } + + if let Some(r_paren) = substring_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn append_keyword_token<'a>( + mut doc: Doc<'a>, + token: Option, + keyword: &'static str, +) -> Doc<'a> { + if let Some(token) = token { doc = doc - .append(leading_comments(end.syntax())) - .append(build_expr(end)); + .append(Doc::space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); } - if let Some(r_brack) = slice_expr.r_brack_token() { - doc = doc.append(comments_before(r_brack)); + doc +} + +fn append_line_keyword_expr<'a>( + mut doc: Doc<'a>, + token: Option, + keyword: &'static str, + expr: Option, +) -> Doc<'a> { + if let Some(token) = token { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&token)) + .append(Doc::text(keyword)); } - doc.append(Doc::text("]")) + if let Some(expr) = expr { + doc = doc + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc } -fn build_tuple_expr<'a>(tuple_expr: ast::TupleExpr) -> Doc<'a> { - let mut doc = if tuple_expr.row_token().is_some() { - Doc::text("row") +fn build_trim_fn<'a>(trim_fn: ast::TrimFn) -> Doc<'a> { + let mut doc = Doc::text("trim"); + if let Some(l_paren) = trim_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + let has_side = if let Some(side) = trim_fn.trim_side() { + body = body + .append(leading_comments(side.syntax())) + .append(match side { + ast::TrimSide::TrimBoth(_) => Doc::text("both"), + ast::TrimSide::TrimLeading(_) => Doc::text("leading"), + ast::TrimSide::TrimTrailing(_) => Doc::text("trailing"), + }); + true } else { - Doc::nil() + false }; - if let Some(l_paren) = tuple_expr.l_paren_token() { - doc = doc.append(comments_before(l_paren)); + if let Some(args) = trim_fn.trim_args() { + if has_side { + body = body.append(Doc::space()); + } + body = body + .append(leading_comments(args.syntax())) + .append(match args { + ast::TrimArgs::TrimFrom(args) => { + let mut body = Doc::text("from"); + if let Some(exprs) = build_comma_separated_exprs(args.exprs()) { + body = body.append(Doc::space()).append(exprs); + } + body + } + ast::TrimArgs::TrimExprFrom(args) => { + let mut exprs = args.exprs(); + let mut body = exprs.next().map(build_expr).unwrap_or_else(Doc::nil); + if let Some(from) = args.from_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&from)) + .append(Doc::text("from")); + } + if let Some(exprs) = build_comma_separated_exprs(exprs) { + body = body.append(Doc::space()).append(exprs); + } + body + } + ast::TrimArgs::TrimExprs(args) => { + build_comma_separated_exprs(args.exprs()).unwrap_or_else(Doc::nil) + } + }); } - doc = doc.append(Doc::text("(")); - let exprs: Vec> = tuple_expr - .exprs() + if let Some(r_paren) = trim_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_comma_separated_exprs<'a>(exprs: impl Iterator) -> Option> { + let exprs: Vec> = exprs .map(|expr| { let leading = leading_comments(expr.syntax()); let trailing = trailing_comments(expr.syntax()); @@ -663,115 +3324,161 @@ fn build_tuple_expr<'a>(tuple_expr: ast::TupleExpr) -> Doc<'a> { }) .collect(); if exprs.is_empty() { - if let Some(r_paren) = tuple_expr.r_paren_token() { - doc = doc.append(comments_before(r_paren)); - } + None } else { - doc = doc.append(Doc::list( - Itertools::intersperse(exprs.into_iter(), Doc::text(",").append(Doc::space())) + Some( + Doc::list( + Itertools::intersperse( + exprs.into_iter(), + Doc::text(",").append(Doc::line_or_space()), + ) .collect(), - )); + ) + .group(), + ) } +} - doc.append(Doc::text(")")) +fn build_position_fn<'a>(position_fn: ast::PositionFn) -> Doc<'a> { + let mut doc = Doc::text("position"); + if let Some(l_paren) = position_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(pos) = position_fn.pos() { + body = body + .append(leading_comments(pos.syntax())) + .append(build_expr(pos)); + } + if let Some(in_token) = position_fn.in_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&in_token)) + .append(Doc::text("in")); + } + if let Some(string) = position_fn.string() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments(string.syntax())) + .append(build_expr(string)); + } + if let Some(r_paren) = position_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_between_expr<'a>(between_expr: ast::BetweenExpr) -> Doc<'a> { - let mut doc = build_expr(between_expr.target().unwrap()); - if between_expr.not_token().is_some() { - doc = doc.append(Doc::space()).append(Doc::text("not")); +fn build_collation_for_fn<'a>(collation_for_fn: ast::CollationForFn) -> Doc<'a> { + let mut doc = Doc::text("collation"); + if let Some(for_token) = collation_for_fn.for_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&for_token)) + .append(Doc::text("for")); } - doc = doc.append(Doc::space()).append(Doc::text("between")); - match between_expr.between_symmetry() { - Some(ast::BetweenSymmetry::Asymmetric(_)) => { - doc = doc.append(Doc::space()).append(Doc::text("asymmetric")); - } - Some(ast::BetweenSymmetry::Symmetric(_)) => { - doc = doc.append(Doc::space()).append(Doc::text("symmetric")); + if let Some(l_paren) = collation_for_fn.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)); } - None => (), + } else { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(expr) = collation_for_fn.expr() { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(r_paren) = collation_for_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc = doc.append(wrap_body(body)); + doc.append(Doc::text(")")).group() +} + +fn build_extract_fn<'a>(extract_fn: ast::ExtractFn) -> Doc<'a> { + let mut doc = Doc::text("extract"); + if let Some(l_paren) = extract_fn.l_paren_token() { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + + if let Some(field) = extract_fn.extract_field() { + body = body + .append(leading_comments(field.syntax())) + .append(match field { + ast::ExtractField::ExtractFieldLiteral(field) => { + field.literal().map(build_literal).unwrap_or_else(Doc::nil) + } + ast::ExtractField::ExtractFieldName(field) => { + if field.ident_token().is_some() { + build_name(field.syntax()) + } else { + build_keyword_node(field.syntax()) + } + } + }); + } + + if let Some(from) = extract_fn.from_token() { + body = body + .append(Doc::line_or_space()) + .append(leading_comments_token(&from)) + .append(Doc::text("from")); + } + if let Some(expr) = extract_fn.expr() { + body = body + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); } - doc.append(Doc::space()) - .append(build_expr(between_expr.start().unwrap())) - .append(Doc::space()) - .append(Doc::text("and")) - .append(Doc::space()) - .append(build_expr(between_expr.end().unwrap())) + if let Some(r_paren) = extract_fn.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() } -fn build_call_expr<'a>(call_expr: ast::CallExpr) -> Doc<'a> { - if let (Some(expr), Some(arg_list)) = (call_expr.expr(), call_expr.arg_list()) { - if call_expr.within_clause().is_some() { - todo!("within clauses on call expressions are not supported yet") - } else if call_expr.filter_clause().is_some() { - todo!("filter clauses on call expressions are not supported yet") - } else if call_expr.null_treatment().is_some() { - todo!("null treatment on call expressions is not supported yet") - } else if call_expr.over_clause().is_some() { - todo!("over clauses on call expressions are not supported yet") - } - build_expr(expr) - .append(comments_before(arg_list.syntax().clone())) - .append(build_call_arg_list(arg_list)) - } else if let Some(_all_fn) = call_expr.all_fn() { - todo!("all function expressions are not supported yet") - } else if let Some(_any_fn) = call_expr.any_fn() { - todo!("any function expressions are not supported yet") - } else if let Some(_collation_for_fn) = call_expr.collation_for_fn() { - todo!("collation_for function expressions are not supported yet") - } else if let Some(_exists_fn) = call_expr.exists_fn() { - todo!("exists function expressions are not supported yet") - } else if let Some(_extract_fn) = call_expr.extract_fn() { - todo!("extract function expressions are not supported yet") - } else if let Some(_graph_table_fn) = call_expr.graph_table_fn() { - todo!("graph_table function expressions are not supported yet") - } else if let Some(_json_array_agg_fn) = call_expr.json_array_agg_fn() { - todo!("json_arrayagg function expressions are not supported yet") - } else if let Some(_json_array_fn) = call_expr.json_array_fn() { - todo!("json_array function expressions are not supported yet") - } else if let Some(_json_exists_fn) = call_expr.json_exists_fn() { - todo!("json_exists function expressions are not supported yet") - } else if let Some(_json_fn) = call_expr.json_fn() { - todo!("json function expressions are not supported yet") - } else if let Some(_json_object_agg_fn) = call_expr.json_object_agg_fn() { - todo!("json_objectagg function expressions are not supported yet") - } else if let Some(_json_object_fn) = call_expr.json_object_fn() { - todo!("json_object function expressions are not supported yet") - } else if let Some(_json_query_fn) = call_expr.json_query_fn() { - todo!("json_query function expressions are not supported yet") - } else if let Some(_json_scalar_fn) = call_expr.json_scalar_fn() { - todo!("json_scalar function expressions are not supported yet") - } else if let Some(_json_serialize_fn) = call_expr.json_serialize_fn() { - todo!("json_serialize function expressions are not supported yet") - } else if let Some(_json_value_fn) = call_expr.json_value_fn() { - todo!("json_value function expressions are not supported yet") - } else if let Some(_overlay_fn) = call_expr.overlay_fn() { - todo!("overlay function expressions are not supported yet") - } else if let Some(_position_fn) = call_expr.position_fn() { - todo!("position function expressions are not supported yet") - } else if let Some(_some_fn) = call_expr.some_fn() { - todo!("some function expressions are not supported yet") - } else if let Some(_substring_fn) = call_expr.substring_fn() { - todo!("substring function expressions are not supported yet") - } else if let Some(_trim_fn) = call_expr.trim_fn() { - todo!("trim function expressions are not supported yet") - } else if let Some(_xml_element_fn) = call_expr.xml_element_fn() { - todo!("xmlelement function expressions are not supported yet") - } else if let Some(_xml_exists_fn) = call_expr.xml_exists_fn() { - todo!("xmlexists function expressions are not supported yet") - } else if let Some(_xml_forest_fn) = call_expr.xml_forest_fn() { - todo!("xmlforest function expressions are not supported yet") - } else if let Some(_xml_parse_fn) = call_expr.xml_parse_fn() { - todo!("xmlparse function expressions are not supported yet") - } else if let Some(_xml_pi_fn) = call_expr.xml_pi_fn() { - todo!("xmlpi function expressions are not supported yet") - } else if let Some(_xml_root_fn) = call_expr.xml_root_fn() { - todo!("xmlroot function expressions are not supported yet") - } else if let Some(_xml_serialize_fn) = call_expr.xml_serialize_fn() { - todo!("xmlserialize function expressions are not supported yet") - } else { - unreachable!("a call expression should contain a supported function node") +fn build_parenthesized_expr_or_select_fn<'a>( + keyword: &'static str, + l_paren: Option, + expr: Option, + select: Option, + r_paren: Option, +) -> Doc<'a> { + let mut doc = Doc::text(keyword); + if let Some(l_paren) = l_paren { + doc = doc.append(comments_before(l_paren)); + } + doc = doc.append(Doc::text("(")); + + let mut body = Doc::nil(); + if let Some(expr) = expr { + body = body + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } else if let Some(select) = select { + body = body + .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"), + }); + } + + if let Some(r_paren) = r_paren { + body = body.append(comments_before(r_paren)); } + doc = doc.append(wrap_body(body)); + doc.append(Doc::text(")")).group() } fn build_call_arg_list<'a>(arg_list: ast::ArgList) -> Doc<'a> { @@ -781,65 +3488,169 @@ fn build_call_arg_list<'a>(arg_list: ast::ArgList) -> Doc<'a> { } doc = doc.append(Doc::text("(")); + let mut body = Doc::nil(); if let Some(star) = arg_list.star_token() { - doc = doc + body = body .append(leading_comments_token(&star)) .append(Doc::text("*")); - if let Some(r_paren) = arg_list.r_paren_token() { - doc = doc.append(comments_before(r_paren)); + } else { + let mut has_quantifier = false; + if let Some(quantifier) = arg_list.all_or_distinct() { + has_quantifier = true; + body = body + .append(leading_comments(quantifier.syntax())) + .append(match quantifier { + ast::AllOrDistinct::All(_) => Doc::text("all"), + ast::AllOrDistinct::Distinct(_) => Doc::text("distinct"), + }); + } + + let args = arg_list.args().map(|arg| { + let syntax = arg.syntax().clone(); + let doc = leading_comments(arg.syntax()).append(build_call_arg(arg)); + (doc, syntax) + }); + if let Some(args) = build_comma_separated_docs(args) { + if has_quantifier { + body = body.append(Doc::space()); + } + body = body.append(args); } - return doc.append(Doc::text(")")); } - let mut has_quantifier = false; - if let Some(quantifier) = arg_list.all_or_distinct() { - has_quantifier = true; - doc = doc - .append(leading_comments(quantifier.syntax())) - .append(match quantifier { - ast::AllOrDistinct::All(_) => Doc::text("all"), - ast::AllOrDistinct::Distinct(_) => Doc::text("distinct"), - }); + if let Some(r_paren) = arg_list.r_paren_token() { + body = body.append(comments_before(r_paren)); } + doc = doc.append(wrap_body(body)); - let args: Vec> = arg_list - .args() - .map(|arg| { - let leading = leading_comments(arg.syntax()); - let trailing = trailing_comments(arg.syntax()); - leading.append(build_call_arg(arg)).append(trailing) - }) - .collect(); - if args.is_empty() { - if let Some(r_paren) = arg_list.r_paren_token() { - doc = doc.append(comments_before(r_paren)); - } + doc.append(Doc::text(")")).group() +} + +fn build_call_arg<'a>(arg: ast::Arg) -> Doc<'a> { + let mut doc = if let Some(named_arg) = arg.named_arg() { + build_named_call_arg(named_arg) } else { - if has_quantifier { - doc = doc.append(Doc::space()); + let mut doc = Doc::nil(); + if arg.variadic_token().is_some() { + doc = doc.append(Doc::text("variadic")).append(Doc::space()); } - doc = doc.append(Doc::list( - Itertools::intersperse(args.into_iter(), Doc::text(",").append(Doc::space())).collect(), - )); + if let Some(expr) = arg.expr() { + doc = doc + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + doc + }; + if let Some(order_by_clause) = arg.order_by_clause() { + doc = doc + .append(Doc::space()) + .append(leading_comments(order_by_clause.syntax())) + .append(build_order_by_clause(order_by_clause)); + } + doc +} + +fn build_order_by_clause<'a>(clause: ast::OrderByClause) -> Doc<'a> { + let mut doc = Doc::text("order").append(Doc::space()); + if let Some(by_token) = clause.by_token() { + doc = doc.append(leading_comments_token(&by_token)); } + doc = doc.append(Doc::text("by")); - doc.append(Doc::text(")")) + if let Some(list) = clause.sort_by_list() { + 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(), + )); + } + doc } -fn build_call_arg<'a>(arg: ast::Arg) -> Doc<'a> { - if let Some(_named_arg) = arg.named_arg() { - todo!("named function arguments are not supported yet") +fn build_sort_by<'a>(sort_by: ast::SortBy) -> Doc<'a> { + let mut doc = Doc::nil(); + if let Some(expr) = sort_by.expr() { + doc = doc + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + + if let Some(order) = sort_by.sort_order() { + doc = doc + .append(Doc::space()) + .append(leading_comments(order.syntax())) + .append(match order { + ast::SortOrder::SortAsc(_) => Doc::text("asc"), + ast::SortOrder::SortDesc(_) => Doc::text("desc"), + ast::SortOrder::SortUsing(using) => { + let mut doc = Doc::text("using"); + if let Some(operator_call) = using.operator_call() { + doc = doc + .append(Doc::space()) + .append(leading_comments(operator_call.syntax())) + .append(build_operator_call(&operator_call)); + } else if let Some(op) = using.op() { + doc = doc + .append(Doc::space()) + .append(leading_comments(op.syntax())) + .append(build_operator(&op)); + } + doc + } + }); } - if let Some(_order_by_clause) = arg.order_by_clause() { - todo!("order by clauses in function arguments are not supported yet") + + if let Some(nulls_order) = sort_by.nulls_order() { + doc = doc + .append(Doc::space()) + .append(leading_comments(nulls_order.syntax())) + .append(Doc::text("nulls")) + .append(Doc::space()); + let suffix = match nulls_order { + ast::NullsOrder::NullsFirst(first) => first + .first_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil) + .append(Doc::text("first")), + ast::NullsOrder::NullsLast(last) => last + .last_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil) + .append(Doc::text("last")), + }; + doc = doc.append(suffix); } + doc.append(trailing_comments(sort_by.syntax())) +} + +fn build_named_call_arg<'a>(arg: ast::NamedArg) -> Doc<'a> { let mut doc = Doc::nil(); - if arg.variadic_token().is_some() { - doc = doc.append(Doc::text("variadic")).append(Doc::space()); + if let Some(name) = arg.name() { + doc = doc + .append(leading_comments(name.syntax())) + .append(build_name(name.syntax())); + } + + if let Some(fat_arrow) = arg.fat_arrow_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&fat_arrow)) + .append(Doc::text("=>")); + } else if let Some(colon_eq) = arg.colon_eq_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&colon_eq)) + .append(Doc::text(":=")); } + if let Some(expr) = arg.expr() { doc = doc + .append(Doc::space()) .append(leading_comments(expr.syntax())) .append(build_expr(expr)); } @@ -949,20 +3760,22 @@ fn build_cast_expr<'a>(cast_expr: ast::CastExpr) -> Doc<'a> { if let Some(l_paren) = cast_expr.l_paren_token() { doc = doc.append(comments_before(l_paren)); } - doc = doc - .append(Doc::text("(")) - .append(leading_comments(expr.syntax())) + let mut body = leading_comments(expr.syntax()) .append(build_expr(expr)) - .append(Doc::space()) + .append(Doc::line_or_space()) .append(leading_comments_token(&as_token)) .append(Doc::text("as")) - .append(Doc::space()) + .append(Doc::line_or_space()) .append(leading_comments(ty.syntax())) - .append(build_type(ty)); + .append(build_type(ty)) + .group(); if let Some(r_paren) = cast_expr.r_paren_token() { - doc = doc.append(comments_before(r_paren)); + body = body.append(comments_before(r_paren)); } - doc = doc.append(Doc::text(")")) + doc = doc + .append(Doc::text("(")) + .append(wrap_body(body)) + .append(Doc::text(")")) } else { let literal = cast_expr.literal().unwrap(); doc = doc @@ -981,12 +3794,17 @@ fn build_cast_expr<'a>(cast_expr: ast::CastExpr) -> Doc<'a> { } fn build_collate_expr<'a>(collate: ast::Collate) -> Doc<'a> { - let mut doc = collate.expr().map(build_expr).unwrap_or_else(Doc::nil); + let expr = collate.expr(); + let has_expr = expr.is_some(); + let mut doc = expr.map(build_expr).unwrap_or_else(Doc::nil); if let Some(collate_token) = collate.collate_token() { doc = doc.append(comments_before(collate_token)); } - doc = doc.append(Doc::space()).append(Doc::text("collate")); + if has_expr { + doc = doc.append(Doc::space()); + } + doc = doc.append(Doc::text("collate")); if let Some(collation) = collate.collation_ref() { doc = doc @@ -1006,10 +3824,14 @@ fn build_paren_expr<'a>(paren_expr: ast::ParenExpr) -> Doc<'a> { } doc = doc.append(Doc::text("(")); + let mut body = Doc::nil(); if let Some(expr) = paren_expr.expr() { - doc = doc + body = body .append(leading_comments(expr.syntax())) - .append(build_expr(expr)); + .append(match expr { + ast::Expr::BinExpr(binary) => build_bin_expr_doc(binary, false), + 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() { @@ -1027,9 +3849,9 @@ fn build_paren_expr<'a>(paren_expr: ast::ParenExpr) -> Doc<'a> { } if let Some(r_paren) = paren_expr.r_paren_token() { - doc = doc.append(comments_before(r_paren)); + body = body.append(comments_before(r_paren)); } - doc.append(Doc::text(")")) + doc.append(wrap_body(body)).append(Doc::text(")")).group() } fn build_postfix_expr<'a>(postfix_expr: ast::PostfixExpr) -> Doc<'a> { @@ -1101,30 +3923,327 @@ fn build_normalized_postfix<'a>( } fn build_bin_expr<'a>(bin_expr: ast::BinExpr) -> Doc<'a> { + build_bin_expr_doc(bin_expr, true) +} + +fn build_bin_expr_doc<'a>(bin_expr: ast::BinExpr, wrap: bool) -> Doc<'a> { let lhs = bin_expr.lhs().unwrap(); let rhs = bin_expr.rhs().unwrap(); let before_op = trailing_comments(lhs.syntax()); let after_op = leading_comments(rhs.syntax()); + let rhs_is_uncommented_quantifier = comment_tokens_before(rhs.syntax().clone()).is_empty() + && match &rhs { + ast::Expr::CallExpr(call) => { + call.all_fn().is_some() || call.any_fn().is_some() || call.some_fn().is_some() + } + _ => false, + }; - build_expr(lhs) + let doc = build_expr(lhs) .append(before_op) - .append(Doc::space()) + .append(if rhs_is_uncommented_quantifier { + Doc::space() + } else { + Doc::line_or_space() + }) .append(build_op(bin_expr.op().unwrap())) .append(Doc::space()) .append(after_op) - .append(build_expr(rhs)) + .append(build_expr(rhs)); + if rhs_is_uncommented_quantifier || !wrap { + doc + } else { + doc.nest(2).group() + } +} + +fn build_within_clause<'a>(within_clause: ast::WithinClause) -> Doc<'a> { + let mut doc = Doc::text("within").append(Doc::space()); + if let Some(group_token) = within_clause.group_token() { + doc = doc.append(leading_comments_token(&group_token)); + } + doc = doc.append(Doc::text("group")); + if let Some(l_paren) = within_clause.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); + } + + let mut body = Doc::nil(); + if let Some(order_by) = within_clause.order_by_clause() { + body = body + .append(leading_comments(order_by.syntax())) + .append(build_order_by_clause(order_by)); + } + if let Some(r_paren) = within_clause.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_over_clause<'a>(over_clause: ast::OverClause) -> Doc<'a> { + let mut doc = Doc::text("over"); + if let Some(target) = over_clause.over_target() { + doc = doc + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(match target { + ast::OverTarget::WindowRef(window_ref) => build_name(window_ref.syntax()), + ast::OverTarget::OverWindowSpec(window_spec) => build_over_window_spec(window_spec), + }); + } + doc +} + +fn build_over_window_spec<'a>(over_window_spec: ast::OverWindowSpec) -> Doc<'a> { + let doc = Doc::text("("); + let mut body = Doc::nil(); + if let Some(window_spec) = over_window_spec.window_spec() { + body = body + .append(leading_comments(window_spec.syntax())) + .append(build_window_spec(window_spec)); + } + if let Some(r_paren) = over_window_spec.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_window_spec<'a>(window_spec: ast::WindowSpec) -> Doc<'a> { + let mut parts = Vec::new(); + if let Some(window_ref) = window_spec.window_ref() { + parts.push(leading_comments(window_ref.syntax()).append(build_name(window_ref.syntax()))); + } + if let Some(partition_by) = window_spec.partition_by_clause() { + parts.push( + leading_comments(partition_by.syntax()).append(build_partition_by_clause(partition_by)), + ); + } + if let Some(order_by) = window_spec.order_by_clause() { + parts.push(leading_comments(order_by.syntax()).append(build_order_by_clause(order_by))); + } + if let Some(frame) = window_spec.frame_clause() { + parts.push(leading_comments(frame.syntax()).append(build_frame_clause(frame))); + } + + Doc::list(Itertools::intersperse(parts.into_iter(), Doc::line_or_space()).collect()).group() +} + +fn build_partition_by_clause<'a>(partition_by: ast::PartitionByClause) -> Doc<'a> { + let mut doc = Doc::text("partition").append(Doc::space()); + if let Some(by_token) = partition_by.by_token() { + doc = doc.append(leading_comments_token(&by_token)); + } + doc = doc.append(Doc::text("by")); + if let Some(exprs) = build_comma_separated_exprs(partition_by.exprs()) { + doc = doc.append(Doc::space()).append(exprs); + } + doc +} + +fn build_frame_clause<'a>(frame: ast::FrameClause) -> Doc<'a> { + let mut doc = match frame.frame_units() { + Some(ast::FrameUnits::FrameGroups(_)) => Doc::text("groups"), + Some(ast::FrameUnits::FrameRange(_)) => Doc::text("range"), + Some(ast::FrameUnits::FrameRows(_)) => Doc::text("rows"), + None => Doc::nil(), + }; + if let Some(extent) = frame.frame_extent() { + doc = doc + .append(Doc::space()) + .append(leading_comments(extent.syntax())) + .append(build_frame_extent(extent)); + } + if let Some(exclude) = frame.frame_exclude() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments(exclude.syntax())) + .append(build_frame_exclude(exclude)); + } + doc.nest(2).group() +} + +fn build_frame_extent<'a>(extent: ast::FrameExtent) -> Doc<'a> { + match extent { + ast::FrameExtent::FrameBetween(between) => { + let mut doc = Doc::text("between"); + if let Some(start) = between.start() { + doc = doc + .append(Doc::space()) + .append(leading_comments(start.syntax())) + .append(build_frame_bound(start)); + } + if let Some(and_token) = between.and_token() { + doc = doc + .append(Doc::line_or_space()) + .append(leading_comments_token(&and_token)) + .append(Doc::text("and")); + } + if let Some(end) = between.end() { + doc = doc + .append(Doc::space()) + .append(leading_comments(end.syntax())) + .append(build_frame_bound(end)); + } + doc.nest(2).group() + } + ast::FrameExtent::FrameBound(bound) => build_frame_bound(bound), + } +} + +fn build_frame_bound<'a>(bound: ast::FrameBound) -> Doc<'a> { + match bound { + ast::FrameBound::CurrentRow(current_row) => Doc::text("current") + .append(Doc::space()) + .append( + current_row + .row_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("row")), + ast::FrameBound::ExprFollowing(following) => { + build_expr_frame_bound(following.expr(), following.following_token(), "following") + } + ast::FrameBound::ExprPreceding(preceding) => { + build_expr_frame_bound(preceding.expr(), preceding.preceding_token(), "preceding") + } + ast::FrameBound::UnboundedFollowing(following) => Doc::text("unbounded") + .append(Doc::space()) + .append( + following + .following_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("following")), + ast::FrameBound::UnboundedPreceding(preceding) => Doc::text("unbounded") + .append(Doc::space()) + .append( + preceding + .preceding_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("preceding")), + } +} + +fn build_expr_frame_bound<'a>( + expr: Option, + suffix_token: Option, + suffix: &'static str, +) -> Doc<'a> { + let mut doc = expr.map(build_expr).unwrap_or_else(Doc::nil); + if let Some(suffix_token) = suffix_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&suffix_token)) + .append(Doc::text(suffix)); + } + doc +} + +fn build_frame_exclude<'a>(exclude: ast::FrameExclude) -> Doc<'a> { + let mut doc = Doc::text("exclude"); + if let Some(target) = exclude.frame_exclude_target() { + doc = doc + .append(Doc::space()) + .append(leading_comments(target.syntax())) + .append(match target { + ast::FrameExcludeTarget::CurrentRow(current_row) => Doc::text("current") + .append(Doc::space()) + .append( + current_row + .row_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("row")), + ast::FrameExcludeTarget::Group(_) => Doc::text("group"), + ast::FrameExcludeTarget::NoOthers(no_others) => Doc::text("no") + .append(Doc::space()) + .append( + no_others + .others_token() + .map(|token| leading_comments_token(&token)) + .unwrap_or_else(Doc::nil), + ) + .append(Doc::text("others")), + ast::FrameExcludeTarget::Ties(_) => Doc::text("ties"), + }); + } + doc +} + +fn build_filter_clause<'a>(filter_clause: ast::FilterClause) -> Doc<'a> { + let mut doc = Doc::text("filter"); + if let Some(l_paren) = filter_clause.l_paren_token() { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&l_paren)) + .append(Doc::text("(")); + } + + let mut body = Doc::nil(); + if let Some(where_token) = filter_clause.where_token() { + body = body + .append(leading_comments_token(&where_token)) + .append(Doc::text("where")); + } + if let Some(expr) = filter_clause.expr() { + body = body + .append(Doc::space()) + .append(leading_comments(expr.syntax())) + .append(build_expr(expr)); + } + if let Some(r_paren) = filter_clause.r_paren_token() { + body = body.append(comments_before(r_paren)); + } + doc.append(wrap_body(body)).append(Doc::text(")")).group() +} + +fn build_null_treatment<'a>(null_treatment: ast::NullTreatment) -> Doc<'a> { + let (keyword, nulls_token) = match null_treatment { + ast::NullTreatment::IgnoreNulls(ignore_nulls) => ("ignore", ignore_nulls.nulls_token()), + ast::NullTreatment::RespectNulls(respect_nulls) => ("respect", respect_nulls.nulls_token()), + }; + + let mut doc = Doc::text(keyword); + if let Some(nulls_token) = nulls_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&nulls_token)) + .append(Doc::text("nulls")); + } + doc } fn build_json_keys_unique_clause<'a>(clause: ast::JsonKeysUniqueClause) -> Doc<'a> { - let prefix = match clause { - ast::JsonKeysUniqueClause::JsonWithoutUniqueKeys(_) => "without", - ast::JsonKeysUniqueClause::JsonWithUniqueKeys(_) => "with", + let (prefix, unique_token, keys_token) = match clause { + ast::JsonKeysUniqueClause::JsonWithoutUniqueKeys(clause) => { + ("without", clause.unique_token(), clause.keys_token()) + } + ast::JsonKeysUniqueClause::JsonWithUniqueKeys(clause) => { + ("with", clause.unique_token(), clause.keys_token()) + } }; - Doc::text(prefix) - .append(Doc::space()) - .append(Doc::text("unique")) - .append(Doc::space()) - .append(Doc::text("keys")) + + let mut doc = Doc::text(prefix); + if let Some(unique_token) = unique_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&unique_token)) + .append(Doc::text("unique")); + } + if let Some(keys_token) = keys_token { + doc = doc + .append(Doc::space()) + .append(leading_comments_token(&keys_token)) + .append(Doc::text("keys")); + } + doc } fn build_unicode_normal_form<'a>(form: ast::UnicodeNormalForm) -> Doc<'a> { diff --git a/crates/squawk_fmt/tests/after/create_table.snap b/crates/squawk_fmt/tests/after/create_table.snap index 69f9e429..6414cfd6 100644 --- a/crates/squawk_fmt/tests/after/create_table.snap +++ b/crates/squawk_fmt/tests/after/create_table.snap @@ -2,18 +2,18 @@ source: crates/squawk_fmt/tests/tests.rs input_file: crates/squawk_fmt/tests/before/create_table.sql --- -create table u(); +create table u (); -create table t( +create table t ( a int, b text ); -- users table -create table users(id int); +create table users (id int); -- columns that have various quoting requirements -create table cols( +create table cols ( "left" int, "select" text, data int, @@ -23,48 +23,55 @@ create table cols( ); -- table names -create table public.accounts(id int); -create table "Public"."Users"(id int); -create table foo.quoted_names( +create table public.accounts (id int); +create table "Public"."Users" (id int); +create table foo.quoted_names ( data int, value text ); -create table "left"(id int); -create table "table"(id int); -create table U&"d\0061t\+000061"(id int); -create table U&"d!0061tum" uescape '!'(id int); -create table /* foo */ foo /* bar */./* buzz */ bar(id int); +create table "left" (id int); +create table "table" (id int); +create table U&"d\0061t\+000061" (id int); +create table U&"d!0061tum" uescape '!' (id int); +create table /* foo */ foo /* bar */./* buzz */ bar (id int); -- comments inside a name node -create table U&"d!0061tum" /* mid */ uescape '!'(id int); -create table t(U&"c!006fl" /* c */ uescape '!' int); -create table t(U&"c!006fl" uescape /* c */ '!' int); +create table U&"d!0061tum" /* mid */ uescape '!' (id int); +create table t (U&"c!006fl" /* c */ uescape '!' int); +create table t (U&"c!006fl" uescape /* c */ '!' int); -- comments inside a path -create table foo /*a*/ /*b*/.bar(id int); +create table foo /*a*/ /*b*/.bar (id int); create table foo -- a line comment -.bar(id int); +.bar (id int); -- comments between table args -create table t( +create table t ( a int /*x*/, b int ); -create table t( +create table t ( /*a*/ a int, /*b*/ b int /*c*/ ); -create table t( +create table t ( a int -- one , b int ); -create table t( +create table t ( a int, b int -- two ); -- line comment before the semicolon -create table t(a int)-- one +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 +); diff --git a/crates/squawk_fmt/tests/after/create_table_like.snap b/crates/squawk_fmt/tests/after/create_table_like.snap index 21170705..9c1d3c3a 100644 --- a/crates/squawk_fmt/tests/after/create_table_like.snap +++ b/crates/squawk_fmt/tests/after/create_table_like.snap @@ -2,24 +2,24 @@ source: crates/squawk_fmt/tests/tests.rs input_file: crates/squawk_fmt/tests/before/create_table_like.sql --- -create table t(like foo); -create table t( +create table t (like foo); +create table t ( like foo, id int ); -create table t( +create table t ( id int, like foo, like bar ); -- like options -create table t(like foo including all); -create table t(like foo excluding indexes); -create table t( +create table t (like foo including all); +create table t (like foo excluding indexes); +create table t ( like foo including defaults excluding constraints including identity ); -create table t( +create table t ( like foo including comments including compression @@ -29,16 +29,29 @@ create table t( ); -- table names -create table t(like public.accounts); -create table t(like "Public"."Users" including all); -create table t(like foo.bar); -create table t(like "select"); -create table t(like U&"d!0061tum" uescape '!' including all); +create table t (like public.accounts); +create table t (like "Public"."Users" including all); +create table t (like foo.bar); +create table t (like "select"); +create table t (like U&"d!0061tum" uescape '!' including all); -- comments -create table t(like /* a */ foo /* b */ including /* c */ all); -create table t( +create table t (like /* a */ foo /* b */ including /* c */ all); +create table t ( like foo -- a line comment including all ); + +create table a_very_long_destination_table_name ( + like a_very_long_source_schema_name.a_very_long_source_table_name + including comments + including compression + including constraints + including defaults + including generated + including identity + including indexes + including statistics + including storage +); diff --git a/crates/squawk_fmt/tests/after/custom_operator.snap b/crates/squawk_fmt/tests/after/custom_operator.snap index 41c9fc2b..6fc3bb35 100644 --- a/crates/squawk_fmt/tests/after/custom_operator.snap +++ b/crates/squawk_fmt/tests/after/custom_operator.snap @@ -4,3 +4,7 @@ input_file: crates/squawk_fmt/tests/before/custom_operator.sql --- select 1 /*before*/ <<<< /*after*/ 1; select ##### /*after*/ 1; +select + a_very_long_left_operand_name <<<< a_very_long_right_operand_name, + a_second_very_long_left_operand_name + ##### a_second_very_long_right_operand_name; diff --git a/crates/squawk_fmt/tests/after/from.snap b/crates/squawk_fmt/tests/after/from.snap index 38b71d64..7f6cf144 100644 --- a/crates/squawk_fmt/tests/after/from.snap +++ b/crates/squawk_fmt/tests/after/from.snap @@ -4,8 +4,33 @@ input_file: crates/squawk_fmt/tests/before/from.sql --- select * from foo; select * from public.foo as f, bar b; -select * from users tablesample bernoulli(10) repeatable(42); -select - * +select * from foo as f (id, display_name); +select * from foo f (id int, display_name text collate "C"); +select * +from foo /* before alias */ as /* before alias name */ f /* before open paren */ ( + /* after open paren */ id /* before comma */, + /* after comma */ display_name /* before close paren */ + ); +select * from users tablesample bernoulli (10) repeatable (42); +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 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; diff --git a/crates/squawk_fmt/tests/after/graph_table.snap b/crates/squawk_fmt/tests/after/graph_table.snap new file mode 100644 index 00000000..01c1852c --- /dev/null +++ b/crates/squawk_fmt/tests/after/graph_table.snap @@ -0,0 +1,77 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/graph_table.sql +--- +select * from graph_table(g match (a) columns (a)); + +select * +from lateral graph_table( + public.g + match + (a is person)-[e is knows where e.weight > 1]->(b) + where a.active + columns (a.name as person_name, b.name) + ) as matches; + +select * +from graph_table( + g + match + (a)<-[left_edge]-(b), + (c)-[any_edge]-(d), + (e)<-(f), + (h)->(i), + (j)-(k), + ((x)->(y) where x.active) + columns (a) + ); + +select * +from graph_table(g match (a)->{1}(b), (c)-{, 3}(d), (e)-{2, 4}(f) columns (a)); + +select * +from graph_table( + a_very_long_property_graph_name + match + (a_very_long_vertex_variable + is a_very_long_vertex_label + where a_very_long_vertex_filter_expression) + -[a_very_long_edge_variable + is a_very_long_edge_label + where a_very_long_edge_filter_expression]-> + (a_second_very_long_vertex_variable is a_second_very_long_vertex_label), + (a_third_very_long_vertex_variable) + -[a_second_very_long_edge_variable]- + (a_fourth_very_long_vertex_variable) + where a_very_long_graph_filter_expression + columns ( + a_very_long_vertex_variable.long_property_name as first_long_output_column_name, + a_second_very_long_vertex_variable.other_long_property_name as second_output_column_name + ) + ); + +select * +from /* before graph table */ graph_table /* before outer opening paren */( + /* before graph */ public /* before graph dot */./* before graph name */ g + /* 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 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 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), + (d) + /* before any edge */ - /* before any opening bracket */[any_edge /* before any closing bracket */] /* before any ending minus */- + (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 */) + /* 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 */, + /* before second column */ b.name /* before columns closing paren */ + ) /* before outer closing paren */ + ) /* after graph table */ as /* before alias */ result; diff --git a/crates/squawk_fmt/tests/after/group_by.snap b/crates/squawk_fmt/tests/after/group_by.snap index ec389997..67282137 100644 --- a/crates/squawk_fmt/tests/after/group_by.snap +++ b/crates/squawk_fmt/tests/after/group_by.snap @@ -3,13 +3,37 @@ source: crates/squawk_fmt/tests/tests.rs input_file: crates/squawk_fmt/tests/before/group_by.sql --- select 1 group by 1, foo + 2; -select 1 group by all rollup(1, 2), cube(3, 4); -select 1 group by distinct grouping sets((), (1, 2), rollup(3), cube(4)); +select 1 group by all rollup (1, 2), cube (3, 4); +select 1 group by distinct grouping sets ((), (1, 2), rollup (3), cube (4)); +select + 1 +group by grouping sets ( + (first_very_long_grouping_expression, second_very_long_grouping_expression), + rollup ( + third_very_long_grouping_expression, + fourth_very_long_grouping_expression + ), + cube ( + fifth_very_long_grouping_expression, + sixth_very_long_grouping_expression + ) + ); select 1 -group /* before by */ by /* before distinct */ distinct /* before grouping */ grouping /* before sets */ sets /* before outer paren */(/* before rollup */ rollup /* before rollup paren */(/* before first expression */ 1 /* before expression comma */, - /* before second expression */ 2 /* before rollup close */) /* before group-by comma */, - /* before cube */ cube /* before cube paren */(/* before cube expression */ 3 /* before cube close */) /* before second group-by comma */, - /* before nested grouping */ grouping /* before nested sets */ sets /* before nested paren */(/* before empty tuple */ () /* before nested comma */, - /* before grouping expression */ (4 /* before tuple comma */, /* before tuple expression */ 5 /* before tuple close */) /* before nested close */) /* before outer close */)/* before semicolon */; +group /* before by */ by /* before distinct */ distinct /* before grouping */ grouping /* before sets */ sets /* before outer paren */( + /* before rollup */ rollup /* before rollup paren */( + /* before first expression */ 1 /* before expression comma */, + /* before second expression */ 2 /* before rollup close */ + ) /* before group-by comma */, + /* before cube */ cube /* before cube paren */( + /* before cube expression */ 3 /* before cube close */ + ) /* before second group-by comma */, + /* before nested grouping */ grouping /* before nested sets */ sets /* before nested paren */( + /* before empty tuple */ () /* before nested comma */, + /* before grouping expression */ ( + 4 /* before tuple comma */, + /* before tuple expression */ 5 /* before tuple close */ + ) /* before nested close */ + ) /* before outer close */ + )/* before semicolon */; diff --git a/crates/squawk_fmt/tests/after/select.snap b/crates/squawk_fmt/tests/after/select.snap index ad014cdf..10ff9ae7 100644 --- a/crates/squawk_fmt/tests/after/select.snap +++ b/crates/squawk_fmt/tests/after/select.snap @@ -20,3 +20,12 @@ select 1 foo, 2 "filter", 3 "day", 4 "array", 5 "Mixed"; select 1 as foo, 2 as filter, 3 as day, 4 as array; select 1 /*a*/ group /* b */ by /*c */ 1; + +select + a_very_long_first_target_expression as a_very_long_first_column_alias, + a_very_long_second_target_expression a_very_long_second_column_alias, + a_very_long_third_target_expression as "A Very Long Quoted Third Column Alias" +from a_very_long_schema_name.a_very_long_table_name as a_very_long_table_alias +group by a_very_long_first_target_expression, + a_very_long_second_target_expression, + a_very_long_third_target_expression; diff --git a/crates/squawk_fmt/tests/after/select_expr.snap b/crates/squawk_fmt/tests/after/select_expr.snap index d690b6b1..4fbaa901 100644 --- a/crates/squawk_fmt/tests/after/select_expr.snap +++ b/crates/squawk_fmt/tests/after/select_expr.snap @@ -7,14 +7,47 @@ select array[1, 2], array(select 1), array[[1, 2], [3, 4]], + /* before array */ array /* before opening bracket */[ + /* before first */ 1 /* before comma */, + /* before second */ 2 /* before closing bracket */ + ] /* after array */, + array[ + a_very_long_first_array_expression, + a_very_long_second_array_expression, + a_very_long_third_array_expression + ], + array( + select a_very_long_array_select_expression + from a_very_long_array_select_relation_name + ), + array[ + [ + a_very_long_first_nested_array_expression, + a_very_long_second_nested_array_expression + ], + [ + a_very_long_third_nested_array_expression, + a_very_long_fourth_nested_array_expression + ] + ], + array[ + [a_very_long_first, a_very_long_second], + [a_very_long_third, a_very_long_fourth] + ], -- between expr 2 between 1 and 3, 2 not between 1 and 3, 2 between asymmetric 1 and 3, 2 between symmetric 1 and 3, + a_very_long_between_target_expression + between symmetric a_very_long_between_start_expression + and a_very_long_between_end_expression, -- bin expr 1 + 1, 1 /* before op */ + /* after op */ 1, + a_very_long_binary_left_expression + + a_very_long_binary_middle_expression + * a_very_long_binary_right_expression, 2 @@@ 2, true and false, ts at time zone 'UTC', @@ -42,7 +75,8 @@ select 'foo' not similar to 'f%', 1 operator(+) 1, 1 operator(public.+) 1, - 1 operator /* before paren */(/* before path */ public /* before dot */./* before op */ + /* after op */) /* after paren */ 1, + 1 + operator /* before paren */(/* before path */ public /* before dot */./* before op */ + /* after op */) /* after paren */ 1, true or false, (1, 2) overlaps (3, 4), 10 % 3, @@ -50,8 +84,161 @@ select 'foo' similar to 'f%', 6 / 2, 2 * 3, + a_very_long_binary_left_expression + + a_very_long_binary_middle_expression + * a_very_long_binary_right_expression, -- call expr + 1 = any(array[1, 2]), + 2 = all(select x from things), + 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), + 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 + ), + exists( + select a_very_long_exists_select_expression + from a_very_long_exists_relation_name + ), + /* before exists */ exists /* before opening paren */( + /* before select */ select + 1 /* before closing paren */ + ) /* after exists */, + collation for (b + c), + collation for ( + a_very_long_collation_input_expression_that_forces_the_builtin_to_wrap + ), + /* before collation */ collation /* before for */ for /* before opening paren */( + /* before expr */ foo /* before closing paren */ + ) /* after collation */, + extract(year from timestamp '2001-02-16 20:38:40'), + extract('month' from ts), + extract("Field" from ts), + extract( + a_very_long_field_name + from a_very_long_timestamp_expression_that_forces_the_builtin_to_wrap + ), + /* before extract */ extract /* before opening paren */( + /* before field */ day + /* before from */ from /* before expr */ ts /* before closing paren */ + ) /* after extract */, + position('om' in 'Thomas'), + position( + a_very_long_substring_expression_that_forces_wrapping + in + a_very_long_string_expression_that_forces_wrapping + ), + /* before position */ position /* before opening paren */( + /* before substring */ 'om' + /* before in */ in + /* before string */ 'Thomas' /* before closing paren */ + ) /* after position */, + overlay('Txxxxas' placing 'hom' from 2 for 4), + overlay('Txxxxas' placing 'hom' from 2), + overlay(), + overlay('Txxxxas', 'hom', 2, count => 4), + overlay( + a_very_long_string_expression + placing a_very_long_replacement_expression + from a_very_long_start_expression + for a_very_long_count_expression + ), + overlay( + a_very_long_string_expression, + a_very_long_replacement_expression, + a_very_long_start_expression, + a_very_long_count_name => a_very_long_count_expression + ), + /* before overlay */ overlay /* before opening paren */( + /* before string */ 'Txxxxas' + /* before placing */ placing /* before replacement */ 'hom' + /* before from */ from /* before start */ 2 + /* before for */ for /* before count */ 4 /* before closing paren */ + ) /* after overlay */, + overlay /* before opening paren */( + /* before string */ 'Txxxxas' /* before comma */, + /* before replacement */ 'hom' /* before comma */, + /* before start */ 2 /* before comma */, + /* before name */ count /* before arrow */ => /* before count */ 4 /* before closing paren */ + ), + substring('Thomas' from 2 for 3), + substring('Thomas' for 3 from 2), + substring('Thomas' from 2), + substring('Thomas' for 3), + substring('Thomas' similar '%#"o_a#"_' escape '#'), + substring('Thomas', 2, 3), + substring( + a_very_long_string_expression + from a_very_long_start_expression + for a_very_long_count_expression + ), + substring( + a_very_long_string_expression + for a_very_long_count_expression + from a_very_long_start_expression + ), + substring( + a_very_long_string_expression + similar a_very_long_pattern_expression + escape a_very_long_escape_expression + ), + substring( + a_very_long_string_expression, + a_very_long_start_expression, + a_very_long_count_expression + ), + substring( + 'Thomas' /* before comma */, /* before start */ 2, /* before count */ 3 + ), + /* before substring */ substring /* before opening paren */( + /* before string */ 'Thomas' + /* before from */ from /* before start */ 2 + /* before for */ for /* before count */ 3 /* before closing paren */ + ) /* after substring */, + trim(' foo '), + trim(both 'x' from 'xfoox'), + trim(leading from ' foo '), + trim(trailing 'x' from 'foox'), + trim('x' from 'xfoox'), + trim(foo, bar), + trim( + both a_very_long_trim_character_expression + from a_very_long_trim_input_expression + ), + trim( + leading from a_very_long_first_trim_input_expression, + a_very_long_second_trim_input_expression + ), + trim( + a_very_long_trim_character_expression + from a_very_long_first_trim_input_expression, + a_very_long_second_trim_input_expression + ), + trim( + a_very_long_trim_expression, + a_second_very_long_trim_expression, + a_third_very_long_trim_expression + ), + /* before trim */ trim /* before opening paren */( + /* before side */ both /* before trim char */ 'x' + /* before from */ from /* before string */ 'xfoox' /* before closing paren */ + ) /* after trim */, date_trunc('month', now()), + a_very_long_function_name( + first_very_long_argument_name, + second_very_long_argument_name, + third_very_long_argument_name + ), foo(), foo(*), foo(all 1, 2), @@ -60,8 +247,306 @@ select foo(/* before distinct */ distinct /* after distinct */ 1), foo(variadic xs), foo(/* before variadic */ variadic /* after variadic */ xs), + foo(a => 1, b := 2), + foo( + /* before name */ a /* before arrow */ => /* before value */ 1 /* before comma */, + /* before arg */ b /* before assign */ := /* before value 2 */ 2 + ), + array_agg(x order by y desc nulls last, z asc), + foo(a => x order by y), + array_agg(x order by y using > nulls first), + a_very_long_function_name( + all a_very_long_first_quantified_argument_name, + a_very_long_second_quantified_argument_name + ), + a_very_long_function_name( + distinct a_very_long_first_distinct_argument_name, + a_very_long_second_distinct_argument_name + ), + a_very_long_function_name( + variadic a_very_long_variadic_argument_expression_that_forces_wrapping + ), + a_very_long_function_name( + a_very_long_first_argument_name => a_very_long_first_argument_expression, + 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 + ), + 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 + ), + json_arrayagg(v), + json_arrayagg( + v format json + order by sort_col desc + null on null + returning jsonb format json + ), + json_arrayagg( + a_very_long_json_arrayagg_value format json encoding utf8 + order by a_very_long_json_arrayagg_sort_expression desc + absent on null + returning a_very_long_json_arrayagg_return_type format json + ), + json_arrayagg /* before opening paren */( + /* before value */ v + /* before value format */ format /* before value json */ json + /* before encoding */ encoding /* before encoding name */ utf8 + /* before order */ order /* before by */ by /* before sort */ x + /* before absent */ absent /* before on */ on /* before null */ null + /* before returning */ returning /* before type */ jsonb + /* before returning format */ format /* before returning json */ json /* before closing paren */ + ), + json_array(), + json_array(1, 2 format json returning jsonb), + json_array( + first_very_long_json_array_expression, + second_very_long_json_array_expression, + third_very_long_json_array_expression + returning a_very_long_json_array_return_type + ), + 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 /* 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'), + json_exists(doc, '$.a' passing x as foo, y as bar true on error), + json_exists( + a_very_long_json_exists_document + format json encoding utf8, + a_very_long_json_exists_path + passing a_very_long_json_exists_argument as a_very_long_json_exists_name + false on error + ), + json_exists /* before opening paren */( + /* before document */ doc + /* before format */ format /* before json */ json + /* before encoding */ encoding /* before encoding name */ utf8 /* before comma */, + /* before path */ '$.a' + /* before passing */ passing /* before arg */ x /* before as */ as /* before name */ foo /* before arg comma */, + /* before second arg */ y /* before second as */ as /* before second name */ bar + /* before behavior */ default /* before default */ false /* before on */ on /* before error */ error /* before closing paren */ + ), + json_exists(doc, '$' error on error), + json_exists(doc, '$' null on error), + json_exists(doc, '$' false on error), + json_exists(doc, '$' unknown on error), + json_exists(doc, '$' empty array on error), + json_exists(doc, '$' empty object on error), + json_scalar(1), + json_scalar( + a_very_long_json_scalar_input_expression_that_forces_the_builtin_to_wrap + ), + json_scalar(), + json_scalar /* before opening paren */( + /* before expression */ 1 /* before closing paren */ + ), + json_serialize(doc), + json_serialize(doc format json returning text format json), + json_serialize( + a_very_long_json_serialize_document + format json + encoding a_very_long_json_encoding_name_that_forces_the_json_format_clause_to_wrap + returning a_very_long_json_serialize_return_type_name_that_forces_returning_to_wrap + format json + ), + json_serialize /* before opening paren */( + /* before document */ doc + /* before format */ format /* before json */ json + /* before returning */ returning /* before type */ text + /* before returning format */ format /* before returning json */ json /* before closing paren */ + ), + json_query(doc, '$.a'), + json_query(doc, '$.a' with wrapper), + json_query(doc, '$.a' with unconditional array wrapper omit quotes), + json_query(doc, '$.a' without array wrapper), + json_query( + a_very_long_json_query_document + format json encoding utf8, + a_very_long_json_query_path + passing a_very_long_json_query_argument as a_very_long_json_query_name + returning a_very_long_json_query_return_type format json + with unconditional array wrapper + keep quotes on scalar string + default a_very_long_json_query_empty_value on empty + error on error + ), + json_query /* before opening paren */( + /* before document */ doc + /* before format */ format /* before json */ json + /* before encoding */ encoding /* before encoding name */ utf8 /* before comma */, + /* before path */ '$.a' + /* before passing */ passing /* before arg */ x /* before as */ as /* before name */ foo + /* before returning */ returning /* before type */ text + /* before returning format */ format /* before returning json */ json + /* before wrapper */ with /* before conditional */ conditional /* before array */ array /* before wrapper keyword */ wrapper + /* before keep */ keep /* before quotes */ quotes /* before on scalar */ on /* before scalar */ scalar /* before string */ string + /* before empty behavior */ default /* before default */ 'empty' /* before empty on */ on /* before empty */ empty + /* before error behavior */ empty /* before object */ object /* before error on */ on /* before error */ error /* before closing paren */ + ), + json_value(doc, '$.a'), + json_value( + a_very_long_json_value_document + format json encoding utf8, + a_very_long_json_value_path + passing a_very_long_json_value_argument as a_very_long_json_value_name + returning a_very_long_json_value_return_type + default a_very_long_json_value_empty_value on empty + error on error + ), + json_value /* before opening paren */( + /* before document */ doc + /* before format */ format /* before json */ json /* before comma */, + /* before path */ '$.a' + /* before passing */ passing /* before arg */ x /* before as */ as /* before name */ foo + /* before returning */ returning /* before type */ text + /* before empty behavior */ null /* before empty on */ on /* before empty */ empty + /* before error behavior */ error /* before error on */ on /* before error */ error /* before closing paren */ + ), + json(doc), + json(doc format json encoding utf8 with unique keys), + json(doc without unique keys), + json( + a_very_long_json_document_expression + format json encoding utf8 + with unique keys + ), + json /* before opening paren */( + /* before expression */ doc + /* before format */ format /* before json */ json + /* before encoding */ encoding /* before encoding name */ utf8 + /* before without */ without /* before unique */ unique /* before keys */ keys /* before closing paren */ + ), + json_objectagg(k: v), + json_objectagg( + k value v format json + absent on null + with unique keys + returning jsonb format json + ), + json_objectagg( + a_very_long_json_objectagg_key value a_very_long_json_objectagg_value + format json + absent on null + with unique keys + returning a_very_long_json_objectagg_return_type format json + ), + json_objectagg /* before opening paren */( + /* before key */ k /* before value keyword */ value /* before value */ v + /* before value format */ format /* before value json */ json + /* before null */ null /* before on */ on /* before second null */ null + /* before without */ without /* before unique */ unique /* before keys */ keys + /* before returning */ returning /* before type */ jsonb + /* before returning format */ format /* before returning json */ json /* before closing paren */ + ), + json_object(), + json_object('a', 1, 'b', 2), + json_object( + 'a': 1, + 'b' value 2 format json + null on null + with unique keys + returning jsonb format json + ), + json_object( + a_very_long_json_object_key value a_very_long_json_object_value format json, + a_second_very_long_json_object_key value a_second_very_long_json_object_value + format json + absent on null + with unique keys + returning a_very_long_json_object_return_type format json + ), + json_object(returning jsonb), + json_object /* before opening paren */( + /* before key */ 'a' /* before colon */: /* before value */ 1 /* before comma */, + /* before second key */ 'b' /* before value keyword */ value /* before second value */ 2 + /* before format */ format /* before json */ json + /* before absent */ absent /* before on */ on /* before null */ null + /* before with */ with /* before unique */ unique /* before keys */ keys + /* before returning */ returning /* before type */ jsonb + /* before returning format */ format /* before returning json */ json /* before closing paren */ + ), public.foo(1), - foo /* before opening paren */(/* before first arg */ 1 /* before comma */, /* before second arg */ 2 /* before closing paren */), + foo /* before opening paren */( + /* before first arg */ 1 /* before comma */, + /* before second arg */ 2 /* before closing paren */ + ), + percentile_cont(0.5) within group (order by x), + mode() within group (order by y desc), + 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 */ + ) /* after within */, + count(*) filter (where x > 1), + sum(x) /* before filter */ filter /* before opening paren */ ( + /* before where */ where /* before expr */ x > 0 /* before closing paren */ + ) /* after filter */, + first_value(x) ignore nulls, + last_value(y) respect nulls, + first_value( + x + ) /* before treatment */ ignore /* before nulls */ nulls /* after nulls */, + a_very_long_ordered_set_aggregate_name( + a_very_long_direct_argument_expression + ) within group (order by a_very_long_ordered_set_expression desc nulls last), + a_very_long_filtered_aggregate_name( + a_very_long_filter_argument_expression + ) filter ( + where a_very_long_filter_condition_expression + > a_very_long_filter_threshold_expression + ), + a_very_long_window_function_name( + a_very_long_null_treatment_argument_expression + ) ignore nulls, + sum(x) over window_name, + count(*) over (), + avg(x) over ( + w + partition by a, b + order by c desc + rows between unbounded preceding and current row exclude ties + ), + sum(x) over (range 1 preceding), + sum(x) over ( + groups between current row and unbounded following exclude group + ), + sum(x) over (rows between 1 preceding and 2 following exclude no others), + sum(a_very_long_window_value_expression) over ( + a_very_long_window_reference + partition by a_very_long_partition_expression, + a_second_very_long_partition_expression + order by a_very_long_order_expression desc + rows between a_very_long_frame_start_expression preceding + and a_very_long_frame_end_expression following + exclude ties + ), + sum(x) /* before over */ over /* before target */ ( + /* before partition */ partition /* before by */ by /* before expr */ a /* before comma */, + /* before second */ b + /* before order */ order /* before order by */ by /* before sort */ c + /* before rows */ rows /* before between */ between /* before start */ 1 /* before preceding */ preceding + /* before and */ and /* before end */ current /* before row */ row + /* before exclude */ exclude /* before ties */ ties /* before close */ + ) /* after over */, -- case expr case when x > 1 then 1 else 0 end, case x when 1 then 'one' when 2 then 'two' else 'other' end, @@ -81,10 +566,26 @@ select /* after else */ 3 /* before end */ end, -- cast expr - cast(1 as int8), - treat(2 as bigint), + cast( + 1 as int8 + ), + treat( + 2 as bigint + ), 1::int8, int8 '1', + cast( + a_very_long_cast_input_expression + as + a_very_long_type_schema_name.a_very_long_cast_type_name + ), + treat( + a_very_long_treat_input_expression + as + a_very_long_type_schema_name.a_very_long_treat_type_name + ), + a_very_long_postgres_cast_input_expression::a_very_long_type_schema_name.a_very_long_postgres_cast_type_name, + a_very_long_type_schema_name.a_very_long_typed_literal_type_name 'a very long typed literal input value', -- field expr foo.bar, foo.bar.baz, @@ -92,23 +593,38 @@ select (foo).bar, (/* after opening paren */ foo /* before closing paren */).bar, foo /* before dot */./* before field */ bar, + a_very_long_field_base_expression.a_very_long_first_field_name.a_very_long_second_field_name, + ( + a_very_long_parenthesized_field_base_expression_that_forces_wrapping + ).a_very_long_field_name_that_forces_wrapping, -- index expr a[1], a[1][2], 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 + ], -- literal 42, + 'a very long literal expression value that forces the literal expression target to wrap across the configured line width', -- name ref foo, foo, "Mixed", U&"@0066@006f@006f" uescape '@', + a_very_long_unquoted_name_reference_that_forces_the_name_reference_target_to_wrap_past_eighty_characters, + U&"a@005fvery@005flong@005funicode@005fname@005freference" uescape '@', -- paren expr (1), (1 + 2), ((1)), (/* before expr */ 1 /* before closing paren */), + ( + a_very_long_parenthesized_expression + + a_second_very_long_parenthesized_expression + ), -- postfix expr 1 isnull, 2 notnull, @@ -129,12 +645,16 @@ select x is not json value, x is not normalized, x is not nfkd 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 @-@ 10, operator(public.+) /* after op */ 1, +1, -1, not true, + not -+a_very_long_prefix_input_expression_that_is_long_enough_to_force_wrapping_past_eighty_characters, + operator(a_very_long_operator_schema_name.###) a_very_long_custom_prefix_input_expression, -- slice expr c[:2][2:], c[1:2], @@ -142,10 +662,31 @@ select c[:3], c[:], c /* before bracket */[/* before start */ 1 /* before colon */:/* before end */ 2 /* before closing bracket */], + a_very_long_sliced_expression[a_very_long_slice_start_expression:a_very_long_slice_end_expression][a_second_very_long_slice_start_expression:a_second_very_long_slice_end_expression], -- tuple expr (1, 2, 3), + ( + first_very_long_tuple_expression, + second_very_long_tuple_expression, + third_very_long_tuple_expression + ), row(), row(1), row /* before opening paren */(1), row(1, 2), - (/* before first */ 1 /* before comma */, /* before second */ 2 /* before closing paren */); + row( + a_very_long_first_row_expression, + a_very_long_second_row_expression, + a_very_long_third_row_expression + ), + ( + /* 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 + ); diff --git a/crates/squawk_fmt/tests/after/select_literals.snap b/crates/squawk_fmt/tests/after/select_literals.snap index 20fe325b..92aa1f34 100644 --- a/crates/squawk_fmt/tests/after/select_literals.snap +++ b/crates/squawk_fmt/tests/after/select_literals.snap @@ -53,3 +53,30 @@ select select x'AF' 'BE'; + +select + null as a_very_long_null_literal_column_alias, + true as a_very_long_true_literal_column_alias, + false as a_very_long_false_literal_column_alias, + $1234567890 as a_very_long_positional_parameter_column_alias; +select + 1234567890123456789012345678901234567890, + 12345678901234567890.12345678901234567890, + 'a very long ordinary string literal value that forces the literal target list to wrap', + e'a very long escaped string literal value that forces the literal target list to wrap\n', + u&'a very long unicode string literal value that forces the literal target list to wrap', + $$a very long dollar quoted string literal value that forces the literal target list to wrap$$, + $a_very_long_dollar_quote_tag$a very long tagged dollar quoted string literal value$a_very_long_dollar_quote_tag$, + b'10101010101010101010101010101010101010101010101010101010101010101010101010101010', + x'ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF'; +select + 'a very long continued ordinary string literal value' + 'with a very long continuation segment', + e'a very long continued escaped string literal value\n' + 'with a very long continuation segment', + u&'a very long continued unicode string literal value' + 'with a very long continuation segment', + b'1010101010101010101010101010101010101010' + '0101010101010101010101010101010101010101', + x'ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF' + '123456123456123456123456123456123456'; diff --git a/crates/squawk_fmt/tests/after/table_constraints.snap b/crates/squawk_fmt/tests/after/table_constraints.snap new file mode 100644 index 00000000..830f4c87 --- /dev/null +++ b/crates/squawk_fmt/tests/after/table_constraints.snap @@ -0,0 +1,127 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/table_constraints.sql +--- +create table simple_constraints ( + id bigint, + parent_id bigint, + name text, + primary key (id), + unique nulls not distinct (name), + check (id > 0), + foreign key (parent_id) references parents (id) +); + +create table named_constraints ( + id bigint, + valid_at tstzrange, + constraint pk primary key (id) deferrable initially deferred, + constraint name_unique unique ( + first_very_long_unique_column_name, + second_very_long_unique_column_name + ) + include ( + first_very_long_included_column_name, + second_very_long_included_column_name + ) + with (fillfactor = 70, a_very_long_storage_parameter_name = false) + using index tablespace fast, + constraint id_check check ( + a_long_check_expression_name > another_long_check_expression_name + ) + not valid + no inherit, + constraint long_check check ( + a_very_long_check_left_hand_expression_name + > a_very_long_check_right_hand_expression_name + ), + constraint parent_fk foreign key ( + first_very_long_foreign_key_column_name, + second_very_long_foreign_key_column_name + ) + references public.parents ( + first_very_long_referenced_column_name, + second_very_long_referenced_column_name + ) + match full + on delete set null ( + first_very_long_foreign_key_column_name, + second_very_long_foreign_key_column_name + ) + on update no action + not deferrable, + constraint no_overlap exclude using gist ( + first_very_long_exclusion_expression with =, + second_very_long_exclusion_expression with && + ) + include ( + first_very_long_excluded_column_name, + second_very_long_excluded_column_name + ) + with ( + fillfactor = 80, + an_extremely_long_exclusion_storage_parameter_name_that_forces_wrapping = false + ) + using index tablespace fast + where ( + a_very_long_exclusion_predicate_expression + > an_extremely_long_exclusion_predicate_value_that_forces_wrapping + ) + deferrable +); + +create table using_indexes ( + id bigint, + unique using index existing_unique, + primary key using index existing_primary +); + +create table a_very_long_table_name_using_existing_indexes ( + a_very_long_first_identifier_column_name bigint, + a_very_long_second_identifier_column_name bigint, + unique using index a_very_long_existing_unique_index_name, + primary key using index a_very_long_existing_primary_index_name +); + +create table commented_constraints ( + id bigint, + parent_id bigint, + valid_at tstzrange, + /* before constraint */ constraint /* before constraint name */ named_pk /* before primary */ primary /* before key */ key/* before column opening paren */ ( + /* before column */ id /* before column closing paren */ + ) + /* before deferrable */ deferrable, + constraint named_check /* before check */ check /* before check opening paren */ ( + /* before check expression */ id > 0 /* before check closing paren */ + ) + /* before not */ not /* before valid */ valid, + constraint named_fk /* before foreign */ foreign /* before key */ key /* before from opening paren */ ( + /* before from column */ parent_id /* before from closing paren */ + ) + /* before references */ references /* before table */ public /* before dot */./* before table name */ parents /* before to opening paren */ ( + /* before to column */ id /* before to closing paren */ + ) + /* before match */ match /* before simple */ simple + /* before on delete */ on /* before delete */ delete /* before set */ set /* before null */ null /* before set columns */ ( + parent_id + ) + /* before on update */ on /* before update */ update /* before cascade */ cascade + /* before enforced */ enforced, + constraint named_exclude /* before exclude */ exclude /* before using */ using /* before method */ gist /* before exclusion opening paren */ ( + /* before exclusion expression */ id + /* before exclusion with */ with /* before exclusion op */ = /* before exclusion comma */, + /* before second exclusion */ valid_at + with /* before operator */ operator /* before operator opening paren */(/* before operator name */ public /* before operator dot */./* before operator op */ && /* before operator closing paren */) /* before exclusion closing paren */ + ) + /* before include */ include /* before include opening paren */ ( + id /* before include closing paren */ + ) + /* before with params */ with /* before params opening paren */ ( + /* before param */ fillfactor /* before equals */ = /* before value */ 80 /* before params closing paren */ + ) + /* before tablespace using */ using /* before index */ index /* before tablespace */ tablespace /* before tablespace name */ fast + /* before where */ where /* before where opening paren */( + /* before where expression */ id > 0 /* before where closing paren */ + ) + /* before initially */ initially /* before immediate */ immediate +); diff --git a/crates/squawk_fmt/tests/after/types.snap b/crates/squawk_fmt/tests/after/types.snap index bb7da10c..95e5c425 100644 --- a/crates/squawk_fmt/tests/after/types.snap +++ b/crates/squawk_fmt/tests/after/types.snap @@ -3,7 +3,7 @@ source: crates/squawk_fmt/tests/tests.rs input_file: crates/squawk_fmt/tests/before/types.sql --- -- keywords in types are lowercased -create table t( +create table t ( a int, b numeric(10, 2), c pg_catalog.varchar(10), @@ -11,7 +11,7 @@ create table t( ); -- character types -create table t( +create table t ( a varchar(10), b character varying, c national char varying(2), @@ -21,7 +21,7 @@ create table t( ); -- bit & double types -create table t( +create table t ( a bit, b bit(4), c bit varying, @@ -30,7 +30,7 @@ create table t( ); -- date & time types -create table t( +create table t ( a time, b time(3) with time zone, c timestamp without time zone, @@ -38,7 +38,7 @@ create table t( ); -- interval types -create table t( +create table t ( a interval, b interval(6), c interval year, @@ -50,7 +50,7 @@ create table t( ); -- array types -create table t( +create table t ( a int[], b text array, c text array[4], @@ -61,8 +61,12 @@ create table t( -- types in casts select 1::int8, - cast(1 as int8), - treat(2 as bigint), + cast( + 1 as int8 + ), + treat( + 2 as bigint + ), pg_catalog.varchar(10) 'foo'; select '1'::interval day to second(3), @@ -71,7 +75,7 @@ select select 1::setof int; -- comments inside types -create table t( +create table t ( a national /*a*/ char /*b*/ varying /*c*/(2), b int /*d*/[], c interval /*e*/ day to /*f*/ second /*g*/(3), @@ -89,7 +93,7 @@ create table t( select 1::setof /*a*/ int, 2::pg_catalog /*b*/./*c*/ int4; -- line comments inside types -create table t( +create table t ( a int -- one [], b numeric -- two @@ -111,12 +115,17 @@ select -- comments around casts select 1 /*a*/::/*b*/ int8; select cast /*c*/(/*d*/ 1 /*e*/ as /*f*/ int8 /*g*/); +select + a_very_long_expression_name_that_forces_the_select_to_wrap, + cast /*c*/( + /*d*/ 1 /*e*/ as /*f*/ int8 /*g*/ + ); select treat /*h*/(2 as /*i*/ bigint); select pg_catalog.varchar(10) /*j*/ 'foo'; select interval '4' /*k*/ year to month; -- line comments before a type's trailing keywords -create table t( +create table t ( a double -- one precision, b bit -- two @@ -135,3 +144,36 @@ create table t( select 1::double -- eight precision; + +create table a_very_long_table_name_for_type_wrapping ( + a_very_long_numeric_column_name numeric(12345, 12345), + a_very_long_varchar_column_name varchar(12345), + a_very_long_character_varying_column_name character varying(12345), + a_very_long_national_character_varying_column_name national character varying(12345), + a_very_long_nchar_column_name nchar(12345), + a_very_long_bit_varying_column_name bit varying(12345), + a_very_long_double_precision_column_name double precision, + a_very_long_timestamp_column_name timestamp(12345) without time zone, + a_very_long_time_column_name time(12345) with time zone, + a_very_long_interval_column_name interval day to second(12345), + a_very_long_array_column_name text[12345][12345] +); +select + a_very_long_expression_name::a_very_long_type_schema_name.a_very_long_type_name, + cast( + a_very_long_expression_name as character varying(12345) + ), + cast( + a_very_long_expression_name_long_long_long_long_long_long + as + character varying(12345) + ), + treat( + a_very_long_expression_name + as + a_very_long_type_schema_name.a_very_long_type_name + ), + a_very_long_type_schema_name.a_very_long_type_name( + 12345 + ) 'a very long typed string literal value', + interval 'a very long interval literal value' day to second(12345); diff --git a/crates/squawk_fmt/tests/after/xml_functions.snap b/crates/squawk_fmt/tests/after/xml_functions.snap new file mode 100644 index 00000000..991d2745 --- /dev/null +++ b/crates/squawk_fmt/tests/after/xml_functions.snap @@ -0,0 +1,102 @@ +--- +source: crates/squawk_fmt/tests/tests.rs +input_file: crates/squawk_fmt/tests/before/xml_functions.sql +--- +select + xmlelement(name foo), + xmlelement(name foo, 1, 2), + xmlelement(name foo, xmlattributes(a, b as c)), + xmlelement(name foo, xmlattributes(a as attr), x, y), + xmlelement( + name a_very_long_element_name, + xmlattributes( + a_very_long_xml_attribute_expression as a_very_long_xml_attribute_name, + a_second_very_long_xml_attribute_expression as a_second_very_long_xml_attribute_name + ), + first_very_long_content_expression, + second_very_long_content_expression, + third_very_long_content_expression + ), + /* before element */ xmlelement /* before outer opening paren */( + /* before name */ name /* before tag */ tag /* before attributes comma */, + /* before xmlattributes */ xmlattributes /* before attributes opening paren */( + /* before first attribute */ a /* before as */ as /* before attribute name */ attr /* before attribute comma */, + /* before second attribute */ b /* before attributes closing paren */ + ) /* before content comma */, + /* before content */ x /* before outer closing paren */ + ) /* after element */, + xmlexists('/foo' passing doc), + xmlexists('/foo' passing by ref doc), + xmlexists('/foo' passing doc by value), + xmlexists( + a_very_long_xml_exists_path_expression + passing + by ref + a_very_long_xml_exists_document_expression + by value + ), + /* before exists */ xmlexists /* before opening paren */( + /* before row */ '/foo' + /* before passing */ passing + /* before first by */ by /* before ref */ ref + /* before document */ doc + /* before second by */ by /* before value */ value /* before closing paren */ + ) /* after exists */, + xmlforest(a, b as foo), + xmlforest( + first_very_long_expression as first_very_long_element_name, + second_very_long_expression as second_very_long_element_name + ), + /* before forest */ xmlforest /* before opening paren */( + /* before first expression */ a /* before as */ as /* before tag */ first /* before comma */, + /* before second expression */ b /* before closing paren */ + ) /* after forest */, + xmlparse(document '' preserve whitespace), + xmlparse(content value strip whitespace), + xmlparse( + document + a_very_long_xml_parse_document_expression_that_forces_the_xml_parse_node_to_wrap + preserve whitespace + ), + /* before parse */ xmlparse /* before opening paren */( + /* before kind */ document + /* before expression */ value + /* before preserve */ preserve /* before whitespace */ whitespace /* before closing paren */ + ) /* after parse */, + xmlpi(name php), + xmlpi(name php, 'echo'), + xmlpi( + name a_very_long_xml_processing_instruction_target, + a_very_long_xml_processing_instruction_expression_that_forces_the_xml_pi_node_to_wrap + ), + /* before pi */ xmlpi /* before opening paren */( + /* before name */ name /* before target */ php /* before comma */, + /* before expression */ 'echo' /* before closing paren */ + ) /* after pi */, + xmlroot(doc, version '1.0'), + xmlroot(doc, version no value, standalone yes), + xmlroot(doc, version '1.0', standalone no), + xmlroot(doc, version '1.0', standalone no value), + xmlroot( + a_very_long_xml_root_document_expression_that_forces_the_xml_root_node_to_wrap, + version a_very_long_xml_root_version_expression, + standalone no value + ), + /* before root */ xmlroot /* before opening paren */( + /* before expression */ doc /* before version comma */, + /* before version */ version /* before no */ no /* before version value */ value/* before standalone comma */ , + /* before standalone */ standalone /* before standalone no */ no /* before standalone value */ value /* before closing paren */ + ) /* after root */, + xmlserialize(document doc as text), + xmlserialize(content doc as varchar(20) indent), + xmlserialize(content doc as text no indent), + xmlserialize( + content a_long_xml_serialize_content_expression_that_forces_wrapping + as a_very_long_xml_serialize_return_type + no indent + ), + /* before serialize */ xmlserialize /* before opening paren */( + /* before kind */ content /* before expression */ doc + /* before as */ as /* before type */ text + /* before no */ no /* before indent */ indent /* before closing paren */ + )/* after serialize */; diff --git a/crates/squawk_fmt/tests/before/create_table.sql b/crates/squawk_fmt/tests/before/create_table.sql index 52bcaff2..d7e1dd74 100644 --- a/crates/squawk_fmt/tests/before/create_table.sql +++ b/crates/squawk_fmt/tests/before/create_table.sql @@ -39,3 +39,5 @@ create table t (a int, b int -- two -- line comment before the semicolon 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); diff --git a/crates/squawk_fmt/tests/before/create_table_like.sql b/crates/squawk_fmt/tests/before/create_table_like.sql index ddcb1db4..8f698423 100644 --- a/crates/squawk_fmt/tests/before/create_table_like.sql +++ b/crates/squawk_fmt/tests/before/create_table_like.sql @@ -19,3 +19,5 @@ create table t (like U&"d!0061tum" uescape '!' including all); create table t (like /* a */ foo /* b */ including /* c */ all); create table t (like foo -- a line comment including all); + +create table a_very_long_destination_table_name (like a_very_long_source_schema_name.a_very_long_source_table_name including comments including compression including constraints including defaults including generated including identity including indexes including statistics including storage); diff --git a/crates/squawk_fmt/tests/before/custom_operator.sql b/crates/squawk_fmt/tests/before/custom_operator.sql index 949bd8ba..33b823fa 100644 --- a/crates/squawk_fmt/tests/before/custom_operator.sql +++ b/crates/squawk_fmt/tests/before/custom_operator.sql @@ -1,2 +1,3 @@ select 1 /*before*/ <<<< /*after*/ 1; select ##### /*after*/ 1; +select a_very_long_left_operand_name <<<< a_very_long_right_operand_name, a_second_very_long_left_operand_name ##### a_second_very_long_right_operand_name; diff --git a/crates/squawk_fmt/tests/before/from.sql b/crates/squawk_fmt/tests/before/from.sql index a13b4de4..42856827 100644 --- a/crates/squawk_fmt/tests/before/from.sql +++ b/crates/squawk_fmt/tests/before/from.sql @@ -1,5 +1,12 @@ select * from foo; select * from public.foo as f, bar b; +select * from foo as f (id, display_name); +select * from foo f (id int, display_name text collate "C"); +select * from foo + /* before alias */ as /* before alias name */ f /* before open paren */ ( + /* after open paren */ id /* before comma */, + /* after comma */ display_name /* before close paren */ + ); select * from users tablesample bernoulli(10) repeatable (42); select * /* before from */ from @@ -7,3 +14,8 @@ select * /* before star */ * /* before alias */ as /* before alias name */ f /* before item comma */, /* before second item */ other /* before second alias */ o; + +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; diff --git a/crates/squawk_fmt/tests/before/graph_table.sql b/crates/squawk_fmt/tests/before/graph_table.sql new file mode 100644 index 00000000..63e52c53 --- /dev/null +++ b/crates/squawk_fmt/tests/before/graph_table.sql @@ -0,0 +1,29 @@ +select * from GRAPH_TABLE ( g MATCH (a) COLUMNS (a) ); + +select * from lateral GRAPH_TABLE(public.g MATCH (a IS person)-[e IS knows WHERE e.weight>1]->(b) WHERE a.active COLUMNS(a.name AS person_name,b.name)) AS matches; + +select * from GRAPH_TABLE(g MATCH (a)<-[left_edge]-(b), (c)-[any_edge]-(d), (e)<-(f), (h)->(i), (j)-(k), ((x)->(y) WHERE x.active) COLUMNS(a)); + +select * from GRAPH_TABLE(g MATCH (a)->{1}(b), (c)-{,3}(d), (e)-{2,4}(f) COLUMNS(a)); + +select * from GRAPH_TABLE(a_very_long_property_graph_name MATCH (a_very_long_vertex_variable IS a_very_long_vertex_label WHERE a_very_long_vertex_filter_expression)-[a_very_long_edge_variable IS a_very_long_edge_label WHERE a_very_long_edge_filter_expression]->(a_second_very_long_vertex_variable IS a_second_very_long_vertex_label), (a_third_very_long_vertex_variable)-[a_second_very_long_edge_variable]-(a_fourth_very_long_vertex_variable) WHERE a_very_long_graph_filter_expression COLUMNS (a_very_long_vertex_variable.long_property_name AS first_long_output_column_name, a_second_very_long_vertex_variable.other_long_property_name AS second_output_column_name)); + +select * from + /* before graph table */ GRAPH_TABLE /* before outer opening paren */ ( + /* before graph */ public /* before graph dot */ . /* before graph name */ g + /* 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 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 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), + (d) /* before any edge */ - /* before any opening bracket */ [any_edge /* before any closing bracket */ ] /* before any ending minus */ - (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 */ ) + /* 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 */, + /* before second column */ b.name + /* before columns closing paren */ ) + /* before outer closing paren */ + ) /* after graph table */ AS /* before alias */ result; diff --git a/crates/squawk_fmt/tests/before/group_by.sql b/crates/squawk_fmt/tests/before/group_by.sql index bcadeb04..5e070fec 100644 --- a/crates/squawk_fmt/tests/before/group_by.sql +++ b/crates/squawk_fmt/tests/before/group_by.sql @@ -1,6 +1,7 @@ select 1 group by 1, foo + 2; select 1 group by all rollup (1, 2), cube (3, 4); select 1 group by distinct grouping sets ((), (1, 2), rollup (3), cube (4)); +select 1 group by grouping sets ((first_very_long_grouping_expression, second_very_long_grouping_expression), rollup (third_very_long_grouping_expression, fourth_very_long_grouping_expression), cube (fifth_very_long_grouping_expression, sixth_very_long_grouping_expression)); select 1 group /* before by */ by /* before distinct */ distinct /* before grouping */ grouping /* before sets */ sets /* before outer paren */ ( diff --git a/crates/squawk_fmt/tests/before/select.sql b/crates/squawk_fmt/tests/before/select.sql index 04c31722..9a8a7959 100644 --- a/crates/squawk_fmt/tests/before/select.sql +++ b/crates/squawk_fmt/tests/before/select.sql @@ -14,3 +14,5 @@ select 1 "foo", 2 "filter", 3 "day", 4 "array", 5 "Mixed"; select 1 as "foo", 2 as "filter", 3 as "day", 4 as "array"; select 1 /*a*/group /* b */by/*c */ 1; + +select a_very_long_first_target_expression as a_very_long_first_column_alias, a_very_long_second_target_expression a_very_long_second_column_alias, a_very_long_third_target_expression as "A Very Long Quoted Third Column Alias" from a_very_long_schema_name.a_very_long_table_name as a_very_long_table_alias group by a_very_long_first_target_expression, a_very_long_second_target_expression, a_very_long_third_target_expression; diff --git a/crates/squawk_fmt/tests/before/select_expr.sql b/crates/squawk_fmt/tests/before/select_expr.sql index 96ee9506..6f2c5340 100644 --- a/crates/squawk_fmt/tests/before/select_expr.sql +++ b/crates/squawk_fmt/tests/before/select_expr.sql @@ -3,14 +3,21 @@ select array[1,2], array(select 1), array[[1,2],[3,4]], + /* before array */ ARRAY /* before opening bracket */ [ /* before first */ 1 /* before comma */, /* before second */ 2 /* before closing bracket */ ] /* after array */, + array[a_very_long_first_array_expression, a_very_long_second_array_expression, a_very_long_third_array_expression], + array(select a_very_long_array_select_expression from a_very_long_array_select_relation_name), + array[[a_very_long_first_nested_array_expression, a_very_long_second_nested_array_expression], [a_very_long_third_nested_array_expression, a_very_long_fourth_nested_array_expression]], + array[[a_very_long_first, a_very_long_second], [a_very_long_third, a_very_long_fourth]], -- between expr 2 between 1 and 3, 2 not between 1 and 3, 2 between asymmetric 1 and 3, 2 between symmetric 1 and 3, + a_very_long_between_target_expression between symmetric a_very_long_between_start_expression and a_very_long_between_end_expression, -- bin expr 1 + 1, 1 /* before op */ + /* after op */ 1, + a_very_long_binary_left_expression + a_very_long_binary_middle_expression * a_very_long_binary_right_expression, 2@@@2, true and false, ts at time zone 'UTC', @@ -46,8 +53,62 @@ select 'foo' similar to 'f%', 6 / 2, 2 * 3, + a_very_long_binary_left_expression + a_very_long_binary_middle_expression * a_very_long_binary_right_expression, -- call expr + 1 = ANY ( ARRAY [ 1 , 2 ] ), + 2 = ALL ( SELECT x FROM things ), + 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 ), + 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), + EXISTS(SELECT a_very_long_exists_select_expression FROM a_very_long_exists_relation_name), + /* before exists */ EXISTS /* before opening paren */ ( /* before select */ SELECT 1 /* before closing paren */ ) /* after exists */, + COLLATION FOR ( b + c ), + COLLATION FOR ( a_very_long_collation_input_expression_that_forces_the_builtin_to_wrap ), + /* before collation */ COLLATION /* before for */ FOR /* before opening paren */ ( /* before expr */ foo /* before closing paren */ ) /* after collation */, + EXTRACT ( YEAR FROM TIMESTAMP '2001-02-16 20:38:40' ), + EXTRACT ( 'month' FROM ts ), + EXTRACT ( "Field" FROM ts ), + EXTRACT(a_very_long_field_name FROM a_very_long_timestamp_expression_that_forces_the_builtin_to_wrap), + /* before extract */ EXTRACT /* before opening paren */ ( /* before field */ DAY /* before from */ FROM /* before expr */ ts /* before closing paren */ ) /* after extract */, + POSITION ( 'om' IN 'Thomas' ), + POSITION(a_very_long_substring_expression_that_forces_wrapping IN a_very_long_string_expression_that_forces_wrapping), + /* before position */ POSITION /* before opening paren */ ( /* before substring */ 'om' /* before in */ IN /* before string */ 'Thomas' /* before closing paren */ ) /* after position */, + OVERLAY ( 'Txxxxas' PLACING 'hom' FROM 2 FOR 4 ), + OVERLAY ( 'Txxxxas' PLACING 'hom' FROM 2 ), + OVERLAY ( ), + OVERLAY ( 'Txxxxas', 'hom', 2, count => 4 ), + OVERLAY(a_very_long_string_expression PLACING a_very_long_replacement_expression FROM a_very_long_start_expression FOR a_very_long_count_expression), + OVERLAY(a_very_long_string_expression, a_very_long_replacement_expression, a_very_long_start_expression, a_very_long_count_name => a_very_long_count_expression), + /* before overlay */ OVERLAY /* before opening paren */ ( /* before string */ 'Txxxxas' /* before placing */ PLACING /* before replacement */ 'hom' /* before from */ FROM /* before start */ 2 /* before for */ FOR /* before count */ 4 /* before closing paren */ ) /* after overlay */, + OVERLAY /* before opening paren */ ( /* before string */ 'Txxxxas' /* before comma */, /* before replacement */ 'hom' /* before comma */, /* before start */ 2 /* before comma */, /* before name */ count /* before arrow */ => /* before count */ 4 /* before closing paren */ ), + SUBSTRING ( 'Thomas' FROM 2 FOR 3 ), + SUBSTRING ( 'Thomas' FOR 3 FROM 2 ), + SUBSTRING ( 'Thomas' FROM 2 ), + SUBSTRING ( 'Thomas' FOR 3 ), + SUBSTRING ( 'Thomas' SIMILAR '%#"o_a#"_' ESCAPE '#' ), + SUBSTRING ( 'Thomas', 2, 3 ), + SUBSTRING(a_very_long_string_expression FROM a_very_long_start_expression FOR a_very_long_count_expression), + SUBSTRING(a_very_long_string_expression FOR a_very_long_count_expression FROM a_very_long_start_expression), + SUBSTRING(a_very_long_string_expression SIMILAR a_very_long_pattern_expression ESCAPE a_very_long_escape_expression), + SUBSTRING(a_very_long_string_expression, a_very_long_start_expression, a_very_long_count_expression), + SUBSTRING ( 'Thomas' /* before comma */, /* before start */ 2, /* before count */ 3 ), + /* before substring */ SUBSTRING /* before opening paren */ ( /* before string */ 'Thomas' /* before from */ FROM /* before start */ 2 /* before for */ FOR /* before count */ 3 /* before closing paren */ ) /* after substring */, + TRIM ( ' foo ' ), + TRIM ( BOTH 'x' FROM 'xfoox' ), + TRIM ( LEADING FROM ' foo ' ), + TRIM ( TRAILING 'x' FROM 'foox' ), + TRIM ( 'x' FROM 'xfoox' ), + TRIM ( foo, bar ), + TRIM(BOTH a_very_long_trim_character_expression FROM a_very_long_trim_input_expression), + TRIM(LEADING FROM a_very_long_first_trim_input_expression, a_very_long_second_trim_input_expression), + TRIM(a_very_long_trim_character_expression FROM a_very_long_first_trim_input_expression, a_very_long_second_trim_input_expression), + TRIM(a_very_long_trim_expression, a_second_very_long_trim_expression, a_third_very_long_trim_expression), + /* before trim */ TRIM /* before opening paren */ ( /* before side */ BOTH /* before trim char */ 'x' /* before from */ FROM /* before string */ 'xfoox' /* before closing paren */ ) /* after trim */, date_trunc('month', now()), + a_very_long_function_name(first_very_long_argument_name, second_very_long_argument_name, third_very_long_argument_name), foo ( ), foo ( * ), foo ( ALL 1, 2 ), @@ -56,8 +117,90 @@ select foo ( /* before distinct */ DISTINCT /* after distinct */ 1 ), foo ( VARIADIC xs ), foo ( /* before variadic */ VARIADIC /* after variadic */ xs ), + foo(a => 1, b := 2), + foo(/* before name */ a /* before arrow */ => /* before value */ 1 /* before comma */, /* before arg */ b /* before assign */ := /* before value 2 */ 2), + array_agg(x order by y desc nulls last, z asc), + foo(a => x order by y), + array_agg(x order by y using > nulls first), + a_very_long_function_name(ALL a_very_long_first_quantified_argument_name, a_very_long_second_quantified_argument_name), + a_very_long_function_name(DISTINCT a_very_long_first_distinct_argument_name, a_very_long_second_distinct_argument_name), + a_very_long_function_name(VARIADIC a_very_long_variadic_argument_expression_that_forces_wrapping), + a_very_long_function_name(a_very_long_first_argument_name => a_very_long_first_argument_expression, 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), + 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), + JSON_ARRAYAGG(v), + JSON_ARRAYAGG(v FORMAT JSON ORDER BY sort_col DESC NULL ON NULL RETURNING jsonb FORMAT JSON), + JSON_ARRAYAGG(a_very_long_json_arrayagg_value FORMAT JSON ENCODING UTF8 ORDER BY a_very_long_json_arrayagg_sort_expression DESC ABSENT ON NULL RETURNING a_very_long_json_arrayagg_return_type FORMAT JSON), + JSON_ARRAYAGG /* before opening paren */ (/* before value */ v /* before value format */ FORMAT /* before value json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before order */ ORDER /* before by */ BY /* before sort */ x /* before absent */ ABSENT /* before on */ ON /* before null */ NULL /* before returning */ RETURNING /* before type */ jsonb /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), + JSON_ARRAY(), + JSON_ARRAY(1, 2 FORMAT JSON RETURNING jsonb), + JSON_ARRAY(first_very_long_json_array_expression, second_very_long_json_array_expression, third_very_long_json_array_expression RETURNING a_very_long_json_array_return_type), + 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 /* 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'), + JSON_EXISTS(doc, '$.a' PASSING x AS foo, y AS bar TRUE ON ERROR), + JSON_EXISTS(a_very_long_json_exists_document FORMAT JSON ENCODING UTF8, a_very_long_json_exists_path PASSING a_very_long_json_exists_argument AS a_very_long_json_exists_name FALSE ON ERROR), + JSON_EXISTS /* before opening paren */ (/* before document */ doc /* before format */ FORMAT /* before json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before comma */, /* before path */ '$.a' /* before passing */ PASSING /* before arg */ x /* before as */ AS /* before name */ foo /* before arg comma */, /* before second arg */ y /* before second as */ AS /* before second name */ bar /* before behavior */ DEFAULT /* before default */ false /* before on */ ON /* before error */ ERROR /* before closing paren */), + JSON_EXISTS(doc, '$' ERROR ON ERROR), + JSON_EXISTS(doc, '$' NULL ON ERROR), + JSON_EXISTS(doc, '$' FALSE ON ERROR), + JSON_EXISTS(doc, '$' UNKNOWN ON ERROR), + JSON_EXISTS(doc, '$' EMPTY ARRAY ON ERROR), + JSON_EXISTS(doc, '$' EMPTY OBJECT ON ERROR), + JSON_SCALAR(1), + JSON_SCALAR(a_very_long_json_scalar_input_expression_that_forces_the_builtin_to_wrap), + JSON_SCALAR(), + JSON_SCALAR /* before opening paren */ (/* before expression */ 1 /* before closing paren */), + JSON_SERIALIZE(doc), + JSON_SERIALIZE(doc FORMAT JSON RETURNING text FORMAT JSON), + JSON_SERIALIZE(a_very_long_json_serialize_document FORMAT JSON ENCODING a_very_long_json_encoding_name_that_forces_the_json_format_clause_to_wrap RETURNING a_very_long_json_serialize_return_type_name_that_forces_returning_to_wrap FORMAT JSON), + JSON_SERIALIZE /* before opening paren */ (/* before document */ doc /* before format */ FORMAT /* before json */ JSON /* before returning */ RETURNING /* before type */ text /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), + JSON_QUERY(doc, '$.a'), + JSON_QUERY(doc, '$.a' WITH WRAPPER), + JSON_QUERY(doc, '$.a' WITH UNCONDITIONAL ARRAY WRAPPER OMIT QUOTES), + JSON_QUERY(doc, '$.a' WITHOUT ARRAY WRAPPER), + JSON_QUERY(a_very_long_json_query_document FORMAT JSON ENCODING UTF8, a_very_long_json_query_path PASSING a_very_long_json_query_argument AS a_very_long_json_query_name RETURNING a_very_long_json_query_return_type FORMAT JSON WITH UNCONDITIONAL ARRAY WRAPPER KEEP QUOTES ON SCALAR STRING DEFAULT a_very_long_json_query_empty_value ON EMPTY ERROR ON ERROR), + JSON_QUERY /* before opening paren */ (/* before document */ doc /* before format */ FORMAT /* before json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before comma */, /* before path */ '$.a' /* before passing */ PASSING /* before arg */ x /* before as */ AS /* before name */ foo /* before returning */ RETURNING /* before type */ text /* before returning format */ FORMAT /* before returning json */ JSON /* before wrapper */ WITH /* before conditional */ CONDITIONAL /* before array */ ARRAY /* before wrapper keyword */ WRAPPER /* before keep */ KEEP /* before quotes */ QUOTES /* before on scalar */ ON /* before scalar */ SCALAR /* before string */ STRING /* before empty behavior */ DEFAULT /* before default */ 'empty' /* before empty on */ ON /* before empty */ EMPTY /* before error behavior */ EMPTY /* before object */ OBJECT /* before error on */ ON /* before error */ ERROR /* before closing paren */), + JSON_VALUE(doc, '$.a'), + JSON_VALUE(a_very_long_json_value_document FORMAT JSON ENCODING UTF8, a_very_long_json_value_path PASSING a_very_long_json_value_argument AS a_very_long_json_value_name RETURNING a_very_long_json_value_return_type DEFAULT a_very_long_json_value_empty_value ON EMPTY ERROR ON ERROR), + JSON_VALUE /* before opening paren */ (/* before document */ doc /* before format */ FORMAT /* before json */ JSON /* before comma */, /* before path */ '$.a' /* before passing */ PASSING /* before arg */ x /* before as */ AS /* before name */ foo /* before returning */ RETURNING /* before type */ text /* before empty behavior */ NULL /* before empty on */ ON /* before empty */ EMPTY /* before error behavior */ ERROR /* before error on */ ON /* before error */ ERROR /* before closing paren */), + JSON(doc), + JSON(doc FORMAT JSON ENCODING UTF8 WITH UNIQUE KEYS), + JSON(doc WITHOUT UNIQUE KEYS), + JSON(a_very_long_json_document_expression FORMAT JSON ENCODING UTF8 WITH UNIQUE KEYS), + JSON /* before opening paren */ (/* before expression */ doc /* before format */ FORMAT /* before json */ JSON /* before encoding */ ENCODING /* before encoding name */ UTF8 /* before without */ WITHOUT /* before unique */ UNIQUE /* before keys */ KEYS /* before closing paren */), + JSON_OBJECTAGG(k: v), + JSON_OBJECTAGG(k VALUE v FORMAT JSON ABSENT ON NULL WITH UNIQUE KEYS RETURNING jsonb FORMAT JSON), + JSON_OBJECTAGG(a_very_long_json_objectagg_key VALUE a_very_long_json_objectagg_value FORMAT JSON ABSENT ON NULL WITH UNIQUE KEYS RETURNING a_very_long_json_objectagg_return_type FORMAT JSON), + JSON_OBJECTAGG /* before opening paren */ (/* before key */ k /* before value keyword */ VALUE /* before value */ v /* before value format */ FORMAT /* before value json */ JSON /* before null */ NULL /* before on */ ON /* before second null */ NULL /* before without */ WITHOUT /* before unique */ UNIQUE /* before keys */ KEYS /* before returning */ RETURNING /* before type */ jsonb /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), + JSON_OBJECT(), + JSON_OBJECT('a', 1, 'b', 2), + JSON_OBJECT('a': 1, 'b' VALUE 2 FORMAT JSON NULL ON NULL WITH UNIQUE KEYS RETURNING jsonb FORMAT JSON), + JSON_OBJECT(a_very_long_json_object_key VALUE a_very_long_json_object_value FORMAT JSON, a_second_very_long_json_object_key VALUE a_second_very_long_json_object_value FORMAT JSON ABSENT ON NULL WITH UNIQUE KEYS RETURNING a_very_long_json_object_return_type FORMAT JSON), + JSON_OBJECT(RETURNING jsonb), + JSON_OBJECT /* before opening paren */ (/* before key */ 'a' /* before colon */ : /* before value */ 1 /* before comma */, /* before second key */ 'b' /* before value keyword */ VALUE /* before second value */ 2 /* before format */ FORMAT /* before json */ JSON /* before absent */ ABSENT /* before on */ ON /* before null */ NULL /* before with */ WITH /* before unique */ UNIQUE /* before keys */ KEYS /* before returning */ RETURNING /* before type */ jsonb /* before returning format */ FORMAT /* before returning json */ JSON /* before closing paren */), public.foo(1), foo /* before opening paren */ ( /* before first arg */ 1 /* before comma */, /* before second arg */ 2 /* before closing paren */ ), + percentile_cont(0.5) WITHIN GROUP (ORDER BY x), + mode() WITHIN GROUP (ORDER BY y DESC), + 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 */ ) /* after within */, + count(*) FILTER (WHERE x > 1), + sum(x) /* before filter */ FILTER /* before opening paren */ ( /* before where */ WHERE /* before expr */ x > 0 /* before closing paren */ ) /* after filter */, + first_value(x) IGNORE NULLS, + last_value(y) RESPECT NULLS, + first_value(x) /* before treatment */ IGNORE /* before nulls */ NULLS /* after nulls */, + a_very_long_ordered_set_aggregate_name(a_very_long_direct_argument_expression) WITHIN GROUP (ORDER BY a_very_long_ordered_set_expression DESC NULLS LAST), + a_very_long_filtered_aggregate_name(a_very_long_filter_argument_expression) FILTER (WHERE a_very_long_filter_condition_expression > a_very_long_filter_threshold_expression), + a_very_long_window_function_name(a_very_long_null_treatment_argument_expression) IGNORE NULLS, + sum(x) OVER window_name, + count(*) OVER (), + avg(x) OVER (w PARTITION BY a, b ORDER BY c DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES), + sum(x) OVER (RANGE 1 PRECEDING), + sum(x) OVER (GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING EXCLUDE GROUP), + sum(x) OVER (ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING EXCLUDE NO OTHERS), + sum(a_very_long_window_value_expression) OVER (a_very_long_window_reference PARTITION BY a_very_long_partition_expression, a_second_very_long_partition_expression ORDER BY a_very_long_order_expression DESC ROWS BETWEEN a_very_long_frame_start_expression PRECEDING AND a_very_long_frame_end_expression FOLLOWING EXCLUDE TIES), + sum(x) /* before over */ OVER /* before target */ ( /* before partition */ PARTITION /* before by */ BY /* before expr */ a /* before comma */, /* before second */ b /* before order */ ORDER /* before order by */ BY /* before sort */ c /* before rows */ ROWS /* before between */ BETWEEN /* before start */ 1 /* before preceding */ PRECEDING /* before and */ AND /* before end */ CURRENT /* before row */ ROW /* before exclude */ EXCLUDE /* before ties */ TIES /* before close */ ) /* after over */, -- case expr case when x > 1 then 1 else 0 end, case x when 1 then 'one' when 2 then 'two' else 'other' end, @@ -69,6 +212,10 @@ select treat(2 as bigint), 1::int8, int8 '1', + cast(a_very_long_cast_input_expression as a_very_long_type_schema_name.a_very_long_cast_type_name), + treat(a_very_long_treat_input_expression as a_very_long_type_schema_name.a_very_long_treat_type_name), + a_very_long_postgres_cast_input_expression::a_very_long_type_schema_name.a_very_long_postgres_cast_type_name, + a_very_long_type_schema_name.a_very_long_typed_literal_type_name 'a very long typed literal input value', -- field expr foo . bar, foo.bar.baz, @@ -76,23 +223,30 @@ select (foo) . bar, ( /* after opening paren */ foo /* before closing paren */ ) . bar, foo /* before dot */ . /* before field */ "bar", + a_very_long_field_base_expression.a_very_long_first_field_name.a_very_long_second_field_name, + (a_very_long_parenthesized_field_base_expression_that_forces_wrapping).a_very_long_field_name_that_forces_wrapping, -- index expr a [ 1 ], a[1][2], 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], -- literal 42, + 'a very long literal expression value that forces the literal expression target to wrap across the configured line width', -- name ref FOO, "foo", "Mixed", U&"@0066@006f@006f" UESCAPE '@', + a_very_long_unquoted_name_reference_that_forces_the_name_reference_target_to_wrap_past_eighty_characters, + U&"a@005fvery@005flong@005funicode@005fname@005freference" UESCAPE '@', -- paren expr ( 1 ), ( 1 + 2 ), ( ( 1 ) ), ( /* before expr */ 1 /* before closing paren */ ), + (a_very_long_parenthesized_expression + a_second_very_long_parenthesized_expression), -- postfix expr 1 isnull, 2 notnull, @@ -113,12 +267,16 @@ select x is not json value, x is not normalized, x is not nfkd 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 @-@ 10, OPERATOR ( PUBLIC . + ) /* after op */ 1, + 1, - 1, not true, + not - + a_very_long_prefix_input_expression_that_is_long_enough_to_force_wrapping_past_eighty_characters, + OPERATOR(a_very_long_operator_schema_name.###) a_very_long_custom_prefix_input_expression, -- slice expr c[:2][2:], c[1:2], @@ -126,10 +284,15 @@ select c[:3], c[:], c /* before bracket */ [ /* before start */ 1 /* before colon */ : /* before end */ 2 /* before closing bracket */ ], + a_very_long_sliced_expression[a_very_long_slice_start_expression:a_very_long_slice_end_expression][a_second_very_long_slice_start_expression:a_second_very_long_slice_end_expression], -- tuple expr ( 1 , 2 , 3 ), + (first_very_long_tuple_expression, second_very_long_tuple_expression, third_very_long_tuple_expression), row ( ), row ( 1 ), row /* before opening paren */ ( 1 ), row ( 1 , 2 ), + row(a_very_long_first_row_expression, a_very_long_second_row_expression, a_very_long_third_row_expression), ( /* 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); diff --git a/crates/squawk_fmt/tests/before/select_literals.sql b/crates/squawk_fmt/tests/before/select_literals.sql index 74077f09..a2d95475 100644 --- a/crates/squawk_fmt/tests/before/select_literals.sql +++ b/crates/squawk_fmt/tests/before/select_literals.sql @@ -43,3 +43,12 @@ select B'1010' -- byte string continuation select X'AF' 'BE'; + +select null as a_very_long_null_literal_column_alias, true as a_very_long_true_literal_column_alias, false as a_very_long_false_literal_column_alias, $1234567890 as a_very_long_positional_parameter_column_alias; +select 1234567890123456789012345678901234567890, 12345678901234567890.12345678901234567890, 'a very long ordinary string literal value that forces the literal target list to wrap', E'a very long escaped string literal value that forces the literal target list to wrap\n', U&'a very long unicode string literal value that forces the literal target list to wrap', $$a very long dollar quoted string literal value that forces the literal target list to wrap$$, $a_very_long_dollar_quote_tag$a very long tagged dollar quoted string literal value$a_very_long_dollar_quote_tag$, B'10101010101010101010101010101010101010101010101010101010101010101010101010101010', X'ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF'; +select 'a very long continued ordinary string literal value' + 'with a very long continuation segment', E'a very long continued escaped string literal value\n' + 'with a very long continuation segment', U&'a very long continued unicode string literal value' + 'with a very long continuation segment', B'1010101010101010101010101010101010101010' + '0101010101010101010101010101010101010101', X'ABCDEFABCDEFABCDEFABCDEFABCDEFABCDEF' + '123456123456123456123456123456123456'; diff --git a/crates/squawk_fmt/tests/before/table_constraints.sql b/crates/squawk_fmt/tests/before/table_constraints.sql new file mode 100644 index 00000000..560dfe16 --- /dev/null +++ b/crates/squawk_fmt/tests/before/table_constraints.sql @@ -0,0 +1,38 @@ +create table simple_constraints ( + id bigint, + parent_id bigint, + name text, + PRIMARY KEY (id), + UNIQUE NULLS NOT DISTINCT (name), + CHECK (id > 0), + FOREIGN KEY (parent_id) REFERENCES parents(id) +); + +create table named_constraints ( + id bigint, + valid_at tstzrange, + CONSTRAINT pk PRIMARY KEY (id) DEFERRABLE INITIALLY DEFERRED, + CONSTRAINT name_unique UNIQUE (first_very_long_unique_column_name, second_very_long_unique_column_name) INCLUDE (first_very_long_included_column_name, second_very_long_included_column_name) WITH (fillfactor = 70, a_very_long_storage_parameter_name = false) USING INDEX TABLESPACE fast, + CONSTRAINT id_check CHECK (a_long_check_expression_name > another_long_check_expression_name) NOT VALID NO INHERIT, + CONSTRAINT long_check CHECK (a_very_long_check_left_hand_expression_name > a_very_long_check_right_hand_expression_name), + CONSTRAINT parent_fk FOREIGN KEY (first_very_long_foreign_key_column_name, second_very_long_foreign_key_column_name) REFERENCES public.parents (first_very_long_referenced_column_name, second_very_long_referenced_column_name) MATCH FULL ON DELETE SET NULL (first_very_long_foreign_key_column_name, second_very_long_foreign_key_column_name) ON UPDATE NO ACTION NOT DEFERRABLE, + CONSTRAINT no_overlap EXCLUDE USING gist (first_very_long_exclusion_expression WITH =, second_very_long_exclusion_expression WITH &&) INCLUDE (first_very_long_excluded_column_name, second_very_long_excluded_column_name) WITH (fillfactor = 80, an_extremely_long_exclusion_storage_parameter_name_that_forces_wrapping = false) USING INDEX TABLESPACE fast WHERE (a_very_long_exclusion_predicate_expression > an_extremely_long_exclusion_predicate_value_that_forces_wrapping) DEFERRABLE +); + +create table using_indexes ( + id bigint, + UNIQUE USING INDEX existing_unique, + PRIMARY KEY USING INDEX existing_primary +); + +create table a_very_long_table_name_using_existing_indexes (a_very_long_first_identifier_column_name bigint, a_very_long_second_identifier_column_name bigint, unique using index a_very_long_existing_unique_index_name, primary key using index a_very_long_existing_primary_index_name); + +create table commented_constraints ( + id bigint, + parent_id bigint, + valid_at tstzrange, + /* before constraint */ CONSTRAINT /* before constraint name */ "named_pk" /* before primary */ PRIMARY /* before key */ KEY /* before column opening paren */ ( /* before column */ id /* before column closing paren */ ) /* before deferrable */ DEFERRABLE, + CONSTRAINT named_check /* before check */ CHECK /* before check opening paren */ ( /* before check expression */ id > 0 /* before check closing paren */ ) /* before not */ NOT /* before valid */ VALID, + CONSTRAINT named_fk /* before foreign */ FOREIGN /* before key */ KEY /* before from opening paren */ ( /* before from column */ parent_id /* before from closing paren */ ) /* before references */ REFERENCES /* before table */ public /* before dot */ . /* before table name */ parents /* before to opening paren */ ( /* before to column */ id /* before to closing paren */ ) /* before match */ MATCH /* before simple */ SIMPLE /* before on delete */ ON /* before delete */ DELETE /* before set */ SET /* before null */ NULL /* before set columns */ (parent_id) /* before on update */ ON /* before update */ UPDATE /* before cascade */ CASCADE /* before enforced */ ENFORCED, + CONSTRAINT named_exclude /* before exclude */ EXCLUDE /* before using */ USING /* before method */ gist /* before exclusion opening paren */ ( /* before exclusion expression */ id /* before exclusion with */ WITH /* before exclusion op */ = /* before exclusion comma */, /* before second exclusion */ valid_at WITH /* before operator */ OPERATOR /* before operator opening paren */ ( /* before operator name */ public /* before operator dot */ . /* before operator op */ && /* before operator closing paren */ ) /* before exclusion closing paren */ ) /* before include */ INCLUDE /* before include opening paren */ (id /* before include closing paren */ ) /* before with params */ WITH /* before params opening paren */ ( /* before param */ fillfactor /* before equals */ = /* before value */ 80 /* before params closing paren */ ) /* before tablespace using */ USING /* before index */ INDEX /* before tablespace */ TABLESPACE /* before tablespace name */ fast /* before where */ WHERE /* before where opening paren */ ( /* before where expression */ id > 0 /* before where closing paren */ ) /* before initially */ INITIALLY /* before immediate */ IMMEDIATE +); diff --git a/crates/squawk_fmt/tests/before/types.sql b/crates/squawk_fmt/tests/before/types.sql index 2abe5ecb..875f7b43 100644 --- a/crates/squawk_fmt/tests/before/types.sql +++ b/crates/squawk_fmt/tests/before/types.sql @@ -74,6 +74,7 @@ select interval '1' day to second(3), interval '2' year to month, interval(4) '3 -- comments around casts select 1 /*a*/ :: /*b*/ INT8; select cast /*c*/ ( /*d*/ 1 /*e*/ as /*f*/ INT8 /*g*/ ); +select a_very_long_expression_name_that_forces_the_select_to_wrap, cast /*c*/ ( /*d*/ 1 /*e*/ as /*f*/ INT8 /*g*/ ); select treat /*h*/ ( 2 as /*i*/ BIGINT ); select pg_catalog.varchar(10) /*j*/ 'foo'; select interval '4' /*k*/ year to month; @@ -97,3 +98,6 @@ create table t ( ); select 1::DOUBLE -- eight PRECISION; + +create table a_very_long_table_name_for_type_wrapping (a_very_long_numeric_column_name numeric(12345, 12345), a_very_long_varchar_column_name varchar(12345), a_very_long_character_varying_column_name character varying(12345), a_very_long_national_character_varying_column_name national character varying(12345), a_very_long_nchar_column_name nchar(12345), a_very_long_bit_varying_column_name bit varying(12345), a_very_long_double_precision_column_name double precision, a_very_long_timestamp_column_name timestamp(12345) without time zone, a_very_long_time_column_name time(12345) with time zone, a_very_long_interval_column_name interval day to second(12345), a_very_long_array_column_name text[12345][12345]); +select a_very_long_expression_name::a_very_long_type_schema_name.a_very_long_type_name, cast(a_very_long_expression_name as character varying(12345)), cast(a_very_long_expression_name_long_long_long_long_long_long as character varying(12345)), treat(a_very_long_expression_name as a_very_long_type_schema_name.a_very_long_type_name), a_very_long_type_schema_name.a_very_long_type_name(12345) 'a very long typed string literal value', interval 'a very long interval literal value' day to second(12345); diff --git a/crates/squawk_fmt/tests/before/xml_functions.sql b/crates/squawk_fmt/tests/before/xml_functions.sql new file mode 100644 index 00000000..68cca217 --- /dev/null +++ b/crates/squawk_fmt/tests/before/xml_functions.sql @@ -0,0 +1,34 @@ +select + XMLELEMENT(NAME foo), + XMLELEMENT(NAME foo, 1, 2), + XMLELEMENT(NAME foo, XMLATTRIBUTES(a, b AS c)), + XMLELEMENT(NAME foo, XMLATTRIBUTES(a AS attr), x, y), + XMLELEMENT(NAME a_very_long_element_name, XMLATTRIBUTES(a_very_long_xml_attribute_expression AS a_very_long_xml_attribute_name, a_second_very_long_xml_attribute_expression AS a_second_very_long_xml_attribute_name), first_very_long_content_expression, second_very_long_content_expression, third_very_long_content_expression), + /* before element */ XMLELEMENT /* before outer opening paren */ ( /* before name */ NAME /* before tag */ "tag" /* before attributes comma */, /* before xmlattributes */ XMLATTRIBUTES /* before attributes opening paren */ ( /* before first attribute */ a /* before as */ AS /* before attribute name */ "attr" /* before attribute comma */, /* before second attribute */ b /* before attributes closing paren */ ) /* before content comma */, /* before content */ x /* before outer closing paren */ ) /* after element */, + XMLEXISTS('/foo' PASSING doc), + XMLEXISTS('/foo' PASSING BY REF doc), + XMLEXISTS('/foo' PASSING doc BY VALUE), + XMLEXISTS(a_very_long_xml_exists_path_expression PASSING BY REF a_very_long_xml_exists_document_expression BY VALUE), + /* before exists */ XMLEXISTS /* before opening paren */ ( /* before row */ '/foo' /* before passing */ PASSING /* before first by */ BY /* before ref */ REF /* before document */ doc /* before second by */ BY /* before value */ VALUE /* before closing paren */ ) /* after exists */, + XMLFOREST(a, b AS foo), + XMLFOREST(first_very_long_expression AS first_very_long_element_name, second_very_long_expression AS second_very_long_element_name), + /* before forest */ XMLFOREST /* before opening paren */ ( /* before first expression */ a /* before as */ AS /* before tag */ "first" /* before comma */, /* before second expression */ b /* before closing paren */ ) /* after forest */, + XMLPARSE(DOCUMENT '' PRESERVE WHITESPACE), + XMLPARSE(CONTENT value STRIP WHITESPACE), + XMLPARSE(DOCUMENT a_very_long_xml_parse_document_expression_that_forces_the_xml_parse_node_to_wrap PRESERVE WHITESPACE), + /* before parse */ XMLPARSE /* before opening paren */ ( /* before kind */ DOCUMENT /* before expression */ value /* before preserve */ PRESERVE /* before whitespace */ WHITESPACE /* before closing paren */ ) /* after parse */, + XMLPI(NAME php), + XMLPI(NAME php, 'echo'), + XMLPI(NAME a_very_long_xml_processing_instruction_target, a_very_long_xml_processing_instruction_expression_that_forces_the_xml_pi_node_to_wrap), + /* before pi */ XMLPI /* before opening paren */ ( /* before name */ NAME /* before target */ php /* before comma */, /* before expression */ 'echo' /* before closing paren */ ) /* after pi */, + XMLROOT(doc, VERSION '1.0'), + XMLROOT(doc, VERSION NO VALUE, STANDALONE YES), + XMLROOT(doc, VERSION '1.0', STANDALONE NO), + XMLROOT(doc, VERSION '1.0', STANDALONE NO VALUE), + XMLROOT(a_very_long_xml_root_document_expression_that_forces_the_xml_root_node_to_wrap, VERSION a_very_long_xml_root_version_expression, STANDALONE NO VALUE), + /* before root */ XMLROOT /* before opening paren */ ( /* before expression */ doc /* before version comma */, /* before version */ VERSION /* before no */ NO /* before version value */ VALUE /* before standalone comma */, /* before standalone */ STANDALONE /* before standalone no */ NO /* before standalone value */ VALUE /* before closing paren */ ) /* after root */, + XMLSERIALIZE(DOCUMENT doc AS text), + XMLSERIALIZE(CONTENT doc AS varchar(20) INDENT), + XMLSERIALIZE(CONTENT doc AS text NO INDENT), + XMLSERIALIZE(CONTENT a_long_xml_serialize_content_expression_that_forces_wrapping AS a_very_long_xml_serialize_return_type NO INDENT), + /* before serialize */ XMLSERIALIZE /* before opening paren */ ( /* before kind */ CONTENT /* before expression */ doc /* before as */ AS /* before type */ text /* before no */ NO /* before indent */ INDENT /* before closing paren */ ) /* after serialize */; diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index 208ad5e2..845fca62 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -20,7 +20,10 @@ pub enum SyntaxKind { L_CURLY, R_CURLY, L_ANGLE, + LTEQ, + NEQB, R_ANGLE, + GTEQ, AT, POUND, TILDE, @@ -35,8 +38,11 @@ pub enum SyntaxKind { UNDERSCORE, DOT, COLON, + COLON_EQ, EQ, + FAT_ARROW, BANG, + NEQ, MINUS, BACKTICK, ABORT_KW, @@ -722,7 +728,6 @@ pub enum SyntaxKind { COLLATION_REF, COLLATION_RENAME_TO, COLON_COLON, - COLON_EQ, COLUMN, COLUMN_DEF, COLUMN_DEF_LIST, @@ -1015,7 +1020,6 @@ pub enum SyntaxKind { EXTRACT_FIELD_LITERAL, EXTRACT_FIELD_NAME, EXTRACT_FN, - FAT_ARROW, FDW_OPTION_LIST, FETCH, FETCH_CLAUSE, @@ -1088,7 +1092,6 @@ pub enum SyntaxKind { GROUPING_SETS, GROUP_BY_CLAUSE, GROUP_BY_LIST, - GTEQ, HANDLER_CLAUSE, HAVING_CLAUSE, IF_EXISTS, @@ -1231,7 +1234,6 @@ pub enum SyntaxKind { LOCKING_CLAUSE, LOCKING_OF, LOCK_MODE_CLAUSE, - LTEQ, MATCH_FULL, MATCH_PARTIAL, MATCH_SIMPLE, @@ -1251,8 +1253,6 @@ pub enum SyntaxKind { NAMED_LABEL, NAMED_SCHEMA, NAME_REF, - NEQ, - NEQB, NEW_TABLE, NEXT, NON_STANDARD_PARAM, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index 17c1f8ba..b73fb4b0 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -2364,12 +2364,13 @@ fn arg_expr(p: &mut Parser<'_>) -> Option { // https://www.postgresql.org/docs/17/typeconv-func.html let m = p.start(); p.eat(VARIADIC_KW); - let r = Restrictions { - order_by_allowed: true, - ..Restrictions::default() - }; - match expr_bp(p, 1, &r) { - Some(_) => Some(m.complete(p, ARG)), + match expr_bp(p, 1, &Restrictions::default()) { + Some(_) => { + if p.at(ORDER_KW) { + opt_order_by_clause(p); + } + Some(m.complete(p, ARG)) + } None => { m.abandon(p); None @@ -2969,7 +2970,6 @@ const OVERLAPPING_TOKENS: TokenSet = TokenSet::new(&[OR_KW, AND_KW, IS_KW, COLLA #[derive(Default)] struct Restrictions { - order_by_allowed: bool, escape_disabled: bool, in_disabled: bool, is_disabled: bool, @@ -3012,9 +3012,6 @@ fn expr_bp(p: &mut Parser<'_>, bp: u8, r: &Restrictions) -> Option<(CompletedMar m.complete(p, AS_NAME); return Some((lhs, expr_kind)); } - if r.order_by_allowed && p.at(ORDER_KW) { - opt_order_by_clause(p); - } loop { let (op_bp, op, associativity) = current_op(p, r); if op_bp < bp { @@ -3048,9 +3045,6 @@ fn expr_bp(p: &mut Parser<'_>, bp: u8, r: &Restrictions) -> Option<(CompletedMar }; expr_kind = ExprKind::Other; } - if r.order_by_allowed && p.at(ORDER_KW) { - opt_order_by_clause(p); - } Some((lhs, expr_kind)) } diff --git a/crates/squawk_parser/tests/data/ok/select_funcs.sql b/crates/squawk_parser/tests/data/ok/select_funcs.sql index 6b02dc13..e68b8057 100644 --- a/crates/squawk_parser/tests/data/ok/select_funcs.sql +++ b/crates/squawk_parser/tests/data/ok/select_funcs.sql @@ -230,6 +230,9 @@ select pg_sleep_until('tomorrow 03:00'); -- order by one param select array_agg(v order by v desc) from vals; +-- order by named param +select foo(a => x order by y); + -- order by param 2 select jsonb_object_agg(k, v order by v) from vals; diff --git a/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap b/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap index 9c88ec2c..c2b2fceb 100644 --- a/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__select_funcs_ok.snap @@ -3954,6 +3954,41 @@ SOURCE_FILE IDENT "vals" SEMICOLON ";" WHITESPACE "\n\n" + COMMENT "-- order by named param" + WHITESPACE "\n" + SELECT + SELECT_CLAUSE + SELECT_KW "select" + WHITESPACE " " + TARGET_LIST + TARGET + CALL_EXPR + NAME_REF + IDENT "foo" + ARG_LIST + L_PAREN "(" + ARG + NAMED_ARG + PARAM_NAME_REF + IDENT "a" + WHITESPACE " " + FAT_ARROW "=>" + WHITESPACE " " + NAME_REF + IDENT "x" + WHITESPACE " " + ORDER_BY_CLAUSE + ORDER_KW "order" + WHITESPACE " " + BY_KW "by" + WHITESPACE " " + SORT_BY_LIST + SORT_BY + NAME_REF + IDENT "y" + R_PAREN ")" + SEMICOLON ";" + WHITESPACE "\n\n" COMMENT "-- order by param 2" WHITESPACE "\n" SELECT diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index e1873d6c..dd19d9ae 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -3769,21 +3769,6 @@ impl ColonColon { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ColonEq { - pub(crate) syntax: SyntaxNode, -} -impl ColonEq { - #[inline] - pub fn colon_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::COLON) - } - #[inline] - pub fn eq_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::EQ) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Column { pub(crate) syntax: SyntaxNode, @@ -11100,21 +11085,6 @@ impl ExtractFn { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct FatArrow { - pub(crate) syntax: SyntaxNode, -} -impl FatArrow { - #[inline] - pub fn eq_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::EQ) - } - #[inline] - pub fn r_angle_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::R_ANGLE) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FdwOptionList { pub(crate) syntax: SyntaxNode, @@ -12443,21 +12413,6 @@ impl GroupingSets { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Gteq { - pub(crate) syntax: SyntaxNode, -} -impl Gteq { - #[inline] - pub fn eq_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::EQ) - } - #[inline] - pub fn r_angle_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::R_ANGLE) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct HandlerClause { pub(crate) syntax: SyntaxNode, @@ -14564,8 +14519,8 @@ pub struct JsonTablePlanClause { } impl JsonTablePlanClause { #[inline] - pub fn json_table_plan(&self) -> Option { - support::child(&self.syntax) + pub fn json_table_plans(&self) -> AstChildren { + support::children(&self.syntax) } #[inline] pub fn l_paren_token(&self) -> Option { @@ -14576,10 +14531,6 @@ impl JsonTablePlanClause { support::token(&self.syntax, SyntaxKind::R_PAREN) } #[inline] - pub fn comma_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::COMMA) - } - #[inline] pub fn default_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::DEFAULT_KW) } @@ -14658,15 +14609,19 @@ impl JsonTableValueColumn { support::child(&self.syntax) } #[inline] - pub fn json_path_clause(&self) -> Option { + pub fn json_on_empty_clause(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn json_quotes_clause(&self) -> Option { + pub fn json_on_error_clause(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn json_path_clause(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn json_table_value_behavior(&self) -> Option { + pub fn json_quotes_clause(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -15215,21 +15170,6 @@ impl LockingOf { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Lteq { - pub(crate) syntax: SyntaxNode, -} -impl Lteq { - #[inline] - pub fn l_angle_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::L_ANGLE) - } - #[inline] - pub fn eq_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::EQ) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MatchFull { pub(crate) syntax: SyntaxNode, @@ -15592,20 +15532,20 @@ pub struct NamedArg { } impl NamedArg { #[inline] - pub fn colon_eq(&self) -> Option { + pub fn expr(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn expr(&self) -> Option { + pub fn name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn fat_arrow(&self) -> Option { - support::child(&self.syntax) + pub fn colon_eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COLON_EQ) } #[inline] - pub fn name(&self) -> Option { - support::child(&self.syntax) + pub fn fat_arrow_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FAT_ARROW) } } @@ -15643,36 +15583,6 @@ impl NamedSchema { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Neq { - pub(crate) syntax: SyntaxNode, -} -impl Neq { - #[inline] - pub fn bang_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::BANG) - } - #[inline] - pub fn eq_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::EQ) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Neqb { - pub(crate) syntax: SyntaxNode, -} -impl Neqb { - #[inline] - pub fn l_angle_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::L_ANGLE) - } - #[inline] - pub fn r_angle_token(&self) -> Option { - support::token(&self.syntax, SyntaxKind::R_ANGLE) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NewTable { pub(crate) syntax: SyntaxNode, @@ -17274,22 +17184,10 @@ impl Op { support::child(&self.syntax) } #[inline] - pub fn colon_eq(&self) -> Option { - support::child(&self.syntax) - } - #[inline] pub fn custom_op(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn fat_arrow(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn gteq(&self) -> Option { - support::child(&self.syntax) - } - #[inline] pub fn is_distinct_from(&self) -> Option { support::child(&self.syntax) } @@ -17350,18 +17248,6 @@ impl Op { support::child(&self.syntax) } #[inline] - pub fn lteq(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn neq(&self) -> Option { - support::child(&self.syntax) - } - #[inline] - pub fn neqb(&self) -> Option { - support::child(&self.syntax) - } - #[inline] pub fn not_ilike(&self) -> Option { support::child(&self.syntax) } @@ -17390,6 +17276,10 @@ impl Op { support::child(&self.syntax) } #[inline] + pub fn neq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NEQ) + } + #[inline] pub fn percent_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::PERCENT) } @@ -17410,18 +17300,38 @@ impl Op { support::token(&self.syntax, SyntaxKind::COLON) } #[inline] + pub fn colon_eq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::COLON_EQ) + } + #[inline] pub fn l_angle_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::L_ANGLE) } #[inline] + pub fn lteq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::LTEQ) + } + #[inline] + pub fn neqb_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::NEQB) + } + #[inline] pub fn eq_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::EQ) } #[inline] + pub fn fat_arrow_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::FAT_ARROW) + } + #[inline] pub fn r_angle_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::R_ANGLE) } #[inline] + pub fn gteq_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::GTEQ) + } + #[inline] pub fn caret_token(&self) -> Option { support::token(&self.syntax, SyntaxKind::CARET) } @@ -29061,12 +28971,6 @@ pub enum JsonTablePlanOperator { JsonTablePlanUnion(JsonTablePlanUnion), } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum JsonTableValueBehavior { - JsonOnEmptyClause(JsonOnEmptyClause), - JsonOnErrorClause(JsonOnErrorClause), -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum JsonWrapperBehaviorClause { JsonWithConditionalWrapper(JsonWithConditionalWrapper), @@ -32795,24 +32699,6 @@ impl AstNode for ColonColon { &self.syntax } } -impl AstNode for ColonEq { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::COLON_EQ - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} impl AstNode for Column { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -38069,24 +37955,6 @@ impl AstNode for ExtractFn { &self.syntax } } -impl AstNode for FatArrow { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::FAT_ARROW - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} impl AstNode for FdwOptionList { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -39383,24 +39251,6 @@ impl AstNode for GroupingSets { &self.syntax } } -impl AstNode for Gteq { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::GTEQ - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} impl AstNode for HandlerClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -41957,24 +41807,6 @@ impl AstNode for LockingOf { &self.syntax } } -impl AstNode for Lteq { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::LTEQ - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} impl AstNode for MatchFull { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -42317,42 +42149,6 @@ impl AstNode for NamedSchema { &self.syntax } } -impl AstNode for Neq { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::NEQ - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} -impl AstNode for Neqb { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - kind == SyntaxKind::NEQB - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - if Self::can_cast(syntax.kind()) { - Some(Self { syntax }) - } else { - None - } - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - &self.syntax - } -} impl AstNode for NewTable { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -63952,49 +63748,6 @@ impl From for JsonTablePlanOperator { JsonTablePlanOperator::JsonTablePlanUnion(node) } } -impl AstNode for JsonTableValueBehavior { - #[inline] - fn can_cast(kind: SyntaxKind) -> bool { - matches!( - kind, - SyntaxKind::JSON_ON_EMPTY_CLAUSE | SyntaxKind::JSON_ON_ERROR_CLAUSE - ) - } - #[inline] - fn cast(syntax: SyntaxNode) -> Option { - let res = match syntax.kind() { - SyntaxKind::JSON_ON_EMPTY_CLAUSE => { - JsonTableValueBehavior::JsonOnEmptyClause(JsonOnEmptyClause { syntax }) - } - SyntaxKind::JSON_ON_ERROR_CLAUSE => { - JsonTableValueBehavior::JsonOnErrorClause(JsonOnErrorClause { syntax }) - } - _ => { - return None; - } - }; - Some(res) - } - #[inline] - fn syntax(&self) -> &SyntaxNode { - match self { - JsonTableValueBehavior::JsonOnEmptyClause(it) => &it.syntax, - JsonTableValueBehavior::JsonOnErrorClause(it) => &it.syntax, - } - } -} -impl From for JsonTableValueBehavior { - #[inline] - fn from(node: JsonOnEmptyClause) -> JsonTableValueBehavior { - JsonTableValueBehavior::JsonOnEmptyClause(node) - } -} -impl From for JsonTableValueBehavior { - #[inline] - fn from(node: JsonOnErrorClause) -> JsonTableValueBehavior { - JsonTableValueBehavior::JsonOnErrorClause(node) - } -} impl AstNode for JsonWrapperBehaviorClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { diff --git a/crates/squawk_syntax/src/ast/node_ext.rs b/crates/squawk_syntax/src/ast/node_ext.rs index 2b60e5d6..1ec02abb 100644 --- a/crates/squawk_syntax/src/ast/node_ext.rs +++ b/crates/squawk_syntax/src/ast/node_ext.rs @@ -495,6 +495,31 @@ impl ast::ForeignKeyConstraint { } } +fn second_minus_token(node: &SyntaxNode) -> Option { + node.children_with_tokens() + .filter_map(|element| element.into_token()) + .filter(|token| token.kind() == SyntaxKind::MINUS) + .nth(1) +} + +impl ast::EdgeAny { + pub fn end_minus_token(&self) -> Option { + second_minus_token(self.syntax()) + } +} + +impl ast::EdgeLeft { + pub fn end_minus_token(&self) -> Option { + second_minus_token(self.syntax()) + } +} + +impl ast::EdgeRight { + pub fn end_minus_token(&self) -> Option { + second_minus_token(self.syntax()) + } +} + impl ast::XmlPiFn { #[inline] pub fn target(&self) -> Option { @@ -846,6 +871,53 @@ impl ast::ExtractFieldName { } } +impl ast::JsonNullOnNull { + #[inline] + pub fn on_null_token(&self) -> Option { + self.syntax() + .children_with_tokens() + .filter_map(|element| element.into_token()) + .filter(|token| token.kind() == SyntaxKind::NULL_KW) + .nth(1) + } +} + +impl ast::JsonExistsFn { + #[inline] + pub fn document(&self) -> Option { + support::children(self.syntax()).next() + } + + #[inline] + pub fn path(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + +impl ast::JsonQueryFn { + #[inline] + pub fn document(&self) -> Option { + support::children(self.syntax()).next() + } + + #[inline] + pub fn path(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + +impl ast::JsonValueFn { + #[inline] + pub fn document(&self) -> Option { + support::children(self.syntax()).next() + } + + #[inline] + pub fn path(&self) -> Option { + support::children(self.syntax()).nth(1) + } +} + impl ast::PositionFn { #[inline] pub fn pos(&self) -> Option { diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 2556a14f..a45ee6a3 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -267,7 +267,7 @@ JsonTable = ')' JsonTablePlanClause = - 'plan' 'default'? '(' JsonTablePlan (',' JsonTablePlan)? ')' + 'plan' 'default'? '(' (JsonTablePlan (',' JsonTablePlan)*) ')' JsonTablePlan = JsonPathNameRef @@ -561,7 +561,7 @@ Literal = ) NamedArg = - name:ParamNameRef (FatArrow | ColonEq) Expr + name:ParamNameRef ('=>' | ':=') Expr JsonFormatClause = 'format' 'json' JsonEncodingClause? @@ -572,18 +572,6 @@ JsonValueExpr = JsonKeyValue = Expr (':' | 'value') JsonValueExpr -Gteq = - '>' '=' - -FatArrow = - '=' '>' - -Neqb = - '<' '>' - -Lteq = - '<' '=' - NotLike = 'not' 'like' @@ -617,15 +605,9 @@ IsNotNormalized = OperatorCall = 'operator' '(' Op ')' -ColonEq = - ':' '=' - ColonColon = ':' ':' -Neq = - '!' '=' - SimilarTo = 'similar' 'to' @@ -667,10 +649,10 @@ Op = | AtLocal | AtTimeZone | ColonColon - | ColonEq + | ':=' | CustomOp - | FatArrow - | Gteq + | '=>' + | '>=' | IsDistinctFrom | IsJson | IsJsonArray @@ -686,9 +668,9 @@ Op = | IsNotJsonScalar | IsNotJsonValue | IsNotNormalized - | Lteq - | Neq - | Neqb + | '<=' + | '!=' + | '<>' | NotIlike | NotIn | NotLike @@ -2580,11 +2562,8 @@ JsonTableValueColumn = JsonPathClause? JsonWrapperBehaviorClause? JsonQuotesClause? - JsonTableValueBehavior? - -JsonTableValueBehavior = - JsonOnErrorClause -| JsonOnEmptyClause + JsonOnEmptyClause? + JsonOnErrorClause? JsonTableExistsColumn = ColumnName Type diff --git a/crates/xtask/src/codegen.rs b/crates/xtask/src/codegen.rs index 8ae06aad..e00cae90 100644 --- a/crates/xtask/src/codegen.rs +++ b/crates/xtask/src/codegen.rs @@ -130,7 +130,10 @@ const PUNCT: &[(&str, &str)] = &[ ("{", "L_CURLY"), ("}", "R_CURLY"), ("<", "L_ANGLE"), + ("<=", "LTEQ"), + ("<>", "NEQB"), (">", "R_ANGLE"), + (">=", "GTEQ"), ("@", "AT"), ("#", "POUND"), ("~", "TILDE"), @@ -145,8 +148,11 @@ const PUNCT: &[(&str, &str)] = &[ ("_", "UNDERSCORE"), (".", "DOT"), (":", "COLON"), + (":=", "COLON_EQ"), ("=", "EQ"), + ("=>", "FAT_ARROW"), ("!", "BANG"), + ("!=", "NEQ"), ("-", "MINUS"), ("`", "BACKTICK"), ]; @@ -495,8 +501,12 @@ fn token_to_name(tk: &str) -> Option<&'static str> { "]" => "r_brack", "<" => "l_angle", ">" => "r_angle", + ">=" => "gteq", + "<=" => "lteq", + "<>" => "neqb", "=" => "eq", "!" => "bang", + "!=" => "neq", "*" => "star", "&" => "amp", "+" => "plus", @@ -507,6 +517,7 @@ fn token_to_name(tk: &str) -> Option<&'static str> { "_" => "underscore", "." => "dot", "=>" => "fat_arrow", + ":=" => "colon_eq", "@" => "at", "%" => "percent", ":" => "colon",