feat(plugin): PATCH 部分更新端点 — jsonb_set 字段合并

This commit is contained in:
iven
2026-04-17 10:56:37 +08:00
parent e2e58d3a00
commit b0ee3e495d
5 changed files with 139 additions and 2 deletions

View File

@@ -266,6 +266,44 @@ impl DynamicTableManager {
(sql, values)
}
/// 构建 PATCH SQL — 只更新 data 中提供的字段,未提供的保持不变
/// 使用 jsonb_set 逐层合并,实现部分更新
pub fn build_patch_sql(
table_name: &str,
id: Uuid,
tenant_id: Uuid,
user_id: Uuid,
partial_data: serde_json::Value,
version: i32,
) -> (String, Vec<Value>) {
let mut set_expr = "data".to_string();
if let Some(obj) = partial_data.as_object() {
for key in obj.keys() {
let clean_key = sanitize_identifier(key);
set_expr = format!(
"jsonb_set({}, '{{{}}}', $1::jsonb->'{}', true)",
set_expr, clean_key, clean_key
);
}
}
let sql = format!(
"UPDATE \"{}\" \
SET data = {}, updated_at = NOW(), updated_by = $2, version = version + 1 \
WHERE id = $3 AND tenant_id = $4 AND version = $5 AND deleted_at IS NULL \
RETURNING id, data, created_at, updated_at, version",
table_name, set_expr
);
let values = vec![
serde_json::to_string(&partial_data).unwrap_or_default().into(),
user_id.into(),
id.into(),
tenant_id.into(),
version.into(),
];
(sql, values)
}
/// 构建 DELETE SQL软删除
pub fn build_delete_sql(
table_name: &str,
@@ -871,6 +909,25 @@ mod tests {
);
}
// ===== build_patch_sql 测试 =====
#[test]
fn test_build_patch_sql_merges_fields() {
let (sql, values) = DynamicTableManager::build_patch_sql(
"plugin_test_customer",
Uuid::parse_str("00000000-0000-0000-0000-000000000099").unwrap(),
Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
Uuid::parse_str("00000000-0000-0000-0000-000000000050").unwrap(),
serde_json::json!({"level": "vip", "status": "active"}),
3,
);
assert!(sql.contains("jsonb_set"), "PATCH 应使用 jsonb_set 合并");
assert!(sql.contains("version = version + 1"), "PATCH 应更新版本号");
assert!(sql.contains("WHERE id = $3"), "应有 id 条件");
assert!(sql.contains("version = $5"), "应有乐观锁");
assert_eq!(values.len(), 5, "应有 5 个参数");
}
// ===== build_filtered_count_sql 测试 =====
#[test]