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
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::lexer::Lexer;
221
222 fn parse_with<T>(parser: impl Parser<Token, T, Error = ParserError>, src: &str) -> Option<T> {
223 let (toks, errors) = Lexer::new(src).lex();
224 assert!(errors.is_empty(), "lex errors: {errors:?}");
225 let len = src.len();
226 let stream = chumsky::Stream::from_iter(
227 len..len + 1,
228 toks.into_iter()
229 .filter(|(tok, _)| !matches!(tok, Token::Eof)),
230 );
231 let (result, errors) = parser.then_ignore(end()).parse_recovery(stream);
232 if !errors.is_empty() {
233 eprintln!("parse errors: {errors:?}");
234 }
235 result
236 }
237
238 #[test]
239 fn create_node_table_basic() {
240 let stmt = parse_with(
241 create_node_table(),
242 "CREATE NODE TABLE Person (name STRING, age INT64, PRIMARY KEY (name))",
243 )
244 .unwrap();
245 assert_eq!(stmt.name.0.as_str(), "Person");
246 assert_eq!(stmt.columns.len(), 2);
247 assert_eq!(stmt.primary_key.0.as_str(), "name");
248 assert!(!stmt.if_not_exists);
249 }
250
251 #[test]
252 fn create_node_table_if_not_exists() {
253 let stmt = parse_with(
254 create_node_table(),
255 "CREATE NODE TABLE IF NOT EXISTS Person (id INT64, PRIMARY KEY (id))",
256 )
257 .unwrap();
258 assert!(stmt.if_not_exists);
259 }
260
261 #[test]
262 fn create_rel_table_basic() {
263 let stmt = parse_with(
264 create_rel_table(),
265 "CREATE REL TABLE Knows (FROM Person TO Person, since INT64)",
266 )
267 .unwrap();
268 assert_eq!(stmt.name.0.as_str(), "Knows");
269 assert_eq!(stmt.from_table.0.as_str(), "Person");
270 assert_eq!(stmt.to_table.0.as_str(), "Person");
271 assert_eq!(stmt.columns.len(), 1);
272 }
273
274 #[test]
275 fn drop_table() {
276 let stmt = parse_with(drop_statement(), "DROP TABLE Person").unwrap();
277 assert_eq!(stmt.name.0.as_str(), "Person");
278 assert!(!stmt.if_exists);
279 }
280
281 #[test]
282 fn drop_table_if_exists() {
283 let stmt = parse_with(drop_statement(), "DROP TABLE IF EXISTS Person").unwrap();
284 assert!(stmt.if_exists);
285 }
286
287 #[test]
288 fn alter_add_column() {
289 let stmt = parse_with(alter_table(), "ALTER TABLE Person ADD email STRING").unwrap();
290 assert!(matches!(stmt.action, AlterAction::AddColumn(_)));
291 }
292
293 #[test]
294 fn alter_drop_column() {
295 let stmt = parse_with(alter_table(), "ALTER TABLE Person DROP age").unwrap();
296 assert!(matches!(stmt.action, AlterAction::DropColumn(_)));
297 }
298
299 #[test]
300 fn alter_rename_column() {
301 let stmt =
302 parse_with(alter_table(), "ALTER TABLE Person RENAME name TO full_name").unwrap();
303 assert!(matches!(stmt.action, AlterAction::RenameColumn { .. }));
304 }
305
306 #[test]
307 fn copy_from_basic() {
308 let stmt = parse_with(copy_from(), "COPY Person FROM 'persons.csv'").unwrap();
309 assert_eq!(stmt.table_name.0.as_str(), "Person");
310 }
311}