dmsc/database/
row.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 serde::Serialize;
19use serde::Deserialize;
20use std::collections::HashMap;
21
22#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
23#[derive(Debug, Clone)]
24pub struct DMSCDBRow {
25    pub columns: Vec<String>,
26    pub values: Vec<Option<serde_json::Value>>,
27}
28
29impl DMSCDBRow {
30    pub fn new() -> Self {
31        Self {
32            columns: Vec::new(),
33            values: Vec::new(),
34        }
35    }
36
37    pub fn len(&self) -> usize {
38        self.columns.len()
39    }
40
41    pub fn is_empty(&self) -> bool {
42        self.columns.is_empty()
43    }
44
45    pub fn column_names(&self) -> &[String] {
46        &self.columns
47    }
48
49    pub fn has_column(&self, name: &str) -> bool {
50        self.columns.iter().any(|c| c.eq_ignore_ascii_case(name))
51    }
52
53    pub fn index_of(&self, name: &str) -> Option<usize> {
54        self.columns.iter().position(|c| c.eq_ignore_ascii_case(name))
55    }
56
57    pub fn get<T: for<'de> Deserialize<'de> + Send + Sync>(&self, name: &str) -> Option<T> {
58        let idx = self.index_of(name)?;
59        let value = self.values[idx].as_ref()?;
60        serde_json::from_value(value.clone()).ok()
61    }
62
63    pub fn get_opt<T: for<'de> Deserialize<'de> + Send + Sync>(&self, name: &str) -> Option<Option<T>> {
64        let idx = self.index_of(name)?;
65        if self.values[idx].is_none() {
66            return Some(None);
67        }
68        Some(self.get(name))
69    }
70
71    pub fn get_by_index<T: for<'de> Deserialize<'de> + Send + Sync>(&self, index: usize) -> Option<T> {
72        if index >= self.values.len() {
73            return None;
74        }
75        let value = self.values[index].as_ref()?;
76        serde_json::from_value(value.clone()).ok()
77    }
78
79    pub fn get_raw(&self, name: &str) -> Option<&serde_json::Value> {
80        let idx = self.index_of(name)?;
81        self.values[idx].as_ref()
82    }
83
84    pub fn get_raw_by_index(&self, index: usize) -> Option<&serde_json::Value> {
85        if index >= self.values.len() {
86            return None;
87        }
88        self.values[index].as_ref()
89    }
90
91    pub fn try_get<T: for<'de> Deserialize<'de> + Send + Sync>(&self, name: &str) -> Result<T, crate::core::DMSCError> {
92        self.get(name).ok_or_else(|| crate::core::DMSCError::InvalidInput(format!("Column '{} not found or type mismatch", name)))
93    }
94
95    pub fn get_i32(&self, name: &str) -> Option<i32> {
96        self.get::<i32>(name)
97    }
98
99    pub fn get_i64(&self, name: &str) -> Option<i64> {
100        self.get::<i64>(name)
101    }
102
103    pub fn get_f64(&self, name: &str) -> Option<f64> {
104        self.get::<f64>(name)
105    }
106
107    pub fn get_bool(&self, name: &str) -> Option<bool> {
108        self.get::<bool>(name)
109    }
110
111    pub fn get_string(&self, name: &str) -> Option<String> {
112        self.get::<String>(name)
113    }
114
115    pub fn get_bytes(&self, name: &str) -> Option<Vec<u8>> {
116        self.get::<Vec<u8>>(name)
117    }
118
119    pub fn is_null(&self, name: &str) -> bool {
120        let idx = match self.index_of(name) {
121            Some(i) => i,
122            None => return false,
123        };
124        self.values[idx].is_none()
125    }
126
127    pub fn iter(&self) -> impl Iterator<Item = (usize, &str, Option<&serde_json::Value>)> {
128        self.columns.iter().enumerate().map(|(i, col)| {
129            (i, col.as_str(), self.values[i].as_ref())
130        })
131    }
132
133    pub fn to_map(&self) -> HashMap<String, Option<serde_json::Value>> {
134        let mut map = HashMap::new();
135        for (i, col) in self.columns.iter().enumerate() {
136            map.insert(col.clone(), self.values[i].clone());
137        }
138        map
139    }
140}
141
142#[cfg(feature = "pyo3")]
143#[pyo3::prelude::pymethods]
144impl DMSCDBRow {
145    #[new]
146    fn py_new() -> Self {
147        Self::new()
148    }
149
150    fn get_length(&self) -> usize {
151        self.columns.len()
152    }
153
154    fn is_empty_row(&self) -> bool {
155        self.is_empty()
156    }
157
158    fn check_has_column(&self, name: &str) -> bool {
159        self.has_column(name)
160    }
161
162    fn get_string_value(&self, name: &str) -> Option<String> {
163        self.get_string(name)
164    }
165
166    fn get_column_names(&self) -> Vec<String> {
167        self.columns.clone()
168    }
169
170    fn to_dict(&self) -> HashMap<String, Option<String>> {
171        let mut map = HashMap::new();
172        for (i, col) in self.columns.iter().enumerate() {
173            let value = self.values[i].as_ref().map(|v| v.to_string());
174            map.insert(col.clone(), value);
175        }
176        map
177    }
178}
179
180#[allow(dead_code)]
181pub struct DMSCRowBuilder {
182    row: DMSCDBRow,
183}
184
185#[allow(dead_code)]
186impl DMSCRowBuilder {
187    pub fn new() -> Self {
188        Self { row: DMSCDBRow::new() }
189    }
190
191    pub fn add_column(mut self, name: &str) -> Self {
192        self.row.columns.push(name.to_string());
193        self.row.values.push(None);
194        self
195    }
196
197    pub fn add_null(mut self, name: &str) -> Self {
198        self.row.columns.push(name.to_string());
199        self.row.values.push(None);
200        self
201    }
202
203    pub fn add_value<T: Serialize + Send + Sync>(mut self, name: &str, value: T) -> Self {
204        if let Some(idx) = self.row.index_of(name) {
205            let json = serde_json::to_value(value).unwrap_or_default();
206            self.row.values[idx] = Some(json);
207        } else {
208            let json = serde_json::to_value(value).unwrap_or_default();
209            self.row.columns.push(name.to_string());
210            self.row.values.push(Some(json));
211        }
212        self
213    }
214
215    pub fn build(self) -> DMSCDBRow {
216        self.row
217    }
218}
219
220#[allow(dead_code)]
221impl Default for DMSCRowBuilder {
222    fn default() -> Self {
223        Self::new()
224    }
225}