Skip to main content

tuwunel_admin/query/raw/
mod.rs

1mod clear;
2mod compact;
3mod count;
4mod del;
5mod get;
6mod iter;
7mod keys;
8mod keys_sizes;
9mod keys_total;
10mod maps;
11mod sequence;
12mod vals_sizes;
13mod vals_total;
14
15use std::{fmt::Write, sync::Arc};
16
17use clap::Subcommand;
18use tuwunel_core::{Result, err, itertools::Itertools, utils::math::Expected};
19use tuwunel_database::Map;
20use tuwunel_service::Services;
21
22use crate::admin_command_dispatch;
23
24#[admin_command_dispatch(handler_prefix = "raw")]
25#[derive(Debug, Subcommand)]
26/// Query tables from database
27pub(crate) enum RawCommand {
28	/// - List database maps
29	Maps,
30
31	/// - Current rocksdb sequence number.
32	Sequence,
33
34	/// - Raw database query
35	Get {
36		/// Map name
37		map: String,
38
39		/// Key
40		key: String,
41
42		/// Encode as base64
43		#[arg(long, short)]
44		base64: bool,
45	},
46
47	/// - Raw database keys iteration
48	Keys {
49		/// Map name
50		map: String,
51
52		/// Key prefix
53		prefix: Option<String>,
54
55		/// Limit
56		#[arg(short, long)]
57		limit: Option<usize>,
58
59		/// Lower bound
60		#[arg(short, long)]
61		from: Option<String>,
62
63		/// Reverse iteration order
64		#[arg(short, long, default_value("false"))]
65		backwards: bool,
66	},
67
68	/// - Raw database items iteration
69	Iter {
70		/// Map name
71		map: String,
72
73		/// Key prefix
74		prefix: Option<String>,
75
76		/// Limit
77		#[arg(short, long)]
78		limit: Option<usize>,
79
80		/// Lower bound
81		#[arg(short, long)]
82		from: Option<String>,
83
84		/// Reverse iteration order
85		#[arg(short, long, default_value("false"))]
86		backwards: bool,
87	},
88
89	/// - Raw database key size breakdown
90	KeysSizes {
91		/// Map name
92		map: Option<String>,
93
94		/// Key prefix
95		prefix: Option<String>,
96	},
97
98	/// - Raw database keys total bytes
99	KeysTotal {
100		/// Map name
101		map: Option<String>,
102
103		/// Key prefix
104		prefix: Option<String>,
105	},
106
107	/// - Raw database values size breakdown
108	ValsSizes {
109		/// Map name
110		map: Option<String>,
111
112		/// Key prefix
113		prefix: Option<String>,
114	},
115
116	/// - Raw database values total bytes
117	ValsTotal {
118		/// Map name
119		map: Option<String>,
120
121		/// Key prefix
122		prefix: Option<String>,
123	},
124
125	/// - Raw database record count
126	Count {
127		/// Map name
128		map: Option<String>,
129
130		/// Key prefix
131		prefix: Option<String>,
132	},
133
134	/// - Raw database delete (for string keys) DANGER!!!
135	Del {
136		/// Map name
137		map: String,
138
139		/// Key
140		key: String,
141	},
142
143	/// - Clear database table DANGER!!!
144	Clear {
145		/// Map name
146		map: String,
147
148		/// Confirm
149		#[arg(long)]
150		confirm: bool,
151	},
152
153	/// - Compact database DANGER!!!
154	Compact {
155		#[arg(short, long, alias("column"))]
156		maps: Option<Vec<String>>,
157
158		#[arg(long)]
159		start: Option<String>,
160
161		#[arg(long)]
162		stop: Option<String>,
163
164		#[arg(long)]
165		from: Option<usize>,
166
167		#[arg(long)]
168		into: Option<usize>,
169
170		/// There is one compaction job per column; then this controls how many
171		/// columns are compacted in parallel. If zero, one compaction job is
172		/// still run at a time here, but in exclusive-mode blocking any other
173		/// automatic compaction jobs until complete.
174		#[arg(long)]
175		parallelism: Option<usize>,
176
177		#[arg(long, default_value("false"))]
178		exhaustive: bool,
179	},
180}
181
182fn with_map_or(map: Option<&str>, services: &Services) -> Result<Vec<Arc<Map>>> {
183	with_maps_or(
184		map.map(|map| [map])
185			.as_ref()
186			.map(<[&str; 1]>::as_slice),
187		services,
188	)
189}
190
191fn with_maps_or<S: AsRef<str>>(maps: Option<&[S]>, services: &Services) -> Result<Vec<Arc<Map>>> {
192	Ok(if let Some(maps) = maps {
193		maps.iter()
194			.map(|map| {
195				let map = map.as_ref();
196				services
197					.db
198					.get(map)
199					.cloned()
200					.map_err(|_| err!("map {map} not found"))
201			})
202			.try_collect()?
203	} else {
204		services.db.iter().map(|x| x.1.clone()).collect()
205	})
206}
207
208#[expect(clippy::as_conversions)]
209fn encode(data: &[u8]) -> String {
210	let mut res = String::with_capacity(data.len().expected_mul(4));
211
212	for byte in data {
213		if *byte < 0x20 || *byte > 0x7E {
214			_ = write!(res, "\\x{byte:02x}");
215		} else {
216			res.push(*byte as char);
217		}
218	}
219
220	res
221}