#![forbid(unsafe_code)]
pub mod derived;
pub mod doc_store;
pub mod docs_ops;
pub mod error;
pub mod graph;
pub mod history;
pub mod ids;
pub mod links;
pub mod macros;
pub mod mutate;
pub mod observe;
pub mod order_key;
pub mod plan;
pub mod properties;
pub mod read;
pub mod schema;
pub mod search;
pub mod time;
pub mod tree;
pub mod writers;
pub mod yaml_emit;
use std::path::Path;
use rusqlite::functions::{Context, FunctionFlags};
use rusqlite::types::ValueRef;
use rusqlite::{Connection, Transaction, params};
pub use derived::{GcResult, RebuildTarget};
pub use doc_store::{DocStore, FsDocStore, MemDocStore, NullDocStore};
pub use docs_ops::{DocMoveResult, DocOpContext, DocOpResult, Retargeted};
pub use error::{Error, Result};
pub use graph::ResolvedEdge;
pub use history::{ChangesPage, CommitDigest, DigestRevision};
pub use ids::{IdMinter, RandomMinter, SequentialMinter, is_valid_id, prefix_of};
pub use links::InboundLink;
pub use macros::{LinkRepair, LinkRepairCount, LinkRepairPlan, RetargetHit};
pub use mutate::{
ApplyOrigin, ApplyRequest, ApplyResult, Diff, DocInfo, Revision, SetFrontmatter,
find_doc_by_ref, is_id_ref, load_mut_doc,
};
pub use observe::{
BatchItem, BatchOutcome, Committed, DeleteOutcome, ObserveOutcome, has_conflict_markers,
};
pub use omgbase_graph::{EdgeDescriptor, ProjectedNode};
pub use omgbase_mutate::{
self as mutate_kernel, At, Expect, MutBlock, MutDoc, MutationError, Op, OpResult, Opset,
Parent, PlanOp, To,
};
pub use omgbase_properties::PropertyRow;
pub use omgbase_reconcile::{Config, MatchBlock, PoolEntry};
pub use omgbase_search::{
Boosts, DocEmbedBlockRef, DocEmbedMethod, DocEmbedTask, EmbedTask, EmbeddingProvider, Evidence,
};
pub use read::RevisionRead;
pub use schema::{SCHEMA_SQL, SCHEMA_VERSION};
pub use search::{
BlockContexts, ContextScope, DocEmbedStats, DocVectorHit, DocVectorRow, DrainStats, EmbedStats,
ForeignVectors, HybridHit, HybridQuery, QueryVector, ResolveHit, TextHit, TextSearchResult,
VectorHit, block_vector,
};
pub use tree::{TreeEntry, canonical_attrs, canonical_json, serialize_tree_entries, tree_hash};
pub use writers::{NewCommit, NewRevision, Origin, TreeInputBlock};
pub const SPEC_VERSION: &str = "13.4";
pub const FORMAT_MARKDOWN: &str = "markdown";
pub struct Store {
conn: Connection,
minter: Box<dyn IdMinter>,
}
impl std::fmt::Debug for Store {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Store").finish_non_exhaustive()
}
}
#[must_use]
pub fn cosine_bytes(a: &[u8], b: &[u8]) -> f64 {
omgbase_search::cosine_bytes(a, b)
}
fn cosine_udf(ctx: &Context<'_>) -> rusqlite::Result<Option<f64>> {
let blob = |i: usize| -> rusqlite::Result<Option<&[u8]>> {
match ctx.get_raw(i) {
ValueRef::Null => Ok(None),
ValueRef::Blob(b) => Ok(Some(b)),
other => Err(rusqlite::Error::InvalidFunctionParameterType(
i,
other.data_type(),
)),
}
};
match (blob(0)?, blob(1)?) {
(Some(a), Some(b)) => Ok(Some(cosine_bytes(a, b))),
_ => Ok(None),
}
}
impl Store {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
Self::open_with_minter(path, Box::new(RandomMinter))
}
pub fn open_with_minter(path: impl AsRef<Path>, minter: Box<dyn IdMinter>) -> Result<Self> {
let path = path.as_ref();
if path.as_os_str() != ":memory:" {
if let Some(dir) = path.parent().filter(|d| !d.as_os_str().is_empty()) {
std::fs::create_dir_all(dir)
.map_err(|e| Error::Other(format!("cannot create {}: {e}", dir.display())))?;
}
}
Self::from_connection(Connection::open(path)?, minter)
}
pub fn open_in_memory() -> Result<Self> {
Self::from_connection(Connection::open_in_memory()?, Box::new(RandomMinter))
}
pub fn open_in_memory_with_minter(minter: Box<dyn IdMinter>) -> Result<Self> {
Self::from_connection(Connection::open_in_memory()?, minter)
}
pub fn from_connection(conn: Connection, mut minter: Box<dyn IdMinter>) -> Result<Self> {
conn.pragma_update(None, "journal_mode", "WAL")?;
conn.pragma_update(None, "synchronous", "NORMAL")?;
conn.pragma_update(None, "foreign_keys", "ON")?;
conn.create_scalar_function(
"cosine",
2,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
cosine_udf,
)?;
schema::migrate(&conn, &mut *minter)?;
Ok(Self { conn, minter })
}
#[must_use]
pub fn conn(&self) -> &Connection {
&self.conn
}
pub fn minter_mut(&mut self) -> &mut dyn IdMinter {
&mut *self.minter
}
pub fn mint(&mut self, prefix: &str) -> String {
self.minter.mint(prefix)
}
pub fn transaction(&self) -> Result<Transaction<'_>> {
Ok(self.conn.unchecked_transaction()?)
}
pub fn create_repo(&mut self, slug: &str) -> Result<String> {
let repo_id = self.minter.mint("rp");
self.conn.execute(
"INSERT INTO repos (repo_id, slug) VALUES (?1, ?2)",
params![repo_id, slug],
)?;
Ok(repo_id)
}
pub fn repo_by_slug(&self, slug: &str) -> Result<Option<String>> {
use rusqlite::OptionalExtension;
Ok(self
.conn
.query_row(
"SELECT repo_id FROM repos WHERE slug = ?1",
params![slug],
|r| r.get(0),
)
.optional()?)
}
pub fn user_version(&self) -> Result<i64> {
schema::user_version(&self.conn)
}
pub fn put_blob(&self, text: &str) -> Result<String> {
writers::put_blob(&self.conn, text)
}
pub fn put_tree_node(&self, entries: &[TreeEntry]) -> Result<String> {
writers::put_tree_node(&self.conn, entries)
}
pub fn write_block_tree(&self, blocks: &[TreeInputBlock]) -> Result<String> {
writers::write_block_tree(&self.conn, blocks)
}
pub fn new_commit(&mut self, input: &NewCommit<'_>) -> Result<(String, i64)> {
writers::new_commit(&self.conn, &mut *self.minter, input)
}
pub fn write_revision(&mut self, input: &NewRevision<'_>) -> Result<(String, i64)> {
writers::write_revision(&self.conn, &mut *self.minter, input)
}
pub fn reconstruct(&self, doc_id: &str) -> Result<Option<String>> {
read::reconstruct(&self.conn, doc_id)
}
pub fn read_at_revision(&self, doc_id: &str, rev_id: &str) -> Result<Option<RevisionRead>> {
read::read_at_revision(&self.conn, doc_id, rev_id)
}
pub fn changes_since(
&self,
repo_id: &str,
cursor: i64,
limit: usize,
origin: Option<&str>,
) -> Result<ChangesPage> {
history::changes_since(&self.conn, repo_id, cursor, limit, origin)
}
pub fn load_old_match_blocks(&self, doc_id: &str) -> Result<Vec<MatchBlock>> {
read::load_old_match_blocks(&self.conn, doc_id)
}
pub fn load_pool(&self, repo_id: &str, ts: &str) -> Result<Vec<PoolEntry>> {
read::load_pool(&self.conn, repo_id, ts)
}
pub fn properties(&self, doc_id: &str) -> Result<Vec<PropertyRow>> {
properties::read_doc_properties(&self.conn, doc_id)
}
pub fn properties_grouped(&self, doc_id: &str) -> Result<serde_json::Value> {
Ok(omgbase_properties::grouped(&self.properties(doc_id)?))
}
pub fn properties_merged(&self, doc_id: &str) -> Result<serde_json::Value> {
Ok(omgbase_properties::merged(&self.properties(doc_id)?))
}
pub fn rebuild_doc_edges(&self, doc_id: &str) -> Result<()> {
graph::rebuild_doc_edges(&self.conn, doc_id)
}
pub fn rebuild_sections(&self, doc_id: &str) -> Result<()> {
derived::rebuild_sections(&self.conn, doc_id)
}
pub fn rebuild_index(&self, target: RebuildTarget) -> Result<()> {
derived::rebuild_index(&self.conn, target)
}
pub fn gc(&self, enabled: bool) -> Result<GcResult> {
derived::run_gc(&self.conn, enabled)
}
pub fn gc_dry_run(&self) -> Result<GcResult> {
derived::gc_dry_run(&self.conn)
}
pub fn sweep_pool(&self, ts: &str) -> Result<usize> {
derived::sweep_pool(&self.conn, ts)
}
pub fn close(self) -> Result<()> {
self.conn.close().map_err(|(_, e)| Error::Sqlite(e))
}
}
#[cfg(test)]
mod tests;