Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/content/docs/guides/websocket-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ Writes of facts are idempotent: inserting a fact that is present, or deleting on
}
```

The reply to a program that changed persistent state of a knowledge graph also carries `revision`: the knowledge graph revision the program's changes are visible at, the same revision [standing-query pushes](#standing-queries) carry. A delta at that revision or later reflects them. The statements that carry it are committed fact, schema and rule writes, `.rel drop`, `.clear prefix`, `.index create`, `.index drop`, `.index rebuild`, `.kg create` (the new graph's first revision) and `.ontology install`, `remove` and `upgrade`; a program with several names the last one's. A write that changed nothing (inserting a fact that is present, for example) names the current revision, which already holds its effect. Queries without writes, programs whose writes were rolled back, `.kg use`, `.kg drop`, and session-scoped schemas, facts and rules carry no `revision`. Like push revisions, it is comparable only within one knowledge graph and one `stream_epoch`.

```json
{"type": "result", "columns": ["message"], "rows": [["Inserted 1 fact(s) into 'eta'."]], "revision": 1731, "errors": []}
```

### Error Response

```json
Expand Down
12 changes: 12 additions & 0 deletions docs/spec/asyncapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,15 @@ components:
Fact statements the program committed, with their counts, in
program order. Absent when the program committed no fact statement
(a read, a failed program, or only schema and rule changes).
revision:
type: integer
format: int64
description: |
Knowledge graph revision the program's committed writes are visible at,
the revision pushes carry: a delta at this revision or later reflects them.
Present only when the program changed persistent state of a knowledge graph;
see the WebSocket API guide for which statements carry it. Comparable only
within one knowledge graph and stream epoch.
subscribed:
$ref: '#/components/schemas/Subscribed'
description: Set on the reply to `.subscribe`, whose `rows` are the snapshot
Expand Down Expand Up @@ -1348,6 +1357,9 @@ components:
type: array
items:
$ref: '#/components/schemas/StatementCounts'
revision:
type: integer
format: int64
subscribed:
$ref: '#/components/schemas/Subscribed'
examples:
Expand Down
2 changes: 1 addition & 1 deletion gui/lib/docs-bundle.ts

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/bin/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ impl WsClient {
timing_breakdown: start.timing_breakdown,
errors: start.errors,
statements: start.statements,
revision: start.revision,
subscribed: None,
});
}
Expand Down
118 changes: 79 additions & 39 deletions src/protocol/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,39 +235,38 @@ mod index_commands {
storage: &StorageEngine,
kg: &str,
opts: &IndexCreateOptions,
) -> Result<String, ProgramError> {
let stats = storage
) -> Result<(String, u64), ProgramError> {
let (stats, revision) = storage
.create_index_in(kg, opts)
.map_err(ProgramError::from)?;
Ok(format!(
let message = format!(
"Index '{}' created on {}.{} ({} vectors).",
stats.name, stats.relation, stats.column, stats.tuple_count
))
);
Ok((message, revision))
}

pub(super) fn drop(
storage: &StorageEngine,
kg: &str,
name: &str,
) -> Result<String, ProgramError> {
storage
) -> Result<(String, u64), ProgramError> {
let revision = storage
.drop_index_in(kg, name)
.map_err(ProgramError::from)?;
Ok(format!("Index '{name}' dropped."))
Ok((format!("Index '{name}' dropped."), revision))
}

pub(super) fn rebuild(
storage: &StorageEngine,
kg: &str,
name: &str,
) -> Result<String, ProgramError> {
let stats = storage
) -> Result<(String, u64), ProgramError> {
let (stats, revision) = storage
.rebuild_index_in(kg, name)
.map_err(ProgramError::from)?;
Ok(format!(
"Index '{name}' rebuilt ({} vectors).",
stats.tuple_count
))
let message = format!("Index '{name}' rebuilt ({} vectors).", stats.tuple_count);
Ok((message, revision))
}

pub(super) fn stats(
Expand Down Expand Up @@ -862,6 +861,7 @@ impl ProofSnapshot {
),
errors: Vec::new(),
statements: Vec::new(),
revision: None,
})
}

Expand Down Expand Up @@ -914,6 +914,7 @@ impl ProofSnapshot {
timing_breakdown: proof_timing(timing_mode, start, query_us, "explanation", explain_us),
errors: Vec::new(),
statements: Vec::new(),
revision: None,
})
}
}
Expand Down Expand Up @@ -1655,6 +1656,7 @@ impl Handler {
timing_breakdown: None,
errors: Vec::new(),
statements: Vec::new(),
revision: None,
})
}

