blob: 5b4b6157be33ea4114126e47f7b9c7ae47fd97d5 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* @author Jorge Bay Gondra
*/
import { Graph } from '../structure/graph.js';
import { TraversalStrategies } from './traversal-strategy.js';
import GremlinLang from './gremlin-lang.js';
const itemDone = Object.freeze({ value: null, done: true });
const asyncIteratorSymbol = Symbol.asyncIterator || Symbol('@@asyncIterator');
export class Traversal {
results: any[] | null = null;
sideEffects?: any = null;
private _traversalStrategiesPromise: Promise<void> | null = null;
private _resultsIteratorIndex = 0;
constructor(
public graph: Graph | null,
public traversalStrategies: TraversalStrategies | null,
public gremlinLang: GremlinLang = new GremlinLang(),
) {}
/**
* Async iterable method implementation.
*/
[asyncIteratorSymbol]() {
return this;
}
/** @returns {GremlinLang} */
getGremlinLang(): GremlinLang {
return this.gremlinLang;
}
/**
* Returns an Array containing the traverser objects.
* @returns {Promise.<Array>}
*/
toList<T>(): Promise<T[]> {
return this._applyStrategies().then(() => {
const result: T[] = [];
let it;
while ((it = this._getNext()) && !it.done) {
result.push(it.value as T);
}
return result;
});
}
/**
* Determines if there are any more items to iterate from the traversal.
* @returns {Promise.<boolean>}
*/
hasNext() {
return this._applyStrategies().then(
() => {
if (!this.results || this.results.length <= 0 || this._resultsIteratorIndex >= this.results.length) {
return false;
}
if (this.results[this._resultsIteratorIndex] instanceof Traverser) {
return this.results[this._resultsIteratorIndex].bulk > 0 || this._resultsIteratorIndex + 1 < this.results.length;
} else {
return true;
}
}
);
}
/**
* Iterates all Traverser instances in the traversal.
* @returns {Promise}
*/
iterate() {
this.gremlinLang.addStep('discard');
return this._applyStrategies().then(() => {
let it;
while ((it = this._getNext()) && !it.done) {
//
}
});
}
/**
* Async iterator method implementation.
* Returns a promise containing an iterator item.
* @returns {Promise.<{value, done}>}
*/
next<T>(): Promise<IteratorResult<T, null>> {
return this._applyStrategies().then(() => this._getNext<T>());
}
/**
* Synchronous iterator of traversers including
* @private
*/
_getNext<T>(): IteratorResult<T, null> {
while (this.results && this._resultsIteratorIndex < this.results.length) {
const next = this.results[this._resultsIteratorIndex];
if (next instanceof Traverser) {
if (next.bulk > 0) {
next.bulk--;
return { value: next.object, done: false };
}
}
this._resultsIteratorIndex++;
if (!(next instanceof Traverser)) {
return { value: next, done: false };
}
}
return itemDone;
}
_applyStrategies() {
if (this._traversalStrategiesPromise) {
// Apply strategies only once
return this._traversalStrategiesPromise;
}
return (this._traversalStrategiesPromise = this.traversalStrategies?.applyStrategies(this) ?? Promise.resolve());
}
/**
* Returns step instructions during JSON serialization
* @returns {Array}
*/
toJSON() {
return this.gremlinLang.getGremlin();
}
/**
* Returns the GremlinLang representation of the traversal
* @returns {String}
*/
toString() {
return this.gremlinLang.getGremlin();
}
}
export class IO {
static get graphml() {
return 'graphml';
}
static get graphson() {
return 'graphson';
}
static get gryo() {
return 'gryo';
}
static get reader() {
return '~tinkerpop.io.reader';
}
static get registry() {
return '~tinkerpop.io.registry';
}
static get writer() {
return '~tinkerpop.io.writer';
}
}
// eslint-disable-next-line no-unused-vars
class ConnectedComponent {
static get component() {
return 'gremlin.connectedComponentVertexProgram.component';
}
static get edges() {
return '~tinkerpop.connectedComponent.edges';
}
static get propertyName() {
return '~tinkerpop.connectedComponent.propertyName';
}
}
// eslint-disable-next-line no-unused-vars
class ShortestPath {
static get distance() {
return '~tinkerpop.shortestPath.distance';
}
static get edges() {
return '~tinkerpop.shortestPath.edges';
}
static get includeEdges() {
return '~tinkerpop.shortestPath.includeEdges';
}
static get maxDistance() {
return '~tinkerpop.shortestPath.maxDistance';
}
static get target() {
return '~tinkerpop.shortestPath.target';
}
}
// eslint-disable-next-line no-unused-vars
class PageRank {
static get edges() {
return '~tinkerpop.pageRank.edges';
}
static get propertyName() {
return '~tinkerpop.pageRank.propertyName';
}
static get times() {
return '~tinkerpop.pageRank.times';
}
}
// eslint-disable-next-line no-unused-vars
class PeerPressure {
static get edges() {
return '~tinkerpop.peerPressure.edges';
}
static get propertyName() {
return '~tinkerpop.peerPressure.propertyName';
}
static get times() {
return '~tinkerpop.peerPressure.times';
}
}
export class P<T1 = any, T2 = any> {
/**
* Represents an operation.
*/
constructor(
public operator: string,
public value: T1,
public other: T2,
) {}
/**
* Returns the string representation of the instance.
* @returns {string}
*/
toString() {
function formatValue(value: T1 | T2): string {
if (Array.isArray(value)) {
const acc = [];
for (const item of value) {
acc.push(formatValue(item));
}
return '[' + acc.join(', ') + ']';
}
if (value && typeof value === 'string') {
return `'${value}'`;
}
return String(value);
}
if (this.other === undefined || this.other === null) {
return this.operator + '(' + formatValue(this.value) + ')';
}
return this.operator + '(' + formatValue(this.value) + ', ' + formatValue(this.other) + ')';
}
and(arg?: any) {
return new P('and', this, arg);
}
or(arg?: any) {
return new P('or', this, arg);
}
static within(...args: any[]) {
if (args.length === 1 && Array.isArray(args[0])) {
return new P('within', args[0], null);
}
return new P('within', args, null);
}
static without(...args: any[]) {
if (args.length === 1 && Array.isArray(args[0])) {
return new P('without', args[0], null);
}
return new P('without', args, null);
}
/** @param {...Object} args */
static between(...args: any[]) {
return createP('between', args);
}
/** @param {...Object} args */
static eq(...args: any[]) {
return createP('eq', args);
}
/** @param {...Object} args */
static gt(...args: any[]) {
return createP('gt', args);
}
/** @param {...Object} args */
static gte(...args: any[]) {
return createP('gte', args);
}
/** @param {...Object} args */
static inside(...args: any[]) {
return createP('inside', args);
}
/** @param {...Object} args */
static lt(...args: any[]) {
return createP('lt', args);
}
/** @param {...Object} args */
static lte(...args: any[]) {
return createP('lte', args);
}
/** @param {...Object} args */
static neq(...args: any[]) {
return createP('neq', args);
}
/** @param {...Object} args */
static not(...args: any[]) {
return createP('not', args);
}
/** @param {...Object} args */
static outside(...args: any[]) {
return createP('outside', args);
}
/** @param {...Object} args */
static typeOf(...args: any[]) {
return createP('typeOf', args);
}
/** @param {...Object} args */
static test(...args: any[]) {
return createP('test', args);
}
}
function createP(operator: string, args: any) {
args.unshift(null, operator);
return new (Function.prototype.bind.apply(P, args))();
}
export class TextP<T1 = any, T2 = any> {
/**
* Represents an operation.
*/
constructor(
public operator: string,
public value: T1,
public other: T2,
) {}
/**
* Returns the string representation of the instance.
* @returns {string}
*/
toString() {
function formatValue(value: any) {
if (value && typeof value === 'string') {
return `'${value}'`;
}
return value;
}
if (this.other === undefined) {
return this.operator + '(' + formatValue(this.value) + ')';
}
return this.operator + '(' + formatValue(this.value) + ', ' + formatValue(this.other) + ')';
}
and(arg: any) {
return new P('and', this, arg);
}
or(arg: any) {
return new P('or', this, arg);
}
/** @param {...Object} args */
static containing(...args: any[]) {
return createTextP('containing', args);
}
/** @param {...Object} args */
static endingWith(...args: any[]) {
return createTextP('endingWith', args);
}
/** @param {...Object} args */
static notContaining(...args: any[]) {
return createTextP('notContaining', args);
}
/** @param {...Object} args */
static notEndingWith(...args: any[]) {
return createTextP('notEndingWith', args);
}
/** @param {...Object} args */
static notStartingWith(...args: any[]) {
return createTextP('notStartingWith', args);
}
/** @param {...Object} args */
static startingWith(...args: any[]) {
return createTextP('startingWith', args);
}
/** @param {...Object} args */
static regex(...args: any[]) {
return createTextP('regex', args);
}
/** @param {...Object} args */
static notRegex(...args: any[]) {
return createTextP('notRegex', args);
}
}
function createTextP(operator: string, args: any) {
args.unshift(null, operator);
return new (Function.prototype.bind.apply(TextP, args))();
}
export class Traverser<T = any> {
constructor(
public object: T,
public bulk: number,
) {
this.bulk = bulk || 1;
}
}
export class TraversalSideEffects {}
export const withOptions = {
tokens: '~tinkerpop.valueMap.tokens',
none: 0,
ids: 1,
labels: 2,
keys: 4,
values: 8,
all: 15,
indexer: '~tinkerpop.index.indexer',
list: 0,
map: 1,
};
function toEnum(typeName: string, keys: string) {
const result: Record<string, EnumValue> = {};
keys.split(' ').forEach((k) => {
let jsKey = k;
if (jsKey === jsKey.toUpperCase()) {
jsKey = jsKey.toLowerCase();
}
result[jsKey] = new EnumValue(typeName, k);
});
return result;
}
const directionAlias = {
from_: 'out',
to: 'in',
} as const;
// for direction enums, maps the same EnumValue object to the enum aliases after creating them
function toDirectionEnum(typeName: string, keys: string) {
const result = toEnum(typeName, keys);
Object.keys(directionAlias).forEach((k) => {
result[k] = result[directionAlias[k as keyof typeof directionAlias]];
});
return result;
}
function toGTypeEnum(typeName: string, keys: string) {
const result: { [key: string]: any } = {};
keys.split(' ').forEach((k) => {
result[k] = new EnumValue(typeName, k.toUpperCase());
});
return result;
}
export class EnumValue {
constructor(
public typeName: string,
public elementName: string,
) {}
toString() {
return this.typeName + '.' + this.elementName;
}
}
export const barrier = toEnum('Barrier', 'normSack');
export const cardinality = toEnum('Cardinality', 'list set single');
export const column = toEnum('Column', 'keys values');
export const direction = toDirectionEnum('Direction', 'BOTH IN OUT from_ to');
export const dt = toEnum('DT', 'second minute hour day');
export const gType = toGTypeEnum('GType', 'bigDecimal bigInt binary boolean byte char datetime double duration edge float graph int list long map null number path property set short string tree uuid vertex vproperty');
export const graphSONVersion = toEnum('GraphSONVersion', 'V1_0 V2_0 V3_0');
export const gryoVersion = toEnum('GryoVersion', 'V1_0 V3_0');
export const merge = toEnum('Merge', 'onCreate onMatch outV inV');
export const operator = toEnum('Operator', 'addAll and assign div max min minus mult or sum sumLong');
export const order = toEnum('Order', 'asc desc shuffle');
export const pick = toEnum('Pick', 'any none unproductive');
export const pop = toEnum('Pop', 'all first last mixed');
export const scope = toEnum('Scope', 'global local');
export const t = toEnum('T', 'id key label value');
export const n = toEnum('N', 'byte_ short_ int_ long_ float_ double_ bigInt bigDecimal');