feat(mongodb): fill object statistics over the legacy agent

This commit is contained in:
Loc Nguyen authored and GitHub committed 2026-09-22 14:22:15 +08:00
1 parent 48f89a2a1a
commit def42987a4
5 files changed
+52 -2

No files matched your search

+1 -1
View File
@@ -107,7 +107,7 @@ fn sort_mongo_collection_specs(
/// Decode both generations of the Legacy Agent response. Existing installed
/// Agents return names, while current Agents opt into `name` + `kind` specs.
fn mongo_collection_specs_from_agent_response(
pub(crate) fn mongo_collection_specs_from_agent_response(
value: serde_json::Value,
) -> Result<Vec<mongo_driver::MongoCollectionSpec>, String> {
let values =
+32
View File
@@ -40,6 +40,38 @@ async fn agent_run_command_document(
mongo_driver::json_object_to_document_extended_json(response)
}
/// The database view's per-collection "rows"/"size" columns over the legacy agent: one
/// `collStats` per non-view collection through `runCommand`, mirroring
/// `mongo_driver::list_object_statistics`. A collection whose `collStats` fails is left out so
/// the view keeps that row blank instead of failing the whole listing. The agent connection is
/// a single RPC channel, so the round trips run sequentially.
pub async fn mongo_agent_list_object_statistics(
client: &crate::db::agent_driver::PooledAgentClient,
database: &str,
) -> Result<Vec<crate::db::ObjectStatistics>, String> {
let database = database.trim();
if database.is_empty() {
return Err("Database name is required".to_string());
}
let specs = {
let mut client = client.lock().await;
if !client.supports_capability(AgentCapability::MongoRunCommand) {
return Ok(Vec::new());
}
crate::document_ops::mongo_collection_specs_from_agent_response(
client.mongo_list_collection_specs(database).await?,
)?
};
let mut statistics = Vec::with_capacity(specs.len());
for spec in specs.into_iter().filter(|spec| spec.kind != mongo_driver::MongoCollectionKind::View) {
let Ok(command) = mongo_driver::collection_stats_command(&spec.name, None) else { continue };
if let Ok(result) = agent_run_command_document(client, database, command, "collection stats").await {
statistics.push(mongo_driver::object_statistics_from_collection_stats(&spec.name, database, &result));
}
}
Ok(statistics)
}
async fn ensure_document_pool(state: &AppState, connection_id: &str) -> Result<(), String> {
state.get_or_create_pool(connection_id, None).await.map(|_| ())
}
+3
View File
@@ -6318,6 +6318,9 @@ async fn list_object_statistics_once(
}
}
if let Some(client) = extract_pool!(pool_handle.as_ref(), Agent) {
if db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::MongoDb) {
return crate::mongo_ops::mongo_agent_list_object_statistics(&client, database).await;
}
if db_config.as_ref().is_some_and(|config| config.db_type == DatabaseType::Oracle) {
return oracle_agent_list_object_statistics(
client,
@@ -234,6 +234,21 @@ async fn collection_stats_and_create_database_run_over_the_legacy_agent() {
)
.await;
// The database view's object statistics take the same numbers per collection, skipping views.
command(&state, id, &database, doc! { "create": "orders_view", "viewOn": "orders", "pipeline": [] }).await;
command(&state, id, &database, doc! { "create": "empty" }).await;
let mut objects = dbx_core::schema::list_object_statistics_core(&state, id, &database, "").await.unwrap();
objects.sort_by(|a, b| a.name.cmp(&b.name));
// `system.views` is a real collection and is listed on the native path too; the view is not.
let names: Vec<&str> = objects.iter().map(|o| o.name.as_str()).collect();
assert!(!names.contains(&"orders_view"), "views own no storage and are skipped: {names:?}");
let by_name =
|name: &str| objects.iter().find(|o| o.name == name).unwrap_or_else(|| panic!("{name} missing: {names:?}"));
assert_eq!(by_name("orders").estimated_rows, Some(3), "{objects:?}");
assert!(by_name("orders").total_bytes.is_some_and(|bytes| bytes > 0), "{objects:?}");
assert_eq!(by_name("orders").schema.as_deref(), Some(database.as_str()));
assert_eq!(by_name("empty").estimated_rows, Some(0), "{objects:?}");
let stats = mongo_collection_stats_core(&state, id, &database, "orders", None).await.unwrap();
assert_eq!(stats.count, serde_json::json!(3), "{stats:?}");
assert_eq!(stats.nindexes, serde_json::json!(2), "{stats:?}");
+1 -1
View File
@@ -311,7 +311,7 @@ pub async fn list_object_statistics(client: &Client, database: &str) -> Result<V
/// Map `collStats` onto the shared statistics shape: the document count, and the
/// on-disk footprint (data + indexes) the SQL engines report as
/// `DATA_LENGTH + INDEX_LENGTH`.
fn object_statistics_from_collection_stats(name: &str, database: &str, result: &Document) -> ObjectStatistics {
pub fn object_statistics_from_collection_stats(name: &str, database: &str, result: &Document) -> ObjectStatistics {
ObjectStatistics {
name: name.to_string(),
schema: Some(database.to_string()),