pub mod connection;
pub mod migrations;
pub mod store;
pub mod txn;
pub use store::{SqlitePendingStore, SqlitePersistenceStore};
pub use mempill_core::ports::OraclePort;
#[derive(Debug, thiserror::Error)]
pub enum SqliteStoreError {
#[error("SQLite error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("Migration error: {0}")]
Migration(#[from] migrations::MigrationError),
#[error("Mapping error: {0}")]
Mapping(String),
#[error("a transaction is already open on this store; commit or rollback before beginning a new one")]
TxnAlreadyOpen,
}
const _: () = {
fn assert_send_sync<T: Send + Sync + 'static>() {}
fn check() { assert_send_sync::<SqliteStoreError>(); }
};
pub type DefaultEngine = mempill_core::EngineHandle<
SqlitePersistenceStore,
mempill_core::NoOpOracle,
mempill_core::NoOpVector,
>;
pub type OracleEngine<O> = mempill_core::EngineHandle<
SqlitePersistenceStore,
O,
mempill_core::NoOpVector,
>;
pub fn open_with_oracle<O>(
path: &str,
oracle: std::sync::Arc<O>,
) -> Result<OracleEngine<O>, SqliteStoreError>
where
O: OraclePort + Send + Sync + 'static,
{
let conn = connection::open(path)?;
let store = std::sync::Arc::new(SqlitePersistenceStore::new(conn));
let pending_store: std::sync::Arc<dyn mempill_core::ErasedPendingStore> =
std::sync::Arc::new(mempill_core::ErasedPendingStoreAdapter::new(store.pending_store()));
Ok(mempill_core::EngineHandle::new_with_pending_store::<()>(
store,
Some(oracle),
None::<std::sync::Arc<mempill_core::NoOpVector>>,
pending_store,
mempill_core::EngineConfig::default(),
))
}
pub fn open_with_oracle_in_memory<O>(
oracle: std::sync::Arc<O>,
) -> Result<OracleEngine<O>, SqliteStoreError>
where
O: OraclePort + Send + Sync + 'static,
{
let conn = connection::open_in_memory()?;
let store = std::sync::Arc::new(SqlitePersistenceStore::new(conn));
let pending_store: std::sync::Arc<dyn mempill_core::ErasedPendingStore> =
std::sync::Arc::new(mempill_core::ErasedPendingStoreAdapter::new(store.pending_store()));
Ok(mempill_core::EngineHandle::new_with_pending_store::<()>(
store,
Some(oracle),
None::<std::sync::Arc<mempill_core::NoOpVector>>,
pending_store,
mempill_core::EngineConfig::default(),
))
}
pub fn open_default(path: &str) -> Result<DefaultEngine, SqliteStoreError> {
let conn = connection::open(path)?;
let store = std::sync::Arc::new(SqlitePersistenceStore::new(conn));
Ok(mempill_core::EngineHandle::new(
store,
None,
None,
mempill_core::EngineConfig::default(),
))
}
pub fn open_default_in_memory() -> Result<DefaultEngine, SqliteStoreError> {
let conn = connection::open_in_memory()?;
let store = std::sync::Arc::new(SqlitePersistenceStore::new(conn));
Ok(mempill_core::EngineHandle::new(
store,
None,
None,
mempill_core::EngineConfig::default(),
))
}
#[cfg(test)]
mod tests {
use super::*;
use mempill_core::application::{IngestClaimRequest, QueryMemoryRequest};
use mempill_types::{
AgentId, BeliefStatus, Cardinality, Confidence, Criticality, ExternalKind, ProvenanceLabel,
};
#[tokio::test]
async fn e2e_ingest_then_query_returns_belief() {
let engine = open_default_in_memory().expect("in-memory engine must open");
let agent = AgentId("e2e-agent".into());
let ingest_req = IngestClaimRequest {
agent_id: agent.clone(),
subject: "user".into(),
predicate: "city".into(),
value: serde_json::json!("Berlin"),
provenance: ProvenanceLabel::External(ExternalKind::UserAsserted),
cardinality: Cardinality::Functional,
valid_time: None,
confidence: Confidence { value_confidence: 0.95, valid_time_confidence: 0.0 },
criticality: Criticality::Medium,
derived_from: vec![],
};
let ingest_resp = engine.ingest_claim(ingest_req).await
.expect("ingest must succeed");
assert!(!ingest_resp.claim_ref.0.is_nil(), "claim_ref must be non-nil");
assert_eq!(ingest_resp.disposition, mempill_types::Disposition::CommittedCheap,
"first External claim must be CommittedCheap");
let query_req = QueryMemoryRequest {
agent_id: agent.clone(),
subject: "user".into(),
predicate: "city".into(),
as_of_tx_time: None,
};
let query_resp = engine.query_memory(query_req).await
.expect("query must succeed");
assert!(
matches!(
query_resp.belief.status,
BeliefStatus::Resolved | BeliefStatus::TimingUncertain
),
"belief status must be Resolved or TimingUncertain after ingest, got {:?}",
query_resp.belief.status
);
assert!(query_resp.belief.primary.is_some(), "primary belief must be present");
let primary = query_resp.belief.primary.unwrap();
assert_eq!(primary.fact.value, serde_json::json!("Berlin"),
"fact value must match the ingested value");
assert_eq!(primary.claim_ref, ingest_resp.claim_ref,
"queried claim_ref must match the ingested claim_ref");
}
#[test]
fn default_engine_type_alias_exists_in_mempill_sqlite() {
fn assert_is_default_engine(_: &DefaultEngine) {}
let engine = open_default_in_memory().unwrap();
assert_is_default_engine(&engine);
}
}