From 1f51a0946a9aa9f2b3bf52931b9da5ca65225439 Mon Sep 17 00:00:00 2001 From: boxbeam Date: Thu, 1 Feb 2024 12:38:37 -0500 Subject: [PATCH] refactor(db): Rewrite database code to use sqlx instead of rusqlite (#1340) * sqlx initialization * Finish initial pass of refactor * Finish refactor, document how to use sqlx * Remove rusqlite dependency * Fix most tests * [autofix.ci] apply automated fixes * All tests pass * [autofix.ci] apply automated fixes * Update docs * Fix conflicts --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .env | 1 + Cargo.lock | 595 ++++++++++++++++-- ee/tabby-db/Cargo.toml | 9 +- ee/tabby-db/docs/sqlx.md | 42 ++ ...=> 0001_registration-token-table.down.sql} | 0 ...l => 0001_registration-token-table.up.sql} | 2 +- .../down.sql => 0002_users-table.down.sql} | 0 .../up.sql => 0002_users-table.up.sql} | 3 +- ...wn.sql => 0003_invitations-table.down.sql} | 0 .../up.sql => 0003_invitations-table.up.sql} | 2 +- .../down.sql => 0004_refresh-tokens.down.sql} | 0 .../up.sql => 0004_refresh-tokens.up.sql} | 2 +- .../down.sql => 0005_job-runs-table.down.sql} | 0 .../up.sql => 0005_job-runs-table.up.sql} | 2 +- ... => 0006_github-oauth-credential.down.sql} | 0 .../0006_github-oauth-credential.up.sql | 7 + .../down.sql => 0007_email-setting.down.sql} | 0 .../migrations/0007_email-setting.up.sql | 6 + ...n.sql => 0008_repositories-table.down.sql} | 0 .../up.sql => 0008_repositories-table.up.sql} | 2 +- ... => 0009_google-oauth-credential.down.sql} | 0 ...ql => 0009_google-oauth-credential.up.sql} | 2 +- .../06-github-oauth-credential/up.sql | 7 - .../migrations/07-email-setting/up.sql | 6 - .../migrations/10-users-active/down.sql | 1 - ee/tabby-db/migrations/10-users-active/up.sql | 1 - ee/tabby-db/schema.sqlite | Bin 0 -> 94208 bytes ee/tabby-db/src/email_setting.rs | 92 ++- ee/tabby-db/src/github_oauth_credential.rs | 75 +-- ee/tabby-db/src/google_oauth_credential.rs | 95 +-- ee/tabby-db/src/invitations.rs | 107 +--- ee/tabby-db/src/job_runs.rs | 100 +-- ee/tabby-db/src/lib.rs | 116 ++-- ee/tabby-db/src/refresh_tokens.rs | 86 +-- ee/tabby-db/src/repositories.rs | 76 +-- ee/tabby-db/src/users.rs | 149 ++--- 36 files changed, 899 insertions(+), 687 deletions(-) create mode 100644 .env create mode 100644 ee/tabby-db/docs/sqlx.md rename ee/tabby-db/migrations/{01-registration-token-table/down.sql => 0001_registration-token-table.down.sql} (100%) rename ee/tabby-db/migrations/{01-registration-token-table/up.sql => 0001_registration-token-table.up.sql} (82%) rename ee/tabby-db/migrations/{02-users-table/down.sql => 0002_users-table.down.sql} (100%) rename ee/tabby-db/migrations/{02-users-table/up.sql => 0002_users-table.up.sql} (85%) rename ee/tabby-db/migrations/{03-invitations-table/down.sql => 0003_invitations-table.down.sql} (100%) rename ee/tabby-db/migrations/{03-invitations-table/up.sql => 0003_invitations-table.up.sql} (88%) rename ee/tabby-db/migrations/{04-refresh-tokens/down.sql => 0004_refresh-tokens.down.sql} (100%) rename ee/tabby-db/migrations/{04-refresh-tokens/up.sql => 0004_refresh-tokens.up.sql} (85%) rename ee/tabby-db/migrations/{05-job-runs-table/down.sql => 0005_job-runs-table.down.sql} (100%) rename ee/tabby-db/migrations/{05-job-runs-table/up.sql => 0005_job-runs-table.up.sql} (89%) rename ee/tabby-db/migrations/{06-github-oauth-credential/down.sql => 0006_github-oauth-credential.down.sql} (100%) create mode 100644 ee/tabby-db/migrations/0006_github-oauth-credential.up.sql rename ee/tabby-db/migrations/{07-email-setting/down.sql => 0007_email-setting.down.sql} (100%) create mode 100644 ee/tabby-db/migrations/0007_email-setting.up.sql rename ee/tabby-db/migrations/{08-repositories-table/down.sql => 0008_repositories-table.down.sql} (100%) rename ee/tabby-db/migrations/{08-repositories-table/up.sql => 0008_repositories-table.up.sql} (82%) rename ee/tabby-db/migrations/{09-google-oauth-credential/down.sql => 0009_google-oauth-credential.down.sql} (100%) rename ee/tabby-db/migrations/{09-google-oauth-credential/up.sql => 0009_google-oauth-credential.up.sql} (84%) delete mode 100644 ee/tabby-db/migrations/06-github-oauth-credential/up.sql delete mode 100644 ee/tabby-db/migrations/07-email-setting/up.sql delete mode 100644 ee/tabby-db/migrations/10-users-active/down.sql delete mode 100644 ee/tabby-db/migrations/10-users-active/up.sql create mode 100644 ee/tabby-db/schema.sqlite diff --git a/.env b/.env new file mode 100644 index 00000000000..23d6ee49f42 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite://ee/tabby-db/schema.sqlite diff --git a/Cargo.lock b/Cargo.lock index a88c36f4e1d..1407870d6fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" dependencies = [ "cfg-if", + "getrandom 0.2.11", "once_cell", "version_check", "zerocopy", @@ -217,6 +218,25 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand 0.8.5", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -368,6 +388,9 @@ name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +dependencies = [ + "serde", +] [[package]] name = "bitpacking" @@ -592,6 +615,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "core-foundation" version = "0.9.3" @@ -617,6 +646,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -683,14 +727,20 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.16" +name = "crossbeam-queue" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "crunchy" version = "0.2.2" @@ -846,6 +896,17 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.8" @@ -904,6 +965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -945,6 +1007,9 @@ name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +dependencies = [ + "serde", +] [[package]] name = "email-encoding" @@ -1014,16 +1079,21 @@ dependencies = [ ] [[package]] -name = "fallible-iterator" -version = "0.3.0" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] [[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastdivide" @@ -1065,6 +1135,12 @@ dependencies = [ "regex", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1081,6 +1157,17 @@ dependencies = [ "miniz_oxide 0.7.1", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1174,6 +1261,17 @@ dependencies = [ "futures-util", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + [[package]] name = "futures-io" version = "0.3.29" @@ -1394,6 +1492,9 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] [[package]] name = "hermit-abi" @@ -1416,6 +1517,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.5" @@ -1624,25 +1743,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "include_dir" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" -dependencies = [ - "include_dir_macros", -] - -[[package]] -name = "include_dir_macros" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" -dependencies = [ - "proc-macro2", - "quote", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -1740,6 +1840,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -1871,6 +1980,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] [[package]] name = "lettre" @@ -1918,6 +2030,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libsqlite3-sys" version = "0.27.0" @@ -2056,6 +2174,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "measure_time" version = "0.8.2" @@ -2249,6 +2377,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9a91b326434fca226707ed8ec1fd22d4e1c96801abdf10c412afdc7d97116e0" +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "libc", +] + [[package]] name = "nom" version = "4.1.1" @@ -2298,6 +2437,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -2319,6 +2475,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -2326,6 +2493,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2599,6 +2767,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2692,6 +2869,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -3090,7 +3288,7 @@ dependencies = [ "cc", "getrandom 0.2.11", "libc", - "spin", + "spin 0.9.8", "untrusted", "windows-sys 0.48.0", ] @@ -3118,31 +3316,23 @@ dependencies = [ ] [[package]] -name = "rusqlite" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" -dependencies = [ - "bitflags 2.4.0", - "chrono", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - -[[package]] -name = "rusqlite_migration" -version = "1.1.0-beta.1" +name = "rsa" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5767f8cb28e54d1ed745f072b72c6e68bfa6179fabb4cd15bdb8575858e301d" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" dependencies = [ - "include_dir", - "log", - "rusqlite", - "tokio", - "tokio-rusqlite", + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", ] [[package]] @@ -3556,6 +3746,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "similar" version = "2.3.0" @@ -3629,11 +3829,242 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools 0.12.0", + "nom 7.1.3", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap 2.0.1", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +dependencies = [ + "atomic-write-file", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +dependencies = [ + "atoi", + "base64 0.21.5", + "bitflags 2.4.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", +] [[package]] name = "stable_deref_trait" @@ -3666,6 +4097,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "strsim" version = "0.9.3" @@ -3852,13 +4294,9 @@ dependencies = [ "anyhow", "assert_matches", "chrono", - "include_dir", - "lazy_static", - "rusqlite", - "rusqlite_migration", + "sqlx", "tabby-common", "tokio", - "tokio-rusqlite", "uuid 1.6.1", ] @@ -4330,17 +4768,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rusqlite" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc785c98d0c872455381e59be1f33a8f3a6b4e852544212e37601cc2ccb21d39" -dependencies = [ - "crossbeam-channel", - "rusqlite", - "tokio", -] - [[package]] name = "tokio-serde" version = "0.8.0" @@ -4887,12 +5314,24 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + [[package]] name = "unicode-width" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "unreachable" version = "1.0.0" @@ -4928,6 +5367,12 @@ dependencies = [ "regex", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -5227,6 +5672,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + [[package]] name = "winapi" version = "0.3.9" @@ -5474,6 +5925,12 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + [[package]] name = "zip" version = "0.6.6" diff --git a/ee/tabby-db/Cargo.toml b/ee/tabby-db/Cargo.toml index b71a31f7802..c67188855d5 100644 --- a/ee/tabby-db/Cargo.toml +++ b/ee/tabby-db/Cargo.toml @@ -11,16 +11,11 @@ testutils = [] [dependencies] anyhow.workspace = true chrono = { workspace = true, features = ["serde"] } -include_dir = "0.7.3" -lazy_static = "1.4.0" -rusqlite = { version = "0.30.0", features = ["bundled", "chrono"] } -# `alpha-async-tokio-rusqlite` is only available from 1.1.0-alpha.2, will bump up version when it's stable -rusqlite_migration = { version = "1.1.0-beta.1", features = ["alpha-async-tokio-rusqlite", "from-directory"] } +sqlx = { version = "0.7.3", features = ["sqlite", "chrono", "runtime-tokio"] } tabby-common = { path = "../../crates/tabby-common" } tokio = { workspace = true, features = ["fs"] } -tokio-rusqlite = "0.5.0" uuid.workspace = true [dev-dependencies] assert_matches = "1.5.0" -tokio = { workspace = true, features = ["macros", "process"] } \ No newline at end of file +tokio = { workspace = true, features = ["macros", "process"] } diff --git a/ee/tabby-db/docs/sqlx.md b/ee/tabby-db/docs/sqlx.md new file mode 100644 index 00000000000..86f0530dc17 --- /dev/null +++ b/ee/tabby-db/docs/sqlx.md @@ -0,0 +1,42 @@ +# Using SQLX with Tabby + +Tabby uses SQLX for its database, which comes with several advantages: + +- All queries created using the `query!()`, `query_as!()` and `query_scalar!()` macros are syntax- and schema-checked at compiletime +- All migrations are managed by sqlx +- A local copy of our db schema is maintained in `ee/tabby-db/schema.sqlite` + - This is used for checking queries at compiletime to ensure they are valid against the actual schema +- Simplified querying syntax compared to rusqlite, which was previously used + +There is a file in the workspace root called `.env` which is used to specify the relative path of the database for sqlx. +It will check for the database at that location and read its schema. This database must be kept up-to-date to ensure +compiletime query checking continues to work. + +To work with SQLX, it is recommended that you install it as a cargo subcommand: + +``` +cargo install sqlx-cli +``` + +This will allow you to use sqlx's commands like `cargo sqlx`. + +To create the database fresh from migrations: + +``` +rm ee/tabby-db/schema.sqlite +cargo sqlx db setup --source ee/tabby-db/migrations +``` + +To create a new migration: + +``` +cargo sqlx migrate add --source ee/tabby-db/migrations -r -s +``` + +This will create a new `up` and `down` file for the migration. + +To run all migrations and ensure the local schema is up-to-date: + +``` +cargo sqlx migrate run --source ee/tabby-db/migrations +``` diff --git a/ee/tabby-db/migrations/01-registration-token-table/down.sql b/ee/tabby-db/migrations/0001_registration-token-table.down.sql similarity index 100% rename from ee/tabby-db/migrations/01-registration-token-table/down.sql rename to ee/tabby-db/migrations/0001_registration-token-table.down.sql diff --git a/ee/tabby-db/migrations/01-registration-token-table/up.sql b/ee/tabby-db/migrations/0001_registration-token-table.up.sql similarity index 82% rename from ee/tabby-db/migrations/01-registration-token-table/up.sql rename to ee/tabby-db/migrations/0001_registration-token-table.up.sql index 0d8f8ba3760..d9a6b9fdad9 100644 --- a/ee/tabby-db/migrations/01-registration-token-table/up.sql +++ b/ee/tabby-db/migrations/0001_registration-token-table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE registration_token ( +CREATE TABLE IF NOT EXISTS registration_token ( id INTEGER PRIMARY KEY AUTOINCREMENT, token VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT (DATETIME('now')), diff --git a/ee/tabby-db/migrations/02-users-table/down.sql b/ee/tabby-db/migrations/0002_users-table.down.sql similarity index 100% rename from ee/tabby-db/migrations/02-users-table/down.sql rename to ee/tabby-db/migrations/0002_users-table.down.sql diff --git a/ee/tabby-db/migrations/02-users-table/up.sql b/ee/tabby-db/migrations/0002_users-table.up.sql similarity index 85% rename from ee/tabby-db/migrations/02-users-table/up.sql rename to ee/tabby-db/migrations/0002_users-table.up.sql index 8f08549fa7d..e4796fe6a0c 100644 --- a/ee/tabby-db/migrations/02-users-table/up.sql +++ b/ee/tabby-db/migrations/0002_users-table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE users ( +CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email VARCHAR(150) NOT NULL COLLATE NOCASE, password_encrypted VARCHAR(128) NOT NULL, @@ -6,6 +6,7 @@ CREATE TABLE users ( created_at TIMESTAMP DEFAULT (DATETIME('now')), updated_at TIMESTAMP DEFAULT (DATETIME('now')), auth_token VARCHAR(128) NOT NULL, + active BOOLEAN NOT NULL DEFAULT 1, CONSTRAINT `idx_email` UNIQUE (`email`) CONSTRAINT `idx_auth_token` UNIQUE (`auth_token`) diff --git a/ee/tabby-db/migrations/03-invitations-table/down.sql b/ee/tabby-db/migrations/0003_invitations-table.down.sql similarity index 100% rename from ee/tabby-db/migrations/03-invitations-table/down.sql rename to ee/tabby-db/migrations/0003_invitations-table.down.sql diff --git a/ee/tabby-db/migrations/03-invitations-table/up.sql b/ee/tabby-db/migrations/0003_invitations-table.up.sql similarity index 88% rename from ee/tabby-db/migrations/03-invitations-table/up.sql rename to ee/tabby-db/migrations/0003_invitations-table.up.sql index 5dc20f3fb2e..22b557dfbb5 100644 --- a/ee/tabby-db/migrations/03-invitations-table/up.sql +++ b/ee/tabby-db/migrations/0003_invitations-table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE invitations ( +CREATE TABLE IF NOT EXISTS invitations ( id INTEGER PRIMARY KEY AUTOINCREMENT, email VARCHAR(150) NOT NULL COLLATE NOCASE, code VARCHAR(36) NOT NULL, diff --git a/ee/tabby-db/migrations/04-refresh-tokens/down.sql b/ee/tabby-db/migrations/0004_refresh-tokens.down.sql similarity index 100% rename from ee/tabby-db/migrations/04-refresh-tokens/down.sql rename to ee/tabby-db/migrations/0004_refresh-tokens.down.sql diff --git a/ee/tabby-db/migrations/04-refresh-tokens/up.sql b/ee/tabby-db/migrations/0004_refresh-tokens.up.sql similarity index 85% rename from ee/tabby-db/migrations/04-refresh-tokens/up.sql rename to ee/tabby-db/migrations/0004_refresh-tokens.up.sql index c7cf9bf5f9b..3c5c4f008b8 100644 --- a/ee/tabby-db/migrations/04-refresh-tokens/up.sql +++ b/ee/tabby-db/migrations/0004_refresh-tokens.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE refresh_tokens ( +CREATE TABLE IF NOT EXISTS refresh_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, token VARCHAR(255) NOT NULL COLLATE NOCASE, diff --git a/ee/tabby-db/migrations/05-job-runs-table/down.sql b/ee/tabby-db/migrations/0005_job-runs-table.down.sql similarity index 100% rename from ee/tabby-db/migrations/05-job-runs-table/down.sql rename to ee/tabby-db/migrations/0005_job-runs-table.down.sql diff --git a/ee/tabby-db/migrations/05-job-runs-table/up.sql b/ee/tabby-db/migrations/0005_job-runs-table.up.sql similarity index 89% rename from ee/tabby-db/migrations/05-job-runs-table/up.sql rename to ee/tabby-db/migrations/0005_job-runs-table.up.sql index 60cdf23d4d4..9d0566c180c 100644 --- a/ee/tabby-db/migrations/05-job-runs-table/up.sql +++ b/ee/tabby-db/migrations/0005_job-runs-table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE job_runs ( +CREATE TABLE IF NOT EXISTS job_runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, job VARCHAR(255) NOT NULL, start_ts TIMESTAMP NOT NULL, diff --git a/ee/tabby-db/migrations/06-github-oauth-credential/down.sql b/ee/tabby-db/migrations/0006_github-oauth-credential.down.sql similarity index 100% rename from ee/tabby-db/migrations/06-github-oauth-credential/down.sql rename to ee/tabby-db/migrations/0006_github-oauth-credential.down.sql diff --git a/ee/tabby-db/migrations/0006_github-oauth-credential.up.sql b/ee/tabby-db/migrations/0006_github-oauth-credential.up.sql new file mode 100644 index 00000000000..c7ed9088ed6 --- /dev/null +++ b/ee/tabby-db/migrations/0006_github-oauth-credential.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS github_oauth_credential ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + client_id VARCHAR(32) NOT NULL, + client_secret VARCHAR(64) NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')), + updated_at TIMESTAMP NOT NULL DEFAULT (DATETIME('now')) +); diff --git a/ee/tabby-db/migrations/07-email-setting/down.sql b/ee/tabby-db/migrations/0007_email-setting.down.sql similarity index 100% rename from ee/tabby-db/migrations/07-email-setting/down.sql rename to ee/tabby-db/migrations/0007_email-setting.down.sql diff --git a/ee/tabby-db/migrations/0007_email-setting.up.sql b/ee/tabby-db/migrations/0007_email-setting.up.sql new file mode 100644 index 00000000000..94ed8157c53 --- /dev/null +++ b/ee/tabby-db/migrations/0007_email-setting.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS email_setting( + id INTEGER PRIMARY KEY AUTOINCREMENT, + smtp_username VARCHAR(255) NOT NULL, + smtp_password VARCHAR(255) NOT NULL, + smtp_server VARCHAR(255) NOT NULL +); diff --git a/ee/tabby-db/migrations/08-repositories-table/down.sql b/ee/tabby-db/migrations/0008_repositories-table.down.sql similarity index 100% rename from ee/tabby-db/migrations/08-repositories-table/down.sql rename to ee/tabby-db/migrations/0008_repositories-table.down.sql diff --git a/ee/tabby-db/migrations/08-repositories-table/up.sql b/ee/tabby-db/migrations/0008_repositories-table.up.sql similarity index 82% rename from ee/tabby-db/migrations/08-repositories-table/up.sql rename to ee/tabby-db/migrations/0008_repositories-table.up.sql index 684c071c2d8..fa0425a363f 100644 --- a/ee/tabby-db/migrations/08-repositories-table/up.sql +++ b/ee/tabby-db/migrations/0008_repositories-table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE repositories ( +CREATE TABLE IF NOT EXISTS repositories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255) NOT NULL, git_url VARCHAR(255) NOT NULL, diff --git a/ee/tabby-db/migrations/09-google-oauth-credential/down.sql b/ee/tabby-db/migrations/0009_google-oauth-credential.down.sql similarity index 100% rename from ee/tabby-db/migrations/09-google-oauth-credential/down.sql rename to ee/tabby-db/migrations/0009_google-oauth-credential.down.sql diff --git a/ee/tabby-db/migrations/09-google-oauth-credential/up.sql b/ee/tabby-db/migrations/0009_google-oauth-credential.up.sql similarity index 84% rename from ee/tabby-db/migrations/09-google-oauth-credential/up.sql rename to ee/tabby-db/migrations/0009_google-oauth-credential.up.sql index 050c30850d8..d6a3ce85248 100644 --- a/ee/tabby-db/migrations/09-google-oauth-credential/up.sql +++ b/ee/tabby-db/migrations/0009_google-oauth-credential.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE google_oauth_credential ( +CREATE TABLE IF NOT EXISTS google_oauth_credential ( id INTEGER PRIMARY KEY AUTOINCREMENT, client_id VARCHAR(256) NOT NULL, client_secret VARCHAR(64) NOT NULL, diff --git a/ee/tabby-db/migrations/06-github-oauth-credential/up.sql b/ee/tabby-db/migrations/06-github-oauth-credential/up.sql deleted file mode 100644 index 17d696336de..00000000000 --- a/ee/tabby-db/migrations/06-github-oauth-credential/up.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE github_oauth_credential ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - client_id VARCHAR(32) NOT NULL, - client_secret VARCHAR(64) NOT NULL, - created_at TIMESTAMP DEFAULT (DATETIME('now')), - updated_at TIMESTAMP DEFAULT (DATETIME('now')) -); diff --git a/ee/tabby-db/migrations/07-email-setting/up.sql b/ee/tabby-db/migrations/07-email-setting/up.sql deleted file mode 100644 index 25001e0ecde..00000000000 --- a/ee/tabby-db/migrations/07-email-setting/up.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE email_setting( - id INTEGER PRIMARY KEY AUTOINCREMENT, - smtp_username VARCHAR(255), - smtp_password VARCHAR(255), - smtp_server VARCHAR(255) -); diff --git a/ee/tabby-db/migrations/10-users-active/down.sql b/ee/tabby-db/migrations/10-users-active/down.sql deleted file mode 100644 index 3407693130c..00000000000 --- a/ee/tabby-db/migrations/10-users-active/down.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users DROP COLUMN active; diff --git a/ee/tabby-db/migrations/10-users-active/up.sql b/ee/tabby-db/migrations/10-users-active/up.sql deleted file mode 100644 index eb2bd5b126f..00000000000 --- a/ee/tabby-db/migrations/10-users-active/up.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE users ADD COLUMN active BOOLEAN NOT NULL DEFAULT 1; diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..3c4b2b88c86fa6c1afa21dc6d11fa19f28decfec GIT binary patch literal 94208 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 hi}AW=lH7jdZ3y0m;BAQBGdJJckj-sw?t-7={|S Self { - Self { - smtp_username, - smtp_password, - smtp_server, - } - } -} - impl DbConn { pub async fn read_email_setting(&self) -> Result> { - let res = self - .conn - .call(|c| { - Ok(c.query_row( - "SELECT smtp_username, smtp_password, smtp_server FROM email_setting WHERE id=?", - [EMAIL_CREDENTIAL_ROW_ID], - |row| Ok(EmailSettingDAO::new(row.get(0)?, row.get(1)?, row.get(2)?)), - ) - .optional()) - }) - .await?; - Ok(res?) + let setting = query_as!( + EmailSettingDAO, + "SELECT smtp_username, smtp_password, smtp_server FROM email_setting WHERE id=?", + EMAIL_CREDENTIAL_ROW_ID + ) + .fetch_optional(&self.pool) + .await?; + Ok(setting) } pub async fn update_email_setting( @@ -44,42 +30,36 @@ impl DbConn { smtp_password: Option, smtp_server: String, ) -> Result<()> { - Ok(self - .conn - .call(move |c| { - let transaction = c.transaction()?; - let smtp_password = match smtp_password { - Some(pass) => pass, - None => { - transaction.query_row("SELECT smtp_password FROM email_setting WHERE id = ?", [EMAIL_CREDENTIAL_ROW_ID], |r| r.get(0))? - } - }; - transaction.execute("INSERT INTO email_setting VALUES (:id, :user, :pass, :server) - ON CONFLICT(id) DO UPDATE SET smtp_username = :user, smtp_password = :pass, smtp_server = :server", - named_params! { - ":id": EMAIL_CREDENTIAL_ROW_ID, - ":user": smtp_username, - ":pass": smtp_password, - ":server": smtp_server, - } - )?; - transaction.commit()?; - Ok(()) - }) - .await?) + let mut transaction = self.pool.begin().await?; + let smtp_password = match smtp_password { + Some(pass) => pass, + None => { + query_scalar!( + "SELECT smtp_password FROM email_setting WHERE id = ?", + EMAIL_CREDENTIAL_ROW_ID + ) + .fetch_one(&mut *transaction) + .await? + } + }; + query!("INSERT INTO email_setting VALUES ($1, $2, $3, $4) + ON CONFLICT(id) DO UPDATE SET smtp_username = $2, smtp_password = $3, smtp_server = $4", + EMAIL_CREDENTIAL_ROW_ID, + smtp_username, + smtp_password, + smtp_server).execute(&mut *transaction).await?; + transaction.commit().await?; + Ok(()) } pub async fn delete_email_setting(&self) -> Result<()> { - Ok(self - .conn - .call(move |c| { - c.execute( - "DELETE FROM email_setting WHERE id = ?", - [EMAIL_CREDENTIAL_ROW_ID], - )?; - Ok(()) - }) - .await?) + query!( + "DELETE FROM email_setting WHERE id = ?", + EMAIL_CREDENTIAL_ROW_ID + ) + .execute(&self.pool) + .await?; + Ok(()) } } diff --git a/ee/tabby-db/src/github_oauth_credential.rs b/ee/tabby-db/src/github_oauth_credential.rs index e4122384d9b..5ba84a6be1d 100644 --- a/ee/tabby-db/src/github_oauth_credential.rs +++ b/ee/tabby-db/src/github_oauth_credential.rs @@ -1,11 +1,12 @@ use anyhow::Result; use chrono::{DateTime, Utc}; -use rusqlite::{named_params, OptionalExtension}; +use sqlx::{query, FromRow}; use super::DbConn; const GITHUB_OAUTH_CREDENTIAL_ROW_ID: i32 = 1; +#[derive(FromRow)] pub struct GithubOAuthCredentialDAO { pub client_id: String, pub client_secret: String, @@ -13,17 +14,6 @@ pub struct GithubOAuthCredentialDAO { pub updated_at: DateTime, } -impl GithubOAuthCredentialDAO { - fn from_row(row: &rusqlite::Row<'_>) -> std::result::Result { - Ok(Self { - client_id: row.get(0)?, - client_secret: row.get(1)?, - created_at: row.get(2)?, - updated_at: row.get(3)?, - }) - } -} - /// db read/write operations for `github_oauth_credential` table impl DbConn { pub async fn update_github_oauth_credential( @@ -33,52 +23,35 @@ impl DbConn { ) -> Result<()> { let client_id = client_id.to_string(); let client_secret = client_secret.to_string(); - let sql = r#"INSERT INTO github_oauth_credential (id, client_id, client_secret) - VALUES (:id, :cid, :secret) ON CONFLICT(id) DO UPDATE - SET client_id = :cid, client_secret = :secret, updated_at = datetime('now') - WHERE id = :id"#; - self.conn - .call(move |c| { - let mut stmt = c.prepare(sql)?; - stmt.insert(named_params! { - ":id": GITHUB_OAUTH_CREDENTIAL_ROW_ID, - ":cid": client_id, - ":secret": client_secret, - })?; - Ok(()) - }) - .await?; + query!( + r#"INSERT INTO github_oauth_credential (id, client_id, client_secret) + VALUES ($1, $2, $3) ON CONFLICT(id) DO UPDATE + SET client_id = $2, client_secret = $3, updated_at = datetime('now') + WHERE id = $1"#, + GITHUB_OAUTH_CREDENTIAL_ROW_ID, + client_id, + client_secret + ) + .execute(&self.pool) + .await?; Ok(()) } pub async fn delete_github_oauth_credential(&self) -> Result<()> { - Ok(self - .conn - .call(move |c| { - c.execute( - "DELETE FROM github_oauth_credential WHERE id = ?", - [GITHUB_OAUTH_CREDENTIAL_ROW_ID], - )?; - Ok(()) - }) - .await?) + query!( + "DELETE FROM github_oauth_credential WHERE id = ?", + GITHUB_OAUTH_CREDENTIAL_ROW_ID + ) + .execute(&self.pool) + .await?; + Ok(()) } pub async fn read_github_oauth_credential(&self) -> Result> { - let token = self - .conn - .call(|conn| { - Ok(conn - .query_row( - r#"SELECT client_id, client_secret, created_at, updated_at FROM github_oauth_credential WHERE id = ?"#, - [GITHUB_OAUTH_CREDENTIAL_ROW_ID], - GithubOAuthCredentialDAO::from_row, - ) - .optional()) - }) - .await?; - - Ok(token?) + let token = sqlx::query_as("SELECT client_id, client_secret, created_at, updated_at FROM github_oauth_credential WHERE id = ?") + .bind(GITHUB_OAUTH_CREDENTIAL_ROW_ID) + .fetch_optional(&self.pool).await?; + Ok(token) } } diff --git a/ee/tabby-db/src/google_oauth_credential.rs b/ee/tabby-db/src/google_oauth_credential.rs index 0a8797f6458..b35b7afd44c 100644 --- a/ee/tabby-db/src/google_oauth_credential.rs +++ b/ee/tabby-db/src/google_oauth_credential.rs @@ -1,11 +1,12 @@ use anyhow::Result; use chrono::{DateTime, Utc}; -use rusqlite::{named_params, OptionalExtension}; +use sqlx::{query, FromRow}; use super::DbConn; const GOOGLE_OAUTH_CREDENTIAL_ROW_ID: i32 = 1; +#[derive(FromRow)] pub struct GoogleOAuthCredentialDAO { pub client_id: String, pub client_secret: String, @@ -14,18 +15,6 @@ pub struct GoogleOAuthCredentialDAO { pub updated_at: DateTime, } -impl GoogleOAuthCredentialDAO { - fn from_row(row: &rusqlite::Row<'_>) -> std::result::Result { - Ok(Self { - client_id: row.get(0)?, - client_secret: row.get(1)?, - redirect_uri: row.get(2)?, - created_at: row.get(3)?, - updated_at: row.get(4)?, - }) - } -} - /// db read/write operations for `google_oauth_credential` table impl DbConn { pub async fn update_google_oauth_credential( @@ -38,38 +27,23 @@ impl DbConn { if !client_secret.is_empty() { let client_id = client_id.to_string(); let client_secret = client_secret.to_string(); - let sql = r#"INSERT INTO google_oauth_credential (id, client_id, client_secret, redirect_uri) - VALUES (:id, :cid, :secret, :redirect) ON CONFLICT(id) DO UPDATE - SET client_id = :cid, client_secret = :secret, redirect_uri = :redirect, updated_at = datetime('now') - WHERE id = :id"#; - self.conn - .call(move |c| { - let mut stmt = c.prepare(sql)?; - stmt.insert(named_params! { - ":id": GOOGLE_OAUTH_CREDENTIAL_ROW_ID, - ":cid": client_id, - ":secret": client_secret, - ":redirect": redirect_uri, - })?; - Ok(()) - }) - .await?; + query!( + r#"INSERT INTO google_oauth_credential (id, client_id, client_secret, redirect_uri) + VALUES ($1, $2, $3, $4) ON CONFLICT(id) DO UPDATE + SET client_id = $2, client_secret = $3, redirect_uri = $4, updated_at = datetime('now') + WHERE id = $1"#, + GOOGLE_OAUTH_CREDENTIAL_ROW_ID, + client_id, + client_secret, + redirect_uri + ).execute(&self.pool).await?; Ok(()) } else { - let sql = r#" - UPDATE google_oauth_credential SET redirect_uri = :redirect, updated_at = datetime('now') - WHERE id = :id"#; - let rows = self - .conn - .call(move |c| { - let mut stmt = c.prepare(sql)?; - let rows = stmt.execute(named_params! { - ":id": GOOGLE_OAUTH_CREDENTIAL_ROW_ID, - ":redirect": redirect_uri, - })?; - Ok(rows) - }) - .await?; + let rows = query!( + "UPDATE google_oauth_credential SET redirect_uri = $2, updated_at = datetime('now') WHERE id = $1", + GOOGLE_OAUTH_CREDENTIAL_ROW_ID, + redirect_uri + ).execute(&self.pool).await?.rows_affected(); if rows != 1 { return Err(anyhow::anyhow!( "failed to update: google credential not found" @@ -80,33 +54,20 @@ impl DbConn { } pub async fn read_google_oauth_credential(&self) -> Result> { - let token = self - .conn - .call(|conn| { - Ok(conn - .query_row( - r#"SELECT client_id, client_secret, redirect_uri, created_at, updated_at FROM google_oauth_credential WHERE id = ?"#, - [GOOGLE_OAUTH_CREDENTIAL_ROW_ID], - GoogleOAuthCredentialDAO::from_row, - ) - .optional()) - }) - .await?; - - Ok(token?) + let token = sqlx::query_as( + r#"SELECT client_id, client_secret, redirect_uri, created_at, updated_at FROM google_oauth_credential WHERE id = ?"#, + ).bind(GOOGLE_OAUTH_CREDENTIAL_ROW_ID).fetch_optional(&self.pool).await?; + Ok(token) } pub async fn delete_google_oauth_credential(&self) -> Result<()> { - Ok(self - .conn - .call(move |c| { - c.execute( - "DELETE FROM google_oauth_credential WHERE id = ?", - [GOOGLE_OAUTH_CREDENTIAL_ROW_ID], - )?; - Ok(()) - }) - .await?) + query!( + "DELETE FROM google_oauth_credential WHERE id = ?", + GOOGLE_OAUTH_CREDENTIAL_ROW_ID + ) + .execute(&self.pool) + .await?; + Ok(()) } } diff --git a/ee/tabby-db/src/invitations.rs b/ee/tabby-db/src/invitations.rs index 0bc25b27dff..d88239d00ea 100644 --- a/ee/tabby-db/src/invitations.rs +++ b/ee/tabby-db/src/invitations.rs @@ -1,9 +1,10 @@ use anyhow::{anyhow, Result}; -use rusqlite::{params, OptionalExtension, Row}; +use sqlx::{prelude::FromRow, query}; use uuid::Uuid; use super::DbConn; +#[derive(FromRow)] pub struct InvitationDAO { pub id: i32, pub email: String, @@ -12,17 +13,6 @@ pub struct InvitationDAO { pub created_at: String, } -impl InvitationDAO { - fn from_row(row: &Row<'_>) -> std::result::Result { - Ok(Self { - id: row.get(0)?, - email: row.get(1)?, - code: row.get(2)?, - created_at: row.get(3)?, - }) - } -} - /// db read/write operations for `invitations` table impl DbConn { pub async fn list_invitations_with_filter( @@ -39,52 +29,30 @@ impl DbConn { backwards, ); - let invitations = self - .conn - .call(move |c| { - let mut stmt = c.prepare(&query)?; - let invit_iter = stmt.query_map([], InvitationDAO::from_row)?; - Ok(invit_iter.filter_map(|x| x.ok()).collect::>()) - }) - .await?; + let invitations = sqlx::query_as(&query).fetch_all(&self.pool).await?; Ok(invitations) } pub async fn get_invitation_by_code(&self, code: &str) -> Result> { - let code = code.to_owned(); - let token = self - .conn - .call(|conn| { - Ok(conn - .query_row( - r#"SELECT id, email, code, created_at FROM invitations WHERE code = ?"#, - [code], - InvitationDAO::from_row, - ) - .optional()) - }) - .await?; + let token = + sqlx::query_as(r#"SELECT id, email, code, created_at FROM invitations WHERE code = ?"#) + .bind(code) + .fetch_optional(&self.pool) + .await?; - Ok(token?) + Ok(token) } pub async fn get_invitation_by_email(&self, email: &str) -> Result> { - let email = email.to_owned(); - let token = self - .conn - .call(|conn| { - Ok(conn - .query_row( - r#"SELECT id, email, code, created_at FROM invitations WHERE email = ?"#, - [email], - InvitationDAO::from_row, - ) - .optional()) - }) - .await?; - - Ok(token?) + let token = sqlx::query_as( + r#"SELECT id, email, code, created_at FROM invitations WHERE email = ?"#, + ) + .bind(email) + .fetch_optional(&self.pool) + .await?; + + Ok(token) } pub async fn create_invitation(&self, email: String) -> Result { @@ -93,42 +61,33 @@ impl DbConn { } let code = Uuid::new_v4().to_string(); - let code_clone = code.clone(); - let email_clone = email.clone(); - let res = self - .conn - .call(move |c| { - let mut stmt = - c.prepare(r#"INSERT INTO invitations (email, code) VALUES (?, ?)"#)?; - let rowid = stmt.insert((email, code.clone()))?; - Ok(rowid) - }) - .await; + let res = query!( + "INSERT INTO invitations (email, code) VALUES (?, ?)", + email, + code + ) + .execute(&self.pool) + .await; match res { - Err(tokio_rusqlite::Error::Rusqlite(rusqlite::Error::SqliteFailure(err, msg))) => { - if err.code == rusqlite::ErrorCode::ConstraintViolation { - Err(anyhow!("Failed to create invitation, email already exists")) - } else { - Err(rusqlite::Error::SqliteFailure(err, msg).into()) - } + Err(sqlx::Error::Database(db)) if db.constraint().is_some() => { + Err(anyhow!("Failed to create invitation, email already exists")) } Err(err) => Err(err.into()), - Ok(rowid) => Ok(InvitationDAO { - id: rowid as i32, - email: email_clone, - code: code_clone, + Ok(res) => Ok(InvitationDAO { + id: res.last_insert_rowid() as i32, + email, + code, created_at: "".into(), }), } } pub async fn delete_invitation(&self, id: i32) -> Result { - let res = self - .conn - .call(move |c| Ok(c.execute(r#"DELETE FROM invitations WHERE id = ?"#, params![id]))) + let res = query!("DELETE FROM invitations WHERE id = ?", id) + .execute(&self.pool) .await?; - if res != Ok(1) { + if res.rows_affected() != 1 { return Err(anyhow!("failed to delete invitation")); } diff --git a/ee/tabby-db/src/job_runs.rs b/ee/tabby-db/src/job_runs.rs index fa1382539d8..457b48f86d0 100644 --- a/ee/tabby-db/src/job_runs.rs +++ b/ee/tabby-db/src/job_runs.rs @@ -1,9 +1,10 @@ use anyhow::Result; use chrono::{DateTime, Utc}; +use sqlx::{query, FromRow}; use super::DbConn; -#[derive(Default, Clone)] +#[derive(Default, Clone, FromRow)] pub struct JobRunDAO { pub id: i32, pub job_name: String, @@ -14,84 +15,47 @@ pub struct JobRunDAO { pub stderr: String, } -impl JobRunDAO { - fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result { - Ok(Self { - id: row.get(0)?, - job_name: row.get(1)?, - start_time: row.get(2)?, - finish_time: row.get(3)?, - exit_code: row.get(4)?, - stdout: row.get(5)?, - stderr: row.get(6)?, - }) - } -} - /// db read/write operations for `job_runs` table impl DbConn { pub async fn create_job_run(&self, run: JobRunDAO) -> Result { - let rowid = self - .conn - .call(move |c| { - let mut stmt = c.prepare( - r#"INSERT INTO job_runs (job, start_ts, end_ts, exit_code, stdout, stderr) VALUES (?, ?, ?, ?, ?, ?)"#, - )?; - let rowid = stmt.insert(( - run.job_name, - run.start_time, - run.finish_time, - run.exit_code, - run.stdout, - run.stderr, - ))?; - Ok(rowid) - }) - .await?; + let rowid = query!( + r#"INSERT INTO job_runs (job, start_ts, end_ts, exit_code, stdout, stderr) VALUES (?, ?, ?, ?, ?, ?)"#, + run.job_name, + run.start_time, + run.finish_time, + run.exit_code, + run.stdout, + run.stderr, + ).execute(&self.pool).await?.last_insert_rowid(); Ok(rowid as i32) } pub async fn update_job_stdout(&self, job_id: i32, stdout: String) -> Result<()> { - self.conn - .call(move |c| { - let mut stmt = c.prepare( - r#"UPDATE job_runs SET stdout = stdout || ?, updated_at = datetime('now') WHERE id = ?"#, - )?; - stmt.execute((stdout, job_id))?; - Ok(()) - }) - .await?; + query!( + r#"UPDATE job_runs SET stdout = stdout || ?, updated_at = datetime('now') WHERE id = ?"#, + stdout, + job_id + ).execute(&self.pool).await?; Ok(()) } pub async fn update_job_stderr(&self, job_id: i32, stderr: String) -> Result<()> { - self.conn - .call(move |c| { - let mut stmt = c.prepare( - r#"UPDATE job_runs SET stderr = stderr || ?, updated_at = datetime('now') WHERE id = ?"#, - )?; - stmt.execute((stderr, job_id))?; - Ok(()) - }) - .await?; + query!( + r#"UPDATE job_runs SET stderr = stderr || ?, updated_at = datetime('now') WHERE id = ?"#, + stderr, + job_id + ).execute(&self.pool).await?; Ok(()) } pub async fn update_job_status(&self, run: JobRunDAO) -> Result<()> { - self.conn - .call(move |c| { - let mut stmt = c.prepare( - r#"UPDATE job_runs SET end_ts = ?, exit_code = ?, updated_at = datetime('now') WHERE id = ?"#, - )?; - stmt.execute(( - run.finish_time, - run.exit_code, - run.id, - ))?; - Ok(()) - }) - .await?; + query!( + r#"UPDATE job_runs SET end_ts = ?, exit_code = ?, updated_at = datetime('now') WHERE id = ?"#, + run.finish_time, + run.exit_code, + run.id + ).execute(&self.pool).await?; Ok(()) } @@ -117,15 +81,7 @@ impl DbConn { backwards, ); - let runs = self - .conn - .call(move |c| { - let mut stmt = c.prepare(&query)?; - let run_iter = stmt.query_map([], JobRunDAO::from_row)?; - Ok(run_iter.filter_map(|x| x.ok()).collect::>()) - }) - .await?; - + let runs = sqlx::query_as(&query).fetch_all(&self.pool).await?; Ok(runs) } } diff --git a/ee/tabby-db/src/lib.rs b/ee/tabby-db/src/lib.rs index 2dac0dbdeee..5835989055f 100644 --- a/ee/tabby-db/src/lib.rs +++ b/ee/tabby-db/src/lib.rs @@ -4,6 +4,7 @@ pub use google_oauth_credential::GoogleOAuthCredentialDAO; pub use invitations::InvitationDAO; pub use job_runs::JobRunDAO; pub use repositories::RepositoryDAO; +use sqlx::{query, query_scalar, Pool, Sqlite, SqlitePool}; pub use users::UserDAO; mod email_setting; @@ -17,52 +18,68 @@ mod repositories; mod users; use anyhow::Result; -use include_dir::{include_dir, Dir}; -use lazy_static::lazy_static; -use rusqlite::params; -use rusqlite_migration::AsyncMigrations; -use tokio_rusqlite::Connection; - -static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations"); - -lazy_static! { - static ref MIGRATIONS: AsyncMigrations = - AsyncMigrations::from_directory(&MIGRATIONS_DIR).unwrap(); -} +use sqlx::sqlite::SqliteConnectOptions; #[derive(Clone)] pub struct DbConn { - conn: Connection, + pool: Pool, } impl DbConn { #[cfg(any(test, feature = "testutils"))] pub async fn new_in_memory() -> Result { - let conn = Connection::open_in_memory().await?; - DbConn::init_db(conn).await + use std::str::FromStr; + + use sqlx::sqlite::SqlitePoolOptions; + + let options = SqliteConnectOptions::from_str("sqlite::memory:")?; + let pool = SqlitePoolOptions::new() + .max_connections(1) + .connect_with(options) + .await?; + DbConn::init_db(pool).await } pub async fn new() -> Result { tokio::fs::create_dir_all(path::db_file().parent().unwrap()).await?; - let conn = Connection::open(path::db_file()).await?; - Self::init_db(conn).await + let options = SqliteConnectOptions::new().filename(path::db_file()); + let pool = SqlitePool::connect_with(options).await?; + Self::init_db(pool).await } /// Initialize database, create tables and insert first token if not exist - async fn init_db(mut conn: Connection) -> Result { - MIGRATIONS.to_latest(&mut conn).await?; + async fn init_db(pool: SqlitePool) -> Result { + sqlx::migrate!("./migrations").run(&pool).await?; let token = uuid::Uuid::new_v4().to_string(); - conn.call(move |c| { - Ok(c.execute( - r#"INSERT OR IGNORE INTO registration_token (id, token) VALUES (1, ?)"#, - params![token], - )) - }) - .await??; - - let res = Self { conn }; - Ok(res) + query!( + "INSERT OR IGNORE INTO registration_token (id, token) VALUES (1, ?)", + token + ) + .execute(&pool) + .await?; + + let conn = Self { pool }; + conn.manual_users_active_migration().await?; + Ok(conn) + } + + /// This migration is applied manually to make the transition between rusqlite and sqlx smooth, + /// since there is no way to conditionally alter a table with a pure SQLite script. + /// Once all users can reasonably be expected to have moved to the sqlx version, + /// we can remove this function. + async fn manual_users_active_migration(&self) -> Result<()> { + let active_exists = + sqlx::query("SELECT * FROM pragma_table_info('users') WHERE name='active'") + .fetch_optional(&self.pool) + .await?; + + if active_exists.is_none() { + sqlx::query("ALTER TABLE users ADD COLUMN active BOOLEAN NOT NULL DEFAULT 1") + .execute(&self.pool) + .await?; + } + Ok(()) } fn make_pagination_query( @@ -105,18 +122,11 @@ impl DbConn { /// Query token from database. /// Since token is global unique for each tabby server, by right there's only one row in the table. pub async fn read_registration_token(&self) -> Result { - let token = self - .conn - .call(|conn| { - Ok(conn.query_row( - r#"SELECT token FROM registration_token WHERE id = 1"#, - [], - |row| row.get(0), - )) - }) - .await?; - - Ok(token?) + Ok( + query_scalar!("SELECT token FROM registration_token WHERE id = 1") + .fetch_one(&self.pool) + .await?, + ) } /// Update token in database. @@ -125,16 +135,14 @@ impl DbConn { let result = token.clone(); let updated_at = chrono::Utc::now(); - let res = self - .conn - .call(move |conn| { - Ok(conn.execute( - r#"UPDATE registration_token SET token = ?, updated_at = ? WHERE id = 1"#, - params![token, updated_at], - )) - }) - .await?; - if res != Ok(1) { + let res = query!( + "UPDATE registration_token SET token = ?, updated_at = ? WHERE id = 1", + token, + updated_at + ) + .execute(&self.pool) + .await?; + if res.rows_affected() != 1 { return Err(anyhow::anyhow!("failed to update token")); } @@ -144,14 +152,8 @@ impl DbConn { #[cfg(test)] mod tests { - use super::*; - #[tokio::test] - async fn migrations_test() { - assert!(MIGRATIONS.validate().await.is_ok()); - } - #[tokio::test] async fn test_token() { let conn = DbConn::new_in_memory().await.unwrap(); diff --git a/ee/tabby-db/src/refresh_tokens.rs b/ee/tabby-db/src/refresh_tokens.rs index efe4fb8f895..cea9873d1d1 100644 --- a/ee/tabby-db/src/refresh_tokens.rs +++ b/ee/tabby-db/src/refresh_tokens.rs @@ -1,10 +1,11 @@ use anyhow::Result; use chrono::{DateTime, Utc}; -use rusqlite::{params, OptionalExtension, Row}; +use sqlx::{query, FromRow}; use super::DbConn; #[allow(unused)] +#[derive(FromRow)] pub struct RefreshTokenDAO { id: u32, created_at: DateTime, @@ -15,21 +16,6 @@ pub struct RefreshTokenDAO { } impl RefreshTokenDAO { - fn select(clause: &str) -> String { - r#"SELECT id, user_id, token, expires_at, created_at FROM refresh_tokens WHERE "#.to_owned() - + clause - } - - fn from_row(row: &Row<'_>) -> std::result::Result { - Ok(RefreshTokenDAO { - id: row.get(0)?, - user_id: row.get(1)?, - token: row.get(2)?, - expires_at: row.get(3)?, - created_at: row.get(4)?, - }) - } - pub fn is_expired(&self) -> bool { let now = chrono::Utc::now(); self.expires_at < now @@ -39,17 +25,13 @@ impl RefreshTokenDAO { /// db read/write operations for `refresh_tokens` table impl DbConn { pub async fn create_refresh_token(&self, user_id: i32, token: &str) -> Result<()> { - let token = token.to_string(); - let res = self - .conn - .call(move |c| { - Ok(c.execute( - r#"INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES (?, ?, datetime('now', '+7 days'))"#, - params![user_id, token], - )) - }) - .await?; - if res != Ok(1) { + let res = query!( + r#"INSERT INTO refresh_tokens (user_id, token, expires_at) VALUES (?, ?, datetime('now', '+7 days'))"#, + user_id, + token + ).execute(&self.pool).await?; + + if res.rows_affected() != 1 { return Err(anyhow::anyhow!("failed to create refresh token")); } @@ -57,18 +39,15 @@ impl DbConn { } pub async fn replace_refresh_token(&self, old: &str, new: &str) -> Result<()> { - let old = old.to_string(); - let new = new.to_string(); - let res = self - .conn - .call(move |c| { - Ok(c.execute( - r#"UPDATE refresh_tokens SET token = ? WHERE token = ?"#, - params![new, old], - )) - }) - .await?; - if res != Ok(1) { + let res = query!( + "UPDATE refresh_tokens SET token = $1 WHERE token = $2", + new, + old + ) + .execute(&self.pool) + .await?; + + if res.rows_affected() != 1 { return Err(anyhow::anyhow!("failed to replace refresh token")); } @@ -76,34 +55,21 @@ impl DbConn { } pub async fn delete_expired_token(&self) -> Result { - let res = self - .conn - .call(move |c| { - Ok(c.execute( - r#"DELETE FROM refresh_tokens WHERE expires_at < ?"#, - params![Utc::now()], - )) - }) + let time = Utc::now(); + let res = query!(r#"DELETE FROM refresh_tokens WHERE expires_at < ?"#, time) + .execute(&self.pool) .await?; - Ok(res? as i32) + Ok(res.rows_affected() as i32) } pub async fn get_refresh_token(&self, token: &str) -> Result> { - let token = token.to_string(); - let token = self - .conn - .call(move |c| { - Ok(c.query_row( - RefreshTokenDAO::select("token = ?").as_str(), - params![token], - RefreshTokenDAO::from_row, - ) - .optional()) - }) + let token = sqlx::query_as("SELECT * FROM refresh_tokens WHERE token = ?") + .bind(token) + .fetch_optional(&self.pool) .await?; - Ok(token?) + Ok(token) } } diff --git a/ee/tabby-db/src/repositories.rs b/ee/tabby-db/src/repositories.rs index a94613c2d22..1acc2067cfc 100644 --- a/ee/tabby-db/src/repositories.rs +++ b/ee/tabby-db/src/repositories.rs @@ -1,24 +1,15 @@ use anyhow::{anyhow, Result}; -use rusqlite::Row; +use sqlx::{prelude::FromRow, query}; use crate::DbConn; +#[derive(FromRow)] pub struct RepositoryDAO { pub id: i32, pub name: String, pub git_url: String, } -impl RepositoryDAO { - fn new(id: i32, name: String, git_url: String) -> Self { - Self { id, name, git_url } - } - - fn from_row(row: &Row) -> Result { - Ok(Self::new(row.get(0)?, row.get(1)?, row.get(2)?)) - } -} - impl DbConn { pub async fn list_repositories_with_filter( &self, @@ -34,54 +25,39 @@ impl DbConn { backwards, ); - self.conn - .call(move |c| { - let thing: Result> = c - .prepare(&query)? - .query_map([], RepositoryDAO::from_row)? - .map(|r| r.map_err(Into::into)) - .collect(); - Ok(thing) - }) - .await? + let repos = sqlx::query_as(&query).fetch_all(&self.pool).await?; + Ok(repos) } pub async fn delete_repository(&self, id: i32) -> Result { - Ok(self - .conn - .call(move |c| { - let deleted = c - .execute("DELETE FROM repositories WHERE id=?", [id]) - .is_ok(); - Ok(deleted) - }) - .await?) + let res = query!("DELETE FROM repositories WHERE id = ?", id) + .execute(&self.pool) + .await?; + Ok(res.rows_affected() == 1) } pub async fn create_repository(&self, name: String, git_url: String) -> Result { - Ok(self - .conn - .call(|c| { - let id = c - .prepare("INSERT INTO repositories (name, git_url) VALUES (?, ?)")? - .insert([name, git_url])?; - Ok(id as i32) - }) - .await?) + let res = query!( + "INSERT INTO repositories (name, git_url) VALUES (?, ?)", + name, + git_url + ) + .execute(&self.pool) + .await?; + + Ok(res.last_insert_rowid() as i32) } pub async fn update_repository(&self, id: i32, name: String, git_url: String) -> Result<()> { - let updated = self - .conn - .call(move |c| { - let update_count = c.execute( - "UPDATE repositories SET git_url=?, name=? WHERE id=?", - (git_url, name, id), - )?; - Ok(update_count == 1) - }) - .await?; - if updated { + let rows = query!( + "UPDATE repositories SET name = ?, git_url = ? WHERE id = ?", + name, + git_url, + id + ) + .execute(&self.pool) + .await?; + if rows.rows_affected() == 1 { Ok(()) } else { Err(anyhow!("failed to update: repository not found")) diff --git a/ee/tabby-db/src/users.rs b/ee/tabby-db/src/users.rs index 877faa6f001..1f7acda468f 100644 --- a/ee/tabby-db/src/users.rs +++ b/ee/tabby-db/src/users.rs @@ -1,11 +1,12 @@ use anyhow::{anyhow, Result}; use chrono::{DateTime, Utc}; -use rusqlite::{params, OptionalExtension, Row}; +use sqlx::{query, query_scalar, FromRow}; use uuid::Uuid; use super::DbConn; #[allow(unused)] +#[derive(FromRow)] pub struct UserDAO { pub created_at: DateTime, pub updated_at: DateTime, @@ -26,19 +27,6 @@ impl UserDAO { .to_owned() + clause } - - fn from_row(row: &Row<'_>) -> std::result::Result { - Ok(UserDAO { - id: row.get(0)?, - email: row.get(1)?, - password_encrypted: row.get(2)?, - is_admin: row.get(3)?, - created_at: row.get(4)?, - updated_at: row.get(5)?, - auth_token: row.get(6)?, - active: row.get(7)?, - }) - } } /// db read/write operations for `users` table @@ -71,71 +59,42 @@ impl DbConn { is_admin: bool, invitation_id: Option, ) -> Result { - let res = self - .conn - .call(move |c| { - let tx = c.transaction()?; - - if let Some(invitation_id) = invitation_id { - tx.execute("DELETE FROM invitations WHERE id = ?", params![invitation_id])?; - } - - let id = { - let mut stmt = tx.prepare( - r#"INSERT INTO users (email, password_encrypted, is_admin, auth_token) VALUES (?, ?, ?, ?)"#, - )?; - stmt.insert((email, password_encrypted, is_admin, generate_auth_token()))? - }; - - tx.commit()?; - Ok(id) - }) - .await?; + let mut transaction = self.pool.begin().await?; + if let Some(invitation_id) = invitation_id { + query!("DELETE FROM invitations WHERE id = ?", invitation_id) + .execute(&mut *transaction) + .await?; + } + let token = generate_auth_token(); + let res = query!("INSERT INTO users (email, password_encrypted, is_admin, auth_token) VALUES (?, ?, ?, ?)", + email, password_encrypted, is_admin, token) + .execute(&mut *transaction).await?; + transaction.commit().await?; - Ok(res as i32) + Ok(res.last_insert_rowid() as i32) } pub async fn get_user(&self, id: i32) -> Result> { - let user = self - .conn - .call(move |c| { - Ok(c.query_row( - UserDAO::select("id = ?").as_str(), - params![id], - UserDAO::from_row, - ) - .optional()) - }) + let user = sqlx::query_as(&UserDAO::select("id = ?")) + .bind(id) + .fetch_optional(&self.pool) .await?; - Ok(user?) + Ok(user) } pub async fn get_user_by_email(&self, email: &str) -> Result> { - let email = email.to_owned(); - let user = self - .conn - .call(move |c| { - Ok(c.query_row( - UserDAO::select("email = ?").as_str(), - params![email], - UserDAO::from_row, - ) - .optional()) - }) + let user = sqlx::query_as(&UserDAO::select("email = ?")) + .bind(email) + .fetch_optional(&self.pool) .await?; - Ok(user?) + Ok(user) } pub async fn list_admin_users(&self) -> Result> { - let users = self - .conn - .call(move |c| { - let mut stmt = c.prepare(&UserDAO::select("is_admin"))?; - let user_iter = stmt.query_map([], UserDAO::from_row)?; - Ok(user_iter.filter_map(|x| x.ok()).collect::>()) - }) + let users = sqlx::query_as(&UserDAO::select("is_admin")) + .fetch_all(&self.pool) .await?; Ok(users) @@ -164,60 +123,46 @@ impl DbConn { backwards, ); - let users = self - .conn - .call(move |c| { - let mut stmt = c.prepare(&query)?; - let user_iter = stmt.query_map([], UserDAO::from_row)?; - Ok(user_iter.filter_map(|x| x.ok()).collect::>()) - }) - .await?; + let users = sqlx::query_as(&query).fetch_all(&self.pool).await?; Ok(users) } pub async fn verify_auth_token(&self, token: &str) -> bool { let token = token.to_owned(); - let id: Result, _> = self - .conn - .call(move |c| { - Ok(c.query_row( - r#"SELECT id FROM users WHERE auth_token = ?"#, - params![token], - |row| row.get(0), - )) - }) + let id = query_scalar!("SELECT id FROM users WHERE auth_token = ?", token) + .fetch_one(&self.pool) .await; - matches!(id, Ok(Ok(_))) + id.is_ok() } pub async fn reset_user_auth_token_by_email(&self, email: &str) -> Result<()> { let email = email.to_owned(); let updated_at = chrono::Utc::now(); - self.conn - .call(move |c| { - let mut stmt = c.prepare( - r#"UPDATE users SET auth_token = ?, updated_at = ? WHERE email = ?"#, - )?; - stmt.execute((generate_auth_token(), updated_at, email))?; - Ok(()) - }) - .await?; + let token = generate_auth_token(); + query!( + r#"UPDATE users SET auth_token = ?, updated_at = ? WHERE email = ?"#, + token, + updated_at, + email + ) + .execute(&self.pool) + .await?; Ok(()) } pub async fn update_user_active(&self, id: i32, active: bool) -> Result<()> { - let changed = self - .conn - .call(move |c| { - let changed = c.execute( - "UPDATE users SET active=? WHERE id=? AND active=?", - (active, id, !active), - )?; - Ok(changed) - }) - .await?; + let not_active = !active; + let changed = query!( + "UPDATE users SET active = ? WHERE id = ? AND active = ?", + active, + id, + not_active + ) + .execute(&self.pool) + .await? + .rows_affected(); if changed != 1 { Err(anyhow!("user active status was not changed")) } else {