|
|
|
@ -1,5 +1,6 @@ |
|
|
|
|
use crate::error::invalid_data_error; |
|
|
|
|
use crate::model::{GraphNameRef, NamedOrBlankNodeRef, Quad, QuadRef, TermRef}; |
|
|
|
|
use crate::storage::backend::{Reader, Transaction}; |
|
|
|
|
use crate::storage::binary_encoder::{ |
|
|
|
|
decode_term, encode_term, encode_term_pair, encode_term_quad, encode_term_triple, |
|
|
|
|
write_gosp_quad, write_gpos_quad, write_gspo_quad, write_osp_quad, write_ospg_quad, |
|
|
|
@ -11,12 +12,13 @@ use crate::storage::numeric_encoder::{ |
|
|
|
|
}; |
|
|
|
|
use backend::{ |
|
|
|
|
ColumnFamily, ColumnFamilyDefinition, CompactionAction, CompactionFilter, Db, Iter, |
|
|
|
|
MergeOperator, WriteBatchWithIndex, |
|
|
|
|
MergeOperator, |
|
|
|
|
}; |
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
use std::collections::{hash_map, HashMap, HashSet}; |
|
|
|
|
use std::ffi::CString; |
|
|
|
|
use std::io::Result; |
|
|
|
|
use std::mem::swap; |
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
use std::mem::take; |
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
@ -41,7 +43,7 @@ const DPOS_CF: &str = "dpos"; |
|
|
|
|
const DOSP_CF: &str = "dosp"; |
|
|
|
|
const GRAPHS_CF: &str = "graphs"; |
|
|
|
|
const DEFAULT_CF: &str = "default"; |
|
|
|
|
const AUTO_WRITE_BATCH_THRESHOLD: usize = 1024; |
|
|
|
|
const AUTO_WRITE_BATCH_THRESHOLD: usize = 1024 * 1024; |
|
|
|
|
|
|
|
|
|
/// Low level storage primitives
|
|
|
|
|
#[derive(Clone)] |
|
|
|
@ -217,38 +219,46 @@ impl Storage { |
|
|
|
|
|
|
|
|
|
let mut version = this.ensure_version()?; |
|
|
|
|
if version == 0 { |
|
|
|
|
let mut batch = this.db.new_batch(); |
|
|
|
|
let mut transaction = this.db.transaction(); |
|
|
|
|
let mut size = 0; |
|
|
|
|
// We migrate to v1
|
|
|
|
|
for quad in this.quads() { |
|
|
|
|
for quad in this.reader().quads() { |
|
|
|
|
let quad = quad?; |
|
|
|
|
if !quad.graph_name.is_default_graph() { |
|
|
|
|
batch.insert_empty(&this.graphs_cf, &encode_term(&quad.graph_name)); |
|
|
|
|
if batch.len() >= AUTO_WRITE_BATCH_THRESHOLD { |
|
|
|
|
this.db.write(&mut batch)?; |
|
|
|
|
transaction.insert_empty(&this.graphs_cf, &encode_term(&quad.graph_name))?; |
|
|
|
|
size += 1; |
|
|
|
|
if size % AUTO_WRITE_BATCH_THRESHOLD == 0 { |
|
|
|
|
let mut tr = this.db.transaction(); |
|
|
|
|
swap(&mut transaction, &mut tr); |
|
|
|
|
tr.commit()?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
this.db.write(&mut batch)?; |
|
|
|
|
transaction.commit()?; |
|
|
|
|
this.db.flush(&this.graphs_cf)?; |
|
|
|
|
version = 1; |
|
|
|
|
this.update_version(version)?; |
|
|
|
|
} |
|
|
|
|
if version == 1 { |
|
|
|
|
// We migrate to v2
|
|
|
|
|
let mut batch = this.db.new_batch(); |
|
|
|
|
let mut iter = this.db.iter(&this.id2str_cf); |
|
|
|
|
let mut transaction = this.db.transaction(); |
|
|
|
|
let reader = this.db.snapshot(); |
|
|
|
|
let mut size = 0; |
|
|
|
|
let mut iter = reader.iter(&this.id2str_cf); |
|
|
|
|
while let (Some(key), Some(value)) = (iter.key(), iter.value()) { |
|
|
|
|
let mut new_value = Vec::with_capacity(value.len() + 4); |
|
|
|
|
new_value.extend_from_slice(&i32::MAX.to_be_bytes()); |
|
|
|
|
new_value.extend_from_slice(value); |
|
|
|
|
batch.insert(&this.id2str_cf, key, &new_value); |
|
|
|
|
transaction.insert(&this.id2str_cf, key, &new_value)?; |
|
|
|
|
iter.next(); |
|
|
|
|
if batch.len() >= AUTO_WRITE_BATCH_THRESHOLD { |
|
|
|
|
this.db.write(&mut batch)?; |
|
|
|
|
batch.clear(); |
|
|
|
|
size += 1; |
|
|
|
|
if size % AUTO_WRITE_BATCH_THRESHOLD == 0 { |
|
|
|
|
let mut tr = this.db.transaction(); |
|
|
|
|
swap(&mut transaction, &mut tr); |
|
|
|
|
tr.commit()?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
this.db.write(&mut batch)?; |
|
|
|
|
transaction.commit()?; |
|
|
|
|
iter.status()?; |
|
|
|
|
this.db.flush(&this.id2str_cf)?; |
|
|
|
|
version = 2; |
|
|
|
@ -270,7 +280,7 @@ impl Storage { |
|
|
|
|
|
|
|
|
|
fn ensure_version(&self) -> Result<u64> { |
|
|
|
|
Ok( |
|
|
|
|
if let Some(version) = self.db.get(&self.default_cf, b"oxversion")? { |
|
|
|
|
if let Some(version) = self.reader().reader.get(&self.default_cf, b"oxversion")? { |
|
|
|
|
let mut buffer = [0; 8]; |
|
|
|
|
buffer.copy_from_slice(&version); |
|
|
|
|
u64::from_be_bytes(buffer) |
|
|
|
@ -282,28 +292,90 @@ impl Storage { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn update_version(&self, version: u64) -> Result<()> { |
|
|
|
|
let mut batch = self.db.new_batch(); |
|
|
|
|
batch.insert(&self.default_cf, b"oxversion", &version.to_be_bytes()); |
|
|
|
|
self.db.write(&mut batch)?; |
|
|
|
|
let mut transaction = self.db.transaction(); |
|
|
|
|
transaction.insert(&self.default_cf, b"oxversion", &version.to_be_bytes())?; |
|
|
|
|
transaction.commit()?; |
|
|
|
|
self.db.flush(&self.default_cf) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Unsafe reader (data might appear and disapear between two reads)
|
|
|
|
|
/// Use [`snapshot`] if you don't want that.
|
|
|
|
|
pub fn reader(&self) -> StorageReader { |
|
|
|
|
StorageReader { |
|
|
|
|
reader: self.db.reader(), |
|
|
|
|
storage: self.clone(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn snapshot(&self) -> StorageReader { |
|
|
|
|
StorageReader { |
|
|
|
|
reader: self.db.snapshot(), |
|
|
|
|
storage: self.clone(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn transaction(&self) -> StorageWriter { |
|
|
|
|
StorageWriter { |
|
|
|
|
buffer: Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE), |
|
|
|
|
transaction: self.db.transaction(), |
|
|
|
|
storage: self.clone(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
pub fn flush(&self) -> Result<()> { |
|
|
|
|
self.db.flush(&self.default_cf)?; |
|
|
|
|
self.db.flush(&self.gpos_cf)?; |
|
|
|
|
self.db.flush(&self.gpos_cf)?; |
|
|
|
|
self.db.flush(&self.gosp_cf)?; |
|
|
|
|
self.db.flush(&self.spog_cf)?; |
|
|
|
|
self.db.flush(&self.posg_cf)?; |
|
|
|
|
self.db.flush(&self.ospg_cf)?; |
|
|
|
|
self.db.flush(&self.dspo_cf)?; |
|
|
|
|
self.db.flush(&self.dpos_cf)?; |
|
|
|
|
self.db.flush(&self.dosp_cf)?; |
|
|
|
|
self.db.flush(&self.id2str_cf) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
pub fn compact(&self) -> Result<()> { |
|
|
|
|
self.db.compact(&self.default_cf)?; |
|
|
|
|
self.db.compact(&self.gpos_cf)?; |
|
|
|
|
self.db.compact(&self.gpos_cf)?; |
|
|
|
|
self.db.compact(&self.gosp_cf)?; |
|
|
|
|
self.db.compact(&self.spog_cf)?; |
|
|
|
|
self.db.compact(&self.posg_cf)?; |
|
|
|
|
self.db.compact(&self.ospg_cf)?; |
|
|
|
|
self.db.compact(&self.dspo_cf)?; |
|
|
|
|
self.db.compact(&self.dpos_cf)?; |
|
|
|
|
self.db.compact(&self.dosp_cf)?; |
|
|
|
|
self.db.compact(&self.id2str_cf) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub struct StorageReader { |
|
|
|
|
reader: Reader, |
|
|
|
|
storage: Storage, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl StorageReader { |
|
|
|
|
pub fn len(&self) -> Result<usize> { |
|
|
|
|
Ok(self.db.len(&self.gspo_cf)? + self.db.len(&self.dspo_cf)?) |
|
|
|
|
Ok(self.reader.len(&self.storage.gspo_cf)? + self.reader.len(&self.storage.dspo_cf)?) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn is_empty(&self) -> Result<bool> { |
|
|
|
|
Ok(self.db.is_empty(&self.gspo_cf)? && self.db.is_empty(&self.dspo_cf)?) |
|
|
|
|
Ok(self.reader.is_empty(&self.storage.gspo_cf)? |
|
|
|
|
&& self.reader.is_empty(&self.storage.dspo_cf)?) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn contains(&self, quad: &EncodedQuad) -> Result<bool> { |
|
|
|
|
let mut buffer = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE); |
|
|
|
|
if quad.graph_name.is_default_graph() { |
|
|
|
|
write_spo_quad(&mut buffer, quad); |
|
|
|
|
Ok(self.db.contains_key(&self.dspo_cf, &buffer)?) |
|
|
|
|
Ok(self.reader.contains_key(&self.storage.dspo_cf, &buffer)?) |
|
|
|
|
} else { |
|
|
|
|
write_gspo_quad(&mut buffer, quad); |
|
|
|
|
Ok(self.db.contains_key(&self.gspo_cf, &buffer)?) |
|
|
|
|
Ok(self.reader.contains_key(&self.storage.gspo_cf, &buffer)?) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -543,49 +615,49 @@ impl Storage { |
|
|
|
|
|
|
|
|
|
pub fn named_graphs(&self) -> DecodingGraphIterator { |
|
|
|
|
DecodingGraphIterator { |
|
|
|
|
iter: self.db.iter(&self.graphs_cf), |
|
|
|
|
iter: self.reader.iter(&self.storage.graphs_cf), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn contains_named_graph(&self, graph_name: &EncodedTerm) -> Result<bool> { |
|
|
|
|
self.db |
|
|
|
|
.contains_key(&self.graphs_cf, &encode_term(graph_name)) |
|
|
|
|
self.reader |
|
|
|
|
.contains_key(&self.storage.graphs_cf, &encode_term(graph_name)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn spog_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.spog_cf, prefix, QuadEncoding::Spog) |
|
|
|
|
self.inner_quads(&self.storage.spog_cf, prefix, QuadEncoding::Spog) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn posg_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.posg_cf, prefix, QuadEncoding::Posg) |
|
|
|
|
self.inner_quads(&self.storage.posg_cf, prefix, QuadEncoding::Posg) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn ospg_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.ospg_cf, prefix, QuadEncoding::Ospg) |
|
|
|
|
self.inner_quads(&self.storage.ospg_cf, prefix, QuadEncoding::Ospg) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn gspo_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.gspo_cf, prefix, QuadEncoding::Gspo) |
|
|
|
|
self.inner_quads(&self.storage.gspo_cf, prefix, QuadEncoding::Gspo) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn gpos_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.gpos_cf, prefix, QuadEncoding::Gpos) |
|
|
|
|
self.inner_quads(&self.storage.gpos_cf, prefix, QuadEncoding::Gpos) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn gosp_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.gosp_cf, prefix, QuadEncoding::Gosp) |
|
|
|
|
self.inner_quads(&self.storage.gosp_cf, prefix, QuadEncoding::Gosp) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn dspo_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.dspo_cf, prefix, QuadEncoding::Dspo) |
|
|
|
|
self.inner_quads(&self.storage.dspo_cf, prefix, QuadEncoding::Dspo) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn dpos_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.dpos_cf, prefix, QuadEncoding::Dpos) |
|
|
|
|
self.inner_quads(&self.storage.dpos_cf, prefix, QuadEncoding::Dpos) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn dosp_quads(&self, prefix: &[u8]) -> DecodingQuadIterator { |
|
|
|
|
self.inner_quads(&self.dosp_cf, prefix, QuadEncoding::Dosp) |
|
|
|
|
self.inner_quads(&self.storage.dosp_cf, prefix, QuadEncoding::Dosp) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn inner_quads( |
|
|
|
@ -595,62 +667,14 @@ impl Storage { |
|
|
|
|
encoding: QuadEncoding, |
|
|
|
|
) -> DecodingQuadIterator { |
|
|
|
|
DecodingQuadIterator { |
|
|
|
|
iter: self.db.scan_prefix(column_family, prefix), |
|
|
|
|
iter: self.reader.scan_prefix(column_family, prefix), |
|
|
|
|
encoding, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn atomic_writer(&self) -> StorageWriter { |
|
|
|
|
StorageWriter { |
|
|
|
|
buffer: Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE), |
|
|
|
|
batch: self.db.new_batch(), |
|
|
|
|
storage: self.clone(), |
|
|
|
|
auto_commit: false, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn simple_writer(&self) -> StorageWriter { |
|
|
|
|
StorageWriter { |
|
|
|
|
buffer: Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE), |
|
|
|
|
batch: self.db.new_batch(), |
|
|
|
|
storage: self.clone(), |
|
|
|
|
auto_commit: true, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
pub fn flush(&self) -> Result<()> { |
|
|
|
|
self.db.flush(&self.default_cf)?; |
|
|
|
|
self.db.flush(&self.gpos_cf)?; |
|
|
|
|
self.db.flush(&self.gpos_cf)?; |
|
|
|
|
self.db.flush(&self.gosp_cf)?; |
|
|
|
|
self.db.flush(&self.spog_cf)?; |
|
|
|
|
self.db.flush(&self.posg_cf)?; |
|
|
|
|
self.db.flush(&self.ospg_cf)?; |
|
|
|
|
self.db.flush(&self.dspo_cf)?; |
|
|
|
|
self.db.flush(&self.dpos_cf)?; |
|
|
|
|
self.db.flush(&self.dosp_cf)?; |
|
|
|
|
self.db.flush(&self.id2str_cf) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))] |
|
|
|
|
pub fn compact(&self) -> Result<()> { |
|
|
|
|
self.db.compact(&self.default_cf)?; |
|
|
|
|
self.db.compact(&self.gpos_cf)?; |
|
|
|
|
self.db.compact(&self.gpos_cf)?; |
|
|
|
|
self.db.compact(&self.gosp_cf)?; |
|
|
|
|
self.db.compact(&self.spog_cf)?; |
|
|
|
|
self.db.compact(&self.posg_cf)?; |
|
|
|
|
self.db.compact(&self.ospg_cf)?; |
|
|
|
|
self.db.compact(&self.dspo_cf)?; |
|
|
|
|
self.db.compact(&self.dpos_cf)?; |
|
|
|
|
self.db.compact(&self.dosp_cf)?; |
|
|
|
|
self.db.compact(&self.id2str_cf) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn get_str(&self, key: &StrHash) -> Result<Option<String>> { |
|
|
|
|
self.db |
|
|
|
|
.get(&self.id2str_cf, &key.to_be_bytes())? |
|
|
|
|
self.reader |
|
|
|
|
.get(&self.storage.id2str_cf, &key.to_be_bytes())? |
|
|
|
|
.and_then(|v| { |
|
|
|
|
let count = i32::from_be_bytes(v[..4].try_into().unwrap()); |
|
|
|
|
if count > 0 { |
|
|
|
@ -665,8 +689,8 @@ impl Storage { |
|
|
|
|
|
|
|
|
|
pub fn contains_str(&self, key: &StrHash) -> Result<bool> { |
|
|
|
|
Ok(self |
|
|
|
|
.db |
|
|
|
|
.get(&self.id2str_cf, &key.to_be_bytes())? |
|
|
|
|
.reader |
|
|
|
|
.get(&self.storage.id2str_cf, &key.to_be_bytes())? |
|
|
|
|
.map_or(false, |v| { |
|
|
|
|
i32::from_be_bytes(v[..4].try_into().unwrap()) > 0 |
|
|
|
|
})) |
|
|
|
@ -743,7 +767,7 @@ impl Iterator for DecodingGraphIterator { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl StrLookup for Storage { |
|
|
|
|
impl StrLookup for StorageReader { |
|
|
|
|
type Error = std::io::Error; |
|
|
|
|
|
|
|
|
|
fn get_str(&self, key: &StrHash) -> Result<Option<String>> { |
|
|
|
@ -757,86 +781,100 @@ impl StrLookup for Storage { |
|
|
|
|
|
|
|
|
|
pub struct StorageWriter { |
|
|
|
|
buffer: Vec<u8>, |
|
|
|
|
batch: WriteBatchWithIndex, |
|
|
|
|
transaction: Transaction, |
|
|
|
|
storage: Storage, |
|
|
|
|
auto_commit: bool, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl StorageWriter { |
|
|
|
|
pub fn reader(&self) -> StorageReader { |
|
|
|
|
StorageReader { |
|
|
|
|
reader: self.transaction.reader(), |
|
|
|
|
storage: self.storage.clone(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn insert(&mut self, quad: QuadRef<'_>) -> Result<bool> { |
|
|
|
|
let encoded = quad.into(); |
|
|
|
|
self.buffer.clear(); |
|
|
|
|
let result = if quad.graph_name.is_default_graph() { |
|
|
|
|
write_spo_quad(&mut self.buffer, &encoded); |
|
|
|
|
if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.dspo_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.dspo_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
false |
|
|
|
|
} else { |
|
|
|
|
self.batch.insert_empty(&self.storage.dspo_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.dspo_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_pos_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.dpos_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.dpos_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_osp_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.dosp_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.dosp_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.insert_term(quad.subject.into(), &encoded.subject); |
|
|
|
|
self.insert_term(quad.predicate.into(), &encoded.predicate); |
|
|
|
|
self.insert_term(quad.object, &encoded.object); |
|
|
|
|
self.insert_term(quad.subject.into(), &encoded.subject)?; |
|
|
|
|
self.insert_term(quad.predicate.into(), &encoded.predicate)?; |
|
|
|
|
self.insert_term(quad.object, &encoded.object)?; |
|
|
|
|
true |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
write_spog_quad(&mut self.buffer, &encoded); |
|
|
|
|
if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.spog_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.spog_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
false |
|
|
|
|
} else { |
|
|
|
|
self.batch.insert_empty(&self.storage.spog_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.spog_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_posg_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.posg_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.posg_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_ospg_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.ospg_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.ospg_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gspo_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.gspo_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.gspo_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gpos_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.gpos_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.gpos_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gosp_quad(&mut self.buffer, &encoded); |
|
|
|
|
self.batch.insert_empty(&self.storage.gosp_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.gosp_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.insert_term(quad.subject.into(), &encoded.subject); |
|
|
|
|
self.insert_term(quad.predicate.into(), &encoded.predicate); |
|
|
|
|
self.insert_term(quad.object, &encoded.object); |
|
|
|
|
self.insert_term(quad.subject.into(), &encoded.subject)?; |
|
|
|
|
self.insert_term(quad.predicate.into(), &encoded.predicate)?; |
|
|
|
|
self.insert_term(quad.object, &encoded.object)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_term(&mut self.buffer, &encoded.graph_name); |
|
|
|
|
if !self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
self.batch |
|
|
|
|
.insert_empty(&self.storage.graphs_cf, &self.buffer); |
|
|
|
|
self.insert_graph_name(quad.graph_name, &encoded.graph_name); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.graphs_cf, &self.buffer)?; |
|
|
|
|
self.insert_graph_name(quad.graph_name, &encoded.graph_name)?; |
|
|
|
|
} |
|
|
|
|
true |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
self.write_if_needed()?; |
|
|
|
|
Ok(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -846,38 +884,41 @@ impl StorageWriter { |
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_term(&mut self.buffer, &encoded_graph_name); |
|
|
|
|
let result = if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
false |
|
|
|
|
} else { |
|
|
|
|
self.batch |
|
|
|
|
.insert_empty(&self.storage.graphs_cf, &self.buffer); |
|
|
|
|
self.insert_term(graph_name.into(), &encoded_graph_name); |
|
|
|
|
self.transaction |
|
|
|
|
.insert_empty(&self.storage.graphs_cf, &self.buffer)?; |
|
|
|
|
self.insert_term(graph_name.into(), &encoded_graph_name)?; |
|
|
|
|
true |
|
|
|
|
}; |
|
|
|
|
self.write_if_needed()?; |
|
|
|
|
Ok(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn insert_term(&mut self, term: TermRef<'_>, encoded: &EncodedTerm) { |
|
|
|
|
fn insert_term(&mut self, term: TermRef<'_>, encoded: &EncodedTerm) -> Result<()> { |
|
|
|
|
insert_term(term, encoded, &mut |key, value| self.insert_str(key, value)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn insert_graph_name(&mut self, graph_name: GraphNameRef<'_>, encoded: &EncodedTerm) { |
|
|
|
|
fn insert_graph_name( |
|
|
|
|
&mut self, |
|
|
|
|
graph_name: GraphNameRef<'_>, |
|
|
|
|
encoded: &EncodedTerm, |
|
|
|
|
) -> Result<()> { |
|
|
|
|
match graph_name { |
|
|
|
|
GraphNameRef::NamedNode(graph_name) => self.insert_term(graph_name.into(), encoded), |
|
|
|
|
GraphNameRef::BlankNode(graph_name) => self.insert_term(graph_name.into(), encoded), |
|
|
|
|
GraphNameRef::DefaultGraph => (), |
|
|
|
|
GraphNameRef::DefaultGraph => Ok(()), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn insert_str(&mut self, key: &StrHash, value: &str) { |
|
|
|
|
fn insert_str(&mut self, key: &StrHash, value: &str) -> Result<()> { |
|
|
|
|
self.buffer.clear(); |
|
|
|
|
self.buffer.extend_from_slice(&1_i32.to_be_bytes()); |
|
|
|
|
self.buffer.extend_from_slice(value.as_bytes()); |
|
|
|
|
self.batch |
|
|
|
|
.merge(&self.storage.id2str_cf, &key.to_be_bytes(), &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.merge(&self.storage.id2str_cf, &key.to_be_bytes(), &self.buffer) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn remove(&mut self, quad: QuadRef<'_>) -> Result<bool> { |
|
|
|
@ -890,22 +931,25 @@ impl StorageWriter { |
|
|
|
|
write_spo_quad(&mut self.buffer, quad); |
|
|
|
|
|
|
|
|
|
if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.dspo_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.dspo_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
self.batch.remove(&self.storage.dspo_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.dspo_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_pos_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.dpos_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.dpos_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_osp_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.dosp_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.dosp_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.remove_term(&quad.subject); |
|
|
|
|
self.remove_term(&quad.predicate); |
|
|
|
|
self.remove_term(&quad.object); |
|
|
|
|
self.remove_term(&quad.subject)?; |
|
|
|
|
self.remove_term(&quad.predicate)?; |
|
|
|
|
self.remove_term(&quad.object)?; |
|
|
|
|
true |
|
|
|
|
} else { |
|
|
|
|
false |
|
|
|
@ -914,59 +958,64 @@ impl StorageWriter { |
|
|
|
|
write_spog_quad(&mut self.buffer, quad); |
|
|
|
|
|
|
|
|
|
if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.spog_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.spog_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
self.batch.remove(&self.storage.spog_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.spog_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_posg_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.posg_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.posg_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_ospg_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.ospg_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.ospg_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gspo_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.gspo_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.gspo_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gpos_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.gpos_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.gpos_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_gosp_quad(&mut self.buffer, quad); |
|
|
|
|
self.batch.remove(&self.storage.gosp_cf, &self.buffer); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.gosp_cf, &self.buffer)?; |
|
|
|
|
|
|
|
|
|
self.remove_term(&quad.subject); |
|
|
|
|
self.remove_term(&quad.predicate); |
|
|
|
|
self.remove_term(&quad.object); |
|
|
|
|
self.remove_term(&quad.subject)?; |
|
|
|
|
self.remove_term(&quad.predicate)?; |
|
|
|
|
self.remove_term(&quad.object)?; |
|
|
|
|
true |
|
|
|
|
} else { |
|
|
|
|
false |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
self.write_if_needed()?; |
|
|
|
|
Ok(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn clear_graph(&mut self, graph_name: GraphNameRef<'_>) -> Result<()> { |
|
|
|
|
for quad in self.storage.quads_for_graph(&graph_name.into()) { |
|
|
|
|
for quad in self.reader().quads_for_graph(&graph_name.into()) { |
|
|
|
|
self.remove_encoded(&quad?)?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn clear_all_named_graphs(&mut self) -> Result<()> { |
|
|
|
|
for quad in self.storage.quads_in_named_graph() { |
|
|
|
|
for quad in self.reader().quads_in_named_graph() { |
|
|
|
|
self.remove_encoded(&quad?)?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn clear_all_graphs(&mut self) -> Result<()> { |
|
|
|
|
for quad in self.storage.quads() { |
|
|
|
|
for quad in self.reader().quads() { |
|
|
|
|
self.remove_encoded(&quad?)?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
@ -977,64 +1026,60 @@ impl StorageWriter { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn remove_encoded_named_graph(&mut self, graph_name: &EncodedTerm) -> Result<bool> { |
|
|
|
|
for quad in self.storage.quads_for_graph(graph_name) { |
|
|
|
|
for quad in self.reader().quads_for_graph(graph_name) { |
|
|
|
|
self.remove_encoded(&quad?)?; |
|
|
|
|
} |
|
|
|
|
self.buffer.clear(); |
|
|
|
|
write_term(&mut self.buffer, graph_name); |
|
|
|
|
let result = if self |
|
|
|
|
.batch |
|
|
|
|
.contains_key(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
.transaction |
|
|
|
|
.contains_key_for_update(&self.storage.graphs_cf, &self.buffer)? |
|
|
|
|
{ |
|
|
|
|
self.batch.remove(&self.storage.graphs_cf, &self.buffer); |
|
|
|
|
self.remove_term(graph_name); |
|
|
|
|
self.transaction |
|
|
|
|
.remove(&self.storage.graphs_cf, &self.buffer)?; |
|
|
|
|
self.remove_term(graph_name)?; |
|
|
|
|
true |
|
|
|
|
} else { |
|
|
|
|
false |
|
|
|
|
}; |
|
|
|
|
self.write_if_needed()?; |
|
|
|
|
Ok(result) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn remove_all_named_graphs(&mut self) -> Result<()> { |
|
|
|
|
for graph_name in self.storage.named_graphs() { |
|
|
|
|
for graph_name in self.reader().named_graphs() { |
|
|
|
|
self.remove_encoded_named_graph(&graph_name?)?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn clear(&mut self) -> Result<()> { |
|
|
|
|
for graph_name in self.storage.named_graphs() { |
|
|
|
|
for graph_name in self.reader().named_graphs() { |
|
|
|
|
self.remove_encoded_named_graph(&graph_name?)?; |
|
|
|
|
} |
|
|
|
|
for quad in self.storage.quads() { |
|
|
|
|
for quad in self.reader().quads() { |
|
|
|
|
self.remove_encoded(&quad?)?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn remove_term(&mut self, encoded: &EncodedTerm) { |
|
|
|
|
remove_term(encoded, &mut |key| self.remove_str(key)); |
|
|
|
|
fn remove_term(&mut self, encoded: &EncodedTerm) -> Result<()> { |
|
|
|
|
remove_term(encoded, &mut |key| self.remove_str(key)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn remove_str(&mut self, key: &StrHash) { |
|
|
|
|
self.batch.merge( |
|
|
|
|
fn remove_str(&mut self, key: &StrHash) -> Result<()> { |
|
|
|
|
self.transaction.merge( |
|
|
|
|
&self.storage.id2str_cf, |
|
|
|
|
&key.to_be_bytes(), |
|
|
|
|
&(-1_i32).to_be_bytes(), |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn write_if_needed(&mut self) -> Result<()> { |
|
|
|
|
if self.auto_commit && self.batch.len() > AUTO_WRITE_BATCH_THRESHOLD { |
|
|
|
|
self.commit()?; |
|
|
|
|
} |
|
|
|
|
Ok(()) |
|
|
|
|
pub fn commit(self) -> Result<()> { |
|
|
|
|
self.transaction.commit() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn commit(&mut self) -> Result<()> { |
|
|
|
|
self.storage.db.write(&mut self.batch)?; |
|
|
|
|
Ok(()) |
|
|
|
|
pub fn rollback(self) -> Result<()> { |
|
|
|
|
self.transaction.rollback() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1067,14 +1112,14 @@ impl BulkLoader { |
|
|
|
|
let encoded = EncodedQuad::from(quad.as_ref()); |
|
|
|
|
if quad.graph_name.is_default_graph() { |
|
|
|
|
if self.triples.insert(encoded.clone()) { |
|
|
|
|
self.insert_term(quad.subject.as_ref().into(), &encoded.subject); |
|
|
|
|
self.insert_term(quad.predicate.as_ref().into(), &encoded.predicate); |
|
|
|
|
self.insert_term(quad.object.as_ref(), &encoded.object); |
|
|
|
|
self.insert_term(quad.subject.as_ref().into(), &encoded.subject)?; |
|
|
|
|
self.insert_term(quad.predicate.as_ref().into(), &encoded.predicate)?; |
|
|
|
|
self.insert_term(quad.object.as_ref(), &encoded.object)?; |
|
|
|
|
} |
|
|
|
|
} else if self.quads.insert(encoded.clone()) { |
|
|
|
|
self.insert_term(quad.subject.as_ref().into(), &encoded.subject); |
|
|
|
|
self.insert_term(quad.predicate.as_ref().into(), &encoded.predicate); |
|
|
|
|
self.insert_term(quad.object.as_ref(), &encoded.object); |
|
|
|
|
self.insert_term(quad.subject.as_ref().into(), &encoded.subject)?; |
|
|
|
|
self.insert_term(quad.predicate.as_ref().into(), &encoded.predicate)?; |
|
|
|
|
self.insert_term(quad.object.as_ref(), &encoded.object)?; |
|
|
|
|
if self.graphs.insert(encoded.graph_name.clone()) { |
|
|
|
|
self.insert_term( |
|
|
|
|
match quad.graph_name.as_ref() { |
|
|
|
@ -1083,7 +1128,7 @@ impl BulkLoader { |
|
|
|
|
GraphNameRef::DefaultGraph => unreachable!(), |
|
|
|
|
}, |
|
|
|
|
&encoded.graph_name, |
|
|
|
|
); |
|
|
|
|
)?; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
count += 1; |
|
|
|
@ -1223,11 +1268,9 @@ impl BulkLoader { |
|
|
|
|
self.storage.db.write_stt_files(to_load) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn insert_term(&mut self, term: TermRef<'_>, encoded: &EncodedTerm) { |
|
|
|
|
insert_term( |
|
|
|
|
term, |
|
|
|
|
encoded, |
|
|
|
|
&mut |key, value| match self.id2str.entry(*key) { |
|
|
|
|
fn insert_term(&mut self, term: TermRef<'_>, encoded: &EncodedTerm) -> Result<()> { |
|
|
|
|
insert_term(term, encoded, &mut |key, value| { |
|
|
|
|
match self.id2str.entry(*key) { |
|
|
|
|
hash_map::Entry::Occupied(mut e) => { |
|
|
|
|
let e = e.get_mut(); |
|
|
|
|
e.0 = e.0.wrapping_add(1); |
|
|
|
@ -1235,8 +1278,9 @@ impl BulkLoader { |
|
|
|
|
hash_map::Entry::Vacant(e) => { |
|
|
|
|
e.insert((1, value.into())); |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
) |
|
|
|
|
}; |
|
|
|
|
Ok(()) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn build_sst_for_keys(&self, values: impl Iterator<Item = Vec<u8>>) -> Result<PathBuf> { |
|
|
|
@ -1255,6 +1299,27 @@ mod tests { |
|
|
|
|
use super::*; |
|
|
|
|
use crate::model::NamedNodeRef; |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_transaction_isolation() -> Result<()> { |
|
|
|
|
let quad = QuadRef::new( |
|
|
|
|
NamedNodeRef::new_unchecked("http://example.com/s"), |
|
|
|
|
NamedNodeRef::new_unchecked("http://example.com/p"), |
|
|
|
|
NamedNodeRef::new_unchecked("http://example.com/o"), |
|
|
|
|
NamedNodeRef::new_unchecked("http://example.com/g"), |
|
|
|
|
); |
|
|
|
|
let storage = Storage::new()?; |
|
|
|
|
let mut t1 = storage.transaction(); |
|
|
|
|
let snapshot = storage.snapshot(); |
|
|
|
|
t1.insert(quad)?; |
|
|
|
|
t1.commit()?; |
|
|
|
|
assert_eq!(snapshot.len()?, 0); |
|
|
|
|
let mut t2 = storage.transaction(); |
|
|
|
|
let mut t3 = storage.transaction(); |
|
|
|
|
t2.insert(quad)?; |
|
|
|
|
assert!(t3.remove(quad).is_err()); // Already locked
|
|
|
|
|
Ok(()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[test] |
|
|
|
|
fn test_strings_removal() -> Result<()> { |
|
|
|
|
let quad = QuadRef::new( |
|
|
|
@ -1271,37 +1336,40 @@ mod tests { |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
let storage = Storage::new()?; |
|
|
|
|
let mut writer = storage.atomic_writer(); |
|
|
|
|
let reader = storage.reader(); |
|
|
|
|
let mut writer = storage.transaction(); |
|
|
|
|
writer.insert(quad)?; |
|
|
|
|
writer.insert(quad2)?; |
|
|
|
|
writer.remove(quad2)?; |
|
|
|
|
writer.commit()?; |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/s"))? |
|
|
|
|
.is_some()); |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/p"))? |
|
|
|
|
.is_some()); |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/o2"))? |
|
|
|
|
.is_none()); |
|
|
|
|
let mut writer = storage.transaction(); |
|
|
|
|
writer.clear_graph(NamedNodeRef::new_unchecked("http://example.com/g").into())?; |
|
|
|
|
writer.commit()?; |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/s"))? |
|
|
|
|
.is_none()); |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/p"))? |
|
|
|
|
.is_none()); |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/o"))? |
|
|
|
|
.is_none()); |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/g"))? |
|
|
|
|
.is_some()); |
|
|
|
|
let mut writer = storage.transaction(); |
|
|
|
|
writer.remove_named_graph(NamedNodeRef::new_unchecked("http://example.com/g").into())?; |
|
|
|
|
writer.commit()?; |
|
|
|
|
assert!(storage |
|
|
|
|
assert!(reader |
|
|
|
|
.get_str(&StrHash::new("http://example.com/g"))? |
|
|
|
|
.is_none()); |
|
|
|
|
Ok(()) |
|
|
|
|