use crate::api_core::{QueryApi, SchemaApi};
use crate::embedded::hooks::HookBus;
use crate::session_variables::SessionVariables;
use linkrs_core::SessionStatistics;
use linkrs_metrics::StatsManager;
use linkrs_query::executor::expression::functions::FunctionRegistry;
use linkrs_storage::StorageClient;
use linkrs_sync::SyncManager;
use linkrs_transaction::TransactionId;
use linkrs_transaction::TransactionManager;
use parking_lot::RwLock;
use std::sync::Arc;
#[cfg(feature = "vector")]
use tokio::runtime::Runtime;
pub struct Session<S: StorageClient + Clone + Send + Sync + 'static> {
db: Arc<GraphDatabaseInner<S>>,
space_id: Arc<RwLock<Option<u64>>>,
space_name: Arc<RwLock<Option<String>>>,
auto_commit: bool,
current_transaction: Arc<RwLock<Option<TransactionId>>>,
statistics: SessionStatistics,
function_registry: Arc<RwLock<FunctionRegistry>>,
session_variables: Arc<crate::session_variables::SessionVariables>,
interrupted: Arc<std::sync::atomic::AtomicBool>,
}
pub(crate) struct GraphDatabaseInner<S: StorageClient + Clone + Send + Sync + 'static> {
pub(crate) query_api: Arc<RwLock<QueryApi<S>>>,
pub(crate) schema_api: SchemaApi<S>,
pub(crate) txn_manager: Arc<TransactionManager>,
pub(crate) storage: Arc<RwLock<S>>,
#[cfg_attr(not(feature = "vector"), allow(dead_code))]
pub(crate) sync_manager: Option<Arc<SyncManager>>,
pub(crate) stats_manager: Arc<StatsManager>,
pub(crate) hooks: HookBus,
#[cfg(feature = "vector")]
#[allow(dead_code)]
pub(crate) vector_runtime: Arc<Runtime>,
pub(crate) read_only: bool,
}
impl<S: StorageClient + Clone + Send + Sync + 'static + linkrs_storage::UndoTarget> Session<S> {
pub(crate) fn new(db: Arc<GraphDatabaseInner<S>>) -> Self {
Self {
db,
space_id: Arc::new(RwLock::new(None)),
space_name: Arc::new(RwLock::new(None)),
auto_commit: true,
current_transaction: Arc::new(RwLock::new(None)),
statistics: SessionStatistics::new(),
function_registry: Arc::new(RwLock::new(FunctionRegistry::new())),
session_variables: Arc::new(SessionVariables::new()),
interrupted: Arc::new(std::sync::atomic::AtomicBool::new(false)),
}
}
}
impl<S: StorageClient + Clone + Send + Sync + 'static> Drop for Session<S> {
fn drop(&mut self) {
log::debug!(
"Session released, current graph space: {:?}",
self.space_name.read()
);
}
}
pub(crate) mod create_as;
pub(crate) mod execution;
pub(crate) mod extension;
pub(crate) mod state;
pub(crate) mod transactions;
#[cfg(feature = "vector")]
pub(crate) mod vector;
#[cfg(test)]
mod tests {
use super::*;
use crate::embedded::database::GraphDatabase;
use linkrs_storage::{GraphStorage, MockStorage};
#[test]
fn create_as_detection_follows_the_parsed_target() {
assert!(Session::<GraphStorage>::parse_create_as(
"CREATE TAG adults AS (MATCH (n) RETURN n)"
)
.is_some());
assert!(Session::<GraphStorage>::parse_create_as(
"CREATE EDGE worked AS (MATCH (n) RETURN n)"
)
.is_some());
assert!(Session::<GraphStorage>::parse_create_as("CREATE TAG t(name string)").is_none());
assert!(Session::<GraphStorage>::parse_create_as("MATCH (n) RETURN 'AS ('").is_none());
assert!(!Session::<GraphStorage>::starts_with_create(
"MATCH (n) RETURN n"
));
}
#[test]
fn interrupt_flag_fails_execute_fast_and_clears() {
let db = GraphDatabase::<MockStorage>::open_test().expect("test database opens");
let session = db.session().expect("session creates");
assert!(!session.is_interrupted());
session.interrupt();
assert!(session.is_interrupted());
let err = session
.execute("MATCH (n) RETURN n")
.expect_err("interrupted session must fail fast");
assert!(
err.to_string().contains("interrupted"),
"unexpected error: {err}"
);
session.clear_interrupt();
assert!(!session.is_interrupted());
}
}