From b962f59034bdc0997880b15cc37d367115094e89 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 26 Jan 2022 09:30:37 +0100 Subject: [PATCH 01/20] Add initial draft interfaces for the query spec --- index.d.ts | 1 + query.d.ts | 8 ++ query/common.ts | 290 ++++++++++++++++++++++++++++++++++++++++++++ query/filterable.ts | 123 +++++++++++++++++++ query/queryable.ts | 115 ++++++++++++++++++ 5 files changed, 537 insertions(+) create mode 100644 query.d.ts create mode 100644 query/common.ts create mode 100644 query/filterable.ts create mode 100644 query/queryable.ts diff --git a/index.d.ts b/index.d.ts index 9e2c7fb..b7a4b96 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,3 +3,4 @@ export * from './data-model'; export * from './stream'; export * from './dataset'; +export * from './query'; diff --git a/query.d.ts b/query.d.ts new file mode 100644 index 0000000..8e9fd8a --- /dev/null +++ b/query.d.ts @@ -0,0 +1,8 @@ +/* Dataset Interfaces */ +/* https://rdf.js.org/query-spec/ */ + +export * from './query/common'; +export * from './query/filterable'; +export * from './query/queryable'; + + diff --git a/query/common.ts b/query/common.ts new file mode 100644 index 0000000..7d8fbf8 --- /dev/null +++ b/query/common.ts @@ -0,0 +1,290 @@ +/* Query Interfaces - Common */ +/* https://rdf.js.org/query-spec/ */ + +import { EventEmitter } from "events"; +import * as RDF from '../data-model'; +import { Term } from '../data-model'; + +/** + * Helper union type for quad term names. + */ +export type QuadTermName = 'subject' | 'predicate' | 'object' | 'graph'; + +/** + * Custom typings for the RDF/JS ResultStream interface as the current + * typings restrict the generic param Q to extensions of "BaseQuad", + * meaning it cannot be used for Bindings. + */ +export interface ResultStream extends EventEmitter { + read(): Q | null; +} + +/** + * QueryOperationCost represents the cost of a given query operation. + */ +export interface QueryOperationCost { + /** + * An estimation of how many iterations over items are executed. + * This is used to determine the CPU cost. + */ + iterations: number; + /** + * An estimation of how many items are stored in memory. + * This is used to determine the memory cost. + */ + persistedItems: number; + /** + * An estimation of how many items block the stream. + * This is used to determine the time the stream is not progressing anymore. + */ + blockingItems: number; + /** + * An estimation of the time to request items from sources. + * This is used to determine the I/O cost. + */ + requestTime: number; + /** + * Custom properties + */ + [key: string]: any; +} + +/** + * QueryOperationOrder represents an ordering of the results of a given query operation. + * + * These objects can represent orderings of both quad and bindings streams, + * respectively identified by quad term names and variables. + */ +export interface QueryOperationOrder { + cost: QueryOperationCost; + terms: { term: T, direction: 'asc' | 'desc' }[]; +} + +/** + * QueryResultCardinality represents the number of results, which can either be an estimate or exact value. + */ +export interface QueryResultCardinality { + /** + * indicates the type of counting that was done, and MUST either be "estimate" or "exact". + */ + type: 'estimate' | 'exact'; + + /** + * Indicates an estimated of the number of results in the stream if type = "estimate", + * or the exact number of quads in the stream if type = "exact". + */ + value: number; +} + +/** + * A QueryResultMetadata is an object that contains metadata about a certain query result. + */ +export interface QueryResultMetadata { + /** + * A callback field for obtaining the cardinality of the result stream. + */ + cardinality(precision?: 'estimate' | 'exact'): Promise; + + /** + * A callback for obtaining the current result ordering of the result stream. + */ + order(): Promise[]>; + + /** + * A callback for obtaining all available alternative result orderings for the current query. + */ + availableOrders(): Promise[]>; + + /** + * Custom properties + */ + [key: string]: any; +} + +/** + * Options that can be passed when executing a query. + */ +export interface QueryExecuteOptions { + /** + * The required order for the result stream. + */ + order?: QueryOperationOrder; + + /** + * Custom properties + */ + [key: string]: any; +} + +/** + * Generic interface that defines query objects following the Future pattern. + */ +export interface BaseQuery { + /** + * Identifier for the type of result of tis query. + */ + resultType: string; + + /** + * Returns either a stream containing all the items that match the given query, + * a boolean or void depending on the semantics of the given query. + */ + execute(opts?: any): Promise | boolean | void>; + + /** + * Asynchronously metadata of the current result. + */ + metadata?: QueryResultMetadata; +} + +/** + * Query object that returns bindings. + */ +export interface QueryBindings extends BaseQuery { + resultType: 'bindings'; + execute(opts?: QueryExecuteOptions): Promise>; + metadata: QueryResultMetadata & { variables(): Promise; }; +} + +/** + * Query object that returns quads. + */ +export interface QueryQuads extends BaseQuery { + resultType: 'quads'; + execute(opts?: QueryExecuteOptions): Promise>; + metadata: QueryResultMetadata; +} + +/** + * Query object that returns a boolean. + */ +export interface QueryBoolean extends BaseQuery { + resultType: 'boolean'; + execute(): Promise; +} + +/** + * Query object that returns void. + */ +export interface QueryVoid extends BaseQuery { + resultType: 'void'; + execute(): Promise; +} + +/** + * Union type for the different query types. + */ +export type Query = QueryBindings | QueryBoolean | QueryQuads | QueryVoid; + +/** + * Bindings represents the mapping of variables to RDF values using an immutable Map-like representation. + * + * Bindings instances are created using a BindingsFactory. + * + * The internal order of variable-value entries is undefined. + */ +export interface Bindings extends Iterable<[RDF.Variable, RDF.Term]> { + type: 'bindings'; + /** + * Check if a binding exist for the given variable. + * @param key A variable + */ + has: (key: RDF.Variable) => boolean; + /** + * Obtain the binding value for the given variable. + * @param key A variable + */ + get: (key: RDF.Variable) => RDF.Term | undefined; + /** + * Obtain all variables for which mappings exist. + */ + keys: () => Iterator; + /** + * Obtain all values that are mapped to. + */ + values: () => Iterator; + /** + * Iterate over all variable-value pairs. + * @param fn A callback that is called for each variable-value pair + * with value as first argument, and variable as second argument. + */ + forEach: (fn: (value: RDF.Term, key: RDF.Variable) => any) => void; + /** + * The number of variable-value pairs. + */ + size: number; + /** + * Iterator over all variable-value pairs. + */ + [Symbol.iterator]: () => Iterator<[RDF.Variable, RDF.Term]>; + /** + * Check if all entries contained in this Bindings object are equal to all entries in the other Bindings object. + * @param other A Bindings object. + */ + equals(other: Bindings | null | undefined): boolean; +} + +/** + * BindingsFactory can create new instances of Bindings. + */ +export interface BindingsFactory { + /** + * Create a new Bindings object from the given variable-value entries. + * @param entries An array of entries, where each entry is a tuple containing a variable and a term. + */ + bindings: (entries?: [RDF.Variable, RDF.Term][]) => Bindings; + /** + * Create a new Bindings object by adding the given variable and value mapping. + * + * If the variable already exists in the binding, then the existing mapping is overwritten. + * + * @param bindings A Bindings object. + * @param key The variable key. + * @param value The value. + */ + set: (bindings: Bindings, key: RDF.Variable, value: RDF.Term) => Bindings; + /** + * Create a new Bindings object by removing the given variable. + * + * If the variable does not exist in the binding, a copy of the Bindings object is returned. + * + * @param bindings A Bindings object. + * @param key The variable key. + */ + delete: (bindings: Bindings, key: RDF.Variable) => Bindings; + /** + * Create a new Bindings object from the given Bindings object by filtering entries using a callback. + * @param bindings The Bindings to filter. + * @param fn A callback that is applied on each entry. + * Returning true indicates that this entry must be contained in the resulting Bindings object. + */ + filter: (bindings: Bindings, fn: (value: RDF.Term, key: RDF.Variable) => boolean) => Bindings; + /** + * Create a new Bindings object from the given Bindings object by mapping entries using a callback. + * @param bindings The Bindings to map. + * @param fn A callback that is applied on each entry, in which the original value is replaced by the returned value. + */ + map: (bindings: Bindings, fn: (value: RDF.Term, key: RDF.Variable) => RDF.Term) => Bindings; + /** + * Merge two bindings together. + * + * If a merge conflict occurs (left and right have an equal variable with unequal value), + * then undefined is returned. + * + * @param left A Bindings object. + * @param right A Bindings object. + */ + merge: (left: Bindings, right: Bindings) => Bindings | undefined; + /** + * Merge two bindings together, where merge conflicts can be resolved using a callback function. + * @param merger A function that is invoked when a merge conflict occurs, + * for which the returned value is considered the merged value. + * @param left A Bindings object. + * @param right A Bindings object. + */ + mergeWith: ( + merger: (left: RDF.Term, right: RDF.Term, key: RDF.Variable) => RDF.Term, + left: Bindings, + right: Bindings, + ) => Bindings; +} diff --git a/query/filterable.ts b/query/filterable.ts new file mode 100644 index 0000000..8e854da --- /dev/null +++ b/query/filterable.ts @@ -0,0 +1,123 @@ +/* Query Interfaces - Filterable */ +/* https://rdf.js.org/query-spec/ */ + +import * as RDF from '../data-model'; +import { QueryQuads } from './common'; + +/** + * Expression is an abstract interface that represents a generic expression over a stream of quads. + */ +export interface Expression { + /** + * Value that identifies the concrete interface of the expression, + * since the Expression itself is not directly instantiated. + * Possible values include "operator" and "term". + */ + expressionType: string; +} + +/** + * An OperatorExpression is represents an expression that applies a given operator on given sub-expressions. + * + * The WebIDL definition of the Filterable spec contains a list of supported + * operators: https://rdf.js.org/query-spec/#expression-operators + */ +export interface OperatorExpression extends Expression { + + /** + * Contains the constant "operator". + */ + expressionType: 'operator'; + + /** + * Value that identifies an operator. Possible values can be found in the list of operators. + */ + operator: string; + + /** + * Array of Expression's on to which the given operator applies. + * The length of this array depends on the operator. + */ + args: Expression[]; +} + +/** + * A TermExpression is an expression that contains a Term. + */ +export interface TermExpression { + + /** + * The constant "term". + */ + expressionType: 'term'; + + /** + * an RDF Term. + */ + term: RDF.Term; +} + +/** + * ExpressionFactory enables expressions to be created in an idiomatic manner. + */ +export interface ExpressionFactory { + + /** + * Creates a new OperatorExpression instance for the given operator and array of arguments. + */ + operatorExpression(operator: string, args: Expression[]): OperatorExpression; + + /** + * Creates a new TermExpression instance for the given term. + */ + termExpression(term: RDF.Term): TermExpression; +} + +/** + * A FilterableSource is an object that emits quads based on a quad pattern and filter expression. + * + * The emitted quads can be directly contained in this object, or they can be generated on the fly. + * + * FilterableSource is not necessarily an extension of the RDF/JS Source + * interface, but implementers MAY decide to implement both at the same time. + * + * matchExpression() Returns a QueryQuads future that can produce a quad + * stream that contains all quads matching the quad pattern and the expression. + * + * When a Term parameter is defined, and is a NamedNode, Literal or BlankNode, + * it must match each produced quad, according to the Quad.equals semantics. + * When a Term parameter is a Variable, or it is undefined, it acts as a + * wildcard, and can match with any Term. + * + * NOTES: + * - When matching with graph set to undefined or null it MUST match all the + * graphs (sometimes called the union graph). To match only the default graph + * set graph to a DefaultGraph. + * - When an Expression parameter is defined, the complete quad stream is + * filtered according to this expression. When it is undefined, no filter is + * applied. + * + * If parameters of type Variable with an equal variable name are in place, + * then the corresponding quad components in the resulting quad stream MUST be + * equal. + * Expression's MAY contain Variable Term's. If their variable names are equal + * to Variable's in the given quad pattern, then the Expression MUST be + * instantiated for each variable's binding in the resulting quad stream when + * applying the Expression filter. + */ +export interface FilterableSource { + /** + * May reject given an unsupported expression. + */ + matchExpression( + subject?: RDF.Term, + predicate?: RDF.Term, + obj?: RDF.Term, + graph?: RDF.Term, + expression?: Expression, + opts?: { + length?: number; + start?: number; + }, + ): Promise; +} diff --git a/query/queryable.ts b/query/queryable.ts new file mode 100644 index 0000000..1c16729 --- /dev/null +++ b/query/queryable.ts @@ -0,0 +1,115 @@ +/* Query Interfaces - Queryable */ +/* https://rdf.js.org/query-spec/ */ + +import * as RDF from '../data-model'; +import { Bindings, Query, ResultStream } from './common'; + +// TODO: we may consider defining some standards, like 'string', RDF.Source, ... +/** + * Context objects provide a way to pass additional bits information to the query engine when executing a query. + */ +export interface QueryContext { + /** + * An array of data sources the query engine must use. + */ + sources: [SourceType, ...SourceType[]]; + /** + * The date that should be used by SPARQL operations such as NOW(). + */ + queryTimestamp?: Date; + /** + * Other options + */ + [key: string]: any; +} + +/** + * Context object in the case the passed query is a string. + */ +export interface QueryStringContext extends QueryContext { + /** + * The format in which the query string is defined. + * Defaults to { language: 'SPARQL', version: '1.1' } + */ + queryFormat?: QueryFormat; + /** + * The baseIRI for parsing the query. + */ + baseIRI?: string; +} + +/** + * Context object in the case the passed query is an algebra object. + */ +export type QueryAlgebraContext = QueryContext; + +/** + * Represents a specific query format + */ +export interface QueryFormat { + /** + * The query language, e.g. 'SPARQL'. + */ + language: string; + /** + * The version of the query language, e.g. '1.1'. + */ + version: string; + /** + * An optional array of extensions on the query language. + * The representation of these extensions is undefined. + */ + extensions?: string[]; +} + +/** + * Placeholder to represent SPARQL Algebra trees. + * Algebra typings are TBD. Reference implementations include: + * - https://www.npmjs.com/package/sparqlalgebrajs + */ +export type Algebra = any; + +/** + * Generic query engine interfaces. + * It allow engines to return any type of result object for any type of query. + * @param QueryFormatType The format of the query, either string or algebra object. + * @param SourceType The allowed sources over which queries can be executed. + * @param QueryType The allowed query types. + */ +export interface Queryable { + /** + * Initiate a given query. + * + * This will produce a future to a query result, which has to be executed to obtain the query results. + * + * This can reject given an unsupported or invalid query. + * + * @see Query + */ + query(query: QueryFormatType, context?: QueryStringContext): Promise; +} + +/** + * SPARQL-constrainted query interface. + * + * This interface guarantees that result objects are of the expected type as defined by the SPARQL spec. + */ +export type SparqlQueryable = unknown + & (SupportedResultType extends BindingsResult ? { + queryBindings(query: QueryFormatType, context?: QueryStringContext): Promise>; +} : unknown) + & (SupportedResultType extends BooleanResult ? { + queryBoolean(query: QueryFormatType, context?: QueryStringContext): Promise; +} : unknown) + & (SupportedResultType extends QuadsResult ? { + queryQuads(query: QueryFormatType, context?: QueryStringContext): Promise>; +} : unknown) + & (SupportedResultType extends VoidResult ? { + queryVoid(query: QueryFormatType, context?: QueryStringContext): Promise; +} : unknown) + ; + +export type BindingsResult = { bindings: true }; +export type VoidResult = { void: true }; +export type QuadsResult = { quads: true }; +export type BooleanResult = { boolean: true }; From e30f83b3d4ea02df4556de8e4af7b37c918b3f50 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 26 Jan 2022 12:59:04 +0100 Subject: [PATCH 02/20] Move all mutability methods to Bindings --- query/common.ts | 99 +++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/query/common.ts b/query/common.ts index 7d8fbf8..6570850 100644 --- a/query/common.ts +++ b/query/common.ts @@ -178,6 +178,8 @@ export type Query = QueryBindings | QueryBoolean | QueryQuads | QueryVoid; /** * Bindings represents the mapping of variables to RDF values using an immutable Map-like representation. + * This means that methods such as `set` and `delete` do not modify this instance, + * but they return a new Bindings instance that contains the modification. * * Bindings instances are created using a BindingsFactory. * @@ -195,14 +197,31 @@ export interface Bindings extends Iterable<[RDF.Variable, RDF.Term]> { * @param key A variable */ get: (key: RDF.Variable) => RDF.Term | undefined; + /** + * Create a new Bindings object by adding the given variable and value mapping. + * + * If the variable already exists in the binding, then the existing mapping is overwritten. + * + * @param key The variable key. + * @param value The value. + */ + set: (key: RDF.Variable, value: RDF.Term) => Bindings; + /** + * Create a new Bindings object by removing the given variable. + * + * If the variable does not exist in the binding, a copy of the Bindings object is returned. + * + * @param key The variable key. + */ + delete: (key: RDF.Variable) => Bindings; /** * Obtain all variables for which mappings exist. */ - keys: () => Iterator; + keys: () => Iterable; /** * Obtain all values that are mapped to. */ - values: () => Iterator; + values: () => Iterable; /** * Iterate over all variable-value pairs. * @param fn A callback that is called for each variable-value pair @@ -222,69 +241,51 @@ export interface Bindings extends Iterable<[RDF.Variable, RDF.Term]> { * @param other A Bindings object. */ equals(other: Bindings | null | undefined): boolean; -} - -/** - * BindingsFactory can create new instances of Bindings. - */ -export interface BindingsFactory { - /** - * Create a new Bindings object from the given variable-value entries. - * @param entries An array of entries, where each entry is a tuple containing a variable and a term. - */ - bindings: (entries?: [RDF.Variable, RDF.Term][]) => Bindings; - /** - * Create a new Bindings object by adding the given variable and value mapping. - * - * If the variable already exists in the binding, then the existing mapping is overwritten. - * - * @param bindings A Bindings object. - * @param key The variable key. - * @param value The value. - */ - set: (bindings: Bindings, key: RDF.Variable, value: RDF.Term) => Bindings; /** - * Create a new Bindings object by removing the given variable. - * - * If the variable does not exist in the binding, a copy of the Bindings object is returned. - * - * @param bindings A Bindings object. - * @param key The variable key. - */ - delete: (bindings: Bindings, key: RDF.Variable) => Bindings; - /** - * Create a new Bindings object from the given Bindings object by filtering entries using a callback. - * @param bindings The Bindings to filter. + * Create a new Bindings object by filtering entries using a callback. * @param fn A callback that is applied on each entry. * Returning true indicates that this entry must be contained in the resulting Bindings object. */ - filter: (bindings: Bindings, fn: (value: RDF.Term, key: RDF.Variable) => boolean) => Bindings; + filter: (fn: (value: RDF.Term, key: RDF.Variable) => boolean) => Bindings; /** - * Create a new Bindings object from the given Bindings object by mapping entries using a callback. - * @param bindings The Bindings to map. + * Create a new Bindings object by mapping entries using a callback. * @param fn A callback that is applied on each entry, in which the original value is replaced by the returned value. */ - map: (bindings: Bindings, fn: (value: RDF.Term, key: RDF.Variable) => RDF.Term) => Bindings; + map: (fn: (value: RDF.Term, key: RDF.Variable) => RDF.Term) => Bindings; /** - * Merge two bindings together. + * Merge this bindings with another. * - * If a merge conflict occurs (left and right have an equal variable with unequal value), + * If a merge conflict occurs (this and other have an equal variable with unequal value), * then undefined is returned. * - * @param left A Bindings object. - * @param right A Bindings object. + * @param other A Bindings object. */ - merge: (left: Bindings, right: Bindings) => Bindings | undefined; + merge: (other: Bindings) => Bindings | undefined; /** - * Merge two bindings together, where merge conflicts can be resolved using a callback function. + * Merge this bindings with another, where merge conflicts can be resolved using a callback function. * @param merger A function that is invoked when a merge conflict occurs, * for which the returned value is considered the merged value. - * @param left A Bindings object. - * @param right A Bindings object. + * @param other A Bindings object. */ mergeWith: ( - merger: (left: RDF.Term, right: RDF.Term, key: RDF.Variable) => RDF.Term, - left: Bindings, - right: Bindings, + merger: (self: RDF.Term, other: RDF.Term, key: RDF.Variable) => RDF.Term, + other: Bindings, ) => Bindings; } + +/** + * BindingsFactory can create new instances of Bindings. + */ +export interface BindingsFactory { + /** + * Create a new Bindings object from the given variable-value entries. + * @param entries An array of entries, where each entry is a tuple containing a variable and a term. + */ + bindings: (entries?: [RDF.Variable, RDF.Term][]) => Bindings; + + /** + * Create a copy of the given bindings object using this factory. + * @param bindings A Bindings object. + */ + fromBindings: (bindings: Bindings) => Bindings; +} From e2d62fc133951c81825d9ed07d9550768beb0480 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 3 Feb 2022 12:38:44 +0100 Subject: [PATCH 03/20] Tweak queryable interfaces based on implementation tests --- query.d.ts | 2 +- query/{common.ts => common.d.ts} | 45 ++++++---------- query/{filterable.ts => filterable.d.ts} | 0 query/{queryable.ts => queryable.d.ts} | 65 +++++++++++++++++------- 4 files changed, 63 insertions(+), 49 deletions(-) rename query/{common.ts => common.d.ts} (84%) rename query/{filterable.ts => filterable.d.ts} (100%) rename query/{queryable.ts => queryable.d.ts} (50%) diff --git a/query.d.ts b/query.d.ts index 8e9fd8a..399bb4a 100644 --- a/query.d.ts +++ b/query.d.ts @@ -1,4 +1,4 @@ -/* Dataset Interfaces */ +/* Query Interfaces */ /* https://rdf.js.org/query-spec/ */ export * from './query/common'; diff --git a/query/common.ts b/query/common.d.ts similarity index 84% rename from query/common.ts rename to query/common.d.ts index 6570850..4616654 100644 --- a/query/common.ts +++ b/query/common.d.ts @@ -3,7 +3,6 @@ import { EventEmitter } from "events"; import * as RDF from '../data-model'; -import { Term } from '../data-model'; /** * Helper union type for quad term names. @@ -71,35 +70,30 @@ export interface QueryResultCardinality { /** * Indicates an estimated of the number of results in the stream if type = "estimate", - * or the exact number of quads in the stream if type = "exact". + * or the exact number of results in the stream if type = "exact". */ value: number; } /** - * A QueryResultMetadata is an object that contains metadata about a certain query result. + * BaseMetadataQuery is helper interface that provides a metadata callback. */ -export interface QueryResultMetadata { +interface BaseMetadataQuery { /** - * A callback field for obtaining the cardinality of the result stream. + * Asynchronously return metadata of the current result. */ - cardinality(precision?: 'estimate' | 'exact'): Promise; + metadata(opts?: M): Promise>; +} - /** - * A callback for obtaining the current result ordering of the result stream. - */ - order(): Promise[]>; +export type MetadataOpts = CardinalityMetadataOpts | OrderMetadataOpts | AvailableOrdersMetadataOpts; +export interface CardinalityMetadataOpts { cardinality: 'estimate' | 'exact'; } +export interface OrderMetadataOpts { order: true; } +export interface AvailableOrdersMetadataOpts { availableOrders: true; } - /** - * A callback for obtaining all available alternative result orderings for the current query. - */ - availableOrders(): Promise[]>; - - /** - * Custom properties - */ - [key: string]: any; -} +export type ConditionalMetadataType = AdditionalMetadataType + & (M extends CardinalityMetadataOpts ? { cardinality: QueryResultCardinality } : Record) + & (M extends OrderMetadataOpts ? { order: QueryOperationOrder['terms'] } : Record) + & (M extends AvailableOrdersMetadataOpts ? { availableOrders: QueryOperationOrder[] } : Record); /** * Options that can be passed when executing a query. @@ -130,29 +124,22 @@ export interface BaseQuery { * a boolean or void depending on the semantics of the given query. */ execute(opts?: any): Promise | boolean | void>; - - /** - * Asynchronously metadata of the current result. - */ - metadata?: QueryResultMetadata; } /** * Query object that returns bindings. */ -export interface QueryBindings extends BaseQuery { +export interface QueryBindings extends BaseQuery, BaseMetadataQuery { resultType: 'bindings'; execute(opts?: QueryExecuteOptions): Promise>; - metadata: QueryResultMetadata & { variables(): Promise; }; } /** * Query object that returns quads. */ -export interface QueryQuads extends BaseQuery { +export interface QueryQuads extends BaseQuery, BaseMetadataQuery { resultType: 'quads'; execute(opts?: QueryExecuteOptions): Promise>; - metadata: QueryResultMetadata; } /** diff --git a/query/filterable.ts b/query/filterable.d.ts similarity index 100% rename from query/filterable.ts rename to query/filterable.d.ts diff --git a/query/queryable.ts b/query/queryable.d.ts similarity index 50% rename from query/queryable.ts rename to query/queryable.d.ts index 1c16729..6273fff 100644 --- a/query/queryable.ts +++ b/query/queryable.d.ts @@ -4,7 +4,6 @@ import * as RDF from '../data-model'; import { Bindings, Query, ResultStream } from './common'; -// TODO: we may consider defining some standards, like 'string', RDF.Source, ... /** * Context objects provide a way to pass additional bits information to the query engine when executing a query. */ @@ -12,7 +11,7 @@ export interface QueryContext { /** * An array of data sources the query engine must use. */ - sources: [SourceType, ...SourceType[]]; + sources?: [SourceType, ...SourceType[]]; /** * The date that should be used by SPARQL operations such as NOW(). */ @@ -29,7 +28,7 @@ export interface QueryContext { export interface QueryStringContext extends QueryContext { /** * The format in which the query string is defined. - * Defaults to { language: 'SPARQL', version: '1.1' } + * Defaults to { language: 'sparql', version: '1.1' } */ queryFormat?: QueryFormat; /** @@ -48,7 +47,7 @@ export type QueryAlgebraContext = QueryContext; */ export interface QueryFormat { /** - * The query language, e.g. 'SPARQL'. + * The query language, e.g. 'sparql'. */ language: string; /** @@ -76,7 +75,13 @@ export type Algebra = any; * @param SourceType The allowed sources over which queries can be executed. * @param QueryType The allowed query types. */ -export interface Queryable { +export interface Queryable< + QueryFormatTypesAvailable extends string | Algebra, + SourceType, + QueryType extends Query, + QueryStringContextType extends QueryStringContext, + QueryAlgebraContextType extends QueryAlgebraContext, +> { /** * Initiate a given query. * @@ -86,7 +91,10 @@ export interface Queryable): Promise; + query( + query: QueryFormatType, + context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, + ): Promise; } /** @@ -94,22 +102,41 @@ export interface Queryable = unknown - & (SupportedResultType extends BindingsResult ? { - queryBindings(query: QueryFormatType, context?: QueryStringContext): Promise>; +export type SparqlQueryable< + QueryFormatTypesAvailable extends string | Algebra, + SourceType, + QueryStringContextType extends QueryStringContext, + QueryAlgebraContextType extends QueryAlgebraContext, + SupportedResultType, +> = unknown + & (SupportedResultType extends BindingsResultSupport ? { + queryBindings( + query: QueryFormatType, + context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, + ): Promise>; } : unknown) - & (SupportedResultType extends BooleanResult ? { - queryBoolean(query: QueryFormatType, context?: QueryStringContext): Promise; + & (SupportedResultType extends BooleanResultSupport ? { + queryBoolean( + query: QueryFormatType, + context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, + ): Promise; } : unknown) - & (SupportedResultType extends QuadsResult ? { - queryQuads(query: QueryFormatType, context?: QueryStringContext): Promise>; + & (SupportedResultType extends QuadsResultSupport ? { + queryQuads( + query: QueryFormatType, + context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, + ): Promise>; } : unknown) - & (SupportedResultType extends VoidResult ? { - queryVoid(query: QueryFormatType, context?: QueryStringContext): Promise; + & (SupportedResultType extends VoidResultSupport ? { + queryVoid( + query: QueryFormatType, + context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, + ): Promise; } : unknown) ; -export type BindingsResult = { bindings: true }; -export type VoidResult = { void: true }; -export type QuadsResult = { quads: true }; -export type BooleanResult = { boolean: true }; +export type SparqlResultSupport = BindingsResultSupport & VoidResultSupport & QuadsResultSupport & BooleanResultSupport; +export type BindingsResultSupport = { bindings: true }; +export type VoidResultSupport = { void: true }; +export type QuadsResultSupport = { quads: true }; +export type BooleanResultSupport = { boolean: true }; From bc7163ed667355e12c37f0806eeec045bf9d25d5 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 3 Feb 2022 12:42:01 +0100 Subject: [PATCH 04/20] Add changelog for addition of queryable interfaces --- .changeset/tough-guests-flash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tough-guests-flash.md diff --git a/.changeset/tough-guests-flash.md b/.changeset/tough-guests-flash.md new file mode 100644 index 0000000..ac39a32 --- /dev/null +++ b/.changeset/tough-guests-flash.md @@ -0,0 +1,5 @@ +--- +"@rdfjs/types": minor +--- + +Add queryable interfaces From 5577666aa0a7a647efaea2749ad2babe127e9030 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 3 Feb 2022 12:43:43 +0100 Subject: [PATCH 05/20] Enter prerelease mode and version packages --- .changeset/pre.json | 12 ++++++++++++ CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000..a2715e8 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,12 @@ +{ + "mode": "pre", + "tag": "next", + "initialVersions": { + "@rdfjs/types": "1.0.1" + }, + "changesets": [ + "friendly-lies-suffer", + "seven-shrimps-build", + "tough-guests-flash" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 779f93c..2409a4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # @rdfjs/types +## 1.1.0-next.0 + +### Minor Changes + +- 95f1e31: Dataset: Use correct type of `dataset` in methods with callbacks +- bc7163e: Add queryable interfaces + +### Patch Changes + +- 8164183: Documentation Fix: Update reference of Quad to BaseQuad in the definition of Term in order to align with the type declaration. + ## 1.0.1 ### Patch Changes diff --git a/package.json b/package.json index edeb377..a8769fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rdfjs/types", - "version": "1.0.1", + "version": "1.1.0-next.0", "license": "MIT", "types": "index.d.ts", "author": { From 4e5bafbd84956d5c013fe66c1c677aeda66de757 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 3 Feb 2022 12:46:04 +0100 Subject: [PATCH 06/20] Make GitHub CI also publish prereleases from feature/query branch --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cebc897..1e53d3b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - feature/query jobs: release: From 71ee4eac01962a326fecb94506cccb06298e0b75 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Fri, 4 Feb 2022 08:24:31 +0100 Subject: [PATCH 07/20] Allow supported metadata types to be overridden --- query/common.d.ts | 20 ++++++++++++++------ query/filterable.d.ts | 4 ++-- query/queryable.d.ts | 8 ++++++-- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/query/common.d.ts b/query/common.d.ts index 4616654..21f3cb4 100644 --- a/query/common.d.ts +++ b/query/common.d.ts @@ -78,14 +78,22 @@ export interface QueryResultCardinality { /** * BaseMetadataQuery is helper interface that provides a metadata callback. */ -interface BaseMetadataQuery { +interface BaseMetadataQuery { /** * Asynchronously return metadata of the current result. */ - metadata(opts?: M): Promise>; + metadata>(opts?: M): Promise>; } -export type MetadataOpts = CardinalityMetadataOpts | OrderMetadataOpts | AvailableOrdersMetadataOpts; +export type AllMetadataSupport = CardinalityMetadataSupport & OrderMetadataSupport & AvailableOrdersMetadataSupport; +export type CardinalityMetadataSupport = { cardinality: true }; +export type OrderMetadataSupport = { order: true }; +export type AvailableOrdersMetadataSupport = { availableOrders: true }; + +export type MetadataOpts = + (SupportedMetadataType extends CardinalityMetadataSupport ? CardinalityMetadataOpts : unknown) | + (SupportedMetadataType extends OrderMetadataSupport ? OrderMetadataOpts : unknown) | + (SupportedMetadataType extends AvailableOrdersMetadataSupport ? AvailableOrdersMetadataOpts : unknown); export interface CardinalityMetadataOpts { cardinality: 'estimate' | 'exact'; } export interface OrderMetadataOpts { order: true; } export interface AvailableOrdersMetadataOpts { availableOrders: true; } @@ -129,7 +137,7 @@ export interface BaseQuery { /** * Query object that returns bindings. */ -export interface QueryBindings extends BaseQuery, BaseMetadataQuery { +export interface QueryBindings extends BaseQuery, BaseMetadataQuery { resultType: 'bindings'; execute(opts?: QueryExecuteOptions): Promise>; } @@ -137,7 +145,7 @@ export interface QueryBindings extends BaseQuery, BaseMetadataQuery { +export interface QueryQuads extends BaseQuery, BaseMetadataQuery { resultType: 'quads'; execute(opts?: QueryExecuteOptions): Promise>; } @@ -161,7 +169,7 @@ export interface QueryVoid extends BaseQuery { /** * Union type for the different query types. */ -export type Query = QueryBindings | QueryBoolean | QueryQuads | QueryVoid; +export type Query = QueryBindings | QueryBoolean | QueryQuads | QueryVoid; /** * Bindings represents the mapping of variables to RDF values using an immutable Map-like representation. diff --git a/query/filterable.d.ts b/query/filterable.d.ts index 8e854da..f092dec 100644 --- a/query/filterable.d.ts +++ b/query/filterable.d.ts @@ -105,7 +105,7 @@ export interface ExpressionFactory { * instantiated for each variable's binding in the resulting quad stream when * applying the Expression filter. */ -export interface FilterableSource { +export interface FilterableSource { /** * May reject given an unsupported expression. */ @@ -119,5 +119,5 @@ export interface FilterableSource { length?: number; start?: number; }, - ): Promise; + ): Promise>; } diff --git a/query/queryable.d.ts b/query/queryable.d.ts index 6273fff..7f32174 100644 --- a/query/queryable.d.ts +++ b/query/queryable.d.ts @@ -71,14 +71,18 @@ export type Algebra = any; /** * Generic query engine interfaces. * It allow engines to return any type of result object for any type of query. - * @param QueryFormatType The format of the query, either string or algebra object. + * @param QueryFormatTypesAvailable The format of the query, either string or algebra object. * @param SourceType The allowed sources over which queries can be executed. + * @param SupportedMetadataType The allowed metadata types. * @param QueryType The allowed query types. + * @param QueryStringContextType Type of the string-based query context. + * @param QueryAlgebraContextType Type of the algebra-based query context. */ export interface Queryable< QueryFormatTypesAvailable extends string | Algebra, SourceType, - QueryType extends Query, + SupportedMetadataType, + QueryType extends Query, QueryStringContextType extends QueryStringContext, QueryAlgebraContextType extends QueryAlgebraContext, > { From 1a2e38effa4c2788c3791688d7502257b0737f40 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Fri, 4 Feb 2022 09:50:21 +0100 Subject: [PATCH 08/20] Add TODO to merge Stream types --- query/common.d.ts | 1 + stream.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/query/common.d.ts b/query/common.d.ts index 21f3cb4..dc13d3b 100644 --- a/query/common.d.ts +++ b/query/common.d.ts @@ -9,6 +9,7 @@ import * as RDF from '../data-model'; */ export type QuadTermName = 'subject' | 'predicate' | 'object' | 'graph'; +// TODO: merge this with Stream upon the next major change /** * Custom typings for the RDF/JS ResultStream interface as the current * typings restrict the generic param Q to extensions of "BaseQuad", diff --git a/stream.d.ts b/stream.d.ts index bc78df6..2217114 100644 --- a/stream.d.ts +++ b/stream.d.ts @@ -6,6 +6,7 @@ import { EventEmitter } from "events"; import { BaseQuad, Quad, Term } from './data-model'; +// TODO: merge this with ResultStream upon the next major change /** * A quad stream. * This stream is only readable, not writable. From 60ff0707231c49c06b7a0b35c4a64065504ec8ec Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Fri, 4 Feb 2022 09:54:16 +0100 Subject: [PATCH 09/20] Release 1.1.0-next.1 --- .changeset/long-files-look.md | 5 +++++ .changeset/pre.json | 1 + CHANGELOG.md | 6 ++++++ package.json | 2 +- 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/long-files-look.md diff --git a/.changeset/long-files-look.md b/.changeset/long-files-look.md new file mode 100644 index 0000000..d5e9e6d --- /dev/null +++ b/.changeset/long-files-look.md @@ -0,0 +1,5 @@ +--- +"@rdfjs/types": patch +--- + +Make queryable metadata types configurable diff --git a/.changeset/pre.json b/.changeset/pre.json index a2715e8..7b2891b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -6,6 +6,7 @@ }, "changesets": [ "friendly-lies-suffer", + "long-files-look", "seven-shrimps-build", "tough-guests-flash" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 2409a4a..8100a4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # @rdfjs/types +## 1.1.0-next.1 + +### Patch Changes + +- Make queryable metadata types configurable + ## 1.1.0-next.0 ### Minor Changes diff --git a/package.json b/package.json index a8769fc..2c7bed2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rdfjs/types", - "version": "1.1.0-next.0", + "version": "1.1.0-next.1", "license": "MIT", "types": "index.d.ts", "author": { From f64f002dd206f250b5e0652333b820c0128f3728 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 10 Feb 2022 15:38:30 +0100 Subject: [PATCH 10/20] Temporarily remove filterable typings --- query.d.ts | 1 - query/filterable.d.ts | 123 ------------------------------------------ 2 files changed, 124 deletions(-) delete mode 100644 query/filterable.d.ts diff --git a/query.d.ts b/query.d.ts index 399bb4a..9645aa3 100644 --- a/query.d.ts +++ b/query.d.ts @@ -2,7 +2,6 @@ /* https://rdf.js.org/query-spec/ */ export * from './query/common'; -export * from './query/filterable'; export * from './query/queryable'; diff --git a/query/filterable.d.ts b/query/filterable.d.ts deleted file mode 100644 index f092dec..0000000 --- a/query/filterable.d.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* Query Interfaces - Filterable */ -/* https://rdf.js.org/query-spec/ */ - -import * as RDF from '../data-model'; -import { QueryQuads } from './common'; - -/** - * Expression is an abstract interface that represents a generic expression over a stream of quads. - */ -export interface Expression { - /** - * Value that identifies the concrete interface of the expression, - * since the Expression itself is not directly instantiated. - * Possible values include "operator" and "term". - */ - expressionType: string; -} - -/** - * An OperatorExpression is represents an expression that applies a given operator on given sub-expressions. - * - * The WebIDL definition of the Filterable spec contains a list of supported - * operators: https://rdf.js.org/query-spec/#expression-operators - */ -export interface OperatorExpression extends Expression { - - /** - * Contains the constant "operator". - */ - expressionType: 'operator'; - - /** - * Value that identifies an operator. Possible values can be found in the list of operators. - */ - operator: string; - - /** - * Array of Expression's on to which the given operator applies. - * The length of this array depends on the operator. - */ - args: Expression[]; -} - -/** - * A TermExpression is an expression that contains a Term. - */ -export interface TermExpression { - - /** - * The constant "term". - */ - expressionType: 'term'; - - /** - * an RDF Term. - */ - term: RDF.Term; -} - -/** - * ExpressionFactory enables expressions to be created in an idiomatic manner. - */ -export interface ExpressionFactory { - - /** - * Creates a new OperatorExpression instance for the given operator and array of arguments. - */ - operatorExpression(operator: string, args: Expression[]): OperatorExpression; - - /** - * Creates a new TermExpression instance for the given term. - */ - termExpression(term: RDF.Term): TermExpression; -} - -/** - * A FilterableSource is an object that emits quads based on a quad pattern and filter expression. - * - * The emitted quads can be directly contained in this object, or they can be generated on the fly. - * - * FilterableSource is not necessarily an extension of the RDF/JS Source - * interface, but implementers MAY decide to implement both at the same time. - * - * matchExpression() Returns a QueryQuads future that can produce a quad - * stream that contains all quads matching the quad pattern and the expression. - * - * When a Term parameter is defined, and is a NamedNode, Literal or BlankNode, - * it must match each produced quad, according to the Quad.equals semantics. - * When a Term parameter is a Variable, or it is undefined, it acts as a - * wildcard, and can match with any Term. - * - * NOTES: - * - When matching with graph set to undefined or null it MUST match all the - * graphs (sometimes called the union graph). To match only the default graph - * set graph to a DefaultGraph. - * - When an Expression parameter is defined, the complete quad stream is - * filtered according to this expression. When it is undefined, no filter is - * applied. - * - * If parameters of type Variable with an equal variable name are in place, - * then the corresponding quad components in the resulting quad stream MUST be - * equal. - * Expression's MAY contain Variable Term's. If their variable names are equal - * to Variable's in the given quad pattern, then the Expression MUST be - * instantiated for each variable's binding in the resulting quad stream when - * applying the Expression filter. - */ -export interface FilterableSource { - /** - * May reject given an unsupported expression. - */ - matchExpression( - subject?: RDF.Term, - predicate?: RDF.Term, - obj?: RDF.Term, - graph?: RDF.Term, - expression?: Expression, - opts?: { - length?: number; - start?: number; - }, - ): Promise>; -} From d32e71cdc69e47a74c7d150c34fcddac9bbb5a88 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 14 Feb 2022 09:33:18 +0100 Subject: [PATCH 11/20] Also allow Bindings interaction via strings --- query/common.d.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/query/common.d.ts b/query/common.d.ts index dc13d3b..f2a63cf 100644 --- a/query/common.d.ts +++ b/query/common.d.ts @@ -185,31 +185,31 @@ export interface Bindings extends Iterable<[RDF.Variable, RDF.Term]> { type: 'bindings'; /** * Check if a binding exist for the given variable. - * @param key A variable + * @param key A variable term or string. If it is a string, no `?` prefix must be given. */ - has: (key: RDF.Variable) => boolean; + has: (key: RDF.Variable | string) => boolean; /** * Obtain the binding value for the given variable. - * @param key A variable + * @param key A variable term or string. If it is a string, no `?` prefix must be given. */ - get: (key: RDF.Variable) => RDF.Term | undefined; + get: (key: RDF.Variable | string) => RDF.Term | undefined; /** * Create a new Bindings object by adding the given variable and value mapping. * * If the variable already exists in the binding, then the existing mapping is overwritten. * - * @param key The variable key. + * @param key The variable key term or string. If it is a string, no `?` prefix must be given. * @param value The value. */ - set: (key: RDF.Variable, value: RDF.Term) => Bindings; + set: (key: RDF.Variable | string, value: RDF.Term) => Bindings; /** * Create a new Bindings object by removing the given variable. * * If the variable does not exist in the binding, a copy of the Bindings object is returned. * - * @param key The variable key. + * @param key The variable key term or string. If it is a string, no `?` prefix must be given. */ - delete: (key: RDF.Variable) => Bindings; + delete: (key: RDF.Variable | string) => Bindings; /** * Obtain all variables for which mappings exist. */ From 2b8f571268c051b2c222027f55f18ee40f87df2e Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 14 Feb 2022 09:33:45 +0100 Subject: [PATCH 12/20] Fix typo in SparqlQueryable tsdoc --- query/queryable.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/query/queryable.d.ts b/query/queryable.d.ts index 7f32174..b51a40f 100644 --- a/query/queryable.d.ts +++ b/query/queryable.d.ts @@ -102,7 +102,7 @@ export interface Queryable< } /** - * SPARQL-constrainted query interface. + * SPARQL-constrained query interface. * * This interface guarantees that result objects are of the expected type as defined by the SPARQL spec. */ From 762a892eaa7d046090528bb6bec0abf8bb1254b0 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Mon, 14 Feb 2022 11:39:19 +0100 Subject: [PATCH 13/20] Add tests for queryable interface --- rdf-js-query-tests.ts | 82 +++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 6 ++-- 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 rdf-js-query-tests.ts diff --git a/rdf-js-query-tests.ts b/rdf-js-query-tests.ts new file mode 100644 index 0000000..4e02ebc --- /dev/null +++ b/rdf-js-query-tests.ts @@ -0,0 +1,82 @@ +/* eslint-disable no-case-declarations */ +import { + DataFactory, + BindingsFactory, + Bindings, + Term, + Queryable, + SparqlResultSupport, + MetadataOpts, + QueryStringContext, + QueryAlgebraContext, + AllMetadataSupport, Query, Variable, ResultStream, Quad, SparqlQueryable, BindingsResultSupport, QuadsResultSupport +} from "."; + +function test_bindings() { + const df: DataFactory = {}; + const bf: BindingsFactory = {}; + + const b1: Bindings = bf.bindings([ + [ df.variable!('varA'), df.namedNode('ex:a'), ], + [ df.variable!('varB'), df.literal('B'), ], + ]); + + const valueA: Term | undefined = b1.get('varA'); + const valueB: Term | undefined = b1.get(df.variable!('varB')); + + const b2: Bindings = b1 + .set('varA', df.namedNode('ex:2')) + .delete('varB') + .set(df.variable!('varB'), df.literal('B2')); + + for (const [ key, value ] of b2) { + const keytype: 'Variable' = key.termType; + const valuetype: string = value.termType; + } + for (const key of b2.keys()) { + const type: 'Variable' = key.termType; + } +} + +async function test_queryable() { + const engine: Queryable, QueryStringContext, QueryAlgebraContext> = {}; + + const query: Query = await engine.query('SELECT * WHERE { ... }'); + switch (query.resultType) { + case 'bindings': + const metadata = await query.metadata(); + const variables: Variable[] = metadata.variables; + const bindings: ResultStream = await query.execute(); + bindings.on('data', (bindings: Bindings) => console.log(bindings)); + break; + case 'quads': + const quads: ResultStream = await query.execute(); + break; + case 'boolean': + const bool: boolean = await query.execute(); + break; + case 'void': + const done: void = await query.execute(); + break; + } +} + +async function test_sparqlqueryable() { + const engine: SparqlQueryable, QueryAlgebraContext, SparqlResultSupport> = {}; + + const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); + const quads: ResultStream = await engine.queryQuads('CONSTRUCT WHERE { ... }'); + const bool: boolean = await engine.queryBoolean('ASK WHERE { ... }'); + const done: void = await engine.queryVoid('INSERT WHERE { ... }'); +} + +async function test_sparqlqueryable_partial() { + const engine: SparqlQueryable, QueryAlgebraContext, BindingsResultSupport & QuadsResultSupport> = {}; + + const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); + const quads: ResultStream = await engine.queryQuads('CONSTRUCT WHERE { ... }'); + // @ts-ignore + const bool: boolean = await engine.queryBoolean('ASK WHERE { ... }'); // Unsupported + // @ts-ignore + const done: void = await engine.queryVoid('INSERT WHERE { ... }'); // Unsupported +} diff --git a/tsconfig.json b/tsconfig.json index f7c711a..e996f9e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,10 +11,12 @@ "strictNullChecks": true, "strictFunctionTypes": true, "noEmit": true, - "forceConsistentCasingInFileNames": true + "forceConsistentCasingInFileNames": true, + "downlevelIteration": true }, "files": [ "index.d.ts", - "rdf-js-tests.ts" + "rdf-js-tests.ts", + "rdf-js-query-tests.ts" ] } From 3b7ba4020616cd12d1b2dacfe700899da702dc17 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Sat, 12 Feb 2022 19:06:19 +0100 Subject: [PATCH 14/20] modularizes Queryable and SparqlQueryable interfaces --- query/queryable.d.ts | 117 +++++++++++++++++++++++++------------------ 1 file changed, 68 insertions(+), 49 deletions(-) diff --git a/query/queryable.d.ts b/query/queryable.d.ts index b51a40f..c1ebf5d 100644 --- a/query/queryable.d.ts +++ b/query/queryable.d.ts @@ -5,13 +5,9 @@ import * as RDF from '../data-model'; import { Bindings, Query, ResultStream } from './common'; /** - * Context objects provide a way to pass additional bits information to the query engine when executing a query. + * Context properties provide a way to pass additional bits information to the query engine when executing a query. */ -export interface QueryContext { - /** - * An array of data sources the query engine must use. - */ - sources?: [SourceType, ...SourceType[]]; +export interface QueryContext { /** * The date that should be used by SPARQL operations such as NOW(). */ @@ -23,9 +19,9 @@ export interface QueryContext { } /** - * Context object in the case the passed query is a string. + * Context properties in the case the passed query is a string. */ -export interface QueryStringContext extends QueryContext { +export interface QueryStringContext extends QueryContext { /** * The format in which the query string is defined. * Defaults to { language: 'sparql', version: '1.1' } @@ -38,9 +34,19 @@ export interface QueryStringContext extends QueryContext } /** - * Context object in the case the passed query is an algebra object. + * Context properties in the case the passed query is an algebra object. */ -export type QueryAlgebraContext = QueryContext; +export type QueryAlgebraContext = QueryContext; + +/** + * Context properties for engines that can query upon dynamic sets of sources. + */ +export interface QuerySourceContext { + /** + * An array of data sources the query engine must use. + */ + sources: [SourceType, ...SourceType[]]; +} /** * Represents a specific query format @@ -70,21 +76,13 @@ export type Algebra = any; /** * Generic query engine interfaces. - * It allow engines to return any type of result object for any type of query. - * @param QueryFormatTypesAvailable The format of the query, either string or algebra object. - * @param SourceType The allowed sources over which queries can be executed. + * It allow engines to return any type of result object for string queries. * @param SupportedMetadataType The allowed metadata types. - * @param QueryType The allowed query types. * @param QueryStringContextType Type of the string-based query context. - * @param QueryAlgebraContextType Type of the algebra-based query context. */ -export interface Queryable< - QueryFormatTypesAvailable extends string | Algebra, - SourceType, +export interface StringQueryable< SupportedMetadataType, - QueryType extends Query, - QueryStringContextType extends QueryStringContext, - QueryAlgebraContextType extends QueryAlgebraContext, + QueryStringContextType extends QueryStringContext = QueryStringContext, > { /** * Initiate a given query. @@ -95,10 +93,29 @@ export interface Queryable< * * @see Query */ - query( - query: QueryFormatType, - context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, - ): Promise; + query(query: string, context?: QueryStringContextType): Promise>; +} + +/** + * Generic query engine interfaces. + * It allow engines to return any type of result object for Algebra queries. + * @param SupportedMetadataType The allowed metadata types. + * @param QueryStringContextType Type of the algebra-based query context. + */ + export interface AlgebraQueryable< + SupportedMetadataType, + QueryAlgebraContextType extends QueryAlgebraContext = QueryAlgebraContext, +> { + /** + * Initiate a given query. + * + * This will produce a future to a query result, which has to be executed to obtain the query results. + * + * This can reject given an unsupported or invalid query. + * + * @see Query + */ + query(query: Algebra, context?: QueryAlgebraContextType): Promise>; } /** @@ -106,38 +123,40 @@ export interface Queryable< * * This interface guarantees that result objects are of the expected type as defined by the SPARQL spec. */ -export type SparqlQueryable< - QueryFormatTypesAvailable extends string | Algebra, - SourceType, - QueryStringContextType extends QueryStringContext, - QueryAlgebraContextType extends QueryAlgebraContext, - SupportedResultType, -> = unknown +export type StringSparqlQueryable = unknown & (SupportedResultType extends BindingsResultSupport ? { - queryBindings( - query: QueryFormatType, - context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, - ): Promise>; + queryBindings(query: string, context?: QueryStringContextType): Promise>; } : unknown) & (SupportedResultType extends BooleanResultSupport ? { - queryBoolean( - query: QueryFormatType, - context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, - ): Promise; + queryBoolean(query: string, context?: QueryStringContextType): Promise; } : unknown) & (SupportedResultType extends QuadsResultSupport ? { - queryQuads( - query: QueryFormatType, - context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, - ): Promise>; + queryQuads(query: string, context?: QueryStringContextType): Promise>; } : unknown) & (SupportedResultType extends VoidResultSupport ? { - queryVoid( - query: QueryFormatType, - context?: QueryFormatType extends string ? QueryStringContextType : QueryAlgebraContextType, - ): Promise; + queryVoid(query: string, context?: QueryStringContextType): Promise; +} : unknown) +; + +/** + * SPARQL-constrainted query interface. + * + * This interface guarantees that result objects are of the expected type as defined by the SPARQL spec. + */ + export type AlgebraSparqlQueryable = unknown + & (SupportedResultType extends BindingsResultSupport ? { + queryBindings(query: Algebra, context?: QueryAlgebraContextType): Promise>; +} : unknown) + & (SupportedResultType extends BooleanResultSupport ? { + queryBoolean(query: Algebra, context?: QueryAlgebraContextType): Promise; +} : unknown) + & (SupportedResultType extends QuadsResultSupport ? { + queryQuads(query: Algebra, context?: QueryAlgebraContextType): Promise>; +} : unknown) + & (SupportedResultType extends VoidResultSupport ? { + queryVoid(query: Algebra, context?: QueryAlgebraContextType): Promise; } : unknown) - ; +; export type SparqlResultSupport = BindingsResultSupport & VoidResultSupport & QuadsResultSupport & BooleanResultSupport; export type BindingsResultSupport = { bindings: true }; From 61f5a6ae0519434742de98cfd3d7d3dced1104df Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Mon, 14 Feb 2022 12:32:22 +0100 Subject: [PATCH 15/20] mentions the difference between queries provided as strings and queries provided as algebra objects in comments --- query/queryable.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/query/queryable.d.ts b/query/queryable.d.ts index c1ebf5d..7ea0988 100644 --- a/query/queryable.d.ts +++ b/query/queryable.d.ts @@ -85,7 +85,7 @@ export interface StringQueryable< QueryStringContextType extends QueryStringContext = QueryStringContext, > { /** - * Initiate a given query. + * Initiate a given query provided as a string. * * This will produce a future to a query result, which has to be executed to obtain the query results. * @@ -107,7 +107,7 @@ export interface StringQueryable< QueryAlgebraContextType extends QueryAlgebraContext = QueryAlgebraContext, > { /** - * Initiate a given query. + * Initiate a given query provided as an Algebra object. * * This will produce a future to a query result, which has to be executed to obtain the query results. * @@ -119,7 +119,7 @@ export interface StringQueryable< } /** - * SPARQL-constrained query interface. + * SPARQL-constrained query interface for queries provided as strings. * * This interface guarantees that result objects are of the expected type as defined by the SPARQL spec. */ @@ -139,7 +139,7 @@ export type StringSparqlQueryable Date: Mon, 14 Feb 2022 12:51:45 +0100 Subject: [PATCH 16/20] adds support for multiple algebra types, drops conceptual dependency on a specific algebra implementation --- query/queryable.d.ts | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/query/queryable.d.ts b/query/queryable.d.ts index 7ea0988..7a98c86 100644 --- a/query/queryable.d.ts +++ b/query/queryable.d.ts @@ -67,13 +67,6 @@ export interface QueryFormat { extensions?: string[]; } -/** - * Placeholder to represent SPARQL Algebra trees. - * Algebra typings are TBD. Reference implementations include: - * - https://www.npmjs.com/package/sparqlalgebrajs - */ -export type Algebra = any; - /** * Generic query engine interfaces. * It allow engines to return any type of result object for string queries. @@ -99,10 +92,12 @@ export interface StringQueryable< /** * Generic query engine interfaces. * It allow engines to return any type of result object for Algebra queries. + * @param AlgebraType The supported algebra types. * @param SupportedMetadataType The allowed metadata types. * @param QueryStringContextType Type of the algebra-based query context. */ export interface AlgebraQueryable< + AlgebraType, SupportedMetadataType, QueryAlgebraContextType extends QueryAlgebraContext = QueryAlgebraContext, > { @@ -115,7 +110,7 @@ export interface StringQueryable< * * @see Query */ - query(query: Algebra, context?: QueryAlgebraContextType): Promise>; + query(query: AlgebraType, context?: QueryAlgebraContextType): Promise>; } /** @@ -143,18 +138,18 @@ export type StringSparqlQueryable = unknown + export type AlgebraSparqlQueryable = unknown & (SupportedResultType extends BindingsResultSupport ? { - queryBindings(query: Algebra, context?: QueryAlgebraContextType): Promise>; + queryBindings(query: AlgebraType, context?: QueryAlgebraContextType): Promise>; } : unknown) & (SupportedResultType extends BooleanResultSupport ? { - queryBoolean(query: Algebra, context?: QueryAlgebraContextType): Promise; + queryBoolean(query: AlgebraType, context?: QueryAlgebraContextType): Promise; } : unknown) & (SupportedResultType extends QuadsResultSupport ? { - queryQuads(query: Algebra, context?: QueryAlgebraContextType): Promise>; + queryQuads(query: AlgebraType, context?: QueryAlgebraContextType): Promise>; } : unknown) & (SupportedResultType extends VoidResultSupport ? { - queryVoid(query: Algebra, context?: QueryAlgebraContextType): Promise; + queryVoid(query: AlgebraType, context?: QueryAlgebraContextType): Promise; } : unknown) ; From a666eabfcdd0f1855e986a47a9c87d6d7d166858 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Mon, 14 Feb 2022 12:57:00 +0100 Subject: [PATCH 17/20] updates test to use modularized interfaces --- rdf-js-query-tests.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/rdf-js-query-tests.ts b/rdf-js-query-tests.ts index 4e02ebc..dfbb72c 100644 --- a/rdf-js-query-tests.ts +++ b/rdf-js-query-tests.ts @@ -4,12 +4,21 @@ import { BindingsFactory, Bindings, Term, - Queryable, + StringQueryable, + AlgebraQueryable, SparqlResultSupport, MetadataOpts, QueryStringContext, QueryAlgebraContext, - AllMetadataSupport, Query, Variable, ResultStream, Quad, SparqlQueryable, BindingsResultSupport, QuadsResultSupport + AllMetadataSupport, + Query, + Variable, + ResultStream, + Quad, + StringSparqlQueryable, + AlgebraSparqlQueryable, + BindingsResultSupport, + QuadsResultSupport, } from "."; function test_bindings() { @@ -39,7 +48,7 @@ function test_bindings() { } async function test_queryable() { - const engine: Queryable, QueryStringContext, QueryAlgebraContext> = {}; + const engine: StringQueryable = {}; const query: Query = await engine.query('SELECT * WHERE { ... }'); switch (query.resultType) { @@ -62,7 +71,7 @@ async function test_queryable() { } async function test_sparqlqueryable() { - const engine: SparqlQueryable, QueryAlgebraContext, SparqlResultSupport> = {}; + const engine: StringSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); const quads: ResultStream = await engine.queryQuads('CONSTRUCT WHERE { ... }'); @@ -71,7 +80,7 @@ async function test_sparqlqueryable() { } async function test_sparqlqueryable_partial() { - const engine: SparqlQueryable, QueryAlgebraContext, BindingsResultSupport & QuadsResultSupport> = {}; + const engine: StringSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); const quads: ResultStream = await engine.queryQuads('CONSTRUCT WHERE { ... }'); From 186c2b7463b06c7f96bc398c91883f635ab40303 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Mon, 14 Feb 2022 13:05:44 +0100 Subject: [PATCH 18/20] adds tests for Algebra interfaces --- rdf-js-query-tests.ts | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/rdf-js-query-tests.ts b/rdf-js-query-tests.ts index dfbb72c..91626fb 100644 --- a/rdf-js-query-tests.ts +++ b/rdf-js-query-tests.ts @@ -47,7 +47,7 @@ function test_bindings() { } } -async function test_queryable() { +async function test_stringqueryable() { const engine: StringQueryable = {}; const query: Query = await engine.query('SELECT * WHERE { ... }'); @@ -70,7 +70,7 @@ async function test_queryable() { } } -async function test_sparqlqueryable() { +async function test_stringsparqlqueryable() { const engine: StringSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); @@ -79,7 +79,20 @@ async function test_sparqlqueryable() { const done: void = await engine.queryVoid('INSERT WHERE { ... }'); } -async function test_sparqlqueryable_partial() { +async function test_algebrasparqlqueryable() { + interface AlgebraType { mock: 'algebra' }; + const engine: AlgebraSparqlQueryable = {}; + + const bindings: ResultStream = await engine.queryBindings({ mock: 'algebra' }); + const quads: ResultStream = await engine.queryQuads({ mock: 'algebra' }); + const bool: boolean = await engine.queryBoolean({ mock: 'algebra' }); + const done: void = await engine.queryVoid({ mock: 'algebra' }); + + // @ts-ignore + await engine.queryBoolean('ASK WHERE { ... }'); // Query type doesn't match AlgebraType +} + +async function test_stringsparqlqueryable_partial() { const engine: StringSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings('SELECT * WHERE { ... }'); @@ -89,3 +102,15 @@ async function test_sparqlqueryable_partial() { // @ts-ignore const done: void = await engine.queryVoid('INSERT WHERE { ... }'); // Unsupported } + +async function test_algebrasparqlqueryable_partial() { + interface AlgebraType { mock: 'algebra' }; + const engine: AlgebraSparqlQueryable = {}; + + const bindings: ResultStream = await engine.queryBindings({ mock: 'algebra' }); + const quads: ResultStream = await engine.queryQuads({ mock: 'algebra' }); + // @ts-ignore + const bool: boolean = await engine.queryBoolean({ mock: 'algebra' }); // Unsupported + // @ts-ignore + const done: void = await engine.queryVoid({ mock: 'algebra' }); // Unsupported +} From d47c8b12f8856210c3c7fb2c9d0cfd7ead0a7d1f Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Mon, 14 Feb 2022 13:13:52 +0100 Subject: [PATCH 19/20] drops redundant semicolons --- rdf-js-query-tests.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rdf-js-query-tests.ts b/rdf-js-query-tests.ts index 91626fb..fc67df0 100644 --- a/rdf-js-query-tests.ts +++ b/rdf-js-query-tests.ts @@ -80,7 +80,7 @@ async function test_stringsparqlqueryable() { } async function test_algebrasparqlqueryable() { - interface AlgebraType { mock: 'algebra' }; + interface AlgebraType { mock: 'algebra' } const engine: AlgebraSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings({ mock: 'algebra' }); @@ -104,7 +104,7 @@ async function test_stringsparqlqueryable_partial() { } async function test_algebrasparqlqueryable_partial() { - interface AlgebraType { mock: 'algebra' }; + interface AlgebraType { mock: 'algebra' } const engine: AlgebraSparqlQueryable = {}; const bindings: ResultStream = await engine.queryBindings({ mock: 'algebra' }); From 09ad6310c9a56058448397ed5dcca39fd43394df Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Thu, 17 Feb 2022 11:42:23 +0100 Subject: [PATCH 20/20] Exit prerelease mode --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 7b2891b..1d976b1 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "@rdfjs/types": "1.0.1"