linkrs_query/parser/parsing/ddl_parser/
create_parser.rs1use crate::parser::ast::stmt::*;
2
3use crate::parser::core::error::{ParseError, ParseErrorKind};
4use crate::parser::parsing::parse_context::ParseContext;
5use crate::parser::TokenKind;
6
7use super::consume_space_or_graph;
8use super::DdlParser;
9
10fn is_identifier_spelled_builtin_type(name: &str) -> bool {
16 match name.to_uppercase().as_str() {
17 "DECIMAL" | "UNION" | "FIXED_STRING" | "FIXEDSTRING" | "VECTOR" => true,
18 _ => name
19 .to_uppercase()
20 .parse::<crate::parser::ast::types::DataType>()
21 .is_ok(),
22 }
23}
24
25type TagEdgeDefsResult = (
26 Vec<linkrs_core::types::PropertyDef>,
27 Option<i64>,
28 Option<String>,
29);
30
31impl DdlParser {
32 pub fn parse_create_after_token(
33 &mut self,
34 ctx: &mut ParseContext,
35 start_span: crate::parser::ast::types::Span,
36 ) -> Result<Stmt, ParseError> {
37 if ctx.match_token(TokenKind::Tag) {
38 if ctx.check_token(TokenKind::Index) {
39 ctx.match_token(TokenKind::Index);
40 let mut if_not_exists = false;
41 if ctx.match_token(TokenKind::If) {
42 ctx.expect_token(TokenKind::Not)?;
43 ctx.expect_token(TokenKind::Exists)?;
44 if_not_exists = true;
45 }
46 let name = ctx.expect_identifier()?;
47 ctx.expect_token(TokenKind::On)?;
48 let on = ctx.expect_identifier()?;
49 ctx.expect_token(TokenKind::LParen)?;
50 let mut properties = vec![];
51 loop {
52 properties.push(ctx.expect_identifier()?);
53 if !ctx.match_token(TokenKind::Comma) {
54 break;
55 }
56 }
57 ctx.expect_token(TokenKind::RParen)?;
58 return Ok(Stmt::Create(CreateStmt {
59 span: start_span,
60 target: CreateTarget::Index {
61 index_type: IndexType::Tag,
62 name,
63 on,
64 properties,
65 },
66 if_not_exists,
67 }));
68 }
69
70 let mut if_not_exists = false;
71 if ctx.match_token(TokenKind::If) {
72 ctx.expect_token(TokenKind::Not)?;
73 ctx.expect_token(TokenKind::Exists)?;
74 if_not_exists = true;
75 }
76 let name = ctx.expect_identifier()?;
77 if ctx.match_token(TokenKind::As) {
78 let query_text = Self::capture_subquery_text(ctx)?;
79 return Ok(Stmt::Create(CreateStmt {
80 span: start_span,
81 target: CreateTarget::TagAsQuery { name, query_text },
82 if_not_exists,
83 }));
84 }
85 let (properties, ttl_duration, ttl_col) = self.parse_tag_edge_defs(ctx)?;
86 Ok(Stmt::Create(CreateStmt {
87 span: start_span,
88 target: CreateTarget::Tag {
89 name,
90 properties,
91 ttl_duration,
92 ttl_col,
93 },
94 if_not_exists,
95 }))
96 } else if ctx.check_token(TokenKind::Edge) {
97 ctx.next_token();
98
99 if ctx.check_token(TokenKind::Index) {
100 ctx.next_token();
101 let mut if_not_exists = false;
102 if ctx.match_token(TokenKind::If) {
103 ctx.expect_token(TokenKind::Not)?;
104 ctx.expect_token(TokenKind::Exists)?;
105 if_not_exists = true;
106 }
107 let name = ctx.expect_identifier()?;
108 ctx.expect_token(TokenKind::On)?;
109 let on = ctx.expect_identifier()?;
110 ctx.expect_token(TokenKind::LParen)?;
111 let mut properties = vec![];
112 loop {
113 properties.push(ctx.expect_identifier()?);
114 if !ctx.match_token(TokenKind::Comma) {
115 break;
116 }
117 }
118 ctx.expect_token(TokenKind::RParen)?;
119 return Ok(Stmt::Create(CreateStmt {
120 span: start_span,
121 target: CreateTarget::Index {
122 index_type: IndexType::Edge,
123 name,
124 on,
125 properties,
126 },
127 if_not_exists,
128 }));
129 }
130
131 let mut if_not_exists = false;
132 if ctx.match_token(TokenKind::If) {
133 ctx.expect_token(TokenKind::Not)?;
134 ctx.expect_token(TokenKind::Exists)?;
135 if_not_exists = true;
136 }
137 let name = ctx.expect_identifier()?;
138 if ctx.match_token(TokenKind::As) {
139 let query_text = Self::capture_subquery_text(ctx)?;
140 return Ok(Stmt::Create(CreateStmt {
141 span: start_span,
142 target: CreateTarget::EdgeAsQuery { name, query_text },
143 if_not_exists,
144 }));
145 }
146 let (properties, ttl_duration, ttl_col) = self.parse_tag_edge_defs(ctx)?;
147 let (src_tag, dst_tag) = self.parse_edge_src_dst(ctx)?;
148 Ok(Stmt::Create(CreateStmt {
149 span: start_span,
150 target: CreateTarget::EdgeType {
151 name,
152 properties,
153 ttl_duration,
154 ttl_col,
155 src_tag,
156 dst_tag,
157 },
158 if_not_exists,
159 }))
160 } else if consume_space_or_graph(ctx)? {
161 let mut if_not_exists = false;
162 if ctx.match_token(TokenKind::If) {
163 ctx.expect_token(TokenKind::Not)?;
164 ctx.expect_token(TokenKind::Exists)?;
165 if_not_exists = true;
166 }
167 let name = ctx.expect_dcl_name()?;
168
169 let mut vid_type = "INT64".to_string();
170 let mut comment = None;
171
172 if ctx.match_token(TokenKind::LParen) {
173 loop {
174 if ctx.check_token(TokenKind::RParen) {
175 ctx.expect_token(TokenKind::RParen)?;
176 break;
177 }
178
179 if ctx.match_token(TokenKind::VIdType) {
180 ctx.expect_token(TokenKind::Assign)?;
181 vid_type = self.parse_vid_type_value(ctx)?;
182 } else if ctx.match_token(TokenKind::Comment) {
183 ctx.expect_token(TokenKind::Assign)?;
184 comment = Some(ctx.expect_string_literal()?);
185 } else {
186 return Err(ParseError::new(
187 ParseErrorKind::SyntaxError,
188 "Unsupported space option; supported options: vid_type, comment"
189 .to_string(),
190 ctx.current_position(),
191 ));
192 }
193
194 if !ctx.match_token(TokenKind::Comma) {
195 ctx.expect_token(TokenKind::RParen)?;
196 break;
197 }
198 }
199 }
200
201 self.parse_space_with_params(ctx, &mut comment)?;
202
203 Ok(Stmt::Create(CreateStmt {
204 span: start_span,
205 target: CreateTarget::Space {
206 name,
207 vid_type,
208 comment,
209 },
210 if_not_exists,
211 }))
212 } else if ctx.match_token(TokenKind::Index) {
213 let mut if_not_exists = false;
214 if ctx.match_token(TokenKind::If) {
215 ctx.expect_token(TokenKind::Not)?;
216 ctx.expect_token(TokenKind::Exists)?;
217 if_not_exists = true;
218 }
219 let name = ctx.expect_identifier()?;
220 ctx.expect_token(TokenKind::On)?;
221 let on = ctx.expect_identifier()?;
222 ctx.expect_token(TokenKind::LParen)?;
223 let mut properties = vec![];
224 loop {
225 properties.push(ctx.expect_identifier()?);
226 if !ctx.match_token(TokenKind::Comma) {
227 break;
228 }
229 }
230 ctx.expect_token(TokenKind::RParen)?;
231 Ok(Stmt::Create(CreateStmt {
232 span: start_span,
233 target: CreateTarget::Index {
234 index_type: crate::parser::ast::stmt::IndexType::Tag,
235 name,
236 on,
237 properties,
238 },
239 if_not_exists,
240 }))
241 } else if ctx.match_token(TokenKind::Sequence) {
242 let mut if_not_exists = false;
243 if ctx.match_token(TokenKind::If) {
244 ctx.expect_token(TokenKind::Not)?;
245 ctx.expect_token(TokenKind::Exists)?;
246 if_not_exists = true;
247 }
248 let name = ctx.expect_identifier()?;
249 let mut start = None;
250 let mut increment = None;
251 let mut min_value = None;
252 let mut max_value = None;
253 let mut cycle = false;
254
255 loop {
257 if ctx.check_keyword("START") && start.is_none() {
258 ctx.next_token();
259 ctx.expect_token(TokenKind::Assign)?;
260 start = Some(ctx.expect_integer_literal()?);
261 } else if ctx.check_keyword("INCREMENT") && increment.is_none() {
262 ctx.next_token();
263 ctx.expect_token(TokenKind::Assign)?;
264 increment = Some(ctx.expect_integer_literal()?);
265 } else if ctx.check_keyword("MINVALUE") && min_value.is_none() {
266 ctx.next_token();
267 ctx.expect_token(TokenKind::Assign)?;
268 min_value = Some(ctx.expect_integer_literal()?);
269 } else if ctx.check_keyword("MAXVALUE") && max_value.is_none() {
270 ctx.next_token();
271 ctx.expect_token(TokenKind::Assign)?;
272 max_value = Some(ctx.expect_integer_literal()?);
273 } else if ctx.match_token(TokenKind::Cycle) {
274 cycle = true;
275 } else if ctx.check_keyword("NOCYCLE") {
276 ctx.next_token();
277 cycle = false;
278 } else {
279 break;
280 }
281 }
282
283 Ok(Stmt::Create(CreateStmt {
284 span: start_span,
285 target: CreateTarget::Sequence {
286 name,
287 start,
288 increment,
289 min_value,
290 max_value,
291 cycle,
292 },
293 if_not_exists,
294 }))
295 } else if ctx.check_keyword("MACRO") {
296 ctx.consume_keyword("MACRO")?;
297 let mut if_not_exists = false;
298 if ctx.match_token(TokenKind::If) {
299 ctx.expect_token(TokenKind::Not)?;
300 ctx.expect_token(TokenKind::Exists)?;
301 if_not_exists = true;
302 }
303 let name = ctx.expect_identifier()?;
304 ctx.expect_token(TokenKind::LParen)?;
305 let mut params = vec![];
306 while !ctx.check_token(TokenKind::RParen) {
307 let param_name = ctx.expect_identifier()?;
308 let default_value = if ctx.match_token(TokenKind::Assign) {
309 let expr = crate::parser::parsing::expr_parser::parse_expression_with_context(
310 ctx,
311 ctx.expression_context_clone(),
312 )?;
313 Some(expr)
314 } else {
315 None
316 };
317 params.push(MacroParam {
318 name: param_name,
319 default_value,
320 });
321 if !ctx.match_token(TokenKind::Comma) {
322 break;
323 }
324 }
325 ctx.expect_token(TokenKind::RParen)?;
326 ctx.consume_keyword("AS")?;
327 let body = crate::parser::parsing::expr_parser::parse_expression_with_context(
328 ctx,
329 ctx.expression_context_clone(),
330 )?;
331 let span = ctx.span_since(start_span.start);
332 Ok(Stmt::CreateMacro(CreateMacroStmt {
333 span,
334 name,
335 params,
336 body,
337 if_not_exists,
338 }))
339 } else if ctx.check_keyword("TYPE") {
340 ctx.consume_keyword("TYPE")?;
341 let mut if_not_exists = false;
342 if ctx.match_token(TokenKind::If) {
343 ctx.expect_token(TokenKind::Not)?;
344 ctx.expect_token(TokenKind::Exists)?;
345 if_not_exists = true;
346 }
347 let name = ctx.expect_identifier()?;
348 ctx.consume_keyword("AS")?;
349 let ckpt = ctx.checkpoint();
355 let leading_is_identifier =
362 matches!(ctx.current_token().kind, TokenKind::Identifier(_));
363 let leading_lexeme = ctx.current_token().lexeme.clone();
364 let (underlying_type, underlying_type_text) = match self.parse_data_type(ctx) {
365 Ok(parsed) => {
366 let text = if leading_is_identifier
371 && !is_identifier_spelled_builtin_type(&leading_lexeme)
372 {
373 leading_lexeme
374 } else {
375 parsed.to_string()
376 };
377 (parsed, text)
378 }
379 Err(parse_err) => {
380 if !leading_is_identifier {
381 return Err(parse_err);
382 }
383 ctx.restore(ckpt);
384 let alias_name = ctx.expect_identifier()?;
385 (DataType::Unknown, alias_name.clone())
386 }
387 };
388 let span = ctx.span_since(start_span.start);
389 Ok(Stmt::CreateType(CreateTypeStmt {
390 span,
391 name,
392 underlying_type,
393 underlying_type_text,
394 if_not_exists,
395 }))
396 } else {
397 Err(ParseError::new(
398 ParseErrorKind::UnexpectedToken,
399 "Expected TAG, EDGE, SPACE, INDEX, SEQUENCE, MACRO, or TYPE after CREATE"
400 .to_string(),
401 ctx.current_position(),
402 ))
403 }
404 }
405
406 fn parse_tag_edge_defs(
407 &mut self,
408 ctx: &mut ParseContext,
409 ) -> Result<TagEdgeDefsResult, ParseError> {
410 let mut properties = Vec::new();
411 let mut ttl_duration = None;
412 let mut ttl_col = None;
413
414 if ctx.match_token(TokenKind::LParen) {
415 while !ctx.check_token(TokenKind::RParen) {
416 if ctx.check_token(TokenKind::TtlDuration) {
417 if ttl_duration.is_some() {
418 return Err(ParseError::new(
419 ParseErrorKind::SyntaxError,
420 "duplicate ttl_duration clause".to_string(),
421 ctx.current_position(),
422 ));
423 }
424 ctx.next_token();
425 ctx.expect_token(TokenKind::Assign)?;
426 let duration = ctx.expect_integer_literal()?;
427 if duration <= 0 {
428 return Err(ParseError::new(
429 ParseErrorKind::SyntaxError,
430 format!(
431 "ttl_duration must be a positive integer (seconds), got {duration}"
432 ),
433 ctx.current_position(),
434 ));
435 }
436 ttl_duration = Some(duration);
437 } else if ctx.check_token(TokenKind::TtlCol) {
438 if ttl_col.is_some() {
439 return Err(ParseError::new(
440 ParseErrorKind::SyntaxError,
441 "duplicate ttl_col clause".to_string(),
442 ctx.current_position(),
443 ));
444 }
445 ctx.next_token();
446 ctx.expect_token(TokenKind::Assign)?;
447 let col = if let TokenKind::StringLiteral(s) = ctx.current_token().kind.clone()
448 {
449 ctx.next_token();
450 s
451 } else {
452 ctx.expect_identifier()?
453 };
454 if col.is_empty() {
455 return Err(ParseError::new(
456 ParseErrorKind::SyntaxError,
457 "ttl_col must not be empty".to_string(),
458 ctx.current_position(),
459 ));
460 }
461 ttl_col = Some(col);
462 } else {
463 let prop = self.parse_single_property_def(ctx)?;
464 properties.push(prop);
465 }
466
467 if !ctx.match_token(TokenKind::Comma) {
468 break;
469 }
470 }
471 ctx.expect_token(TokenKind::RParen)?;
472 }
473
474 if ttl_duration.is_some() != ttl_col.is_some() {
475 return Err(ParseError::new(
476 ParseErrorKind::SyntaxError,
477 "TTL requires both ttl_duration and ttl_col to be specified together; omit both to disable TTL"
478 .to_string(),
479 ctx.current_position(),
480 ));
481 }
482
483 Ok((properties, ttl_duration, ttl_col))
484 }
485
486 fn parse_edge_src_dst(
487 &mut self,
488 ctx: &mut ParseContext,
489 ) -> Result<(Option<String>, Option<String>), ParseError> {
490 if ctx.match_token(TokenKind::From) {
491 let src_tag = ctx.expect_identifier()?;
492 ctx.expect_token(TokenKind::To)?;
493 let dst_tag = ctx.expect_identifier()?;
494 Ok((Some(src_tag), Some(dst_tag)))
495 } else {
496 Ok((None, None))
497 }
498 }
499
500 fn capture_subquery_text(ctx: &mut ParseContext) -> Result<String, ParseError> {
505 ctx.expect_token(TokenKind::LParen)?;
506 let mut depth = 1;
507 let mut query_text = String::new();
508 while depth > 0 {
509 match ctx.current_token().kind {
510 crate::parser::TokenKind::LParen => {
511 depth += 1;
512 query_text.push('(');
513 ctx.next_token();
514 }
515 crate::parser::TokenKind::RParen => {
516 depth -= 1;
517 if depth > 0 {
518 query_text.push(')');
519 }
520 ctx.next_token();
521 }
522 crate::parser::TokenKind::Eof => {
523 return Err(ParseError::new(
524 ParseErrorKind::SyntaxError,
525 "Unexpected end of input in subquery".to_string(),
526 ctx.current_position(),
527 ));
528 }
529 _ => {
530 if !query_text.is_empty() {
531 query_text.push(' ');
532 }
533 query_text.push_str(&ctx.current_token().lexeme);
534 ctx.next_token();
535 }
536 }
537 }
538 Ok(query_text)
539 }
540
541 fn parse_space_with_params(
544 &mut self,
545 ctx: &mut ParseContext,
546 comment: &mut Option<String>,
547 ) -> Result<(), ParseError> {
548 while ctx.match_token(TokenKind::With) {
549 let key = match &ctx.current_token().kind {
554 TokenKind::Comment => {
555 ctx.next_token();
556 "comment".to_string()
557 }
558 TokenKind::PartitionNum | TokenKind::ReplicaFactor | TokenKind::VIdType => {
559 let lexeme = ctx.current_token().lexeme.clone();
560 ctx.next_token();
561 lexeme
562 }
563 _ => ctx.expect_identifier()?,
564 };
565 let key_pos = ctx.current_position();
566 ctx.expect_token(TokenKind::Assign)?;
567 let value = if let TokenKind::StringLiteral(s) = ctx.current_token().kind.clone() {
568 ctx.next_token();
569 s
570 } else {
571 ctx.expect_integer_literal()?.to_string()
572 };
573 if key.eq_ignore_ascii_case("comment") {
574 *comment = Some(value);
575 } else {
576 return Err(ParseError::new(
577 ParseErrorKind::SyntaxError,
578 format!(
579 "Unsupported space option '{}'; supported options: comment",
580 key
581 ),
582 key_pos,
583 ));
584 }
585 }
586 Ok(())
587 }
588}