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
80 changes: 80 additions & 0 deletions src/main/java/fr/inria/corese/core/next/query/Repositories.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fr.inria.corese.core.next.query;

import fr.inria.corese.core.next.data.api.io.format.RDFFormat;
import fr.inria.corese.core.next.query.api.exception.RepositoryException;
import fr.inria.corese.core.next.query.api.repository.Repository;
import fr.inria.corese.core.next.query.impl.repository.CoreseRepository;
Expand All @@ -8,7 +9,11 @@
import fr.inria.corese.core.next.storage.Storages;
import fr.inria.corese.core.next.storage.api.plugin.PluginException;

import java.io.InputStream;
import java.nio.file.Path;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
* Public entry point for creating ready-to-use Corese repositories.
Expand Down Expand Up @@ -82,4 +87,79 @@ public static Repository create(StorageManager storage, StorageConfig config) {
Objects.requireNonNull(storage, "storage"),
Objects.requireNonNull(config, "config"));
}

/**
* Creates an open in-memory repository pre-loaded with RDF statements from the specified file.
* The file format is inferred from the file extension.
*
* @param source RDF source file
* @return a ready-to-use repository containing the loaded data
* @throws RepositoryException if repository creation or RDF loading fails
* @throws NullPointerException if {@code source} is {@code null}
*/
public static Repository createFrom(Path source) {
Objects.requireNonNull(source, "source");
return createAndLoad(repo -> repo.load(source));
}

/**
* Creates an open in-memory repository pre-loaded with RDF statements from the specified input stream.
* The input stream is consumed by the parser but remains caller-owned.
*
* @param source RDF input stream
* @param format explicit RDF serialization format
* @return a ready-to-use repository containing the loaded data
* @throws RepositoryException if repository creation or RDF loading fails
* @throws NullPointerException if {@code source} or {@code format} is {@code null}
*/
public static Repository createFrom(InputStream source, RDFFormat format) {
Objects.requireNonNull(source, "source");
Objects.requireNonNull(format, "format");
return createAndLoad(repo -> repo.load(source, format));
}

private static Repository createAndLoad(Consumer<Repository> loader) {
Repository repository = create();
try {
loader.accept(repository);
return repository;
} catch (RuntimeException | Error e) {
try {
repository.close();
} catch (Exception closeEx) {
e.addSuppressed(closeEx);
}
throw e;
}
}

/**
* Executes work against a temporary in-memory repository, ensuring deterministic closure.
*
* @param consumer repository callback
* @throws RepositoryException if repository creation, execution, or closure fails
* @throws NullPointerException if {@code consumer} is {@code null}
*/
public static void withMemory(Consumer<? super Repository> consumer) {
Objects.requireNonNull(consumer, "consumer");
try (Repository repository = create()) {
consumer.accept(repository);
}
}

