Skip to main content

linkrs_query/parser/parsing/ddl_parser/
create_parser.rs

1use 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
10/// Whether a type identifier spelled with a leading identifier denotes a
11/// builtin type (including DECIMAL/UNION/FIXED_STRING/VECTOR, which carry
12/// parameters), as opposed to a user-defined type alias. Used to decide
13/// whether the original spelling — rather than the resolved canonical type —
14/// must be kept as the underlying text for alias dependency tracking.
15fn 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            // Parse optional clauses: START, INCREMENT, MINVALUE, MAXVALUE, CYCLE/NOCYCLE
256            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            // The underlying type is either a builtin type or a reference to
350            // another alias. Builtins parse directly; an unknown leading
351            // identifier is kept as raw alias text (DataType::Unknown) and
352            // resolved at plan time through the type-alias catalog, where
353            // cycle detection also runs.
354            let ckpt = ctx.checkpoint();
355            // Capture the original spelling of the underlying type. When it
356            // denotes a user-defined alias, `parse_data_type` resolves it to
357            // the underlying builtin and `parsed.to_string()` no longer
358            // mentions the alias. Preserving the original identifier text is
359            // required for alias-to-alias dependency tracking and the
360            // drop-referenced-alias check in the type-alias catalog.
361            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                    // A leading identifier that is neither a builtin spelling
367                    // nor a DECIMAL/UNION/FIXED_STRING/VECTOR spelling must be
368                    // an alias reference — keep it verbatim. Genuine builtin
369                    // spellings retain their canonical `to_string()` form.
370                    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    /// Capture a parenthesized subquery as raw text by joining lexemes.
501    ///
502    /// The opening `(` is consumed here; tokens are collected until the
503    /// matching `)` so the text can be re-parsed when the statement executes.
504    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    /// Parse optional space parameters: (vid_type=..., comment='...') and
542    /// WITH key=value clauses (e.g. WITH comment='...').
543    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            // Option keys may lex as dedicated keywords (`comment`,
550            // `partition_num`, ...); accept their spellings so unknown keys
551            // reach the explicit rejection below instead of dying in
552            // `expect_identifier`.
553            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}