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 (db, rwopt_cfs) = {
327 let opt_cfs = match tables_db_options_override {
328 None => [
329 #(
330 (stringify!(#cf_names).to_owned(), #default_options_override_fn_names()),
331 )*
332 ],
333 Some(o) => [
334 #(
335 (stringify!(#cf_names).to_owned(), o.to_map().get(stringify!(#cf_names)).unwrap().clone()),
336 )*
337 ]
338 };
339 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();
341 let opt_cfs: Vec<_> = opt_cfs.iter().map(|q| (q.0.as_str(), q.1.options.clone())).collect();
342 let db = match (as_secondary_with_path.clone(), is_transaction) {
343 (Some(p), _) => typed_store::rocks::open_cf_opts_secondary(path, Some(&p), global_db_options_override, metric_conf, &opt_cfs),
344 (_, true) => typed_store::rocks::open_cf_opts_transactional(path, global_db_options_override, metric_conf, &opt_cfs),
345 _ => typed_store::rocks::open_cf_opts(path, global_db_options_override, metric_conf, &opt_cfs)
346 };
347 db.map(|d| (d, rwopt_cfs))
348 }.expect(&format!("Cannot open DB at {:?}", path));
349 let deprecated_tables = vec![#(stringify!(#deprecated_cfs),)*];
350 let (
351 #(
352 #field_names
353 ),*
354 ) = (#(
355 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))[..])
356 ),*);
357
358 if as_secondary_with_path.is_none() && remove_deprecated_tables {
359 #(
360 db.drop_cf(stringify!(#deprecated_cfs)).expect("failed to drop a deprecated cf");
361 )*
362 }
363 Self {
364 #(
365 #field_names,
366 )*
367 }
368 }
369 }
370
371
372 impl <
375 #(
376 #generics_names: #generics_bounds_token,
377 )*
378 > #name #generics {
379 #[expect(unused_parens)]
384 pub fn open_tables_read_write(
385 path: std::path::PathBuf,
386 metric_conf: typed_store::rocks::MetricConf,
387 global_db_options_override: Option<typed_store::rocksdb::Options>,
388 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>
389 ) -> Self {
390 let inner = #intermediate_db_map_struct_name::open_tables_impl(path, None, false, metric_conf, global_db_options_override, tables_db_options_override, false);
391 Self {
392 #(
393 #field_names: #post_process_fn(inner.#field_names),
394 )*
395 }
396 }
397
398 #[expect(unused_parens)]
399 pub fn open_tables_read_write_with_deprecation_option(
400 path: std::path::PathBuf,
401 metric_conf: typed_store::rocks::MetricConf,
402 global_db_options_override: Option<typed_store::rocksdb::Options>,
403 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>,
404 remove_deprecated_tables: bool,
405 ) -> Self {
406 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);
407 Self {
408 #(
409 #field_names: #post_process_fn(inner.#field_names),
410 )*
411 }
412 }
413
414 #[expect(unused_parens)]
419 pub fn open_tables_transactional(
420 path: std::path::PathBuf,
421 metric_conf: typed_store::rocks::MetricConf,
422 global_db_options_override: Option<typed_store::rocksdb::Options>,
423 tables_db_options_override: Option<typed_store::rocks::DBMapTableConfigMap>
424 ) -> Self {
425 let inner = #intermediate_db_map_struct_name::open_tables_impl(path, None, true, metric_conf, global_db_options_override, tables_db_options_override, false);
426 Self {
427 #(
428 #field_names: #post_process_fn(inner.#field_names),
429 )*
430 }
431 }
432
433 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
435 vec![#(
436 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
437 )*].into_iter().collect()
438 }
439
440 pub fn get_read_only_handle (
442 primary_path: std::path::PathBuf,
443 with_secondary_path: Option<std::path::PathBuf>,
444 global_db_options_override: Option<typed_store::rocksdb::Options>,
445 metric_conf: typed_store::rocks::MetricConf,
446 ) -> #secondary_db_map_struct_name #generics {
447 #secondary_db_map_struct_name::open_tables_read_only(primary_path, with_secondary_path, metric_conf, global_db_options_override)
448 }
449 }
450
451
452 pub struct #secondary_db_map_struct_name #generics {
456 #(
457 pub #field_names : DBMap #inner_types,
458 )*
459 }
460
461 impl <
462 #(
463 #generics_names: #generics_bounds_token,
464 )*
465 > #secondary_db_map_struct_name #generics {
466 pub fn open_tables_read_only(
468 primary_path: std::path::PathBuf,
469 with_secondary_path: Option<std::path::PathBuf>,
470 metric_conf: typed_store::rocks::MetricConf,
471 global_db_options_override: Option<typed_store::rocksdb::Options>,
472 ) -> Self {
473 let inner = match with_secondary_path {
474 Some(q) => #intermediate_db_map_struct_name::open_tables_impl(primary_path, Some(q), false, metric_conf, global_db_options_override, None, false),
475 None => {
476 let p: std::path::PathBuf = tempfile::tempdir()
477 .expect("Failed to open temporary directory")
478 .into_path();
479 #intermediate_db_map_struct_name::open_tables_impl(primary_path, Some(p), false, metric_conf, global_db_options_override, None, false)
480 }
481 };
482 Self {
483 #(
484 #field_names: inner.#field_names,
485 )*
486 }
487 }
488
489 fn cf_name_to_table_name(cf_name: &str) -> eyre::Result<&'static str> {
490 Ok(match cf_name {
491 #(
492 stringify!(#cf_names) => stringify!(#field_names),
493 )*
494 _ => eyre::bail!("No such cf name: {}", cf_name),
495 })
496 }
497
498 pub fn dump(&self, cf_name: &str, page_size: u16, page_number: usize) -> eyre::Result<std::collections::BTreeMap<String, String>> {
501 let table_name = Self::cf_name_to_table_name(cf_name)?;
502
503 Ok(match table_name {
504 #(
505 stringify!(#field_names) => {
506 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
507 typed_store::traits::Map::unbounded_iter(&self.#field_names)
508 .skip((page_number * (page_size) as usize))
509 .take(page_size as usize)
510 .map(|(k, v)| (format!("{:?}", k), format!("{:?}", v)))
511 .collect::<std::collections::BTreeMap<_, _>>()
512 }
513 )*
514
515 _ => eyre::bail!("No such table name: {}", table_name),
516 })
517 }
518
519 pub fn table_summary(&self, table_name: &str) -> eyre::Result<typed_store::traits::TableSummary> {
522 let mut count = 0;
523 let mut key_bytes = 0;
524 let mut value_bytes = 0;
525 match table_name {
526 #(
527 stringify!(#field_names) => {
528 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
529 self.#field_names.table_summary()
530 }
531 )*
532
533 _ => eyre::bail!("No such table name: {}", table_name),
534 }
535 }
536
537 pub fn count_keys(&self, table_name: &str) -> eyre::Result<usize> {
540 Ok(match table_name {
541 #(
542 stringify!(#field_names) => {
543 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
544 typed_store::traits::Map::unbounded_iter(&self.#field_names).count()
545 }
546 )*
547
548 _ => eyre::bail!("No such table name: {}", table_name),
549 })
550 }
551
552 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
553 vec![#(
554 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
555 )*].into_iter().collect()
556 }
557
558 pub fn try_catch_up_with_primary_all(&self) -> eyre::Result<()> {
561 #(
562 typed_store::traits::Map::try_catch_up_with_primary(&self.#field_names)?;
563 )*
564 Ok(())
565 }
566 }
567
568 impl <
569 #(
570 #generics_names: #generics_bounds_token,
571 )*
572 > TypedStoreDebug for #secondary_db_map_struct_name #generics {
573 fn dump_table(
574 &self,
575 table_name: String,
576 page_size: u16,
577 page_number: usize,
578 ) -> eyre::Result<std::collections::BTreeMap<String, String>> {
579 self.dump(table_name.as_str(), page_size, page_number)
580 }
581
582 fn primary_db_name(&self) -> String {
583 stringify!(#name).to_owned()
584 }
585
586 fn describe_all_tables(&self) -> std::collections::BTreeMap<String, (String, String)> {
587 Self::describe_tables()
588 }
589
590 fn count_table_keys(&self, table_name: String) -> eyre::Result<usize> {
591 self.count_keys(table_name.as_str())
592 }
593
594 fn table_summary(&self, table_name: String) -> eyre::Result<TableSummary> {
595 self.table_summary(table_name.as_str())
596 }
597
598
599 }
600
601 })
602}
603
604#[proc_macro_derive(SallyDB, attributes(default_options_override_fn))]
605pub fn derive_sallydb_general(input: TokenStream) -> TokenStream {
606 let input = parse_macro_input!(input as ItemStruct);
608 let name = &input.ident;
609 let generics = &input.generics;
610 let generics_names = extract_generics_names(generics);
611
612 let allowed_types_with_post_process_fn: BTreeMap<_, _> =
613 [("SallyColumn", "")].into_iter().collect();
614 let allowed_strs = allowed_types_with_post_process_fn
615 .keys()
616 .map(|s| s.to_string())
617 .collect();
618
619 let ExtractedStructInfo {
623 field_names,
624 inner_types,
625 derived_table_options,
626 simple_field_type_name_str,
627 ..
628 } = extract_struct_info(input.clone(), allowed_strs);
629
630 let (key_names, value_names): (Vec<_>, Vec<_>) = inner_types
631 .iter()
632 .map(|q| (q.args.first().unwrap(), q.args.last().unwrap()))
633 .unzip();
634
635 let post_process_fn_str = allowed_types_with_post_process_fn
637 .get(&simple_field_type_name_str.as_str())
638 .unwrap();
639 let post_process_fn: proc_macro2::TokenStream = post_process_fn_str.parse().unwrap();
640
641 let default_options_override_fn_names: Vec<proc_macro2::TokenStream> = derived_table_options
642 .iter()
643 .map(|q| {
644 let GeneralTableOptions::OverrideFunction(fn_name) = q;
645 fn_name.parse().unwrap()
646 })
647 .collect();
648
649 let generics_bounds =
650 "std::fmt::Debug + serde::Serialize + for<'de> serde::de::Deserialize<'de>";
651 let generics_bounds_token: proc_macro2::TokenStream = generics_bounds.parse().unwrap();
652
653 let config_struct_name_str = format!("{name}SallyConfigurator");
654 let sally_config_struct_name: proc_macro2::TokenStream =
655 config_struct_name_str.parse().unwrap();
656
657 let intermediate_db_map_struct_name_str = format!("{name}Primary");
658 let intermediate_db_map_struct_name: proc_macro2::TokenStream =
659 intermediate_db_map_struct_name_str.parse().unwrap();
660
661 let secondary_db_map_struct_name_str = format!("{name}ReadOnly");
662 let secondary_db_map_struct_name: proc_macro2::TokenStream =
663 secondary_db_map_struct_name_str.parse().unwrap();
664
665 TokenStream::from(quote! {
666
667 pub struct #sally_config_struct_name {
671 #(
672 pub #field_names : typed_store::sally::SallyColumnOptions,
673 )*
674 }
675
676 impl #sally_config_struct_name {
677 pub fn init() -> Self {
679 Self {
680 #(
681 #field_names : typed_store::sally::default_column_options(),
682 )*
683 }
684 }
685
686 pub fn build(&self) -> typed_store::sally::SallyDBConfigMap {
688 typed_store::sally::SallyDBConfigMap::new([
689 #(
690 (stringify!(#field_names).to_owned(), self.#field_names.clone()),
691 )*
692 ].into_iter().collect())
693 }
694 }
695
696
697 impl <
698 #(
699 #generics_names: #generics_bounds_token,
700 )*
701 > #name #generics {
702
703 pub fn configurator() -> #sally_config_struct_name {
704 #sally_config_struct_name::init()
705 }
706 }
707
708
709 struct #intermediate_db_map_struct_name #generics {
714 #(
715 pub #field_names : SallyColumn #inner_types,
716 )*
717 }
718
719
720 impl <
721 #(
722 #generics_names: #generics_bounds_token,
723 )*
724 > #intermediate_db_map_struct_name #generics {
725 pub fn init(db_options: typed_store::sally::SallyDBOptions) -> Self {
728 match db_options {
729 typed_store::sally::SallyDBOptions::TestDB => {
730 let (
731 #(
732 #field_names
733 ),*
734 ) = (#(
735 SallyColumn::TestDB((typed_store::test_db::TestDB::#inner_types::open(), typed_store::sally::SallyConfig::default()))
736 ),*);
737
738 Self {
739 #(
740 #field_names,
741 )*
742 }
743 },
744 typed_store::sally::SallyDBOptions::RocksDB((path, metric_conf, access_type, global_db_options_override, tables_db_options_override)) => {
745 let path = &path;
746 let (db, rwopt_cfs) = {
747 let opt_cfs = match tables_db_options_override {
748 None => [
749 #(
750 (stringify!(#field_names).to_owned(), #default_options_override_fn_names().clone()),
751 )*
752 ],
753 Some(o) => [
754 #(
755 (stringify!(#field_names).to_owned(), o.to_map().get(stringify!(#field_names)).unwrap().clone()),
756 )*
757 ]
758 };
759 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();
761 let opt_cfs: Vec<_> = opt_cfs.iter().map(|q| (q.0.as_str(), q.1.options.clone())).collect();
762 let db = match access_type {
763 RocksDBAccessType::Secondary(Some(p)) => typed_store::rocks::open_cf_opts_secondary(path, Some(&p), global_db_options_override, metric_conf, &opt_cfs),
764 _ => typed_store::rocks::open_cf_opts(path, global_db_options_override, metric_conf, &opt_cfs)
765 };
766 db.map(|d| (d, rwopt_cfs))
767 }.expect(&format!("Cannot open DB at {:?}", path));
768 let (
769 #(
770 #field_names
771 ),*
772 ) = (#(
773 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()))
774 ),*);
775
776 Self {
777 #(
778 #field_names,
779 )*
780 }
781 }
782 }
783 }
784 }
785
786
787 impl <
789 #(
790 #generics_names: #generics_bounds_token,
791 )*
792 > #name #generics {
793 #[expect(unused_parens)]
798 pub fn init(
799 db_options: typed_store::sally::SallyDBOptions
800 ) -> Self {
801 let inner = #intermediate_db_map_struct_name::init(db_options);
802 Self {
803 #(
804 #field_names: #post_process_fn(inner.#field_names),
805 )*
806 }
807 }
808
809 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
811 vec![#(
812 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
813 )*].into_iter().collect()
814 }
815
816 pub fn get_read_only_handle (
818 db_options: typed_store::sally::SallyReadOnlyDBOptions
819 ) -> #secondary_db_map_struct_name #generics {
820 #secondary_db_map_struct_name::init_read_only(db_options)
821 }
822 }
823
824 pub struct #secondary_db_map_struct_name #generics {
828 #(
829 pub #field_names : SallyColumn #inner_types,
830 )*
831 }
832
833 impl <
834 #(
835 #generics_names: #generics_bounds_token,
836 )*
837 > #secondary_db_map_struct_name #generics {
838 pub fn init_read_only(
840 db_options: typed_store::sally::SallyReadOnlyDBOptions,
841 ) -> Self {
842 match db_options {
843 typed_store::sally::SallyReadOnlyDBOptions::TestDB => {
844 let inner = #intermediate_db_map_struct_name::init(SallyDBOptions::TestDB);
845 Self {
846 #(
847 #field_names: inner.#field_names,
848 )*
849 }
850 },
851 typed_store::sally::SallyReadOnlyDBOptions::RocksDB(b) => {
852 let inner = match b.2 {
853 Some(q) => #intermediate_db_map_struct_name::init(SallyDBOptions::RocksDB((b.0, b.1, RocksDBAccessType::Secondary(Some(q)), b.3, None))),
854 None => {
855 let p: std::path::PathBuf = tempfile::tempdir()
856 .expect("Failed to open temporary directory")
857 .into_path();
858 #intermediate_db_map_struct_name::init(SallyDBOptions::RocksDB((b.0, b.1, RocksDBAccessType::Secondary(Some(p)), b.3, None)))
859 }
860 };
861 Self {
862 #(
863 #field_names: inner.#field_names,
864 )*
865 }
866 }
867 }
868 }
869
870 pub fn dump(&self, table_name: &str, page_size: u16,
873 page_number: usize) -> eyre::Result<std::collections::BTreeMap<String, String>> {
874 Ok(match table_name {
875 #(
876 stringify!(#field_names) => {
877 match &self.#field_names {
878 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
879 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
880 typed_store::traits::Map::unbounded_iter(db_map)
881 .skip((page_number * (page_size) as usize))
882 .take(page_size as usize)
883 .map(|(k, v)| (format!("{:?}", k), format!("{:?}", v)))
884 .collect::<std::collections::BTreeMap<_, _>>()
885 }
886 _ => unimplemented!(),
887 }
888 }
889 )*
890 _ => eyre::bail!("No such table name: {}", table_name),
891 })
892 }
893
894 pub fn table_summary(&self, table_name: &str) -> eyre::Result<typed_store::traits::TableSummary> {
895 let mut count = 0;
896 let mut key_bytes = 0;
897 let mut value_bytes = 0;
898 match table_name {
899 #(
900 stringify!(#field_names) => {
901 match &self.#field_names {
902 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
903 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
904 db_map.table_summary()
905 }
906 _ => unimplemented!(),
907 }
908 }
909 )*
910
911 _ => eyre::bail!("No such table name: {}", table_name),
912 }
913 }
914
915 pub fn count_keys(&self, table_name: &str) -> eyre::Result<usize> {
918 Ok(match table_name {
919 #(
920 stringify!(#field_names) => {
921 match &self.#field_names {
922 SallyColumn::RocksDB((db_map, typed_store::sally::SallyConfig { mode: typed_store::sally::SallyRunMode::FallbackToDB })) => {
923 typed_store::traits::Map::try_catch_up_with_primary(db_map)?;
924 typed_store::traits::Map::unbounded_iter(db_map).count()
925 }
926 _ => unimplemented!(),
927 }
928 }
929 )*
930
931 _ => eyre::bail!("No such table name: {}", table_name),
932 })
933 }
934
935 pub fn describe_tables() -> std::collections::BTreeMap<String, (String, String)> {
936 vec![#(
937 (stringify!(#field_names).to_owned(), (stringify!(#key_names).to_owned(), stringify!(#value_names).to_owned())),
938 )*].into_iter().collect()
939 }
940 }
941
942
943 impl <
944 #(
945 #generics_names: #generics_bounds_token,
946 )*
947 > TypedStoreDebug for #secondary_db_map_struct_name #generics {
948 fn dump_table(
949 &self,
950 table_name: String,
951 page_size: u16,
952 page_number: usize,
953 ) -> eyre::Result<std::collections::BTreeMap<String, String>> {
954 self.dump(table_name.as_str(), page_size, page_number)
955 }
956
957 fn primary_db_name(&self) -> String {
958 stringify!(#name).to_owned()
959 }
960
961 fn describe_all_tables(&self) -> std::collections::BTreeMap<String, (String, String)> {
962 Self::describe_tables()
963 }
964
965 fn count_table_keys(&self, table_name: String) -> eyre::Result<usize> {
966 self.count_keys(table_name.as_str())
967 }
968 fn table_summary(&self, table_name: String) -> eyre::Result<TableSummary> {
969 self.table_summary(table_name.as_str())
970 }
971
972 }
973
974 })
975}