use super::{
Arc, DeepModel, Engine, ManagedConnection, Path, PersistentStorageBackend,
PersistentStorageProvider, PersistentStorageSession, SQLiteCompressedContainerAnchor,
SQLiteCompressionOptions, SQLiteError, SQLiteStorageProvider, StorageBackendError,
StorageBackendResult,
};
struct BackendSessionProvider {
backend: Arc<dyn PersistentStorageBackend>,
}
impl PersistentStorageProvider for BackendSessionProvider {
fn open_session(&self) -> StorageBackendResult<PersistentStorageSession> {
self.backend.open_session()
}
fn storage_identity(
&self,
) -> StorageBackendResult<Option<uqa_storage::PersistentStorageIdentity>> {
self.backend.storage_identity()
}
}
impl Engine {
pub fn open(path: &Path) -> Result<Self, SQLiteError> {
let conn = ManagedConnection::open(path)?;
Self::open_with_connection(&conn)
}
pub fn detect_database_file(path: &Path) -> std::io::Result<uqa_storage::DatabaseFileFormat> {
uqa_storage::detect_database_file_format(path)
}
pub fn open_auto(path: &Path, key: Option<&str>) -> Result<Self, SQLiteError> {
use uqa_storage::DatabaseFileFormat;
let key = match key {
Some("") => return Err(SQLiteError::EmptyEncryptionKey),
other => other,
};
match uqa_storage::detect_database_file_format(path)? {
DatabaseFileFormat::Missing => match key {
Some(key) => Self::open_encrypted(path, key),
None => Self::open(path),
},
DatabaseFileFormat::PlainSQLite => match key {
Some(_) => Err(SQLiteError::NotEncrypted),
None => Self::open(path),
},
DatabaseFileFormat::CompressedContainer { encrypted: true } => match key {
Some(key) => {
Self::open_compressed_encrypted(path, key, SQLiteCompressionOptions::default())
}
None => Err(SQLiteError::EncryptionKeyRequired),
},
DatabaseFileFormat::CompressedContainer { encrypted: false } => match key {
Some(_) => Err(SQLiteError::NotEncrypted),
None => Self::open_compressed(path, SQLiteCompressionOptions::default()),
},
DatabaseFileFormat::Unrecognized => match key {
Some(key) => Self::open_encrypted(path, key),
None => Err(SQLiteError::EncryptionKeyRequired),
},
}
}
pub fn open_encrypted(path: &Path, key: &str) -> Result<Self, SQLiteError> {
let conn = ManagedConnection::open_encrypted(path, key)?;
Self::open_with_connection(&conn)
}
pub fn open_compressed(
path: &Path,
compression: SQLiteCompressionOptions,
) -> Result<Self, SQLiteError> {
let conn = ManagedConnection::open_compressed(path, compression)?;
Self::open_with_connection(&conn)
}
pub fn open_compressed_encrypted(
path: &Path,
key: &str,
compression: SQLiteCompressionOptions,
) -> Result<Self, SQLiteError> {
let conn = ManagedConnection::open_compressed_encrypted(path, key, compression)?;
Self::open_with_connection(&conn)
}
pub fn open_compressed_encrypted_with_anchor(
path: &Path,
key: &str,
compression: SQLiteCompressionOptions,
trusted_anchor: SQLiteCompressedContainerAnchor,
) -> Result<Self, SQLiteError> {
let conn = ManagedConnection::open_compressed_encrypted_with_anchor(
path,
key,
compression,
trusted_anchor,
)?;
Self::open_with_connection(&conn)
}
pub fn compressed_container_anchor(
path: &Path,
key: &str,
) -> Result<SQLiteCompressedContainerAnchor, SQLiteError> {
Ok(uqa_storage::read_authenticated_anchor(path, key)?)
}
fn open_with_connection(conn: &ManagedConnection) -> Result<Self, SQLiteError> {
let provider: Arc<dyn PersistentStorageProvider> =
Arc::new(SQLiteStorageProvider::new(conn.clone()));
Self::from_persistent_provider(provider).map_err(Self::sqlite_open_error)
}
pub fn new_session(&self) -> StorageBackendResult<Self> {
let provider = self.storage.provider.as_ref().ok_or_else(|| {
StorageBackendError::Other(
"independent sessions require a PersistentStorageProvider".into(),
)
})?;
let storage_session = provider.open_session()?;
let mut session =
Self::from_persistent_session(storage_session, Some(Arc::clone(provider)))?;
session.row_locks = Arc::clone(&self.row_locks);
session.session_id = self.row_locks.allocate_session();
session.epochs.share_published_from(&self.epochs);
session.extensions = super::RuntimeExtensions::shared_from(&self.extensions);
session.synchronize_table_catalog()?;
session.synchronize_table_data()?;
session.synchronize_catalog_registries()?;
Ok(session)
}
#[allow(clippy::needless_pass_by_value)]
pub fn from_persistent_provider(
provider: Arc<dyn PersistentStorageProvider>,
) -> StorageBackendResult<Self> {
let identity = provider.storage_identity()?;
let session = provider.open_session()?;
let mut engine = Self::from_persistent_session(session, Some(Arc::clone(&provider)))?;
let row_locks = crate::row_locks::shared_provider_manager(identity, &provider);
engine.session_id = row_locks.allocate_session();
engine.row_locks = row_locks;
Ok(engine)
}
pub fn from_persistent_backends(
catalog: Arc<dyn uqa_storage::CatalogFacade>,
backend: Arc<dyn PersistentStorageBackend>,
) -> StorageBackendResult<Self> {
let identity = backend.storage_identity()?;
let row_locks = crate::row_locks::shared_backend_manager(identity, &backend);
let provider: Arc<dyn PersistentStorageProvider> = Arc::new(BackendSessionProvider {
backend: Arc::clone(&backend),
});
let mut engine = Self::from_persistent_session(
PersistentStorageSession::new(catalog, backend),
Some(provider),
)?;
engine.session_id = row_locks.allocate_session();
engine.row_locks = row_locks;
Ok(engine)
}
fn from_persistent_session(
storage_session: PersistentStorageSession,
provider: Option<Arc<dyn PersistentStorageProvider>>,
) -> StorageBackendResult<Self> {
let PersistentStorageSession { catalog, backend } = storage_session;
let restore_catalog = Arc::clone(&catalog);
let restore_backend = Arc::clone(&backend);
let row_locks = Arc::new(crate::row_locks::RowLockManager::new());
let session_id = row_locks.allocate_session();
let mut engine = Self {
storage: super::StorageContext::persistent(catalog, backend, provider),
durable: Arc::new(super::DurableCatalogState::new()),
session: Arc::new(super::SessionContext::new(super::initial_random_state())),
extensions: super::RuntimeExtensions::new(),
epochs: super::EpochCoordinator::new(),
runtime: super::QueryRuntime::new(super::SQL_FUNCTION_DEPTH_LIMIT),
row_locks,
session_id,
owns_session_registration: true,
query_table_snapshots: None,
query_view_snapshots: None,
query_sql_function_snapshots: None,
query_catalog_snapshot: None,
query_transaction_overlay: None,
query_transaction_origin: None,
};
Self::prepare_catalog_for_initial_restore(restore_catalog.as_ref())?;
restore_backend.migrate_inverted_index_storage()?;
engine.restore_from_catalog(restore_catalog.as_ref(), restore_backend.as_ref())?;
engine.repair_reset_fts_storage(restore_catalog.as_ref())?;
engine.repair_persistent_value_indexes_on_open()?;
for (name, json) in restore_catalog.load_models()? {
let model = serde_json::from_str::<DeepModel>(&json)?;
engine.durable.models.write().insert(name, model);
}
for (name, json) in restore_catalog.load_all_scoring_params()? {
engine.durable.scoring_params.write().insert(name, json);
}
if let Some(version) = restore_backend.change_version()? {
engine
.epochs
.seen_storage_change_version
.store(version, std::sync::atomic::Ordering::Release);
}
Ok(engine)
}
fn sqlite_open_error(err: StorageBackendError) -> SQLiteError {
match err {
StorageBackendError::Analysis(err) => SQLiteError::Analysis(err),
StorageBackendError::SQLite(err) => err,
StorageBackendError::Serde(err) => SQLiteError::Serde(err),
StorageBackendError::Backend { backend, source } => {
SQLiteError::StorageBackend(format!("{backend} storage failed: {source}"))
}
StorageBackendError::Other(msg) => SQLiteError::StorageBackend(msg),
}
}
}