Skip to main content

mongreldb_kit/
schema.rs

1//! Schema/value conversion between the kit model and MongrelDB core.
2
3use crate::error::{KitError, Result};
4use mongreldb_core::memtable::Value as CoreValue;
5use mongreldb_core::schema::{
6    ColumnDef, ColumnFlags, IndexDef, IndexKind, Schema as CoreSchema, TypeId,
7};
8use mongreldb_kit_core::schema::{
9    Column, ColumnType, IndexKind as KitIndexKind, Table as KitTable,
10};
11use serde_json::{Map, Value};
12
13/// Convert a kit table to a core schema.
14pub fn to_core_schema(table: &KitTable) -> CoreSchema {
15    let columns: Vec<ColumnDef> = table
16        .columns
17        .iter()
18        .map(|c| ColumnDef {
19            id: c.id as u16,
20            name: c.name.clone(),
21            ty: match c.storage_type {
22                ColumnType::Embedding => TypeId::Embedding {
23                    dim: c.embedding_dim.unwrap_or(0),
24                },
25                other => to_core_type(other),
26            },
27            flags: to_core_flags(table, c),
28        })
29        .collect();
30
31    let mut indexes: Vec<IndexDef> = Vec::new();
32    for idx in &table.indexes {
33        let kind = match idx.kind {
34            KitIndexKind::Bitmap => IndexKind::Bitmap,
35            KitIndexKind::Fm => IndexKind::FmIndex,
36            KitIndexKind::Ann => IndexKind::Ann,
37            KitIndexKind::Sparse => IndexKind::Sparse,
38            KitIndexKind::MinHash => IndexKind::MinHash,
39            KitIndexKind::LearnedRange => IndexKind::LearnedRange,
40        };
41        for col_name in &idx.columns {
42            if let Some(col) = table.column(col_name) {
43                indexes.push(IndexDef {
44                    name: format!("{}_{}", idx.name, col_name),
45                    column_id: col.id as u16,
46                    kind,
47                    predicate: None,
48                });
49            }
50        }
51    }
52    for uq in &table.unique_constraints {
53        for col_name in &uq.columns {
54            if let Some(col) = table.column(col_name) {
55                indexes.push(IndexDef {
56                    name: format!("uq_{}_{}", uq.name, col_name),
57                    column_id: col.id as u16,
58                    kind: IndexKind::Bitmap,
59                    predicate: None,
60                });
61            }
62        }
63    }
64
65    CoreSchema {
66        schema_id: table.id as u64,
67        columns,
68        indexes,
69        colocation: Vec::new(),
70        constraints: Default::default(),
71        clustered: false,
72    }
73}
74
75pub(crate) fn to_core_flags(table: &KitTable, column: &Column) -> ColumnFlags {
76    let mut flags = ColumnFlags::empty();
77    if column.nullable {
78        flags = flags.with(ColumnFlags::NULLABLE);
79    }
80    if table.primary_key.contains(&column.name) || column.primary_key {
81        flags = flags.with(ColumnFlags::PRIMARY_KEY);
82    }
83    if column.encrypted {
84        flags = flags.with(ColumnFlags::ENCRYPTED);
85    }
86    if column.encrypted_indexable {
87        flags = flags.with(ColumnFlags::ENCRYPTED_INDEXABLE);
88    }
89    flags
90}
91
92pub(crate) fn to_core_type(ty: ColumnType) -> TypeId {
93    match ty {
94        ColumnType::Bool => TypeId::Bool,
95        ColumnType::Int8 | ColumnType::Int16 | ColumnType::Int32 | ColumnType::Int64 => {
96            TypeId::Int64
97        }
98        ColumnType::Float32 | ColumnType::Float64 => TypeId::Float64,
99        ColumnType::Text
100        | ColumnType::Bytes
101        | ColumnType::Json
102        | ColumnType::Date
103        | ColumnType::DateTime => TypeId::Bytes,
104        ColumnType::TimestampNanos => TypeId::Int64,
105        ColumnType::Date64 => TypeId::Date64,
106        ColumnType::Time64 => TypeId::Time64,
107        ColumnType::Interval => TypeId::Interval,
108        ColumnType::Decimal128 => TypeId::Decimal128 {
109            precision: 38,
110            scale: 2,
111        },
112        // Dimension is filled from the column's `embedding_dim` in
113        // `to_core_schema`; a bare type has no dimension context.
114        ColumnType::Embedding => TypeId::Embedding { dim: 0 },
115        // Sparse vectors are stored as bincode'd `Vec<(u32, f32)>` in a Bytes
116        // column; the Sparse index reads the tokens from those bytes.
117        ColumnType::Sparse => TypeId::Bytes,
118    }
119}
120
121/// Convert a JSON value to a core cell value using the column type for guidance.
122pub fn json_to_core(value: &Value, ty: ColumnType) -> Result<CoreValue> {
123    Ok(match value {
124        Value::Null => CoreValue::Null,
125        Value::Bool(b) => CoreValue::Bool(*b),
126        Value::Number(n) => {
127            if let Some(i) = n.as_i64() {
128                CoreValue::Int64(i)
129            } else {
130                CoreValue::Float64(n.as_f64().unwrap_or(f64::NAN))
131            }
132        }
133        Value::String(s) => CoreValue::Bytes(s.as_bytes().to_vec()),
134        Value::Array(arr) => {
135            if ty == ColumnType::Sparse {
136                let mut terms: Vec<(u32, f32)> = Vec::with_capacity(arr.len());
137                for pair in arr {
138                    let p = pair
139                        .as_array()
140                        .ok_or_else(|| KitError::Validation("sparse expects pairs".into()))?;
141                    let token =
142                        p.first().and_then(|v| v.as_u64()).ok_or_else(|| {
143                            KitError::Validation("sparse token must be u32".into())
144                        })? as u32;
145                    let weight = p.get(1).and_then(|v| v.as_f64()).ok_or_else(|| {
146                        KitError::Validation("sparse weight must be number".into())
147                    })? as f32;
148                    terms.push((token, weight));
149                }
150                CoreValue::Bytes(
151                    bincode::serialize(&terms).map_err(|e| KitError::Validation(e.to_string()))?,
152                )
153            } else if ty == ColumnType::Embedding {
154                let mut vec = Vec::with_capacity(arr.len());
155                for v in arr {
156                    match v.as_f64() {
157                        Some(f) => vec.push(f as f32),
158                        None => {
159                            return Err(KitError::Validation("embedding expects numbers".into()))
160                        }
161                    }
162                }
163                CoreValue::Embedding(vec)
164            } else if ty == ColumnType::Bytes {
165                let mut bytes = Vec::with_capacity(arr.len());
166                for v in arr {
167                    match v {
168                        Value::Number(n) => bytes.push(n.as_i64().unwrap_or(0) as u8),
169                        _ => return Err(KitError::Validation("bytes array expected".into())),
170                    }
171                }
172                CoreValue::Bytes(bytes)
173            } else {
174                CoreValue::Bytes(serde_json::to_vec(value)?)
175            }
176        }
177        Value::Object(_) => CoreValue::Bytes(serde_json::to_vec(value)?),
178    })
179}
180
181/// Convert a core cell value back to JSON, guided by the column type.
182pub fn core_to_json(value: &CoreValue, ty: ColumnType) -> Result<Value> {
183    Ok(match (value, ty) {
184        (CoreValue::Null, _) => Value::Null,
185        (CoreValue::Bool(b), _) => Value::Bool(*b),
186        (CoreValue::Int64(i), ColumnType::Int8) => Value::Number((*i as i8).into()),
187        (CoreValue::Int64(i), ColumnType::Int16) => Value::Number((*i as i16).into()),
188        (CoreValue::Int64(i), ColumnType::Int32) => Value::Number((*i as i32).into()),
189        (CoreValue::Int64(i), ColumnType::Int64) => Value::Number((*i).into()),
190        (CoreValue::Int64(i), ColumnType::TimestampNanos) => Value::Number((*i).into()),
191        (CoreValue::Int64(i), _) => Value::Number((*i).into()),
192        (CoreValue::Float64(f), ColumnType::Float32) => serde_json::to_value(*f as f32)?,
193        (CoreValue::Float64(f), _) => serde_json::to_value(*f)?,
194        (CoreValue::Bytes(b), ColumnType::Sparse) => {
195            let terms: Vec<(u32, f32)> =
196                bincode::deserialize(b).map_err(|e| KitError::Validation(e.to_string()))?;
197            Value::Array(
198                terms
199                    .into_iter()
200                    .map(|(t, w)| Value::Array(vec![Value::from(t), Value::from(w as f64)]))
201                    .collect(),
202            )
203        }
204        (CoreValue::Bytes(b), ColumnType::Bytes) => {
205            Value::Array(b.iter().map(|x| Value::Number((*x).into())).collect())
206        }
207        (CoreValue::Bytes(b), _) => match std::str::from_utf8(b) {
208            Ok(s) => Value::String(s.to_string()),
209            Err(_) => Value::Array(b.iter().map(|x| Value::Number((*x).into())).collect()),
210        },
211        (CoreValue::Embedding(v), _) => serde_json::to_value(v)?,
212        (CoreValue::Decimal(d), _) => Value::String(d.to_string()),
213        (
214            CoreValue::Interval {
215                months,
216                days,
217                nanos,
218            },
219            _,
220        ) => {
221            serde_json::json!({ "months": months, "days": days, "nanos": nanos })
222        }
223    })
224}
225
226/// Build a JSON row from a core row and a kit table definition.
227pub fn core_row_to_json(row: &mongreldb_core::memtable::Row, table: &KitTable) -> Result<Row> {
228    let mut values = Map::new();
229    for col in &table.columns {
230        let v = row
231            .columns
232            .get(&(col.id as u16))
233            .cloned()
234            .unwrap_or(CoreValue::Null);
235        values.insert(col.name.clone(), core_to_json(&v, col.storage_type)?);
236    }
237    Ok(Row {
238        row_id: row.row_id.0,
239        values,
240    })
241}
242
243/// A kit row, identified by its internal storage row id and column values.
244#[derive(Debug, Clone, PartialEq)]
245pub struct Row {
246    pub row_id: u64,
247    pub values: Map<String, Value>,
248}
249
250impl Row {
251    /// Extract the primary-key value(s) as a JSON value.
252    ///
253    /// Single-column primary keys return the scalar value; composite keys return
254    /// an object.
255    pub fn pk(&self, table: &KitTable) -> Option<Value> {
256        if table.primary_key.len() == 1 {
257            self.values.get(&table.primary_key[0]).cloned()
258        } else {
259            let mut obj = Map::new();
260            for name in &table.primary_key {
261                obj.insert(
262                    name.clone(),
263                    self.values.get(name).cloned().unwrap_or(Value::Null),
264                );
265            }
266            Some(Value::Object(obj))
267        }
268    }
269}
270
271/// Extract the primary-key value(s) from a JSON value map.
272pub fn pk_value(values: &Map<String, Value>, table: &KitTable) -> Option<Value> {
273    if table.primary_key.len() == 1 {
274        values.get(&table.primary_key[0]).cloned()
275    } else {
276        let mut obj = Map::new();
277        for name in &table.primary_key {
278            obj.insert(
279                name.clone(),
280                values.get(name).cloned().unwrap_or(Value::Null),
281            );
282        }
283        Some(Value::Object(obj))
284    }
285}
286
287/// Convert a primary-key value into the column values for lookup.
288pub fn pk_to_map(pk: &Value, table: &KitTable) -> Result<Map<String, Value>> {
289    let mut map = Map::new();
290    match pk {
291        Value::Object(obj) => {
292            for name in &table.primary_key {
293                let v = obj
294                    .get(name)
295                    .cloned()
296                    .ok_or_else(|| KitError::Validation(format!("missing pk column {name}")))?;
297                map.insert(name.clone(), v);
298            }
299        }
300        scalar if table.primary_key.len() == 1 => {
301            map.insert(table.primary_key[0].clone(), scalar.clone());
302        }
303        _ => {
304            return Err(KitError::Validation(
305                "primary key value shape mismatch".into(),
306            ))
307        }
308    }
309    Ok(map)
310}
311
312/// Build a core cell vector from a JSON row and kit table definition.
313pub fn row_to_core_cells(
314    values: &Map<String, Value>,
315    table: &KitTable,
316) -> Result<Vec<(u16, CoreValue)>> {
317    let mut cells = Vec::with_capacity(table.columns.len());
318    for col in &table.columns {
319        let v = values.get(&col.name).cloned().unwrap_or(Value::Null);
320        cells.push((col.id as u16, json_to_core(&v, col.storage_type)?));
321    }
322    Ok(cells)
323}