├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── docs └── img.png └── src ├── core ├── btree.rs ├── command.rs ├── mod.rs ├── renderer.rs └── structs.rs ├── db ├── client.rs └── mod.rs ├── main.rs └── templates ├── render_level.hbs ├── render_page.hbs └── render_tree.hbs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | history.* 4 | *.html 5 | *.log 6 | -------------------------------------------------------------------------------- /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 = "anstream" 22 | version = "0.6.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 25 | dependencies = [ 26 | "anstyle", 27 | "anstyle-parse", 28 | "anstyle-query", 29 | "anstyle-wincon", 30 | "colorchoice", 31 | "is_terminal_polyfill", 32 | "utf8parse", 33 | ] 34 | 35 | [[package]] 36 | name = "anstyle" 37 | version = "1.0.7" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 40 | 41 | [[package]] 42 | name = "anstyle-parse" 43 | version = "0.2.4" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 46 | dependencies = [ 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle-query" 52 | version = "1.0.3" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" 55 | dependencies = [ 56 | "windows-sys 0.52.0", 57 | ] 58 | 59 | [[package]] 60 | name = "anstyle-wincon" 61 | version = "3.0.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 64 | dependencies = [ 65 | "anstyle", 66 | "windows-sys 0.52.0", 67 | ] 68 | 69 | [[package]] 70 | name = "async-trait" 71 | version = "0.1.80" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 74 | dependencies = [ 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "autocfg" 82 | version = "1.2.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 85 | 86 | [[package]] 87 | name = "backtrace" 88 | version = "0.3.71" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" 91 | dependencies = [ 92 | "addr2line", 93 | "cc", 94 | "cfg-if", 95 | "libc", 96 | "miniz_oxide", 97 | "object", 98 | "rustc-demangle", 99 | ] 100 | 101 | [[package]] 102 | name = "base64" 103 | version = "0.21.7" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 106 | 107 | [[package]] 108 | name = "bitflags" 109 | version = "1.3.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 112 | 113 | [[package]] 114 | name = "bitflags" 115 | version = "2.5.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 118 | 119 | [[package]] 120 | name = "block-buffer" 121 | version = "0.10.4" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 124 | dependencies = [ 125 | "generic-array", 126 | ] 127 | 128 | [[package]] 129 | name = "bumpalo" 130 | version = "3.16.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 133 | 134 | [[package]] 135 | name = "byteorder" 136 | version = "1.5.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 139 | 140 | [[package]] 141 | name = "bytes" 142 | version = "1.6.0" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 145 | 146 | [[package]] 147 | name = "cc" 148 | version = "1.0.96" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "065a29261d53ba54260972629f9ca6bffa69bac13cd1fed61420f7fa68b9f8bd" 151 | 152 | [[package]] 153 | name = "cfg-if" 154 | version = "1.0.0" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 157 | 158 | [[package]] 159 | name = "clap" 160 | version = "4.5.4" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" 163 | dependencies = [ 164 | "clap_builder", 165 | "clap_derive", 166 | ] 167 | 168 | [[package]] 169 | name = "clap_builder" 170 | version = "4.5.2" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" 173 | dependencies = [ 174 | "anstream", 175 | "anstyle", 176 | "clap_lex", 177 | "strsim", 178 | ] 179 | 180 | [[package]] 181 | name = "clap_derive" 182 | version = "4.5.4" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" 185 | dependencies = [ 186 | "heck", 187 | "proc-macro2", 188 | "quote", 189 | "syn", 190 | ] 191 | 192 | [[package]] 193 | name = "clap_lex" 194 | version = "0.7.0" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 197 | 198 | [[package]] 199 | name = "colorchoice" 200 | version = "1.0.1" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 203 | 204 | [[package]] 205 | name = "cpufeatures" 206 | version = "0.2.12" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 209 | dependencies = [ 210 | "libc", 211 | ] 212 | 213 | [[package]] 214 | name = "crypto-common" 215 | version = "0.1.6" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 218 | dependencies = [ 219 | "generic-array", 220 | "typenum", 221 | ] 222 | 223 | [[package]] 224 | name = "digest" 225 | version = "0.10.7" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 228 | dependencies = [ 229 | "block-buffer", 230 | "crypto-common", 231 | "subtle", 232 | ] 233 | 234 | [[package]] 235 | name = "fallible-iterator" 236 | version = "0.2.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 239 | 240 | [[package]] 241 | name = "finl_unicode" 242 | version = "1.2.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" 245 | 246 | [[package]] 247 | name = "futures-channel" 248 | version = "0.3.30" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 251 | dependencies = [ 252 | "futures-core", 253 | "futures-sink", 254 | ] 255 | 256 | [[package]] 257 | name = "futures-core" 258 | version = "0.3.30" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 261 | 262 | [[package]] 263 | name = "futures-macro" 264 | version = "0.3.30" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 267 | dependencies = [ 268 | "proc-macro2", 269 | "quote", 270 | "syn", 271 | ] 272 | 273 | [[package]] 274 | name = "futures-sink" 275 | version = "0.3.30" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 278 | 279 | [[package]] 280 | name = "futures-task" 281 | version = "0.3.30" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 284 | 285 | [[package]] 286 | name = "futures-util" 287 | version = "0.3.30" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 290 | dependencies = [ 291 | "futures-core", 292 | "futures-macro", 293 | "futures-sink", 294 | "futures-task", 295 | "pin-project-lite", 296 | "pin-utils", 297 | "slab", 298 | ] 299 | 300 | [[package]] 301 | name = "generic-array" 302 | version = "0.14.7" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 305 | dependencies = [ 306 | "typenum", 307 | "version_check", 308 | ] 309 | 310 | [[package]] 311 | name = "getrandom" 312 | version = "0.2.14" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" 315 | dependencies = [ 316 | "cfg-if", 317 | "libc", 318 | "wasi", 319 | ] 320 | 321 | [[package]] 322 | name = "gimli" 323 | version = "0.28.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 326 | 327 | [[package]] 328 | name = "handlebars" 329 | version = "5.1.2" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" 332 | dependencies = [ 333 | "log", 334 | "pest", 335 | "pest_derive", 336 | "serde", 337 | "serde_json", 338 | "thiserror", 339 | ] 340 | 341 | [[package]] 342 | name = "heck" 343 | version = "0.5.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 346 | 347 | [[package]] 348 | name = "hmac" 349 | version = "0.12.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 352 | dependencies = [ 353 | "digest", 354 | ] 355 | 356 | [[package]] 357 | name = "is_terminal_polyfill" 358 | version = "1.70.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 361 | 362 | [[package]] 363 | name = "itoa" 364 | version = "1.0.11" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 367 | 368 | [[package]] 369 | name = "js-sys" 370 | version = "0.3.69" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 373 | dependencies = [ 374 | "wasm-bindgen", 375 | ] 376 | 377 | [[package]] 378 | name = "libc" 379 | version = "0.2.154" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" 382 | 383 | [[package]] 384 | name = "lock_api" 385 | version = "0.4.12" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 388 | dependencies = [ 389 | "autocfg", 390 | "scopeguard", 391 | ] 392 | 393 | [[package]] 394 | name = "log" 395 | version = "0.4.21" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 398 | 399 | [[package]] 400 | name = "md-5" 401 | version = "0.10.6" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" 404 | dependencies = [ 405 | "cfg-if", 406 | "digest", 407 | ] 408 | 409 | [[package]] 410 | name = "memchr" 411 | version = "2.7.2" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 414 | 415 | [[package]] 416 | name = "miniz_oxide" 417 | version = "0.7.2" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" 420 | dependencies = [ 421 | "adler", 422 | ] 423 | 424 | [[package]] 425 | name = "mio" 426 | version = "0.8.11" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 429 | dependencies = [ 430 | "libc", 431 | "wasi", 432 | "windows-sys 0.48.0", 433 | ] 434 | 435 | [[package]] 436 | name = "object" 437 | version = "0.32.2" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" 440 | dependencies = [ 441 | "memchr", 442 | ] 443 | 444 | [[package]] 445 | name = "once_cell" 446 | version = "1.19.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 449 | 450 | [[package]] 451 | name = "parking_lot" 452 | version = "0.12.2" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" 455 | dependencies = [ 456 | "lock_api", 457 | "parking_lot_core", 458 | ] 459 | 460 | [[package]] 461 | name = "parking_lot_core" 462 | version = "0.9.10" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 465 | dependencies = [ 466 | "cfg-if", 467 | "libc", 468 | "redox_syscall 0.5.1", 469 | "smallvec", 470 | "windows-targets 0.52.5", 471 | ] 472 | 473 | [[package]] 474 | name = "percent-encoding" 475 | version = "2.3.1" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 478 | 479 | [[package]] 480 | name = "pest" 481 | version = "2.7.10" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" 484 | dependencies = [ 485 | "memchr", 486 | "thiserror", 487 | "ucd-trie", 488 | ] 489 | 490 | [[package]] 491 | name = "pest_derive" 492 | version = "2.7.10" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" 495 | dependencies = [ 496 | "pest", 497 | "pest_generator", 498 | ] 499 | 500 | [[package]] 501 | name = "pest_generator" 502 | version = "2.7.10" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" 505 | dependencies = [ 506 | "pest", 507 | "pest_meta", 508 | "proc-macro2", 509 | "quote", 510 | "syn", 511 | ] 512 | 513 | [[package]] 514 | name = "pest_meta" 515 | version = "2.7.10" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" 518 | dependencies = [ 519 | "once_cell", 520 | "pest", 521 | "sha2", 522 | ] 523 | 524 | [[package]] 525 | name = "pg_index_inspector" 526 | version = "0.1.0" 527 | dependencies = [ 528 | "bytes", 529 | "clap", 530 | "handlebars", 531 | "log", 532 | "postgres", 533 | "postgres-types", 534 | "serde", 535 | "serde_json", 536 | ] 537 | 538 | [[package]] 539 | name = "phf" 540 | version = "0.11.2" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" 543 | dependencies = [ 544 | "phf_shared", 545 | ] 546 | 547 | [[package]] 548 | name = "phf_shared" 549 | version = "0.11.2" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" 552 | dependencies = [ 553 | "siphasher", 554 | ] 555 | 556 | [[package]] 557 | name = "pin-project-lite" 558 | version = "0.2.14" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 561 | 562 | [[package]] 563 | name = "pin-utils" 564 | version = "0.1.0" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 567 | 568 | [[package]] 569 | name = "postgres" 570 | version = "0.19.7" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "7915b33ed60abc46040cbcaa25ffa1c7ec240668e0477c4f3070786f5916d451" 573 | dependencies = [ 574 | "bytes", 575 | "fallible-iterator", 576 | "futures-util", 577 | "log", 578 | "tokio", 579 | "tokio-postgres", 580 | ] 581 | 582 | [[package]] 583 | name = "postgres-protocol" 584 | version = "0.6.6" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" 587 | dependencies = [ 588 | "base64", 589 | "byteorder", 590 | "bytes", 591 | "fallible-iterator", 592 | "hmac", 593 | "md-5", 594 | "memchr", 595 | "rand", 596 | "sha2", 597 | "stringprep", 598 | ] 599 | 600 | [[package]] 601 | name = "postgres-types" 602 | version = "0.2.6" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" 605 | dependencies = [ 606 | "bytes", 607 | "fallible-iterator", 608 | "postgres-protocol", 609 | ] 610 | 611 | [[package]] 612 | name = "ppv-lite86" 613 | version = "0.2.17" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 616 | 617 | [[package]] 618 | name = "proc-macro2" 619 | version = "1.0.81" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" 622 | dependencies = [ 623 | "unicode-ident", 624 | ] 625 | 626 | [[package]] 627 | name = "quote" 628 | version = "1.0.36" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 631 | dependencies = [ 632 | "proc-macro2", 633 | ] 634 | 635 | [[package]] 636 | name = "rand" 637 | version = "0.8.5" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 640 | dependencies = [ 641 | "libc", 642 | "rand_chacha", 643 | "rand_core", 644 | ] 645 | 646 | [[package]] 647 | name = "rand_chacha" 648 | version = "0.3.1" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 651 | dependencies = [ 652 | "ppv-lite86", 653 | "rand_core", 654 | ] 655 | 656 | [[package]] 657 | name = "rand_core" 658 | version = "0.6.4" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 661 | dependencies = [ 662 | "getrandom", 663 | ] 664 | 665 | [[package]] 666 | name = "redox_syscall" 667 | version = "0.4.1" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 670 | dependencies = [ 671 | "bitflags 1.3.2", 672 | ] 673 | 674 | [[package]] 675 | name = "redox_syscall" 676 | version = "0.5.1" 677 | source = "registry+https://github.com/rust-lang/crates.io-index" 678 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 679 | dependencies = [ 680 | "bitflags 2.5.0", 681 | ] 682 | 683 | [[package]] 684 | name = "rustc-demangle" 685 | version = "0.1.23" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 688 | 689 | [[package]] 690 | name = "ryu" 691 | version = "1.0.18" 692 | source = "registry+https://github.com/rust-lang/crates.io-index" 693 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 694 | 695 | [[package]] 696 | name = "scopeguard" 697 | version = "1.2.0" 698 | source = "registry+https://github.com/rust-lang/crates.io-index" 699 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 700 | 701 | [[package]] 702 | name = "serde" 703 | version = "1.0.201" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" 706 | dependencies = [ 707 | "serde_derive", 708 | ] 709 | 710 | [[package]] 711 | name = "serde_derive" 712 | version = "1.0.201" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" 715 | dependencies = [ 716 | "proc-macro2", 717 | "quote", 718 | "syn", 719 | ] 720 | 721 | [[package]] 722 | name = "serde_json" 723 | version = "1.0.117" 724 | source = "registry+https://github.com/rust-lang/crates.io-index" 725 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 726 | dependencies = [ 727 | "itoa", 728 | "ryu", 729 | "serde", 730 | ] 731 | 732 | [[package]] 733 | name = "sha2" 734 | version = "0.10.8" 735 | source = "registry+https://github.com/rust-lang/crates.io-index" 736 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 737 | dependencies = [ 738 | "cfg-if", 739 | "cpufeatures", 740 | "digest", 741 | ] 742 | 743 | [[package]] 744 | name = "siphasher" 745 | version = "0.3.11" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 748 | 749 | [[package]] 750 | name = "slab" 751 | version = "0.4.9" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 754 | dependencies = [ 755 | "autocfg", 756 | ] 757 | 758 | [[package]] 759 | name = "smallvec" 760 | version = "1.13.2" 761 | source = "registry+https://github.com/rust-lang/crates.io-index" 762 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 763 | 764 | [[package]] 765 | name = "socket2" 766 | version = "0.5.7" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 769 | dependencies = [ 770 | "libc", 771 | "windows-sys 0.52.0", 772 | ] 773 | 774 | [[package]] 775 | name = "stringprep" 776 | version = "0.1.4" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" 779 | dependencies = [ 780 | "finl_unicode", 781 | "unicode-bidi", 782 | "unicode-normalization", 783 | ] 784 | 785 | [[package]] 786 | name = "strsim" 787 | version = "0.11.1" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 790 | 791 | [[package]] 792 | name = "subtle" 793 | version = "2.5.0" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 796 | 797 | [[package]] 798 | name = "syn" 799 | version = "2.0.60" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" 802 | dependencies = [ 803 | "proc-macro2", 804 | "quote", 805 | "unicode-ident", 806 | ] 807 | 808 | [[package]] 809 | name = "thiserror" 810 | version = "1.0.60" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" 813 | dependencies = [ 814 | "thiserror-impl", 815 | ] 816 | 817 | [[package]] 818 | name = "thiserror-impl" 819 | version = "1.0.60" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" 822 | dependencies = [ 823 | "proc-macro2", 824 | "quote", 825 | "syn", 826 | ] 827 | 828 | [[package]] 829 | name = "tinyvec" 830 | version = "1.6.0" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 833 | dependencies = [ 834 | "tinyvec_macros", 835 | ] 836 | 837 | [[package]] 838 | name = "tinyvec_macros" 839 | version = "0.1.1" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 842 | 843 | [[package]] 844 | name = "tokio" 845 | version = "1.37.0" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" 848 | dependencies = [ 849 | "backtrace", 850 | "bytes", 851 | "libc", 852 | "mio", 853 | "pin-project-lite", 854 | "socket2", 855 | "windows-sys 0.48.0", 856 | ] 857 | 858 | [[package]] 859 | name = "tokio-postgres" 860 | version = "0.7.10" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" 863 | dependencies = [ 864 | "async-trait", 865 | "byteorder", 866 | "bytes", 867 | "fallible-iterator", 868 | "futures-channel", 869 | "futures-util", 870 | "log", 871 | "parking_lot", 872 | "percent-encoding", 873 | "phf", 874 | "pin-project-lite", 875 | "postgres-protocol", 876 | "postgres-types", 877 | "rand", 878 | "socket2", 879 | "tokio", 880 | "tokio-util", 881 | "whoami", 882 | ] 883 | 884 | [[package]] 885 | name = "tokio-util" 886 | version = "0.7.10" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" 889 | dependencies = [ 890 | "bytes", 891 | "futures-core", 892 | "futures-sink", 893 | "pin-project-lite", 894 | "tokio", 895 | "tracing", 896 | ] 897 | 898 | [[package]] 899 | name = "tracing" 900 | version = "0.1.40" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 903 | dependencies = [ 904 | "pin-project-lite", 905 | "tracing-core", 906 | ] 907 | 908 | [[package]] 909 | name = "tracing-core" 910 | version = "0.1.32" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 913 | dependencies = [ 914 | "once_cell", 915 | ] 916 | 917 | [[package]] 918 | name = "typenum" 919 | version = "1.17.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 922 | 923 | [[package]] 924 | name = "ucd-trie" 925 | version = "0.1.6" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" 928 | 929 | [[package]] 930 | name = "unicode-bidi" 931 | version = "0.3.15" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 934 | 935 | [[package]] 936 | name = "unicode-ident" 937 | version = "1.0.12" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 940 | 941 | [[package]] 942 | name = "unicode-normalization" 943 | version = "0.1.23" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 946 | dependencies = [ 947 | "tinyvec", 948 | ] 949 | 950 | [[package]] 951 | name = "utf8parse" 952 | version = "0.2.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 955 | 956 | [[package]] 957 | name = "version_check" 958 | version = "0.9.4" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 961 | 962 | [[package]] 963 | name = "wasi" 964 | version = "0.11.0+wasi-snapshot-preview1" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 967 | 968 | [[package]] 969 | name = "wasite" 970 | version = "0.1.0" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" 973 | 974 | [[package]] 975 | name = "wasm-bindgen" 976 | version = "0.2.92" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 979 | dependencies = [ 980 | "cfg-if", 981 | "wasm-bindgen-macro", 982 | ] 983 | 984 | [[package]] 985 | name = "wasm-bindgen-backend" 986 | version = "0.2.92" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 989 | dependencies = [ 990 | "bumpalo", 991 | "log", 992 | "once_cell", 993 | "proc-macro2", 994 | "quote", 995 | "syn", 996 | "wasm-bindgen-shared", 997 | ] 998 | 999 | [[package]] 1000 | name = "wasm-bindgen-macro" 1001 | version = "0.2.92" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 1004 | dependencies = [ 1005 | "quote", 1006 | "wasm-bindgen-macro-support", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "wasm-bindgen-macro-support" 1011 | version = "0.2.92" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 1014 | dependencies = [ 1015 | "proc-macro2", 1016 | "quote", 1017 | "syn", 1018 | "wasm-bindgen-backend", 1019 | "wasm-bindgen-shared", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "wasm-bindgen-shared" 1024 | version = "0.2.92" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 1027 | 1028 | [[package]] 1029 | name = "web-sys" 1030 | version = "0.3.69" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 1033 | dependencies = [ 1034 | "js-sys", 1035 | "wasm-bindgen", 1036 | ] 1037 | 1038 | [[package]] 1039 | name = "whoami" 1040 | version = "1.5.1" 1041 | source = "registry+https://github.com/rust-lang/crates.io-index" 1042 | checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" 1043 | dependencies = [ 1044 | "redox_syscall 0.4.1", 1045 | "wasite", 1046 | "web-sys", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "windows-sys" 1051 | version = "0.48.0" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1054 | dependencies = [ 1055 | "windows-targets 0.48.5", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "windows-sys" 1060 | version = "0.52.0" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1063 | dependencies = [ 1064 | "windows-targets 0.52.5", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "windows-targets" 1069 | version = "0.48.5" 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" 1071 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1072 | dependencies = [ 1073 | "windows_aarch64_gnullvm 0.48.5", 1074 | "windows_aarch64_msvc 0.48.5", 1075 | "windows_i686_gnu 0.48.5", 1076 | "windows_i686_msvc 0.48.5", 1077 | "windows_x86_64_gnu 0.48.5", 1078 | "windows_x86_64_gnullvm 0.48.5", 1079 | "windows_x86_64_msvc 0.48.5", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "windows-targets" 1084 | version = "0.52.5" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 1087 | dependencies = [ 1088 | "windows_aarch64_gnullvm 0.52.5", 1089 | "windows_aarch64_msvc 0.52.5", 1090 | "windows_i686_gnu 0.52.5", 1091 | "windows_i686_gnullvm", 1092 | "windows_i686_msvc 0.52.5", 1093 | "windows_x86_64_gnu 0.52.5", 1094 | "windows_x86_64_gnullvm 0.52.5", 1095 | "windows_x86_64_msvc 0.52.5", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "windows_aarch64_gnullvm" 1100 | version = "0.48.5" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1103 | 1104 | [[package]] 1105 | name = "windows_aarch64_gnullvm" 1106 | version = "0.52.5" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 1109 | 1110 | [[package]] 1111 | name = "windows_aarch64_msvc" 1112 | version = "0.48.5" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1115 | 1116 | [[package]] 1117 | name = "windows_aarch64_msvc" 1118 | version = "0.52.5" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 1121 | 1122 | [[package]] 1123 | name = "windows_i686_gnu" 1124 | version = "0.48.5" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1127 | 1128 | [[package]] 1129 | name = "windows_i686_gnu" 1130 | version = "0.52.5" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 1133 | 1134 | [[package]] 1135 | name = "windows_i686_gnullvm" 1136 | version = "0.52.5" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 1139 | 1140 | [[package]] 1141 | name = "windows_i686_msvc" 1142 | version = "0.48.5" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1145 | 1146 | [[package]] 1147 | name = "windows_i686_msvc" 1148 | version = "0.52.5" 1149 | source = "registry+https://github.com/rust-lang/crates.io-index" 1150 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 1151 | 1152 | [[package]] 1153 | name = "windows_x86_64_gnu" 1154 | version = "0.48.5" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1157 | 1158 | [[package]] 1159 | name = "windows_x86_64_gnu" 1160 | version = "0.52.5" 1161 | source = "registry+https://github.com/rust-lang/crates.io-index" 1162 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 1163 | 1164 | [[package]] 1165 | name = "windows_x86_64_gnullvm" 1166 | version = "0.48.5" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1169 | 1170 | [[package]] 1171 | name = "windows_x86_64_gnullvm" 1172 | version = "0.52.5" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 1175 | 1176 | [[package]] 1177 | name = "windows_x86_64_msvc" 1178 | version = "0.48.5" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1181 | 1182 | [[package]] 1183 | name = "windows_x86_64_msvc" 1184 | version = "0.52.5" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 1187 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pg_index_inspector" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Uddeshya Singh "] 6 | rust-version = "1.71.0" 7 | description = "Postgres index inspection tool" 8 | repository = "https://github.com/uds5501/postgres-page-inspector" 9 | keywords = ["postgres", "indexes", "btrees", "databases", "distributed-system"] 10 | categories = ["database"] 11 | license = "MIT" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | postgres = "0.19.7" 17 | postgres-types = "0.2.6" 18 | bytes = "1.6.0" 19 | serde = { version = "1.0.201", features = ["derive"] } 20 | handlebars = "5.1.2" 21 | serde_json = "1.0.117" 22 | clap = { version = "4.5.4", features = ["derive"] } 23 | log = "0.4.21" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Postgres Index Inspector 2 | 3 | This Rust tool is a port of the python tool - [`pageinspect_inspector`](https://github.com/louiseGrandjonc/pageinspect_inspector) tool by [Louise Grandjonc](https://www.louisemeta.com/). 4 | It allows you to inspect and analyze the internal structure of PostgreSQL indexes, highlighting page layout and tuples and in my opinion, it's a pretty cool project to work on :D 5 | 6 | ## Introduction 7 | 8 | This tool currently only supports btree indexes. (Support GIN indexes planned later on). It leverages `handlebars` for visualization of the trees with minor tweaks from original 9 | python project. 10 | 11 | Sample output of the tool: 12 | ![img.png](docs/img.png) 13 | 14 | ## How to Use 15 | 1. Install [`pageinspect`](https://www.postgresql.org/docs/10/static/pageinspect.html) extension in your database. 16 | 2. Clone the repository and run ` cargo run --release -- -o -i -u ` 17 | 3. You can run `cargo run --release -- --help` for more flags. 18 | 19 | ## Caveats 20 | 1. The tool currently only supports btree indexes. 21 | 2. This tool's UI could use some bug fixes. (PRs are welcome) 22 | 3. With large indexes, the tool might take a while to generate the output (imagine ~1 min), and dynamic rendering of lines might break the UI. 23 | -------------------------------------------------------------------------------- /docs/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uds5501/postgres-page-inspector/c6dd1b011a453c20873a0f545a8a7f2537d17ee5/docs/img.png -------------------------------------------------------------------------------- /src/core/btree.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::sync::Arc; 4 | use postgres::Client; 5 | use crate::db::{get_metadata_page, get_page, IndexInfo}; 6 | pub use crate::core::structs::Tree; 7 | 8 | pub fn generate_btree(client: Arc>, index_name: String, index_info: Rc) -> Tree { 9 | let metadata_page = get_metadata_page(Arc::clone(&client), index_name.clone()); 10 | println!("Metadata page: {:?}", metadata_page); 11 | if metadata_page.root == 0 { 12 | panic!("Root page is not set"); 13 | } 14 | let root = get_page(client.clone(), metadata_page.root, index_name.clone(), index_info.clone()); 15 | Tree::new(metadata_page, root, index_name, index_info) 16 | } 17 | -------------------------------------------------------------------------------- /src/core/command.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::path::Path; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | use crate::core::btree::generate_btree; 6 | use crate::core::render; 7 | use crate::db; 8 | use clap::Parser; 9 | use log::{error, info}; 10 | 11 | /// Postgres CLI args 12 | #[derive(Parser, Debug)] 13 | #[command(version, about, long_about = None)] 14 | struct Args { 15 | /// Postgres host 16 | #[arg(long, default_value = "localhost")] 17 | host: String, 18 | 19 | /// Postgres port 20 | #[arg(short, long, default_value = "5432")] 21 | port: String, 22 | 23 | /// Postgres user 24 | #[arg(short, long, default_value = "postgres")] 25 | user: String, 26 | 27 | /// Postgres database 28 | #[arg(short, long, default_value = "postgres")] 29 | db: String, 30 | 31 | /// Postgres password 32 | #[arg(short = 'x', long, default_value = "")] 33 | password: String, 34 | 35 | /// Postgres index 36 | #[arg(short, long)] 37 | index: String, 38 | 39 | /// Output file path 40 | #[arg(short, long, default_value = "output.html")] 41 | output: String, 42 | } 43 | 44 | pub fn handle_command_call() { 45 | let args = Args::parse(); 46 | 47 | // Connect to the database 48 | let client_ref = Arc::new(db::init_client(args.host, args.port, args.db, 49 | args.user, args.password)); 50 | let index_information = db::get_index_info(Arc::clone(&client_ref), args.index.clone()); 51 | if index_information.index_type == "btree" { 52 | let output_path = Path::new(args.output.as_str()); 53 | let tree = generate_btree(Arc::clone(&client_ref), args.index, Rc::new(index_information)); 54 | render(tree, output_path); 55 | info!("Output file generated at: {}", args.output); 56 | } else { 57 | error!("Index type is not btree"); 58 | } 59 | } -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | pub(crate) mod btree; 3 | pub(crate) mod structs; 4 | pub(crate) mod renderer; 5 | 6 | pub use command::handle_command_call; 7 | pub use structs::{MetadataPage, Page, Tid}; 8 | pub use btree::Tree; 9 | pub use renderer::render; 10 | 11 | -------------------------------------------------------------------------------- /src/core/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crate::core::{Page, Tid, Tree}; 3 | use std::path::{Path}; 4 | use std::env; 5 | use std::fs::{File}; 6 | use std::io::Write; 7 | use handlebars::*; 8 | use handlebars::Handlebars; 9 | use serde_json::json; 10 | 11 | struct IsArrayHelper; 12 | 13 | struct TidRenderHelper; 14 | 15 | struct HasChildrenHelper; 16 | 17 | struct SampleLookupHelper; 18 | 19 | impl HelperDef for SampleLookupHelper { 20 | fn call_inner<'reg: 'rc, 'rc>( 21 | &self, 22 | h: &Helper<'rc>, 23 | _: &'reg Handlebars<'reg>, 24 | _: &'rc Context, 25 | _: &mut RenderContext<'reg, 'rc>, 26 | ) -> Result, RenderError> { 27 | let map = h.param(0).unwrap(); 28 | let key = h.param(1).unwrap(); 29 | let mp: HashMap> = serde_json::from_value(map.value().clone()).unwrap(); 30 | let page: Page = serde_json::from_value(key.value().clone()).unwrap(); 31 | let k = format!("{}", page.id); 32 | if page.is_leaf { 33 | return Ok(json!([]).into()); 34 | } 35 | let val = match mp.get(&k) { 36 | Some(val) => val, 37 | None => { 38 | return Ok(json!([]).into()); 39 | } 40 | }; 41 | let val_json = json!(val); 42 | Ok(val_json.clone().into()) 43 | } 44 | } 45 | 46 | impl HelperDef for HasChildrenHelper { 47 | fn call<'reg: 'rc, 'rc>( 48 | &self, 49 | h: &Helper, 50 | _: &Handlebars, 51 | _: &Context, 52 | _: &mut RenderContext, 53 | out: &mut dyn Output, 54 | ) -> HelperResult { 55 | let param = h.param(0).unwrap(); 56 | if param.is_value_missing() { 57 | // TODO: recheck why is this happening? 58 | out.write("false").unwrap(); 59 | return Ok(()); 60 | } 61 | let pages: Vec = serde_json::from_value(param.value().clone()).unwrap(); 62 | if pages.len() == 0 { 63 | out.write("false").unwrap(); 64 | return Ok(()); 65 | } 66 | if pages[0].is_leaf { 67 | out.write("false").unwrap(); 68 | } else { 69 | out.write("true").unwrap(); 70 | } 71 | Ok(()) 72 | } 73 | } 74 | 75 | impl HelperDef for IsArrayHelper { 76 | fn call<'reg: 'rc, 'rc>( 77 | &self, 78 | h: &Helper, 79 | _: &Handlebars, 80 | _: &Context, 81 | _: &mut RenderContext, 82 | out: &mut dyn Output, 83 | ) -> HelperResult { 84 | let param = h.param(0).unwrap(); 85 | 86 | // Check if the parameter is an array 87 | let is_array = match param.value() { 88 | JsonValue::Array(_) => true, 89 | _ => false, 90 | }; 91 | 92 | if is_array { 93 | out.write("true").unwrap() 94 | } else { 95 | out.write("false").unwrap() 96 | } 97 | 98 | Ok(()) 99 | } 100 | } 101 | 102 | impl HelperDef for TidRenderHelper { 103 | fn call<'reg: 'rc, 'rc>( 104 | &self, 105 | h: &Helper, 106 | _: &Handlebars, 107 | _: &Context, 108 | _: &mut RenderContext, 109 | out: &mut dyn Output, 110 | ) -> HelperResult { 111 | let param = h.param(0).unwrap(); 112 | let tid: Tid = serde_json::from_value(param.value().clone()).unwrap(); 113 | let val = format!("({}, {})", tid.block_number, tid.offset_number); 114 | out.write(val.as_str()).unwrap(); 115 | Ok(()) 116 | } 117 | } 118 | 119 | // Implement your IsString helper 120 | struct IsStringHelper; 121 | 122 | impl HelperDef for IsStringHelper { 123 | fn call<'reg: 'rc, 'rc>( 124 | &self, 125 | h: &Helper, 126 | _: &Handlebars, 127 | _: &Context, 128 | _: &mut RenderContext, 129 | out: &mut dyn Output, 130 | ) -> HelperResult { 131 | let param = h.param(0).unwrap(); 132 | 133 | // Check if the parameter is a string 134 | let is_string = match param.value() { 135 | JsonValue::String(_) => true, 136 | _ => false, 137 | }; 138 | 139 | if is_string { 140 | out.write("true").unwrap(); 141 | } else { 142 | out.write("false").unwrap(); 143 | } 144 | 145 | Ok(()) 146 | } 147 | } 148 | 149 | fn get_parent_child_mapping(pages: Vec) -> HashMap> { 150 | let mut parent_child_map: HashMap> = HashMap::new(); 151 | for page in pages { 152 | let mut child_pages: Vec = vec![]; 153 | if page.is_leaf { 154 | // if leaf page, don't add. 155 | return parent_child_map; 156 | } 157 | for item in page.items.clone() { 158 | if let Some(child) = item.child { 159 | let mut child_hash_map = get_parent_child_mapping(vec![*child.clone()]); 160 | for (page, child) in child_hash_map { 161 | if parent_child_map.contains_key(&page) { 162 | let mut existing_child = parent_child_map.get_mut(&page).unwrap(); 163 | existing_child.append(&mut child.clone()); 164 | } else { 165 | parent_child_map.insert(page, child.clone()); 166 | } 167 | } 168 | child_pages.push(*child); 169 | } 170 | } 171 | parent_child_map.insert(format!("{}", page.id), child_pages); 172 | } 173 | parent_child_map 174 | } 175 | 176 | pub fn render(tree: Tree, output_path: &Path) { 177 | let mut templates_dir = env::current_dir().unwrap(); 178 | templates_dir = templates_dir.join("src").join("templates"); 179 | let mut handlebars = Handlebars::new(); 180 | 181 | 182 | let render_tree_file = templates_dir.join("render_tree.hbs"); 183 | let render_page_file = templates_dir.join("render_page.hbs"); 184 | let render_level_file = templates_dir.join("render_level.hbs"); 185 | handlebars.register_template_file("render_tree", render_tree_file).unwrap(); 186 | handlebars.register_template_file("render_page", render_page_file).unwrap(); 187 | handlebars.register_template_file("render_level", render_level_file).unwrap(); 188 | handlebars.register_helper("isArray", Box::new(IsArrayHelper)); 189 | handlebars.register_helper("isString", Box::new(IsStringHelper)); 190 | handlebars.register_helper("renderTid", Box::new(TidRenderHelper)); 191 | handlebars.register_helper("hasChildren", Box::new(HasChildrenHelper)); 192 | handlebars.register_helper("sample-lookup", Box::new(SampleLookupHelper)); 193 | 194 | let mut map = serde_json::Map::new(); 195 | map.insert("tree".to_string(), serde_json::to_value(&tree).unwrap()); 196 | map.insert("index_type".to_string(), serde_json::to_value(&tree.index_type.unwrap()).unwrap()); 197 | map.insert("parent_child_map".to_string(), serde_json::to_value(&get_parent_child_mapping(vec![tree.root.clone()])).unwrap()); 198 | let rendered = handlebars.render("render_tree", &map).unwrap(); 199 | let mut file = File::create(output_path).expect("Unable to create file"); 200 | file.write_all(rendered.as_bytes()).expect("Unable to write data to file"); 201 | } 202 | 203 | #[cfg(test)] 204 | mod tests { 205 | use serde_json::json; 206 | use crate::core::{Page, Tid}; 207 | use crate::core::structs::Item; 208 | 209 | #[test] 210 | pub fn test_string_to_page_conversion() { 211 | let original_page = Page { 212 | id: 0, 213 | level: 0, 214 | is_leaf: false, 215 | is_root: false, 216 | items: vec![Item { 217 | value: "abc".to_string(), 218 | child: None, 219 | pointer: None, 220 | obj_id: Some(Tid { 221 | block_number: 1, 222 | offset_number: 2, 223 | }), 224 | }], 225 | prev_page_id: Some(1), 226 | next_page_id: Some(1), 227 | high_key: None, 228 | prev_item: None, 229 | nb_items: None, 230 | }; 231 | let v = vec![original_page]; 232 | let json_str = json!(v).to_string(); 233 | println!("{:?}", json_str); 234 | let page: Vec = serde_json::from_str(json_str.as_str()).unwrap(); 235 | println!("{:?}", page); 236 | } 237 | 238 | #[test] 239 | pub fn test_get_parent_child_mapping() { 240 | let leaf_a = Page { 241 | id: 0, 242 | level: 0, 243 | is_leaf: true, 244 | is_root: false, 245 | items: vec![], 246 | prev_page_id: None, 247 | next_page_id: None, 248 | high_key: None, 249 | prev_item: None, 250 | nb_items: None, 251 | }; 252 | let leaf_b = Page { 253 | id: 1, 254 | level: 0, 255 | is_leaf: true, 256 | is_root: false, 257 | items: vec![], 258 | prev_page_id: None, 259 | next_page_id: None, 260 | high_key: None, 261 | prev_item: None, 262 | nb_items: None, 263 | }; 264 | let leaf_c = Page { 265 | id: 2, 266 | level: 0, 267 | is_leaf: true, 268 | is_root: false, 269 | items: vec![], 270 | prev_page_id: None, 271 | next_page_id: None, 272 | high_key: None, 273 | prev_item: None, 274 | nb_items: None, 275 | }; 276 | let par_ab = Page { 277 | id: 3, 278 | level: 1, 279 | is_leaf: false, 280 | is_root: false, 281 | items: vec![Item { 282 | value: "abc".to_string(), 283 | child: Some(Box::new(leaf_a.clone())), 284 | pointer: None, 285 | obj_id: None, 286 | }, Item { 287 | value: "def".to_string(), 288 | child: Some(Box::new(leaf_b.clone())), 289 | pointer: None, 290 | obj_id: None, 291 | }], 292 | prev_page_id: None, 293 | next_page_id: None, 294 | high_key: None, 295 | prev_item: None, 296 | nb_items: None, 297 | }; 298 | let par_c = Page { 299 | id: 4, 300 | level: 1, 301 | is_leaf: false, 302 | is_root: false, 303 | items: vec![Item { 304 | value: "ghi".to_string(), 305 | child: Some(Box::new(leaf_c.clone())), 306 | pointer: None, 307 | obj_id: None, 308 | }], 309 | prev_page_id: None, 310 | next_page_id: None, 311 | high_key: None, 312 | prev_item: None, 313 | nb_items: None, 314 | }; 315 | let root = Page { 316 | id: 5, 317 | level: 2, 318 | is_leaf: false, 319 | is_root: true, 320 | items: vec![Item { 321 | value: "jkl".to_string(), 322 | child: Some(Box::new(par_ab.clone())), 323 | pointer: None, 324 | obj_id: None, 325 | }, Item { 326 | value: "mno".to_string(), 327 | child: Some(Box::new(par_c.clone())), 328 | pointer: None, 329 | obj_id: None, 330 | }], 331 | prev_page_id: None, 332 | next_page_id: None, 333 | high_key: None, 334 | prev_item: None, 335 | nb_items: None, 336 | }; 337 | let mut expected_map = std::collections::HashMap::new(); 338 | expected_map.insert("5".to_string(), vec![par_ab, par_c]); 339 | expected_map.insert("3".to_string(), vec![leaf_a, leaf_b]); 340 | expected_map.insert("4".to_string(), vec![leaf_c]); 341 | let parent_child_map = super::get_parent_child_mapping(vec![root]); 342 | assert_eq!(parent_child_map, expected_map); 343 | } 344 | } -------------------------------------------------------------------------------- /src/core/structs.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt::{Display, Formatter}; 3 | use std::rc::Rc; 4 | use bytes::{Buf, BufMut, BytesMut}; 5 | use postgres_types::{FromSql, IsNull, to_sql_checked, ToSql, Type}; 6 | use crate::db::IndexInfo; 7 | use serde::{Serialize, Deserialize}; 8 | 9 | 10 | #[derive(PartialEq, Debug, Serialize, Deserialize)] 11 | pub struct MetadataPage { 12 | pub version: i32, 13 | pub root: i64, 14 | pub level: i64, 15 | pub fast_root: i64, 16 | pub fast_level: i64, 17 | } 18 | 19 | impl MetadataPage { 20 | pub fn new(version: i32, root: i64, level: i64, fast_root: i64, fast_level: i64) -> MetadataPage { 21 | MetadataPage { 22 | version, 23 | root, 24 | level, 25 | fast_root, 26 | fast_level, 27 | } 28 | } 29 | } 30 | 31 | 32 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 33 | pub struct Page { 34 | pub id: i64, 35 | pub level: i64, 36 | pub is_leaf: bool, 37 | pub is_root: bool, 38 | pub items: Vec, 39 | pub prev_page_id: Option, 40 | pub next_page_id: Option, 41 | pub high_key: Option, 42 | pub prev_item: Option>, 43 | pub nb_items: Option, 44 | } 45 | 46 | impl Page { 47 | pub fn new(block_number: i64, level: i64, is_leaf: bool, is_root: bool, next_page_id: i64, prev_page_id: i64) -> Self { 48 | Self { 49 | id: block_number, 50 | level, 51 | is_leaf, 52 | is_root, 53 | items: vec![], 54 | prev_page_id: Some(prev_page_id), 55 | next_page_id: Some(next_page_id), 56 | high_key: None, 57 | prev_item: None, 58 | nb_items: None, 59 | } 60 | } 61 | } 62 | 63 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 64 | pub struct Item { 65 | pub value: String, 66 | pub child: Option>, 67 | pub pointer: Option, 68 | pub obj_id: Option, 69 | } 70 | 71 | impl Item { 72 | pub fn new(value: String, child: Option>, pointer: Option, obj_id: Option) -> Self { 73 | Self { 74 | value, 75 | child, 76 | pointer, 77 | obj_id, 78 | } 79 | } 80 | } 81 | 82 | #[derive(Debug, Serialize, Deserialize)] 83 | pub struct Tree { 84 | metadata_page: Option, 85 | pub root: Page, 86 | index_name: String, 87 | table_name: String, 88 | columns: Vec, 89 | pub index_type: Option, 90 | } 91 | 92 | impl Tree { 93 | pub fn new(metadata_page: MetadataPage, root: Page, index_name: String, index_info: Rc) -> Self { 94 | Self { 95 | metadata_page: Some(metadata_page), 96 | root, 97 | index_name, 98 | table_name: index_info.table_name.clone(), 99 | columns: index_info.columns.clone(), 100 | index_type: Some("btree".to_string()), 101 | } 102 | } 103 | } 104 | 105 | #[derive(Debug)] 106 | pub struct RowData { 107 | pub primary_key_data: Option>, 108 | pub column_data: Option>, 109 | pub byte_values: Option, 110 | } 111 | 112 | impl RowData { 113 | pub fn new(primary_key_data: Vec, column_data: Vec) -> Self { 114 | Self { 115 | primary_key_data: Some(primary_key_data), 116 | column_data: Some(column_data), 117 | byte_values: None, 118 | } 119 | } 120 | pub fn new_bytes(byte_values: String) -> Self { 121 | Self { 122 | primary_key_data: None, 123 | column_data: None, 124 | byte_values: Some(byte_values), 125 | } 126 | } 127 | } 128 | 129 | 130 | #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] 131 | pub struct Tid { 132 | pub block_number: u32, 133 | pub offset_number: u16, 134 | } 135 | 136 | impl Display for Tid { 137 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 138 | write!(f, "({},{})", self.block_number, self.offset_number) 139 | } 140 | } 141 | 142 | 143 | impl ToSql for Tid { 144 | fn to_sql(&self, _: &Type, w: &mut BytesMut) -> Result> where Self: Sized { 145 | let mut tid_str = format!("({},{})", self.block_number, self.offset_number); 146 | 147 | w.reserve(tid_str.len()); 148 | w.put_slice(tid_str.as_bytes()); 149 | 150 | Ok(IsNull::No) 151 | } 152 | 153 | fn accepts(ty: &Type) -> bool where Self: Sized { 154 | matches!(*ty,Type::TID) 155 | } 156 | 157 | to_sql_checked!(); 158 | } 159 | 160 | fn tid_from_sql(mut buf: &[u8]) -> Result> { 161 | Ok(Tid { 162 | block_number: buf.get_u32(), 163 | offset_number: buf.get_u16(), 164 | }) 165 | } 166 | 167 | impl<'a> FromSql<'a> for Tid { 168 | fn from_sql(_: &Type, raw: &'a [u8]) -> Result> { 169 | tid_from_sql(raw) 170 | } 171 | 172 | fn accepts(ty: &Type) -> bool { 173 | matches!(*ty,Type::TID) 174 | } 175 | } -------------------------------------------------------------------------------- /src/db/client.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | use std::sync::Arc; 5 | use log::{debug, info}; 6 | use postgres; 7 | use postgres::{Client, Row}; 8 | use crate::core::structs::{Item, MetadataPage, RowData, Tid}; 9 | use crate::core::Page; 10 | 11 | pub fn init_client(host: String, port: String, db: String, user: String, pass: String) -> RefCell { 12 | let mut connection_string = format!("host={} port={} dbname={}", host, port, db); 13 | if user != "" { 14 | connection_string.push_str(&format!(" user={}", user)); 15 | } 16 | if pass != "" { 17 | connection_string.push_str(&format!(" password={}", pass)); 18 | } 19 | 20 | RefCell::new(Client::connect(connection_string.as_str(), postgres::NoTls).unwrap()) 21 | } 22 | 23 | pub fn get(client: Arc>, query: String) -> Vec { 24 | client.borrow_mut().query(&query, &[]).unwrap() 25 | } 26 | 27 | #[derive(PartialEq, Debug)] 28 | pub struct IndexInfo { 29 | pub index_type: String, 30 | pub columns: Vec, 31 | pub table_name: String, 32 | pub table_oid: postgres::types::Oid, 33 | pub primary_indexed_attributes: Vec, 34 | } 35 | 36 | pub fn get_index_info(client: Arc>, index: String) -> IndexInfo { 37 | let mut index_info = IndexInfo { 38 | index_type: "".to_string(), 39 | columns: vec![], 40 | table_name: "".to_string(), 41 | table_oid: 0, 42 | primary_indexed_attributes: vec![], 43 | }; 44 | 45 | let index_type_query = r#" 46 | SELECT 47 | t.relname as table_name, 48 | i.relname as index_name, 49 | am.amname, 50 | t.oid as table_oid, 51 | array_to_string(array_agg(a.attname), ', ') as column_names 52 | FROM pg_index ix 53 | JOIN pg_class t ON (t.oid = ix.indrelid AND t.relkind = 'r') 54 | JOIN pg_class i ON (i.oid = ix.indexrelid) 55 | JOIN pg_am am ON (am.oid = i.relam) 56 | JOIN pg_attribute a ON (a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)) 57 | WHERE i.relname = $1 58 | GROUP BY t.relname, i.relname, am.amname, t.oid; 59 | "#; 60 | let result = client.borrow_mut().query(index_type_query, &[&index]).unwrap(); 61 | 62 | let (table_name, index_type, columns, table_oid) = match result.get(0) { 63 | Some(row) => { 64 | let table_name: String = row.get(0); 65 | let table_oid: postgres::types::Oid = row.get(3); 66 | let index_type: String = row.get(2); 67 | let column_str: String = row.get(4); 68 | let columns = column_str.split(",") 69 | .map(|s| { 70 | s.to_string().trim().to_string() 71 | }) 72 | .collect(); 73 | (table_name, index_type, columns, table_oid) 74 | } 75 | None => ("".to_string(), "".to_string(), vec![], 0), 76 | }; 77 | index_info.table_oid = table_oid; 78 | index_info.index_type = index_type; 79 | index_info.columns = columns; 80 | index_info.table_name = table_name.clone(); 81 | debug!("t: {:?} {:?}", table_name, table_oid); 82 | let table_indexed_attributes_query = r#" 83 | SELECT COALESCE(array_agg(cast(a.attname as TEXT)), '{}') 84 | FROM pg_index i 85 | JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) 86 | WHERE i.indrelid = $1 87 | AND i.indisprimary; 88 | "#; 89 | let result_indexed_attributes = client.borrow_mut().query(table_indexed_attributes_query, &[&table_oid]).unwrap(); 90 | debug!("{:?}", result_indexed_attributes); 91 | let indexed_attributes = match result_indexed_attributes.get(0) { 92 | Some(indexed_attributes) => { 93 | let indexed_columns: Vec = indexed_attributes.get(0); 94 | indexed_columns 95 | } 96 | None => vec![], 97 | }; 98 | index_info.primary_indexed_attributes = indexed_attributes; 99 | index_info 100 | } 101 | 102 | pub fn get_metadata_page(client: Arc>, index_name: String) -> MetadataPage { 103 | let btree_metadata_query = r#" 104 | SELECT 105 | version, 106 | root, 107 | level, 108 | fastroot, 109 | fastlevel 110 | FROM bt_metap($1); 111 | "#; 112 | info!("Getting metadata page for index: {}", index_name); 113 | let result_metadata = client.borrow_mut().query(btree_metadata_query, &[&index_name]).unwrap(); 114 | let metadata_page = match result_metadata.get(0) { 115 | Some(row) => { 116 | let version: i32 = row.get(0); 117 | let root: i64 = row.get(1); 118 | let level: i64 = row.get(2); 119 | let fast_root: i64 = row.get(3); 120 | let fast_level: i64 = row.get(4); 121 | MetadataPage::new(version, root, level, fast_root, fast_level) 122 | } 123 | None => MetadataPage::new(0, 0, 0, 0, 0), 124 | }; 125 | metadata_page 126 | } 127 | 128 | pub fn get_page(client: Arc>, page_id: i64, index_name: String, index_info: Rc) -> Page { 129 | println!("getting page {}", page_id); 130 | let page_query = r#" 131 | SELECT 132 | blkno, 133 | type::text, 134 | live_items, 135 | dead_items, 136 | avg_item_size, 137 | page_size, 138 | free_size, 139 | btpo_prev, 140 | btpo_next, 141 | btpo_level, 142 | btpo_flags 143 | FROM bt_page_stats($1, $2) 144 | "#.to_string(); 145 | let result_page = client.borrow_mut().query(&page_query, &[&index_name, &page_id]).unwrap(); 146 | let mut page = match result_page.get(0) { 147 | Some(row) => { 148 | let block_number: i64 = row.get(0); 149 | let rtype: String = row.get(1); 150 | let level: i64 = row.get(9); 151 | let next_page_id: i64 = row.get(8); 152 | let prev_page_id: i64 = row.get(7); 153 | Page::new(block_number, level, rtype == "l", rtype == "r", next_page_id, prev_page_id) 154 | } 155 | None => Page::new(page_id, 0, false, false, 0, 0), 156 | }; 157 | let (items, prev_item, next_item) = get_items(client.clone(), Rc::new(page.clone()), index_name.clone(), index_info.clone()); 158 | 159 | page.items = items; 160 | if next_item.is_some() { 161 | page.high_key = Some(next_item.unwrap().value) 162 | } 163 | page.prev_item = match prev_item { 164 | None => { None } 165 | Some(item) => { Some(Box::new(item)) } 166 | }; 167 | page 168 | } 169 | 170 | pub fn get_row(client: Arc>, ct_ids: Vec, index_info: Rc) -> HashMap { 171 | info!("getting {} rows", ct_ids.len()); 172 | let primary_key_columns = index_info.primary_indexed_attributes.iter().map(|pk| format!("{}::text", pk)).collect::>().join(", "); 173 | let columns = index_info.columns.iter().map(|pk| format!("{}::text", pk)).collect::>().join(", "); 174 | 175 | 176 | let ct_ids_array = ct_ids.iter() 177 | .map(|tid| format!("({},{})", tid.block_number, tid.offset_number)) 178 | .collect::>() 179 | .join(", "); 180 | 181 | let mut row_query = "".to_string(); 182 | if primary_key_columns == "" { 183 | row_query = format!(r#" 184 | SELECT ctid, {} 185 | FROM {} 186 | WHERE ctid IN (SELECT ('('|| block_num || ',' || offset_num || ')')::tid FROM unnest(ARRAY[{}]) AS t(block_num integer , offset_num integer)) 187 | "#, columns, index_info.table_name, ct_ids_array); 188 | } else { 189 | row_query = format!(r#" 190 | SELECT ctid, {}, {} 191 | FROM {} 192 | WHERE ctid IN (SELECT ('('|| block_num || ',' || offset_num || ')')::tid FROM unnest(ARRAY[{}]) AS t(block_num integer , offset_num integer)) 193 | "#, primary_key_columns, columns, index_info.table_name, ct_ids_array); 194 | } 195 | let rows = client.borrow_mut().query(&row_query, &[]).unwrap(); 196 | 197 | 198 | // Rows data in page should Map 199 | // Row Data -> primary key data, standard_index_data 200 | let mut row_data: HashMap = HashMap::new(); 201 | for row in rows.iter() { 202 | let mut i = 0; 203 | let ct_id: Tid = row.get(i); 204 | i += 1; 205 | 206 | let mut pks_left = index_info.primary_indexed_attributes.len(); 207 | let mut pk_values: Vec = vec![]; 208 | while pks_left > 0 { 209 | let pk: String = row.get(i); 210 | pks_left -= 1; 211 | i += 1; 212 | &pk_values.push(pk); 213 | } 214 | 215 | let mut cols_left = index_info.columns.len(); 216 | let mut col_vals: Vec = vec![]; 217 | while cols_left > 0 { 218 | let col: String = row.get(i); 219 | cols_left -= 1; 220 | i += 1; 221 | &col_vals.push(col); 222 | } 223 | row_data.insert(ct_id, RowData::new(pk_values, col_vals)); 224 | } 225 | 226 | row_data 227 | } 228 | 229 | pub fn get_items(client: Arc>, page: Rc, index_name: String, index_info: Rc) -> (Vec, Option, Option) { 230 | info!("getting items for page {}", page.id); 231 | let btree_item_query = r#" 232 | SELECT * 233 | FROM bt_page_items($1, $2); 234 | "#; 235 | let result_items = client.borrow_mut().query(btree_item_query, &[&index_name, &page.id]).unwrap(); 236 | let mut items: Vec = vec![]; 237 | 238 | if page.is_leaf { 239 | let ct_ids: Vec = result_items.iter().map(|item| { 240 | let ct_id: Tid = item.get(1); 241 | ct_id 242 | }).collect(); 243 | if ct_ids.len() > 0 { 244 | let rows = get_row(client.clone(), ct_ids, index_info.clone()); 245 | for item in result_items.iter() { 246 | let row_id: Tid = item.get(1); 247 | let row_id_value = rows.get(&row_id); 248 | let value: String = match row_id_value { 249 | Some(row_data) => row_data.byte_values.clone().unwrap_or_else(|| item.get(5)), 250 | None => item.get(5), 251 | }; 252 | items.push(Item::new(value, None, Some(row_id.block_number as i64), Some(row_id))); 253 | } 254 | } 255 | } else { 256 | for item in result_items.iter() { 257 | let next_page_tid: Tid = item.get(1); 258 | let next_page_pointer: i64 = next_page_tid.block_number as i64; 259 | let child_page = get_page(client.clone(), next_page_pointer, index_name.clone(), index_info.clone()); 260 | 261 | let value: String = match item.get(5) { 262 | Some(value) => value, 263 | None => "".to_string(), 264 | }; 265 | items.push(Item::new(value, Some(Box::new(child_page)), Some(next_page_pointer), None)); 266 | } 267 | } 268 | 269 | let mut prev_item: Option = None; 270 | let next_item: Option = match page.next_page_id { 271 | None => None, 272 | Some(_) => { 273 | if items.len() > 0 { 274 | Some(items.remove(0)) 275 | } else { 276 | None 277 | } 278 | } 279 | }; 280 | if items.len() > 0 { 281 | if page.prev_page_id.is_some() || (page.prev_page_id.is_none() && !page.is_root && !page.is_leaf) { 282 | prev_item = Some(items.remove(0)); 283 | } 284 | } 285 | 286 | (items, prev_item, next_item) 287 | } 288 | 289 | #[cfg(test)] 290 | mod tests { 291 | use std::cell::RefCell; 292 | use std::rc::Rc; 293 | use std::sync::Arc; 294 | use postgres::Client; 295 | use crate::core::{Tid}; 296 | use crate::db::client::{get_index_info, get_metadata_page, get_row, IndexInfo}; 297 | use crate::db::get_page; 298 | use crate::core::btree::generate_btree; 299 | 300 | fn setup_test_data(client: Arc>) { 301 | tear_down_test_data(Arc::clone(&client)); 302 | client.borrow_mut().batch_execute( 303 | "CREATE TABLE IF NOT EXISTS test_table ( 304 | id SERIAL PRIMARY KEY, 305 | name TEXT NOT NULL, 306 | email TEXT NOT NULL UNIQUE, 307 | created_at TIMESTAMP NOT NULL DEFAULT NOW() 308 | )").unwrap(); 309 | 310 | client.borrow_mut().batch_execute( 311 | "CREATE INDEX IF NOT EXISTS idx_users_name_email ON test_table (name, email)" 312 | ).unwrap(); 313 | } 314 | 315 | fn insert_data(client: Arc>) { 316 | client.borrow_mut().batch_execute( 317 | "INSERT INTO test_table(id, name, email) VALUES \ 318 | (1, 'foo', 'foo@gmail.com'),\ 319 | (2, 'bar', 'bar@gmail.com'),\ 320 | (3, 'alice', 'alice@gmail.com'),\ 321 | (4, 'foo2', 'foo2@gmail.com'),\ 322 | (5, 'bob2', 'bob2@gmail.com'),\ 323 | (6, 'alice2', 'alice2@gmail.com'),\ 324 | (7, 'foo3', 'foo3@gmail.com'),\ 325 | (8, 'bob3', 'bob3@gmail.com')" 326 | ).unwrap() 327 | } 328 | 329 | fn tear_down_test_data(client: Arc>) { 330 | client.borrow_mut().batch_execute( 331 | "DROP TABLE IF EXISTS test_table" 332 | ).unwrap(); 333 | } 334 | 335 | fn assert_index_info(expected_index_info: &IndexInfo, actual_index_info: &IndexInfo) { 336 | assert_eq!(expected_index_info.index_type, actual_index_info.index_type); 337 | assert_eq!(expected_index_info.columns, actual_index_info.columns); 338 | assert_eq!(expected_index_info.table_name, actual_index_info.table_name); 339 | assert_eq!(expected_index_info.primary_indexed_attributes, actual_index_info.primary_indexed_attributes); 340 | } 341 | 342 | #[test] 343 | pub fn test_index_info() { 344 | let client_ref = Arc::new(super::init_client( 345 | "localhost".to_string(), "5432".to_string(), "postgres".to_string(), "postgres".to_string(), "".to_string(), 346 | )); 347 | setup_test_data(Arc::clone(&client_ref)); 348 | let actual_index_info = get_index_info(Arc::clone(&client_ref), "idx_users_name_email".to_string()); 349 | let expected_index_info = IndexInfo { 350 | index_type: "btree".to_string(), 351 | columns: vec!["name".to_string(), "email".to_string()], 352 | table_name: "test_table".to_string(), 353 | primary_indexed_attributes: vec!["id".to_string()], 354 | table_oid: 0, 355 | }; 356 | assert_index_info(&expected_index_info, &actual_index_info); 357 | assert_ne!(0, actual_index_info.table_oid); 358 | tear_down_test_data(Arc::clone(&client_ref)); 359 | } 360 | 361 | #[test] 362 | pub fn test_metadata_page_information() { 363 | let client_ref = Arc::new(super::init_client( 364 | "localhost".to_string(), "5432".to_string(), "postgres".to_string(), "postgres".to_string(), "".to_string(), 365 | )); 366 | setup_test_data(Arc::clone(&client_ref)); 367 | let actual_metadata_page = get_metadata_page(Arc::clone(&client_ref), "idx_users_name_email".to_string()); 368 | let expected_metadata_page = super::MetadataPage::new(1, 1, 1, 0, 0); 369 | assert_ne!(expected_metadata_page, actual_metadata_page); 370 | tear_down_test_data(Arc::clone(&client_ref)); 371 | } 372 | 373 | #[test] 374 | pub fn test_get_row() { 375 | // Todo: update this test with predictable data 376 | let client_ref = Arc::new(super::init_client( 377 | "localhost".to_string(), "5432".to_string(), "postgres".to_string(), "postgres".to_string(), "".to_string(), 378 | )); 379 | setup_test_data(Arc::clone(&client_ref)); 380 | insert_data(Arc::clone(&client_ref)); 381 | let actual_index_info = get_index_info(Arc::clone(&client_ref), "idx_users_name_email".to_string()); 382 | let row_data = get_row(Arc::clone(&client_ref), vec![Tid { block_number: 0, offset_number: 1 }, Tid { block_number: 0, offset_number: 2 }], Rc::new(actual_index_info)); 383 | for (k, v) in row_data.iter() { 384 | println!("Key: {}, Value: {:?}", k, v); 385 | } 386 | tear_down_test_data(Arc::clone(&client_ref)); 387 | } 388 | 389 | #[test] 390 | pub fn test_get_page() { 391 | // Todo: update this test with predictable data 392 | let client_ref = Arc::new(super::init_client( 393 | "localhost".to_string(), "5432".to_string(), "postgres".to_string(), "postgres".to_string(), "".to_string(), 394 | )); 395 | setup_test_data(Arc::clone(&client_ref)); 396 | insert_data(Arc::clone(&client_ref)); 397 | let index_name = "idx_users_name_email".to_string(); 398 | let actual_index_info = get_index_info(Arc::clone(&client_ref), index_name.clone()); 399 | let metadata_page = get_metadata_page(Arc::clone(&client_ref), index_name.clone()); 400 | let page = get_page(Arc::clone(&client_ref), metadata_page.root, index_name, Rc::new(actual_index_info)); 401 | println!("{:?}", page); 402 | tear_down_test_data(Arc::clone(&client_ref)); 403 | } 404 | 405 | #[test] 406 | pub fn test_get_tree() { 407 | let client_ref = Arc::new(super::init_client( 408 | "localhost".to_string(), "5432".to_string(), "postgres".to_string(), "".to_string(), "".to_string(), 409 | )); 410 | setup_test_data(Arc::clone(&client_ref)); 411 | insert_data(Arc::clone(&client_ref)); 412 | let index_name = "idx_users_name_email".to_string(); 413 | let actual_index_info = get_index_info(Arc::clone(&client_ref), index_name.clone()); 414 | let tree = generate_btree(Arc::clone(&client_ref), index_name.clone(), Rc::new(actual_index_info)); 415 | tear_down_test_data(Arc::clone(&client_ref)); 416 | } 417 | } -------------------------------------------------------------------------------- /src/db/mod.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | 3 | pub use client::{init_client, get_index_info, get_metadata_page, get_page}; 4 | pub use client::IndexInfo; 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod core; 2 | mod db; 3 | 4 | use crate::core::handle_command_call; 5 | 6 | fn main() { 7 | handle_command_call(); 8 | } -------------------------------------------------------------------------------- /src/templates/render_level.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#each pages as |page|}} 3 | {{> render_page page=page index_type=../index_type}} 4 | {{/each}} 5 |
6 | 7 | {{#if (hasChildren pages)}} 8 | {{#each pages as |page|}} 9 | {{> render_level pages=(sample-lookup ../parent_child_map page) index_type=../index_type parent_child_map=../parent_child_map}} 10 | {{/each}} 11 | {{/if}} -------------------------------------------------------------------------------- /src/templates/render_page.hbs: -------------------------------------------------------------------------------- 1 |
4 | 5 |
6 | {{#if page.is_root}} 7 | 8 | {{else if page.is_leaf}} 9 | 10 | {{else}} 11 | 12 | {{/if}} 13 | 14 |
    15 |
  • {{page.id}}
  • 16 |
  • {{page.level}}
  • 17 | {{#if (eq index_type 'btree')}} 18 |
  • {{page.high_key}}
  • 19 | {{/if}} 20 |
  • : {{len page.items}}
  • 21 |
22 |
23 | 24 | 50 |
-------------------------------------------------------------------------------- /src/templates/render_tree.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 167 | 168 | 248 | 249 | 250 | 251 | 252 | Tree for the index {{tree.index_name}} on table {{tree.table_name}} ({{tree.columns}}) 253 | 254 |

Tree for the index {{tree.index_name}} on table {{tree.table_name}} ({{tree.columns}})

255 | 256 |
257 | Loading ... 258 |
259 | 260 | 292 | 293 | 294 | --------------------------------------------------------------------------------