Skip to main content

linkrs_query/parser/parsing/
user_parser.rs

1//! User Management Statement Parsing Module
2//!
3//! Responsible for parsing statements related to user management, including CREATE USER, ALTER USER, DROP USER, CHANGE PASSWORD, etc.
4
5use crate::parser::ast::stmt::*;
6use crate::parser::ast::types::Span;
7use crate::parser::core::error::{ParseError, ParseErrorKind};
8use crate::parser::parsing::parse_context::ParseContext;
9use crate::parser::TokenKind;
10
11/// User Management Parser
12pub struct UserParser;
13
14impl UserParser {
15    pub fn new() -> Self {
16        Self
17    }
18
19    /// Analysis of the CREATE USER statement
20    pub fn parse_create_user_statement(
21        &mut self,
22        ctx: &mut ParseContext,
23    ) -> Result<Stmt, ParseError> {
24        let start_span = ctx.current_span();
25        ctx.expect_token(TokenKind::CreateUser)?;
26        self.parse_create_user_internal(ctx, start_span)
27    }
28
29    /// Analysis of the CREATE USER statement (the CREATE token has already been consumed)
30    pub fn parse_create_user_statement_after_create(
31        &mut self,
32        ctx: &mut ParseContext,
33        start_span: Span,
34    ) -> Result<Stmt, ParseError> {
35        ctx.expect_token(TokenKind::User)?;
36        self.parse_create_user_internal(ctx, start_span)
37    }
38
39    /// Analyzing the internal implementation of the CREATE USER statement
40    fn parse_create_user_internal(
41        &mut self,
42        ctx: &mut ParseContext,
43        start_span: Span,
44    ) -> Result<Stmt, ParseError> {
45        let mut if_not_exists = false;
46        if ctx.match_token(TokenKind::If) {
47            ctx.expect_token(TokenKind::Not)?;
48            ctx.expect_token(TokenKind::Exists)?;
49            if_not_exists = true;
50        }
51
52        let username = ctx.expect_dcl_name()?;
53
54        // Password is optional for Nebula compatibility; when omitted it
55        // defaults to an empty password that still has to pass the password
56        // policy at execution time.
57        let mut password = String::new();
58        if ctx.match_token(TokenKind::With) {
59            // Nebula writes `WITH PASSWORD '...'`; the PASSWORD keyword is
60            // accepted but optional so both spellings parse.
61            ctx.match_token(TokenKind::Password);
62            password = ctx.expect_string_literal()?;
63        } else if ctx.match_token(TokenKind::Password) {
64            password = ctx.expect_string_literal()?;
65        } else if matches!(ctx.current_token().kind, TokenKind::StringLiteral(_)) {
66            // Bare `CREATE USER name 'password'` is also accepted.
67            password = ctx.expect_string_literal()?;
68        }
69
70        let span = ctx.span_since(start_span.start);
71
72        Ok(Stmt::CreateUser(CreateUserStmt {
73            span,
74            username,
75            password,
76            if_not_exists,
77        }))
78    }
79
80    /// Analysis of the ALTER USER statement
81    pub fn parse_alter_user_statement(
82        &mut self,
83        ctx: &mut ParseContext,
84    ) -> Result<Stmt, ParseError> {
85        let start_span = ctx.current_span();
86        ctx.expect_token(TokenKind::AlterUser)?;
87        self.parse_alter_user_internal(ctx, start_span)
88    }
89
90    /// Analysis of the internal implementation of the ALTER USER command
91    pub fn parse_alter_user_internal(
92        &mut self,
93        ctx: &mut ParseContext,
94        start_span: Span,
95    ) -> Result<Stmt, ParseError> {
96        ctx.expect_token(TokenKind::User)?;
97
98        let username = ctx.expect_dcl_name()?;
99
100        let mut password = None;
101        let mut is_locked = None;
102
103        // Analyzing the WITH PASSWORD clause
104        if ctx.match_token(TokenKind::With) {
105            ctx.expect_token(TokenKind::Password)?;
106            password = Some(ctx.expect_string_literal()?);
107        }
108
109        // The SET LOCKED = <true|false> syntax is also supported.
110        while ctx.match_token(TokenKind::Set) {
111            if ctx.match_token(TokenKind::Locked) {
112                ctx.expect_token(TokenKind::Assign)?;
113                is_locked = Some(ctx.expect_boolean_literal()?);
114            } else {
115                return Err(ParseError::new(
116                    ParseErrorKind::SyntaxError,
117                    "Expected LOCKED after SET in ALTER USER".to_string(),
118                    ctx.current_position(),
119                ));
120            }
121        }
122
123        let span = ctx.span_since(start_span.start);
124
125        Ok(Stmt::AlterUser(AlterUserStmt {
126            span,
127            username,
128            password,
129            is_locked,
130        }))
131    }
132
133    /// Analysis of the DROP USER statement
134    pub fn parse_drop_user_statement(
135        &mut self,
136        ctx: &mut ParseContext,
137    ) -> Result<Stmt, ParseError> {
138        let start_span = ctx.current_span();
139        ctx.expect_token(TokenKind::DropUser)?;
140
141        let mut if_exists = false;
142        if ctx.match_token(TokenKind::If) {
143            ctx.expect_token(TokenKind::Exists)?;
144            if_exists = true;
145        }
146
147        let username = ctx.expect_dcl_name()?;
148
149        let span = ctx.span_since(start_span.start);
150
151        Ok(Stmt::DropUser(DropUserStmt {
152            span,
153            username,
154            if_exists,
155        }))
156    }
157
158    /// Analysis of the CHANGE PASSWORD statement
159    pub fn parse_change_password_statement(
160        &mut self,
161        ctx: &mut ParseContext,
162    ) -> Result<Stmt, ParseError> {
163        let start_span = ctx.current_span();
164        ctx.expect_token(TokenKind::ChangePassword)?;
165
166        self.parse_change_password_internal(ctx, start_span)
167    }
168
169    /// Analysis of the internal implementation of the “CHANGE PASSWORD” command
170    pub fn parse_change_password_internal(
171        &mut self,
172        ctx: &mut ParseContext,
173        start_span: Span,
174    ) -> Result<Stmt, ParseError> {
175        // Parse the optional username. A username is present only when the next
176        // token is not the old password literal and not the Nebula `FROM`
177        // keyword that introduces the old password.
178        let username = if matches!(ctx.current_token().kind, TokenKind::StringLiteral(_))
179            || ctx.check_token(TokenKind::From)
180        {
181            None
182        } else {
183            Some(ctx.expect_dcl_name()?)
184        };
185
186        // Nebula spells `CHANGE PASSWORD name FROM 'old' TO 'new'`; both that
187        // form and the shorter positional form are accepted here.
188        ctx.match_token(TokenKind::From);
189
190        let old_password = ctx.expect_string_literal()?;
191        ctx.expect_token(TokenKind::To)?;
192        let new_password = ctx.expect_string_literal()?;
193
194        let span = ctx.span_since(start_span.start);
195
196        Ok(Stmt::ChangePassword(ChangePasswordStmt {
197            span,
198            username,
199            old_password,
200            new_password,
201        }))
202    }
203
204    /// Analysis of the CHANGE statement (currently only CHANGE PASSWORD is supported)
205    pub fn parse_change_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
206        let start_span = ctx.current_span();
207        ctx.expect_token(TokenKind::Change)?;
208
209        // Check whether it is “CHANGE PASSWORD”.
210        if ctx.match_token(TokenKind::Password) {
211            return self.parse_change_password_internal(ctx, start_span);
212        }
213
214        Err(ParseError::new(
215            ParseErrorKind::UnexpectedToken,
216            "Expected PASSWORD after CHANGE".to_string(),
217            ctx.current_position(),
218        ))
219    }
220
221    /// Analyzing character types (supporting keyword-based searches)
222    fn parse_role_type(&mut self, ctx: &mut ParseContext) -> Result<RoleType, ParseError> {
223        let token = ctx.current_token();
224        let role_str = match token.kind {
225            TokenKind::God => {
226                ctx.next_token();
227                "GOD".to_string()
228            }
229            TokenKind::Admin => {
230                ctx.next_token();
231                "ADMIN".to_string()
232            }
233            TokenKind::Dba => {
234                ctx.next_token();
235                "DBA".to_string()
236            }
237            TokenKind::Guest => {
238                ctx.next_token();
239                "GUEST".to_string()
240            }
241            TokenKind::User => {
242                ctx.next_token();
243                "USER".to_string()
244            }
245            _ => ctx.expect_identifier()?,
246        };
247
248        role_str
249            .parse::<RoleType>()
250            .map_err(|e| ParseError::new(ParseErrorKind::SyntaxError, e, ctx.current_position()))
251    }
252
253    /// Analysis of the GRANT statement
254    /// Syntax:  `GRANT ROLE <role_type> ON <space_name> TO <username>`
255    pub fn parse_grant_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
256        let start_span = ctx.current_span();
257        ctx.expect_token(TokenKind::Grant)?;
258
259        // Analyzing the ROLE keyword (optional)
260        let _ = ctx.match_token(TokenKind::Role);
261
262        // Analyzing character types
263        let role = self.parse_role_type(ctx)?;
264
265        // Analysis of the ON keyword
266        ctx.expect_token(TokenKind::On)?;
267
268        // Analysis of the Space name
269        let space_name = ctx.expect_dcl_name()?;
270
271        // Analysis of the TO keyword
272        ctx.expect_token(TokenKind::To)?;
273
274        // Analyzing the username
275        let username = ctx.expect_dcl_name()?;
276
277        let span = ctx.span_since(start_span.start);
278
279        Ok(Stmt::Grant(GrantStmt {
280            span,
281            role,
282            space_name,
283            username,
284        }))
285    }
286
287    /// Analysis of the REVOKE statement
288    /// Syntax: `REVOKE ROLE <role_type> ON <space_name> FROM <username>`
289    pub fn parse_revoke_statement(&mut self, ctx: &mut ParseContext) -> Result<Stmt, ParseError> {
290        let start_span = ctx.current_span();
291        ctx.expect_token(TokenKind::Revoke)?;
292
293        // Analysis of the ROLE keyword (optional)
294        let _ = ctx.match_token(TokenKind::Role);
295
296        // Analyzing character types
297        let role = self.parse_role_type(ctx)?;
298
299        // Analysis of the ON keyword
300        ctx.expect_token(TokenKind::On)?;
301
302        // Analyzing the name “Space”
303        let space_name = ctx.expect_dcl_name()?;
304
305        // Analysis of the FROM keyword
306        ctx.expect_token(TokenKind::From)?;
307
308        // Analyzing the username
309        let username = ctx.expect_dcl_name()?;
310
311        let span = ctx.span_since(start_span.start);
312
313        Ok(Stmt::Revoke(RevokeStmt {
314            span,
315            role,
316            space_name,
317            username,
318        }))
319    }
320}
321
322impl Default for UserParser {
323    fn default() -> Self {
324        Self::new()
325    }
326}
327
328// ==================== Unit Tests ====================
329
330#[cfg(test)]
331mod tests {
332    use crate::parser::Parser;
333
334    #[test]
335    fn test_parse_create_user_unicode_username() {
336        let query = "CREATE USER ユーザー WITH PASSWORD 'password'";
337        let mut parser = Parser::new(query);
338        let result = parser.parse();
339        assert!(
340            result.is_ok(),
341            "Unicode username should parse: {:?}",
342            result.err()
343        );
344    }
345
346    #[test]
347    fn test_parse_create_user_unicode_password() {
348        let query = "CREATE USER testuser WITH PASSWORD 'パスワード123'";
349        let mut parser = Parser::new(query);
350        let result = parser.parse();
351        assert!(
352            result.is_ok(),
353            "Unicode password should parse: {:?}",
354            result.err()
355        );
356    }
357
358    #[test]
359    fn test_parse_create_user_special_chars_username() {
360        let query = "CREATE USER user_123_test WITH PASSWORD 'password'";
361        let mut parser = Parser::new(query);
362        let result = parser.parse();
363        assert!(
364            result.is_ok(),
365            "Username with numbers and underscores should parse: {:?}",
366            result.err()
367        );
368    }
369
370    #[test]
371    fn test_parse_create_user_special_chars_password() {
372        let query = "CREATE USER user WITH PASSWORD 'P@$$w0rd!#%&*'";
373        let mut parser = Parser::new(query);
374        let result = parser.parse();
375        assert!(
376            result.is_ok(),
377            "Password with special chars should parse: {:?}",
378            result.err()
379        );
380    }
381
382    #[test]
383    fn test_parse_create_user_rejects_with_role() {
384        let query = "CREATE USER testuser WITH PASSWORD 'pass' WITH ROLE ADMIN";
385        let mut parser = Parser::new(query);
386        assert!(
387            parser.parse().is_err(),
388            "WITH ROLE is no longer part of CREATE USER syntax"
389        );
390    }
391
392    #[test]
393    fn test_parse_alter_user_set_locked() {
394        for (value, expected) in [("true", true), ("false", false)] {
395            let query = format!("ALTER USER testuser SET LOCKED = {}", value);
396            let mut parser = Parser::new(&query);
397            let result = parser.parse();
398            assert!(
399                result.is_ok(),
400                "SET LOCKED = {} should parse: {:?}",
401                value,
402                result.err()
403            );
404            match result.expect("parsed").ast.stmt() {
405                crate::parser::ast::Stmt::AlterUser(stmt) => {
406                    assert_eq!(stmt.is_locked, Some(expected))
407                }
408                other => panic!("expected AlterUser, got {:?}", other),
409            }
410        }
411    }
412
413    #[test]
414    fn test_parse_alter_user_set_locked_rejects_non_boolean() {
415        for query in [
416            "ALTER USER testuser SET LOCKED = yes",
417            "ALTER USER testuser SET LOCKED = 1",
418        ] {
419            let mut parser = Parser::new(query);
420            assert!(
421                parser.parse().is_err(),
422                "{} should be rejected, not silently treated as false",
423                query
424            );
425        }
426    }
427
428    #[test]
429    fn test_parse_alter_user_set_role_rejected() {
430        let mut parser = Parser::new("ALTER USER testuser SET ROLE = DBA");
431        assert!(
432            parser.parse().is_err(),
433            "SET ROLE is no longer part of ALTER USER syntax"
434        );
435    }
436
437    #[test]
438    fn test_parse_create_user_case_insensitive_keywords() {
439        let query = "create user testuser with password 'pass'";
440        let mut parser = Parser::new(query);
441        let result = parser.parse();
442        assert!(
443            result.is_ok(),
444            "Lowercase keywords should parse: {:?}",
445            result.err()
446        );
447    }
448
449    #[test]
450    fn test_parse_alter_user_unicode() {
451        let query = "ALTER USER ユーザー WITH PASSWORD 'ニューパスワード'";
452        let mut parser = Parser::new(query);
453        let result = parser.parse();
454        assert!(
455            result.is_ok(),
456            "ALTER USER with unicode should parse: {:?}",
457            result.err()
458        );
459    }
460
461    #[test]
462    fn test_parse_drop_user_unicode() {
463        let query = "DROP USER ユーザー";
464        let mut parser = Parser::new(query);
465        let result = parser.parse();
466        assert!(
467            result.is_ok(),
468            "DROP USER with unicode should parse: {:?}",
469            result.err()
470        );
471    }
472
473    #[test]
474    fn test_parse_change_password_unicode() {
475        let query = "CHANGE PASSWORD ユーザー 'オールドパスワード' TO 'ニューパスワード'";
476        let mut parser = Parser::new(query);
477        let result = parser.parse();
478        assert!(
479            result.is_ok(),
480            "CHANGE PASSWORD with unicode should parse: {:?}",
481            result.err()
482        );
483    }
484
485    #[test]
486    fn test_parse_grant_unicode() {
487        let query = "GRANT ADMIN ON スペース TO ユーザー";
488        let mut parser = Parser::new(query);
489        let result = parser.parse();
490        assert!(
491            result.is_ok(),
492            "GRANT with unicode should parse: {:?}",
493            result.err()
494        );
495    }
496
497    #[test]
498    fn test_parse_revoke_unicode() {
499        let query = "REVOKE ADMIN ON スペース FROM ユーザー";
500        let mut parser = Parser::new(query);
501        let result = parser.parse();
502        assert!(
503            result.is_ok(),
504            "REVOKE with unicode should parse: {:?}",
505            result.err()
506        );
507    }
508
509    #[test]
510    fn test_parse_describe_user_unicode() {
511        let query = "DESCRIBE USER ユーザー";
512        let mut parser = Parser::new(query);
513        let result = parser.parse();
514        assert!(
515            result.is_ok(),
516            "DESCRIBE USER with unicode should parse: {:?}",
517            result.err()
518        );
519    }
520
521    #[test]
522    fn test_parse_show_roles_with_unicode_space() {
523        let query = "SHOW ROLES IN スペース";
524        let mut parser = Parser::new(query);
525        let result = parser.parse();
526        assert!(
527            result.is_ok(),
528            "SHOW ROLES IN with unicode space should parse: {:?}",
529            result.err()
530        );
531    }
532
533    #[test]
534    fn test_parse_create_user_long_username() {
535        let long_username = "a".repeat(255);
536        let query = format!("CREATE USER {} WITH PASSWORD 'pass'", long_username);
537        let mut parser = Parser::new(&query);
538        let result = parser.parse();
539        assert!(
540            result.is_ok(),
541            "Long username should parse: {:?}",
542            result.err()
543        );
544    }
545
546    #[test]
547    fn test_parse_create_user_long_password() {
548        let long_password = "a".repeat(1000);
549        let query = format!("CREATE USER user WITH PASSWORD '{}'", long_password);
550        let mut parser = Parser::new(&query);
551        let result = parser.parse();
552        assert!(
553            result.is_ok(),
554            "Long password should parse: {:?}",
555            result.err()
556        );
557    }
558
559    #[test]
560    fn test_parse_create_user_empty_like_password() {
561        let query = "CREATE USER user WITH PASSWORD 'a'";
562        let mut parser = Parser::new(query);
563        let result = parser.parse();
564        assert!(
565            result.is_ok(),
566            "Single char password should parse: {:?}",
567            result.err()
568        );
569    }
570
571    #[test]
572    fn test_parse_grant_case_insensitive_role() {
573        for role_case in ["ADMIN", "Admin", "admin"] {
574            let query = format!("GRANT {} ON space TO user", role_case);
575            let mut parser = Parser::new(&query);
576            let result = parser.parse();
577            assert!(
578                result.is_ok(),
579                "GRANT with role case {} should parse: {:?}",
580                role_case,
581                result.err()
582            );
583        }
584    }
585
586    #[test]
587    fn test_parse_show_roles_with_and_without_space() {
588        let query1 = "SHOW ROLES";
589        let query2 = "SHOW ROLES IN myspace";
590
591        let mut parser1 = Parser::new(query1);
592        let result1 = parser1.parse();
593        assert!(
594            result1.is_ok(),
595            "SHOW ROLES without space should parse: {:?}",
596            result1.err()
597        );
598
599        let mut parser2 = Parser::new(query2);
600        let result2 = parser2.parse();
601        assert!(
602            result2.is_ok(),
603            "SHOW ROLES with space should parse: {:?}",
604            result2.err()
605        );
606    }
607
608    #[test]
609    fn test_parse_whitespace_handling() {
610        let queries = vec![
611            "CREATE  USER  user  WITH  PASSWORD  'pass'",
612            "CREATE\tUSER\tuser\tWITH\tPASSWORD\t'pass'",
613            "CREATE\nUSER\nuser\nWITH\nPASSWORD\n'pass'",
614        ];
615
616        for query in queries {
617            let mut parser = Parser::new(query);
618            let result = parser.parse();
619            assert!(
620                result.is_ok(),
621                "Query with extra whitespace should parse: {:?}",
622                result.err()
623            );
624        }
625    }
626}