qail_core/parser/
columns.rs

1use nom::{
2    branch::alt,
3    bytes::complete::{tag, take_until, take_while1},
4    character::complete::{char, multispace0},
5    combinator::{map, opt, value},
6    multi::{many0, separated_list1},
7    sequence::{delimited, pair, preceded, tuple},
8    IResult,
9};
10
11use crate::ast::*;
12use super::tokens::*;
13
14/// Parse columns using the label syntax ('col).
15pub fn parse_columns(input: &str) -> IResult<&str, Vec<Column>> {
16    many0(preceded(ws_or_comment, parse_any_column))(input)
17}
18
19fn parse_any_column(input: &str) -> IResult<&str, Column> {
20    alt((
21        // Label: 'col...
22        preceded(char('\''), parse_label_column),
23        // v0.8.0: Allow bare identifiers (e.g. drop::users:password)
24        parse_column_full_def_or_named,
25    ))(input)
26}
27
28/// Parse a column with the label syntax ('col).
29fn parse_label_column(input: &str) -> IResult<&str, Column> {
30    alt((
31        // Wildcard: '_ for all columns
32        value(Column::Star, char('_')),
33        // Named or complex column
34        parse_column_full_def_or_named,
35    ))(input)
36}
37
38fn parse_column_full_def_or_named(input: &str) -> IResult<&str, Column> {
39    // 1. Parse Name
40    let (input, name) = parse_identifier(input)?;
41    
42    // 2. Opt: Aggregates (#func)
43    if let Ok((input, Some(func))) = opt(preceded(char('#'), parse_agg_func))(input) {
44        return Ok((input, Column::Aggregate {
45             col: name.to_string(),
46             func
47        }));
48    }
49    
50    // 3. Opt: check for colon (type definition)
51    if let Ok((input, _)) = char::<_, nom::error::Error<&str>>(':')(input) {
52        // We have a type OR a window function.
53        let (input, type_or_func) = parse_identifier(input)?;
54        
55        let (input, _) = ws_or_comment(input)?;
56        
57        // Peek/Check for open paren `(` for window function
58        if let Ok((input, _)) = char::<_, nom::error::Error<&str>>('(')(input) {
59            // It IS a function call -> Window Column
60            let (input, _) = ws_or_comment(input)?;
61            let (input, args) = opt(tuple((
62                parse_value,
63                many0(preceded(
64                    tuple((ws_or_comment, char(','), ws_or_comment)),
65                    parse_value
66                ))
67            )))(input)?;
68            let (input, _) = ws_or_comment(input)?;
69            let (input, _) = char(')')(input)?;
70            
71            let params = match args {
72                Some((first, mut rest)) => {
73                    let mut v = vec![first];
74                    v.append(&mut rest);
75                    v
76                },
77                None => vec![],
78            };
79
80            // Parse Order Cages (e.g. ^!amount)
81            let (input, sorts) = many0(parse_window_sort)(input)?;
82            
83            // Parse Partition: {Part=...}
84            let (input, partitions) = opt(parse_partition_block)(input)?;
85            let partition = partitions.unwrap_or_default();
86
87            return Ok((input, Column::Window {
88                name: name.to_string(),
89                func: type_or_func.to_string(),
90                params,
91                partition,
92                order: sorts,
93            }));
94        } else {
95            // It is just a Type Definition
96            let (input, constraints) = parse_constraints(input)?;
97            
98            return Ok((input, Column::Def { 
99                name: name.to_string(), 
100                data_type: type_or_func.to_string(), 
101                constraints 
102            }));
103        }
104    }
105    
106    // No colon, check for constraints (inferred type Def)
107    let (input, constraints) = parse_constraints(input)?;
108    if !constraints.is_empty() {
109         Ok((input, Column::Def { 
110            name: name.to_string(), 
111            data_type: "str".to_string(), 
112            constraints 
113        }))
114    } else {
115        // Just a named column
116        Ok((input, Column::Named(name.to_string())))
117    }
118}
119
120fn parse_constraints(input: &str) -> IResult<&str, Vec<Constraint>> {
121    many0(alt((
122        // ^pk without parentheses (column-level PK)
123        map(
124            tuple((tag("^pk"), nom::combinator::not(char('(')))),
125            |_| Constraint::PrimaryKey
126        ),
127        // ^uniq without following 'ue(' (to avoid matching ^unique())
128        map(
129            tuple((tag("^uniq"), nom::combinator::not(tag("ue(")))),
130            |_| Constraint::Unique
131        ),
132        value(Constraint::Nullable, char('?')),
133        parse_default_constraint,
134        parse_check_constraint,
135        parse_comment_constraint,
136    )))(input)
137}
138
139/// Parse DEFAULT value constraint: `= value` or `= func()`
140fn parse_default_constraint(input: &str) -> IResult<&str, Constraint> {
141    let (input, _) = preceded(multispace0, char('='))(input)?;
142    let (input, _) = multispace0(input)?;
143    
144    // Parse function call like uuid(), now(), or literal values
145    let (input, value) = alt((
146        // Function call: name()
147        map(
148            pair(
149                take_while1(|c: char| c.is_alphanumeric() || c == '_'),
150                tag("()")
151            ),
152            |(name, parens): (&str, &str)| format!("{}{}", name, parens)
153        ),
154        // Numeric literal
155        map(
156            take_while1(|c: char| c.is_numeric() || c == '.' || c == '-'),
157            |s: &str| s.to_string()
158        ),
159        // Quoted string
160        map(
161            delimited(char('"'), take_until("\""), char('"')),
162            |s: &str| format!("'{}'", s)
163        ),
164    ))(input)?;
165    
166    Ok((input, Constraint::Default(value)))
167}
168
169/// Parse CHECK constraint: `^check("a","b","c")`
170fn parse_check_constraint(input: &str) -> IResult<&str, Constraint> {
171    let (input, _) = tag("^check(")(input)?;
172    let (input, values) = separated_list1(
173        char(','),
174        delimited(
175            multispace0,
176            delimited(char('"'), take_until("\""), char('"')),
177            multispace0
178        )
179    )(input)?;
180    let (input, _) = char(')')(input)?;
181    
182    Ok((input, Constraint::Check(values.into_iter().map(|s| s.to_string()).collect())))
183}
184
185/// Parse COMMENT constraint: `^comment("description")`
186fn parse_comment_constraint(input: &str) -> IResult<&str, Constraint> {
187    let (input, _) = tag("^comment(\"")(input)?;
188    let (input, text) = take_until("\"")(input)?;
189    let (input, _) = tag("\")")(input)?;
190    Ok((input, Constraint::Comment(text.to_string())))
191}
192
193/// Parse index columns: 'col1-col2-col3
194pub fn parse_index_columns(input: &str) -> IResult<&str, Vec<String>> {
195    let (input, _) = char('\'')(input)?;
196    let (input, first) = parse_identifier(input)?;
197    let (input, rest) = many0(preceded(char('-'), parse_identifier))(input)?;
198    
199    let mut cols = vec![first.to_string()];
200    cols.extend(rest.iter().map(|s| s.to_string()));
201    Ok((input, cols))
202}
203
204/// Parse table-level constraints: ^unique(col1, col2) or ^pk(col1, col2)
205pub fn parse_table_constraints(input: &str) -> IResult<&str, Vec<TableConstraint>> {
206    many0(alt((
207        parse_table_unique,
208        parse_table_pk,
209    )))(input)
210}
211
212/// Parse ^unique(col1, col2)
213fn parse_table_unique(input: &str) -> IResult<&str, TableConstraint> {
214    let (input, _) = tag("^unique(")(input)?;
215    let (input, cols) = parse_constraint_columns(input)?;
216    let (input, _) = char(')')(input)?;
217    Ok((input, TableConstraint::Unique(cols)))
218}
219
220/// Parse ^pk(col1, col2)
221fn parse_table_pk(input: &str) -> IResult<&str, TableConstraint> {
222    let (input, _) = tag("^pk(")(input)?;
223    let (input, cols) = parse_constraint_columns(input)?;
224    let (input, _) = char(')')(input)?;
225    Ok((input, TableConstraint::PrimaryKey(cols)))
226}
227
228/// Parse comma-separated column names: col1, col2, col3
229fn parse_constraint_columns(input: &str) -> IResult<&str, Vec<String>> {
230    let (input, _) = multispace0(input)?;
231    let (input, first) = parse_identifier(input)?;
232    let (input, rest) = many0(preceded(
233        tuple((multispace0, char(','), multispace0)),
234        parse_identifier
235    ))(input)?;
236    let (input, _) = multispace0(input)?;
237    
238    let mut cols = vec![first.to_string()];
239    cols.extend(rest.iter().map(|s| s.to_string()));
240    Ok((input, cols))
241}
242
243fn parse_agg_func(input: &str) -> IResult<&str, AggregateFunc> {
244    alt((
245        value(AggregateFunc::Count, tag("count")),
246        value(AggregateFunc::Sum, tag("sum")),
247        value(AggregateFunc::Avg, tag("avg")),
248        value(AggregateFunc::Min, tag("min")),
249        value(AggregateFunc::Max, tag("max")),
250    ))(input)
251}
252
253fn parse_partition_block(input: &str) -> IResult<&str, Vec<String>> {
254    let (input, _) = char('{')(input)?;
255    let (input, _) = ws_or_comment(input)?;
256    let (input, _) = tag("Part")(input)?;
257    let (input, _) = ws_or_comment(input)?;
258    let (input, _) = char('=')(input)?;
259    let (input, _) = ws_or_comment(input)?;
260    
261    let (input, first) = parse_identifier(input)?;
262    let (input, rest) = many0(preceded(
263        tuple((ws_or_comment, char(','), ws_or_comment)),
264        parse_identifier
265    ))(input)?;
266    
267    let (input, _) = ws_or_comment(input)?;
268    let (input, _) = char('}')(input)?;
269    
270    let mut cols = vec![first.to_string()];
271    cols.append(&mut rest.iter().map(|s| s.to_string()).collect());
272    Ok((input, cols))
273}
274
275/// Parse sort cage [^col] or [^!col] for window functions.
276fn parse_window_sort(input: &str) -> IResult<&str, Cage> {
277    let (input, _) = char('^')(input)?;
278    let (input, desc) = opt(char('!'))(input)?;
279    let (input, col) = parse_identifier(input)?;
280    
281    let order = if desc.is_some() {
282        SortOrder::Desc
283    } else {
284        SortOrder::Asc
285    };
286    
287    Ok((
288        input,
289        Cage {
290            kind: CageKind::Sort(order),
291            conditions: vec![Condition {
292                column: col.to_string(),
293                op: Operator::Eq,
294                value: Value::Null,
295                is_array_unnest: false,
296            }],
297            logical_op: LogicalOp::And,
298        },
299    ))
300}