qail_core/parser/grammar/
ddl.rs

1use super::base::parse_identifier;
2use crate::ast::*;
3use nom::{
4    IResult, Parser,
5    branch::alt,
6    bytes::complete::{tag_no_case, take_while1},
7    character::complete::{char, multispace0, multispace1},
8    combinator::{map, opt, recognize, value},
9    multi::{many0, separated_list1},
10    sequence::{delimited, preceded},
11};
12
13/// Parse CREATE TABLE: make users id:uuid:pk, name:varchar:notnull
14pub fn parse_create_table<'a>(input: &'a str, table: &str) -> IResult<&'a str, Qail> {
15    let (input, columns) = separated_list1(
16        (multispace0, char(','), multispace0),
17        parse_column_definition,
18    )
19    .parse(input)?;
20
21    let (input, _) = multispace0(input)?;
22    let (input, table_constraints) = many0(parse_table_constraint).parse(input)?;
23
24    Ok((
25        input,
26        Qail {
27            action: Action::Make,
28            table: table.to_string(),
29            columns,
30            joins: vec![],
31            cages: vec![],
32            distinct: false,
33            distinct_on: vec![],
34            index_def: None,
35            table_constraints,
36            set_ops: vec![],
37            having: vec![],
38            group_by_mode: GroupByMode::default(),
39            ctes: vec![],
40            returning: None,
41            on_conflict: None,
42            source_query: None,
43            channel: None,
44            payload: None,
45            savepoint_name: None,
46            from_tables: vec![],
47            using_tables: vec![],
48            lock_mode: None,
49            fetch: None,
50            default_values: false,
51            overriding: None,
52            sample: None,
53            only_table: false,
54            vector: None,
55            score_threshold: None,
56            vector_name: None,
57            with_vector: false,
58            vector_size: None,
59            distance: None,
60            on_disk: None,
61            function_def: None,
62            trigger_def: None,
63            raw_value: None,
64            redis_ttl: None,
65            redis_set_condition: None,
66        },
67    ))
68}
69
70/// Parse table constraint: primary key (col1, col2) or unique (col1, col2)
71pub fn parse_table_constraint(input: &str) -> IResult<&str, TableConstraint> {
72    let (input, _) = multispace0(input)?;
73
74    alt((
75        // primary key (col1, col2)
76        map(
77            (
78                tag_no_case("primary"),
79                multispace1,
80                tag_no_case("key"),
81                multispace0,
82                delimited(
83                    char('('),
84                    separated_list1((multispace0, char(','), multispace0), parse_identifier),
85                    char(')'),
86                ),
87            ),
88            |(_, _, _, _, cols): (_, _, _, _, Vec<&str>)| {
89                TableConstraint::PrimaryKey(cols.iter().map(|s| s.to_string()).collect())
90            },
91        ),
92        // unique (col1, col2)
93        map(
94            (
95                tag_no_case("unique"),
96                multispace0,
97                delimited(
98                    char('('),
99                    separated_list1((multispace0, char(','), multispace0), parse_identifier),
100                    char(')'),
101                ),
102            ),
103            |(_, _, cols): (_, _, Vec<&str>)| {
104                TableConstraint::Unique(cols.iter().map(|s| s.to_string()).collect())
105            },
106        ),
107    ))
108    .parse(input)
109}
110
111/// Parse column definition: name:type[:constraint1[:constraint2]]
112pub fn parse_column_definition(input: &str) -> IResult<&str, Expr> {
113    let (input, name) = take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)?;
114    let (input, _) = char(':').parse(input)?;
115
116    let (input, data_type) = take_while1(|c: char| c.is_alphanumeric() || c == '_').parse(input)?;
117
118    let (input, constraints) = many0(preceded(char(':'), parse_constraint)).parse(input)?;
119
120    Ok((
121        input,
122        Expr::Def {
123            name: name.to_string(),
124            data_type: data_type.to_string(),
125            constraints,
126        },
127    ))
128}
129
130/// Parse column constraint: pk, unique, notnull, default=value, check=expr
131pub fn parse_constraint(input: &str) -> IResult<&str, Constraint> {
132    alt((
133        // Primary key
134        value(Constraint::PrimaryKey, tag_no_case("pk")),
135        value(Constraint::PrimaryKey, tag_no_case("primarykey")),
136        // Unique
137        value(Constraint::Unique, tag_no_case("unique")),
138        value(Constraint::Unique, tag_no_case("uniq")),
139        // Not null (opposite of nullable)
140        value(Constraint::Nullable, tag_no_case("notnull")),
141        value(Constraint::Nullable, tag_no_case("nn")),
142        // Default value: default=uuid() or default=0
143        map(
144            preceded(
145                alt((tag_no_case("default="), tag_no_case("def="))),
146                recognize(take_while1(|c: char| c != ',' && c != ':' && c != ' ')),
147            ),
148            |val: &str| Constraint::Default(val.to_string()),
149        ),
150        map(
151            preceded(
152                tag_no_case("check="),
153                recognize(take_while1(|c: char| c != ',' && c != ':' && c != ' ')),
154            ),
155            |expr: &str| Constraint::Check(vec![expr.to_string()]),
156        ),
157    ))
158    .parse(input)
159}
160
161/// Parse CREATE INDEX: index idx_name on table_name col1, col2 [unique]
162pub fn parse_create_index(input: &str) -> IResult<&str, Qail> {
163    let (input, _) = tag_no_case("index").parse(input)?;
164    let (input, _) = multispace1(input)?;
165
166    let (input, index_name) = parse_identifier(input)?;
167    let (input, _) = multispace1(input)?;
168
169    let (input, _) = tag_no_case("on").parse(input)?;
170    let (input, _) = multispace1(input)?;
171
172    let (input, table_name) = parse_identifier(input)?;
173    let (input, _) = multispace1(input)?;
174
175    let (input, columns) =
176        separated_list1((multispace0, char(','), multispace0), parse_identifier).parse(input)?;
177    let (input, _) = multispace0(input)?;
178
179    let (input, unique) = opt(tag_no_case("unique")).parse(input)?;
180
181    Ok((
182        input,
183        Qail {
184            action: Action::Index,
185            table: String::new(),
186            columns: vec![],
187            joins: vec![],
188            cages: vec![],
189            distinct: false,
190            distinct_on: vec![],
191            index_def: Some(IndexDef {
192                name: index_name.to_string(),
193                table: table_name.to_string(),
194                columns: columns.iter().map(|s| s.to_string()).collect(),
195                unique: unique.is_some(),
196                index_type: None,
197            }),
198            table_constraints: vec![],
199            set_ops: vec![],
200            having: vec![],
201            group_by_mode: GroupByMode::default(),
202            ctes: vec![],
203            returning: None,
204            on_conflict: None,
205            source_query: None,
206            channel: None,
207            payload: None,
208            savepoint_name: None,
209            from_tables: vec![],
210            using_tables: vec![],
211            lock_mode: None,
212            fetch: None,
213            default_values: false,
214            overriding: None,
215            sample: None,
216            only_table: false,
217            vector: None,
218            score_threshold: None,
219            vector_name: None,
220            with_vector: false,
221            vector_size: None,
222            distance: None,
223            on_disk: None,
224            function_def: None,
225            trigger_def: None,
226            raw_value: None,
227            redis_ttl: None,
228            redis_set_condition: None,
229        },
230    ))
231}