Skip to main content

surrealdb_sql/statements/define/
user.rs

1use common::fmt::{EscapeKwFreeIdent, QuoteStr};
2use surrealdb_types::{SqlFormat, ToSql, write_sql};
3
4use super::DefineKind;
5use crate::{Base, CoverStmts, Expr, Literal};
6
7#[derive(Clone, Debug, Default, Eq, PartialEq)]
8#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
9pub enum PassType {
10	#[default]
11	Unset,
12	Hash(String),
13	Password(String),
14}
15
16#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct DefineUserStatement {
18	pub kind: DefineKind,
19	pub name: Expr,
20	pub base: Base,
21	pub pass_type: PassType,
22	/// Optional SCRAM-SHA-256 verifier string (`SCRAM-SHA-256$...`). Additive:
23	/// it may coexist with `PASSHASH`, which is how export/import round-trips
24	/// both the Argon2 hash and the SCRAM verifier. When `PASSWORD` is given and
25	/// this is `None`, the verifier is derived from the plaintext.
26	pub scram: Option<String>,
27	pub roles: Vec<String>,
28	pub token_duration: Expr,
29	pub session_duration: Expr,
30
31	pub comment: Expr,
32}
33
34impl Default for DefineUserStatement {
35	fn default() -> Self {
36		Self {
37			kind: DefineKind::Default,
38			name: Expr::Literal(Literal::None),
39			base: Base::Root,
40			pass_type: PassType::Unset,
41			scram: None,
42			roles: vec![],
43			// Tokens default to a 1-hour expiry when DURATION FOR TOKEN is
44			// omitted. Sessions default to no expiry.
45			token_duration: Expr::Literal(Literal::Duration(std::time::Duration::from_secs(3600))),
46			session_duration: Expr::Literal(Literal::None),
47			comment: Expr::Literal(Literal::None),
48		}
49	}
50}
51
52impl ToSql for DefineUserStatement {
53	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
54		write_sql!(f, fmt, "DEFINE USER");
55		match self.kind {
56			DefineKind::Default => {}
57			DefineKind::Overwrite => write_sql!(f, fmt, " OVERWRITE"),
58			DefineKind::IfNotExists => write_sql!(f, fmt, " IF NOT EXISTS"),
59		}
60
61		write_sql!(f, fmt, " {} ON {}", CoverStmts(&self.name), &self.base);
62
63		match self.pass_type {
64			PassType::Unset => {}
65			PassType::Hash(ref x) => write_sql!(f, fmt, " PASSHASH {}", QuoteStr(x)),
66			PassType::Password(ref x) => write_sql!(f, fmt, " PASSWORD {}", QuoteStr(x)),
67		}
68
69		if let Some(ref x) = self.scram {
70			write_sql!(f, fmt, " PASSSCRAM {}", QuoteStr(x));
71		}
72
73		write_sql!(f, fmt, " ROLES ");
74		for (idx, r) in self.roles.iter().enumerate() {
75			if idx != 0 {
76				f.push_str(", ");
77			}
78
79			let r = r.to_uppercase();
80			EscapeKwFreeIdent(&r).fmt_sql(f, fmt);
81		}
82
83		// Always print relevant durations so defaults can be changed in the future
84		// If default values were not printed, exports would not be forward compatible
85		// None values need to be printed, as they are different from the default values
86		f.push_str(" DURATION FOR TOKEN ");
87		CoverStmts(&self.token_duration).fmt_sql(f, fmt);
88		f.push_str(", FOR SESSION ");
89		CoverStmts(&self.session_duration).fmt_sql(f, fmt);
90		if !matches!(self.comment, Expr::Literal(Literal::None)) {
91			write_sql!(f, fmt, " COMMENT {}", CoverStmts(&self.comment));
92		}
93	}
94}