1use chumsky::prelude::*;
4use smol_str::SmolStr;
5
6use crate::ast::*;
7use crate::span::Spanned;
8use crate::token::Token;
9
10use super::expression::expression_parser;
11use super::pattern::ident;
12
13type ParserError = Simple<Token>;
14
15fn type_name() -> impl Parser<Token, Spanned<SmolStr>, Error = ParserError> + Clone {
17 select! {
18 Token::BoolType => SmolStr::new("BOOL"),
19 Token::Int8Type => SmolStr::new("INT8"),
20 Token::Int16Type => SmolStr::new("INT16"),
21 Token::Int32Type => SmolStr::new("INT32"),
22 Token::Int64Type => SmolStr::new("INT64"),
23 Token::Int128Type => SmolStr::new("INT128"),
24 Token::UInt8Type => SmolStr::new("UINT8"),
25 Token::UInt16Type => SmolStr::new("UINT16"),
26 Token::UInt32Type => SmolStr::new("UINT32"),
27 Token::UInt64Type => SmolStr::new("UINT64"),
28 Token::FloatType => SmolStr::new("FLOAT"),
29 Token::DoubleType => SmolStr::new("DOUBLE"),
30 Token::StringType => SmolStr::new("STRING"),
31 Token::DateType => SmolStr::new("DATE"),
32 Token::TimestampType => SmolStr::new("TIMESTAMP"),
33 Token::IntervalType => SmolStr::new("INTERVAL"),
34 Token::BlobType => SmolStr::new("BLOB"),
35 Token::UuidType => SmolStr::new("UUID"),
36 Token::SerialType => SmolStr::new("SERIAL"),
37 Token::Ident(name) => name,
38 }
39 .map_with_span(|n, s| (n, s))
40}
41
42fn column_definition() -> impl Parser<Token, ColumnDefinition, Error = ParserError> + Clone {
44 let name = ident().map_with_span(|n, s| (n, s));
45 let data_type = type_name();
46 let default = just(Token::Default)
47 .ignore_then(expression_parser())
48 .or_not();
49
50 name.then(data_type)
51 .then(default)
52 .map(|((name, data_type), default_value)| ColumnDefinition {
53 name,
54 data_type,
55 default_value,
56 })
57}
58
59fn if_not_exists() -> impl Parser<Token, bool, Error = ParserError> + Clone {
61 just(Token::If)
62 .then_ignore(just(Token::Not))
63 .then_ignore(just(Token::Exists))
64 .or_not()
65 .map(|o| o.is_some())
66}
67
68fn if_exists() -> impl Parser<Token, bool, Error = ParserError> + Clone {
70 just(Token::If)
71 .then_ignore(just(Token::Exists))
72 .or_not()
73 .map(|o| o.is_some())
74}
75
76pub fn create_node_table() -> impl Parser<Token, CreateNodeTable, Error = ParserError> + Clone {
78 let primary_key = just(Token::Primary)
79 .ignore_then(just(Token::Key))
80 .ignore_then(
81 ident()
82 .map_with_span(|n, s| (n, s))
83 .delimited_by(just(Token::LeftParen), just(Token::RightParen)),
84 );
85
86 just(Token::Create)
87 .ignore_then(just(Token::Node))
88 .ignore_then(just(Token::Table))
89 .ignore_then(if_not_exists())
90 .then(ident().map_with_span(|n, s| (n, s)))
91 .then(
92 column_definition()
93 .separated_by(just(Token::Comma))
94 .then_ignore(just(Token::Comma).or_not())
95 .then(primary_key)
96 .delimited_by(just(Token::LeftParen), just(Token::RightParen)),
97 )
98 .map(
99 |((if_not_exists, name), (columns, primary_key))| CreateNodeTable {
100 name,
101 if_not_exists,
102 columns,
103 primary_key,
104 },
105 )
106 .labelled("create node table")
107}
108
109pub fn create_rel_table() -> impl Parser<Token, CreateRelTable, Error = ParserError> + Clone {
111 let from_to = just(Token::From)
112 .ignore_then(ident().map_with_span(|n, s| (n, s)))
113 .then_ignore(just(Token::To))
114 .then(ident().map_with_span(|n, s| (n, s)));
115
116 let columns = just(Token::Comma)
117 .ignore_then(column_definition().separated_by(just(Token::Comma)))
118 .or_not()
119 .map(|c| c.unwrap_or_default());
120
121 just(Token::Create)
122 .ignore_then(just(Token::Rel))
123 .ignore_then(just(Token::Table))
124 .ignore_then(if_not_exists())
125 .then(ident().map_with_span(|n, s| (n, s)))
126 .then(
127 from_to
128 .then(columns)
129 .delimited_by(just(Token::LeftParen), just(Token::RightParen)),
130 )
131 .map(
132 |((if_not_exists, name), ((from_table, to_table), columns))| CreateRelTable {
133 name,
134 if_not_exists,
135 from_table,
136 to_table,
137 columns,
138 },
139 )
140 .labelled("create rel table")
141}
142
143pub fn drop_statement() -> impl Parser<Token, DropStatement, Error = ParserError> + Clone {
145 just(Token::Drop)
146 .ignore_then(just(Token::Table).to(DropObjectType::Table))
147 .then(if_exists())
148 .then(ident().map_with_span(|n, s| (n, s)))
149 .map(|((object_type, if_exists), name)| DropStatement {
150 object_type,
151 name,
152 if_exists,
153 })
154 .labelled("drop statement")
155}
156
157pub fn alter_table() -> impl Parser<Token, AlterTable, Error = ParserError> + Clone {
159 let add_column = just(Token::Add)
160 .ignore_then(just(Token::Column).or_not())
161 .ignore_then(column_definition())
162 .map(AlterAction::AddColumn);
163
164 let drop_column = just(Token::Drop)
165 .ignore_then(just(Token::Column).or_not())
166 .ignore_then(ident().map_with_span(|n, s| (n, s)))
167 .map(AlterAction::DropColumn);
168
169 let rename_column = just(Token::Rename)
170 .ignore_then(just(Token::Column).or_not())
171 .ignore_then(ident().map_with_span(|n, s| (n, s)))
172 .then_ignore(just(Token::To))
173 .then(ident().map_with_span(|n, s| (n, s)))
174 .map(|(old_name, new_name)| AlterAction::RenameColumn { old_name, new_name });
175
176 let rename_table = just(Token::Rename)
177 .ignore_then(just(Token::To))
178 .ignore_then(ident().map_with_span(|n, s| (n, s)))
179 .map(AlterAction::RenameTable);
180
181 let action = choice((add_column, drop_column, rename_column, rename_table));
182
183 just(Token::Alter)
184 .ignore_then(just(Token::Table))
185 .ignore_then(ident().map_with_span(|n, s| (n, s)))
186 .then(action)
187 .map(|(table_name, action)| AlterTable { table_name, action })
188 .labelled("alter table")
189}
190
191pub fn copy_from() -> impl Parser<Token, CopyFrom, Error = ParserError> + Clone {
193 let option = ident()
194 .map_with_span(|n, s| (n, s))
195 .then_ignore(just(Token::Eq).or(just(Token::Colon)))
196 .then(expression_parser());
197
198 let options = option
199 .separated_by(just(Token::Comma))
200 .delimited_by(just(Token::LeftParen), just(Token::RightParen))
201 .or_not()
202 .map(|o| o.unwrap_or_default());
203
204 just(Token::Copy)
205 .ignore_then(ident().map_with_span(|n, s| (n, s)))
206 .then_ignore(just(Token::From))
207 .then(expression_parser())
208 .then(options)
209 .map(|((table_name, source), options)| CopyFrom {
210 table_name,
211 source,
212 options,
213 })
214 .labelled("copy from")
215}
216
217pub fn load_from() -> impl Parser<Token, LoadFrom, Error = ParserError> + Clone {
221 just(Token::Load)
222 .ignore_then(just(Token::From))
223 .ignore_then(expression_parser())
224 .map(|source| LoadFrom { source })
225 .labelled("load from")
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::lexer::Lexer;
232
233 fn parse_with<T>(parser: impl Parser<Token, T, Error = ParserError>, src: &str) -> Option<T> {
234 let (toks, errors) = Lexer::new(src).lex();
235 assert!(errors.is_empty(), "lex errors: {errors:?}");
236 let len = src.len();
237 let stream = chumsky::Stream::from_iter(
238 len..len + 1,
239 toks.into_iter()
240 .filter(|(tok, _)| !matches!(tok, Token::Eof)),
241 );
242 let (result, errors) = parser.then_ignore(end()).parse_recovery(stream);
243 if !errors.is_empty() {
244 eprintln!("parse errors: {errors:?}");
245 }
246 result
247 }
248
249 #[test]
250 fn create_node_table_basic() {
251 let stmt = parse_with(
252 create_node_table(),
253 "CREATE NODE TABLE Person (name STRING, age INT64, PRIMARY KEY (name))",
254 )
255 .unwrap();
256 assert_eq!(stmt.name.0.as_str(), "Person");
257 assert_eq!(stmt.columns.len(), 2);
258 assert_eq!(stmt.primary_key.0.as_str(), "name");
259 assert!(!stmt.if_not_exists);
260 }
261
262 #[test]
263 fn create_node_table_if_not_exists() {
264 let stmt = parse_with(
265 create_node_table(),
266 "CREATE NODE TABLE IF NOT EXISTS Person (id INT64, PRIMARY KEY (id))",
267 )
268 .unwrap();
269 assert!(stmt.if_not_exists);
270 }
271
272 #[test]
273 fn create_rel_table_basic() {
274 let stmt = parse_with(
275 create_rel_table(),
276 "CREATE REL TABLE Knows (FROM Person TO Person, since INT64)",
277 )
278 .unwrap();
279 assert_eq!(stmt.name.0.as_str(), "Knows");
280 assert_eq!(stmt.from_table.0.as_str(), "Person");
281 assert_eq!(stmt.to_table.0.as_str(), "Person");
282 assert_eq!(stmt.columns.len(), 1);
283 }
284
285 #[test]
286 fn drop_table() {
287 let stmt = parse_with(drop_statement(), "DROP TABLE Person").unwrap();
288 assert_eq!(stmt.name.0.as_str(), "Person");
289 assert!(!stmt.if_exists);
290 }
291
292 #[test]
293 fn drop_table_if_exists() {
294 let stmt = parse_with(drop_statement(), "DROP TABLE IF EXISTS Person").unwrap();
295 assert!(stmt.if_exists);
296 }
297
298 #[test]
299 fn alter_add_column() {
300 let stmt = parse_with(alter_table(), "ALTER TABLE Person ADD email STRING").unwrap();
301 assert!(matches!(stmt.action, AlterAction::AddColumn(_)));
302 }
303
304 #[test]
305 fn alter_drop_column() {
306 let stmt = parse_with(alter_table(), "ALTER TABLE Person DROP age").unwrap();
307 assert!(matches!(stmt.action, AlterAction::DropColumn(_)));
308 }
309
310 #[test]
311 fn alter_rename_column() {
312 let stmt =
313 parse_with(alter_table(), "ALTER TABLE Person RENAME name TO full_name").unwrap();
314 assert!(matches!(stmt.action, AlterAction::RenameColumn { .. }));
315 }
316
317 #[test]
318 fn copy_from_basic() {
319 let stmt = parse_with(copy_from(), "COPY Person FROM 'persons.csv'").unwrap();
320 assert_eq!(stmt.table_name.0.as_str(), "Person");
321 }
322}