1use std::collections::HashMap;
6
7use super::types::{
8 BatchUpdate, BatchUpdateResult, BatchUpsertResult, ColumnStoreError, ColumnValue, ExpireResult,
9 TypedColumn, UpsertResult,
10};
11use super::ColumnStore;
12
13impl ColumnStore {
14 pub fn batch_update(&mut self, updates: &[BatchUpdate]) -> BatchUpdateResult {
16 let mut result = BatchUpdateResult::default();
17 let by_column = self.partition_batch_updates(updates, &mut result);
18 let row_to_pk = self.build_row_to_pk_map(updates);
19 self.apply_column_updates(by_column, &row_to_pk, &mut result);
20 result
21 }
22
23 fn partition_batch_updates<'a>(
25 &self,
26 updates: &'a [BatchUpdate],
27 result: &mut BatchUpdateResult,
28 ) -> HashMap<&'a str, Vec<(usize, ColumnValue)>> {
29 let mut by_column: HashMap<&str, Vec<(usize, ColumnValue)>> = HashMap::new();
30
31 for update in updates {
32 if self
33 .primary_key_column
34 .as_ref()
35 .is_some_and(|pk_col| pk_col == &update.column)
36 {
37 result
38 .failed
39 .push((update.pk, ColumnStoreError::PrimaryKeyUpdate));
40 continue;
41 }
42
43 match self.primary_index.get(&update.pk) {
44 Some(&row_idx) if !self.deleted_rows.contains(&row_idx) => {
45 by_column
46 .entry(update.column.as_str())
47 .or_default()
48 .push((row_idx, update.value.clone()));
49 }
50 _ => {
51 result
52 .failed
53 .push((update.pk, ColumnStoreError::RowNotFound(update.pk)));
54 }
55 }
56 }
57
58 by_column
59 }
60
61 fn build_row_to_pk_map(&self, updates: &[BatchUpdate]) -> HashMap<usize, i64> {
63 let mut row_to_pk: HashMap<usize, i64> = HashMap::new();
64 for update in updates {
65 if let Some(&row_idx) = self.primary_index.get(&update.pk) {
66 row_to_pk.insert(row_idx, update.pk);
67 }
68 }
69 row_to_pk
70 }
71
72 fn apply_column_updates(
74 &mut self,
75 by_column: HashMap<&str, Vec<(usize, ColumnValue)>>,
76 row_to_pk: &HashMap<usize, i64>,
77 result: &mut BatchUpdateResult,
78 ) {
79 for (col_name, col_updates) in by_column {
80 if let Some(col) = self.columns.get_mut(col_name) {
81 for (row_idx, value) in col_updates {
82 let actual_type = Self::value_type_name(&value);
83 if Self::set_column_value(col, row_idx, value).is_ok() {
84 result.successful += 1;
85 } else {
86 let pk = row_to_pk.get(&row_idx).copied().unwrap_or(0);
87 result.failed.push((
88 pk,
89 ColumnStoreError::TypeMismatch {
90 expected: Self::column_type_name(col),
91 actual: actual_type,
92 },
93 ));
94 }
95 }
96 } else {
97 for (row_idx, _) in col_updates {
98 let pk = row_to_pk.get(&row_idx).copied().unwrap_or(0);
99 result
100 .failed
101 .push((pk, ColumnStoreError::ColumnNotFound(col_name.to_string())));
102 }
103 }
104 }
105 }
106
107 pub fn batch_update_same_value(
109 &mut self,
110 pks: &[i64],
111 column: &str,
112 value: &ColumnValue,
113 ) -> BatchUpdateResult {
114 let updates: Vec<BatchUpdate> = pks
115 .iter()
116 .map(|&pk| BatchUpdate {
117 pk,
118 column: column.to_string(),
119 value: value.clone(),
120 })
121 .collect();
122 self.batch_update(&updates)
123 }
124
125 pub fn set_ttl(&mut self, pk: i64, ttl_seconds: u64) -> Result<(), ColumnStoreError> {
131 let row_idx = self.resolve_live_row(pk)?;
132 let expiry_ts = Self::now_timestamp() + ttl_seconds;
133 self.row_expiry.insert(row_idx, expiry_ts);
134 Ok(())
135 }
136
137 pub fn expire_rows(&mut self) -> ExpireResult {
139 let now = Self::now_timestamp();
140 let mut result = ExpireResult::default();
141
142 let expired_rows: Vec<usize> = self
143 .row_expiry
144 .iter()
145 .filter(|(_, &expiry)| expiry <= now)
146 .map(|(&row_idx, _)| row_idx)
147 .collect();
148
149 for row_idx in expired_rows {
150 if let Some(&pk) = self.row_idx_to_pk.get(&row_idx) {
151 self.deleted_rows.insert(row_idx);
152 if let Ok(idx) = u32::try_from(row_idx) {
154 self.deletion_bitmap.insert(idx);
155 }
156 self.row_expiry.remove(&row_idx);
157 result.pks.push(pk);
158 result.expired_count += 1;
159 }
160 }
161
162 result
163 }
164
165 pub fn upsert(
172 &mut self,
173 values: &[(&str, ColumnValue)],
174 ) -> Result<UpsertResult, ColumnStoreError> {
175 let pk_col = self
176 .primary_key_column
177 .clone()
178 .ok_or(ColumnStoreError::MissingPrimaryKey)?;
179
180 let pk_value = Self::extract_pk_value(values, &pk_col)?;
181 Self::validate_columns_exist(&self.columns, values, &pk_col)?;
182
183 if let Some(&row_idx) = self.primary_index.get(&pk_value) {
184 self.upsert_existing_row(values, row_idx, &pk_col)
185 } else {
186 self.insert_row(values)?;
187 Ok(UpsertResult::Inserted)
188 }
189 }
190
191 fn validate_columns_exist(
193 columns: &HashMap<String, TypedColumn>,
194 values: &[(&str, ColumnValue)],
195 pk_col: &str,
196 ) -> Result<(), ColumnStoreError> {
197 for (col_name, _) in values {
198 if *col_name != pk_col && !columns.contains_key(*col_name) {
199 return Err(ColumnStoreError::ColumnNotFound((*col_name).to_string()));
200 }
201 }
202 Ok(())
203 }
204
205 fn upsert_existing_row(
207 &mut self,
208 values: &[(&str, ColumnValue)],
209 row_idx: usize,
210 pk_col: &str,
211 ) -> Result<UpsertResult, ColumnStoreError> {
212 if self.deleted_rows.contains(&row_idx) {
213 Self::validate_value_types(&self.columns, values, Some(pk_col))?;
214 self.deleted_rows.remove(&row_idx);
215 if let Ok(idx) = u32::try_from(row_idx) {
216 self.deletion_bitmap.remove(idx);
217 }
218 self.row_expiry.remove(&row_idx);
219 self.set_row_values(values, row_idx, Some(pk_col))?;
220 return Ok(UpsertResult::Inserted);
221 }
222
223 Self::validate_value_types(&self.columns, values, Some(pk_col))?;
224 self.update_non_pk_values(values, row_idx, pk_col)?;
225 Ok(UpsertResult::Updated)
226 }
227
228 fn update_non_pk_values(
230 &mut self,
231 values: &[(&str, ColumnValue)],
232 row_idx: usize,
233 pk_col: &str,
234 ) -> Result<(), ColumnStoreError> {
235 for (col_name, value) in values {
236 if *col_name == pk_col {
237 continue;
238 }
239 if let Some(col) = self.columns.get_mut(*col_name) {
240 Self::set_column_value(col, row_idx, value.clone())?;
241 }
242 }
243 Ok(())
244 }
245
246 pub fn batch_upsert(&mut self, rows: &[Vec<(&str, ColumnValue)>]) -> BatchUpsertResult {
248 let mut result = BatchUpsertResult::default();
249
250 for row in rows {
251 match self.upsert(row) {
252 Ok(UpsertResult::Inserted) => result.inserted += 1,
253 Ok(UpsertResult::Updated) => result.updated += 1,
254 Err(e) => {
255 let pk = row
256 .iter()
257 .find(|(name, _)| {
258 self.primary_key_column
259 .as_ref()
260 .is_some_and(|pk| pk.as_str() == *name)
261 })
262 .and_then(|(_, v)| {
263 if let ColumnValue::Int(pk) = v {
264 Some(*pk)
265 } else {
266 None
267 }
268 })
269 .unwrap_or(0);
270 result.failed.push((pk, e));
271 }
272 }
273 }
274
275 result
276 }
277
278 pub(super) fn validate_type_match(
279 col: &TypedColumn,
280 value: &ColumnValue,
281 ) -> Result<(), ColumnStoreError> {
282 let type_matches = matches!(
283 (col, value),
284 (TypedColumn::Int(_), ColumnValue::Int(_))
285 | (TypedColumn::Float(_), ColumnValue::Float(_))
286 | (TypedColumn::String(_), ColumnValue::String(_))
287 | (TypedColumn::Bool(_), ColumnValue::Bool(_))
288 | (TypedColumn::Array { .. }, ColumnValue::Array(_))
289 | (TypedColumn::GeoPoint(_), ColumnValue::GeoPoint(_, _))
290 | (_, ColumnValue::Null)
291 );
292
293 if type_matches {
294 Ok(())
295 } else {
296 Err(ColumnStoreError::TypeMismatch {
297 expected: Self::column_type_name(col),
298 actual: Self::value_type_name(value),
299 })
300 }
301 }
302
303 pub(super) fn set_column_value(
304 col: &mut TypedColumn,
305 row_idx: usize,
306 value: ColumnValue,
307 ) -> Result<(), ColumnStoreError> {
308 if matches!(value, ColumnValue::Null) {
309 return Self::set_column_null(col, row_idx);
310 }
311
312 match (col, value) {
313 (TypedColumn::Int(vec), ColumnValue::Int(v)) => {
314 Self::checked_set(vec, row_idx, Some(v))
315 }
316 (TypedColumn::Float(vec), ColumnValue::Float(v)) => {
317 Self::checked_set(vec, row_idx, Some(v))
318 }
319 (TypedColumn::String(vec), ColumnValue::String(v)) => {
320 Self::checked_set(vec, row_idx, Some(v))
321 }
322 (TypedColumn::Bool(vec), ColumnValue::Bool(v)) => {
323 Self::checked_set(vec, row_idx, Some(v))
324 }
325 (TypedColumn::Array { data, .. }, ColumnValue::Array(arr)) => {
326 Self::checked_set_array(data, row_idx, arr)
327 }
328 (TypedColumn::GeoPoint(vec), ColumnValue::GeoPoint(lat, lng)) => {
329 Self::checked_set_geopoint(vec, row_idx, lat, lng)
330 }
331 (col, value) => Err(ColumnStoreError::TypeMismatch {
332 expected: Self::column_type_name(col),
333 actual: Self::value_type_name(&value),
334 }),
335 }
336 }
337
338 fn checked_set_array(
340 data: &mut [Option<smallvec::SmallVec<[ColumnValue; 8]>>],
341 row_idx: usize,
342 arr: Vec<ColumnValue>,
343 ) -> Result<(), ColumnStoreError> {
344 if row_idx >= data.len() {
345 return Err(ColumnStoreError::IndexOutOfBounds(row_idx));
346 }
347 data[row_idx] = Some(smallvec::SmallVec::from_vec(arr));
348 Ok(())
349 }
350
351 fn checked_set_geopoint(
353 vec: &mut [Option<(f64, f64)>],
354 row_idx: usize,
355 lat: f64,
356 lng: f64,
357 ) -> Result<(), ColumnStoreError> {
358 if row_idx >= vec.len() {
359 return Err(ColumnStoreError::IndexOutOfBounds(row_idx));
360 }
361 vec[row_idx] = Some((lat, lng));
362 Ok(())
363 }
364
365 fn set_column_null(col: &mut TypedColumn, row_idx: usize) -> Result<(), ColumnStoreError> {
367 match col {
368 TypedColumn::Int(vec) => Self::checked_set(vec, row_idx, None),
369 TypedColumn::Float(vec) => Self::checked_set(vec, row_idx, None),
370 TypedColumn::String(vec) => Self::checked_set(vec, row_idx, None),
371 TypedColumn::Bool(vec) => Self::checked_set(vec, row_idx, None),
372 TypedColumn::Array { data, .. } => {
373 if row_idx >= data.len() {
374 return Err(ColumnStoreError::IndexOutOfBounds(row_idx));
375 }
376 data[row_idx] = None;
377 Ok(())
378 }
379 TypedColumn::GeoPoint(vec) => Self::checked_set(vec, row_idx, None),
380 }
381 }
382
383 fn checked_set<T>(
385 vec: &mut [Option<T>],
386 row_idx: usize,
387 value: Option<T>,
388 ) -> Result<(), ColumnStoreError> {
389 if row_idx >= vec.len() {
390 return Err(ColumnStoreError::IndexOutOfBounds(row_idx));
391 }
392 vec[row_idx] = value;
393 Ok(())
394 }
395
396 pub(super) fn column_type_name(col: &TypedColumn) -> String {
397 match col {
398 TypedColumn::Int(_) => "Int".to_string(),
399 TypedColumn::Float(_) => "Float".to_string(),
400 TypedColumn::String(_) => "String".to_string(),
401 TypedColumn::Bool(_) => "Bool".to_string(),
402 TypedColumn::Array { .. } => "Array".to_string(),
403 TypedColumn::GeoPoint(_) => "GeoPoint".to_string(),
404 }
405 }
406
407 pub(super) fn value_type_name(value: &ColumnValue) -> String {
408 match value {
409 ColumnValue::Int(_) => "Int".to_string(),
410 ColumnValue::Float(_) => "Float".to_string(),
411 ColumnValue::String(_) => "String".to_string(),
412 ColumnValue::Bool(_) => "Bool".to_string(),
413 ColumnValue::Null => "Null".to_string(),
414 ColumnValue::Array(_) => "Array".to_string(),
415 ColumnValue::GeoPoint(_, _) => "GeoPoint".to_string(),
416 }
417 }
418
419 pub(super) fn now_timestamp() -> u64 {
420 std::time::SystemTime::now()
421 .duration_since(std::time::UNIX_EPOCH)
422 .map_or(0, |d| d.as_secs())
423 }
424}
425
426