|
3 | 3 |
|
4 | 4 | use codegraph_core::{CodeGraphError, Result}; |
5 | 5 | use serde::{Deserialize, Serialize}; |
| 6 | +use serde_json::json; |
6 | 7 | use std::sync::Arc; |
7 | 8 | use surrealdb::{engine::any::Any, Surreal}; |
8 | 9 | use tracing::{debug, error}; |
@@ -269,6 +270,60 @@ impl GraphFunctions { |
269 | 270 |
|
270 | 271 | Ok(result) |
271 | 272 | } |
| 273 | + |
| 274 | + /// Count nodes for the current project (used for health checks) |
| 275 | + pub async fn count_nodes_for_project(&self) -> Result<usize> { |
| 276 | + let mut response = self |
| 277 | + .db |
| 278 | + .query("SELECT VALUE count() FROM nodes WHERE project_id = $project_id") |
| 279 | + .bind(("project_id", self.project_id.clone())) |
| 280 | + .await |
| 281 | + .map_err(|e| { |
| 282 | + CodeGraphError::Database(format!( |
| 283 | + "count_nodes_for_project query failed: {}", |
| 284 | + e |
| 285 | + )) |
| 286 | + })?; |
| 287 | + |
| 288 | + let count: Option<usize> = response.take(0).map_err(|e| { |
| 289 | + CodeGraphError::Database(format!("Failed to deserialize count: {}", e)) |
| 290 | + })?; |
| 291 | + |
| 292 | + Ok(count.unwrap_or(0)) |
| 293 | + } |
| 294 | + |
| 295 | + /// Find nodes by (partial) name within the current project |
| 296 | + pub async fn find_nodes_by_name( |
| 297 | + &self, |
| 298 | + needle: &str, |
| 299 | + limit: usize, |
| 300 | + ) -> Result<Vec<NodeReference>> { |
| 301 | + let max = limit.clamp(1, 50) as i64; |
| 302 | + |
| 303 | + debug!( |
| 304 | + "Calling fn::find_nodes_by_name({}, project={}, limit={})", |
| 305 | + needle, self.project_id, max |
| 306 | + ); |
| 307 | + |
| 308 | + let result: Vec<NodeReference> = self |
| 309 | + .db |
| 310 | + .query("RETURN fn::find_nodes_by_name($project_id, $needle, $limit)") |
| 311 | + .bind(("project_id", self.project_id.clone())) |
| 312 | + .bind(("needle", needle.to_string())) |
| 313 | + .bind(("limit", max)) |
| 314 | + .await |
| 315 | + .map_err(|e| { |
| 316 | + error!("Failed to call find_nodes_by_name: {}", e); |
| 317 | + CodeGraphError::Database(format!("find_nodes_by_name failed: {}", e)) |
| 318 | + })? |
| 319 | + .take(0) |
| 320 | + .map_err(|e| { |
| 321 | + error!("Failed to deserialize find_nodes_by_name results: {}", e); |
| 322 | + CodeGraphError::Database(format!("Deserialization failed: {}", e)) |
| 323 | + })?; |
| 324 | + |
| 325 | + Ok(result) |
| 326 | + } |
272 | 327 | } |
273 | 328 |
|
274 | 329 | // ============================================================================ |
@@ -416,4 +471,50 @@ mod tests { |
416 | 471 | let json = serde_json::to_string(&node).unwrap(); |
417 | 472 | assert!(json.contains("test_function")); |
418 | 473 | } |
| 474 | + |
| 475 | + #[cfg(feature = "surrealdb")] |
| 476 | + #[tokio::test] |
| 477 | + async fn count_nodes_for_project_filters_by_project() { |
| 478 | + use surrealdb::opt::auth::Root; |
| 479 | + |
| 480 | + let db: Surreal<Any> = Surreal::init(); |
| 481 | + db.connect("mem://").await.unwrap(); |
| 482 | + db.use_ns("test").use_db("test").await.unwrap(); |
| 483 | + db.signin(Root { |
| 484 | + username: "root", |
| 485 | + password: "root", |
| 486 | + }) |
| 487 | + .await |
| 488 | + .ok(); // mem engine ignores auth |
| 489 | + |
| 490 | + // Two projects, only one should be counted |
| 491 | + db.query("CREATE nodes CONTENT $doc") |
| 492 | + .bind(( |
| 493 | + "doc", |
| 494 | + json!({ |
| 495 | + "id": "nodes:a1", |
| 496 | + "name": "A1", |
| 497 | + "project_id": "proj-a" |
| 498 | + }), |
| 499 | + )) |
| 500 | + .await |
| 501 | + .unwrap(); |
| 502 | + |
| 503 | + db.query("CREATE nodes CONTENT $doc") |
| 504 | + .bind(( |
| 505 | + "doc", |
| 506 | + json!({ |
| 507 | + "id": "nodes:b1", |
| 508 | + "name": "B1", |
| 509 | + "project_id": "proj-b" |
| 510 | + }), |
| 511 | + )) |
| 512 | + .await |
| 513 | + .unwrap(); |
| 514 | + |
| 515 | + let gf = GraphFunctions::new_with_project_id(Arc::new(db), "proj-a"); |
| 516 | + let count = gf.count_nodes_for_project().await.unwrap(); |
| 517 | + |
| 518 | + assert_eq!(count, 1, "Should only count nodes in proj-a"); |
| 519 | + } |
419 | 520 | } |
0 commit comments