Skip to main content

reinhardt_query/dcl/
create_user.rs

1//! CREATE USER statement builder
2//!
3//! This module provides a fluent API for building CREATE USER statements for both
4//! PostgreSQL and MySQL databases.
5//!
6//! # PostgreSQL
7//!
8//! PostgreSQL doesn't have a separate CREATE USER command; it's an alias for CREATE ROLE WITH LOGIN.
9//! This builder wraps CreateRoleStatement with the LOGIN attribute.
10//!
11//! # MySQL
12//!
13//! MySQL has a native CREATE USER command that supports user@host specification,
14//! DEFAULT ROLE, and user options.
15//!
16//! # Examples
17//!
18//! PostgreSQL example:
19//!
20//! ```
21//! use reinhardt_query::dcl::{CreateUserStatement, RoleAttribute};
22//!
23//! let stmt = CreateUserStatement::new()
24//!     .user("app_user")
25//!     .attribute(RoleAttribute::Password("secret".to_string()));
26//! ```
27//!
28//! MySQL example:
29//!
30//! ```
31//! use reinhardt_query::dcl::{CreateUserStatement, UserOption};
32//!
33//! let stmt = CreateUserStatement::new()
34//!     .user("app_user@localhost")
35//!     .if_not_exists(true)
36//!     .default_role(vec!["app_role".to_string()])
37//!     .option(UserOption::Comment("Application user".to_string()));
38//! ```
39
40use super::{RoleAttribute, UserOption, validate_name};
41
42/// CREATE USER statement builder
43///
44/// This struct provides a fluent API for building CREATE USER statements.
45///
46/// # PostgreSQL
47///
48/// PostgreSQL CREATE USER is an alias for CREATE ROLE WITH LOGIN.
49/// Use the `` `attribute()` `` method to add role attributes.
50///
51/// # MySQL
52///
53/// MySQL CREATE USER supports:
54/// - IF NOT EXISTS clause
55/// - User@host specification
56/// - DEFAULT ROLE clause
57/// - User options
58///
59/// # Examples
60///
61/// Create a simple user:
62///
63/// ```
64/// use reinhardt_query::dcl::CreateUserStatement;
65///
66/// let stmt = CreateUserStatement::new()
67///     .user("app_user");
68/// ```
69///
70/// Create a user with password (PostgreSQL):
71///
72/// ```
73/// use reinhardt_query::dcl::{CreateUserStatement, RoleAttribute};
74///
75/// let stmt = CreateUserStatement::new()
76///     .user("app_user")
77///     .attribute(RoleAttribute::Password("password123".to_string()))
78///     .attribute(RoleAttribute::ConnectionLimit(5));
79/// ```
80///
81/// Create a user with default role (MySQL):
82///
83/// ```
84/// use reinhardt_query::dcl::{CreateUserStatement, UserOption};
85///
86/// let stmt = CreateUserStatement::new()
87///     .user("app_user@localhost")
88///     .if_not_exists(true)
89///     .default_role(vec!["app_role".to_string()])
90///     .option(UserOption::Comment("My application user".to_string()));
91/// ```
92#[derive(Debug, Clone, Default)]
93pub struct CreateUserStatement {
94	/// User name (with optional @host for MySQL)
95	pub user_name: String,
96	/// IF NOT EXISTS clause (MySQL only)
97	pub if_not_exists: bool,
98	/// PostgreSQL role attributes (PostgreSQL only)
99	pub attributes: Vec<RoleAttribute>,
100	/// MySQL DEFAULT ROLE clause (MySQL only)
101	pub default_roles: Vec<String>,
102	/// MySQL user options
103	pub options: Vec<UserOption>,
104}
105
106impl CreateUserStatement {
107	/// Create a new CREATE USER statement
108	///
109	/// # Examples
110	///
111	/// ```
112	/// use reinhardt_query::dcl::CreateUserStatement;
113	///
114	/// let stmt = CreateUserStatement::new();
115	/// ```
116	pub fn new() -> Self {
117		Self::default()
118	}
119
120	/// Set the user name
121	///
122	/// # Examples
123	///
124	/// ```
125	/// use reinhardt_query::dcl::CreateUserStatement;
126	///
127	/// let stmt = CreateUserStatement::new()
128	///     .user("app_user");
129	/// ```
130	///
131	/// MySQL with host:
132	///
133	/// ```
134	/// use reinhardt_query::dcl::CreateUserStatement;
135	///
136	/// let stmt = CreateUserStatement::new()
137	///     .user("app_user@localhost");
138	/// ```
139	pub fn user(mut self, name: impl Into<String>) -> Self {
140		self.user_name = name.into();
141		self
142	}
143
144	/// Set IF NOT EXISTS flag (MySQL only)
145	///
146	/// # Examples
147	///
148	/// ```
149	/// use reinhardt_query::dcl::CreateUserStatement;
150	///
151	/// let stmt = CreateUserStatement::new()
152	///     .user("app_user")
153	///     .if_not_exists(true);
154	/// ```
155	pub fn if_not_exists(mut self, flag: bool) -> Self {
156		self.if_not_exists = flag;
157		self
158	}
159
160	/// Add a single PostgreSQL role attribute
161	///
162	/// # Examples
163	///
164	/// ```
165	/// use reinhardt_query::dcl::{CreateUserStatement, RoleAttribute};
166	///
167	/// let stmt = CreateUserStatement::new()
168	///     .user("app_user")
169	///     .attribute(RoleAttribute::Password("secret".to_string()))
170	///     .attribute(RoleAttribute::CreateDb);
171	/// ```
172	pub fn attribute(mut self, attr: RoleAttribute) -> Self {
173		self.attributes.push(attr);
174		self
175	}
176
177	/// Set all PostgreSQL role attributes at once
178	///
179	/// # Examples
180	///
181	/// ```
182	/// use reinhardt_query::dcl::{CreateUserStatement, RoleAttribute};
183	///
184	/// let stmt = CreateUserStatement::new()
185	///     .user("app_user")
186	///     .attributes(vec![
187	///         RoleAttribute::Password("secret".to_string()),
188	///         RoleAttribute::CreateDb,
189	///         RoleAttribute::ConnectionLimit(10),
190	///     ]);
191	/// ```
192	pub fn attributes(mut self, attrs: Vec<RoleAttribute>) -> Self {
193		self.attributes = attrs;
194		self
195	}
196
197	/// Set DEFAULT ROLE clause (MySQL only)
198	///
199	/// # Examples
200	///
201	/// ```
202	/// use reinhardt_query::dcl::CreateUserStatement;
203	///
204	/// let stmt = CreateUserStatement::new()
205	///     .user("app_user@localhost")
206	///     .default_role(vec!["app_role".to_string(), "admin_role".to_string()]);
207	/// ```
208	pub fn default_role(mut self, roles: Vec<String>) -> Self {
209		self.default_roles = roles;
210		self
211	}
212
213	/// Add a single MySQL user option
214	///
215	/// # Examples
216	///
217	/// ```
218	/// use reinhardt_query::dcl::{CreateUserStatement, UserOption};
219	///
220	/// let stmt = CreateUserStatement::new()
221	///     .user("app_user")
222	///     .option(UserOption::Comment("Application user".to_string()));
223	/// ```
224	pub fn option(mut self, opt: UserOption) -> Self {
225		self.options.push(opt);
226		self
227	}
228
229	/// Set all MySQL user options at once
230	///
231	/// # Examples
232	///
233	/// ```
234	/// use reinhardt_query::dcl::{CreateUserStatement, UserOption};
235	///
236	/// let stmt = CreateUserStatement::new()
237	///     .user("app_user")
238	///     .options(vec![
239	///         UserOption::Comment("Application user".to_string()),
240	///         UserOption::AccountLock,
241	///     ]);
242	/// ```
243	pub fn options(mut self, opts: Vec<UserOption>) -> Self {
244		self.options = opts;
245		self
246	}
247
248	/// Validate the CREATE USER statement
249	///
250	/// # Validation Rules
251	///
252	/// 1. User name cannot be empty
253	///
254	/// # Examples
255	///
256	/// ```
257	/// use reinhardt_query::dcl::CreateUserStatement;
258	///
259	/// let stmt = CreateUserStatement::new()
260	///     .user("app_user");
261	///
262	/// assert!(stmt.validate().is_ok());
263	/// ```
264	///
265	/// ```
266	/// use reinhardt_query::dcl::CreateUserStatement;
267	///
268	/// let stmt = CreateUserStatement::new();
269	/// assert!(stmt.validate().is_err());
270	/// ```
271	pub fn validate(&self) -> Result<(), String> {
272		validate_name(&self.user_name, "User name")?;
273		Ok(())
274	}
275}
276
277#[cfg(test)]
278mod tests {
279	use super::*;
280
281	#[test]
282	fn test_create_user_new() {
283		let stmt = CreateUserStatement::new();
284		assert!(stmt.user_name.is_empty());
285		assert!(!stmt.if_not_exists);
286		assert!(stmt.attributes.is_empty());
287		assert!(stmt.default_roles.is_empty());
288		assert!(stmt.options.is_empty());
289	}
290
291	#[test]
292	fn test_create_user_basic() {
293		let stmt = CreateUserStatement::new().user("app_user");
294		assert_eq!(stmt.user_name, "app_user");
295		assert!(stmt.validate().is_ok());
296	}
297
298	#[test]
299	fn test_create_user_if_not_exists() {
300		let stmt = CreateUserStatement::new()
301			.user("app_user")
302			.if_not_exists(true);
303		assert!(stmt.if_not_exists);
304	}
305
306	#[test]
307	fn test_create_user_with_attributes() {
308		let stmt = CreateUserStatement::new()
309			.user("app_user")
310			.attribute(RoleAttribute::Password("secret".to_string()))
311			.attribute(RoleAttribute::CreateDb);
312		assert_eq!(stmt.attributes.len(), 2);
313	}
314
315	#[test]
316	fn test_create_user_with_default_role() {
317		let stmt = CreateUserStatement::new()
318			.user("app_user@localhost")
319			.default_role(vec!["app_role".to_string()]);
320		assert_eq!(stmt.default_roles.len(), 1);
321	}
322
323	#[test]
324	fn test_create_user_with_options() {
325		let stmt = CreateUserStatement::new()
326			.user("app_user")
327			.option(UserOption::Comment("Test user".to_string()));
328		assert_eq!(stmt.options.len(), 1);
329	}
330
331	#[test]
332	fn test_create_user_validation_empty_name() {
333		let stmt = CreateUserStatement::new();
334		assert!(stmt.validate().is_err());
335	}
336}