├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── posts └── week2.md └── src ├── sstweek └── main.rs ├── week1 └── main.rs └── week2 └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | db 3 | -------------------------------------------------------------------------------- /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 = "autocfg" 22 | version = "1.3.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 25 | 26 | [[package]] 27 | name = "backtrace" 28 | version = "0.3.71" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 31 | dependencies = [ 32 | "addr2line", 33 | "cc", 34 | "cfg-if", 35 | "libc", 36 | "miniz_oxide", 37 | "object", 38 | "rustc-demangle", 39 | ] 40 | 41 | [[package]] 42 | name = "bitflags" 43 | version = "2.5.0" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 46 | 47 | [[package]] 48 | name = "bytes" 49 | version = "1.6.0" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 52 | 53 | [[package]] 54 | name = "cc" 55 | version = "1.0.97" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" 58 | 59 | [[package]] 60 | name = "cfg-if" 61 | version = "1.0.0" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 64 | 65 | [[package]] 66 | name = "futures" 67 | version = "0.3.30" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 70 | dependencies = [ 71 | "futures-channel", 72 | "futures-core", 73 | "futures-executor", 74 | "futures-io", 75 | "futures-sink", 76 | "futures-task", 77 | "futures-util", 78 | ] 79 | 80 | [[package]] 81 | name = "futures-channel" 82 | version = "0.3.30" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 85 | dependencies = [ 86 | "futures-core", 87 | "futures-sink", 88 | ] 89 | 90 | [[package]] 91 | name = "futures-core" 92 | version = "0.3.30" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 95 | 96 | [[package]] 97 | name = "futures-executor" 98 | version = "0.3.30" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 101 | dependencies = [ 102 | "futures-core", 103 | "futures-task", 104 | "futures-util", 105 | ] 106 | 107 | [[package]] 108 | name = "futures-io" 109 | version = "0.3.30" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 112 | 113 | [[package]] 114 | name = "futures-macro" 115 | version = "0.3.30" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 118 | dependencies = [ 119 | "proc-macro2", 120 | "quote", 121 | "syn", 122 | ] 123 | 124 | [[package]] 125 | name = "futures-sink" 126 | version = "0.3.30" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 129 | 130 | [[package]] 131 | name = "futures-task" 132 | version = "0.3.30" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 135 | 136 | [[package]] 137 | name = "futures-util" 138 | version = "0.3.30" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 141 | dependencies = [ 142 | "futures-channel", 143 | "futures-core", 144 | "futures-io", 145 | "futures-macro", 146 | "futures-sink", 147 | "futures-task", 148 | "memchr", 149 | "pin-project-lite", 150 | "pin-utils", 151 | "slab", 152 | ] 153 | 154 | [[package]] 155 | name = "gimli" 156 | version = "0.28.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 159 | 160 | [[package]] 161 | name = "hermit-abi" 162 | version = "0.3.9" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 165 | 166 | [[package]] 167 | name = "itoa" 168 | version = "1.0.11" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 171 | 172 | [[package]] 173 | name = "libc" 174 | version = "0.2.154" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 177 | 178 | [[package]] 179 | name = "lock_api" 180 | version = "0.4.12" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 183 | dependencies = [ 184 | "autocfg", 185 | "scopeguard", 186 | ] 187 | 188 | [[package]] 189 | name = "memchr" 190 | version = "2.7.2" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 193 | 194 | [[package]] 195 | name = "miniz_oxide" 196 | version = "0.7.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 199 | dependencies = [ 200 | "adler", 201 | ] 202 | 203 | [[package]] 204 | name = "mio" 205 | version = "0.8.11" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 208 | dependencies = [ 209 | "libc", 210 | "wasi", 211 | "windows-sys 0.48.0", 212 | ] 213 | 214 | [[package]] 215 | name = "nulldb" 216 | version = "0.1.0" 217 | dependencies = [ 218 | "futures", 219 | "serde", 220 | "serde_json", 221 | "tokio", 222 | ] 223 | 224 | [[package]] 225 | name = "num_cpus" 226 | version = "1.16.0" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 229 | dependencies = [ 230 | "hermit-abi", 231 | "libc", 232 | ] 233 | 234 | [[package]] 235 | name = "object" 236 | version = "0.32.2" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 239 | dependencies = [ 240 | "memchr", 241 | ] 242 | 243 | [[package]] 244 | name = "parking_lot" 245 | version = "0.12.2" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 248 | dependencies = [ 249 | "lock_api", 250 | "parking_lot_core", 251 | ] 252 | 253 | [[package]] 254 | name = "parking_lot_core" 255 | version = "0.9.10" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 258 | dependencies = [ 259 | "cfg-if", 260 | "libc", 261 | "redox_syscall", 262 | "smallvec", 263 | "windows-targets 0.52.5", 264 | ] 265 | 266 | [[package]] 267 | name = "pin-project-lite" 268 | version = "0.2.14" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 271 | 272 | [[package]] 273 | name = "pin-utils" 274 | version = "0.1.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 277 | 278 | [[package]] 279 | name = "proc-macro2" 280 | version = "1.0.82" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" 283 | dependencies = [ 284 | "unicode-ident", 285 | ] 286 | 287 | [[package]] 288 | name = "quote" 289 | version = "1.0.36" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 292 | dependencies = [ 293 | "proc-macro2", 294 | ] 295 | 296 | [[package]] 297 | name = "redox_syscall" 298 | version = "0.5.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 301 | dependencies = [ 302 | "bitflags", 303 | ] 304 | 305 | [[package]] 306 | name = "rustc-demangle" 307 | version = "0.1.24" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 310 | 311 | [[package]] 312 | name = "ryu" 313 | version = "1.0.18" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 316 | 317 | [[package]] 318 | name = "scopeguard" 319 | version = "1.2.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 322 | 323 | [[package]] 324 | name = "serde" 325 | version = "1.0.201" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" 328 | dependencies = [ 329 | "serde_derive", 330 | ] 331 | 332 | [[package]] 333 | name = "serde_derive" 334 | version = "1.0.201" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" 337 | dependencies = [ 338 | "proc-macro2", 339 | "quote", 340 | "syn", 341 | ] 342 | 343 | [[package]] 344 | name = "serde_json" 345 | version = "1.0.117" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 348 | dependencies = [ 349 | "itoa", 350 | "ryu", 351 | "serde", 352 | ] 353 | 354 | [[package]] 355 | name = "signal-hook-registry" 356 | version = "1.4.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 359 | dependencies = [ 360 | "libc", 361 | ] 362 | 363 | [[package]] 364 | name = "slab" 365 | version = "0.4.9" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 368 | dependencies = [ 369 | "autocfg", 370 | ] 371 | 372 | [[package]] 373 | name = "smallvec" 374 | version = "1.13.2" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 377 | 378 | [[package]] 379 | name = "socket2" 380 | version = "0.5.7" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 383 | dependencies = [ 384 | "libc", 385 | "windows-sys 0.52.0", 386 | ] 387 | 388 | [[package]] 389 | name = "syn" 390 | version = "2.0.63" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" 393 | dependencies = [ 394 | "proc-macro2", 395 | "quote", 396 | "unicode-ident", 397 | ] 398 | 399 | [[package]] 400 | name = "tokio" 401 | version = "1.37.0" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 404 | dependencies = [ 405 | "backtrace", 406 | "bytes", 407 | "libc", 408 | "mio", 409 | "num_cpus", 410 | "parking_lot", 411 | "pin-project-lite", 412 | "signal-hook-registry", 413 | "socket2", 414 | "tokio-macros", 415 | "windows-sys 0.48.0", 416 | ] 417 | 418 | [[package]] 419 | name = "tokio-macros" 420 | version = "2.2.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" 423 | dependencies = [ 424 | "proc-macro2", 425 | "quote", 426 | "syn", 427 | ] 428 | 429 | [[package]] 430 | name = "unicode-ident" 431 | version = "1.0.12" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 434 | 435 | [[package]] 436 | name = "wasi" 437 | version = "0.11.0+wasi-snapshot-preview1" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 440 | 441 | [[package]] 442 | name = "windows-sys" 443 | version = "0.48.0" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 446 | dependencies = [ 447 | "windows-targets 0.48.5", 448 | ] 449 | 450 | [[package]] 451 | name = "windows-sys" 452 | version = "0.52.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 455 | dependencies = [ 456 | "windows-targets 0.52.5", 457 | ] 458 | 459 | [[package]] 460 | name = "windows-targets" 461 | version = "0.48.5" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 464 | dependencies = [ 465 | "windows_aarch64_gnullvm 0.48.5", 466 | "windows_aarch64_msvc 0.48.5", 467 | "windows_i686_gnu 0.48.5", 468 | "windows_i686_msvc 0.48.5", 469 | "windows_x86_64_gnu 0.48.5", 470 | "windows_x86_64_gnullvm 0.48.5", 471 | "windows_x86_64_msvc 0.48.5", 472 | ] 473 | 474 | [[package]] 475 | name = "windows-targets" 476 | version = "0.52.5" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 479 | dependencies = [ 480 | "windows_aarch64_gnullvm 0.52.5", 481 | "windows_aarch64_msvc 0.52.5", 482 | "windows_i686_gnu 0.52.5", 483 | "windows_i686_gnullvm", 484 | "windows_i686_msvc 0.52.5", 485 | "windows_x86_64_gnu 0.52.5", 486 | "windows_x86_64_gnullvm 0.52.5", 487 | "windows_x86_64_msvc 0.52.5", 488 | ] 489 | 490 | [[package]] 491 | name = "windows_aarch64_gnullvm" 492 | version = "0.48.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 495 | 496 | [[package]] 497 | name = "windows_aarch64_gnullvm" 498 | version = "0.52.5" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 501 | 502 | [[package]] 503 | name = "windows_aarch64_msvc" 504 | version = "0.48.5" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 507 | 508 | [[package]] 509 | name = "windows_aarch64_msvc" 510 | version = "0.52.5" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 513 | 514 | [[package]] 515 | name = "windows_i686_gnu" 516 | version = "0.48.5" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 519 | 520 | [[package]] 521 | name = "windows_i686_gnu" 522 | version = "0.52.5" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 525 | 526 | [[package]] 527 | name = "windows_i686_gnullvm" 528 | version = "0.52.5" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 531 | 532 | [[package]] 533 | name = "windows_i686_msvc" 534 | version = "0.48.5" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 537 | 538 | [[package]] 539 | name = "windows_i686_msvc" 540 | version = "0.52.5" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 543 | 544 | [[package]] 545 | name = "windows_x86_64_gnu" 546 | version = "0.48.5" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 549 | 550 | [[package]] 551 | name = "windows_x86_64_gnu" 552 | version = "0.52.5" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 555 | 556 | [[package]] 557 | name = "windows_x86_64_gnullvm" 558 | version = "0.48.5" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 561 | 562 | [[package]] 563 | name = "windows_x86_64_gnullvm" 564 | version = "0.52.5" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 567 | 568 | [[package]] 569 | name = "windows_x86_64_msvc" 570 | version = "0.48.5" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 573 | 574 | [[package]] 575 | name = "windows_x86_64_msvc" 576 | version = "0.52.5" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 579 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nulldb" 3 | version = "0.1.0" 4 | edition = "2021" 5 | default-run = "week2" 6 | 7 | [[bin]] 8 | name = "week1" 9 | path = "src/week1/main.rs" 10 | 11 | [[bin]] 12 | name = "week2" 13 | path = "src/week2/main.rs" 14 | 15 | [dependencies] 16 | futures = "0.3.30" 17 | serde = { version = "1.0.201", features = ["derive"] } 18 | serde_json = "1.0.117" 19 | tokio = { version = "1.37.0", features = ["full"] } 20 | -------------------------------------------------------------------------------- /posts/week2.md: -------------------------------------------------------------------------------- 1 | I didn't realize how hard it would be to bin-pack episodes of this thing into the roughly ~750 word chunks that I try to keep issues of this newsletter at. 2 | Lots of things are like, oh that's small, so I'm going to stuff it in with 3 | something else, but then that something else is sort of big and so it's iffy to 4 | cram something else in there too. Alas! 5 | 6 | When we [last left our heroes](https://buttondown.email/jaffray/archive/null-bitmap-builds-a-database-1-the-log-is/) we were writing all of our writes into a log, and 7 | serving reads by scanning the entire log to figure out what the value of a given 8 | key should be. 9 | 10 | Today we're going to fix that with a little fella we like to call the memtable. 11 | 12 | There's a fairly natural solution to this which is to just keep an in-memory data structure that supports fast reads and fast writes. 13 | This is the titular "memtable." 14 | This thing won't actually be stored durably anywhere, but we still have the log for that. 15 | 16 | 17 | ```rust 18 | #[derive(Default)] 19 | struct Memtable { 20 | data: BTreeMap, Vec>, 21 | } 22 | 23 | impl Queryable for Memtable { 24 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 25 | Ok(self.data.get(key).cloned()) 26 | } 27 | } 28 | 29 | impl Memtable { 30 | fn put(&mut self, key: Vec, value: Vec) { 31 | self.data.insert(key, value); 32 | } 33 | } 34 | ``` 35 | 36 | I think of this as like, there's a bunch of boxes we need ticked, and [no man has all three](https://buttondown.email/jaffray/archive/the-three-places-for-data-in-an-lsm/): 37 | 38 | * Fast reads, 39 | * fast writes, 40 | * durability. 41 | 42 | Our log gave us fast writes (because they're just appends) and durability (because it's stored on disk). 43 | Our memtable gives us fast reads and fast writes, but no durability. 44 | 45 | So we have to update our database to use the memtable instead of the log. 46 | 47 | ```rust 48 | struct Db { 49 | log: Log, 50 | memtable: Memtable, 51 | } 52 | 53 | impl Db { 54 | async fn new(db_dir: impl AsRef) -> Result { 55 | if !db_dir.as_ref().exists() { 56 | tokio::fs::create_dir_all(&db_dir).await?; 57 | } 58 | let log = Log::open(db_dir.as_ref().join("log")).await?; 59 | let memtable = Memtable::default(); 60 | Ok(Db { log, memtable }) 61 | } 62 | 63 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 64 | self.log.put(key, value).await?; 65 | self.memtable.put(key.into(), value.into()); 66 | 67 | Ok(()) 68 | } 69 | 70 | async fn get(&mut self, key: &[u8]) -> Result>, NdbError> { 71 | Ok(self.memtable.get(key).await?) 72 | } 73 | } 74 | ``` 75 | 76 | There's another thing we have to do though, if we write data into the database, and then restart it, we need to be able to access the data from before. 77 | This was easy before, because we just always consulted the on-disk index, but now, we need to "rehydrate" the memtable so that it contains all the data from our log. 78 | We can do this by just replaying the entire log so that it's as though we're seeing each of its entries for the first time again. 79 | 80 | ```rust 81 | impl Memtable { 82 | async fn hydrate(log: &Log) -> Result { 83 | let mut data = BTreeMap::new(); 84 | let reader = File::open(&log.path).await?; 85 | let reader = BufReader::new(reader); 86 | let mut lines = reader.lines(); 87 | while let Some(line) = lines.next_line().await? { 88 | let put: Put = serde_json::from_str(&line)?; 89 | data.insert(put.key, put.value); 90 | } 91 | 92 | Ok(Memtable { data }) 93 | } 94 | } 95 | ``` 96 | 97 | ```rust 98 | async fn new(db_dir: impl AsRef) -> Result { 99 | if !db_dir.as_ref().exists() { 100 | tokio::fs::create_dir_all(&db_dir).await?; 101 | } 102 | let log = Log::open(db_dir.as_ref().join("log")).await?; 103 | let memtable = Memtable::hydrate(&log).await?; 104 | Ok(Db { log, memtable }) 105 | } 106 | ``` 107 | 108 | I need to emphasize again we're skipping some important steps here in the name of expediency (and we're not even going very fast!). 109 | If you're following along to make a database anyone actually intends to use then 110 | you should make sure you actually know what you're doing first and consult a 111 | source that doesn't have a graffiti-style logo. 112 | 113 | This handling of the log-replay is actually not safe because of 114 | the way fsync works (the way we write into the log at *all* is actually not safe, but this problem is different and subtle so I want to talk about it). 115 | 116 | Happy path number one is something like this: 117 | 118 | I do a write, it gets written into the log, fsync'd, then we crash. 119 | The database comes back up, we rehydrate the log, it contains my write, and if I try to read again, I'll see it. This is great. All working as intended. 120 | 121 | Happy path number two is something like this: 122 | 123 | I do a write, before you're able to apply it to the log, we crash. 124 | The database comes back up, and we rehydrate the log, it doesn't contain my 125 | write, and so reads don't see it. This is fine, because the writer never got an 126 | acknowledgment that the write succeeded, and so they shouldn't have taken any action assuming it had, so this is all fine. 127 | 128 | Now here's sad path: 129 | 130 | I do a write, and it goes into the log, and then the database crashes *before we fsync*. 131 | We come back up, and the reader, having not gotten an acknowledgment that their write succeeded, must do a read to see if it did or not. 132 | They do a read, and then the write, having made it to the OS's in-memory buffers, is returned. 133 | Now the reader would be justified in believing that the write is durable: they saw it, after all. 134 | But now we *hard crash*, and the whole server goes down, losing the contents of the file buffers. 135 | Now the write is lost, even though we served it! 136 | 137 | The solution is easy: just fsync the log on startup so that any reads we do are based off of data that has made it to disk: 138 | 139 | ```rust 140 | log.get_ref().sync_all().await?; 141 | ``` 142 | 143 | -------------------------------------------------------------------------------- /src/sstweek/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | error::Error, 4 | fmt::{self, Display, Formatter}, 5 | io::SeekFrom, 6 | path::{Path, PathBuf}, 7 | time::{SystemTime, UNIX_EPOCH}, 8 | }; 9 | 10 | use futures::{stream::FuturesUnordered, Stream, StreamExt}; 11 | use serde::{Deserialize, Serialize}; 12 | use tokio::{ 13 | fs::{File, OpenOptions}, 14 | io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter}, 15 | }; 16 | 17 | #[derive(Debug)] 18 | enum NdbError { 19 | Io(std::io::Error), 20 | Serde(serde_json::Error), 21 | } 22 | 23 | impl Display for NdbError { 24 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 25 | match self { 26 | NdbError::Io(err) => write!(f, "IO error: {}", err), 27 | NdbError::Serde(err) => write!(f, "Serde error: {}", err), 28 | } 29 | } 30 | } 31 | 32 | impl Error for NdbError {} 33 | 34 | impl From for NdbError { 35 | fn from(err: std::io::Error) -> NdbError { 36 | NdbError::Io(err) 37 | } 38 | } 39 | 40 | impl From for NdbError { 41 | fn from(err: serde_json::Error) -> NdbError { 42 | NdbError::Serde(err) 43 | } 44 | } 45 | 46 | #[tokio::main] 47 | async fn main() -> Result<(), NdbError> { 48 | let mut db = Db::new("db").await?; 49 | 50 | db.put("foo".as_bytes(), "bar".as_bytes()).await?; 51 | db.put("baz".as_bytes(), "qux".as_bytes()).await?; 52 | db.put("foo".as_bytes(), "goo".as_bytes()).await?; 53 | 54 | println!( 55 | "{:?}", 56 | String::from_utf8(db.get("foo".as_bytes()).await?.unwrap()).unwrap() 57 | ); 58 | 59 | db.flush_memtable().await?; 60 | 61 | println!( 62 | "{:?}", 63 | String::from_utf8(db.get("foo".as_bytes()).await?.unwrap()).unwrap() 64 | ); 65 | 66 | Ok(()) 67 | } 68 | 69 | #[derive(Serialize, Deserialize)] 70 | struct Put { 71 | key: Vec, 72 | value: Vec, 73 | } 74 | 75 | trait Queryable { 76 | async fn get(&self, key: &[u8]) -> Result>, NdbError>; 77 | } 78 | 79 | #[derive(Serialize, Deserialize)] 80 | struct SSTableMetadata { 81 | written_timestamp: u64, 82 | meta_path: PathBuf, 83 | data_path: PathBuf, 84 | index_path: PathBuf, 85 | } 86 | 87 | struct SSTable { 88 | meta: SSTableMetadata, 89 | data_file: File, 90 | index_file: File, 91 | index: Vec<(Vec, u64)>, 92 | } 93 | 94 | impl SSTable { 95 | async fn open(path: impl AsRef) -> Result { 96 | let meta_path = path.as_ref().with_extension("meta"); 97 | let mut meta_file = File::open(&meta_path).await?; 98 | let mut contents = String::new(); 99 | meta_file.read_to_string(&mut contents).await?; 100 | let meta: SSTableMetadata = serde_json::from_str(&contents)?; 101 | 102 | let data_file = File::open(&meta.data_path).await?; 103 | let mut index_file = File::open(&meta.index_path).await?; 104 | let mut index_contents = String::new(); 105 | index_file.read_to_string(&mut index_contents).await?; 106 | let index: Vec<(Vec, u64)> = serde_json::from_str(&index_contents)?; 107 | 108 | Ok(SSTable { 109 | meta, 110 | data_file, 111 | index_file, 112 | index, 113 | }) 114 | } 115 | } 116 | 117 | impl Queryable for SSTable { 118 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 119 | // TODO: fix this, when you read from something too small 120 | let loc = match self.index.binary_search_by(|(k, _)| k.as_slice().cmp(key)) { 121 | Ok(i) => i, 122 | Err(i) => i, 123 | } - 1; 124 | 125 | let mut location = self.index[loc].1; 126 | 127 | let mut data_file = BufReader::new(self.data_file.try_clone().await?); 128 | 129 | data_file.seek(SeekFrom::Start(location)).await?; 130 | 131 | let file_len = self.data_file.metadata().await?.len(); 132 | while location < file_len { 133 | let key_len = data_file.read_u32().await?; 134 | location += 4; 135 | let mut current_key = vec![0; key_len as usize]; 136 | data_file.read_exact(&mut current_key).await?; 137 | location += key_len as u64; 138 | 139 | let value_len = data_file.read_u32().await?; 140 | location += 4; 141 | let mut value = vec![0; value_len as usize]; 142 | data_file.read_exact(&mut value).await?; 143 | location += value_len as u64; 144 | 145 | println!("at: {:?} {:?}", current_key, value); 146 | println!("seeking: {:?}", key); 147 | 148 | if current_key == key { 149 | return Ok(Some(value)); 150 | } else if current_key.as_slice() > key { 151 | break; 152 | } 153 | } 154 | 155 | Ok(None) 156 | } 157 | } 158 | 159 | impl PartialOrd for SSTable { 160 | fn partial_cmp(&self, other: &Self) -> Option { 161 | Some( 162 | self.meta 163 | .written_timestamp 164 | .cmp(&other.meta.written_timestamp) 165 | .reverse(), 166 | ) 167 | } 168 | } 169 | 170 | impl PartialEq for SSTable { 171 | fn eq(&self, other: &Self) -> bool { 172 | self.meta.written_timestamp == other.meta.written_timestamp 173 | } 174 | } 175 | 176 | impl Eq for SSTable {} 177 | 178 | impl Ord for SSTable { 179 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 180 | self.partial_cmp(other).unwrap() 181 | } 182 | } 183 | 184 | impl SSTable { 185 | // `data` must be ordered by key. 186 | async fn construct( 187 | dir: impl AsRef, 188 | data: impl Iterator, Vec)>, 189 | ) -> Result { 190 | // Get the current unix epoch. 191 | let now = SystemTime::now() 192 | .duration_since(UNIX_EPOCH) 193 | .unwrap() 194 | .as_secs(); 195 | 196 | let data_path = dir.as_ref().join(format!("{}.sst", now)); 197 | let index_path = dir.as_ref().join(format!("{}.idx", now)); 198 | 199 | let mut data_file = BufWriter::new( 200 | OpenOptions::new() 201 | .write(true) 202 | .create(true) 203 | .open(&data_path) 204 | .await?, 205 | ); 206 | 207 | let mut index = Vec::new(); 208 | 209 | for (i, (key, value)) in data.enumerate() { 210 | let offset = data_file.seek(SeekFrom::Current(0)).await?; 211 | data_file.write_u32(key.len() as u32).await?; 212 | data_file.write_all(&key).await?; 213 | data_file.write_u32(value.len() as u32).await?; 214 | data_file.write_all(&value).await?; 215 | if i % 16 == 0 { 216 | index.push((key, offset)); 217 | } 218 | } 219 | 220 | data_file.flush().await?; 221 | data_file.get_ref().sync_all().await?; 222 | 223 | let mut index_file = OpenOptions::new() 224 | .write(true) 225 | .create(true) 226 | .open(&index_path) 227 | .await?; 228 | 229 | index_file 230 | .write_all(serde_json::to_string(&index)?.as_bytes()) 231 | .await?; 232 | 233 | index_file.sync_all().await?; 234 | 235 | let meta_path = dir.as_ref().join(format!("{}.meta", now)); 236 | let meta = SSTableMetadata { 237 | meta_path: meta_path.clone(), 238 | data_path, 239 | index_path, 240 | written_timestamp: now, 241 | }; 242 | let mut meta_file = OpenOptions::new() 243 | .write(true) 244 | .create(true) 245 | .open(&meta_path) 246 | .await?; 247 | meta_file 248 | .write_all(serde_json::to_string(&meta)?.as_bytes()) 249 | .await?; 250 | 251 | Ok(SSTable { 252 | meta, 253 | data_file: data_file.into_inner(), 254 | index_file, 255 | index, 256 | }) 257 | } 258 | } 259 | 260 | #[derive(Default)] 261 | struct Memtable { 262 | data: BTreeMap, Vec>, 263 | } 264 | 265 | impl Queryable for Memtable { 266 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 267 | Ok(self.data.get(key).cloned()) 268 | } 269 | } 270 | 271 | impl Memtable { 272 | fn put(&mut self, key: Vec, value: Vec) { 273 | self.data.insert(key, value); 274 | } 275 | } 276 | 277 | impl Memtable { 278 | async fn hydrate(log: &Log) -> Result { 279 | let mut data = BTreeMap::new(); 280 | let reader = File::open(&log.path).await?; 281 | let reader = BufReader::new(reader); 282 | let mut lines = reader.lines(); 283 | while let Some(line) = lines.next_line().await? { 284 | let put: Put = serde_json::from_str(&line)?; 285 | data.insert(put.key, put.value); 286 | } 287 | 288 | Ok(Memtable { data }) 289 | } 290 | } 291 | 292 | struct Log { 293 | path: PathBuf, 294 | log: BufWriter, 295 | } 296 | 297 | impl Log { 298 | async fn open(path: impl AsRef) -> Result { 299 | let log = BufWriter::new( 300 | OpenOptions::new() 301 | .append(true) 302 | .create(true) 303 | .open(&path) 304 | .await?, 305 | ); 306 | Ok(Log { 307 | path: path.as_ref().to_path_buf(), 308 | log, 309 | }) 310 | } 311 | 312 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 313 | let put = Put { 314 | key: key.into(), 315 | value: value.into(), 316 | }; 317 | 318 | let serialized = serde_json::to_string(&put)?; 319 | self.log.write_all(serialized.as_bytes()).await?; 320 | self.log.write_all(b"\n").await?; 321 | self.log.flush().await?; 322 | self.log.get_ref().sync_all().await?; 323 | 324 | Ok(()) 325 | } 326 | } 327 | 328 | impl Queryable for Log { 329 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 330 | let reader = File::open(&self.path).await?; 331 | let reader = BufReader::new(reader); 332 | let mut lines = reader.lines(); 333 | let mut result = None; 334 | while let Some(line) = lines.next_line().await? { 335 | let put: Put = serde_json::from_str(&line)?; 336 | if put.key == key { 337 | result = Some(put.value); 338 | } 339 | } 340 | 341 | Ok(result) 342 | } 343 | } 344 | 345 | #[derive(Serialize, Deserialize, Clone)] 346 | struct DbMeta { 347 | sstables: Vec, 348 | wal: PathBuf, 349 | } 350 | 351 | struct Db { 352 | dir: PathBuf, 353 | log: Log, 354 | memtable: Memtable, 355 | sstables: Vec, 356 | meta: DbMeta, 357 | } 358 | 359 | impl Db { 360 | async fn new(db_dir: impl AsRef) -> Result { 361 | if !db_dir.as_ref().exists() { 362 | tokio::fs::create_dir_all(&db_dir).await?; 363 | } 364 | let meta_path = db_dir.as_ref().join("meta.json"); 365 | let meta = if meta_path.exists() { 366 | let mut meta_file = File::open(&meta_path).await?; 367 | let mut contents = String::new(); 368 | meta_file.read_to_string(&mut contents).await?; 369 | serde_json::from_str(&contents)? 370 | } else { 371 | let meta = DbMeta { 372 | sstables: Vec::new(), 373 | wal: db_dir.as_ref().join("log"), 374 | }; 375 | let mut meta_file = File::create(&meta_path).await?; 376 | meta_file 377 | .write_all(serde_json::to_string(&meta)?.as_bytes()) 378 | .await?; 379 | meta 380 | }; 381 | 382 | let log = Log::open(db_dir.as_ref().join("log")).await?; 383 | let memtable = Memtable::hydrate(&log).await?; 384 | let sstable_results: Vec<_> = meta 385 | .sstables 386 | .iter() 387 | .map(|path| SSTable::open(path)) 388 | .collect::>() 389 | .collect() 390 | .await; 391 | 392 | let mut sstables = sstable_results.into_iter().collect::, _>>()?; 393 | sstables.sort(); 394 | 395 | Ok(Db { 396 | dir: db_dir.as_ref().into(), 397 | log, 398 | memtable, 399 | sstables, 400 | meta, 401 | }) 402 | } 403 | 404 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 405 | self.log.put(key, value).await?; 406 | self.memtable.data.insert(key.into(), value.into()); 407 | 408 | Ok(()) 409 | } 410 | 411 | async fn get(&mut self, key: &[u8]) -> Result>, NdbError> { 412 | if let Some(value) = self.memtable.get(key).await? { 413 | return Ok(Some(value)); 414 | } 415 | for sstable in &self.sstables { 416 | if let Some(value) = sstable.get(key).await? { 417 | return Ok(Some(value)); 418 | } 419 | } 420 | 421 | Ok(None) 422 | } 423 | 424 | async fn update_meta(&mut self, meta: DbMeta) -> Result<(), NdbError> { 425 | let meta_path = self.dir.join("meta.json"); 426 | let mut meta_file = File::create(&meta_path).await?; 427 | meta_file 428 | .write_all(serde_json::to_string(&meta)?.as_bytes()) 429 | .await?; 430 | self.meta = meta; 431 | Ok(()) 432 | } 433 | 434 | fn get_filename(&self, prefix: &str) -> String { 435 | format!( 436 | "{}-{}", 437 | prefix, 438 | SystemTime::now() 439 | .duration_since(UNIX_EPOCH) 440 | .unwrap() 441 | .as_secs() 442 | ) 443 | } 444 | 445 | async fn flush_memtable(&mut self) -> Result { 446 | let data = std::mem::take(&mut self.memtable); 447 | let sstable = SSTable::construct("db", data.data.into_iter()).await?; 448 | // Start a fresh log. 449 | let log_path = self.dir.join(self.get_filename("log")); 450 | self.log = Log::open(&log_path).await?; 451 | 452 | let mut new_meta = self.meta.clone(); 453 | new_meta 454 | .sstables 455 | .push(sstable.meta.meta_path.to_string_lossy().into_owned()); 456 | new_meta.wal = log_path; 457 | self.update_meta(new_meta).await?; 458 | 459 | self.memtable = Memtable::hydrate(&self.log).await?; 460 | Ok(sstable) 461 | } 462 | } 463 | -------------------------------------------------------------------------------- /src/week1/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{self, Display, Formatter}, 4 | path::{Path, PathBuf}, 5 | }; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | use tokio::{ 9 | fs::{File, OpenOptions}, 10 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, 11 | }; 12 | 13 | #[derive(Debug)] 14 | enum NdbError { 15 | Io(std::io::Error), 16 | Serde(serde_json::Error), 17 | } 18 | 19 | impl Display for NdbError { 20 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 21 | match self { 22 | NdbError::Io(err) => write!(f, "IO error: {}", err), 23 | NdbError::Serde(err) => write!(f, "Serde error: {}", err), 24 | } 25 | } 26 | } 27 | 28 | impl Error for NdbError {} 29 | 30 | impl From for NdbError { 31 | fn from(err: std::io::Error) -> NdbError { 32 | NdbError::Io(err) 33 | } 34 | } 35 | 36 | impl From for NdbError { 37 | fn from(err: serde_json::Error) -> NdbError { 38 | NdbError::Serde(err) 39 | } 40 | } 41 | 42 | #[tokio::main] 43 | async fn main() -> Result<(), NdbError> { 44 | let mut db = Db::new("db").await?; 45 | 46 | db.put("foo".as_bytes(), "bar".as_bytes()).await?; 47 | db.put("baz".as_bytes(), "qux".as_bytes()).await?; 48 | db.put("foo".as_bytes(), "goo".as_bytes()).await?; 49 | 50 | println!( 51 | "{:?}", 52 | String::from_utf8(db.get("foo".as_bytes()).await?.unwrap()).unwrap() 53 | ); 54 | 55 | Ok(()) 56 | } 57 | 58 | #[derive(Serialize, Deserialize)] 59 | struct Put { 60 | key: Vec, 61 | value: Vec, 62 | } 63 | 64 | trait Queryable { 65 | async fn get(&self, key: &[u8]) -> Result>, NdbError>; 66 | } 67 | 68 | struct Log { 69 | path: PathBuf, 70 | log: BufWriter, 71 | } 72 | 73 | impl Log { 74 | async fn open(path: impl AsRef) -> Result { 75 | let log = BufWriter::new( 76 | OpenOptions::new() 77 | .append(true) 78 | .create(true) 79 | .open(&path) 80 | .await?, 81 | ); 82 | Ok(Log { 83 | path: path.as_ref().to_path_buf(), 84 | log, 85 | }) 86 | } 87 | 88 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 89 | let put = Put { 90 | key: key.into(), 91 | value: value.into(), 92 | }; 93 | 94 | let serialized = serde_json::to_string(&put)?; 95 | self.log.write_all(serialized.as_bytes()).await?; 96 | self.log.write_all(b"\n").await?; 97 | self.log.flush().await?; 98 | self.log.get_ref().sync_all().await?; 99 | 100 | Ok(()) 101 | } 102 | } 103 | 104 | impl Queryable for Log { 105 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 106 | let reader = File::open(&self.path).await?; 107 | let reader = BufReader::new(reader); 108 | let mut lines = reader.lines(); 109 | let mut result = None; 110 | while let Some(line) = lines.next_line().await? { 111 | let put: Put = serde_json::from_str(&line)?; 112 | if put.key == key { 113 | result = Some(put.value); 114 | } 115 | } 116 | 117 | Ok(result) 118 | } 119 | } 120 | 121 | struct Db { 122 | log: Log, 123 | } 124 | 125 | impl Db { 126 | async fn new(db_dir: impl AsRef) -> Result { 127 | if !db_dir.as_ref().exists() { 128 | tokio::fs::create_dir_all(&db_dir).await?; 129 | } 130 | let log = Log::open(db_dir.as_ref().join("log")).await?; 131 | Ok(Db { log }) 132 | } 133 | 134 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 135 | self.log.put(key, value).await?; 136 | 137 | Ok(()) 138 | } 139 | 140 | async fn get(&mut self, key: &[u8]) -> Result>, NdbError> { 141 | Ok(self.log.get(key).await?) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/week2/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | error::Error, 4 | fmt::{self, Display, Formatter}, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | use serde::{Deserialize, Serialize}; 9 | use tokio::{ 10 | fs::{File, OpenOptions}, 11 | io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}, 12 | }; 13 | 14 | #[derive(Debug)] 15 | enum NdbError { 16 | Io(std::io::Error), 17 | Serde(serde_json::Error), 18 | } 19 | 20 | impl Display for NdbError { 21 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 22 | match self { 23 | NdbError::Io(err) => write!(f, "IO error: {}", err), 24 | NdbError::Serde(err) => write!(f, "Serde error: {}", err), 25 | } 26 | } 27 | } 28 | 29 | impl Error for NdbError {} 30 | 31 | impl From for NdbError { 32 | fn from(err: std::io::Error) -> NdbError { 33 | NdbError::Io(err) 34 | } 35 | } 36 | 37 | impl From for NdbError { 38 | fn from(err: serde_json::Error) -> NdbError { 39 | NdbError::Serde(err) 40 | } 41 | } 42 | 43 | #[tokio::main] 44 | async fn main() -> Result<(), NdbError> { 45 | let mut db = Db::new("db").await?; 46 | 47 | db.put("foo".as_bytes(), "bar".as_bytes()).await?; 48 | db.put("baz".as_bytes(), "qux".as_bytes()).await?; 49 | db.put("foo".as_bytes(), "goo".as_bytes()).await?; 50 | 51 | println!( 52 | "{:?}", 53 | String::from_utf8(db.get("foo".as_bytes()).await?.unwrap()).unwrap() 54 | ); 55 | 56 | Ok(()) 57 | } 58 | 59 | #[derive(Serialize, Deserialize)] 60 | struct Put { 61 | key: Vec, 62 | value: Vec, 63 | } 64 | 65 | trait Queryable { 66 | async fn get(&self, key: &[u8]) -> Result>, NdbError>; 67 | } 68 | 69 | struct Log { 70 | path: PathBuf, 71 | log: BufWriter, 72 | } 73 | 74 | impl Log { 75 | async fn open(path: impl AsRef) -> Result { 76 | let log = BufWriter::new( 77 | OpenOptions::new() 78 | .append(true) 79 | .create(true) 80 | .open(&path) 81 | .await?, 82 | ); 83 | log.get_ref().sync_all().await?; 84 | Ok(Log { 85 | path: path.as_ref().to_path_buf(), 86 | log, 87 | }) 88 | } 89 | 90 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 91 | let put = Put { 92 | key: key.into(), 93 | value: value.into(), 94 | }; 95 | 96 | let serialized = serde_json::to_string(&put)?; 97 | self.log.write_all(serialized.as_bytes()).await?; 98 | self.log.write_all(b"\n").await?; 99 | self.log.flush().await?; 100 | self.log.get_ref().sync_all().await?; 101 | 102 | Ok(()) 103 | } 104 | } 105 | 106 | impl Queryable for Log { 107 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 108 | let reader = File::open(&self.path).await?; 109 | let reader = BufReader::new(reader); 110 | let mut lines = reader.lines(); 111 | let mut result = None; 112 | while let Some(line) = lines.next_line().await? { 113 | let put: Put = serde_json::from_str(&line)?; 114 | if put.key == key { 115 | result = Some(put.value); 116 | } 117 | } 118 | 119 | Ok(result) 120 | } 121 | } 122 | 123 | struct Db { 124 | log: Log, 125 | memtable: Memtable, 126 | } 127 | 128 | impl Db { 129 | async fn new(db_dir: impl AsRef) -> Result { 130 | if !db_dir.as_ref().exists() { 131 | tokio::fs::create_dir_all(&db_dir).await?; 132 | } 133 | let log = Log::open(db_dir.as_ref().join("log")).await?; 134 | let memtable = Memtable::hydrate(&log).await?; 135 | Ok(Db { log, memtable }) 136 | } 137 | 138 | async fn put(&mut self, key: &[u8], value: &[u8]) -> Result<(), NdbError> { 139 | self.log.put(key, value).await?; 140 | self.memtable.put(key.into(), value.into()); 141 | 142 | Ok(()) 143 | } 144 | 145 | async fn get(&mut self, key: &[u8]) -> Result>, NdbError> { 146 | Ok(self.memtable.get(key).await?) 147 | } 148 | } 149 | 150 | #[derive(Default)] 151 | struct Memtable { 152 | data: BTreeMap, Vec>, 153 | } 154 | 155 | impl Queryable for Memtable { 156 | async fn get(&self, key: &[u8]) -> Result>, NdbError> { 157 | Ok(self.data.get(key).cloned()) 158 | } 159 | } 160 | 161 | impl Memtable { 162 | fn put(&mut self, key: Vec, value: Vec) { 163 | self.data.insert(key, value); 164 | } 165 | 166 | async fn hydrate(log: &Log) -> Result { 167 | let mut data = BTreeMap::new(); 168 | let reader = File::open(&log.path).await?; 169 | let reader = BufReader::new(reader); 170 | let mut lines = reader.lines(); 171 | while let Some(line) = lines.next_line().await? { 172 | let put: Put = serde_json::from_str(&line)?; 173 | data.insert(put.key, put.value); 174 | } 175 | 176 | Ok(Memtable { data }) 177 | } 178 | } 179 | --------------------------------------------------------------------------------