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::Drop => {
535 self.advance()?;
536 Ok("drop".into())
537 }
538 Token::Create => {
539 self.advance()?;
540 Ok("create".into())
541 }
542 Token::Alter => {
543 self.advance()?;
544 Ok("alter".into())
545 }
546 Token::Ident(_) => {
547 let raw = self.expect_ident()?;
548 Ok(raw.to_ascii_lowercase())
549 }
550 other => Err(ParseError::expected(
551 vec!["action keyword"],
552 other,
553 self.position(),
554 )),
555 }
556 }
557
558 fn parse_privilege_list(
567 &mut self,
568 ) -> Result<(Vec<String>, bool, Option<Vec<String>>), ParseError> {
569 if self.consume(&Token::All)? || self.consume_ident_ci("ALL")? {
571 let _ = self.consume_ident_ci("PRIVILEGES")?;
572 let columns = self.parse_optional_column_list()?;
573 return Ok((vec!["ALL".to_string()], true, columns));
574 }
575
576 let mut actions = Vec::new();
578 loop {
579 actions.push(self.parse_privilege_keyword()?);
580 if !self.consume(&Token::Comma)? {
581 break;
582 }
583 }
584 let columns = self.parse_optional_column_list()?;
585 Ok((actions, false, columns))
586 }
587
588 fn parse_privilege_keyword(&mut self) -> Result<String, ParseError> {
592 match self.peek() {
593 Token::Select => {
594 self.advance()?;
595 Ok("SELECT".to_string())
596 }
597 Token::Insert => {
598 self.advance()?;
599 Ok("INSERT".to_string())
600 }
601 Token::Update => {
602 self.advance()?;
603 Ok("UPDATE".to_string())
604 }
605 Token::Delete => {
606 self.advance()?;
607 Ok("DELETE".to_string())
608 }
609 Token::Truncate => {
610 self.advance()?;
611 Ok("TRUNCATE".to_string())
612 }
613 Token::Ident(name)
614 if matches!(
615 name.to_ascii_uppercase().as_str(),
616 "REFERENCES" | "EXECUTE" | "USAGE"
617 ) =>
618 {
619 let upper = name.to_ascii_uppercase();
620 self.advance()?;
621 Ok(upper)
622 }
623 other => Err(ParseError::expected(
624 vec![
625 "SELECT",
626 "INSERT",
627 "UPDATE",
628 "DELETE",
629 "TRUNCATE",
630 "REFERENCES",
631 "EXECUTE",
632 "USAGE",
633 ],
634 other,
635 self.position(),
636 )),
637 }
638 }
639
640 fn parse_optional_column_list(&mut self) -> Result<Option<Vec<String>>, ParseError> {
643 if !self.check(&Token::LParen) {
644 return Ok(None);
645 }
646 self.expect(Token::LParen)?;
647 let mut cols = Vec::new();
648 loop {
649 cols.push(self.expect_ident()?);
650 if !self.consume(&Token::Comma)? {
651 break;
652 }
653 }
654 self.expect(Token::RParen)?;
655 Ok(Some(cols))
656 }
657
658 fn parse_grant_object_kind(&mut self) -> Result<GrantObjectKind, ParseError> {
662 if self.consume(&Token::Table)? {
663 Ok(GrantObjectKind::Table)
664 } else if self.consume(&Token::Schema)? {
665 Ok(GrantObjectKind::Schema)
666 } else if self.consume_ident_ci("DATABASE")? {
667 Ok(GrantObjectKind::Database)
668 } else if self.consume_ident_ci("FUNCTION")? {
669 Ok(GrantObjectKind::Function)
670 } else {
671 Ok(GrantObjectKind::Table)
673 }
674 }
675
676 fn parse_grant_object_list(
678 &mut self,
679 kind: &GrantObjectKind,
680 ) -> Result<Vec<GrantObject>, ParseError> {
681 let mut out = Vec::new();
682 loop {
683 if matches!(kind, GrantObjectKind::Database) {
686 let name = self.expect_ident()?;
687 out.push(GrantObject { schema: None, name });
688 } else {
689 let first = self.expect_ident()?;
690 let (schema, name) = if self.consume(&Token::Dot)? {
691 let second = self.expect_ident_or_keyword()?;
692 (Some(first), second)
693 } else {
694 (None, first)
695 };
696 out.push(GrantObject { schema, name });
697 }
698 if !self.consume(&Token::Comma)? {
699 break;
700 }
701 }
702 Ok(out)
703 }
704
705 fn parse_grant_principal_list(&mut self) -> Result<Vec<GrantPrincipalRef>, ParseError> {
710 let mut out = Vec::new();
711 loop {
712 if self.consume_ident_ci("PUBLIC")? {
713 out.push(GrantPrincipalRef::Public);
714 } else if self.consume(&Token::Group)? || self.consume_ident_ci("GROUP")? {
715 let g = self.expect_ident()?;
716 out.push(GrantPrincipalRef::Group(g));
717 } else {
718 let (tenant, name) = self.parse_user_name()?;
719 out.push(GrantPrincipalRef::User { tenant, name });
720 }
721 if !self.consume(&Token::Comma)? {
722 break;
723 }
724 }
725 Ok(out)
726 }
727
728 fn parse_user_name(&mut self) -> Result<(Option<String>, String), ParseError> {
730 let first = self.expect_ident()?;
731 if self.consume(&Token::Dot)? {
732 let name = self.expect_ident()?;
733 Ok((Some(first), name))
734 } else {
735 Ok((None, first))
736 }
737 }
738
739 fn consume_grant_option_suffix(&mut self) -> Result<bool, ParseError> {
741 if self.consume(&Token::With)? {
742 if !self.consume_ident_ci("GRANT")? {
743 return Err(ParseError::expected(
744 vec!["GRANT"],
745 self.peek(),
746 self.position(),
747 ));
748 }
749 if !self.consume_ident_ci("OPTION")? {
750 return Err(ParseError::expected(
751 vec!["OPTION"],
752 self.peek(),
753 self.position(),
754 ));
755 }
756 Ok(true)
757 } else {
758 Ok(false)
759 }
760 }
761
762 fn consume_grant_option_for_prefix(&mut self) -> Result<bool, ParseError> {
764 let saved_pos = self.position();
767 if !matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("GRANT")) {
768 return Ok(false);
769 }
770 self.advance()?;
772 if !self.consume_ident_ci("OPTION")? {
773 return Err(ParseError::expected(vec!["OPTION"], self.peek(), saved_pos));
777 }
778 if !self.consume(&Token::For)? && !self.consume_ident_ci("FOR")? {
779 return Err(ParseError::expected(vec!["FOR"], self.peek(), saved_pos));
780 }
781 Ok(true)
782 }
783}
784
785#[cfg(test)]
786mod tests {
787 use super::*;
788
789 fn parser(input: &str) -> Parser<'_> {
790 Parser::new(input).unwrap_or_else(|err| panic!("failed to lex {input:?}: {err:?}"))
791 }
792
793 #[test]
794 fn parse_grant_statement_covers_columns_default_table_and_principals() {
795 let grant = parser(
796 "GRANT SELECT, UPDATE (id, email) ON public.users TO PUBLIC, GROUP analysts, tenant.alice WITH GRANT OPTION",
797 )
798 .parse_grant_statement()
799 .expect("grant");
800
801 assert_eq!(grant.actions, vec!["SELECT", "UPDATE"]);
802 assert_eq!(
803 grant.columns.as_deref(),
804 Some(&["id".to_string(), "email".to_string()][..])
805 );
806 assert_eq!(grant.object_kind, GrantObjectKind::Table);
807 assert_eq!(grant.objects.len(), 1);
808 assert_eq!(grant.objects[0].schema.as_deref(), Some("public"));
809 assert_eq!(grant.objects[0].name, "users");
810 assert!(matches!(grant.principals[0], GrantPrincipalRef::Public));
811 assert!(matches!(
812 &grant.principals[1],
813 GrantPrincipalRef::Group(group) if group == "analysts"
814 ));
815 assert!(matches!(
816 &grant.principals[2],
817 GrantPrincipalRef::User { tenant: Some(t), name } if t == "tenant" && name == "alice"
818 ));
819 assert!(grant.with_grant_option);
820 assert!(!grant.all);
821 }
822
823 #[test]
824 fn parse_revoke_statement_covers_grant_option_for_all_and_function_objects() {
825 let revoke = parser(
826 "REVOKE GRANT OPTION FOR ALL PRIVILEGES (id) ON FUNCTION public.recalc FROM GROUP analysts",
827 )
828 .parse_revoke_statement()
829 .expect("revoke");
830
831 assert!(revoke.grant_option_for);
832 assert!(revoke.all);
833 assert_eq!(revoke.actions, vec!["ALL"]);
834 assert_eq!(revoke.columns.as_deref(), Some(&["id".to_string()][..]));
835 assert_eq!(revoke.object_kind, GrantObjectKind::Function);
836 assert_eq!(revoke.objects[0].schema.as_deref(), Some("public"));
837 assert_eq!(revoke.objects[0].name, "recalc");
838 assert!(matches!(
839 &revoke.principals[0],
840 GrantPrincipalRef::Group(group) if group == "analysts"
841 ));
842
843 let revoke = parser("REVOKE USAGE ON SCHEMA analytics FROM bob")
844 .parse_revoke_statement()
845 .expect("revoke without grant option");
846 assert!(!revoke.grant_option_for);
847 assert_eq!(revoke.object_kind, GrantObjectKind::Schema);
848 assert_eq!(revoke.objects[0].name, "analytics");
849 }
850
851 #[test]
852 fn parse_grant_and_revoke_option_errors_are_specific() {
853 let err = parser("GRANT SELECT ON TABLE users TO alice WITH OPTION")
854 .parse_grant_statement()
855 .unwrap_err();
856 assert!(err.to_string().contains("expected: GRANT"), "{err}");
857
858 let err = parser("GRANT SELECT ON TABLE users TO alice WITH GRANT")
859 .parse_grant_statement()
860 .unwrap_err();
861 assert!(err.to_string().contains("expected: OPTION"), "{err}");
862
863 let err = parser("REVOKE GRANT SELECT ON TABLE users FROM alice")
864 .parse_revoke_statement()
865 .unwrap_err();
866 assert!(err.to_string().contains("expected: OPTION"), "{err}");
867
868 let err = parser("REVOKE GRANT OPTION SELECT ON TABLE users FROM alice")
869 .parse_revoke_statement()
870 .unwrap_err();
871 assert!(err.to_string().contains("expected: FOR"), "{err}");
872 }
873
874 #[test]
875 fn parse_alter_user_statement_covers_attribute_variants_and_errors() {
876 let mut p = parser(
877 "ALTER USER tenant.bob VALID UNTIL '2030-01-01' CONNECTION LIMIT 10 ENABLE \
878 SET search_path TO 'public,analytics' ADD GROUP analysts DROP GROUP temp PASSWORD 'pw'",
879 );
880 p.expect(Token::Alter).expect("ALTER");
881 let stmt = p.parse_alter_user_statement().expect("alter user");
882
883 assert_eq!(stmt.tenant.as_deref(), Some("tenant"));
884 assert_eq!(stmt.username, "bob");
885 assert!(matches!(
886 &stmt.attributes[0],
887 AlterUserAttribute::ValidUntil(value) if value == "2030-01-01"
888 ));
889 assert!(matches!(
890 stmt.attributes[1],
891 AlterUserAttribute::ConnectionLimit(10)
892 ));
893 assert!(matches!(stmt.attributes[2], AlterUserAttribute::Enable));
894 assert!(matches!(
895 &stmt.attributes[3],
896 AlterUserAttribute::SetSearchPath(value) if value == "public,analytics"
897 ));
898 assert!(matches!(
899 &stmt.attributes[4],
900 AlterUserAttribute::AddGroup(group) if group == "analysts"
901 ));
902 assert!(matches!(
903 &stmt.attributes[5],
904 AlterUserAttribute::DropGroup(group) if group == "temp"
905 ));
906 assert!(matches!(
907 &stmt.attributes[6],
908 AlterUserAttribute::Password(password) if password == "pw"
909 ));
910
911 let mut p = parser("ALTER USER alice");
912 p.expect(Token::Alter).expect("ALTER");
913 let err = p.parse_alter_user_statement().unwrap_err();
914 assert!(err.to_string().contains("expected:"), "{err}");
915
916 let mut p = parser("ALTER USER alice ADD ROLE analysts");
917 p.expect(Token::Alter).expect("ALTER");
918 let err = p.parse_alter_user_statement().unwrap_err();
919 assert!(err.to_string().contains("expected: GROUP"), "{err}");
920 }
921
922 #[test]
923 fn parse_create_user_statement_covers_role_and_password_errors() {
924 let mut p = parser("CREATE USER tenant.alice WITH PASSWORD 'pw' ROLE admin");
925 p.expect(Token::Create).expect("CREATE");
926 let stmt = p.parse_create_user_statement().expect("create user");
927 assert_eq!(stmt.tenant.as_deref(), Some("tenant"));
928 assert_eq!(stmt.username, "alice");
929 assert_eq!(stmt.password, "pw");
930 assert_eq!(stmt.role, "admin");
931
932 let mut p = parser("CREATE USER bob PASSWORD 'pw'");
933 p.expect(Token::Create).expect("CREATE");
934 let stmt = p.parse_create_user_statement().expect("create user");
935 assert_eq!(stmt.tenant, None);
936 assert_eq!(stmt.username, "bob");
937 assert_eq!(stmt.role, "read");
938
939 let mut p = parser("CREATE USER alice ROLE write");
940 p.expect(Token::Create).expect("CREATE");
941 let err = p.parse_create_user_statement().unwrap_err();
942 assert!(err.to_string().contains("expected: PASSWORD"), "{err}");
943 }
944
945 #[test]
946 fn parse_iam_policy_helpers_cover_policy_sources_and_principals() {
947 assert!(matches!(
948 parser("'readonly' AS '{\"Statement\":[]}'")
949 .parse_create_iam_policy_after_keywords()
950 .expect("create iam policy"),
951 QueryExpr::CreateIamPolicy { ref id, ref json }
952 if id == "readonly" && json == "{\"Statement\":[]}"
953 ));
954 assert!(matches!(
955 parser("'readonly'")
956 .parse_drop_iam_policy_after_keywords()
957 .expect("drop iam policy"),
958 QueryExpr::DropIamPolicy { ref id } if id == "readonly"
959 ));
960 assert!(matches!(
961 parser("LINT POLICY JSON '{\"Statement\":[]}'")
962 .parse_lint_policy()
963 .expect("lint json"),
964 QueryExpr::LintPolicy {
965 source: LintPolicySource::Json(ref json),
966 } if json == "{\"Statement\":[]}"
967 ));
968 assert!(matches!(
969 parser("LINT POLICY 'readonly'")
970 .parse_lint_policy()
971 .expect("lint id"),
972 QueryExpr::LintPolicy {
973 source: LintPolicySource::Id(ref id),
974 } if id == "readonly"
975 ));
976 assert!(matches!(
977 parser("MIGRATE POLICY MODE TO 'policy_only' DRY RUN")
978 .parse_migrate_policy_mode()
979 .expect("migrate policy mode"),
980 QueryExpr::MigratePolicyMode { ref target, dry_run }
981 if target == "policy_only" && dry_run
982 ));
983
984 assert!(matches!(
985 parser("ATTACH POLICY 'readonly' TO USER tenant.alice")
986 .parse_attach_policy()
987 .expect("attach policy"),
988 QueryExpr::AttachPolicy {
989 ref policy_id,
990 principal: PolicyPrincipalRef::User(ref user),
991 } if policy_id == "readonly"
992 && user.tenant.as_deref() == Some("tenant")
993 && user.username == "alice"
994 ));
995 assert!(matches!(
996 parser("DETACH POLICY 'readonly' FROM GROUP analysts")
997 .parse_detach_policy()
998 .expect("detach policy"),
999 QueryExpr::DetachPolicy {
1000 ref policy_id,
1001 principal: PolicyPrincipalRef::Group(ref group),
1002 } if policy_id == "readonly" && group == "analysts"
1003 ));
1004 }
1005
1006 #[test]
1007 fn parse_show_and_simulate_helpers_cover_resources_and_action_errors() {
1008 let mut p = parser("SHOW POLICIES FOR USER tenant.alice");
1009 p.advance().expect("SHOW");
1010 assert!(matches!(
1011 p.parse_show_iam_after_show()
1012 .expect("show policies")
1013 .expect("iam show"),
1014 QueryExpr::ShowPolicies {
1015 filter: Some(PolicyPrincipalRef::User(ref user)),
1016 } if user.tenant.as_deref() == Some("tenant") && user.username == "alice"
1017 ));
1018
1019 let mut p = parser("SHOW EFFECTIVE PERMISSIONS FOR alice ON TABLE:public.orders");
1020 p.advance().expect("SHOW");
1021 assert!(matches!(
1022 p.parse_show_iam_after_show()
1023 .expect("show effective")
1024 .expect("iam show"),
1025 QueryExpr::ShowEffectivePermissions {
1026 ref user,
1027 resource: Some(ref resource),
1028 } if user.tenant.is_none()
1029 && user.username == "alice"
1030 && resource.kind == "table"
1031 && resource.name == "public.orders"
1032 ));
1033
1034 assert!(matches!(
1035 parser("SIMULATE alice ACTION DELETE ON 'table:public.orders'")
1036 .parse_simulate_policy()
1037 .expect("simulate"),
1038 QueryExpr::SimulatePolicy {
1039 ref user,
1040 ref action,
1041 ref resource,
1042 } if user.username == "alice"
1043 && action == "delete"
1044 && resource.kind == "table"
1045 && resource.name == "public.orders"
1046 ));
1047
1048 let err = parser("SIMULATE alice ACTION 42 ON table:public.orders")
1049 .parse_simulate_policy()
1050 .unwrap_err();
1051 assert!(
1052 err.to_string().contains("expected: action keyword"),
1053 "{err}"
1054 );
1055
1056 let err = parser("SIMULATE alice ACTION SELECT ON 'missing-colon'")
1057 .parse_simulate_policy()
1058 .unwrap_err();
1059 assert!(err.to_string().contains("kind:name"), "{err}");
1060 }
1061}