1use std::path::PathBuf;
6use std::sync::Arc;
7use std::time::Duration;
8
9#[cfg(feature = "embedding")]
10use llm_embedding::{EmbeddingConfig, EmbeddingService, OpenAICompatibleProvider};
11#[cfg(feature = "vector")]
12use log::warn;
13use log::{error, info};
14#[cfg(feature = "vector-qdrant")]
15use vector_client::VectorManager;
16
17use crate::config::Config;
18use crate::storage::{
19 GraphStorage, MetricsStorage, PersistenceConfig, PropertyGraphConfig, ResourceConfig,
20 StorageCommitOps, StorageTtlPurgeOps, SyncWrapper,
21};
22use crate::{GraphService, HttpServer};
23use linkrs_core::error::DBResult;
24use linkrs_core::types::set_bcrypt_cost;
25#[cfg(feature = "vector")]
26use linkrs_sync::backend::VectorBackend;
27use linkrs_transaction::{TransactionConfig, TransactionManager, TransactionManagerConfig};
28
29pub async fn start_service() -> DBResult<()> {
31 let (config, config_path) = match Config::load_user_config() {
32 Ok(config) => {
33 let path = Config::user_config_path().ok().filter(|p| p.exists());
34 (config, path)
35 }
36 Err(e) => {
37 error!("Failed to load user config, using default config: {}", e);
38 (Config::default(), None)
39 }
40 };
41 start_service_with_config_path(config, config_path).await
42}
43
44pub async fn start_service_with_config(config: Config) -> DBResult<()> {
46 start_service_with_config_path(config, None).await
47}
48
49pub async fn start_service_with_config_path(
52 config: Config,
53 config_path: Option<PathBuf>,
54) -> DBResult<()> {
55 info!("Initializing Linkrs service...");
56
57 config.validate().map_err(|error| {
58 linkrs_core::DBError::validation(format!("Invalid configuration: {}", error))
59 })?;
60
61 let mut config = config;
62 match config.resolve_bootstrap_password() {
63 Ok(source) => info!(
64 "Bootstrap account '{}' password source: {:?}",
65 config.server.auth.default_username, source
66 ),
67 Err(error) => {
68 return Err(linkrs_core::DBError::validation(format!(
69 "Failed to resolve the bootstrap password: {}",
70 error
71 )));
72 }
73 }
74 info!(
75 "Listeners: http {}:{}, grpc {}:{}",
76 config.http_bind_address(),
77 config.http_port(),
78 config.grpc_bind_address(),
79 config.grpc_port()
80 );
81
82 set_bcrypt_cost(config.server.auth.bcrypt_cost);
86
87 info!(
88 "Log system has been initialized: {}/{}",
89 config.log_dir(),
90 config.log_basename()
91 );
92
93 let slow_query_config = config.to_slow_query_config();
95 let m = &config.monitoring;
96 let stats_manager = Arc::new(
97 linkrs_metrics::StatsManager::with_slow_query_logger(
98 m.enabled,
99 m.memory_cache_size,
100 m.slow_query_threshold_ms * 1000,
101 slow_query_config,
102 )
103 .expect("Failed to create StatsManager with slow query logger"),
104 );
105 stats_manager.set_timeseries_capacity(m.timeseries_retention_secs);
106 stats_manager.set_histogram_max_samples(m.histogram_max_samples);
107
108 let storage_path = PathBuf::from(config.storage_path());
109 let mut persistence_config = PersistenceConfig::for_work_dir(&storage_path)
110 .with_property_graph_config(property_graph_config_from_config(&config.storage));
111 persistence_config.sync_policy = Some(config.storage.sync_mode.to_sync_policy());
112 if config.storage.checkpoint_interval_secs > 0 {
113 persistence_config.auto_checkpoint_interval =
114 Duration::from_secs(config.storage.checkpoint_interval_secs);
115 }
116 let mut graph_storage =
117 GraphStorage::open_with_persistence_config(storage_path, persistence_config)?;
118 graph_storage = graph_storage.set_stats_manager(stats_manager.clone());
119 let version_manager = graph_storage.version_manager();
120 let inner_storage = Arc::new(MetricsStorage::new(graph_storage));
121 info!(
122 "Storage initialized (persistent mode at {}, metrics enabled)",
123 config.storage_path()
124 );
125
126 #[cfg(feature = "vector")]
128 let (vector_backend, local_engine_handle): (
129 Option<VectorBackend>,
130 Option<Arc<simvec::LocalVectorEngine>>,
131 ) = if config.is_vector_enabled() {
132 match config.vector_config().engine {
133 linkrs_config::VectorEngineKind::Local => {
134 let data_dir = config.vector_data_dir();
135 match simvec::LocalVectorEngine::open(&data_dir) {
136 Ok(engine) => {
137 if let Some(hnsw) = linkrs_api::vector_config::local_hnsw_config(
138 &config.vector_config().local,
139 ) {
140 engine.set_default_hnsw_config(hnsw);
141 }
142 if let Some(ivf) = linkrs_api::vector_config::local_ivf_config(
143 &config.vector_config().local,
144 ) {
145 engine.set_default_ivf_config(ivf);
146 }
147 if let Some(quant) = linkrs_api::vector_config::local_quantization_config(
148 &config.vector_config().local,
149 ) {
150 engine.set_default_quantization_config(quant);
151 }
152 info!("Local vector engine initialized at {}", data_dir.display());
153 let arc = Arc::new(engine);
154 let handle = Arc::clone(&arc);
155 (Some(VectorBackend::from_local_arc(arc)), Some(handle))
156 }
157 Err(e) => {
158 warn!(
159 "Failed to open local vector engine at {}: {}. Vector search will be disabled.",
160 data_dir.display(),
161 e
162 );
163 (None, None)
164 }
165 }
166 }
167 #[cfg(feature = "vector-qdrant")]
168 linkrs_config::VectorEngineKind::Qdrant => {
169 match VectorManager::new(config.vector_config().qdrant.clone()).await {
170 Ok(vm) => {
171 info!("VectorManager initialized");
172 (Some(VectorBackend::qdrant(Arc::new(vm))), None)
173 }
174 Err(e) => {
175 warn!(
176 "Failed to create VectorManager: {}. Vector search will be disabled.",
177 e
178 );
179 (None, None)
180 }
181 }
182 }
183 }
184 } else {
185 (None, None)
186 };
187 #[cfg(not(feature = "vector"))]
188 let _vector_backend = (None::<()>, None::<()>);
189
190 #[cfg(feature = "vector")]
192 if let Some(engine) = local_engine_handle {
193 crate::vector_metrics::spawn_vector_metrics_sampler(engine, stats_manager.clone());
194 info!("vector metrics sampling enabled");
195 }
196
197 #[cfg(feature = "vector-qdrant")]
199 if let Some(manager) = vector_backend.as_ref().and_then(|b| b.as_qdrant_manager()) {
200 let conn = &manager.config().connection;
201 let http_port = conn.http_port.unwrap_or(6333);
202 crate::vector_metrics::spawn_remote_vector_metrics_sampler(
203 conn.host.clone(),
204 http_port,
205 conn.api_key.clone(),
206 stats_manager.clone(),
207 );
208 info!(
209 "remote vector metrics sampling enabled ({}:{})",
210 conn.host, http_port
211 );
212 }
213
214 let mut sync_manager = if config.fulltext.enabled || config.is_vector_enabled() {
215 use linkrs_sync::SyncManager;
216
217 let sync_manager = if config.fulltext.enabled {
218 #[cfg(feature = "fulltext")]
219 {
220 use linkrs_fulltext::manager::FulltextIndexManager;
221
222 let manager = Arc::new(
223 FulltextIndexManager::new(config.fulltext.clone())
224 .expect("Failed to create FulltextIndexManager"),
225 );
226
227 use linkrs_fulltext::SyncConfig;
228
229 let sync_config = SyncConfig {
230 queue_size: config.fulltext.sync.queue_size,
231 commit_interval_ms: config.fulltext.sync.commit_interval_ms,
232 batch_size: config.fulltext.sync.batch_size,
233 failure_policy: config.fulltext.sync.failure_policy,
234 };
235
236 let batch_config = linkrs_sync::batch::BatchConfig::from(sync_config.clone());
237 let sync_coordinator =
238 Arc::new(linkrs_sync::fulltext_sync::FulltextSyncCoordinator::new(
239 manager.clone(),
240 batch_config,
241 ));
242
243 let sync_manager = SyncManager::with_sync_config(sync_coordinator, sync_config);
244
245 #[cfg(feature = "vector")]
247 let sync_manager = if let Some(backend) = &vector_backend {
248 attach_vector_coordinator(sync_manager, backend.clone(), &config).map_err(
249 |error| {
250 linkrs_core::DBError::storage(format!(
251 "Invalid vector embedding configuration: {}",
252 error
253 ))
254 },
255 )?
256 } else {
257 sync_manager
258 };
259
260 Some(Arc::new(sync_manager))
261 }
262 #[cfg(not(feature = "fulltext"))]
263 {
264 let sync_manager = SyncManager::new_without_fulltext();
265
266 #[cfg(feature = "vector")]
268 let sync_manager = if let Some(backend) = &vector_backend {
269 attach_vector_coordinator(sync_manager, backend.clone(), &config).map_err(
270 |error| {
271 linkrs_core::DBError::storage(format!(
272 "Invalid vector embedding configuration: {}",
273 error
274 ))
275 },
276 )?
277 } else {
278 sync_manager
279 };
280
281 Some(Arc::new(sync_manager))
282 }
283 } else {
284 let sync_manager = SyncManager::new_without_fulltext();
285
286 #[cfg(feature = "vector")]
288 let sync_manager = if let Some(backend) = &vector_backend {
289 attach_vector_coordinator(sync_manager, backend.clone(), &config).map_err(
290 |error| {
291 linkrs_core::DBError::storage(format!(
292 "Invalid vector embedding configuration: {}",
293 error
294 ))
295 },
296 )?
297 } else {
298 sync_manager
299 };
300
301 Some(Arc::new(sync_manager))
302 };
303
304 if sync_manager.is_some() {
305 info!("SyncManager initialized");
306 }
307
308 sync_manager
309 } else {
310 None
311 };
312
313 if let Some(manager) = sync_manager.as_mut().and_then(Arc::get_mut) {
314 manager.set_stats_manager(stats_manager.clone());
315 let outbox_path = PathBuf::from(config.storage_path()).join("outbox/outbox.sqlite");
316 if let Err(error) = manager.configure_outbox(outbox_path) {
317 return Err(linkrs_core::DBError::storage(format!(
318 "Failed to initialize sync outbox: {}",
319 error
320 )));
321 }
322 if let Err(error) = manager.retry_outbox_sync() {
323 error!("Initial outbox delivery will be retried later: {}", error);
324 }
325 let recovered = inner_storage
326 .recover_outbox_projection(manager)
327 .map_err(|error| {
328 linkrs_core::DBError::storage(format!(
329 "Failed to recover the SQLite outbox projection: {}",
330 error
331 ))
332 })?;
333 if recovered > 0 {
334 info!("Recovered {} outbox intents from committed WAL", recovered);
335 }
336 }
337
338 if let Some(manager) = sync_manager.as_ref() {
339 manager.start().await.map_err(|error| {
340 linkrs_core::DBError::storage(format!("Failed to start sync manager: {}", error))
341 })?;
342 match manager.recover_stale_rebuilds().await {
346 Ok(0) => {}
347 Ok(failed) => info!(
348 "Rebuild startup recovery failed {} stranded generation(s)",
349 failed
350 ),
351 Err(error) => log::warn!(
352 "Rebuild startup recovery reported an error: {}; continuing startup",
353 error
354 ),
355 }
356 #[cfg(feature = "vector")]
358 {
359 let retention = config.vector.retention.clone();
360 if retention.enabled {
361 let interval_secs = retention.prune_interval_secs;
362 let grace = retention.grace_lsn_distance;
363 let max_age = retention.max_applied_age_ms;
364 info!(
365 "Outbox retention enabled interval={}s grace={} max_age_ms={}",
366 interval_secs, grace, max_age
367 );
368 let mgr = manager.clone();
369 tokio::spawn(async move {
370 let interval = std::time::Duration::from_secs(interval_secs.max(60));
371 loop {
372 tokio::time::sleep(interval).await;
373 match mgr.run_retention_once(grace, max_age) {
374 Ok((pruned, archived, lsn)) if pruned + archived > 0 => {
375 info!(
376 "Outbox retention pruned {} archived {} retention_lsn={}",
377 pruned, archived, lsn
378 );
379 }
380 Ok(_) => {}
381 Err(e) => {
382 log::warn!("Outbox retention cycle failed: {}", e);
383 }
384 }
385 }
386 });
387 }
388 }
389 }
390
391 let storage = if let Some(ref sync_manager) = sync_manager {
392 let sync_storage =
393 SyncWrapper::with_sync_manager((*inner_storage).clone(), sync_manager.clone());
394 info!("Sync enabled for fulltext and vector indexes");
395 Arc::new(sync_storage)
396 } else {
397 let sync_storage = SyncWrapper::new((*inner_storage).clone());
398 Arc::new(sync_storage)
399 };
400
401 if config.storage.ttl_purge_enabled && config.storage.ttl_purge_interval_secs > 0 {
404 storage.start_ttl_purge(Duration::from_secs(config.storage.ttl_purge_interval_secs));
405 info!(
406 "Background TTL purge enabled (interval {}s)",
407 config.storage.ttl_purge_interval_secs
408 );
409 }
410
411 let txn_config = TransactionManagerConfig {
413 default_timeout: std::time::Duration::from_secs(config.transaction.default_timeout),
414 max_concurrent_transactions: config.transaction.max_concurrent_transactions,
415 auto_cleanup: true,
416 commit_retry_attempts: 3,
417 abort_retry_attempts: 3,
418 txn_config: TransactionConfig {
419 auto_commit: config.transaction.auto_commit,
420 ..TransactionConfig::default()
421 },
422 ..TransactionManagerConfig::default()
423 };
424
425 let mut transaction_manager = TransactionManager::with_shared_version_manager(
426 txn_config,
427 stats_manager.clone(),
428 version_manager,
429 );
430 if let Some(ref sync_manager) = sync_manager {
431 transaction_manager = transaction_manager.with_sync_manager(sync_manager.clone());
432 }
433 transaction_manager = transaction_manager.with_commit_sink(storage.clone());
434 let transaction_manager = Arc::new(transaction_manager);
435 let _cleanup_task = transaction_manager.start_auto_cleanup_task();
436 info!("Transaction manager initialized with StatsManager");
437
438 transaction_manager.set_recovery_sidecar_path(
445 PathBuf::from(config.storage_path())
446 .join("wal")
447 .join("pending.sidecar"),
448 );
449 match transaction_manager.startup_recovery() {
450 Ok(0) => {}
451 Ok(recovered) => info!(
452 "Transaction startup recovery completed {} commit(s)",
453 recovered
454 ),
455 Err(error) => log::warn!(
456 "Transaction startup recovery reported an error: {}; continuing startup",
457 error
458 ),
459 }
460
461 #[cfg(feature = "vector")]
463 let graph_service = if let Some(backend) = &vector_backend {
464 GraphService::with_shared_vector_backend(
465 config.clone(),
466 storage.clone(),
467 transaction_manager.clone(),
468 stats_manager.clone(),
469 backend.clone(),
470 )
471 .await
472 } else {
473 GraphService::new_with_transaction_manager_and_stats(
474 config.clone(),
475 storage.clone(),
476 transaction_manager.clone(),
477 stats_manager.clone(),
478 )
479 .await
480 };
481
482 #[cfg(not(feature = "vector"))]
483 let graph_service = GraphService::new_with_transaction_manager_and_stats(
484 config.clone(),
485 storage.clone(),
486 transaction_manager.clone(),
487 stats_manager.clone(),
488 )
489 .await;
490 info!("Graph service initialized with transaction management");
491
492 #[cfg(feature = "fulltext")]
494 if let Some(sync_api) = graph_service.sync_api() {
495 let fulltext_manager = sync_api.sync_manager().fulltext_manager();
496 let stats_manager = graph_service.get_stats_manager().clone();
497 fulltext_manager.set_stats_manager(stats_manager);
498 info!("StatsManager injected into FulltextIndexManager for search metrics");
499 }
500
501 let http_server = Arc::new(HttpServer::new(
503 graph_service.clone(),
504 Arc::new(parking_lot::RwLock::new((*storage).clone())),
505 transaction_manager,
506 &config,
507 config_path,
508 ));
509 info!("HTTP server created");
510
511 let http_enabled = config.server.http.enabled;
512 let grpc_enabled = {
513 #[cfg(feature = "grpc")]
514 {
515 config.server.grpc.enabled
516 }
517 #[cfg(not(feature = "grpc"))]
518 {
519 false
520 }
521 };
522 if !http_enabled && !grpc_enabled {
523 return Err(linkrs_core::DBError::validation(
524 "at least one of http.enabled and grpc.enabled must be true",
525 ));
526 }
527
528 if http_enabled {
529 info!(
530 "Starting HTTP server on {}:{}",
531 config.http_bind_address(),
532 config.http_port()
533 );
534 }
535 if grpc_enabled {
536 info!(
537 "Starting gRPC server on {}:{}",
538 config.grpc_bind_address(),
539 config.grpc_port()
540 );
541 }
542
543 #[cfg(feature = "grpc")]
544 if let Err(e) = super::start_http_and_grpc_servers(http_server, &config).await {
545 error!("HTTP/gRPC server error: {}", e);
546 }
547 #[cfg(not(feature = "grpc"))]
548 if let Err(e) = super::start_http_server(http_server, &config).await {
549 error!("HTTP server error: {}", e);
550 }
551
552 super::shutdown_signal().await;
553
554 info!("Shutting down Linkrs service...");
555 graph_service.shutdown();
556 Ok(())
557}
558
559#[cfg(feature = "embedding")]
562fn resolve_effective_embedding(_config: &Config) -> Option<EmbeddingConfig> {
563 let top = _config.vector_config().embedding.clone();
564 #[cfg(feature = "vector-qdrant")]
565 {
566 if let Some(qdrant) = _config.vector_config().qdrant.embedding.clone() {
567 return Some(qdrant);
568 }
569 }
570 top
571}
572
573#[cfg(feature = "embedding")]
577fn build_embedding_services(
578 _config: &Config,
579 auto_embed: bool,
580) -> Result<
581 (
582 Option<Arc<linkrs_sync::vector_sync::EmbeddingService>>,
583 Option<Arc<linkrs_sync::vector_sync::EmbeddingService>>,
584 ),
585 String,
586> {
587 let Some(doc_config) = resolve_effective_embedding(_config) else {
588 if auto_embed {
589 return Err(
590 "vector.auto_embed_text requires [vector.embedding] with explicit dimension"
591 .to_string(),
592 );
593 }
594 return Ok((None, None));
595 };
596 let build = |config: EmbeddingConfig| {
597 OpenAICompatibleProvider::new(config)
598 .map(|provider| {
599 Arc::new(EmbeddingService::new(
600 provider,
601 linkrs_sync::vector_sync::EMBEDDING_BATCH_SIZE,
602 ))
603 })
604 .map_err(|err| format!("Failed to create embedding service: {}", err))
605 };
606 let doc_service = match build(doc_config.clone()) {
607 Ok(service) => service,
608 Err(err) => {
609 if auto_embed {
610 return Err(err);
611 }
612 warn!("Failed to create embedding service: {}", err);
613 return Ok((None, None));
614 }
615 };
616 let query_service = match _config.vector_config().embedding_query_preprocessor.clone() {
617 Some(preprocessor) => {
618 let mut query_config = doc_config;
619 query_config.preprocessor = preprocessor;
620 match build(query_config) {
621 Ok(service) => Some(service),
622 Err(err) => {
623 if auto_embed {
624 return Err(err);
625 }
626 warn!("Failed to create query embedding service: {}", err);
627 None
628 }
629 }
630 }
631 None => None,
632 };
633 Ok((Some(doc_service), query_service))
634}
635
636#[cfg(feature = "rerank")]
639fn build_rerank_service(
640 _config: &Config,
641) -> Option<(
642 Arc<llm_rerank::CohereRerankProvider>,
643 linkrs_config::VectorRerankConfig,
644)> {
645 let rerank = _config.vector_config().rerank.clone()?;
646 if let Err(err) = rerank.validate() {
647 warn!(
648 "Invalid vector rerank configuration, rerank disabled: {}",
649 err
650 );
651 return None;
652 }
653 match llm_rerank::CohereRerankProvider::new(rerank.endpoint.clone()) {
654 Ok(provider) => {
655 info!("Vector rerank enabled (model {})", rerank.endpoint.model);
656 Some((Arc::new(provider), rerank))
657 }
658 Err(err) => {
659 warn!("Failed to create rerank service, rerank disabled: {}", err);
660 None
661 }
662 }
663}
664
665#[cfg(feature = "vector")]
667fn attach_vector_coordinator(
668 sync_manager: linkrs_sync::SyncManager,
669 backend: VectorBackend,
670 _config: &Config,
671) -> Result<linkrs_sync::SyncManager, String> {
672 let handle = tokio::runtime::Handle::current();
673 let auto_embed = _config.vector_config().auto_embed_text;
674 #[cfg(feature = "embedding")]
675 let (embedding_service, query_service) = build_embedding_services(_config, auto_embed)
676 .map_err(|error| format!("Invalid vector embedding configuration: {}", error))?;
677 #[cfg(feature = "embedding")]
678 let vector_coordinator = {
679 let coordinator = linkrs_sync::vector_sync::VectorSyncCoordinator::new(
680 backend.clone(),
681 embedding_service,
682 handle,
683 );
684 let coordinator = match query_service {
685 Some(query) => coordinator.with_query_embedding_service(query),
686 None => coordinator,
687 };
688 #[cfg(feature = "rerank")]
689 let coordinator = match build_rerank_service(_config) {
690 Some((service, rerank)) => coordinator.with_rerank_service(service, rerank),
691 None => coordinator,
692 };
693 Arc::new(coordinator)
694 };
695 #[cfg(not(feature = "embedding"))]
696 let vector_coordinator = {
697 let coordinator = linkrs_sync::vector_sync::VectorSyncCoordinator::new_without_embedding(
698 backend.clone(),
699 handle,
700 );
701 #[cfg(feature = "rerank")]
702 let coordinator = match build_rerank_service(_config) {
703 Some((service, rerank)) => coordinator.with_rerank_service(service, rerank),
704 None => coordinator,
705 };
706 Arc::new(coordinator)
707 };
708 #[cfg(not(feature = "embedding"))]
709 if auto_embed {
710 return Err(
711 "vector.auto_embed_text requires the embedding build with a configured service"
712 .to_string(),
713 );
714 }
715 {
716 let granularity = _config.vector_config().collection.granularity;
717 vector_coordinator.set_granularity(granularity);
718 info!("Vector collection granularity: {:?}", granularity);
719 }
720 info!("Vector index sync enabled");
721 let mut manager = sync_manager.with_vector_coordinator(vector_coordinator);
722 manager.set_auto_embed_text(auto_embed);
723 if auto_embed {
724 info!("Vector write-time auto-embedding enabled");
725 }
726 let policy = linkrs_sync::backend::BackendDeliveryPolicy::from_config(_config.vector_config());
729 info!(
730 "Vector delivery policy: {} batch={} lease_ms={} retries={} concurrency={}",
731 policy.policy_name,
732 policy.batch_size,
733 policy.lease_ms,
734 policy.max_retries,
735 policy.max_concurrency
736 );
737 manager.configure_backend_policy(policy);
738 Ok(manager)
739}
740
741fn property_graph_config_from_config(
742 storage: &crate::config::StorageConfig,
743) -> PropertyGraphConfig {
744 let mut config = PropertyGraphConfig::default();
745 let cache_memory = match usize::try_from(storage.max_memory_bytes) {
746 Ok(value) => value.min(config.cache_memory),
747 Err(_) => config.cache_memory,
748 };
749 config.cache_memory = cache_memory;
750 config.resources = ResourceConfig {
751 max_memory_bytes: storage.max_memory_bytes,
752 index_memory_bytes: storage.index_memory_bytes,
753 memory_soft_ratio: storage.memory_soft_ratio,
754 memory_hard_ratio: storage.memory_hard_ratio,
755 max_active_snapshots: storage.max_active_snapshots,
756 max_snapshot_age: Duration::from_secs(storage.max_snapshot_age_secs),
757 max_tombstones: storage.max_tombstones,
758 max_tombstone_bytes: storage.max_tombstone_bytes,
759 index_gc_batch: storage.index_gc_batch,
760 operation_timeout: Duration::from_secs(storage.operation_timeout_secs),
761 dirty_flush_operations: storage.dirty_flush_operations,
762 dirty_flush_bytes: storage.dirty_flush_bytes,
763 cache_ttl: (storage.cache_ttl_secs > 0)
764 .then(|| Duration::from_secs(storage.cache_ttl_secs)),
765 cache_tti: (storage.cache_tti_secs > 0)
766 .then(|| Duration::from_secs(storage.cache_tti_secs)),
767 index_pool_capacity_bytes: storage.index_pool_capacity_bytes,
768 index_eviction_enabled: storage.index_eviction_enabled,
769 index_eviction_high_ratio: storage.index_eviction_high_ratio,
770 index_eviction_low_ratio: storage.index_eviction_low_ratio,
771 ..config.resources
772 };
773 config
774}
775
776pub async fn execute_query(query_str: &str) -> DBResult<()> {
778 info!("Executing query: {}", query_str);
779
780 let config = crate::config::Config::default();
781 let inner_storage = Arc::new(GraphStorage::new()?);
782
783 let sync_storage = SyncWrapper::new((*inner_storage).clone());
784 let storage = Arc::new(sync_storage);
785
786 let graph_service =
787 GraphService::<SyncWrapper<GraphStorage>>::new_for_test(config, storage).await;
788
789 let session = match graph_service
790 .get_session_manager()
791 .create_session("anonymous".into(), "127.0.0.1".to_string())
792 .await
793 {
794 Ok(session) => session,
795 Err(e) => {
796 error!("Failed to create session: {}", e);
797 return Err(linkrs_core::error::DBError::from(
798 crate::session::SessionError::manager_error(format!(
799 "Failed to create session: {}",
800 e
801 )),
802 ));
803 }
804 };
805
806 let session_id = session.id();
807
808 match graph_service.execute(session_id, query_str).await {
809 Ok(result) => {
810 info!("Query executed successfully: {:?}", result);
811 }
812 Err(e) => {
813 error!("Query execution error: {}", e);
814 }
815 }
816
817 Ok(())
818}