"use strict";
/**
* @typedef {import('./graph')} Graph
*/
const Statistics = require("./statistics"),
Record = require("./record"),
Node = require("./node"),
Edge = require("./edge"),
Path = require("./path"),
ReplyError = require("redis").ReplyError;
/**
* @enum {number}
* @readonly
*/
const ResultSetColumnTypes = {
COLUMN_UNKNOWN: 0,
COLUMN_SCALAR: 1,
COLUMN_NODE: 2,
COLUMN_RELATION: 3,
};
/**
* @enum {number}
* @readonly
*/
const ResultSetValueTypes = {
VALUE_UNKNOWN: 0,
VALUE_NULL: 1,
VALUE_STRING: 2,
VALUE_INTEGER: 3,
VALUE_BOOLEAN: 4,
VALUE_DOUBLE: 5,
VALUE_ARRAY: 6,
VALUE_EDGE: 7,
VALUE_NODE: 8,
VALUE_PATH: 9,
VALUE_MAP: 10,
VALUE_POINT: 11,
};
/**
* Hold a query result
*/
class ResultSet {
/**
* Builds an empty ResultSet object.
*
* @param {Graph} graph
*/
constructor(graph) {
this._graph = graph; //_graph is graph api
this._position = 0; //allowing iterator like behevior
this._resultsCount = 0; //total number of records in this result set
this._header = []; //reponse schema columns labels
this._results = []; //result records
}
/**
* Parse raw response data to ResultSet object.
* @async
* @param {object[]} resp - raw response representation - the raw representation of response is at most 3 lists of objects.
* The last list is the statistics list.
*/
async parseResponse(resp) {
if (Array.isArray(resp)) {
let statistics = resp[resp.length - 1];
if (statistics instanceof ReplyError) throw statistics;
if (resp.length < 3) {
this._statistics = new Statistics(statistics);
} else {
await this.parseResults(resp);
this._resultsCount = this._results.length;
this._statistics = new Statistics(resp[2]);
}
} else {
this._statistics = new Statistics(resp);
}
return this;
}
/**
* Parse a raw response body into header an records.
* @async
* @param {object[]} resp raw response
*/
async parseResults(resp) {
this.parseHeader(resp[0]);
await this.parseRecords(resp);
}
/**
* A raw representation of a header (query response schema) is a list.
* Each entry in the list is a tuple (list of size 2).
* tuple[0] represents the type of the column, and tuple[1] represents the name of the column.
* @param {object[]} rawHeader raw header
*/
parseHeader(rawHeader) {
// An array of column name/column type pairs.
this._header = rawHeader;
// Discard header types.
this._typelessHeader = new Array(this._header.length);
for (var i = 0; i < this._header.length; i++) {
this._typelessHeader[i] = this._header[i][1];
}
}
/**
* The raw representation of response is at most 3 lists of objects. rawResultSet[1] contains the data records.
* Each entry in the record can be either a node, an edge or a scalar
* @async
* @param {object[]} rawResultSet raw result set representation
*/
async parseRecords(rawResultSet) {
let result_set = rawResultSet[1];
this._results = new Array(result_set.length);
for (var i = 0; i < result_set.length; i++) {
let row = result_set[i];
let record = new Array(row.length);
for (var j = 0; j < row.length; j++) {
let cell = row[j];
let cellType = this._header[j][0];
switch (cellType) {
case ResultSetColumnTypes.COLUMN_SCALAR:
record[j] = await this.parseScalar(cell);
break;
case ResultSetColumnTypes.COLUMN_NODE:
record[j] = await this.parseNode(cell);
break;
case ResultSetColumnTypes.COLUMN_RELATION:
record[j] = await this.parseEdge(cell);
break;
default:
console.log("Unknown column type.\n" + cellType);
break;
}
}
this._results[i] = new Record(this._typelessHeader, record);
}
}
/**
* Parse raw entity properties representation into a Map
* @async
* @param {object[]} props raw properties representation
* @returns {Promise<object>} Map with the parsed properties.
*/
async parseEntityProperties(props) {
// [[name, value, value type] X N]
let properties = {};
for (var i = 0; i < props.length; i++) {
let prop = props[i];
var propIndex = prop[0];
let prop_name = this._graph.getProperty(propIndex);
// will try to get the right property for at most 10 times
var tries = 0;
while (prop_name == undefined && tries < 10) {
prop_name = await this._graph.fetchAndGetProperty(propIndex);
tries++;
}
if (prop_name == undefined) {
console.warn(
"unable to retrive property name value for propety index " +
propIndex
);
}
let prop_value = await this.parseScalar(prop.slice(1, prop.length));
properties[prop_name] = prop_value;
}
return properties;
}
/**
* Parse raw node representation into a Node object.
* @async
* @param {object[]} cell raw node representation.
* @returns {Promise<Node>} Node object.
*/
async parseNode(cell) {
// Node ID (integer),
// [label string offset (integer)],
// [[name, value, value type] X N]
let node_id = cell[0];
let label = this._graph.getLabel(cell[1][0]);
// will try to get the right label for at most 10 times
var tries = 0;
while (label == undefined && tries < 10) {
label = await this._graph.fetchAndGetLabel(cell[1][0]);
tries++;
}
if (label == undefined) {
console.warn(
"unable to retrive label value for label index " + cell[1][0]
);
}
let properties = await this.parseEntityProperties(cell[2]);
let node = new Node(label, properties);
node.setId(node_id);
return node;
}
/**
* Parse a raw edge representation into an Edge object.
* @async
* @param {object[]} cell raw edge representation
* @returns {Promise<Edge>} Edge object.
*/
async parseEdge(cell) {
// Edge ID (integer),
// reltype string offset (integer),
// src node ID offset (integer),
// dest node ID offset (integer),
// [[name, value, value type] X N]
let edge_id = cell[0];
let relation = this._graph.getRelationship(cell[1]);
// will try to get the right relationship type for at most 10 times
var tries = 0;
while (relation == undefined && tries < 10) {
relation = await this._graph.fetchAndGetRelationship(cell[1]);
tries++;
}
if (relation == undefined) {
console.warn(
"unable to retrive relationship type value for relationship index " +
cell[1]
);
}
let src_node_id = cell[2];
let dest_node_id = cell[3];
let properties = await this.parseEntityProperties(cell[4]);
let edge = new Edge(src_node_id, relation, dest_node_id, properties);
edge.setId(edge_id);
return edge;
}
/**
* Parse and in-place replace raw array into an array of values or objects.
* @async
* @param {object[]} rawArray raw array representation
* @returns {Promise<object[]>} Parsed array.
*/
async parseArray(rawArray) {
for (var i = 0; i < rawArray.length; i++) {
rawArray[i] = await this.parseScalar(rawArray[i]);
}
return rawArray;
}
/**
* Parse a raw path representation into Path object.
* @async
* @param {object[]} rawPath raw path representation
* @returns {Promise<Path>} Path object.
*/
async parsePath(rawPath) {
let nodes = await this.parseScalar(rawPath[0]);
let edges = await this.parseScalar(rawPath[1]);
return new Path(nodes, edges);
}
/**
* Parse a raw map representation into Map object.
* @async
* @param {object[]} rawMap raw map representation
* @returns {Promise<object>} Map object.
*/
async parseMap(rawMap) {
let m = {};
for (var i = 0; i < rawMap.length; i+=2) {
var key = rawMap[i];
m[key] = await this.parseScalar(rawMap[i+1]);
}
return m;
}
/**
* Parse a raw Point representation into a lat-lon Map object.
* @param {object[]} rawPoint 2-valued lat-lon array representation
* @returns {{ latitude: number, longitude: number }} Map object with latitude and longitude keys.
*/
parsePoint(rawPoint) {
let m = {};
m["latitude"] = Number(rawPoint[0])
m["longitude"] = Number(rawPoint[1])
return m;
}
/**
* Parse a raw value into its actual value.
* @async
* @param {object[]} cell raw value representation
* @returns {Promise<object>} Actual value - scalar, array, Node, Edge, Path
*/
async parseScalar(cell) {
let scalar_type = cell[0];
let value = cell[1];
let scalar = undefined;
switch (scalar_type) {
case ResultSetValueTypes.VALUE_NULL:
scalar = null;
break;
case ResultSetValueTypes.VALUE_STRING:
scalar = String(value);
break;
case ResultSetValueTypes.VALUE_INTEGER:
case ResultSetValueTypes.VALUE_DOUBLE:
scalar = Number(value);
break;
case ResultSetValueTypes.VALUE_BOOLEAN:
if (value === "true") {
scalar = true;
} else if (value === "false") {
scalar = false;
} else {
console.log("Unknown boolean type\n");
}
break;
case ResultSetValueTypes.VALUE_ARRAY:
scalar = this.parseArray(value);
break;
case ResultSetValueTypes.VALUE_NODE:
scalar = await this.parseNode(value);
break;
case ResultSetValueTypes.VALUE_EDGE:
scalar = await this.parseEdge(value);
break;
case ResultSetValueTypes.VALUE_PATH:
scalar = await this.parsePath(value);
break;
case ResultSetValueTypes.VALUE_MAP:
scalar = await this.parseMap(value);
break;
case ResultSetValueTypes.VALUE_POINT:
scalar = this.parsePoint(value);
break;
case ResultSetValueTypes.VALUE_UNKNOWN:
console.log("Unknown scalar type\n");
break;
}
return scalar;
}
/**
* @returns {string[] }ResultSet's header.
*/
getHeader() {
return this._typelessHeader;
}
/**
* @returns {boolean} If the ResultSet object can return additional records.
*/
hasNext() {
return this._position < this._resultsCount;
}
/**
* @returns {Record} The current record.
*/
next() {
return this._results[this._position++];
}
/**
* @returns {Statistics} ResultsSet's statistics.
*/
getStatistics() {
return this._statistics;
}
/**
* @returns {number} Result set size. (integer)
*/
size() {
return this._resultsCount;
}
}
module.exports = ResultSet;