///|
/// Require an active transaction before inspecting or mutating its candidate.
fn WriteTransaction::require_active(
self : WriteTransaction,
) -> Unit raise DatabaseError {
match self.status.val {
Active => ()
Failed => raise DatabaseError::TransactionFailed
Committing => raise DatabaseError::TransactionCommitInProgress
Committed => raise DatabaseError::TransactionAlreadyCommitted
RolledBack => raise DatabaseError::TransactionAlreadyRolledBack
}
}
///|
/// Persist and publish the detached candidate as one atomic transaction.
pub async fn WriteTransaction::commit(
self : WriteTransaction,
) -> Unit raise DatabaseError {
self.require_active()
let state = self.database.val
if state.closed {
raise DatabaseError::DatabaseClosed
}
if state.recovery_required {
raise DatabaseError::DatabaseRecoveryRequired
}
if state.transaction_id == 18446744073709551615UL {
raise DatabaseError::TransactionIdExhausted
}
let transaction_id = state.transaction_id + 1UL
let record = WalRecord::{
transaction_id,
operations: wal_encode_transaction_operations(self.operations),
}
self.status.update(fn(_) { Committing })
errdefer (if self.status.val is Committing {
state.recovery_required = true
state.active = None
self.status.update(fn(_) { Failed })
})
match state.wal_path {
Some(path) =>
wal_append_record(path, record) catch {
error =>
match error {
DatabaseError::IoError(operation="open", ..) => {
self.status.update(fn(_) { Active })
raise error
}
DatabaseError::CorruptLogRecord(..) => {
self.status.update(fn(_) { Active })
raise error
}
_ => {
state.recovery_required = true
state.active = None
self.status.update(fn(_) { Failed })
raise DatabaseError::CommitOutcomeUnknown
}
}
}
None => ()
}
let candidate = self.candidate
state.committed = candidate
state.transaction_id = transaction_id
state.active = None
self.status.update(fn(_) { Committed })
}
///|
/// Discard the detached candidate and release the database writer slot.
/// Failed transactions may only be recovered through rollback.
pub fn WriteTransaction::rollback(
self : WriteTransaction,
) -> Unit raise DatabaseError {
match self.status.val {
Active | Failed => ()
Committing => raise DatabaseError::TransactionCommitInProgress
Committed => raise DatabaseError::TransactionAlreadyCommitted
RolledBack => raise DatabaseError::TransactionAlreadyRolledBack
}
self.database.update(fn(state) {
state.active = None
state
})
self.status.update(fn(_) { RolledBack })
}
///|
/// Create a node in the detached candidate graph.
pub fn WriteTransaction::create_node(
self : WriteTransaction,
labels : Array[String],
properties : Properties,
) -> NodeId raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
let id = self.candidate.create_node(labels, properties)
let node = self.candidate.get_node(id)
self.operations.push(TransactionOperation::CreateNode(id, node))
id
}
///|
/// Read a detached node from the candidate graph.
pub fn WriteTransaction::get_node(
self : WriteTransaction,
id : NodeId,
) -> Node raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.get_node(id)
}
///|
/// Return candidate node IDs carrying a label.
pub fn WriteTransaction::node_ids_with_label(
self : WriteTransaction,
label : String,
) -> Array[NodeId] raise DatabaseError {
self.require_active()
self.candidate.node_ids_with_label(label)
}
///|
/// Add a label to a candidate node.
pub fn WriteTransaction::add_label(
self : WriteTransaction,
id : NodeId,
label : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.add_label(id, label)
self.operations.push(TransactionOperation::AddNodeLabel(id, label))
}
///|
/// Remove a label from a candidate node.
pub fn WriteTransaction::remove_label(
self : WriteTransaction,
id : NodeId,
label : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.remove_label(id, label)
self.operations.push(TransactionOperation::RemoveNodeLabel(id, label))
}
///|
/// Set or replace a candidate node property.
pub fn WriteTransaction::set_node_property(
self : WriteTransaction,
id : NodeId,
key : String,
value : PropertyValue,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.set_node_property(id, key, value)
let stored_value = normalize_property_value(key, value)
self.operations.push(
TransactionOperation::SetNodeProperty(id, key, stored_value),
)
}
///|
/// Remove a candidate node property.
pub fn WriteTransaction::remove_node_property(
self : WriteTransaction,
id : NodeId,
key : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.remove_node_property(id, key)
self.operations.push(TransactionOperation::RemoveNodeProperty(id, key))
}
///|
/// Create a directed edge in the detached candidate graph.
pub fn WriteTransaction::create_edge(
self : WriteTransaction,
from : NodeId,
to : NodeId,
edge_type : String,
properties : Properties,
) -> EdgeId raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
let id = self.candidate.create_edge(from, to, edge_type, properties)
let edge = self.candidate.get_edge(id)
self.operations.push(TransactionOperation::CreateEdge(edge))
id
}
///|
/// Read a detached edge from the candidate graph.
pub fn WriteTransaction::get_edge(
self : WriteTransaction,
id : EdgeId,
) -> Edge raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.get_edge(id)
}
///|
/// Return detached candidate edges leaving a node.
pub fn WriteTransaction::outgoing_edges(
self : WriteTransaction,
node_id : NodeId,
) -> Array[Edge] raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.outgoing_edges(node_id)
}
///|
/// Return detached candidate edges entering a node.
pub fn WriteTransaction::incoming_edges(
self : WriteTransaction,
node_id : NodeId,
) -> Array[Edge] raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.incoming_edges(node_id)
}
///|
/// Return candidate edge IDs carrying an edge type.
pub fn WriteTransaction::edge_ids_with_type(
self : WriteTransaction,
edge_type : String,
) -> Array[EdgeId] raise DatabaseError {
self.require_active()
self.candidate.edge_ids_with_type(edge_type)
}
///|
/// Set or replace a candidate edge property.
pub fn WriteTransaction::set_edge_property(
self : WriteTransaction,
id : EdgeId,
key : String,
value : PropertyValue,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.set_edge_property(id, key, value)
let stored_value = normalize_property_value(key, value)
self.operations.push(
TransactionOperation::SetEdgeProperty(id, key, stored_value),
)
}
///|
/// Remove a candidate edge property.
pub fn WriteTransaction::remove_edge_property(
self : WriteTransaction,
id : EdgeId,
key : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.remove_edge_property(id, key)
self.operations.push(TransactionOperation::RemoveEdgeProperty(id, key))
}
///|
/// Delete a candidate edge.
pub fn WriteTransaction::delete_edge(
self : WriteTransaction,
id : EdgeId,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.delete_edge(id)
self.operations.push(TransactionOperation::DeleteEdge(id))
}
///|
/// Delete a candidate node only when it has no incident edges.
pub fn WriteTransaction::delete_node(
self : WriteTransaction,
id : NodeId,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.delete_node(id)
self.operations.push(TransactionOperation::DeleteNode(id))
}
///|
/// Delete a candidate node and its incident edges.
pub fn WriteTransaction::delete_node_cascade(
self : WriteTransaction,
id : NodeId,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.delete_node_cascade(id)
self.operations.push(TransactionOperation::DeleteNodeCascade(id))
}
///|
/// Define a candidate node-property equality index.
pub fn WriteTransaction::create_node_property_index(
self : WriteTransaction,
label : String,
property : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.create_node_property_index(label, property)
self.operations.push(
TransactionOperation::CreateNodePropertyIndex(label, property),
)
}
///|
/// Return IDs from a candidate node-property equality index.
pub fn WriteTransaction::node_ids_by_property_index(
self : WriteTransaction,
label : String,
property : String,
value : PropertyValue,
) -> Array[NodeId]? raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.node_ids_by_property_index(label, property, value)
}
///|
/// Define a candidate edge-property equality index.
pub fn WriteTransaction::create_edge_property_index(
self : WriteTransaction,
edge_type : String,
property : String,
) -> Unit raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.create_edge_property_index(edge_type, property)
self.operations.push(
TransactionOperation::CreateEdgePropertyIndex(edge_type, property),
)
}
///|
/// Return IDs from a candidate edge-property equality index.
pub fn WriteTransaction::edge_ids_by_property_index(
self : WriteTransaction,
edge_type : String,
property : String,
value : PropertyValue,
) -> Array[EdgeId]? raise DatabaseError {
self.require_active()
errdefer self.status.update(fn(_) { Failed })
self.candidate.edge_ids_by_property_index(edge_type, property, value)
}