Skip to main content

uqa_storage/key_value/
catalog.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Catalog facade implementation for key/value-backed persistence.
8
9use std::sync::Arc;
10
11use parking_lot::Mutex;
12use serde::{Deserialize, Serialize};
13
14use crate::catalog::{
15    CatalogFacade, CatalogIndexRow, ColumnStatsInput, ColumnStatsRow, EdgeRow, ForeignTableRow,
16    GraphSnapshot, RelationIdentity, RelationKind, SchemaRow, SequenceOptions,
17    SequenceReservationResult, SequenceRow, SequenceSetValueResult, TableSchema, ViewRow,
18};
19use crate::{StorageBackendError, StorageBackendResult};
20
21use super::codec::{
22    decode_stored_document_value, decode_string, decode_value, doc_length_key,
23    doc_length_key_prefix, document_key_prefix, encode_stored_document_value, encode_value,
24    field_stats_key, field_stats_key_prefix, key_with_tag, posting_cluster_positions_field_prefix,
25    posting_cluster_positions_key_prefix, posting_cluster_score_field_prefix,
26    posting_cluster_score_key_prefix, posting_document_key, posting_document_key_prefix,
27    posting_field_prefix, posting_key_prefix, push_str, push_u64, read_str, read_u64,
28    reverse_posting_key, reverse_posting_key_prefix, single_str_key, string_value,
29    vector_field_prefix, vector_key_prefix,
30};
31use super::{
32    KeyValueBatch, KeyValueStore, TAG_ANALYZER, TAG_ANALYZER_DESCRIPTOR, TAG_CATALOG_INDEX,
33    TAG_COLUMN_STATS, TAG_EDGE, TAG_FIELD_ANALYZER_BINDING, TAG_FOREIGN_SERVER, TAG_FOREIGN_TABLE,
34    TAG_GRAPH_MEMBERSHIP, TAG_METADATA, TAG_MODEL, TAG_NAMED_GRAPH, TAG_PATH_INDEX, TAG_RELATION,
35    TAG_SCHEMA, TAG_SCORING_PARAMS, TAG_SEQUENCE, TAG_TABLE, TAG_TABLE_FIELD_ANALYZER, TAG_VERTEX,
36    TAG_VIEW,
37};
38
39mod analyzers;
40mod foreign;
41mod graph_access;
42mod graph_guards;
43mod graph_observations;
44mod graph_view;
45mod graphs;
46mod indexes;
47pub(super) mod keys;
48mod migration;
49mod models;
50mod occurrence_lifecycle;
51mod path_index_data;
52mod physical_indexes;
53mod records;
54mod relation_acl;
55mod relations;
56mod schema_table;
57mod sequences;
58mod table_data;
59mod tables;
60mod views;
61
62use keys::{
63    batch_put_or_keep_existing, batch_rekey_prefix, batch_rekey_prefix_or_keep_existing,
64    catalog_index_references_column, catalog_index_rename_column, column_stats_key,
65    column_stats_prefix, decode_catalog_relation_key, decode_relation_key, edge_key,
66    ensure_prefix_absent, graph_membership_graph_prefix, graph_membership_key,
67    graph_membership_prefix, load_single_string_rows, register_migration_relation, relation_key,
68    table_field_analyzer_field_prefix, table_field_analyzer_key, table_field_analyzer_prefix,
69    vertex_key,
70};
71use migration::{
72    apply_relation_migrations, collect_relation_migrations, validate_relation_parents,
73};
74use records::{
75    StoredCatalogIndex, StoredColumnStats, StoredEdge, StoredForeignServer, StoredForeignTable,
76    StoredRelation, StoredSequence, StoredVertex, StoredView,
77    STORED_FOREIGN_TABLE_SECURITY_VERSION,
78};
79
80#[derive(Clone)]
81pub struct KeyValueCatalog {
82    store: Arc<dyn KeyValueStore>,
83    graph_indexes_lock: Arc<Mutex<()>>,
84}
85
86impl KeyValueCatalog {
87    pub fn new(store: Arc<dyn KeyValueStore>) -> Self {
88        Self {
89            store,
90            graph_indexes_lock: Arc::new(Mutex::new(())),
91        }
92    }
93
94    pub fn store(&self) -> Arc<dyn KeyValueStore> {
95        Arc::clone(&self.store)
96    }
97}
98
99impl CatalogFacade for KeyValueCatalog {
100    fn transaction_model(&self) -> crate::StorageTransactionModel {
101        self.store.transaction_model()
102    }
103
104    fn transaction_affinity(&self) -> Option<crate::StorageSessionAffinity> {
105        self.store.transaction_affinity()
106    }
107
108    fn clear_path_index_data(&self, index: &str) -> StorageBackendResult<()> {
109        self.clear_path_index_data_impl(index)
110    }
111
112    fn save_path_index_pairs(
113        &self,
114        index: &str,
115        sequence: &str,
116        pairs: &[(u64, u64)],
117    ) -> StorageBackendResult<()> {
118        self.save_path_index_pairs_impl(index, sequence, pairs)
119    }
120
121    fn finish_path_index_data(
122        &self,
123        index: &str,
124        graph: &str,
125        definition: &str,
126    ) -> StorageBackendResult<()> {
127        self.finish_path_index_data_impl(index, graph, definition)
128    }
129
130    fn path_index_data_is_current(
131        &self,
132        index: &str,
133        definition: &str,
134    ) -> StorageBackendResult<bool> {
135        self.path_index_data_is_current_impl(index, definition)
136    }
137
138    fn path_index_pairs(
139        &self,
140        index: &str,
141        sequence: &str,
142        after: Option<(u64, u64)>,
143        limit: usize,
144    ) -> StorageBackendResult<Vec<(u64, u64)>> {
145        self.path_index_pairs_impl(index, sequence, after, limit)
146    }
147
148    fn graph_vertex(&self, id: u64) -> StorageBackendResult<Option<crate::GraphVertexRow>> {
149        self.graph_vertex_impl(id)
150    }
151
152    fn graph_edge(&self, id: u64) -> StorageBackendResult<Option<EdgeRow>> {
153        self.graph_edge_impl(id)
154    }
155
156    fn graph_entity_ids(
157        &self,
158        filter: crate::GraphEntityFilter<'_>,
159        after: Option<u64>,
160        limit: usize,
161    ) -> StorageBackendResult<Vec<u64>> {
162        self.graph_entity_ids_impl(filter, after, limit)
163    }
164
165    fn graph_entity_count(
166        &self,
167        filter: crate::GraphEntityFilter<'_>,
168    ) -> StorageBackendResult<u64> {
169        self.with_graph_read(|read| {
170            let mut after = None;
171            let mut count = 0_u64;
172            loop {
173                let ids = read.ids(filter, after, 256)?;
174                if ids.is_empty() {
175                    return Ok(count);
176                }
177                after = ids.last().copied();
178                count = count
179                    .checked_add(
180                        u64::try_from(ids.len())
181                            .map_err(|error| StorageBackendError::Other(error.to_string()))?,
182                    )
183                    .ok_or_else(|| {
184                        StorageBackendError::Other("graph entity count overflow".into())
185                    })?;
186            }
187        })
188    }
189
190    fn graph_entity_max_id(
191        &self,
192        kind: crate::GraphEntityKind,
193    ) -> StorageBackendResult<Option<u64>> {
194        self.with_graph_read(|read| {
195            let filter = crate::GraphEntityFilter::new(kind, None);
196            let mut after = None;
197            loop {
198                let ids = read.ids(filter, after, 256)?;
199                if ids.is_empty() {
200                    return Ok(after);
201                }
202                after = ids.last().copied();
203            }
204        })
205    }
206
207    fn graph_entity_memberships(
208        &self,
209        kind: crate::GraphEntityKind,
210        id: u64,
211    ) -> StorageBackendResult<Vec<String>> {
212        self.graph_entity_memberships_impl(kind, id)
213    }
214
215    fn graph_has_membership(
216        &self,
217        kind: crate::GraphEntityKind,
218        id: u64,
219        graph: &str,
220    ) -> StorageBackendResult<bool> {
221        self.store
222            .contains_key(&graph_membership_key(kind.as_str(), id, graph)?)
223    }
224
225    fn set_metadata(&self, key: &str, value: &str) -> StorageBackendResult<()> {
226        self.set_metadata_impl(key, value)
227    }
228
229    fn delete_metadata(&self, key: &str) -> StorageBackendResult<()> {
230        self.delete_metadata_impl(key)
231    }
232
233    fn get_metadata(&self, key: &str) -> StorageBackendResult<Option<String>> {
234        self.get_metadata_impl(key)
235    }
236
237    fn metadata_has_private_changes(&self, key: &str) -> StorageBackendResult<bool> {
238        self.metadata_has_private_changes_impl(key)
239    }
240    fn metadata_with_prefix(&self, prefix: &str) -> StorageBackendResult<Vec<(String, String)>> {
241        self.metadata_with_prefix_impl(prefix)
242    }
243
244    fn save_statistics_maintenance(
245        &self,
246        table: &str,
247        state: &crate::statistics_maintenance::StatisticsMaintenance,
248    ) -> StorageBackendResult<()> {
249        let key = single_str_key(
250            TAG_METADATA,
251            &crate::statistics_maintenance::StatisticsMaintenance::key(table),
252        )?;
253        self.store.with_mutation(&mut |read, batch| {
254            let value = state
255                .encode(read.control())
256                .map_err(crate::mvcc::VersionError::into_storage_error)?;
257            batch.replace_statistics_maintenance(&key, &value)
258        })
259    }
260
261    fn migrate_relation_namespace(&self) -> StorageBackendResult<()> {
262        self.migrate_relation_namespace_impl()?;
263        self.ensure_graph_lookup_indexes()
264    }
265
266    fn save_schema_row(&self, schema: &SchemaRow) -> StorageBackendResult<()> {
267        self.save_schema_row_impl(schema)
268    }
269
270    fn schema_has_private_changes(&self, name: &str) -> StorageBackendResult<bool> {
271        self.named_record_has_private_changes(TAG_SCHEMA, name)
272    }
273
274    fn drop_schema(&self, name: &str) -> StorageBackendResult<()> {
275        self.drop_schema_impl(name)
276    }
277
278    fn load_schema_rows(&self) -> StorageBackendResult<Vec<SchemaRow>> {
279        self.load_schema_rows_impl()
280    }
281
282    fn save_table(&self, schema: &TableSchema) -> StorageBackendResult<()> {
283        self.save_table_impl(schema)
284    }
285
286    fn load_tables(&self) -> StorageBackendResult<Vec<TableSchema>> {
287        self.load_tables_impl()
288    }
289
290    fn drop_table(&self, name: &str) -> StorageBackendResult<()> {
291        self.drop_table_impl(name)
292    }
293
294    fn drop_table_and_data(&self, name: &str) -> StorageBackendResult<()> {
295        self.drop_table_and_data_impl(name)
296    }
297
298    fn purge_table_data(&self, name: &str) -> StorageBackendResult<()> {
299        self.purge_table_data_impl(name)
300    }
301
302    fn rename_table_data(&self, from: &str, to: &str) -> StorageBackendResult<()> {
303        self.rename_table_data_impl(from, to)
304    }
305
306    fn drop_column_data(&self, table_name: &str, column_name: &str) -> StorageBackendResult<()> {
307        self.drop_column_data_impl(table_name, column_name)
308    }
309
310    fn rename_column_data(
311        &self,
312        table_name: &str,
313        from: &str,
314        to: &str,
315    ) -> StorageBackendResult<()> {
316        self.rename_column_data_impl(table_name, from, to)
317    }
318
319    fn save_model(&self, name: &str, json: &str) -> StorageBackendResult<()> {
320        self.save_model_impl(name, json)
321    }
322
323    fn load_models(&self) -> StorageBackendResult<Vec<(String, String)>> {
324        self.load_models_impl()
325    }
326
327    fn load_model(&self, name: &str) -> StorageBackendResult<Option<String>> {
328        self.load_model_impl(name)
329    }
330
331    fn drop_model(&self, name: &str) -> StorageBackendResult<()> {
332        self.drop_model_impl(name)
333    }
334
335    fn save_scoring_params(&self, name: &str, params_json: &str) -> StorageBackendResult<()> {
336        self.save_scoring_params_impl(name, params_json)
337    }
338
339    fn load_scoring_params(&self, name: &str) -> StorageBackendResult<Option<String>> {
340        self.load_scoring_params_impl(name)
341    }
342
343    fn load_all_scoring_params(&self) -> StorageBackendResult<Vec<(String, String)>> {
344        self.load_all_scoring_params_impl()
345    }
346
347    fn drop_scoring_params(&self, name: &str) -> StorageBackendResult<()> {
348        self.drop_scoring_params_impl(name)
349    }
350
351    fn create_sequence_row(&self, sequence: &SequenceRow) -> StorageBackendResult<bool> {
352        self.create_sequence_row_impl(sequence)
353    }
354
355    fn replace_sequence_row(&self, sequence: &SequenceRow) -> StorageBackendResult<bool> {
356        self.replace_sequence_row_impl(sequence)
357    }
358
359    fn rename_sequence_row(&self, from: &str, to: &str) -> StorageBackendResult<bool> {
360        self.rename_sequence_row_impl(from, to)
361    }
362
363    fn drop_sequence_row(&self, name: &str) -> StorageBackendResult<bool> {
364        self.drop_sequence_row_impl(name)
365    }
366
367    fn load_sequence_rows(&self) -> StorageBackendResult<Vec<SequenceRow>> {
368        self.load_sequence_rows_impl()
369    }
370
371    fn sequence_has_private_changes(
372        &self,
373        relation: &RelationIdentity,
374        _object_id: [u8; 16],
375    ) -> StorageBackendResult<bool> {
376        self.sequence_has_private_changes_impl(relation)
377    }
378
379    fn reserve_sequence_values(
380        &self,
381        name: &str,
382        object_id: [u8; 16],
383        definition_generation: [u8; 16],
384    ) -> StorageBackendResult<SequenceReservationResult> {
385        self.reserve_sequence_values_impl(name, object_id, definition_generation)
386    }
387
388    fn set_sequence_value(
389        &self,
390        name: &str,
391        object_id: [u8; 16],
392        definition_generation: [u8; 16],
393        value: i64,
394        called: bool,
395        log_count: i64,
396    ) -> StorageBackendResult<SequenceSetValueResult> {
397        self.set_sequence_value_impl(
398            name,
399            object_id,
400            definition_generation,
401            value,
402            called,
403            log_count,
404        )
405    }
406
407    fn save_view(&self, view: &ViewRow) -> StorageBackendResult<()> {
408        self.save_view_impl(view)
409    }
410
411    fn rename_view(
412        &self,
413        from: &RelationIdentity,
414        to: &RelationIdentity,
415    ) -> StorageBackendResult<bool> {
416        self.rename_view_impl(from, to)
417    }
418
419    fn drop_view(&self, relation: &RelationIdentity) -> StorageBackendResult<bool> {
420        self.drop_view_impl(relation)
421    }
422
423    fn load_views(&self) -> StorageBackendResult<Vec<ViewRow>> {
424        self.load_views_impl()
425    }
426
427    fn save_named_graph(&self, name: &str) -> StorageBackendResult<()> {
428        self.save_named_graph_impl(name)
429    }
430    fn guard_graph_definition(&self, graph: Option<&str>) -> StorageBackendResult<()> {
431        self.guard_graph_definition_impl(graph)
432    }
433    fn load_named_graph_snapshot(&self, name: &str) -> StorageBackendResult<Option<GraphSnapshot>> {
434        let (mut result, namespace) =
435            self.with_graph_read(|read| Ok((read.snapshot(name)?, read.identifier_namespace()?)))?;
436        if let (Some(snapshot), Some(allocator)) =
437            (result.as_mut(), self.store.identifier_allocator())
438        {
439            snapshot.label_registry_json = crate::catalog::graph_identifiers::export_registry(
440                &snapshot.label_registry_json,
441                name,
442                namespace,
443                |key| allocator.identifier_watermark(key),
444            )?;
445        }
446        Ok(result)
447    }
448
449    fn drop_named_graph(&self, name: &str) -> StorageBackendResult<()> {
450        self.drop_named_graph_impl(name)
451    }
452
453    fn load_named_graphs(&self) -> StorageBackendResult<Vec<String>> {
454        self.load_named_graphs_impl()
455    }
456    fn named_graph_exists(&self, name: &str) -> StorageBackendResult<bool> {
457        self.store
458            .contains_key(&single_str_key(TAG_NAMED_GRAPH, name)?)
459    }
460
461    fn save_vertex(
462        &self,
463        vertex_id: u64,
464        label: &str,
465        properties_json: &str,
466    ) -> StorageBackendResult<()> {
467        self.save_vertex_impl(vertex_id, label, properties_json)
468    }
469
470    fn delete_vertex(&self, vertex_id: u64) -> StorageBackendResult<()> {
471        self.delete_vertex_impl(vertex_id)
472    }
473
474    fn load_vertices(&self) -> StorageBackendResult<Vec<(u64, String, String)>> {
475        self.load_vertices_impl()
476    }
477
478    fn save_edge(
479        &self,
480        edge_id: u64,
481        source_id: u64,
482        target_id: u64,
483        label: &str,
484        properties_json: &str,
485    ) -> StorageBackendResult<()> {
486        self.save_edge_impl(edge_id, source_id, target_id, label, properties_json)
487    }
488
489    fn delete_edge(&self, edge_id: u64) -> StorageBackendResult<()> {
490        self.delete_edge_impl(edge_id)
491    }
492
493    fn load_edges(&self) -> StorageBackendResult<Vec<EdgeRow>> {
494        self.load_edges_impl()
495    }
496
497    fn save_graph_membership(
498        &self,
499        entity_type: &str,
500        entity_id: u64,
501        graph_name: &str,
502    ) -> StorageBackendResult<()> {
503        self.save_graph_membership_impl(entity_type, entity_id, graph_name)
504    }
505
506    fn delete_graph_membership(
507        &self,
508        entity_type: &str,
509        entity_id: u64,
510        graph_name: &str,
511    ) -> StorageBackendResult<()> {
512        self.delete_graph_membership_impl(entity_type, entity_id, graph_name)
513    }
514
515    fn delete_graph_membership_for_graph(&self, graph_name: &str) -> StorageBackendResult<()> {
516        self.delete_graph_membership_for_graph_impl(graph_name)
517    }
518
519    fn load_graph_memberships(&self) -> StorageBackendResult<Vec<(String, u64, String)>> {
520        self.load_graph_memberships_impl()
521    }
522
523    fn purge_orphan_graph_entities(&self) -> StorageBackendResult<()> {
524        self.purge_orphan_graph_entities_impl()
525    }
526
527    fn replace_named_graph(
528        &self,
529        graph_name: &str,
530        snapshot: &GraphSnapshot,
531    ) -> StorageBackendResult<()> {
532        self.replace_named_graph_impl(graph_name, snapshot)
533    }
534
535    fn drop_named_graph_data(&self, graph_name: &str) -> StorageBackendResult<()> {
536        self.drop_named_graph_data_impl(graph_name)
537    }
538
539    fn save_analyzer(&self, name: &str, config_json: &str) -> StorageBackendResult<()> {
540        self.save_analyzer_impl(name, config_json)
541    }
542
543    fn drop_analyzer(&self, name: &str) -> StorageBackendResult<()> {
544        self.drop_analyzer_impl(name)
545    }
546
547    fn load_analyzers(&self) -> StorageBackendResult<Vec<(String, String)>> {
548        self.load_analyzers_impl()
549    }
550
551    fn save_analyzer_revision(
552        &self,
553        name: &str,
554        config_json: &str,
555        descriptor_json: &str,
556    ) -> StorageBackendResult<()> {
557        self.save_analyzer_revision_impl(name, config_json, descriptor_json)
558    }
559
560    fn load_analyzer_descriptors(&self) -> StorageBackendResult<Vec<(String, String)>> {
561        load_single_string_rows(self.store.as_ref(), TAG_ANALYZER_DESCRIPTOR)
562    }
563
564    fn replace_table_field_analyzer_binding(
565        &self,
566        table: &str,
567        field: &str,
568        phase: &str,
569        name: &str,
570        binding_json: &str,
571    ) -> StorageBackendResult<()> {
572        self.replace_table_field_analyzer_binding_impl(table, field, phase, name, binding_json)
573    }
574
575    fn load_table_field_analyzer_bindings(
576        &self,
577    ) -> StorageBackendResult<Vec<(String, String, String)>> {
578        self.load_table_field_analyzer_bindings_impl()
579    }
580
581    fn save_table_field_analyzer(
582        &self,
583        table_name: &str,
584        field: &str,
585        phase: &str,
586        analyzer_name: &str,
587    ) -> StorageBackendResult<()> {
588        self.save_table_field_analyzer_impl(table_name, field, phase, analyzer_name)
589    }
590
591    fn replace_table_field_analyzer(
592        &self,
593        table_name: &str,
594        field: &str,
595        phase: &str,
596        analyzer_name: &str,
597    ) -> StorageBackendResult<()> {
598        self.replace_table_field_analyzer_impl(table_name, field, phase, analyzer_name)
599    }
600
601    fn drop_table_field_analyzer_field(
602        &self,
603        table_name: &str,
604        field: &str,
605    ) -> StorageBackendResult<()> {
606        self.drop_table_field_analyzer_field_impl(table_name, field)
607    }
608
609    fn drop_table_field_analyzers(&self, table_name: &str) -> StorageBackendResult<()> {
610        self.drop_table_field_analyzers_impl(table_name)
611    }
612
613    fn load_table_field_analyzers(
614        &self,
615    ) -> StorageBackendResult<Vec<(String, String, String, String)>> {
616        self.load_table_field_analyzers_impl()
617    }
618
619    fn save_foreign_server(
620        &self,
621        name: &str,
622        fdw_type: &str,
623        options_json: &str,
624    ) -> StorageBackendResult<()> {
625        self.save_foreign_server_impl(name, fdw_type, options_json)
626    }
627
628    fn drop_foreign_server(&self, name: &str) -> StorageBackendResult<()> {
629        self.drop_foreign_server_impl(name)
630    }
631
632    fn load_foreign_servers(&self) -> StorageBackendResult<Vec<(String, String, String)>> {
633        self.load_foreign_servers_impl()
634    }
635
636    fn save_foreign_table(&self, row: &ForeignTableRow) -> StorageBackendResult<()> {
637        self.save_foreign_table_impl(row)
638    }
639
640    fn rename_foreign_table(
641        &self,
642        from: &RelationIdentity,
643        to: &RelationIdentity,
644    ) -> StorageBackendResult<bool> {
645        self.rename_foreign_table_impl(from, to)
646    }
647
648    fn update_foreign_table_security(
649        &self,
650        relation: &RelationIdentity,
651        security: &crate::RelationSecurityRow,
652    ) -> StorageBackendResult<bool> {
653        self.update_foreign_table_security_impl(relation, security)
654    }
655
656    fn drop_foreign_table(&self, relation: &RelationIdentity) -> StorageBackendResult<()> {
657        self.drop_foreign_table_impl(relation)
658    }
659
660    fn load_foreign_tables(&self) -> StorageBackendResult<Vec<ForeignTableRow>> {
661        self.load_foreign_tables_impl()
662    }
663
664    fn save_catalog_index_row(&self, index: &CatalogIndexRow) -> StorageBackendResult<()> {
665        self.save_catalog_index_impl(index)
666    }
667
668    fn drop_catalog_index(&self, relation: &RelationIdentity) -> StorageBackendResult<()> {
669        self.drop_catalog_index_impl(relation)
670    }
671
672    fn drop_catalog_indexes_for_table(&self, table_name: &str) -> StorageBackendResult<()> {
673        self.drop_catalog_indexes_for_table_impl(table_name)
674    }
675
676    fn load_catalog_indexes(&self) -> StorageBackendResult<Vec<CatalogIndexRow>> {
677        self.load_catalog_indexes_impl()
678    }
679
680    fn save_path_index(
681        &self,
682        graph_name: &str,
683        label_sequences_json: &str,
684    ) -> StorageBackendResult<()> {
685        self.save_path_index_impl(graph_name, label_sequences_json)
686    }
687
688    fn drop_path_index(&self, graph_name: &str) -> StorageBackendResult<()> {
689        self.drop_path_index_impl(graph_name)
690    }
691
692    fn load_path_indexes(&self) -> StorageBackendResult<Vec<(String, String)>> {
693        self.load_path_indexes_impl()
694    }
695
696    fn save_column_stats(&self, stats: ColumnStatsInput<'_>) -> StorageBackendResult<()> {
697        self.save_column_stats_impl(stats)
698    }
699
700    fn replace_column_stats(
701        &self,
702        table_name: &str,
703        stats: &[ColumnStatsInput<'_>],
704    ) -> StorageBackendResult<()> {
705        self.replace_column_stats_impl(table_name, stats)
706    }
707
708    fn load_column_stats(&self, table_name: &str) -> StorageBackendResult<Vec<ColumnStatsRow>> {
709        self.load_column_stats_impl(table_name)
710    }
711
712    fn delete_column_stats(&self, table_name: &str) -> StorageBackendResult<()> {
713        self.delete_column_stats_impl(table_name)
714    }
715}
716
717#[cfg(test)]
718mod tests;