Skip to main content

reinhardt_query/query/procedure/
create_procedure.rs

1//! CREATE PROCEDURE statement builder
2//!
3//! This module provides the `CreateProcedureStatement` type for building SQL CREATE PROCEDURE queries.
4
5use crate::{
6	backend::QueryBuilder,
7	types::{
8		IntoIden,
9		function::{FunctionBehavior, FunctionLanguage, FunctionSecurity},
10		procedure::ProcedureDef,
11	},
12};
13
14use crate::query::traits::{QueryBuilderTrait, QueryStatementBuilder, QueryStatementWriter};
15
16/// CREATE PROCEDURE statement builder
17///
18/// This struct provides a fluent API for constructing CREATE PROCEDURE queries.
19///
20/// # Examples
21///
22/// ```rust
23/// use reinhardt_query::prelude::*;
24/// use reinhardt_query::types::function::{FunctionLanguage, FunctionBehavior};
25///
26/// // CREATE PROCEDURE my_proc() LANGUAGE SQL AS 'SELECT 1'
27/// let query = Query::create_procedure()
28///     .name("my_proc")
29///     .language(FunctionLanguage::Sql)
30///     .body("SELECT 1");
31///
32/// // CREATE OR REPLACE PROCEDURE my_proc(a integer)
33/// // LANGUAGE PLPGSQL IMMUTABLE AS 'BEGIN INSERT INTO log VALUES (a); END;'
34/// let query = Query::create_procedure()
35///     .name("my_proc")
36///     .or_replace()
37///     .add_parameter("a", "integer")
38///     .language(FunctionLanguage::PlPgSql)
39///     .behavior(FunctionBehavior::Immutable)
40///     .body("BEGIN INSERT INTO log VALUES (a); END;");
41/// ```
42#[derive(Debug, Clone)]
43pub struct CreateProcedureStatement {
44	pub(crate) procedure_def: ProcedureDef,
45}
46
47impl CreateProcedureStatement {
48	/// Create a new CREATE PROCEDURE statement
49	///
50	/// # Examples
51	///
52	/// ```rust
53	/// use reinhardt_query::prelude::*;
54	///
55	/// let query = Query::create_procedure();
56	/// ```
57	pub fn new() -> Self {
58		// Start with empty name - will be set via .name()
59		Self {
60			procedure_def: ProcedureDef::new(""),
61		}
62	}
63
64	/// Take the ownership of data in the current [`CreateProcedureStatement`]
65	pub fn take(&mut self) -> Self {
66		let taken = Self {
67			procedure_def: self.procedure_def.clone(),
68		};
69		// Reset self to empty state
70		self.procedure_def = ProcedureDef::new("");
71		taken
72	}
73
74	/// Set the procedure name
75	///
76	/// # Examples
77	///
78	/// ```rust
79	/// use reinhardt_query::prelude::*;
80	///
81	/// let query = Query::create_procedure()
82	///     .name("my_proc");
83	/// ```
84	pub fn name<N>(&mut self, name: N) -> &mut Self
85	where
86		N: IntoIden,
87	{
88		self.procedure_def.name = name.into_iden();
89		self
90	}
91
92	/// Add OR REPLACE clause
93	///
94	/// # Examples
95	///
96	/// ```rust
97	/// use reinhardt_query::prelude::*;
98	///
99	/// let query = Query::create_procedure()
100	///     .name("my_proc")
101	///     .or_replace();
102	/// ```
103	pub fn or_replace(&mut self) -> &mut Self {
104		self.procedure_def.or_replace = true;
105		self
106	}
107
108	/// Add a procedure parameter
109	///
110	/// # Examples
111	///
112	/// ```rust
113	/// use reinhardt_query::prelude::*;
114	///
115	/// let query = Query::create_procedure()
116	///     .name("my_proc")
117	///     .add_parameter("param1", "integer")
118	///     .add_parameter("param2", "text");
119	/// ```
120	pub fn add_parameter<N: IntoIden, T: Into<String>>(
121		&mut self,
122		name: N,
123		param_type: T,
124	) -> &mut Self {
125		self.procedure_def = self.procedure_def.clone().add_parameter(name, param_type);
126		self
127	}
128
129	/// Set LANGUAGE
130	///
131	/// # Examples
132	///
133	/// ```rust
134	/// use reinhardt_query::prelude::*;
135	/// use reinhardt_query::types::function::FunctionLanguage;
136	///
137	/// let query = Query::create_procedure()
138	///     .name("my_proc")
139	///     .language(FunctionLanguage::PlPgSql);
140	/// ```
141	pub fn language(&mut self, language: FunctionLanguage) -> &mut Self {
142		self.procedure_def.language = Some(language);
143		self
144	}
145
146	/// Set procedure behavior (IMMUTABLE/STABLE/VOLATILE)
147	///
148	/// # Examples
149	///
150	/// ```rust
151	/// use reinhardt_query::prelude::*;
152	/// use reinhardt_query::types::function::FunctionBehavior;
153	///
154	/// let query = Query::create_procedure()
155	///     .name("my_proc")
156	///     .behavior(FunctionBehavior::Immutable);
157	/// ```
158	pub fn behavior(&mut self, behavior: FunctionBehavior) -> &mut Self {
159		self.procedure_def.behavior = Some(behavior);
160		self
161	}
162
163	/// Set security context (DEFINER/INVOKER)
164	///
165	/// # Examples
166	///
167	/// ```rust
168	/// use reinhardt_query::prelude::*;
169	/// use reinhardt_query::types::function::FunctionSecurity;
170	///
171	/// let query = Query::create_procedure()
172	///     .name("my_proc")
173	///     .security(FunctionSecurity::Definer);
174	/// ```
175	pub fn security(&mut self, security: FunctionSecurity) -> &mut Self {
176		self.procedure_def.security = Some(security);
177		self
178	}
179
180	/// Set procedure body (AS clause)
181	///
182	/// # Examples
183	///
184	/// ```rust
185	/// use reinhardt_query::prelude::*;
186	///
187	/// let query = Query::create_procedure()
188	///     .name("my_proc")
189	///     .body("SELECT 1");
190	/// ```
191	pub fn body<B: Into<String>>(&mut self, body: B) -> &mut Self {
192		self.procedure_def.body = Some(body.into());
193		self
194	}
195}
196
197impl Default for CreateProcedureStatement {
198	fn default() -> Self {
199		Self::new()
200	}
201}
202
203impl QueryStatementBuilder for CreateProcedureStatement {
204	fn build_any(&self, query_builder: &dyn QueryBuilderTrait) -> (String, crate::value::Values) {
205		// Downcast to concrete QueryBuilder type
206		use std::any::Any;
207		if let Some(builder) =
208			(query_builder as &dyn Any).downcast_ref::<crate::backend::PostgresQueryBuilder>()
209		{
210			return builder.build_create_procedure(self);
211		}
212		if let Some(builder) =
213			(query_builder as &dyn Any).downcast_ref::<crate::backend::MySqlQueryBuilder>()
214		{
215			return builder.build_create_procedure(self);
216		}
217		if let Some(builder) =
218			(query_builder as &dyn Any).downcast_ref::<crate::backend::SqliteQueryBuilder>()
219		{
220			return builder.build_create_procedure(self);
221		}
222		if let Some(builder) =
223			(query_builder as &dyn Any).downcast_ref::<crate::backend::CockroachDBQueryBuilder>()
224		{
225			return builder.build_create_procedure(self);
226		}
227		panic!("Unsupported query builder type");
228	}
229}
230
231impl QueryStatementWriter for CreateProcedureStatement {}
232
233#[cfg(test)]
234mod tests {
235	use super::*;
236	use rstest::*;
237
238	#[rstest]
239	fn test_create_procedure_new() {
240		let stmt = CreateProcedureStatement::new();
241		assert!(stmt.procedure_def.name.to_string().is_empty());
242		assert!(!stmt.procedure_def.or_replace);
243		assert!(stmt.procedure_def.parameters.is_empty());
244		assert!(stmt.procedure_def.language.is_none());
245		assert!(stmt.procedure_def.behavior.is_none());
246		assert!(stmt.procedure_def.security.is_none());
247		assert!(stmt.procedure_def.body.is_none());
248	}
249
250	#[rstest]
251	fn test_create_procedure_with_name() {
252		let mut stmt = CreateProcedureStatement::new();
253		stmt.name("my_proc");
254		assert_eq!(stmt.procedure_def.name.to_string(), "my_proc");
255	}
256
257	#[rstest]
258	fn test_create_procedure_or_replace() {
259		let mut stmt = CreateProcedureStatement::new();
260		stmt.name("my_proc").or_replace();
261		assert!(stmt.procedure_def.or_replace);
262	}
263
264	#[rstest]
265	fn test_create_procedure_add_parameter() {
266		let mut stmt = CreateProcedureStatement::new();
267		stmt.name("my_proc").add_parameter("param1", "integer");
268		assert_eq!(stmt.procedure_def.parameters.len(), 1);
269		assert_eq!(
270			stmt.procedure_def.parameters[0]
271				.name
272				.as_ref()
273				.unwrap()
274				.to_string(),
275			"param1"
276		);
277		assert_eq!(
278			stmt.procedure_def.parameters[0]
279				.param_type
280				.as_ref()
281				.unwrap(),
282			"integer"
283		);
284	}
285
286	#[rstest]
287	fn test_create_procedure_multiple_parameters() {
288		let mut stmt = CreateProcedureStatement::new();
289		stmt.name("my_proc")
290			.add_parameter("param1", "integer")
291			.add_parameter("param2", "text");
292		assert_eq!(stmt.procedure_def.parameters.len(), 2);
293		assert_eq!(
294			stmt.procedure_def.parameters[0]
295				.name
296				.as_ref()
297				.unwrap()
298				.to_string(),
299			"param1"
300		);
301		assert_eq!(
302			stmt.procedure_def.parameters[1]
303				.name
304				.as_ref()
305				.unwrap()
306				.to_string(),
307			"param2"
308		);
309	}
310
311	#[rstest]
312	fn test_create_procedure_language() {
313		let mut stmt = CreateProcedureStatement::new();
314		stmt.name("my_proc").language(FunctionLanguage::PlPgSql);
315		assert_eq!(stmt.procedure_def.language, Some(FunctionLanguage::PlPgSql));
316	}
317
318	#[rstest]
319	fn test_create_procedure_behavior() {
320		let mut stmt = CreateProcedureStatement::new();
321		stmt.name("my_proc").behavior(FunctionBehavior::Immutable);
322		assert_eq!(
323			stmt.procedure_def.behavior,
324			Some(FunctionBehavior::Immutable)
325		);
326	}
327
328	#[rstest]
329	fn test_create_procedure_security() {
330		let mut stmt = CreateProcedureStatement::new();
331		stmt.name("my_proc").security(FunctionSecurity::Definer);
332		assert_eq!(stmt.procedure_def.security, Some(FunctionSecurity::Definer));
333	}
334
335	#[rstest]
336	fn test_create_procedure_body() {
337		let mut stmt = CreateProcedureStatement::new();
338		stmt.name("my_proc").body("SELECT 1");
339		assert_eq!(stmt.procedure_def.body.as_ref().unwrap(), "SELECT 1");
340	}
341
342	#[rstest]
343	fn test_create_procedure_all_options() {
344		let mut stmt = CreateProcedureStatement::new();
345		stmt.name("my_proc")
346			.or_replace()
347			.add_parameter("a", "integer")
348			.add_parameter("b", "text")
349			.language(FunctionLanguage::PlPgSql)
350			.behavior(FunctionBehavior::Immutable)
351			.security(FunctionSecurity::Definer)
352			.body("BEGIN INSERT INTO log VALUES (a, b); END;");
353
354		assert_eq!(stmt.procedure_def.name.to_string(), "my_proc");
355		assert!(stmt.procedure_def.or_replace);
356		assert_eq!(stmt.procedure_def.parameters.len(), 2);
357		assert_eq!(stmt.procedure_def.language, Some(FunctionLanguage::PlPgSql));
358		assert_eq!(
359			stmt.procedure_def.behavior,
360			Some(FunctionBehavior::Immutable)
361		);
362		assert_eq!(stmt.procedure_def.security, Some(FunctionSecurity::Definer));
363		assert_eq!(
364			stmt.procedure_def.body.as_ref().unwrap(),
365			"BEGIN INSERT INTO log VALUES (a, b); END;"
366		);
367	}
368
369	#[rstest]
370	fn test_create_procedure_take() {
371		let mut stmt = CreateProcedureStatement::new();
372		stmt.name("my_proc");
373		let taken = stmt.take();
374		assert!(stmt.procedure_def.name.to_string().is_empty());
375		assert_eq!(taken.procedure_def.name.to_string(), "my_proc");
376	}
377}