Expand Down Expand Up @@ -2249,6 +2251,7 @@ impl Handler {
let storage = self.storage.read();
storage
.clear_relations_by_prefix_in(kg, prefix)
.map(|(cleared, _)| cleared)
.map_err(ProgramError::from)
}

Expand All @@ -2272,12 +2275,12 @@ impl Handler {
kg: &str,
opts: &IndexCreateOptions,
) -> Result<String, ProgramError> {
index_commands::create(&self.storage.read(), kg, opts)
index_commands::create(&self.storage.read(), kg, opts).map(|(message, _)| message)
}

/// Drop an index.
pub fn drop_index(&self, kg: &str, name: &str) -> Result<String, ProgramError> {
index_commands::drop(&self.storage.read(), kg, name)
index_commands::drop(&self.storage.read(), kg, name).map(|(message, _)| message)
}

/// List all indexes of a knowledge graph.
Expand All @@ -2292,7 +2295,7 @@ impl Handler {

/// Rebuild an index from base data, dropping tombstones.
pub fn rebuild_index(&self, kg: &str, name: &str) -> Result<String, ProgramError> {
index_commands::rebuild(&self.storage.read(), kg, name)
index_commands::rebuild(&self.storage.read(), kg, name).map(|(message, _)| message)
}

/// Execute an IQL program and return results.
Expand Down Expand Up @@ -2492,6 +2495,9 @@ impl QueryJob {
let mut session_rules_parsed: Vec<crate::ast::Rule> = Vec::new();
let mut errors: Vec<StatementError> = Vec::new();
let mut stmt_index: usize;
// The revision the program's last write to persistent state committed
// at, if it made one.
let mut revision = None;
// Records a failure of the current statement and reports it as a
// message row too.
macro_rules! fail {
Expand Down Expand Up @@ -2525,7 +2531,13 @@ impl QueryJob {
#[cfg(test)]
test_hook::run(test_hook::Point::ProofSearch);
match snapshot.explain(proof, timing_mode) {
Ok(qr) => return Ok(QueryResult { errors, ..qr }),
Ok(qr) => {
return Ok(QueryResult {
errors,
revision,
..qr
})
}
Err(e) => {
storage = self.storage.read();
fail!(
Expand Down Expand Up @@ -2660,9 +2672,10 @@ impl QueryJob {
}
MetaCommand::KgCreate(name) => {
info!(kg = %name, "meta_kg_create_start");
match storage.create_knowledge_graph(&name) {
Ok(()) => {
match storage.create_knowledge_graph_at(&name) {
Ok(created) => {
info!(kg = %name, "meta_kg_create_ok");
revision = Some(created);
self.notify_kg_change(&name, "created");
messages.push(format!(
"Knowledge graph '{name}' created."
Expand Down Expand Up @@ -2813,7 +2826,8 @@ impl QueryJob {

MetaCommand::RelDrop(name) => {
match storage.drop_relation_in(kg, &name) {
Ok(()) => {
Ok(published) => {
revision = Some(published);
self.notify_schema_change(kg, &name, "dropped");
messages
.push(format!("Relation '{name}' dropped."));
Expand Down Expand Up @@ -2900,7 +2914,8 @@ impl QueryJob {
// === Clear commands ===
MetaCommand::ClearPrefix(prefix) => {
match storage.clear_relations_by_prefix_in(kg, &prefix) {
Ok(cleared) => {
Ok((cleared, published)) => {
revision = Some(published);
if cleared.is_empty() {
messages.push(format!(
"No relations matching prefix '{prefix}'."
Expand Down Expand Up @@ -3047,7 +3062,8 @@ impl QueryJob {
MetaCommand::IndexCreate(opts) => {
info!(index = %opts.name, "meta_index_create_start");
match index_commands::create(&storage, kg, &opts) {
Ok(msg) => {
Ok((msg, published)) => {
revision = Some(published);
info!(index = %opts.name, "meta_index_create_ok");
messages.push(msg);
}
Expand Down Expand Up @@ -3075,7 +3091,8 @@ impl QueryJob {
MetaCommand::IndexDrop(name) => {
info!(index = %name, "meta_index_drop_start");
match index_commands::drop(&storage, kg, &name) {
Ok(msg) => {
Ok((msg, published)) => {
revision = Some(published);
info!(index = %name, "meta_index_drop_ok");
messages.push(msg);
}
Expand Down Expand Up @@ -3155,7 +3172,10 @@ impl QueryJob {
}
MetaCommand::IndexRebuild(name) => {
match index_commands::rebuild(&storage, kg, &name) {
Ok(msg) => messages.push(msg),
Ok((msg, published)) => {
revision = Some(published);
messages.push(msg);
}
Err(e) => fail!(
e.code
.filter(|code| matches!(
Expand Down Expand Up @@ -3264,9 +3284,10 @@ impl QueryJob {
if !write_run.is_empty() {
if errors.is_empty() {
match self.commit_write_run(&storage, &kg_name, &mut write_run, &mut messages) {
Ok((base, counts)) => {
Ok((base, counts, committed_at)) => {
committed = Some(base);
statement_counts = counts;
revision = committed_at.or(revision);
}
Err(failure) => {
stmt_index = failure.index;
Expand Down Expand Up @@ -3326,6 +3347,7 @@ impl QueryJob {
errors,
statements: statement_counts,
execution_time_ms: start.elapsed().as_millis() as u64,
revision,
..Handler::messages_result(messages)
});
}
Expand All @@ -3346,6 +3368,7 @@ impl QueryJob {
errors,
statements: statement_counts,
execution_time_ms: start.elapsed().as_millis() as u64,
revision,
..Handler::messages_result(messages)
});
}
Expand All @@ -3368,6 +3391,7 @@ impl QueryJob {
errors,
statements: statement_counts,
execution_time_ms: start.elapsed().as_millis() as u64,
revision,
..Handler::messages_result(messages)
});
}
Expand Down Expand Up @@ -3569,6 +3593,7 @@ impl QueryJob {
timing_breakdown,
errors,
statements: statement_counts,
revision,
})
}
}
Expand Down Expand Up @@ -3858,6 +3883,7 @@ impl Handler {
timing_breakdown,
errors: Vec::new(),
statements: Vec::new(),
revision: None,
})
}

Expand Down Expand Up @@ -4543,6 +4569,7 @@ impl Handler {
timing_breakdown: None,
errors: Vec::new(),
statements: Vec::new(),
revision: None,
}
}

Expand Down Expand Up @@ -4636,6 +4663,7 @@ impl Handler {
timing_breakdown: None,
errors: Vec::new(),
statements: Vec::new(),
revision: None,
}
}

Expand Down Expand Up @@ -4973,18 +5001,21 @@ impl Handler {
.into());
}

Ok(Self::messages_result(vec![
format!(
"installed {}@{} into {kg} ({statement_count} statements)",
name, entry.version
),
format!("digest {}", entry.digest),
format!(
"recorded {} rule(s), {} relation(s) in pack_item",
items.iter().filter(|(k, _)| k == "rule").count(),
items.iter().filter(|(k, _)| k == "relation").count()
),
]))
Ok(QueryResult {
revision: recorded.revision,
..Self::messages_result(vec![
format!(
"installed {}@{} into {kg} ({statement_count} statements)",
name, entry.version
),
format!("digest {}", entry.digest),
format!(
"recorded {} rule(s), {} relation(s) in pack_item",
items.iter().filter(|(k, _)| k == "rule").count(),
items.iter().filter(|(k, _)| k == "relation").count()
),
])
})
}

/// `.ontology remove <name>`: drop the pack's recorded rules and
Expand Down Expand Up @@ -5052,6 +5083,7 @@ impl Handler {
// Failed drops are caught by the read-back below. Surface every
// sub-result row: drops that fail phrase their errors in many ways,
// and silence here would misreport a partial removal.
let mut revision = None;
for program in programs {
let result = Box::pin(self.run_execute_program(
session_id,
Expand All @@ -5062,6 +5094,7 @@ impl Handler {
))
.await?;
Self::result_problem_rows(&result)?;
revision = result.revision.or(revision);
for row in &result.rows {
if let Some(WireValue::String(s)) = row.values.first() {
messages.push(format!(" {s}"));
Expand Down Expand Up @@ -5119,7 +5152,10 @@ impl Handler {
retained.len()
));
}
Ok(Self::messages_result(messages))
Ok(QueryResult {
revision: cleanup.revision.or(revision),
..Self::messages_result(messages)
})
}

/// `.ontology upgrade <name[@version]>`: re-deploy the pack's rules at
Expand Down Expand Up @@ -5207,7 +5243,10 @@ impl Handler {
messages.push(s.clone());
}
}
Ok(Self::messages_result(messages))
Ok(QueryResult {
revision: install.revision,
..Self::messages_result(messages)
})
}

/// Handle `.session` list command
Expand Down Expand Up @@ -5265,6 +5304,7 @@ impl Handler {
timing_breakdown: None,
errors: Vec::new(),
statements: Vec::new(),
revision: None,
})
}

Expand Down
Loading
Loading