Skip to content

Commit 76692bb

Browse files
TimelordUKclaude
andcommitted
feat: Add UNNEST foundation - AST, lexer, and formatter support
Foundation work for UNNEST row expansion feature to handle FIX repeated groups and other delimited string splitting with row multiplication. Changes: - Add Unnest variant to SqlExpression enum in AST - Stores column expression and delimiter - Documented as row expansion function - Add UNNEST keyword token to lexer - Recognized in both keyword mappings - Update formatters to handle Unnest expressions - AST formatter shows Unnest structure - SQL formatter outputs UNNEST(column, 'delimiter') - Add UNNEST to text navigation token formatting Architecture: - UNNEST will be an expression that causes row multiplication - Each row with UNNEST expression splits into N rows - NULL padding for mismatched lengths across multiple UNNEST calls - Use case: Expanding FIX allocations like "ACC_1|ACC_2|ACC_3" Next steps: Parser implementation, evaluator, and query executor All tests passing (397 passed) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f442f01 commit 76692bb

4 files changed

Lines changed: 26 additions & 1 deletion

File tree

src/sql/parser/ast.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,19 @@ pub enum SqlExpression {
178178
ScalarSubquery {
179179
query: Box<SelectStatement>,
180180
},
181-
/// IN subquery that returns multiple values
181+
/// IN subquery that returns multiple values
182182
/// Used in expressions like: WHERE col IN (SELECT id FROM table WHERE ...)
183183
InSubquery {
184184
expr: Box<SqlExpression>,
185185
subquery: Box<SelectStatement>,
186186
},
187+
/// UNNEST - Row expansion function that splits delimited strings
188+
/// Used like: SELECT UNNEST(accounts, '|') AS account FROM fix_trades
189+
/// Causes row multiplication - one input row becomes N output rows
190+
Unnest {
191+
column: Box<SqlExpression>,
192+
delimiter: String,
193+
},
187194
/// NOT IN subquery
188195
/// Used in expressions like: WHERE col NOT IN (SELECT id FROM table WHERE ...)
189196
NotInSubquery {

src/sql/parser/formatter.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,13 @@ pub fn format_expression_ast(expr: &SqlExpression) -> String {
318318
format_expression_ast(expr)
319319
)
320320
}
321+
SqlExpression::Unnest { column, delimiter } => {
322+
format!(
323+
"Unnest {{ column: {}, delimiter: \"{}\" }}",
324+
format_expression_ast(column),
325+
delimiter
326+
)
327+
}
321328
}
322329
}
323330

@@ -817,6 +824,9 @@ pub fn format_expression(expr: &SqlExpression) -> String {
817824
SqlExpression::NotInSubquery { expr, subquery: _ } => {
818825
format!("{} NOT IN (SELECT ...)", format_expression(expr))
819826
}
827+
SqlExpression::Unnest { column, delimiter } => {
828+
format!("UNNEST({}, '{}')", format_expression(column), delimiter)
829+
}
820830
}
821831
}
822832

src/sql/parser/lexer.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ pub enum Token {
5454
// Special CTE keyword
5555
Web, // WEB (for WEB CTEs)
5656

57+
// Row expansion functions
58+
Unnest, // UNNEST (for expanding delimited strings into rows)
59+
5760
// JOIN keywords
5861
Join, // JOIN keyword
5962
Inner, // INNER JOIN
@@ -142,6 +145,7 @@ impl Token {
142145
"INTERSECT" => Some(Token::Intersect),
143146
"EXCEPT" => Some(Token::Except),
144147
"WEB" => Some(Token::Web),
148+
"UNNEST" => Some(Token::Unnest),
145149
"JOIN" => Some(Token::Join),
146150
"INNER" => Some(Token::Inner),
147151
"LEFT" => Some(Token::Left),
@@ -575,6 +579,8 @@ impl Lexer {
575579
"EXCEPT" => Token::Except,
576580
// Special CTE keyword
577581
"WEB" => Token::Web,
582+
// Row expansion functions
583+
"UNNEST" => Token::Unnest,
578584
// JOIN keywords
579585
"JOIN" => Token::Join,
580586
"INNER" => Token::Inner,

src/text_navigation.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ impl TextNavigator {
205205
Token::Except => "EXCEPT",
206206
// Special CTE keyword
207207
Token::Web => "WEB",
208+
// Row expansion functions
209+
Token::Unnest => "UNNEST",
208210
Token::Identifier(s) => s,
209211
Token::QuotedIdentifier(s) => s,
210212
Token::StringLiteral(s) => s,

0 commit comments

Comments
 (0)