1use crate::codec::KeyCodec;
2use crate::metadata::TableMetadata;
3use crate::{BucketHash, FieldId, LogicalType, Result, TableError, TableSchema, Value, ValueCodec};
4use bytes::Bytes;
5use cobble::{
6 ColumnFamilyOptions, Config, Db, DbIterator, ReadOnlyDb, ReadOptions, ScanOptions, Schema,
7 ShardSnapshotMetadata, ShardSnapshotRef, WriteOptions,
8};
9use std::collections::HashMap;
10use std::sync::{Arc, mpsc};
11
12pub(crate) struct CompiledTable {
13 schema: Arc<TableSchema>,
14 column_family_options: ColumnFamilyOptions,
15 key_positions: Vec<usize>,
16 key_types: Vec<LogicalType>,
17 bucket_key_fields: usize,
18 value_positions: Vec<usize>,
19 value_types: Vec<LogicalType>,
20 physical_columns: usize,
21 bucket_hash: BucketHash,
22}
23
24struct TableKeyData {
25 values: Vec<Value>,
26 bucket: u16,
27 encoded: Vec<u8>,
28}
29
30#[derive(Clone)]
34pub struct TableKey {
35 inner: Arc<TableKeyData>,
36}
37
38impl TableKey {
39 #[must_use]
41 pub fn bucket(&self) -> u16 {
42 self.inner.bucket
43 }
44}
45
46pub struct TableKeyBuilder {
48 compiled: Arc<CompiledTable>,
49 values: Vec<Value>,
50}
51
52enum ProjectedFieldSource {
53 Key(usize),
54 Value {
55 projected_column: usize,
57 physical_column: usize,
59 },
60}
61
62struct ProjectionPlan {
63 sources: Vec<ProjectedFieldSource>,
64 has_key_fields: bool,
65}
66
67#[derive(Clone)]
68pub(crate) enum ReadBackend {
69 Writable(Arc<Db>),
70 Shard(Arc<ReadOnlyDb>),
71 Global(Arc<crate::runtime::GlobalReaderState>),
72}
73
74impl ReadBackend {
75 pub(crate) fn get_with_options(
76 &self,
77 bucket: u16,
78 key: &[u8],
79 options: &ReadOptions,
80 ) -> Result<Option<Vec<Option<Bytes>>>> {
81 match self {
82 Self::Writable(db) => Ok(db.get_with_options(bucket, key, options)?),
83 Self::Shard(db) => Ok(db.get_with_options(bucket, key, options)?),
84 Self::Global(state) => state.get(bucket, key, options),
85 }
86 }
87
88 pub(crate) fn multi_get_with_options(
89 &self,
90 keys: &[(u16, &[u8])],
91 options: &ReadOptions,
92 ) -> Result<Vec<Option<Vec<Option<Bytes>>>>> {
93 match self {
94 Self::Writable(db) => Ok(db.multi_get_with_options(keys, options)?),
95 Self::Shard(db) => Ok(db.multi_get_with_options(keys, options)?),
96 Self::Global(state) => state.multi_get(keys, options),
97 }
98 }
99
100 pub(crate) fn scan_with_options_bounds(
101 &self,
102 bucket: u16,
103 start: Option<&[u8]>,
104 end: Option<&[u8]>,
105 options: &ScanOptions,
106 ) -> Result<DbIterator> {
107 match self {
108 Self::Writable(db) => Ok(db.scan_with_options_bounds(bucket, start, end, options)?),
109 Self::Shard(db) => Ok(db.scan_with_options_bounds(bucket, start, end, options)?),
110 Self::Global(state) => state.scan(bucket, start, end, options),
111 }
112 }
113}
114
115pub struct TableProjection {
117 backend: ReadBackend,
118 compiled: Arc<CompiledTable>,
119 plan: Arc<ProjectionPlan>,
120 read_options: ReadOptions,
121 scan_options: ScanOptions,
122}
123
124impl TableKeyBuilder {
125 pub fn push(&mut self, value: Value) -> &mut Self {
127 self.values.push(value);
128 self
129 }
130
131 pub fn build(self) -> Result<TableKey> {
133 let mut encoded = Vec::new();
134 let prefix_end = KeyCodec::encode_row_with_prefix_validated(
135 &self.compiled.key_types,
136 &self.values,
137 self.compiled.bucket_key_fields,
138 &mut encoded,
139 )?;
140 let bucket = self.compiled.bucket_hash.bucket(&encoded[..prefix_end]);
141 Ok(TableKey {
142 inner: Arc::new(TableKeyData {
143 values: self.values,
144 bucket,
145 encoded,
146 }),
147 })
148 }
149}
150
151pub struct Table {
153 db: Arc<Db>,
154 read_backend: ReadBackend,
155 name: String,
156 compiled: Arc<CompiledTable>,
157 read_options: ReadOptions,
158 scan_options: ScanOptions,
159 write_options: WriteOptions,
160}
161
162pub struct ReadOnlyTable {
164 typed: TypedRead,
165}
166
167pub(crate) struct TypedRead {
168 name: String,
169 compiled: Arc<CompiledTable>,
170 read_backend: ReadBackend,
171 read_options: ReadOptions,
172 scan_options: ScanOptions,
173}
174
175impl Table {
176 pub fn create(db: Arc<Db>, name: impl Into<String>, schema: TableSchema) -> Result<Self> {
178 let name = validate_name(name.into())?;
179 let metadata = ensure_table_schema(db.as_ref(), &name, schema)?;
180 Self::from_metadata(db, name, metadata)
181 }
182
183 pub fn open(db: Arc<Db>, name: impl Into<String>) -> Result<Self> {
185 let name = validate_name(name.into())?;
186 let current = db.current_schema();
187 let metadata = load_table_metadata(¤t, &name)?;
188 Self::from_metadata(db, name, metadata)
189 }
190
191 pub fn schema(&self) -> &TableSchema {
193 &self.compiled.schema
194 }
195
196 pub fn metrics(&self) -> Vec<cobble::MetricSample> {
201 self.db.metrics()
202 }
203
204 pub fn key_builder(&self) -> TableKeyBuilder {
206 TableKeyBuilder {
207 compiled: Arc::clone(&self.compiled),
208 values: Vec::with_capacity(self.compiled.key_positions.len()),
209 }
210 }
211
212 pub fn project_by_names<S: AsRef<str>>(&self, field_names: &[S]) -> Result<TableProjection> {
214 build_projection(
215 self.read_backend.clone(),
216 &self.name,
217 Arc::clone(&self.compiled),
218 field_names,
219 )
220 }
221
222 pub fn put(&self, row: &[Value]) -> Result<()> {
224 self.put_bound(row, &self.write_options)
225 }
226
227 pub fn put_with_options(&self, row: &[Value], options: &WriteOptions) -> Result<()> {
229 let bound = self.rebound_write_options(options);
230 self.put_bound(row, &bound)
231 }
232
233 pub fn delete(&self, key: &TableKey) -> Result<()> {
235 self.db.delete_row_with_options(
236 key.inner.bucket,
237 key.inner.encoded.as_slice(),
238 &self.write_options,
239 )?;
240 Ok(())
241 }
242
243 pub fn delete_batch(&self, keys: &[TableKey]) -> Result<()> {
245 if keys.is_empty() {
246 return Ok(());
247 }
248 let requests = keys
249 .iter()
250 .map(|key| (key.inner.bucket, key.inner.encoded.as_slice()))
251 .collect::<Vec<_>>();
252 self.db
253 .delete_rows_with_options(&requests, &self.write_options)?;
254 Ok(())
255 }
256
257 fn put_bound(&self, row: &[Value], options: &WriteOptions) -> Result<()> {
258 let (bucket, key, values) = encode_table_row(&self.compiled, row)?;
259 self.db
260 .put_columns_with_options(bucket, key, &values, options)?;
261 Ok(())
262 }
263
264 pub(crate) fn rebound_write_options(&self, options: &WriteOptions) -> WriteOptions {
265 let mut bound = self.write_options.clone();
266 bound.ttl_seconds = options.ttl_seconds;
267 bound.await_durable = options.await_durable;
268 bound
269 }
270
271 pub fn get(&self, key: &TableKey) -> Result<Option<Vec<Value>>> {
273 self.db
274 .get_with_options(key.inner.bucket, &key.inner.encoded, &self.read_options)?
275 .map(|columns| {
276 assemble_row_from_key_values(&self.compiled, &key.inner.values, &columns)
277 })
278 .transpose()
279 }
280
281 pub fn multi_get(&self, keys: &[TableKey]) -> Result<Vec<Option<Vec<Value>>>> {
283 let mut requests = Vec::with_capacity(keys.len());
284 for key in keys {
285 requests.push((key.inner.bucket, key.inner.encoded.as_slice()));
286 }
287 self.db
288 .multi_get_with_options(&requests, &self.read_options)?
289 .into_iter()
290 .zip(keys)
291 .map(|(columns, key)| {
292 columns
293 .map(|columns| {
294 assemble_row_from_key_values(&self.compiled, &key.inner.values, &columns)
295 })
296 .transpose()
297 })
298 .collect()
299 }
300
301 pub fn scan(&self, bucket: u16) -> Result<TableScan> {
303 self.scan_bounds(bucket, None, None)
304 }
305
306 pub fn scan_bounds(
308 &self,
309 bucket: u16,
310 start_key_inclusive: Option<&TableKey>,
311 end_key_exclusive: Option<&TableKey>,
312 ) -> Result<TableScan> {
313 validate_bound(bucket, start_key_inclusive)?;
314 validate_bound(bucket, end_key_exclusive)?;
315 Ok(TableScan {
316 inner: self.read_backend.scan_with_options_bounds(
317 bucket,
318 start_key_inclusive.map(|key| key.inner.encoded.as_slice()),
319 end_key_exclusive.map(|key| key.inner.encoded.as_slice()),
320 &self.scan_options,
321 )?,
322 _read_backend: self.read_backend.clone(),
323 compiled: Arc::clone(&self.compiled),
324 })
325 }
326
327 pub fn snapshot(&self) -> Result<u64> {
329 Ok(self.db.snapshot()?)
330 }
331
332 pub fn snapshot_with_callback<F>(&self, callback: F) -> Result<u64>
334 where
335 F: Fn(cobble::Result<ShardSnapshotMetadata>) + Send + Sync + 'static,
336 {
337 Ok(self.db.snapshot_with_callback(callback)?)
338 }
339
340 pub fn snapshot_and_wait(&self) -> Result<ShardSnapshotMetadata> {
342 let (sender, receiver) = mpsc::sync_channel(1);
343 self.snapshot_with_callback(move |result| {
344 let _ = sender.send(result);
345 })?;
346 receiver
347 .recv()
348 .map_err(|err| TableError::internal(format!("snapshot callback disconnected: {err}")))?
349 .map_err(Into::into)
350 }
351
352 pub fn shard_snapshot_metadata(&self, snapshot_id: u64) -> Result<ShardSnapshotMetadata> {
354 Ok(self.db.shard_snapshot_metadata(snapshot_id)?)
355 }
356
357 pub fn refresh_schema(&mut self) -> Result<bool> {
362 let metadata = load_table_metadata(&self.db.current_schema(), &self.name)?;
363 if self.compiled.column_family_options.metadata.as_ref() == Some(&metadata.to_value()?) {
364 return Ok(false);
365 }
366 self.compiled = compile_table(metadata, self.db.total_buckets())?;
367 (self.read_options, self.scan_options, self.write_options) =
368 build_bound_options(&self.name, &self.compiled);
369 Ok(true)
370 }
371
372 pub(crate) fn db(&self) -> &Arc<Db> {
373 &self.db
374 }
375
376 pub(crate) fn name(&self) -> &str {
377 &self.name
378 }
379
380 pub(crate) fn from_metadata(
381 db: Arc<Db>,
382 name: String,
383 metadata: TableMetadata,
384 ) -> Result<Self> {
385 let compiled = compile_table(metadata, db.total_buckets())?;
386 let (read_options, scan_options, write_options) = build_bound_options(&name, &compiled);
387 Ok(Self {
388 read_backend: ReadBackend::Writable(Arc::clone(&db)),
389 db,
390 name,
391 compiled,
392 read_options,
393 scan_options,
394 write_options,
395 })
396 }
397
398 #[cfg(feature = "ffi")]
399 pub(crate) fn ffi_schema_binding(&self) -> crate::ffi::TableSchemaBinding {
400 crate::ffi::TableSchemaBinding {
401 options: self.compiled.column_family_options.clone(),
402 physical_columns: self.compiled.physical_columns,
403 }
404 }
405
406 #[cfg(feature = "ffi")]
407 pub(crate) fn ffi_raw_access(&self) -> crate::ffi::RawTableAccess {
408 crate::ffi::RawTableAccess::new(
409 self.read_backend.clone(),
410 self.read_options.clone(),
411 self.scan_options.clone(),
412 )
413 }
414
415 #[cfg(feature = "ffi")]
416 pub(crate) fn ffi_write_options(&self) -> &WriteOptions {
417 &self.write_options
418 }
419
420 #[cfg(feature = "ffi")]
421 pub(crate) fn ffi_raw_projection(
422 &self,
423 field_names: &[String],
424 ) -> Result<crate::ffi::RawTableAccess> {
425 Ok(self.project_by_names(field_names)?.ffi_into_raw_access())
426 }
427}
428
429fn build_bound_options(
430 name: &str,
431 compiled: &CompiledTable,
432) -> (ReadOptions, ScanOptions, WriteOptions) {
433 (
434 ReadOptions::default()
435 .with_column_family(name)
436 .bound_to_column_family_schema(
437 compiled.column_family_options.clone(),
438 compiled.physical_columns,
439 ),
440 ScanOptions::default()
441 .with_column_family(name)
442 .bound_to_column_family_schema(
443 compiled.column_family_options.clone(),
444 compiled.physical_columns,
445 ),
446 WriteOptions::with_column_family(name).bound_to_column_family_schema(
447 compiled.column_family_options.clone(),
448 compiled.physical_columns,
449 ),
450 )
451}
452
453fn ensure_table_schema(db: &Db, name: &str, schema: TableSchema) -> Result<TableMetadata> {
454 let metadata = TableMetadata::compile(schema)?;
455 let expected_columns = metadata.layout.value_columns.len().max(1);
456 let current = db.current_schema();
457 if let Some(id) = current.column_family_ids().get(name).copied() {
458 let existing = load_metadata(¤t.column_family_options_in_family(id))?;
459 if existing != metadata || current.num_columns_in_family(id) != Some(expected_columns) {
460 return Err(TableError::InvalidSchema(format!(
461 "column family '{name}' is not this table"
462 )));
463 }
464 } else {
465 let mut builder = db.update_schema();
466 builder.ensure_column_family_exists(name.to_string())?;
467 for column in 0..expected_columns {
468 builder.add_column(column, None, None, Some(name.to_string()))?;
469 }
470 builder.set_column_family_options(
471 Some(name.to_string()),
472 ColumnFamilyOptions {
473 metadata: Some(metadata.to_value()?),
474 ..ColumnFamilyOptions::default()
475 },
476 )?;
477 builder.commit();
478 }
479 Ok(metadata)
480}
481
482impl ReadOnlyTable {
483 pub fn open(db: Arc<ReadOnlyDb>, name: impl Into<String>) -> Result<Self> {
485 let name = validate_name(name.into())?;
486 let current = db.current_schema();
487 let metadata = load_table_metadata(¤t, &name)?;
488 Self::from_shard_metadata(db, name, metadata)
489 }
490
491 #[cfg(feature = "ffi")]
492 pub(crate) fn ffi_schema_binding(&self) -> crate::ffi::TableSchemaBinding {
493 crate::ffi::TableSchemaBinding {
494 options: self.typed.compiled.column_family_options.clone(),
495 physical_columns: self.typed.compiled.physical_columns,
496 }
497 }
498
499 #[cfg(feature = "ffi")]
500 pub(crate) fn ffi_shard_db(&self) -> Arc<ReadOnlyDb> {
501 match &self.typed.read_backend {
502 ReadBackend::Shard(db) => Arc::clone(db),
503 ReadBackend::Writable(_) | ReadBackend::Global(_) => {
504 unreachable!("read-only table must use a shard database")
505 }
506 }
507 }
508
509 pub(crate) fn from_shard_metadata(
510 db: Arc<ReadOnlyDb>,
511 name: String,
512 metadata: TableMetadata,
513 ) -> Result<Self> {
514 Ok(Self {
515 typed: TypedRead::from_shard_metadata(db, name, metadata)?,
516 })
517 }
518
519 pub fn schema(&self) -> &TableSchema {
521 self.typed.schema()
522 }
523
524 pub fn key_builder(&self) -> TableKeyBuilder {
526 self.typed.key_builder()
527 }
528
529 pub fn project_by_names<S: AsRef<str>>(&self, field_names: &[S]) -> Result<TableProjection> {
531 self.typed.project_by_names(field_names)
532 }
533
534 pub fn get(&self, key: &TableKey) -> Result<Option<Vec<Value>>> {
536 self.typed.get(key)
537 }
538
539 pub fn multi_get(&self, keys: &[TableKey]) -> Result<Vec<Option<Vec<Value>>>> {
541 self.typed.multi_get(keys)
542 }
543
544 pub fn scan(&self, bucket: u16) -> Result<TableScan> {
546 self.typed.scan(bucket)
547 }
548
549 pub fn scan_bounds(
551 &self,
552 bucket: u16,
553 start_key_inclusive: Option<&TableKey>,
554 end_key_exclusive: Option<&TableKey>,
555 ) -> Result<TableScan> {
556 self.typed
557 .scan_bounds(bucket, start_key_inclusive, end_key_exclusive)
558 }
559}
560
561impl TypedRead {
562 pub(crate) fn from_shard_metadata(
563 db: Arc<ReadOnlyDb>,
564 name: String,
565 metadata: TableMetadata,
566 ) -> Result<Self> {
567 let compiled = compile_table(metadata, db.total_buckets())?;
568 Ok(Self::new(name, compiled, ReadBackend::Shard(db)))
569 }
570
571 pub(crate) fn from_global_metadata(
572 state: Arc<crate::runtime::GlobalReaderState>,
573 name: String,
574 metadata: TableMetadata,
575 ) -> Result<Self> {
576 let total_buckets = state.total_buckets();
577 let compiled = compile_table(metadata, total_buckets)?;
578 Ok(Self::new(name, compiled, ReadBackend::Global(state)))
579 }
580
581 fn new(name: String, compiled: Arc<CompiledTable>, read_backend: ReadBackend) -> Self {
582 Self {
583 name: name.clone(),
584 compiled,
585 read_backend,
586 read_options: ReadOptions::default().with_column_family(name.clone()),
587 scan_options: ScanOptions::default().with_column_family(name),
588 }
589 }
590
591 pub(crate) fn global_state(&self) -> Option<&Arc<crate::runtime::GlobalReaderState>> {
592 match &self.read_backend {
593 ReadBackend::Global(state) => Some(state),
594 ReadBackend::Writable(_) | ReadBackend::Shard(_) => None,
595 }
596 }
597
598 pub fn schema(&self) -> &TableSchema {
600 &self.compiled.schema
601 }
602
603 pub(crate) fn schema_arc(&self) -> Arc<TableSchema> {
604 Arc::clone(&self.compiled.schema)
605 }
606
607 #[cfg(feature = "ffi")]
608 pub(crate) fn ffi_schema_binding(&self) -> crate::ffi::TableSchemaBinding {
609 crate::ffi::TableSchemaBinding {
610 options: self.compiled.column_family_options.clone(),
611 physical_columns: self.compiled.physical_columns,
612 }
613 }
614
615 pub fn key_builder(&self) -> TableKeyBuilder {
617 TableKeyBuilder {
618 compiled: Arc::clone(&self.compiled),
619 values: Vec::with_capacity(self.compiled.key_positions.len()),
620 }
621 }
622
623 pub fn project_by_names<S: AsRef<str>>(&self, field_names: &[S]) -> Result<TableProjection> {
625 build_projection(
626 self.read_backend.clone(),
627 &self.name,
628 Arc::clone(&self.compiled),
629 field_names,
630 )
631 }
632
633 pub fn get(&self, key: &TableKey) -> Result<Option<Vec<Value>>> {
635 self.read_backend
636 .get_with_options(key.inner.bucket, &key.inner.encoded, &self.read_options)?
637 .map(|columns| {
638 assemble_row_from_key_values(&self.compiled, &key.inner.values, &columns)
639 })
640 .transpose()
641 }
642
643 pub fn multi_get(&self, keys: &[TableKey]) -> Result<Vec<Option<Vec<Value>>>> {
645 let requests = keys
646 .iter()
647 .map(|key| (key.inner.bucket, key.inner.encoded.as_slice()))
648 .collect::<Vec<_>>();
649 self.read_backend
650 .multi_get_with_options(&requests, &self.read_options)?
651 .into_iter()
652 .zip(keys)
653 .map(|(columns, key)| {
654 columns
655 .map(|columns| {
656 assemble_row_from_key_values(&self.compiled, &key.inner.values, &columns)
657 })
658 .transpose()
659 })
660 .collect()
661 }
662
663 pub fn scan(&self, bucket: u16) -> Result<TableScan> {
665 self.scan_bounds(bucket, None, None)
666 }
667
668 pub fn scan_bounds(
670 &self,
671 bucket: u16,
672 start_key_inclusive: Option<&TableKey>,
673 end_key_exclusive: Option<&TableKey>,
674 ) -> Result<TableScan> {
675 validate_bound(bucket, start_key_inclusive)?;
676 validate_bound(bucket, end_key_exclusive)?;
677 Ok(TableScan {
678 inner: self.read_backend.scan_with_options_bounds(
679 bucket,
680 start_key_inclusive.map(|key| key.inner.encoded.as_slice()),
681 end_key_exclusive.map(|key| key.inner.encoded.as_slice()),
682 &self.scan_options,
683 )?,
684 _read_backend: self.read_backend.clone(),
685 compiled: Arc::clone(&self.compiled),
686 })
687 }
688}
689
690impl TableProjection {
691 #[cfg(feature = "ffi")]
692 pub(crate) fn ffi_into_raw_access(self) -> crate::ffi::RawTableAccess {
693 crate::ffi::RawTableAccess::new(self.backend, self.read_options, self.scan_options)
694 }
695 pub fn get(&self, key: &TableKey) -> Result<Option<Vec<Value>>> {
697 self.backend
698 .get_with_options(key.inner.bucket, &key.inner.encoded, &self.read_options)?
699 .map(|columns| {
700 assemble_projected_row(
701 &self.compiled,
702 &self.plan,
703 Some(&key.inner.values),
704 &columns,
705 )
706 })
707 .transpose()
708 }
709
710 pub fn multi_get(&self, keys: &[TableKey]) -> Result<Vec<Option<Vec<Value>>>> {
712 let requests = keys
713 .iter()
714 .map(|key| (key.inner.bucket, key.inner.encoded.as_slice()))
715 .collect::<Vec<_>>();
716 self.backend
717 .multi_get_with_options(&requests, &self.read_options)?
718 .into_iter()
719 .zip(keys)
720 .map(|(columns, key)| {
721 columns
722 .map(|columns| {
723 assemble_projected_row(
724 &self.compiled,
725 &self.plan,
726 Some(&key.inner.values),
727 &columns,
728 )
729 })
730 .transpose()
731 })
732 .collect()
733 }
734
735 pub fn scan(&self, bucket: u16) -> Result<ProjectedTableScan> {
737 self.scan_bounds(bucket, None, None)
738 }
739
740 pub fn scan_bounds(
742 &self,
743 bucket: u16,
744 start_key_inclusive: Option<&TableKey>,
745 end_key_exclusive: Option<&TableKey>,
746 ) -> Result<ProjectedTableScan> {
747 validate_bound(bucket, start_key_inclusive)?;
748 validate_bound(bucket, end_key_exclusive)?;
749 Ok(ProjectedTableScan {
750 inner: self.backend.scan_with_options_bounds(
751 bucket,
752 start_key_inclusive.map(|key| key.inner.encoded.as_slice()),
753 end_key_exclusive.map(|key| key.inner.encoded.as_slice()),
754 &self.scan_options,
755 )?,
756 _read_backend: self.backend.clone(),
757 compiled: Arc::clone(&self.compiled),
758 plan: Arc::clone(&self.plan),
759 })
760 }
761}
762
763pub struct TableScan {
765 inner: DbIterator,
766 _read_backend: ReadBackend,
769 compiled: Arc<CompiledTable>,
770}
771
772pub struct ProjectedTableScan {
774 inner: DbIterator,
775 _read_backend: ReadBackend,
776 compiled: Arc<CompiledTable>,
777 plan: Arc<ProjectionPlan>,
778}
779
780impl Iterator for ProjectedTableScan {
781 type Item = Result<Vec<Value>>;
782
783 fn next(&mut self) -> Option<Self::Item> {
784 self.inner.next().map(|row| {
785 let (key, columns) = row?;
786 let key_values = self
787 .plan
788 .has_key_fields
789 .then(|| KeyCodec::decode_row_validated(&self.compiled.key_types, &key))
790 .transpose()?;
791 assemble_projected_row(&self.compiled, &self.plan, key_values.as_deref(), &columns)
792 })
793 }
794}
795
796impl Iterator for TableScan {
797 type Item = Result<Vec<Value>>;
798
799 fn next(&mut self) -> Option<Self::Item> {
800 self.inner.next().map(|row| {
801 let (key, columns) = row?;
802 decode_table_scan_row(&self.compiled, &key, &columns)
803 })
804 }
805}
806
807pub(crate) fn decode_table_scan_row(
808 compiled: &CompiledTable,
809 key: &[u8],
810 columns: &[Option<Bytes>],
811) -> Result<Vec<Value>> {
812 let mut row = vec![Value::Null; compiled.schema.fields.len()];
813 KeyCodec::decode_row_into_positions_validated(
814 &compiled.key_types,
815 key,
816 &compiled.key_positions,
817 &mut row,
818 )?;
819 decode_value_columns(compiled, &mut row, columns)?;
820 Ok(row)
821}
822
823fn assemble_row_from_key_values(
824 compiled: &CompiledTable,
825 key_values: &[Value],
826 columns: &[Option<Bytes>],
827) -> Result<Vec<Value>> {
828 debug_assert_eq!(key_values.len(), compiled.key_positions.len());
829 let mut row = vec![Value::Null; compiled.schema.fields.len()];
830 for (position, value) in compiled.key_positions.iter().zip(key_values) {
831 row[*position] = value.clone();
832 }
833 decode_value_columns(compiled, &mut row, columns)?;
834 Ok(row)
835}
836
837fn encode_table_row(
838 compiled: &CompiledTable,
839 row: &[Value],
840) -> Result<(u16, Vec<u8>, Vec<Vec<u8>>)> {
841 if row.len() != compiled.schema.fields.len() {
842 return Err(TableError::codec("row field count does not match schema"));
843 }
844 let (encoded, prefix_end) = KeyCodec::encode_row_from_positions_validated(
845 &compiled.key_types,
846 row,
847 &compiled.key_positions,
848 compiled.bucket_key_fields,
849 )?;
850 let mut values = Vec::with_capacity(compiled.physical_columns);
851 for (position, logical_type) in compiled.value_positions.iter().zip(&compiled.value_types) {
852 values.push(ValueCodec::encode_validated(logical_type, &row[*position])?);
853 }
854 if values.is_empty() {
855 values.push(vec![1]);
856 }
857 Ok((
858 compiled.bucket_hash.bucket(&encoded[..prefix_end]),
859 encoded,
860 values,
861 ))
862}
863
864fn assemble_projected_row(
865 compiled: &CompiledTable,
866 plan: &ProjectionPlan,
867 key_values: Option<&[Value]>,
868 columns: &[Option<Bytes>],
869) -> Result<Vec<Value>> {
870 let mut row = Vec::with_capacity(plan.sources.len());
871 for source in &plan.sources {
872 match source {
873 ProjectedFieldSource::Key(key_index) => {
874 row.push(key_values.expect("projection decoded key fields")[*key_index].clone());
875 }
876 ProjectedFieldSource::Value {
877 projected_column,
878 physical_column,
879 } => {
880 let value = columns
881 .get(*projected_column)
882 .and_then(|value| value.as_ref())
883 .ok_or_else(|| TableError::codec("table row is missing a value column"))?;
884 row.push(ValueCodec::decode_bytes_validated(
885 &compiled.value_types[*physical_column],
886 value.clone(),
887 )?);
888 }
889 }
890 }
891 Ok(row)
892}
893
894fn decode_value_columns(
895 compiled: &CompiledTable,
896 row: &mut [Value],
897 columns: &[Option<Bytes>],
898) -> Result<()> {
899 for (column, (logical_type, position)) in compiled
900 .value_types
901 .iter()
902 .zip(&compiled.value_positions)
903 .enumerate()
904 {
905 let value = columns
906 .get(column)
907 .and_then(|value| value.as_ref())
908 .ok_or_else(|| TableError::codec("table row is missing a value column"))?;
909 row[*position] = ValueCodec::decode_bytes_validated(logical_type, value.clone())?;
910 }
911 Ok(())
912}
913
914fn build_projection<S: AsRef<str>>(
915 backend: ReadBackend,
916 name: &str,
917 compiled: Arc<CompiledTable>,
918 field_names: &[S],
919) -> Result<TableProjection> {
920 let (plan, read_options, scan_options) =
921 build_projection_parts(name, Arc::clone(&compiled), field_names)?;
922 Ok(TableProjection {
923 backend,
924 compiled,
925 plan,
926 read_options,
927 scan_options,
928 })
929}
930
931fn build_projection_parts<S: AsRef<str>>(
932 name: &str,
933 compiled: Arc<CompiledTable>,
934 field_names: &[S],
935) -> Result<(Arc<ProjectionPlan>, ReadOptions, ScanOptions)> {
936 if field_names.is_empty() {
937 return Err(TableError::InvalidSchema(
938 "table projection must contain at least one field".to_string(),
939 ));
940 }
941 let mut seen = std::collections::HashSet::with_capacity(field_names.len());
942 let mut sources = Vec::with_capacity(field_names.len());
943 let mut physical_columns = Vec::new();
944 let mut has_key_fields = false;
945 for field_name in field_names {
946 let field_name = field_name.as_ref();
947 if !seen.insert(field_name) {
948 return Err(TableError::InvalidSchema(format!(
949 "duplicate projection field: '{field_name}'"
950 )));
951 }
952 let schema_position = compiled
953 .schema
954 .fields
955 .iter()
956 .position(|field| field.name == field_name)
957 .ok_or_else(|| {
958 TableError::InvalidSchema(format!("projection field '{field_name}' does not exist"))
959 })?;
960 if let Some(key_index) = compiled
961 .key_positions
962 .iter()
963 .position(|position| *position == schema_position)
964 {
965 sources.push(ProjectedFieldSource::Key(key_index));
966 has_key_fields = true;
967 } else {
968 let physical_column = compiled
969 .value_positions
970 .iter()
971 .position(|position| *position == schema_position)
972 .expect("compiled table maps every non-key field");
973 let projected_column = physical_columns.len();
974 physical_columns.push(physical_column);
975 sources.push(ProjectedFieldSource::Value {
976 projected_column,
977 physical_column,
978 });
979 }
980 }
981 if physical_columns.is_empty() {
982 physical_columns.push(0);
983 }
984 Ok((
985 Arc::new(ProjectionPlan {
986 sources,
987 has_key_fields,
988 }),
989 ReadOptions::for_columns_in_family(name.to_string(), physical_columns.clone())
990 .bound_to_column_family_schema(
991 compiled.column_family_options.clone(),
992 compiled.physical_columns,
993 ),
994 ScanOptions::for_columns(physical_columns)
995 .with_column_family(name.to_string())
996 .bound_to_column_family_schema(
997 compiled.column_family_options.clone(),
998 compiled.physical_columns,
999 ),
1000 ))
1001}
1002
1003#[cfg(feature = "ffi")]
1004pub(crate) fn build_scan_options_for_fields<S: AsRef<str>>(
1005 name: &str,
1006 compiled: Arc<CompiledTable>,
1007 field_names: &[S],
1008) -> Result<ScanOptions> {
1009 let (_, _, scan_options) = build_projection_parts(name, compiled, field_names)?;
1010 Ok(scan_options)
1011}
1012
1013pub(crate) fn compile_table(
1014 metadata: TableMetadata,
1015 total_buckets: u32,
1016) -> Result<Arc<CompiledTable>> {
1017 metadata.validate()?;
1018 let column_family_options = ColumnFamilyOptions {
1019 metadata: Some(metadata.to_value()?),
1020 ..ColumnFamilyOptions::default()
1021 };
1022 let positions = metadata
1023 .schema
1024 .fields
1025 .iter()
1026 .enumerate()
1027 .map(|(position, field)| (field.id, position))
1028 .collect::<HashMap<FieldId, usize>>();
1029 let key_positions = metadata
1030 .layout
1031 .key_fields
1032 .iter()
1033 .map(|id| positions[id])
1034 .collect::<Vec<_>>();
1035 let key_types = key_positions
1036 .iter()
1037 .map(|position| metadata.schema.fields[*position].logical_type.clone())
1038 .collect::<Vec<_>>();
1039 let value_positions = metadata
1040 .layout
1041 .value_columns
1042 .iter()
1043 .map(|column| positions[&column.field_id])
1044 .collect::<Vec<_>>();
1045 let value_types = value_positions
1046 .iter()
1047 .map(|position| metadata.schema.fields[*position].logical_type.clone())
1048 .collect::<Vec<_>>();
1049 Ok(Arc::new(CompiledTable {
1050 schema: Arc::new(metadata.schema),
1051 column_family_options,
1052 key_positions,
1053 key_types,
1054 bucket_key_fields: metadata.layout.bucket_fields.len(),
1055 value_positions,
1056 value_types,
1057 physical_columns: metadata.layout.value_columns.len().max(1),
1058 bucket_hash: BucketHash::new(total_buckets)?,
1059 }))
1060}
1061
1062pub(crate) fn load_table_metadata(schema: &Schema, name: &str) -> Result<TableMetadata> {
1063 let id = schema
1064 .column_family_ids()
1065 .get(name)
1066 .copied()
1067 .ok_or_else(|| TableError::InvalidSchema(format!("unknown table '{name}'")))?;
1068 load_table_metadata_from_options(
1069 &schema.column_family_options_in_family(id),
1070 schema.num_columns_in_family(id),
1071 name,
1072 )
1073}
1074
1075pub(crate) fn load_table_metadata_from_snapshot(
1076 snapshot: &ShardSnapshotMetadata,
1077 name: &str,
1078) -> Result<TableMetadata> {
1079 let family = snapshot
1080 .column_families
1081 .get(name)
1082 .ok_or_else(|| TableError::InvalidSchema(format!("unknown table '{name}'")))?;
1083 load_table_metadata_from_options(&family.options, Some(family.num_columns), name)
1084}
1085
1086pub(crate) fn load_table_metadata_for_shard(
1087 config: &Config,
1088 shard: &ShardSnapshotRef,
1089 name: &str,
1090) -> Result<TableMetadata> {
1091 let snapshot =
1092 cobble::load_shard_snapshot_metadata(config, &shard.db_id, &shard.manifest_path)?;
1093 if snapshot.snapshot_id != shard.snapshot_id {
1094 return Err(TableError::InvalidSchema(format!(
1095 "shard manifest snapshot {} does not match assigned snapshot {}",
1096 snapshot.snapshot_id, shard.snapshot_id
1097 )));
1098 }
1099 load_table_metadata_from_snapshot(&snapshot, name)
1100}
1101
1102pub(crate) fn table_metadata_from_shard_snapshot(
1103 snapshot: &ShardSnapshotMetadata,
1104) -> Result<std::collections::BTreeMap<String, TableMetadata>> {
1105 let mut tables = std::collections::BTreeMap::new();
1106 for (name, family) in &snapshot.column_families {
1107 let is_table = family
1108 .options
1109 .metadata
1110 .as_ref()
1111 .and_then(|metadata| metadata.get("format"))
1112 .and_then(serde_json::Value::as_str)
1113 == Some(crate::metadata::TABLE_METADATA_FORMAT);
1114 if !is_table {
1115 continue;
1116 }
1117 tables.insert(
1118 name.clone(),
1119 load_table_metadata_from_options(&family.options, Some(family.num_columns), name)?,
1120 );
1121 }
1122 if tables.is_empty() {
1123 return Err(TableError::InvalidSchema(
1124 "table shard snapshot contains no table metadata".into(),
1125 ));
1126 }
1127 Ok(tables)
1128}
1129
1130fn load_table_metadata_from_options(
1131 options: &ColumnFamilyOptions,
1132 num_columns: Option<usize>,
1133 name: &str,
1134) -> Result<TableMetadata> {
1135 let metadata = load_metadata(options)?;
1136 if num_columns != Some(metadata.layout.value_columns.len().max(1)) {
1137 return Err(TableError::InvalidSchema(format!(
1138 "table '{name}' has an incompatible physical column count"
1139 )));
1140 }
1141 Ok(metadata)
1142}
1143
1144fn validate_bound(bucket: u16, key: Option<&TableKey>) -> Result<()> {
1145 if key.is_some_and(|key| key.inner.bucket != bucket) {
1146 return Err(TableError::codec(
1147 "table scan bound belongs to a different bucket",
1148 ));
1149 }
1150 Ok(())
1151}
1152
1153fn load_metadata(options: &ColumnFamilyOptions) -> Result<TableMetadata> {
1154 let metadata = options
1155 .metadata
1156 .as_ref()
1157 .ok_or_else(|| TableError::InvalidSchema("column family is not a table".to_string()))?;
1158 TableMetadata::from_value(metadata)
1159}
1160
1161pub(crate) fn validate_name(name: String) -> Result<String> {
1162 if name.is_empty() || name != name.trim() {
1163 return Err(TableError::InvalidSchema(
1164 "table name must be non-empty without surrounding whitespace".to_string(),
1165 ));
1166 }
1167 Ok(name)
1168}