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(|((if_not_exists, name), (columns, primary_key))| CreateNodeTable {
99 name,
100 if_not_exists,
101 columns,
102 primary_key,
103 })
104 .labelled("create node table")
105}
106
107pub fn create_rel_table() -> impl Parser<Token, CreateRelTable, Error = ParserError> + Clone {
109 let from_to = just(Token::From)
110 .ignore_then(ident().map_with_span(|n, s| (n, s)))
111 .then_ignore(just(Token::To))
112 .then(ident().map_with_span(|n, s| (n, s)));
113
114 let columns = just(Token::Comma)
115 .ignore_then(column_definition().separated_by(just(Token::Comma)))
116 .or_not()
117 .map(|c| c.unwrap_or_default());
118
119 just(Token::Create)
120 .ignore_then(just(Token::Rel))
121 .ignore_then(just(Token::Table))
122 .ignore_then(if_not_exists())
123 .then(ident().map_with_span(|n, s| (n, s)))
124 .then(
125 from_to
126 .then(columns)
127 .delimited_by(just(Token::LeftParen), just(Token::RightParen)),
128 )
129 .map(
130 |((if_not_exists, name), ((from_table, to_table), columns))| CreateRelTable {
131 name,
132 if_not_exists,
133 from_table,
134 to_table,
135 columns,
136 },
137 )
138 .labelled("create rel table")
139}
140
141pub fn drop_statement() -> impl Parser<Token, DropStatement, Error = ParserError> + Clone {
143 just(Token::Drop)
144 .ignore_then(just(Token::Table).to(DropObjectType::Table))
145 .then(if_exists())
146 .then(ident().map_with_span(|n, s| (n, s)))
147 .map(|((object_type, if_exists), name)| DropStatement {
148 object_type,
149 name,
150 if_exists,
151 })
152 .labelled("drop statement")
153}
154
155pub fn alter_table() -> impl Parser<Token, AlterTable, Error = ParserError> + Clone {
157 let add_column = just(Token::Add)
158 .ignore_then(just(Token::Column).or_not())
159 .ignore_then(column_definition())
160 .map(AlterAction::AddColumn);
161
162 let drop_column = just(Token::Drop)
163 .ignore_then(just(Token::Column).or_not())
164 .ignore_then(ident().map_with_span(|n, s| (n, s)))
165 .map(AlterAction::DropColumn);
166
167 let rename_column = just(Token::Rename)
168 .ignore_then(just(Token::Column).or_not())
169 .ignore_then(ident().map_with_span(|n, s| (n, s)))
170 .then_ignore(just(Token::To))
171 .then(ident().map_with_span(|n, s| (n, s)))
172 .map(|(old_name, new_name)| AlterAction::RenameColumn { old_name, new_name });
173
174 let rename_table = just(Token::Rename)
175 .ignore_then(just(Token::To))
176 .ignore_then(ident().map_with_span(|n, s| (n, s)))
177 .map(AlterAction::RenameTable);
178
179 let action = choice((add_column, drop_column, rename_column, rename_table));
180
181 just(Token::Alter)
182 .ignore_then(just(Token::Table))
183 .ignore_then(ident().map_with_span(|n, s| (n, s)))
184 .then(action)
185 .map(|(table_name, action)| AlterTable { table_name, action })
186 .labelled("alter table")
187}
188
189pub fn copy_from() -> impl Parser<Token, CopyFrom, Error = ParserError> + Clone {
191 let option = ident()
192 .map_with_span(|n, s| (n, s))
193 .then_ignore(just(Token::Eq).or(just(Token::Colon)))
194 .then(expression_parser());
195
196 let options = option
197 .separated_by(just(Token::Comma))
198 .delimited_by(just(Token::LeftParen), just(Token::RightParen))
199 .or_not()
200 .map(|o| o.unwrap_or_default());
201
202 just(Token::Copy)
203 .ignore_then(ident().map_with_span(|n, s| (n, s)))
204 .then_ignore(just(Token::From))
205 .then(expression_parser())
206 .then(options)
207 .map(|((table_name, source), options)| CopyFrom {
208 table_name,
209 source,
210 options,
211 })
212 .labelled("copy from")
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::lexer::Lexer;
219
220 fn parse_with<T>(
221 parser: impl Parser<Token, T, Error = ParserError>,
222 src: &str,
223 ) -> Option<T> {
224 let (toks, errors) = Lexer::new(src).lex();
225 assert!(errors.is_empty(), "lex errors: {errors:?}");
226 let len = src.len();
227 let stream = chumsky::Stream::from_iter(
228 len..len + 1,
229 toks.into_iter()
230 .filter(|(tok, _)| !matches!(tok, Token::Eof)),
231 );
232 let (result, errors) = parser.then_ignore(end()).parse_recovery(stream);
233 if !errors.is_empty() {
234 eprintln!("parse errors: {errors:?}");
235 }
236 result
237 }
238
239 #[test]
240 fn create_node_table_basic() {
241 let stmt = parse_with(
242 create_node_table(),
243 "CREATE NODE TABLE Person (name STRING, age INT64, PRIMARY KEY (name))",
244 )
245 .unwrap();
246 assert_eq!(stmt.name.0.as_str(), "Person");
247 assert_eq!(stmt.columns.len(), 2);
248 assert_eq!(stmt.primary_key.0.as_str(), "name");
249 assert!(!stmt.if_not_exists);
250 }
251
252 #[test]
253 fn create_node_table_if_not_exists() {
254 let stmt = parse_with(
255 create_node_table(),
256 "CREATE NODE TABLE IF NOT EXISTS Person (id INT64, PRIMARY KEY (id))",
257 )
258 .unwrap();
259 assert!(stmt.if_not_exists);
260 }
261
262 #[test]
263 fn create_rel_table_basic() {
264 let stmt = parse_with(
265 create_rel_table(),
266 "CREATE REL TABLE Knows (FROM Person TO Person, since INT64)",
267 )
268 .unwrap();
269 assert_eq!(stmt.name.0.as_str(), "Knows");
270 assert_eq!(stmt.from_table.0.as_str(), "Person");
271 assert_eq!(stmt.to_table.0.as_str(), "Person");
272 assert_eq!(stmt.columns.len(), 1);
273 }
274
275 #[test]
276 fn drop_table() {
277 let stmt = parse_with(drop_statement(), "DROP TABLE Person").unwrap();
278 assert_eq!(stmt.name.0.as_str(), "Person");
279 assert!(!stmt.if_exists);
280 }
281
282 #[test]
283 fn drop_table_if_exists() {
284 let stmt = parse_with(drop_statement(), "DROP TABLE IF EXISTS Person").unwrap();
285 assert!(stmt.if_exists);
286 }
287
288 #[test]
289 fn alter_add_column() {
290 let stmt = parse_with(
291 alter_table(),
292 "ALTER TABLE Person ADD email STRING",
293 )
294 .unwrap();
295 assert!(matches!(stmt.action, AlterAction::AddColumn(_)));
296 }
297
298 #[test]
299 fn alter_drop_column() {
300 let stmt = parse_with(alter_table(), "ALTER TABLE Person DROP age").unwrap();
301 assert!(matches!(stmt.action, AlterAction::DropColumn(_)));
302 }
303
304 #[test]
305 fn alter_rename_column() {
306 let stmt = parse_with(
307 alter_table(),
308 "ALTER TABLE Person RENAME name TO full_name",
309 )
310 .unwrap();
311 assert!(matches!(
312 stmt.action,
313 AlterAction::RenameColumn { .. }
314 ));
315 }
316
317 #[test]
318 fn copy_from_basic() {
319 let stmt = parse_with(
320 copy_from(),
321 "COPY Person FROM 'persons.csv'",
322 )
323 .unwrap();
324 assert_eq!(stmt.table_name.0.as_str(), "Person");
325 }
326}