├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── compare.md ├── memds-cli ├── Cargo.toml ├── TODO.md └── src │ ├── keys.rs │ ├── list.rs │ ├── main.rs │ ├── server.rs │ ├── set.rs │ ├── string.rs │ └── util.rs ├── memds-proto ├── Cargo.toml ├── build.rs └── src │ ├── .gitignore │ ├── codec.rs │ ├── error.rs │ ├── lib.rs │ ├── memds-api.proto │ └── util.rs ├── memds-server ├── .gitignore ├── Cargo.toml ├── example-memds.conf └── src │ ├── config.rs │ ├── keys.rs │ ├── list.rs │ ├── main.rs │ ├── rpcservice.rs │ ├── server.rs │ ├── set.rs │ └── string.rs └── notes.md /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install deps 13 | run: sudo apt-get -y install protobuf-compiler 14 | - name: Build 15 | run: cargo build --verbose 16 | - name: Run tests 17 | run: cargo test --verbose 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target/ 3 | 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "atty" 31 | version = "0.2.14" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 34 | dependencies = [ 35 | "hermit-abi 0.1.19", 36 | "libc", 37 | "winapi 0.3.9", 38 | ] 39 | 40 | [[package]] 41 | name = "autocfg" 42 | version = "1.2.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 45 | 46 | [[package]] 47 | name = "backtrace" 48 | version = "0.3.71" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 51 | dependencies = [ 52 | "addr2line", 53 | "cc", 54 | "cfg-if 1.0.0", 55 | "libc", 56 | "miniz_oxide", 57 | "object", 58 | "rustc-demangle", 59 | ] 60 | 61 | [[package]] 62 | name = "bindgen" 63 | version = "0.51.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "ebd71393f1ec0509b553aa012b9b58e81dadbdff7130bd3b8cba576e69b32f75" 66 | dependencies = [ 67 | "bitflags 1.3.2", 68 | "cexpr", 69 | "cfg-if 0.1.10", 70 | "clang-sys", 71 | "lazy_static", 72 | "peeking_take_while", 73 | "proc-macro2", 74 | "quote", 75 | "regex", 76 | "rustc-hash", 77 | "shlex", 78 | ] 79 | 80 | [[package]] 81 | name = "bitflags" 82 | version = "1.3.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 85 | 86 | [[package]] 87 | name = "bitflags" 88 | version = "2.5.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 91 | 92 | [[package]] 93 | name = "bytes" 94 | version = "0.5.6" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 97 | 98 | [[package]] 99 | name = "cc" 100 | version = "1.0.90" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" 103 | 104 | [[package]] 105 | name = "cexpr" 106 | version = "0.3.6" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" 109 | dependencies = [ 110 | "nom", 111 | ] 112 | 113 | [[package]] 114 | name = "cfg-if" 115 | version = "0.1.10" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 118 | 119 | [[package]] 120 | name = "cfg-if" 121 | version = "1.0.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 124 | 125 | [[package]] 126 | name = "clang-sys" 127 | version = "0.28.1" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" 130 | dependencies = [ 131 | "glob", 132 | "libc", 133 | "libloading", 134 | ] 135 | 136 | [[package]] 137 | name = "clap" 138 | version = "3.2.25" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 141 | dependencies = [ 142 | "atty", 143 | "bitflags 1.3.2", 144 | "clap_lex", 145 | "indexmap 1.9.3", 146 | "strsim", 147 | "termcolor", 148 | "textwrap", 149 | ] 150 | 151 | [[package]] 152 | name = "clap_lex" 153 | version = "0.2.4" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 156 | dependencies = [ 157 | "os_str_bytes", 158 | ] 159 | 160 | [[package]] 161 | name = "cmake" 162 | version = "0.1.50" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" 165 | dependencies = [ 166 | "cc", 167 | ] 168 | 169 | [[package]] 170 | name = "crc" 171 | version = "3.0.1" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" 174 | dependencies = [ 175 | "crc-catalog", 176 | ] 177 | 178 | [[package]] 179 | name = "crc-catalog" 180 | version = "2.4.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 183 | 184 | [[package]] 185 | name = "either" 186 | version = "1.10.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" 189 | 190 | [[package]] 191 | name = "equivalent" 192 | version = "1.0.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 195 | 196 | [[package]] 197 | name = "errno" 198 | version = "0.3.8" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" 201 | dependencies = [ 202 | "libc", 203 | "windows-sys", 204 | ] 205 | 206 | [[package]] 207 | name = "failure" 208 | version = "0.1.8" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 211 | dependencies = [ 212 | "backtrace", 213 | "failure_derive", 214 | ] 215 | 216 | [[package]] 217 | name = "failure_derive" 218 | version = "0.1.8" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 221 | dependencies = [ 222 | "proc-macro2", 223 | "quote", 224 | "syn 1.0.109", 225 | "synstructure", 226 | ] 227 | 228 | [[package]] 229 | name = "fastrand" 230 | version = "2.0.2" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" 233 | 234 | [[package]] 235 | name = "fnv" 236 | version = "1.0.7" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 239 | 240 | [[package]] 241 | name = "fuchsia-zircon" 242 | version = "0.3.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 245 | dependencies = [ 246 | "bitflags 1.3.2", 247 | "fuchsia-zircon-sys", 248 | ] 249 | 250 | [[package]] 251 | name = "fuchsia-zircon-sys" 252 | version = "0.3.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 255 | 256 | [[package]] 257 | name = "futures" 258 | version = "0.1.31" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" 261 | 262 | [[package]] 263 | name = "futures-core" 264 | version = "0.3.30" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 267 | 268 | [[package]] 269 | name = "futures-sink" 270 | version = "0.3.30" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 273 | 274 | [[package]] 275 | name = "gimli" 276 | version = "0.28.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 279 | 280 | [[package]] 281 | name = "glob" 282 | version = "0.3.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 285 | 286 | [[package]] 287 | name = "grpcio" 288 | version = "0.5.4" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "40601ccb2814bbb83312b229cf220eb39460dafcf1c561c6b2e9213291976db5" 291 | dependencies = [ 292 | "futures", 293 | "grpcio-sys", 294 | "libc", 295 | "log", 296 | "protobuf", 297 | ] 298 | 299 | [[package]] 300 | name = "grpcio-compiler" 301 | version = "0.5.0" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "e89d54316f4fdc3181139f2725944de33e9a2fde1bf9128e9e4b88543ad6955e" 304 | dependencies = [ 305 | "protobuf", 306 | ] 307 | 308 | [[package]] 309 | name = "grpcio-sys" 310 | version = "0.5.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "a303c6c0576a9327f522ad5152a4869160f48df0c5bc5fe908397e31f8db0df2" 313 | dependencies = [ 314 | "bindgen", 315 | "cc", 316 | "cmake", 317 | "libc", 318 | "libz-sys", 319 | "pkg-config", 320 | "walkdir", 321 | ] 322 | 323 | [[package]] 324 | name = "hashbrown" 325 | version = "0.12.3" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 328 | 329 | [[package]] 330 | name = "hashbrown" 331 | version = "0.14.3" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 334 | 335 | [[package]] 336 | name = "hermit-abi" 337 | version = "0.1.19" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 340 | dependencies = [ 341 | "libc", 342 | ] 343 | 344 | [[package]] 345 | name = "hermit-abi" 346 | version = "0.3.9" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 349 | 350 | [[package]] 351 | name = "home" 352 | version = "0.5.9" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 355 | dependencies = [ 356 | "windows-sys", 357 | ] 358 | 359 | [[package]] 360 | name = "indexmap" 361 | version = "1.9.3" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 364 | dependencies = [ 365 | "autocfg", 366 | "hashbrown 0.12.3", 367 | ] 368 | 369 | [[package]] 370 | name = "indexmap" 371 | version = "2.2.6" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 374 | dependencies = [ 375 | "equivalent", 376 | "hashbrown 0.14.3", 377 | ] 378 | 379 | [[package]] 380 | name = "iovec" 381 | version = "0.1.4" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 384 | dependencies = [ 385 | "libc", 386 | ] 387 | 388 | [[package]] 389 | name = "kernel32-sys" 390 | version = "0.2.2" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 393 | dependencies = [ 394 | "winapi 0.2.8", 395 | "winapi-build", 396 | ] 397 | 398 | [[package]] 399 | name = "lazy_static" 400 | version = "1.4.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 403 | 404 | [[package]] 405 | name = "libc" 406 | version = "0.2.153" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 409 | 410 | [[package]] 411 | name = "libloading" 412 | version = "0.5.2" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" 415 | dependencies = [ 416 | "cc", 417 | "winapi 0.3.9", 418 | ] 419 | 420 | [[package]] 421 | name = "libz-sys" 422 | version = "1.1.16" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" 425 | dependencies = [ 426 | "cc", 427 | "libc", 428 | "pkg-config", 429 | "vcpkg", 430 | ] 431 | 432 | [[package]] 433 | name = "linux-raw-sys" 434 | version = "0.4.13" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" 437 | 438 | [[package]] 439 | name = "log" 440 | version = "0.4.21" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 443 | 444 | [[package]] 445 | name = "memchr" 446 | version = "2.7.2" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 449 | 450 | [[package]] 451 | name = "memds-cli" 452 | version = "0.2.0" 453 | dependencies = [ 454 | "clap", 455 | "futures", 456 | "grpcio", 457 | "log", 458 | "memds-proto", 459 | "protobuf", 460 | ] 461 | 462 | [[package]] 463 | name = "memds-proto" 464 | version = "0.2.0" 465 | dependencies = [ 466 | "bytes", 467 | "crc", 468 | "futures", 469 | "grpcio", 470 | "protobuf", 471 | "protoc-grpcio", 472 | "tokio", 473 | "tokio-util", 474 | ] 475 | 476 | [[package]] 477 | name = "memds-server" 478 | version = "0.2.0" 479 | dependencies = [ 480 | "bytes", 481 | "clap", 482 | "futures", 483 | "grpcio", 484 | "log", 485 | "memds-proto", 486 | "nix", 487 | "protobuf", 488 | "serde", 489 | "serde_derive", 490 | "tokio", 491 | "tokio-util", 492 | "toml", 493 | ] 494 | 495 | [[package]] 496 | name = "miniz_oxide" 497 | version = "0.7.2" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 500 | dependencies = [ 501 | "adler", 502 | ] 503 | 504 | [[package]] 505 | name = "mio" 506 | version = "0.6.23" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" 509 | dependencies = [ 510 | "cfg-if 0.1.10", 511 | "fuchsia-zircon", 512 | "fuchsia-zircon-sys", 513 | "iovec", 514 | "kernel32-sys", 515 | "libc", 516 | "log", 517 | "miow 0.2.2", 518 | "net2", 519 | "slab", 520 | "winapi 0.2.8", 521 | ] 522 | 523 | [[package]] 524 | name = "mio-named-pipes" 525 | version = "0.1.7" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" 528 | dependencies = [ 529 | "log", 530 | "mio", 531 | "miow 0.3.7", 532 | "winapi 0.3.9", 533 | ] 534 | 535 | [[package]] 536 | name = "mio-uds" 537 | version = "0.6.8" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 540 | dependencies = [ 541 | "iovec", 542 | "libc", 543 | "mio", 544 | ] 545 | 546 | [[package]] 547 | name = "miow" 548 | version = "0.2.2" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" 551 | dependencies = [ 552 | "kernel32-sys", 553 | "net2", 554 | "winapi 0.2.8", 555 | "ws2_32-sys", 556 | ] 557 | 558 | [[package]] 559 | name = "miow" 560 | version = "0.3.7" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 563 | dependencies = [ 564 | "winapi 0.3.9", 565 | ] 566 | 567 | [[package]] 568 | name = "net2" 569 | version = "0.2.39" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" 572 | dependencies = [ 573 | "cfg-if 0.1.10", 574 | "libc", 575 | "winapi 0.3.9", 576 | ] 577 | 578 | [[package]] 579 | name = "nix" 580 | version = "0.17.0" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 583 | dependencies = [ 584 | "bitflags 1.3.2", 585 | "cc", 586 | "cfg-if 0.1.10", 587 | "libc", 588 | "void", 589 | ] 590 | 591 | [[package]] 592 | name = "nom" 593 | version = "4.2.3" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" 596 | dependencies = [ 597 | "memchr", 598 | "version_check", 599 | ] 600 | 601 | [[package]] 602 | name = "num_cpus" 603 | version = "1.16.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 606 | dependencies = [ 607 | "hermit-abi 0.3.9", 608 | "libc", 609 | ] 610 | 611 | [[package]] 612 | name = "object" 613 | version = "0.32.2" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 616 | dependencies = [ 617 | "memchr", 618 | ] 619 | 620 | [[package]] 621 | name = "once_cell" 622 | version = "1.19.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 625 | 626 | [[package]] 627 | name = "os_str_bytes" 628 | version = "6.6.1" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 631 | 632 | [[package]] 633 | name = "peeking_take_while" 634 | version = "0.1.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" 637 | 638 | [[package]] 639 | name = "pin-project-lite" 640 | version = "0.1.12" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" 643 | 644 | [[package]] 645 | name = "pkg-config" 646 | version = "0.3.30" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 649 | 650 | [[package]] 651 | name = "proc-macro2" 652 | version = "1.0.79" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 655 | dependencies = [ 656 | "unicode-ident", 657 | ] 658 | 659 | [[package]] 660 | name = "protobuf" 661 | version = "2.28.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" 664 | 665 | [[package]] 666 | name = "protobuf-codegen" 667 | version = "2.28.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" 670 | dependencies = [ 671 | "protobuf", 672 | ] 673 | 674 | [[package]] 675 | name = "protoc" 676 | version = "2.28.0" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" 679 | dependencies = [ 680 | "log", 681 | "which", 682 | ] 683 | 684 | [[package]] 685 | name = "protoc-grpcio" 686 | version = "1.2.0" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "cb806500438b4f850d8b9a97b1506c08bb319e758fc9c3dc61cad4a01a5dd822" 689 | dependencies = [ 690 | "failure", 691 | "grpcio-compiler", 692 | "protobuf", 693 | "protobuf-codegen", 694 | "protoc", 695 | "tempfile", 696 | ] 697 | 698 | [[package]] 699 | name = "quote" 700 | version = "1.0.35" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 703 | dependencies = [ 704 | "proc-macro2", 705 | ] 706 | 707 | [[package]] 708 | name = "regex" 709 | version = "1.10.4" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" 712 | dependencies = [ 713 | "aho-corasick", 714 | "memchr", 715 | "regex-automata", 716 | "regex-syntax", 717 | ] 718 | 719 | [[package]] 720 | name = "regex-automata" 721 | version = "0.4.6" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" 724 | dependencies = [ 725 | "aho-corasick", 726 | "memchr", 727 | "regex-syntax", 728 | ] 729 | 730 | [[package]] 731 | name = "regex-syntax" 732 | version = "0.8.3" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" 735 | 736 | [[package]] 737 | name = "rustc-demangle" 738 | version = "0.1.23" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 741 | 742 | [[package]] 743 | name = "rustc-hash" 744 | version = "1.1.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 747 | 748 | [[package]] 749 | name = "rustix" 750 | version = "0.38.32" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" 753 | dependencies = [ 754 | "bitflags 2.5.0", 755 | "errno", 756 | "libc", 757 | "linux-raw-sys", 758 | "windows-sys", 759 | ] 760 | 761 | [[package]] 762 | name = "same-file" 763 | version = "1.0.6" 764 | source = "registry+https://github.com/rust-lang/crates.io-index" 765 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 766 | dependencies = [ 767 | "winapi-util", 768 | ] 769 | 770 | [[package]] 771 | name = "serde" 772 | version = "1.0.197" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" 775 | dependencies = [ 776 | "serde_derive", 777 | ] 778 | 779 | [[package]] 780 | name = "serde_derive" 781 | version = "1.0.197" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" 784 | dependencies = [ 785 | "proc-macro2", 786 | "quote", 787 | "syn 2.0.55", 788 | ] 789 | 790 | [[package]] 791 | name = "serde_spanned" 792 | version = "0.6.5" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" 795 | dependencies = [ 796 | "serde", 797 | ] 798 | 799 | [[package]] 800 | name = "shlex" 801 | version = "0.1.1" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" 804 | 805 | [[package]] 806 | name = "signal-hook-registry" 807 | version = "1.4.1" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 810 | dependencies = [ 811 | "libc", 812 | ] 813 | 814 | [[package]] 815 | name = "slab" 816 | version = "0.4.9" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 819 | dependencies = [ 820 | "autocfg", 821 | ] 822 | 823 | [[package]] 824 | name = "strsim" 825 | version = "0.10.0" 826 | source = "registry+https://github.com/rust-lang/crates.io-index" 827 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 828 | 829 | [[package]] 830 | name = "syn" 831 | version = "1.0.109" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 834 | dependencies = [ 835 | "proc-macro2", 836 | "quote", 837 | "unicode-ident", 838 | ] 839 | 840 | [[package]] 841 | name = "syn" 842 | version = "2.0.55" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" 845 | dependencies = [ 846 | "proc-macro2", 847 | "quote", 848 | "unicode-ident", 849 | ] 850 | 851 | [[package]] 852 | name = "synstructure" 853 | version = "0.12.6" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 856 | dependencies = [ 857 | "proc-macro2", 858 | "quote", 859 | "syn 1.0.109", 860 | "unicode-xid", 861 | ] 862 | 863 | [[package]] 864 | name = "tempfile" 865 | version = "3.10.1" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" 868 | dependencies = [ 869 | "cfg-if 1.0.0", 870 | "fastrand", 871 | "rustix", 872 | "windows-sys", 873 | ] 874 | 875 | [[package]] 876 | name = "termcolor" 877 | version = "1.4.1" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 880 | dependencies = [ 881 | "winapi-util", 882 | ] 883 | 884 | [[package]] 885 | name = "textwrap" 886 | version = "0.16.1" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 889 | 890 | [[package]] 891 | name = "tokio" 892 | version = "0.2.25" 893 | source = "registry+https://github.com/rust-lang/crates.io-index" 894 | checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" 895 | dependencies = [ 896 | "bytes", 897 | "fnv", 898 | "futures-core", 899 | "iovec", 900 | "lazy_static", 901 | "libc", 902 | "memchr", 903 | "mio", 904 | "mio-named-pipes", 905 | "mio-uds", 906 | "num_cpus", 907 | "pin-project-lite", 908 | "signal-hook-registry", 909 | "slab", 910 | "tokio-macros", 911 | "winapi 0.3.9", 912 | ] 913 | 914 | [[package]] 915 | name = "tokio-macros" 916 | version = "0.2.6" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" 919 | dependencies = [ 920 | "proc-macro2", 921 | "quote", 922 | "syn 1.0.109", 923 | ] 924 | 925 | [[package]] 926 | name = "tokio-util" 927 | version = "0.2.0" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930" 930 | dependencies = [ 931 | "bytes", 932 | "futures-core", 933 | "futures-sink", 934 | "log", 935 | "pin-project-lite", 936 | "tokio", 937 | ] 938 | 939 | [[package]] 940 | name = "toml" 941 | version = "0.8.12" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" 944 | dependencies = [ 945 | "serde", 946 | "serde_spanned", 947 | "toml_datetime", 948 | "toml_edit", 949 | ] 950 | 951 | [[package]] 952 | name = "toml_datetime" 953 | version = "0.6.5" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" 956 | dependencies = [ 957 | "serde", 958 | ] 959 | 960 | [[package]] 961 | name = "toml_edit" 962 | version = "0.22.9" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" 965 | dependencies = [ 966 | "indexmap 2.2.6", 967 | "serde", 968 | "serde_spanned", 969 | "toml_datetime", 970 | "winnow", 971 | ] 972 | 973 | [[package]] 974 | name = "unicode-ident" 975 | version = "1.0.12" 976 | source = "registry+https://github.com/rust-lang/crates.io-index" 977 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 978 | 979 | [[package]] 980 | name = "unicode-xid" 981 | version = "0.2.4" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 984 | 985 | [[package]] 986 | name = "vcpkg" 987 | version = "0.2.15" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 990 | 991 | [[package]] 992 | name = "version_check" 993 | version = "0.1.5" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" 996 | 997 | [[package]] 998 | name = "void" 999 | version = "1.0.2" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1002 | 1003 | [[package]] 1004 | name = "walkdir" 1005 | version = "2.5.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1008 | dependencies = [ 1009 | "same-file", 1010 | "winapi-util", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "which" 1015 | version = "4.4.2" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1018 | dependencies = [ 1019 | "either", 1020 | "home", 1021 | "once_cell", 1022 | "rustix", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "winapi" 1027 | version = "0.2.8" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1030 | 1031 | [[package]] 1032 | name = "winapi" 1033 | version = "0.3.9" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1036 | dependencies = [ 1037 | "winapi-i686-pc-windows-gnu", 1038 | "winapi-x86_64-pc-windows-gnu", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "winapi-build" 1043 | version = "0.1.1" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1046 | 1047 | [[package]] 1048 | name = "winapi-i686-pc-windows-gnu" 1049 | version = "0.4.0" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1052 | 1053 | [[package]] 1054 | name = "winapi-util" 1055 | version = "0.1.6" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" 1058 | dependencies = [ 1059 | "winapi 0.3.9", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "winapi-x86_64-pc-windows-gnu" 1064 | version = "0.4.0" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1067 | 1068 | [[package]] 1069 | name = "windows-sys" 1070 | version = "0.52.0" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1073 | dependencies = [ 1074 | "windows-targets", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "windows-targets" 1079 | version = "0.52.4" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" 1082 | dependencies = [ 1083 | "windows_aarch64_gnullvm", 1084 | "windows_aarch64_msvc", 1085 | "windows_i686_gnu", 1086 | "windows_i686_msvc", 1087 | "windows_x86_64_gnu", 1088 | "windows_x86_64_gnullvm", 1089 | "windows_x86_64_msvc", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "windows_aarch64_gnullvm" 1094 | version = "0.52.4" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" 1097 | 1098 | [[package]] 1099 | name = "windows_aarch64_msvc" 1100 | version = "0.52.4" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" 1103 | 1104 | [[package]] 1105 | name = "windows_i686_gnu" 1106 | version = "0.52.4" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" 1109 | 1110 | [[package]] 1111 | name = "windows_i686_msvc" 1112 | version = "0.52.4" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" 1115 | 1116 | [[package]] 1117 | name = "windows_x86_64_gnu" 1118 | version = "0.52.4" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" 1121 | 1122 | [[package]] 1123 | name = "windows_x86_64_gnullvm" 1124 | version = "0.52.4" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" 1127 | 1128 | [[package]] 1129 | name = "windows_x86_64_msvc" 1130 | version = "0.52.4" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" 1133 | 1134 | [[package]] 1135 | name = "winnow" 1136 | version = "0.6.5" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" 1139 | dependencies = [ 1140 | "memchr", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "ws2_32-sys" 1145 | version = "0.2.1" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1148 | dependencies = [ 1149 | "winapi 0.2.8", 1150 | "winapi-build", 1151 | ] 1152 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "memds-cli", 6 | "memds-proto", 7 | "memds-server", 8 | ] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bloq, Inc. 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 | # memds 2 | Memory database - "redis v3, in Rust" 3 | 4 | ## Goals and Journey 5 | 6 | memds intends to be "the next thing after redis" 7 | 8 | Looking through software history, we can consider [memcached](https://memcached.org/) as Version 1: memcached provides a single key/value namespace for string values, plus some small mods for atomic numbers. [redis](https://redis.io/) is Version 2: Differences between abstract data types (ADTs) are made explicit with strings, sets, lists, hash [tables], streams and more. 9 | 10 | memds is Version 3: Formally model the network protocol and database namespace. Represent abstract data types as CLASS.METHOD internal Remote Procedure Calls (RPCs), batched together as a bytecode-like stream of database mutation operations. 11 | 12 | ## Status 13 | 14 | This project is alpha status; brand new; wet cement. Proceed with with that caveat in mind. 15 | 16 | Contributors via Pull Request are welcome. 17 | 18 | ## Model & design comparisons 19 | 20 | ### Model caveats 21 | 22 | The current code is still a work in progress, in terms of implementing the models described below. See the following markdown docs for more detailed information: 23 | * [TODO](TODO.md) 24 | * [Detailed redis feature comparison](compare.md) 25 | * [Other project notes](notes.md) 26 | 27 | ### Old-vs-New 28 | 29 | Old redis model: 30 | ``` 31 | [database number] [key] [abstract data type] 32 | ``` 33 | New memds model: 34 | ``` 35 | [database key] [key] [abstract data type, possibly with its own hierarchy] 36 | ``` 37 | 38 | * Old redis protocol hierarchy: All ADTs overloaded in a single command namespace ("HLEN","LLEN"). 39 | * New memds protocol hierarchy: Each ADT in its own class-specific namespace. (like "HASH.LEN","LIST.LEN", but with integer identifiers). 40 | 41 | * Old redis network protocol: Custom protocol, requiring custom clients across N programming languages. 42 | * New memds network protocol: Protobuf schema, automatically generating correct, compliant, fast client codecs for many languages. 43 | 44 | ## Components 45 | 46 | * `memds-cli`: Command line client 47 | * `memds-proto`: wire protocol library 48 | * `memds-server`: Database server 49 | 50 | ## Installation 51 | 52 | All building is done via the standard [Rust](https://www.rust-lang.org/) tool [Cargo](https://doc.rust-lang.org/cargo/). 53 | 54 | ``` 55 | $ cargo build --release 56 | $ cargo test --release 57 | ``` 58 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # To-do 3 | 4 | ## Architecture 5 | 6 | - [ ] Convert from dumb "big match stmt" dispatch method to an operation 7 | call/return sequence that calls MODULE.METHOD, with modules 8 | registering their list of methods. 9 | 10 | ## Protocol 11 | 12 | - [ ] Adjunct/Task under MODULE.METHOD architecture to-do: Strongly 13 | consider a "tighter" + more flexible encoding for module calls: 14 | 15 | ``` 16 | module: uint32, 17 | method: uint32, 18 | enum Params 19 | value_list: Vec 20 | pair_list: Vec<(bytes,bytes)> 21 | ``` 22 | 23 | then re-introduce a schema checking (module.method+params validation) 24 | definition that restores what we lose by departing from protobufs a bit. 25 | 26 | - [ ] Protocol return values echo HTTP for want of a better practice. 27 | Update code to a better practice. 28 | 29 | ## Bugs 30 | 31 | - [ ] BGSAVE leaves zombie processes. Need to wait(2) 32 | 33 | ## Code organization and Q/A 34 | 35 | - [ ] Reduce amount of boilerplate code in per-operation processing. 36 | - [ ] Improve per-op tests beyond it-works 37 | - [ ] CLI integration tests 38 | 39 | -------------------------------------------------------------------------------- /compare.md: -------------------------------------------------------------------------------- 1 | 2 | # Comparison with redis 3 | 4 | ## General features 5 | 6 | - [x] Component: Memory database server 7 | - [x] Component: Command-line client 8 | - [ ] ADT: HashMap 9 | - [x] ADT: Lists 10 | - [ ] ADT: LRU cache 11 | - [x] ADT: Sets 12 | - [x] ADT: Strings 13 | - [x] I/O: Fork and dump to fs 14 | - [x] I/O: Import dump 15 | - [ ] I/O: Write-ahead logging 16 | - [ ] Memory limits 17 | - [ ] Network: Clusters 18 | - [ ] Statistics 19 | 20 | ## Keys operations 21 | 22 | - [x] DEL 23 | - [x] DUMP 24 | - [x] EXISTS 25 | - [ ] EXPIRE 26 | - [ ] EXPIREAT 27 | - [ ] KEYS 28 | - [ ] MIGRATE 29 | - [ ] MOVE 30 | - [ ] OBJECT 31 | - [ ] PERSIST 32 | - [ ] PEXPIRE 33 | - [ ] PEXPIREAT 34 | - [ ] PTTL 35 | - [ ] RANDOMKEY 36 | - [x] RENAME 37 | - [x] RENAMENX 38 | - [x] RESTORE 39 | - [ ] SORT 40 | - [ ] TOUCH 41 | - [ ] TTL 42 | - [x] TYPE 43 | - [ ] UNLINK 44 | - [ ] WAIT 45 | - [ ] SCAN 46 | 47 | ## List operations 48 | 49 | - [ ] BLPOP 50 | - [ ] BRPOP 51 | - [ ] BRPOPLPUSH 52 | - [x] LINDEX 53 | - [ ] LINSERT 54 | - [x] LLEN 55 | - [x] LPOP 56 | - [x] LPUSH 57 | - [x] LPUSHX 58 | - [ ] LRANGE 59 | - [ ] LREM 60 | - [ ] LSET 61 | - [ ] LTRIM 62 | - [x] RPOP 63 | - [ ] RPOPLPUSH 64 | - [x] RPUSH 65 | - [x] RPUSHX 66 | 67 | ## Server operations 68 | 69 | - [ ] BGREWRITEAOF 70 | - [x] BGSAVE 71 | - [ ] CLIENT ID 72 | - [ ] CLIENT KILL 73 | - [ ] CLIENT LIST 74 | - [ ] CLIENT GETNAME 75 | - [ ] CLIENT PAUSE 76 | - [ ] CLIENT REPLY 77 | - [ ] CLIENT SETNAME 78 | - [ ] CLIENT UNBLOCK 79 | - [ ] COMMAND 80 | - [ ] COMMAND COUNT 81 | - [ ] COMMAND GETKEYS 82 | - [ ] COMMAND INFO 83 | - [ ] CONFIG GET 84 | - [ ] CONFIG REWRITE 85 | - [ ] CONFIG SET 86 | - [ ] CONFIG RESETSTAT 87 | - [x] DBSIZE 88 | - [ ] DEBUG OBJECT 89 | - [ ] DEBUG SEGFAULT 90 | - [x] FLUSHALL 91 | - [x] FLUSHDB 92 | - [ ] INFO 93 | - [ ] LOLWUT 94 | - [ ] LASTSAVE 95 | - [ ] MEMORY DOCTOR 96 | - [ ] MEMORY HELP 97 | - [ ] MEMORY MALLOC-STATS 98 | - [ ] MEMORY PURGE 99 | - [ ] MEMORY STATS 100 | - [ ] MEMORY USAGE 101 | - [ ] MODULE LIST 102 | - [ ] MODULE LOAD 103 | - [ ] MODULE UNLOAD 104 | - [ ] MONITOR 105 | - [ ] ROLE 106 | - [ ] SAVE 107 | - [ ] SHUTDOWN 108 | - [ ] SLAVEOF 109 | - [ ] REPLICAOF 110 | - [ ] SLOWLOG 111 | - [ ] SYNC 112 | - [ ] PSYNC 113 | - [x] TIME 114 | - [ ] LATENCY DOCTOR 115 | - [ ] LATENCY GRAPH 116 | - [ ] LATENCY HISTORY 117 | - [ ] LATENCY LATEST 118 | - [ ] LATENCY RESET 119 | - [ ] LATENCY HELP 120 | 121 | ## Set operations 122 | 123 | - [x] SADD 124 | - [x] SCARD 125 | - [x] SDIFF 126 | - [x] SDIFFSTORE 127 | - [ ] SINTER 128 | - [ ] SINTERSTORE 129 | - [x] SISMEMBER 130 | - [x] SMEMBERS 131 | - [ ] SMOVE 132 | - [ ] SPOP 133 | - [ ] SRANDMEMBER 134 | - [x] SREM 135 | - [x] SUNION 136 | - [x] SUNIONSTORE 137 | - [ ] SSCAN 138 | 139 | ## String operations 140 | 141 | - [x] APPEND 142 | - [ ] BITCOUNT 143 | - [ ] BITFIELD 144 | - [ ] BITOP 145 | - [ ] BITPOS 146 | - [x] DECR 147 | - [x] DECRBY 148 | - [x] GET 149 | - [ ] GETBIT 150 | - [x] GETRANGE 151 | - [x] GETSET 152 | - [x] INCR 153 | - [x] INCRBY 154 | - [ ] INCRBYFLOAT 155 | - [x] MGET 156 | - [x] MSET 157 | - [ ] MSETNX 158 | - [ ] PSETEX 159 | - [x] SET 160 | - [ ] SET options, notably expiration 161 | - [ ] SETBIT 162 | - [ ] SETEX 163 | - [x] SETNX 164 | - [ ] SETRANGE 165 | - [x] STRLEN 166 | 167 | ## Transactions 168 | 169 | - [x] DISCARD 170 | - [x] EXEC 171 | - [x] MULTI 172 | - [ ] UNWATCH 173 | - [ ] WATCH 174 | 175 | -------------------------------------------------------------------------------- /memds-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memds-cli" 3 | version = "0.2.0" 4 | authors = ["Jeff Garzik "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | memds-proto = { path = "../memds-proto" } 11 | clap = "3" 12 | futures = "0.1" 13 | grpcio = { version = "0.5", features = ["openssl"] } 14 | protobuf = "~2" 15 | log = "0.4" 16 | -------------------------------------------------------------------------------- /memds-cli/TODO.md: -------------------------------------------------------------------------------- 1 | 2 | # TODO list 3 | 4 | ## Bugs 5 | 6 | * value_t macro parsing for STR_GETRANGE should accept negative 7 | numbers, but does not. 8 | 9 | ## Technical debt and cleanups 10 | 11 | * The protocol is binary buffers. For simplicity, when running commands 12 | such as `sdiff`, multiple elements are output separated by a newline 13 | (\n). This does not work with keys containing binary nuls and other 14 | non-display values. 15 | 16 | * Create our own Error, and stop overloading io::Result 17 | 18 | -------------------------------------------------------------------------------- /memds-cli/src/keys.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, Error, ErrorKind, Read, Write}; 3 | 4 | use memds_proto::memds_api::*; 5 | use memds_proto::memds_api_grpc::MemdsClient; 6 | 7 | use crate::util; 8 | 9 | pub fn del_exist(client: &MemdsClient, keys: &Vec<&str>, remove_it: bool) -> io::Result<()> { 10 | let mut op_req = KeyListOp::new(); 11 | for key in keys { 12 | op_req.keys.push(key.as_bytes().to_vec()); 13 | } 14 | 15 | let mut op = Operation::new(); 16 | op.otype = match remove_it { 17 | true => OpType::KEYS_DEL, 18 | false => OpType::KEYS_EXIST, 19 | }; 20 | op.set_key_list(op_req); 21 | 22 | let mut req = RequestMsg::new(); 23 | req.ops.push(op); 24 | 25 | let resp = util::rpc_exec(&client, &req)?; 26 | 27 | if !resp.ok { 28 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 29 | return Err(Error::new(ErrorKind::Other, msg)); 30 | } 31 | 32 | let results = resp.get_results(); 33 | assert_eq!(results.len(), 1); 34 | 35 | let result = &results[0]; 36 | if !result.ok { 37 | let msg = format!("{}...: {}", keys[0], result.err_message); 38 | return Err(Error::new(ErrorKind::Other, msg)); 39 | } 40 | 41 | let count_res = results[0].get_count(); 42 | println!("{}", count_res.n); 43 | Ok(()) 44 | } 45 | 46 | pub fn rename( 47 | client: &MemdsClient, 48 | old_key: &str, 49 | new_key: &str, 50 | create_excl: bool, 51 | ) -> io::Result<()> { 52 | let mut key_req = KeyRenameOp::new(); 53 | key_req.set_old_key(old_key.as_bytes().to_vec()); 54 | key_req.set_new_key(new_key.as_bytes().to_vec()); 55 | key_req.create_excl = create_excl; 56 | 57 | let mut op = Operation::new(); 58 | op.otype = OpType::KEYS_RENAME; 59 | op.set_rename(key_req); 60 | 61 | let mut req = RequestMsg::new(); 62 | req.ops.push(op); 63 | 64 | let resp = util::rpc_exec(&client, &req)?; 65 | 66 | if !resp.ok { 67 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 68 | return Err(Error::new(ErrorKind::Other, msg)); 69 | } 70 | 71 | let results = resp.get_results(); 72 | assert!(results.len() == 1); 73 | 74 | let result = &results[0]; 75 | if !result.ok { 76 | let msg = format!("keys-rename: {}", result.err_message); 77 | return Err(Error::new(ErrorKind::Other, msg)); 78 | } 79 | 80 | println!("ok"); 81 | Ok(()) 82 | } 83 | 84 | pub fn dump(client: &MemdsClient, key: &str) -> io::Result<()> { 85 | let mut key_req = KeyOp::new(); 86 | key_req.set_key(key.as_bytes().to_vec()); 87 | 88 | let mut op = Operation::new(); 89 | op.otype = OpType::KEY_DUMP; 90 | op.set_key(key_req); 91 | 92 | let mut req = RequestMsg::new(); 93 | req.ops.push(op); 94 | 95 | let resp = util::rpc_exec(&client, &req)?; 96 | 97 | if !resp.ok { 98 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 99 | return Err(Error::new(ErrorKind::Other, msg)); 100 | } 101 | 102 | let results = resp.get_results(); 103 | assert!(results.len() == 1); 104 | 105 | let result = &results[0]; 106 | if !result.ok { 107 | let msg = format!("{}: {}", key, result.err_message); 108 | return Err(Error::new(ErrorKind::Other, msg)); 109 | } 110 | 111 | let get_res = results[0].get_get(); 112 | let value = get_res.get_value(); 113 | io::stdout().write_all(value)?; 114 | Ok(()) 115 | } 116 | 117 | pub fn typ(client: &MemdsClient, key: &str) -> io::Result<()> { 118 | let mut key_req = KeyOp::new(); 119 | key_req.set_key(key.as_bytes().to_vec()); 120 | 121 | let mut op = Operation::new(); 122 | op.otype = OpType::KEYS_TYPE; 123 | op.set_key(key_req); 124 | 125 | let mut req = RequestMsg::new(); 126 | req.ops.push(op); 127 | 128 | let resp = util::rpc_exec(&client, &req)?; 129 | 130 | if !resp.ok { 131 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 132 | return Err(Error::new(ErrorKind::Other, msg)); 133 | } 134 | 135 | let results = resp.get_results(); 136 | assert!(results.len() == 1); 137 | 138 | let result = &results[0]; 139 | if !result.ok { 140 | let msg = format!("{}: {}", key, result.err_message); 141 | return Err(Error::new(ErrorKind::Other, msg)); 142 | } 143 | 144 | let type_res = results[0].get_typ(); 145 | println!("{:?}", type_res.typ); 146 | Ok(()) 147 | } 148 | 149 | fn read_file(in_fn: &str) -> io::Result> { 150 | let mut f = File::open(in_fn)?; 151 | let mut buffer = Vec::new(); 152 | f.read_to_end(&mut buffer)?; 153 | Ok(buffer) 154 | } 155 | 156 | pub fn restore(client: &MemdsClient, key: Option<&str>, restore_fn: &str) -> io::Result<()> { 157 | let mut set_req = StrSetOp::new(); 158 | if key.is_some() { 159 | set_req.set_key(key.unwrap().as_bytes().to_vec()); 160 | } 161 | 162 | let wire_data = read_file(restore_fn)?; 163 | set_req.set_value(wire_data); 164 | 165 | let mut op = Operation::new(); 166 | op.otype = OpType::KEY_RESTORE; 167 | op.set_set(set_req); 168 | 169 | let mut req = RequestMsg::new(); 170 | req.ops.push(op); 171 | 172 | let resp = util::rpc_exec(&client, &req)?; 173 | 174 | if !resp.ok { 175 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 176 | return Err(Error::new(ErrorKind::Other, msg)); 177 | } 178 | 179 | let results = resp.get_results(); 180 | assert!(results.len() == 1); 181 | 182 | let result = &results[0]; 183 | if result.ok { 184 | io::stdout().write_all(b"ok\n")?; 185 | Ok(()) 186 | } else { 187 | let msg = format!("{:?}: {}", key, result.err_message); 188 | Err(Error::new(ErrorKind::Other, msg)) 189 | } 190 | } 191 | 192 | pub mod args { 193 | use clap::{App, Arg, SubCommand}; 194 | 195 | pub fn del() -> App<'static> { 196 | SubCommand::with_name("del") 197 | .about("Keys.Del: Delete listed keys") 198 | .arg( 199 | Arg::with_name("key") 200 | .help("Key to delete") 201 | .required(true) 202 | .multiple(true), 203 | ) 204 | } 205 | 206 | pub fn dump() -> App<'static> { 207 | SubCommand::with_name("dump") 208 | .about("Keys.Dump: Dump listed key") 209 | .arg(Arg::with_name("key").help("Key to dump").required(true)) 210 | } 211 | 212 | pub fn exists() -> App<'static> { 213 | SubCommand::with_name("exists") 214 | .about("Keys.Exists: Count existing listed keys") 215 | .arg( 216 | Arg::with_name("key") 217 | .help("Key to test") 218 | .required(true) 219 | .multiple(true), 220 | ) 221 | } 222 | 223 | pub fn rename() -> App<'static> { 224 | SubCommand::with_name("rename") 225 | .about("Keys.Rename: Rename item key") 226 | .arg( 227 | Arg::with_name("old_key") 228 | .help("Source Key of item to rename") 229 | .required(true), 230 | ) 231 | .arg( 232 | Arg::with_name("new_key") 233 | .help("Destination Key of item") 234 | .required(true), 235 | ) 236 | } 237 | 238 | pub fn renamenx() -> App<'static> { 239 | SubCommand::with_name("renamenx") 240 | .about("Keys.RenameNX: Rename item key, iff new key does not exist") 241 | .arg( 242 | Arg::with_name("old_key") 243 | .help("Source Key of item to rename") 244 | .required(true), 245 | ) 246 | .arg( 247 | Arg::with_name("new_key") 248 | .help("Destination Key of item") 249 | .required(true), 250 | ) 251 | } 252 | 253 | pub fn restore() -> App<'static> { 254 | SubCommand::with_name("restore") 255 | .about("Keys.Restore: Restore item from dumpfile") 256 | .arg( 257 | Arg::with_name("file") 258 | .help("Pathname of item to restore") 259 | .required(true), 260 | ) 261 | .arg( 262 | Arg::with_name("key") 263 | .help("Key of item to query") 264 | .short('k') 265 | .long("key") 266 | .value_name("string"), 267 | ) 268 | } 269 | 270 | pub fn typ() -> App<'static> { 271 | SubCommand::with_name("type") 272 | .about("Keys.Type: Query item data type") 273 | .arg( 274 | Arg::with_name("key") 275 | .help("Key of item to query") 276 | .required(true), 277 | ) 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /memds-cli/src/list.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Error, ErrorKind, Write}; 2 | 3 | use memds_proto::memds_api::*; 4 | use memds_proto::memds_api_grpc::MemdsClient; 5 | 6 | use crate::util; 7 | 8 | pub fn lindex(client: &MemdsClient, key: &str, index: i32) -> io::Result<()> { 9 | let mut op_req = ListIndexOp::new(); 10 | op_req.set_key(key.as_bytes().to_vec()); 11 | op_req.index = index; 12 | 13 | let mut op = Operation::new(); 14 | op.otype = OpType::LIST_INDEX; 15 | op.set_lindex(op_req); 16 | 17 | let mut req = RequestMsg::new(); 18 | req.ops.push(op); 19 | 20 | let resp = util::rpc_exec(&client, &req)?; 21 | 22 | if !resp.ok { 23 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 24 | return Err(Error::new(ErrorKind::Other, msg)); 25 | } 26 | 27 | let results = resp.get_results(); 28 | assert_eq!(results.len(), 1); 29 | 30 | let result = &results[0]; 31 | if !result.ok { 32 | let msg = format!("{}: {}", key, result.err_message); 33 | return Err(Error::new(ErrorKind::Other, msg)); 34 | } 35 | 36 | let list_res = results[0].get_list(); 37 | if list_res.elements.len() == 0 { 38 | println!("not found"); 39 | } else { 40 | for element in list_res.elements.iter() { 41 | io::stdout().write_all(element)?; 42 | } 43 | } 44 | Ok(()) 45 | } 46 | 47 | pub fn llen(client: &MemdsClient, key: &str) -> io::Result<()> { 48 | let mut key_req = KeyOp::new(); 49 | key_req.set_key(key.as_bytes().to_vec()); 50 | 51 | let mut op = Operation::new(); 52 | op.otype = OpType::LIST_INFO; 53 | op.set_key(key_req); 54 | 55 | let mut req = RequestMsg::new(); 56 | req.ops.push(op); 57 | 58 | let resp = util::rpc_exec(&client, &req)?; 59 | 60 | if !resp.ok { 61 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 62 | return Err(Error::new(ErrorKind::Other, msg)); 63 | } 64 | 65 | let results = resp.get_results(); 66 | assert!(results.len() == 1); 67 | 68 | let result = &results[0]; 69 | if !result.ok { 70 | let msg = format!("{}: {}", key, result.err_message); 71 | return Err(Error::new(ErrorKind::Other, msg)); 72 | } 73 | 74 | let info_res = results[0].get_list_info(); 75 | println!("{}", info_res.length); 76 | Ok(()) 77 | } 78 | 79 | pub fn pop(client: &MemdsClient, key: &str, at_head: bool) -> io::Result<()> { 80 | let mut op_req = ListPopOp::new(); 81 | op_req.set_key(key.as_bytes().to_vec()); 82 | op_req.at_head = at_head; 83 | 84 | let mut op = Operation::new(); 85 | op.otype = OpType::LIST_POP; 86 | op.set_lpop(op_req); 87 | 88 | let mut req = RequestMsg::new(); 89 | req.ops.push(op); 90 | 91 | let resp = util::rpc_exec(&client, &req)?; 92 | 93 | if !resp.ok { 94 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 95 | return Err(Error::new(ErrorKind::Other, msg)); 96 | } 97 | 98 | let results = resp.get_results(); 99 | assert!(results.len() == 1); 100 | 101 | let result = &results[0]; 102 | if !result.ok { 103 | let msg = format!("{}: {}", key, result.err_message); 104 | return Err(Error::new(ErrorKind::Other, msg)); 105 | } 106 | 107 | let list_res = results[0].get_list(); 108 | if list_res.elements.len() == 0 { 109 | println!("not found"); 110 | } else { 111 | for element in list_res.elements.iter() { 112 | io::stdout().write_all(element)?; 113 | } 114 | } 115 | Ok(()) 116 | } 117 | 118 | pub fn push( 119 | client: &MemdsClient, 120 | key: &str, 121 | elems: &Vec<&str>, 122 | at_head: bool, 123 | require_exist: bool, 124 | ) -> io::Result<()> { 125 | let mut op_req = ListPushOp::new(); 126 | op_req.set_key(key.as_bytes().to_vec()); 127 | op_req.at_head = at_head; 128 | op_req.if_exists = require_exist; 129 | for elem in elems.iter() { 130 | op_req.elements.push(elem.as_bytes().to_vec()); 131 | } 132 | 133 | let mut op = Operation::new(); 134 | op.otype = OpType::LIST_PUSH; 135 | op.set_lpush(op_req); 136 | 137 | let mut req = RequestMsg::new(); 138 | req.ops.push(op); 139 | 140 | let resp = util::rpc_exec(&client, &req)?; 141 | 142 | if !resp.ok { 143 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 144 | return Err(Error::new(ErrorKind::Other, msg)); 145 | } 146 | 147 | let results = resp.get_results(); 148 | assert_eq!(results.len(), 1); 149 | 150 | let result = &results[0]; 151 | if !result.ok { 152 | let msg = format!("{}: {}", key, result.err_message); 153 | return Err(Error::new(ErrorKind::Other, msg)); 154 | } 155 | 156 | let count_res = results[0].get_count(); 157 | println!("{}", count_res.n); 158 | Ok(()) 159 | } 160 | 161 | pub mod args { 162 | use clap::{App, Arg, SubCommand}; 163 | 164 | pub fn lindex() -> App<'static> { 165 | SubCommand::with_name("lindex") 166 | .about("List.Index: Query item at given index") 167 | .arg( 168 | Arg::with_name("key") 169 | .help("Key of list to query") 170 | .required(true), 171 | ) 172 | .arg( 173 | Arg::with_name("index") 174 | .help("Index of item to query") 175 | .required(true), 176 | ) 177 | } 178 | 179 | pub fn rpop() -> App<'static> { 180 | SubCommand::with_name("rpop") 181 | .about("List.RPop: Remove and return list end") 182 | .arg( 183 | Arg::with_name("key") 184 | .help("Key of list to pop") 185 | .required(true), 186 | ) 187 | } 188 | 189 | pub fn lpop() -> App<'static> { 190 | SubCommand::with_name("lpop") 191 | .about("List.LPop: Remove and return list head") 192 | .arg( 193 | Arg::with_name("key") 194 | .help("Key of list to pop") 195 | .required(true), 196 | ) 197 | } 198 | 199 | pub fn llen() -> App<'static> { 200 | SubCommand::with_name("llen") 201 | .about("List.Length: List metadata: length") 202 | .arg( 203 | Arg::with_name("key") 204 | .help("Key of list to query") 205 | .required(true), 206 | ) 207 | } 208 | 209 | pub fn lpush() -> App<'static> { 210 | SubCommand::with_name("lpush") 211 | .about("List.LPush: Store item at list head") 212 | .arg( 213 | Arg::with_name("key") 214 | .help("Key of list to store") 215 | .required(true), 216 | ) 217 | .arg( 218 | Arg::with_name("element") 219 | .help("Value of item to store") 220 | .required(true) 221 | .multiple(true), 222 | ) 223 | } 224 | 225 | pub fn lpushx() -> App<'static> { 226 | SubCommand::with_name("lpushx") 227 | .about("List.LPushX: Store item at list head, iff list exists") 228 | .arg( 229 | Arg::with_name("key") 230 | .help("Key of list to store") 231 | .required(true), 232 | ) 233 | .arg( 234 | Arg::with_name("element") 235 | .help("Value of item to store") 236 | .required(true) 237 | .multiple(true), 238 | ) 239 | } 240 | 241 | pub fn rpush() -> App<'static> { 242 | SubCommand::with_name("rpush") 243 | .about("List.RPush: Store item at list end") 244 | .arg( 245 | Arg::with_name("key") 246 | .help("Key of list to store") 247 | .required(true), 248 | ) 249 | .arg( 250 | Arg::with_name("element") 251 | .help("Value of item to store") 252 | .required(true) 253 | .multiple(true), 254 | ) 255 | } 256 | 257 | pub fn rpushx() -> App<'static> { 258 | SubCommand::with_name("rpushx") 259 | .about("List.RPushX: Store item at list end, iff list exists") 260 | .arg( 261 | Arg::with_name("key") 262 | .help("Key of list to store") 263 | .required(true), 264 | ) 265 | .arg( 266 | Arg::with_name("element") 267 | .help("Value of item to store") 268 | .required(true) 269 | .multiple(true), 270 | ) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /memds-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | 3 | use clap::value_t; 4 | 5 | use grpcio::*; 6 | use memds_proto::memds_api::OpType; 7 | use memds_proto::memds_api_grpc::MemdsClient; 8 | use std::io; 9 | use std::sync::Arc; 10 | 11 | mod keys; 12 | mod list; 13 | mod server; 14 | mod set; 15 | mod string; 16 | mod util; 17 | 18 | const APPNAME: &'static str = "memds-cli"; 19 | const VERSION: &'static str = env!("CARGO_PKG_VERSION"); 20 | const DEF_BIND_HOST: &'static str = "127.0.0.1"; 21 | 22 | fn main() -> io::Result<()> { 23 | // parse command line 24 | let cli_matches = clap::App::new(APPNAME) 25 | .version(VERSION) 26 | .about("Memds CLI") 27 | .subcommand(keys::args::del()) 28 | .subcommand(keys::args::dump()) 29 | .subcommand(keys::args::exists()) 30 | .subcommand(keys::args::rename()) 31 | .subcommand(keys::args::renamenx()) 32 | .subcommand(keys::args::restore()) 33 | .subcommand(keys::args::typ()) 34 | .subcommand(list::args::lindex()) 35 | .subcommand(list::args::llen()) 36 | .subcommand(list::args::lpop()) 37 | .subcommand(list::args::lpush()) 38 | .subcommand(list::args::lpushx()) 39 | .subcommand(list::args::rpop()) 40 | .subcommand(list::args::rpush()) 41 | .subcommand(list::args::rpushx()) 42 | .subcommand(server::args::bgsave()) 43 | .subcommand(server::args::dbsize()) 44 | .subcommand(server::args::flushall()) 45 | .subcommand(server::args::flushdb()) 46 | .subcommand(server::args::time()) 47 | .subcommand(set::args::sadd()) 48 | .subcommand(set::args::scard()) 49 | .subcommand(set::args::sdiff()) 50 | .subcommand(set::args::sdiffstore()) 51 | .subcommand(set::args::sinter()) 52 | .subcommand(set::args::sinterstore()) 53 | .subcommand(set::args::sismember()) 54 | .subcommand(set::args::smembers()) 55 | .subcommand(set::args::smove()) 56 | .subcommand(set::args::srem()) 57 | .subcommand(set::args::sunion()) 58 | .subcommand(set::args::sunionstore()) 59 | .subcommand(string::args::append()) 60 | .subcommand(string::args::decr()) 61 | .subcommand(string::args::decrby()) 62 | .subcommand(string::args::get()) 63 | .subcommand(string::args::getrange()) 64 | .subcommand(string::args::getset()) 65 | .subcommand(string::args::incr()) 66 | .subcommand(string::args::incrby()) 67 | .subcommand(string::args::set()) 68 | .subcommand(string::args::setnx()) 69 | .subcommand(string::args::strlen()) 70 | .get_matches(); 71 | 72 | let endpoint = format!("{}:{}", DEF_BIND_HOST, memds_proto::DEF_PORT); 73 | 74 | let env = Arc::new(Environment::new(2)); 75 | let channel = ChannelBuilder::new(env).connect(&endpoint); 76 | let client = MemdsClient::new(channel); 77 | 78 | match cli_matches.subcommand() { 79 | Some(("append", matches)) => { 80 | let key = matches.value_of("key").unwrap(); 81 | let value = matches.value_of("value").unwrap(); 82 | string::set(&client, key, value, false, true, false) 83 | } 84 | Some(("bgsave", _matches)) => server::bgsave(&client), 85 | Some(("dbsize", _matches)) => server::dbsize(&client), 86 | Some(("decr", matches)) => { 87 | let key = matches.value_of("key").unwrap(); 88 | string::incrdecr(&client, OpType::STR_DECR, key, 1) 89 | } 90 | Some(("decrby", matches)) => { 91 | let key = matches.value_of("key").unwrap(); 92 | let n = value_t!(matches, "n", i64).unwrap_or(1); 93 | string::incrdecr(&client, OpType::STR_DECRBY, key, n) 94 | } 95 | Some(("del", matches)) => { 96 | let keys: Vec<_> = matches.values_of("key").unwrap().collect(); 97 | keys::del_exist(&client, &keys, true) 98 | } 99 | Some(("dump", matches)) => { 100 | let key = matches.value_of("key").unwrap(); 101 | keys::dump(&client, key) 102 | } 103 | Some(("exists", matches)) => { 104 | let keys: Vec<_> = matches.values_of("key").unwrap().collect(); 105 | keys::del_exist(&client, &keys, false) 106 | } 107 | Some(("flushall", _matches)) => server::flush(&client, true), 108 | Some(("flushdb", _matches)) => server::flush(&client, false), 109 | Some(("incr", matches)) => { 110 | let key = matches.value_of("key").unwrap(); 111 | string::incrdecr(&client, OpType::STR_INCR, key, 1) 112 | } 113 | Some(("incrby", matches)) => { 114 | let key = matches.value_of("key").unwrap(); 115 | let n = value_t!(matches, "n", i64).unwrap_or(1); 116 | string::incrdecr(&client, OpType::STR_INCRBY, key, n) 117 | } 118 | Some(("get", matches)) => { 119 | let key = matches.value_of("key").unwrap(); 120 | string::get(&client, key) 121 | } 122 | Some(("getrange", matches)) => { 123 | let key = matches.value_of("key").unwrap(); 124 | let start = value_t!(matches, "start", i32).unwrap_or(0); 125 | let end = value_t!(matches, "end", i32).unwrap_or(0); 126 | string::getrange(&client, key, start, end) 127 | } 128 | Some(("getset", matches)) => { 129 | let key = matches.value_of("key").unwrap(); 130 | let value = matches.value_of("value").unwrap(); 131 | string::set(&client, key, value, true, false, false) 132 | } 133 | Some(("lindex", matches)) => { 134 | let key = matches.value_of("key").unwrap(); 135 | let n = value_t!(matches, "index", i32).unwrap(); 136 | list::lindex(&client, key, n) 137 | } 138 | Some(("llen", matches)) => { 139 | let key = matches.value_of("key").unwrap(); 140 | list::llen(&client, key) 141 | } 142 | Some(("lpop", matches)) => { 143 | let key = matches.value_of("key").unwrap(); 144 | list::pop(&client, key, true) 145 | } 146 | Some(("lpush", matches)) => { 147 | let key = matches.value_of("key").unwrap(); 148 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 149 | list::push(&client, key, &elems, true, false) 150 | } 151 | Some(("lpushx", matches)) => { 152 | let key = matches.value_of("key").unwrap(); 153 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 154 | list::push(&client, key, &elems, true, true) 155 | } 156 | Some(("rename", matches)) => { 157 | let old_key = matches.value_of("old_key").unwrap(); 158 | let new_key = matches.value_of("new_key").unwrap(); 159 | keys::rename(&client, old_key, new_key, false) 160 | } 161 | Some(("renamenx", matches)) => { 162 | let old_key = matches.value_of("old_key").unwrap(); 163 | let new_key = matches.value_of("new_key").unwrap(); 164 | keys::rename(&client, old_key, new_key, true) 165 | } 166 | Some(("restore", matches)) => { 167 | let key = matches.value_of("key"); 168 | let restore_fn = matches.value_of("file").unwrap(); 169 | keys::restore(&client, key, restore_fn) 170 | } 171 | Some(("rpop", matches)) => { 172 | let key = matches.value_of("key").unwrap(); 173 | list::pop(&client, key, false) 174 | } 175 | Some(("rpush", matches)) => { 176 | let key = matches.value_of("key").unwrap(); 177 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 178 | list::push(&client, key, &elems, false, false) 179 | } 180 | Some(("rpushx", matches)) => { 181 | let key = matches.value_of("key").unwrap(); 182 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 183 | list::push(&client, key, &elems, false, true) 184 | } 185 | Some(("sadd", matches)) => { 186 | let key = matches.value_of("key").unwrap(); 187 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 188 | set::add_del(&client, key, &elems, false) 189 | } 190 | Some(("scard", matches)) => { 191 | let key = matches.value_of("key").unwrap(); 192 | set::info(&client, key) 193 | } 194 | Some(("sdiff", matches)) => { 195 | let key1 = matches.value_of("key1").unwrap(); 196 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 197 | keys.insert(0, key1); 198 | let empty = String::from(""); 199 | set::cmpstore(&client, &keys, &empty, OpType::SET_DIFF) 200 | } 201 | Some(("sdiffstore", matches)) => { 202 | let store_key = matches.value_of("destination").unwrap(); 203 | let key1 = matches.value_of("key1").unwrap(); 204 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 205 | keys.insert(0, key1); 206 | set::cmpstore(&client, &keys, &store_key, OpType::SET_DIFF) 207 | } 208 | Some(("sinter", matches)) => { 209 | let key1 = matches.value_of("key1").unwrap(); 210 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 211 | keys.insert(0, key1); 212 | let empty = String::from(""); 213 | set::cmpstore(&client, &keys, &empty, OpType::SET_INTERSECT) 214 | } 215 | Some(("sinterstore", matches)) => { 216 | let store_key = matches.value_of("destination").unwrap(); 217 | let key1 = matches.value_of("key1").unwrap(); 218 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 219 | keys.insert(0, key1); 220 | set::cmpstore(&client, &keys, &store_key, OpType::SET_INTERSECT) 221 | } 222 | Some(("sismember", matches)) => { 223 | let key = matches.value_of("key").unwrap(); 224 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 225 | set::is_member(&client, key, &elems) 226 | } 227 | Some(("smembers", matches)) => { 228 | let key = matches.value_of("key").unwrap(); 229 | set::members(&client, key) 230 | } 231 | Some(("srem", matches)) => { 232 | let key = matches.value_of("key").unwrap(); 233 | let elems: Vec<_> = matches.values_of("element").unwrap().collect(); 234 | set::add_del(&client, key, &elems, true) 235 | } 236 | Some(("set", matches)) => { 237 | let key = matches.value_of("key").unwrap(); 238 | let value = matches.value_of("value").unwrap(); 239 | string::set(&client, key, value, false, false, false) 240 | } 241 | Some(("setnx", matches)) => { 242 | let key = matches.value_of("key").unwrap(); 243 | let value = matches.value_of("value").unwrap(); 244 | string::set(&client, key, value, false, false, true) 245 | } 246 | Some(("smove", matches)) => { 247 | let src_key = matches.value_of("src_key").unwrap(); 248 | let dest_key = matches.value_of("dest_key").unwrap(); 249 | let member = matches.value_of("member").unwrap(); 250 | set::mov(&client, src_key, dest_key, member) 251 | } 252 | Some(("strlen", matches)) => { 253 | let key = matches.value_of("key").unwrap(); 254 | string::strlen(&client, key) 255 | } 256 | Some(("sunion", matches)) => { 257 | let key1 = matches.value_of("key1").unwrap(); 258 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 259 | keys.insert(0, key1); 260 | let empty = String::from(""); 261 | set::cmpstore(&client, &keys, &empty, OpType::SET_UNION) 262 | } 263 | Some(("sunionstore", matches)) => { 264 | let store_key = matches.value_of("destination").unwrap(); 265 | let key1 = matches.value_of("key1").unwrap(); 266 | let mut keys: Vec<_> = matches.values_of("keys").unwrap().collect(); 267 | keys.insert(0, key1); 268 | set::cmpstore(&client, &keys, &store_key, OpType::SET_UNION) 269 | } 270 | Some(("time", _matches)) => server::time(&client), 271 | Some(("type", matches)) => { 272 | let key = matches.value_of("key").unwrap(); 273 | keys::typ(&client, key) 274 | } 275 | Some((_, _)) | None => { 276 | println!("No subcommand specified. Run with --help for help."); 277 | Ok(()) 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /memds-cli/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Error, ErrorKind}; 2 | 3 | use memds_proto::memds_api::*; 4 | use memds_proto::memds_api_grpc::MemdsClient; 5 | 6 | use crate::util; 7 | 8 | pub fn bgsave(client: &MemdsClient) -> io::Result<()> { 9 | let mut op = Operation::new(); 10 | op.otype = OpType::SRV_BGSAVE; 11 | 12 | let mut req = RequestMsg::new(); 13 | req.ops.push(op); 14 | 15 | let resp = util::rpc_exec(&client, &req)?; 16 | 17 | if !resp.ok { 18 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 19 | return Err(Error::new(ErrorKind::Other, msg)); 20 | } 21 | 22 | let results = resp.get_results(); 23 | assert!(results.len() == 1); 24 | 25 | let result = &results[0]; 26 | if !result.ok { 27 | let msg = format!("dbsize: {}", result.err_message); 28 | return Err(Error::new(ErrorKind::Other, msg)); 29 | } 30 | 31 | println!("ok"); 32 | Ok(()) 33 | } 34 | 35 | pub fn dbsize(client: &MemdsClient) -> io::Result<()> { 36 | let mut op = Operation::new(); 37 | op.otype = OpType::SRV_DBSIZE; 38 | 39 | let mut req = RequestMsg::new(); 40 | req.ops.push(op); 41 | 42 | let resp = util::rpc_exec(&client, &req)?; 43 | 44 | if !resp.ok { 45 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 46 | return Err(Error::new(ErrorKind::Other, msg)); 47 | } 48 | 49 | let results = resp.get_results(); 50 | assert!(results.len() == 1); 51 | 52 | let result = &results[0]; 53 | if !result.ok { 54 | let msg = format!("dbsize: {}", result.err_message); 55 | return Err(Error::new(ErrorKind::Other, msg)); 56 | } 57 | 58 | let count_res = results[0].get_count(); 59 | println!("{}", count_res.n); 60 | Ok(()) 61 | } 62 | 63 | pub fn flush(client: &MemdsClient, flush_all: bool) -> io::Result<()> { 64 | let mut op = Operation::new(); 65 | op.otype = match flush_all { 66 | true => OpType::SRV_FLUSHALL, 67 | false => OpType::SRV_FLUSHDB, 68 | }; 69 | 70 | let mut req = RequestMsg::new(); 71 | req.ops.push(op); 72 | 73 | let resp = util::rpc_exec(&client, &req)?; 74 | 75 | if !resp.ok { 76 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 77 | return Err(Error::new(ErrorKind::Other, msg)); 78 | } 79 | 80 | let results = resp.get_results(); 81 | assert!(results.len() == 1); 82 | 83 | let result = &results[0]; 84 | if !result.ok { 85 | let msg = format!("flush: {}", result.err_message); 86 | return Err(Error::new(ErrorKind::Other, msg)); 87 | } 88 | 89 | println!("ok"); 90 | Ok(()) 91 | } 92 | 93 | pub fn time(client: &MemdsClient) -> io::Result<()> { 94 | let mut op = Operation::new(); 95 | op.otype = OpType::SRV_TIME; 96 | 97 | let mut req = RequestMsg::new(); 98 | req.ops.push(op); 99 | 100 | let resp = util::rpc_exec(&client, &req)?; 101 | 102 | if !resp.ok { 103 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 104 | return Err(Error::new(ErrorKind::Other, msg)); 105 | } 106 | 107 | let results = resp.get_results(); 108 | assert!(results.len() == 1); 109 | 110 | let result = &results[0]; 111 | if !result.ok { 112 | let msg = format!("server-time: {}", result.err_message); 113 | return Err(Error::new(ErrorKind::Other, msg)); 114 | } 115 | 116 | let time_res = results[0].get_srv_time(); 117 | println!("{:?}", time_res); 118 | Ok(()) 119 | } 120 | 121 | pub mod args { 122 | use clap::{App, SubCommand}; 123 | 124 | pub fn bgsave() -> App<'static> { 125 | SubCommand::with_name("bgsave").about("Server.BGSave: Dump entire database to filesystem") 126 | } 127 | 128 | pub fn dbsize() -> App<'static> { 129 | SubCommand::with_name("dbsize") 130 | .about("Server.DBSize: Retrieve item count of current database") 131 | } 132 | 133 | pub fn flushdb() -> App<'static> { 134 | SubCommand::with_name("flushdb").about("Server.FlushDB: Empty current database") 135 | } 136 | 137 | pub fn flushall() -> App<'static> { 138 | SubCommand::with_name("flushall").about("Server.FlushAll: Empty all databases") 139 | } 140 | 141 | pub fn time() -> App<'static> { 142 | SubCommand::with_name("time").about("Server.Time: Retrieve server time") 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /memds-cli/src/set.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Error, ErrorKind, Write}; 2 | 3 | use memds_proto::memds_api::*; 4 | use memds_proto::memds_api_grpc::MemdsClient; 5 | 6 | use crate::util; 7 | 8 | pub fn info(client: &MemdsClient, key: &str) -> io::Result<()> { 9 | let mut key_req = KeyOp::new(); 10 | key_req.set_key(key.as_bytes().to_vec()); 11 | 12 | let mut op = Operation::new(); 13 | op.otype = OpType::SET_INFO; 14 | op.set_key(key_req); 15 | 16 | let mut req = RequestMsg::new(); 17 | req.ops.push(op); 18 | 19 | let resp = util::rpc_exec(&client, &req)?; 20 | 21 | if !resp.ok { 22 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 23 | return Err(Error::new(ErrorKind::Other, msg)); 24 | } 25 | 26 | let results = resp.get_results(); 27 | assert!(results.len() == 1); 28 | 29 | let result = &results[0]; 30 | if !result.ok { 31 | let msg = format!("{}: {}", key, result.err_message); 32 | return Err(Error::new(ErrorKind::Other, msg)); 33 | } 34 | 35 | let info_res = results[0].get_set_info(); 36 | println!("{}", info_res.length); 37 | Ok(()) 38 | } 39 | 40 | pub fn add_del( 41 | client: &MemdsClient, 42 | key: &str, 43 | elems: &Vec<&str>, 44 | do_delete: bool, 45 | ) -> io::Result<()> { 46 | let mut op_req = KeyedListOp::new(); 47 | op_req.set_key(key.as_bytes().to_vec()); 48 | for elem in elems.iter() { 49 | op_req.elements.push(elem.as_bytes().to_vec()); 50 | } 51 | 52 | let mut op = Operation::new(); 53 | op.otype = match do_delete { 54 | true => OpType::SET_DEL, 55 | false => OpType::SET_ADD, 56 | }; 57 | op.set_keyed_list(op_req); 58 | 59 | let mut req = RequestMsg::new(); 60 | req.ops.push(op); 61 | 62 | let resp = util::rpc_exec(&client, &req)?; 63 | 64 | if !resp.ok { 65 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 66 | return Err(Error::new(ErrorKind::Other, msg)); 67 | } 68 | 69 | let results = resp.get_results(); 70 | assert_eq!(results.len(), 1); 71 | 72 | let result = &results[0]; 73 | if !result.ok { 74 | let msg = format!("{}: {}", key, result.err_message); 75 | return Err(Error::new(ErrorKind::Other, msg)); 76 | } 77 | 78 | let count_res = results[0].get_count(); 79 | println!("{}", count_res.n); 80 | Ok(()) 81 | } 82 | 83 | pub fn cmpstore( 84 | client: &MemdsClient, 85 | keys: &Vec<&str>, 86 | store_key: &str, 87 | otype: OpType, 88 | ) -> io::Result<()> { 89 | let mut op_req = CmpStoreOp::new(); 90 | for key in keys.iter() { 91 | op_req.keys.push(key.as_bytes().to_vec()); 92 | } 93 | let have_store_key = { 94 | if store_key.len() > 0 { 95 | op_req.set_store_key(store_key.as_bytes().to_vec()); 96 | true 97 | } else { 98 | false 99 | } 100 | }; 101 | 102 | let mut op = Operation::new(); 103 | op.otype = otype; 104 | op.set_cmp_stor(op_req); 105 | 106 | let mut req = RequestMsg::new(); 107 | req.ops.push(op); 108 | 109 | let resp = util::rpc_exec(&client, &req)?; 110 | 111 | if !resp.ok { 112 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 113 | return Err(Error::new(ErrorKind::Other, msg)); 114 | } 115 | 116 | let results = resp.get_results(); 117 | assert_eq!(results.len(), 1); 118 | 119 | let result = &results[0]; 120 | if !result.ok { 121 | let msg = format!("{:?}: {}", keys, result.err_message); 122 | return Err(Error::new(ErrorKind::Other, msg)); 123 | } 124 | 125 | if have_store_key { 126 | let count_res = results[0].get_count(); 127 | println!("{}", count_res.n); 128 | } else { 129 | let list_res = results[0].get_list(); 130 | for element in list_res.elements.iter() { 131 | io::stdout().write_all(element)?; 132 | io::stdout().write_all(b"\n")?; 133 | } 134 | } 135 | 136 | Ok(()) 137 | } 138 | 139 | pub fn is_member(client: &MemdsClient, key: &str, elems: &Vec<&str>) -> io::Result<()> { 140 | let mut op_req = KeyedListOp::new(); 141 | op_req.set_key(key.as_bytes().to_vec()); 142 | for elem in elems.iter() { 143 | op_req.elements.push(elem.as_bytes().to_vec()); 144 | } 145 | 146 | let mut op = Operation::new(); 147 | op.otype = OpType::SET_ISMEMBER; 148 | op.set_keyed_list(op_req); 149 | 150 | let mut req = RequestMsg::new(); 151 | req.ops.push(op); 152 | 153 | let resp = util::rpc_exec(&client, &req)?; 154 | 155 | if !resp.ok { 156 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 157 | return Err(Error::new(ErrorKind::Other, msg)); 158 | } 159 | 160 | let results = resp.get_results(); 161 | assert_eq!(results.len(), 1); 162 | 163 | let result = &results[0]; 164 | if !result.ok { 165 | let msg = format!("{}: {}", key, result.err_message); 166 | return Err(Error::new(ErrorKind::Other, msg)); 167 | } 168 | 169 | let count_res = results[0].get_count(); 170 | println!("{}", count_res.n); 171 | Ok(()) 172 | } 173 | 174 | pub fn members(client: &MemdsClient, key: &str) -> io::Result<()> { 175 | let mut key_req = KeyOp::new(); 176 | key_req.set_key(key.as_bytes().to_vec()); 177 | 178 | let mut op = Operation::new(); 179 | op.otype = OpType::SET_MEMBERS; 180 | op.set_key(key_req); 181 | 182 | let mut req = RequestMsg::new(); 183 | req.ops.push(op); 184 | 185 | let resp = util::rpc_exec(&client, &req)?; 186 | 187 | if !resp.ok { 188 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 189 | return Err(Error::new(ErrorKind::Other, msg)); 190 | } 191 | 192 | let results = resp.get_results(); 193 | assert!(results.len() == 1); 194 | 195 | let result = &results[0]; 196 | if !result.ok { 197 | let msg = format!("{}: {}", key, result.err_message); 198 | return Err(Error::new(ErrorKind::Other, msg)); 199 | } 200 | 201 | let list_res = results[0].get_list(); 202 | for element in list_res.elements.iter() { 203 | io::stdout().write_all(element)?; 204 | io::stdout().write_all(b"\n")?; 205 | } 206 | Ok(()) 207 | } 208 | 209 | pub fn mov(client: &MemdsClient, src_key: &str, dest_key: &str, member: &str) -> io::Result<()> { 210 | let mut op_req = SetMoveOp::new(); 211 | op_req.set_src_key(src_key.as_bytes().to_vec()); 212 | op_req.set_dest_key(dest_key.as_bytes().to_vec()); 213 | op_req.set_member(member.as_bytes().to_vec()); 214 | 215 | let mut op = Operation::new(); 216 | op.otype = OpType::SET_MOVE; 217 | op.set_set_move(op_req); 218 | 219 | let mut req = RequestMsg::new(); 220 | req.ops.push(op); 221 | 222 | let resp = util::rpc_exec(&client, &req)?; 223 | 224 | if !resp.ok { 225 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 226 | return Err(Error::new(ErrorKind::Other, msg)); 227 | } 228 | 229 | let results = resp.get_results(); 230 | assert_eq!(results.len(), 1); 231 | 232 | let result = &results[0]; 233 | if !result.ok { 234 | let msg = format!("set-move: {}", result.err_message); 235 | return Err(Error::new(ErrorKind::Other, msg)); 236 | } 237 | 238 | let count_res = results[0].get_count(); 239 | println!("{}", count_res.n); 240 | 241 | Ok(()) 242 | } 243 | 244 | pub mod args { 245 | use clap::{App, Arg, SubCommand}; 246 | 247 | pub fn sadd() -> App<'static> { 248 | SubCommand::with_name("sadd") 249 | .about("Set.Add: Store items in set") 250 | .arg( 251 | Arg::with_name("key") 252 | .help("Key of set to store") 253 | .required(true), 254 | ) 255 | .arg( 256 | Arg::with_name("element") 257 | .help("Value of item to store") 258 | .required(true) 259 | .multiple(true), 260 | ) 261 | } 262 | 263 | pub fn scard() -> App<'static> { 264 | SubCommand::with_name("scard") 265 | .about("Set.Card: Set metadata") 266 | .arg( 267 | Arg::with_name("key") 268 | .help("Key of set to query") 269 | .required(true), 270 | ) 271 | } 272 | 273 | pub fn sdiff() -> App<'static> { 274 | SubCommand::with_name("sdiff") 275 | .about("Set.Diff: Diff sets") 276 | .arg( 277 | Arg::with_name("key1") 278 | .help("1st Set for difference") 279 | .required(true), 280 | ) 281 | .arg( 282 | Arg::with_name("keys") 283 | .help("List of subtractive sets") 284 | .multiple(true), 285 | ) 286 | } 287 | 288 | pub fn sdiffstore() -> App<'static> { 289 | SubCommand::with_name("sdiffstore") 290 | .about("Set.DiffStore: Diff sets, and store result") 291 | .arg( 292 | Arg::with_name("destination") 293 | .help("Set receiving difference results") 294 | .required(true), 295 | ) 296 | .arg( 297 | Arg::with_name("key1") 298 | .help("1st Set for difference") 299 | .required(true), 300 | ) 301 | .arg( 302 | Arg::with_name("keys") 303 | .help("List of subtractive sets") 304 | .multiple(true), 305 | ) 306 | } 307 | 308 | pub fn sinter() -> App<'static> { 309 | SubCommand::with_name("sinter") 310 | .about("Set.Intersect: Intersect sets") 311 | .arg( 312 | Arg::with_name("key1") 313 | .help("1st Set for intersect") 314 | .required(true), 315 | ) 316 | .arg( 317 | Arg::with_name("keys") 318 | .help("List of intersected sets") 319 | .multiple(true), 320 | ) 321 | } 322 | 323 | pub fn sinterstore() -> App<'static> { 324 | SubCommand::with_name("sinterstore") 325 | .about("Set.IntersectStore: Intersect sets, and store result") 326 | .arg( 327 | Arg::with_name("destination") 328 | .help("Set receiving inter results") 329 | .required(true), 330 | ) 331 | .arg( 332 | Arg::with_name("key1") 333 | .help("1st Set for intersect") 334 | .required(true), 335 | ) 336 | .arg( 337 | Arg::with_name("keys") 338 | .help("List of intersected sets") 339 | .multiple(true), 340 | ) 341 | } 342 | 343 | pub fn sismember() -> App<'static> { 344 | SubCommand::with_name("sismember") 345 | .about("Set.IsMember: Test existence of items in a set") 346 | .arg( 347 | Arg::with_name("key") 348 | .help("Key of set to query") 349 | .required(true), 350 | ) 351 | .arg( 352 | Arg::with_name("element") 353 | .help("Value of item to test") 354 | .required(true) 355 | .multiple(true), 356 | ) 357 | } 358 | 359 | pub fn smembers() -> App<'static> { 360 | SubCommand::with_name("smembers") 361 | .about("Set.Members: Query all Set members") 362 | .arg( 363 | Arg::with_name("key") 364 | .help("Key of set to query") 365 | .required(true), 366 | ) 367 | } 368 | 369 | pub fn smove() -> App<'static> { 370 | SubCommand::with_name("smove") 371 | .about("Set.Move: Move member between sets") 372 | .arg(Arg::with_name("src_key").help("Source set").required(true)) 373 | .arg( 374 | Arg::with_name("dest_key") 375 | .help("Destination Set") 376 | .required(true), 377 | ) 378 | .arg( 379 | Arg::with_name("member") 380 | .help("Set member to move") 381 | .required(true), 382 | ) 383 | } 384 | 385 | pub fn srem() -> App<'static> { 386 | SubCommand::with_name("srem") 387 | .about("Set.Remove: Remove items from set") 388 | .arg( 389 | Arg::with_name("key") 390 | .help("Key of set to update") 391 | .required(true), 392 | ) 393 | .arg( 394 | Arg::with_name("element") 395 | .help("Value of item to remove") 396 | .required(true) 397 | .multiple(true), 398 | ) 399 | } 400 | 401 | pub fn sunion() -> App<'static> { 402 | SubCommand::with_name("sunion") 403 | .about("Set.Union: Union sets") 404 | .arg( 405 | Arg::with_name("key1") 406 | .help("1st Set for union") 407 | .required(true), 408 | ) 409 | .arg( 410 | Arg::with_name("keys") 411 | .help("List of unioned sets") 412 | .multiple(true), 413 | ) 414 | } 415 | 416 | pub fn sunionstore() -> App<'static> { 417 | SubCommand::with_name("sunionstore") 418 | .about("Set.UnionStore: Union sets, and store result") 419 | .arg( 420 | Arg::with_name("destination") 421 | .help("Set receiving union results") 422 | .required(true), 423 | ) 424 | .arg( 425 | Arg::with_name("key1") 426 | .help("1st Set for union") 427 | .required(true), 428 | ) 429 | .arg( 430 | Arg::with_name("keys") 431 | .help("List of unioned sets") 432 | .multiple(true), 433 | ) 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /memds-cli/src/string.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Error, ErrorKind, Write}; 2 | 3 | use memds_proto::memds_api::*; 4 | use memds_proto::memds_api_grpc::MemdsClient; 5 | 6 | use crate::util; 7 | 8 | pub fn get(client: &MemdsClient, key: &str) -> io::Result<()> { 9 | let mut get_req = StrGetOp::new(); 10 | get_req.set_key(key.as_bytes().to_vec()); 11 | 12 | let mut op = Operation::new(); 13 | op.otype = OpType::STR_GET; 14 | op.set_get(get_req); 15 | 16 | let mut req = RequestMsg::new(); 17 | req.ops.push(op); 18 | 19 | let resp = util::rpc_exec(&client, &req)?; 20 | 21 | if !resp.ok { 22 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 23 | return Err(Error::new(ErrorKind::Other, msg)); 24 | } 25 | 26 | let results = resp.get_results(); 27 | assert!(results.len() == 1); 28 | 29 | let result = &results[0]; 30 | if result.ok { 31 | let get_res = results[0].get_get(); 32 | let value = get_res.get_value(); 33 | io::stdout().write_all(value)?; 34 | Ok(()) 35 | } else { 36 | let msg = format!("{}: {}", key, result.err_message); 37 | Err(Error::new(ErrorKind::Other, msg)) 38 | } 39 | } 40 | 41 | pub fn getrange(client: &MemdsClient, key: &str, start: i32, end: i32) -> io::Result<()> { 42 | let mut get_req = StrGetOp::new(); 43 | get_req.set_key(key.as_bytes().to_vec()); 44 | get_req.substr = true; 45 | get_req.range_start = start; 46 | get_req.range_end = end; 47 | 48 | let mut op = Operation::new(); 49 | op.otype = OpType::STR_GETRANGE; 50 | op.set_get(get_req); 51 | 52 | let mut req = RequestMsg::new(); 53 | req.ops.push(op); 54 | 55 | let resp = util::rpc_exec(&client, &req)?; 56 | 57 | if !resp.ok { 58 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 59 | return Err(Error::new(ErrorKind::Other, msg)); 60 | } 61 | 62 | let results = resp.get_results(); 63 | assert!(results.len() == 1); 64 | 65 | let result = &results[0]; 66 | if result.ok { 67 | let get_res = results[0].get_get(); 68 | let value = get_res.get_value(); 69 | io::stdout().write_all(value)?; 70 | Ok(()) 71 | } else { 72 | let msg = format!("{}: {}", key, result.err_message); 73 | Err(Error::new(ErrorKind::Other, msg)) 74 | } 75 | } 76 | 77 | pub fn set( 78 | client: &MemdsClient, 79 | key: &str, 80 | value: &str, 81 | return_old: bool, 82 | append: bool, 83 | create_excl: bool, 84 | ) -> io::Result<()> { 85 | let mut set_req = StrSetOp::new(); 86 | set_req.set_key(key.as_bytes().to_vec()); 87 | set_req.set_value(value.as_bytes().to_vec()); 88 | set_req.return_old = return_old; 89 | set_req.create_excl = create_excl; 90 | 91 | let mut op = Operation::new(); 92 | op.otype = match append { 93 | false => OpType::STR_SET, 94 | true => OpType::STR_APPEND, 95 | }; 96 | op.set_set(set_req); 97 | 98 | let mut req = RequestMsg::new(); 99 | req.ops.push(op); 100 | 101 | let resp = util::rpc_exec(&client, &req)?; 102 | 103 | if !resp.ok { 104 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 105 | return Err(Error::new(ErrorKind::Other, msg)); 106 | } 107 | 108 | let results = resp.get_results(); 109 | assert!(results.len() == 1); 110 | 111 | let result = &results[0]; 112 | if result.ok { 113 | if return_old { 114 | let set_res = results[0].get_set(); 115 | io::stdout().write_all(set_res.get_old_value())?; 116 | } else { 117 | io::stdout().write_all(b"ok\n")?; 118 | } 119 | Ok(()) 120 | } else { 121 | let msg = format!("{}: {}", key, result.err_message); 122 | Err(Error::new(ErrorKind::Other, msg)) 123 | } 124 | } 125 | 126 | pub fn incrdecr(client: &MemdsClient, otype: OpType, key: &str, n: i64) -> io::Result<()> { 127 | let mut num_req = NumOp::new(); 128 | num_req.set_key(key.as_bytes().to_vec()); 129 | num_req.n = n; 130 | 131 | let mut op = Operation::new(); 132 | op.otype = otype; 133 | op.set_num(num_req); 134 | 135 | let mut req = RequestMsg::new(); 136 | req.ops.push(op); 137 | 138 | let resp = util::rpc_exec(&client, &req)?; 139 | 140 | if !resp.ok { 141 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 142 | return Err(Error::new(ErrorKind::Other, msg)); 143 | } 144 | 145 | let results = resp.get_results(); 146 | assert!(results.len() == 1); 147 | 148 | let result = &results[0]; 149 | if result.ok { 150 | let num_res = results[0].get_num(); 151 | let old_value = num_res.get_old_value(); 152 | println!("{}", old_value); 153 | Ok(()) 154 | } else { 155 | let msg = format!("{}: {}", key, result.err_message); 156 | Err(Error::new(ErrorKind::Other, msg)) 157 | } 158 | } 159 | 160 | pub fn strlen(client: &MemdsClient, key: &str) -> io::Result<()> { 161 | let mut get_req = StrGetOp::new(); 162 | get_req.set_key(key.as_bytes().to_vec()); 163 | get_req.want_length = true; 164 | 165 | let mut op = Operation::new(); 166 | op.otype = OpType::STR_GET; 167 | op.set_get(get_req); 168 | 169 | let mut req = RequestMsg::new(); 170 | req.ops.push(op); 171 | 172 | let resp = util::rpc_exec(&client, &req)?; 173 | 174 | if !resp.ok { 175 | let msg = format!("Batch failure {}: {}", resp.err_code, resp.err_message); 176 | return Err(Error::new(ErrorKind::Other, msg)); 177 | } 178 | 179 | let results = resp.get_results(); 180 | assert!(results.len() == 1); 181 | 182 | let result = &results[0]; 183 | if result.ok { 184 | let get_res = results[0].get_get(); 185 | let value_length = get_res.value_length; 186 | println!("{}", value_length); 187 | Ok(()) 188 | } else { 189 | let msg = format!("{}: {}", key, result.err_message); 190 | Err(Error::new(ErrorKind::Other, msg)) 191 | } 192 | } 193 | 194 | pub mod args { 195 | use clap::{App, Arg, SubCommand}; 196 | 197 | pub fn append() -> App<'static> { 198 | SubCommand::with_name("append") 199 | .about("String.Append: Append to item") 200 | .arg( 201 | Arg::with_name("key") 202 | .help("Key of item to store") 203 | .required(true), 204 | ) 205 | .arg( 206 | Arg::with_name("value") 207 | .help("Value of item to append") 208 | .required(true), 209 | ) 210 | } 211 | 212 | pub fn decr() -> App<'static> { 213 | SubCommand::with_name("decr") 214 | .about("String.Decr: Decrement numeric item by 1") 215 | .arg( 216 | Arg::with_name("key") 217 | .help("Key of item to update") 218 | .required(true), 219 | ) 220 | } 221 | 222 | pub fn decrby() -> App<'static> { 223 | SubCommand::with_name("decrby") 224 | .about("String.DecrBy: Decrement numeric item") 225 | .arg( 226 | Arg::with_name("key") 227 | .help("Key of item to update") 228 | .required(true), 229 | ) 230 | .arg( 231 | Arg::with_name("n") 232 | .help("Numeric delta for operation (default: 1, if invalid number provided)") 233 | .required(true), 234 | ) 235 | } 236 | 237 | pub fn get() -> App<'static> { 238 | SubCommand::with_name("get") 239 | .about("String.Get: Retrieve item") 240 | .arg( 241 | Arg::with_name("key") 242 | .help("Key of item to retrieve") 243 | .required(true), 244 | ) 245 | } 246 | 247 | pub fn getrange() -> App<'static> { 248 | SubCommand::with_name("getrange") 249 | .about("String.GetRange: Retrieve subset of item") 250 | .arg( 251 | Arg::with_name("key") 252 | .help("Key of item to retrieve") 253 | .required(true), 254 | ) 255 | .arg( 256 | Arg::with_name("start") 257 | .help("Start position within string (Negative offsets measure from end of string)") 258 | .required(true), 259 | ) 260 | .arg( 261 | Arg::with_name("end") 262 | .help("End position within string (Negative offsets measure from end of string)") 263 | .required(true), 264 | ) 265 | } 266 | 267 | pub fn getset() -> App<'static> { 268 | SubCommand::with_name("getset") 269 | .about("String.GetSet: Store item, return old value") 270 | .arg( 271 | Arg::with_name("key") 272 | .help("Key of item to retrieve+store") 273 | .required(true), 274 | ) 275 | .arg( 276 | Arg::with_name("value") 277 | .help("Value of item to store") 278 | .required(true), 279 | ) 280 | } 281 | 282 | pub fn incr() -> App<'static> { 283 | SubCommand::with_name("incr") 284 | .about("String.Incr: Increment numeric item by 1") 285 | .arg( 286 | Arg::with_name("key") 287 | .help("Key of item to update") 288 | .required(true), 289 | ) 290 | } 291 | 292 | pub fn incrby() -> App<'static> { 293 | SubCommand::with_name("incrby") 294 | .about("String.IncrBy: Increment numeric item") 295 | .arg( 296 | Arg::with_name("key") 297 | .help("Key of item to update") 298 | .required(true), 299 | ) 300 | .arg( 301 | Arg::with_name("n") 302 | .help("Numeric delta for operation (default: 1, if invalid number provided)") 303 | .required(true), 304 | ) 305 | } 306 | 307 | pub fn set() -> App<'static> { 308 | SubCommand::with_name("set") 309 | .about("String.Set: Store item") 310 | .arg( 311 | Arg::with_name("key") 312 | .help("Key of item to store") 313 | .required(true), 314 | ) 315 | .arg( 316 | Arg::with_name("value") 317 | .help("Value of item to store") 318 | .required(true), 319 | ) 320 | } 321 | 322 | pub fn setnx() -> App<'static> { 323 | SubCommand::with_name("setnx") 324 | .about("String.SetNX: Store item, if key does not exist") 325 | .arg( 326 | Arg::with_name("key") 327 | .help("Key of item to store") 328 | .required(true), 329 | ) 330 | .arg( 331 | Arg::with_name("value") 332 | .help("Value of item to store") 333 | .required(true), 334 | ) 335 | } 336 | 337 | pub fn strlen() -> App<'static> { 338 | SubCommand::with_name("strlen") 339 | .about("String.Strlen: Retrieve item length") 340 | .arg( 341 | Arg::with_name("key") 342 | .help("Key of item whose length shall be retrieved") 343 | .required(true), 344 | ) 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /memds-cli/src/util.rs: -------------------------------------------------------------------------------- 1 | use futures::Future; 2 | use std::io::{self, Error, ErrorKind}; 3 | 4 | use memds_proto::memds_api::*; 5 | use memds_proto::memds_api_grpc::MemdsClient; 6 | 7 | pub fn rpc_exec(client: &MemdsClient, req: &RequestMsg) -> io::Result { 8 | let exec = client.exec_async(&req).unwrap(); 9 | match exec.wait() { 10 | Err(e) => { 11 | let msg = format!("RPC.Exec failed: {:?}", e); 12 | Err(Error::new(ErrorKind::Other, msg)) 13 | } 14 | Ok(resp) => Ok(resp), 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /memds-proto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memds-proto" 3 | version = "0.2.0" 4 | authors = ["Jeff Garzik "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | futures = "0.1" 11 | grpcio = { version = "0.5", features = ["openssl"] } 12 | protobuf = "~2" 13 | tokio = { version = "0.2", features = ["full"] } 14 | tokio-util = { version = "0.2", features = ["full"] } 15 | bytes = "0.5" 16 | crc = "3" 17 | 18 | [build-dependencies] 19 | protoc-grpcio = "1.0.2" 20 | 21 | [lib] 22 | doctest = false 23 | 24 | -------------------------------------------------------------------------------- /memds-proto/build.rs: -------------------------------------------------------------------------------- 1 | extern crate protoc_grpcio; 2 | 3 | fn main() { 4 | let proto_root = "src"; 5 | println!("cargo:rerun-if-changed={}", proto_root); 6 | protoc_grpcio::compile_grpc_protos(&["src/memds-api.proto"], &[proto_root], &proto_root, None) 7 | .expect("Failed to compile gRPC definitions!"); 8 | } 9 | -------------------------------------------------------------------------------- /memds-proto/src/.gitignore: -------------------------------------------------------------------------------- 1 | memds_api.rs 2 | memds_api_grpc.rs 3 | -------------------------------------------------------------------------------- /memds-proto/src/codec.rs: -------------------------------------------------------------------------------- 1 | // Encoder/decoder for our protobuf-based wire protocol 2 | // 3 | // Framing: 4 | // [4-byte header; 1 byte magic, 3 bytes size] 5 | // [4-byte checksum] 6 | // [message data] 7 | // 8 | // Checksumming: 9 | // [4-byte CRC from previous frame] 10 | // [4-byte header] 11 | // [message data] 12 | // 13 | // All integers stored in big endian (aka network byte order). 14 | // 15 | 16 | use bytes::{Buf, BufMut, BytesMut}; 17 | use crc::{Crc, CRC_32_ISCSI}; 18 | use protobuf::Message; 19 | use std::io::Cursor; 20 | use tokio_util::codec::{Decoder, Encoder}; 21 | 22 | use crate::error::*; 23 | use crate::memds_api::MemdsMessage; 24 | 25 | const HDR_SIZE: usize = 4; // [1 byte magic][3 byte size] 26 | const MAGIC: u32 = 0x4D; // ASCII 'M' 27 | const MAGIC_SHIFT: usize = 24; 28 | const MSG_SIZE_MASK: u32 = 0xffffff; // lower 24 bits; thus max msg sz = 16M 29 | const CRC32_GENESIS: u32 = 0xdeadbeef; 30 | 31 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] 32 | enum DecodeState { 33 | Head, 34 | Data(usize), 35 | } 36 | 37 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] 38 | pub struct MemdsCodec { 39 | state: DecodeState, 40 | last_dec_crc: u32, 41 | last_enc_crc: u32, 42 | expect_crc: u32, 43 | hdr_buf: [u8; HDR_SIZE], 44 | } 45 | 46 | impl MemdsCodec { 47 | pub fn new() -> MemdsCodec { 48 | MemdsCodec { 49 | state: DecodeState::Head, 50 | last_dec_crc: CRC32_GENESIS, 51 | last_enc_crc: CRC32_GENESIS, 52 | expect_crc: 0, 53 | hdr_buf: [0; HDR_SIZE], 54 | } 55 | } 56 | 57 | fn decode_head(&mut self, src: &mut BytesMut) -> Result, MemdsError> { 58 | // check hdr len + 1 (+1 for "something else follows") 59 | if src.len() <= HDR_SIZE { 60 | return Ok(None); 61 | } 62 | 63 | // parse header: [4-byte magic/size][4-byte checksum] 64 | let msg_size = { 65 | let mut src = Cursor::new(&mut *src); 66 | 67 | let header = src.get_uint(HDR_SIZE) as u32; 68 | 69 | let magic = header >> MAGIC_SHIFT; 70 | if magic != MAGIC { 71 | return Err(MemdsError::InvalidFrame); 72 | } 73 | 74 | let crc = src.get_uint(HDR_SIZE) as u32; 75 | self.expect_crc = crc; 76 | 77 | (header & MSG_SIZE_MASK) as usize 78 | }; 79 | 80 | // advance cursor past header 81 | let hdr_buf = src.split_to(HDR_SIZE); 82 | src.advance(4); // skip crc 83 | 84 | // remember top HDR_SIZE bytes, by copying hdr_bytes -> self.hdr_buf 85 | let hdr_bytes = hdr_buf.clone().freeze(); 86 | for (&x, p) in hdr_bytes.iter().zip(self.hdr_buf.iter_mut()) { 87 | *p = x; 88 | } 89 | 90 | // switch to data state, and prepare for msg_size packet 91 | self.state = DecodeState::Data(msg_size); 92 | src.reserve(msg_size); 93 | Ok(Some(msg_size)) 94 | } 95 | 96 | fn decode_data( 97 | &mut self, 98 | msg_size: usize, 99 | src: &mut BytesMut, 100 | ) -> Result, MemdsError> { 101 | if src.len() < msg_size { 102 | return Ok(None); 103 | } 104 | 105 | // carve away our portion of the frame 106 | let data = src.split_to(msg_size); 107 | 108 | // build BE buffer containing last-frame-crc 109 | let crc_buf = self.last_dec_crc.to_be_bytes(); 110 | 111 | // build CRC for current frame 112 | let crcer = Crc::::new(&CRC_32_ISCSI); 113 | let mut digest = crcer.digest(); 114 | digest.update(&crc_buf); 115 | digest.update(&self.hdr_buf); 116 | digest.update(&data); 117 | self.last_dec_crc = digest.finalize(); 118 | 119 | // verify CRC matches expected 120 | if self.last_dec_crc != self.expect_crc { 121 | return Err(MemdsError::InvalidChecksum); 122 | } 123 | 124 | // execute protobuf decode of full frame 125 | match MemdsMessage::parse_from_bytes(&data) { 126 | Err(_e) => Err(MemdsError::ProtobufDecode), 127 | Ok(req) => { 128 | self.state = DecodeState::Head; 129 | src.reserve(HDR_SIZE); 130 | Ok(Some(req)) 131 | } 132 | } 133 | } 134 | } 135 | 136 | impl Decoder for MemdsCodec { 137 | type Item = MemdsMessage; 138 | type Error = MemdsError; 139 | 140 | fn decode(&mut self, src: &mut BytesMut) -> Result, MemdsError> { 141 | // parse header; exit early if incomplete or error 142 | let msg_size = match self.state { 143 | DecodeState::Head => match self.decode_head(src) { 144 | Err(e) => return Err(e), 145 | Ok(opt) => match opt { 146 | None => return Ok(None), 147 | Some(n) => n, 148 | }, 149 | }, 150 | DecodeState::Data(n) => n, 151 | }; 152 | 153 | // parse data 154 | match self.decode_data(msg_size, src) { 155 | Err(e) => Err(e), 156 | Ok(opt) => match opt { 157 | None => Ok(None), 158 | Some(msg) => Ok(Some(msg)), 159 | }, 160 | } 161 | } 162 | } 163 | 164 | impl Encoder for MemdsCodec { 165 | type Item = MemdsMessage; 166 | type Error = MemdsError; 167 | 168 | fn encode(&mut self, msg: MemdsMessage, dst: &mut BytesMut) -> Result<(), MemdsError> { 169 | let msg_bytes = msg.write_to_bytes().unwrap(); 170 | if msg_bytes.len() > MSG_SIZE_MASK as usize { 171 | return Err(MemdsError::InvalidFrame); 172 | } 173 | 174 | // build header 175 | let msg_len: u32 = msg_bytes.len() as u32; 176 | let header = (msg_len & MSG_SIZE_MASK) | (MAGIC << 24); 177 | let hdr_buf = header.to_be_bytes(); 178 | 179 | // canonical encoding of last-frame-CRC 180 | let crc_buf = self.last_enc_crc.to_be_bytes(); 181 | 182 | // build CRC of current frame 183 | let crcer = Crc::::new(&CRC_32_ISCSI); 184 | let mut digest = crcer.digest(); 185 | digest.update(&crc_buf); 186 | digest.update(&hdr_buf); 187 | digest.update(&msg_bytes); 188 | self.last_enc_crc = digest.finalize(); 189 | 190 | // assemble frame parts in linear buffer 191 | dst.reserve(HDR_SIZE + 4 + msg_len as usize); 192 | dst.put_uint(header as u64, HDR_SIZE); 193 | dst.put_uint(self.last_enc_crc as u64, 4); 194 | dst.extend_from_slice(&msg_bytes); 195 | 196 | Ok(()) 197 | } 198 | } 199 | 200 | #[cfg(test)] 201 | mod tests { 202 | use crate::memds_api::{AtomType, DbValue, MemdsMessage, MemdsMessage_MsgType}; 203 | use crate::{MemdsCodec, MemdsError}; 204 | use bytes::BytesMut; 205 | use tokio_util::codec::{Decoder, Encoder}; 206 | 207 | #[test] 208 | fn basic_codec() { 209 | let mut codec = MemdsCodec::new(); 210 | 211 | // message #1 212 | let mut dbv = DbValue::new(); 213 | dbv.typ = AtomType::STRING; 214 | dbv.set_key(b"foo".to_vec()); 215 | dbv.set_str(b"bar".to_vec()); 216 | let mut enc_msg = MemdsMessage::new(); 217 | enc_msg.mtype = MemdsMessage_MsgType::DBVAL; 218 | enc_msg.set_dbv(dbv); 219 | 220 | let enc_msg_raw = &mut BytesMut::new(); 221 | codec.encode(enc_msg.clone(), enc_msg_raw).unwrap(); 222 | 223 | let dec_msg = codec.decode(enc_msg_raw).unwrap().unwrap(); 224 | assert_eq!(enc_msg, dec_msg); 225 | 226 | // message #2 227 | let mut dbv = DbValue::new(); 228 | dbv.typ = AtomType::STRING; 229 | dbv.set_key(b"foo".to_vec()); 230 | dbv.set_str(b"bar".to_vec()); 231 | let mut enc_msg = MemdsMessage::new(); 232 | enc_msg.mtype = MemdsMessage_MsgType::DBVAL; 233 | enc_msg.set_dbv(dbv); 234 | 235 | let enc_msg_raw = &mut BytesMut::new(); 236 | codec.encode(enc_msg.clone(), enc_msg_raw).unwrap(); 237 | 238 | let dec_msg = codec.decode(enc_msg_raw).unwrap().unwrap(); 239 | assert_eq!(enc_msg, dec_msg); 240 | } 241 | 242 | #[test] 243 | fn invalid_checksum() { 244 | let mut codec = MemdsCodec::new(); 245 | 246 | // encode message 247 | let mut dbv = DbValue::new(); 248 | dbv.typ = AtomType::STRING; 249 | dbv.set_key(b"foo".to_vec()); 250 | dbv.set_str(b"bar".to_vec()); 251 | let mut enc_msg = MemdsMessage::new(); 252 | enc_msg.mtype = MemdsMessage_MsgType::DBVAL; 253 | enc_msg.set_dbv(dbv); 254 | 255 | let enc_msg_raw = &mut BytesMut::new(); 256 | codec.encode(enc_msg.clone(), enc_msg_raw).unwrap(); 257 | 258 | // change last char of message data 259 | let last_pos = enc_msg_raw.len() - 1; 260 | let last_char = enc_msg_raw[last_pos]; 261 | enc_msg_raw[last_pos] = last_char + 1; 262 | 263 | // decode should indicate bad csum 264 | let res = codec.decode(enc_msg_raw); 265 | match res { 266 | Ok(_) => assert!(false), 267 | Err(e) => match e { 268 | MemdsError::InvalidChecksum => {} 269 | _ => { 270 | assert!(false); 271 | } 272 | }, 273 | } 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /memds-proto/src/error.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum MemdsError { 3 | InvalidFrame, 4 | InvalidChecksum, 5 | ProtobufDecode, 6 | IO(std::io::Error), 7 | } 8 | 9 | impl From for MemdsError { 10 | fn from(err: std::io::Error) -> MemdsError { 11 | MemdsError::IO(err) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /memds-proto/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | mod codec; 4 | mod error; 5 | 6 | pub const DEF_PORT: u16 = 16900; 7 | 8 | #[derive(Clone)] 9 | pub enum Atom { 10 | String(Vec), 11 | List(Vec>), 12 | Set(HashSet>), 13 | } 14 | 15 | pub mod memds_api; 16 | pub mod memds_api_grpc; 17 | pub mod util; 18 | 19 | pub use codec::MemdsCodec; 20 | pub use error::MemdsError; 21 | -------------------------------------------------------------------------------- /memds-proto/src/memds-api.proto: -------------------------------------------------------------------------------- 1 | 2 | syntax = "proto3"; 3 | 4 | option java_multiple_files = true; 5 | option java_package = "com.bloq.memds"; 6 | option java_outer_classname = "MemdsProto"; 7 | option objc_class_prefix = "MDS"; 8 | 9 | package memds; 10 | 11 | service Memds { 12 | rpc Exec (RequestMsg) returns (ResponseMsg) {} 13 | } 14 | 15 | enum AtomType { 16 | NOTYPE = 0; 17 | STRING = 1; 18 | LIST = 2; 19 | SET = 3; 20 | } 21 | 22 | message StrGetOp { 23 | bytes key = 1; // key of item to retrieve 24 | bool want_length = 2; // (true) return value length, (false) return value 25 | bool substr = 3; // (true) return substring, (false) return whole value 26 | sint32 range_start = 4; 27 | sint32 range_end = 5; 28 | } 29 | 30 | message StrGetRes { 31 | bytes value = 1; // if !want_length, value requested 32 | uint64 value_length = 2; // if want_length, filled with value length 33 | } 34 | 35 | message StrSetOp { 36 | bytes key = 1; // key of item to store 37 | bytes value = 2; // value of item to store 38 | bool return_old = 3; // if true & old item present, return old-value stored at key 39 | bool create_excl = 4; // if true, store iff string does NOT exist 40 | } 41 | 42 | message StrSetRes { 43 | bytes old_value = 1; // if return_old, old-value stored at key 44 | } 45 | 46 | message NumOp { 47 | bytes key = 1; // key of item to increment 48 | sint64 n = 2; // (optional) amount of increment 49 | } 50 | 51 | message NumRes { 52 | sint64 old_value = 1; // value of key prior to operation 53 | } 54 | 55 | message KeyOp { 56 | bytes key = 1; // key upon which to query/update 57 | } 58 | 59 | message KeyRenameOp { 60 | bytes old_key = 1; 61 | bytes new_key = 2; 62 | bool create_excl = 3; // if true, store iff new_key does NOT exist 63 | } 64 | 65 | message KeyListOp { 66 | repeated bytes keys = 1; 67 | } 68 | 69 | message ListPushOp { 70 | bytes key = 1; // key of list 71 | bool at_head = 2; // (true) push head, (false) push tail 72 | bool if_exists = 3; // if true, push iff list exists 73 | repeated bytes elements = 4; // items to push onto list 74 | } 75 | 76 | message ListPopOp { 77 | bytes key = 1; // key of list 78 | bool at_head = 2; // (true) pop head, (false) pop tail 79 | } 80 | 81 | message ListIndexOp { 82 | bytes key = 1; // key of list 83 | sint32 index = 2; // index of item to return 84 | } 85 | 86 | message ListRes { 87 | repeated bytes elements = 1; // list of elements returned 88 | } 89 | 90 | message KeyedListOp { 91 | bytes key = 1; // key of set 92 | repeated bytes elements = 4; // items to query or update within key 93 | } 94 | 95 | message SetMoveOp { 96 | bytes src_key = 1; 97 | bytes dest_key = 2; 98 | bytes member = 3; 99 | } 100 | 101 | message SetInfoRes { 102 | uint32 length = 1; 103 | } 104 | 105 | message CmpStoreOp { 106 | repeated bytes keys = 1; // keys upon which to operate 107 | bytes store_key = 2; // if empty, return results. 108 | // if non-empty, store results in this key. 109 | } 110 | 111 | message CountRes { 112 | uint64 n = 1; // many operations return a count, or length 113 | } 114 | 115 | message ListInfoRes { 116 | uint32 length = 1; 117 | } 118 | 119 | message TimeRes { 120 | uint64 secs = 1; 121 | uint32 nanosecs = 2; 122 | } 123 | 124 | message TypeRes { 125 | AtomType typ = 1; 126 | } 127 | 128 | enum OpType { 129 | NOOP = 0; 130 | 131 | KEYS_DEL = 10; 132 | KEYS_EXIST = 11; 133 | KEYS_RENAME = 13; 134 | KEYS_TYPE = 12; 135 | KEY_DUMP = 14; 136 | KEY_RESTORE = 15; 137 | 138 | LIST_PUSH = 20; 139 | LIST_POP = 21; 140 | LIST_INDEX = 22; 141 | LIST_INFO = 23; 142 | 143 | SET_ADD = 50; 144 | SET_INFO = 51; 145 | SET_DEL = 52; 146 | SET_DIFF = 55; 147 | SET_MEMBERS = 53; 148 | SET_INTERSECT = 57; 149 | SET_ISMEMBER = 54; 150 | SET_UNION = 56; 151 | SET_MOVE = 58; 152 | 153 | SRV_BGSAVE = 44; 154 | SRV_DBSIZE = 41; 155 | SRV_FLUSHDB = 42; 156 | SRV_FLUSHALL = 43; 157 | SRV_TIME = 40; 158 | 159 | STR_GET = 31; 160 | STR_GETRANGE = 32; 161 | STR_SET = 38; 162 | STR_DECR = 33; 163 | STR_DECRBY = 34; 164 | STR_INCR = 35; 165 | STR_INCRBY = 36; 166 | STR_APPEND = 37; 167 | } 168 | 169 | message Operation { 170 | OpType otype = 1; 171 | 172 | StrGetOp get = 10; 173 | StrSetOp set = 11; 174 | NumOp num = 12; 175 | ListPushOp lpush = 13; 176 | ListPopOp lpop = 14; 177 | ListIndexOp lindex = 15; 178 | KeyOp key = 16; 179 | KeyListOp key_list = 17; 180 | KeyRenameOp rename = 18; 181 | KeyedListOp keyed_list = 19; 182 | CmpStoreOp cmp_stor = 20; 183 | SetMoveOp set_move = 21; 184 | } 185 | 186 | message RequestMsg { 187 | repeated Operation ops = 1; 188 | } 189 | 190 | message OpResult { 191 | bool ok = 1; // success? 192 | int32 err_code = 2; // error code, if !ok 193 | string err_message = 3; // error message, if !ok 194 | 195 | OpType otype = 4; 196 | 197 | StrGetRes get = 10; 198 | StrSetRes set = 11; 199 | NumRes num = 12; 200 | ListRes list = 13; 201 | CountRes count = 14; 202 | ListInfoRes list_info = 15; 203 | SetInfoRes set_info = 18; 204 | TypeRes typ = 16; 205 | TimeRes srv_time = 17; 206 | } 207 | 208 | message ResponseMsg { 209 | bool ok = 1; // success? 210 | int32 err_code = 2; // error code, if !ok 211 | string err_message = 3; // error message, if !ok 212 | 213 | repeated OpResult results = 4; 214 | } 215 | 216 | message DbValue { 217 | AtomType typ = 1; 218 | bytes key = 2; 219 | 220 | bytes str = 3; 221 | repeated bytes elements = 4; 222 | } 223 | 224 | message MemdsMessage { 225 | enum MsgType { 226 | NULLMSG = 0; 227 | DBVAL = 1; 228 | END = 2; 229 | } 230 | 231 | MsgType mtype = 1; 232 | DbValue dbv = 2; 233 | } 234 | 235 | -------------------------------------------------------------------------------- /memds-proto/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::memds_api::OpResult; 2 | 3 | pub fn result_err(code: i32, message: &str) -> OpResult { 4 | let mut res = OpResult::new(); 5 | res.set_ok(false); 6 | res.set_err_code(code); 7 | res.set_err_message(message.to_string()); 8 | 9 | res 10 | } 11 | -------------------------------------------------------------------------------- /memds-server/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | memds.conf 3 | 4 | -------------------------------------------------------------------------------- /memds-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memds-server" 3 | version = "0.2.0" 4 | authors = ["Jeff Garzik "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | memds-proto = { path = "../memds-proto" } 11 | futures = "0.1" 12 | grpcio = { version = "0.5", features = ["openssl"] } 13 | protobuf = "~2" 14 | log = "0.4" 15 | clap = "3" 16 | tokio = { version = "0.2", features = ["full"] } 17 | tokio-util = { version = "0.2", features = ["full"] } 18 | bytes = "0.5" 19 | nix = "0.17" 20 | serde = "1.0" 21 | serde_derive = "1.0" 22 | toml = "0.8" 23 | -------------------------------------------------------------------------------- /memds-server/example-memds.conf: -------------------------------------------------------------------------------- 1 | [network] 2 | bind_addr = "127.0.0.1" 3 | bind_port = 16900 4 | -------------------------------------------------------------------------------- /memds-server/src/config.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | 3 | use clap::value_t; 4 | use serde_derive::Deserialize; 5 | 6 | const APPNAME: &'static str = "memds-server"; 7 | const VERSION: &'static str = env!("CARGO_PKG_VERSION"); 8 | 9 | const DEF_BIND_ADDR: &'static str = "127.0.0.1"; 10 | const DEF_CONFIG_FN: &'static str = "memds.conf"; 11 | 12 | #[derive(Deserialize)] 13 | struct TomlConfig { 14 | network: Option, 15 | fs: Option, 16 | } 17 | 18 | #[derive(Deserialize)] 19 | struct TomlNetworkConfig { 20 | bind_addr: Option, 21 | bind_port: Option, 22 | } 23 | 24 | #[derive(Deserialize)] 25 | struct TomlFsConfig { 26 | import: Option, 27 | } 28 | 29 | pub struct Config { 30 | pub network: NetworkConfig, 31 | pub fs: FsConfig, 32 | } 33 | 34 | pub struct NetworkConfig { 35 | pub bind_addr: String, 36 | pub bind_port: u16, 37 | } 38 | 39 | pub struct FsConfig { 40 | pub import: Option, 41 | } 42 | 43 | pub fn get() -> Config { 44 | // parse command line 45 | let bind_addr_help = format!("socket bind address (default: {})", DEF_BIND_ADDR); 46 | let port_help = format!("socket bind port (default: {})", memds_proto::DEF_PORT); 47 | let config_fn_help = format!("Read configuration file (default: {})", DEF_CONFIG_FN); 48 | let cli_matches = clap::App::new(APPNAME) 49 | .version(VERSION) 50 | .about("Memory Database Service") 51 | .arg( 52 | clap::Arg::with_name("bind-addr") 53 | .short('a') 54 | .long("bind-addr") 55 | .value_name("IP-ADDRESS") 56 | .help(&*bind_addr_help) 57 | .takes_value(true), 58 | ) 59 | .arg( 60 | clap::Arg::with_name("bind-port") 61 | .short('p') 62 | .long("bind-port") 63 | .value_name("PORT") 64 | .help(&*port_help) 65 | .takes_value(true), 66 | ) 67 | .arg( 68 | clap::Arg::with_name("config") 69 | .short('c') 70 | .long("config") 71 | .value_name("TOML-FILE") 72 | .help(&*config_fn_help) 73 | .takes_value(true), 74 | ) 75 | .arg( 76 | clap::Arg::with_name("import") 77 | .long("import") 78 | .value_name("MEMDS-FILE") 79 | .help("Import serialized database file") 80 | .takes_value(true), 81 | ) 82 | .get_matches(); 83 | 84 | let config_fn = cli_matches.value_of("config").unwrap_or(DEF_CONFIG_FN); 85 | 86 | // read config file 87 | let cfg_res = std::fs::read_to_string(config_fn); 88 | let f_cfg = { 89 | // read config, or create default 90 | let mut f_cfg: TomlConfig; 91 | if cfg_res.is_ok() { 92 | f_cfg = toml::from_str(&cfg_res.unwrap()).unwrap(); 93 | } else { 94 | f_cfg = TomlConfig { 95 | network: None, 96 | fs: None, 97 | }; 98 | } 99 | 100 | // if network section missing, create default one 101 | if f_cfg.network.is_none() { 102 | f_cfg.network = Some(TomlNetworkConfig { 103 | bind_addr: None, 104 | bind_port: None, 105 | }); 106 | } 107 | 108 | // if fs section missing, create default one 109 | if f_cfg.fs.is_none() { 110 | f_cfg.fs = Some(TomlFsConfig { import: None }); 111 | } 112 | 113 | let f_fs_cfg = f_cfg.fs.as_mut().unwrap(); 114 | 115 | if cli_matches.is_present("import") { 116 | f_fs_cfg.import = Some(cli_matches.value_of("import").unwrap().to_string()); 117 | } 118 | 119 | let f_net_cfg = f_cfg.network.as_mut().unwrap(); 120 | 121 | // CLI arg overrides config file value; else if missing, provide def. 122 | if cli_matches.is_present("bind-addr") { 123 | f_net_cfg.bind_addr = Some(cli_matches.value_of("bind-addr").unwrap().to_string()); 124 | } else if f_net_cfg.bind_addr.is_none() { 125 | f_net_cfg.bind_addr = Some(DEF_BIND_ADDR.to_string()); 126 | } 127 | 128 | // CLI arg overrides config file value; else if missing, provide def. 129 | if cli_matches.is_present("bind-port") { 130 | f_net_cfg.bind_port = Some(value_t!(cli_matches, "bind-port", u16).unwrap()); 131 | } else if f_net_cfg.bind_port.is_none() { 132 | f_net_cfg.bind_port = Some(memds_proto::DEF_PORT); 133 | } 134 | 135 | f_cfg 136 | }; 137 | 138 | let f_net_cfg = f_cfg.network.unwrap(); 139 | let f_fs_cfg = f_cfg.fs.unwrap(); 140 | 141 | Config { 142 | network: NetworkConfig { 143 | bind_addr: f_net_cfg.bind_addr.unwrap(), 144 | bind_port: f_net_cfg.bind_port.unwrap(), 145 | }, 146 | fs: FsConfig { 147 | import: f_fs_cfg.import, 148 | }, 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /memds-server/src/keys.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::collections::HashSet; 3 | 4 | use bytes::{BufMut, Bytes, BytesMut}; 5 | use memds_proto::memds_api::{ 6 | AtomType, CountRes, DbValue, KeyListOp, KeyOp, KeyRenameOp, MemdsMessage, MemdsMessage_MsgType, 7 | OpResult, OpType, StrGetRes, StrSetOp, TypeRes, 8 | }; 9 | use memds_proto::util::result_err; 10 | use memds_proto::{Atom, MemdsCodec}; 11 | use tokio_util::codec::{Decoder, Encoder}; 12 | 13 | pub fn del_exist(db: &mut HashMap, Atom>, req: &KeyListOp, remove_item: bool) -> OpResult { 14 | let mut count: u64 = 0; 15 | 16 | // iterate through provided key list 17 | for key in req.get_keys().iter() { 18 | // if we're deleting, attempt to remove item 19 | if remove_item { 20 | if db.remove(key).is_some() { 21 | count += 1; 22 | } 23 | 24 | // if we're testing existence, do so 25 | } else { 26 | if db.contains_key(key) { 27 | count += 1; 28 | } 29 | } 30 | } 31 | 32 | // return number of keys matched (== operations successful, for delete) 33 | let mut count_res = CountRes::new(); 34 | count_res.n = count; 35 | 36 | // standard operation result assignment & final return 37 | let mut op_res = OpResult::new(); 38 | 39 | op_res.ok = true; 40 | op_res.otype = match remove_item { 41 | true => OpType::KEYS_DEL, 42 | false => OpType::KEYS_EXIST, 43 | }; 44 | op_res.set_count(count_res); 45 | 46 | op_res 47 | } 48 | 49 | pub fn rename(db: &mut HashMap, Atom>, req: &KeyRenameOp) -> OpResult { 50 | let old_key = req.get_old_key(); 51 | let new_key = req.get_new_key(); 52 | 53 | if req.create_excl && db.contains_key(new_key) { 54 | return result_err(-412, "Precondition failed: key exists"); 55 | } 56 | 57 | // remove value stored at old key 58 | let value = { 59 | let rm_res = db.remove(old_key); 60 | if rm_res.is_none() { 61 | return result_err(-404, "Not Found"); 62 | } 63 | 64 | rm_res.unwrap() 65 | }; 66 | 67 | // store value at new key 68 | db.insert(new_key.to_vec(), value); 69 | 70 | // standard operation result assignment & final return 71 | let mut op_res = OpResult::new(); 72 | 73 | op_res.ok = true; 74 | op_res.otype = OpType::KEYS_RENAME; 75 | 76 | op_res 77 | } 78 | 79 | pub fn typ(db: &mut HashMap, Atom>, req: &KeyOp) -> OpResult { 80 | let key = req.get_key(); 81 | 82 | // get value stored at key 83 | let typ = match db.get(key) { 84 | None => { 85 | return result_err(-404, "Not Found"); 86 | } 87 | 88 | // match type 89 | Some(atom) => match atom { 90 | Atom::String(_) => AtomType::STRING, 91 | Atom::List(_) => AtomType::LIST, 92 | Atom::Set(_) => AtomType::SET, 93 | }, 94 | }; 95 | 96 | // return type 97 | let mut type_res = TypeRes::new(); 98 | type_res.typ = typ; 99 | 100 | // standard operation result assignment & final return 101 | let mut op_res = OpResult::new(); 102 | 103 | op_res.ok = true; 104 | op_res.otype = OpType::KEYS_TYPE; 105 | op_res.set_typ(type_res); 106 | 107 | op_res 108 | } 109 | 110 | pub fn import_dbv(db: &mut HashMap, Atom>, in_key: Option<&[u8]>, dbv: &DbValue) -> bool { 111 | let key = match in_key { 112 | None => &dbv.key, 113 | Some(k) => k, 114 | }; 115 | 116 | let value = match dbv.typ { 117 | AtomType::NOTYPE => { 118 | return false; 119 | } 120 | AtomType::STRING => Atom::String(dbv.get_str().to_vec()), 121 | AtomType::LIST => { 122 | let mut v = Vec::new(); 123 | for elem in dbv.elements.iter() { 124 | v.push(elem.to_vec()); 125 | } 126 | Atom::List(v) 127 | } 128 | AtomType::SET => { 129 | let mut hs = HashSet::new(); 130 | for elem in dbv.elements.iter() { 131 | hs.insert(elem.to_vec()); 132 | } 133 | Atom::Set(hs) 134 | } 135 | }; 136 | 137 | db.insert(key.to_vec(), value); 138 | 139 | true 140 | } 141 | 142 | pub fn export_dbv(db: &HashMap, Atom>, key: &[u8]) -> Option { 143 | // create result DbValue 144 | let mut dbv = DbValue::new(); 145 | dbv.set_key(key.to_vec()); 146 | 147 | // encode DbValue value 148 | match db.get(key) { 149 | None => return None, 150 | 151 | // match type 152 | Some(atom) => match atom { 153 | Atom::String(s) => { 154 | dbv.typ = AtomType::STRING; 155 | dbv.set_str(s.clone()); 156 | } 157 | Atom::List(l) => { 158 | dbv.typ = AtomType::LIST; 159 | for elem in l.iter() { 160 | dbv.elements.push(elem.clone()); 161 | } 162 | } 163 | Atom::Set(st) => { 164 | dbv.typ = AtomType::SET; 165 | for elem in st.iter() { 166 | dbv.elements.push(elem.clone()); 167 | } 168 | } 169 | }, 170 | }; 171 | 172 | Some(dbv) 173 | } 174 | 175 | pub fn dump(db: &mut HashMap, Atom>, req: &KeyOp) -> OpResult { 176 | // create result DbValue 177 | let dbv = { 178 | match export_dbv(db, req.get_key()) { 179 | None => { 180 | return result_err(-404, "Not Found"); 181 | } 182 | Some(dbv) => dbv, 183 | } 184 | }; 185 | 186 | // encode DbValue wrapper message 187 | let mut msg = MemdsMessage::new(); 188 | msg.mtype = MemdsMessage_MsgType::DBVAL; 189 | msg.set_dbv(dbv); 190 | 191 | // encode to wire protocol using tokio codec 192 | let mut codec = MemdsCodec::new(); 193 | let msg_raw = &mut BytesMut::new(); 194 | codec.encode(msg, msg_raw).unwrap(); 195 | 196 | // encode wire bytes into StrGetRes result bytes 197 | let mut get_res = StrGetRes::new(); 198 | get_res.set_value(msg_raw.to_vec()); 199 | 200 | // standard operation result assignment & final return 201 | let mut op_res = OpResult::new(); 202 | 203 | op_res.ok = true; 204 | op_res.otype = OpType::KEY_DUMP; 205 | op_res.set_get(get_res); 206 | 207 | op_res 208 | } 209 | 210 | pub fn restore(db: &mut HashMap, Atom>, req: &StrSetOp) -> OpResult { 211 | let msg = { 212 | let mut codec = MemdsCodec::new(); 213 | let buf = Bytes::from(req.value.clone()); 214 | let msg_raw = &mut BytesMut::new(); 215 | msg_raw.put(buf); 216 | match codec.decode(msg_raw) { 217 | Err(_) => { 218 | return result_err(-400, "Deser failed"); 219 | } 220 | Ok(None) => { 221 | return result_err(-400, "Deser empty"); 222 | } 223 | Ok(Some(dec_msg)) => { 224 | if (dec_msg.mtype != MemdsMessage_MsgType::DBVAL) || (!dec_msg.has_dbv()) { 225 | return result_err(-400, "not dbv"); 226 | } 227 | 228 | dec_msg 229 | } 230 | } 231 | }; 232 | 233 | let key_opt = { 234 | if req.key.len() > 0 { 235 | Some(&req.key[..]) 236 | } else { 237 | None 238 | } 239 | }; 240 | 241 | import_dbv(db, key_opt, msg.get_dbv()); 242 | 243 | // standard operation result assignment & final return 244 | let mut op_res = OpResult::new(); 245 | 246 | op_res.ok = true; 247 | op_res.otype = OpType::KEY_RESTORE; 248 | 249 | op_res 250 | } 251 | 252 | #[cfg(test)] 253 | mod tests { 254 | use crate::{keys, string}; 255 | use memds_proto::memds_api::{ 256 | AtomType, KeyListOp, KeyOp, KeyRenameOp, OpType, StrGetOp, StrSetOp, 257 | }; 258 | use memds_proto::Atom; 259 | use std::collections::HashMap; 260 | 261 | fn get_test_db() -> HashMap, Atom> { 262 | let mut db: HashMap, Atom> = HashMap::new(); 263 | db.insert(b"foo".to_vec(), Atom::String(b"bar".to_vec())); 264 | db.insert(b"name".to_vec(), Atom::String(b"Jane Doe".to_vec())); 265 | db.insert(b"age".to_vec(), Atom::String(b"25".to_vec())); 266 | 267 | db 268 | } 269 | 270 | #[test] 271 | fn del() { 272 | let mut db = get_test_db(); 273 | 274 | let mut req = KeyListOp::new(); 275 | req.keys.push(b"foo".to_vec()); 276 | req.keys.push(b"age".to_vec()); 277 | req.keys.push(b"does-not-exist".to_vec()); 278 | 279 | let res = keys::del_exist(&mut db, &req, true); 280 | 281 | assert_eq!(res.ok, true); 282 | assert_eq!(res.otype, OpType::KEYS_DEL); 283 | assert!(res.has_count()); 284 | 285 | let count_res = res.get_count(); 286 | assert_eq!(count_res.n, 2); 287 | } 288 | 289 | #[test] 290 | fn exist() { 291 | let mut db = get_test_db(); 292 | 293 | // count=2 keys of 3 in test set 294 | let mut req = KeyListOp::new(); 295 | req.keys.push(b"foo".to_vec()); 296 | req.keys.push(b"age".to_vec()); 297 | req.keys.push(b"does-not-exist".to_vec()); 298 | 299 | let res = keys::del_exist(&mut db, &req, false); 300 | 301 | assert_eq!(res.ok, true); 302 | assert_eq!(res.otype, OpType::KEYS_EXIST); 303 | assert!(res.has_count()); 304 | 305 | let count_res = res.get_count(); 306 | assert_eq!(count_res.n, 2); 307 | 308 | // repeat same test, to make sure keys did not disappear 309 | let mut req = KeyListOp::new(); 310 | req.keys.push(b"foo".to_vec()); 311 | req.keys.push(b"age".to_vec()); 312 | req.keys.push(b"does-not-exist".to_vec()); 313 | 314 | let res = keys::del_exist(&mut db, &req, false); 315 | 316 | assert_eq!(res.ok, true); 317 | assert_eq!(res.otype, OpType::KEYS_EXIST); 318 | assert!(res.has_count()); 319 | 320 | let count_res = res.get_count(); 321 | assert_eq!(count_res.n, 2); 322 | } 323 | 324 | #[test] 325 | fn typ() { 326 | let mut db = get_test_db(); 327 | 328 | let mut req = KeyOp::new(); 329 | req.set_key(b"foo".to_vec()); 330 | 331 | let res = keys::typ(&mut db, &req); 332 | 333 | assert_eq!(res.ok, true); 334 | assert_eq!(res.otype, OpType::KEYS_TYPE); 335 | assert!(res.has_typ()); 336 | 337 | let type_res = res.get_typ(); 338 | assert_eq!(type_res.typ, AtomType::STRING); 339 | } 340 | 341 | #[test] 342 | fn rename() { 343 | let mut db = get_test_db(); 344 | 345 | // rename "foo" to "food" 346 | let mut req = KeyRenameOp::new(); 347 | req.set_old_key(b"foo".to_vec()); 348 | req.set_new_key(b"food".to_vec()); 349 | req.create_excl = true; 350 | 351 | let res = keys::rename(&mut db, &req); 352 | 353 | assert_eq!(res.ok, true); 354 | assert_eq!(res.otype, OpType::KEYS_RENAME); 355 | 356 | // get "foo" == not found 357 | let mut req = StrGetOp::new(); 358 | req.set_key(b"foo".to_vec()); 359 | 360 | let res = string::get(&mut db, &req, OpType::STR_GET); 361 | 362 | assert_eq!(res.ok, false); 363 | assert_eq!(res.otype, OpType::NOOP); 364 | assert_eq!(res.err_code, -404); 365 | 366 | // get "food" == found 367 | let mut req = StrGetOp::new(); 368 | req.set_key(b"food".to_vec()); 369 | 370 | let res = string::get(&mut db, &req, OpType::STR_GET); 371 | 372 | assert_eq!(res.ok, true); 373 | 374 | let get_res = res.get_get(); 375 | assert_eq!(get_res.value, b"bar".to_vec()); 376 | } 377 | 378 | #[test] 379 | fn dump_string() { 380 | let mut db = get_test_db(); 381 | 382 | // dump (serialize) string 383 | let mut req = KeyOp::new(); 384 | req.set_key(b"foo".to_vec()); 385 | 386 | let res = keys::dump(&mut db, &req); 387 | 388 | assert_eq!(res.ok, true); 389 | assert_eq!(res.otype, OpType::KEY_DUMP); 390 | assert!(res.has_get()); 391 | 392 | let get_res = res.get_get(); 393 | let enc_wire_data = get_res.get_value(); 394 | 395 | // restore to different key, and compare 396 | let mut set_req = StrSetOp::new(); 397 | set_req.set_key(b"foo2".to_vec()); 398 | set_req.set_value(enc_wire_data.to_vec()); 399 | 400 | let res = keys::restore(&mut db, &set_req); 401 | 402 | assert_eq!(res.ok, true); 403 | assert_eq!(res.otype, OpType::KEY_RESTORE); 404 | 405 | // get "foo2" == "bar" 406 | let mut req = StrGetOp::new(); 407 | req.set_key(b"foo2".to_vec()); 408 | 409 | let res = string::get(&mut db, &req, OpType::STR_GET); 410 | 411 | assert_eq!(res.ok, true); 412 | 413 | let get_res = res.get_get(); 414 | assert_eq!(get_res.value, b"bar".to_vec()); 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /memds-server/src/list.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use memds_proto::memds_api::{ 4 | CountRes, KeyOp, ListIndexOp, ListInfoRes, ListPopOp, ListPushOp, ListRes, OpResult, OpType, 5 | }; 6 | use memds_proto::util::result_err; 7 | use memds_proto::Atom; 8 | 9 | pub fn info(db: &mut HashMap, Atom>, req: &KeyOp) -> OpResult { 10 | // get list to query 11 | let l = { 12 | let key = req.get_key(); 13 | match db.get_mut(key) { 14 | None => { 15 | return result_err(-404, "Not Found"); 16 | } 17 | Some(atom) => match atom { 18 | Atom::List(l) => l, 19 | _ => { 20 | return result_err(-400, "not a list"); 21 | } 22 | }, 23 | } 24 | }; 25 | 26 | // return list info (aka list metadata) 27 | // at present there is only item count (list length), 28 | // but more metadata is presumed in the future. 29 | let mut info_res = ListInfoRes::new(); 30 | info_res.length = l.len() as u32; 31 | 32 | // standard operation result assignment & final return 33 | let mut op_res = OpResult::new(); 34 | 35 | op_res.ok = true; 36 | op_res.otype = OpType::LIST_INFO; 37 | op_res.set_list_info(info_res); 38 | 39 | op_res 40 | } 41 | 42 | pub fn push(db: &mut HashMap, Atom>, req: &ListPushOp) -> OpResult { 43 | // get list to mutate 44 | let l = { 45 | let key = req.get_key(); 46 | match db.get_mut(key) { 47 | None => { 48 | if req.if_exists { 49 | return result_err(-404, "Not Found"); 50 | } 51 | db.insert(key.to_vec(), Atom::List(Vec::new())); 52 | match db.get_mut(key) { 53 | None => unreachable!(), 54 | Some(atom) => match atom { 55 | Atom::List(l) => l, 56 | _ => unreachable!(), 57 | }, 58 | } 59 | } 60 | Some(atom) => match atom { 61 | Atom::List(l) => l, 62 | _ => { 63 | return result_err(-400, "not a list"); 64 | } 65 | }, 66 | } 67 | }; 68 | 69 | // insert, at head or tail as requested 70 | if req.at_head { 71 | for element in req.elements.iter() { 72 | l.insert(0, element.to_vec()); 73 | } 74 | } else { 75 | for element in req.elements.iter() { 76 | l.insert(l.len(), element.to_vec()); 77 | } 78 | } 79 | 80 | // return new list length, after mutations (if any) 81 | let mut count_res = CountRes::new(); 82 | count_res.n = l.len() as u64; 83 | 84 | // standard operation result assignment & final return 85 | let mut op_res = OpResult::new(); 86 | 87 | op_res.ok = true; 88 | op_res.otype = OpType::LIST_PUSH; 89 | op_res.set_count(count_res); 90 | 91 | op_res 92 | } 93 | 94 | pub fn pop(db: &mut HashMap, Atom>, req: &ListPopOp) -> OpResult { 95 | // get list to mutate 96 | let l = { 97 | let key = req.get_key(); 98 | match db.get_mut(key) { 99 | None => { 100 | return result_err(-404, "Not Found"); 101 | } 102 | Some(atom) => match atom { 103 | Atom::List(l) => l, 104 | _ => { 105 | return result_err(-400, "not a list"); 106 | } 107 | }, 108 | } 109 | }; 110 | 111 | let mut list_res = ListRes::new(); 112 | 113 | // remove, and return removed values, at head or tail as requested 114 | if l.len() > 0 { 115 | let value = { 116 | if req.at_head { 117 | l.remove(0) 118 | } else { 119 | l.pop().unwrap() 120 | } 121 | }; 122 | 123 | list_res.elements.push(value); 124 | } 125 | 126 | // standard operation result assignment & final return 127 | let mut op_res = OpResult::new(); 128 | 129 | op_res.ok = true; 130 | op_res.otype = OpType::LIST_POP; 131 | op_res.set_list(list_res); 132 | 133 | op_res 134 | } 135 | 136 | pub fn index(db: &mut HashMap, Atom>, req: &ListIndexOp) -> OpResult { 137 | // get list to query 138 | match db.get(req.get_key()) { 139 | Some(atom) => match atom { 140 | Atom::List(l) => { 141 | let mut index_res = ListRes::new(); 142 | 143 | // get absolute pos, calculating from absolute (non-neg) 144 | // or relative (negative) supplied positions. 145 | let pos = { 146 | if req.index < 0 { 147 | let tmp: i64 = (l.len() as i64) + req.index as i64; 148 | if tmp < 0 { 149 | 0 150 | } else { 151 | tmp as usize 152 | } 153 | } else { 154 | req.index as usize 155 | } 156 | }; 157 | 158 | // return element, if position valid 159 | if pos < l.len() { 160 | index_res.elements.push(l[pos].clone()); 161 | } 162 | 163 | // standard operation result assignment & final return 164 | let mut op_res = OpResult::new(); 165 | op_res.ok = true; 166 | op_res.otype = OpType::LIST_INDEX; 167 | op_res.set_list(index_res); 168 | 169 | op_res 170 | } 171 | _ => result_err(-400, "not a list"), 172 | }, 173 | None => result_err(-404, "Not Found"), 174 | } 175 | } 176 | 177 | #[cfg(test)] 178 | mod tests { 179 | use crate::list; 180 | use memds_proto::memds_api::{KeyOp, ListIndexOp, ListPopOp, ListPushOp, OpType}; 181 | use memds_proto::Atom; 182 | use std::collections::HashMap; 183 | 184 | fn get_test_db() -> HashMap, Atom> { 185 | let mut db: HashMap, Atom> = HashMap::new(); 186 | db.insert(b"foo".to_vec(), Atom::String(b"bar".to_vec())); 187 | db.insert(b"name".to_vec(), Atom::String(b"Jane Doe".to_vec())); 188 | db.insert(b"age".to_vec(), Atom::String(b"25".to_vec())); 189 | 190 | db 191 | } 192 | 193 | #[test] 194 | fn basic() { 195 | let mut db = get_test_db(); 196 | 197 | // push 1 item onto empty list 198 | 199 | let mut req = ListPushOp::new(); 200 | req.set_key(b"lst".to_vec()); 201 | req.elements.push(b"two".to_vec()); 202 | 203 | let res = list::push(&mut db, &req); 204 | 205 | assert_eq!(res.ok, true); 206 | assert_eq!(res.otype, OpType::LIST_PUSH); 207 | assert!(res.has_count()); 208 | 209 | let count_res = res.get_count(); 210 | assert_eq!(count_res.n, 1); 211 | 212 | // push 1 item onto list head 213 | let mut req = ListPushOp::new(); 214 | req.set_key(b"lst".to_vec()); 215 | req.at_head = true; 216 | req.elements.push(b"one".to_vec()); 217 | 218 | let res = list::push(&mut db, &req); 219 | 220 | assert_eq!(res.ok, true); 221 | assert_eq!(res.otype, OpType::LIST_PUSH); 222 | assert!(res.has_count()); 223 | 224 | let count_res = res.get_count(); 225 | assert_eq!(count_res.n, 2); 226 | 227 | // verify index 0 == "one" 228 | let mut req = ListIndexOp::new(); 229 | req.set_key(b"lst".to_vec()); 230 | req.index = 0; 231 | 232 | let res = list::index(&mut db, &req); 233 | 234 | assert_eq!(res.ok, true); 235 | assert_eq!(res.otype, OpType::LIST_INDEX); 236 | assert!(res.has_list()); 237 | 238 | let list_res = res.get_list(); 239 | assert_eq!(list_res.elements.len(), 1); 240 | assert_eq!(list_res.elements[0], b"one"); 241 | 242 | // verify index 1 == "two" 243 | let mut req = ListIndexOp::new(); 244 | req.set_key(b"lst".to_vec()); 245 | req.index = 1; 246 | 247 | let res = list::index(&mut db, &req); 248 | 249 | assert_eq!(res.ok, true); 250 | assert_eq!(res.otype, OpType::LIST_INDEX); 251 | assert!(res.has_list()); 252 | 253 | let list_res = res.get_list(); 254 | assert_eq!(list_res.elements.len(), 1); 255 | assert_eq!(list_res.elements[0], b"two"); 256 | 257 | // verify index -1 == "two" 258 | let mut req = ListIndexOp::new(); 259 | req.set_key(b"lst".to_vec()); 260 | req.index = -1; 261 | 262 | let res = list::index(&mut db, &req); 263 | 264 | assert_eq!(res.ok, true); 265 | assert_eq!(res.otype, OpType::LIST_INDEX); 266 | assert!(res.has_list()); 267 | 268 | let list_res = res.get_list(); 269 | assert_eq!(list_res.elements.len(), 1); 270 | assert_eq!(list_res.elements[0], b"two"); 271 | 272 | // verify pop() == "two" 273 | let mut req = ListPopOp::new(); 274 | req.set_key(b"lst".to_vec()); 275 | 276 | let res = list::pop(&mut db, &req); 277 | 278 | assert_eq!(res.ok, true); 279 | assert_eq!(res.otype, OpType::LIST_POP); 280 | assert!(res.has_list()); 281 | 282 | let list_res = res.get_list(); 283 | assert_eq!(list_res.elements.len(), 1); 284 | assert_eq!(list_res.elements[0], b"two"); 285 | 286 | // verify with list metadata 287 | let mut req = KeyOp::new(); 288 | req.set_key(b"lst".to_vec()); 289 | 290 | let res = list::info(&mut db, &req); 291 | 292 | assert_eq!(res.ok, true); 293 | assert_eq!(res.otype, OpType::LIST_INFO); 294 | assert!(res.has_list_info()); 295 | 296 | let info_res = res.get_list_info(); 297 | assert_eq!(info_res.length, 1); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /memds-server/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate futures; 2 | extern crate grpcio; 3 | extern crate memds_proto; 4 | #[macro_use] 5 | extern crate log; 6 | 7 | use std::collections::HashMap; 8 | use std::fs::File; 9 | use std::io::{self, Error, ErrorKind, Read}; 10 | use std::sync::Arc; 11 | use std::sync::Mutex; 12 | 13 | use bytes::BytesMut; 14 | use futures::sync::oneshot; 15 | use futures::Future; 16 | use grpcio::{Environment, ServerBuilder}; 17 | use tokio_util::codec::Decoder; 18 | 19 | use memds_proto::memds_api::MemdsMessage_MsgType; 20 | use memds_proto::memds_api_grpc; 21 | use memds_proto::{Atom, MemdsCodec}; 22 | 23 | mod config; 24 | mod keys; 25 | mod list; 26 | mod rpcservice; 27 | mod server; 28 | mod set; 29 | mod string; 30 | 31 | fn init_db(cfg: &config::Config) -> io::Result, Atom>> { 32 | let mut db = HashMap::new(); 33 | 34 | // get filename; if missing, return success 35 | let import_fn = match &cfg.fs.import { 36 | None => { 37 | return Ok(db); 38 | } 39 | Some(n) => n, 40 | }; 41 | 42 | // open file and set up import & decode 43 | let mut codec = MemdsCodec::new(); 44 | let mut f = File::open(import_fn)?; 45 | let mut accum = BytesMut::with_capacity(4096); 46 | let mut buf = [0; 4096]; 47 | 48 | // read each chunk of data from the file 49 | loop { 50 | // read next 4096-byte chunk 51 | let n_read = f.read(&mut buf)?; 52 | if n_read == 0 { 53 | // EOF 54 | break; 55 | } 56 | 57 | // append to accumulated buffer 58 | accum.extend_from_slice(&buf[0..n_read]); 59 | 60 | // for each decodable record... 61 | loop { 62 | // attempt to decode record from accumulated bytes 63 | match codec.decode(&mut accum) { 64 | // decode err 65 | Err(_) => { 66 | return Err(Error::new(ErrorKind::Other, "protobuf decode")); 67 | } 68 | 69 | // incomplete record 70 | Ok(None) => break, 71 | 72 | // complete record; import it. 73 | Ok(Some(msg)) => { 74 | // EOF record; return success 75 | if msg.mtype == MemdsMessage_MsgType::END { 76 | return Ok(db); 77 | 78 | // require DBVAL records 79 | } else if (msg.mtype != MemdsMessage_MsgType::DBVAL) || !msg.has_dbv() { 80 | return Err(Error::new(ErrorKind::Other, "unexpected record type")); 81 | } 82 | 83 | // import record 84 | if !keys::import_dbv(&mut db, None, msg.get_dbv()) { 85 | return Err(Error::new(ErrorKind::Other, "record import failed")); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | Err(Error::new( 93 | ErrorKind::Other, 94 | "truncated file; EOF without terminator", 95 | )) 96 | } 97 | 98 | fn main() { 99 | let env = Arc::new(Environment::new(1)); 100 | 101 | let cfg = config::get(); 102 | 103 | let initial_db = init_db(&cfg).unwrap(); 104 | 105 | let service = memds_api_grpc::create_memds(rpcservice::MemdsService { 106 | map: Arc::new(Mutex::new(initial_db)), 107 | }); 108 | let mut server = ServerBuilder::new(env) 109 | .register_service(service) 110 | .bind(cfg.network.bind_addr, cfg.network.bind_port) 111 | .build() 112 | .unwrap(); 113 | server.start(); 114 | for (host, port) in server.bind_addrs() { 115 | println!("listening on {}:{}", host, port); 116 | } 117 | let (tx, rx) = oneshot::channel(); 118 | std::thread::spawn(move || { 119 | println!("Press ENTER to exit..."); 120 | let _ = io::stdin().read(&mut [0]).unwrap(); 121 | tx.send(()) 122 | }); 123 | let _ = rx.wait(); 124 | let _ = server.shutdown().wait(); 125 | } 126 | -------------------------------------------------------------------------------- /memds-server/src/rpcservice.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::sync::Arc; 3 | use std::sync::Mutex; 4 | 5 | use futures::Future; 6 | use grpcio::{RpcContext, UnarySink}; 7 | 8 | use memds_proto::memds_api::{OpResult, OpType, RequestMsg, ResponseMsg}; 9 | use memds_proto::memds_api_grpc::Memds; 10 | use memds_proto::util::result_err; 11 | use memds_proto::Atom; 12 | 13 | use crate::keys; 14 | use crate::list; 15 | use crate::server; 16 | use crate::set; 17 | use crate::string; 18 | 19 | /// The in-memory database shared amongst all clients. 20 | /// 21 | /// This database will be shared via `Arc`, so to mutate the internal map we're 22 | /// going to use a `Mutex` for interior mutability. 23 | 24 | #[derive(Clone)] 25 | pub struct MemdsService { 26 | pub map: Arc, Atom>>>, 27 | } 28 | 29 | impl Memds for MemdsService { 30 | fn exec(&mut self, ctx: RpcContext, msg_req: RequestMsg, sink: UnarySink) { 31 | let mut out_resp = ResponseMsg::new(); 32 | out_resp.ok = true; 33 | 34 | // lock db 35 | let mut db = self.map.lock().unwrap(); 36 | 37 | // handle requests 38 | let ops = msg_req.get_ops(); 39 | for op in ops.iter() { 40 | match op.otype { 41 | OpType::KEY_DUMP => { 42 | if !op.has_key() { 43 | out_resp.results.push(result_err(-400, "Invalid op")); 44 | continue; 45 | } 46 | let op_req = op.get_key(); 47 | let op_res = keys::dump(&mut db, op_req); 48 | out_resp.results.push(op_res); 49 | } 50 | 51 | OpType::KEY_RESTORE => { 52 | if !op.has_set() { 53 | out_resp.results.push(result_err(-400, "Invalid op")); 54 | continue; 55 | } 56 | 57 | let op_req = op.get_set(); 58 | let op_res = keys::restore(&mut db, op_req); 59 | out_resp.results.push(op_res); 60 | } 61 | 62 | OpType::KEYS_DEL | OpType::KEYS_EXIST => { 63 | if !op.has_key_list() { 64 | out_resp.results.push(result_err(-400, "Invalid op")); 65 | continue; 66 | } 67 | let keys_req = op.get_key_list(); 68 | let remove_it = op.otype == OpType::KEYS_DEL; 69 | let op_res = keys::del_exist(&mut db, keys_req, remove_it); 70 | out_resp.results.push(op_res); 71 | } 72 | 73 | OpType::KEYS_RENAME => { 74 | if !op.has_rename() { 75 | out_resp.results.push(result_err(-400, "Invalid op")); 76 | continue; 77 | } 78 | let rn_req = op.get_rename(); 79 | let op_res = keys::rename(&mut db, rn_req); 80 | out_resp.results.push(op_res); 81 | } 82 | 83 | OpType::KEYS_TYPE => { 84 | if !op.has_key() { 85 | out_resp.results.push(result_err(-400, "Invalid op")); 86 | continue; 87 | } 88 | let key_req = op.get_key(); 89 | let op_res = keys::typ(&mut db, key_req); 90 | out_resp.results.push(op_res); 91 | } 92 | 93 | OpType::SET_ADD | OpType::SET_DEL | OpType::SET_ISMEMBER => { 94 | if !op.has_keyed_list() { 95 | out_resp.results.push(result_err(-400, "Invalid op")); 96 | continue; 97 | } 98 | let op_req = op.get_keyed_list(); 99 | let op_res = { 100 | if op.otype == OpType::SET_ISMEMBER { 101 | set::is_member(&mut db, op_req) 102 | } else { 103 | set::add_del(&mut db, op_req, op.otype) 104 | } 105 | }; 106 | out_resp.results.push(op_res); 107 | } 108 | 109 | OpType::SET_DIFF | OpType::SET_UNION | OpType::SET_INTERSECT => { 110 | if !op.has_cmp_stor() { 111 | out_resp.results.push(result_err(-400, "Invalid op")); 112 | continue; 113 | } 114 | let op_req = op.get_cmp_stor(); 115 | let op_res = match op.otype { 116 | OpType::SET_DIFF => set::diff(&mut db, op_req), 117 | OpType::SET_UNION => set::union(&mut db, op_req), 118 | OpType::SET_INTERSECT => set::intersect(&mut db, op_req), 119 | _ => unreachable!(), 120 | }; 121 | 122 | out_resp.results.push(op_res); 123 | } 124 | 125 | OpType::SET_INFO | OpType::SET_MEMBERS => { 126 | if !op.has_key() { 127 | out_resp.results.push(result_err(-400, "Invalid op")); 128 | continue; 129 | } 130 | let op_req = op.get_key(); 131 | let op_res = { 132 | if op.otype == OpType::SET_INFO { 133 | set::info(&mut db, op_req) 134 | } else { 135 | set::members(&mut db, op_req) 136 | } 137 | }; 138 | out_resp.results.push(op_res); 139 | } 140 | 141 | OpType::SET_MOVE => { 142 | if !op.has_set_move() { 143 | out_resp.results.push(result_err(-400, "Invalid op")); 144 | continue; 145 | } 146 | let op_req = op.get_set_move(); 147 | let op_res = set::mov(&mut db, op_req); 148 | out_resp.results.push(op_res); 149 | } 150 | 151 | OpType::SRV_BGSAVE => { 152 | let op_res = server::bgsave(&mut db); 153 | out_resp.results.push(op_res); 154 | } 155 | 156 | OpType::SRV_DBSIZE => { 157 | let op_res = server::dbsize(&mut db); 158 | out_resp.results.push(op_res); 159 | } 160 | 161 | OpType::SRV_FLUSHDB | OpType::SRV_FLUSHALL => { 162 | let op_res = server::flush(&mut db, op.otype); 163 | out_resp.results.push(op_res); 164 | } 165 | 166 | OpType::SRV_TIME => { 167 | let op_res = server::time(); 168 | out_resp.results.push(op_res); 169 | } 170 | 171 | OpType::STR_GET | OpType::STR_GETRANGE => { 172 | if !op.has_get() { 173 | out_resp.results.push(result_err(-400, "Invalid op")); 174 | continue; 175 | } 176 | let get_req = op.get_get(); 177 | let op_res = string::get(&mut db, get_req, op.otype); 178 | out_resp.results.push(op_res); 179 | } 180 | 181 | OpType::STR_SET | OpType::STR_APPEND => { 182 | if !op.has_set() { 183 | out_resp.results.push(result_err(-400, "Invalid op")); 184 | continue; 185 | } 186 | 187 | let set_req = op.get_set(); 188 | let op_res = { 189 | if op.otype == OpType::STR_SET { 190 | string::set(&mut db, set_req) 191 | } else { 192 | string::append(&mut db, set_req) 193 | } 194 | }; 195 | out_resp.results.push(op_res); 196 | } 197 | 198 | OpType::STR_DECR | OpType::STR_DECRBY | OpType::STR_INCR | OpType::STR_INCRBY => { 199 | if !op.has_num() { 200 | out_resp.results.push(result_err(-400, "Invalid op")); 201 | continue; 202 | } 203 | 204 | let num_req = op.get_num(); 205 | let op_res = string::incrdecr(&mut db, op.otype, num_req); 206 | out_resp.results.push(op_res); 207 | } 208 | 209 | OpType::LIST_PUSH => { 210 | if !op.has_lpush() { 211 | out_resp.results.push(result_err(-400, "Invalid op")); 212 | continue; 213 | } 214 | 215 | let lpush_req = op.get_lpush(); 216 | let op_res = list::push(&mut db, lpush_req); 217 | out_resp.results.push(op_res); 218 | } 219 | 220 | OpType::LIST_POP => { 221 | if !op.has_lpop() { 222 | out_resp.results.push(result_err(-400, "Invalid op")); 223 | continue; 224 | } 225 | 226 | let lpop_req = op.get_lpop(); 227 | let op_res = list::pop(&mut db, lpop_req); 228 | out_resp.results.push(op_res); 229 | } 230 | 231 | OpType::LIST_INFO => { 232 | if !op.has_key() { 233 | out_resp.results.push(result_err(-400, "Invalid op")); 234 | continue; 235 | } 236 | 237 | let key_req = op.get_key(); 238 | let op_res = list::info(&mut db, key_req); 239 | out_resp.results.push(op_res); 240 | } 241 | 242 | OpType::LIST_INDEX => { 243 | if !op.has_lindex() { 244 | out_resp.results.push(result_err(-400, "Invalid op")); 245 | continue; 246 | } 247 | 248 | let lindex_req = op.get_lindex(); 249 | let op_res = list::index(&mut db, lindex_req); 250 | out_resp.results.push(op_res); 251 | } 252 | 253 | _ => { 254 | let mut res = OpResult::new(); 255 | res.ok = false; 256 | res.err_code = -400; 257 | res.err_message = String::from("Invalid op"); 258 | out_resp.results.push(res); 259 | } 260 | } 261 | } 262 | 263 | let f = sink 264 | .success(out_resp) 265 | .map_err(|e| error!("exec req failed: {:?}", e)); 266 | ctx.spawn(f) 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /memds-server/src/server.rs: -------------------------------------------------------------------------------- 1 | use bytes::BytesMut; 2 | use nix::unistd::{fork, ForkResult}; 3 | use std::collections::HashMap; 4 | use std::fs::File; 5 | use std::io::Write; 6 | use std::time::{Duration, SystemTime}; 7 | use tokio_util::codec::Encoder; 8 | 9 | use crate::keys; 10 | use memds_proto::memds_api::{ 11 | CountRes, MemdsMessage, MemdsMessage_MsgType, OpResult, OpType, TimeRes, 12 | }; 13 | use memds_proto::util::result_err; 14 | use memds_proto::{Atom, MemdsCodec}; 15 | 16 | const EXPORT_FN: &'static str = "memds-export.dat"; 17 | 18 | fn systime() -> Duration { 19 | SystemTime::now() 20 | .duration_since(SystemTime::UNIX_EPOCH) 21 | .unwrap() 22 | } 23 | 24 | pub fn dbsize(db: &mut HashMap, Atom>) -> OpResult { 25 | // query db item count 26 | let mut info_res = CountRes::new(); 27 | info_res.n = db.len() as u64; 28 | 29 | // standard operation result assignment & final return 30 | let mut op_res = OpResult::new(); 31 | 32 | op_res.ok = true; 33 | op_res.otype = OpType::SRV_DBSIZE; 34 | op_res.set_count(info_res); 35 | 36 | op_res 37 | } 38 | 39 | pub fn flush(db: &mut HashMap, Atom>, otype: OpType) -> OpResult { 40 | // clear entire db; we only have 1 db right now, 41 | // making flush [one] db operation equivalent to flush-all-dbs. 42 | db.clear(); 43 | 44 | // standard operation result assignment & final return 45 | let mut op_res = OpResult::new(); 46 | 47 | op_res.ok = true; 48 | op_res.otype = otype; 49 | 50 | op_res 51 | } 52 | 53 | pub fn time() -> OpResult { 54 | // query system time 55 | let now = systime(); 56 | 57 | // calculate secs & nanosecs 58 | let mut time_res = TimeRes::new(); 59 | time_res.secs = now.as_secs(); 60 | time_res.nanosecs = now.subsec_nanos(); 61 | 62 | // standard operation result assignment & final return 63 | let mut op_res = OpResult::new(); 64 | op_res.ok = true; 65 | op_res.otype = OpType::SRV_TIME; 66 | op_res.set_srv_time(time_res); 67 | 68 | op_res 69 | } 70 | 71 | pub fn bgsave(db: &mut HashMap, Atom>) -> OpResult { 72 | match fork() { 73 | Ok(ForkResult::Parent { child: _, .. }) => { 74 | // standard operation result assignment & final return 75 | let mut op_res = OpResult::new(); 76 | 77 | op_res.ok = true; 78 | op_res.otype = OpType::SRV_BGSAVE; 79 | 80 | return op_res; 81 | } 82 | Ok(ForkResult::Child) => {} 83 | 84 | Err(_) => { 85 | return result_err(-500, "Internal error - fork"); 86 | } 87 | } 88 | 89 | // child continues... 90 | 91 | let f_res = File::create(EXPORT_FN); 92 | if f_res.is_err() { 93 | println!("Internal error I/O - create"); 94 | std::process::exit(1); 95 | } 96 | let mut f = f_res.unwrap(); 97 | let mut codec = MemdsCodec::new(); 98 | 99 | for key in db.keys() { 100 | // serialize key+value into protobuf message 101 | let dbv = keys::export_dbv(db, key).unwrap(); 102 | let mut msg = MemdsMessage::new(); 103 | msg.mtype = MemdsMessage_MsgType::DBVAL; 104 | msg.set_dbv(dbv); 105 | 106 | // encode message into checksummed stream 107 | let msg_raw = &mut BytesMut::new(); 108 | codec.encode(msg, msg_raw).unwrap(); 109 | 110 | // write packet to file 111 | let res = f.write_all(msg_raw); 112 | if res.is_err() { 113 | println!("Internal error I/O - write"); 114 | std::process::exit(1); 115 | } 116 | } 117 | 118 | // stream terminator 119 | let mut end_msg = MemdsMessage::new(); 120 | end_msg.mtype = MemdsMessage_MsgType::END; 121 | 122 | // encode terminator message into checksummed stream 123 | let end_msg_raw = &mut BytesMut::new(); 124 | codec.encode(end_msg, end_msg_raw).unwrap(); 125 | 126 | // write terminating packet to file 127 | let res = f.write_all(end_msg_raw); 128 | if res.is_err() { 129 | println!("Internal error I/O - write end"); 130 | std::process::exit(1); 131 | } 132 | 133 | // flush to disk 134 | if f.sync_data().is_err() { 135 | println!("Internal error I/O - sync"); 136 | std::process::exit(1); 137 | } 138 | 139 | std::process::exit(0); 140 | } 141 | 142 | #[cfg(test)] 143 | mod tests { 144 | use crate::server; 145 | use memds_proto::memds_api::OpType; 146 | use memds_proto::Atom; 147 | use std::collections::HashMap; 148 | 149 | fn get_test_db() -> HashMap, Atom> { 150 | let mut db: HashMap, Atom> = HashMap::new(); 151 | db.insert(b"foo".to_vec(), Atom::String(b"bar".to_vec())); 152 | db.insert(b"name".to_vec(), Atom::String(b"Jane Doe".to_vec())); 153 | db.insert(b"age".to_vec(), Atom::String(b"25".to_vec())); 154 | 155 | db 156 | } 157 | 158 | #[test] 159 | fn dbsize() { 160 | let mut db = get_test_db(); 161 | 162 | let res = server::dbsize(&mut db); 163 | 164 | assert_eq!(res.ok, true); 165 | assert_eq!(res.otype, OpType::SRV_DBSIZE); 166 | assert!(res.has_count()); 167 | 168 | let count_res = res.get_count(); 169 | assert_eq!(count_res.n, 3); 170 | } 171 | 172 | #[test] 173 | fn flush() { 174 | let mut db = get_test_db(); 175 | 176 | let res = server::flush(&mut db, OpType::SRV_FLUSHALL); 177 | 178 | assert_eq!(res.ok, true); 179 | assert_eq!(res.otype, OpType::SRV_FLUSHALL); 180 | 181 | let res = server::dbsize(&mut db); 182 | let count_res = res.get_count(); 183 | assert_eq!(count_res.n, 0); 184 | 185 | let res = server::flush(&mut db, OpType::SRV_FLUSHDB); 186 | 187 | assert_eq!(res.ok, true); 188 | assert_eq!(res.otype, OpType::SRV_FLUSHDB); 189 | 190 | let res = server::dbsize(&mut db); 191 | let count_res = res.get_count(); 192 | assert_eq!(count_res.n, 0); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /memds-server/src/set.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::collections::HashSet; 3 | 4 | use memds_proto::memds_api::{ 5 | CmpStoreOp, CountRes, KeyOp, KeyedListOp, ListRes, OpResult, OpType, SetInfoRes, SetMoveOp, 6 | }; 7 | use memds_proto::util::result_err; 8 | use memds_proto::Atom; 9 | 10 | pub fn add_del(db: &mut HashMap, Atom>, req: &KeyedListOp, otype: OpType) -> OpResult { 11 | let do_delete = match otype { 12 | OpType::SET_DEL => true, 13 | _ => false, 14 | }; 15 | 16 | // get set to mutate. 17 | let st = { 18 | let key = req.get_key(); 19 | match db.get_mut(key) { 20 | None => { 21 | // if we're deleting, go no further 22 | if do_delete { 23 | return result_err(-404, "Not Found"); 24 | } 25 | 26 | // does not exist; create empty set 27 | db.insert(key.to_vec(), Atom::Set(HashSet::new())); 28 | match db.get_mut(key) { 29 | None => unreachable!(), 30 | Some(atom) => match atom { 31 | Atom::Set(st) => st, 32 | _ => unreachable!(), 33 | }, 34 | } 35 | } 36 | 37 | // found the key. grab ref. 38 | Some(atom) => match atom { 39 | Atom::Set(st) => st, 40 | _ => { 41 | return result_err(-400, "not a list"); 42 | } 43 | }, 44 | } 45 | }; 46 | 47 | // iterate through each element in request, adding/deleting 48 | let mut n_updates = 0; 49 | for element in req.elements.iter() { 50 | if do_delete { 51 | if st.remove(element) { 52 | n_updates += 1; 53 | } 54 | } else { 55 | if st.insert(element.to_vec()) { 56 | n_updates += 1; 57 | } 58 | } 59 | } 60 | 61 | // return number of updates (not number of elements) 62 | let mut count_res = CountRes::new(); 63 | count_res.n = n_updates as u64; 64 | 65 | // standard operation result assignment & final return 66 | let mut op_res = OpResult::new(); 67 | 68 | op_res.ok = true; 69 | op_res.otype = otype; 70 | op_res.set_count(count_res); 71 | 72 | op_res 73 | } 74 | 75 | pub fn mov(db: &mut HashMap, Atom>, req: &SetMoveOp) -> OpResult { 76 | // test sets 77 | let src_key = req.get_src_key(); 78 | match db.get(src_key) { 79 | None => { 80 | return result_err(-404, "Not Found"); 81 | } 82 | Some(atom) => match atom { 83 | Atom::Set(_st) => {} 84 | _ => { 85 | return result_err(-400, "not a set"); 86 | } 87 | }, 88 | } 89 | let dest_key = req.get_dest_key(); 90 | match db.get(dest_key) { 91 | None => { 92 | db.insert(dest_key.to_vec(), Atom::Set(HashSet::new())); 93 | } 94 | Some(atom) => match atom { 95 | Atom::Set(_st) => {} 96 | _ => { 97 | return result_err(-400, "not a set"); 98 | } 99 | }, 100 | } 101 | 102 | let src_st = { 103 | match db.get_mut(src_key) { 104 | None => unreachable!(), 105 | Some(atom) => match atom { 106 | Atom::Set(st) => st, 107 | _ => unreachable!(), 108 | }, 109 | } 110 | }; 111 | 112 | let mut n_updated = 0; 113 | let member = req.get_member(); 114 | if src_st.remove(member) { 115 | n_updated += 1; 116 | } 117 | 118 | // get dest set, and insert 119 | if n_updated > 0 { 120 | match db.get_mut(dest_key) { 121 | None => unreachable!(), 122 | Some(atom) => match atom { 123 | Atom::Set(st) => { 124 | st.insert(member.to_vec()); 125 | } 126 | _ => unreachable!(), 127 | }, 128 | } 129 | } 130 | 131 | // return set info aka metadata. at present, just the element count. 132 | let mut count_res = CountRes::new(); 133 | count_res.n = n_updated as u64; 134 | 135 | // standard operation result assignment & final return 136 | let mut op_res = OpResult::new(); 137 | 138 | op_res.ok = true; 139 | op_res.otype = OpType::SET_MOVE; 140 | op_res.set_count(count_res); 141 | 142 | op_res 143 | } 144 | 145 | pub fn info(db: &mut HashMap, Atom>, req: &KeyOp) -> OpResult { 146 | // get set to query 147 | let st = { 148 | let key = req.get_key(); 149 | match db.get(key) { 150 | None => { 151 | return result_err(-404, "Not Found"); 152 | } 153 | Some(atom) => match atom { 154 | Atom::Set(st) => st, 155 | _ => { 156 | return result_err(-400, "not a set"); 157 | } 158 | }, 159 | } 160 | }; 161 | 162 | // return set info aka metadata. at present, just the element count. 163 | let mut info_res = SetInfoRes::new(); 164 | info_res.length = st.len() as u32; 165 | 166 | // standard operation result assignment & final return 167 | let mut op_res = OpResult::new(); 168 | 169 | op_res.ok = true; 170 | op_res.otype = OpType::SET_INFO; 171 | op_res.set_set_info(info_res); 172 | 173 | op_res 174 | } 175 | 176 | pub fn members(db: &mut HashMap, Atom>, req: &KeyOp) -> OpResult { 177 | // get set to query 178 | let st = { 179 | let key = req.get_key(); 180 | match db.get(key) { 181 | None => { 182 | return result_err(-404, "Not Found"); 183 | } 184 | Some(atom) => match atom { 185 | Atom::Set(st) => st, 186 | _ => { 187 | return result_err(-400, "not a set"); 188 | } 189 | }, 190 | } 191 | }; 192 | 193 | // push entire set contents into result. 194 | // todo: there is probably a Rust-y way to .zip() or .collect() here 195 | let mut list_res = ListRes::new(); 196 | for item in st.iter() { 197 | list_res.elements.push(item.to_vec()); 198 | } 199 | 200 | // standard operation result assignment & final return 201 | let mut op_res = OpResult::new(); 202 | 203 | op_res.ok = true; 204 | op_res.otype = OpType::SET_MEMBERS; 205 | op_res.set_list(list_res); 206 | 207 | op_res 208 | } 209 | 210 | pub fn intersect(db: &mut HashMap, Atom>, req: &CmpStoreOp) -> OpResult { 211 | if req.keys.len() < 1 { 212 | return result_err(-400, "at least one key required"); 213 | } 214 | 215 | // iterate through list of provided keys 216 | let mut sect_result = HashSet::new(); 217 | let mut first_key = true; 218 | for key in req.keys.iter() { 219 | // first key: read set, or empty set upon exception 220 | if first_key { 221 | first_key = false; 222 | 223 | let atom_res = db.get(key); 224 | match atom_res { 225 | Some(Atom::Set(st)) => { 226 | sect_result = st.clone(); 227 | } 228 | _ => { 229 | sect_result = HashSet::new(); 230 | } 231 | } 232 | 233 | // following keys: intersect with working set 234 | } else { 235 | match db.get(key) { 236 | Some(Atom::Set(st)) => { 237 | let mut tmp_result = HashSet::new(); 238 | for sect_elem in sect_result.iter() { 239 | if st.contains(sect_elem) { 240 | tmp_result.insert(sect_elem.clone()); 241 | } 242 | } 243 | 244 | sect_result = tmp_result; 245 | } 246 | _ => { 247 | sect_result.clear(); 248 | } 249 | }; 250 | } 251 | 252 | // shortcut: if empty set, no need to continue 253 | if sect_result.is_empty() { 254 | break; 255 | } 256 | } 257 | 258 | // standard operation result assignment 259 | let mut op_res = OpResult::new(); 260 | 261 | op_res.ok = true; 262 | op_res.otype = OpType::SET_INTERSECT; 263 | 264 | let do_store = req.store_key.len() > 0; 265 | 266 | // if storing in db, do so + return count stored 267 | if do_store { 268 | let n_results = sect_result.len() as u64; 269 | db.insert(req.store_key.to_vec(), Atom::Set(sect_result)); 270 | 271 | let mut count_res = CountRes::new(); 272 | count_res.n = n_results; 273 | op_res.set_count(count_res); 274 | 275 | // otherwise return calculated result directly to client 276 | } else { 277 | let mut list_res = ListRes::new(); 278 | for elem in sect_result.iter() { 279 | list_res.elements.push(elem.to_vec()); 280 | } 281 | op_res.set_list(list_res); 282 | } 283 | 284 | op_res 285 | } 286 | 287 | pub fn diff(db: &mut HashMap, Atom>, req: &CmpStoreOp) -> OpResult { 288 | if req.keys.len() < 1 { 289 | return result_err(-400, "at least one key required"); 290 | } 291 | 292 | // iterate through list of provided keys 293 | let mut diff_result = HashSet::new(); 294 | let mut first_key = true; 295 | for key in req.keys.iter() { 296 | // first key: read set, or empty set upon exception 297 | if first_key { 298 | first_key = false; 299 | 300 | let atom_res = db.get(key); 301 | match atom_res { 302 | Some(Atom::Set(st)) => { 303 | diff_result = st.clone(); 304 | } 305 | _ => { 306 | diff_result = HashSet::new(); 307 | } 308 | } 309 | 310 | // following keys: attempt to remove from difference set 311 | } else { 312 | let atom_res = db.get(key); 313 | let operand = match atom_res { 314 | Some(Atom::Set(st)) => st.clone(), 315 | _ => HashSet::new(), 316 | }; 317 | 318 | for oper_elem in operand.iter() { 319 | diff_result.remove(oper_elem); 320 | } 321 | } 322 | } 323 | 324 | // standard operation result assignment 325 | let mut op_res = OpResult::new(); 326 | 327 | op_res.ok = true; 328 | op_res.otype = OpType::SET_DIFF; 329 | 330 | let do_store = req.store_key.len() > 0; 331 | 332 | // if storing in db, do so + return count stored 333 | if do_store { 334 | let n_results = diff_result.len() as u64; 335 | db.insert(req.store_key.to_vec(), Atom::Set(diff_result)); 336 | 337 | let mut count_res = CountRes::new(); 338 | count_res.n = n_results; 339 | op_res.set_count(count_res); 340 | 341 | // otherwise return calculated result directly to client 342 | } else { 343 | let mut list_res = ListRes::new(); 344 | for elem in diff_result.iter() { 345 | list_res.elements.push(elem.to_vec()); 346 | } 347 | op_res.set_list(list_res); 348 | } 349 | 350 | op_res 351 | } 352 | 353 | pub fn union(db: &mut HashMap, Atom>, req: &CmpStoreOp) -> OpResult { 354 | if req.keys.len() < 1 { 355 | return result_err(-400, "at least one key required"); 356 | } 357 | 358 | // iterate through list of provided keys 359 | // inserting into result union 360 | let mut diff_result = HashSet::new(); 361 | for key in req.keys.iter() { 362 | match db.get(key) { 363 | Some(Atom::Set(st)) => { 364 | for elem in st.iter() { 365 | diff_result.insert(elem.clone()); 366 | } 367 | } 368 | _ => {} 369 | } 370 | } 371 | 372 | // standard operation result assignment 373 | let mut op_res = OpResult::new(); 374 | 375 | op_res.ok = true; 376 | op_res.otype = OpType::SET_UNION; 377 | 378 | let do_store = req.store_key.len() > 0; 379 | 380 | // if storing in db, do so + return count stored 381 | if do_store { 382 | let n_results = diff_result.len() as u64; 383 | db.insert(req.store_key.to_vec(), Atom::Set(diff_result)); 384 | 385 | let mut count_res = CountRes::new(); 386 | count_res.n = n_results; 387 | op_res.set_count(count_res); 388 | 389 | // otherwise return calculated result directly to client 390 | } else { 391 | let mut list_res = ListRes::new(); 392 | for elem in diff_result.iter() { 393 | list_res.elements.push(elem.to_vec()); 394 | } 395 | op_res.set_list(list_res); 396 | } 397 | 398 | op_res 399 | } 400 | 401 | pub fn is_member(db: &mut HashMap, Atom>, req: &KeyedListOp) -> OpResult { 402 | // get set to query 403 | let st = { 404 | let key = req.get_key(); 405 | match db.get(key) { 406 | None => { 407 | return result_err(-404, "Not Found"); 408 | } 409 | Some(atom) => match atom { 410 | Atom::Set(st) => st, 411 | _ => { 412 | return result_err(-400, "not a set"); 413 | } 414 | }, 415 | } 416 | }; 417 | 418 | // count number of matches of set intersecting with provided list 419 | let mut n_match = 0; 420 | for item in req.elements.iter() { 421 | if st.contains(item) { 422 | n_match += 1; 423 | } 424 | } 425 | 426 | // return matched count 427 | let mut count_res = CountRes::new(); 428 | count_res.n = n_match; 429 | 430 | // standard operation result assignment & final return 431 | let mut op_res = OpResult::new(); 432 | 433 | op_res.ok = true; 434 | op_res.otype = OpType::SET_ISMEMBER; 435 | op_res.set_count(count_res); 436 | 437 | op_res 438 | } 439 | 440 | #[cfg(test)] 441 | mod tests { 442 | use crate::set; 443 | use memds_proto::memds_api::{CmpStoreOp, KeyOp, KeyedListOp, OpType, SetMoveOp}; 444 | use memds_proto::Atom; 445 | use std::collections::HashMap; 446 | use std::collections::HashSet; 447 | 448 | fn get_test_db() -> HashMap, Atom> { 449 | let mut db: HashMap, Atom> = HashMap::new(); 450 | db.insert(b"foo".to_vec(), Atom::String(b"bar".to_vec())); 451 | db.insert(b"name".to_vec(), Atom::String(b"Jane Doe".to_vec())); 452 | db.insert(b"age".to_vec(), Atom::String(b"25".to_vec())); 453 | 454 | let mut st = HashSet::new(); 455 | st.insert(b"a".to_vec()); 456 | st.insert(b"b".to_vec()); 457 | st.insert(b"c".to_vec()); 458 | st.insert(b"d".to_vec()); 459 | db.insert(b"set1".to_vec(), Atom::Set(st)); 460 | 461 | let mut st = HashSet::new(); 462 | st.insert(b"c".to_vec()); 463 | db.insert(b"set2".to_vec(), Atom::Set(st)); 464 | 465 | let mut st = HashSet::new(); 466 | st.insert(b"a".to_vec()); 467 | st.insert(b"c".to_vec()); 468 | st.insert(b"e".to_vec()); 469 | db.insert(b"set3".to_vec(), Atom::Set(st)); 470 | 471 | db 472 | } 473 | 474 | #[test] 475 | fn add() { 476 | let mut db = get_test_db(); 477 | 478 | // add one,two,two == set(one,two) 479 | let mut req = KeyedListOp::new(); 480 | req.set_key(b"a_set".to_vec()); 481 | req.elements.push(b"one".to_vec()); 482 | req.elements.push(b"two".to_vec()); 483 | req.elements.push(b"two".to_vec()); 484 | 485 | let res = set::add_del(&mut db, &req, OpType::SET_ADD); 486 | 487 | assert_eq!(res.ok, true); 488 | assert_eq!(res.otype, OpType::SET_ADD); 489 | assert!(res.has_count()); 490 | 491 | let count_res = res.get_count(); 492 | assert_eq!(count_res.n, 2); 493 | 494 | // get set info, verify count again 495 | let mut req = KeyOp::new(); 496 | req.set_key(b"a_set".to_vec()); 497 | 498 | let res = set::info(&mut db, &req); 499 | assert_eq!(res.ok, true); 500 | assert_eq!(res.otype, OpType::SET_INFO); 501 | assert!(res.has_set_info()); 502 | 503 | let info_res = res.get_set_info(); 504 | assert_eq!(info_res.length, 2); 505 | } 506 | 507 | #[test] 508 | fn del() { 509 | let mut db = get_test_db(); 510 | 511 | // add one,two,two == set(one,two) 512 | let mut req = KeyedListOp::new(); 513 | req.set_key(b"a_set".to_vec()); 514 | req.elements.push(b"one".to_vec()); 515 | req.elements.push(b"two".to_vec()); 516 | req.elements.push(b"two".to_vec()); 517 | 518 | let res = set::add_del(&mut db, &req, OpType::SET_ADD); 519 | 520 | assert_eq!(res.ok, true); 521 | assert_eq!(res.otype, OpType::SET_ADD); 522 | assert!(res.has_count()); 523 | 524 | let count_res = res.get_count(); 525 | assert_eq!(count_res.n, 2); 526 | 527 | // del one == set(two) 528 | let mut req = KeyedListOp::new(); 529 | req.set_key(b"a_set".to_vec()); 530 | req.elements.push(b"one".to_vec()); 531 | 532 | let res = set::add_del(&mut db, &req, OpType::SET_DEL); 533 | 534 | assert_eq!(res.ok, true); 535 | assert_eq!(res.otype, OpType::SET_DEL); 536 | assert!(res.has_count()); 537 | 538 | let count_res = res.get_count(); 539 | assert_eq!(count_res.n, 1); 540 | 541 | // get set info, verify count again 542 | let mut req = KeyOp::new(); 543 | req.set_key(b"a_set".to_vec()); 544 | 545 | let res = set::info(&mut db, &req); 546 | assert_eq!(res.ok, true); 547 | assert_eq!(res.otype, OpType::SET_INFO); 548 | assert!(res.has_set_info()); 549 | 550 | let info_res = res.get_set_info(); 551 | assert_eq!(info_res.length, 1); 552 | } 553 | 554 | #[test] 555 | fn members() { 556 | let mut db = get_test_db(); 557 | 558 | // add one,two,two == set(one,two) 559 | let mut req = KeyedListOp::new(); 560 | req.set_key(b"a_set".to_vec()); 561 | req.elements.push(b"one".to_vec()); 562 | req.elements.push(b"two".to_vec()); 563 | req.elements.push(b"two".to_vec()); 564 | 565 | let res = set::add_del(&mut db, &req, OpType::SET_ADD); 566 | 567 | assert_eq!(res.ok, true); 568 | assert_eq!(res.otype, OpType::SET_ADD); 569 | assert!(res.has_count()); 570 | 571 | let count_res = res.get_count(); 572 | assert_eq!(count_res.n, 2); 573 | 574 | // get set info, verify count again 575 | let mut req = KeyOp::new(); 576 | req.set_key(b"a_set".to_vec()); 577 | 578 | let mut res = set::members(&mut db, &req); 579 | assert_eq!(res.ok, true); 580 | assert_eq!(res.otype, OpType::SET_MEMBERS); 581 | assert!(res.has_list()); 582 | 583 | let list_res = res.mut_list(); 584 | list_res.elements.sort(); 585 | assert_eq!(list_res.elements.len(), 2); 586 | assert_eq!(list_res.elements[0], b"one"); 587 | assert_eq!(list_res.elements[1], b"two"); 588 | } 589 | 590 | #[test] 591 | fn is_member() { 592 | let mut db = get_test_db(); 593 | 594 | // add one,two,two == set(one,two) 595 | let mut req = KeyedListOp::new(); 596 | req.set_key(b"a_set".to_vec()); 597 | req.elements.push(b"one".to_vec()); 598 | req.elements.push(b"two".to_vec()); 599 | req.elements.push(b"two".to_vec()); 600 | 601 | let res = set::add_del(&mut db, &req, OpType::SET_ADD); 602 | 603 | assert_eq!(res.ok, true); 604 | assert_eq!(res.otype, OpType::SET_ADD); 605 | assert!(res.has_count()); 606 | 607 | let count_res = res.get_count(); 608 | assert_eq!(count_res.n, 2); 609 | 610 | // get set info, verify count again 611 | let mut req = KeyedListOp::new(); 612 | req.set_key(b"a_set".to_vec()); 613 | req.elements.push(b"one".to_vec()); 614 | req.elements.push(b"does-not-exist".to_vec()); 615 | 616 | let res = set::is_member(&mut db, &req); 617 | assert_eq!(res.ok, true); 618 | assert_eq!(res.otype, OpType::SET_ISMEMBER); 619 | assert!(res.has_count()); 620 | 621 | let count_res = res.get_count(); 622 | assert_eq!(count_res.n, 1); 623 | } 624 | 625 | #[test] 626 | fn diff() { 627 | let mut db = get_test_db(); 628 | 629 | // add one,two,two == set(one,two) 630 | let mut req = CmpStoreOp::new(); 631 | req.keys.push(b"set1".to_vec()); 632 | req.keys.push(b"set2".to_vec()); 633 | req.keys.push(b"set3".to_vec()); 634 | 635 | let mut res = set::diff(&mut db, &req); 636 | 637 | assert_eq!(res.ok, true); 638 | assert_eq!(res.otype, OpType::SET_DIFF); 639 | assert!(res.has_list()); 640 | 641 | let list_res = res.mut_list(); 642 | list_res.elements.sort(); 643 | assert_eq!(list_res.elements.len(), 2); 644 | assert_eq!(list_res.elements[0], b"b"); 645 | assert_eq!(list_res.elements[1], b"d"); 646 | } 647 | 648 | #[test] 649 | fn union() { 650 | let mut db = get_test_db(); 651 | 652 | // add one,two,two == set(one,two) 653 | let mut req = CmpStoreOp::new(); 654 | req.keys.push(b"set1".to_vec()); 655 | req.keys.push(b"set2".to_vec()); 656 | req.keys.push(b"set3".to_vec()); 657 | 658 | let mut res = set::union(&mut db, &req); 659 | 660 | assert_eq!(res.ok, true); 661 | assert_eq!(res.otype, OpType::SET_UNION); 662 | assert!(res.has_list()); 663 | 664 | let list_res = res.mut_list(); 665 | list_res.elements.sort(); 666 | assert_eq!(list_res.elements.len(), 5); 667 | assert_eq!(list_res.elements[0], b"a"); 668 | assert_eq!(list_res.elements[1], b"b"); 669 | assert_eq!(list_res.elements[2], b"c"); 670 | assert_eq!(list_res.elements[3], b"d"); 671 | assert_eq!(list_res.elements[4], b"e"); 672 | } 673 | 674 | #[test] 675 | fn intersect() { 676 | let mut db = get_test_db(); 677 | 678 | // add one,two,two == set(one,two) 679 | let mut req = CmpStoreOp::new(); 680 | req.keys.push(b"set1".to_vec()); 681 | req.keys.push(b"set2".to_vec()); 682 | req.keys.push(b"set3".to_vec()); 683 | 684 | let mut res = set::intersect(&mut db, &req); 685 | 686 | assert_eq!(res.ok, true); 687 | assert_eq!(res.otype, OpType::SET_INTERSECT); 688 | assert!(res.has_list()); 689 | 690 | let list_res = res.mut_list(); 691 | assert_eq!(list_res.elements.len(), 1); 692 | assert_eq!(list_res.elements[0], b"c"); 693 | } 694 | 695 | #[test] 696 | fn mov() { 697 | let mut db = get_test_db(); 698 | 699 | // add one,two,two == set(one,two) 700 | let mut req = SetMoveOp::new(); 701 | req.set_src_key(b"set1".to_vec()); 702 | req.set_dest_key(b"setx".to_vec()); 703 | req.set_member(b"d".to_vec()); 704 | 705 | let res = set::mov(&mut db, &req); 706 | 707 | assert_eq!(res.ok, true); 708 | assert_eq!(res.otype, OpType::SET_MOVE); 709 | assert!(res.has_count()); 710 | 711 | let count_res = res.get_count(); 712 | assert_eq!(count_res.n, 1); 713 | 714 | // check 'd' is a member of setx 715 | let mut req = KeyedListOp::new(); 716 | req.set_key(b"setx".to_vec()); 717 | req.elements.push(b"d".to_vec()); 718 | 719 | let res = set::is_member(&mut db, &req); 720 | assert_eq!(res.ok, true); 721 | assert_eq!(res.otype, OpType::SET_ISMEMBER); 722 | assert!(res.has_count()); 723 | 724 | let count_res = res.get_count(); 725 | assert_eq!(count_res.n, 1); 726 | 727 | // check 'd' is not a member of set1 728 | let mut req = KeyedListOp::new(); 729 | req.set_key(b"set1".to_vec()); 730 | req.elements.push(b"d".to_vec()); 731 | 732 | let res = set::is_member(&mut db, &req); 733 | assert_eq!(res.ok, true); 734 | assert_eq!(res.otype, OpType::SET_ISMEMBER); 735 | assert!(res.has_count()); 736 | 737 | let count_res = res.get_count(); 738 | assert_eq!(count_res.n, 0); 739 | } 740 | } 741 | -------------------------------------------------------------------------------- /memds-server/src/string.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::str; 3 | 4 | use memds_proto::memds_api::{ 5 | NumOp, NumRes, OpResult, OpType, StrGetOp, StrGetRes, StrSetOp, StrSetRes, 6 | }; 7 | use memds_proto::util::result_err; 8 | use memds_proto::Atom; 9 | 10 | pub fn incrdecr(db: &mut HashMap, Atom>, otype: OpType, req: &NumOp) -> OpResult { 11 | // parameterize based on operation 12 | let (has_n, is_incr) = match otype { 13 | OpType::STR_DECR => (false, false), 14 | OpType::STR_DECRBY => (true, false), 15 | OpType::STR_INCR => (false, true), 16 | OpType::STR_INCRBY => (true, true), 17 | _ => unreachable!(), 18 | }; 19 | 20 | // get old value from db, or init 21 | let old_val: i64 = match db.get(req.get_key()) { 22 | None => 0, 23 | Some(atom) => { 24 | let s = { 25 | match atom { 26 | Atom::String(val) => { 27 | let s_res = str::from_utf8(val); 28 | if s_res.is_err() { 29 | return result_err(-400, "value not a string"); 30 | } 31 | s_res.unwrap() 32 | } 33 | _ => { 34 | return result_err(-400, "value not a string"); 35 | } 36 | } 37 | }; 38 | 39 | let sv_res = s.parse::(); 40 | if sv_res.is_err() { 41 | return result_err(-400, "value not i64"); 42 | } 43 | 44 | sv_res.unwrap() 45 | } 46 | }; 47 | 48 | // determine inc/dec operand 49 | let n = match has_n { 50 | true => req.n, 51 | false => 1, 52 | }; 53 | 54 | // perform inc/dec 55 | let new_val = match is_incr { 56 | true => old_val + n, 57 | false => old_val - n, 58 | }; 59 | 60 | // store value in database as string 61 | db.insert( 62 | req.get_key().to_vec(), 63 | Atom::String(new_val.to_string().as_bytes().to_vec()), 64 | ); 65 | 66 | // return success(old value) 67 | let mut num_res = NumRes::new(); 68 | num_res.old_value = old_val; 69 | 70 | // standard operation result assignment & final return 71 | let mut op_res = OpResult::new(); 72 | op_res.ok = true; 73 | op_res.otype = otype; 74 | op_res.set_num(num_res); 75 | 76 | op_res 77 | } 78 | 79 | fn str_index(len: i64, pos_requested: i64) -> usize { 80 | // non-negative positions are absolute. 81 | // negative positions relative to end of buffer 82 | let mut pos = { 83 | if pos_requested >= 0 { 84 | pos_requested 85 | } else { 86 | len + pos_requested + 1 87 | } 88 | }; 89 | 90 | // clamp values to unsigned buffer bounds 91 | if pos < 0 { 92 | pos = 0; 93 | } else if pos > len { 94 | pos = len; 95 | } 96 | 97 | pos as usize 98 | } 99 | 100 | fn sanitize_range(in_start: i32, in_end: i32, value_len: i32) -> (usize, usize) { 101 | let start = str_index(value_len as i64, in_start as i64); 102 | let mut end = str_index(value_len as i64, in_end as i64); 103 | if start > end { 104 | end = start; 105 | } 106 | 107 | (start, end) 108 | } 109 | 110 | pub fn get(db: &mut HashMap, Atom>, req: &StrGetOp, otype: OpType) -> OpResult { 111 | // get item by key 112 | match db.get(req.get_key()) { 113 | Some(atom) => match atom { 114 | Atom::String(value) => { 115 | let mut get_res = StrGetRes::new(); 116 | if req.want_length { 117 | get_res.set_value_length(value.len() as u64); 118 | } else if otype == OpType::STR_GETRANGE { 119 | let (start, end) = 120 | sanitize_range(req.range_start, req.range_end, value.len() as i32); 121 | let sub = &value[start..end]; 122 | get_res.set_value(sub.to_vec()); 123 | } else { 124 | get_res.set_value(value.to_vec()); 125 | } 126 | 127 | // standard operation result assignment & final return 128 | let mut op_res = OpResult::new(); 129 | op_res.ok = true; 130 | op_res.otype = otype; 131 | op_res.set_get(get_res); 132 | 133 | op_res 134 | } 135 | _ => result_err(-400, "not a string"), 136 | }, 137 | None => result_err(-404, "Not Found"), 138 | } 139 | } 140 | 141 | pub fn set(db: &mut HashMap, Atom>, req: &StrSetOp) -> OpResult { 142 | let key = req.get_key(); 143 | 144 | // option test: create iff key does not exist 145 | if req.create_excl && db.contains_key(key) { 146 | return result_err(-412, "Precondition failed: key exists"); 147 | } 148 | 149 | // insert, and return previous item stored at key (if any) 150 | let previous = db.insert(key.to_vec(), Atom::String(req.get_value().to_vec())); 151 | 152 | // if old-value requested, return it 153 | let mut set_res = StrSetRes::new(); 154 | if req.return_old && previous.is_some() { 155 | let prev_atom = previous.unwrap(); 156 | match prev_atom { 157 | Atom::String(s) => set_res.set_old_value(s), 158 | _ => {} 159 | } 160 | } 161 | 162 | // standard operation result assignment & final return 163 | let mut op_res = OpResult::new(); 164 | op_res.ok = true; 165 | op_res.otype = OpType::STR_SET; 166 | op_res.set_set(set_res); 167 | 168 | op_res 169 | } 170 | 171 | pub fn append(db: &mut HashMap, Atom>, req: &StrSetOp) -> OpResult { 172 | // get old value, or use "" if none 173 | let res = db.get(req.get_key()); 174 | let mut value: Vec = match res { 175 | Some(atom) => match atom { 176 | Atom::String(s) => s.to_vec(), 177 | _ => { 178 | return result_err(-400, "not a string"); 179 | } 180 | }, 181 | None => Vec::new(), 182 | }; 183 | 184 | // begin success result 185 | let mut set_res = StrSetRes::new(); 186 | if req.return_old { 187 | set_res.set_old_value(value.clone()); 188 | } 189 | 190 | // append to value 191 | value.extend_from_slice(req.get_value()); 192 | db.insert(req.get_key().to_vec(), Atom::String(value)); 193 | 194 | // standard operation result assignment & final return 195 | let mut op_res = OpResult::new(); 196 | op_res.ok = true; 197 | op_res.otype = OpType::STR_APPEND; 198 | op_res.set_set(set_res); 199 | 200 | op_res 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use crate::string; 206 | use memds_proto::memds_api::{NumOp, OpType, StrGetOp, StrSetOp}; 207 | use memds_proto::Atom; 208 | use std::collections::HashMap; 209 | 210 | fn get_test_db() -> HashMap, Atom> { 211 | let mut db: HashMap, Atom> = HashMap::new(); 212 | db.insert(b"foo".to_vec(), Atom::String(b"bar".to_vec())); 213 | db.insert(b"name".to_vec(), Atom::String(b"Jane Doe".to_vec())); 214 | db.insert(b"age".to_vec(), Atom::String(b"25".to_vec())); 215 | 216 | db 217 | } 218 | 219 | #[test] 220 | fn basic_get() { 221 | let mut db = get_test_db(); 222 | 223 | let mut req = StrGetOp::new(); 224 | req.set_key(b"foo".to_vec()); 225 | req.set_want_length(false); 226 | 227 | let res = string::get(&mut db, &req, OpType::STR_GET); 228 | 229 | assert_eq!(res.ok, true); 230 | assert_eq!(res.otype, OpType::STR_GET); 231 | assert!(res.has_get()); 232 | assert!(!res.has_set()); 233 | 234 | let get_res = res.get_get(); 235 | assert_eq!(get_res.value, b"bar".to_vec()); 236 | } 237 | 238 | #[test] 239 | fn get_length() { 240 | let mut db = get_test_db(); 241 | 242 | let mut req = StrGetOp::new(); 243 | req.set_key(b"foo".to_vec()); 244 | req.set_want_length(true); 245 | 246 | let res = string::get(&mut db, &req, OpType::STR_GET); 247 | 248 | assert_eq!(res.ok, true); 249 | assert_eq!(res.otype, OpType::STR_GET); 250 | assert!(res.has_get()); 251 | assert!(!res.has_set()); 252 | 253 | let get_res = res.get_get(); 254 | assert_eq!(get_res.value_length, 3); 255 | } 256 | 257 | #[test] 258 | fn get_range() { 259 | let mut db = get_test_db(); 260 | 261 | // testing: (0,4) substr of "Jane Doe" 262 | let mut req = StrGetOp::new(); 263 | req.set_key(b"name".to_vec()); 264 | req.substr = true; 265 | req.range_start = 0; 266 | req.range_end = -4; 267 | 268 | let res = string::get(&mut db, &req, OpType::STR_GETRANGE); 269 | 270 | assert_eq!(res.ok, true); 271 | assert_eq!(res.otype, OpType::STR_GETRANGE); 272 | assert!(res.has_get()); 273 | assert!(!res.has_set()); 274 | 275 | let get_res = res.get_get(); 276 | assert_eq!(get_res.value, b"Jane ".to_vec()); 277 | 278 | // testing: (0,-1) substr of "Jane Doe" 279 | let mut req = StrGetOp::new(); 280 | req.set_key(b"name".to_vec()); 281 | req.substr = true; 282 | req.range_start = 0; 283 | req.range_end = -1; 284 | 285 | let res = string::get(&mut db, &req, OpType::STR_GETRANGE); 286 | 287 | assert_eq!(res.ok, true); 288 | assert_eq!(res.otype, OpType::STR_GETRANGE); 289 | assert!(res.has_get()); 290 | assert!(!res.has_set()); 291 | 292 | let get_res = res.get_get(); 293 | assert_eq!(get_res.value, b"Jane Doe".to_vec()); 294 | } 295 | 296 | #[test] 297 | fn get_not_found() { 298 | let mut db = get_test_db(); 299 | 300 | let mut req = StrGetOp::new(); 301 | req.set_key(b"does not exist".to_vec()); 302 | req.set_want_length(false); 303 | 304 | let res = string::get(&mut db, &req, OpType::STR_GET); 305 | 306 | assert_eq!(res.ok, false); 307 | assert_eq!(res.otype, OpType::NOOP); 308 | assert_eq!(res.err_code, -404); 309 | assert!(!res.has_get()); 310 | assert!(!res.has_set()); 311 | } 312 | 313 | #[test] 314 | fn basic_set() { 315 | let mut db = get_test_db(); 316 | 317 | let mut req = StrSetOp::new(); 318 | req.set_key(b"barn".to_vec()); 319 | req.set_value(b"door".to_vec()); 320 | req.set_return_old(true); 321 | 322 | let res = string::set(&mut db, &req); 323 | 324 | assert_eq!(res.ok, true); 325 | assert_eq!(res.otype, OpType::STR_SET); 326 | assert!(!res.has_get()); 327 | assert!(res.has_set()); 328 | 329 | let set_res = res.get_set(); 330 | assert_eq!(set_res.old_value, b"".to_vec()); 331 | } 332 | 333 | #[test] 334 | fn set_with_old_value() { 335 | let mut db = get_test_db(); 336 | 337 | let mut req = StrSetOp::new(); 338 | req.set_key(b"foo".to_vec()); 339 | req.set_value(b"door".to_vec()); 340 | req.set_return_old(true); 341 | 342 | let res = string::set(&mut db, &req); 343 | 344 | assert_eq!(res.ok, true); 345 | assert_eq!(res.otype, OpType::STR_SET); 346 | assert!(!res.has_get()); 347 | assert!(res.has_set()); 348 | 349 | let set_res = res.get_set(); 350 | assert_eq!(set_res.old_value, b"bar".to_vec()); // expect: old value 351 | 352 | let mut req = StrSetOp::new(); 353 | req.set_key(b"foo".to_vec()); 354 | req.set_value(b"door".to_vec()); 355 | 356 | let res = string::set(&mut db, &req); 357 | 358 | assert_eq!(res.ok, true); 359 | assert_eq!(res.otype, OpType::STR_SET); 360 | assert!(!res.has_get()); 361 | assert!(res.has_set()); 362 | 363 | let set_res = res.get_set(); 364 | assert_eq!(set_res.old_value, b"".to_vec()); // expect: blank 365 | } 366 | 367 | #[test] 368 | fn basic_incr_decr() { 369 | let mut db = get_test_db(); 370 | 371 | let mut req = NumOp::new(); 372 | req.set_key(b"num".to_vec()); 373 | req.n = 0; 374 | 375 | // INCR(item not yet in db) => 1; old-value==0 376 | let res = string::incrdecr(&mut db, OpType::STR_INCR, &req); 377 | 378 | assert_eq!(res.ok, true); 379 | assert_eq!(res.otype, OpType::STR_INCR); 380 | assert!(res.has_num()); 381 | 382 | let num_res = res.get_num(); 383 | assert_eq!(num_res.old_value.to_string().as_bytes(), b"0"); 384 | 385 | // DECR(num) => 0; old-value==1 386 | let res = string::incrdecr(&mut db, OpType::STR_DECR, &req); 387 | 388 | assert_eq!(res.ok, true); 389 | assert_eq!(res.otype, OpType::STR_DECR); 390 | assert!(res.has_num()); 391 | 392 | let num_res = res.get_num(); 393 | assert_eq!(num_res.old_value.to_string().as_bytes(), b"1"); 394 | 395 | // DECRBY(num,2) => -2; old-value==0 396 | req.n = 2; 397 | let res = string::incrdecr(&mut db, OpType::STR_DECRBY, &req); 398 | 399 | assert_eq!(res.ok, true); 400 | assert_eq!(res.otype, OpType::STR_DECRBY); 401 | assert!(res.has_num()); 402 | 403 | let num_res = res.get_num(); 404 | assert_eq!(num_res.old_value.to_string().as_bytes(), b"0"); 405 | 406 | // INCRBY(num,2) => 0; old-value==-2 407 | let res = string::incrdecr(&mut db, OpType::STR_INCRBY, &req); 408 | 409 | assert_eq!(res.ok, true); 410 | assert_eq!(res.otype, OpType::STR_INCRBY); 411 | assert!(res.has_num()); 412 | 413 | let num_res = res.get_num(); 414 | assert_eq!(num_res.old_value.to_string().as_bytes(), b"-2"); 415 | 416 | // verify final value is indeed 0, from previous operation 417 | let mut req = StrGetOp::new(); 418 | req.set_key(b"num".to_vec()); 419 | 420 | let res = string::get(&mut db, &req, OpType::STR_GET); 421 | assert_eq!(res.ok, true); 422 | let get_res = res.get_get(); 423 | assert_eq!(get_res.value, b"0".to_vec()); 424 | } 425 | 426 | #[test] 427 | fn basic_append() { 428 | let mut db = get_test_db(); 429 | 430 | let mut req = StrSetOp::new(); 431 | req.set_key(b"app".to_vec()); 432 | req.set_value(b"door".to_vec()); 433 | req.set_return_old(true); 434 | 435 | let res = string::append(&mut db, &req); 436 | 437 | assert_eq!(res.ok, true); 438 | assert_eq!(res.otype, OpType::STR_APPEND); 439 | assert!(!res.has_get()); 440 | assert!(res.has_set()); 441 | 442 | let set_res = res.get_set(); 443 | assert_eq!(set_res.old_value, b"".to_vec()); // expect: old value 444 | 445 | let mut req = StrSetOp::new(); 446 | req.set_key(b"app".to_vec()); 447 | req.set_value(b"door".to_vec()); 448 | req.set_return_old(true); 449 | 450 | let res = string::append(&mut db, &req); 451 | 452 | assert_eq!(res.ok, true); 453 | assert_eq!(res.otype, OpType::STR_APPEND); 454 | assert!(!res.has_get()); 455 | assert!(res.has_set()); 456 | 457 | let set_res = res.get_set(); 458 | assert_eq!(set_res.old_value, b"door".to_vec()); // expect: blank 459 | 460 | // verify final value is indeed doordoor, from previous operation 461 | let mut req = StrGetOp::new(); 462 | req.set_key(b"app".to_vec()); 463 | 464 | let res = string::get(&mut db, &req, OpType::STR_GET); 465 | assert_eq!(res.ok, true); 466 | let get_res = res.get_get(); 467 | assert_eq!(get_res.value, b"doordoor".to_vec()); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | 2 | # Implementation thoughts and notes 3 | 4 | ## List ADT 5 | 6 | * Consider using `VecDeque` for lists, which probably want efficient 7 | insertion at both ends. 8 | 9 | --------------------------------------------------------------------------------