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
2 changes: 1 addition & 1 deletion docs/concepts/datasets-and-named-graphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ Two CLI commands cover the rest of the lifecycle:
- **[`fluree graph list`](../cli/graph.md#fluree-graph-list)** — lists user graphs registered on a branch (with `--include-system` to also show the default and system graphs). Reads the `named-graphs` section of the standard `/info` response.
- **[`fluree graph drop`](../cli/graph.md#fluree-graph-drop)** — transactionally retracts every triple currently asserted under a named graph. Produces one new commit at `t + 1` whose flakes are all retractions; history at older `t` values is preserved, and the graph IRI keeps its `g_id` so future inserts land in the same slot. Drops are per-branch.

The default graph, `urn:fluree:{ledger_id}#txn-meta`, and `urn:fluree:{ledger_id}#config` cannot be dropped. The Rust API entry point is `Fluree::drop_named_graph(ledger_id, graph_iri)`; over HTTP it is `POST /v1/fluree/drop-graph` (admin-protected). See the [server-integration contract](../cli/server-integration.md#drop-named-graph-contract) for the wire details.
The default graph, `urn:fluree:{ledger_id}#txn-meta`, and `urn:fluree:{ledger_id}#config` cannot be dropped. The Rust API entry point is `Fluree::drop_named_graph(ledger_id, graph_iri)`, whose report carries the drop commit's `commit_id` when it committed; over HTTP it is `POST /v1/fluree/drop-graph` (admin-protected). See the [server-integration contract](../cli/server-integration.md#drop-named-graph-contract) for the wire details.

### Graph Metadata

Expand Down
39 changes: 39 additions & 0 deletions docs/getting-started/rust-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,44 @@ async fn main() -> Result<()> {
}
```

#### Parameters

`params` binds variables to values: each named variable (`?name` or `$name`,
the same variable) is replaced by its value wherever it appears — subqueries,
`OPTIONAL`, `FILTER`s and update templates included — before the query is
planned, so it runs exactly as if the value had been written inline. Values
take the JSON-LD forms: a JSON string, number or boolean; `{"@id": iri}`;
`{"@value": v, "@type": datatype}` (`"@type": "@id"` reads `v` as an IRI);
`{"@value": s, "@language": tag}`; and an embedding vector as its numbers,
`{"@value": [0.1, 0.2], "@type": "https://ns.flur.ee/db#embeddingVector"}`.
IRIs are full IRIs — neither the query's `PREFIX`es nor a `@context` apply. A
blank node is the `_:fdb-…` id a query returned for a stored node; any other
label is refused, since written inline it would be a variable and match every
node.

```rust
let params = json!({ "name": "Alice", "min": 21 });
let result = fluree.graph("mydb:main")
.query()
.sparql("PREFIX schema: <http://schema.org/>
SELECT ?person WHERE { ?person schema:name $name ; schema:age ?age FILTER(?age > $min) }")
.params(params.as_object().unwrap().clone())
.execute_formatted()
.await?;
```

A projected parameter stays a column holding its value. A parameter the query
never mentions is an error — its misspelt variable would otherwise be unbound
and match everything — as is one the query assigns itself (`BIND`, `VALUES`,
`AS`), one used inside a remote `SERVICE` (whose body is sent as written), and
any parameter on a JSON-LD query. Without parameters nothing changes: the
query is not rewritten at all.

The same parameters go on `QueryExecutionOptions::with_params`,
`Fluree::explain_sparql_with_params`, a transact builder's
`sparql_update_with_params(sparql, &params)`, and
`TxnOperation::SparqlUpdate(sparql, Some(params))`.

### Streaming Query Results (NDJSON)

The buffered `.query()` paths above collect the whole result set into a
Expand Down Expand Up @@ -695,6 +733,7 @@ async fn main() -> Result<()> {
DELETE { ex:alice ex:age ?a } INSERT { ex:alice ex:age ?b } \
WHERE { ex:alice ex:age ?a BIND(?a + 1 AS ?b) }"
.into(),
None, // parameters
))
.await?;

Expand Down
14 changes: 14 additions & 0 deletions docs/guides/cookbook-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ fluree query '{
}'
```

The same search in SPARQL:

```bash
fluree query '
PREFIX ex: <http://example.org/>
SELECT ?title ?score WHERE {
?doc a ex:Article ; ex:body ?body ; ex:title ?title .
BIND(fulltext(?body, "graph database relationships") AS ?score)
FILTER(?score > 0)
}
ORDER BY DESC(?score)
LIMIT 10'
```

The `fulltext()` function returns a BM25 relevance score. Higher scores mean better matches. Documents with none of the search terms score 0.

### 3. Combine search with graph filters
Expand Down
11 changes: 11 additions & 0 deletions docs/indexing-and-search/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ For small-to-medium corpora (up to hundreds of thousands of documents per predic
}
```

**Or in SPARQL:**
```sparql
SELECT ?title ?score WHERE {
?doc ex:content ?content ; ex:title ?title .
BIND(fulltext(?content, "Rust programming") AS ?score)
FILTER(?score > 0)
}
ORDER BY DESC(?score)
LIMIT 10
```

See [Inline Fulltext Search](fulltext.md) for details.

## Full-Text Search (BM25 Graph Source)
Expand Down
11 changes: 5 additions & 6 deletions docs/indexing-and-search/fulltext.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ Each language becomes its own arena; queries automatically look up the right one

## The `fulltext()` Scoring Function

The `fulltext()` function computes a BM25 relevance score for a bound text value against a query string. Use it in `bind` expressions within JSON-LD queries.
The `fulltext()` function computes a BM25 relevance score for a bound text value against a query string. Use it in `bind` expressions in JSON-LD queries, or in `BIND` in SPARQL (see [SPARQL Support](#sparql-support)).

### Basic usage

Expand Down Expand Up @@ -698,29 +698,28 @@ Expected results (ordered by relevance):

### Inserting data

Fulltext annotation works in SPARQL UPDATE today using the `^^f:fullText` typed literal syntax (see the Turtle/SPARQL insertion examples above).
Annotate values in SPARQL UPDATE (and Turtle) with the `^^f:fullText` typed literal, as in the insertion examples above, or configure the property with [`f:fullTextDefaults`](#configured-full-text-properties-ffulltextdefaults) and insert plain strings.

### Querying

The `fulltext()` scoring function is currently available in **JSON-LD Query only**. SPARQL query support is planned for a future release, with anticipated syntax like:
`fulltext()` is a SPARQL function, used in `BIND` like the vector similarity functions:

```sparql
PREFIX ex: <http://example.org/>
PREFIX f: <https://ns.flur.ee/db#>

SELECT ?title ?score
WHERE {
?doc a ex:Article ;
ex:content ?content ;
ex:title ?title .
BIND(f:fulltext(?content, "Rust programming") AS ?score)
BIND(fulltext(?content, "Rust programming") AS ?score)
FILTER(?score > 0)
}
ORDER BY DESC(?score)
LIMIT 10
```

This mirrors the pattern established by inline vector similarity functions (`dotProduct`, `cosineSimilarity`, `euclideanDistance`), which also support JSON-LD Query today with SPARQL planned.
It scores exactly as the JSON-LD form does, over indexed and unindexed documents alike, for both `@fulltext` values and `f:fullTextDefaults` properties. The name is case-insensitive (`fulltext`, `FULLTEXT`, `full_text`), and the namespaced `f:fulltext(...)` (`<https://ns.flur.ee/db#fulltext>`) is the same function.

## Related Documentation

Expand Down
4 changes: 2 additions & 2 deletions docs/indexing-and-search/vector-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ Or with the full IRI:
]
```

## Inline Similarity Functions (JSON-LD Query)
## Inline Similarity Functions

Fluree provides three vector similarity functions that can be used in `bind` expressions within JSON-LD queries. These compute similarity scores directly during query execution without requiring a pre-built index.
Fluree provides three vector similarity functions for `bind` expressions in JSON-LD queries and `BIND` in SPARQL ([SPARQL support](#sparql-support)). These compute similarity scores directly during query execution without requiring a pre-built index.

Function names are case-insensitive; `dotProduct`, `dotproduct`, and `dot_product` are all equivalent.

Expand Down
47 changes: 47 additions & 0 deletions docs/query/sparql.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,26 @@ WHERE {
}
```

### Parameters

When Fluree is embedded (the Rust API and the Python package), a query or
update can take parameters: values bound to variables by name. A parameter
replaces its variable — written `?name` or `$name`, which SPARQL treats as the
same variable — everywhere in the request before it is planned, so
`SELECT ?person WHERE { ?person ex:name $name }` with `name = "Alice"` runs
exactly as `SELECT ?person WHERE { ?person ex:name "Alice" }`. Unlike a
`VALUES` block, the value is visible inside every `FILTER`, `OPTIONAL` and
subquery.

- A projected parameter (`SELECT $name ?age`) is still a column, holding the
value; `SELECT *` does not list it.
- A parameter that the request never mentions is an error, since a misspelt
variable would otherwise be unbound and match everything. So is a parameter
the request assigns itself (`BIND`, `VALUES`, `AS`), and one used inside a
remote `SERVICE`, whose body is sent to the endpoint as written.
- `INSERT DATA` and `DELETE DATA` take no variables, so no parameters; use
`INSERT { ... } WHERE { ... }`.

## Aggregation

### GROUP BY
Expand Down Expand Up @@ -755,6 +775,33 @@ Per W3C SPARQL 1.1 §17.5, XSD constructor functions cast values between datatyp
- `ISLITERAL(?x)` - Is literal
- `ISNUMERIC(?x)` - Is numeric

### Search and Similarity Functions

Fluree extensions that score values for ranking, used in `BIND`:

- `fulltext(?text, "query")` - BM25 relevance of a full-text value to a query string; `0` when no query term occurs. Also written `f:fulltext(...)`. See [Inline Fulltext Search](../indexing-and-search/fulltext.md).
- `dotProduct(?v1, ?v2)` - Dot product of two vectors
- `cosineSimilarity(?v1, ?v2)` - Cosine similarity of two vectors
- `euclideanDistance(?v1, ?v2)` - Euclidean (L2) distance between two vectors

Vectors are `f:embeddingVector` literals (`"[0.1, 0.2]"^^f:embeddingVector`); see [Vector Search](../indexing-and-search/vector-search.md). Names are case-insensitive and accept underscores (`cosine_similarity`).

```sparql
PREFIX ex: <http://example.org/>
PREFIX f: <https://ns.flur.ee/db#>

SELECT ?doc ?text ?similar WHERE {
VALUES ?q { "[0.88, 0.12, 0.08]"^^f:embeddingVector }
?doc ex:body ?body ;
ex:embedding ?vec .
BIND(fulltext(?body, "graph databases") AS ?text)
BIND(cosineSimilarity(?vec, ?q) AS ?similar)
FILTER(?text > 0 || ?similar > 0.8)
}
ORDER BY DESC(0.6 * ?text + 0.4 * ?similar)
LIMIT 10
```

## Subqueries

Nest queries:
Expand Down
4 changes: 3 additions & 1 deletion docs/transactions/sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,9 @@ let report = fluree
assert!(report.committed || (report.asserted == 0 && report.retracted == 0));
```

`SyncGraphOpts { dry_run, allow_empty }` mirror the query parameters;
`SyncGraphOpts { dry_run, allow_empty }` mirror the query parameters, and
`message` (Rust API only) is recorded as the commit's `f:message`. When the
sync committed, the report's `commit_id` names the new commit.
`sync_named_graph_with` additionally takes explicit `TxnOpts` and a
`PolicyContext`, and `sync_named_graph_rdf_with` is the same call for
Turtle / N-Triples / TriG text. `sync_graph_with(ledger, &GraphSel, GraphPayload, …)`
Expand Down
20 changes: 19 additions & 1 deletion fluree-db-api/src/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ pub struct DropNamedGraphReport {
/// Current commit `t` for the branch after the drop. Equal to the
/// pre-drop `t` when `committed = false`.
pub t: i64,
/// The drop commit's id; `None` when `committed = false`.
pub commit_id: Option<ContentId>,
}

/// Options for [`Fluree::sync_named_graph`].
Expand All @@ -153,6 +155,8 @@ pub struct SyncGraphOpts {
/// graph. Off by default so a truncated or accidentally-empty export
/// cannot silently wipe the graph.
pub allow_empty: bool,
/// Recorded as the commit's `f:message`.
pub message: Option<String>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Optional / nit — the sync docs don't mention the new option or the commit ids.

docs/transactions/sync.md:126 still says "SyncGraphOpts { dry_run, allow_empty } mirror the query parameters", and nothing in the sync or drop docs mentions message or the new commit_id on SyncGraphReport/DropNamedGraphReport. A sentence there (that message is recorded as the commit's f:message, Rust API only for now) would keep the reference honest. Minor; fold-in close as above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 976d58b

}

/// Report of a [`Fluree::sync_named_graph`] call.
Expand All @@ -176,6 +180,8 @@ pub struct SyncGraphReport {
/// Whether a new commit was created. `false` when the payload matched
/// the graph exactly, and always `false` for a dry run.
pub committed: bool,
/// The new commit, when `committed`.
pub commit_id: Option<fluree_db_core::ContentId>,
/// Whether this was a dry run (staged and counted, nothing committed).
pub dry_run: bool,
/// Current commit `t` for the branch after the call. Equal to the
Expand Down Expand Up @@ -1025,6 +1031,7 @@ impl crate::Fluree {
retracted,
committed,
t: new_t,
commit_id: committed.then(|| result.receipt.commit_id.clone()),
})
}

Expand Down Expand Up @@ -1167,15 +1174,25 @@ impl crate::Fluree {
asserted,
retracted,
committed: false,
commit_id: None,
dry_run: true,
t: staged_against_t,
});
}

let mut commit_opts = fluree_db_transact::CommitOpts::default();
if let Some(message) = opts.message {
commit_opts = commit_opts.with_txn_meta(vec![fluree_db_core::TxnMetaEntry::new(
fluree_vocab::namespaces::FLUREE_DB,
"message",
fluree_db_core::TxnMetaValue::string(message),
)]);
}
let mut builder = self
.stage(&handle)
.sync_graph_payload(graph.clone(), payload, opts.allow_empty)
.txn_opts(txn_opts);
.txn_opts(txn_opts)
.commit_opts(commit_opts);
if let Some(policy) = policy {
builder = builder.policy(policy);
}
Expand Down Expand Up @@ -1209,6 +1226,7 @@ impl crate::Fluree {
asserted: result.receipt.assert_count,
retracted: result.receipt.retract_count,
committed,
commit_id: committed.then_some(result.receipt.commit_id),
dry_run: false,
t,
})
Expand Down
9 changes: 6 additions & 3 deletions fluree-db-api/src/branch_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::collections::{BTreeSet, HashMap};
#[derive(Clone, Debug, Default)]
pub(crate) struct BranchOpValidation {
/// Formatted violation report, `None` when the view conforms.
pub(crate) report: Option<String>,
pub(crate) report: Option<fluree_db_transact::ShaclViolations>,
}

impl BranchOpValidation {
Expand All @@ -49,15 +49,18 @@ impl BranchOpValidation {
None => Ok(()),
#[cfg(feature = "shacl")]
Some(report) => {
Err(fluree_db_transact::TransactError::ShaclViolation(describe(report)).into())
let message = describe(report.message().to_string());
let report =
fluree_db_transact::ShaclViolations::new(message, report.results().to_vec());
Err(fluree_db_transact::TransactError::ShaclViolation(report).into())
}
// Without the feature no validator runs, so no report is ever
// produced; keep the match total without naming a variant that
// does not exist in this configuration.
#[cfg(not(feature = "shacl"))]
Some(report) => Err(crate::error::ApiError::internal(format!(
"SHACL violation reported without the shacl feature: {}",
describe(report)
describe(report.to_string())
))),
}
}
Expand Down
7 changes: 4 additions & 3 deletions fluree-db-api/src/explain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ pub async fn explain_sparql(
snapshot: &fluree_db_core::LedgerSnapshot,
sparql: &str,
) -> Result<JsonValue> {
explain_sparql_for_view(snapshot, sparql, None, false)
explain_sparql_for_view(snapshot, sparql, None, None, false)
}

/// Explain an openCypher query against a LedgerSnapshot.
Expand Down Expand Up @@ -772,16 +772,17 @@ pub async fn explain_sparql_with_default_context(
sparql: &str,
default_context: Option<&JsonValue>,
) -> Result<JsonValue> {
explain_sparql_for_view(snapshot, sparql, default_context, false)
explain_sparql_for_view(snapshot, sparql, default_context, None, false)
}

pub(crate) fn explain_sparql_for_view(
snapshot: &fluree_db_core::LedgerSnapshot,
sparql: &str,
default_context: Option<&JsonValue>,
params: Option<&fluree_db_sparql::ParamMap>,
allow_semantic_elision: bool,
) -> Result<JsonValue> {
let (vars, parsed) = parse_sparql_to_ir(sparql, snapshot, default_context)?;
let (vars, parsed) = parse_sparql_to_ir(sparql, snapshot, default_context, params)?;

explain_from_parsed(
snapshot,
Expand Down
12 changes: 12 additions & 0 deletions fluree-db-api/src/graph_query_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ impl<'a, 'g> GraphQueryBuilder<'a, 'g> {
self
}

/// Bind SPARQL variables to values; see [`QueryExecutionOptions::with_params`].
pub fn params(mut self, params: fluree_db_sparql::ParamMap) -> Self {
self.core.set_params(params);
self
}

/// Set query execution controls.
pub fn execution_options(mut self, options: QueryExecutionOptions) -> Self {
self.core.set_execution_options(options);
Expand Down Expand Up @@ -464,6 +470,12 @@ impl<'a: 'v, 'v> GraphSnapshotQueryBuilder<'a, 'v> {
self
}

/// Bind SPARQL variables to values; see [`QueryExecutionOptions::with_params`].
pub fn params(mut self, params: fluree_db_sparql::ParamMap) -> Self {
self.core.set_params(params);
self
}

/// Set query execution controls.
pub fn execution_options(mut self, options: QueryExecutionOptions) -> Self {
self.core.set_execution_options(options);
Expand Down
Loading