nodedb_lite/engine/columnar/store/
mutate.rs1use super::segments::columnar_err_to_lite;
6use super::state::ColumnarEngine;
7use crate::error::LiteError;
8use crate::storage::engine::StorageEngine;
9use nodedb_types::columnar::ColumnarProfile;
10use nodedb_types::value::Value;
11#[cfg(not(target_arch = "wasm32"))]
12use std::sync::Arc;
13
14impl<S: StorageEngine> ColumnarEngine<S> {
15 pub fn insert(&self, collection: &str, values: &[Value]) -> Result<(), LiteError> {
21 let state_arc = self.lookup(collection)?;
22 let mut s = Self::lock_state(&state_arc)?;
23 s.mutation.insert(values).map_err(columnar_err_to_lite)?;
24 Ok(())
25 }
26
27 #[cfg(not(target_arch = "wasm32"))]
38 pub async fn enqueue_outbound(
39 &self,
40 collection: &str,
41 rows: &[Vec<Value>],
42 ) -> Result<(), LiteError> {
43 if rows.is_empty() {
44 return Ok(());
45 }
46
47 enum OutboundRoute {
50 Timeseries { column_names: Vec<String> },
51 Columnar { schema_bytes: Vec<u8> },
52 None,
53 }
54
55 let route: OutboundRoute = {
56 let state_arc = self.lookup(collection)?;
57 let s = Self::lock_state(&state_arc)?;
58 if matches!(s.profile, ColumnarProfile::Timeseries { .. }) {
59 if self.timeseries_outbound.is_some() {
60 let column_names: Vec<String> = s
61 .mutation
62 .schema()
63 .columns
64 .iter()
65 .map(|c| c.name.clone())
66 .collect();
67 OutboundRoute::Timeseries { column_names }
68 } else {
69 OutboundRoute::None
70 }
71 } else if self.outbound.is_some() {
72 let schema_bytes = zerompk::to_msgpack_vec(s.mutation.schema()).unwrap_or_default();
73 OutboundRoute::Columnar { schema_bytes }
74 } else {
75 OutboundRoute::None
76 }
77 };
79
80 match route {
81 OutboundRoute::Timeseries { column_names } => {
82 let queue = match &self.timeseries_outbound {
83 Some(q) => Arc::clone(q),
84 None => return Ok(()),
85 };
86 for row in rows {
87 crate::sync::reconcile_outbound_enqueue(
88 queue
89 .enqueue_row(collection, column_names.clone(), row.clone())
90 .await,
91 "timeseries insert",
92 collection,
93 "",
94 )?;
95 }
96 }
97 OutboundRoute::Columnar { schema_bytes } => {
98 let queue = match &self.outbound {
99 Some(q) => Arc::clone(q),
100 None => return Ok(()),
101 };
102 for row in rows {
103 crate::sync::reconcile_outbound_enqueue(
104 queue
105 .enqueue_row(collection, row.clone(), schema_bytes.clone())
106 .await,
107 "columnar insert",
108 collection,
109 "",
110 )?;
111 }
112 }
113 OutboundRoute::None => {}
114 }
115
116 Ok(())
117 }
118
119 pub fn delete(&self, collection: &str, pk: &Value) -> Result<bool, LiteError> {
121 let state_arc = self.lookup(collection)?;
122 let mut s = Self::lock_state(&state_arc)?;
123
124 if matches!(s.profile, ColumnarProfile::Timeseries { .. }) {
125 return Err(LiteError::BadRequest {
126 detail: format!(
127 "DELETE not allowed on timeseries collection '{collection}' (append-only)"
128 ),
129 });
130 }
131
132 match s.mutation.delete(pk) {
133 Ok(_) => Ok(true),
134 Err(nodedb_columnar::ColumnarError::PrimaryKeyNotFound) => Ok(false),
135 Err(e) => Err(columnar_err_to_lite(e)),
136 }
137 }
138
139 pub fn update(
141 &self,
142 collection: &str,
143 old_pk: &Value,
144 new_values: &[Value],
145 ) -> Result<bool, LiteError> {
146 let state_arc = self.lookup(collection)?;
147 let mut s = Self::lock_state(&state_arc)?;
148
149 if matches!(s.profile, ColumnarProfile::Timeseries { .. }) {
150 return Err(LiteError::BadRequest {
151 detail: format!(
152 "UPDATE not allowed on timeseries collection '{collection}' (append-only)"
153 ),
154 });
155 }
156
157 match s.mutation.update(old_pk, new_values, None) {
159 Ok(_) => Ok(true),
160 Err(nodedb_columnar::ColumnarError::PrimaryKeyNotFound) => Ok(false),
161 Err(e) => Err(columnar_err_to_lite(e)),
162 }
163 }
164}