Fixes #41. MySQL columns now decode by their declared type instead of a blind try_get chain, so every non-string type (integers, DECIMAL, BIT, YEAR, DATE/TIME/DATETIME/TIMESTAMP) renders its value instead of NULL. MySQL restore runs in-process over the sqlx driver (no bundled MariaDB client, so MySQL 8 caching_sha2_password works) and the Clean toggle is implemented. .sql files can be opened with Gridline from the OS file manager. Verified: cargo test --lib 442 passed / 0 failed; bun run test 133 files / 1151 tests exit 0; tsc clean; both live MySQL 8 integration tests pass with the bundled CLI hidden.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
//! Per-connection cancellation handles, stored independently of the pool
|
||||
//! lock so `cancel_query` can dispatch while a long query holds the pool mutex.
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use rusqlite::InterruptHandle;
|
||||
@@ -59,6 +60,12 @@ pub enum CancelHandle {
|
||||
#[derive(Default)]
|
||||
pub struct CancelRegistry {
|
||||
map: Mutex<HashMap<String, CancelHandle>>,
|
||||
/// Live "cancel requested" flags, keyed by connection id. `cancel_query`
|
||||
/// sets one for MySQL; long-running in-process operations (currently the
|
||||
/// native restore engine) poll it between statements and clear it when
|
||||
/// they finish. Kept in a separate map from the handles so it needs no
|
||||
/// `Clone` on the registry and survives handle replacement.
|
||||
flags: Mutex<HashMap<String, Arc<AtomicBool>>>,
|
||||
}
|
||||
|
||||
impl CancelRegistry {
|
||||
@@ -66,6 +73,41 @@ impl CancelRegistry {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Get (or create) the cancellation flag for a connection. Polling callers
|
||||
/// should clear it before starting and after finishing.
|
||||
pub fn cancel_flag(&self, id: &str) -> Arc<AtomicBool> {
|
||||
self.flags
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(id.to_string())
|
||||
.or_default()
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Mark a connection as cancel-requested. Called by `cancel_query` before
|
||||
/// it sends the driver-specific cancel so in-process consumers observe the
|
||||
/// request even when the driver surfaces it as an unrecognized error.
|
||||
pub fn mark_cancelled(&self, id: &str) {
|
||||
self.cancel_flag(id).store(true, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
/// Whether a cancel has been requested for this connection.
|
||||
pub fn is_cancelled(&self, id: &str) -> bool {
|
||||
self.flags
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(id)
|
||||
.map(|f| f.load(Ordering::SeqCst))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Clear the cancel-requested flag for this connection.
|
||||
pub fn clear_cancelled(&self, id: &str) {
|
||||
if let Some(f) = self.flags.lock().unwrap().get(id) {
|
||||
f.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pg(&self, id: &str, c: PgCancel) {
|
||||
self.set(id, CancelHandle::Pg(c));
|
||||
}
|
||||
@@ -157,6 +199,21 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_flag_is_tracked_per_connection() {
|
||||
let reg = CancelRegistry::new();
|
||||
assert!(!reg.is_cancelled("c1"));
|
||||
reg.mark_cancelled("c1");
|
||||
assert!(reg.is_cancelled("c1"));
|
||||
assert!(!reg.is_cancelled("c2"), "flag must not leak across connections");
|
||||
reg.clear_cancelled("c1");
|
||||
assert!(!reg.is_cancelled("c1"));
|
||||
// The same Arc is shared between getter and setter.
|
||||
let flag = reg.cancel_flag("c1");
|
||||
reg.mark_cancelled("c1");
|
||||
assert!(flag.load(Ordering::SeqCst));
|
||||
}
|
||||
|
||||
fn fake_opts() -> sqlx::mysql::MySqlConnectOptions {
|
||||
sqlx::mysql::MySqlConnectOptions::new()
|
||||
.host("127.0.0.1").port(1).username("u").password("p").database("d")
|
||||
|
||||
Reference in New Issue
Block a user