1pub mod ast;
28pub mod body;
29pub mod column_name;
30pub mod decoded_text;
31mod generated;
32mod parsing;
33pub mod plpgsql;
34mod ptr;
35pub mod quote;
36pub mod sql_body;
37pub mod syntax_error;
38mod syntax_node;
39mod token_text;
40pub mod unescape;
41mod validation;
42
43#[cfg(test)]
44mod test;
45
46use std::{marker::PhantomData, sync::Arc};
47
48pub use squawk_parser::SyntaxKind;
49
50use ast::AstNode;
51pub use ptr::{AstPtr, SyntaxNodePtr};
52use rowan::GreenNode;
53use syntax_error::SyntaxError;
54pub use syntax_node::{SyntaxElement, SyntaxNode, SyntaxToken};
55pub use token_text::TokenText;
56
57#[derive(Debug, PartialEq, Eq)]
63pub struct Parse<T> {
64 green: GreenNode,
65 errors: Option<Arc<[SyntaxError]>>,
66 _ty: PhantomData<fn() -> T>,
67}
68
69impl<T> Clone for Parse<T> {
70 fn clone(&self) -> Parse<T> {
71 Parse {
72 green: self.green.clone(),
73 errors: self.errors.clone(),
74 _ty: PhantomData,
75 }
76 }
77}
78
79impl<T> Parse<T> {
80 fn new(green: GreenNode, errors: Vec<SyntaxError>) -> Parse<T> {
81 Parse {
82 green,
83 errors: if errors.is_empty() {
84 None
85 } else {
86 Some(errors.into())
87 },
88 _ty: PhantomData,
89 }
90 }
91
92 pub fn syntax_node(&self) -> SyntaxNode {
93 SyntaxNode::new_root(self.green.clone())
94 }
95
96 pub fn errors(&self) -> Vec<SyntaxError> {
97 let mut errors = if let Some(e) = self.errors.as_deref() {
98 e.to_vec()
99 } else {
100 vec![]
101 };
102 validation::validate(&self.syntax_node(), &mut errors);
103 let file = SourceFile::cast(self.syntax_node()).expect("parse root is always a SourceFile");
104 errors.extend(file.sql_body_errors());
105 errors.sort_by_key(|error| error.range().start());
106 errors
107 }
108}
109
110impl<T: AstNode> Parse<T> {
111 pub fn to_syntax(self) -> Parse<SyntaxNode> {
113 Parse {
114 green: self.green,
115 errors: self.errors,
116 _ty: PhantomData,
117 }
118 }
119
120 pub fn tree(&self) -> T {
127 T::cast(self.syntax_node()).unwrap()
128 }
129
130 pub fn ok(self) -> Result<T, Vec<SyntaxError>> {
132 match self.errors() {
133 errors if !errors.is_empty() => Err(errors),
134 _ => Ok(self.tree()),
135 }
136 }
137}
138
139impl Parse<SyntaxNode> {
140 pub fn cast<N: AstNode>(self) -> Option<Parse<N>> {
141 if N::cast(self.syntax_node()).is_some() {
142 Some(Parse {
143 green: self.green,
144 errors: self.errors,
145 _ty: PhantomData,
146 })
147 } else {
148 None
149 }
150 }
151}
152
153pub use crate::ast::SourceFile;
155
156impl SourceFile {
157 pub fn parse(text: &str) -> Parse<SourceFile> {
158 let (green, errors) = parsing::parse_text(text);
159 let root = SyntaxNode::new_root(green.clone());
160
161 assert_eq!(root.kind(), SyntaxKind::SOURCE_FILE);
162 Parse::new(green, errors)
163 }
164}
165
166#[macro_export]
181macro_rules! match_ast {
182 (match $node:ident { $($tt:tt)* }) => { $crate::match_ast!(match ($node) { $($tt)* }) };
183
184 (match ($node:expr) {
185 $( $( $path:ident )::+ ($it:pat) => $res:expr, )*
186 _ => $catch_all:expr $(,)?
187 }) => {{
188 $( if let Some($it) = $($path::)+cast($node.clone()) { $res } else )*
189 { $catch_all }
190 }};
191}
192
193#[test]
196fn api_walkthrough() {
197 use ast::SourceFile;
198 use rowan::{Direction, NodeOrToken, SyntaxText, TextRange, WalkEvent};
199 use std::fmt::Write;
200
201 let source_code = "
202 create function foo(p int8)
203 returns int
204 as 'select 1 + 1'
205 language sql;
206 ";
207 let parse = SourceFile::parse(source_code);
212 assert!(parse.errors().is_empty());
213
214 let file: SourceFile = parse.tree();
217
218 let mut func = None;
221 for stmt in file.stmts() {
222 match stmt {
223 ast::Stmt::CreateFunction(f) => func = Some(f),
224 _ => unreachable!(),
225 }
226 }
227 let func: ast::CreateFunction = func.unwrap();
228
229 let path: Option<ast::Path> = func.name().and_then(|name| name.path());
235 let name: ast::PathSegment = path.unwrap().segment().unwrap();
236 assert_eq!(name.text(), "foo");
237
238 let ret_type: Option<ast::RetType> = func.ret_type();
240 let r_ty = ret_type.unwrap().func_type().unwrap();
241 let type_: ast::PathType = match r_ty {
242 ast::FuncType::Type(ast::Type::PathType(r)) => r,
243 _ => unreachable!(),
244 };
245 let type_path: ast::PathRef = type_.path_ref().unwrap();
246 assert_eq!(type_path.syntax().to_string(), "int");
247
248 let param_list: ast::ParamList = func.param_list().unwrap();
250 let param: ast::Param = param_list.params().next().unwrap();
251
252 let param_name: ast::ParamName = param.name().unwrap();
253 assert_eq!(param_name.syntax().to_string(), "p");
254
255 let param_ty: ast::FuncType = param.func_type().unwrap();
256 assert_eq!(param_ty.syntax().to_string(), "int8");
257
258 let func_option_list: ast::FuncOptionList = func.option_list().unwrap();
259
260 let func_option = func_option_list.options().next().unwrap();
265 let option: &ast::AsFuncOption = match &func_option {
266 ast::FuncOption::AsFuncOption(o) => o,
267 _ => unreachable!(),
268 };
269 let as_definition: ast::AsDefinition = match option.as_func_target().unwrap() {
270 ast::AsFuncTarget::AsDefinition(d) => d,
271 _ => unreachable!(),
272 };
273 let definition: ast::Literal = as_definition.literal().unwrap();
274 assert_eq!(definition.syntax().to_string(), "'select 1 + 1'");
275
276 let func_option_syntax = func_option.syntax();
279
280 assert!(func_option_syntax == option.syntax());
282
283 let _expr: ast::FuncOption = match ast::FuncOption::cast(func_option_syntax.clone()) {
285 Some(e) => e,
286 None => unreachable!(),
287 };
288
289 assert_eq!(func_option_syntax.kind(), SyntaxKind::AS_FUNC_OPTION);
291
292 assert_eq!(
294 func_option_syntax.text_range(),
295 TextRange::new(65.into(), 82.into())
296 );
297
298 let text: SyntaxText = func_option_syntax.text();
301 assert_eq!(text.to_string(), "as 'select 1 + 1'");
302
303 assert_eq!(
305 func_option_syntax.parent().as_ref(),
306 Some(func_option_list.syntax())
307 );
308 assert_eq!(
309 param_list
310 .syntax()
311 .first_child_or_token()
312 .map(|it| it.kind()),
313 Some(SyntaxKind::L_PAREN)
314 );
315 assert_eq!(
316 func_option_syntax
317 .next_sibling_or_token()
318 .map(|it| it.kind()),
319 Some(SyntaxKind::WHITESPACE)
320 );
321
322 let f = func_option_syntax
324 .ancestors()
325 .find_map(ast::CreateFunction::cast);
326 assert_eq!(f, Some(func));
327 assert!(
328 param
329 .syntax()
330 .siblings_with_tokens(Direction::Next)
331 .any(|it| it.kind() == SyntaxKind::R_PAREN)
332 );
333 assert_eq!(
334 func_option_syntax.descendants_with_tokens().count(),
335 6, );
340
341 let mut buf = String::new();
343 let mut indent = 0;
344 for event in func_option_syntax.preorder_with_tokens() {
345 match event {
346 WalkEvent::Enter(node) => {
347 let text = match &node {
348 NodeOrToken::Node(it) => it.text().to_string(),
349 NodeOrToken::Token(it) => it.text().to_owned(),
350 };
351 buf.write_fmt(format_args!(
352 "{:indent$}{:?} {:?}\n",
353 " ",
354 text,
355 node.kind(),
356 indent = indent
357 ))
358 .unwrap();
359 indent += 2;
360 }
361 WalkEvent::Leave(_) => indent -= 2,
362 }
363 }
364 assert_eq!(indent, 0);
365 assert_eq!(
366 buf.trim(),
367 r#"
368"as 'select 1 + 1'" AS_FUNC_OPTION
369 "as" AS_KW
370 " " WHITESPACE
371 "'select 1 + 1'" AS_DEFINITION
372 "'select 1 + 1'" LITERAL
373 "'select 1 + 1'" STRING
374 "#
375 .trim()
376 );
377
378 let exprs_cast: Vec<String> = file
385 .syntax()
386 .descendants()
387 .filter_map(ast::FuncOption::cast)
388 .map(|expr| expr.syntax().text().to_string())
389 .collect();
390
391 let mut exprs_visit = Vec::new();
393 for node in file.syntax().descendants() {
394 match_ast! {
395 match node {
396 ast::FuncOption(it) => {
397 let res = it.syntax().text().to_string();
398 exprs_visit.push(res);
399 },
400 _ => (),
401 }
402 }
403 }
404 assert_eq!(exprs_cast, exprs_visit);
405}
406
407#[test]
408fn create_table() {
409 use insta::assert_debug_snapshot;
410
411 let source_code = "
412 create table users (
413 id int8 primary key,
414 name varchar(255) not null,
415 email text,
416 created_at timestamp default now()
417 );
418
419 create table posts (
420 id serial primary key,
421 title varchar(500),
422 content text,
423 user_id int8 references users(id)
424 );
425 ";
426
427 let parse = SourceFile::parse(source_code);
428 assert!(parse.errors().is_empty());
429 let file: SourceFile = parse.tree();
430
431 let mut tables: Vec<(String, Vec<(String, String)>)> = vec![];
432
433 for stmt in file.stmts() {
434 if let ast::Stmt::CreateTable(create_table) = stmt {
435 let table_name = create_table
436 .table_name()
437 .and_then(|table| table.path())
438 .unwrap()
439 .syntax()
440 .to_string();
441 let mut columns = vec![];
442 for arg in create_table.table_arg_list().unwrap().args() {
443 match arg {
444 ast::TableArg::Column(column) => {
445 let column_name = column.name().unwrap();
446 let column_type = column.ty().unwrap();
447 columns.push((
448 column_name.syntax().to_string(),
449 column_type.syntax().to_string(),
450 ));
451 }
452 ast::TableArg::TableConstraint(_) | ast::TableArg::LikeClause(_) => (),
453 }
454 }
455 tables.push((table_name, columns));
456 }
457 }
458
459 assert_debug_snapshot!(tables, @r#"
460 [
461 (
462 "users",
463 [
464 (
465 "id",
466 "int8",
467 ),
468 (
469 "name",
470 "varchar(255)",
471 ),
472 (
473 "email",
474 "text",
475 ),
476 (
477 "created_at",
478 "timestamp",
479 ),
480 ],
481 ),
482 (
483 "posts",
484 [
485 (
486 "id",
487 "serial",
488 ),
489 (
490 "title",
491 "varchar(500)",
492 ),
493 (
494 "content",
495 "text",
496 ),
497 (
498 "user_id",
499 "int8",
500 ),
501 ],
502 ),
503 ]
504 "#)
505}
506
507#[test]
508fn bin_expr() {
509 use insta::assert_debug_snapshot;
510
511 let source_code = "select 1 is not null;";
512 let parse = SourceFile::parse(source_code);
513 assert!(parse.errors().is_empty());
514 let file: SourceFile = parse.tree();
515
516 let ast::Stmt::Select(select) = file.stmts().next().unwrap() else {
517 unreachable!()
518 };
519
520 let target_list = select.select_clause().unwrap().target_list().unwrap();
521 let target = target_list.targets().next().unwrap();
522 let ast::Expr::BinExpr(bin_expr) = target.expr().unwrap() else {
523 unreachable!()
524 };
525
526 let lhs = bin_expr.lhs();
527 let op = bin_expr.op();
528 let rhs = bin_expr.rhs();
529
530 assert_debug_snapshot!(lhs, @r#"
531 Some(
532 Literal(
533 Literal {
534 syntax: LITERAL@7..8
535 INT_NUMBER@7..8 "1"
536 ,
537 },
538 ),
539 )
540 "#);
541 assert_debug_snapshot!(op, @r#"
542 Some(
543 IsNot(
544 IsNot {
545 syntax: IS_NOT@9..15
546 IS_KW@9..11 "is"
547 WHITESPACE@11..12 " "
548 NOT_KW@12..15 "not"
549 ,
550 },
551 ),
552 )
553 "#);
554 assert_debug_snapshot!(rhs, @r#"
555 Some(
556 Literal(
557 Literal {
558 syntax: LITERAL@16..20
559 NULL_KW@16..20 "null"
560 ,
561 },
562 ),
563 )
564 "#);
565}