dmsc/database/orm/
py_repository.rs

1//! Copyright © 2025-2026 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! you may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//!     http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18use crate::core::DMSCResult;
19use crate::database::{DMSCDatabase, DMSCDatabasePool};
20use pyo3::prelude::*;
21use pyo3::types::{PyDict, PyList};
22use tokio::runtime::Runtime;
23
24#[pyclass]
25pub struct DMSCPyORMRepository {
26    pool: DMSCDatabasePool,
27    table_name: String,
28    rt: Runtime,
29}
30
31#[pymethods]
32impl DMSCPyORMRepository {
33    #[new]
34    #[pyo3(signature = (pool, table_name))]
35    pub fn new(pool: DMSCDatabasePool, table_name: &str) -> PyResult<Self> {
36        let rt = Runtime::new().map_err(|e| pyo3::PyErr::from(crate::core::DMSCError::Other(e.to_string())))?;
37        Ok(Self {
38            pool,
39            table_name: table_name.to_string(),
40            rt,
41        })
42    }
43
44    pub fn get_table_name(&self) -> &str {
45        &self.table_name
46    }
47
48    pub fn find_all(&self, py: Python) -> PyResult<Py<PyList>> {
49        let result = self.rt.block_on(async {
50            self.find_all_impl().await
51        }).map_err(|e| pyo3::PyErr::from(e))?;
52        let list = PyList::empty(py);
53        for value in result {
54            let dict = PyDict::new(py);
55            if let serde_json::Value::Object(map) = value {
56                for (k, v) in map {
57                    Self::json_to_py(py, k, v, &dict);
58                }
59            }
60            list.append(dict).map_err(|e| pyo3::PyErr::from(e))?;
61        }
62        Ok(list.into())
63    }
64
65    pub fn find_by_id(&self, id: &str, py: Python) -> PyResult<Option<Py<PyDict>>> {
66        let result = self.rt.block_on(async {
67            self.find_by_id_impl(id).await
68        }).map_err(|e| pyo3::PyErr::from(e))?;
69        match result {
70            Some(value) => {
71                let dict = PyDict::new(py);
72                if let serde_json::Value::Object(map) = value {
73                    for (k, v) in map {
74                        Self::json_to_py(py, k, v, &dict);
75                    }
76                }
77                Ok(Some(dict.into()))
78            }
79            None => Ok(None),
80        }
81    }
82
83    pub fn count(&self) -> PyResult<u64> {
84        self.rt.block_on(async {
85            self.count_impl().await
86        }).map_err(|e| pyo3::PyErr::from(e))
87    }
88
89    pub fn exists(&self, id: &str) -> PyResult<bool> {
90        self.rt.block_on(async {
91            self.exists_impl(id).await
92        }).map_err(|e| pyo3::PyErr::from(e))
93    }
94
95    pub fn delete(&self, id: &str) -> PyResult<()> {
96        self.rt.block_on(async {
97            self.delete_impl(id).await
98        }).map_err(|e| pyo3::PyErr::from(e))
99    }
100}
101
102impl DMSCPyORMRepository {
103    fn json_to_py(py: Python, key: String, value: serde_json::Value, dict: &Bound<PyDict>) {
104        match value {
105            serde_json::Value::Null => {},
106            serde_json::Value::Bool(b) => { let _ = dict.set_item(key, b); },
107            serde_json::Value::Number(n) => {
108                if let Some(i) = n.as_i64() {
109                    let _ = dict.set_item(key, i);
110                } else if let Some(f) = n.as_f64() {
111                    let _ = dict.set_item(key, f);
112                } else {
113                    let _ = dict.set_item(key, n.to_string());
114                }
115            }
116            serde_json::Value::String(s) => { let _ = dict.set_item(key, s); },
117            serde_json::Value::Array(arr) => {
118                let list = PyList::empty(py);
119                for (idx, v) in arr.into_iter().enumerate() {
120                    let item = PyDict::new(py);
121                    Self::json_to_py(py, idx.to_string(), v, &item);
122                    let _ = list.append(item);
123                }
124                let _ = dict.set_item(key, list);
125            }
126            serde_json::Value::Object(map) => {
127                let nested = PyDict::new(py);
128                for (k, v) in map {
129                    Self::json_to_py(py, k, v, &nested);
130                }
131                let _ = dict.set_item(key, nested);
132            }
133        }
134    }
135
136    async fn find_all_impl(&self) -> DMSCResult<Vec<serde_json::Value>> {
137        let db = self.pool.get().await?;
138        let sql = format!("SELECT * FROM {}", self.table_name);
139        let result = db.query(&sql).await?;
140        let mut entities = Vec::new();
141        for row in result {
142            let json_value = serde_json::to_value(row.to_map())?;
143            entities.push(json_value);
144        }
145        Ok(entities)
146    }
147
148    async fn find_by_id_impl(&self, id: &str) -> DMSCResult<Option<serde_json::Value>> {
149        let db = self.pool.get().await?;
150        let sql = format!("SELECT * FROM {} WHERE id = ?", self.table_name);
151        let result = db.query_with_params(&sql, &[serde_json::json!(id)]).await?;
152        if let Some(row) = result.first() {
153            let json_value = serde_json::to_value(row.to_map())?;
154            Ok(Some(json_value))
155        } else {
156            Ok(None)
157        }
158    }
159
160    async fn count_impl(&self) -> DMSCResult<u64> {
161        let db = self.pool.get().await?;
162        let sql = format!("SELECT COUNT(*) as total FROM {}", self.table_name);
163        if let Some(row) = db.query_one(&sql).await? {
164            Ok(row.get::<i64>("total").map(|v| v as u64).unwrap_or(0))
165        } else {
166            Ok(0)
167        }
168    }
169
170    async fn exists_impl(&self, id: &str) -> DMSCResult<bool> {
171        let db = self.pool.get().await?;
172        let sql = format!("SELECT 1 FROM {} WHERE id = ? LIMIT 1", self.table_name);
173        let result = db.query_with_params(&sql, &[serde_json::json!(id)]).await?;
174        Ok(!result.is_empty())
175    }
176
177    async fn delete_impl(&self, id: &str) -> DMSCResult<()> {
178        let db = self.pool.get().await?;
179        let sql = format!("DELETE FROM {} WHERE id = ?", self.table_name);
180        db.execute_with_params(&sql, &[serde_json::json!(id)]).await?;
181        Ok(())
182    }
183}