From b962f59034bdc0997880b15cc37d367115094e89 Mon Sep 17 00:00:00 2001 From: Ruben Taelman Date: Wed, 26 Jan 2022 09:30:37 +0100 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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": {