diff --git a/Cargo.lock b/Cargo.lock index 0c229cf57..11b2b9a99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1662,6 +1662,7 @@ name = "dbx-core" version = "0.1.0" dependencies = [ "anyhow", + "base64 0.22.1", "calamine", "chrono", "csv", diff --git a/crates/dbx-core/Cargo.toml b/crates/dbx-core/Cargo.toml index 395a8512e..9107c2e89 100644 --- a/crates/dbx-core/Cargo.toml +++ b/crates/dbx-core/Cargo.toml @@ -29,3 +29,4 @@ csv = "1" calamine = "0.30.1" odbc-api = "25" rust-gaussdb = { git = "https://github.com/t8y2/rust-gaussdb", branch = "main" } +base64 = "0.22" diff --git a/crates/dbx-core/src/connection_secrets.rs b/crates/dbx-core/src/connection_secrets.rs index 09b2c2f39..062e883a5 100644 --- a/crates/dbx-core/src/connection_secrets.rs +++ b/crates/dbx-core/src/connection_secrets.rs @@ -287,6 +287,7 @@ mod tests { ssh_expose_lan: false, ssl: false, connection_string: None, + sysdba: false, } } diff --git a/crates/dbx-core/src/db/redis_driver.rs b/crates/dbx-core/src/db/redis_driver.rs index 271ad5d1d..aad94e96e 100644 --- a/crates/dbx-core/src/db/redis_driver.rs +++ b/crates/dbx-core/src/db/redis_driver.rs @@ -1,4 +1,5 @@ -use redis::{AsyncCommands, FromRedisValue, Value as RedisRawValue}; +use base64::Engine; +use redis::{FromRedisValue, Value as RedisRawValue}; use serde::{Deserialize, Serialize}; const STREAM_ENTRY_LIMIT: usize = 100; @@ -6,7 +7,8 @@ const DEFAULT_REDIS_DATABASES: u32 = 16; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RedisKeyInfo { - pub key: String, + pub key_display: String, + pub key_raw: String, pub key_type: String, pub ttl: i64, } @@ -19,9 +21,11 @@ pub struct RedisScanResult { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RedisValue { - pub key: String, + pub key_display: String, + pub key_raw: String, pub key_type: String, pub ttl: i64, + pub value_is_binary: bool, pub value: serde_json::Value, } @@ -94,7 +98,7 @@ pub async fn scan_keys_page( pattern: &str, count: usize, ) -> Result { - let (next_cursor, keys): (u64, Vec) = redis::cmd("SCAN") + let raw: RedisRawValue = redis::cmd("SCAN") .arg(cursor) .arg("MATCH") .arg(pattern) @@ -104,56 +108,76 @@ pub async fn scan_keys_page( .await .map_err(|e| e.to_string())?; + let (next_cursor, keys) = parse_scan_keys(raw)?; + let mut result = Vec::new(); for key in &keys { let key_type: String = - redis::cmd("TYPE").arg(key.as_str()).query_async(con).await.unwrap_or_else(|_| "unknown".to_string()); + redis::cmd("TYPE").arg(key).query_async(con).await.unwrap_or_else(|_| "unknown".to_string()); - let ttl: i64 = con.ttl(key.as_str()).await.unwrap_or(-1); + let ttl: i64 = redis::cmd("TTL").arg(key).query_async(con).await.unwrap_or(-1); - result.push(RedisKeyInfo { key: key.clone(), key_type, ttl }); + result.push(RedisKeyInfo { + key_display: redis_key_bytes_to_display(key), + key_raw: redis_key_bytes_to_raw(key), + key_type, + ttl, + }); } Ok(RedisScanResult { cursor: next_cursor, keys: result }) } -pub async fn get_value(con: &mut redis::aio::MultiplexedConnection, key: &str) -> Result { +pub async fn get_value(con: &mut redis::aio::MultiplexedConnection, key: &[u8]) -> Result { let key_type: String = redis::cmd("TYPE").arg(key).query_async(con).await.map_err(|e| e.to_string())?; - let ttl: i64 = con.ttl(key).await.unwrap_or(-1); + let ttl: i64 = redis::cmd("TTL").arg(key).query_async(con).await.unwrap_or(-1); - let value = match key_type.as_str() { + let (value, value_is_binary) = match key_type.as_str() { "string" => { - let v: String = con.get(key).await.map_err(|e| e.to_string())?; - serde_json::Value::String(v) + let v: RedisRawValue = redis::cmd("GET").arg(key).query_async(con).await.map_err(|e| e.to_string())?; + let value_is_binary = redis_value_contains_binary(&v); + (redis_raw_to_json(v), value_is_binary) } "list" => { - let v: Vec = con.lrange(key, 0, -1).await.map_err(|e| e.to_string())?; - serde_json::json!(v) + let v: RedisRawValue = redis::cmd("LRANGE").arg(key).arg(0).arg(-1).query_async(con).await.map_err(|e| e.to_string())?; + (redis_array_to_json(v), false) } "set" => { - let v: Vec = con.smembers(key).await.map_err(|e| e.to_string())?; - serde_json::json!(v) + let v: RedisRawValue = redis::cmd("SMEMBERS").arg(key).query_async(con).await.map_err(|e| e.to_string())?; + (redis_array_to_json(v), false) } "zset" => { - let v: Vec<(String, f64)> = con.zrange_withscores(key, 0, -1).await.map_err(|e| e.to_string())?; - serde_json::json!(v.iter().map(|(m, s)| serde_json::json!({"member": m, "score": s})).collect::>()) + let v: RedisRawValue = redis::cmd("ZRANGE") + .arg(key) + .arg(0) + .arg(-1) + .arg("WITHSCORES") + .query_async(con) + .await + .map_err(|e| e.to_string())?; + (parse_zset_entries(v), false) } "hash" => { - let v: Vec<(String, String)> = con.hgetall(key).await.map_err(|e| e.to_string())?; - let map: serde_json::Map = - v.into_iter().map(|(k, v)| (k, serde_json::Value::String(v))).collect(); - serde_json::Value::Object(map) + let v: RedisRawValue = redis::cmd("HGETALL").arg(key).query_async(con).await.map_err(|e| e.to_string())?; + (parse_hash_entries(v), false) } - "stream" => get_stream_entries(con, key).await?, - _ => serde_json::Value::Null, + "stream" => (get_stream_entries(con, key).await?, false), + _ => (serde_json::Value::Null, false), }; - Ok(RedisValue { key: key.to_string(), key_type, ttl, value }) + Ok(RedisValue { + key_display: redis_key_bytes_to_display(key), + key_raw: redis_key_bytes_to_raw(key), + key_type, + ttl, + value_is_binary, + value, + }) } async fn get_stream_entries( con: &mut redis::aio::MultiplexedConnection, - key: &str, + key: &[u8], ) -> Result { let raw: RedisRawValue = redis::cmd("XRANGE") .arg(key) @@ -168,6 +192,69 @@ async fn get_stream_entries( Ok(parse_stream_entries(raw)) } +fn parse_scan_keys(raw: RedisRawValue) -> Result<(u64, Vec>), String> { + let RedisRawValue::Array(parts) = raw else { + return Err("Invalid Redis SCAN response".to_string()); + }; + if parts.len() != 2 { + return Err("Invalid Redis SCAN response".to_string()); + } + + let cursor = redis_value_to_string(parts[0].clone()) + .ok_or_else(|| "Invalid Redis SCAN cursor".to_string())? + .parse::() + .map_err(|_| "Invalid Redis SCAN cursor".to_string())?; + + let RedisRawValue::Array(keys) = &parts[1] else { + return Err("Invalid Redis SCAN keys payload".to_string()); + }; + + let mut parsed = Vec::with_capacity(keys.len()); + for key in keys { + parsed.push(redis_value_to_bytes(key.clone()).ok_or_else(|| "Invalid Redis key payload".to_string())?); + } + + Ok((cursor, parsed)) +} + +fn parse_hash_entries(raw: RedisRawValue) -> serde_json::Value { + let RedisRawValue::Array(entries) = raw else { + return serde_json::Value::Null; + }; + + let mut map = serde_json::Map::new(); + let mut iter = entries.into_iter(); + while let Some(field) = iter.next() { + let Some(value) = iter.next() else { + break; + }; + let field = redis_value_to_string(field).unwrap_or_default(); + map.insert(field, redis_raw_to_json(value)); + } + + serde_json::Value::Object(map) +} + +fn parse_zset_entries(raw: RedisRawValue) -> serde_json::Value { + let RedisRawValue::Array(entries) = raw else { + return serde_json::Value::Null; + }; + + let mut rows = Vec::new(); + let mut iter = entries.into_iter(); + while let Some(member) = iter.next() { + let Some(score) = iter.next() else { + break; + }; + rows.push(serde_json::json!({ + "member": redis_value_to_string(member).unwrap_or_default(), + "score": redis_value_to_string(score).unwrap_or_default(), + })); + } + + serde_json::Value::Array(rows) +} + fn parse_stream_entries(raw: RedisRawValue) -> serde_json::Value { match raw { RedisRawValue::Array(entries) => { @@ -209,70 +296,139 @@ fn parse_stream_entry(entry: RedisRawValue) -> Option { fn redis_value_to_string(value: RedisRawValue) -> Option { match value { - RedisRawValue::BulkString(bytes) => Some(String::from_utf8_lossy(&bytes).to_string()), + RedisRawValue::BulkString(bytes) => Some(redis_bytes_to_display(&bytes)), RedisRawValue::SimpleString(value) => Some(value), RedisRawValue::Int(value) => Some(value.to_string()), RedisRawValue::Double(value) => Some(value.to_string()), RedisRawValue::Boolean(value) => Some(value.to_string()), - RedisRawValue::VerbatimString { text, .. } => Some(text), + RedisRawValue::VerbatimString { text, .. } => Some(redis_bytes_to_display(text.as_bytes())), RedisRawValue::Okay => Some("OK".to_string()), _ => None, } } +fn redis_value_contains_binary(value: &RedisRawValue) -> bool { + match value { + RedisRawValue::BulkString(bytes) => redis_bytes_need_escape(bytes), + RedisRawValue::VerbatimString { text, .. } => redis_bytes_need_escape(text.as_bytes()), + _ => false, + } +} + +fn redis_value_to_bytes(value: RedisRawValue) -> Option> { + match value { + RedisRawValue::BulkString(bytes) => Some(bytes), + RedisRawValue::SimpleString(value) => Some(value.into_bytes()), + RedisRawValue::Int(value) => Some(value.to_string().into_bytes()), + RedisRawValue::Double(value) => Some(value.to_string().into_bytes()), + RedisRawValue::Boolean(value) => Some(value.to_string().into_bytes()), + RedisRawValue::VerbatimString { text, .. } => Some(text.into_bytes()), + RedisRawValue::Okay => Some(b"OK".to_vec()), + _ => None, + } +} + +fn redis_array_to_json(value: RedisRawValue) -> serde_json::Value { + match value { + RedisRawValue::Array(values) => serde_json::Value::Array(values.into_iter().map(redis_raw_to_json).collect()), + other => redis_raw_to_json(other), + } +} + +fn redis_raw_to_json(value: RedisRawValue) -> serde_json::Value { + match value { + RedisRawValue::Nil => serde_json::Value::Null, + RedisRawValue::Array(values) => serde_json::Value::Array(values.into_iter().map(redis_raw_to_json).collect()), + other => serde_json::Value::String(redis_value_to_string(other).unwrap_or_default()), + } +} + +fn redis_bytes_need_escape(bytes: &[u8]) -> bool { + bytes.iter().any(|&byte| !matches!(byte, 0x20..=0x7e) || byte == b'\\') +} + +fn redis_bytes_to_display(bytes: &[u8]) -> String { + let mut output = String::new(); + for &byte in bytes { + match byte { + b'\\' => output.push_str("\\\\"), + 0x20..=0x7e => output.push(byte as char), + _ => output.push_str(&format!("\\x{:02x}", byte)), + } + } + output +} + +pub fn redis_key_bytes_to_display(bytes: &[u8]) -> String { + redis_bytes_to_display(bytes) +} + +pub fn redis_key_bytes_to_raw(bytes: &[u8]) -> String { + base64::engine::general_purpose::STANDARD.encode(bytes) +} + +pub fn redis_key_raw_to_bytes(value: &str) -> Result, String> { + base64::engine::general_purpose::STANDARD + .decode(value) + .map_err(|e| format!("Invalid Redis key encoding: {e}")) +} + pub async fn set_string( con: &mut redis::aio::MultiplexedConnection, - key: &str, + key: &[u8], value: &str, ttl: Option, ) -> Result<(), String> { - con.set::<_, _, ()>(key, value).await.map_err(|e| e.to_string())?; + redis::cmd("SET").arg(key).arg(value).query_async::<()>(con).await.map_err(|e| e.to_string())?; if let Some(t) = ttl { if t > 0 { - con.expire::<_, ()>(key, t).await.map_err(|e| e.to_string())?; + redis::cmd("EXPIRE").arg(key).arg(t).query_async::<()>(con).await.map_err(|e| e.to_string())?; } } Ok(()) } -pub async fn delete_key(con: &mut redis::aio::MultiplexedConnection, key: &str) -> Result<(), String> { - con.del::<_, ()>(key).await.map_err(|e| e.to_string()) +pub async fn delete_key(con: &mut redis::aio::MultiplexedConnection, key: &[u8]) -> Result<(), String> { + redis::cmd("DEL").arg(key).query_async::<()>(con).await.map_err(|e| e.to_string()) } pub async fn hash_set( con: &mut redis::aio::MultiplexedConnection, - key: &str, + key: &[u8], field: &str, value: &str, ) -> Result<(), String> { - con.hset::<_, _, _, ()>(key, field, value).await.map_err(|e| e.to_string()) + redis::cmd("HSET").arg(key).arg(field).arg(value).query_async::<()>(con).await.map_err(|e| e.to_string()) } -pub async fn hash_del(con: &mut redis::aio::MultiplexedConnection, key: &str, field: &str) -> Result<(), String> { - con.hdel::<_, _, ()>(key, field).await.map_err(|e| e.to_string()) +pub async fn hash_del(con: &mut redis::aio::MultiplexedConnection, key: &[u8], field: &str) -> Result<(), String> { + redis::cmd("HDEL").arg(key).arg(field).query_async::<()>(con).await.map_err(|e| e.to_string()) } -pub async fn list_push(con: &mut redis::aio::MultiplexedConnection, key: &str, value: &str) -> Result<(), String> { - con.rpush::<_, _, ()>(key, value).await.map_err(|e| e.to_string()) +pub async fn list_push(con: &mut redis::aio::MultiplexedConnection, key: &[u8], value: &str) -> Result<(), String> { + redis::cmd("RPUSH").arg(key).arg(value).query_async::<()>(con).await.map_err(|e| e.to_string()) } -pub async fn list_remove(con: &mut redis::aio::MultiplexedConnection, key: &str, index: i64) -> Result<(), String> { +pub async fn list_remove(con: &mut redis::aio::MultiplexedConnection, key: &[u8], index: i64) -> Result<(), String> { let placeholder = "__DELETED_PLACEHOLDER__"; redis::cmd("LSET").arg(key).arg(index).arg(placeholder).query_async::<()>(con).await.map_err(|e| e.to_string())?; - con.lrem::<_, _, ()>(key, 1, placeholder).await.map_err(|e| e.to_string()) + redis::cmd("LREM").arg(key).arg(1).arg(placeholder).query_async::<()>(con).await.map_err(|e| e.to_string()) } -pub async fn set_add(con: &mut redis::aio::MultiplexedConnection, key: &str, member: &str) -> Result<(), String> { - con.sadd::<_, _, ()>(key, member).await.map_err(|e| e.to_string()) +pub async fn set_add(con: &mut redis::aio::MultiplexedConnection, key: &[u8], member: &str) -> Result<(), String> { + redis::cmd("SADD").arg(key).arg(member).query_async::<()>(con).await.map_err(|e| e.to_string()) } -pub async fn set_remove(con: &mut redis::aio::MultiplexedConnection, key: &str, member: &str) -> Result<(), String> { - con.srem::<_, _, ()>(key, member).await.map_err(|e| e.to_string()) +pub async fn set_remove(con: &mut redis::aio::MultiplexedConnection, key: &[u8], member: &str) -> Result<(), String> { + redis::cmd("SREM").arg(key).arg(member).query_async::<()>(con).await.map_err(|e| e.to_string()) } #[cfg(test)] mod tests { - use super::{parse_database_count, parse_stream_entries, RedisRawValue}; + use super::{ + parse_database_count, parse_scan_keys, parse_stream_entries, redis_key_bytes_to_display, redis_key_bytes_to_raw, + redis_key_raw_to_bytes, redis_raw_to_json, RedisRawValue, + }; fn bulk(value: &str) -> RedisRawValue { RedisRawValue::BulkString(value.as_bytes().to_vec()) @@ -335,4 +491,44 @@ mod tests { assert_eq!(parse_database_count(value), Some(32)); } + + #[test] + fn formats_binary_keys_like_rdm() { + let bytes = [0xAC, 0xED, 0x00, 0x05, b't', 0x00, b'A', b'\\']; + + assert_eq!(redis_key_bytes_to_display(&bytes), "\\xac\\xed\\x00\\x05t\\x00A\\\\"); + } + + #[test] + fn round_trips_raw_key_transport() { + let bytes = b"\xAC\xED\x00\x05t\x00token"; + let encoded = redis_key_bytes_to_raw(bytes); + + assert_eq!(redis_key_raw_to_bytes(&encoded).unwrap(), bytes); + } + + #[test] + fn parses_scan_response_with_binary_keys() { + let raw = RedisRawValue::Array(vec![ + RedisRawValue::BulkString(b"17".to_vec()), + RedisRawValue::Array(vec![ + RedisRawValue::BulkString(vec![0xAC, 0xED, 0x00, 0x05, b't']), + RedisRawValue::BulkString(b"plain:key".to_vec()), + ]), + ]); + + let (cursor, keys) = parse_scan_keys(raw).unwrap(); + + assert_eq!(cursor, 17); + assert_eq!(keys, vec![vec![0xAC, 0xED, 0x00, 0x05, b't'], b"plain:key".to_vec()]); + } + + #[test] + fn formats_binary_string_values_like_rdm() { + let raw = RedisRawValue::BulkString(vec![0xAC, 0xED, 0x00, 0x05, b's', b'r']); + + let value = redis_raw_to_json(raw); + + assert_eq!(value, serde_json::Value::String("\\xac\\xed\\x00\\x05sr".to_string())); + } } diff --git a/crates/dbx-core/src/redis_ops.rs b/crates/dbx-core/src/redis_ops.rs index bddabe41a..45630eb05 100644 --- a/crates/dbx-core/src/redis_ops.rs +++ b/crates/dbx-core/src/redis_ops.rs @@ -34,12 +34,23 @@ pub async fn redis_scan_keys_core( } pub async fn redis_get_value_core(state: &AppState, connection_id: &str, key: &str) -> Result { + redis_get_value_in_db_core(state, connection_id, 0, key).await +} + +pub async fn redis_get_value_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, +) -> Result { let connections = state.connections.lock().await; let pool = connections.get(connection_id).ok_or("Connection not found")?; match pool { PoolKind::Redis(con) => { let mut con = con.lock().await; - redis_driver::get_value(&mut con, key).await + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::get_value(&mut con, &key).await } _ => Err("Not a Redis connection".to_string()), } @@ -51,25 +62,49 @@ pub async fn redis_set_string_core( key: &str, value: &str, ttl: Option, +) -> Result<(), String> { + redis_set_string_in_db_core(state, connection_id, 0, key, value, ttl).await +} + +pub async fn redis_set_string_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + value: &str, + ttl: Option, ) -> Result<(), String> { let connections = state.connections.lock().await; let pool = connections.get(connection_id).ok_or("Connection not found")?; match pool { PoolKind::Redis(con) => { let mut con = con.lock().await; - redis_driver::set_string(&mut con, key, value, ttl).await + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::set_string(&mut con, &key, value, ttl).await } _ => Err("Not a Redis connection".to_string()), } } pub async fn redis_delete_key_core(state: &AppState, connection_id: &str, key: &str) -> Result<(), String> { + redis_delete_key_in_db_core(state, connection_id, 0, key).await +} + +pub async fn redis_delete_key_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, +) -> Result<(), String> { let connections = state.connections.lock().await; let pool = connections.get(connection_id).ok_or("Connection not found")?; match pool { PoolKind::Redis(con) => { let mut con = con.lock().await; - redis_driver::delete_key(&mut con, key).await + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::delete_key(&mut con, &key).await } _ => Err("Not a Redis connection".to_string()), } @@ -81,26 +116,72 @@ pub async fn redis_hash_set_core( key: &str, field: &str, value: &str, +) -> Result<(), String> { + redis_hash_set_in_db_core(state, connection_id, 0, key, field, value).await +} + +pub async fn redis_hash_set_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + field: &str, + value: &str, ) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::hash_set(&mut *con.lock().await, key, field, value).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::hash_set(&mut con, &key, field, value).await + } _ => Err("Not a Redis connection".to_string()), } } pub async fn redis_hash_del_core(state: &AppState, connection_id: &str, key: &str, field: &str) -> Result<(), String> { + redis_hash_del_in_db_core(state, connection_id, 0, key, field).await +} + +pub async fn redis_hash_del_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + field: &str, +) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::hash_del(&mut *con.lock().await, key, field).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::hash_del(&mut con, &key, field).await + } _ => Err("Not a Redis connection".to_string()), } } pub async fn redis_list_push_core(state: &AppState, connection_id: &str, key: &str, value: &str) -> Result<(), String> { + redis_list_push_in_db_core(state, connection_id, 0, key, value).await +} + +pub async fn redis_list_push_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + value: &str, +) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::list_push(&mut *con.lock().await, key, value).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::list_push(&mut con, &key, value).await + } _ => Err("Not a Redis connection".to_string()), } } @@ -110,18 +191,48 @@ pub async fn redis_list_remove_core( connection_id: &str, key: &str, index: i64, +) -> Result<(), String> { + redis_list_remove_in_db_core(state, connection_id, 0, key, index).await +} + +pub async fn redis_list_remove_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + index: i64, ) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::list_remove(&mut *con.lock().await, key, index).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::list_remove(&mut con, &key, index).await + } _ => Err("Not a Redis connection".to_string()), } } pub async fn redis_set_add_core(state: &AppState, connection_id: &str, key: &str, member: &str) -> Result<(), String> { + redis_set_add_in_db_core(state, connection_id, 0, key, member).await +} + +pub async fn redis_set_add_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + member: &str, +) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::set_add(&mut *con.lock().await, key, member).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::set_add(&mut con, &key, member).await + } _ => Err("Not a Redis connection".to_string()), } } @@ -131,10 +242,25 @@ pub async fn redis_set_remove_core( connection_id: &str, key: &str, member: &str, +) -> Result<(), String> { + redis_set_remove_in_db_core(state, connection_id, 0, key, member).await +} + +pub async fn redis_set_remove_in_db_core( + state: &AppState, + connection_id: &str, + db: u32, + key_raw: &str, + member: &str, ) -> Result<(), String> { let connections = state.connections.lock().await; match connections.get(connection_id).ok_or("Not found")? { - PoolKind::Redis(con) => redis_driver::set_remove(&mut *con.lock().await, key, member).await, + PoolKind::Redis(con) => { + let mut con = con.lock().await; + let key = redis_driver::redis_key_raw_to_bytes(key_raw)?; + redis_driver::select_db(&mut con, db).await?; + redis_driver::set_remove(&mut con, &key, member).await + } _ => Err("Not a Redis connection".to_string()), } } diff --git a/src-tauri/src/commands/redis_cmd.rs b/src-tauri/src/commands/redis_cmd.rs index 827ce7069..4869c78a6 100644 --- a/src-tauri/src/commands/redis_cmd.rs +++ b/src-tauri/src/commands/redis_cmd.rs @@ -25,88 +25,97 @@ pub async fn redis_scan_keys( pub async fn redis_get_value( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, ) -> Result { - dbx_core::redis_ops::redis_get_value_core(&state, &connection_id, &key).await + dbx_core::redis_ops::redis_get_value_in_db_core(&state, &connection_id, db, &key_raw).await } #[tauri::command] pub async fn redis_set_string( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, value: String, ttl: Option, ) -> Result<(), String> { - dbx_core::redis_ops::redis_set_string_core(&state, &connection_id, &key, &value, ttl).await + dbx_core::redis_ops::redis_set_string_in_db_core(&state, &connection_id, db, &key_raw, &value, ttl).await } #[tauri::command] pub async fn redis_delete_key( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_delete_key_core(&state, &connection_id, &key).await + dbx_core::redis_ops::redis_delete_key_in_db_core(&state, &connection_id, db, &key_raw).await } #[tauri::command] pub async fn redis_hash_set( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, field: String, value: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_hash_set_core(&state, &connection_id, &key, &field, &value).await + dbx_core::redis_ops::redis_hash_set_in_db_core(&state, &connection_id, db, &key_raw, &field, &value).await } #[tauri::command] pub async fn redis_hash_del( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, field: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_hash_del_core(&state, &connection_id, &key, &field).await + dbx_core::redis_ops::redis_hash_del_in_db_core(&state, &connection_id, db, &key_raw, &field).await } #[tauri::command] pub async fn redis_list_push( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, value: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_list_push_core(&state, &connection_id, &key, &value).await + dbx_core::redis_ops::redis_list_push_in_db_core(&state, &connection_id, db, &key_raw, &value).await } #[tauri::command] pub async fn redis_list_remove( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, index: i64, ) -> Result<(), String> { - dbx_core::redis_ops::redis_list_remove_core(&state, &connection_id, &key, index).await + dbx_core::redis_ops::redis_list_remove_in_db_core(&state, &connection_id, db, &key_raw, index).await } #[tauri::command] pub async fn redis_set_add( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, member: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_set_add_core(&state, &connection_id, &key, &member).await + dbx_core::redis_ops::redis_set_add_in_db_core(&state, &connection_id, db, &key_raw, &member).await } #[tauri::command] pub async fn redis_set_remove( state: State<'_, Arc>, connection_id: String, - key: String, + db: u32, + key_raw: String, member: String, ) -> Result<(), String> { - dbx_core::redis_ops::redis_set_remove_core(&state, &connection_id, &key, &member).await + dbx_core::redis_ops::redis_set_remove_in_db_core(&state, &connection_id, db, &key_raw, &member).await } diff --git a/src-web/src/routes/redis.rs b/src-web/src/routes/redis.rs index 7e018dccd..d731b0181 100644 --- a/src-web/src/routes/redis.rs +++ b/src-web/src/routes/redis.rs @@ -27,14 +27,16 @@ pub struct RedisScanRequest { #[serde(rename_all = "camelCase")] pub struct RedisKeyRequest { pub connection_id: String, - pub key: String, + pub db: u32, + pub key_raw: String, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct RedisSetStringRequest { pub connection_id: String, - pub key: String, + pub db: u32, + pub key_raw: String, pub value: String, pub ttl: Option, } @@ -43,7 +45,8 @@ pub struct RedisSetStringRequest { #[serde(rename_all = "camelCase")] pub struct RedisHashRequest { pub connection_id: String, - pub key: String, + pub db: u32, + pub key_raw: String, pub field: String, pub value: Option, } @@ -52,7 +55,8 @@ pub struct RedisHashRequest { #[serde(rename_all = "camelCase")] pub struct RedisListRequest { pub connection_id: String, - pub key: String, + pub db: u32, + pub key_raw: String, pub value: Option, pub index: Option, } @@ -61,7 +65,8 @@ pub struct RedisListRequest { #[serde(rename_all = "camelCase")] pub struct RedisSetRequest { pub connection_id: String, - pub key: String, + pub db: u32, + pub key_raw: String, pub member: String, } @@ -95,8 +100,9 @@ pub async fn get_value( State(state): State>, Json(req): Json, ) -> Result, AppError> { - let result = - dbx_core::redis_ops::redis_get_value_core(&state.app, &req.connection_id, &req.key).await.map_err(AppError)?; + let result = dbx_core::redis_ops::redis_get_value_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw) + .await + .map_err(AppError)?; Ok(Json(serde_json::to_value(result).map_err(|e| AppError(e.to_string()))?)) } @@ -104,9 +110,16 @@ pub async fn set_string( State(state): State>, Json(req): Json, ) -> Result, AppError> { - dbx_core::redis_ops::redis_set_string_core(&state.app, &req.connection_id, &req.key, &req.value, req.ttl) - .await - .map_err(AppError)?; + dbx_core::redis_ops::redis_set_string_in_db_core( + &state.app, + &req.connection_id, + req.db, + &req.key_raw, + &req.value, + req.ttl, + ) + .await + .map_err(AppError)?; Ok(Json(())) } @@ -114,7 +127,9 @@ pub async fn delete_key( State(state): State>, Json(req): Json, ) -> Result, AppError> { - dbx_core::redis_ops::redis_delete_key_core(&state.app, &req.connection_id, &req.key).await.map_err(AppError)?; + dbx_core::redis_ops::redis_delete_key_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw) + .await + .map_err(AppError)?; Ok(Json(())) } @@ -123,7 +138,14 @@ pub async fn hash_set( Json(req): Json, ) -> Result, AppError> { let value = req.value.as_deref().unwrap_or(""); - dbx_core::redis_ops::redis_hash_set_core(&state.app, &req.connection_id, &req.key, &req.field, value) + dbx_core::redis_ops::redis_hash_set_in_db_core( + &state.app, + &req.connection_id, + req.db, + &req.key_raw, + &req.field, + value, + ) .await .map_err(AppError)?; Ok(Json(())) @@ -133,7 +155,7 @@ pub async fn hash_del( State(state): State>, Json(req): Json, ) -> Result, AppError> { - dbx_core::redis_ops::redis_hash_del_core(&state.app, &req.connection_id, &req.key, &req.field) + dbx_core::redis_ops::redis_hash_del_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw, &req.field) .await .map_err(AppError)?; Ok(Json(())) @@ -144,7 +166,7 @@ pub async fn list_push( Json(req): Json, ) -> Result, AppError> { let value = req.value.as_deref().unwrap_or(""); - dbx_core::redis_ops::redis_list_push_core(&state.app, &req.connection_id, &req.key, value) + dbx_core::redis_ops::redis_list_push_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw, value) .await .map_err(AppError)?; Ok(Json(())) @@ -155,7 +177,7 @@ pub async fn list_remove( Json(req): Json, ) -> Result, AppError> { let index = req.index.unwrap_or(0); - dbx_core::redis_ops::redis_list_remove_core(&state.app, &req.connection_id, &req.key, index) + dbx_core::redis_ops::redis_list_remove_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw, index) .await .map_err(AppError)?; Ok(Json(())) @@ -165,7 +187,7 @@ pub async fn set_add( State(state): State>, Json(req): Json, ) -> Result, AppError> { - dbx_core::redis_ops::redis_set_add_core(&state.app, &req.connection_id, &req.key, &req.member) + dbx_core::redis_ops::redis_set_add_in_db_core(&state.app, &req.connection_id, req.db, &req.key_raw, &req.member) .await .map_err(AppError)?; Ok(Json(())) @@ -175,7 +197,13 @@ pub async fn set_remove( State(state): State>, Json(req): Json, ) -> Result, AppError> { - dbx_core::redis_ops::redis_set_remove_core(&state.app, &req.connection_id, &req.key, &req.member) + dbx_core::redis_ops::redis_set_remove_in_db_core( + &state.app, + &req.connection_id, + req.db, + &req.key_raw, + &req.member, + ) .await .map_err(AppError)?; Ok(Json(())) diff --git a/src/components/redis/RedisKeyBrowser.vue b/src/components/redis/RedisKeyBrowser.vue index f02b2cb77..ab9898d74 100644 --- a/src/components/redis/RedisKeyBrowser.vue +++ b/src/components/redis/RedisKeyBrowser.vue @@ -1,7 +1,7 @@