1use std::collections::{BTreeMap, HashSet};
6
7use itertools::Itertools;
8use proc_macro::TokenStream;
9use proc_macro2::Ident;
10use quote::quote;
11use syn::{
12 AngleBracketedGenericArguments, Attribute, Generics, ItemStruct, Lit, Meta, PathArguments,
13 Type::{self},
14 parse_macro_input,
15};
16
17const DEFAULT_DB_OPTIONS_CUSTOM_FN: &str = "typed_store::rocks::default_db_options";
19const DB_OPTIONS_CUSTOM_FUNCTION: &str = "default_options_override_fn";
22const DB_OPTIONS_RENAME: &str = "rename";
24const DB_OPTIONS_DEPRECATE: &str = "deprecated";
26
27enum GeneralTableOptions {
29 OverrideFunction(String),
30}
31
32impl Default for GeneralTableOptions {
33 fn default() -> Self {
34 Self::OverrideFunction(DEFAULT_DB_OPTIONS_CUSTOM_FN.to_owned())
35 }
36}
37
38fn extract_struct_info(
41 input: ItemStruct,
42 allowed_map_type_names: HashSet<String>,
43) -> ExtractedStructInfo {
44 let allowed_strs: Vec<_> = allowed_map_type_names
46 .iter()
47 .map(|s| format!("{s}<K, V>"))
48 .collect();
49 let allowed_strs = allowed_strs.join(" or ");
50 let mut deprecated_cfs = vec![];
51
52 let info = input.fields.iter().map(|f| {
53 let attrs: BTreeMap<_, _> = f
54 .attrs
55 .iter()
56 .filter(|a| {
57 a.path.is_ident(DB_OPTIONS_CUSTOM_FUNCTION)
58 || a.path.is_ident(DB_OPTIONS_RENAME)
59 || a.path.is_ident(DB_OPTIONS_DEPRECATE)
60 })
61 .map(|a| (a.path.get_ident().unwrap().to_string(), a))
62 .collect();
63
64 let options = if let Some(options) = attrs.get(DB_OPTIONS_CUSTOM_FUNCTION) {
65 GeneralTableOptions::OverrideFunction(get_options_override_function(options).unwrap())
66 } else {
67 GeneralTableOptions::default()
68 };
69
70 let ty = &f.ty;
71 if let Type::Path(p) = ty {
72 let type_info = &p.path.segments.first().unwrap();
73 let inner_type =
74 if let PathArguments::AngleBracketed(angle_bracket_type) = &type_info.arguments {
75 angle_bracket_type.clone()
76 } else {
77 panic!("All struct members must be of type {allowed_strs}");
78 };
79
80 let type_str = format!("{}", &type_info.ident);
81 if allowed_map_type_names.contains(&type_str) {
83 let field_name = f.ident.as_ref().unwrap().clone();
84 let cf_name = if let Some(rename) = attrs.get(DB_OPTIONS_RENAME) {
85 match rename.parse_meta().expect("Cannot parse meta of attribute") {
86 Meta::NameValue(val) => {
87 if let Lit::Str(s) = val.lit {
88 s.parse().expect("Rename value must be identifier")
90 } else {
91 panic!("Expected string value for rename")
92 }
93 }
94 _ => panic!("Expected string value for rename"),
95 }
96 } else {
97 field_name.clone()
98 };
99 if attrs.contains_key(DB_OPTIONS_DEPRECATE) {
100 deprecated_cfs.push(field_name.clone());
101 }
102
103 return ((field_name, cf_name, type_str), (inner_type, options));
104 } else {
105 panic!("All struct members must be of type {allowed_strs}");
106 }
107 }
108 panic!("All struct members must be of type {allowed_strs}");
109 });
110
111 let (field_info, inner_types_with_opts): (Vec<_>, Vec<_>) = info.unzip();
112 let (field_names, cf_names, simple_field_type_names): (Vec<_>, Vec<_>, Vec<_>) =
113 field_info.into_iter().multiunzip();
114
115 if let Some(first) = simple_field_type_names.first() {
117 simple_field_type_names.iter().for_each(|q| {
118 if q != first {
119 panic!("All struct members must be of same type");
120 }
121 })
122 } else {
123 panic!("Cannot derive on empty struct");
124 };
125
126 let (inner_types, options): (Vec<_>, Vec<_>) = inner_types_with_opts.into_iter().unzip();
127
128 ExtractedStructInfo {
129 field_names,
130 cf_names,
131 inner_types,
132 derived_table_options: options,
133 simple_field_type_name_str: simple_field_type_names.first().unwrap().clone(),
134 deprecated_cfs,
135 }
136}
137
138fn get_options_override_function(attr: &Attribute) -> syn::Result<String> {
141 let meta = attr.parse_meta()?;
142
143 let val = match meta.clone() {
144 Meta::NameValue(val) => val,
145 _ => {
146 return Err(syn::Error::new_spanned(
147 meta,
148 format!(
149 "Expected function name in format `#[{DB_OPTIONS_CUSTOM_FUNCTION} = {{function_name}}]`"
150 ),
151 ));
152 }
153 };
154
155 if !val.path.is_ident(DB_OPTIONS_CUSTOM_FUNCTION) {
156 return Err(syn::Error::new_spanned(
157 meta,
158 format!(
159 "Expected function name in format `#[{DB_OPTIONS_CUSTOM_FUNCTION} = {{function_name}}]`"
160 ),
161 ));
162 }
163
164 let fn_name = match val.lit {
165 Lit::Str(fn_name) => fn_name,
166 _ => {
167 return Err(syn::Error::new_spanned(
168 meta,
169 format!(
170 "Expected function name in format `#[{DB_OPTIONS_CUSTOM_FUNCTION} = {{function_name}}]`"
171 ),
172 ));
173 }
174 };
175 Ok(fn_name.value())
176}
177
178fn extract_generics_names(generics: &Generics) -> Vec<Ident> {
179 generics
180 .params
181 .iter()
182 .map(|g| match g {
183 syn::GenericParam::Type(t) => t.ident.clone(),
184 _ => panic!("Unsupported generic type"),
185 })
186 .collect()
187}
188
189struct ExtractedStructInfo {
190 field_names: Vec<Ident>,
191 cf_names: Vec<Ident>,
192 inner_types: Vec<AngleBracketedGenericArguments>,
193 derived_table_options: Vec<GeneralTableOptions>,
194 simple_field_type_name_str: String,
195 deprecated_cfs: Vec<Ident>,
196}
197
198#[proc_macro_derive(DBMapUtils, attributes(default_options_override_fn, rename))]
199pub fn derive_dbmap_utils_general(input: TokenStream) -> TokenStream {
200 let input = parse_macro_input!(input as ItemStruct);
201 let name = &input.ident;
202 let generics = &input.generics;
203 let generics_names = extract_generics_names(generics);
204
205 let allowed_types_with_post_process_fn: BTreeMap<_, _> =
206 [("SallyColumn", ""), ("DBMap", "")].into_iter().collect();
207 let allowed_strs = allowed_types_with_post_process_fn
208 .keys()
209 .map(|s| s.to_string())
210 .collect();
211
212 let ExtractedStructInfo {
214 field_names,
215 cf_names,
216 inner_types,
217 derived_table_options,
218 simple_field_type_name_str,
219 deprecated_cfs,
220 } = extract_struct_info(input.clone(), allowed_strs);
221
222 let (key_names, value_names): (Vec<_>, Vec<_>) = inner_types
223 .iter()
224 .map(|q| (q.args.first().unwrap(), q.args.last().unwrap()))
225 .unzip();
226
227 let post_process_fn_str = allowed_types_with_post_process_fn
229 .get(&simple_field_type_name_str.as_str())
230 .unwrap();
231 let post_process_fn: proc_macro2::TokenStream = post_process_fn_str.parse().unwrap();
232
233 let default_options_override_fn_names: Vec<proc_macro2::TokenStream> = derived_table_options
234 .iter()
235 .map(|q| {
236 let GeneralTableOptions::OverrideFunction(fn_name) = q;
237 fn_name.parse().unwrap()
238 })
239 .collect();
240
241 let generics_bounds =
242 "std::fmt::Debug + serde::Serialize + for<'de> serde::de::Deserialize<'de>";
243 let generics_bounds_token: proc_macro2::TokenStream = generics_bounds.parse().unwrap();
244
245 let config_struct_name_str = format!("{name}Configurator");
246 let config_struct_name: proc_macro2::TokenStream = config_struct_name_str.parse().unwrap();
247
248 let intermediate_db_map_struct_name_str = format!("{name}IntermediateDBMapStructPrimary");
249 let intermediate_db_map_struct_name: proc_macro2::TokenStream =
250 intermediate_db_map_struct_name_str.parse().unwrap();
251
252 let secondary_db_map_struct_name_str = format!("{name}ReadOnly");
253 let secondary_db_map_struct_name: proc_macro2::TokenStream =
254 secondary_db_map_struct_name_str.parse().unwrap();
255
256 TokenStream::from(quote! {
257
258 pub struct #config_struct_name {
262 #(
263 pub #field_names : typed_store::rocks::DBOptions,
264 )*
265 }
266
267 impl #config_struct_name {
268 pub fn init() -> Self {
270 Self {
271 #(
272 #field_names : typed_store::rocks::default_db_options(),
273 )*
274 }
275 }
276
277 pub fn build(&self) -> typed_store::rocks::DBMapTableConfigMap {
279 typed_store::rocks::DBMapTableConfigMap::new([
280 #(
281 (stringify!(#field_names).to_owned(), self.#field_names.clone()),
282 )*
283 ].into_iter().collect())
284 }
285 }
286
287 impl <
288 #(
289 #generics_names: #generics_bounds_token,
290 )*
291 > #name #generics {
292
293 pub fn configurator() -> #config_struct_name {
294 #config_struct_name::init()
295 }
296 }
297
298 struct #intermediate_db_map_struct_name #generics {
303 #(
304 pub #field_names : DBMap #inner_types,
305 )*
306 }
307
308
309 impl <
310 #(
311 #generics_names: #generics_bounds_token,
312 )*
313 > #intermediate_db_map_struct_name #generics {
314 pub fn open_tables_impl(
317 path: std::path::PathBuf,
318 as_secondary_with_path: Option<std::path::PathBuf>,
319 is_transaction: bool,
320 metric_conf: typed_store::rocks::MetricConf,
321 global_db_options_override: Option<typed_store::rocksdb::Options>,
322 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>,
323 remove_deprecated_tables: bool,
324 ) -> Self {
325 let path = &path;
326 let default_cf_opt = if let Some(opt) = global_db_options_override.as_ref() {
327 typed_store::rocks::DBOptions {
328 options: opt.clone(),
329 rw_options: typed_store::rocks::default_db_options().rw_options,
330 }
331 } else {
332 typed_store::rocks::default_db_options()
333 };
334 let (db, rwopt_cfs) = {
335 let opt_cfs = match tables_db_options_override {
336 None => [
337 #(
338 (stringify!(#cf_names).to_owned(), #default_options_override_fn_names()),
339 )*
340 ],
341 Some(o) => [
342 #(
343 (stringify!(#cf_names).to_owned(), o.to_map().get(stringify!(#cf_names)).unwrap_or(&default_cf_opt).clone()),
344 )*
345 ]
346 };
347 let rwopt_cfs: std::collections::HashMap<String, typed_store::rocks::ReadWriteOptions> = opt_cfs.iter().map(|q| (q.0.as_str().to_string(), q.1.rw_options.clone())).collect();
349 let opt_cfs: Vec<_> = opt_cfs.iter().map(|q| (q.0.as_str(), q.1.options.clone())).collect();
350 let db = match (as_secondary_with_path.clone(), is_transaction) {
351 (Some(p), _) => typed_store::rocks::open_cf_opts_secondary(path, Some(&p), global_db_options_override, metric_conf, &opt_cfs),
352 (_, true) => typed_store::rocks::open_cf_opts_transactional(path, global_db_options_override, metric_conf, &opt_cfs),
353 _ => typed_store::rocks::open_cf_opts(path, global_db_options_override, metric_conf, &opt_cfs)
354 };
355 db.map(|d| (d, rwopt_cfs))
356 }.expect(&format!("Cannot open DB at {:?}", path));
357 let deprecated_tables = vec![#(stringify!(#deprecated_cfs),)*];
358 let (
359 #(
360 #field_names
361 ),*
362 ) = (#(
363 DBMap::#inner_types::reopen(&db, Some(stringify!(#cf_names)), rwopt_cfs.get(stringify!(#cf_names)).unwrap_or(&typed_store::rocks::ReadWriteOptions::default()), remove_deprecated_tables && deprecated_tables.contains(&stringify!(#cf_names))).expect(&format!("Cannot open {} CF.", stringify!(#cf_names))[..])
364 ),*);
365
366 if as_secondary_with_path.is_none() && remove_deprecated_tables {
367 #(
368 db.drop_cf(stringify!(#deprecated_cfs)).expect("failed to drop a deprecated cf");
369 )*
370 }
371 Self {
372 #(
373 #field_names,
374 )*
375 }
376 }
377 }
378
379
380 impl <
383 #(
384 #generics_names: #generics_bounds_token,
385 )*
386 > #name #generics {
387 #[expect(unused_parens)]
392 pub fn open_tables_read_write(
393 path: std::path::PathBuf,
394 metric_conf: typed_store::rocks::MetricConf,
395 global_db_options_override: Option<typed_store::rocksdb::Options>,
396 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>
397 ) -> Self {
398 let inner = #intermediate_db_map_struct_name::open_tables_impl(path, None, false, metric_conf, global_db_options_override, tables_db_options_override, false);
399 Self {
400 #(
401 #field_names: #post_process_fn(inner.#field_names),
402 )*
403 }
404 }
405
406 #[expect(unused_parens)]
407 pub fn open_tables_read_write_with_deprecation_option(
408 path: std::path::PathBuf,
409 metric_conf: typed_store::rocks::MetricConf,
410 global_db_options_override: Option<typed_store::rocksdb::Options>,
411 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>,
412 remove_deprecated_tables: bool,
413 ) -> Self {
414 let inner = #intermediate_db_map_struct_name::open_tables_impl(path, None, false, metric_conf, global_db_options_override, tables_db_options_override, remove_deprecated_tables);
415 Self {
416 #(
417 #field_names: #post_process_fn(inner.#field_names),
418 )*
419 }
420 }
421
422 #[expect(unused_parens)]
427 pub fn open_tables_transactional(
428 path: std::path::PathBuf,
429 metric_conf: typed_store::rocks::MetricConf,
430 global_db_options_override: Option<typed_store::rocksdb::Options>,
431 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>
432 ) -> Self {
433 let inner = #intermediate_db_map_struct_name::open_tables_impl(path, None, true, metric_conf, global_db_options_override, tables_db_options_override, false);
434 Self {
435 #(
436 #field_names: #post_process_fn(inner.#field_names),
437 )*
438 }
439 }
440
441 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
443 vec![#(
444 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
445 )*].into_iter().collect()
446 }
447
448 pub fn get_read_only_handle (
450 primary_path: std::path::PathBuf,
451 with_secondary_path: Option<std::path::PathBuf>,
452 global_db_options_override: Option<typed_store::rocksdb::Options>,
453 metric_conf: typed_store::rocks::MetricConf,
454 ) -> #secondary_db_map_struct_name #generics {
455 #secondary_db_map_struct_name::open_tables_read_only(primary_path, with_secondary_path, metric_conf, global_db_options_override)
456 }
457 }
458
459
460 pub struct #secondary_db_map_struct_name #generics {
464 #(
465 pub #field_names : DBMap #inner_types,
466 )*
467 }
468
469 impl <
470 #(
471 #generics_names: #generics_bounds_token,
472 )*
473 > #secondary_db_map_struct_name #generics {
474 pub fn open_tables_read_only(
476 primary_path: std::path::PathBuf,
477 with_secondary_path: Option<std::path::PathBuf>,
478 metric_conf: typed_store::rocks::MetricConf,
479 global_db_options_override: Option<typed_store::rocksdb::Options>,
480 ) -> Self {
481 let inner = match with_secondary_path {
482 Some(q) => #intermediate_db_map_struct_name::open_tables_impl(primary_path, Some(q), false, metric_conf, global_db_options_override, None, false),
483 None => {
484 let p: std::path::PathBuf = tempfile::tempdir()
485 .expect("Failed to open temporary directory")
486 .into_path();
487 #intermediate_db_map_struct_name::open_tables_impl(primary_path, Some(p), false, metric_conf, global_db_options_override, None, false)
488 }
489 };
490 Self {
491 #(
492 #field_names: inner.#field_names,
493 )*
494 }
495 }
496
497 fn cf_name_to_table_name(cf_name: &str) -> eyre::Result<&'static str> {
498 Ok(match cf_name {
499 #(
500 stringify!(#cf_names) => stringify!(#field_names),
501 )*
502 _ => eyre::bail!("No such cf name: {}", cf_name),
503 })
504 }
505
506 pub fn dump(&self, cf_name: &str, page_size: u16, page_number: usize) -> eyre::Result<std::collections::BTreeMap<String, String>> {
509 let table_name = Self::cf_name_to_table_name(cf_name)?;
510
511 Ok(match table_name {
512 #(
513 stringify!(#field_names) => {
514 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
515 typed_store::traits::Map::unbounded_iter(&self.#field_names)
516 .skip((page_number * (page_size) as usize))
517 .take(page_size as usize)
518 .map(|(k, v)| (format!("{:?}", k), format!("{:?}", v)))
519 .collect::<std::collections::BTreeMap<_, _>>()
520 }
521 )*
522
523 _ => eyre::bail!("No such table name: {}", table_name),
524 })
525 }
526
527 pub fn table_summary(&self, table_name: &str) -> eyre::Result<typed_store::traits::TableSummary> {
530 let mut count = 0;
531 let mut key_bytes = 0;
532 let mut value_bytes = 0;
533 match table_name {
534 #(
535 stringify!(#field_names) => {
536 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
537 self.#field_names.table_summary()
538 }
539 )*
540
541 _ => eyre::bail!("No such table name: {}", table_name),
542 }
543 }
544
545 pub fn count_keys(&self, table_name: &str) -> eyre::Result<usize> {
548 Ok(match table_name {
549 #(
550 stringify!(#field_names) => {
551 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
552 typed_store::traits::Map::unbounded_iter(&self.#field_names).count()
553 }
554 )*
555
556 _ => eyre::bail!("No such table name: {}", table_name),
557 })
558 }
559
560 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
561 vec![#(
562 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
563 )*].into_iter().collect()
564 }
565
566 pub fn try_catch_up_with_primary_all(&self) -> eyre::Result<()> {
569 #(
570 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
571 )*
572 Ok(())
573 }
574 }
575
576 impl <
577 #(
578 #generics_names: #generics_bounds_token,
579 )*
580 > TypedStoreDebug for #secondary_db_map_struct_name #generics {
581 fn dump_table(
582 &self,
583 table_name: String,
584 page_size: u16,
585 page_number: usize,
586 ) -> eyre::Result<std::collections::BTreeMap<String, String>> {
587 self.dump(table_name.as_str(), page_size, page_number)
588 }
589
590 fn primary_db_name(&self) -> String {
591 stringify!(#name).to_owned()
592 }
593
594 fn describe_all_tables(&self) -> std::collections::BTreeMap<String, (String, String)> {
595 Self::describe_tables()
596 }
597
598 fn count_table_keys(&self, table_name: String) -> eyre::Result<usize> {
599 self.count_keys(table_name.as_str())
600 }
601
602 fn table_summary(&self, table_name: String) -> eyre::Result<TableSummary> {
603 self.table_summary(table_name.as_str())
604 }
605
606
607 }
608
609 })
610}
611
612#[proc_macro_derive(SallyDB, attributes(default_options_override_fn))]
613pub fn derive_sallydb_general(input: TokenStream) -> TokenStream {
614 let input = parse_macro_input!(input as ItemStruct);
616 let name = &input.ident;
617 let generics = &input.generics;
618 let generics_names = extract_generics_names(generics);
619
620 let allowed_types_with_post_process_fn: BTreeMap<_, _> =
621 [("SallyColumn", "")].into_iter().collect();
622 let allowed_strs = allowed_types_with_post_process_fn
623 .keys()
624 .map(|s| s.to_string())
625 .collect();
626
627 let ExtractedStructInfo {
631 field_names,
632 inner_types,
633 derived_table_options,
634 simple_field_type_name_str,
635 ..
636 } = extract_struct_info(input.clone(), allowed_strs);
637
638 let (key_names, value_names): (Vec<_>, Vec<_>) = inner_types
639 .iter()
640 .map(|q| (q.args.first().unwrap(), q.args.last().unwrap()))
641 .unzip();
642
643 let post_process_fn_str = allowed_types_with_post_process_fn
645 .get(&simple_field_type_name_str.as_str())
646 .unwrap();
647 let post_process_fn: proc_macro2::TokenStream = post_process_fn_str.parse().unwrap();
648
649 let default_options_override_fn_names: Vec<proc_macro2::TokenStream> = derived_table_options
650 .iter()
651 .map(|q| {
652 let GeneralTableOptions::OverrideFunction(fn_name) = q;
653 fn_name.parse().unwrap()
654 })
655 .collect();
656
657 let generics_bounds =
658 "std::fmt::Debug + serde::Serialize + for<'de> serde::de::Deserialize<'de>";
659 let generics_bounds_token: proc_macro2::TokenStream = generics_bounds.parse().unwrap();
660
661 let config_struct_name_str = format!("{name}SallyConfigurator");
662 let sally_config_struct_name: proc_macro2::TokenStream =
663 config_struct_name_str.parse().unwrap();
664
665 let intermediate_db_map_struct_name_str = format!("{name}Primary");
666 let intermediate_db_map_struct_name: proc_macro2::TokenStream =
667 intermediate_db_map_struct_name_str.parse().unwrap();
668
669 let secondary_db_map_struct_name_str = format!("{name}ReadOnly");
670 let secondary_db_map_struct_name: proc_macro2::TokenStream =
671 secondary_db_map_struct_name_str.parse().unwrap();
672
673 TokenStream::from(quote! {
674
675 pub struct #sally_config_struct_name {
679 #(
680 pub #field_names : typed_store::sally::SallyColumnOptions,
681 )*
682 }
683
684 impl #sally_config_struct_name {
685 pub fn init() -> Self {
687 Self {
688 #(
689 #field_names : typed_store::sally::default_column_options(),
690 )*
691 }
692 }
693
694 pub fn build(&self) -> typed_store::sally::SallyDBConfigMap {
696 typed_store::sally::SallyDBConfigMap::new([
697 #(
698 (stringify!(#field_names).to_owned(), self.#field_names.clone()),
699 )*
700 ].into_iter().collect())
701 }
702 }
703
704
705 impl <
706 #(
707 #generics_names: #generics_bounds_token,
708 )*
709 > #name #generics {
710
711 pub fn configurator() -> #sally_config_struct_name {
712 #sally_config_struct_name::init()
713 }
714 }
715
716
717 struct #intermediate_db_map_struct_name #generics {
722 #(
723 pub #field_names : SallyColumn #inner_types,
724 )*
725 }
726
727
728 impl <
729 #(
730 #generics_names: #generics_bounds_token,
731 )*
732 > #intermediate_db_map_struct_name #generics {
733 pub fn init(db_options: typed_store::sally::SallyDBOptions) -> Self {
736 match db_options {
737 typed_store::sally::SallyDBOptions::TestDB => {
738 let (
739 #(
740 #field_names
741 ),*
742 ) = (#(
743 SallyColumn::TestDB((typed_store::test_db::TestDB::#inner_types::open(), typed_store::sally::SallyConfig::default()))
744 ),*);
745
746 Self {
747 #(
748 #field_names,
749 )*
750 }
751 },
752 typed_store::sally::SallyDBOptions::RocksDB((path, metric_conf, access_type, global_db_options_override, tables_db_options_override)) => {
753 let path = &path;
754 let (db, rwopt_cfs) = {
755 let opt_cfs = match tables_db_options_override {
756 None => [
757 #(
758 (stringify!(#field_names).to_owned(), #default_options_override_fn_names().clone()),
759 )*
760 ],
761 Some(o) => [
762 #(
763 (stringify!(#field_names).to_owned(), o.to_map().get(stringify!(#field_names)).unwrap().clone()),
764 )*
765 ]
766 };
767 let rwopt_cfs: std::collections::HashMap<String, typed_store::rocks::ReadWriteOptions> = opt_cfs.iter().map(|q| (q.0.as_str().to_string(), q.1.rw_options.clone())).collect();
769 let opt_cfs: Vec<_> = opt_cfs.iter().map(|q| (q.0.as_str(), q.1.options.clone())).collect();
770 let db = match access_type {
771 RocksDBAccessType::Secondary(Some(p)) => typed_store::rocks::open_cf_opts_secondary(path, Some(&p), global_db_options_override, metric_conf, &opt_cfs),
772 _ => typed_store::rocks::open_cf_opts(path, global_db_options_override, metric_conf, &opt_cfs)
773 };
774 db.map(|d| (d, rwopt_cfs))
775 }.expect(&format!("Cannot open DB at {:?}", path));
776 let (
777 #(
778 #field_names
779 ),*
780 ) = (#(
781 SallyColumn::RocksDB((DBMap::#inner_types::reopen(&db, Some(stringify!(#field_names)), rwopt_cfs.get(stringify!(#field_names)).unwrap_or(&typed_store::rocks::ReadWriteOptions::default()), false).expect(&format!("Cannot open {} CF.", stringify!(#field_names))[..]), typed_store::sally::SallyConfig::default()))
782 ),*);
783
784 Self {
785 #(
786 #field_names,
787 )*
788 }
789 }
790 }
791 }
792 }
793
794
795 impl <
797 #(
798 #generics_names: #generics_bounds_token,
799 )*
800 > #name #generics {
801 #[expect(unused_parens)]
806 pub fn init(
807 db_options: typed_store::sally::SallyDBOptions
808 ) -> Self {
809 let inner = #intermediate_db_map_struct_name::init(db_options);
810 Self {
811 #(
812 #field_names: #post_process_fn(inner.#field_names),
813 )*
814 }
815 }
816
817 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
819 vec![#(
820 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
821 )*].into_iter().collect()
822 }
823
824 pub fn get_read_only_handle (
826 db_options: typed_store::sally::SallyReadOnlyDBOptions
827 ) -> #secondary_db_map_struct_name #generics {
828 #secondary_db_map_struct_name::init_read_only(db_options)
829 }
830 }
831
832 pub struct #secondary_db_map_struct_name #generics {
836 #(
837 pub #field_names : SallyColumn #inner_types,
838 )*
839 }
840
841 impl <
842 #(
843 #generics_names: #generics_bounds_token,
844 )*
845 > #secondary_db_map_struct_name #generics {
846 pub fn init_read_only(
848 db_options: typed_store::sally::SallyReadOnlyDBOptions,
849 ) -> Self {
850 match db_options {
851 typed_store::sally::SallyReadOnlyDBOptions::TestDB => {
852 let inner = #intermediate_db_map_struct_name::init(SallyDBOptions::TestDB);
853 Self {
854 #(
855 #field_names: inner.#field_names,
856 )*
857 }
858 },
859 typed_store::sally::SallyReadOnlyDBOptions::RocksDB(b) => {
860 let inner = match b.2 {
861 Some(q) => #intermediate_db_map_struct_name::init(SallyDBOptions::RocksDB((b.0, b.1, RocksDBAccessType::Secondary(Some(q)), b.3, None))),
862 None => {
863 let p: std::path::PathBuf = tempfile::tempdir()
864 .expect("Failed to open temporary directory")
865 .into_path();
866 #intermediate_db_map_struct_name::init(SallyDBOptions::RocksDB((b.0, b.1, RocksDBAccessType::Secondary(Some(p)), b.3, None)))
867 }
868 };
869 Self {
870 #(
871 #field_names: inner.#field_names,
872 )*
873 }
874 }
875 }
876 }
877
878 pub fn dump(&self, table_name: &str, page_size: u16,
881 page_number: usize) -> eyre::Result<std::collections::BTreeMap<String, String>> {
882 Ok(match table_name {
883 #(
884 stringify!(#field_names) => {
885 match &self.#field_names {
886 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
887 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
888 typed_store::traits::Map::unbounded_iter(db_map)
889 .skip((page_number * (page_size) as usize))
890 .take(page_size as usize)
891 .map(|(k, v)| (format!("{:?}", k), format!("{:?}", v)))
892 .collect::<std::collections::BTreeMap<_, _>>()
893 }
894 _ => unimplemented!(),
895 }
896 }
897 )*
898 _ => eyre::bail!("No such table name: {}", table_name),
899 })
900 }
901
902 pub fn table_summary(&self, table_name: &str) -> eyre::Result<typed_store::traits::TableSummary> {
903 let mut count = 0;
904 let mut key_bytes = 0;
905 let mut value_bytes = 0;
906 match table_name {
907 #(
908 stringify!(#field_names) => {
909 match &self.#field_names {
910 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
911 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
912 db_map.table_summary()
913 }
914 _ => unimplemented!(),
915 }
916 }
917 )*
918
919 _ => eyre::bail!("No such table name: {}", table_name),
920 }
921 }
922
923 pub fn count_keys(&self, table_name: &str) -> eyre::Result<usize> {
926 Ok(match table_name {
927 #(
928 stringify!(#field_names) => {
929 match &self.#field_names {
930 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
931 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
932 typed_store::traits::Map::unbounded_iter(db_map).count()
933 }
934 _ => unimplemented!(),
935 }
936 }
937 )*
938
939 _ => eyre::bail!("No such table name: {}", table_name),
940 })
941 }
942
943 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
944 vec![#(
945 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
946 )*].into_iter().collect()
947 }
948 }
949
950
951 impl <
952 #(
953 #generics_names: #generics_bounds_token,
954 )*
955 > TypedStoreDebug for #secondary_db_map_struct_name #generics {
956 fn dump_table(
957 &self,
958 table_name: String,
959 page_size: u16,
960 page_number: usize,
961 ) -> eyre::Result<std::collections::BTreeMap<String, String>> {
962 self.dump(table_name.as_str(), page_size, page_number)
963 }
964
965 fn primary_db_name(&self) -> String {
966 stringify!(#name).to_owned()
967 }
968
969 fn describe_all_tables(&self) -> std::collections::BTreeMap<String, (String, String)> {
970 Self::describe_tables()
971 }
972
973 fn count_table_keys(&self, table_name: String) -> eyre::Result<usize> {
974 self.count_keys(table_name.as_str())
975 }
976 fn table_summary(&self, table_name: String) -> eyre::Result<TableSummary> {
977 self.table_summary(table_name.as_str())
978 }
979
980 }
981
982 })
983}