├── .env ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── build.rs ├── docker-compose.yaml ├── migrations ├── 20221009093923_create_tables.down.sql └── 20221009093923_create_tables.up.sql └── src ├── main.rs ├── pg_logicaldec.proto ├── replication.rs └── types ├── mod.rs └── tenant ├── fixtures └── tenants.sql ├── mod.rs └── queries ├── create.sql ├── delete.sql ├── retrieve.sql ├── retrieve_all.sql ├── retrieve_many.sql └── update.sql /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL="postgres://postgres:password@localhost:5432/postgres" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "anyhow" 18 | version = "1.0.66" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" 21 | 22 | [[package]] 23 | name = "async-trait" 24 | version = "0.1.58" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" 27 | dependencies = [ 28 | "proc-macro2", 29 | "quote", 30 | "syn", 31 | ] 32 | 33 | [[package]] 34 | name = "atoi" 35 | version = "1.0.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" 38 | dependencies = [ 39 | "num-traits", 40 | ] 41 | 42 | [[package]] 43 | name = "autocfg" 44 | version = "1.1.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 47 | 48 | [[package]] 49 | name = "base64" 50 | version = "0.13.1" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 53 | 54 | [[package]] 55 | name = "bitflags" 56 | version = "1.3.2" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 59 | 60 | [[package]] 61 | name = "block-buffer" 62 | version = "0.10.3" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 65 | dependencies = [ 66 | "generic-array", 67 | ] 68 | 69 | [[package]] 70 | name = "bumpalo" 71 | version = "3.11.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 74 | 75 | [[package]] 76 | name = "byteorder" 77 | version = "1.4.3" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 80 | 81 | [[package]] 82 | name = "bytes" 83 | version = "1.2.1" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" 86 | 87 | [[package]] 88 | name = "cc" 89 | version = "1.0.76" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" 92 | 93 | [[package]] 94 | name = "cfg-if" 95 | version = "1.0.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 98 | 99 | [[package]] 100 | name = "core-foundation" 101 | version = "0.9.3" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 104 | dependencies = [ 105 | "core-foundation-sys", 106 | "libc", 107 | ] 108 | 109 | [[package]] 110 | name = "core-foundation-sys" 111 | version = "0.8.3" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 114 | 115 | [[package]] 116 | name = "cpufeatures" 117 | version = "0.2.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 120 | dependencies = [ 121 | "libc", 122 | ] 123 | 124 | [[package]] 125 | name = "crc" 126 | version = "3.0.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" 129 | dependencies = [ 130 | "crc-catalog", 131 | ] 132 | 133 | [[package]] 134 | name = "crc-catalog" 135 | version = "2.1.0" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" 138 | 139 | [[package]] 140 | name = "crossbeam-channel" 141 | version = "0.5.6" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 144 | dependencies = [ 145 | "cfg-if", 146 | "crossbeam-utils", 147 | ] 148 | 149 | [[package]] 150 | name = "crossbeam-queue" 151 | version = "0.3.6" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" 154 | dependencies = [ 155 | "cfg-if", 156 | "crossbeam-utils", 157 | ] 158 | 159 | [[package]] 160 | name = "crossbeam-utils" 161 | version = "0.8.12" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" 164 | dependencies = [ 165 | "cfg-if", 166 | ] 167 | 168 | [[package]] 169 | name = "crypto-common" 170 | version = "0.1.6" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 173 | dependencies = [ 174 | "generic-array", 175 | "typenum", 176 | ] 177 | 178 | [[package]] 179 | name = "digest" 180 | version = "0.10.5" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" 183 | dependencies = [ 184 | "block-buffer", 185 | "crypto-common", 186 | "subtle", 187 | ] 188 | 189 | [[package]] 190 | name = "dirs" 191 | version = "4.0.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 194 | dependencies = [ 195 | "dirs-sys", 196 | ] 197 | 198 | [[package]] 199 | name = "dirs-sys" 200 | version = "0.3.7" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 203 | dependencies = [ 204 | "libc", 205 | "redox_users", 206 | "winapi", 207 | ] 208 | 209 | [[package]] 210 | name = "dotenvy" 211 | version = "0.15.6" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" 214 | 215 | [[package]] 216 | name = "either" 217 | version = "1.8.0" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 220 | 221 | [[package]] 222 | name = "event-listener" 223 | version = "2.5.3" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 226 | 227 | [[package]] 228 | name = "fallible-iterator" 229 | version = "0.2.0" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 232 | 233 | [[package]] 234 | name = "fastrand" 235 | version = "1.8.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 238 | dependencies = [ 239 | "instant", 240 | ] 241 | 242 | [[package]] 243 | name = "fixedbitset" 244 | version = "0.4.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 247 | 248 | [[package]] 249 | name = "foreign-types" 250 | version = "0.3.2" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 253 | dependencies = [ 254 | "foreign-types-shared", 255 | ] 256 | 257 | [[package]] 258 | name = "foreign-types-shared" 259 | version = "0.1.1" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 262 | 263 | [[package]] 264 | name = "form_urlencoded" 265 | version = "1.1.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 268 | dependencies = [ 269 | "percent-encoding", 270 | ] 271 | 272 | [[package]] 273 | name = "futures" 274 | version = "0.3.25" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 277 | dependencies = [ 278 | "futures-channel", 279 | "futures-core", 280 | "futures-executor", 281 | "futures-io", 282 | "futures-sink", 283 | "futures-task", 284 | "futures-util", 285 | ] 286 | 287 | [[package]] 288 | name = "futures-channel" 289 | version = "0.3.25" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 292 | dependencies = [ 293 | "futures-core", 294 | "futures-sink", 295 | ] 296 | 297 | [[package]] 298 | name = "futures-core" 299 | version = "0.3.25" 300 | source = "registry+https://github.com/rust-lang/crates.io-index" 301 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 302 | 303 | [[package]] 304 | name = "futures-executor" 305 | version = "0.3.25" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 308 | dependencies = [ 309 | "futures-core", 310 | "futures-task", 311 | "futures-util", 312 | ] 313 | 314 | [[package]] 315 | name = "futures-intrusive" 316 | version = "0.4.2" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" 319 | dependencies = [ 320 | "futures-core", 321 | "lock_api", 322 | "parking_lot 0.11.2", 323 | ] 324 | 325 | [[package]] 326 | name = "futures-io" 327 | version = "0.3.25" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 330 | 331 | [[package]] 332 | name = "futures-macro" 333 | version = "0.3.25" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 336 | dependencies = [ 337 | "proc-macro2", 338 | "quote", 339 | "syn", 340 | ] 341 | 342 | [[package]] 343 | name = "futures-sink" 344 | version = "0.3.25" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 347 | 348 | [[package]] 349 | name = "futures-task" 350 | version = "0.3.25" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 353 | 354 | [[package]] 355 | name = "futures-util" 356 | version = "0.3.25" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 359 | dependencies = [ 360 | "futures-channel", 361 | "futures-core", 362 | "futures-io", 363 | "futures-macro", 364 | "futures-sink", 365 | "futures-task", 366 | "memchr", 367 | "pin-project-lite", 368 | "pin-utils", 369 | "slab", 370 | ] 371 | 372 | [[package]] 373 | name = "generic-array" 374 | version = "0.14.6" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 377 | dependencies = [ 378 | "typenum", 379 | "version_check", 380 | ] 381 | 382 | [[package]] 383 | name = "getrandom" 384 | version = "0.2.8" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 387 | dependencies = [ 388 | "cfg-if", 389 | "libc", 390 | "wasi", 391 | ] 392 | 393 | [[package]] 394 | name = "hashbrown" 395 | version = "0.12.3" 396 | source = "registry+https://github.com/rust-lang/crates.io-index" 397 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 398 | dependencies = [ 399 | "ahash", 400 | ] 401 | 402 | [[package]] 403 | name = "hashlink" 404 | version = "0.8.1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" 407 | dependencies = [ 408 | "hashbrown", 409 | ] 410 | 411 | [[package]] 412 | name = "heck" 413 | version = "0.4.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 416 | dependencies = [ 417 | "unicode-segmentation", 418 | ] 419 | 420 | [[package]] 421 | name = "hermit-abi" 422 | version = "0.1.19" 423 | source = "registry+https://github.com/rust-lang/crates.io-index" 424 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 425 | dependencies = [ 426 | "libc", 427 | ] 428 | 429 | [[package]] 430 | name = "hex" 431 | version = "0.4.3" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 434 | 435 | [[package]] 436 | name = "hkdf" 437 | version = "0.12.3" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 440 | dependencies = [ 441 | "hmac", 442 | ] 443 | 444 | [[package]] 445 | name = "hmac" 446 | version = "0.12.1" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 449 | dependencies = [ 450 | "digest", 451 | ] 452 | 453 | [[package]] 454 | name = "idna" 455 | version = "0.3.0" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 458 | dependencies = [ 459 | "unicode-bidi", 460 | "unicode-normalization", 461 | ] 462 | 463 | [[package]] 464 | name = "indexmap" 465 | version = "1.9.1" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 468 | dependencies = [ 469 | "autocfg", 470 | "hashbrown", 471 | ] 472 | 473 | [[package]] 474 | name = "instant" 475 | version = "0.1.12" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 478 | dependencies = [ 479 | "cfg-if", 480 | ] 481 | 482 | [[package]] 483 | name = "itertools" 484 | version = "0.10.5" 485 | source = "registry+https://github.com/rust-lang/crates.io-index" 486 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 487 | dependencies = [ 488 | "either", 489 | ] 490 | 491 | [[package]] 492 | name = "itoa" 493 | version = "1.0.4" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" 496 | 497 | [[package]] 498 | name = "js-sys" 499 | version = "0.3.60" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 502 | dependencies = [ 503 | "wasm-bindgen", 504 | ] 505 | 506 | [[package]] 507 | name = "lazy_static" 508 | version = "1.4.0" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 511 | 512 | [[package]] 513 | name = "libc" 514 | version = "0.2.137" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" 517 | 518 | [[package]] 519 | name = "lock_api" 520 | version = "0.4.9" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 523 | dependencies = [ 524 | "autocfg", 525 | "scopeguard", 526 | ] 527 | 528 | [[package]] 529 | name = "log" 530 | version = "0.4.17" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 533 | dependencies = [ 534 | "cfg-if", 535 | ] 536 | 537 | [[package]] 538 | name = "logicaldecoding" 539 | version = "0.1.0" 540 | dependencies = [ 541 | "anyhow", 542 | "bytes", 543 | "crossbeam-channel", 544 | "futures", 545 | "prost", 546 | "prost-build", 547 | "rand", 548 | "sqlx", 549 | "tokio", 550 | "tokio-postgres", 551 | "tracing", 552 | "tracing-subscriber", 553 | "uuid", 554 | ] 555 | 556 | [[package]] 557 | name = "matchers" 558 | version = "0.1.0" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 561 | dependencies = [ 562 | "regex-automata", 563 | ] 564 | 565 | [[package]] 566 | name = "md-5" 567 | version = "0.10.5" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" 570 | dependencies = [ 571 | "digest", 572 | ] 573 | 574 | [[package]] 575 | name = "memchr" 576 | version = "2.5.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 579 | 580 | [[package]] 581 | name = "minimal-lexical" 582 | version = "0.2.1" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 585 | 586 | [[package]] 587 | name = "mio" 588 | version = "0.8.5" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 591 | dependencies = [ 592 | "libc", 593 | "log", 594 | "wasi", 595 | "windows-sys 0.42.0", 596 | ] 597 | 598 | [[package]] 599 | name = "multimap" 600 | version = "0.8.3" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" 603 | 604 | [[package]] 605 | name = "native-tls" 606 | version = "0.2.11" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" 609 | dependencies = [ 610 | "lazy_static", 611 | "libc", 612 | "log", 613 | "openssl", 614 | "openssl-probe", 615 | "openssl-sys", 616 | "schannel", 617 | "security-framework", 618 | "security-framework-sys", 619 | "tempfile", 620 | ] 621 | 622 | [[package]] 623 | name = "nom" 624 | version = "7.1.1" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" 627 | dependencies = [ 628 | "memchr", 629 | "minimal-lexical", 630 | ] 631 | 632 | [[package]] 633 | name = "nu-ansi-term" 634 | version = "0.46.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 637 | dependencies = [ 638 | "overload", 639 | "winapi", 640 | ] 641 | 642 | [[package]] 643 | name = "num-traits" 644 | version = "0.2.15" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 647 | dependencies = [ 648 | "autocfg", 649 | ] 650 | 651 | [[package]] 652 | name = "num_cpus" 653 | version = "1.14.0" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 656 | dependencies = [ 657 | "hermit-abi", 658 | "libc", 659 | ] 660 | 661 | [[package]] 662 | name = "once_cell" 663 | version = "1.16.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 666 | 667 | [[package]] 668 | name = "openssl" 669 | version = "0.10.42" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" 672 | dependencies = [ 673 | "bitflags", 674 | "cfg-if", 675 | "foreign-types", 676 | "libc", 677 | "once_cell", 678 | "openssl-macros", 679 | "openssl-sys", 680 | ] 681 | 682 | [[package]] 683 | name = "openssl-macros" 684 | version = "0.1.0" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" 687 | dependencies = [ 688 | "proc-macro2", 689 | "quote", 690 | "syn", 691 | ] 692 | 693 | [[package]] 694 | name = "openssl-probe" 695 | version = "0.1.5" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 698 | 699 | [[package]] 700 | name = "openssl-sys" 701 | version = "0.9.77" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" 704 | dependencies = [ 705 | "autocfg", 706 | "cc", 707 | "libc", 708 | "pkg-config", 709 | "vcpkg", 710 | ] 711 | 712 | [[package]] 713 | name = "overload" 714 | version = "0.1.1" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 717 | 718 | [[package]] 719 | name = "parking_lot" 720 | version = "0.11.2" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 723 | dependencies = [ 724 | "instant", 725 | "lock_api", 726 | "parking_lot_core 0.8.5", 727 | ] 728 | 729 | [[package]] 730 | name = "parking_lot" 731 | version = "0.12.1" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 734 | dependencies = [ 735 | "lock_api", 736 | "parking_lot_core 0.9.4", 737 | ] 738 | 739 | [[package]] 740 | name = "parking_lot_core" 741 | version = "0.8.5" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 744 | dependencies = [ 745 | "cfg-if", 746 | "instant", 747 | "libc", 748 | "redox_syscall", 749 | "smallvec", 750 | "winapi", 751 | ] 752 | 753 | [[package]] 754 | name = "parking_lot_core" 755 | version = "0.9.4" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" 758 | dependencies = [ 759 | "cfg-if", 760 | "libc", 761 | "redox_syscall", 762 | "smallvec", 763 | "windows-sys 0.42.0", 764 | ] 765 | 766 | [[package]] 767 | name = "paste" 768 | version = "1.0.9" 769 | source = "registry+https://github.com/rust-lang/crates.io-index" 770 | checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" 771 | 772 | [[package]] 773 | name = "percent-encoding" 774 | version = "2.2.0" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 777 | 778 | [[package]] 779 | name = "petgraph" 780 | version = "0.6.2" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" 783 | dependencies = [ 784 | "fixedbitset", 785 | "indexmap", 786 | ] 787 | 788 | [[package]] 789 | name = "phf" 790 | version = "0.11.1" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" 793 | dependencies = [ 794 | "phf_shared", 795 | ] 796 | 797 | [[package]] 798 | name = "phf_shared" 799 | version = "0.11.1" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" 802 | dependencies = [ 803 | "siphasher", 804 | ] 805 | 806 | [[package]] 807 | name = "pin-project-lite" 808 | version = "0.2.9" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 811 | 812 | [[package]] 813 | name = "pin-utils" 814 | version = "0.1.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 817 | 818 | [[package]] 819 | name = "pkg-config" 820 | version = "0.3.26" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" 823 | 824 | [[package]] 825 | name = "postgres-protocol" 826 | version = "0.6.4" 827 | source = "git+https://github.com/MaterializeInc/rust-postgres#abff35ecc553dc23ca8f85e64945e87a93cbba28" 828 | dependencies = [ 829 | "base64", 830 | "byteorder", 831 | "bytes", 832 | "fallible-iterator", 833 | "hmac", 834 | "md-5", 835 | "memchr", 836 | "rand", 837 | "sha2", 838 | "stringprep", 839 | ] 840 | 841 | [[package]] 842 | name = "postgres-types" 843 | version = "0.2.3" 844 | source = "git+https://github.com/MaterializeInc/rust-postgres#abff35ecc553dc23ca8f85e64945e87a93cbba28" 845 | dependencies = [ 846 | "bytes", 847 | "fallible-iterator", 848 | "postgres-protocol", 849 | ] 850 | 851 | [[package]] 852 | name = "ppv-lite86" 853 | version = "0.2.17" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 856 | 857 | [[package]] 858 | name = "prettyplease" 859 | version = "0.1.21" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" 862 | dependencies = [ 863 | "proc-macro2", 864 | "syn", 865 | ] 866 | 867 | [[package]] 868 | name = "proc-macro2" 869 | version = "1.0.47" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" 872 | dependencies = [ 873 | "unicode-ident", 874 | ] 875 | 876 | [[package]] 877 | name = "prost" 878 | version = "0.11.2" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" 881 | dependencies = [ 882 | "bytes", 883 | "prost-derive", 884 | ] 885 | 886 | [[package]] 887 | name = "prost-build" 888 | version = "0.11.2" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511" 891 | dependencies = [ 892 | "bytes", 893 | "heck", 894 | "itertools", 895 | "lazy_static", 896 | "log", 897 | "multimap", 898 | "petgraph", 899 | "prettyplease", 900 | "prost", 901 | "prost-types", 902 | "regex", 903 | "syn", 904 | "tempfile", 905 | "which", 906 | ] 907 | 908 | [[package]] 909 | name = "prost-derive" 910 | version = "0.11.2" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" 913 | dependencies = [ 914 | "anyhow", 915 | "itertools", 916 | "proc-macro2", 917 | "quote", 918 | "syn", 919 | ] 920 | 921 | [[package]] 922 | name = "prost-types" 923 | version = "0.11.2" 924 | source = "registry+https://github.com/rust-lang/crates.io-index" 925 | checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" 926 | dependencies = [ 927 | "bytes", 928 | "prost", 929 | ] 930 | 931 | [[package]] 932 | name = "quote" 933 | version = "1.0.21" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 936 | dependencies = [ 937 | "proc-macro2", 938 | ] 939 | 940 | [[package]] 941 | name = "rand" 942 | version = "0.8.5" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 945 | dependencies = [ 946 | "libc", 947 | "rand_chacha", 948 | "rand_core", 949 | ] 950 | 951 | [[package]] 952 | name = "rand_chacha" 953 | version = "0.3.1" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 956 | dependencies = [ 957 | "ppv-lite86", 958 | "rand_core", 959 | ] 960 | 961 | [[package]] 962 | name = "rand_core" 963 | version = "0.6.4" 964 | source = "registry+https://github.com/rust-lang/crates.io-index" 965 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 966 | dependencies = [ 967 | "getrandom", 968 | ] 969 | 970 | [[package]] 971 | name = "redox_syscall" 972 | version = "0.2.16" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 975 | dependencies = [ 976 | "bitflags", 977 | ] 978 | 979 | [[package]] 980 | name = "redox_users" 981 | version = "0.4.3" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 984 | dependencies = [ 985 | "getrandom", 986 | "redox_syscall", 987 | "thiserror", 988 | ] 989 | 990 | [[package]] 991 | name = "regex" 992 | version = "1.7.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 995 | dependencies = [ 996 | "regex-syntax", 997 | ] 998 | 999 | [[package]] 1000 | name = "regex-automata" 1001 | version = "0.1.10" 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" 1003 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1004 | dependencies = [ 1005 | "regex-syntax", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "regex-syntax" 1010 | version = "0.6.28" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 1013 | 1014 | [[package]] 1015 | name = "remove_dir_all" 1016 | version = "0.5.3" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1019 | dependencies = [ 1020 | "winapi", 1021 | ] 1022 | 1023 | [[package]] 1024 | name = "ryu" 1025 | version = "1.0.11" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 1028 | 1029 | [[package]] 1030 | name = "schannel" 1031 | version = "0.1.20" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" 1034 | dependencies = [ 1035 | "lazy_static", 1036 | "windows-sys 0.36.1", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "scopeguard" 1041 | version = "1.1.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1044 | 1045 | [[package]] 1046 | name = "security-framework" 1047 | version = "2.7.0" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" 1050 | dependencies = [ 1051 | "bitflags", 1052 | "core-foundation", 1053 | "core-foundation-sys", 1054 | "libc", 1055 | "security-framework-sys", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "security-framework-sys" 1060 | version = "2.6.1" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1063 | dependencies = [ 1064 | "core-foundation-sys", 1065 | "libc", 1066 | ] 1067 | 1068 | [[package]] 1069 | name = "serde" 1070 | version = "1.0.147" 1071 | source = "registry+https://github.com/rust-lang/crates.io-index" 1072 | checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" 1073 | dependencies = [ 1074 | "serde_derive", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "serde_derive" 1079 | version = "1.0.147" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" 1082 | dependencies = [ 1083 | "proc-macro2", 1084 | "quote", 1085 | "syn", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "serde_json" 1090 | version = "1.0.87" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" 1093 | dependencies = [ 1094 | "itoa", 1095 | "ryu", 1096 | "serde", 1097 | ] 1098 | 1099 | [[package]] 1100 | name = "sha1" 1101 | version = "0.10.5" 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" 1103 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1104 | dependencies = [ 1105 | "cfg-if", 1106 | "cpufeatures", 1107 | "digest", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "sha2" 1112 | version = "0.10.6" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1115 | dependencies = [ 1116 | "cfg-if", 1117 | "cpufeatures", 1118 | "digest", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "sharded-slab" 1123 | version = "0.1.4" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1126 | dependencies = [ 1127 | "lazy_static", 1128 | ] 1129 | 1130 | [[package]] 1131 | name = "signal-hook-registry" 1132 | version = "1.4.0" 1133 | source = "registry+https://github.com/rust-lang/crates.io-index" 1134 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1135 | dependencies = [ 1136 | "libc", 1137 | ] 1138 | 1139 | [[package]] 1140 | name = "siphasher" 1141 | version = "0.3.10" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" 1144 | 1145 | [[package]] 1146 | name = "slab" 1147 | version = "0.4.7" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1150 | dependencies = [ 1151 | "autocfg", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "smallvec" 1156 | version = "1.10.0" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1159 | 1160 | [[package]] 1161 | name = "socket2" 1162 | version = "0.4.7" 1163 | source = "registry+https://github.com/rust-lang/crates.io-index" 1164 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 1165 | dependencies = [ 1166 | "libc", 1167 | "winapi", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "sqlformat" 1172 | version = "0.2.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "f87e292b4291f154971a43c3774364e2cbcaec599d3f5bf6fa9d122885dbc38a" 1175 | dependencies = [ 1176 | "itertools", 1177 | "nom", 1178 | "unicode_categories", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "sqlx" 1183 | version = "0.6.2" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "9249290c05928352f71c077cc44a464d880c63f26f7534728cca008e135c0428" 1186 | dependencies = [ 1187 | "sqlx-core", 1188 | "sqlx-macros", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "sqlx-core" 1193 | version = "0.6.2" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" 1196 | dependencies = [ 1197 | "ahash", 1198 | "atoi", 1199 | "base64", 1200 | "bitflags", 1201 | "byteorder", 1202 | "bytes", 1203 | "crc", 1204 | "crossbeam-queue", 1205 | "dirs", 1206 | "dotenvy", 1207 | "either", 1208 | "event-listener", 1209 | "futures-channel", 1210 | "futures-core", 1211 | "futures-intrusive", 1212 | "futures-util", 1213 | "hashlink", 1214 | "hex", 1215 | "hkdf", 1216 | "hmac", 1217 | "indexmap", 1218 | "itoa", 1219 | "libc", 1220 | "log", 1221 | "md-5", 1222 | "memchr", 1223 | "once_cell", 1224 | "paste", 1225 | "percent-encoding", 1226 | "rand", 1227 | "serde", 1228 | "serde_json", 1229 | "sha1", 1230 | "sha2", 1231 | "smallvec", 1232 | "sqlformat", 1233 | "sqlx-rt", 1234 | "stringprep", 1235 | "thiserror", 1236 | "tokio-stream", 1237 | "url", 1238 | "uuid", 1239 | "whoami", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "sqlx-macros" 1244 | version = "0.6.2" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" 1247 | dependencies = [ 1248 | "dotenvy", 1249 | "either", 1250 | "heck", 1251 | "once_cell", 1252 | "proc-macro2", 1253 | "quote", 1254 | "serde_json", 1255 | "sha2", 1256 | "sqlx-core", 1257 | "sqlx-rt", 1258 | "syn", 1259 | "url", 1260 | ] 1261 | 1262 | [[package]] 1263 | name = "sqlx-rt" 1264 | version = "0.6.2" 1265 | source = "registry+https://github.com/rust-lang/crates.io-index" 1266 | checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" 1267 | dependencies = [ 1268 | "native-tls", 1269 | "once_cell", 1270 | "tokio", 1271 | "tokio-native-tls", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "stringprep" 1276 | version = "0.1.2" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" 1279 | dependencies = [ 1280 | "unicode-bidi", 1281 | "unicode-normalization", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "subtle" 1286 | version = "2.4.1" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1289 | 1290 | [[package]] 1291 | name = "syn" 1292 | version = "1.0.103" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 1295 | dependencies = [ 1296 | "proc-macro2", 1297 | "quote", 1298 | "unicode-ident", 1299 | ] 1300 | 1301 | [[package]] 1302 | name = "tempfile" 1303 | version = "3.3.0" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1306 | dependencies = [ 1307 | "cfg-if", 1308 | "fastrand", 1309 | "libc", 1310 | "redox_syscall", 1311 | "remove_dir_all", 1312 | "winapi", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "thiserror" 1317 | version = "1.0.37" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 1320 | dependencies = [ 1321 | "thiserror-impl", 1322 | ] 1323 | 1324 | [[package]] 1325 | name = "thiserror-impl" 1326 | version = "1.0.37" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 1329 | dependencies = [ 1330 | "proc-macro2", 1331 | "quote", 1332 | "syn", 1333 | ] 1334 | 1335 | [[package]] 1336 | name = "thread_local" 1337 | version = "1.1.4" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1340 | dependencies = [ 1341 | "once_cell", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "tinyvec" 1346 | version = "1.6.0" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1349 | dependencies = [ 1350 | "tinyvec_macros", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "tinyvec_macros" 1355 | version = "0.1.0" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1358 | 1359 | [[package]] 1360 | name = "tokio" 1361 | version = "1.21.2" 1362 | source = "registry+https://github.com/rust-lang/crates.io-index" 1363 | checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" 1364 | dependencies = [ 1365 | "autocfg", 1366 | "bytes", 1367 | "libc", 1368 | "memchr", 1369 | "mio", 1370 | "num_cpus", 1371 | "parking_lot 0.12.1", 1372 | "pin-project-lite", 1373 | "signal-hook-registry", 1374 | "socket2", 1375 | "tokio-macros", 1376 | "winapi", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "tokio-macros" 1381 | version = "1.8.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" 1384 | dependencies = [ 1385 | "proc-macro2", 1386 | "quote", 1387 | "syn", 1388 | ] 1389 | 1390 | [[package]] 1391 | name = "tokio-native-tls" 1392 | version = "0.3.0" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" 1395 | dependencies = [ 1396 | "native-tls", 1397 | "tokio", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "tokio-postgres" 1402 | version = "0.7.6" 1403 | source = "git+https://github.com/MaterializeInc/rust-postgres#abff35ecc553dc23ca8f85e64945e87a93cbba28" 1404 | dependencies = [ 1405 | "async-trait", 1406 | "byteorder", 1407 | "bytes", 1408 | "fallible-iterator", 1409 | "futures", 1410 | "log", 1411 | "parking_lot 0.12.1", 1412 | "percent-encoding", 1413 | "phf", 1414 | "pin-project-lite", 1415 | "postgres-protocol", 1416 | "postgres-types", 1417 | "socket2", 1418 | "tokio", 1419 | "tokio-util", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "tokio-stream" 1424 | version = "0.1.11" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" 1427 | dependencies = [ 1428 | "futures-core", 1429 | "pin-project-lite", 1430 | "tokio", 1431 | ] 1432 | 1433 | [[package]] 1434 | name = "tokio-util" 1435 | version = "0.7.4" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 1438 | dependencies = [ 1439 | "bytes", 1440 | "futures-core", 1441 | "futures-sink", 1442 | "pin-project-lite", 1443 | "tokio", 1444 | "tracing", 1445 | ] 1446 | 1447 | [[package]] 1448 | name = "tracing" 1449 | version = "0.1.37" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1452 | dependencies = [ 1453 | "cfg-if", 1454 | "pin-project-lite", 1455 | "tracing-attributes", 1456 | "tracing-core", 1457 | ] 1458 | 1459 | [[package]] 1460 | name = "tracing-attributes" 1461 | version = "0.1.23" 1462 | source = "registry+https://github.com/rust-lang/crates.io-index" 1463 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1464 | dependencies = [ 1465 | "proc-macro2", 1466 | "quote", 1467 | "syn", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "tracing-core" 1472 | version = "0.1.30" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1475 | dependencies = [ 1476 | "once_cell", 1477 | "valuable", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "tracing-log" 1482 | version = "0.1.3" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1485 | dependencies = [ 1486 | "lazy_static", 1487 | "log", 1488 | "tracing-core", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "tracing-subscriber" 1493 | version = "0.3.16" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 1496 | dependencies = [ 1497 | "matchers", 1498 | "nu-ansi-term", 1499 | "once_cell", 1500 | "regex", 1501 | "sharded-slab", 1502 | "smallvec", 1503 | "thread_local", 1504 | "tracing", 1505 | "tracing-core", 1506 | "tracing-log", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "typenum" 1511 | version = "1.15.0" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 1514 | 1515 | [[package]] 1516 | name = "unicode-bidi" 1517 | version = "0.3.8" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1520 | 1521 | [[package]] 1522 | name = "unicode-ident" 1523 | version = "1.0.5" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1526 | 1527 | [[package]] 1528 | name = "unicode-normalization" 1529 | version = "0.1.22" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1532 | dependencies = [ 1533 | "tinyvec", 1534 | ] 1535 | 1536 | [[package]] 1537 | name = "unicode-segmentation" 1538 | version = "1.10.0" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" 1541 | 1542 | [[package]] 1543 | name = "unicode_categories" 1544 | version = "0.1.1" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 1547 | 1548 | [[package]] 1549 | name = "url" 1550 | version = "2.3.1" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 1553 | dependencies = [ 1554 | "form_urlencoded", 1555 | "idna", 1556 | "percent-encoding", 1557 | ] 1558 | 1559 | [[package]] 1560 | name = "uuid" 1561 | version = "1.2.1" 1562 | source = "registry+https://github.com/rust-lang/crates.io-index" 1563 | checksum = "feb41e78f93363bb2df8b0e86a2ca30eed7806ea16ea0c790d757cf93f79be83" 1564 | dependencies = [ 1565 | "getrandom", 1566 | ] 1567 | 1568 | [[package]] 1569 | name = "valuable" 1570 | version = "0.1.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1573 | 1574 | [[package]] 1575 | name = "vcpkg" 1576 | version = "0.2.15" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1579 | 1580 | [[package]] 1581 | name = "version_check" 1582 | version = "0.9.4" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1585 | 1586 | [[package]] 1587 | name = "wasi" 1588 | version = "0.11.0+wasi-snapshot-preview1" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1591 | 1592 | [[package]] 1593 | name = "wasm-bindgen" 1594 | version = "0.2.83" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 1597 | dependencies = [ 1598 | "cfg-if", 1599 | "wasm-bindgen-macro", 1600 | ] 1601 | 1602 | [[package]] 1603 | name = "wasm-bindgen-backend" 1604 | version = "0.2.83" 1605 | source = "registry+https://github.com/rust-lang/crates.io-index" 1606 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 1607 | dependencies = [ 1608 | "bumpalo", 1609 | "log", 1610 | "once_cell", 1611 | "proc-macro2", 1612 | "quote", 1613 | "syn", 1614 | "wasm-bindgen-shared", 1615 | ] 1616 | 1617 | [[package]] 1618 | name = "wasm-bindgen-macro" 1619 | version = "0.2.83" 1620 | source = "registry+https://github.com/rust-lang/crates.io-index" 1621 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 1622 | dependencies = [ 1623 | "quote", 1624 | "wasm-bindgen-macro-support", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "wasm-bindgen-macro-support" 1629 | version = "0.2.83" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 1632 | dependencies = [ 1633 | "proc-macro2", 1634 | "quote", 1635 | "syn", 1636 | "wasm-bindgen-backend", 1637 | "wasm-bindgen-shared", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "wasm-bindgen-shared" 1642 | version = "0.2.83" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 1645 | 1646 | [[package]] 1647 | name = "web-sys" 1648 | version = "0.3.60" 1649 | source = "registry+https://github.com/rust-lang/crates.io-index" 1650 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 1651 | dependencies = [ 1652 | "js-sys", 1653 | "wasm-bindgen", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "which" 1658 | version = "4.3.0" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" 1661 | dependencies = [ 1662 | "either", 1663 | "libc", 1664 | "once_cell", 1665 | ] 1666 | 1667 | [[package]] 1668 | name = "whoami" 1669 | version = "1.2.3" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "d6631b6a2fd59b1841b622e8f1a7ad241ef0a46f2d580464ce8140ac94cbd571" 1672 | dependencies = [ 1673 | "bumpalo", 1674 | "wasm-bindgen", 1675 | "web-sys", 1676 | ] 1677 | 1678 | [[package]] 1679 | name = "winapi" 1680 | version = "0.3.9" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1683 | dependencies = [ 1684 | "winapi-i686-pc-windows-gnu", 1685 | "winapi-x86_64-pc-windows-gnu", 1686 | ] 1687 | 1688 | [[package]] 1689 | name = "winapi-i686-pc-windows-gnu" 1690 | version = "0.4.0" 1691 | source = "registry+https://github.com/rust-lang/crates.io-index" 1692 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1693 | 1694 | [[package]] 1695 | name = "winapi-x86_64-pc-windows-gnu" 1696 | version = "0.4.0" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1699 | 1700 | [[package]] 1701 | name = "windows-sys" 1702 | version = "0.36.1" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 1705 | dependencies = [ 1706 | "windows_aarch64_msvc 0.36.1", 1707 | "windows_i686_gnu 0.36.1", 1708 | "windows_i686_msvc 0.36.1", 1709 | "windows_x86_64_gnu 0.36.1", 1710 | "windows_x86_64_msvc 0.36.1", 1711 | ] 1712 | 1713 | [[package]] 1714 | name = "windows-sys" 1715 | version = "0.42.0" 1716 | source = "registry+https://github.com/rust-lang/crates.io-index" 1717 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1718 | dependencies = [ 1719 | "windows_aarch64_gnullvm", 1720 | "windows_aarch64_msvc 0.42.0", 1721 | "windows_i686_gnu 0.42.0", 1722 | "windows_i686_msvc 0.42.0", 1723 | "windows_x86_64_gnu 0.42.0", 1724 | "windows_x86_64_gnullvm", 1725 | "windows_x86_64_msvc 0.42.0", 1726 | ] 1727 | 1728 | [[package]] 1729 | name = "windows_aarch64_gnullvm" 1730 | version = "0.42.0" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 1733 | 1734 | [[package]] 1735 | name = "windows_aarch64_msvc" 1736 | version = "0.36.1" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 1739 | 1740 | [[package]] 1741 | name = "windows_aarch64_msvc" 1742 | version = "0.42.0" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 1745 | 1746 | [[package]] 1747 | name = "windows_i686_gnu" 1748 | version = "0.36.1" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 1751 | 1752 | [[package]] 1753 | name = "windows_i686_gnu" 1754 | version = "0.42.0" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 1757 | 1758 | [[package]] 1759 | name = "windows_i686_msvc" 1760 | version = "0.36.1" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 1763 | 1764 | [[package]] 1765 | name = "windows_i686_msvc" 1766 | version = "0.42.0" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 1769 | 1770 | [[package]] 1771 | name = "windows_x86_64_gnu" 1772 | version = "0.36.1" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 1775 | 1776 | [[package]] 1777 | name = "windows_x86_64_gnu" 1778 | version = "0.42.0" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 1781 | 1782 | [[package]] 1783 | name = "windows_x86_64_gnullvm" 1784 | version = "0.42.0" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 1787 | 1788 | [[package]] 1789 | name = "windows_x86_64_msvc" 1790 | version = "0.36.1" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 1793 | 1794 | [[package]] 1795 | name = "windows_x86_64_msvc" 1796 | version = "0.42.0" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 1799 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "logicaldecoding" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.66" 10 | bytes = "1.2.1" 11 | futures = { version = "0.3.25", features = ["executor"] } 12 | sqlx = { version = "0.6.2", features = ["runtime-tokio-native-tls", "postgres", "macros", "migrate", "uuid", "json"] } 13 | tokio = { version = "1.21.2", features = ["full"] } 14 | tokio-postgres = { git = "https://github.com/MaterializeInc/rust-postgres" } 15 | tracing = "0.1.37" 16 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 17 | uuid = { version = "1.2.1", features = ["v4"] } 18 | prost = "0.11.2" 19 | 20 | [dev-dependencies] 21 | crossbeam-channel = "0.5.6" 22 | rand = "0.8.5" 23 | 24 | [build-dependencies] 25 | prost-build = "0.11.2" 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:14.5 2 | 3 | RUN apt-get update &&\ 4 | apt-get install -y \ 5 | postgresql-14-decoderbufs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mike Seddon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Logical Replication 2 | 3 | A project to play with and test Postgres [logical replication](https://www.postgresql.org/docs/current/logical-replication.html) using Rust. 4 | 5 | ## Why 6 | 7 | [Logical replication](https://www.postgresql.org/docs/current/logical-replication.html) gives the ability to subscribe to the Postgres write-ahead-log messages and decode them into usable (and transactional) data. There are many uses for this functionality, for example: 8 | 9 | - a web server could store (and invalidate) a local cache of a table in a database to prevent a database round-trip. 10 | - a notification could be sent to a user as a result of an action by another user connected to a different web server instance. 11 | 12 | [Logical replication](https://www.postgresql.org/docs/current/logical-replication.html) is lower level than the Postgres [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) functionality, causes [no performance impact](https://reorchestrate.com/posts/debezium-performance-impact/) and does not require the user to choose which tables to listen to. 13 | 14 | ## What 15 | 16 | The main test is in [types/mod.rs](./src/types/mod.rs). 17 | 18 | This test attempts to perform deterministic simulation by first attaching the `logicalreplication` listener to an empty database then: 19 | 20 | 1. Deterministically produce random batches of transactions against an in-memory representation of the table. 21 | 2. Applying the batched transactions to the Postgres database. 22 | 3. Listening to the logical replication stream and trying to apply them to a second in-memory representation of the table. 23 | 4. Stopping the test after `n` iterations and then testing that all three representations align. 24 | 25 | ## How 26 | 27 | 1. Start postgres with logical replication mode - see the `docker-compose.yaml` and the `Dockerfile` for configuration. 28 | 2. Run `cargo install sqlx-cli` to set up the [sqlx](https://github.com/launchbadge/sqlx) command line utility to allow database migrations. 29 | 3. Run `sqlx migrate run` to set up the intial database. 30 | 4. Run `cargo test`. 31 | 32 | ## Further 33 | 34 | Ideas of what would be helpful: 35 | 36 | - It would be good to build a [procedural macro](https://doc.rust-lang.org/reference/procedural-macros.html) similar to [structmap](https://crates.io/crates/structmap) which automates the generation of applying what is received from the logical decoding (effectively a vector of hashmaps) directly to structs. 37 | 38 | - This version deliberately chooses [decoderbufs](https://github.com/debezium/postgres-decoderbufs) but work could be done to ensure it works with [wal2json](https://github.com/eulerto/wal2json) too and that output data is standardised. 39 | 40 | ## Acknowledgements 41 | 42 | Thank you to: 43 | 44 | - `rust-postgres`: https://github.com/sfackler/rust-postgres/issues/116 45 | - [Materialize](https://materialize.com/)'s fork of `rust-postgres` with the patches required to support logical decoding: https://github.com/MaterializeInc/rust-postgres 46 | - `postgres-decoderbufs`: https://github.com/debezium/postgres-decoderbufs 47 | - this example: https://github.com/debate-map/app/blob/afc6467b6c6c961f7bcc7b7f901f0ff5cd79d440/Packages/app-server-rs/src/pgclient.rs 48 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | fn main() -> Result<()> { 3 | prost_build::compile_protos(&["src/pg_logicaldec.proto"], &["src/"])?; 4 | Ok(()) 5 | } 6 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | image: postgres:14.5 4 | build: 5 | context: ./ 6 | dockerfile: ./Dockerfile 7 | ports: 8 | - "5432:5432" 9 | environment: 10 | - POSTGRES_PASSWORD=password 11 | command: 12 | - "postgres" 13 | - "-c" 14 | - "wal_level=logical" -------------------------------------------------------------------------------- /migrations/20221009093923_create_tables.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS tenants CASCADE; -------------------------------------------------------------------------------- /migrations/20221009093923_create_tables.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE tenants ( 2 | tenant_id UUID NOT NULL, 3 | id UUID PRIMARY KEY NOT NULL, 4 | name TEXT NOT NULL, 5 | short_description TEXT, 6 | long_description TEXT 7 | ); -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod replication; 2 | mod types; 3 | use std::env; 4 | 5 | use anyhow::Result; 6 | use replication::Transaction; 7 | use sqlx::{migrate::Migrator, PgPool}; 8 | use tokio::task; 9 | use tracing_subscriber::EnvFilter; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | if std::env::var("RUST_LOG").is_err() { 14 | std::env::set_var("RUST_LOG", "logicaldecoding=info") 15 | } 16 | tracing_subscriber::fmt::fmt() 17 | .with_env_filter(EnvFilter::from_default_env()) 18 | .init(); 19 | 20 | let m = Migrator::new(std::path::Path::new("./migrations")).await?; 21 | let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?; 22 | m.run(&pool).await?; 23 | 24 | let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<()>(); 25 | let (tx, _rx) = tokio::sync::broadcast::channel::(100); 26 | 27 | let streaming_handle = 28 | task::spawn(async { replication::start_streaming_changes("postgres", ready_tx, tx).await }); 29 | 30 | // block waiting for replication 31 | ready_rx.await.unwrap(); 32 | 33 | streaming_handle.await.unwrap().unwrap(); 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/pg_logicaldec.proto: -------------------------------------------------------------------------------- 1 | package decoderbufs; 2 | 3 | option java_package="io.debezium.connector.postgresql.proto"; 4 | option java_outer_classname = "PgProto"; 5 | option optimize_for = SPEED; 6 | 7 | enum Op { 8 | UNKNOWN = -1; 9 | INSERT = 0; 10 | UPDATE = 1; 11 | DELETE = 2; 12 | BEGIN = 3; 13 | COMMIT = 4; 14 | } 15 | 16 | message Point { 17 | required double x = 1; 18 | required double y = 2; 19 | } 20 | 21 | message DatumMessage { 22 | optional string column_name = 1; 23 | optional int64 column_type = 2; 24 | oneof datum { 25 | int32 datum_int32 = 3; 26 | int64 datum_int64 = 4; 27 | float datum_float = 5; 28 | double datum_double = 6; 29 | bool datum_bool = 7; 30 | string datum_string = 8; 31 | bytes datum_bytes = 9; 32 | Point datum_point = 10; 33 | bool datum_missing = 11; 34 | } 35 | } 36 | 37 | message TypeInfo { 38 | required string modifier = 1; 39 | required bool value_optional = 2; 40 | } 41 | 42 | message RowMessage { 43 | optional uint32 transaction_id = 1; 44 | optional uint64 commit_time = 2; 45 | optional string table = 3; 46 | optional Op op = 4; 47 | repeated DatumMessage new_tuple = 5; 48 | repeated DatumMessage old_tuple = 6; 49 | repeated TypeInfo new_typeinfo = 7; 50 | } -------------------------------------------------------------------------------- /src/replication.rs: -------------------------------------------------------------------------------- 1 | pub mod decoderbufs { 2 | include!(concat!(env!("OUT_DIR"), "/decoderbufs.rs")); 3 | } 4 | use bytes::{BufMut, BytesMut}; 5 | use decoderbufs::{Op, RowMessage}; 6 | use futures::{ 7 | future::{self}, 8 | ready, Sink, StreamExt, 9 | }; 10 | use prost::Message; 11 | use std::{ 12 | task::Poll, 13 | time::{SystemTime, UNIX_EPOCH}, 14 | }; 15 | use tokio::sync::{broadcast, oneshot}; 16 | use tokio_postgres::{NoTls, SimpleQueryMessage}; 17 | use tracing::{debug, trace}; 18 | 19 | static MICROSECONDS_FROM_UNIX_EPOCH_TO_2000: u128 = 946_684_800_000_000; 20 | 21 | #[derive(Debug, Clone)] 22 | #[allow(dead_code)] 23 | pub struct Transaction { 24 | pub xid: u32, 25 | pub commit_time: u64, 26 | pub events: Vec, 27 | } 28 | 29 | /// starts streaming changes 30 | pub async fn start_streaming_changes( 31 | database: impl Into + std::fmt::Display, 32 | ready: oneshot::Sender<()>, 33 | tx: broadcast::Sender, 34 | ) -> Result<(), tokio_postgres::Error> { 35 | let db_config = format!( 36 | "user=postgres password=password host=localhost port=5432 dbname={} replication=database", 37 | database 38 | ); 39 | println!("CONNECT"); 40 | 41 | // connect to the database 42 | let (client, connection) = tokio_postgres::connect(&db_config, NoTls).await.unwrap(); 43 | 44 | // the connection object performs the actual communication with the database, so spawn it off to run on its own 45 | tokio::spawn(async move { connection.await }); 46 | 47 | //let slot_name = "slot"; 48 | let slot_name = "slot_".to_owned() 49 | + &SystemTime::now() 50 | .duration_since(UNIX_EPOCH) 51 | .unwrap() 52 | .as_millis() 53 | .to_string(); 54 | let slot_query = format!( 55 | "CREATE_REPLICATION_SLOT {} TEMPORARY LOGICAL \"decoderbufs\"", 56 | slot_name 57 | ); 58 | 59 | let lsn = client 60 | .simple_query(&slot_query) 61 | .await 62 | .unwrap() 63 | .into_iter() 64 | .filter_map(|msg| match msg { 65 | SimpleQueryMessage::Row(row) => Some(row), 66 | _ => None, 67 | }) 68 | .collect::>() 69 | .first() 70 | .unwrap() 71 | .get("consistent_point") 72 | .unwrap() 73 | .to_owned(); 74 | 75 | let query = format!("START_REPLICATION SLOT {} LOGICAL {}", slot_name, lsn); 76 | let duplex_stream = client 77 | .copy_both_simple::(&query) 78 | .await 79 | .unwrap(); 80 | let mut duplex_stream_pin = Box::pin(duplex_stream); 81 | 82 | // see here for format details: https://www.postgresql.org/docs/current/protocol-replication.html 83 | let mut keepalive = BytesMut::with_capacity(34); 84 | keepalive.put_u8(b'r'); 85 | // the last 8 bytes of these are overwritten with a timestamp to meet the protocol spec 86 | keepalive.put_bytes(0, 32); 87 | keepalive.put_u8(1); 88 | 89 | // set the timestamp of the keepalive message 90 | keepalive[26..34].swap_with_slice( 91 | &mut ((SystemTime::now() 92 | .duration_since(UNIX_EPOCH) 93 | .unwrap() 94 | .as_micros() 95 | - MICROSECONDS_FROM_UNIX_EPOCH_TO_2000) as u64) 96 | .to_be_bytes(), 97 | ); 98 | 99 | // send the keepalive to ensure connection is functioning 100 | future::poll_fn(|cx| { 101 | ready!(duplex_stream_pin.as_mut().poll_ready(cx)).unwrap(); 102 | duplex_stream_pin 103 | .as_mut() 104 | .start_send(keepalive.clone().into()) 105 | .unwrap(); 106 | ready!(duplex_stream_pin.as_mut().poll_flush(cx)).unwrap(); 107 | Poll::Ready(()) 108 | }) 109 | .await; 110 | 111 | // notify ready 112 | ready.send(()).unwrap(); 113 | 114 | let mut transaction = None; 115 | loop { 116 | match duplex_stream_pin.as_mut().next().await { 117 | None => break, 118 | Some(Err(_)) => continue, 119 | // type: XLogData (WAL data, ie. change of data in db) 120 | Some(Ok(event)) if event[0] == b'w' => { 121 | let row_message = RowMessage::decode(&event[25..]).unwrap(); 122 | debug!("Got XLogData/data-change event: {:?}", row_message); 123 | 124 | match row_message.op { 125 | Some(op) if op == Op::Begin as i32 => { 126 | transaction = Some(Transaction { 127 | xid: row_message.transaction_id(), 128 | commit_time: row_message.commit_time(), 129 | events: vec![], 130 | }) 131 | } 132 | Some(op) if op == Op::Commit as i32 => { 133 | debug!("{:?}", &transaction.as_ref().unwrap()); 134 | let transaction = transaction.take().unwrap(); 135 | tx.send(transaction).unwrap(); 136 | } 137 | Some(_) => { 138 | transaction.as_mut().unwrap().events.push(row_message); 139 | } 140 | None => unimplemented!(), 141 | } 142 | } 143 | // type: keepalive message 144 | Some(Ok(event)) if event[0] == b'k' => { 145 | let last_byte = event.last().unwrap(); 146 | let timeout_imminent = last_byte == &1; 147 | trace!( 148 | "Got keepalive message:{:x?} @timeoutImminent:{}", 149 | event, 150 | timeout_imminent 151 | ); 152 | if timeout_imminent { 153 | keepalive[26..34].swap_with_slice( 154 | &mut ((SystemTime::now() 155 | .duration_since(UNIX_EPOCH) 156 | .unwrap() 157 | .as_micros() 158 | - MICROSECONDS_FROM_UNIX_EPOCH_TO_2000) 159 | as u64) 160 | .to_be_bytes(), 161 | ); 162 | 163 | trace!( 164 | "Trying to send response to keepalive message/warning!:{:x?}", 165 | keepalive 166 | ); 167 | 168 | future::poll_fn(|cx| { 169 | ready!(duplex_stream_pin.as_mut().poll_ready(cx)).unwrap(); 170 | duplex_stream_pin 171 | .as_mut() 172 | .start_send(keepalive.clone().into()) 173 | .unwrap(); 174 | ready!(duplex_stream_pin.as_mut().poll_flush(cx)).unwrap(); 175 | Poll::Ready(()) 176 | }) 177 | .await; 178 | 179 | trace!( 180 | "Sent response to keepalive message/warning!:{:x?}", 181 | keepalive 182 | ); 183 | } 184 | } 185 | _ => (), 186 | } 187 | } 188 | 189 | Ok(()) 190 | } 191 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod tenant; 2 | use crate::replication::decoderbufs::RowMessage; 3 | pub use tenant::Tenant; 4 | 5 | #[derive(Debug)] 6 | #[allow(dead_code)] 7 | struct Transaction { 8 | xid: u32, 9 | commit_time: u64, 10 | changes: Vec, 11 | } 12 | 13 | #[cfg(test)] 14 | mod test { 15 | use crate::replication::Transaction; 16 | 17 | use super::Tenant; 18 | use crate::replication::decoderbufs::{datum_message::Datum, Op}; 19 | use anyhow::Result; 20 | use rand::{rngs::StdRng, Rng, SeedableRng}; 21 | use sqlx::PgPool; 22 | use std::{collections::HashMap, time::Duration}; 23 | use tokio::task; 24 | use tracing::trace; 25 | use uuid::Uuid; 26 | 27 | fn gen_uuid(rng: &mut StdRng) -> Uuid { 28 | let mut b = [0u8; 16]; 29 | rng.fill(&mut b[..]); 30 | Uuid::from_bytes(b) 31 | } 32 | 33 | async fn subscriber( 34 | tx: tokio::sync::broadcast::Sender, 35 | mut done: tokio::sync::mpsc::Receiver<()>, 36 | ) -> (usize, HashMap) { 37 | let mut rx = tx.subscribe(); 38 | let mut transactions = 0; 39 | let mut tenants = HashMap::new(); 40 | 41 | loop { 42 | tokio::select! { 43 | _ = done.recv() => { 44 | break 45 | } 46 | Ok(transaction) = rx.recv() => { 47 | transactions += 1; 48 | println!("SUBSCRIBER {:?}", transaction.xid ); 49 | 50 | transaction.events.iter().for_each(|event| { 51 | match Op::from_i32(event.op.unwrap()).unwrap() { 52 | Op::Insert => { 53 | let inserts = event 54 | .new_tuple 55 | .iter() 56 | .map(|datum| (datum.column_name(), datum)) 57 | .collect::>(); 58 | 59 | let id = match &inserts.get("id").unwrap().datum.as_ref().unwrap() { 60 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 61 | _ => unimplemented!(), 62 | }; 63 | 64 | tenants.insert( 65 | id, 66 | Tenant { 67 | xmin: Some(event.transaction_id() as i64), 68 | tenant_id: match &inserts 69 | .get("tenant_id") 70 | .unwrap() 71 | .datum 72 | .as_ref() 73 | .unwrap() 74 | { 75 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 76 | _ => unimplemented!(), 77 | }, 78 | id: match &inserts.get("id").unwrap().datum.as_ref().unwrap() { 79 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 80 | _ => unimplemented!(), 81 | }, 82 | name: match &inserts.get("name").unwrap().datum.as_ref().unwrap() { 83 | Datum::DatumString(value) => value.to_string(), 84 | _ => unimplemented!(), 85 | }, 86 | short_description: match &inserts 87 | .get("short_description") 88 | .unwrap() 89 | .datum 90 | .as_ref() 91 | { 92 | Some(Datum::DatumString(value)) => Some(value.to_string()), 93 | _ => None, 94 | }, 95 | long_description: match &inserts 96 | .get("long_description") 97 | .unwrap() 98 | .datum 99 | .as_ref() 100 | { 101 | Some(Datum::DatumString(value)) => Some(value.to_string()), 102 | _ => None, 103 | }, 104 | }, 105 | ); 106 | } 107 | Op::Update => { 108 | let mut updates = event 109 | .new_tuple 110 | .iter() 111 | .map(|datum| (datum.column_name(), datum)) 112 | .collect::>(); 113 | 114 | let id = match &updates.remove("id").unwrap().datum.as_ref().unwrap() { 115 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 116 | _ => unimplemented!(), 117 | }; 118 | 119 | let mut tenant = tenants.get_mut(&id).unwrap(); 120 | tenant.xmin = Some(event.transaction_id() as i64); 121 | 122 | updates.into_iter().for_each(|(k, v)| match k { 123 | "tenant_id" => { 124 | tenant.tenant_id = match v.datum.as_ref().unwrap() { 125 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 126 | _ => unimplemented!(), 127 | } 128 | } 129 | "name" => { 130 | tenant.name = match v.datum.as_ref().unwrap() { 131 | Datum::DatumString(value) => value.to_string(), 132 | _ => unimplemented!(), 133 | } 134 | } 135 | "short_description" => { 136 | tenant.short_description = match v.datum.as_ref() { 137 | Some(Datum::DatumString(value)) => Some(value.to_string()), 138 | _ => None, 139 | } 140 | } 141 | "long_description" => { 142 | tenant.long_description = match v.datum.as_ref() { 143 | Some(Datum::DatumString(value)) => Some(value.to_string()), 144 | _ => None, 145 | } 146 | } 147 | k => panic!("unimplemented key: {:?}", k), 148 | }) 149 | } 150 | Op::Delete => { 151 | let mut deletes = event 152 | .old_tuple 153 | .iter() 154 | .map(|datum| (datum.column_name(), datum)) 155 | .collect::>(); 156 | 157 | let id = match &deletes.remove("id").unwrap().datum.as_ref().unwrap() { 158 | Datum::DatumString(value) => Uuid::parse_str(&value).unwrap(), 159 | _ => unimplemented!(), 160 | }; 161 | 162 | tenants.remove(&id); 163 | } 164 | Op::Begin => unreachable!(), 165 | Op::Commit => unreachable!(), 166 | Op::Unknown => unreachable!(), 167 | }; 168 | }); 169 | } 170 | } 171 | } 172 | 173 | (transactions, tenants) 174 | } 175 | 176 | /// this test tries an end to end cycle of creating records, updating and applying them to both the database 177 | /// and an in memory hashmap representation of the table 178 | #[sqlx::test] 179 | async fn test_create(db: PgPool) -> Result<()> { 180 | let iterations = 10; 181 | let interval = Duration::from_millis(100); 182 | 183 | let current_database = { 184 | let mut conn = db.acquire().await.unwrap(); 185 | sqlx::query!("SELECT current_database()") 186 | .fetch_one(&mut conn) 187 | .await? 188 | .current_database 189 | .unwrap() 190 | }; 191 | 192 | let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<()>(); 193 | let (tx, _) = tokio::sync::broadcast::channel::(100); 194 | 195 | let tx_clone = tx.clone(); 196 | let listener_handle = task::spawn(async move { 197 | crate::replication::start_streaming_changes(current_database, ready_tx, tx_clone).await 198 | }); 199 | 200 | // block waiting for replication 201 | ready_rx.await.unwrap(); 202 | 203 | let tx_clone = tx.clone(); 204 | 205 | let (done_tx, done_rx) = tokio::sync::mpsc::channel::<()>(1); 206 | 207 | let subscriber_handle = task::spawn(async move { subscriber(tx_clone, done_rx).await }); 208 | 209 | let db_clone = db.clone(); 210 | let generator_handle = task::spawn(async move { 211 | let mut interval = tokio::time::interval(interval); 212 | let mut rng = StdRng::seed_from_u64(42); 213 | let mut tenants: HashMap = HashMap::new(); 214 | let mut transactions = 0_usize; 215 | 216 | for _ in 0..iterations { 217 | interval.tick().await; 218 | 219 | // create transactions 220 | let mut txn = db_clone.begin().await.unwrap(); 221 | let xid = sqlx::query!("SELECT pg_current_xact_id()::TEXT::BIGINT AS xid") 222 | .fetch_one(&mut *txn) 223 | .await 224 | .unwrap() 225 | .xid 226 | .unwrap(); 227 | trace!("BEGIN {:?}", xid); 228 | 229 | let rollback = tenants.clone(); 230 | 231 | for _ in 0..rng.gen_range(1..10) { 232 | match rng.gen_range(0..=2) { 233 | // create 234 | 0 => { 235 | let key = gen_uuid(&mut rng); 236 | let mut tenant = Tenant { 237 | xmin: None, 238 | tenant_id: key, 239 | id: key, 240 | name: gen_uuid(&mut rng).to_string(), 241 | short_description: Some(gen_uuid(&mut rng).to_string()), 242 | long_description: Some(gen_uuid(&mut rng).to_string()), 243 | }; 244 | 245 | tenant.create(&mut *txn).await.unwrap(); 246 | trace!("CREATE {:?} {:?}", tenant.xmin, tenant.id); 247 | 248 | tenants.insert(key, tenant); 249 | } 250 | // update 251 | 1 => { 252 | if !tenants.is_empty() { 253 | let keys = tenants.keys().cloned().collect::>(); 254 | if let Some(key) = keys.get(rng.gen_range(0..keys.len())) { 255 | let mut tenant = tenants.remove(key).unwrap(); 256 | tenant.name = gen_uuid(&mut rng).to_string(); 257 | tenant.short_description = Some(gen_uuid(&mut rng).to_string()); 258 | tenant.long_description = if rng.gen_bool(0.5) { 259 | Some(gen_uuid(&mut rng).to_string()) 260 | } else { 261 | None 262 | }; 263 | 264 | tenant.update(&mut *txn).await.unwrap(); 265 | trace!("UPDATE {:?} {:?}", tenant.xmin, tenant.id); 266 | tenants.insert(*key, tenant); 267 | } 268 | } 269 | } 270 | // delete 271 | 2 => { 272 | if !tenants.is_empty() { 273 | let keys = tenants.keys().cloned().collect::>(); 274 | if let Some(key) = keys.get(rng.gen_range(0..keys.len())) { 275 | let tenant = tenants.remove(key).unwrap(); 276 | trace!("DELETE {:?} {:?}", tenant.xmin, tenant.id); 277 | tenant.delete(&mut *txn).await.unwrap(); 278 | } 279 | } 280 | } 281 | _ => (), 282 | }; 283 | } 284 | 285 | match rng.gen_bool(0.1) { 286 | true => { 287 | trace!("ROLLBACK {:?}", xid); 288 | txn.rollback().await.unwrap(); 289 | tenants = rollback; 290 | } 291 | false => { 292 | trace!("COMMIT {:?}", xid); 293 | txn.commit().await.unwrap(); 294 | transactions += 1; 295 | } 296 | } 297 | } 298 | 299 | tokio::time::sleep(Duration::from_millis(500)).await; 300 | (transactions, tenants) 301 | }); 302 | 303 | // shutdown generator 304 | let (generator_transactions, tenants) = generator_handle.await.unwrap(); 305 | done_tx.send(()).await.unwrap(); 306 | listener_handle.abort(); 307 | let (subscriber_transactions, subscriber) = subscriber_handle.await.unwrap(); 308 | println!( 309 | "generator_transactions {:?} subscriber_transactions {:?}", 310 | generator_transactions, subscriber_transactions 311 | ); 312 | 313 | // retrieve tenants from db 314 | let mut conn = db.acquire().await.unwrap(); 315 | let actual_tenants = Tenant::retrieve_all(&mut conn) 316 | .await 317 | .unwrap() 318 | .into_iter() 319 | .map(|tenant| (tenant.id, tenant)) 320 | .collect::>(); 321 | 322 | // test the generator / database / subscriber produce the same results 323 | assert_eq!(tenants, actual_tenants); 324 | assert_eq!(subscriber, actual_tenants); 325 | 326 | Ok(()) 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/types/tenant/fixtures/tenants.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | tenants( 3 | "tenant_id", 4 | "id", 5 | "name", 6 | "short_description", 7 | "long_description" 8 | ) 9 | VALUES 10 | ( 11 | E'c497c1be-cf70-41aa-8665-971e2ffaefcd', 12 | E'c497c1be-cf70-41aa-8665-971e2ffaefcd', 13 | E'tenant0', 14 | NULL, 15 | NULL 16 | ); -------------------------------------------------------------------------------- /src/types/tenant/mod.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use sqlx::PgConnection; 3 | use uuid::Uuid; 4 | 5 | #[derive(Clone, Debug, Eq, PartialEq)] 6 | pub struct Tenant { 7 | pub xmin: Option, 8 | pub tenant_id: Uuid, 9 | pub id: Uuid, 10 | pub name: String, 11 | pub short_description: Option, 12 | pub long_description: Option, 13 | } 14 | 15 | impl Tenant { 16 | #[allow(dead_code)] 17 | pub async fn create(&mut self, conn: &mut PgConnection) -> Result { 18 | self.xmin = sqlx::query_file!( 19 | "src/types/tenant/queries/create.sql", 20 | self.id, 21 | self.name, 22 | self.short_description, 23 | self.long_description 24 | ) 25 | .fetch_one(&mut *conn) 26 | .await? 27 | .xmin; 28 | 29 | Ok(1) 30 | } 31 | 32 | #[allow(dead_code)] 33 | pub async fn retrieve(conn: &mut PgConnection, id: Uuid) -> Result { 34 | Ok( 35 | sqlx::query_file_as!(Self, "src/types/tenant/queries/retrieve.sql", id) 36 | .fetch_one(&mut *conn) 37 | .await?, 38 | ) 39 | } 40 | 41 | #[allow(dead_code)] 42 | pub async fn retrieve_many(conn: &mut PgConnection, ids: &[Uuid]) -> Result> { 43 | Ok( 44 | sqlx::query_file_as!(Self, "src/types/tenant/queries/retrieve_many.sql", ids) 45 | .fetch_all(&mut *conn) 46 | .await?, 47 | ) 48 | } 49 | 50 | #[allow(dead_code)] 51 | pub async fn retrieve_all(conn: &mut PgConnection) -> Result> { 52 | Ok( 53 | sqlx::query_file_as!(Self, "src/types/tenant/queries/retrieve_all.sql") 54 | .fetch_all(&mut *conn) 55 | .await?, 56 | ) 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub async fn update(&mut self, conn: &mut PgConnection) -> Result { 61 | self.xmin = sqlx::query_file!( 62 | "src/types/tenant/queries/update.sql", 63 | self.id, 64 | self.name, 65 | self.short_description, 66 | self.long_description 67 | ) 68 | .fetch_one(&mut *conn) 69 | .await? 70 | .xmin; 71 | 72 | Ok(1) 73 | } 74 | 75 | #[allow(dead_code)] 76 | pub async fn delete(&self, conn: &mut PgConnection) -> Result { 77 | Ok( 78 | sqlx::query_file!("src/types/tenant/queries/delete.sql", self.id) 79 | .execute(&mut *conn) 80 | .await? 81 | .rows_affected(), 82 | ) 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod test { 88 | use super::*; 89 | use sqlx::PgPool; 90 | 91 | #[sqlx::test(fixtures("tenants"))] 92 | async fn test_retrieve(db: PgPool) -> Result<()> { 93 | let mut conn = db.acquire().await.unwrap(); 94 | 95 | Tenant::retrieve( 96 | &mut conn, 97 | Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?, 98 | ) 99 | .await?; 100 | 101 | Ok(()) 102 | } 103 | 104 | #[sqlx::test(fixtures("tenants"))] 105 | async fn test_retrieve_many(db: PgPool) -> Result<()> { 106 | let mut conn = db.acquire().await.unwrap(); 107 | 108 | let tenants = Tenant::retrieve_many( 109 | &mut conn, 110 | &[Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?], 111 | ) 112 | .await?; 113 | 114 | assert_eq!(tenants.len(), 1); 115 | 116 | Ok(()) 117 | } 118 | 119 | #[sqlx::test(fixtures("tenants"))] 120 | async fn test_retrieve_all(db: PgPool) -> Result<()> { 121 | let mut conn = db.acquire().await.unwrap(); 122 | 123 | let tenants = Tenant::retrieve_all(&mut conn).await?; 124 | 125 | assert_eq!(tenants.len(), 1); 126 | 127 | Ok(()) 128 | } 129 | 130 | #[sqlx::test(fixtures("tenants"))] 131 | async fn test_update(db: PgPool) -> Result<()> { 132 | let mut txn = db.begin().await.unwrap(); 133 | let mut tenant = Tenant::retrieve( 134 | &mut *txn, 135 | Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?, 136 | ) 137 | .await?; 138 | 139 | tenant.name = "NAME".to_string(); 140 | tenant.short_description = Some("SHORT_DESCRIPTION".to_string()); 141 | tenant.long_description = Some("LONG_DESCRIPTION".to_string()); 142 | 143 | let rows_affected = tenant.update(&mut *txn).await?; 144 | assert_eq!(rows_affected, 1); 145 | 146 | txn.commit().await.unwrap(); 147 | 148 | let mut conn = db.acquire().await.unwrap(); 149 | let new_tenant = Tenant::retrieve( 150 | &mut conn, 151 | Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?, 152 | ) 153 | .await?; 154 | 155 | assert_eq!(tenant.name, new_tenant.name); 156 | assert_eq!(tenant.short_description, new_tenant.short_description); 157 | assert_eq!(tenant.long_description, new_tenant.long_description); 158 | 159 | Ok(()) 160 | } 161 | 162 | #[sqlx::test(fixtures("tenants"))] 163 | async fn test_delete(db: PgPool) -> Result<()> { 164 | let mut conn = db.acquire().await.unwrap(); 165 | 166 | let tenant = Tenant::retrieve( 167 | &mut conn, 168 | Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?, 169 | ) 170 | .await?; 171 | 172 | let rows_affected = tenant.delete(&mut conn).await?; 173 | assert_eq!(rows_affected, 1); 174 | 175 | assert!(Tenant::retrieve( 176 | &mut conn, 177 | Uuid::parse_str("c497c1be-cf70-41aa-8665-971e2ffaefcd")?, 178 | ) 179 | .await 180 | .map_err(|err| err.to_string()) 181 | .unwrap_err() 182 | .contains("no rows returned")); 183 | 184 | Ok(()) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/types/tenant/queries/create.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO 2 | tenants (tenant_id, id, name, short_description, long_description) 3 | VALUES 4 | ($1, $1, $2, $3, $4) RETURNING xmin::text::bigint; -------------------------------------------------------------------------------- /src/types/tenant/queries/delete.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM 2 | tenants 3 | WHERE 4 | id = $1; -------------------------------------------------------------------------------- /src/types/tenant/queries/retrieve.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | xmin::text::bigint AS xmin, 3 | * 4 | FROM 5 | tenants 6 | WHERE 7 | id = $1; -------------------------------------------------------------------------------- /src/types/tenant/queries/retrieve_all.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | xmin::text::bigint AS xmin, 3 | * 4 | FROM 5 | tenants; -------------------------------------------------------------------------------- /src/types/tenant/queries/retrieve_many.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | xmin::text::bigint AS xmin, 3 | * 4 | FROM 5 | tenants 6 | WHERE 7 | id = ANY($1); -------------------------------------------------------------------------------- /src/types/tenant/queries/update.sql: -------------------------------------------------------------------------------- 1 | UPDATE 2 | tenants 3 | SET 4 | name = $2, 5 | short_description = $3, 6 | long_description = $4 7 | WHERE 8 | id = $1 9 | RETURNING xmin::text::bigint; --------------------------------------------------------------------------------