diff --git a/tree-sitter-ggsql/grammar.js b/tree-sitter-ggsql/grammar.js index 10067b54..ef793a21 100644 --- a/tree-sitter-ggsql/grammar.js +++ b/tree-sitter-ggsql/grammar.js @@ -60,6 +60,7 @@ module.exports = grammar({ select_body: $ => prec.left(repeat1(choice( $.from_clause, $.window_function, // Window functions like ROW_NUMBER() OVER (...) + $.cast_expression, // CAST(expr AS type), TRY_CAST(expr AS type) $.function_call, // Regular function calls like COUNT(), SUM() $.sql_keyword, $.string, @@ -175,6 +176,7 @@ module.exports = grammar({ // Token-by-token fallback for any other subquery content subquery_body: $ => repeat1(choice( $.window_function, + $.cast_expression, $.function_call, $.sql_keyword, $.string, @@ -185,6 +187,23 @@ module.exports = grammar({ token(/[^\s;(),'\"]+/) )), + // CAST/TRY_CAST expression: CAST(expr AS type) or TRY_CAST(expr AS type) + // Higher precedence than function_call to win over treating CAST as a regular function + cast_expression: $ => prec(3, seq( + choice(caseInsensitive('CAST'), caseInsensitive('TRY_CAST')), + '(', + $.positional_arg, + caseInsensitive('AS'), + $.type_name, + ')' + )), + + // Type name for CAST expressions: DATE, VARCHAR, DECIMAL(10,2), etc. + type_name: $ => seq( + $.identifier, + optional(seq('(', $.number, optional(seq(',', $.number)), ')')) + ), + // Function call with parentheses (can be empty like ROW_NUMBER()) // Used in window functions and general SQL function_call: $ => prec(2, seq( @@ -288,6 +307,8 @@ module.exports = grammar({ $.number, $.string, '*', + // CAST/TRY_CAST expression + $.cast_expression, // Nested function call $.function_call, // Arithmetic/comparison expression (binary operators) diff --git a/tree-sitter-ggsql/test/corpus/basic.txt b/tree-sitter-ggsql/test/corpus/basic.txt index 8593b445..a2d14b9c 100644 --- a/tree-sitter-ggsql/test/corpus/basic.txt +++ b/tree-sitter-ggsql/test/corpus/basic.txt @@ -2240,3 +2240,99 @@ SCALE DISCRETE x RENAMING 'A' => 'Alpha', * => 'Category {}' value: (string)) (renaming_assignment value: (string))))))) + +================================================================================ +CAST expression +================================================================================ + +SELECT CAST(sale_date AS DATE) as period FROM sales VISUALISE period AS x DRAW point + +-------------------------------------------------------------------------------- + +(query + (sql_portion + (sql_statement + (select_statement + (select_body + (cast_expression + (positional_arg + (qualified_name + (identifier + (bare_identifier)))) + (type_name + (identifier + (bare_identifier)))) + (identifier + (bare_identifier)) + (identifier + (bare_identifier)) + (from_clause + (table_ref + table: (qualified_name + (identifier + (bare_identifier))))))))) + (visualise_statement + (visualise_keyword) + (global_mapping + (mapping_list + (mapping_element + (explicit_mapping + value: (mapping_value + (column_reference + (identifier + (bare_identifier)))) + name: (aesthetic_name))))) + (viz_clause + (draw_clause + (geom_type))))) + +================================================================================ +TRY_CAST nested in function argument +================================================================================ + +SELECT SUM(TRY_CAST(price AS INTEGER)) as total FROM data VISUALISE DRAW bar MAPPING x AS x + +-------------------------------------------------------------------------------- + +(query + (sql_portion + (sql_statement + (select_statement + (select_body + (function_call + (identifier + (bare_identifier)) + (function_args + (function_arg + (positional_arg + (cast_expression + (positional_arg + (qualified_name + (identifier + (bare_identifier)))) + (type_name + (identifier + (bare_identifier)))))))) + (identifier + (bare_identifier)) + (identifier + (bare_identifier)) + (from_clause + (table_ref + table: (qualified_name + (identifier + (bare_identifier))))))))) + (visualise_statement + (visualise_keyword) + (viz_clause + (draw_clause + (geom_type) + (mapping_clause + (mapping_list + (mapping_element + (explicit_mapping + value: (mapping_value + (column_reference + (identifier + (bare_identifier)))) + name: (aesthetic_name)))))))))