1use crate::ast::{
35 AlterUserAttribute, AlterUserStmt, CreateUserStmt, GrantObject, GrantObjectKind,
36 GrantPrincipalRef, GrantStmt, LintPolicySource, PolicyPrincipalRef, PolicyResourceRef,
37 PolicyUserRef, QueryExpr, RevokeStmt,
38};
39use crate::lexer::Token;
40use crate::parser::{ParseError, Parser};
41
42impl<'a> Parser<'a> {
43 pub fn parse_create_user_statement(&mut self) -> Result<CreateUserStmt, ParseError> {
46 if !self.consume_ident_ci("USER")? {
47 return Err(ParseError::expected(
48 vec!["USER"],
49 self.peek(),
50 self.position(),
51 ));
52 }
53 let (tenant, username) = self.parse_user_name()?;
54
55 let mut password = None;
56 let mut role = "read".to_string();
57
58 let _ = self.consume(&Token::With)? || self.consume_ident_ci("WITH")?;
59 loop {
60 if self.consume_ident_ci("PASSWORD")? {
61 password = Some(self.parse_string()?);
62 } else if self.consume_ident_ci("ROLE")? {
63 role = self.expect_ident()?.to_ascii_lowercase();
64 } else {
65 break;
66 }
67 }
68
69 let password = password
70 .ok_or_else(|| ParseError::expected(vec!["PASSWORD"], self.peek(), self.position()))?;
71
72 Ok(CreateUserStmt {
73 tenant,
74 username,
75 password,
76 role,
77 })
78 }
79
80 pub fn parse_grant_statement(&mut self) -> Result<GrantStmt, ParseError> {
84 self.advance()?;
86
87 let (actions, all, columns) = self.parse_privilege_list()?;
88 self.expect(Token::On)?;
89 let object_kind = self.parse_grant_object_kind()?;
90 let objects = self.parse_grant_object_list(&object_kind)?;
91 self.expect(Token::To)?;
92 let principals = self.parse_grant_principal_list()?;
93
94 let with_grant_option = self.consume_grant_option_suffix()?;
95
96 Ok(GrantStmt {
97 actions,
98 columns,
99 object_kind,
100 objects,
101 principals,
102 with_grant_option,
103 all,
104 })
105 }
106
107 pub fn parse_revoke_statement(&mut self) -> Result<RevokeStmt, ParseError> {
110 self.advance()?;
112
113 let grant_option_for = self.consume_grant_option_for_prefix()?;
115
116 let (actions, all, columns) = self.parse_privilege_list()?;
117 self.expect(Token::On)?;
118 let object_kind = self.parse_grant_object_kind()?;
119 let objects = self.parse_grant_object_list(&object_kind)?;
120 self.expect(Token::From)?;
121 let principals = self.parse_grant_principal_list()?;
122
123 Ok(RevokeStmt {
124 actions,
125 columns,
126 object_kind,
127 objects,
128 principals,
129 grant_option_for,
130 all,
131 })
132 }
133
134 pub fn parse_alter_user_statement(&mut self) -> Result<AlterUserStmt, ParseError> {
137 if !self.consume_ident_ci("USER")? {
139 return Err(ParseError::expected(
140 vec!["USER"],
141 self.peek(),
142 self.position(),
143 ));
144 }
145 let (tenant, username) = self.parse_user_name()?;
146
147 let mut attributes = Vec::new();
148 loop {
149 if self.consume_ident_ci("VALID")? {
150 if !self.consume_ident_ci("UNTIL")? {
151 return Err(ParseError::expected(
152 vec!["UNTIL"],
153 self.peek(),
154 self.position(),
155 ));
156 }
157 let ts = self.parse_string()?;
158 attributes.push(AlterUserAttribute::ValidUntil(ts));
159 } else if self.consume_ident_ci("CONNECTION")? {
160 if !self.consume(&Token::Limit)? && !self.consume_ident_ci("LIMIT")? {
161 return Err(ParseError::expected(
162 vec!["LIMIT"],
163 self.peek(),
164 self.position(),
165 ));
166 }
167 let n = self.parse_integer()?;
168 attributes.push(AlterUserAttribute::ConnectionLimit(n));
169 } else if self.consume(&Token::Enable)? {
170 attributes.push(AlterUserAttribute::Enable);
171 } else if self.consume(&Token::Disable)? {
172 attributes.push(AlterUserAttribute::Disable);
173 } else if self.consume(&Token::Set)? {
174 if !self.consume_ident_ci("SEARCH_PATH")? {
176 return Err(ParseError::expected(
177 vec!["search_path"],
178 self.peek(),
179 self.position(),
180 ));
181 }
182 if !self.consume(&Token::Eq)? && !self.consume(&Token::To)? {
183 return Err(ParseError::expected(
184 vec!["="],
185 self.peek(),
186 self.position(),
187 ));
188 }
189 let value = self.parse_string()?;
190 attributes.push(AlterUserAttribute::SetSearchPath(value));
191 } else if self.consume(&Token::Add)? || self.consume_ident_ci("ADD")? {
192 if !self.consume(&Token::Group)? && !self.consume_ident_ci("GROUP")? {
193 return Err(ParseError::expected(
194 vec!["GROUP"],
195 self.peek(),
196 self.position(),
197 ));
198 }
199 let group = self.expect_ident()?;
200 attributes.push(AlterUserAttribute::AddGroup(group));
201 } else if self.consume(&Token::Drop)? || self.consume_ident_ci("DROP")? {
202 if !self.consume(&Token::Group)? && !self.consume_ident_ci("GROUP")? {
203 return Err(ParseError::expected(
204 vec!["GROUP"],
205 self.peek(),
206 self.position(),
207 ));
208 }
209 let group = self.expect_ident()?;
210 attributes.push(AlterUserAttribute::DropGroup(group));
211 } else if self.consume_ident_ci("PASSWORD")? {
212 let pw = self.parse_string()?;
213 attributes.push(AlterUserAttribute::Password(pw));
214 } else {
215 break;
216 }
217 }
218
219 if attributes.is_empty() {
220 return Err(ParseError::expected(
221 vec![
222 "VALID",
223 "CONNECTION",
224 "ENABLE",
225 "DISABLE",
226 "SET",
227 "ADD",
228 "DROP",
229 "PASSWORD",
230 ],
231 self.peek(),
232 self.position(),
233 ));
234 }
235
236 Ok(AlterUserStmt {
237 tenant,
238 username,
239 attributes,
240 })
241 }
242
243 pub fn parse_create_iam_policy_after_keywords(&mut self) -> Result<QueryExpr, ParseError> {
252 let id = self.parse_string()?;
253 if !self.consume(&Token::As)? && !self.consume_ident_ci("AS")? {
254 return Err(ParseError::expected(
255 vec!["AS"],
256 self.peek(),
257 self.position(),
258 ));
259 }
260 let json = self.parse_string()?;
261 Ok(QueryExpr::CreateIamPolicy { id, json })
262 }
263
264 pub fn parse_drop_iam_policy_after_keywords(&mut self) -> Result<QueryExpr, ParseError> {
267 let id = self.parse_string()?;
268 Ok(QueryExpr::DropIamPolicy { id })
269 }
270
271 pub fn parse_attach_policy(&mut self) -> Result<QueryExpr, ParseError> {
275 self.advance()?; if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
277 return Err(ParseError::expected(
278 vec!["POLICY"],
279 self.peek(),
280 self.position(),
281 ));
282 }
283 let policy_id = self.parse_string()?;
284 self.expect(Token::To)?;
285 let principal = self.parse_iam_principal_kind()?;
286 Ok(QueryExpr::AttachPolicy {
287 policy_id,
288 principal,
289 })
290 }
291
292 pub fn parse_detach_policy(&mut self) -> Result<QueryExpr, ParseError> {
294 self.advance()?; if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
296 return Err(ParseError::expected(
297 vec!["POLICY"],
298 self.peek(),
299 self.position(),
300 ));
301 }
302 let policy_id = self.parse_string()?;
303 self.expect(Token::From)?;
304 let principal = self.parse_iam_principal_kind()?;
305 Ok(QueryExpr::DetachPolicy {
306 policy_id,
307 principal,
308 })
309 }
310
311 pub fn parse_simulate_policy(&mut self) -> Result<QueryExpr, ParseError> {
313 self.advance()?; let user = self.parse_iam_user_ref()?;
315 if !self.consume_ident_ci("ACTION")? {
316 return Err(ParseError::expected(
317 vec!["ACTION"],
318 self.peek(),
319 self.position(),
320 ));
321 }
322 let action = self.parse_iam_action_token()?;
323 self.expect(Token::On)?;
324 let resource = self.parse_iam_resource_ref()?;
325 Ok(QueryExpr::SimulatePolicy {
326 user,
327 action,
328 resource,
329 })
330 }
331
332 pub fn parse_migrate_policy_mode(&mut self) -> Result<QueryExpr, ParseError> {
336 self.advance()?; if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
338 return Err(ParseError::expected(
339 vec!["POLICY"],
340 self.peek(),
341 self.position(),
342 ));
343 }
344 if !self.consume(&Token::Mode)? && !self.consume_ident_ci("MODE")? {
345 return Err(ParseError::expected(
346 vec!["MODE"],
347 self.peek(),
348 self.position(),
349 ));
350 }
351 if !self.consume(&Token::To)? && !self.consume_ident_ci("TO")? {
352 return Err(ParseError::expected(
353 vec!["TO"],
354 self.peek(),
355 self.position(),
356 ));
357 }
358 let target = self.parse_string()?;
359 let dry_run = if self.consume_ident_ci("DRY")? {
360 if !self.consume_ident_ci("RUN")? {
361 return Err(ParseError::expected(
362 vec!["RUN"],
363 self.peek(),
364 self.position(),
365 ));
366 }
367 true
368 } else {
369 false
370 };
371 Ok(QueryExpr::MigratePolicyMode { target, dry_run })
372 }
373
374 pub fn parse_lint_policy(&mut self) -> Result<QueryExpr, ParseError> {
378 self.advance()?; if !self.consume(&Token::Policy)? && !self.consume_ident_ci("POLICY")? {
380 return Err(ParseError::expected(
381 vec!["POLICY"],
382 self.peek(),
383 self.position(),
384 ));
385 }
386 if self.consume(&Token::Json)? || self.consume_ident_ci("JSON")? {
390 let json = self.parse_string()?;
391 return Ok(QueryExpr::LintPolicy {
392 source: LintPolicySource::Json(json),
393 });
394 }
395 let id = self.parse_string()?;
396 Ok(QueryExpr::LintPolicy {
397 source: LintPolicySource::Id(id),
398 })
399 }
400
401 pub fn parse_show_iam_after_show(&mut self) -> Result<Option<QueryExpr>, ParseError> {
405 if self.consume_ident_ci("POLICIES")? {
407 if self.consume(&Token::For)? || self.consume_ident_ci("FOR")? {
409 let principal = self.parse_iam_principal_kind()?;
410 return Ok(Some(QueryExpr::ShowPolicies {
411 filter: Some(principal),
412 }));
413 }
414 return Ok(Some(QueryExpr::ShowPolicies { filter: None }));
415 }
416 if self.consume_ident_ci("EFFECTIVE")? {
417 if !self.consume_ident_ci("PERMISSIONS")? {
418 return Err(ParseError::expected(
419 vec!["PERMISSIONS"],
420 self.peek(),
421 self.position(),
422 ));
423 }
424 if !self.consume(&Token::For)? && !self.consume_ident_ci("FOR")? {
425 return Err(ParseError::expected(
426 vec!["FOR"],
427 self.peek(),
428 self.position(),
429 ));
430 }
431 let user = self.parse_iam_user_ref()?;
432 let resource = if self.consume(&Token::On)? || self.consume_ident_ci("ON")? {
433 Some(self.parse_iam_resource_ref()?)
434 } else {
435 None
436 };
437 return Ok(Some(QueryExpr::ShowEffectivePermissions { user, resource }));
438 }
439 Ok(None)
440 }
441
442 pub(crate) fn parse_iam_principal_kind(&mut self) -> Result<PolicyPrincipalRef, ParseError> {
445 if self.consume_ident_ci("USER")? {
446 let user = self.parse_iam_user_ref()?;
447 Ok(PolicyPrincipalRef::User(user))
448 } else if self.consume(&Token::Group)? || self.consume_ident_ci("GROUP")? {
449 let g = self.expect_ident()?;
450 Ok(PolicyPrincipalRef::Group(g))
451 } else {
452 Err(ParseError::expected(
453 vec!["USER", "GROUP"],
454 self.peek(),
455 self.position(),
456 ))
457 }
458 }
459
460 fn parse_iam_user_ref(&mut self) -> Result<PolicyUserRef, ParseError> {
461 let (tenant, username) = self.parse_user_name()?;
462 Ok(PolicyUserRef { tenant, username })
463 }
464
465 fn parse_iam_resource_ref(&mut self) -> Result<PolicyResourceRef, ParseError> {
466 if matches!(self.peek(), Token::String(_)) {
470 let raw = self.parse_string()?;
471 let (kind, name) = raw.split_once(':').ok_or_else(|| {
472 ParseError::new(
473 format!("resource must be `kind:name`, got {raw:?}"),
478 self.position(),
479 )
480 })?;
481 return Ok(PolicyResourceRef {
482 kind: kind.to_string(),
483 name: name.to_string(),
484 });
485 }
486 let kind = self.expect_ident_or_keyword()?.to_ascii_lowercase();
490 if !self.consume(&Token::Colon)? {
491 return Err(ParseError::expected(
492 vec![":"],
493 self.peek(),
494 self.position(),
495 ));
496 }
497 let mut name = self.expect_ident_or_keyword()?;
500 while self.consume(&Token::Dot)? {
501 let next = self.expect_ident_or_keyword()?;
502 name.push('.');
503 name.push_str(&next);
504 }
505 Ok(PolicyResourceRef { kind, name })
506 }
507
508 fn parse_iam_action_token(&mut self) -> Result<String, ParseError> {
509 if matches!(self.peek(), Token::String(_)) {
510 return self.parse_string();
511 }
512 match self.peek() {
515 Token::Select => {
516 self.advance()?;
517 Ok("select".into())
518 }
519 Token::Insert => {
520 self.advance()?;
521 Ok("insert".into())
522 }
523 Token::Update => {
524 self.advance()?;
525 Ok("update".into())
526 }
527 Token::Delete => {
528 self.advance()?;
529 Ok("delete".into())
530 }
531 Token::Ident(_) => {
532 let raw = self.expect_ident()?;
533 Ok(raw.to_ascii_lowercase())
534 }
535 other => Err(ParseError::expected(
536 vec!["action keyword"],
537 other,
538 self.position(),
539 )),
540 }
541 }
542
543 fn parse_privilege_list(
552 &mut self,
553 ) -> Result<(Vec<String>, bool, Option<Vec<String>>), ParseError> {
554 if self.consume(&Token::All)? || self.consume_ident_ci("ALL")? {
556 let _ = self.consume_ident_ci("PRIVILEGES")?;
557 let columns = self.parse_optional_column_list()?;
558 return Ok((vec!["ALL".to_string()], true, columns));
559 }
560
561 let mut actions = Vec::new();
563 loop {
564 actions.push(self.parse_privilege_keyword()?);
565 if !self.consume(&Token::Comma)? {
566 break;
567 }
568 }
569 let columns = self.parse_optional_column_list()?;
570 Ok((actions, false, columns))
571 }
572
573 fn parse_privilege_keyword(&mut self) -> Result<String, ParseError> {
577 match self.peek() {
578 Token::Select => {
579 self.advance()?;
580 Ok("SELECT".to_string())
581 }
582 Token::Insert => {
583 self.advance()?;
584 Ok("INSERT".to_string())
585 }
586 Token::Update => {
587 self.advance()?;
588 Ok("UPDATE".to_string())
589 }
590 Token::Delete => {
591 self.advance()?;
592 Ok("DELETE".to_string())
593 }
594 Token::Truncate => {
595 self.advance()?;
596 Ok("TRUNCATE".to_string())
597 }
598 Token::Ident(name)
599 if matches!(
600 name.to_ascii_uppercase().as_str(),
601 "REFERENCES" | "EXECUTE" | "USAGE"
602 ) =>
603 {
604 let upper = name.to_ascii_uppercase();
605 self.advance()?;
606 Ok(upper)
607 }
608 other => Err(ParseError::expected(
609 vec![
610 "SELECT",
611 "INSERT",
612 "UPDATE",
613 "DELETE",
614 "TRUNCATE",
615 "REFERENCES",
616 "EXECUTE",
617 "USAGE",
618 ],
619 other,
620 self.position(),
621 )),
622 }
623 }
624
625 fn parse_optional_column_list(&mut self) -> Result<Option<Vec<String>>, ParseError> {
628 if !self.check(&Token::LParen) {
629 return Ok(None);
630 }
631 self.expect(Token::LParen)?;
632 let mut cols = Vec::new();
633 loop {
634 cols.push(self.expect_ident()?);
635 if !self.consume(&Token::Comma)? {
636 break;
637 }
638 }
639 self.expect(Token::RParen)?;
640 Ok(Some(cols))
641 }
642
643 fn parse_grant_object_kind(&mut self) -> Result<GrantObjectKind, ParseError> {
647 if self.consume(&Token::Table)? {
648 Ok(GrantObjectKind::Table)
649 } else if self.consume(&Token::Schema)? {
650 Ok(GrantObjectKind::Schema)
651 } else if self.consume_ident_ci("DATABASE")? {
652 Ok(GrantObjectKind::Database)
653 } else if self.consume_ident_ci("FUNCTION")? {
654 Ok(GrantObjectKind::Function)
655 } else {
656 Ok(GrantObjectKind::Table)
658 }
659 }
660
661 fn parse_grant_object_list(
663 &mut self,
664 kind: &GrantObjectKind,
665 ) -> Result<Vec<GrantObject>, ParseError> {
666 let mut out = Vec::new();
667 loop {
668 if matches!(kind, GrantObjectKind::Database) {
671 let name = self.expect_ident()?;
672 out.push(GrantObject { schema: None, name });
673 } else {
674 let first = self.expect_ident()?;
675 let (schema, name) = if self.consume(&Token::Dot)? {
676 let second = self.expect_ident_or_keyword()?;
677 (Some(first), second)
678 } else {
679 (None, first)
680 };
681 out.push(GrantObject { schema, name });
682 }
683 if !self.consume(&Token::Comma)? {
684 break;
685 }
686 }
687 Ok(out)
688 }
689
690 fn parse_grant_principal_list(&mut self) -> Result<Vec<GrantPrincipalRef>, ParseError> {
695 let mut out = Vec::new();
696 loop {
697 if self.consume_ident_ci("PUBLIC")? {
698 out.push(GrantPrincipalRef::Public);
699 } else if self.consume(&Token::Group)? || self.consume_ident_ci("GROUP")? {
700 let g = self.expect_ident()?;
701 out.push(GrantPrincipalRef::Group(g));
702 } else {
703 let (tenant, name) = self.parse_user_name()?;
704 out.push(GrantPrincipalRef::User { tenant, name });
705 }
706 if !self.consume(&Token::Comma)? {
707 break;
708 }
709 }
710 Ok(out)
711 }
712
713 fn parse_user_name(&mut self) -> Result<(Option<String>, String), ParseError> {
715 let first = self.expect_ident()?;
716 if self.consume(&Token::Dot)? {
717 let name = self.expect_ident()?;
718 Ok((Some(first), name))
719 } else {
720 Ok((None, first))
721 }
722 }
723
724 fn consume_grant_option_suffix(&mut self) -> Result<bool, ParseError> {
726 if self.consume(&Token::With)? {
727 if !self.consume_ident_ci("GRANT")? {
728 return Err(ParseError::expected(
729 vec!["GRANT"],
730 self.peek(),
731 self.position(),
732 ));
733 }
734 if !self.consume_ident_ci("OPTION")? {
735 return Err(ParseError::expected(
736 vec!["OPTION"],
737 self.peek(),
738 self.position(),
739 ));
740 }
741 Ok(true)
742 } else {
743 Ok(false)
744 }
745 }
746
747 fn consume_grant_option_for_prefix(&mut self) -> Result<bool, ParseError> {
749 let saved_pos = self.position();
752 if !matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("GRANT")) {
753 return Ok(false);
754 }
755 self.advance()?;
757 if !self.consume_ident_ci("OPTION")? {
758 return Err(ParseError::expected(vec!["OPTION"], self.peek(), saved_pos));
762 }
763 if !self.consume(&Token::For)? && !self.consume_ident_ci("FOR")? {
764 return Err(ParseError::expected(vec!["FOR"], self.peek(), saved_pos));
765 }
766 Ok(true)
767 }
768}
769
770#[cfg(test)]
771mod tests {
772 use super::*;
773
774 fn parser(input: &str) -> Parser<'_> {
775 Parser::new(input).unwrap_or_else(|err| panic!("failed to lex {input:?}: {err:?}"))
776 }
777
778 #[test]
779 fn parse_grant_statement_covers_columns_default_table_and_principals() {
780 let grant = parser(
781 "GRANT SELECT, UPDATE (id, email) ON public.users TO PUBLIC, GROUP analysts, tenant.alice WITH GRANT OPTION",
782 )
783 .parse_grant_statement()
784 .expect("grant");
785
786 assert_eq!(grant.actions, vec!["SELECT", "UPDATE"]);
787 assert_eq!(
788 grant.columns.as_deref(),
789 Some(&["id".to_string(), "email".to_string()][..])
790 );
791 assert_eq!(grant.object_kind, GrantObjectKind::Table);
792 assert_eq!(grant.objects.len(), 1);
793 assert_eq!(grant.objects[0].schema.as_deref(), Some("public"));
794 assert_eq!(grant.objects[0].name, "users");
795 assert!(matches!(grant.principals[0], GrantPrincipalRef::Public));
796 assert!(matches!(
797 &grant.principals[1],
798 GrantPrincipalRef::Group(group) if group == "analysts"
799 ));
800 assert!(matches!(
801 &grant.principals[2],
802 GrantPrincipalRef::User { tenant: Some(t), name } if t == "tenant" && name == "alice"
803 ));
804 assert!(grant.with_grant_option);
805 assert!(!grant.all);
806 }
807
808 #[test]
809 fn parse_revoke_statement_covers_grant_option_for_all_and_function_objects() {
810 let revoke = parser(
811 "REVOKE GRANT OPTION FOR ALL PRIVILEGES (id) ON FUNCTION public.recalc FROM GROUP analysts",
812 )
813 .parse_revoke_statement()
814 .expect("revoke");
815
816 assert!(revoke.grant_option_for);
817 assert!(revoke.all);
818 assert_eq!(revoke.actions, vec!["ALL"]);
819 assert_eq!(revoke.columns.as_deref(), Some(&["id".to_string()][..]));
820 assert_eq!(revoke.object_kind, GrantObjectKind::Function);
821 assert_eq!(revoke.objects[0].schema.as_deref(), Some("public"));
822 assert_eq!(revoke.objects[0].name, "recalc");
823 assert!(matches!(
824 &revoke.principals[0],
825 GrantPrincipalRef::Group(group) if group == "analysts"
826 ));
827
828 let revoke = parser("REVOKE USAGE ON SCHEMA analytics FROM bob")
829 .parse_revoke_statement()
830 .expect("revoke without grant option");
831 assert!(!revoke.grant_option_for);
832 assert_eq!(revoke.object_kind, GrantObjectKind::Schema);
833 assert_eq!(revoke.objects[0].name, "analytics");
834 }
835
836 #[test]
837 fn parse_grant_and_revoke_option_errors_are_specific() {
838 let err = parser("GRANT SELECT ON TABLE users TO alice WITH OPTION")
839 .parse_grant_statement()
840 .unwrap_err();
841 assert!(err.to_string().contains("expected: GRANT"), "{err}");
842
843 let err = parser("GRANT SELECT ON TABLE users TO alice WITH GRANT")
844 .parse_grant_statement()
845 .unwrap_err();
846 assert!(err.to_string().contains("expected: OPTION"), "{err}");
847
848 let err = parser("REVOKE GRANT SELECT ON TABLE users FROM alice")
849 .parse_revoke_statement()
850 .unwrap_err();
851 assert!(err.to_string().contains("expected: OPTION"), "{err}");
852
853 let err = parser("REVOKE GRANT OPTION SELECT ON TABLE users FROM alice")
854 .parse_revoke_statement()
855 .unwrap_err();
856 assert!(err.to_string().contains("expected: FOR"), "{err}");
857 }
858
859 #[test]
860 fn parse_alter_user_statement_covers_attribute_variants_and_errors() {
861 let mut p = parser(
862 "ALTER USER tenant.bob VALID UNTIL '2030-01-01' CONNECTION LIMIT 10 ENABLE \
863 SET search_path TO 'public,analytics' ADD GROUP analysts DROP GROUP temp PASSWORD 'pw'",
864 );
865 p.expect(Token::Alter).expect("ALTER");
866 let stmt = p.parse_alter_user_statement().expect("alter user");
867
868 assert_eq!(stmt.tenant.as_deref(), Some("tenant"));
869 assert_eq!(stmt.username, "bob");
870 assert!(matches!(
871 &stmt.attributes[0],
872 AlterUserAttribute::ValidUntil(value) if value == "2030-01-01"
873 ));
874 assert!(matches!(
875 stmt.attributes[1],
876 AlterUserAttribute::ConnectionLimit(10)
877 ));
878 assert!(matches!(stmt.attributes[2], AlterUserAttribute::Enable));
879 assert!(matches!(
880 &stmt.attributes[3],
881 AlterUserAttribute::SetSearchPath(value) if value == "public,analytics"
882 ));
883 assert!(matches!(
884 &stmt.attributes[4],
885 AlterUserAttribute::AddGroup(group) if group == "analysts"
886 ));
887 assert!(matches!(
888 &stmt.attributes[5],
889 AlterUserAttribute::DropGroup(group) if group == "temp"
890 ));
891 assert!(matches!(
892 &stmt.attributes[6],
893 AlterUserAttribute::Password(password) if password == "pw"
894 ));
895
896 let mut p = parser("ALTER USER alice");
897 p.expect(Token::Alter).expect("ALTER");
898 let err = p.parse_alter_user_statement().unwrap_err();
899 assert!(err.to_string().contains("expected:"), "{err}");
900
901 let mut p = parser("ALTER USER alice ADD ROLE analysts");
902 p.expect(Token::Alter).expect("ALTER");
903 let err = p.parse_alter_user_statement().unwrap_err();
904 assert!(err.to_string().contains("expected: GROUP"), "{err}");
905 }
906
907 #[test]
908 fn parse_create_user_statement_covers_role_and_password_errors() {
909 let mut p = parser("CREATE USER tenant.alice WITH PASSWORD 'pw' ROLE admin");
910 p.expect(Token::Create).expect("CREATE");
911 let stmt = p.parse_create_user_statement().expect("create user");
912 assert_eq!(stmt.tenant.as_deref(), Some("tenant"));
913 assert_eq!(stmt.username, "alice");
914 assert_eq!(stmt.password, "pw");
915 assert_eq!(stmt.role, "admin");
916
917 let mut p = parser("CREATE USER bob PASSWORD 'pw'");
918 p.expect(Token::Create).expect("CREATE");
919 let stmt = p.parse_create_user_statement().expect("create user");
920 assert_eq!(stmt.tenant, None);
921 assert_eq!(stmt.username, "bob");
922 assert_eq!(stmt.role, "read");
923
924 let mut p = parser("CREATE USER alice ROLE write");
925 p.expect(Token::Create).expect("CREATE");
926 let err = p.parse_create_user_statement().unwrap_err();
927 assert!(err.to_string().contains("expected: PASSWORD"), "{err}");
928 }
929
930 #[test]
931 fn parse_iam_policy_helpers_cover_policy_sources_and_principals() {
932 assert!(matches!(
933 parser("'readonly' AS '{\"Statement\":[]}'")
934 .parse_create_iam_policy_after_keywords()
935 .expect("create iam policy"),
936 QueryExpr::CreateIamPolicy { ref id, ref json }
937 if id == "readonly" && json == "{\"Statement\":[]}"
938 ));
939 assert!(matches!(
940 parser("'readonly'")
941 .parse_drop_iam_policy_after_keywords()
942 .expect("drop iam policy"),
943 QueryExpr::DropIamPolicy { ref id } if id == "readonly"
944 ));
945 assert!(matches!(
946 parser("LINT POLICY JSON '{\"Statement\":[]}'")
947 .parse_lint_policy()
948 .expect("lint json"),
949 QueryExpr::LintPolicy {
950 source: LintPolicySource::Json(ref json),
951 } if json == "{\"Statement\":[]}"
952 ));
953 assert!(matches!(
954 parser("LINT POLICY 'readonly'")
955 .parse_lint_policy()
956 .expect("lint id"),
957 QueryExpr::LintPolicy {
958 source: LintPolicySource::Id(ref id),
959 } if id == "readonly"
960 ));
961 assert!(matches!(
962 parser("MIGRATE POLICY MODE TO 'policy_only' DRY RUN")
963 .parse_migrate_policy_mode()
964 .expect("migrate policy mode"),
965 QueryExpr::MigratePolicyMode { ref target, dry_run }
966 if target == "policy_only" && dry_run
967 ));
968
969 assert!(matches!(
970 parser("ATTACH POLICY 'readonly' TO USER tenant.alice")
971 .parse_attach_policy()
972 .expect("attach policy"),
973 QueryExpr::AttachPolicy {
974 ref policy_id,
975 principal: PolicyPrincipalRef::User(ref user),
976 } if policy_id == "readonly"
977 && user.tenant.as_deref() == Some("tenant")
978 && user.username == "alice"
979 ));
980 assert!(matches!(
981 parser("DETACH POLICY 'readonly' FROM GROUP analysts")
982 .parse_detach_policy()
983 .expect("detach policy"),
984 QueryExpr::DetachPolicy {
985 ref policy_id,
986 principal: PolicyPrincipalRef::Group(ref group),
987 } if policy_id == "readonly" && group == "analysts"
988 ));
989 }
990
991 #[test]
992 fn parse_show_and_simulate_helpers_cover_resources_and_action_errors() {
993 let mut p = parser("SHOW POLICIES FOR USER tenant.alice");
994 p.advance().expect("SHOW");
995 assert!(matches!(
996 p.parse_show_iam_after_show()
997 .expect("show policies")
998 .expect("iam show"),
999 QueryExpr::ShowPolicies {
1000 filter: Some(PolicyPrincipalRef::User(ref user)),
1001 } if user.tenant.as_deref() == Some("tenant") && user.username == "alice"
1002 ));
1003
1004 let mut p = parser("SHOW EFFECTIVE PERMISSIONS FOR alice ON TABLE:public.orders");
1005 p.advance().expect("SHOW");
1006 assert!(matches!(
1007 p.parse_show_iam_after_show()
1008 .expect("show effective")
1009 .expect("iam show"),
1010 QueryExpr::ShowEffectivePermissions {
1011 ref user,
1012 resource: Some(ref resource),
1013 } if user.tenant.is_none()
1014 && user.username == "alice"
1015 && resource.kind == "table"
1016 && resource.name == "public.orders"
1017 ));
1018
1019 assert!(matches!(
1020 parser("SIMULATE alice ACTION DELETE ON 'table:public.orders'")
1021 .parse_simulate_policy()
1022 .expect("simulate"),
1023 QueryExpr::SimulatePolicy {
1024 ref user,
1025 ref action,
1026 ref resource,
1027 } if user.username == "alice"
1028 && action == "delete"
1029 && resource.kind == "table"
1030 && resource.name == "public.orders"
1031 ));
1032
1033 let err = parser("SIMULATE alice ACTION 42 ON table:public.orders")
1034 .parse_simulate_policy()
1035 .unwrap_err();
1036 assert!(
1037 err.to_string().contains("expected: action keyword"),
1038 "{err}"
1039 );
1040
1041 let err = parser("SIMULATE alice ACTION SELECT ON 'missing-colon'")
1042 .parse_simulate_policy()
1043 .unwrap_err();
1044 assert!(err.to_string().contains("kind:name"), "{err}");
1045 }
1046}