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:
parent
a9a9adabb0
commit
d16172bec2
|
@ -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"
|
||||
|
|
|
@ -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 }
|
|
@ -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)))
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[build]
|
||||
target = "wasm32-wasi"
|
|
@ -0,0 +1 @@
|
|||
target/
|
|
@ -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]
|
|
@ -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);
|
|
@ -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"
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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, ¶ms)
|
||||
.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, ¶ms)
|
||||
.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
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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>
|
Loading…
Reference in New Issue