1use std::collections::HashMap;
27use std::mem;
28use std::sync::Arc;
29
30use arrow::array::{Array, BinaryArray, BinaryBuilder, UInt64Array};
31use arrow::datatypes::{DataType, Field, Schema};
32use arrow::record_batch::RecordBatch;
33use bitcode::{Decode, Encode};
34
35use crate::constants::CONSTRAINT_SCAN_CHUNK_SIZE;
36use crate::constraints::{
37 ConstraintId, ConstraintRecord, decode_constraint_row_id, encode_constraint_row_id,
38};
39use crate::types::{FieldId, ROW_ID_FIELD_ID, RowId, TableId};
40use llkv_column_map::store::scan::{
41 PrimitiveSortedVisitor, PrimitiveSortedWithRowIdsVisitor, PrimitiveVisitor,
42 PrimitiveWithRowIdsVisitor, ScanBuilder, ScanOptions,
43};
44
45use llkv_column_map::types::LogicalFieldId;
46use llkv_column_map::{
47 ColumnStore,
48 store::{GatherNullPolicy, ROW_ID_COLUMN_NAME, rowid_fid},
49};
50use llkv_result::{self, Result as LlkvResult};
51use llkv_storage::pager::{MemPager, Pager};
52use simd_r_drive_entry_handle::EntryHandle;
53
54use crate::reserved::*;
56
57#[inline]
61fn lfid(table_id: TableId, col_id: u32) -> LogicalFieldId {
62 LogicalFieldId::for_user(table_id, col_id)
63}
64
65#[inline]
67fn rid_table(table_id: TableId) -> u64 {
68 LogicalFieldId::for_user(table_id, ROW_ID_FIELD_ID).into()
69}
70
71#[inline]
73fn rid_col(table_id: TableId, col_id: u32) -> u64 {
74 rowid_fid(lfid(table_id, col_id)).into()
75}
76
77#[inline]
78fn constraint_meta_lfid() -> LogicalFieldId {
79 lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CONSTRAINT_META_ID)
80}
81
82#[inline]
83fn constraint_name_lfid() -> LogicalFieldId {
84 lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CONSTRAINT_NAME_ID)
85}
86
87#[inline]
88fn constraint_row_lfid() -> LogicalFieldId {
89 rowid_fid(constraint_meta_lfid())
90}
91
92#[derive(Clone, Debug, Encode, Decode)]
93pub struct ConstraintNameRecord {
94 pub constraint_id: ConstraintId,
95 pub name: Option<String>,
96}
97
98fn decode_constraint_record(bytes: &[u8]) -> LlkvResult<ConstraintRecord> {
99 bitcode::decode(bytes).map_err(|err| {
100 llkv_result::Error::Internal(format!("failed to decode constraint metadata: {err}"))
101 })
102}
103
104struct ConstraintRowCollector<'a, P, F>
105where
106 P: Pager<Blob = EntryHandle> + Send + Sync,
107 F: FnMut(Vec<ConstraintRecord>),
108{
109 store: &'a ColumnStore<P>,
110 lfid: LogicalFieldId,
111 table_id: TableId,
112 on_batch: &'a mut F,
113 buffer: Vec<RowId>,
114 error: Option<llkv_result::Error>,
115}
116
117impl<'a, P, F> ConstraintRowCollector<'a, P, F>
118where
119 P: Pager<Blob = EntryHandle> + Send + Sync,
120 F: FnMut(Vec<ConstraintRecord>),
121{
122 fn flush_buffer(&mut self) -> LlkvResult<()> {
123 if self.buffer.is_empty() {
124 return Ok(());
125 }
126
127 let row_ids = mem::take(&mut self.buffer);
128 let batch =
129 self.store
130 .gather_rows(&[self.lfid], &row_ids, GatherNullPolicy::IncludeNulls)?;
131
132 if batch.num_columns() == 0 {
133 return Ok(());
134 }
135
136 let array = batch
137 .column(0)
138 .as_any()
139 .downcast_ref::<BinaryArray>()
140 .ok_or_else(|| {
141 llkv_result::Error::Internal(
142 "constraint metadata column stored unexpected type".into(),
143 )
144 })?;
145
146 let mut records = Vec::with_capacity(row_ids.len());
147 for (idx, row_id) in row_ids.into_iter().enumerate() {
148 if array.is_null(idx) {
149 continue;
150 }
151
152 let record = decode_constraint_record(array.value(idx))?;
153 let (table_from_id, constraint_id) = decode_constraint_row_id(row_id);
154 if table_from_id != self.table_id {
155 continue;
156 }
157 if record.constraint_id != constraint_id {
158 return Err(llkv_result::Error::Internal(
159 "constraint metadata id mismatch".into(),
160 ));
161 }
162 records.push(record);
163 }
164
165 if !records.is_empty() {
166 (self.on_batch)(records);
167 }
168
169 Ok(())
170 }
171
172 fn finish(&mut self) -> LlkvResult<()> {
173 if let Some(err) = self.error.take() {
174 return Err(err);
175 }
176 self.flush_buffer()
177 }
178}
179
180impl<'a, P, F> PrimitiveVisitor for ConstraintRowCollector<'a, P, F>
181where
182 P: Pager<Blob = EntryHandle> + Send + Sync,
183 F: FnMut(Vec<ConstraintRecord>),
184{
185 fn u64_chunk(&mut self, values: &UInt64Array) {
186 if self.error.is_some() {
187 return;
188 }
189
190 for idx in 0..values.len() {
191 let row_id = values.value(idx);
192 let (table_id, _) = decode_constraint_row_id(row_id);
193 if table_id != self.table_id {
194 continue;
195 }
196 self.buffer.push(row_id);
197 if self.buffer.len() >= CONSTRAINT_SCAN_CHUNK_SIZE
198 && let Err(err) = self.flush_buffer()
199 {
200 self.error = Some(err);
201 return;
202 }
203 }
204 }
205}
206
207impl<'a, P, F> PrimitiveWithRowIdsVisitor for ConstraintRowCollector<'a, P, F>
208where
209 P: Pager<Blob = EntryHandle> + Send + Sync,
210 F: FnMut(Vec<ConstraintRecord>),
211{
212}
213
214impl<'a, P, F> PrimitiveSortedVisitor for ConstraintRowCollector<'a, P, F>
215where
216 P: Pager<Blob = EntryHandle> + Send + Sync,
217 F: FnMut(Vec<ConstraintRecord>),
218{
219}
220
221impl<'a, P, F> PrimitiveSortedWithRowIdsVisitor for ConstraintRowCollector<'a, P, F>
222where
223 P: Pager<Blob = EntryHandle> + Send + Sync,
224 F: FnMut(Vec<ConstraintRecord>),
225{
226}
227
228#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
234pub struct TableMeta {
235 pub table_id: TableId,
237 pub name: Option<String>,
239 pub created_at_micros: u64,
241 pub flags: u32,
243 pub epoch: u64,
245 pub view_definition: Option<String>,
248}
249
250#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
254pub struct ColMeta {
255 pub col_id: u32,
257 pub name: Option<String>,
259 pub flags: u32,
261 pub default: Option<Vec<u8>>,
263}
264
265#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
269pub struct SchemaMeta {
270 pub name: String,
272 pub created_at_micros: u64,
274 pub flags: u32,
276}
277
278#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
283pub struct CustomTypeMeta {
284 pub name: String,
286 pub base_type_sql: String,
289 pub created_at_micros: u64,
291}
292
293#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
297pub struct MultiColumnIndexEntryMeta {
298 pub index_name: Option<String>,
300 pub canonical_name: String,
302 pub column_ids: Vec<FieldId>,
304 pub unique: bool,
306}
307
308#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
310pub struct SingleColumnIndexEntryMeta {
311 pub index_name: String,
313 pub canonical_name: String,
315 pub column_id: FieldId,
317 pub column_name: String,
319 pub unique: bool,
321 pub ascending: bool,
323 pub nulls_first: bool,
325}
326
327#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
329pub struct TableSingleColumnIndexMeta {
330 pub table_id: TableId,
332 pub indexes: Vec<SingleColumnIndexEntryMeta>,
334}
335
336#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
338pub struct TableMultiColumnIndexMeta {
339 pub table_id: TableId,
341 pub indexes: Vec<MultiColumnIndexEntryMeta>,
343}
344
345#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq)]
347pub enum TriggerTimingMeta {
348 Before,
349 After,
350 InsteadOf,
351}
352
353#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
355pub enum TriggerEventMeta {
356 Insert,
357 Update { columns: Vec<String> },
358 Delete,
359}
360
361#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
363pub struct TriggerEntryMeta {
364 pub name: String,
366 pub canonical_name: String,
368 pub timing: TriggerTimingMeta,
370 pub event: TriggerEventMeta,
372 pub for_each_row: bool,
374 pub condition: Option<String>,
376 pub body_sql: String,
378}
379
380#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)]
382pub struct TableTriggerMeta {
383 pub table_id: TableId,
385 pub triggers: Vec<TriggerEntryMeta>,
387}
388
389pub struct SysCatalog<'a, P = MemPager>
402where
403 P: Pager<Blob = EntryHandle> + Send + Sync,
404{
405 store: &'a ColumnStore<P>,
406}
407
408impl<'a, P> SysCatalog<'a, P>
409where
410 P: Pager<Blob = EntryHandle> + Send + Sync,
411{
412 fn write_null_entries(&self, meta_field: LogicalFieldId, row_ids: &[RowId]) -> LlkvResult<()> {
413 if row_ids.is_empty() {
414 return Ok(());
415 }
416
417 let lfid_val: u64 = meta_field.into();
418 let schema = Arc::new(Schema::new(vec![
419 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
420 Field::new("meta", DataType::Binary, true).with_metadata(HashMap::from([(
421 crate::constants::FIELD_ID_META_KEY.to_string(),
422 lfid_val.to_string(),
423 )])),
424 ]));
425
426 let row_array = Arc::new(UInt64Array::from(row_ids.to_vec()));
427 let mut builder = BinaryBuilder::new();
428 for _ in row_ids {
429 builder.append_null();
430 }
431 let meta_array = Arc::new(builder.finish());
432
433 let batch = RecordBatch::try_new(schema, vec![row_array, meta_array])?;
434 self.store.append(&batch)?;
435 Ok(())
436 }
437
438 pub fn new(store: &'a ColumnStore<P>) -> Self {
440 Self { store }
441 }
442
443 pub fn put_table_meta(&self, meta: &TableMeta) {
448 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TABLE_META_ID).into();
449 let schema = Arc::new(Schema::new(vec![
450 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
451 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
452 crate::constants::FIELD_ID_META_KEY.to_string(),
453 lfid_val.to_string(),
454 )])),
455 ]));
456
457 let row_id = Arc::new(UInt64Array::from(vec![rid_table(meta.table_id)]));
458 let meta_encoded = bitcode::encode(meta);
459 let meta_bytes = Arc::new(BinaryArray::from(vec![meta_encoded.as_slice()]));
460
461 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes]).unwrap();
462 self.store.append(&batch).unwrap();
463 }
464
465 pub fn get_table_meta(&self, table_id: TableId) -> Option<TableMeta> {
469 let row_id = rid_table(table_id);
470 let catalog_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TABLE_META_ID);
471 let batch = self
472 .store
473 .gather_rows(&[catalog_field], &[row_id], GatherNullPolicy::IncludeNulls)
474 .ok()?;
475
476 if batch.num_rows() == 0 || batch.num_columns() == 0 {
477 return None;
478 }
479
480 let array = batch
481 .column(0)
482 .as_any()
483 .downcast_ref::<BinaryArray>()
484 .expect("table meta column must be BinaryArray");
485
486 if array.is_null(0) {
487 return None;
488 }
489
490 bitcode::decode(array.value(0)).ok()
491 }
492
493 pub fn put_col_meta(&self, table_id: TableId, meta: &ColMeta) {
495 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_COL_META_ID).into();
496 let schema = Arc::new(Schema::new(vec![
497 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
498 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
499 crate::constants::FIELD_ID_META_KEY.to_string(),
500 lfid_val.to_string(),
501 )])),
502 ]));
503
504 let rid_value = rid_col(table_id, meta.col_id);
505 let row_id = Arc::new(UInt64Array::from(vec![rid_value]));
506 let meta_encoded = bitcode::encode(meta);
507 let meta_bytes = Arc::new(BinaryArray::from(vec![meta_encoded.as_slice()]));
508
509 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes]).unwrap();
510 self.store.append(&batch).unwrap();
511 }
512
513 pub fn get_cols_meta(&self, table_id: TableId, col_ids: &[u32]) -> Vec<Option<ColMeta>> {
515 if col_ids.is_empty() {
516 return Vec::new();
517 }
518
519 let row_ids: Vec<RowId> = col_ids.iter().map(|&cid| rid_col(table_id, cid)).collect();
520 let catalog_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_COL_META_ID);
521
522 let batch =
523 match self
524 .store
525 .gather_rows(&[catalog_field], &row_ids, GatherNullPolicy::IncludeNulls)
526 {
527 Ok(batch) => batch,
528 Err(_) => return vec![None; col_ids.len()],
529 };
530
531 let meta_col = batch
532 .column(0)
533 .as_any()
534 .downcast_ref::<BinaryArray>()
535 .expect("catalog meta column should be Binary");
536
537 col_ids
538 .iter()
539 .enumerate()
540 .map(|(idx, _)| {
541 if meta_col.is_null(idx) {
542 None
543 } else {
544 bitcode::decode(meta_col.value(idx)).ok()
545 }
546 })
547 .collect()
548 }
549
550 pub fn delete_col_meta(&self, table_id: TableId, col_ids: &[FieldId]) -> LlkvResult<()> {
552 if col_ids.is_empty() {
553 return Ok(());
554 }
555
556 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_COL_META_ID);
557 let row_ids: Vec<RowId> = col_ids
558 .iter()
559 .map(|&col_id| rid_col(table_id, col_id))
560 .collect();
561 self.write_null_entries(meta_field, &row_ids)
562 }
563
564 pub fn delete_table_meta(&self, table_id: TableId) -> LlkvResult<()> {
566 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TABLE_META_ID);
567 let row_id = rid_table(table_id);
568 self.write_null_entries(meta_field, &[row_id])
569 }
570
571 pub fn delete_constraint_records(
573 &self,
574 table_id: TableId,
575 constraint_ids: &[ConstraintId],
576 ) -> LlkvResult<()> {
577 if constraint_ids.is_empty() {
578 return Ok(());
579 }
580
581 let meta_field = constraint_meta_lfid();
582 let row_ids: Vec<RowId> = constraint_ids
583 .iter()
584 .map(|&constraint_id| encode_constraint_row_id(table_id, constraint_id))
585 .collect();
586 self.write_null_entries(meta_field, &row_ids)
587 }
588
589 pub fn delete_constraint_names(
591 &self,
592 table_id: TableId,
593 constraint_ids: &[ConstraintId],
594 ) -> LlkvResult<()> {
595 if constraint_ids.is_empty() {
596 return Ok(());
597 }
598
599 let lfid = constraint_name_lfid();
600 let row_ids: Vec<RowId> = constraint_ids
601 .iter()
602 .map(|&constraint_id| encode_constraint_row_id(table_id, constraint_id))
603 .collect();
604 self.write_null_entries(lfid, &row_ids)
605 }
606
607 pub fn delete_multi_column_indexes(&self, table_id: TableId) -> LlkvResult<()> {
609 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_MULTI_COLUMN_UNIQUE_META_ID);
610 let row_id = rid_table(table_id);
611 self.write_null_entries(meta_field, &[row_id])
612 }
613
614 pub fn delete_single_column_indexes(&self, table_id: TableId) -> LlkvResult<()> {
616 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SINGLE_COLUMN_INDEX_META_ID);
617 let row_id = rid_table(table_id);
618 self.write_null_entries(meta_field, &[row_id])
619 }
620
621 pub fn put_multi_column_indexes(
623 &self,
624 table_id: TableId,
625 indexes: &[MultiColumnIndexEntryMeta],
626 ) -> LlkvResult<()> {
627 let lfid_val: u64 =
628 lfid(CATALOG_TABLE_ID, CATALOG_FIELD_MULTI_COLUMN_UNIQUE_META_ID).into();
629 let schema = Arc::new(Schema::new(vec![
630 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
631 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
632 crate::constants::FIELD_ID_META_KEY.to_string(),
633 lfid_val.to_string(),
634 )])),
635 ]));
636
637 let row_id = Arc::new(UInt64Array::from(vec![rid_table(table_id)]));
638 let meta = TableMultiColumnIndexMeta {
639 table_id,
640 indexes: indexes.to_vec(),
641 };
642 let encoded = bitcode::encode(&meta);
643 let meta_bytes = Arc::new(BinaryArray::from(vec![encoded.as_slice()]));
644
645 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes])?;
646 self.store.append(&batch)?;
647 Ok(())
648 }
649
650 pub fn put_single_column_indexes(
652 &self,
653 table_id: TableId,
654 indexes: &[SingleColumnIndexEntryMeta],
655 ) -> LlkvResult<()> {
656 let lfid_val: u64 =
657 lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SINGLE_COLUMN_INDEX_META_ID).into();
658 let schema = Arc::new(Schema::new(vec![
659 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
660 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
661 crate::constants::FIELD_ID_META_KEY.to_string(),
662 lfid_val.to_string(),
663 )])),
664 ]));
665
666 let row_id = Arc::new(UInt64Array::from(vec![rid_table(table_id)]));
667 let meta = TableSingleColumnIndexMeta {
668 table_id,
669 indexes: indexes.to_vec(),
670 };
671 let encoded = bitcode::encode(&meta);
672 let meta_bytes = Arc::new(BinaryArray::from(vec![encoded.as_slice()]));
673
674 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes])?;
675 self.store.append(&batch)?;
676 Ok(())
677 }
678
679 pub fn get_multi_column_indexes(
681 &self,
682 table_id: TableId,
683 ) -> LlkvResult<Vec<MultiColumnIndexEntryMeta>> {
684 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_MULTI_COLUMN_UNIQUE_META_ID);
685 let row_id = rid_table(table_id);
686 let batch = match self
687 .store
688 .gather_rows(&[lfid], &[row_id], GatherNullPolicy::IncludeNulls)
689 {
690 Ok(batch) => batch,
691 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
692 Err(err) => return Err(err),
693 };
694
695 if batch.num_columns() == 0 || batch.num_rows() == 0 {
696 return Ok(Vec::new());
697 }
698
699 let array = batch
700 .column(0)
701 .as_any()
702 .downcast_ref::<BinaryArray>()
703 .ok_or_else(|| {
704 llkv_result::Error::Internal(
705 "catalog multi-column index column stored unexpected type".into(),
706 )
707 })?;
708
709 if array.is_null(0) {
710 return Ok(Vec::new());
711 }
712
713 let meta: TableMultiColumnIndexMeta = bitcode::decode(array.value(0)).map_err(|err| {
714 llkv_result::Error::Internal(format!(
715 "failed to decode multi-column index metadata: {err}"
716 ))
717 })?;
718
719 Ok(meta.indexes)
720 }
721
722 pub fn get_single_column_indexes(
724 &self,
725 table_id: TableId,
726 ) -> LlkvResult<Vec<SingleColumnIndexEntryMeta>> {
727 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SINGLE_COLUMN_INDEX_META_ID);
728 let row_id = rid_table(table_id);
729 let batch = match self
730 .store
731 .gather_rows(&[lfid], &[row_id], GatherNullPolicy::IncludeNulls)
732 {
733 Ok(batch) => batch,
734 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
735 Err(err) => return Err(err),
736 };
737
738 if batch.num_columns() == 0 || batch.num_rows() == 0 {
739 return Ok(Vec::new());
740 }
741
742 let array = batch
743 .column(0)
744 .as_any()
745 .downcast_ref::<BinaryArray>()
746 .ok_or_else(|| {
747 llkv_result::Error::Internal(
748 "catalog single-column index column stored unexpected type".into(),
749 )
750 })?;
751
752 if array.is_null(0) {
753 return Ok(Vec::new());
754 }
755
756 let meta: TableSingleColumnIndexMeta = bitcode::decode(array.value(0)).map_err(|err| {
757 llkv_result::Error::Internal(format!(
758 "failed to decode single-column index metadata: {err}"
759 ))
760 })?;
761
762 Ok(meta.indexes)
763 }
764
765 pub fn put_triggers(&self, table_id: TableId, triggers: &[TriggerEntryMeta]) -> LlkvResult<()> {
767 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TRIGGER_META_ID).into();
768 let schema = Arc::new(Schema::new(vec![
769 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
770 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
771 crate::constants::FIELD_ID_META_KEY.to_string(),
772 lfid_val.to_string(),
773 )])),
774 ]));
775
776 let row_id = Arc::new(UInt64Array::from(vec![rid_table(table_id)]));
777 let meta = TableTriggerMeta {
778 table_id,
779 triggers: triggers.to_vec(),
780 };
781 let encoded = bitcode::encode(&meta);
782 let meta_bytes = Arc::new(BinaryArray::from(vec![encoded.as_slice()]));
783
784 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes])?;
785 self.store.append(&batch)?;
786 Ok(())
787 }
788
789 pub fn delete_triggers(&self, table_id: TableId) -> LlkvResult<()> {
791 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TRIGGER_META_ID);
792 let row_id = rid_table(table_id);
793 self.write_null_entries(meta_field, &[row_id])
794 }
795
796 pub fn get_triggers(&self, table_id: TableId) -> LlkvResult<Vec<TriggerEntryMeta>> {
798 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TRIGGER_META_ID);
799 let row_id = rid_table(table_id);
800 let batch = match self
801 .store
802 .gather_rows(&[lfid], &[row_id], GatherNullPolicy::IncludeNulls)
803 {
804 Ok(batch) => batch,
805 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
806 Err(err) => return Err(err),
807 };
808
809 if batch.num_columns() == 0 || batch.num_rows() == 0 {
810 return Ok(Vec::new());
811 }
812
813 let array = batch
814 .column(0)
815 .as_any()
816 .downcast_ref::<BinaryArray>()
817 .ok_or_else(|| {
818 llkv_result::Error::Internal(
819 "catalog trigger metadata column stored unexpected type".into(),
820 )
821 })?;
822
823 if array.is_null(0) {
824 return Ok(Vec::new());
825 }
826
827 let meta: TableTriggerMeta = bitcode::decode(array.value(0)).map_err(|err| {
828 llkv_result::Error::Internal(format!("failed to decode trigger metadata: {err}"))
829 })?;
830
831 Ok(meta.triggers)
832 }
833
834 pub fn all_multi_column_index_metas(&self) -> LlkvResult<Vec<TableMultiColumnIndexMeta>> {
836 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_MULTI_COLUMN_UNIQUE_META_ID);
837 let row_field = rowid_fid(meta_field);
838
839 struct RowIdCollector {
840 row_ids: Vec<RowId>,
841 }
842
843 impl PrimitiveVisitor for RowIdCollector {
844 fn u64_chunk(&mut self, values: &UInt64Array) {
845 for i in 0..values.len() {
846 self.row_ids.push(values.value(i));
847 }
848 }
849 }
850 impl PrimitiveWithRowIdsVisitor for RowIdCollector {}
851 impl PrimitiveSortedVisitor for RowIdCollector {}
852 impl PrimitiveSortedWithRowIdsVisitor for RowIdCollector {}
853
854 let mut collector = RowIdCollector {
855 row_ids: Vec::new(),
856 };
857 match ScanBuilder::new(self.store, row_field)
858 .options(ScanOptions::default())
859 .run(&mut collector)
860 {
861 Ok(()) => {}
862 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
863 Err(err) => return Err(err),
864 }
865
866 if collector.row_ids.is_empty() {
867 return Ok(Vec::new());
868 }
869
870 let batch = match self.store.gather_rows(
871 &[meta_field],
872 &collector.row_ids,
873 GatherNullPolicy::IncludeNulls,
874 ) {
875 Ok(batch) => batch,
876 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
877 Err(err) => return Err(err),
878 };
879
880 if batch.num_columns() == 0 {
881 return Ok(Vec::new());
882 }
883
884 let array = batch
885 .column(0)
886 .as_any()
887 .downcast_ref::<BinaryArray>()
888 .ok_or_else(|| {
889 llkv_result::Error::Internal(
890 "catalog multi-column index column stored unexpected type".into(),
891 )
892 })?;
893
894 let mut metas = Vec::with_capacity(batch.num_rows());
895 for idx in 0..batch.num_rows() {
896 if array.is_null(idx) {
897 continue;
898 }
899 let meta: TableMultiColumnIndexMeta =
900 bitcode::decode(array.value(idx)).map_err(|err| {
901 llkv_result::Error::Internal(format!(
902 "failed to decode multi-column index metadata: {err}"
903 ))
904 })?;
905 metas.push(meta);
906 }
907
908 Ok(metas)
909 }
910
911 pub fn put_constraint_records(
913 &self,
914 table_id: TableId,
915 records: &[ConstraintRecord],
916 ) -> LlkvResult<()> {
917 if records.is_empty() {
918 return Ok(());
919 }
920
921 let lfid_val: u64 = constraint_meta_lfid().into();
922 let schema = Arc::new(Schema::new(vec![
923 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
924 Field::new("constraint", DataType::Binary, false).with_metadata(HashMap::from([(
925 crate::constants::FIELD_ID_META_KEY.to_string(),
926 lfid_val.to_string(),
927 )])),
928 ]));
929
930 let row_ids: Vec<RowId> = records
931 .iter()
932 .map(|record| encode_constraint_row_id(table_id, record.constraint_id))
933 .collect();
934
935 let row_ids_array = Arc::new(UInt64Array::from(row_ids));
936 let payload_array = Arc::new(BinaryArray::from_iter_values(
937 records.iter().map(bitcode::encode),
938 ));
939
940 let batch = RecordBatch::try_new(schema, vec![row_ids_array, payload_array])?;
941 self.store.append(&batch)?;
942 Ok(())
943 }
944
945 pub fn put_constraint_names(
947 &self,
948 table_id: TableId,
949 names: &[ConstraintNameRecord],
950 ) -> LlkvResult<()> {
951 if names.is_empty() {
952 return Ok(());
953 }
954
955 let lfid_val: u64 = constraint_name_lfid().into();
956 let schema = Arc::new(Schema::new(vec![
957 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
958 Field::new("constraint_name", DataType::Binary, false).with_metadata(HashMap::from([
959 (
960 crate::constants::FIELD_ID_META_KEY.to_string(),
961 lfid_val.to_string(),
962 ),
963 ])),
964 ]));
965
966 let row_ids: Vec<RowId> = names
967 .iter()
968 .map(|record| encode_constraint_row_id(table_id, record.constraint_id))
969 .collect();
970 let row_ids_array = Arc::new(UInt64Array::from(row_ids));
971 let payload_array = Arc::new(BinaryArray::from_iter_values(
972 names.iter().map(bitcode::encode),
973 ));
974
975 let batch = RecordBatch::try_new(schema, vec![row_ids_array, payload_array])?;
976 self.store.append(&batch)?;
977 Ok(())
978 }
979
980 pub fn get_constraint_records(
982 &self,
983 table_id: TableId,
984 constraint_ids: &[ConstraintId],
985 ) -> LlkvResult<Vec<Option<ConstraintRecord>>> {
986 if constraint_ids.is_empty() {
987 return Ok(Vec::new());
988 }
989
990 let lfid = constraint_meta_lfid();
991 let row_ids: Vec<RowId> = constraint_ids
992 .iter()
993 .map(|&constraint_id| encode_constraint_row_id(table_id, constraint_id))
994 .collect();
995
996 let batch = match self
997 .store
998 .gather_rows(&[lfid], &row_ids, GatherNullPolicy::IncludeNulls)
999 {
1000 Ok(batch) => batch,
1001 Err(llkv_result::Error::NotFound) => {
1002 return Ok(vec![None; constraint_ids.len()]);
1003 }
1004 Err(err) => return Err(err),
1005 };
1006
1007 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1008 return Ok(vec![None; constraint_ids.len()]);
1009 }
1010
1011 let array = batch
1012 .column(0)
1013 .as_any()
1014 .downcast_ref::<BinaryArray>()
1015 .ok_or_else(|| {
1016 llkv_result::Error::Internal(
1017 "constraint metadata column stored unexpected type".into(),
1018 )
1019 })?;
1020
1021 let mut results = Vec::with_capacity(constraint_ids.len());
1022 for (idx, &constraint_id) in constraint_ids.iter().enumerate() {
1023 if array.is_null(idx) {
1024 results.push(None);
1025 continue;
1026 }
1027 let record = decode_constraint_record(array.value(idx))?;
1028 if record.constraint_id != constraint_id {
1029 return Err(llkv_result::Error::Internal(
1030 "constraint metadata id mismatch".into(),
1031 ));
1032 }
1033 results.push(Some(record));
1034 }
1035
1036 Ok(results)
1037 }
1038
1039 pub fn get_constraint_names(
1041 &self,
1042 table_id: TableId,
1043 constraint_ids: &[ConstraintId],
1044 ) -> LlkvResult<Vec<Option<String>>> {
1045 if constraint_ids.is_empty() {
1046 return Ok(Vec::new());
1047 }
1048
1049 let lfid = constraint_name_lfid();
1050 let row_ids: Vec<RowId> = constraint_ids
1051 .iter()
1052 .map(|&constraint_id| encode_constraint_row_id(table_id, constraint_id))
1053 .collect();
1054
1055 let batch = match self
1056 .store
1057 .gather_rows(&[lfid], &row_ids, GatherNullPolicy::IncludeNulls)
1058 {
1059 Ok(batch) => batch,
1060 Err(llkv_result::Error::NotFound) => {
1061 return Ok(vec![None; constraint_ids.len()]);
1062 }
1063 Err(err) => return Err(err),
1064 };
1065
1066 if batch.num_columns() == 0 {
1067 return Ok(vec![None; constraint_ids.len()]);
1068 }
1069
1070 let array = batch
1071 .column(0)
1072 .as_any()
1073 .downcast_ref::<BinaryArray>()
1074 .ok_or_else(|| {
1075 llkv_result::Error::Internal(
1076 "constraint name metadata column stored unexpected type".into(),
1077 )
1078 })?;
1079
1080 let mut results = Vec::with_capacity(row_ids.len());
1081 for idx in 0..row_ids.len() {
1082 if array.is_null(idx) {
1083 results.push(None);
1084 } else {
1085 let record: ConstraintNameRecord =
1086 bitcode::decode(array.value(idx)).map_err(|err| {
1087 llkv_result::Error::Internal(format!(
1088 "failed to decode constraint name metadata: {err}"
1089 ))
1090 })?;
1091 results.push(record.name);
1092 }
1093 }
1094
1095 Ok(results)
1096 }
1097
1098 pub fn scan_constraint_records_for_table<F>(
1100 &self,
1101 table_id: TableId,
1102 mut on_batch: F,
1103 ) -> LlkvResult<()>
1104 where
1105 F: FnMut(Vec<ConstraintRecord>),
1106 {
1107 let row_field = constraint_row_lfid();
1108 let mut visitor = ConstraintRowCollector {
1109 store: self.store,
1110 lfid: constraint_meta_lfid(),
1111 table_id,
1112 on_batch: &mut on_batch,
1113 buffer: Vec::with_capacity(CONSTRAINT_SCAN_CHUNK_SIZE),
1114 error: None,
1115 };
1116
1117 match ScanBuilder::new(self.store, row_field)
1118 .options(ScanOptions::default())
1119 .run(&mut visitor)
1120 {
1121 Ok(()) => {}
1122 Err(llkv_result::Error::NotFound) => return Ok(()),
1123 Err(err) => return Err(err),
1124 }
1125
1126 visitor.finish()
1127 }
1128
1129 pub fn constraint_records_for_table(
1131 &self,
1132 table_id: TableId,
1133 ) -> LlkvResult<Vec<ConstraintRecord>> {
1134 let mut all = Vec::new();
1135 self.scan_constraint_records_for_table(table_id, |mut chunk| {
1136 all.append(&mut chunk);
1137 })?;
1138 Ok(all)
1139 }
1140
1141 pub fn put_next_table_id(&self, next_id: TableId) -> LlkvResult<()> {
1142 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_NEXT_TABLE_ID).into();
1143 let schema = Arc::new(Schema::new(vec![
1144 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1145 Field::new("next_table_id", DataType::UInt64, false).with_metadata(HashMap::from([(
1146 crate::constants::FIELD_ID_META_KEY.to_string(),
1147 lfid_val.to_string(),
1148 )])),
1149 ]));
1150
1151 let row_id = Arc::new(UInt64Array::from(vec![CATALOG_NEXT_TABLE_ROW_ID]));
1152 let value_array = Arc::new(UInt64Array::from(vec![next_id as u64]));
1153 let batch = RecordBatch::try_new(schema, vec![row_id, value_array])?;
1154 self.store.append(&batch)?;
1155 Ok(())
1156 }
1157
1158 pub fn get_next_table_id(&self) -> LlkvResult<Option<TableId>> {
1159 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_NEXT_TABLE_ID);
1160 let batch = match self.store.gather_rows(
1161 &[lfid],
1162 &[CATALOG_NEXT_TABLE_ROW_ID],
1163 GatherNullPolicy::IncludeNulls,
1164 ) {
1165 Ok(batch) => batch,
1166 Err(llkv_result::Error::NotFound) => return Ok(None),
1167 Err(err) => return Err(err),
1168 };
1169
1170 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1171 return Ok(None);
1172 }
1173
1174 let array = batch
1175 .column(0)
1176 .as_any()
1177 .downcast_ref::<UInt64Array>()
1178 .ok_or_else(|| {
1179 llkv_result::Error::Internal(
1180 "catalog next_table_id column stored unexpected type".into(),
1181 )
1182 })?;
1183 if array.is_empty() || array.is_null(0) {
1184 return Ok(None);
1185 }
1186
1187 let value = array.value(0);
1188 if value > TableId::MAX as u64 {
1189 return Err(llkv_result::Error::InvalidArgumentError(
1190 "persisted next_table_id exceeds TableId range".into(),
1191 ));
1192 }
1193
1194 Ok(Some(value as TableId))
1195 }
1196
1197 pub fn max_table_id(&self) -> LlkvResult<Option<TableId>> {
1198 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TABLE_META_ID);
1199 let row_field = rowid_fid(meta_field);
1200
1201 let mut collector = MaxRowIdCollector { max: None };
1202 match ScanBuilder::new(self.store, row_field)
1203 .options(ScanOptions::default())
1204 .run(&mut collector)
1205 {
1206 Ok(()) => {}
1207 Err(llkv_result::Error::NotFound) => return Ok(None),
1208 Err(err) => return Err(err),
1209 }
1210
1211 let max_value = match collector.max {
1212 Some(value) => value,
1213 None => return Ok(None),
1214 };
1215
1216 let logical: LogicalFieldId = max_value.into();
1217 Ok(Some(logical.table_id()))
1218 }
1219
1220 pub fn all_table_metas(&self) -> LlkvResult<Vec<(TableId, TableMeta)>> {
1226 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_TABLE_META_ID);
1227 let row_field = rowid_fid(meta_field);
1228
1229 struct RowIdCollector {
1231 row_ids: Vec<RowId>,
1232 }
1233
1234 impl PrimitiveVisitor for RowIdCollector {
1235 fn u64_chunk(&mut self, values: &UInt64Array) {
1236 for i in 0..values.len() {
1237 self.row_ids.push(values.value(i));
1238 }
1239 }
1240 }
1241 impl PrimitiveWithRowIdsVisitor for RowIdCollector {}
1242 impl PrimitiveSortedVisitor for RowIdCollector {}
1243 impl PrimitiveSortedWithRowIdsVisitor for RowIdCollector {}
1244
1245 let mut collector = RowIdCollector {
1246 row_ids: Vec::new(),
1247 };
1248 match ScanBuilder::new(self.store, row_field)
1249 .options(ScanOptions::default())
1250 .run(&mut collector)
1251 {
1252 Ok(()) => {}
1253 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
1254 Err(err) => return Err(err),
1255 }
1256
1257 if collector.row_ids.is_empty() {
1258 return Ok(Vec::new());
1259 }
1260
1261 let batch = self.store.gather_rows(
1263 &[meta_field],
1264 &collector.row_ids,
1265 GatherNullPolicy::IncludeNulls,
1266 )?;
1267
1268 let meta_col = batch
1269 .column(0)
1270 .as_any()
1271 .downcast_ref::<BinaryArray>()
1272 .ok_or_else(|| {
1273 llkv_result::Error::Internal("catalog table_meta column should be Binary".into())
1274 })?;
1275
1276 let mut result = Vec::new();
1277 for (idx, &row_id) in collector.row_ids.iter().enumerate() {
1278 if !meta_col.is_null(idx) {
1279 let bytes = meta_col.value(idx);
1280 if let Ok(meta) = bitcode::decode::<TableMeta>(bytes) {
1281 let logical: LogicalFieldId = row_id.into();
1282 let table_id = logical.table_id();
1283 result.push((table_id, meta));
1284 }
1285 }
1286 }
1287
1288 Ok(result)
1289 }
1290
1291 pub fn put_next_txn_id(&self, next_txn_id: u64) -> LlkvResult<()> {
1293 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_NEXT_TXN_ID).into();
1294 let schema = Arc::new(Schema::new(vec![
1295 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1296 Field::new("next_txn_id", DataType::UInt64, false).with_metadata(HashMap::from([(
1297 crate::constants::FIELD_ID_META_KEY.to_string(),
1298 lfid_val.to_string(),
1299 )])),
1300 ]));
1301
1302 let row_id = Arc::new(UInt64Array::from(vec![CATALOG_NEXT_TXN_ROW_ID]));
1303 let value_array = Arc::new(UInt64Array::from(vec![next_txn_id]));
1304 let batch = RecordBatch::try_new(schema, vec![row_id, value_array])?;
1305 self.store.append(&batch)?;
1306 Ok(())
1307 }
1308
1309 pub fn get_next_txn_id(&self) -> LlkvResult<Option<u64>> {
1311 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_NEXT_TXN_ID);
1312 let batch = match self.store.gather_rows(
1313 &[lfid],
1314 &[CATALOG_NEXT_TXN_ROW_ID],
1315 GatherNullPolicy::IncludeNulls,
1316 ) {
1317 Ok(batch) => batch,
1318 Err(llkv_result::Error::NotFound) => return Ok(None),
1319 Err(err) => return Err(err),
1320 };
1321
1322 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1323 return Ok(None);
1324 }
1325
1326 let array = batch
1327 .column(0)
1328 .as_any()
1329 .downcast_ref::<UInt64Array>()
1330 .ok_or_else(|| {
1331 llkv_result::Error::Internal(
1332 "catalog next_txn_id column stored unexpected type".into(),
1333 )
1334 })?;
1335 if array.is_empty() || array.is_null(0) {
1336 return Ok(None);
1337 }
1338
1339 let value = array.value(0);
1340 Ok(Some(value))
1341 }
1342
1343 pub fn put_last_committed_txn_id(&self, last_committed: u64) -> LlkvResult<()> {
1345 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_LAST_COMMITTED_TXN_ID).into();
1346 let schema = Arc::new(Schema::new(vec![
1347 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1348 Field::new("last_committed_txn_id", DataType::UInt64, false).with_metadata(
1349 HashMap::from([(
1350 crate::constants::FIELD_ID_META_KEY.to_string(),
1351 lfid_val.to_string(),
1352 )]),
1353 ),
1354 ]));
1355
1356 let row_id = Arc::new(UInt64Array::from(vec![CATALOG_LAST_COMMITTED_TXN_ROW_ID]));
1357 let value_array = Arc::new(UInt64Array::from(vec![last_committed]));
1358 let batch = RecordBatch::try_new(schema, vec![row_id, value_array])?;
1359 self.store.append(&batch)?;
1360 Ok(())
1361 }
1362
1363 pub fn get_last_committed_txn_id(&self) -> LlkvResult<Option<u64>> {
1365 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_LAST_COMMITTED_TXN_ID);
1366 let batch = match self.store.gather_rows(
1367 &[lfid],
1368 &[CATALOG_LAST_COMMITTED_TXN_ROW_ID],
1369 GatherNullPolicy::IncludeNulls,
1370 ) {
1371 Ok(batch) => batch,
1372 Err(llkv_result::Error::NotFound) => return Ok(None),
1373 Err(err) => return Err(err),
1374 };
1375
1376 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1377 return Ok(None);
1378 }
1379
1380 let array = batch
1381 .column(0)
1382 .as_any()
1383 .downcast_ref::<UInt64Array>()
1384 .ok_or_else(|| {
1385 llkv_result::Error::Internal(
1386 "catalog last_committed_txn_id column stored unexpected type".into(),
1387 )
1388 })?;
1389 if array.is_empty() || array.is_null(0) {
1390 return Ok(None);
1391 }
1392
1393 let value = array.value(0);
1394 Ok(Some(value))
1395 }
1396
1397 pub fn put_catalog_state(&self, state: &crate::catalog::TableCatalogState) -> LlkvResult<()> {
1402 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CATALOG_STATE).into();
1403 let schema = Arc::new(Schema::new(vec![
1404 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1405 Field::new("catalog_state", DataType::Binary, false).with_metadata(HashMap::from([(
1406 crate::constants::FIELD_ID_META_KEY.to_string(),
1407 lfid_val.to_string(),
1408 )])),
1409 ]));
1410
1411 let row_id = Arc::new(UInt64Array::from(vec![CATALOG_STATE_ROW_ID]));
1412 let encoded = bitcode::encode(state);
1413 let state_bytes = Arc::new(BinaryArray::from(vec![encoded.as_slice()]));
1414
1415 let batch = RecordBatch::try_new(schema, vec![row_id, state_bytes])?;
1416 self.store.append(&batch)?;
1417 Ok(())
1418 }
1419
1420 pub fn get_catalog_state(&self) -> LlkvResult<Option<crate::catalog::TableCatalogState>> {
1424 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CATALOG_STATE);
1425 let batch = match self.store.gather_rows(
1426 &[lfid],
1427 &[CATALOG_STATE_ROW_ID],
1428 GatherNullPolicy::IncludeNulls,
1429 ) {
1430 Ok(batch) => batch,
1431 Err(llkv_result::Error::NotFound) => return Ok(None),
1432 Err(err) => return Err(err),
1433 };
1434
1435 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1436 return Ok(None);
1437 }
1438
1439 let array = batch
1440 .column(0)
1441 .as_any()
1442 .downcast_ref::<BinaryArray>()
1443 .ok_or_else(|| {
1444 llkv_result::Error::Internal("catalog state column stored unexpected type".into())
1445 })?;
1446 if array.is_empty() || array.is_null(0) {
1447 return Ok(None);
1448 }
1449
1450 let bytes = array.value(0);
1451 let state = bitcode::decode(bytes).map_err(|e| {
1452 llkv_result::Error::Internal(format!("Failed to decode catalog state: {}", e))
1453 })?;
1454 Ok(Some(state))
1455 }
1456
1457 pub fn put_schema_meta(&self, meta: &SchemaMeta) -> LlkvResult<()> {
1462 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SCHEMA_META_ID).into();
1463 let schema = Arc::new(Schema::new(vec![
1464 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1465 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
1466 crate::constants::FIELD_ID_META_KEY.to_string(),
1467 lfid_val.to_string(),
1468 )])),
1469 ]));
1470
1471 let canonical = meta.name.to_ascii_lowercase();
1473 let row_id_val = schema_name_to_row_id(&canonical);
1474 let row_id = Arc::new(UInt64Array::from(vec![row_id_val]));
1475 let meta_encoded = bitcode::encode(meta);
1476 let meta_bytes = Arc::new(BinaryArray::from(vec![meta_encoded.as_slice()]));
1477
1478 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes])?;
1479 self.store.append(&batch)?;
1480 Ok(())
1481 }
1482
1483 pub fn get_schema_meta(&self, schema_name: &str) -> LlkvResult<Option<SchemaMeta>> {
1487 let canonical = schema_name.to_ascii_lowercase();
1488 let row_id = schema_name_to_row_id(&canonical);
1489 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SCHEMA_META_ID);
1490
1491 let batch = match self
1492 .store
1493 .gather_rows(&[lfid], &[row_id], GatherNullPolicy::IncludeNulls)
1494 {
1495 Ok(batch) => batch,
1496 Err(llkv_result::Error::NotFound) => return Ok(None),
1497 Err(err) => return Err(err),
1498 };
1499
1500 if batch.num_columns() == 0 || batch.num_rows() == 0 {
1501 return Ok(None);
1502 }
1503
1504 let array = batch
1505 .column(0)
1506 .as_any()
1507 .downcast_ref::<BinaryArray>()
1508 .ok_or_else(|| {
1509 llkv_result::Error::Internal("catalog schema_meta column should be Binary".into())
1510 })?;
1511
1512 if array.is_empty() || array.is_null(0) {
1513 return Ok(None);
1514 }
1515
1516 let bytes = array.value(0);
1517 let meta = bitcode::decode(bytes).map_err(|e| {
1518 llkv_result::Error::Internal(format!("Failed to decode schema metadata: {}", e))
1519 })?;
1520 Ok(Some(meta))
1521 }
1522
1523 pub fn all_schema_metas(&self) -> LlkvResult<Vec<SchemaMeta>> {
1527 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_SCHEMA_META_ID);
1528 let row_field = rowid_fid(meta_field);
1529
1530 struct RowIdCollector {
1532 row_ids: Vec<RowId>,
1533 }
1534
1535 impl PrimitiveVisitor for RowIdCollector {
1536 fn u64_chunk(&mut self, values: &UInt64Array) {
1537 for i in 0..values.len() {
1538 self.row_ids.push(values.value(i));
1539 }
1540 }
1541 }
1542 impl PrimitiveWithRowIdsVisitor for RowIdCollector {}
1543 impl PrimitiveSortedVisitor for RowIdCollector {}
1544 impl PrimitiveSortedWithRowIdsVisitor for RowIdCollector {}
1545
1546 let mut collector = RowIdCollector {
1547 row_ids: Vec::new(),
1548 };
1549 match ScanBuilder::new(self.store, row_field)
1550 .options(ScanOptions::default())
1551 .run(&mut collector)
1552 {
1553 Ok(()) => {}
1554 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
1555 Err(err) => return Err(err),
1556 }
1557
1558 if collector.row_ids.is_empty() {
1559 return Ok(Vec::new());
1560 }
1561
1562 let batch = self.store.gather_rows(
1564 &[meta_field],
1565 &collector.row_ids,
1566 GatherNullPolicy::IncludeNulls,
1567 )?;
1568
1569 let meta_col = batch
1570 .column(0)
1571 .as_any()
1572 .downcast_ref::<BinaryArray>()
1573 .ok_or_else(|| {
1574 llkv_result::Error::Internal("catalog schema_meta column should be Binary".into())
1575 })?;
1576
1577 let mut result = Vec::new();
1578 for idx in 0..collector.row_ids.len() {
1579 if !meta_col.is_null(idx) {
1580 let bytes = meta_col.value(idx);
1581 if let Ok(meta) = bitcode::decode::<SchemaMeta>(bytes) {
1582 result.push(meta);
1583 }
1584 }
1585 }
1586
1587 Ok(result)
1588 }
1589
1590 pub fn put_custom_type_meta(&self, meta: &CustomTypeMeta) -> LlkvResult<()> {
1594 let lfid_val: u64 = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CUSTOM_TYPE_META_ID).into();
1595 let schema = Arc::new(Schema::new(vec![
1596 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1597 Field::new("meta", DataType::Binary, false).with_metadata(HashMap::from([(
1598 crate::constants::FIELD_ID_META_KEY.to_string(),
1599 lfid_val.to_string(),
1600 )])),
1601 ]));
1602
1603 let canonical = meta.name.to_ascii_lowercase();
1605 let row_id_val = schema_name_to_row_id(&canonical); let row_id = Arc::new(UInt64Array::from(vec![row_id_val]));
1607 let meta_encoded = bitcode::encode(meta);
1608 let meta_bytes = Arc::new(BinaryArray::from(vec![meta_encoded.as_slice()]));
1609
1610 let batch = RecordBatch::try_new(schema, vec![row_id, meta_bytes])?;
1611 self.store.append(&batch)?;
1612 Ok(())
1613 }
1614
1615 pub fn get_custom_type_meta(&self, type_name: &str) -> LlkvResult<Option<CustomTypeMeta>> {
1619 let canonical = type_name.to_ascii_lowercase();
1620 let row_id = schema_name_to_row_id(&canonical);
1621 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CUSTOM_TYPE_META_ID);
1622
1623 let batch = match self
1624 .store
1625 .gather_rows(&[lfid], &[row_id], GatherNullPolicy::IncludeNulls)
1626 {
1627 Ok(batch) => batch,
1628 Err(llkv_result::Error::NotFound) => return Ok(None),
1629 Err(err) => return Err(err),
1630 };
1631
1632 let meta_col = batch
1633 .column(0)
1634 .as_any()
1635 .downcast_ref::<BinaryArray>()
1636 .ok_or_else(|| {
1637 llkv_result::Error::Internal(
1638 "catalog custom_type_meta column should be Binary".into(),
1639 )
1640 })?;
1641
1642 if meta_col.is_null(0) {
1643 return Ok(None);
1644 }
1645
1646 let bytes = meta_col.value(0);
1647 let meta = bitcode::decode(bytes).map_err(|err| {
1648 llkv_result::Error::Internal(format!("failed to decode custom type metadata: {err}"))
1649 })?;
1650 Ok(Some(meta))
1651 }
1652
1653 pub fn delete_custom_type_meta(&self, type_name: &str) -> LlkvResult<()> {
1657 let canonical = type_name.to_ascii_lowercase();
1658 let row_id = schema_name_to_row_id(&canonical);
1659 let lfid = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CUSTOM_TYPE_META_ID);
1660
1661 let lfid_val: u64 = lfid.into();
1663 let schema = Arc::new(Schema::new(vec![
1664 Field::new(ROW_ID_COLUMN_NAME, DataType::UInt64, false),
1665 Field::new("meta", DataType::Binary, true).with_metadata(HashMap::from([(
1666 crate::constants::FIELD_ID_META_KEY.to_string(),
1667 lfid_val.to_string(),
1668 )])),
1669 ]));
1670
1671 let row_id_arr = Arc::new(UInt64Array::from(vec![row_id]));
1672 let mut meta_builder = BinaryBuilder::new();
1673 meta_builder.append_null();
1674 let meta_arr = Arc::new(meta_builder.finish());
1675
1676 let batch = RecordBatch::try_new(schema, vec![row_id_arr, meta_arr])?;
1677 self.store.append(&batch)?;
1678 Ok(())
1679 }
1680
1681 pub fn all_custom_type_metas(&self) -> LlkvResult<Vec<CustomTypeMeta>> {
1685 let meta_field = lfid(CATALOG_TABLE_ID, CATALOG_FIELD_CUSTOM_TYPE_META_ID);
1686 let row_field = rowid_fid(meta_field);
1687
1688 struct RowIdCollector {
1690 row_ids: Vec<RowId>,
1691 }
1692
1693 impl PrimitiveVisitor for RowIdCollector {
1694 fn u64_chunk(&mut self, values: &UInt64Array) {
1695 for i in 0..values.len() {
1696 self.row_ids.push(values.value(i));
1697 }
1698 }
1699 }
1700 impl PrimitiveWithRowIdsVisitor for RowIdCollector {}
1701 impl PrimitiveSortedVisitor for RowIdCollector {}
1702 impl PrimitiveSortedWithRowIdsVisitor for RowIdCollector {}
1703
1704 let mut collector = RowIdCollector {
1705 row_ids: Vec::new(),
1706 };
1707 match ScanBuilder::new(self.store, row_field)
1708 .options(ScanOptions::default())
1709 .run(&mut collector)
1710 {
1711 Ok(()) => {}
1712 Err(llkv_result::Error::NotFound) => return Ok(Vec::new()),
1713 Err(err) => return Err(err),
1714 }
1715
1716 if collector.row_ids.is_empty() {
1717 return Ok(Vec::new());
1718 }
1719
1720 let batch = self.store.gather_rows(
1722 &[meta_field],
1723 &collector.row_ids,
1724 GatherNullPolicy::IncludeNulls,
1725 )?;
1726
1727 let meta_col = batch
1728 .column(0)
1729 .as_any()
1730 .downcast_ref::<BinaryArray>()
1731 .ok_or_else(|| {
1732 llkv_result::Error::Internal(
1733 "catalog custom_type_meta column should be Binary".into(),
1734 )
1735 })?;
1736
1737 let mut result = Vec::new();
1738 for idx in 0..collector.row_ids.len() {
1739 if !meta_col.is_null(idx) {
1740 let bytes = meta_col.value(idx);
1741 if let Ok(meta) = bitcode::decode::<CustomTypeMeta>(bytes) {
1742 result.push(meta);
1743 }
1744 }
1745 }
1746
1747 Ok(result)
1748 }
1749}
1750
1751fn schema_name_to_row_id(canonical_name: &str) -> RowId {
1756 const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
1758 const FNV_PRIME: u64 = 0x1000_0000_01b3;
1759
1760 let mut hash = FNV_OFFSET;
1761 for byte in canonical_name.as_bytes() {
1762 hash ^= u64::from(*byte);
1763 hash = hash.wrapping_mul(FNV_PRIME);
1764 }
1765
1766 hash | (1u64 << 63)
1768}
1769
1770struct MaxRowIdCollector {
1771 max: Option<RowId>,
1772}
1773
1774impl PrimitiveVisitor for MaxRowIdCollector {
1775 fn u64_chunk(&mut self, values: &UInt64Array) {
1776 for i in 0..values.len() {
1777 let value = values.value(i);
1778 self.max = match self.max {
1779 Some(curr) if curr >= value => Some(curr),
1780 _ => Some(value),
1781 };
1782 }
1783 }
1784}
1785
1786impl PrimitiveWithRowIdsVisitor for MaxRowIdCollector {}
1787impl PrimitiveSortedVisitor for MaxRowIdCollector {}
1788impl PrimitiveSortedWithRowIdsVisitor for MaxRowIdCollector {}
1789
1790#[cfg(test)]
1791mod tests {
1792 use super::*;
1793 use crate::constraints::{
1794 ConstraintKind, ConstraintState, PrimaryKeyConstraint, UniqueConstraint,
1795 };
1796 use llkv_column_map::ColumnStore;
1797 use std::sync::Arc;
1798
1799 #[test]
1800 fn constraint_records_roundtrip() {
1801 let pager = Arc::new(MemPager::default());
1802 let store = ColumnStore::open(Arc::clone(&pager)).unwrap();
1803 let catalog = SysCatalog::new(&store);
1804
1805 let table_id: TableId = 42;
1806 let record1 = ConstraintRecord {
1807 constraint_id: 1,
1808 kind: ConstraintKind::PrimaryKey(PrimaryKeyConstraint {
1809 field_ids: vec![1, 2],
1810 }),
1811 state: ConstraintState::Active,
1812 revision: 1,
1813 last_modified_micros: 100,
1814 };
1815 let record2 = ConstraintRecord {
1816 constraint_id: 2,
1817 kind: ConstraintKind::Unique(UniqueConstraint { field_ids: vec![3] }),
1818 state: ConstraintState::Active,
1819 revision: 2,
1820 last_modified_micros: 200,
1821 };
1822 catalog
1823 .put_constraint_records(table_id, &[record1.clone(), record2.clone()])
1824 .unwrap();
1825
1826 let other_table_record = ConstraintRecord {
1827 constraint_id: 1,
1828 kind: ConstraintKind::Unique(UniqueConstraint { field_ids: vec![5] }),
1829 state: ConstraintState::Active,
1830 revision: 1,
1831 last_modified_micros: 150,
1832 };
1833 catalog
1834 .put_constraint_records(7, &[other_table_record])
1835 .unwrap();
1836
1837 let mut fetched = catalog.constraint_records_for_table(table_id).unwrap();
1838 fetched.sort_by_key(|record| record.constraint_id);
1839
1840 assert_eq!(fetched.len(), 2);
1841 assert_eq!(fetched[0], record1);
1842 assert_eq!(fetched[1], record2);
1843
1844 let single = catalog
1845 .get_constraint_records(table_id, &[record1.constraint_id])
1846 .unwrap();
1847 assert_eq!(single.len(), 1);
1848 assert_eq!(single[0].as_ref(), Some(&record1));
1849
1850 let missing = catalog.get_constraint_records(table_id, &[999]).unwrap();
1851 assert_eq!(missing.len(), 1);
1852 assert!(missing[0].is_none());
1853 }
1854}