├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── main.rs └── sledimporter.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "anyhow" 7 | version = "1.0.97" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" 10 | 11 | [[package]] 12 | name = "argh" 13 | version = "0.1.13" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "34ff18325c8a36b82f992e533ece1ec9f9a9db446bd1c14d4f936bac88fcd240" 16 | dependencies = [ 17 | "argh_derive", 18 | "argh_shared", 19 | "rust-fuzzy-search", 20 | ] 21 | 22 | [[package]] 23 | name = "argh_derive" 24 | version = "0.1.13" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "adb7b2b83a50d329d5d8ccc620f5c7064028828538bdf5646acd60dc1f767803" 27 | dependencies = [ 28 | "argh_shared", 29 | "proc-macro2", 30 | "quote", 31 | "syn", 32 | ] 33 | 34 | [[package]] 35 | name = "argh_shared" 36 | version = "0.1.13" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "a464143cc82dedcdc3928737445362466b7674b5db4e2eb8e869846d6d84f4f6" 39 | dependencies = [ 40 | "serde", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.4.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 48 | 49 | [[package]] 50 | name = "bitflags" 51 | version = "1.3.2" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 54 | 55 | [[package]] 56 | name = "bitflags" 57 | version = "2.9.0" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 60 | 61 | [[package]] 62 | name = "byteorder" 63 | version = "1.5.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 66 | 67 | [[package]] 68 | name = "cc" 69 | version = "1.2.17" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" 72 | dependencies = [ 73 | "jobserver", 74 | "libc", 75 | "shlex", 76 | ] 77 | 78 | [[package]] 79 | name = "cfg-if" 80 | version = "1.0.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 83 | 84 | [[package]] 85 | name = "crc32fast" 86 | version = "1.4.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 89 | dependencies = [ 90 | "cfg-if", 91 | ] 92 | 93 | [[package]] 94 | name = "crossbeam-epoch" 95 | version = "0.9.18" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 98 | dependencies = [ 99 | "crossbeam-utils", 100 | ] 101 | 102 | [[package]] 103 | name = "crossbeam-utils" 104 | version = "0.8.21" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 107 | 108 | [[package]] 109 | name = "fs2" 110 | version = "0.4.3" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 113 | dependencies = [ 114 | "libc", 115 | "winapi", 116 | ] 117 | 118 | [[package]] 119 | name = "fxhash" 120 | version = "0.2.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 123 | dependencies = [ 124 | "byteorder", 125 | ] 126 | 127 | [[package]] 128 | name = "getrandom" 129 | version = "0.3.2" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 132 | dependencies = [ 133 | "cfg-if", 134 | "libc", 135 | "r-efi", 136 | "wasi", 137 | ] 138 | 139 | [[package]] 140 | name = "hex" 141 | version = "0.4.3" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 144 | 145 | [[package]] 146 | name = "instant" 147 | version = "0.1.13" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 150 | dependencies = [ 151 | "cfg-if", 152 | ] 153 | 154 | [[package]] 155 | name = "itoa" 156 | version = "1.0.15" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 159 | 160 | [[package]] 161 | name = "jobserver" 162 | version = "0.1.33" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 165 | dependencies = [ 166 | "getrandom", 167 | "libc", 168 | ] 169 | 170 | [[package]] 171 | name = "libc" 172 | version = "0.2.171" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" 175 | 176 | [[package]] 177 | name = "lock_api" 178 | version = "0.4.12" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 181 | dependencies = [ 182 | "autocfg", 183 | "scopeguard", 184 | ] 185 | 186 | [[package]] 187 | name = "log" 188 | version = "0.4.26" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" 191 | 192 | [[package]] 193 | name = "memchr" 194 | version = "2.7.4" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 197 | 198 | [[package]] 199 | name = "parking_lot" 200 | version = "0.11.2" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 203 | dependencies = [ 204 | "instant", 205 | "lock_api", 206 | "parking_lot_core", 207 | ] 208 | 209 | [[package]] 210 | name = "parking_lot_core" 211 | version = "0.8.6" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" 214 | dependencies = [ 215 | "cfg-if", 216 | "instant", 217 | "libc", 218 | "redox_syscall", 219 | "smallvec", 220 | "winapi", 221 | ] 222 | 223 | [[package]] 224 | name = "proc-macro2" 225 | version = "1.0.94" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" 228 | dependencies = [ 229 | "unicode-ident", 230 | ] 231 | 232 | [[package]] 233 | name = "quote" 234 | version = "1.0.40" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 237 | dependencies = [ 238 | "proc-macro2", 239 | ] 240 | 241 | [[package]] 242 | name = "r-efi" 243 | version = "5.2.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 246 | 247 | [[package]] 248 | name = "redox_syscall" 249 | version = "0.2.16" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 252 | dependencies = [ 253 | "bitflags 1.3.2", 254 | ] 255 | 256 | [[package]] 257 | name = "rust-fuzzy-search" 258 | version = "0.1.1" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "a157657054ffe556d8858504af8a672a054a6e0bd9e8ee531059100c0fa11bb2" 261 | 262 | [[package]] 263 | name = "ryu" 264 | version = "1.0.20" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 267 | 268 | [[package]] 269 | name = "scopeguard" 270 | version = "1.2.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 273 | 274 | [[package]] 275 | name = "serde" 276 | version = "1.0.219" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 279 | dependencies = [ 280 | "serde_derive", 281 | ] 282 | 283 | [[package]] 284 | name = "serde_derive" 285 | version = "1.0.219" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "serde_json" 296 | version = "1.0.140" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 299 | dependencies = [ 300 | "itoa", 301 | "memchr", 302 | "ryu", 303 | "serde", 304 | ] 305 | 306 | [[package]] 307 | name = "shlex" 308 | version = "1.3.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 311 | 312 | [[package]] 313 | name = "sled" 314 | version = "0.34.7" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935" 317 | dependencies = [ 318 | "crc32fast", 319 | "crossbeam-epoch", 320 | "crossbeam-utils", 321 | "fs2", 322 | "fxhash", 323 | "libc", 324 | "log", 325 | "parking_lot", 326 | "zstd", 327 | ] 328 | 329 | [[package]] 330 | name = "sledtool" 331 | version = "0.1.3" 332 | dependencies = [ 333 | "anyhow", 334 | "argh", 335 | "hex", 336 | "serde", 337 | "serde_json", 338 | "sled", 339 | ] 340 | 341 | [[package]] 342 | name = "smallvec" 343 | version = "1.14.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 346 | 347 | [[package]] 348 | name = "syn" 349 | version = "2.0.100" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 352 | dependencies = [ 353 | "proc-macro2", 354 | "quote", 355 | "unicode-ident", 356 | ] 357 | 358 | [[package]] 359 | name = "unicode-ident" 360 | version = "1.0.18" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 363 | 364 | [[package]] 365 | name = "wasi" 366 | version = "0.14.2+wasi-0.2.4" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 369 | dependencies = [ 370 | "wit-bindgen-rt", 371 | ] 372 | 373 | [[package]] 374 | name = "winapi" 375 | version = "0.3.9" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 378 | dependencies = [ 379 | "winapi-i686-pc-windows-gnu", 380 | "winapi-x86_64-pc-windows-gnu", 381 | ] 382 | 383 | [[package]] 384 | name = "winapi-i686-pc-windows-gnu" 385 | version = "0.4.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 388 | 389 | [[package]] 390 | name = "winapi-x86_64-pc-windows-gnu" 391 | version = "0.4.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 394 | 395 | [[package]] 396 | name = "wit-bindgen-rt" 397 | version = "0.39.0" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 400 | dependencies = [ 401 | "bitflags 2.9.0", 402 | ] 403 | 404 | [[package]] 405 | name = "zstd" 406 | version = "0.9.2+zstd.1.5.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" 409 | dependencies = [ 410 | "zstd-safe", 411 | ] 412 | 413 | [[package]] 414 | name = "zstd-safe" 415 | version = "4.1.3+zstd.1.5.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" 418 | dependencies = [ 419 | "libc", 420 | "zstd-sys", 421 | ] 422 | 423 | [[package]] 424 | name = "zstd-sys" 425 | version = "1.6.2+zstd.1.5.1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" 428 | dependencies = [ 429 | "cc", 430 | "libc", 431 | ] 432 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sledtool" 3 | version = "0.1.3" 4 | authors = ["Vitaly _Vi Shukela "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Command-line tool to query, modify, import and export Sled key-value databases." 8 | keywords = ["sled", "cli"] 9 | categories = ["database", "command-line-utilities"] 10 | repository = "https://github.com/vi/sledtool" 11 | 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [features] 16 | default = ["compression"] 17 | compression = ["sled/compression"] 18 | 19 | [dependencies] 20 | anyhow = "1.0.37" 21 | argh = "0.1.4" 22 | hex = "0.4.2" 23 | serde = "1.0.118" 24 | serde_json = "1.0.61" 25 | sled = {version="0.34.6",features=[]} 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vitaly Shukela 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sledtool 2 | CLI tool to work with [Sled](https://github.com/spacejam/sled) key-value databases. 3 | 4 | It may be used for making migrations between Sled versions or different compression settings. 5 | 6 | ``` 7 | Usage: sledtool [-c] [-C ] [-N] [-F] [-L] [] 8 | 9 | CLI tool to work with Sled databases 10 | 11 | Options: 12 | -c, --compress set `use_compression` in `sled::Config` to true 13 | -C, --compression-factor 14 | set `compression_factor` in `sled::Config` to specified 15 | value 16 | -N, --create-new set `create_new` in `sled::Config` to true, making it a 17 | failure to open existing database 18 | -F, --throughput-mode 19 | set `mode` in `sled::Config` to HighThroughput 20 | -L, --low-space set `mode` in `sled::Config` to LowSpace 21 | --help display usage information 22 | 23 | Commands: 24 | export Outout entire content of the database to JSON with 25 | hex-encoded buffers 26 | import Import entire content of the database from JSON with 27 | hex-encoded buffers 28 | get Get value of specific key (or first/last) from the database 29 | set Set value of specific key in the database 30 | rm Remove specific key or range of keys 31 | nop No operation, just open and close the database 32 | idle Open Sled database, then wait indefinitely 33 | treenames List tree names 34 | genid Generate monotonic ID 35 | checksum Call `checksum` and output the result 36 | sizeondisk Call `size_on_disk` and output the result 37 | 38 | ``` 39 | 40 | ``` 41 | $ sledtool get --help 42 | Usage: sledtool get [-t ] [-r] [-R] [-T] [--gt] [--lt] [-K] [-q] [-f] [-l] 43 | 44 | Get value of specific key (or first/last) from the database 45 | 46 | Options: 47 | -t, --tree tree to use 48 | -r, --raw-value inhibit hex-encoding the value 49 | -R, --raw-key inhibit hex-decoding or hex-encoding the key 50 | -T, --raw-tree-name 51 | inhibit hex-decoding the tree name 52 | --gt use `get_gt` instead of `get` 53 | --lt use `get_lt` instead of `get` 54 | -K, --print-key print key in addition to the value, with `=` sign in between 55 | -q, --quiet do not print `Not found` to console, just set exit code 1 56 | -f, --first ignore key, get first record instead 57 | -l, --last ignore key, get last record instead 58 | --help display usage information 59 | ``` 60 | 61 | ``` 62 | $ sledtool /path/to/db export 63 | { 64 | "5f5f736c65645f5f64656661756c74":{ 65 | "71717132": "71776572747961736466" 66 | } 67 | } 68 | ``` 69 | 70 | # See also 71 | 72 | * [sledviewer](https://gitlab.com/timokoesters/sledviewer) - GUI tool 73 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{io::Write, path::PathBuf}; 2 | 3 | /// CLI tool to work with Sled databases 4 | #[derive(argh::FromArgs)] 5 | struct Opts { 6 | #[argh(positional)] 7 | dbpath: PathBuf, 8 | 9 | /// set `use_compression` in `sled::Config` to true 10 | #[argh(switch,short='c')] 11 | compress: bool, 12 | 13 | /// set `compression_factor` in `sled::Config` to specified value 14 | #[argh(option,short='C')] 15 | compression_factor: Option, 16 | 17 | /// set `cache_capacity` in `sled::Config` to the specified value 18 | #[argh(option,short='P')] 19 | cache_capacity: Option, 20 | 21 | /// set `create_new` in `sled::Config` to true, making it a failure to open existing database 22 | #[argh(switch,short='N')] 23 | create_new: bool, 24 | 25 | /// set `mode` in `sled::Config` to HighThroughput 26 | #[argh(switch,short='F')] 27 | throughput_mode: bool, 28 | 29 | /// set `mode` in `sled::Config` to LowSpace 30 | #[argh(switch,short='L')] 31 | low_space: bool, 32 | 33 | #[argh(subcommand)] 34 | cmd: Cmd, 35 | } 36 | 37 | /// Outout entire content of the database to JSON with hex-encoded buffers 38 | #[derive(argh::FromArgs)] 39 | #[argh(subcommand, name = "export")] 40 | struct Export {} 41 | 42 | /// Import entire content of the database from JSON with hex-encoded buffers 43 | #[derive(argh::FromArgs)] 44 | #[argh(subcommand, name = "import")] 45 | struct Import {} 46 | 47 | /// Get value of specific key (or first/last) from the database 48 | #[derive(argh::FromArgs)] 49 | #[argh(subcommand, name = "get")] 50 | struct Get { 51 | #[argh(positional)] 52 | key: String, 53 | 54 | /// tree to use 55 | #[argh(option, short = 't')] 56 | tree: Option, 57 | 58 | /// inhibit hex-encoding the value 59 | #[argh(switch, short = 'r')] 60 | raw_value: bool, 61 | 62 | /// inhibit hex-decoding or hex-encoding the key 63 | #[argh(switch, short = 'R')] 64 | raw_key: bool, 65 | 66 | /// inhibit hex-decoding the tree name 67 | #[argh(switch, short = 'T')] 68 | raw_tree_name: bool, 69 | 70 | /// use `get_gt` instead of `get` 71 | #[argh(switch)] 72 | gt: bool, 73 | 74 | /// use `get_lt` instead of `get` 75 | #[argh(switch)] 76 | lt: bool, 77 | 78 | /// print key in addition to the value, with `=` sign in between 79 | #[argh(switch, short = 'K')] 80 | print_key: bool, 81 | 82 | /// do not print `Not found` to console, just set exit code 1 83 | #[argh(switch, short = 'q')] 84 | quiet: bool, 85 | 86 | /// ignore key, get first record instead 87 | #[argh(switch, short = 'f')] 88 | first: bool, 89 | 90 | /// ignore key, get last record instead 91 | #[argh(switch, short = 'l')] 92 | last: bool, 93 | } 94 | /// Set value of specific key in the database 95 | #[derive(argh::FromArgs)] 96 | #[argh(subcommand, name = "set")] 97 | struct Set { 98 | #[argh(positional)] 99 | key: String, 100 | 101 | #[argh(positional)] 102 | value: String, 103 | 104 | /// tree to use 105 | #[argh(option, short = 't')] 106 | tree: Option, 107 | 108 | /// inhibit hex-decoding or hex-encoding the value 109 | #[argh(switch, short = 'r')] 110 | raw_value: bool, 111 | 112 | /// inhibit hex-decoding the key 113 | #[argh(switch, short = 'R')] 114 | raw_key: bool, 115 | 116 | /// inhibit hex-decoding the tree name 117 | #[argh(switch, short = 'T')] 118 | raw_tree_name: bool, 119 | 120 | /// do not the old value 121 | #[argh(switch, short = 'q')] 122 | quiet: bool, 123 | } 124 | 125 | /// Remove specific key or range of keys 126 | #[derive(argh::FromArgs)] 127 | #[argh(subcommand, name = "rm")] 128 | struct Remove { 129 | #[argh(positional)] 130 | key: String, 131 | 132 | /// remove range of keys (ignoring missing) up to this one 133 | #[argh(option, short = 'U')] 134 | end_key: Option, 135 | 136 | /// tree to use 137 | #[argh(option, short = 't')] 138 | tree: Option, 139 | 140 | /// inhibit hex-decoding the key 141 | #[argh(switch, short = 'R')] 142 | raw_key: bool, 143 | 144 | /// inhibit hex-decoding the tree name 145 | #[argh(switch, short = 'T')] 146 | raw_tree_name: bool, 147 | 148 | /// do not print `Not found` to console, just set exit code 1 149 | #[argh(switch, short = 'q')] 150 | quiet: bool, 151 | 152 | /// do not remove `end_key` entry itself, only up to it. 153 | #[argh(switch, short = 'r')] 154 | right_exclusive: bool, 155 | 156 | } 157 | 158 | /// Open Sled database, then wait indefinitely 159 | #[derive(argh::FromArgs)] 160 | #[argh(subcommand, name = "idle")] 161 | struct Idle {} 162 | 163 | /// List tree names 164 | #[derive(argh::FromArgs)] 165 | #[argh(subcommand, name = "treenames")] 166 | struct TreeNames { 167 | /// inhibit hex-decoding the tree names 168 | #[argh(switch, short = 'T')] 169 | raw_tree_names: bool, 170 | } 171 | /// Generate monotonic ID 172 | #[derive(argh::FromArgs)] 173 | #[argh(subcommand, name = "genid")] 174 | struct GenerateId { 175 | } 176 | /// No operation, just open and close the database 177 | #[derive(argh::FromArgs)] 178 | #[argh(subcommand, name = "nop")] 179 | struct Noop { 180 | /// wait to some stdin input before exiting from the program 181 | #[argh(switch, short = 'w')] 182 | wait: bool, 183 | } 184 | 185 | /// Call `checksum` and output the result 186 | #[derive(argh::FromArgs)] 187 | #[argh(subcommand, name = "checksum")] 188 | struct Checksum { 189 | } 190 | 191 | /// Call `size_on_disk` and output the result 192 | #[derive(argh::FromArgs)] 193 | #[argh(subcommand, name = "sizeondisk")] 194 | struct SizeOnDisk { 195 | } 196 | 197 | #[derive(argh::FromArgs)] 198 | #[argh(subcommand)] 199 | enum Cmd { 200 | Export(Export), 201 | Import(Import), 202 | Get(Get), 203 | Set(Set), 204 | Remove(Remove), 205 | Noop(Noop), 206 | Idle(Idle), 207 | TreeNames(TreeNames), 208 | GenerateId(GenerateId), 209 | Checksum(Checksum), 210 | SizeOnDisk(SizeOnDisk), 211 | } 212 | 213 | pub mod sledimporter; 214 | 215 | fn main() -> anyhow::Result<()> { 216 | let opts: Opts = argh::from_env(); 217 | 218 | let mut config = sled::Config::default().path(opts.dbpath); 219 | 220 | if opts.compress { 221 | config = config.use_compression(true); 222 | } 223 | if let Some(x) = opts.compression_factor { 224 | config = config.compression_factor(x); 225 | } 226 | if opts.create_new { 227 | config = config.create_new(true); 228 | } 229 | if opts.low_space { 230 | config = config.mode(sled::Mode::LowSpace); 231 | } 232 | if opts.throughput_mode { 233 | config = config.mode(sled::Mode::HighThroughput); 234 | } 235 | if let Some(x) = opts.cache_capacity { 236 | config = config.cache_capacity(x); 237 | } 238 | 239 | let db: sled::Db = config.open()?; 240 | 241 | match opts.cmd { 242 | Cmd::Export(Export {}) => { 243 | let so = std::io::stdout(); 244 | let so = so.lock(); 245 | let mut so = std::io::BufWriter::with_capacity(8192, so); 246 | 247 | writeln!(so, "{{")?; 248 | for (tn, tree_name) in db.tree_names().into_iter().enumerate() { 249 | if tn > 0 { 250 | write!(so, ",")?; 251 | } else { 252 | write!(so, " ")?; 253 | } 254 | writeln!(so, r#""{}":{{"#, hex::encode(&tree_name))?; 255 | let tree = db.open_tree(&tree_name)?; 256 | for (vn, x) in tree.into_iter().enumerate() { 257 | if vn > 0 { 258 | write!(so, " ,")?; 259 | } else { 260 | write!(so, " ")?; 261 | } 262 | let (k, v) = x?; 263 | writeln!(so, r#""{}": "{}""#, hex::encode(k), hex::encode(v))?; 264 | } 265 | writeln!(so, " }}")?; 266 | } 267 | writeln!(so, "}}")?; 268 | } 269 | Cmd::Import(Import {}) => { 270 | let si = std::io::stdin(); 271 | let si = si.lock(); 272 | let si = std::io::BufReader::with_capacity(8192, si); 273 | use serde::de::DeserializeSeed; 274 | let () = sledimporter::DbDeserializer(&db) 275 | .deserialize(&mut serde_json::Deserializer::from_reader(si))?; 276 | } 277 | Cmd::Get(Get { 278 | key, 279 | tree, 280 | raw_value, 281 | raw_key, 282 | raw_tree_name, 283 | gt, 284 | lt, 285 | print_key, 286 | quiet, 287 | first, 288 | last, 289 | }) => { 290 | if lt && gt { 291 | anyhow::bail!("--gt and --lt options are specified simultaneously"); 292 | } 293 | if first && last && !quiet { 294 | eprintln!("Warning: with both --first and --last options active it would only succeed with exactly one record in the tree."); 295 | } 296 | if (first || last) && ! key.is_empty() && !quiet { 297 | eprintln!("Specifying non-empty (`\"\"`) key with --first or --last asserts it is indeed that key and fails otherwise."); 298 | } 299 | let mut t: &sled::Tree = &db; 300 | let tree_buf; 301 | if let Some(tree_name) = tree { 302 | let tn = if raw_tree_name { 303 | tree_name.as_bytes().to_vec() 304 | } else { 305 | hex::decode(tree_name)? 306 | }; 307 | tree_buf = db.open_tree(tn)?; 308 | t = &tree_buf; 309 | } 310 | 311 | let mut k = if raw_key { 312 | key.as_bytes().to_vec() 313 | } else { 314 | hex::decode(key)? 315 | }; 316 | 317 | let v: Option<_>; 318 | match (first, last) { 319 | (true, true) => { 320 | let x1 = t.first()?; 321 | let x2 = t.last()?; 322 | v = if let (Some(x1), Some(x2)) = (x1,x2) { 323 | let kd = x1.0.to_vec(); 324 | if k.is_empty() { 325 | if x1.0 == x2.0 { 326 | k = kd; 327 | Some(x1.1) 328 | } else { 329 | None 330 | } 331 | } else if kd == k && x1.0 == x2.0 { 332 | Some(x1.1) 333 | } else { 334 | None 335 | } 336 | } else { 337 | // empty tree 338 | None 339 | }; 340 | } 341 | (true, false) | (false, true) => { 342 | let x = if first { 343 | t.first()? 344 | } else { 345 | t.last()? 346 | }; 347 | v = if let Some(x) = x { 348 | let kd = x.0.to_vec(); 349 | if k.is_empty() { 350 | k = kd; 351 | Some(x.1) 352 | } else if k == kd { 353 | Some(x.1) 354 | } else { 355 | // found, but key not matches the one specified by user 356 | None 357 | } 358 | } else { 359 | // not found, empty tree 360 | None 361 | }; 362 | } 363 | (false, false) => { 364 | match (lt, gt) { 365 | (false, false) => v = t.get(&k)?, 366 | (true, false) => { 367 | v = t.get_lt(&k)?.map(|(ke, va)| { 368 | k = ke.to_vec(); 369 | va 370 | }) 371 | } 372 | (false, true) => { 373 | v = t.get_gt(&k)?.map(|(ke, va)| { 374 | k = ke.to_vec(); 375 | va 376 | }) 377 | } 378 | (true, true) => unreachable!(), 379 | } 380 | } 381 | }; 382 | 383 | if let Some(v) = v { 384 | if print_key { 385 | if raw_key { 386 | print!("{}=", String::from_utf8_lossy(&k)); 387 | } else { 388 | print!("{}=", hex::encode(k)); 389 | } 390 | } 391 | if raw_value { 392 | println!("{}", String::from_utf8_lossy(&v)); 393 | } else { 394 | println!("{}", hex::encode(v)); 395 | } 396 | } else { 397 | if !quiet { 398 | eprintln!("Not found"); 399 | } 400 | std::process::exit(1); 401 | } 402 | } 403 | Cmd::Set(Set { 404 | key, 405 | value, 406 | tree, 407 | raw_value, 408 | raw_key, 409 | raw_tree_name, 410 | quiet, 411 | }) => { 412 | let mut t: &sled::Tree = &db; 413 | let tree_buf; 414 | if let Some(tree_name) = tree { 415 | let tn = if raw_tree_name { 416 | tree_name.as_bytes().to_vec() 417 | } else { 418 | hex::decode(tree_name)? 419 | }; 420 | tree_buf = db.open_tree(tn)?; 421 | t = &tree_buf; 422 | } 423 | 424 | let k = if raw_key { 425 | key.as_bytes().to_vec() 426 | } else { 427 | hex::decode(key)? 428 | }; 429 | 430 | let v = if raw_value { 431 | value.as_bytes().to_vec() 432 | } else { 433 | hex::decode(value)? 434 | }; 435 | 436 | let ov = t.insert(k, v)?; 437 | 438 | if let Some(ov) = ov { 439 | if !quiet { 440 | if raw_value { 441 | println!("{}", String::from_utf8_lossy(&ov)); 442 | } else { 443 | println!("{}", hex::encode(ov)); 444 | } 445 | } 446 | } 447 | } 448 | Cmd::Remove(Remove { key, end_key, tree, raw_key, raw_tree_name , quiet, right_exclusive}) => { 449 | let mut t: &sled::Tree = &db; 450 | let tree_buf; 451 | if let Some(tree_name) = tree { 452 | let tn = if raw_tree_name { 453 | tree_name.as_bytes().to_vec() 454 | } else { 455 | hex::decode(tree_name)? 456 | }; 457 | tree_buf = db.open_tree(tn)?; 458 | t = &tree_buf; 459 | } 460 | 461 | let k = if raw_key { 462 | key.as_bytes().to_vec() 463 | } else { 464 | hex::decode(key)? 465 | }; 466 | 467 | let endkey = match end_key { 468 | None => None, 469 | Some(ek) => { 470 | Some(if raw_key { 471 | ek.as_bytes().to_vec() 472 | } else { 473 | hex::decode(ek)? 474 | }) 475 | } 476 | }; 477 | 478 | if let Some(ek) = endkey { 479 | let iter = if right_exclusive { 480 | t.range(k..ek) 481 | } else { 482 | t.range(k..=ek) 483 | }; 484 | let mut ctr = 0usize; 485 | for x in iter { 486 | let x = x?; 487 | if t.remove(x.0)?.is_some() { 488 | ctr += 1; 489 | } 490 | } 491 | if !quiet { 492 | println!("{} entries removed", ctr); 493 | } 494 | } else if t.remove(k)?.is_some() { 495 | // OK 496 | } else { 497 | if !quiet { 498 | eprintln!("Not found"); 499 | } 500 | std::process::exit(1); 501 | } 502 | 503 | } 504 | Cmd::Idle(Idle {}) => loop { 505 | std::thread::sleep(std::time::Duration::from_secs(3600)); 506 | }, 507 | Cmd::Noop(Noop {wait}) => { 508 | if wait { 509 | let _ = std::io::stdin().read_line(&mut String::new()); 510 | } 511 | } 512 | Cmd::TreeNames(TreeNames { raw_tree_names }) => { 513 | for tree_name in db.tree_names() { 514 | if raw_tree_names { 515 | println!("{}", String::from_utf8_lossy(&tree_name)); 516 | } else { 517 | println!("{}", hex::encode(tree_name)); 518 | } 519 | } 520 | } 521 | Cmd::GenerateId(GenerateId{}) => { 522 | println!("{}", db.generate_id()?); 523 | } 524 | Cmd::Checksum(Checksum{}) => { 525 | println!("{}", db.checksum()?); 526 | } 527 | Cmd::SizeOnDisk(SizeOnDisk{}) => { 528 | println!("{}", db.size_on_disk()?); 529 | } 530 | } 531 | 532 | Ok(()) 533 | } 534 | -------------------------------------------------------------------------------- /src/sledimporter.rs: -------------------------------------------------------------------------------- 1 | use sled::{Batch, Db, Tree}; 2 | 3 | use serde::de::Error as DeError; 4 | use serde::de::Unexpected; 5 | use serde::de::{DeserializeSeed, Visitor}; 6 | 7 | const MAX_BATCH_SIZE: usize = 128; 8 | 9 | pub struct DbDeserializer<'a>(pub &'a Db); 10 | pub struct TreeDeserializer<'a>(pub &'a Tree); 11 | 12 | impl<'de, 'a> DeserializeSeed<'de> for DbDeserializer<'a> { 13 | type Value = (); 14 | 15 | fn deserialize(self, deserializer: D) -> Result 16 | where 17 | D: serde::Deserializer<'de>, 18 | { 19 | deserializer.deserialize_map(self) 20 | } 21 | } 22 | 23 | impl<'de, 'a> Visitor<'de> for DbDeserializer<'a> { 24 | type Value = (); 25 | 26 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 27 | write!(formatter, "expecting a map") 28 | } 29 | 30 | fn visit_map(self, mut map: A) -> Result 31 | where 32 | A: serde::de::MapAccess<'de>, 33 | { 34 | while let Some(tree_name) = map.next_key()? { 35 | let tree_name: String = tree_name; 36 | let tree_name = hex::decode(&tree_name).map_err(|_| { 37 | DeError::invalid_value(Unexpected::Str(&tree_name), &"A hex-encoded byte string") 38 | })?; 39 | let tree: Tree = self.0.open_tree(tree_name).map_err(DeError::custom)?; 40 | 41 | let () = map.next_value_seed(TreeDeserializer(&tree))?; 42 | } 43 | Ok(()) 44 | } 45 | } 46 | 47 | impl<'de, 'a> DeserializeSeed<'de> for TreeDeserializer<'a> { 48 | type Value = (); 49 | 50 | fn deserialize(self, deserializer: D) -> Result 51 | where 52 | D: serde::Deserializer<'de>, 53 | { 54 | deserializer.deserialize_map(self) 55 | } 56 | } 57 | 58 | impl<'de, 'a> Visitor<'de> for TreeDeserializer<'a> { 59 | type Value = (); 60 | 61 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 62 | write!(formatter, "expecting a map") 63 | } 64 | 65 | fn visit_map(self, mut map: A) -> Result 66 | where 67 | A: serde::de::MapAccess<'de>, 68 | { 69 | let mut batch = Batch::default(); 70 | let mut counter: usize = 0; 71 | while let Some(key) = map.next_key()? { 72 | let key: String = key; 73 | let key = hex::decode(&key).map_err(|_| { 74 | DeError::invalid_value(Unexpected::Str(&key), &"A hex-encoded byte string") 75 | })?; 76 | 77 | let value: String = map.next_value()?; 78 | let value = hex::decode(&value).map_err(|_| { 79 | DeError::invalid_value(Unexpected::Str(&value), &"A hex-encoded byte string") 80 | })?; 81 | 82 | batch.insert(key, value); 83 | 84 | counter += 1; 85 | 86 | if counter >= MAX_BATCH_SIZE { 87 | self.0.apply_batch(batch).map_err(DeError::custom)?; 88 | //self.0.flush().map_err(DeError::custom)?; 89 | counter = 0; 90 | batch = Batch::default(); 91 | } 92 | } 93 | self.0.apply_batch(batch).map_err(DeError::custom)?; 94 | Ok(()) 95 | } 96 | } 97 | --------------------------------------------------------------------------------