/**
* Computes a result against a temporary in-memory repository, ensuring deterministic closure.
*
* @param function computation callback
* @param <T> result type
* @return the computed value
* @throws RepositoryException if repository creation, execution, or closure fails
* @throws NullPointerException if {@code function} is {@code null}
*/
public static <T> T computeWithMemory(Function<? super Repository, ? extends T> function) {
Objects.requireNonNull(function, "function");
try (Repository repository = create()) {
return function.apply(repository);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fr.inria.corese.core.next.data.api.term.IRI;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Objects;
Expand Down Expand Up @@ -38,6 +39,25 @@ public static Dataset empty() {
return EMPTY;
}

/**
* Creates an immutable dataset with the specified default graphs and no named graphs.
*
* @param defaultGraphs default graph IRIs
* @return an immutable Dataset instance
* @throws NullPointerException if {@code defaultGraphs} or any element is {@code null}
*/
public static Dataset of(IRI... defaultGraphs) {
Objects.requireNonNull(defaultGraphs, "defaultGraphs");
if (defaultGraphs.length == 0) {
return empty();
}
Builder builder = builder();
for (IRI graph : defaultGraphs) {
builder.defaultGraph(graph);
}
return builder.build();
}

/** Returns a builder for an immutable dataset. */
public static Builder builder() {
return new Builder();
Expand Down Expand Up @@ -94,12 +114,30 @@ public Builder defaultGraph(IRI graph) {
return this;
}

/** Adds multiple default graphs. */
public Builder defaultGraphs(Collection<? extends IRI> graphs) {
Objects.requireNonNull(graphs, "graphs");
for (IRI graph : graphs) {
defaultGraph(graph);
}
return this;
}

/** Adds a named graph. */
public Builder namedGraph(IRI graph) {
namedGraphs.add(Objects.requireNonNull(graph, "graph"));
return this;
}

/** Adds multiple named graphs. */
public Builder namedGraphs(Collection<? extends IRI> graphs) {
Objects.requireNonNull(graphs, "graphs");
for (IRI graph : graphs) {
namedGraph(graph);
}
return this;
}

/** Builds an immutable snapshot. */
public Dataset build() {
if (defaultGraphs.isEmpty() && namedGraphs.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
/**
* Dataset abstraction for SPARQL query evaluation scope.
* Dataset abstraction and dataset override specifications for SPARQL query evaluation in Corese v5.
*
* <p>{@link fr.inria.corese.core.next.query.api.dataset.Dataset} models the FROM / FROM NAMED
* graph sets that restrict which graphs are visible during query execution.
* It can be applied at the connection level
* ({@link fr.inria.corese.core.next.query.api.repository.RepositoryConnection#setDataset})
* or overridden per individual query
* ({@link fr.inria.corese.core.next.query.api.Query#setDataset}).</p>
* <h2>SPARQL Dataset Semantics</h2>
* <p>{@link fr.inria.corese.core.next.query.api.dataset.Dataset} models the {@code FROM} (default graph)
* and {@code FROM NAMED} (named graph) collections that define the active RDF dataset for query evaluation.</p>
*
* <h2>Dataset Override vs. Reset</h2>
* <ul>
* <li><strong>Override:</strong> Providing a non-null {@code Dataset} via
* {@link fr.inria.corese.core.next.query.api.repository.RepositoryConnection#setDataset(Dataset)} or
* {@link fr.inria.corese.core.next.query.api.Query#setDataset(Dataset)} replaces all {@code FROM} and
* {@code FROM NAMED} clauses specified in the SPARQL query text.</li>
* <li><strong>Empty Override ({@link fr.inria.corese.core.next.query.api.dataset.Dataset#empty()}):</strong> Explicitly
* restricts the dataset to contain zero graphs, yielding empty results.</li>
* <li><strong>Reset ({@code null}):</strong> Passing {@code null} removes any active override, restoring the default
* behavior where the query text's own clauses or all repository graphs are consulted.</li>
* </ul>
*
* <p>All {@code Dataset} instances are immutable and inherently thread-safe.</p>
*/
package fr.inria.corese.core.next.query.api.dataset;
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import fr.inria.corese.core.next.query.api.result.GraphQueryResult;
import fr.inria.corese.core.next.query.api.result.TupleQueryResult;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
Expand All @@ -20,19 +21,20 @@ private MaterializedResults() {
}

static TupleQueryResult tuple(List<String> bindingNames, List<BindingSet> rows) {
List<String> orderedNames = List.copyOf(bindingNames);
List<BindingSet> snapshots = rows.stream()
.map(MaterializedResults::snapshot)
.map(row -> snapshot(orderedNames, row))
.toList();
return new TupleResult(List.copyOf(bindingNames), snapshots.iterator());
return new TupleResult(orderedNames, snapshots.iterator());
}

static GraphQueryResult graph(List<Statement> statements) {
return new GraphResult(List.copyOf(statements).iterator());
}

private static BindingSet snapshot(BindingSet source) {
private static BindingSet snapshot(List<String> bindingNames, BindingSet source) {
Map<String, Value> values = new LinkedHashMap<>();
for (String name : source.getBindingNames()) {
for (String name : bindingNames) {
Value value = source.getValue(name);
if (value != null) {
values.put(name, value);
Expand All @@ -44,7 +46,7 @@ private static BindingSet snapshot(BindingSet source) {
private static final class TupleResult implements TupleQueryResult {
private final List<String> bindingNames;
private final Iterator<BindingSet> rows;
private boolean closed;
private volatile boolean closed;

private TupleResult(List<String> bindingNames, Iterator<BindingSet> rows) {
this.bindingNames = bindingNames;
Expand Down Expand Up @@ -82,7 +84,7 @@ private void checkOpen() {

private static final class GraphResult implements GraphQueryResult {
private final Iterator<Statement> statements;
private boolean closed;
private volatile boolean closed;

private GraphResult(Iterator<Statement> statements) {
this.statements = statements;
Expand Down Expand Up @@ -115,7 +117,7 @@ private void checkOpen() {
private record ImmutableBindingSet(Map<String, Value> values) implements BindingSet {

private ImmutableBindingSet {
values = Map.copyOf(values);
values = Collections.unmodifiableMap(new LinkedHashMap<>(values));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import fr.inria.corese.core.next.query.Repositories;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.io.InputStream;
import java.io.Reader;
Expand All @@ -19,7 +21,10 @@
import java.util.function.Consumer;
import java.util.function.Function;
import fr.inria.corese.core.next.data.api.io.format.RDFFormat;
import fr.inria.corese.core.next.data.api.term.IRI;
import fr.inria.corese.core.next.data.api.term.Resource;
import fr.inria.corese.core.next.data.api.term.Value;
import java.util.Objects;

/**
* A Corese repository that contains RDF data that can be queried and updated.
Expand Down Expand Up @@ -99,6 +104,35 @@ default TupleQueryResult select(String sparqlSelect) throws QuerySyntaxException
return MaterializedResults.tuple(names, bindings);
}

/**
* Evaluates a SPARQL SELECT query directly against this repository in one call,
* mapping each solution {@link BindingSet} using the provided mapper function.
*
* <p>The repository connection is automatically opened and closed within this call.
* The mapper should extract needed terms eagerly rather than retaining references
* to the row, because the underlying query cursor is closed upon return.</p>
*
* @param sparqlSelect the SPARQL SELECT query text
* @param mapper mapping function converting each {@link BindingSet} into a result element
* @param <T> the mapped result type
* @return an unmodifiable list of mapped results
* @throws QuerySyntaxException if the query string is syntactically invalid
* @throws RepositoryException if evaluation fails
* @throws NullPointerException if {@code mapper} is {@code null}
*/
default <T> List<T> select(String sparqlSelect, Function<? super BindingSet, ? extends T> mapper)
throws QuerySyntaxException, RepositoryException {
Objects.requireNonNull(mapper, "mapper");
List<T> results = new ArrayList<>();
try (RepositoryConnection conn = getConnection();
TupleQueryResult raw = conn.prepareTupleQuery(sparqlSelect).evaluate()) {
while (raw.hasNext()) {
results.add(mapper.apply(raw.next()));
}
}
return Collections.unmodifiableList(results);
}

/**
* Evaluates a SPARQL ASK query directly against this repository in one call.
*
Expand Down Expand Up @@ -139,6 +173,67 @@ default GraphQueryResult graph(String sparqlGraph) throws QuerySyntaxException,
return MaterializedResults.graph(statements);
}

/**
* Evaluates a SPARQL CONSTRUCT or DESCRIBE query directly against this repository in one call.
*
* <p>This is a natural semantic alias for {@link #graph(String)} following SPARQL 1.1 terminology.</p>
*
* @param sparqlConstruct the SPARQL CONSTRUCT query text
* @return a materialized {@link GraphQueryResult} containing output statements
* @throws QuerySyntaxException if the query string is syntactically invalid
* @throws RepositoryException if evaluation fails
* @see #graph(String)
*/
default GraphQueryResult construct(String sparqlConstruct) throws QuerySyntaxException, RepositoryException {
return graph(sparqlConstruct);
}

/**
* Tests whether at least one statement matching the specified pattern exists in this repository.
* A {@code null} term is treated as a wildcard.
*
* @param subject statement subject to match, or {@code null} for wildcard
* @param predicate statement predicate to match, or {@code null} for wildcard
* @param object statement object to match, or {@code null} for wildcard
* @param contexts contexts to match, or empty for every graph
* @return {@code true} if a matching statement exists, {@code false} otherwise
* @throws RepositoryException if the check fails
*/
default boolean hasStatement(Resource subject, IRI predicate, Value object, Resource... contexts)
throws RepositoryException {
try (RepositoryConnection conn = getConnection()) {
return conn.hasStatement(subject, predicate, object, contexts);
}
}

/**
* Returns the total number of statements in the designated graph contexts.
* An empty {@code contexts} array counts across all graphs (default and named graphs).
*
* @param contexts contexts to count, or none for every graph
* @return number of statements in the selected contexts
* @throws RepositoryException if the count cannot be performed
*/
default long size(Resource... contexts) throws RepositoryException {
try (RepositoryConnection conn = getConnection()) {
return conn.size(contexts);
}
}

/**
* Adds the specified statements to this repository in an auto-managed connection.
*
* @param statements statements to add
* @throws RepositoryException if the addition fails
* @throws NullPointerException if {@code statements} or any element is {@code null}
*/
default void add(Statement... statements) throws RepositoryException {
Objects.requireNonNull(statements, "statements");
try (RepositoryConnection conn = getConnection()) {
conn.add(Arrays.asList(statements));
}
}

/**
* Executes a SPARQL UPDATE request directly against this repository in one call.
*
Expand Down
Loading
Loading