推进
This commit is contained in:
1 parent
a45e33f94b
commit
4b8c9d9e78
67 files changed
+2551
-876
No files matched your search
@@ -122,6 +122,7 @@ pub enum Request {
|
||||
#[serde(default)]
|
||||
columns: Option<Vec<String>>,
|
||||
},
|
||||
ValidateWrite { table: String },
|
||||
/// 分片扫描:第 shard 片只读总页数按 shards 均分后的那一段
|
||||
ScanShard {
|
||||
table: String,
|
||||
@@ -231,7 +232,7 @@ impl Request {
|
||||
/// 普通表操作的统一访问边界
|
||||
pub fn table(&self) -> Option<&str> {
|
||||
match self {
|
||||
Self::Scan { table, .. } | Self::ScanShard { table, .. }
|
||||
Self::Scan { table, .. } | Self::ScanShard { table, .. } | Self::ValidateWrite { table }
|
||||
| Self::Insert { table, .. }
|
||||
| Self::InsertBatch { table, .. } | Self::DeleteKeys { table, .. }
|
||||
| Self::LookupByIndex { table, .. } | Self::ReplaceAll { table, .. }
|
||||
|
||||
@@ -1335,7 +1335,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
/// 持读门时复查绑定身份、版本和列定义
|
||||
/// 持库门时复查绑定身份、版本和列定义
|
||||
fn validate_bound_schema(&self, table: &str, expected: &crate::protocol::BoundSchema) -> std::io::Result<()> {
|
||||
use std::io::Error;
|
||||
if self.recovery_required.load(Ordering::Acquire) { return Err(Error::other("storage recovery required")); }
|
||||
@@ -1378,6 +1378,7 @@ impl Server {
|
||||
}
|
||||
}
|
||||
match req {
|
||||
Request::ValidateWrite { .. } => Response::Ok,
|
||||
Request::BootstrapTypes { types } => match self.bootstrap_types(types) {
|
||||
Ok(rows) => Response::Rows { rows },
|
||||
Err(e) => Response::Error { message: e.to_string() },
|
||||
@@ -2190,10 +2191,17 @@ impl Databases {
|
||||
fn request(&self, mut value: serde_json::Value) -> std::io::Result<Response> {
|
||||
use std::io::Error;
|
||||
let method = value.get("method").and_then(|v| v.as_str()).unwrap_or("").to_string();
|
||||
if value.get("schema_guard").is_some()
|
||||
&& !matches!(method.as_str(), "scan" | "scan_shard" | "lookup_by_index" | "range_by_index") {
|
||||
return Err(Error::other("schema guard requires a table read"));
|
||||
if method == "validate_write" && value.get("schema_guard").is_none() {
|
||||
return Err(Error::other("write validation requires a schema guard"));
|
||||
}
|
||||
let guarded_write = if value.get("schema_guard").is_some() {
|
||||
match method.as_str() {
|
||||
"scan" | "scan_shard" | "lookup_by_index" | "range_by_index" | "validate_write" => false,
|
||||
"insert" | "insert_batch" | "delete_keys" | "replace_all" | "replace_schema"
|
||||
| "drop_column" | "drop_table" | "create_index" | "drop_index" => true,
|
||||
_ => return Err(Error::other("schema guard requires a supported table operation")),
|
||||
}
|
||||
} else { false };
|
||||
let name = match value.get("database") {
|
||||
None => String::new(),
|
||||
Some(serde_json::Value::String(s)) => s.to_ascii_lowercase(),
|
||||
@@ -2328,7 +2336,6 @@ impl Databases {
|
||||
let server = if global || selected == SYSTEM_DATABASE { Arc::clone(&self.system) }
|
||||
else { Arc::clone(named.get(&selected).ok_or_else(|| Error::other("unknown database"))?) };
|
||||
if let Some(bindings) = guard {
|
||||
if !concurrent_read(&request) { return Err(Error::other("schema guard requires a table read")); }
|
||||
let mut servers = std::collections::BTreeMap::from([(selected.clone(), Arc::clone(&server))]);
|
||||
let mut dependencies = Vec::new();
|
||||
for binding in bindings {
|
||||
@@ -2341,11 +2348,19 @@ impl Databases {
|
||||
dependencies.push((database, table.to_owned(), binding));
|
||||
}
|
||||
if !dependencies.iter().any(|(database, table, _)| database == &selected && Some(table.as_str()) == request.table()) {
|
||||
return Err(Error::other("bound read table has no schema guard"));
|
||||
return Err(Error::other("bound table operation has no schema guard"));
|
||||
}
|
||||
drop(named);
|
||||
let _gates = servers.values().map(|server| server.gate.read()
|
||||
.map_err(|_| Error::other("storage read gate poisoned"))).collect::<std::io::Result<Vec<_>>>()?;
|
||||
// 全部库按名称取门;写入目标独占,其他依赖共享。
|
||||
let _gates = servers.iter().map(|(database, server)| {
|
||||
if guarded_write && database == &selected {
|
||||
server.gate.write().map(|gate| (None, Some(gate)))
|
||||
.map_err(|_| Error::other("storage write gate poisoned"))
|
||||
} else {
|
||||
server.gate.read().map(|gate| (Some(gate), None))
|
||||
.map_err(|_| Error::other("storage read gate poisoned"))
|
||||
}
|
||||
}).collect::<std::io::Result<Vec<_>>>()?;
|
||||
for (database, table, binding) in &dependencies {
|
||||
servers[database].validate_bound_schema(table, binding)?;
|
||||
}
|
||||
@@ -2369,6 +2384,7 @@ fn concurrent_read(req: &Request) -> bool {
|
||||
matches!(
|
||||
req,
|
||||
Request::Scan { .. }
|
||||
| Request::ValidateWrite { .. }
|
||||
| Request::ScanShard { .. }
|
||||
| Request::LookupByIndex { .. }
|
||||
| Request::RangeByIndex { .. }
|
||||
|
||||
@@ -198,7 +198,7 @@ fn bound_reads_recheck_dependencies_in_other_databases() -> Result<(), Box<dyn s
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 每种读取都复查守卫,拒绝旧版本和写入
|
||||
/// 每种读取都复查守卫,拒绝旧版本与库管理
|
||||
#[test]
|
||||
fn bound_reads_validate_dependencies_and_acknowledge_guards() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_server, _data) = start_server();
|
||||
@@ -235,6 +235,93 @@ fn bound_reads_validate_dependencies_and_acknowledge_guards() -> Result<(), Box<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 写入持独占门并拒绝过期或不含目标的守卫
|
||||
#[test]
|
||||
fn bound_writes_validate_before_publishing() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_server, _data) = start_server();
|
||||
let mut c = connect()?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","table":"writes","columns":[{"name":"id","ty":"int"}]}))?;
|
||||
let catalog: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"list_catalog"}"#))?;
|
||||
let mut binding = catalog["schemas"][0].clone();
|
||||
binding["database_id"] = binding["database_id"].as_u64().unwrap().to_string().into();
|
||||
binding["object_id"] = binding["object_id"].as_u64().unwrap().to_string().into();
|
||||
let ready: serde_json::Value = serde_json::from_str(&send(&mut c, &serde_json::json!({"method":"validate_write","table":"writes","schema_guard":[binding]}).to_string()))?;
|
||||
assert_eq!(ready["status"], "guarded");
|
||||
assert_eq!(ready["response"]["status"], "ok");
|
||||
let unguarded: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"validate_write","table":"writes"}"#))?;
|
||||
assert_eq!(unguarded["status"], "error");
|
||||
for mut request in [
|
||||
serde_json::json!({"method":"insert","table":"writes","row":{"id":1}}),
|
||||
serde_json::json!({"method":"insert_batch","table":"writes","rows":[{"id":2}]}),
|
||||
serde_json::json!({"method":"replace_all","table":"writes","rows":[{"id":3}]}),
|
||||
serde_json::json!({"method":"delete_keys","table":"writes","keys":[3]}),
|
||||
] {
|
||||
request["schema_guard"] = serde_json::json!([binding]);
|
||||
let response: serde_json::Value = serde_json::from_str(&send(&mut c, &request.to_string()))?;
|
||||
assert_eq!(response["status"], "guarded", "{response}");
|
||||
assert_eq!(response["response"]["status"], "ok", "{response}");
|
||||
}
|
||||
let missing: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"insert","table":"writes","row":{"id":9},"schema_guard":[]}"#))?;
|
||||
assert_eq!(missing["status"], "error");
|
||||
request_ok(&mut c, serde_json::json!({"method":"drop_table","table":"writes"}))?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","table":"writes","columns":[{"name":"id","ty":"int"}]}))?;
|
||||
let stale: serde_json::Value = serde_json::from_str(&send(&mut c, &serde_json::json!({"method":"insert","table":"writes","row":{"id":9},"schema_guard":[binding]}).to_string()))?;
|
||||
assert_eq!(stale["status"], "error");
|
||||
assert!(stale["message"].as_str().unwrap().contains("bound query object changed"));
|
||||
let rows: serde_json::Value = serde_json::from_str(&send(&mut c, r#"{"method":"scan","table":"writes"}"#))?;
|
||||
assert_eq!(rows["rows"], serde_json::json!([]));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 互相依赖的跨库写入按统一顺序取门
|
||||
#[test]
|
||||
fn guarded_cross_database_writes_do_not_deadlock() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_server, _data) = start_server();
|
||||
let mut c = connect()?;
|
||||
for database in ["guard_a", "guard_b"] {
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_database","database":database}))?;
|
||||
request_ok(&mut c, serde_json::json!({"method":"create_table","database":database,"table":"items","columns":[{"name":"id","ty":"int"}]}))?;
|
||||
}
|
||||
let catalog: serde_json::Value = serde_json::from_str(&send_raw(&mut c, r#"{"method":"all_catalogs"}"#))?;
|
||||
let bindings: Vec<_> = catalog["schemas"].as_array().unwrap().iter().filter(|entry|
|
||||
entry["table"] == "guard_a.items" || entry["table"] == "guard_b.items").map(|entry| {
|
||||
let mut binding = entry.clone();
|
||||
binding["database_id"] = binding["database_id"].as_u64().unwrap().to_string().into();
|
||||
binding["object_id"] = binding["object_id"].as_u64().unwrap().to_string().into();
|
||||
binding
|
||||
}).collect();
|
||||
assert_eq!(bindings.len(), 2);
|
||||
let barrier = Arc::new(std::sync::Barrier::new(2));
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let workers: Vec<_> = ["guard_a", "guard_b"].into_iter().map(|database| {
|
||||
let storage = Arc::clone(&c.0);
|
||||
let barrier = Arc::clone(&barrier);
|
||||
let sender = sender.clone();
|
||||
let bindings = bindings.clone();
|
||||
std::thread::spawn(move || {
|
||||
for id in 0..16 {
|
||||
barrier.wait();
|
||||
let response = storage.request_line(&serde_json::json!({
|
||||
"method":"insert","database":database,"table":"items","row":{"id":id},"schema_guard":bindings
|
||||
}).to_string());
|
||||
sender.send(response).unwrap();
|
||||
}
|
||||
})
|
||||
}).collect();
|
||||
drop(sender);
|
||||
for _ in 0..32 {
|
||||
let response: serde_json::Value = serde_json::from_str(&receiver.recv_timeout(std::time::Duration::from_secs(10))?)?;
|
||||
assert_eq!(response["status"], "guarded", "{response}");
|
||||
assert_eq!(response["response"]["status"], "ok", "{response}");
|
||||
}
|
||||
for worker in workers { worker.join().unwrap(); }
|
||||
for database in ["guard_a", "guard_b"] {
|
||||
let rows: serde_json::Value = serde_json::from_str(&send_raw(&mut c, &serde_json::json!({"method":"scan","database":database,"table":"items"}).to_string()))?;
|
||||
assert_eq!(rows["rows"].as_array().unwrap().len(), 16);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 描述与目录均透传精确版本字符串
|
||||
#[test]
|
||||
fn catalog_schema_versions_are_exact_and_validated() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
Reference in new issue
Block a user