Skip to content
This repository has been archived by the owner on Sep 19, 2023. It is now read-only.

Add SPARQL-star operators and comparison #179

Merged
merged 1 commit into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/expressions/Expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ export type SpecialOperatorExpression = IExpressionProps & {

// TODO: Create alias Term = TermExpression
export function asTermType(type: string): TermType | undefined {
if (type === 'namedNode' || type === 'literal' || type === 'blankNode') {
if (type === 'namedNode' || type === 'literal' || type === 'blankNode' || type === 'quad') {
return type;
}
return undefined;
}
export type TermType = 'namedNode' | 'literal' | 'blankNode';
export type TermType = 'namedNode' | 'literal' | 'blankNode' | 'quad';
export type TermExpression = IExpressionProps & {
expressionType: ExpressionType.Term;
termType: TermType;
Expand Down
42 changes: 42 additions & 0 deletions lib/expressions/Term.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type * as RDF from '@rdfjs/types';
import { DataFactory } from 'rdf-data-factory';
import { TermTransformer } from '../transformers/TermTransformer';
import * as C from '../util/Consts';
import { TypeAlias, TypeURL } from '../util/Consts';

Expand Down Expand Up @@ -65,6 +66,47 @@ export class BlankNode extends Term {
}
}

// Quads -----------------------------------------------------------------
export class Quad extends Term {
public termType: TermType = 'quad';
private readonly transformer: TermTransformer;
private readonly valueTerm: RDF.BaseQuad;

public constructor(input: RDF.BaseQuad, superTypeProvider: ISuperTypeProvider) {
super();
this.transformer = new TermTransformer(superTypeProvider);
this.valueTerm = input;
}

public toRDF(): RDF.BaseQuad {
return this.valueTerm;
}

public get subject(): Term {
return this.transformer.transformRDFTermUnsafe(this.RDFsubject);
}

public get predicate(): Term {
return this.transformer.transformRDFTermUnsafe(this.RDFpredicate);
}

public get object(): Term {
return this.transformer.transformRDFTermUnsafe(this.RDFobject);
}

public get RDFsubject(): RDF.Term {
return this.toRDF().subject;
}

public get RDFpredicate(): RDF.Term {
return this.toRDF().predicate;
}

public get RDFobject(): RDF.Term {
return this.toRDF().object;
}
}

// Literals-- -----------------------------------------------------------------
export function isLiteralTermExpression(expr: TermExpression): Literal<any> | undefined {
if (expr.termType === 'literal') {
Expand Down
11 changes: 10 additions & 1 deletion lib/functions/Helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type * as RDF from '@rdfjs/types';
import { DataFactory } from 'rdf-data-factory';
import type { ICompleteSharedContext } from '../evaluators/evaluatorHelpers/BaseExpressionEvaluator';
import type { Literal, TermExpression } from '../expressions';
import type { Literal, TermExpression, Quad } from '../expressions';
import * as E from '../expressions';
import { NonLexicalLiteral } from '../expressions';
import * as C from '../util/Consts';
Expand Down Expand Up @@ -118,6 +118,15 @@ export class Builder {
);
}

public onTerm3(op: (context: ICompleteSharedContext) => (t1: Term, t2: Term, t3: Term) => Term): Builder {
return this.set([ 'term', 'term', 'term' ],
context => ([ t1, t2, t3 ]: [Term, Term, Term]) => op(context)(t1, t2, t3));
}

public onQuad1(op: (context: ICompleteSharedContext) => (term: Term & Quad) => Term): Builder {
return this.set([ 'quad' ], context => ([ term ]: [Term & Quad]) => op(context)(term));
}

public onLiteral1<T>(op: (context: ICompleteSharedContext) => (lit: E.Literal<T>) => Term,
addInvalidHandling = true): Builder {
return this.set(
Expand Down
136 changes: 134 additions & 2 deletions lib/functions/RegularFunctions.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type * as RDF from '@rdfjs/types';
import { BigNumber } from 'bignumber.js';
import { sha1, sha256, sha384, sha512 } from 'hash.js';
import { DataFactory } from 'rdf-data-factory';
Expand All @@ -7,9 +8,10 @@ import * as uuid from 'uuid';

import type { ICompleteSharedContext } from '../evaluators/evaluatorHelpers/BaseExpressionEvaluator';
import * as E from '../expressions';
import type { Quad } from '../expressions';
import { TermTransformer } from '../transformers/TermTransformer';
import * as C from '../util/Consts';
import { TypeAlias, TypeURL } from '../util/Consts';
import { RegularOperator, TypeAlias, TypeURL } from '../util/Consts';
import type { IDayTimeDurationRepresentation } from '../util/DateTimeHelpers';
import {
dayTimeDurationsToSeconds,
Expand All @@ -24,12 +26,14 @@ import {
yearMonthDurationsToMonths,
} from '../util/DateTimeHelpers';
import * as Err from '../util/Errors';
import { orderTypes } from '../util/Ordering';
import { addDurationToDateTime, elapsedDuration } from '../util/SpecAlgos';
import type { IOverloadedDefinition } from './Core';
import { RegularFunction } from './Core';
import { bool, decimal, declare, double, integer, langString, string } from './Helpers';
import * as X from './XPathFunctions';

const DF = new DataFactory();
const DF = new DataFactory<RDF.BaseQuad>();

type Term = E.TermExpression;

Expand Down Expand Up @@ -185,6 +189,18 @@ const equality = {
from: [ TypeURL.XSD_DATE_TIME, TypeURL.XSD_DATE_TIME ],
to: [ TypeURL.XSD_DATE, TypeURL.XSD_DATE ],
})
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => {
const op: RegularFunction = new RegularFunction(RegularOperator.EQUAL, equality);
Copy link
Member

Choose a reason for hiding this comment

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

Could we already use the cast made by regularFunctions in index.ts

Copy link
Member Author

Choose a reason for hiding this comment

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

We can't due that due to the circular dependency.

return bool(
(<E.BooleanLiteral> op.apply([ (<Quad> left).subject, (<Quad> right).subject ], context)).coerceEBV() &&
(<E.BooleanLiteral> op.apply([ (<Quad> left).predicate, (<Quad> right).predicate ], context)).coerceEBV() &&
(<E.BooleanLiteral> op.apply([ (<Quad> left).object, (<Quad> right).object ], context)).coerceEBV(),
);
},
false,
Copy link
Member

Choose a reason for hiding this comment

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

Why should we not handle invalid lexicals here?

Copy link
Member

Choose a reason for hiding this comment

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

(and in the other functions)

Copy link
Member Author

Choose a reason for hiding this comment

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

Honestly, I'm not sure what this flag does exactly. The documentation didn't help me out much.
What would change when setting this to true?

Copy link
Member

Choose a reason for hiding this comment

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

The flag (when true = default value) makes sure that the current implementation throws an error when passed an invalid literal. (some functions don't want this, since they handle invalid literals themselves (see equality of terms))
I don't think this implementation will run into trouble so we can keep it as is.

)
.set(
[ 'term', 'term' ],
() => ([ left, right ]) => bool(RDFTermEqual(left, right)),
Expand Down Expand Up @@ -222,6 +238,18 @@ const inequality = {
.booleanTest(() => (left, right) => left !== right)
.dateTimeTest(({ defaultTimeZone }) => (left, right) =>
toUTCDate(left, defaultTimeZone).getTime() !== toUTCDate(right, defaultTimeZone).getTime())
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => {
const op: RegularFunction = new RegularFunction(RegularOperator.NOT_EQUAL, inequality);
return bool(
(<E.BooleanLiteral> op.apply([ (<Quad> left).subject, (<Quad> right).subject ], context)).coerceEBV() ||
(<E.BooleanLiteral> op.apply([ (<Quad> left).predicate, (<Quad> right).predicate ], context)).coerceEBV() ||
(<E.BooleanLiteral> op.apply([ (<Quad> left).object, (<Quad> right).object ], context)).coerceEBV(),
);
},
false,
)
.set(
[ 'term', 'term' ],
() => ([ left, right ]) => bool(!RDFTermEqual(left, right)),
Expand Down Expand Up @@ -250,6 +278,11 @@ const lesserThan = {
.numberTest(() => (left, right) => left < right)
.stringTest(() => (left, right) => left.localeCompare(right) === -1)
.booleanTest(() => (left, right) => left < right)
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => bool(orderTypes(left.toRDF(), right.toRDF(), true) === -1),
false,
)
.dateTimeTest(({ defaultTimeZone }) => (left, right) =>
toUTCDate(left, defaultTimeZone).getTime() < toUTCDate(right, defaultTimeZone).getTime())
.copy({
Expand Down Expand Up @@ -281,6 +314,11 @@ const greaterThan = {
.numberTest(() => (left, right) => left > right)
.stringTest(() => (left, right) => left.localeCompare(right) === 1)
.booleanTest(() => (left, right) => left > right)
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => bool(orderTypes(left.toRDF(), right.toRDF(), true) === 1),
false,
)
.dateTimeTest(({ defaultTimeZone }) => (left, right) =>
toUTCDate(left, defaultTimeZone).getTime() > toUTCDate(right, defaultTimeZone).getTime())
.copy({
Expand Down Expand Up @@ -312,6 +350,18 @@ const lesserThanEqual = {
.numberTest(() => (left, right) => left <= right)
.stringTest(() => (left, right) => left.localeCompare(right) !== 1)
.booleanTest(() => (left, right) => left <= right)
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => {
const opEq: RegularFunction = new RegularFunction(RegularOperator.EQUAL, equality);
const opLt: RegularFunction = new RegularFunction(RegularOperator.LT, lesserThan);
return bool(
(<E.BooleanLiteral> opEq.apply([ left, right ], context)).coerceEBV() ||
(<E.BooleanLiteral> opLt.apply([ left, right ], context)).coerceEBV(),
);
},
false,
)
.dateTimeTest(({ defaultTimeZone }) => (left, right) =>
toUTCDate(left, defaultTimeZone).getTime() <= toUTCDate(right, defaultTimeZone).getTime())
.set([ TypeURL.XSD_YEAR_MONTH_DURATION, TypeURL.XSD_YEAR_MONTH_DURATION ], () =>
Expand Down Expand Up @@ -340,6 +390,18 @@ const greaterThanEqual = {
.numberTest(() => (left, right) => left >= right)
.stringTest(() => (left, right) => left.localeCompare(right) !== -1)
.booleanTest(() => (left, right) => left >= right)
.set(
[ 'quad', 'quad' ],
context => ([ left, right ]) => {
const opEq: RegularFunction = new RegularFunction(RegularOperator.EQUAL, equality);
const opGt: RegularFunction = new RegularFunction(RegularOperator.GT, greaterThan);
return bool(
(<E.BooleanLiteral> opEq.apply([ left, right ], context)).coerceEBV() ||
(<E.BooleanLiteral> opGt.apply([ left, right ], context)).coerceEBV(),
);
},
false,
)
.dateTimeTest(({ defaultTimeZone }) => (left, right) =>
toUTCDate(left, defaultTimeZone).getTime() >= toUTCDate(right, defaultTimeZone).getTime())
.set([ TypeURL.XSD_YEAR_MONTH_DURATION, TypeURL.XSD_YEAR_MONTH_DURATION ], () =>
Expand Down Expand Up @@ -1013,6 +1075,66 @@ const SHA512 = {
.collect(),
};

// ----------------------------------------------------------------------------
// Functions for quoted triples
// https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#triple-function
// ----------------------------------------------------------------------------

/**
* https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#triple-function
*/
const triple = {
arity: 3,
overloads: declare(C.RegularOperator.TRIPLE)
.onTerm3(
context => (...args) => new E.Quad(
DF.quad(args[0].toRDF(), args[1].toRDF(), args[2].toRDF()),
context.superTypeProvider,
),
)
.collect(),
};

/**
* https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#subject
*/
const subject = {
arity: 1,
overloads: declare(C.RegularOperator.SUBJECT)
.onQuad1(() => quad => quad.subject)
.collect(),
};

/**
* https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#predicate
*/
const predicate = {
arity: 1,
overloads: declare(C.RegularOperator.PREDICATE)
.onQuad1(() => quad => quad.predicate)
.collect(),
};

/**
* https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#object
*/
const object = {
arity: 1,
overloads: declare(C.RegularOperator.OBJECT)
.onQuad1(() => quad => quad.object)
.collect(),
};

/**
* https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#istriple
*/
const istriple = {
arity: 1,
overloads: declare(C.RegularOperator.IS_TRIPLE)
.onTerm1(() => term => bool(term.termType === 'quad'))
.collect(),
};

// End definitions.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -1112,4 +1234,14 @@ export const definitions: Record<C.RegularOperator, IOverloadedDefinition> = {
sha256: SHA256,
sha384: SHA384,
sha512: SHA512,

// --------------------------------------------------------------------------
// Functions for quoted triples
// https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#triple-function
// --------------------------------------------------------------------------
triple,
subject,
predicate,
object,
istriple,
};
2 changes: 2 additions & 0 deletions lib/transformers/TermTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ export class TermTransformer implements ITermTransformer {
return new E.NamedNode(term.term.value);
case 'BlankNode':
return new E.BlankNode(term.term.value);
case 'Quad':
return new E.Quad(term.term, this.superTypeProvider);
default:
throw new Err.InvalidTermType(term);
}
Expand Down
8 changes: 8 additions & 0 deletions lib/util/Consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,14 @@ export enum RegularOperator {
// XPath Constructor functions
// https://www.w3.org/TR/sparql11-query/#FunctionMapping
// See Named Operators

// Functions for quoted triples
// https://w3c.github.io/rdf-star/cg-spec/editors_draft.html#triple-function
TRIPLE = 'triple',
SUBJECT = 'subject',
PREDICATE = 'predicate',
OBJECT = 'object',
IS_TRIPLE = 'istriple',
}

export enum SpecialOperator {
Expand Down
9 changes: 9 additions & 0 deletions lib/util/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,15 @@ export class InvalidArgumentTypes extends ExpressionError {
}
}

/**
* Terms were being compared that are not supported.
*/
export class InvalidCompareArgumentTypes extends ExpressionError {
public constructor(public arg0: RDF.Term, public arg1: RDF.Term) {
super(`Compared argument types are supported: '${arg0.termType}' and '${arg1.termType}'`);
}
}

/**
* An invalid typecast happened.
*/
Expand Down
Loading