diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1d2f0d2..f7839f5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,16 +13,28 @@ jobs: name: Build LibSqlite_Turso runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Checkout code + uses: actions/checkout@v3 + - uses: jirutka/setup-alpine@v1 with: - toolchain: stable - - run: | - cargo build - cargo build --release --all-features - + arch: x86_64 + packages: > + openssl-dev + build-base + pkgconf + lld + rustup + - name: Install Rust stable toolchain via rustup + run: rustup-init --default-toolchain nightly --profile minimal -y + shell: alpine.sh {0} + - run: | + export RUSTFLAGS="-C target-feature=-crt-static" + cargo build + cargo build --release + shell: alpine.sh {0} + - name: Find built shared library id: find_artifact shell: bash diff --git a/Cargo.lock b/Cargo.lock index 9ce8925..2ba8cb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" @@ -26,6 +26,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "async-compression" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +dependencies = [ + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -34,9 +47,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -73,18 +86,18 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "core-foundation" @@ -102,6 +115,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -144,6 +166,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -255,9 +287,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -372,9 +404,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ "base64", "bytes", @@ -513,6 +545,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "ipnet" version = "2.11.0" @@ -531,9 +574,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" @@ -582,9 +625,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -594,9 +637,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] @@ -696,9 +739,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -723,18 +766,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -780,6 +823,7 @@ version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ + "async-compression", "base64", "bytes", "encoding_rs", @@ -807,6 +851,7 @@ dependencies = [ "sync_wrapper", "tokio", "tokio-native-tls", + "tokio-util", "tower", "tower-http", "tower-service", @@ -832,28 +877,28 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.28" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ "once_cell", "rustls-pki-types", @@ -873,9 +918,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.3" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -890,9 +935,9 @@ checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "schannel" @@ -928,18 +973,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -948,9 +993,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.134" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -990,12 +1035,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1012,9 +1057,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.91" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1087,17 +1132,19 @@ dependencies = [ [[package]] name = "tokio" -version = "1.42.0" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "pin-project-lite", + "slab", "socket2", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1122,9 +1169,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -1205,9 +1252,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "untrusted" @@ -1402,7 +1449,7 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.3", ] [[package]] @@ -1423,10 +1470,11 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ + "windows-link", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -1612,9 +1660,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", diff --git a/Cargo.toml b/Cargo.toml index 34bfa83..a7b530d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,4 @@ regex = "1.11.1" serde = { version = "1.0.216", features = ["derive"] } serde_json = "1.0.134" tokio = { version = "1.42.0", features = ["rt-multi-thread"] } -reqwest = { version = "0.12.9", features = ["json", "blocking"] } +reqwest = { version = "0.12.9", features = ["json", "blocking", "gzip"] } diff --git a/bin/sqlite_test.dart b/bin/sqlite_test.dart index 746dc48..6c66d33 100644 --- a/bin/sqlite_test.dart +++ b/bin/sqlite_test.dart @@ -21,7 +21,7 @@ void main() { // "INSERT INTO users (name, email) VALUES ('Alice', 'alice@gmail.com')", ]; - final db = sqlite3.open('test-database-chima'); + final db = sqlite3.open('untrue-necklace'); for (final command in commmands) db.execute(command); // fetch data diff --git a/src/auth.rs b/src/auth.rs new file mode 100644 index 0000000..eab170e --- /dev/null +++ b/src/auth.rs @@ -0,0 +1,64 @@ +use std::{future::Future, pin::Pin}; + +use crate::utils::TursoConfig; + +pub trait DbAuthStrategy { + fn resolve<'a>( + &'a self, + db_name: &'a str, + client: &'a reqwest::Client, + ) -> Pin>> + Send + 'a>>; +} + +pub struct GlobeStrategy; + +impl DbAuthStrategy for GlobeStrategy { + fn resolve<'a>( + &'a self, + db_name: &'a str, + client: &'a reqwest::Client, + ) -> Pin>> + Send + 'a>> + { + Box::pin(async move { + let globe_auth_api = std::env::var("GLOBE_DS_API")?; + + let request_body = serde_json::json!({ "db_name": db_name }); + + let response = client + .post(format!("{}/db/auth", globe_auth_api)) + .body(request_body.to_string()) + .send() + .await + .map_err(|_| "Failed to fetch auth credentials for database")?; + + if !response.status().is_success() { + return Err(format!("Failed to get Auth Token: {}", response.status()).into()); + } + + let json = response.json().await?; + let config = serde_json::from_value(json)?; + Ok(config) + }) + } +} + +pub struct EnvVarStrategy; + +impl DbAuthStrategy for EnvVarStrategy { + fn resolve<'a>( + &'a self, + _: &'a str, + _client: &'a reqwest::Client, + ) -> Pin>> + Send + 'a>> + { + Box::pin(async move { + let url = std::env::var("TURSO_DB_URL")?; + let token = std::env::var("TURSO_DB_TOKEN")?; + + Ok(TursoConfig { + db_url: url, + db_token: token, + }) + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index bf971b9..580242a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,6 @@ use std::{ collections::HashMap, ffi::{c_int, c_uint, c_void, CStr, CString}, os::raw::c_char, - process::exit, slice, sync::Mutex, }; @@ -13,13 +12,17 @@ use sqlite::{ SQLITE_DONE, SQLITE_ERROR, SQLITE_FLOAT, SQLITE_INTEGER, SQLITE_MISUSE, SQLITE_NULL, SQLITE_OK, SQLITE_RANGE, SQLITE_TEXT, }; -use utils::{execute_async_task, read_turso_config}; - -use crate::utils::{ - count_parameters, extract_column_names, sql_is_begin_transaction, sql_is_commit, sql_is_pragma, - sql_is_rollback, TursoConfig, +use utils::execute_async_task; + +use crate::{ + auth::{DbAuthStrategy, GlobeStrategy}, + utils::{ + count_parameters, extract_column_names, get_tokio, sql_is_begin_transaction, sql_is_commit, + sql_is_pragma, sql_is_rollback, + }, }; +mod auth; mod proxy; mod sqlite; mod utils; @@ -60,22 +63,21 @@ pub unsafe extern "C" fn sqlite3_open_v2( let filename = CStr::from_ptr(filename).to_str().unwrap(); if filename.contains(":memory") { eprintln!("LibSqlite3_Turso Error: Memory store is not supported at runtime"); - exit(1); + return SQLITE_MISUSE; } - let turso_config = read_turso_config().unwrap_or_else(|_| TursoConfig { - db_url: format!( - "https://{}.aws-us-west-2.turso.io", - filename - ), - db_token: String::from("eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhIjoicnciLCJnaWQiOiIyMzBiZDc4Ni1iN2I3LTRlYjgtYjkyMy00ZjM5MDRjYTVkMzciLCJpYXQiOjE3NTEwNTc3MzYsInJpZCI6ImQ1N2NjZTQzLWVhNWItNDFmMy1hNWZlLTE2ZWI4MjIxZTkwOCJ9.ItDyuzwvUqeXwc6KsQkjf6dUVAoQ5BkhvlxFD7nDRCl6thxopIKckJ-w7boX-2ms_-jjgVQuhj9PqYAsaycFAg"), - }); - let reqwest_client = reqwest::Client::builder() .user_agent("libsqlite3_turso/1.0.0") .build() .unwrap(); + let auth_strategy = Box::new(GlobeStrategy); + let turso_config = get_tokio().block_on(auth_strategy.resolve(filename, &reqwest_client)); + if turso_config.is_err() { + eprintln!("LibSqlite3_Turso Error: {}", turso_config.unwrap_err()); + return SQLITE_ERROR; + } + let mock_db = Box::into_raw(Box::new(SQLite3 { client: reqwest_client, error_stack: Mutex::new(vec![]), @@ -84,13 +86,9 @@ pub unsafe extern "C" fn sqlite3_open_v2( delete_hook: Mutex::new(None), insert_hook: Mutex::new(None), update_hook: Mutex::new(None), - turso_config: turso_config, + turso_config: turso_config.unwrap(), })); - if let Ok(config) = read_turso_config() { - (*mock_db).turso_config = config; - } - *db = mock_db; SQLITE_OK @@ -239,6 +237,22 @@ pub unsafe extern "C" fn sqlite3_bind_int64( SQLITE_OK } +#[no_mangle] +pub extern "C" fn sqlite3_bind_null(stmt_ptr: *mut SQLite3PreparedStmt, index: c_int) -> c_int { + if stmt_ptr.is_null() { + return SQLITE_MISUSE; + } + + let stmt = unsafe { &mut *stmt_ptr }; + + if index <= 0 || index > stmt.param_count { + return SQLITE_RANGE; + } + + stmt.params.insert(index, Value::Null); + SQLITE_OK +} + #[no_mangle] pub unsafe extern "C" fn sqlite3_step(stmt_ptr: *mut SQLite3PreparedStmt) -> c_int { if stmt_ptr.is_null() { @@ -249,7 +263,7 @@ pub unsafe extern "C" fn sqlite3_step(stmt_ptr: *mut SQLite3PreparedStmt) -> c_i let mut exec_state = match stmt.execution_state.lock() { Ok(guard) => guard, - Err(_) => return SQLITE_ERROR, // Lock poisoned + Err(_) => return SQLITE_ERROR, }; match *exec_state { @@ -264,9 +278,7 @@ pub unsafe extern "C" fn sqlite3_step(stmt_ptr: *mut SQLite3PreparedStmt) -> c_i drop(exec_state); let sql = stmt.sql.to_uppercase(); - if sql.starts_with("INSERT") { - return execute_async_task(stmt.db, sqlite::handle_insert(stmt)); - } else if sql.starts_with("SELECT") { + if sql.starts_with("SELECT") { return execute_async_task(stmt.db, sqlite::handle_select(stmt)); } else if sql_is_begin_transaction(&sql) { return execute_async_task(stmt.db, sqlite::begin_tnx_on_db(stmt.db)); @@ -274,7 +286,7 @@ pub unsafe extern "C" fn sqlite3_step(stmt_ptr: *mut SQLite3PreparedStmt) -> c_i return execute_async_task(stmt.db, sqlite::commit_tnx_on_db(stmt.db)); } - SQLITE_ERROR + execute_async_task(stmt.db, sqlite::execute_statement(stmt)) } #[no_mangle] @@ -366,18 +378,18 @@ pub extern "C" fn sqlite3_extended_errcode(db: *mut SQLite3) -> c_int { #[no_mangle] pub extern "C" fn sqlite3_errmsg(db: *mut SQLite3) -> *const c_char { if db.is_null() { - return std::ptr::null(); + return b"Invalid DB pointer\0".as_ptr() as *const c_char; } let db = unsafe { &mut *db }; if let Some(error_entry) = sqlite::get_latest_error(db) { match CString::new(error_entry.0) { - Ok(c_string) => c_string.into_raw(), + Ok(c_string) => c_string.as_ptr(), Err(_) => std::ptr::null(), } } else { - std::ptr::null() + b"No error\0".as_ptr() as *const c_char } } @@ -597,14 +609,14 @@ pub unsafe extern "C" fn sqlite3_exec( if sql_is_pragma(&sql) { return SQLITE_OK; } else if sql_is_begin_transaction(&sql) { - execute_async_task(db, sqlite::begin_tnx_on_db(db)) + return execute_async_task(db, sqlite::begin_tnx_on_db(db)); } else if sql_is_rollback(&sql) { - reset_txn_on_db(db) + return reset_txn_on_db(db); } else if sql_is_commit(&sql) { - execute_async_task(db, sqlite::commit_tnx_on_db(db)) - } else { - execute_async_task(db, sqlite::handle_execute(db, &sql)) + return execute_async_task(db, sqlite::commit_tnx_on_db(db)); } + + execute_async_task(db, sqlite::handle_execute(db, &sql)) } #[no_mangle] diff --git a/src/proxy.rs b/src/proxy.rs index f749725..9c1d5f8 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -111,8 +111,13 @@ async fn send_sql_request( config: &TursoConfig, request: serde_json::Value, ) -> Result> { + if cfg!(debug_assertions) { + println!("Sending SQL Request: {}", request); + } + let response: serde_json::Value = send_remote_request(client, config, "v2/pipeline", request).await?; + let parsed: RemoteSqliteResponse = serde_json::from_value(response)?; Ok(parsed) } @@ -124,7 +129,7 @@ async fn send_remote_request( request: serde_json::Value, ) -> Result> { let response = client - .post(format!("{}/{}", turso_config.db_url, path)) + .post(format!("https://{}/{}", turso_config.db_url, path)) .header("Content-Type", "application/json") .header("Authorization", format!("Bearer {}", turso_config.db_token)) .json(&request) @@ -132,14 +137,27 @@ async fn send_remote_request( .await?; let status = response.status(); - let response_text: String = response.text().await?; + let response_text = response.text().await?; - let parsed_response = serde_json::from_str::(&response_text); - if parsed_response.is_err() && !status.is_success() { + if cfg!(debug_assertions) { + println!("Received Response: {}\n", &response_text); + } + + if !status.is_success() { + if let Ok(error_body) = serde_json::from_str::(&response_text) { + if let Some(error_message) = error_body.get("error").and_then(|e| e.as_str()) { + return Err(error_message.into()); + } + } + return Err(format!("LibSqlite3_Turso Error: {}", response_text).into()); + } + + let parsed_response = serde_json::from_str(&response_text); + if parsed_response.is_err() { return Err(format!("Failed to parse response: {}", parsed_response.unwrap_err()).into()); } - let parsed_response = parsed_response.unwrap(); + let parsed_response: serde_json::Value = parsed_response.unwrap(); if let Some(results) = parsed_response.get("results").and_then(|r| r.as_array()) { for result in results { if let Some(error) = result @@ -152,21 +170,6 @@ async fn send_remote_request( } } - // Check if the status code indicates success - if !status.is_success() { - if let Ok(error_body) = serde_json::from_str::(&response_text) { - if let Some(error_message) = error_body.get("error").and_then(|e| e.as_str()) { - return Err(error_message.into()); - } - } - - return Err(format!("LibSqlite3_Turso Error: {}", response_text).into()); - } - - if cfg!(debug_assertions) { - println!("SQL Request: {}\nResponse: {}\n", request, parsed_response); - } - Ok(parsed_response) } @@ -181,12 +184,12 @@ pub fn convert_params_to_json(params: &HashMap) -> Vec serde_json::json!({ "type": "integer", - "value": *i + "value": *i.to_string() }), Value::Real(f) => serde_json::json!({ "type": "float", - "value": *f + "value": *f.to_string() }), Value::Text(s) => serde_json::json!({ "type": "text", diff --git a/src/sqlite.rs b/src/sqlite.rs index db5885b..79ec431 100644 --- a/src/sqlite.rs +++ b/src/sqlite.rs @@ -141,58 +141,68 @@ pub struct SQLite3PreparedStmt { pub db: *mut SQLite3, // Pointer to the associated database } -async unsafe fn execute_stmt( - stmt: &mut SQLite3PreparedStmt, -) -> Result> { - let db: &SQLite3 = &*stmt.db; - let baton_str = { - let baton = db.transaction_baton.lock().unwrap(); - baton.as_ref().map(|s| s.as_str()).map(|s| s.to_owned()) - }; +impl SQLite3PreparedStmt { + pub fn new(db: *mut SQLite3, sql: &str) -> Self { + SQLite3PreparedStmt { + sql: sql.to_string(), + param_count: 0, + params: HashMap::new(), + execution_state: Mutex::new(ExecutionState::Prepared), + result_rows: Mutex::new(Vec::new()), + current_row: Mutex::new(None), + column_names: Vec::new(), + db, + } + } +} - let params = convert_params_to_json(&stmt.params); - let response = execute_sql_and_params(db, &stmt.sql, params, baton_str.as_ref()).await?; +pub fn get_latest_error(db: &SQLite3) -> Option<(String, c_int)> { + if let Ok(stack) = db.error_stack.lock() { + stack.last().cloned() + } else { + None + } +} - let result = get_execution_result(db, &response)?; +pub fn reset_txn_on_db(db: *mut SQLite3) -> c_int { + if db.is_null() { + return SQLITE_MISUSE; + } - Ok(result.clone()) -} + let db = unsafe { &mut *db }; -async unsafe fn execute_stmt_and_populate_result_rows( - stmt: &mut SQLite3PreparedStmt, -) -> Result> { - let response = execute_stmt(stmt).await?; - let mut result_rows = stmt.result_rows.lock().unwrap(); + if !db.transaction_active() { + return SQLITE_OK; + } - let rows = response.rows; - let columns = response.cols; - stmt.column_names = columns.iter().map(|col| col.name.clone()).collect(); + db.transaction_baton.lock().unwrap().take(); - *result_rows = rows - .iter() - .map(|row| { - let result = row - .iter() - .map(|row| match row.r#type.as_str() { - "integer" => match &row.value { - serde_json::Value::String(s) => { - Value::Integer(s.parse::().unwrap_or(0)) - } - serde_json::Value::Number(n) => Value::Integer(n.as_i64().unwrap_or(0)), - _ => Value::Integer(0), - }, - "float" => Value::Real(row.value.as_f64().unwrap_or(0.0)), - "text" => Value::Text(row.value.as_str().unwrap_or("").to_string()), - "null" => Value::Null, - _ => Value::Null, - }) - .collect(); + SQLITE_OK +} - result - }) - .collect(); +pub fn push_error(db: *mut SQLite3, error: (String, c_int)) { + if db.is_null() { + return; + } - Ok(SQLITE_OK) + // Safety: Convert the raw pointer to a mutable reference + let db = unsafe { &mut *db }; + + if let Ok(mut stack) = db.error_stack.lock() { + stack.push(error); + } +} + +pub async unsafe fn execute_statement( + stmt: &mut SQLite3PreparedStmt, +) -> Result> { + match execute_stmt(stmt).await { + Ok(_) => Ok(SQLITE_OK), + Err(e) => { + push_error(stmt.db, (e.to_string(), SQLITE_ERROR)); + Err(e) + } + } } pub async unsafe fn handle_select(stmt: &mut SQLite3PreparedStmt) -> Result> { @@ -253,59 +263,14 @@ pub async unsafe fn handle_select(stmt: &mut SQLite3PreparedStmt) -> Result Result> { - match execute_stmt(stmt).await { - Ok(_) => Ok(SQLITE_OK), - Err(e) => Err(e), - } -} - -pub fn get_latest_error(db: &SQLite3) -> Option<(String, c_int)> { - if let Ok(stack) = db.error_stack.lock() { - stack.last().cloned() - } else { - None - } -} - -pub fn reset_txn_on_db(db: *mut SQLite3) -> c_int { - if db.is_null() { - return SQLITE_MISUSE; - } - - let db = unsafe { &mut *db }; - - if !db.transaction_active() { - return SQLITE_OK; - } - - db.transaction_baton.lock().unwrap().take(); - - SQLITE_OK -} - -pub fn push_error(db: *mut SQLite3, error: (String, c_int)) { - if db.is_null() { - return; - } - - // Safety: Convert the raw pointer to a mutable reference - let db = unsafe { &mut *db }; - - if let Ok(mut stack) = db.error_stack.lock() { - stack.push(error); - } -} - pub async unsafe fn handle_execute(db: *mut SQLite3, sql: &str) -> Result> { if db.is_null() { return Err("Database pointer is null".into()); } - let db = &mut *db; - let baton = db.transaction_baton.lock().unwrap(); + let mut stmt = SQLite3PreparedStmt::new(db, sql); - match execute_sql_and_params(db, sql, vec![], baton.as_ref()).await { + match execute_stmt(&mut stmt).await { Ok(_) => Ok(SQLITE_OK), Err(e) => Err(e), } @@ -363,3 +328,57 @@ pub async fn commit_tnx_on_db(db: *mut SQLite3) -> Result> Ok(SQLITE_OK) } + +async unsafe fn execute_stmt( + stmt: &mut SQLite3PreparedStmt, +) -> Result> { + let db: &SQLite3 = &*stmt.db; + let baton_str = { + let baton = db.transaction_baton.lock().unwrap(); + baton.as_ref().map(|s| s.as_str()).map(|s| s.to_owned()) + }; + + let params = convert_params_to_json(&stmt.params); + let response = execute_sql_and_params(db, &stmt.sql, params, baton_str.as_ref()).await?; + + let result = get_execution_result(db, &response)?; + + Ok(result.clone()) +} + +async unsafe fn execute_stmt_and_populate_result_rows( + stmt: &mut SQLite3PreparedStmt, +) -> Result> { + let response = execute_stmt(stmt).await?; + let mut result_rows = stmt.result_rows.lock().unwrap(); + + let rows = response.rows; + let columns = response.cols; + stmt.column_names = columns.iter().map(|col| col.name.clone()).collect(); + + *result_rows = rows + .iter() + .map(|row| { + let result = row + .iter() + .map(|row| match row.r#type.as_str() { + "integer" => match &row.value { + serde_json::Value::String(s) => { + Value::Integer(s.parse::().unwrap_or(0)) + } + serde_json::Value::Number(n) => Value::Integer(n.as_i64().unwrap_or(0)), + _ => Value::Integer(0), + }, + "float" => Value::Real(row.value.as_f64().unwrap_or(0.0)), + "text" => Value::Text(row.value.as_str().unwrap_or("").to_string()), + "null" => Value::Null, + _ => Value::Null, + }) + .collect(); + + result + }) + .collect(); + + Ok(SQLITE_OK) +} diff --git a/src/utils.rs b/src/utils.rs index 98f8445..702d4c1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ -use std::{env, ffi::c_int, sync::OnceLock}; +use std::{ffi::c_int, sync::OnceLock}; use regex::Regex; +use serde::Deserialize; use tokio::runtime::{self, Runtime}; use crate::sqlite::{push_error, SQLite3, SQLITE_ERROR}; @@ -66,21 +67,12 @@ where } } -#[derive(Debug)] +#[derive(Debug, Deserialize)] pub struct TursoConfig { pub db_url: String, pub db_token: String, } -pub fn read_turso_config() -> Result { - let db_url = env::var("TURSO_DB_URL") - .map_err(|_| "Missing environment variable: TURSO_DB_URL".to_string())?; - let db_token = env::var("TURSO_DB_TOKEN") - .map_err(|_| "Missing environment variable: TURSO_DB_TOKEN".to_string())?; - - Ok(TursoConfig { db_url, db_token }) -} - #[inline] pub fn sql_is_begin_transaction(sql: &String) -> bool { sql.starts_with("BEGIN")