1use crate::core::{DMSCResult, DMSCError};
23use serde::{Deserialize, Serialize};
24use std::collections::HashMap;
25use crate::database::DMSCDatabase;
26
27pub mod repository;
28
29pub use repository::{DMSCORMSimpleRepository, DMSCORMCrudRepository, DMSCORMRepository};
30
31#[cfg(feature = "pyo3")]
32pub mod py_repository;
33
34#[cfg(feature = "pyo3")]
35pub use py_repository::DMSCPyORMRepository;
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
38#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
39pub struct ColumnDefinition {
40 pub name: String,
41 pub column_type: String,
42 pub is_primary_key: bool,
43 pub is_nullable: bool,
44 pub default_value: Option<String>,
45 pub is_unique: bool,
46 pub max_length: Option<usize>,
47}
48
49impl Default for ColumnDefinition {
50 fn default() -> Self {
51 Self {
52 name: String::new(),
53 column_type: "TEXT".to_string(),
54 is_primary_key: false,
55 is_nullable: true,
56 default_value: None,
57 is_unique: false,
58 max_length: None,
59 }
60 }
61}
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
65pub struct IndexDefinition {
66 pub name: String,
67 pub columns: Vec<String>,
68 pub is_unique: bool,
69 pub is_full_text: bool,
70}
71
72impl Default for IndexDefinition {
73 fn default() -> Self {
74 Self {
75 name: String::new(),
76 columns: Vec::new(),
77 is_unique: false,
78 is_full_text: false,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
85pub struct ForeignKeyDefinition {
86 pub name: String,
87 pub column: String,
88 pub referenced_table: String,
89 pub referenced_column: String,
90 pub on_delete: String,
91 pub on_update: String,
92}
93
94impl Default for ForeignKeyDefinition {
95 fn default() -> Self {
96 Self {
97 name: String::new(),
98 column: String::new(),
99 referenced_table: String::new(),
100 referenced_column: String::new(),
101 on_delete: "CASCADE".to_string(),
102 on_update: "CASCADE".to_string(),
103 }
104 }
105}
106
107#[derive(Debug, Clone)]
108#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
109pub struct TableDefinition {
110 pub table_name: String,
111 pub columns: HashMap<String, ColumnDefinition>,
112 pub primary_key: Vec<String>,
113 pub indexes: Vec<IndexDefinition>,
114 pub foreign_keys: Vec<ForeignKeyDefinition>,
115 pub engine: Option<String>,
116 pub charset: Option<String>,
117}
118
119impl TableDefinition {
120 pub fn new(table_name: &str) -> Self {
121 Self {
122 table_name: table_name.to_string(),
123 columns: HashMap::new(),
124 primary_key: Vec::new(),
125 indexes: Vec::new(),
126 foreign_keys: Vec::new(),
127 engine: None,
128 charset: None,
129 }
130 }
131
132 pub fn add_column(&mut self, column: ColumnDefinition) {
133 self.columns.insert(column.name.clone(), column);
134 }
135
136 pub fn set_primary_key(&mut self, columns: Vec<String>) {
137 self.primary_key = columns;
138 }
139
140 pub fn add_index(&mut self, index: IndexDefinition) {
141 self.indexes.push(index);
142 }
143
144 pub fn add_foreign_key(&mut self, fk: ForeignKeyDefinition) {
145 self.foreign_keys.push(fk);
146 }
147
148 pub fn get_create_sql(&self) -> String {
149 let mut sql = format!("CREATE TABLE IF NOT EXISTS {} (", self.table_name);
150
151 let mut column_defs = Vec::new();
152
153 for (name, col) in &self.columns {
154 let mut def = format!("{} {}", name, col.column_type);
155
156 if !col.is_nullable {
157 def.push_str(" NOT NULL");
158 }
159
160 if col.is_primary_key {
161 def.push_str(" PRIMARY KEY");
162 }
163
164 if let Some(default) = &col.default_value {
165 def.push_str(&format!(" DEFAULT {}", default));
166 }
167
168 if col.is_unique {
169 def.push_str(" UNIQUE");
170 }
171
172 column_defs.push(def);
173 }
174
175 if !self.primary_key.is_empty() {
176 column_defs.push(format!("PRIMARY KEY ({})", self.primary_key.join(", ")));
177 }
178
179 sql.push_str(&column_defs.join(", "));
180
181 for fk in &self.foreign_keys {
182 sql.push_str(&format!(
183 ", FOREIGN KEY ({}) REFERENCES {}({}) ON DELETE {} ON UPDATE {}",
184 fk.column, fk.referenced_table, fk.referenced_column, fk.on_delete, fk.on_update
185 ));
186 }
187
188 sql.push_str(")");
189
190 if let Some(engine) = &self.engine {
191 sql.push_str(&format!(" ENGINE={}", engine));
192 }
193
194 if let Some(charset) = &self.charset {
195 sql.push_str(&format!(" DEFAULT CHARSET={}", charset));
196 }
197
198 sql.push_str(";");
199
200 sql
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
205#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
206pub enum ComparisonOperator {
207 Equal,
208 NotEqual,
209 GreaterThan,
210 GreaterThanOrEqual,
211 LessThan,
212 LessThanOrEqual,
213 Like,
214 ILike,
215 In,
216 NotIn,
217 IsNull,
218 IsNotNull,
219 Between,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
223#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
224pub enum LogicalOperator {
225 And,
226 Or,
227 Not,
228}
229
230#[derive(Debug, Clone)]
231#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
232pub struct Criteria {
233 pub column: String,
234 pub operator: ComparisonOperator,
235 pub value: serde_json::Value,
236}
237
238impl Criteria {
239 pub fn new(column: &str, operator: ComparisonOperator, value: serde_json::Value) -> Self {
240 Self {
241 column: column.to_string(),
242 operator,
243 value,
244 }
245 }
246
247 pub fn to_sql(&self) -> (String, Vec<serde_json::Value>) {
248 let (op_str, value) = match self.operator {
249 ComparisonOperator::Equal => ("=".to_string(), vec![self.value.clone()]),
250 ComparisonOperator::NotEqual => ("!=".to_string(), vec![self.value.clone()]),
251 ComparisonOperator::GreaterThan => (">".to_string(), vec![self.value.clone()]),
252 ComparisonOperator::GreaterThanOrEqual => (">=".to_string(), vec![self.value.clone()]),
253 ComparisonOperator::LessThan => ("<".to_string(), vec![self.value.clone()]),
254 ComparisonOperator::LessThanOrEqual => ("<=".to_string(), vec![self.value.clone()]),
255 ComparisonOperator::Like => ("LIKE".to_string(), vec![self.value.clone()]),
256 ComparisonOperator::ILike => ("ILIKE".to_string(), vec![self.value.clone()]),
257 ComparisonOperator::In => {
258 if let serde_json::Value::Array(arr) = &self.value {
259 let placeholders = (0..arr.len()).map(|_| "?").collect::<Vec<_>>().join(", ");
260 (format!("IN ({})", placeholders), arr.clone())
261 } else {
262 ("= ?".to_string(), vec![self.value.clone()])
263 }
264 }
265 ComparisonOperator::NotIn => {
266 if let serde_json::Value::Array(arr) = &self.value {
267 let placeholders = (0..arr.len()).map(|_| "?").collect::<Vec<_>>().join(", ");
268 (format!("NOT IN ({})", placeholders), arr.clone())
269 } else {
270 ("!= ?".to_string(), vec![self.value.clone()])
271 }
272 }
273 ComparisonOperator::IsNull => ("IS NULL".to_string(), Vec::new()),
274 ComparisonOperator::IsNotNull => ("IS NOT NULL".to_string(), Vec::new()),
275 ComparisonOperator::Between => {
276 if let serde_json::Value::Array(arr) = &self.value {
277 if arr.len() == 2 {
278 ("BETWEEN ? AND ?".to_string(), arr.clone())
279 } else {
280 ("BETWEEN ? AND ?".to_string(), vec![self.value.clone()])
281 }
282 } else {
283 ("BETWEEN ? AND ?".to_string(), vec![self.value.clone()])
284 }
285 }
286 };
287
288 let placeholder = if matches!(self.operator, ComparisonOperator::IsNull | ComparisonOperator::IsNotNull) {
289 "".to_string()
290 } else {
291 "?".to_string()
292 };
293
294 (format!("{} {} {}", self.column, op_str, placeholder), value)
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, Eq)]
299#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
300pub struct SortOrder {
301 pub column: String,
302 pub ascending: bool,
303}
304
305impl SortOrder {
306 pub fn new(column: &str, ascending: bool) -> Self {
307 Self {
308 column: column.to_string(),
309 ascending,
310 }
311 }
312
313 pub fn asc(column: &str) -> Self {
314 Self::new(column, true)
315 }
316
317 pub fn desc(column: &str) -> Self {
318 Self::new(column, false)
319 }
320}
321
322#[derive(Debug, Clone)]
323#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
324pub struct Pagination {
325 pub page: u64,
326 pub page_size: u64,
327}
328
329impl Default for Pagination {
330 fn default() -> Self {
331 Self {
332 page: 1,
333 page_size: 20,
334 }
335 }
336}
337
338impl Pagination {
339 pub fn new(page: u64, page_size: u64) -> Self {
340 Self { page, page_size }
341 }
342
343 pub fn offset(&self) -> u64 {
344 (self.page - 1) * self.page_size
345 }
346
347 pub fn limit(&self) -> u64 {
348 self.page_size
349 }
350}
351
352#[derive(Debug, Clone)]
353#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
354pub struct QueryBuilder {
355 pub table_name: String,
356 pub criteria: Vec<Criteria>,
357 pub sort_orders: Vec<SortOrder>,
358 pub pagination: Option<Pagination>,
359 pub select_columns: Option<Vec<String>>,
360 pub group_by_columns: Option<Vec<String>>,
361 pub having_criteria: Vec<Criteria>,
362 pub distinct: bool,
363 pub joins: Vec<JoinClause>,
364}
365
366#[derive(Debug, Clone)]
367#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
368pub struct JoinClause {
369 pub join_type: JoinType,
370 pub table_name: String,
371 pub on_column: String,
372 pub referenced_column: String,
373}
374
375#[derive(Debug, Clone, PartialEq, Eq)]
376#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
377pub enum JoinType {
378 Inner,
379 Left,
380 Right,
381 Full,
382}
383
384impl QueryBuilder {
385 pub fn new(table_name: &str) -> Self {
386 Self {
387 table_name: table_name.to_string(),
388 criteria: Vec::new(),
389 sort_orders: Vec::new(),
390 pagination: None,
391 select_columns: None,
392 group_by_columns: None,
393 having_criteria: Vec::new(),
394 distinct: false,
395 joins: Vec::new(),
396 }
397 }
398
399 pub fn select(&mut self, columns: Vec<&str>) -> &mut Self {
400 self.select_columns = Some(columns.iter().map(|s| s.to_string()).collect());
401 self
402 }
403
404 pub fn where_criteria(&mut self, criteria: Criteria) -> &mut Self {
405 self.criteria.push(criteria);
406 self
407 }
408
409 pub fn and_where(&mut self, criteria: Criteria) -> &mut Self {
410 self.where_criteria(criteria)
411 }
412
413 pub fn or_where(&mut self, criteria: Criteria) -> &mut Self {
414 self.criteria.push(criteria);
415 self
416 }
417
418 pub fn order_by(&mut self, sort_order: SortOrder) -> &mut Self {
419 self.sort_orders.push(sort_order);
420 self
421 }
422
423 pub fn paginate(&mut self, page: u64, page_size: u64) -> &mut Self {
424 self.pagination = Some(Pagination::new(page, page_size));
425 self
426 }
427
428 pub fn distinct(&mut self) -> &mut Self {
429 self.distinct = true;
430 self
431 }
432
433 pub fn group_by(&mut self, columns: Vec<&str>) -> &mut Self {
434 self.group_by_columns = Some(columns.iter().map(|s| s.to_string()).collect());
435 self
436 }
437
438 pub fn inner_join(&mut self, table_name: &str, on_column: &str, referenced_column: &str) -> &mut Self {
439 self.joins.push(JoinClause {
440 join_type: JoinType::Inner,
441 table_name: table_name.to_string(),
442 on_column: on_column.to_string(),
443 referenced_column: referenced_column.to_string(),
444 });
445 self
446 }
447
448 pub fn left_join(&mut self, table_name: &str, on_column: &str, referenced_column: &str) -> &mut Self {
449 self.joins.push(JoinClause {
450 join_type: JoinType::Left,
451 table_name: table_name.to_string(),
452 on_column: on_column.to_string(),
453 referenced_column: referenced_column.to_string(),
454 });
455 self
456 }
457
458 pub fn build(&self) -> (String, Vec<serde_json::Value>) {
459 let mut sql = String::new();
460 let mut params = Vec::new();
461
462 sql.push_str("SELECT ");
463
464 if self.distinct {
465 sql.push_str("DISTINCT ");
466 }
467
468 if let Some(columns) = &self.select_columns {
469 sql.push_str(&columns.join(", "));
470 } else {
471 sql.push_str("*");
472 }
473
474 sql.push_str(&format!(" FROM {}", self.table_name));
475
476 for join in &self.joins {
477 let join_type = match join.join_type {
478 JoinType::Inner => "INNER JOIN",
479 JoinType::Left => "LEFT JOIN",
480 JoinType::Right => "RIGHT JOIN",
481 JoinType::Full => "FULL JOIN",
482 };
483 sql.push_str(&format!(
484 " {} {} ON {}.{} = {}.{}",
485 join_type, join.table_name, self.table_name, join.on_column, join.table_name, join.referenced_column
486 ));
487 }
488
489 if !self.criteria.is_empty() {
490 sql.push_str(" WHERE 1=1");
491 for criteria in &self.criteria {
492 sql.push_str(" AND ");
493 let (clause, values) = criteria.to_sql();
494 sql.push_str(&clause);
495 params.extend(values);
496 }
497 }
498
499 if let Some(group_by) = &self.group_by_columns {
500 sql.push_str(&format!(" GROUP BY {}", group_by.join(", ")));
501 }
502
503 if !self.having_criteria.is_empty() {
504 sql.push_str(" HAVING 1=1");
505 for criteria in &self.having_criteria {
506 sql.push_str(" AND ");
507 let (clause, values) = criteria.to_sql();
508 sql.push_str(&clause);
509 params.extend(values);
510 }
511 }
512
513 if !self.sort_orders.is_empty() {
514 let orders: Vec<String> = self.sort_orders.iter()
515 .map(|o| format!("{} {}", o.column, if o.ascending { "ASC" } else { "DESC" }))
516 .collect();
517 sql.push_str(&format!(" ORDER BY {}", orders.join(", ")));
518 }
519
520 if let Some(pagination) = &self.pagination {
521 sql.push_str(&format!(" LIMIT {} OFFSET {}", pagination.limit(), pagination.offset()));
522 }
523
524 (sql, params)
525 }
526}