From e4af629dd7a4f1dd2a7f4bfebd59a5c2deb53aab Mon Sep 17 00:00:00 2001 From: frectonz Date: Wed, 19 Jun 2024 04:58:45 +0300 Subject: [PATCH] feat: initial libSQL support --- Cargo.lock | 972 ++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + src/main.rs | 493 +++++++++++++++++- ui/src/api.ts | 3 +- ui/src/routes/index.lazy.tsx | 24 +- 5 files changed, 1457 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 826e338..bfd437a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -38,6 +49,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -102,6 +119,34 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.80" @@ -119,6 +164,51 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.71" @@ -140,6 +230,50 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" @@ -155,6 +289,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -172,6 +315,18 @@ name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +dependencies = [ + "serde", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] [[package]] name = "cc" @@ -179,6 +334,15 @@ version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +[[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" @@ -200,6 +364,27 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.6" @@ -273,6 +458,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -329,6 +524,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -344,6 +545,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "eyre" version = "0.6.12" @@ -354,6 +565,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -381,6 +598,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.30" @@ -397,6 +629,34 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -415,9 +675,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -450,6 +714,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "h2" version = "0.3.26" @@ -462,13 +732,19 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -476,6 +752,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.5", ] [[package]] @@ -484,7 +770,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -493,7 +779,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64", + "base64 0.21.7", "bytes", "headers-core", "http 0.2.12", @@ -523,6 +809,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -556,6 +851,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "httparse" version = "1.8.0" @@ -592,6 +893,36 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "webpki-roots", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -650,6 +981,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -657,7 +998,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "block-padding", + "generic-array", ] [[package]] @@ -666,6 +1017,15 @@ version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -687,12 +1047,161 @@ 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 = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + +[[package]] +name = "libsql" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007e45366a683bc61d5a6e4c05a4b91100c96ec04198d1d4529eb5a89b3c589f" +dependencies = [ + "anyhow", + "async-stream", + "async-trait", + "base64 0.21.7", + "bincode", + "bitflags 2.5.0", + "bytes", + "fallible-iterator 0.3.0", + "futures", + "http 0.2.12", + "hyper", + "hyper-rustls", + "libsql-hrana", + "libsql-sqlite3-parser", + "libsql-sys", + "libsql_replication", + "parking_lot", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tonic-web", + "tower", + "tower-http", + "tracing", + "uuid", + "zerocopy", +] + +[[package]] +name = "libsql-ffi" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503716a5a09a73719e011957c363a69348b52818bd7679e4ee463099f23ffa89" +dependencies = [ + "bindgen", + "cc", +] + +[[package]] +name = "libsql-hrana" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeaf5d19e365465e1c23d687a28c805d7462531b3f619f0ba49d3cf369890a3e" +dependencies = [ + "base64 0.21.7", + "bytes", + "prost", + "serde", +] + +[[package]] +name = "libsql-rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11e74daae7edb0a3ab7d1568c6d0be43bb255f00b2b3b6d1b90ae7007a76c6f9" +dependencies = [ + "bitflags 2.5.0", + "fallible-iterator 0.2.0", + "fallible-streaming-iterator", + "hashlink 0.8.4", + "libsql-ffi", + "smallvec", +] + +[[package]] +name = "libsql-sqlite3-parser" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735c12460491141cf29496f258b30130f5830c7923eb373fc4d1c710c2d6d62c" +dependencies = [ + "bitflags 2.5.0", + "cc", + "fallible-iterator 0.3.0", + "indexmap 2.2.6", + "log", + "memchr", + "phf", + "phf_codegen", + "phf_shared", + "smallvec", + "uncased", +] + +[[package]] +name = "libsql-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782109ea979be605ca908c2024cba05a11b32428198a2adb5ccb05b378169903" +dependencies = [ + "bytes", + "libsql-ffi", + "libsql-rusqlite", + "once_cell", + "tracing", + "zerocopy", +] + +[[package]] +name = "libsql_replication" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa2f887b42d9d966e4358d398dcba87bb229bc1f62f1229f983ce663fe264d7" +dependencies = [ + "aes", + "async-stream", + "async-trait", + "bytes", + "cbc", + "libsql-rusqlite", + "libsql-sys", + "parking_lot", + "prost", + "serde", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "uuid", + "zerocopy", +] + [[package]] name = "libsqlite3-sys" version = "0.28.0" @@ -704,6 +1213,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -729,6 +1244,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.2" @@ -751,6 +1272,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.7.3" @@ -789,6 +1316,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -843,6 +1380,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "overload" version = "0.1.1" @@ -884,12 +1427,57 @@ 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 = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", + "uncased", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -934,6 +1522,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.85" @@ -943,6 +1541,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quote" version = "1.0.36" @@ -988,7 +1609,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags", + "bitflags 2.5.0", ] [[package]] @@ -1035,16 +1656,31 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rusqlite" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags", - "fallible-iterator", + "bitflags 2.5.0", + "fallible-iterator 0.3.0", "fallible-streaming-iterator", - "hashlink", + "hashlink 0.9.1", "libsqlite3-sys", "smallvec", ] @@ -1055,12 +1691,100 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1073,6 +1797,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.203" @@ -1136,6 +1883,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -1145,6 +1898,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1184,7 +1943,9 @@ dependencies = [ "chrono", "clap", "color-eyre", + "futures", "include_dir", + "libsql", "open", "rusqlite", "serde", @@ -1202,6 +1963,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.66" @@ -1213,6 +1980,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "thiserror" version = "1.0.61" @@ -1277,6 +2050,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.3.0" @@ -1299,6 +2082,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.21.0" @@ -1324,6 +2129,104 @@ dependencies = [ "tokio", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2", + "http 0.2.12", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", + "webpki-roots", +] + +[[package]] +name = "tonic-web" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc3b0e1cedbf19fdfb78ef3d672cb9928e0a91a9cb4629cc0c916e8cff8aaaa1" +dependencies = [ + "base64 0.21.7", + "bytes", + "http 0.2.12", + "http-body", + "hyper", + "pin-project", + "tokio-stream", + "tonic", + "tower-http", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.5.0", + "bytes", + "futures-core", + "futures-util", + "http 0.2.12", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -1433,6 +2336,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.7.0" @@ -1463,6 +2375,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.0" @@ -1486,6 +2404,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "valuable" version = "0.1.0" @@ -1602,6 +2530,27 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1835,6 +2784,7 @@ version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -1848,3 +2798,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index c69a8da..9d97856 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } warp = "0.3.7" open = "3.2.0" async-trait = "0.1.80" +futures = "0.3.30" +libsql = { version = "0.4.0", features = ["remote"] } [profile.release] strip = true diff --git a/src/main.rs b/src/main.rs index bf8b45c..7390361 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,6 +24,15 @@ enum Command { /// hand, a sample db will be created for you] database: String, }, + + /// A remote SQLite database via libSQL. + Libsql { + /// libSQL server address + url: String, + + /// libSQL authentication token. + auth_token: String, + }, } #[tokio::main] @@ -40,13 +49,14 @@ async fn main() -> color_eyre::Result<()> { let args = Args::parse(); let db = match args.db { - Command::Sqlite { database } => { - if database == "preview" { - tokio::fs::write("sample.db", SAMPLE_DB).await?; - sqlite::Db::open("sample.db".to_string()).await? - } else { - sqlite::Db::open(database).await? - } + Command::Sqlite { database } => AllDbs::Sqlite(if database == "preview" { + tokio::fs::write("sample.db", SAMPLE_DB).await?; + sqlite::Db::open("sample.db".to_string()).await? + } else { + sqlite::Db::open(database).await? + }), + Command::Libsql { url, auth_token } => { + AllDbs::Libsql(libsql::Db::open(url, auth_token).await?) } }; @@ -135,8 +145,6 @@ mod statics { #[async_trait] trait Database: Sized + Clone + Send { - async fn open(path: String) -> color_eyre::Result; - async fn overview(&self) -> color_eyre::Result; async fn tables(&self) -> color_eyre::Result; @@ -149,6 +157,54 @@ trait Database: Sized + Clone + Send { async fn query(&self, query: String) -> color_eyre::Result; } +#[derive(Clone)] +enum AllDbs { + Sqlite(sqlite::Db), + Libsql(libsql::Db), +} + +#[async_trait] +impl Database for AllDbs { + async fn overview(&self) -> color_eyre::Result { + match self { + AllDbs::Sqlite(x) => x.overview().await, + AllDbs::Libsql(x) => x.overview().await, + } + } + + async fn tables(&self) -> color_eyre::Result { + match self { + AllDbs::Sqlite(x) => x.tables().await, + AllDbs::Libsql(x) => x.tables().await, + } + } + + async fn table(&self, name: String) -> color_eyre::Result { + match self { + AllDbs::Sqlite(x) => x.table(name).await, + AllDbs::Libsql(x) => x.table(name).await, + } + } + + async fn table_data( + &self, + name: String, + page: i32, + ) -> color_eyre::Result { + match self { + AllDbs::Sqlite(x) => x.table_data(name, page).await, + AllDbs::Libsql(x) => x.table_data(name, page).await, + } + } + + async fn query(&self, query: String) -> color_eyre::Result { + match self { + AllDbs::Sqlite(x) => x.query(query).await, + AllDbs::Libsql(x) => x.query(query).await, + } + } +} + mod sqlite { use async_trait::async_trait; use color_eyre::eyre::OptionExt; @@ -163,9 +219,8 @@ mod sqlite { conn: Arc, } - #[async_trait] - impl Database for Db { - async fn open(path: String) -> color_eyre::Result { + impl Db { + pub async fn open(path: String) -> color_eyre::Result { let conn = Connection::open_with_flags(&path, OpenFlags::SQLITE_OPEN_READ_ONLY).await?; // This is meant to test if the file at path is actually a DB. @@ -188,7 +243,10 @@ mod sqlite { conn: Arc::new(conn), }) } + } + #[async_trait] + impl Database for Db { async fn overview(&self) -> color_eyre::Result { let file_name = Path::new(&self.path) .file_name() @@ -201,7 +259,7 @@ mod sqlite { let sqlite_version = tokio_rusqlite::version().to_owned(); let file_size = helpers::format_size(metadata.len() as f64); - let modified = metadata.modified()?.into(); + let modified = Some(metadata.modified()?.into()); let created = metadata.created().ok().map(Into::into); let (tables, indexes, triggers, views, counts) = self @@ -416,7 +474,7 @@ mod sqlite { .query_map((), |r| { let mut rows = Vec::with_capacity(columns_len); for i in 0..columns_len { - let val = helpers::value_to_json(r.get_ref(i)?); + let val = helpers::rusqlite_value_to_json(r.get_ref(i)?); rows.push(val); } Ok(rows) @@ -445,7 +503,7 @@ mod sqlite { .query_map((), |r| { let mut rows = Vec::with_capacity(columns_len); for i in 0..columns_len { - let val = helpers::value_to_json(r.get_ref(i)?); + let val = helpers::rusqlite_value_to_json(r.get_ref(i)?); rows.push(val); } Ok(rows) @@ -460,7 +518,398 @@ mod sqlite { } } +mod libsql { + use std::collections::HashMap; + + use async_trait::async_trait; + use color_eyre::eyre::OptionExt; + use futures::{StreamExt, TryStreamExt}; + use libsql::{params, Builder, Connection}; + + use crate::{helpers, responses, Database, ROWS_PER_PAGE}; + + #[derive(Clone)] + pub struct Db { + url: String, + conn: Connection, + } + + impl Db { + pub async fn open(url: String, auth_token: String) -> color_eyre::Result { + let db = Builder::new_remote(url.to_owned(), auth_token) + .build() + .await?; + let conn = db.connect()?; + + let tables = conn + .query( + r#" + SELECT count(*) FROM sqlite_master + WHERE type="table" + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + tracing::info!( + "found {tables} table{} in {url}", + if tables == 1 { "" } else { "s" } + ); + + Ok(Self { url, conn }) + } + } + + #[async_trait] + impl Database for Db { + async fn overview(&self) -> color_eyre::Result { + let file_name = self.url.to_owned(); + + let sqlite_version = self + .conn + .query("SELECT sqlite_version();", ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let file_size = "---".to_owned(); + let modified = None; + let created = None; + + let tables = self + .conn + .query( + r#" + SELECT count(*) FROM sqlite_master + WHERE type="table" + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let indexes = self + .conn + .query( + r#" + SELECT count(*) FROM sqlite_master + WHERE type="index" + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let triggers = self + .conn + .query( + r#" + SELECT count(*) FROM sqlite_master + WHERE type="trigger" + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let views = self + .conn + .query( + r#" + SELECT count(*) FROM sqlite_master + WHERE type="view" + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let table_names = self + .conn + .query(r#"SELECT name FROM sqlite_master WHERE type="table""#, ()) + .await? + .into_stream() + .map_ok(|r| r.get::(0)) + .collect::>() + .await + .into_iter() + .filter_map(|r| r.ok()) + .filter_map(|r| r.ok()) + .collect::>(); + + let mut table_counts = HashMap::with_capacity(table_names.len()); + for name in table_names { + let count = self + .conn + .query(&format!("SELECT count(*) FROM '{name}'"), ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + table_counts.insert(name, count); + } + + let mut counts = table_counts + .into_iter() + .map(|(name, count)| responses::RowCount { name, count }) + .collect::>(); + + counts.sort_by(|a, b| b.count.cmp(&a.count)); + + Ok(responses::Overview { + file_name, + sqlite_version, + file_size, + created, + modified, + tables, + indexes, + triggers, + views, + counts, + }) + } + + async fn tables(&self) -> color_eyre::Result { + let table_names = self + .conn + .query(r#"SELECT name FROM sqlite_master WHERE type="table""#, ()) + .await? + .into_stream() + .map_ok(|r| r.get::(0)) + .collect::>() + .await + .into_iter() + .filter_map(|r| r.ok()) + .filter_map(|r| r.ok()) + .collect::>(); + + let mut table_counts = HashMap::with_capacity(table_names.len()); + for name in table_names { + let count = self + .conn + .query(&format!("SELECT count(*) FROM '{name}'"), ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + table_counts.insert(name, count); + } + + let mut tables = table_counts + .into_iter() + .map(|(name, count)| responses::RowCount { name, count }) + .collect::>(); + + tables.sort_by(|a, b| b.count.cmp(&a.count)); + + Ok(responses::Tables { tables }) + } + + async fn table(&self, name: String) -> color_eyre::Result { + let sql = self + .conn + .query( + r#" + SELECT sql FROM sqlite_master + WHERE type="table" AND name = ?1 + "#, + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let row_count = self + .conn + .query(&format!("SELECT count(*) FROM '{name}'"), ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let table_size = self + .conn + .query( + "SELECT SUM(pgsize) FROM dbstat WHERE name = ?1", + params![name.to_owned()], + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + let table_size = helpers::format_size(table_size as f64); + + let index_count = self + .conn + .query( + "SELECT count(*) FROM sqlite_master WHERE type='index' AND tbl_name=?1", + (), + ) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(0)?; + + let has_primary_key = self + .conn + .query(&format!("PRAGMA table_info('{name}')"), ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(5)? + == 1; + + let index_count = if has_primary_key { + index_count + 1 + } else { + index_count + }; + + let column_count = self + .conn + .query(&format!("PRAGMA table_info('{name}')"), ()) + .await? + .into_stream() + .map_ok(|r| r.get::(1)) + .collect::>() + .await + .into_iter() + .filter_map(|r| r.ok()) + .filter_map(|r| r.ok()) + .count() as i32; + + Ok(responses::Table { + name, + sql, + row_count, + table_size, + index_count, + column_count, + }) + } + + async fn table_data( + &self, + name: String, + page: i32, + ) -> color_eyre::Result { + let first_column = self + .conn + .query(&format!("PRAGMA table_info('{name}')"), ()) + .await? + .next() + .await? + .ok_or_eyre("no row returned from db")? + .get::(1)?; + + let offset = (page - 1) * ROWS_PER_PAGE; + let mut stmt = self + .conn + .prepare(&format!( + r#" + SELECT * + FROM '{name}' + ORDER BY {first_column} + LIMIT {ROWS_PER_PAGE} + OFFSET {offset} + "# + )) + .await?; + + let columns = stmt + .columns() + .into_iter() + .map(|c| c.name().to_owned()) + .collect::>(); + + let columns_len = columns.len(); + let rows = stmt + .query(()) + .await? + .into_stream() + .map_ok(|r| { + let mut rows = Vec::with_capacity(columns_len); + for i in 0..columns_len { + let val = helpers::libsql_value_to_json(r.get_value(i as i32)?); + rows.push(val); + } + color_eyre::eyre::Ok(rows) + }) + .collect::>() + .await + .into_iter() + .filter_map(|r| r.ok()) + .filter_map(|r| r.ok()) + .collect::>(); + + Ok(responses::TableData { columns, rows }) + } + + async fn query(&self, query: String) -> color_eyre::Result { + let mut stmt = self.conn.prepare(&query).await?; + + let columns = stmt + .columns() + .into_iter() + .map(|c| c.name().to_owned()) + .collect::>(); + + let columns_len = columns.len(); + let rows = stmt + .query(()) + .await? + .into_stream() + .map_ok(|r| { + let mut rows = Vec::with_capacity(columns_len); + for i in 0..columns_len { + let val = helpers::libsql_value_to_json(r.get_value(i as i32)?); + rows.push(val); + } + color_eyre::eyre::Ok(rows) + }) + .collect::>() + .await + .into_iter() + .filter_map(|r| r.ok()) + .filter_map(|r| r.ok()) + .collect::>(); + + Ok(responses::Query { columns, rows }) + } + } +} + mod helpers { + use libsql::Value; use tokio_rusqlite::types::ValueRef; pub fn format_size(mut size: f64) -> String { @@ -475,7 +924,7 @@ mod helpers { format!("{:.2} {}", size, UNITS[unit]) } - pub fn value_to_json(v: ValueRef) -> serde_json::Value { + pub fn rusqlite_value_to_json(v: ValueRef) -> serde_json::Value { match v { ValueRef::Null => serde_json::Value::Null, ValueRef::Integer(x) => serde_json::json!(x), @@ -484,6 +933,16 @@ mod helpers { ValueRef::Blob(s) => serde_json::json!(s), } } + + pub fn libsql_value_to_json(v: Value) -> serde_json::Value { + match v { + Value::Null => serde_json::Value::Null, + Value::Integer(x) => serde_json::json!(x), + Value::Real(x) => serde_json::json!(x), + Value::Text(s) => serde_json::Value::String(s), + Value::Blob(s) => serde_json::json!(s), + } + } } mod responses { @@ -496,7 +955,7 @@ mod responses { pub sqlite_version: String, pub file_size: String, pub created: Option>, - pub modified: DateTime, + pub modified: Option>, pub tables: i32, pub indexes: i32, pub triggers: i32, diff --git a/ui/src/api.ts b/ui/src/api.ts index 581372e..7505b41 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -15,7 +15,8 @@ const overview = z.object({ modified: z .string() .datetime() - .transform((x) => new Date(x)), + .transform((x) => new Date(x)) + .nullable(), tables: z.number(), indexes: z.number(), triggers: z.number(), diff --git a/ui/src/routes/index.lazy.tsx b/ui/src/routes/index.lazy.tsx index 9239cad..1d9d16a 100644 --- a/ui/src/routes/index.lazy.tsx +++ b/ui/src/routes/index.lazy.tsx @@ -148,17 +148,19 @@ function Index() { )} - - -
Modified on
-
- The date and time when the DB was last modified. -
-
- - {data.modified.toUTCString()} - -
+ {data.modified && ( + + +
Modified on
+
+ The date and time when the DB was last modified. +
+
+ + {data.modified.toUTCString()} + +
+ )}