feat(outbound_mysql): Add MySQL support

Co-authored-by: Konstantin Shabanov <mail@etehtsea.me>
Signed-off-by: Konstantin Shabanov <mail@etehtsea.me>
This commit is contained in:
itowlson 2022-09-15 16:17:30 +12:00 committed by Konstantin Shabanov
parent a9a9adabb0
commit d16172bec2
No known key found for this signature in database
GPG Key ID: 3A39000B42A2132E
16 changed files with 1178 additions and 5 deletions

597
Cargo.lock generated
View File

@ -140,6 +140,17 @@ dependencies = [
"getrandom 0.2.7",
]
[[package]]
name = "bigdecimal"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "bincode"
version = "1.3.3"
@ -149,6 +160,25 @@ dependencies = [
"serde",
]
[[package]]
name = "bindgen"
version = "0.59.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
]
[[package]]
name = "bindle"
version = "0.8.0"
@ -197,6 +227,18 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
@ -226,6 +268,51 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "borsh"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa"
dependencies = [
"borsh-derive",
"hashbrown 0.11.2",
]
[[package]]
name = "borsh-derive"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775"
dependencies = [
"borsh-derive-internal",
"borsh-schema-derive-internal",
"proc-macro-crate",
"proc-macro2",
"syn",
]
[[package]]
name = "borsh-derive-internal"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "borsh-schema-derive-internal"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bstr"
version = "0.2.17"
@ -244,6 +331,27 @@ version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "bytecheck"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f"
dependencies = [
"bytecheck_derive",
"ptr_meta",
]
[[package]]
name = "bytecheck_derive"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "byteorder"
version = "1.4.3"
@ -344,6 +452,15 @@ dependencies = [
"jobserver",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -375,6 +492,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "clang-sys"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "clap"
version = "2.34.0"
@ -456,6 +584,15 @@ dependencies = [
"uuid",
]
[[package]]
name = "cmake"
version = "0.1.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c"
dependencies = [
"cc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -604,7 +741,7 @@ checksum = "0a6dccc0b16b7b8c1278162e436beebb35f3d321743b639d2b578138d630f43e"
dependencies = [
"cranelift-entity",
"fxhash",
"hashbrown",
"hashbrown 0.12.3",
"indexmap",
"log",
"smallvec",
@ -711,6 +848,20 @@ dependencies = [
"itertools",
]
[[package]]
name = "crossbeam"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
dependencies = [
"cfg-if",
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
@ -745,6 +896,16 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
@ -1200,6 +1361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [
"crc32fast",
"libz-sys",
"miniz_oxide",
]
@ -1233,6 +1395,70 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "frunk"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89c703bf50009f383a0873845357cc400a95fc535f836feddfe015d7df6e1e0"
dependencies = [
"frunk_core",
"frunk_derives",
"frunk_proc_macros",
]
[[package]]
name = "frunk_core"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a446d01a558301dca28ef43222864a9fa2bd9a2e71370f769d5d5d5ec9f3537"
[[package]]
name = "frunk_derives"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b83164912bb4c97cfe0772913c7af7387ee2e00cb6d4636fb65a35b3d0c8f173"
dependencies = [
"frunk_proc_macro_helpers",
"quote",
"syn",
]
[[package]]
name = "frunk_proc_macro_helpers"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "015425591bbeb0f5b8a75593340f1789af428e9f887a4f1e36c0c471f067ef50"
dependencies = [
"frunk_core",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "frunk_proc_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea01524f285deab48affffb342b97f186e657b119c3f1821ac531780e0fbfae0"
dependencies = [
"frunk_core",
"frunk_proc_macros_impl",
"proc-macro-hack",
]
[[package]]
name = "frunk_proc_macros_impl"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a802d974cc18ee7fe1a7868fc9ce31086294fd96ba62f8da64ecb44e92a2653"
dependencies = [
"frunk_core",
"frunk_proc_macro_helpers",
"proc-macro-hack",
"quote",
"syn",
]
[[package]]
name = "fs-set-times"
version = "0.17.1"
@ -1260,6 +1486,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
version = "0.3.24"
@ -1459,6 +1691,15 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1697,7 +1938,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
"serde",
]
@ -1836,12 +2077,91 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "lexical"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6"
dependencies = [
"lexical-core",
]
[[package]]
name = "lexical-core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46"
dependencies = [
"lexical-parse-float",
"lexical-parse-integer",
"lexical-util",
"lexical-write-float",
"lexical-write-integer",
]
[[package]]
name = "lexical-parse-float"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f"
dependencies = [
"lexical-parse-integer",
"lexical-util",
"static_assertions",
]
[[package]]
name = "lexical-parse-integer"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9"
dependencies = [
"lexical-util",
"static_assertions",
]
[[package]]
name = "lexical-util"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc"
dependencies = [
"static_assertions",
]
[[package]]
name = "lexical-write-float"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862"
dependencies = [
"lexical-util",
"lexical-write-integer",
"static_assertions",
]
[[package]]
name = "lexical-write-integer"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446"
dependencies = [
"lexical-util",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.135"
@ -1860,6 +2180,16 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "libz-sys"
version = "1.1.8"
@ -1970,7 +2300,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
dependencies = [
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
@ -2046,6 +2376,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.5.4"
@ -2067,6 +2403,75 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "mysql_async"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456207bb9636a0fdade67a64cea7bdebe6730c3c16ee5e34f2c481838ee5a39e"
dependencies = [
"bytes",
"crossbeam",
"flate2",
"futures-core",
"futures-sink",
"futures-util",
"lazy_static",
"lru",
"mio",
"mysql_common",
"native-tls",
"once_cell",
"pem",
"percent-encoding",
"pin-project",
"serde",
"serde_json",
"socket2",
"thiserror",
"tokio",
"tokio-native-tls",
"tokio-util 0.7.4",
"twox-hash",
"url",
]
[[package]]
name = "mysql_common"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "522f2f30f72de409fc04f88df25a031f98cfc5c398a94e0b892cabb33a1464cb"
dependencies = [
"base64",
"bigdecimal",
"bindgen",
"bitflags",
"bitvec",
"byteorder",
"bytes",
"cc",
"cmake",
"crc32fast",
"flate2",
"frunk",
"lazy_static",
"lexical",
"num-bigint",
"num-traits",
"rand 0.8.5",
"regex",
"rust_decimal",
"saturating",
"serde",
"serde_json",
"sha-1",
"sha2 0.10.6",
"smallvec",
"subprocess",
"thiserror",
"time 0.3.15",
"uuid",
]
[[package]]
name = "native-tls"
version = "0.2.10"
@ -2109,6 +2514,16 @@ dependencies = [
"libc",
]
[[package]]
name = "nom"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -2195,7 +2610,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"crc32fast",
"hashbrown",
"hashbrown 0.12.3",
"indexmap",
"memchr",
]
@ -2306,6 +2721,19 @@ dependencies = [
"wit-bindgen-wasmtime",
]
[[package]]
name = "outbound-mysql"
version = "0.6.0"
dependencies = [
"anyhow",
"mysql_async",
"mysql_common",
"spin-core",
"tokio",
"tracing",
"wit-bindgen-wasmtime",
]
[[package]]
name = "outbound-pg"
version = "0.6.0"
@ -2414,6 +2842,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]]
name = "pem"
version = "1.1.0"
@ -2592,6 +3026,15 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@ -2664,6 +3107,26 @@ dependencies = [
"cc",
]
[[package]]
name = "ptr_meta"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
dependencies = [
"ptr_meta_derive",
]
[[package]]
name = "ptr_meta_derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pulldown-cmark"
version = "0.8.0"
@ -2684,6 +3147,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.7.3"
@ -2866,6 +3335,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "rend"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95"
dependencies = [
"bytecheck",
]
[[package]]
name = "reqwest"
version = "0.11.12"
@ -2925,6 +3403,31 @@ dependencies = [
"winapi",
]
[[package]]
name = "rkyv"
version = "0.7.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15"
dependencies = [
"bytecheck",
"hashbrown 0.12.3",
"ptr_meta",
"rend",
"rkyv_derive",
"seahash",
]
[[package]]
name = "rkyv_derive"
version = "0.7.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "rpassword"
version = "7.0.0"
@ -2935,12 +3438,36 @@ dependencies = [
"winapi",
]
[[package]]
name = "rust_decimal"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c321ee4e17d2b7abe12b5d20c1231db708dd36185c8a21e9de5fed6da4dbe9"
dependencies = [
"arrayvec",
"borsh",
"bytecheck",
"byteorder",
"bytes",
"num-traits",
"rand 0.8.5",
"rkyv",
"serde",
"serde_json",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustify"
version = "0.5.3"
@ -3052,6 +3579,12 @@ dependencies = [
"regex",
]
[[package]]
name = "saturating"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71"
[[package]]
name = "schannel"
version = "0.1.20"
@ -3084,6 +3617,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "seahash"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
[[package]]
name = "security-framework"
version = "2.7.0"
@ -3197,6 +3736,17 @@ dependencies = [
"serde",
]
[[package]]
name = "sha-1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.5",
]
[[package]]
name = "sha1"
version = "0.6.1"
@ -3265,6 +3815,12 @@ dependencies = [
"dirs 4.0.0",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "signal-hook"
version = "0.3.14"
@ -3731,6 +4287,7 @@ dependencies = [
"dirs 4.0.0",
"futures",
"outbound-http",
"outbound-mysql",
"outbound-pg",
"outbound-redis",
"sanitize-filename",
@ -3755,6 +4312,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "stringprep"
version = "0.1.2"
@ -3845,6 +4408,12 @@ dependencies = [
"winx",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.38"
@ -4232,6 +4801,17 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "twox-hash"
version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if",
"rand 0.8.5",
"static_assertions",
]
[[package]]
name = "typenum"
version = "1.15.0"
@ -5112,6 +5692,15 @@ dependencies = [
"wast 35.0.2",
]
[[package]]
name = "wyz"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "xattr"
version = "0.2.3"

View File

@ -0,0 +1,17 @@
[package]
name = "outbound-mysql"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
[lib]
doctest = false
[dependencies]
anyhow = "1.0"
mysql_async = "0.30.0"
mysql_common = "0.29.1"
spin-core = { path = "../core" }
tokio = { version = "1", features = [ "rt-multi-thread" ] }
tracing = { version = "0.1", features = [ "log" ] }
wit-bindgen-wasmtime = { workspace = true }

View File

@ -0,0 +1,211 @@
use mysql_async::consts::ColumnType;
use mysql_async::{from_value_opt, prelude::*};
pub use outbound_mysql::add_to_linker;
use spin_core::HostComponent;
use std::sync::Arc;
use wit_bindgen_wasmtime::async_trait;
wit_bindgen_wasmtime::export!({paths: ["../../wit/ephemeral/outbound-mysql.wit"], async: *});
use outbound_mysql::*;
/// A simple implementation to support outbound mysql connection
#[derive(Default, Clone)]
pub struct OutboundMysql;
impl HostComponent for OutboundMysql {
type Data = Self;
fn add_to_linker<T: Send>(
linker: &mut spin_core::Linker<T>,
get: impl Fn(&mut spin_core::Data<T>) -> &mut Self::Data + Send + Sync + Copy + 'static,
) -> anyhow::Result<()> {
outbound_mysql::add_to_linker(linker, get)
}
fn build_data(&self) -> Self::Data {
Default::default()
}
}
#[async_trait]
impl outbound_mysql::OutboundMysql for OutboundMysql {
async fn execute(
&mut self,
address: &str,
statement: &str,
params: Vec<ParameterValue<'_>>,
) -> Result<(), MysqlError> {
let connection_pool = mysql_async::Pool::new(address);
let mut connection = connection_pool
.get_conn()
.await
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?;
let db_params = params
.iter()
.map(to_sql_parameter)
.collect::<anyhow::Result<Vec<_>>>()
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
let parameters = mysql_async::Params::Positional(db_params);
connection
.exec_batch(statement, &[parameters])
.await
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
Ok(())
}
async fn query(
&mut self,
address: &str,
statement: &str,
params: Vec<ParameterValue<'_>>,
) -> Result<RowSet, MysqlError> {
let connection_pool = mysql_async::Pool::new(address);
let mut connection = connection_pool
.get_conn()
.await
.map_err(|e| MysqlError::ConnectionFailed(format!("{:?}", e)))?;
let db_params = params
.iter()
.map(to_sql_parameter)
.collect::<anyhow::Result<Vec<_>>>()
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
let parameters = mysql_async::Params::Positional(db_params);
let mut query_result = connection
.exec_iter(statement, parameters)
.await
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
// We have to get these before collect() destroys them
let columns = convert_columns(query_result.columns());
match query_result.collect::<mysql_async::Row>().await {
Err(e) => Err(MysqlError::OtherError(format!("{:?}", e))),
Ok(result_set) => {
let rows = result_set
.into_iter()
.map(|row| convert_row(row, &columns))
.collect::<Result<Vec<_>, _>>()
.map_err(|e| MysqlError::QueryFailed(format!("{:?}", e)))?;
Ok(RowSet { columns, rows })
}
}
}
}
fn to_sql_parameter(value: &ParameterValue) -> anyhow::Result<mysql_async::Value> {
match value {
ParameterValue::Boolean(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Int32(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Int64(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Int8(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Int16(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Floating32(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Floating64(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Uint8(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Uint16(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Uint32(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Uint64(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Str(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::Binary(v) => Ok(mysql_async::Value::from(v)),
ParameterValue::DbNull => Ok(mysql_async::Value::NULL),
}
}
fn convert_columns(columns: Option<Arc<[mysql_async::Column]>>) -> Vec<Column> {
match columns {
Some(columns) => columns.iter().map(convert_column).collect(),
None => vec![],
}
}
fn convert_column(column: &mysql_async::Column) -> Column {
let name = column.name_str().to_string();
let data_type = convert_data_type(column);
Column { name, data_type }
}
fn convert_data_type(column: &mysql_async::Column) -> DbDataType {
match (column.column_type(), is_signed(column)) {
(ColumnType::MYSQL_TYPE_BIT, _) => DbDataType::Boolean,
(ColumnType::MYSQL_TYPE_BLOB, _) => DbDataType::Binary,
(ColumnType::MYSQL_TYPE_DOUBLE, _) => DbDataType::Floating64,
(ColumnType::MYSQL_TYPE_FLOAT, _) => DbDataType::Floating32,
(ColumnType::MYSQL_TYPE_LONG, true) => DbDataType::Int32,
(ColumnType::MYSQL_TYPE_LONG, false) => DbDataType::Uint32,
(ColumnType::MYSQL_TYPE_LONGLONG, true) => DbDataType::Int64,
(ColumnType::MYSQL_TYPE_LONGLONG, false) => DbDataType::Uint64,
(ColumnType::MYSQL_TYPE_LONG_BLOB, _) => DbDataType::Binary,
(ColumnType::MYSQL_TYPE_MEDIUM_BLOB, _) => DbDataType::Binary,
(ColumnType::MYSQL_TYPE_SHORT, true) => DbDataType::Int16,
(ColumnType::MYSQL_TYPE_SHORT, false) => DbDataType::Uint16,
(ColumnType::MYSQL_TYPE_STRING, _) => DbDataType::Str,
(ColumnType::MYSQL_TYPE_TINY, true) => DbDataType::Int8,
(ColumnType::MYSQL_TYPE_TINY, false) => DbDataType::Uint8,
(ColumnType::MYSQL_TYPE_VARCHAR, _) => DbDataType::Str,
(ColumnType::MYSQL_TYPE_VAR_STRING, _) => DbDataType::Str,
(_, _) => DbDataType::Other,
}
}
fn is_signed(column: &mysql_async::Column) -> bool {
!column
.flags()
.contains(mysql_async::consts::ColumnFlags::UNSIGNED_FLAG)
}
fn convert_row(mut row: mysql_async::Row, columns: &[Column]) -> Result<Vec<DbValue>, MysqlError> {
let mut result = Vec::with_capacity(row.len());
for index in 0..row.len() {
result.push(convert_entry(&mut row, index, columns)?);
}
Ok(result)
}
fn convert_entry(
row: &mut mysql_async::Row,
index: usize,
columns: &[Column],
) -> Result<DbValue, MysqlError> {
match (row.take(index), columns.get(index)) {
(None, _) => Ok(DbValue::DbNull), // TODO: is this right or is this an "index out of range" thing
(_, None) => Err(MysqlError::OtherError(format!(
"Can't get column at index {}",
index
))),
(Some(mysql_async::Value::NULL), _) => Ok(DbValue::DbNull),
(Some(value), Some(column)) => convert_value(value, column),
}
}
fn convert_value(value: mysql_async::Value, column: &Column) -> Result<DbValue, MysqlError> {
match column.data_type {
DbDataType::Binary => convert_value_to::<Vec<u8>>(value).map(DbValue::Binary),
DbDataType::Boolean => convert_value_to::<bool>(value).map(DbValue::Boolean),
DbDataType::Floating32 => convert_value_to::<f32>(value).map(DbValue::Floating32),
DbDataType::Floating64 => convert_value_to::<f64>(value).map(DbValue::Floating64),
DbDataType::Int8 => convert_value_to::<i8>(value).map(DbValue::Int8),
DbDataType::Int16 => convert_value_to::<i16>(value).map(DbValue::Int16),
DbDataType::Int32 => convert_value_to::<i32>(value).map(DbValue::Int32),
DbDataType::Int64 => convert_value_to::<i64>(value).map(DbValue::Int64),
DbDataType::Str => convert_value_to::<String>(value).map(DbValue::Str),
DbDataType::Uint8 => convert_value_to::<u8>(value).map(DbValue::Uint8),
DbDataType::Uint16 => convert_value_to::<u16>(value).map(DbValue::Uint16),
DbDataType::Uint32 => convert_value_to::<u32>(value).map(DbValue::Uint32),
DbDataType::Uint64 => convert_value_to::<u64>(value).map(DbValue::Uint64),
DbDataType::Other => Err(MysqlError::ValueConversionFailed(format!(
"Cannot convert value {:?} in column {} data type {:?}",
value, column.name, column.data_type
))),
}
}
fn convert_value_to<T: FromValue>(value: mysql_async::Value) -> Result<T, MysqlError> {
from_value_opt::<T>(value).map_err(|e| MysqlError::ValueConversionFailed(format!("{}", e)))
}

View File

@ -11,9 +11,10 @@ clap = { version = "3.1.15", features = ["derive", "env"] }
ctrlc = { version = "3.2", features = ["termination"] }
dirs = "4"
futures = "0.3"
outbound-http = { path = "../outbound-http" }
outbound-http = { path = "../outbound-http" }
outbound-redis = { path = "../outbound-redis" }
outbound-pg = { path = "../outbound-pg" }
outbound-mysql = { path = "../outbound-mysql" }
sanitize-filename = "0.4"
serde = "1.0"
serde_json = "1.0"

View File

@ -94,6 +94,7 @@ impl<Executor: TriggerExecutor> TriggerExecutorBuilder<Executor> {
if !self.disable_default_host_components {
builder.add_host_component(outbound_redis::OutboundRedisComponent)?;
builder.add_host_component(outbound_pg::OutboundPg::default())?;
builder.add_host_component(outbound_mysql::OutboundMysql::default())?;
self.loader.add_dynamic_host_component(
&mut builder,
outbound_http::OutboundHttpComponent,

View File

@ -0,0 +1,2 @@
[build]
target = "wasm32-wasi"

View File

@ -0,0 +1 @@
target/

View File

@ -0,0 +1,25 @@
[package]
name = "rust-outbound-mysql"
authors = ["itowlson <ivan.towlson@fermyon.com>"]
description = "Demo of calling MySQL from a Spin application"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = [ "cdylib" ]
[dependencies]
# Useful crate to handle errors.
anyhow = "1"
# Crate to simplify working with bytes.
bytes = "1"
# General-purpose crate with common HTTP types.
http = "0.2"
serde = "1.0.144"
serde_json = "1.0.85"
# The Spin SDK.
spin-sdk = { path = "../../sdk/rust" }
# Crate that generates Rust Wasm bindings from a WebAssembly interface.
wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen", rev = "cb871cfa1ee460b51eb1d144b175b9aab9c50aba" }
[workspace]

View File

@ -0,0 +1,4 @@
CREATE TABLE pets (id INT PRIMARY KEY, name VARCHAR(100) NOT NULL, prey VARCHAR(100), is_finicky BOOL NOT NULL);
INSERT INTO pets VALUES (1, 'Splodge', NULL, false);
INSERT INTO pets VALUES (2, 'Kiki', 'Cicadas', false);
INSERT INTO pets VALUES (3, 'Slats', 'Temptations', true);

View File

@ -0,0 +1,15 @@
spin_version = "1"
authors = ["itowlson <ivan.towlson@fermyon.com>"]
description = "Demo of calling MySQL from a Spin application"
name = "rust-outbound-mysql"
trigger = { type = "http", base = "/" }
version = "0.1.0"
[[component]]
environment = { DB_URL = "mysql://spin:spin@127.0.0.1/spin_dev" }
id = "rust-outbound-mysql"
source = "target/wasm32-wasi/release/rust_outbound_mysql.wasm"
[component.trigger]
route = "/..."
[component.build]
command = "cargo build --target wasm32-wasi --release"

View File

@ -0,0 +1,50 @@
use anyhow::{anyhow, Result};
use spin_sdk::mysql::{self};
pub(crate) fn as_owned_string(value: &mysql::DbValue) -> Result<String> {
match value {
mysql::DbValue::Str(s) => Ok(s.to_owned()),
_ => Err(anyhow!("Expected string from database but got {:?}", value)),
}
}
pub(crate) fn as_owned_string_opt(value: &mysql::DbValue) -> Result<Option<String>> {
match value {
mysql::DbValue::Str(s) => Ok(Some(s.to_owned())),
mysql::DbValue::DbNull => Ok(None),
_ => Err(anyhow!(
"Expected string or null from database but got {:?}",
value
)),
}
}
pub(crate) fn as_int(value: &mysql::DbValue) -> Result<i32> {
match value {
mysql::DbValue::Int32(n) => Ok(*n),
_ => Err(anyhow!(
"Expected integer from database but got {:?}",
value
)),
}
}
pub(crate) fn as_i8_bool(value: &mysql::DbValue) -> Result<bool> {
// MySQL doesn't have a distinct bool type - BOOL is actually TINYINT and
// surfaces as such
match value {
mysql::DbValue::Int8(n) => Ok(*n != 0),
_ => Err(anyhow!(
"Expected boolean from database but got {:?}",
value
)),
}
}
pub(crate) fn to_i8_bool(value: bool) -> i8 {
if value {
1
} else {
0
}
}

View File

@ -0,0 +1,202 @@
use anyhow::{anyhow, Result};
use convert::to_i8_bool;
use http::{HeaderValue, Method};
use spin_sdk::{
http::{Request, Response},
http_component,
mysql::{self, ParameterValue},
};
use std::{collections::HashMap, str::FromStr};
use crate::model::as_pet;
mod convert;
mod model;
// The environment variable set in `spin.toml` that points to the
// address of the Pg server that the component will write to
const DB_URL_ENV: &str = "DB_URL";
enum RequestAction {
List,
Get(i32),
Create(String, Option<String>, bool),
Error(u16),
}
#[http_component]
fn rust_outbound_mysql(req: Request) -> Result<Response> {
match parse_request(req) {
RequestAction::List => list(),
RequestAction::Get(id) => get(id),
RequestAction::Create(name, prey, is_finicky) => create(&name, &prey, is_finicky),
RequestAction::Error(status) => error(status),
}
}
fn parse_request(req: Request) -> RequestAction {
match *req.method() {
Method::GET => match req.headers().get("spin-path-info") {
None => RequestAction::Error(500),
Some(header_val) => match header_val_to_int(header_val) {
Ok(None) => RequestAction::List,
Ok(Some(id)) => RequestAction::Get(id),
Err(()) => RequestAction::Error(404),
},
},
Method::POST => {
match body_json_to_map(&req) {
Ok(map) => {
let name = match map.get("name") {
Some(n) => n.to_owned(),
None => return RequestAction::Error(400), // If this were a real app it would have error messages
};
let prey = map.get("prey").cloned();
let is_finicky = map
.get("is_finicky")
.map(|s| s == "true")
.unwrap_or_default();
RequestAction::Create(name, prey, is_finicky)
}
Err(_) => RequestAction::Error(400), // Sorry no this isn't helpful either
}
}
_ => RequestAction::Error(405),
}
}
fn header_val_to_int(header_val: &HeaderValue) -> Result<Option<i32>, ()> {
match header_val.to_str() {
Ok(path) => {
let path_parts = &(path.split('/').skip(1).collect::<Vec<_>>()[..]);
match *path_parts {
[""] => Ok(None),
[id_str] => match i32::from_str(id_str) {
Ok(id) => Ok(Some(id)),
Err(_) => Err(()),
},
_ => Err(()),
}
}
Err(_) => Err(()),
}
}
fn body_json_to_map(req: &Request) -> Result<HashMap<String, String>> {
// TODO: easier way?
let body = match req.body().as_ref() {
Some(bytes) => bytes.slice(..),
None => bytes::Bytes::default(),
};
Ok(serde_json::from_slice::<HashMap<String, String>>(&body)?)
}
fn list() -> Result<Response> {
let address = std::env::var(DB_URL_ENV)?;
let sql = "SELECT id, name, prey, is_finicky FROM pets";
let rowset = mysql::query(&address, sql, &[])
.map_err(|e| anyhow!("Error executing MySQL query: {:?}", e))?;
let column_summary = rowset
.columns
.iter()
.map(format_col)
.collect::<Vec<_>>()
.join(", ");
let mut response_lines = vec![];
for row in rowset.rows {
let pet = as_pet(&row);
println!("{:#?}", pet);
response_lines.push(format!("{:#?}", pet));
}
let response = format!(
"Found {} pet(s) as follows:\n{}\n\n(Column info: {})\n",
response_lines.len(),
response_lines.join("\n"),
column_summary,
);
Ok(http::Response::builder()
.status(200)
.body(Some(response.into()))?)
}
fn get(id: i32) -> Result<Response> {
let address = std::env::var(DB_URL_ENV)?;
let sql = "SELECT id, name, prey, is_finicky FROM pets WHERE id = ?";
let params = vec![ParameterValue::Int32(id)];
let rowset = mysql::query(&address, sql, &params)
.map_err(|e| anyhow!("Error executing MySQL query: {:?}", e))?;
match rowset.rows.first() {
None => Ok(http::Response::builder().status(404).body(None)?),
Some(row) => {
let pet = as_pet(row)?;
let response = format!("{:?}", pet);
Ok(http::Response::builder()
.status(200)
.body(Some(response.into()))?)
}
}
}
fn create(name: &str, prey: &Option<String>, is_finicky: bool) -> Result<Response> {
let address = std::env::var(DB_URL_ENV)?;
let id = max_pet_id(&address)? + 1;
let prey_param = match prey {
None => ParameterValue::DbNull,
Some(str) => ParameterValue::Str(str),
};
let is_finicky_param = ParameterValue::Int8(to_i8_bool(is_finicky));
let sql = "INSERT INTO pets (id, name, prey, is_finicky) VALUES (?, ?, ?, ?)";
let params = vec![
ParameterValue::Int32(id),
ParameterValue::Str(name),
prey_param,
is_finicky_param,
];
mysql::execute(&address, sql, &params)
.map_err(|e| anyhow!("Error executing MySQL query: {:?}", e))?;
let location_url = format!("/{}", id);
Ok(http::Response::builder()
.status(201)
.header("Location", location_url)
.body(None)?)
}
fn error(status: u16) -> Result<Response> {
Ok(http::Response::builder().status(status).body(None)?)
}
fn format_col(column: &mysql::Column) -> String {
format!("{}: {:?}", column.name, column.data_type)
}
fn max_pet_id(address: &str) -> Result<i32> {
let sql = "SELECT MAX(id) FROM pets";
let rowset = mysql::query(address, sql, &[])
.map_err(|e| anyhow!("Error executing MySQL query for max id: {:?}", e))?;
match rowset.rows.first() {
None => Ok(0),
Some(row) => match row.first() {
None => Ok(0),
Some(mysql::DbValue::Int32(i)) => Ok(*i),
Some(other) => Err(anyhow!(
"Unexpected non-integer ID {:?}, can't insert",
other
)),
},
}
}

View File

@ -0,0 +1,28 @@
use crate::convert::{as_i8_bool, as_int, as_owned_string, as_owned_string_opt};
use anyhow::Result;
use spin_sdk::mysql::{self};
// Such logic, very business
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct Pet {
id: i32,
name: String,
prey: Option<String>,
is_finicky: bool,
}
pub(crate) fn as_pet(row: &mysql::Row) -> Result<Pet> {
let id = as_int(&row[0])?;
let name = as_owned_string(&row[1])?;
let prey = as_owned_string_opt(&row[2])?;
let is_finicky = as_i8_bool(&row[3])?;
Ok(Pet {
id,
name,
prey,
is_finicky,
})
}

View File

@ -49,6 +49,15 @@ pub mod redis {
/// Implementation of the spin postgres db interface.
pub mod pg;
/// Implementation of the Spin MySQL database interface.
#[allow(missing_docs)]
pub mod mysql {
wit_bindgen_rust::import!("../../wit/ephemeral/outbound-mysql.wit");
/// Exports the generated outbound MySQL items.
pub use outbound_mysql::*;
}
/// Implementation of the spin config interface.
#[allow(missing_docs)]
pub mod config {

View File

@ -0,0 +1,10 @@
// General purpose error.
// TODO: We can provide richer info than this: https://docs.rs/mysql/latest/mysql/error/enum.Error.html
variant mysql-error {
success,
connection-failed(string),
bad-parameter(string),
query-failed(string),
value-conversion-failed(string),
other-error(string)
}

View File

@ -0,0 +1,8 @@
use * from mysql-types
use * from rdbms-types
// query the database: select
query: func(address: string, statement: string, params: list<parameter-value>) -> expected<row-set, mysql-error>
// execute command to the database: insert, update, delete
execute: func(address: string, statement: string, params: list<parameter-value>) -> expected<unit, mysql-error>