├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── architecture.md ├── command-args-derive ├── Cargo.toml └── src │ └── lib.rs ├── command-args ├── Cargo.toml └── src │ └── lib.rs └── memds ├── Cargo.toml ├── src ├── client │ └── mod.rs ├── command │ ├── admin │ │ └── mod.rs │ ├── connection │ │ └── mod.rs │ ├── mod.rs │ ├── set │ │ └── mod.rs │ └── string │ │ └── mod.rs ├── connection │ └── mod.rs ├── database │ └── mod.rs ├── lib.rs ├── main.rs ├── memds │ └── mod.rs ├── server │ └── mod.rs ├── storage │ └── mod.rs └── wal │ └── mod.rs └── tests └── command_tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "ansi_term" 16 | version = "0.12.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" 19 | dependencies = [ 20 | "winapi", 21 | ] 22 | 23 | [[package]] 24 | name = "anyhow" 25 | version = "1.0.57" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" 28 | 29 | [[package]] 30 | name = "assert_matches" 31 | version = "1.5.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" 34 | 35 | [[package]] 36 | name = "atomic-polyfill" 37 | version = "0.1.8" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "e14bf7b4f565e5e717d7a7a65b2a05c0b8c96e4db636d6f780f03b15108cdd1b" 40 | dependencies = [ 41 | "critical-section", 42 | ] 43 | 44 | [[package]] 45 | name = "autocfg" 46 | version = "1.1.0" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 49 | 50 | [[package]] 51 | name = "bare-metal" 52 | version = "0.2.5" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" 55 | dependencies = [ 56 | "rustc_version 0.2.3", 57 | ] 58 | 59 | [[package]] 60 | name = "bare-metal" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" 64 | 65 | [[package]] 66 | name = "bincode" 67 | version = "1.3.3" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 70 | dependencies = [ 71 | "serde", 72 | ] 73 | 74 | [[package]] 75 | name = "bit_field" 76 | version = "0.10.1" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 79 | 80 | [[package]] 81 | name = "bitfield" 82 | version = "0.13.2" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" 85 | 86 | [[package]] 87 | name = "bitflags" 88 | version = "1.3.2" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 91 | 92 | [[package]] 93 | name = "bytes" 94 | version = "1.1.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 97 | 98 | [[package]] 99 | name = "cfg-if" 100 | version = "1.0.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 103 | 104 | [[package]] 105 | name = "command-args" 106 | version = "0.1.0" 107 | 108 | [[package]] 109 | name = "command-args-derive" 110 | version = "0.1.0" 111 | dependencies = [ 112 | "command-args", 113 | "proc-macro2", 114 | "quote", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "const-field-offset" 120 | version = "0.1.2" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "4d6d40fc2c8e0109d6e423fb4c8b6a373c56b4576ca2e0d3e216a7796861fbd0" 123 | dependencies = [ 124 | "const-field-offset-macro", 125 | "field-offset", 126 | ] 127 | 128 | [[package]] 129 | name = "const-field-offset-macro" 130 | version = "0.1.2" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "a7f7f71b9a791f0a0fcadab1293bbac19b6872756ca78b630c3d89f6aa93daff" 133 | dependencies = [ 134 | "proc-macro2", 135 | "quote", 136 | "syn", 137 | ] 138 | 139 | [[package]] 140 | name = "cortex-m" 141 | version = "0.7.4" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "37ff967e867ca14eba0c34ac25cd71ea98c678e741e3915d923999bb2fe7c826" 144 | dependencies = [ 145 | "bare-metal 0.2.5", 146 | "bitfield", 147 | "embedded-hal", 148 | "volatile-register", 149 | ] 150 | 151 | [[package]] 152 | name = "critical-section" 153 | version = "0.2.7" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "95da181745b56d4bd339530ec393508910c909c784e8962d15d722bacf0bcbcd" 156 | dependencies = [ 157 | "bare-metal 1.0.0", 158 | "cfg-if", 159 | "cortex-m", 160 | "riscv", 161 | ] 162 | 163 | [[package]] 164 | name = "deseresp" 165 | version = "0.1.5" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "6411d76e73371144ea1ed79e14aaddb610b9327fdaa1962d0577177ade1d653f" 168 | dependencies = [ 169 | "num", 170 | "serde", 171 | ] 172 | 173 | [[package]] 174 | name = "embedded-hal" 175 | version = "0.2.7" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 178 | dependencies = [ 179 | "nb 0.1.3", 180 | "void", 181 | ] 182 | 183 | [[package]] 184 | name = "field-offset" 185 | version = "0.3.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" 188 | dependencies = [ 189 | "memoffset", 190 | "rustc_version 0.3.3", 191 | ] 192 | 193 | [[package]] 194 | name = "futures" 195 | version = "0.3.21" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 198 | dependencies = [ 199 | "futures-channel", 200 | "futures-core", 201 | "futures-executor", 202 | "futures-io", 203 | "futures-sink", 204 | "futures-task", 205 | "futures-util", 206 | ] 207 | 208 | [[package]] 209 | name = "futures-channel" 210 | version = "0.3.21" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 213 | dependencies = [ 214 | "futures-core", 215 | "futures-sink", 216 | ] 217 | 218 | [[package]] 219 | name = "futures-core" 220 | version = "0.3.21" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 223 | 224 | [[package]] 225 | name = "futures-executor" 226 | version = "0.3.21" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 229 | dependencies = [ 230 | "futures-core", 231 | "futures-task", 232 | "futures-util", 233 | ] 234 | 235 | [[package]] 236 | name = "futures-io" 237 | version = "0.3.21" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 240 | 241 | [[package]] 242 | name = "futures-macro" 243 | version = "0.3.21" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 246 | dependencies = [ 247 | "proc-macro2", 248 | "quote", 249 | "syn", 250 | ] 251 | 252 | [[package]] 253 | name = "futures-sink" 254 | version = "0.3.21" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 257 | 258 | [[package]] 259 | name = "futures-task" 260 | version = "0.3.21" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 263 | 264 | [[package]] 265 | name = "futures-util" 266 | version = "0.3.21" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 269 | dependencies = [ 270 | "futures-channel", 271 | "futures-core", 272 | "futures-io", 273 | "futures-macro", 274 | "futures-sink", 275 | "futures-task", 276 | "memchr", 277 | "pin-project-lite", 278 | "pin-utils", 279 | "slab", 280 | ] 281 | 282 | [[package]] 283 | name = "hermit-abi" 284 | version = "0.1.19" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 287 | dependencies = [ 288 | "libc", 289 | ] 290 | 291 | [[package]] 292 | name = "lazy_static" 293 | version = "1.4.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 296 | 297 | [[package]] 298 | name = "libc" 299 | version = "0.2.126" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 302 | 303 | [[package]] 304 | name = "lock_api" 305 | version = "0.4.7" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 308 | dependencies = [ 309 | "autocfg", 310 | "scopeguard", 311 | ] 312 | 313 | [[package]] 314 | name = "log" 315 | version = "0.4.17" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 318 | dependencies = [ 319 | "cfg-if", 320 | ] 321 | 322 | [[package]] 323 | name = "memchr" 324 | version = "2.5.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 327 | 328 | [[package]] 329 | name = "memds" 330 | version = "0.1.0" 331 | dependencies = [ 332 | "anyhow", 333 | "assert_matches", 334 | "bincode", 335 | "bytes", 336 | "command-args", 337 | "command-args-derive", 338 | "deseresp", 339 | "futures", 340 | "serde", 341 | "tokio", 342 | "tracing", 343 | "tracing-subscriber", 344 | "vtable", 345 | ] 346 | 347 | [[package]] 348 | name = "memoffset" 349 | version = "0.6.5" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 352 | dependencies = [ 353 | "autocfg", 354 | ] 355 | 356 | [[package]] 357 | name = "mio" 358 | version = "0.8.3" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" 361 | dependencies = [ 362 | "libc", 363 | "log", 364 | "wasi", 365 | "windows-sys", 366 | ] 367 | 368 | [[package]] 369 | name = "nb" 370 | version = "0.1.3" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 373 | dependencies = [ 374 | "nb 1.0.0", 375 | ] 376 | 377 | [[package]] 378 | name = "nb" 379 | version = "1.0.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" 382 | 383 | [[package]] 384 | name = "num" 385 | version = "0.4.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" 388 | dependencies = [ 389 | "num-bigint", 390 | "num-complex", 391 | "num-integer", 392 | "num-iter", 393 | "num-rational", 394 | "num-traits", 395 | ] 396 | 397 | [[package]] 398 | name = "num-bigint" 399 | version = "0.4.3" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 402 | dependencies = [ 403 | "autocfg", 404 | "num-integer", 405 | "num-traits", 406 | ] 407 | 408 | [[package]] 409 | name = "num-complex" 410 | version = "0.4.1" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" 413 | dependencies = [ 414 | "num-traits", 415 | ] 416 | 417 | [[package]] 418 | name = "num-integer" 419 | version = "0.1.45" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 422 | dependencies = [ 423 | "autocfg", 424 | "num-traits", 425 | ] 426 | 427 | [[package]] 428 | name = "num-iter" 429 | version = "0.1.43" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" 432 | dependencies = [ 433 | "autocfg", 434 | "num-integer", 435 | "num-traits", 436 | ] 437 | 438 | [[package]] 439 | name = "num-rational" 440 | version = "0.4.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" 443 | dependencies = [ 444 | "autocfg", 445 | "num-bigint", 446 | "num-integer", 447 | "num-traits", 448 | ] 449 | 450 | [[package]] 451 | name = "num-traits" 452 | version = "0.2.15" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 455 | dependencies = [ 456 | "autocfg", 457 | ] 458 | 459 | [[package]] 460 | name = "num_cpus" 461 | version = "1.13.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 464 | dependencies = [ 465 | "hermit-abi", 466 | "libc", 467 | ] 468 | 469 | [[package]] 470 | name = "once_cell" 471 | version = "1.12.0" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 474 | 475 | [[package]] 476 | name = "parking_lot" 477 | version = "0.12.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 480 | dependencies = [ 481 | "lock_api", 482 | "parking_lot_core", 483 | ] 484 | 485 | [[package]] 486 | name = "parking_lot_core" 487 | version = "0.9.3" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 490 | dependencies = [ 491 | "cfg-if", 492 | "libc", 493 | "redox_syscall", 494 | "smallvec", 495 | "windows-sys", 496 | ] 497 | 498 | [[package]] 499 | name = "pest" 500 | version = "2.1.3" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 503 | dependencies = [ 504 | "ucd-trie", 505 | ] 506 | 507 | [[package]] 508 | name = "pin-project-lite" 509 | version = "0.2.9" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 512 | 513 | [[package]] 514 | name = "pin-utils" 515 | version = "0.1.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 518 | 519 | [[package]] 520 | name = "proc-macro2" 521 | version = "1.0.39" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" 524 | dependencies = [ 525 | "unicode-ident", 526 | ] 527 | 528 | [[package]] 529 | name = "quote" 530 | version = "1.0.18" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 533 | dependencies = [ 534 | "proc-macro2", 535 | ] 536 | 537 | [[package]] 538 | name = "redox_syscall" 539 | version = "0.2.13" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 542 | dependencies = [ 543 | "bitflags", 544 | ] 545 | 546 | [[package]] 547 | name = "regex" 548 | version = "1.5.6" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 551 | dependencies = [ 552 | "aho-corasick", 553 | "memchr", 554 | "regex-syntax", 555 | ] 556 | 557 | [[package]] 558 | name = "regex-syntax" 559 | version = "0.6.26" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 562 | 563 | [[package]] 564 | name = "riscv" 565 | version = "0.7.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "6907ccdd7a31012b70faf2af85cd9e5ba97657cc3987c4f13f8e4d2c2a088aba" 568 | dependencies = [ 569 | "bare-metal 1.0.0", 570 | "bit_field", 571 | "riscv-target", 572 | ] 573 | 574 | [[package]] 575 | name = "riscv-target" 576 | version = "0.1.2" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222" 579 | dependencies = [ 580 | "lazy_static", 581 | "regex", 582 | ] 583 | 584 | [[package]] 585 | name = "rustc_version" 586 | version = "0.2.3" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 589 | dependencies = [ 590 | "semver 0.9.0", 591 | ] 592 | 593 | [[package]] 594 | name = "rustc_version" 595 | version = "0.3.3" 596 | source = "registry+https://github.com/rust-lang/crates.io-index" 597 | checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" 598 | dependencies = [ 599 | "semver 0.11.0", 600 | ] 601 | 602 | [[package]] 603 | name = "scopeguard" 604 | version = "1.1.0" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 607 | 608 | [[package]] 609 | name = "semver" 610 | version = "0.9.0" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 613 | dependencies = [ 614 | "semver-parser 0.7.0", 615 | ] 616 | 617 | [[package]] 618 | name = "semver" 619 | version = "0.11.0" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 622 | dependencies = [ 623 | "semver-parser 0.10.2", 624 | ] 625 | 626 | [[package]] 627 | name = "semver-parser" 628 | version = "0.7.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 631 | 632 | [[package]] 633 | name = "semver-parser" 634 | version = "0.10.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 637 | dependencies = [ 638 | "pest", 639 | ] 640 | 641 | [[package]] 642 | name = "serde" 643 | version = "1.0.137" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 646 | dependencies = [ 647 | "serde_derive", 648 | ] 649 | 650 | [[package]] 651 | name = "serde_derive" 652 | version = "1.0.137" 653 | source = "registry+https://github.com/rust-lang/crates.io-index" 654 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 655 | dependencies = [ 656 | "proc-macro2", 657 | "quote", 658 | "syn", 659 | ] 660 | 661 | [[package]] 662 | name = "sharded-slab" 663 | version = "0.1.4" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 666 | dependencies = [ 667 | "lazy_static", 668 | ] 669 | 670 | [[package]] 671 | name = "signal-hook-registry" 672 | version = "1.4.0" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 675 | dependencies = [ 676 | "libc", 677 | ] 678 | 679 | [[package]] 680 | name = "slab" 681 | version = "0.4.6" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" 684 | 685 | [[package]] 686 | name = "smallvec" 687 | version = "1.8.0" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 690 | 691 | [[package]] 692 | name = "socket2" 693 | version = "0.4.4" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 696 | dependencies = [ 697 | "libc", 698 | "winapi", 699 | ] 700 | 701 | [[package]] 702 | name = "stable_deref_trait" 703 | version = "1.2.0" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 706 | 707 | [[package]] 708 | name = "syn" 709 | version = "1.0.95" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" 712 | dependencies = [ 713 | "proc-macro2", 714 | "quote", 715 | "unicode-ident", 716 | ] 717 | 718 | [[package]] 719 | name = "thread_local" 720 | version = "1.1.4" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 723 | dependencies = [ 724 | "once_cell", 725 | ] 726 | 727 | [[package]] 728 | name = "tokio" 729 | version = "1.18.2" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" 732 | dependencies = [ 733 | "bytes", 734 | "libc", 735 | "memchr", 736 | "mio", 737 | "num_cpus", 738 | "once_cell", 739 | "parking_lot", 740 | "pin-project-lite", 741 | "signal-hook-registry", 742 | "socket2", 743 | "tokio-macros", 744 | "winapi", 745 | ] 746 | 747 | [[package]] 748 | name = "tokio-macros" 749 | version = "1.7.0" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 752 | dependencies = [ 753 | "proc-macro2", 754 | "quote", 755 | "syn", 756 | ] 757 | 758 | [[package]] 759 | name = "tracing" 760 | version = "0.1.34" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" 763 | dependencies = [ 764 | "cfg-if", 765 | "pin-project-lite", 766 | "tracing-attributes", 767 | "tracing-core", 768 | ] 769 | 770 | [[package]] 771 | name = "tracing-attributes" 772 | version = "0.1.21" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" 775 | dependencies = [ 776 | "proc-macro2", 777 | "quote", 778 | "syn", 779 | ] 780 | 781 | [[package]] 782 | name = "tracing-core" 783 | version = "0.1.26" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" 786 | dependencies = [ 787 | "lazy_static", 788 | "valuable", 789 | ] 790 | 791 | [[package]] 792 | name = "tracing-log" 793 | version = "0.1.3" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 796 | dependencies = [ 797 | "lazy_static", 798 | "log", 799 | "tracing-core", 800 | ] 801 | 802 | [[package]] 803 | name = "tracing-subscriber" 804 | version = "0.3.11" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" 807 | dependencies = [ 808 | "ansi_term", 809 | "sharded-slab", 810 | "smallvec", 811 | "thread_local", 812 | "tracing-core", 813 | "tracing-log", 814 | ] 815 | 816 | [[package]] 817 | name = "ucd-trie" 818 | version = "0.1.3" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 821 | 822 | [[package]] 823 | name = "unicode-ident" 824 | version = "1.0.0" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" 827 | 828 | [[package]] 829 | name = "valuable" 830 | version = "0.1.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 833 | 834 | [[package]] 835 | name = "vcell" 836 | version = "0.1.3" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" 839 | 840 | [[package]] 841 | name = "void" 842 | version = "1.0.2" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 845 | 846 | [[package]] 847 | name = "volatile-register" 848 | version = "0.2.1" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "9ee8f19f9d74293faf70901bc20ad067dc1ad390d2cbf1e3f75f721ffee908b6" 851 | dependencies = [ 852 | "vcell", 853 | ] 854 | 855 | [[package]] 856 | name = "vtable" 857 | version = "0.1.7" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "cc1c10f91b4297c7bb178f661b33b642d2bdbffd436143899eca9c08ed6abe1b" 860 | dependencies = [ 861 | "atomic-polyfill", 862 | "const-field-offset", 863 | "stable_deref_trait", 864 | "vtable-macro", 865 | ] 866 | 867 | [[package]] 868 | name = "vtable-macro" 869 | version = "0.1.7" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "2c92ea1b9429eeaebf700ff3e7977094ac53c0ee84ab965e910db64127389905" 872 | dependencies = [ 873 | "proc-macro2", 874 | "quote", 875 | "syn", 876 | ] 877 | 878 | [[package]] 879 | name = "wasi" 880 | version = "0.11.0+wasi-snapshot-preview1" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 883 | 884 | [[package]] 885 | name = "winapi" 886 | version = "0.3.9" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 889 | dependencies = [ 890 | "winapi-i686-pc-windows-gnu", 891 | "winapi-x86_64-pc-windows-gnu", 892 | ] 893 | 894 | [[package]] 895 | name = "winapi-i686-pc-windows-gnu" 896 | version = "0.4.0" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 899 | 900 | [[package]] 901 | name = "winapi-x86_64-pc-windows-gnu" 902 | version = "0.4.0" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 905 | 906 | [[package]] 907 | name = "windows-sys" 908 | version = "0.36.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 911 | dependencies = [ 912 | "windows_aarch64_msvc", 913 | "windows_i686_gnu", 914 | "windows_i686_msvc", 915 | "windows_x86_64_gnu", 916 | "windows_x86_64_msvc", 917 | ] 918 | 919 | [[package]] 920 | name = "windows_aarch64_msvc" 921 | version = "0.36.1" 922 | source = "registry+https://github.com/rust-lang/crates.io-index" 923 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 924 | 925 | [[package]] 926 | name = "windows_i686_gnu" 927 | version = "0.36.1" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 930 | 931 | [[package]] 932 | name = "windows_i686_msvc" 933 | version = "0.36.1" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 936 | 937 | [[package]] 938 | name = "windows_x86_64_gnu" 939 | version = "0.36.1" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 942 | 943 | [[package]] 944 | name = "windows_x86_64_msvc" 945 | version = "0.36.1" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 948 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "memds", 4 | "command-args", 5 | "command-args-derive", 6 | ] 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memds 2 | 3 | A reimplementation of Redis for fun. 4 | 5 | ## Contributing Guide 6 | 7 | ### Test 8 | Run test with: 9 | ```bash 10 | cargo test 11 | ``` 12 | 13 | ### Benchmark 14 | 15 | 1. run memds: 16 | ```bash 17 | RUST_LOG=error cargo run --release 18 | ``` 19 | memds is currently hardcoded to run on port 6901 20 | 21 | 1. run redis-benchmark: 22 | with pipelining: 23 | ```bash 24 | redis-benchmark -t set,get -n 1000000 -r 1000000 -p 6901 -P 30 25 | ``` 26 | without pipelining: 27 | ```bash 28 | redis-benchmark -t set,get -n 1000000 -r 1000000 -p 6901 29 | ``` 30 | 31 | -------------------------------------------------------------------------------- /architecture.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | ## Crates list 4 | 5 | 1. `command-args`: Trait for parsing `&[<&str>]` to proper Rust struct that represent a Redis command. 6 | 1. `command-args-derive`: Proc-macro crate to auto generate impl `CommandArgs` trait via `#[derive(CommandArgsBlock)]` attribute, 7 | supports `#[argtoken("TOKEN")]` helper attribute to let user specify command/block token. 8 | 1. `memds`: Binary of this project, a Tokio-based async server. 9 | 10 | ## Command handler 11 | 12 | Trait `CommandHandler` specify how a command being handled and what's its output type is. 13 | New Command added need to be listed in [memds/src/command/mod.rs](/memds/src/command/mod.rs#L74) 14 | 15 | ## Data structure 16 | 17 | Datastructure handling source need to be put in [memds](/memds/src/memds/mod.rs) module. Keeping command handling light. 18 | -------------------------------------------------------------------------------- /command-args-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "command-args-derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = "1.0" 13 | quote = "1.0" 14 | proc-macro2 = "1.0" 15 | command-args = { path = "../command-args" } 16 | -------------------------------------------------------------------------------- /command-args-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | use syn::{parse_macro_input, DeriveInput, Error}; 4 | 5 | #[proc_macro_derive(CommandArgsBlock, attributes(argtoken, argnotoken))] 6 | pub fn derive_command_args_block(input: TokenStream) -> TokenStream { 7 | let input = parse_macro_input!(input as DeriveInput); 8 | 9 | expand::expand(input) 10 | .unwrap_or_else(Error::into_compile_error) 11 | .into() 12 | } 13 | 14 | mod expand { 15 | use proc_macro2::{Span, TokenStream}; 16 | use quote::{quote, quote_spanned}; 17 | use syn::{ 18 | parse_quote, spanned::Spanned, DeriveInput, Error, Expr, ExprField, ExprPath, 19 | GenericArgument, GenericParam, Ident, Lifetime, LifetimeDef, LitStr, Path, PathArguments, 20 | PathSegment, Result, Type, TypePath, 21 | }; 22 | 23 | pub(crate) fn expand(input: DeriveInput) -> Result { 24 | let data_model = parse_data_model(input)?; 25 | 26 | let default_lifetime = LifetimeDef::new(Lifetime::new("'a", Span::call_site())); 27 | let mut impl_generics = data_model.generics.clone(); 28 | if impl_generics.lifetimes().next().is_none() { 29 | impl_generics 30 | .params 31 | .push(GenericParam::Lifetime(default_lifetime)); 32 | } 33 | let lifetime = &impl_generics.lifetimes().next().unwrap().lifetime; 34 | let (_, ty_generics, where_clause) = data_model.generics.split_for_impl(); 35 | let (impl_generics_tok, _, _) = impl_generics.split_for_impl(); 36 | 37 | let name = &data_model.name; 38 | let parse_maybe_fn_content = parse_maybe_fn_content(&data_model)?; 39 | 40 | let parse_fn = quote! { 41 | fn parse_maybe(args: &mut &[&#lifetime str]) -> Result, ::command_args::Error> { 42 | #parse_maybe_fn_content 43 | } 44 | }; 45 | 46 | let encode_fn_content = encode_fn_content(&data_model)?; 47 | let encode_fn = quote! { 48 | fn encode(&self, target: &mut Vec) -> Result<(), ::command_args::Error> { 49 | #encode_fn_content 50 | } 51 | }; 52 | 53 | Ok(quote! { 54 | impl #impl_generics_tok ::command_args::CommandArgs<#lifetime> for #name #ty_generics #where_clause { 55 | #encode_fn 56 | 57 | #parse_fn 58 | } 59 | }) 60 | } 61 | 62 | fn encode_fn_content(data_model: &DataModel) -> Result { 63 | match &data_model.data_type { 64 | DataType::Enum(e) => { 65 | let span = data_model.span; 66 | let encode_variants_match_arms = e 67 | .variants 68 | .iter() 69 | .map(encode_variant_match_arm) 70 | .collect::>>()?; 71 | let notoken_catch_arm = e.notoken_variant.as_ref().map(|v| { 72 | let span = v.span; 73 | let variant_ident = &v.name; 74 | quote_spanned! {span=> 75 | Self::#variant_ident => {} 76 | } 77 | }); 78 | 79 | Ok(quote_spanned! {span=> 80 | match self { 81 | #(#encode_variants_match_arms)* 82 | #notoken_catch_arm 83 | } 84 | 85 | Ok(()) 86 | }) 87 | } 88 | DataType::Struct(s) => encode_struct(s, data_model.span), 89 | } 90 | } 91 | 92 | fn encode_variant_match_arm(v: &Variant) -> Result { 93 | let span = v.span; 94 | let variant_ident = &v.name; 95 | let token = &v.token; 96 | 97 | match &v.fields { 98 | Fields::Unit => Ok(quote_spanned! {span=> 99 | Self::#variant_ident => { 100 | #token.encode(target)?; 101 | } 102 | }), 103 | Fields::Unnamed(t) => { 104 | let mut field_paths = Vec::new(); 105 | let encode_tuple_fields = t 106 | .unnamed 107 | .iter() 108 | .enumerate() 109 | .map(|(i, f)| { 110 | let var_name = Ident::new(&format!("t{}", i), f.span()); 111 | let field: ExprPath = parse_quote! { #var_name }; 112 | field_paths.push(field.clone()); 113 | 114 | encode_field(&field.into(), &f.ty) 115 | }) 116 | .collect::>>()?; 117 | 118 | Ok(quote_spanned! {span=> 119 | Self::#variant_ident( #(#field_paths)* ) => { 120 | #token.encode(target)?; 121 | #(#encode_tuple_fields)* 122 | } 123 | }) 124 | } 125 | Fields::Named(s) => { 126 | let mut field_paths = Vec::new(); 127 | let encode_tuple_fields = s 128 | .named 129 | .iter() 130 | .map(|f| { 131 | let var_name = &f.ident; 132 | let field: ExprPath = parse_quote! { #var_name }; 133 | field_paths.push(field.clone()); 134 | 135 | encode_field(&field.into(), &f.ty) 136 | }) 137 | .collect::>>()?; 138 | 139 | Ok(quote_spanned! {span=> 140 | Self::#variant_ident{ #(#field_paths),* } => { 141 | #token.encode(target)?; 142 | #(#encode_tuple_fields)* 143 | } 144 | }) 145 | } 146 | } 147 | } 148 | 149 | fn encode_struct(s: &Struct, span: Span) -> Result { 150 | let encode_token = s.token.as_ref().map(|tok| { 151 | quote_spanned! {span=> 152 | #tok.encode(target)?; 153 | } 154 | }); 155 | let encode_fields = match &s.fields { 156 | Fields::Unit => TokenStream::new(), 157 | Fields::Unnamed(u) => { 158 | // if has token then prepend whitespace 159 | let encode_tuple_fields = u 160 | .unnamed 161 | .iter() 162 | .enumerate() 163 | .map(|(i, f)| { 164 | let field: ExprField = parse_quote! { self.#i }; 165 | encode_field(&field.into(), &f.ty) 166 | }) 167 | .collect::>>()?; 168 | 169 | quote_spanned! {span=> 170 | #(#encode_tuple_fields)* 171 | } 172 | } 173 | Fields::Named(s) => { 174 | // if has token then prepend whitespace 175 | let encode_struct_fields = s 176 | .named 177 | .iter() 178 | .map(|f| { 179 | let ident = &f.ident; 180 | let field: ExprField = parse_quote! { self.#ident }; 181 | encode_field(&field.into(), &f.ty) 182 | }) 183 | .collect::>>()?; 184 | 185 | quote_spanned! {span=> 186 | #(#encode_struct_fields)* 187 | } 188 | } 189 | }; 190 | 191 | Ok(quote_spanned! {span=> 192 | #encode_token 193 | #encode_fields 194 | 195 | Ok(()) 196 | }) 197 | } 198 | 199 | fn encode_field(field_expr: &Expr, ty: &Type) -> Result { 200 | let ty_span = ty.span(); 201 | Ok(match option_inner_type(ty) { 202 | Some(inner_ty) => quote_spanned! {ty_span=> 203 | if let Some(s) = &#field_expr { 204 | <#inner_ty as ::command_args::CommandArgs>::encode(s, target)?; 205 | } 206 | }, 207 | None => quote_spanned! {ty_span=> 208 | #[allow(clippy::needless_borrow)] 209 | <#ty as ::command_args::CommandArgs>::encode(&#field_expr, target)?; 210 | }, 211 | }) 212 | } 213 | 214 | enum Fields { 215 | Unit, 216 | Unnamed(syn::FieldsUnnamed), 217 | Named(syn::FieldsNamed), 218 | } 219 | 220 | struct Variant { 221 | span: proc_macro2::Span, 222 | name: syn::Ident, 223 | token: LitStr, 224 | fields: Fields, 225 | } 226 | 227 | struct NotokenVariant { 228 | span: proc_macro2::Span, 229 | name: syn::Ident, 230 | } 231 | 232 | struct Enum { 233 | variants: Vec, 234 | notoken_variant: Option, 235 | } 236 | 237 | struct Struct { 238 | token: Option, 239 | fields: Fields, 240 | } 241 | 242 | enum DataType { 243 | Enum(Enum), 244 | Struct(Struct), 245 | } 246 | 247 | struct DataModel { 248 | span: proc_macro2::Span, 249 | name: syn::Ident, 250 | generics: syn::Generics, 251 | data_type: DataType, 252 | } 253 | 254 | /// Turns input token stream into our DataModel 255 | fn parse_data_model(input: DeriveInput) -> Result { 256 | let span = input.span(); 257 | Ok(DataModel { 258 | span: input.span(), 259 | data_type: match input.data { 260 | syn::Data::Struct(s) => { 261 | let s = parse_data_model_struct(s, input.attrs)?; 262 | DataType::Struct(s) 263 | } 264 | syn::Data::Enum(e) => { 265 | let e = parse_data_model_enum(e)?; 266 | DataType::Enum(e) 267 | } 268 | syn::Data::Union(_) => return Err(Error::new(span, "does not support union")), 269 | }, 270 | name: input.ident, 271 | generics: input.generics, 272 | }) 273 | } 274 | 275 | fn parse_data_model_enum(e: syn::DataEnum) -> Result { 276 | let mut variants = Vec::new(); 277 | let mut notoken_variant = None; 278 | 279 | for variant in e.variants { 280 | let notoken_path: Path = parse_quote!(argnotoken); 281 | let is_notoken_variant = variant.attrs.iter().any(|a| a.path == notoken_path); 282 | let variant_span = variant.span(); 283 | 284 | if notoken_variant.is_some() && is_notoken_variant { 285 | return Err(Error::new( 286 | variant.span(), 287 | "only one notoken variant allowed", 288 | )); 289 | } 290 | 291 | let token_path: Path = parse_quote!(argtoken); 292 | let token = variant 293 | .attrs 294 | .iter() 295 | .find(|a| a.path == token_path) 296 | .map(|a| a.parse_args::()) 297 | .transpose()?; 298 | 299 | let name = LitStr::new(&variant.ident.to_string(), variant.span()); 300 | let variant_token = token.unwrap_or(name); 301 | 302 | if is_notoken_variant { 303 | let span = variant.span(); 304 | match variant.fields { 305 | syn::Fields::Unit => {} 306 | _ => return Err(Error::new(span, "notoken variant must be Unit")), 307 | } 308 | notoken_variant = Some(NotokenVariant { 309 | span: variant_span, 310 | name: variant.ident, 311 | }); 312 | } else { 313 | let fields = match variant.fields { 314 | syn::Fields::Named(s) => Fields::Named(s), 315 | syn::Fields::Unnamed(s) => Fields::Unnamed(s), 316 | syn::Fields::Unit => Fields::Unit, 317 | }; 318 | let v = Variant { 319 | span: variant_span, 320 | name: variant.ident, 321 | token: variant_token, 322 | fields, 323 | }; 324 | variants.push(v); 325 | } 326 | } 327 | 328 | Ok(Enum { 329 | variants, 330 | notoken_variant, 331 | }) 332 | } 333 | 334 | fn parse_data_model_struct(s: syn::DataStruct, attrs: Vec) -> Result { 335 | let token_path: Path = parse_quote!(argtoken); 336 | let token = attrs 337 | .iter() 338 | .find(|a| a.path == token_path) 339 | .map(|a| a.parse_args::()) 340 | .transpose()?; 341 | let fields = match s.fields { 342 | syn::Fields::Named(s) => Fields::Named(s), 343 | syn::Fields::Unnamed(tuple) => Fields::Unnamed(tuple), 344 | syn::Fields::Unit => Fields::Unit, 345 | }; 346 | Ok(Struct { token, fields }) 347 | } 348 | 349 | fn parse_maybe_fn_content(input: &DataModel) -> Result { 350 | match &input.data_type { 351 | DataType::Struct(s) => struct_parse_content(s, input.span), 352 | DataType::Enum(e) => enum_variants_match(e, input.span), 353 | // syn::Data::Union(_) => Err(Error::new(input.span(), "union not supported")), 354 | } 355 | } 356 | 357 | /// Turns an enum into a match expr 358 | fn enum_variants_match(e: &Enum, span: Span) -> Result { 359 | let variant_matches = e 360 | .variants 361 | .iter() 362 | .map(variant_match_arm) 363 | .collect::>>()?; 364 | let catch_all_arm = if let Some(variant) = &e.notoken_variant { 365 | let notoken_span = variant.span; 366 | let notoken_ident = &variant.name; 367 | quote_spanned! {notoken_span=> 368 | _ => Some(Self::#notoken_ident), 369 | } 370 | } else { 371 | quote_spanned! {span=> 372 | _ => None, 373 | } 374 | }; 375 | 376 | Ok(quote_spanned! {span=> 377 | let result = match args.get(0) { 378 | #(#variant_matches)* 379 | #catch_all_arm 380 | }; 381 | 382 | Ok(result) 383 | }) 384 | } 385 | 386 | /// Turns a variant to a match arm 387 | /// ```rust,ignore 388 | /// enum NxOrXx { 389 | /// Nx, 390 | /// // ^ current variant 391 | /// Xx, 392 | /// } 393 | /// ``` 394 | /// into 395 | /// ```rust,ignore 396 | /// Some(a) if a.eq_ignore_ascii_case("NX") => Some(Self::Nx) 397 | /// ``` 398 | fn variant_match_arm(variant: &Variant) -> Result { 399 | let span = variant.span; 400 | let ident = &variant.name; 401 | let variant_token = &variant.token; 402 | 403 | Ok(match &variant.fields { 404 | Fields::Named(named) => { 405 | let (field_vars, field_returns) = named_fields_parse(named)?; 406 | let span = named.span(); 407 | 408 | quote_spanned! {span=> 409 | Some(a) if a.eq_ignore_ascii_case(#variant_token) => { 410 | *args = &args[1..]; 411 | #field_vars 412 | 413 | Some(Self::#ident {#field_returns}) 414 | } 415 | } 416 | } 417 | Fields::Unnamed(tuple) => { 418 | let (field_vars, field_returns) = unnamed_fields_parse(tuple)?; 419 | let span = tuple.span(); 420 | 421 | quote_spanned! {span=> 422 | Some(a) if a.eq_ignore_ascii_case(#variant_token) => { 423 | *args = &args[1..]; 424 | #field_vars 425 | 426 | Some(Self::#ident(#field_returns)) 427 | } 428 | } 429 | } 430 | Fields::Unit => { 431 | quote_spanned! {span=> 432 | Some(a) if a.eq_ignore_ascii_case(#variant_token) => { 433 | *args = &args[1..]; 434 | Some(Self::#ident) 435 | } 436 | } 437 | } 438 | }) 439 | } 440 | 441 | fn struct_parse_content(s: &Struct, span: Span) -> Result { 442 | let parse_token = if let Some(token) = &s.token { 443 | let token_span = token.span(); 444 | quote_spanned! {token_span=> 445 | match args.get(0) { 446 | Some(s) if s.eq_ignore_ascii_case(#token) => { 447 | *args = &args[1..]; 448 | }, 449 | _ => { return Ok(None); } 450 | } 451 | } 452 | } else { 453 | // Without token, if args is empty => None 454 | quote_spanned! {span=> 455 | if args.is_empty() { 456 | return Ok(None); 457 | } 458 | } 459 | }; 460 | let parse_fields = match &s.fields { 461 | Fields::Named(named) => { 462 | let (field_vars, field_returns) = named_fields_parse(named)?; 463 | let span = named.span(); 464 | 465 | quote_spanned! {span=> 466 | #field_vars 467 | Ok(Some(Self {#field_returns})) 468 | } 469 | } 470 | Fields::Unnamed(tuple) => { 471 | let (field_vars, field_returns) = unnamed_fields_parse(tuple)?; 472 | let span = tuple.span(); 473 | 474 | quote_spanned! {span=> 475 | #field_vars 476 | Ok(Some(Self(#field_returns))) 477 | } 478 | } 479 | 480 | Fields::Unit => quote! { Ok(Some(Self)) }, 481 | }; 482 | 483 | Ok(quote_spanned! {span=> 484 | #parse_token 485 | #parse_fields 486 | }) 487 | } 488 | 489 | // Get last path segment of a type 490 | // ::std::option::Option => Option 491 | fn last_path_segment(ty: &Type) -> Option<&PathSegment> { 492 | match ty { 493 | &Type::Path(TypePath { 494 | qself: None, 495 | path: 496 | Path { 497 | segments: ref seg, 498 | leading_colon: _, 499 | }, 500 | }) => seg.last(), 501 | _ => None, 502 | } 503 | } 504 | 505 | // if this type is Option and return the Wrapped type 506 | fn option_inner_type(ty: &Type) -> Option<&GenericArgument> { 507 | match last_path_segment(ty) { 508 | Some(PathSegment { 509 | ident, 510 | arguments: PathArguments::AngleBracketed(ref gen_arg), 511 | }) if ident == "Option" => gen_arg.args.first(), 512 | _ => None, 513 | } 514 | } 515 | 516 | /// Turns Unamed fields into code to parse each field element 517 | /// and list of return field. i.e. 518 | /// ```rust,ignore 519 | /// struct A( 520 | /// B, 521 | /// D 522 | /// ) 523 | /// ``` 524 | /// => 525 | /// ( 526 | /// ```rust,ignore 527 | /// let field_0 = ::parse_maybe(args)? 528 | /// .ok_or(::command_args::Error::InvalidLength)?; 529 | /// let field_1 = ::parse_maybe(args)? 530 | /// .ok_or(::command_args::Error::InvalidLength)?; 531 | /// ```, 532 | /// ```rust,ignore 533 | /// field0, field1 534 | /// ``` 535 | /// ) 536 | fn unnamed_fields_parse(unnamed: &syn::FieldsUnnamed) -> Result<(TokenStream, TokenStream)> { 537 | let mut count = 0; 538 | let declare_vars = unnamed.unnamed.iter().map(|f| { 539 | let var_name = Ident::new(&format!("field_{}", count), f.ty.span()); 540 | count += 1; 541 | parse_field_from_type(&f.ty, &var_name) 542 | }); 543 | 544 | let mut count = 0; 545 | let return_fields = unnamed.unnamed.iter().map(|f| { 546 | let r = Ident::new(&format!("field_{}", count), f.ty.span()); 547 | count += 1; 548 | r 549 | }); 550 | 551 | let span = unnamed.span(); 552 | Ok(( 553 | quote_spanned! {span => 554 | #(#declare_vars)* 555 | }, 556 | quote_spanned! {span => 557 | #(#return_fields),* 558 | }, 559 | )) 560 | } 561 | 562 | /// Turns Unamed fields into code to parse each field element 563 | /// and list of return field. i.e. 564 | /// ```rust,ignore 565 | /// struct A { 566 | /// b: B, 567 | /// d: D, 568 | /// } 569 | /// ``` 570 | /// => 571 | /// ( 572 | /// ```rust,ignore 573 | /// let b = ::parse_maybe(args)? 574 | /// .ok_or(::command_args::Error::InvalidLength)?; 575 | /// let d = ::parse_maybe(args)? 576 | /// .ok_or(::command_args::Error::InvalidLength)?; 577 | /// ```, 578 | /// ```rust,ignore 579 | /// b, d 580 | /// ``` 581 | /// ) 582 | fn named_fields_parse(named: &syn::FieldsNamed) -> Result<(TokenStream, TokenStream)> { 583 | let declare_vars = named.named.iter().map(|f| { 584 | let var_name = f.ident.as_ref().unwrap(); 585 | parse_field_from_type(&f.ty, var_name) 586 | }); 587 | let return_fields = named.named.iter().map(|f| f.ident.as_ref()); 588 | 589 | let span = named.span(); 590 | Ok(( 591 | quote_spanned! {span => 592 | #(#declare_vars)* 593 | }, 594 | quote_spanned! {span => 595 | #(#return_fields),* 596 | }, 597 | )) 598 | } 599 | 600 | /// Turn a type and a var name to code to parse 601 | /// ty: `B` 602 | /// var_name: `b` 603 | /// => 604 | /// ```rust,ignore 605 | /// let b = ::parse_maybe(args)? 606 | /// .ok_or(::command_args::Error::InvalidLength)?; 607 | /// ``` 608 | /// ty: `Option` 609 | /// var_name: `field_0` 610 | /// => 611 | /// ```rust,ignore 612 | /// let field_0 = ::parse_maybe(args)?; 613 | /// ``` 614 | /// 615 | fn parse_field_from_type(ty: &Type, var_name: &Ident) -> TokenStream { 616 | let ty_span = ty.span(); 617 | match option_inner_type(ty) { 618 | Some(inner_ty) => quote_spanned! {ty_span=> 619 | let #var_name = <#inner_ty as ::command_args::CommandArgs>::parse_maybe(args)?; 620 | }, 621 | None => quote_spanned! {ty_span=> 622 | let #var_name = <#ty as ::command_args::CommandArgs>::parse_maybe(args)? 623 | .ok_or(::command_args::Error::InvalidLength)?; 624 | }, 625 | } 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /command-args/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "command-args" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /command-args/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub trait CommandArgs<'a>: Sized { 4 | fn encode(&self, target: &mut Vec) -> Result<(), Error>; 5 | fn parse_maybe(args: &mut &[&'a str]) -> Result, Error>; 6 | } 7 | 8 | impl<'a> CommandArgs<'a> for &'a str { 9 | fn encode(&self, target: &mut Vec) -> Result<(), Error> { 10 | target.push(self.to_string()); 11 | Ok(()) 12 | } 13 | 14 | fn parse_maybe(args: &mut &[&'a str]) -> Result, Error> { 15 | if let Some(s) = args.get(0) { 16 | *args = &args[1..]; 17 | Ok(Some(s)) 18 | } else { 19 | Ok(None) 20 | } 21 | } 22 | } 23 | 24 | impl<'a, T: CommandArgs<'a>> CommandArgs<'a> for Vec { 25 | fn encode(&self, target: &mut Vec) -> Result<(), Error> { 26 | for ele in self.iter() { 27 | ele.encode(target)?; 28 | } 29 | 30 | Ok(()) 31 | } 32 | 33 | fn parse_maybe(args: &mut &[&'a str]) -> Result, Error> { 34 | if args.is_empty() { 35 | return Ok(None); 36 | } 37 | 38 | let mut result = Vec::new(); 39 | while let Some(ele) = T::parse_maybe(args)? { 40 | result.push(ele); 41 | } 42 | 43 | Ok(Some(result)) 44 | } 45 | } 46 | 47 | impl<'a> CommandArgs<'a> for usize { 48 | fn encode(&self, target: &mut Vec) -> Result<(), Error> { 49 | target.push(format!("{}", self)); 50 | Ok(()) 51 | } 52 | 53 | fn parse_maybe(args: &mut &[&'a str]) -> Result, Error> { 54 | if let Some(s) = args.get(0) { 55 | *args = &args[1..]; 56 | Ok(Some(s.parse().map_err(|_| Error::Parse)?)) 57 | } else { 58 | Ok(None) 59 | } 60 | } 61 | } 62 | 63 | pub trait CommandBuilder<'a> { 64 | const NAME: &'static str; 65 | } 66 | 67 | #[derive(Debug)] 68 | pub enum Error { 69 | InvalidLength, 70 | Parse, 71 | TokenNotFound(&'static str), 72 | } 73 | 74 | impl Display for Error { 75 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 76 | write!(f, "{:?}", self) 77 | } 78 | } 79 | 80 | impl std::error::Error for Error {} 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | 86 | #[test] 87 | fn test_parse_usize() { 88 | let args = ["1"]; 89 | let s = ::parse_maybe(&mut &args[..]).unwrap(); 90 | assert_eq!(s, Some(1)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /memds/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "memds" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0" 10 | bytes = "1.1.0" 11 | command-args = { path = "../command-args" } 12 | command-args-derive = { path = "../command-args-derive" } 13 | deseresp = "0.1.5" 14 | serde = "1.0" 15 | tokio = { version = "1.18.2", features = [ "full" ] } 16 | tracing = "0.1" 17 | tracing-subscriber = "0.3" 18 | vtable = "0.1.6" 19 | bincode = "1.3" 20 | futures = "0.3" 21 | 22 | [dev-dependencies] 23 | assert_matches = "1.5" 24 | -------------------------------------------------------------------------------- /memds/src/client/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use command_args::CommandArgs; 3 | use serde::{de::DeserializeOwned, Serialize}; 4 | use tokio::net::{tcp::OwnedWriteHalf, TcpStream, ToSocketAddrs}; 5 | 6 | use crate::{ 7 | command::CommandHandler, 8 | connection::{flush, FrameReader}, 9 | }; 10 | 11 | pub struct Client { 12 | frame_reader: FrameReader, 13 | writer: OwnedWriteHalf, 14 | write_buf: Vec, 15 | command_buf: Vec, 16 | } 17 | 18 | impl Client { 19 | pub async fn from_addr(addr: A) -> anyhow::Result { 20 | let conn = TcpStream::connect(addr).await?; 21 | 22 | Ok(Self::new(conn)) 23 | } 24 | 25 | pub fn new(socket: TcpStream) -> Self { 26 | let (reader, writer) = socket.into_split(); 27 | let frame_reader = FrameReader::new(reader); 28 | let write_buf = Vec::new(); 29 | let command_buf = Vec::new(); 30 | 31 | Client { 32 | frame_reader, 33 | writer, 34 | write_buf, 35 | command_buf, 36 | } 37 | } 38 | 39 | pub async fn execute<'a, C>( 40 | &mut self, 41 | command: &C, 42 | ) -> anyhow::Result<::Output> 43 | where 44 | C: CommandHandler + CommandArgs<'a>, 45 | ::Output: DeserializeOwned, 46 | { 47 | self.command_buf.clear(); 48 | command 49 | .encode(&mut self.command_buf) 50 | .context("Failed to encode command")?; 51 | 52 | let mut serializer = deseresp::from_write(&mut self.write_buf); 53 | self.command_buf 54 | .serialize(&mut serializer) 55 | .context("Failed to serialize command to RESP")?; 56 | 57 | flush(&mut self.writer, &mut self.write_buf).await; 58 | 59 | loop { 60 | if let Some(resp) = self.frame_reader.next_buffered_frame::()? { 61 | return Ok(resp); 62 | } 63 | let read_bytes = self.frame_reader.read_to_buf().await?; 64 | 65 | if read_bytes == 0 { 66 | tracing::info!("Session ended"); 67 | break; 68 | } 69 | } 70 | 71 | anyhow::bail!("Server connection ended"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /memds/src/command/admin/mod.rs: -------------------------------------------------------------------------------- 1 | use command_args_derive::CommandArgsBlock; 2 | use deseresp::types::OkResponse; 3 | 4 | use crate::database::Database; 5 | 6 | use super::{CommandHandler, Error}; 7 | 8 | #[derive(Debug, CommandArgsBlock)] 9 | #[argtoken("SAVE")] 10 | pub struct SaveCommand; 11 | 12 | impl CommandHandler for SaveCommand { 13 | type Output = OkResponse; 14 | 15 | fn handle(self, db: &Database) -> Result { 16 | db.save()?; 17 | 18 | Ok(OkResponse) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /memds/src/command/connection/mod.rs: -------------------------------------------------------------------------------- 1 | use command_args_derive::CommandArgsBlock; 2 | use deseresp::types::owned::SimpleString; 3 | use serde::Serialize; 4 | 5 | use crate::database::Database; 6 | 7 | use super::{CommandHandler, Error}; 8 | 9 | #[derive(Debug, CommandArgsBlock)] 10 | #[argtoken("AUTH")] 11 | pub struct HelloAuthArg<'a> { 12 | pub username: &'a str, 13 | pub password: &'a str, 14 | } 15 | 16 | #[derive(Debug, CommandArgsBlock)] 17 | #[argtoken("HELLO")] 18 | pub struct HelloCommand<'a> { 19 | pub protover: usize, 20 | pub auth: Option>, 21 | } 22 | 23 | #[derive(Debug, CommandArgsBlock)] 24 | #[argtoken("COMMAND")] 25 | pub struct CommandCommand; 26 | 27 | #[derive(Debug, Serialize)] 28 | pub struct ServerProperties { 29 | pub server: String, 30 | pub version: String, 31 | pub proto: usize, 32 | } 33 | 34 | #[derive(Debug, CommandArgsBlock)] 35 | #[argtoken("PING")] 36 | pub struct PingCommand; 37 | 38 | impl<'a> CommandHandler for HelloCommand<'a> { 39 | type Output = ServerProperties; 40 | 41 | fn handle(self, _db: &Database) -> Result { 42 | Ok(ServerProperties { 43 | server: String::from("memds"), 44 | version: String::from("0.0.1"), 45 | proto: 3, 46 | }) 47 | } 48 | } 49 | 50 | impl CommandHandler for CommandCommand { 51 | type Output = [usize; 0]; 52 | 53 | fn handle(self, _db: &Database) -> Result { 54 | Ok([]) 55 | } 56 | } 57 | 58 | impl CommandHandler for PingCommand { 59 | type Output = SimpleString; 60 | 61 | fn handle(self, _db: &Database) -> Result { 62 | Ok(SimpleString("PONG".into())) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | use command_args::CommandArgs; 70 | 71 | #[test] 72 | fn test_parse_hello_command() { 73 | let args = ["HELLO", "3", "AUTH", "user", "pass"]; 74 | let command = HelloCommand::parse_maybe(&mut &args[..]).unwrap().unwrap(); 75 | assert_eq!(command.protover, 3); 76 | assert_eq!(command.auth.as_ref().unwrap().username, "user"); 77 | assert_eq!(command.auth.as_ref().unwrap().username, "user"); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /memds/src/command/mod.rs: -------------------------------------------------------------------------------- 1 | use command_args::CommandArgs; 2 | use serde::Serialize; 3 | 4 | use crate::{database::Database, Error}; 5 | 6 | pub mod admin; 7 | pub mod connection; 8 | pub mod set; 9 | pub mod string; 10 | 11 | pub trait CommandHandler { 12 | type Output: Serialize; 13 | fn handle(self, db: &Database) -> Result; 14 | } 15 | 16 | fn parse_handle<'a, T>( 17 | args: &[&'a str], 18 | db: &Database, 19 | write_buf: &mut Vec, 20 | ) -> Result 21 | where 22 | T: CommandHandler, 23 | T: CommandArgs<'a>, 24 | T: std::fmt::Debug, 25 | { 26 | let command = T::parse_maybe(&mut &args[..]).map_err(Error::Parse)?; 27 | 28 | let mut serializer = deseresp::from_write(write_buf); 29 | if let Some(command) = command { 30 | let result = command.handle(db)?; 31 | 32 | result 33 | .serialize(&mut serializer) 34 | .map_err(|e| Error::Serialize(e.to_string()))?; 35 | 36 | Ok(true) 37 | } else { 38 | Ok(false) 39 | } 40 | } 41 | 42 | fn handle_unsupported_command(args: &[&str], write_buf: &mut Vec) -> Result<(), Error> { 43 | let mut serializer = deseresp::from_write(write_buf); 44 | let response = 45 | deseresp::types::owned::SimpleError(format!("ERR command {} not supported", args[0])); 46 | 47 | response 48 | .serialize(&mut serializer) 49 | .map_err(|e| Error::Serialize(e.to_string()))?; 50 | 51 | Ok(()) 52 | } 53 | 54 | macro_rules! try_commands { 55 | (($args:ident, $db:ident, $write_buf:ident) {$command_type:path}) => { 56 | if parse_handle::<$command_type>($args, $db, $write_buf)? { 57 | return Ok(()); 58 | } 59 | }; 60 | (($args:ident, $db:ident, $write_buf:ident) {$cmd_type1:path, $($cmd_type2:path),+}) => { 61 | try_commands!(($args, $db, $write_buf) {$cmd_type1}); 62 | try_commands!(($args, $db, $write_buf) {$($cmd_type2),+}) 63 | } 64 | } 65 | 66 | fn parse_and_handle_main( 67 | args: &[&str], 68 | db: &Database, 69 | write_buf: &mut Vec, 70 | ) -> Result<(), Error> { 71 | try_commands!((args, db, write_buf) { 72 | self::connection::HelloCommand, 73 | self::connection::CommandCommand, 74 | self::connection::PingCommand, 75 | self::string::GetCommand, 76 | self::string::SetCommand, 77 | self::string::IncrCommand, 78 | self::set::SaddCommand, 79 | self::set::SmembersCommand, 80 | self::admin::SaveCommand 81 | }); 82 | 83 | // not supported command 84 | handle_unsupported_command(args, write_buf) 85 | } 86 | 87 | // Entry point of command routing 88 | // Try to parse command, handle and write response to write_buf 89 | // On error, return true for connection event loop to Flush response 90 | // Return Ok(false) if everything ok 91 | pub fn parse_and_handle( 92 | args: &[&str], 93 | db: &Database, 94 | write_buf: &mut Vec, 95 | ) -> Result { 96 | match parse_and_handle_main(args, db, write_buf) { 97 | Ok(_) => Ok(false), 98 | Err(Error::Parse(e)) => { 99 | tracing::error!("Failed to parse command: {:?}, e: {}", &args, e); 100 | let mut serializer = deseresp::from_write(write_buf); 101 | let response = 102 | deseresp::types::owned::SimpleError(format!("ERR failed to parse: {}", args[0])); 103 | 104 | response 105 | .serialize(&mut serializer) 106 | .map_err(|e| Error::Serialize(e.to_string()))?; 107 | 108 | Ok(true) 109 | } 110 | Err(Error::Handle(e)) => { 111 | tracing::error!("Failed to handle command: {:?}, e: {}", &args, e); 112 | let mut serializer = deseresp::from_write(write_buf); 113 | let response = deseresp::types::owned::SimpleError(e); 114 | 115 | response 116 | .serialize(&mut serializer) 117 | .map_err(|e| Error::Serialize(e.to_string()))?; 118 | 119 | Ok(true) 120 | } 121 | Err(e @ Error::Serialize(_)) => { 122 | // failed to serialize response to user, 123 | // nothing to do except log and disconnect 124 | Err(e) 125 | } 126 | } 127 | } 128 | 129 | #[cfg(test)] 130 | mod tests { 131 | use super::*; 132 | 133 | #[test] 134 | fn test_handle_and_parse_hello_command() { 135 | let db = Database::new(String::new()); 136 | let args = ["HELLO", "3", "AUTH", "user", "pass"]; 137 | let mut write_buf = Vec::new(); 138 | parse_and_handle(&args, &db, &mut write_buf).unwrap(); 139 | let result_s = std::str::from_utf8(&write_buf).unwrap(); 140 | assert_eq!( 141 | result_s, 142 | "%3\r\n+server\r\n+memds\r\n+version\r\n+0.0.1\r\n+proto\r\n:3\r\n" 143 | ); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /memds/src/command/set/mod.rs: -------------------------------------------------------------------------------- 1 | use command_args_derive::CommandArgsBlock; 2 | 3 | use crate::database::Database; 4 | 5 | use super::{CommandHandler, Error}; 6 | 7 | #[derive(Debug, CommandArgsBlock)] 8 | #[argtoken("SADD")] 9 | pub struct SaddCommand<'a> { 10 | pub key: &'a str, 11 | pub elements: Vec<&'a str>, 12 | } 13 | 14 | impl<'a> CommandHandler for SaddCommand<'a> { 15 | type Output = usize; 16 | 17 | fn handle(self, db: &Database) -> Result { 18 | db.sadd(self.key, &self.elements) 19 | } 20 | } 21 | 22 | #[derive(Debug, CommandArgsBlock)] 23 | #[argtoken("SMEMBERS")] 24 | pub struct SmembersCommand<'a> { 25 | pub key: &'a str, 26 | } 27 | 28 | impl<'a> CommandHandler for SmembersCommand<'a> { 29 | type Output = Option>; 30 | 31 | fn handle(self, db: &Database) -> Result { 32 | db.smembers(self.key) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /memds/src/command/string/mod.rs: -------------------------------------------------------------------------------- 1 | use command_args_derive::CommandArgsBlock; 2 | use deseresp::types::OkResponse; 3 | 4 | use crate::database::Database; 5 | 6 | use super::{CommandHandler, Error}; 7 | 8 | #[derive(Debug, CommandArgsBlock)] 9 | #[argtoken("INCR")] 10 | pub struct IncrCommand<'a> { 11 | pub key: &'a str, 12 | } 13 | 14 | impl<'a> CommandHandler for IncrCommand<'a> { 15 | type Output = i64; 16 | 17 | fn handle(self, db: &Database) -> Result { 18 | db.incr(self.key) 19 | } 20 | } 21 | 22 | #[derive(Debug, CommandArgsBlock)] 23 | #[argtoken("GET")] 24 | pub struct GetCommand<'a> { 25 | pub key: &'a str, 26 | } 27 | 28 | impl<'a> CommandHandler for GetCommand<'a> { 29 | type Output = Option; 30 | 31 | fn handle(self, db: &Database) -> Result { 32 | db.get(self.key) 33 | } 34 | } 35 | 36 | #[derive(CommandArgsBlock, Debug, PartialEq)] 37 | #[argtoken("SET")] 38 | pub struct SetCommand<'a> { 39 | pub key: &'a str, 40 | pub value: &'a str, 41 | pub exists: Exists, 42 | pub get: Option, 43 | pub expire: Option, 44 | } 45 | 46 | #[derive(CommandArgsBlock, Debug, PartialEq)] 47 | pub enum Exists { 48 | #[argtoken("NX")] 49 | NotExistedOnly, 50 | #[argtoken("XX")] 51 | ExistedOnly, 52 | #[argnotoken] 53 | Any, 54 | } 55 | 56 | #[derive(CommandArgsBlock, Debug, PartialEq)] 57 | #[argtoken("GET")] 58 | pub struct SetGet; 59 | 60 | #[derive(CommandArgsBlock, Debug, PartialEq)] 61 | pub enum ExpireOption { 62 | #[argtoken("EX")] 63 | ExpireAfterSecond(usize), 64 | #[argtoken("PX")] 65 | ExpireAfterMs(usize), 66 | #[argtoken("EXAT")] 67 | ExpireAtSecond(usize), 68 | #[argtoken("PXAT")] 69 | ExpireAtMs(usize), 70 | KeepTTL, 71 | } 72 | 73 | impl<'a> CommandHandler for SetCommand<'a> { 74 | type Output = OkResponse; 75 | 76 | fn handle(self, db: &Database) -> Result { 77 | db.set(self.key, self.value)?; 78 | Ok(OkResponse) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use assert_matches::assert_matches; 86 | use command_args::CommandArgs; 87 | 88 | #[test] 89 | fn test_parse_set() { 90 | let cmd_str = vec!["SET", "a", "b", "NX", "GET", "EX", "20"]; 91 | let s = SetCommand::parse_maybe(&mut &cmd_str[..]).unwrap().unwrap(); 92 | 93 | assert_eq!(s.key, "a"); 94 | assert_eq!(s.value, "b"); 95 | assert_matches!(s.exists, Exists::NotExistedOnly); 96 | assert_matches!(s.get, Some(SetGet)); 97 | assert_matches!(s.expire, Some(ExpireOption::ExpireAfterSecond(20))); 98 | 99 | let cmd_str = vec!["SET", "a", "b", "PXAT", "20"]; 100 | let s = SetCommand::parse_maybe(&mut &cmd_str[..]).unwrap().unwrap(); 101 | 102 | assert_eq!(s.key, "a"); 103 | assert_eq!(s.value, "b"); 104 | assert_matches!(s.exists, Exists::Any); 105 | assert_matches!(s.get, None); 106 | assert_matches!(s.expire, Some(ExpireOption::ExpireAtMs(20))); 107 | } 108 | 109 | #[test] 110 | fn test_encode_set() { 111 | let s = SetCommand { 112 | key: "abc", 113 | value: "def", 114 | exists: Exists::Any, 115 | get: None, 116 | expire: Some(ExpireOption::ExpireAfterSecond(20)), 117 | }; 118 | let mut target: Vec = Vec::new(); 119 | s.encode(&mut target).unwrap(); 120 | 121 | let expected = ["SET", "abc", "def", "EX", "20"] 122 | .into_iter() 123 | .map(String::from) 124 | .collect::>(); 125 | assert_eq!(expected, target); 126 | let target_ref = target.iter().map(String::as_str).collect::>(); 127 | 128 | let source = SetCommand::parse_maybe(&mut &target_ref[..]) 129 | .unwrap() 130 | .unwrap(); 131 | assert_eq!(source, s); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /memds/src/connection/mod.rs: -------------------------------------------------------------------------------- 1 | use bytes::{Buf, BytesMut}; 2 | use serde::Deserialize; 3 | use tokio::{ 4 | io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}, 5 | net::tcp::OwnedReadHalf, 6 | }; 7 | 8 | fn parse<'a, D: Deserialize<'a>>(read_buf: &'a mut BytesMut) -> anyhow::Result> { 9 | let mut deserializer = deseresp::Deserializer::from_slice(read_buf); 10 | match Deserialize::deserialize(&mut deserializer) { 11 | Ok(deserialized) => { 12 | let data = deserialized; 13 | let bytes_consumed = deserializer.get_consumed_bytes(); 14 | Ok(Some((data, bytes_consumed))) 15 | } 16 | Err(deseresp::Error::EOF) => Ok(None), 17 | Err(e) => { 18 | tracing::error!( 19 | "Error parsing command: {}, e: {}", 20 | std::str::from_utf8(read_buf).unwrap_or(&String::from("invalid utf8")), 21 | e 22 | ); 23 | Err(e.into()) 24 | } 25 | } 26 | } 27 | 28 | /// A redis buffered frame reader 29 | pub struct FrameReader { 30 | /// source reader 31 | reader: OwnedReadHalf, 32 | /// Read buffer 33 | read_buf: BytesMut, 34 | /// Number of bytes consumed to decode the last frame 35 | last_frame_bytes_consumed: usize, 36 | } 37 | 38 | impl FrameReader { 39 | /// Creates new frame reader from OwnedReadHalf 40 | pub fn new(reader: OwnedReadHalf) -> Self { 41 | FrameReader { 42 | reader, 43 | read_buf: BytesMut::with_capacity(4096), 44 | last_frame_bytes_consumed: 0, 45 | } 46 | } 47 | 48 | /// Read from reader to the read buffer 49 | pub async fn read_to_buf(&mut self) -> anyhow::Result { 50 | Ok(self.reader.read_buf(&mut self.read_buf).await?) 51 | } 52 | 53 | /// Read a frame out of the read buffer, if not enough data to read a full frame, 54 | /// returns None. 55 | /// 56 | /// Advances read buffer by number of bytes consumed from decoding last frame. 57 | /// If decode frame successful, set new number of bytes to be advanced before decoding next 58 | /// frame. 59 | pub fn next_buffered_frame<'a, D: Deserialize<'a>>(&'a mut self) -> anyhow::Result> { 60 | self.read_buf.advance(self.last_frame_bytes_consumed); 61 | self.last_frame_bytes_consumed = 0; 62 | 63 | Ok(match parse(&mut self.read_buf)? { 64 | Some((frame, bytes_consumed)) => { 65 | self.last_frame_bytes_consumed = bytes_consumed; 66 | Some(frame) 67 | } 68 | None => None, 69 | }) 70 | } 71 | } 72 | 73 | /// Write the buffer to the writer and clear it 74 | pub async fn flush(mut writer: T, write_buf: &mut Vec) 75 | where 76 | T: AsyncWrite + Unpin, 77 | { 78 | if !write_buf.is_empty() { 79 | writer.write_all(&write_buf[..]).await.unwrap(); 80 | write_buf.clear(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /memds/src/database/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Mutex}; 2 | 3 | use crate::{ 4 | memds::{MemDS, SetDS, StringDS}, 5 | storage, Error, 6 | }; 7 | 8 | pub struct Database { 9 | db_path: String, 10 | data: Mutex>, 11 | } 12 | 13 | impl Database { 14 | pub fn new(db_path: String) -> Self { 15 | match storage::load(&db_path) { 16 | Ok(d) => Database { 17 | db_path, 18 | data: Mutex::new(d), 19 | }, 20 | Err(e) => { 21 | tracing::error!("Failed to load data: {}", e); 22 | Database { 23 | db_path, 24 | data: Mutex::new(Default::default()), 25 | } 26 | } 27 | } 28 | } 29 | 30 | pub fn incr(&self, key: &str) -> Result { 31 | let mut lock = self.data.lock().unwrap(); 32 | let string = lock 33 | .entry(key.to_string()) 34 | .or_insert(MemDS::String(StringDS::from("0"))); 35 | string.string_mut(key)?.incr() 36 | } 37 | 38 | pub fn get(&self, key: &str) -> Result, Error> { 39 | self.data 40 | .lock() 41 | .unwrap() 42 | .get(key) 43 | .map(|v| v.string(key).map(StringDS::fetch)) 44 | .transpose() 45 | } 46 | 47 | pub fn set(&self, key: &str, value: &str) -> Result<(), Error> { 48 | self.data 49 | .lock() 50 | .unwrap() 51 | .insert(key.to_owned(), MemDS::String(StringDS::from(value))); 52 | 53 | Ok(()) 54 | } 55 | 56 | pub fn sadd(&self, key: &str, elements: &[&str]) -> Result { 57 | let mut lock = self.data.lock().unwrap(); 58 | let set = lock 59 | .entry(key.to_string()) 60 | .or_insert_with(|| MemDS::Set(SetDS::default())); 61 | let added = set.set_mut(key)?.add(elements.iter()); 62 | 63 | Ok(added) 64 | } 65 | 66 | pub fn smembers(&self, key: &str) -> Result>, Error> { 67 | let lock = self.data.lock().unwrap(); 68 | 69 | match lock.get(key) { 70 | None => Ok(None), 71 | Some(set) => Ok(Some(set.set(key)?.members())), 72 | } 73 | } 74 | 75 | pub fn save(&self) -> Result<(), Error> { 76 | let lock = self.data.lock().unwrap(); 77 | 78 | storage::save(&self.db_path, &lock) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /memds/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod command; 3 | pub mod connection; 4 | pub mod database; 5 | pub mod memds; 6 | mod server; 7 | pub mod storage; 8 | mod wal; 9 | 10 | pub use server::Server; 11 | 12 | use std::{fmt::Display, future::Future, pin::Pin}; 13 | 14 | use futures::future::FutureExt; 15 | 16 | #[derive(Debug)] 17 | pub enum Error { 18 | Parse(command_args::Error), 19 | Handle(String), 20 | Serialize(String), 21 | } 22 | 23 | impl Display for Error { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | write!(f, "{:?}", self) 26 | } 27 | } 28 | 29 | impl std::error::Error for Error {} 30 | 31 | /// A future that will terminate its service on poll 32 | pub struct Terminator { 33 | f: Pin + Send>>, 34 | } 35 | 36 | impl Terminator { 37 | fn from_future + Send + 'static>(f: F) -> Self { 38 | Terminator { f: f.boxed() } 39 | } 40 | } 41 | 42 | impl Future for Terminator { 43 | type Output = (); 44 | 45 | fn poll( 46 | mut self: Pin<&mut Self>, 47 | cx: &mut std::task::Context<'_>, 48 | ) -> std::task::Poll { 49 | self.f.as_mut().poll(cx) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /memds/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use memds::Server; 3 | use tokio::signal; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<()> { 7 | tracing_subscriber::fmt::init(); 8 | let server = Server::new(6901, "db.bin".into()); 9 | 10 | tracing::info!("Listening..."); 11 | let (_addr, server_service) = server.service().await?; 12 | 13 | wait_for_signal().await; 14 | tracing::info!("SIGINT received, shutting down..."); 15 | 16 | server_service.await; 17 | 18 | Ok(()) 19 | } 20 | 21 | async fn wait_for_signal() { 22 | if let Err(e) = signal::ctrl_c().await { 23 | tracing::error!("Error waiting for SIGINT: {}", e); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /memds/src/memds/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fmt::Write; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::Error; 7 | 8 | #[derive(Serialize, Deserialize)] 9 | pub enum MemDS { 10 | String(StringDS), 11 | Set(SetDS), 12 | } 13 | 14 | #[derive(Serialize, Deserialize)] 15 | pub struct StringDS { 16 | s: String, 17 | } 18 | 19 | #[derive(Serialize, Deserialize)] 20 | pub struct SetDS { 21 | s: HashSet, 22 | } 23 | 24 | impl MemDS { 25 | pub fn string(&self, key: &str) -> Result<&StringDS, Error> { 26 | match self { 27 | MemDS::String(s) => Ok(s), 28 | _ => Err(Error::Handle(format!("ERR key {} is not string", key))), 29 | } 30 | } 31 | 32 | pub fn string_mut(&mut self, key: &str) -> Result<&mut StringDS, Error> { 33 | match self { 34 | MemDS::String(s) => Ok(s), 35 | _ => Err(Error::Handle(format!("ERR key {} is not string", key))), 36 | } 37 | } 38 | 39 | pub fn set(&self, key: &str) -> Result<&SetDS, Error> { 40 | match self { 41 | MemDS::Set(s) => Ok(s), 42 | _ => Err(Error::Handle(format!("ERR key {} is not set", key))), 43 | } 44 | } 45 | 46 | pub fn set_mut(&mut self, key: &str) -> Result<&mut SetDS, Error> { 47 | match self { 48 | MemDS::Set(s) => Ok(s), 49 | _ => Err(Error::Handle(format!("ERR key {} is not set", key))), 50 | } 51 | } 52 | } 53 | 54 | impl StringDS { 55 | pub fn from(s: S) -> Self { 56 | Self { s: s.to_string() } 57 | } 58 | 59 | pub fn fetch(&self) -> String { 60 | self.s.to_owned() 61 | } 62 | 63 | pub fn incr(&mut self) -> Result { 64 | match self.s.parse::() { 65 | Ok(mut num) => { 66 | num = num + 1; 67 | self.s.clear(); 68 | write!(self.s, "{}", num).unwrap(); 69 | 70 | Ok(num) 71 | } 72 | Err(_) => Err(Error::Handle(format!( 73 | "value is not an integer or out of range" 74 | ))), 75 | } 76 | } 77 | } 78 | 79 | impl SetDS { 80 | pub fn from(s: S) -> Self { 81 | let mut set = HashSet::new(); 82 | set.insert(s.to_string()); 83 | Self { s: set } 84 | } 85 | 86 | pub fn add(&mut self, elements: E) -> usize 87 | where 88 | E: Iterator, 89 | S: ToString, 90 | { 91 | let mut count = 0; 92 | for e in elements { 93 | if self.s.insert(e.to_string()) { 94 | count += 1; 95 | } 96 | } 97 | 98 | count 99 | } 100 | 101 | pub fn members(&self) -> Vec { 102 | self.s.iter().cloned().collect() 103 | } 104 | } 105 | 106 | impl Default for SetDS { 107 | fn default() -> Self { 108 | Self { 109 | s: Default::default(), 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /memds/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, sync::Arc}; 2 | 3 | use futures::{future, stream::FuturesUnordered, StreamExt}; 4 | use tokio::{ 5 | net::{TcpListener, TcpStream}, 6 | sync::broadcast, 7 | }; 8 | 9 | use crate::{ 10 | connection::{flush, FrameReader}, 11 | database::Database, 12 | Terminator, 13 | }; 14 | 15 | /// 100KB buffer size for pipelined write 16 | const WRITE_BUF_SIZE_LIMIT: usize = 1024 * 100; 17 | 18 | pub struct Server { 19 | port: u16, 20 | db: Arc, 21 | } 22 | 23 | async fn accept_loop( 24 | db: Arc, 25 | listener: TcpListener, 26 | addr: SocketAddr, 27 | shutdown_tx: broadcast::Sender<()>, 28 | mut shutdown_rx: broadcast::Receiver<()>, 29 | ) { 30 | // TODO: use tokio's JoinSet when stable 31 | let mut sessions = FuturesUnordered::new(); 32 | 33 | tracing::info!("Listening... {}", addr); 34 | loop { 35 | tokio::select! { 36 | Some(_) = sessions.next() => { 37 | tracing::debug!("Session ended"); 38 | } 39 | accept_result = listener.accept() => { 40 | match accept_result { 41 | Ok((socket, _)) => { 42 | let conn = 43 | Session::new(socket, db.clone(), shutdown_tx.subscribe()); 44 | 45 | sessions.push(tokio::spawn(async move { 46 | if let Err(e) = conn.handle().await { 47 | tracing::error!("Error: {}", e); 48 | }; 49 | })); 50 | } 51 | Err(e) => { 52 | tracing::error!("Failed to accept request: {}", e); 53 | } 54 | } 55 | } 56 | _ = shutdown_rx.recv() => { 57 | break 58 | } 59 | } 60 | } 61 | 62 | tracing::info!("Wait for sessions to end ..."); 63 | while sessions.next().await.is_some() {} 64 | tracing::info!("All sessions ended."); 65 | } 66 | 67 | impl Server { 68 | pub fn new(port: u16, db_path: String) -> Self { 69 | Server { 70 | port, 71 | db: Arc::new(Database::new(db_path)), 72 | } 73 | } 74 | 75 | pub async fn service(self) -> anyhow::Result<(SocketAddr, Terminator)> { 76 | let listener = TcpListener::bind(("127.0.0.1", self.port)).await?; 77 | 78 | let addr = listener.local_addr().unwrap(); 79 | 80 | let (shutdown_tx, shutdown_rx) = broadcast::channel(1); 81 | let shutdown_tx_terminator = shutdown_tx.clone(); 82 | let service_handler = tokio::spawn(async move { 83 | accept_loop(self.db.clone(), listener, addr, shutdown_tx, shutdown_rx).await; 84 | 85 | tracing::info!("Saving DB"); 86 | if let Err(e) = self.db.save() { 87 | tracing::error!("Failed to save DB: {}", e); 88 | } 89 | }); 90 | 91 | let terminator = Terminator::from_future(async move { 92 | if let Err(e) = shutdown_tx_terminator.send(()) { 93 | tracing::error!("Failed to send shutdow signal: {}", e); 94 | } 95 | if let Err(e) = service_handler.await { 96 | tracing::error!("Failed to wait for server to shutdown: {}", e); 97 | } 98 | }); 99 | 100 | Ok((addr, terminator)) 101 | } 102 | } 103 | 104 | struct Session { 105 | socket: TcpStream, 106 | db: Arc, 107 | shutdown: broadcast::Receiver<()>, 108 | } 109 | 110 | impl Session { 111 | fn new(socket: TcpStream, db: Arc, shutdown: broadcast::Receiver<()>) -> Self { 112 | Session { 113 | socket, 114 | db, 115 | shutdown, 116 | } 117 | } 118 | 119 | async fn handle(mut self) -> anyhow::Result<()> { 120 | let (reader, mut writer) = self.socket.into_split(); 121 | let mut write_buf = Vec::new(); 122 | let mut connection = FrameReader::new(reader); 123 | 124 | 'main: loop { 125 | while let Some(frame) = connection.next_buffered_frame::>()? { 126 | tracing::info!("Received frame: {:?}", frame); 127 | match crate::command::parse_and_handle(&frame[..], &self.db, &mut write_buf) { 128 | Ok(need_flush) => { 129 | if need_flush || write_buf.len() > WRITE_BUF_SIZE_LIMIT { 130 | flush(&mut writer, &mut write_buf).await; 131 | } 132 | } 133 | Err(e) => { 134 | tracing::error!("Error handling command: {:?}, e: {}", &frame, e); 135 | return Err(e.into()); 136 | } 137 | } 138 | } 139 | 140 | let read_write = 141 | future::join(connection.read_to_buf(), flush(&mut writer, &mut write_buf)); 142 | tokio::pin!(read_write); 143 | 144 | tokio::select! { 145 | _ = self.shutdown.recv() => { 146 | tracing::info!("Receive shutdown request, end session."); 147 | break 'main; 148 | } 149 | (read_bytes, ()) = &mut read_write => { 150 | if read_bytes? == 0 { 151 | tracing::info!("Session ended by client."); 152 | break 'main; 153 | } 154 | } 155 | } 156 | } 157 | 158 | Ok(()) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /memds/src/storage/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fs::File, 4 | io::{BufReader, BufWriter}, 5 | }; 6 | 7 | use crate::{memds::MemDS, Error}; 8 | 9 | // TODO: change to storage Error 10 | pub fn save(db_path: &str, db: &HashMap) -> Result<(), Error> { 11 | let file = File::create(db_path) 12 | .map_err(|e| Error::Handle(format!("Failed to open db file {}", e)))?; 13 | let writer = BufWriter::new(file); 14 | 15 | bincode::serialize_into(writer, db).map_err(|e| Error::Handle(format!("Failed to save {}", e))) 16 | } 17 | 18 | pub fn load(db_path: &str) -> Result, Error> { 19 | let file = 20 | File::open(db_path).map_err(|e| Error::Handle(format!("Failed to open db file {}", e)))?; 21 | let reader = BufReader::new(file); 22 | 23 | bincode::deserialize_from(reader).map_err(|e| Error::Handle(format!("Failed to load {}", e))) 24 | } 25 | -------------------------------------------------------------------------------- /memds/src/wal/mod.rs: -------------------------------------------------------------------------------- 1 | struct WalEntry {} 2 | -------------------------------------------------------------------------------- /memds/tests/command_tests.rs: -------------------------------------------------------------------------------- 1 | use memds::{ 2 | client::Client, 3 | command::{ 4 | connection::PingCommand, 5 | string::{GetCommand, IncrCommand}, 6 | }, 7 | Server, 8 | }; 9 | 10 | #[tokio::test] 11 | async fn test_ping_command() { 12 | let port = 0; 13 | let server = Server::new(port, "/dev/null".into()); 14 | 15 | let (addr, server_handle) = server.service().await.unwrap(); 16 | 17 | let mut client = Client::from_addr(addr).await.unwrap(); 18 | let result = client.execute(&PingCommand).await.unwrap(); 19 | assert_eq!(result.0, "PONG"); 20 | 21 | server_handle.await; 22 | } 23 | 24 | #[tokio::test] 25 | async fn test_get_command() { 26 | let port = 1234; 27 | let server = Server::new(port, "/dev/null".into()); 28 | 29 | let (addr, server_handle) = server.service().await.unwrap(); 30 | 31 | let mut client = Client::from_addr(addr).await.unwrap(); 32 | let result = client.execute(&GetCommand { key: "a" }).await.unwrap(); 33 | assert_eq!(result, None); 34 | 35 | server_handle.await; 36 | } 37 | 38 | #[tokio::test] 39 | async fn test_incr_command() { 40 | let port = 1235; 41 | let server = Server::new(port, "/dev/null".into()); 42 | 43 | let (addr, server_handle) = server.service().await.unwrap(); 44 | 45 | let mut client = Client::from_addr(addr).await.unwrap(); 46 | let result = client.execute(&IncrCommand { key: "a" }).await.unwrap(); 47 | assert_eq!(result, 1); 48 | 49 | let result = client.execute(&GetCommand { key: "a" }).await.unwrap(); 50 | assert_eq!(result, Some("1".to_string())); 51 | 52 | let result = client.execute(&IncrCommand { key: "a" }).await.unwrap(); 53 | assert_eq!(result, 2); 54 | 55 | let result = client.execute(&GetCommand { key: "a" }).await.unwrap(); 56 | assert_eq!(result, Some("2".to_string())); 57 | 58 | server_handle.await; 59 | } 60 | --------------------------------------------------------------------------------