diff --git a/crates/squawk_fmt/src/fmt.rs b/crates/squawk_fmt/src/fmt.rs index ea1f0546..5c9dccc2 100644 --- a/crates/squawk_fmt/src/fmt.rs +++ b/crates/squawk_fmt/src/fmt.rs @@ -1943,13 +1943,13 @@ fn build_function_param<'a>(param: ast::Param) -> Doc<'a> { } }); } - if let Some(ty) = param.ty() { + if let Some(func_type) = param.func_type() { if has_prefix { doc = doc.append(Doc::space()); } doc = doc - .append(leading_comments(ty.syntax())) - .append(build_type(ty)); + .append(leading_comments(func_type.syntax())) + .append(build_func_type(func_type)); } if let Some(default) = param.param_default() { doc = doc @@ -1982,10 +1982,10 @@ fn build_function_ret_type<'a>(ret_type: ast::RetType) -> Doc<'a> { .append(leading_comments_token(&table_token)) .append(Doc::text("table")); } - if let Some(args) = ret_type.table_arg_list() { + if let Some(args) = ret_type.return_table_arg_list() { let mut body = Doc::list( Itertools::intersperse( - args.args().map(build_table_arg), + args.args().map(build_return_table_column), Doc::text(",").append(Doc::line_or_space()), ) .collect(), @@ -2007,15 +2007,32 @@ fn build_function_ret_type<'a>(ret_type: ast::RetType) -> Doc<'a> { .append(Doc::space()) .append(leading_comments(args.syntax())) .append(args_doc); - } else if let Some(ty) = ret_type.ty() { + } else if let Some(func_type) = ret_type.func_type() { doc = doc .append(Doc::space()) - .append(leading_comments(ty.syntax())) - .append(build_type(ty)); + .append(leading_comments(func_type.syntax())) + .append(build_func_type(func_type)); } doc } +fn build_return_table_column<'a>(column: ast::ReturnTableColumn) -> Doc<'a> { + let syntax = column.syntax().clone(); + let mut doc = column + .name() + .map(|name| build_name(name.syntax())) + .unwrap_or_else(Doc::nil); + if let Some(func_type) = column.func_type() { + doc = doc + .append(Doc::space()) + .append(leading_comments(func_type.syntax())) + .append(build_func_type(func_type)); + } + leading_comments(&syntax) + .append(doc) + .append(trailing_comments(&syntax)) +} + fn build_function_option<'a>(option: ast::FuncOption) -> Doc<'a> { match option { ast::FuncOption::AsFuncOption(option) => build_as_function_option(option), @@ -22685,6 +22702,34 @@ fn format_string_token(t: &SyntaxToken) -> String { } } +fn build_func_type<'a>(func_type: ast::FuncType) -> Doc<'a> { + match func_type { + ast::FuncType::PercentType(percent_type) => build_percent_type(percent_type), + ast::FuncType::Type(ty) => build_type(ty), + } +} + +fn build_percent_type<'a>(percent_type: ast::PercentType) -> Doc<'a> { + let mut doc = build_setof(percent_type.setof_token()); + if let Some(path) = percent_type.path_ref() { + doc = doc + .append(leading_comments(path.syntax())) + .append(build_path_ref(&path)); + } + if let Some(clause) = percent_type.percent_type_clause() { + doc = doc.append(comments_before(clause.syntax().clone())); + if clause.percent_token().is_some() { + doc = doc.append(Doc::text("%")); + } + if let Some(type_token) = clause.type_token() { + doc = doc + .append(comments_before(type_token)) + .append(Doc::text("type")); + } + } + doc +} + fn build_type<'a>(ty: ast::Type) -> Doc<'a> { match ty { ast::Type::ArrayType(array_type) => { @@ -22754,26 +22799,6 @@ fn build_type<'a>(ty: ast::Type) -> Doc<'a> { } doc.append(build_type_args(arg_list)) } - ast::Type::PercentType(percent_type) => { - let mut doc = build_setof(percent_type.setof_token()); - if let Some(path) = percent_type.path_ref() { - doc = doc - .append(leading_comments(path.syntax())) - .append(build_path_ref(&path)); - } - if let Some(clause) = percent_type.percent_type_clause() { - doc = doc.append(comments_before(clause.syntax().clone())); - if clause.percent_token().is_some() { - doc = doc.append(Doc::text("%")); - } - if let Some(type_token) = clause.type_token() { - doc = doc - .append(comments_before(type_token)) - .append(Doc::text("type")); - } - } - doc - } ast::Type::TimeType(time_type) => { let mut doc = build_setof(time_type.setof_token()); if let Some(time_token) = time_type.time_token() { diff --git a/crates/squawk_fmt/tests/after/create_function.snap b/crates/squawk_fmt/tests/after/create_function.snap index 3b2c2822..68254582 100644 --- a/crates/squawk_fmt/tests/after/create_function.snap +++ b/crates/squawk_fmt/tests/after/create_function.snap @@ -60,6 +60,18 @@ create function returns_null_on_null_input_example() returns text language sql as $$ select null::text $$; +create function percent_type_param( + value accounts.id%type +) returns accounts.id%type + language sql + as $$ select value $$; + +create function percent_type_table( + unused integer +) returns table (value accounts /*pct1*/.id /*pct2*/% /*pct3*/type) + language sql + as $$ select 1 $$; + -- comments in every position create /*a*/ or /*b*/ replace /*c*/ function /*d*/ app /*e*/./*f*/ commented( /*g*/ in /*h*/ value /*i*/ integer /*j*/ default /*k*/ 1 /*l*/, diff --git a/crates/squawk_fmt/tests/before/create_function.sql b/crates/squawk_fmt/tests/before/create_function.sql index 65d52b75..70576cb3 100644 --- a/crates/squawk_fmt/tests/before/create_function.sql +++ b/crates/squawk_fmt/tests/before/create_function.sql @@ -10,6 +10,10 @@ create function option_examples(in first integer, out second text, inout third b create function returns_null_on_null_input_example() returns text returns null on null input language sql as $$ select null::text $$; +create function percent_type_param(value accounts.id%type) returns accounts.id%type language sql as $$ select value $$; + +create function percent_type_table(unused integer) returns table (value accounts /*pct1*/. id /*pct2*/% /*pct3*/type) language sql as $$ select 1 $$; + -- comments in every position create /*a*/ or /*b*/ replace /*c*/ function /*d*/ app /*e*/. /*f*/ commented (/*g*/ in /*h*/ value /*i*/ integer /*j*/ default /*k*/ 1 /*l*/, /*m*/ out /*n*/ result /*o*/ text /*p*/) diff --git a/crates/squawk_ide/src/binder.rs b/crates/squawk_ide/src/binder.rs index 72552a56..e8a9ca10 100644 --- a/crates/squawk_ide/src/binder.rs +++ b/crates/squawk_ide/src/binder.rs @@ -2099,8 +2099,7 @@ fn extract_param_signature(param_list: Option) -> Option bool { | ast::Type::IntervalType(_) | ast::Type::TimeType(_) | ast::Type::TimestampType(_) => true, - ast::Type::ArrayType(_) - | ast::Type::ExprType(_) - | ast::Type::PathType(_) - | ast::Type::PercentType(_) => false, + ast::Type::ArrayType(_) | ast::Type::ExprType(_) | ast::Type::PathType(_) => false, }; } diff --git a/crates/squawk_ide/src/folding_ranges.rs b/crates/squawk_ide/src/folding_ranges.rs index f0d8f9d5..dace3b9c 100644 --- a/crates/squawk_ide/src/folding_ranges.rs +++ b/crates/squawk_ide/src/folding_ranges.rs @@ -125,9 +125,10 @@ fn fold_kind(kind: SyntaxKind) -> Option { } match kind { - SyntaxKind::ARG_LIST | SyntaxKind::TABLE_ARG_LIST | SyntaxKind::PARAM_LIST => { - Some(FoldKind::ArgList) - } + SyntaxKind::ARG_LIST + | SyntaxKind::TABLE_ARG_LIST + | SyntaxKind::RETURN_TABLE_ARG_LIST + | SyntaxKind::PARAM_LIST => Some(FoldKind::ArgList), SyntaxKind::ARRAY_EXPR => Some(FoldKind::Array), SyntaxKind::CALL_EXPR => Some(FoldKind::FunctionCall), SyntaxKind::JOIN => Some(FoldKind::Join), diff --git a/crates/squawk_ide/src/hover.rs b/crates/squawk_ide/src/hover.rs index 12abdd95..8304c6bd 100644 --- a/crates/squawk_ide/src/hover.rs +++ b/crates/squawk_ide/src/hover.rs @@ -1887,7 +1887,9 @@ fn hover_named_arg_parameter(db: &dyn Db, def: Location) -> Option { let def_node = def.to_node(db)?; let param = def_node.ancestors().find_map(ast::Param::cast)?; let param_name = param.name().map(|name| Name::from_node(&name))?; - let param_type = param.ty().map(|ty| ty.syntax().text().to_string()); + let param_type = param + .func_type() + .map(|func_type| func_type.syntax().text().to_string()); for ancestor in def_node.ancestors() { if let Some(create_function) = ast::CreateFunction::cast(ancestor.clone()) { diff --git a/crates/squawk_ide/src/name.rs b/crates/squawk_ide/src/name.rs index f1768590..d1ed6075 100644 --- a/crates/squawk_ide/src/name.rs +++ b/crates/squawk_ide/src/name.rs @@ -246,7 +246,6 @@ pub(crate) fn schema_and_type_name(ty: &ast::Type) -> Option<(Option, Na }; Some((None, Name::from_string(name))) } - ast::Type::PercentType(_) => None, } } diff --git a/crates/squawk_ide/src/resolve.rs b/crates/squawk_ide/src/resolve.rs index 81f4a02d..bc542bf1 100644 --- a/crates/squawk_ide/src/resolve.rs +++ b/crates/squawk_ide/src/resolve.rs @@ -4963,13 +4963,11 @@ fn count_columns_for_call_expr_return_table( .ancestors() .find_map(ast::CreateFunction::cast)?; - if let Some(table_arg_list) = create_function.ret_type().and_then(|r| r.table_arg_list()) { - return Some( - table_arg_list - .args() - .filter(|arg| matches!(arg, ast::TableArg::Column(_))) - .count(), - ); + if let Some(return_table_arg_list) = create_function + .ret_type() + .and_then(|ret_type| ret_type.return_table_arg_list()) + { + return Some(return_table_arg_list.args().count()); } if let Some(param_list) = create_function.param_list() { @@ -4987,7 +4985,9 @@ fn count_columns_for_call_expr_return_table( } } - if let Some(ast::Type::PathType(path_type)) = create_function.ret_type().and_then(|r| r.ty()) + if let Some(ast::FuncType::Type(ast::Type::PathType(path_type))) = create_function + .ret_type() + .and_then(|ret_type| ret_type.func_type()) && let Some(path) = path_type.path_ref() && let Some(column_count) = count_columns_for_path(db, InFile::new(function_loc.file, &path)).or_else(|| { @@ -5019,21 +5019,20 @@ fn resolve_column_from_call_expr_return_table( .find_map(ast::CreateFunction::cast)?; // `returns table(col ...)` - if let Some(table_arg_list) = create_function.ret_type().and_then(|r| r.table_arg_list()) { - let mut index = 0usize; - for arg in table_arg_list.args() { - if let ast::TableArg::Column(column) = arg { - if let Some(name) = column.name() - && Name::from_node(&name) == *column_name - && index >= min_index - { - return Some(smallvec![Location::new( - file, - name.syntax().text_range(), - LocationKind::Column - )]); - } - index += 1; + if let Some(return_table_arg_list) = create_function + .ret_type() + .and_then(|ret_type| ret_type.return_table_arg_list()) + { + for (index, column) in return_table_arg_list.args().enumerate() { + if let Some(name) = column.name() + && Name::from_node(&name) == *column_name + && index >= min_index + { + return Some(smallvec![Location::new( + file, + name.syntax().text_range(), + LocationKind::Column + )]); } } } @@ -5063,7 +5062,9 @@ fn resolve_column_from_call_expr_return_table( } // `returns setof ` or `returns setof ` - if let Some(ast::Type::PathType(path_type)) = create_function.ret_type().and_then(|r| r.ty()) + if let Some(ast::FuncType::Type(ast::Type::PathType(path_type))) = create_function + .ret_type() + .and_then(|ret_type| ret_type.func_type()) && let Some(path) = path_type.path_ref() { if let Some(ptr) = @@ -5191,7 +5192,7 @@ fn resolve_symbol_info_from_parts( fn param_signature(node: &ast::HasParamList) -> Option> { let mut params = vec![]; for param in node.param_list()?.all_params() { - if let Some(ast::Type::PathType(path_type)) = param.ty() + if let Some(ast::FuncType::Type(ast::Type::PathType(path_type))) = param.func_type() && let Some(name_ref) = path_type.path_ref().and_then(|x| x.segment()) { params.push(Name::from_node(&name_ref)); diff --git a/crates/squawk_ide/src/semantic_tokens.rs b/crates/squawk_ide/src/semantic_tokens.rs index 79f7d8bc..99dd4d7f 100644 --- a/crates/squawk_ide/src/semantic_tokens.rs +++ b/crates/squawk_ide/src/semantic_tokens.rs @@ -149,7 +149,6 @@ fn highlight_type(out: &mut SemanticTokenBuilder, ty: ast::Type) { out.push_type(token.into()); } } - ast::Type::PercentType(_) => (), ast::Type::TimeType(time_type) => { if let Some(token) = time_type.setof_token() { out.push_type(token.into()); diff --git a/crates/squawk_linter/src/rules/prefer_bigint_over_int.rs b/crates/squawk_linter/src/rules/prefer_bigint_over_int.rs index c5694039..181f3828 100644 --- a/crates/squawk_linter/src/rules/prefer_bigint_over_int.rs +++ b/crates/squawk_linter/src/rules/prefer_bigint_over_int.rs @@ -35,7 +35,6 @@ fn create_bigint_fix(ty: &ast::Type) -> Option { | ast::Type::CharacterType(_) | ast::Type::DoubleType(_) | ast::Type::ExprType(_) - | ast::Type::PercentType(_) | ast::Type::TimeType(_) | ast::Type::TimestampType(_) | ast::Type::IntervalType(_) => return None, diff --git a/crates/squawk_linter/src/rules/prefer_bigint_over_smallint.rs b/crates/squawk_linter/src/rules/prefer_bigint_over_smallint.rs index 6bf4957d..e9249a76 100644 --- a/crates/squawk_linter/src/rules/prefer_bigint_over_smallint.rs +++ b/crates/squawk_linter/src/rules/prefer_bigint_over_smallint.rs @@ -36,7 +36,6 @@ fn create_bigint_fix(ty: &ast::Type) -> Option { | ast::Type::CharacterType(_) | ast::Type::DoubleType(_) | ast::Type::ExprType(_) - | ast::Type::PercentType(_) | ast::Type::TimeType(_) | ast::Type::TimestampType(_) | ast::Type::IntervalType(_) => return None, diff --git a/crates/squawk_linter/src/rules/prefer_identity.rs b/crates/squawk_linter/src/rules/prefer_identity.rs index 863693ab..a4cb9034 100644 --- a/crates/squawk_linter/src/rules/prefer_identity.rs +++ b/crates/squawk_linter/src/rules/prefer_identity.rs @@ -44,7 +44,6 @@ fn create_identity_fix(ty: &ast::Type) -> Option { | ast::Type::CharacterType(_) | ast::Type::DoubleType(_) | ast::Type::ExprType(_) - | ast::Type::PercentType(_) | ast::Type::TimeType(_) | ast::Type::TimestampType(_) | ast::Type::IntervalType(_) => return None, diff --git a/crates/squawk_linter/src/rules/prefer_text_field.rs b/crates/squawk_linter/src/rules/prefer_text_field.rs index a6367066..5aa378ec 100644 --- a/crates/squawk_linter/src/rules/prefer_text_field.rs +++ b/crates/squawk_linter/src/rules/prefer_text_field.rs @@ -16,7 +16,6 @@ fn is_not_allowed_varchar(ty: &ast::Type) -> bool { false } } - ast::Type::PercentType(_) => false, ast::Type::PathType(path_type) => { let Some(ty_name) = path_type .path_ref() diff --git a/crates/squawk_linter/src/rules/prefer_timestamptz.rs b/crates/squawk_linter/src/rules/prefer_timestamptz.rs index 5846bb71..6788c507 100644 --- a/crates/squawk_linter/src/rules/prefer_timestamptz.rs +++ b/crates/squawk_linter/src/rules/prefer_timestamptz.rs @@ -15,7 +15,6 @@ pub fn is_not_allowed_timestamp(ty: &ast::Type) -> bool { false } } - ast::Type::PercentType(_) => false, ast::Type::PathType(path_type) => { let Some(ty_name) = path_type .path_ref() diff --git a/crates/squawk_linter/src/visitors.rs b/crates/squawk_linter/src/visitors.rs index cf7d6bd5..de11a9ee 100644 --- a/crates/squawk_linter/src/visitors.rs +++ b/crates/squawk_linter/src/visitors.rs @@ -16,7 +16,6 @@ pub(crate) fn is_not_valid_int_type( false } } - ast::Type::PercentType(_) => false, ast::Type::PathType(path_type) => { let Some(ty_name) = path_type .path_ref() diff --git a/crates/squawk_parser/src/generated/syntax_kind.rs b/crates/squawk_parser/src/generated/syntax_kind.rs index 60a99acc..692b0449 100644 --- a/crates/squawk_parser/src/generated/syntax_kind.rs +++ b/crates/squawk_parser/src/generated/syntax_kind.rs @@ -1593,6 +1593,8 @@ pub enum SyntaxKind { RETURNING_OPTION_LIST, RETURNS_NULL_ON_NULL_INPUT_FUNC_OPTION, RETURN_STMT, + RETURN_TABLE_ARG_LIST, + RETURN_TABLE_COLUMN, RET_TYPE, REVOKE, REVOKE_COMMAND, diff --git a/crates/squawk_parser/src/grammar.rs b/crates/squawk_parser/src/grammar.rs index f052780c..bf27a2b2 100644 --- a/crates/squawk_parser/src/grammar.rs +++ b/crates/squawk_parser/src/grammar.rs @@ -2176,9 +2176,10 @@ fn type_mods( p: &mut Parser<'_>, m: Marker, type_args_enabled: bool, + percent_type_enabled: bool, kind: SyntaxKind, ) -> Option { - if opt_percent_type(p).is_some() { + if percent_type_enabled && opt_percent_type(p).is_some() { return Some(m.complete(p, PERCENT_TYPE)); } if p.at(L_PAREN) && type_args_enabled && allows_type_mods(kind) { @@ -2265,7 +2266,11 @@ SimpleTypename is: */ #[must_use] -fn opt_type_name_with(p: &mut Parser<'_>, type_args_enabled: bool) -> Option { +fn opt_type_name_with( + p: &mut Parser<'_>, + type_args_enabled: bool, + percent_type_enabled: bool, +) -> Option { if !p.at_ts(TYPE_NAME_FIRST) { return None; } @@ -2326,7 +2331,7 @@ fn opt_type_name_with(p: &mut Parser<'_>, type_args_enabled: bool) -> Option) { @@ -2347,7 +2352,7 @@ fn opt_with_timezone(p: &mut Parser<'_>) { } fn opt_type_name(p: &mut Parser<'_>) -> bool { - opt_type_name_with(p, true).is_some() + opt_type_name_with(p, true, false).is_some() } fn type_name(p: &mut Parser<'_>) { @@ -2356,8 +2361,14 @@ fn type_name(p: &mut Parser<'_>) { } } +fn func_type(p: &mut Parser<'_>) { + if opt_type_name_with(p, true, true).is_none() { + p.error("expected type name"); + } +} + fn simple_type_name(p: &mut Parser<'_>) { - if opt_type_name_with(p, false).is_none() { + if opt_type_name_with(p, false, false).is_none() { p.error("expected simple type name"); } } @@ -17435,18 +17446,18 @@ fn param(p: &mut Parser<'_>, kind: ParamKind) { _ => false, }; if at_type { - type_name(p); + func_type(p); } else { param_name(p); if !param_mode_seen { opt_param_mode(p); } // argtype - type_name(p); + func_type(p); } } else { // argtype - type_name(p); + func_type(p); } opt_param_default(p); } @@ -17693,6 +17704,31 @@ fn reset_config_param(p: &mut Parser<'_>) -> CompletedMarker { m.complete(p, RESET_CONFIG_PARAM) } +fn return_table_column(p: &mut Parser<'_>) -> bool { + if !p.at_ts(NAME_FIRST) { + return false; + } + let m = p.start(); + column_name(p); + func_type(p); + m.complete(p, RETURN_TABLE_COLUMN); + true +} + +fn return_table_arg_list(p: &mut Parser<'_>) { + let m = p.start(); + delimited( + p, + L_PAREN, + R_PAREN, + COMMA, + || "unexpected comma".to_string(), + NAME_FIRST, + return_table_column, + ); + m.complete(p, RETURN_TABLE_ARG_LIST); +} + fn opt_ret_type(p: &mut Parser<'_>) { // [ RETURNS rettype // | RETURNS TABLE ( column_name column_type [, ...] ) ] @@ -17700,12 +17736,12 @@ fn opt_ret_type(p: &mut Parser<'_>) { if p.eat(RETURNS_KW) { if p.eat(TABLE_KW) { if p.at(L_PAREN) { - table_arg_list(p); + return_table_arg_list(p); } else { - p.error("expected table arg list"); + p.error("expected return table arg list"); } } else { - type_name(p); + func_type(p); } m.complete(p, RET_TYPE); } else { diff --git a/crates/squawk_parser/tests/data/ok/create_function.sql b/crates/squawk_parser/tests/data/ok/create_function.sql index f4321133..913c9441 100644 --- a/crates/squawk_parser/tests/data/ok/create_function.sql +++ b/crates/squawk_parser/tests/data/ok/create_function.sql @@ -54,7 +54,7 @@ as '' language 'sql'; -- returns table create function f() -returns table (a text, b int) +returns table (a text, b int, c t.c%type) as '' language sql; -- transform diff --git a/crates/squawk_parser/tests/snapshots/tests__create_function_err.snap b/crates/squawk_parser/tests/snapshots/tests__create_function_err.snap index 89aadfe3..17844ae2 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_function_err.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_function_err.snap @@ -23,9 +23,9 @@ SOURCE_FILE WHITESPACE " " TABLE_KW "table" WHITESPACE " " - TABLE_ARG_LIST + RETURN_TABLE_ARG_LIST L_PAREN "(" - COLUMN + RETURN_TABLE_COLUMN COLUMN_NAME IDENT "a" WHITESPACE " " @@ -34,7 +34,7 @@ SOURCE_FILE PATH_SEGMENT_REF TEXT_KW "text" WHITESPACE " " - COLUMN + RETURN_TABLE_COLUMN COLUMN_NAME IDENT "b" WHITESPACE " " diff --git a/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap b/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap index f43c247e..5beb5a11 100644 --- a/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap +++ b/crates/squawk_parser/tests/snapshots/tests__create_function_ok.snap @@ -487,9 +487,9 @@ SOURCE_FILE WHITESPACE " " TABLE_KW "table" WHITESPACE " " - TABLE_ARG_LIST + RETURN_TABLE_ARG_LIST L_PAREN "(" - COLUMN + RETURN_TABLE_COLUMN COLUMN_NAME IDENT "a" WHITESPACE " " @@ -499,7 +499,7 @@ SOURCE_FILE TEXT_KW "text" COMMA "," WHITESPACE " " - COLUMN + RETURN_TABLE_COLUMN COLUMN_NAME IDENT "b" WHITESPACE " " @@ -507,6 +507,23 @@ SOURCE_FILE PATH_REF PATH_SEGMENT_REF INT_KW "int" + COMMA "," + WHITESPACE " " + RETURN_TABLE_COLUMN + COLUMN_NAME + IDENT "c" + WHITESPACE " " + PERCENT_TYPE + PATH_REF + PATH_REF + PATH_SEGMENT_REF + IDENT "t" + DOT "." + PATH_SEGMENT_REF + IDENT "c" + PERCENT_TYPE_CLAUSE + PERCENT "%" + TYPE_KW "type" R_PAREN ")" WHITESPACE "\n" FUNC_OPTION_LIST diff --git a/crates/squawk_syntax/src/ast/generated/nodes.rs b/crates/squawk_syntax/src/ast/generated/nodes.rs index 55527067..a14a5ebe 100644 --- a/crates/squawk_syntax/src/ast/generated/nodes.rs +++ b/crates/squawk_syntax/src/ast/generated/nodes.rs @@ -18549,19 +18549,19 @@ pub struct Param { } impl Param { #[inline] - pub fn mode(&self) -> Option { + pub fn func_type(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn name(&self) -> Option { + pub fn mode(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn param_default(&self) -> Option { + pub fn name(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn ty(&self) -> Option { + pub fn param_default(&self) -> Option { support::child(&self.syntax) } } @@ -21671,11 +21671,11 @@ pub struct RetType { } impl RetType { #[inline] - pub fn table_arg_list(&self) -> Option { + pub fn func_type(&self) -> Option { support::child(&self.syntax) } #[inline] - pub fn ty(&self) -> Option { + pub fn return_table_arg_list(&self) -> Option { support::child(&self.syntax) } #[inline] @@ -21707,6 +21707,40 @@ impl ReturnStmt { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReturnTableArgList { + pub(crate) syntax: SyntaxNode, +} +impl ReturnTableArgList { + #[inline] + pub fn args(&self) -> AstChildren { + support::children(&self.syntax) + } + #[inline] + pub fn l_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::L_PAREN) + } + #[inline] + pub fn r_paren_token(&self) -> Option { + support::token(&self.syntax, SyntaxKind::R_PAREN) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ReturnTableColumn { + pub(crate) syntax: SyntaxNode, +} +impl ReturnTableColumn { + #[inline] + pub fn func_type(&self) -> Option { + support::child(&self.syntax) + } + #[inline] + pub fn name(&self) -> Option { + support::child(&self.syntax) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ReturningClause { pub(crate) syntax: SyntaxNode, @@ -28953,6 +28987,12 @@ pub enum FuncOption { VolatilityFuncOption(VolatilityFuncOption), } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum FuncType { + PercentType(PercentType), + Type(Type), +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum GeneratedAs { GeneratedIdentity(GeneratedIdentity), @@ -29939,7 +29979,6 @@ pub enum Type { ExprType(ExprType), IntervalType(IntervalType), PathType(PathType), - PercentType(PercentType), TimeType(TimeType), TimestampType(TimestampType), VarcharType(VarcharType), @@ -48346,6 +48385,42 @@ impl AstNode for ReturnStmt { &self.syntax } } +impl AstNode for ReturnTableArgList { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::RETURN_TABLE_ARG_LIST + } + #[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 ReturnTableColumn { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + kind == SyntaxKind::RETURN_TABLE_COLUMN + } + #[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 ReturningClause { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -63144,6 +63219,38 @@ impl From for FuncOption { FuncOption::WindowFuncOption(node) } } +impl AstNode for FuncType { + #[inline] + fn can_cast(kind: SyntaxKind) -> bool { + matches!(kind, SyntaxKind::PERCENT_TYPE) || Type::can_cast(kind) + } + #[inline] + fn cast(syntax: SyntaxNode) -> Option { + let res = match syntax.kind() { + SyntaxKind::PERCENT_TYPE => FuncType::PercentType(PercentType { syntax }), + _ => { + if let Some(result) = Type::cast(syntax.clone()) { + return Some(FuncType::Type(result)); + } + return None; + } + }; + Some(res) + } + #[inline] + fn syntax(&self) -> &SyntaxNode { + match self { + FuncType::PercentType(it) => &it.syntax, + FuncType::Type(it) => it.syntax(), + } + } +} +impl From for FuncType { + #[inline] + fn from(node: PercentType) -> FuncType { + FuncType::PercentType(node) + } +} impl AstNode for GeneratedAs { #[inline] fn can_cast(kind: SyntaxKind) -> bool { @@ -70893,7 +71000,6 @@ impl AstNode for Type { | SyntaxKind::EXPR_TYPE | SyntaxKind::INTERVAL_TYPE | SyntaxKind::PATH_TYPE - | SyntaxKind::PERCENT_TYPE | SyntaxKind::TIME_TYPE | SyntaxKind::TIMESTAMP_TYPE | SyntaxKind::VARCHAR_TYPE @@ -70910,7 +71016,6 @@ impl AstNode for Type { SyntaxKind::EXPR_TYPE => Type::ExprType(ExprType { syntax }), SyntaxKind::INTERVAL_TYPE => Type::IntervalType(IntervalType { syntax }), SyntaxKind::PATH_TYPE => Type::PathType(PathType { syntax }), - SyntaxKind::PERCENT_TYPE => Type::PercentType(PercentType { syntax }), SyntaxKind::TIME_TYPE => Type::TimeType(TimeType { syntax }), SyntaxKind::TIMESTAMP_TYPE => Type::TimestampType(TimestampType { syntax }), SyntaxKind::VARCHAR_TYPE => Type::VarcharType(VarcharType { syntax }), @@ -70931,7 +71036,6 @@ impl AstNode for Type { Type::ExprType(it) => &it.syntax, Type::IntervalType(it) => &it.syntax, Type::PathType(it) => &it.syntax, - Type::PercentType(it) => &it.syntax, Type::TimeType(it) => &it.syntax, Type::TimestampType(it) => &it.syntax, Type::VarcharType(it) => &it.syntax, @@ -70986,12 +71090,6 @@ impl From for Type { Type::PathType(node) } } -impl From for Type { - #[inline] - fn from(node: PercentType) -> Type { - Type::PercentType(node) - } -} impl From for Type { #[inline] fn from(node: TimeType) -> Type { diff --git a/crates/squawk_syntax/src/column_name.rs b/crates/squawk_syntax/src/column_name.rs index 0aa33ae1..f82871ef 100644 --- a/crates/squawk_syntax/src/column_name.rs +++ b/crates/squawk_syntax/src/column_name.rs @@ -177,9 +177,6 @@ fn name_from_type(ty: ast::Type, unknown_column: bool) -> Option<(ColumnName, Sy return name_from_type(inner_ty, unknown_column); } } - // we shouldn't ever hit this since the following isn't valid syntax: - // select cast('foo' as t.a%TYPE); - ast::Type::PercentType(_) => return None, ast::Type::ExprType(expr_type) => { if let Some(expr) = expr_type.expr() { return name_from_expr(expr, true).map(|(column, node)| { @@ -681,9 +678,6 @@ fn examples() { // interval types assert_snapshot!(name("cast('1 hour' as interval hour to minute)"), @"interval"); - // percent types - assert_snapshot!(name("cast(foo as schema.%TYPE)"), @"foo"); - // time types assert_snapshot!(name("cast('12:00:00' as time(6) without time zone)"), @"time"); assert_snapshot!(name("cast('12:00:00' as time(6) with time zone)"), @"timetz"); diff --git a/crates/squawk_syntax/src/lib.rs b/crates/squawk_syntax/src/lib.rs index 3212ca68..571589c7 100644 --- a/crates/squawk_syntax/src/lib.rs +++ b/crates/squawk_syntax/src/lib.rs @@ -231,9 +231,9 @@ fn api_walkthrough() { // return let ret_type: Option = func.ret_type(); - let r_ty = &ret_type.unwrap().ty().unwrap(); - let type_: &ast::PathType = match &r_ty { - ast::Type::PathType(r) => r, + let r_ty = ret_type.unwrap().func_type().unwrap(); + let type_: ast::PathType = match r_ty { + ast::FuncType::Type(ast::Type::PathType(r)) => r, _ => unreachable!(), }; let type_path: ast::PathRef = type_.path_ref().unwrap(); @@ -246,7 +246,7 @@ fn api_walkthrough() { let param_name: ast::ParamName = param.name().unwrap(); assert_eq!(param_name.syntax().to_string(), "p"); - let param_ty: ast::Type = param.ty().unwrap(); + let param_ty: ast::FuncType = param.func_type().unwrap(); assert_eq!(param_ty.syntax().to_string(), "int8"); let func_option_list: ast::FuncOptionList = func.option_list().unwrap(); diff --git a/crates/squawk_syntax/src/postgresql.ungram b/crates/squawk_syntax/src/postgresql.ungram index 0ebc6b30..7f5e19e6 100644 --- a/crates/squawk_syntax/src/postgresql.ungram +++ b/crates/squawk_syntax/src/postgresql.ungram @@ -59,7 +59,7 @@ ParamDefault = ('default' | '=') Expr Param = - mode:ParamMode? name:ParamName? Type ParamDefault? + mode:ParamMode? name:ParamName? FuncType ParamDefault? ParamName = '#name'? @@ -835,7 +835,6 @@ IntervalSecond = Type = ArrayType -| PercentType | PathType // TODO: I think we can probably simplify the AST nodes for Types & Exprs. | ExprType @@ -848,6 +847,10 @@ Type = | TimestampType | IntervalType +FuncType = + Type +| PercentType + TypeName = Path @@ -1990,7 +1993,13 @@ OrReplace = 'or' 'replace' RetType = - 'returns' ('table' TableArgList | Type) + 'returns' ('table' ReturnTableArgList | FuncType) + +ReturnTableArgList = + '(' args:(ReturnTableColumn (',' ReturnTableColumn)*)? ')' + +ReturnTableColumn = + name:ColumnName FuncType AtomicBody = 'begin' 'atomic' diff --git a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_column_list_validation.snap b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_column_list_validation.snap index f5ea952e..20521791 100644 --- a/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_column_list_validation.snap +++ b/crates/squawk_syntax/src/snapshots/squawk_syntax__test__empty_column_list_validation.snap @@ -182,9 +182,9 @@ SOURCE_FILE@0..573 WHITESPACE@270..271 " " TABLE_KW@271..276 "table" WHITESPACE@276..277 " " - TABLE_ARG_LIST@277..284 + RETURN_TABLE_ARG_LIST@277..284 L_PAREN@277..278 "(" - COLUMN@278..283 + RETURN_TABLE_COLUMN@278..283 COLUMN_NAME@278..279 IDENT@278..279 "a" WHITESPACE@279..280 " " @@ -351,7 +351,7 @@ SOURCE_FILE@0..573 WHITESPACE@535..536 " " TABLE_KW@536..541 "table" WHITESPACE@541..542 " " - TABLE_ARG_LIST@542..544 + RETURN_TABLE_ARG_LIST@542..544 L_PAREN@542..543 "(" R_PAREN@543..544 ")" WHITESPACE@544..545 " " diff --git a/crates/squawk_syntax/src/validation.rs b/crates/squawk_syntax/src/validation.rs index 61b4e220..b1c16c41 100644 --- a/crates/squawk_syntax/src/validation.rs +++ b/crates/squawk_syntax/src/validation.rs @@ -29,7 +29,7 @@ pub(crate) fn validate(root: &SyntaxNode, errors: &mut Vec) { ast::Do(it) => validate_do(it, errors), ast::FuncOptionList(it) => validate_func_option_list(it, errors), ast::FromAlias(it) => validate_non_empty_column_list(it.columns(), errors), - ast::RetType(it) => validate_non_empty_column_list(it.table_arg_list(), errors), + ast::RetType(it) => validate_non_empty_column_list(it.return_table_arg_list(), errors), ast::WithTable(it) => validate_non_empty_column_list(it.column_list(), errors), ast::PrefixExpr(it) => validate_prefix_expr(it, errors), ast::ArrayExpr(it) => validate_array_expr(it, errors),