Socket
Socket
Sign inDemoInstall

@sap/cds

Package Overview
Dependencies
Maintainers
1
Versions
183
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds - npm Package Compare versions

Comparing version 1.15.1 to 1.17.0

_hdbext/index.js

664

cds-queries-geo.js

@@ -1,83 +0,82 @@

var queries = require("./cds-queries");
var Expr = queries.Query.F;
const queries = require('./cds-queries');
const Expr = queries.Query.F;
/*********************************************************************************
/** *******************************************************************************
* Spatial methods
*********************************************************************************/
queries.Query.F.$convexHull = geoPostfix("ST_ConvexHull", [0]);
queries.Query.F.$area = geoPostfix("ST_Area", [0,1]);
queries.Query.F.$buffer = geoPostfix("ST_Buffer", [1,2]);
queries.Query.F.$boundary = geoPostfix("ST_Boundary", [0]);
queries.Query.F.$contains = geoPostfix("ST_Contains", [1]);
queries.Query.F.$covers = geoPostfix("ST_Covers", [1]);
queries.Query.F.$coveredBy = geoPostfix("ST_CoveredBy", [1]);
queries.Query.F.$crosses = geoPostfix("ST_Crosses", [1]);
queries.Query.F.$isEmpty = geoPostfix("ST_IsEmpty", [0]);
queries.Query.F.$isSimple = geoPostfix("ST_IsSimple", [0]);
queries.Query.F.$disjoint = geoPostfix("ST_Disjoint", [1]);
queries.Query.F.$equals = geoPostfix("ST_Equals", [1]);
queries.Query.F.$overlaps = geoPostfix("ST_Overlaps", [1]);
queries.Query.F.$intersects = geoPostfix("ST_Intersects", [1]);
queries.Query.F.$centroid = geoPostfix("ST_Centroid", [0]);
queries.Query.F.$difference = geoPostfix("ST_Difference", [1]);
queries.Query.F.$dimension = geoPostfix("ST_Dimension", [0]);
queries.Query.F.$distance = geoPostfix("ST_Distance", [1,2]);
queries.Query.F.$endPoint = geoPostfix("ST_EndPoint", [0]);
queries.Query.F.$envelope = geoPostfix("ST_Envelope", [0]);
queries.Query.F.$exteriorRing = geoPostfix("ST_ExteriorRing", [0]);
queries.Query.F.$geometryType = geoPostfix("ST_GeometryType", [0]);
queries.Query.F.$geometryN = geoPostfix("ST_GeometryN", [1]);
queries.Query.F.$interiorRingN = geoPostfix("ST_InteriorRingN", [1]);
queries.Query.F.$intersection = geoPostfix("ST_Intersection", [1]);
queries.Query.F.$intersectsFilter = geoPostfix("ST_IntersectsFilter", [1]);
queries.Query.F.$intersectsRect = geoPostfix("ST_IntersectsRect", [2]);
queries.Query.F.$is3D = geoPostfix("ST_Is3D", [0]);
queries.Query.F.$isClosed = geoPostfix("ST_IsClosed", [0]);
queries.Query.F.$isRing = geoPostfix("ST_IsRing", [0]);
queries.Query.F.$isValid = geoPostfix("ST_IsValid", [0]);
queries.Query.F.$length = geoPostfix("ST_Length", [0,1]);
queries.Query.F.$numGeometries = geoPostfix("ST_NumGeometries", [0]);
queries.Query.F.$numInteriorRing = geoPostfix("ST_NumInteriorRing", [0]);
queries.Query.F.$numInteriorRings = geoPostfix("ST_NumInteriorRings", [0]);
queries.Query.F.$numPoints = geoPostfix("ST_NumPoints", [0]);
queries.Query.F.$orderingEquals = geoPostfix("ST_OrderingEquals", [1]);
queries.Query.F.$pointN = geoPostfix("ST_PointN", [1]);
queries.Query.F.$pointOnSurface = geoPostfix("ST_PointOnSurface", [0]);
queries.Query.F.$relate = geoPostfix("ST_Relate", [1]);
queries.Query.F.$srid = geoPostfix("ST_SRID", [0]);
queries.Query.F.$snapToGrid = geoPostfix("ST_SnapToGrid", [1]);
queries.Query.F.$symDifference = geoPostfix("ST_SymDifference", [1]);
queries.Query.F.$touches = geoPostfix("ST_Touches", [1]);
queries.Query.F.$convexHull = geoPostfix('ST_ConvexHull', [0]);
queries.Query.F.$area = geoPostfix('ST_Area', [0, 1]);
queries.Query.F.$buffer = geoPostfix('ST_Buffer', [1, 2]);
queries.Query.F.$boundary = geoPostfix('ST_Boundary', [0]);
queries.Query.F.$contains = geoPostfix('ST_Contains', [1]);
queries.Query.F.$covers = geoPostfix('ST_Covers', [1]);
queries.Query.F.$coveredBy = geoPostfix('ST_CoveredBy', [1]);
queries.Query.F.$crosses = geoPostfix('ST_Crosses', [1]);
queries.Query.F.$isEmpty = geoPostfix('ST_IsEmpty', [0]);
queries.Query.F.$isSimple = geoPostfix('ST_IsSimple', [0]);
queries.Query.F.$disjoint = geoPostfix('ST_Disjoint', [1]);
queries.Query.F.$equals = geoPostfix('ST_Equals', [1]);
queries.Query.F.$overlaps = geoPostfix('ST_Overlaps', [1]);
queries.Query.F.$intersects = geoPostfix('ST_Intersects', [1]);
queries.Query.F.$centroid = geoPostfix('ST_Centroid', [0]);
queries.Query.F.$difference = geoPostfix('ST_Difference', [1]);
queries.Query.F.$dimension = geoPostfix('ST_Dimension', [0]);
queries.Query.F.$distance = geoPostfix('ST_Distance', [1, 2]);
queries.Query.F.$endPoint = geoPostfix('ST_EndPoint', [0]);
queries.Query.F.$envelope = geoPostfix('ST_Envelope', [0]);
queries.Query.F.$exteriorRing = geoPostfix('ST_ExteriorRing', [0]);
queries.Query.F.$geometryType = geoPostfix('ST_GeometryType', [0]);
queries.Query.F.$geometryN = geoPostfix('ST_GeometryN', [1]);
queries.Query.F.$interiorRingN = geoPostfix('ST_InteriorRingN', [1]);
queries.Query.F.$intersection = geoPostfix('ST_Intersection', [1]);
queries.Query.F.$intersectsFilter = geoPostfix('ST_IntersectsFilter', [1]);
queries.Query.F.$intersectsRect = geoPostfix('ST_IntersectsRect', [2]);
queries.Query.F.$is3D = geoPostfix('ST_Is3D', [0]);
queries.Query.F.$isClosed = geoPostfix('ST_IsClosed', [0]);
queries.Query.F.$isRing = geoPostfix('ST_IsRing', [0]);
queries.Query.F.$isValid = geoPostfix('ST_IsValid', [0]);
queries.Query.F.$length = geoPostfix('ST_Length', [0, 1]);
queries.Query.F.$numGeometries = geoPostfix('ST_NumGeometries', [0]);
queries.Query.F.$numInteriorRing = geoPostfix('ST_NumInteriorRing', [0]);
queries.Query.F.$numInteriorRings = geoPostfix('ST_NumInteriorRings', [0]);
queries.Query.F.$numPoints = geoPostfix('ST_NumPoints', [0]);
queries.Query.F.$orderingEquals = geoPostfix('ST_OrderingEquals', [1]);
queries.Query.F.$pointN = geoPostfix('ST_PointN', [1]);
queries.Query.F.$pointOnSurface = geoPostfix('ST_PointOnSurface', [0]);
queries.Query.F.$relate = geoPostfix('ST_Relate', [1]);
queries.Query.F.$srid = geoPostfix('ST_SRID', [0]);
queries.Query.F.$snapToGrid = geoPostfix('ST_SnapToGrid', [1]);
queries.Query.F.$symDifference = geoPostfix('ST_SymDifference', [1]);
queries.Query.F.$touches = geoPostfix('ST_Touches', [1]);
//queries.Query.F.$unionAggr = geoPostfix("ST_UnionAggr", [1]);
// queries.Query.F.$unionAggr = geoPostfix("ST_UnionAggr", [1]);
exports.unionAggr = unionAggr;
function unionAggr(arg) {
return Expr.$geoPreOp("ST_UnionAggr", arg);
return Expr.$geoPreOp('ST_UnionAggr', arg);
}
queries.Query.F.$startPoint = geoPostfix("ST_StartPoint", [0]);
queries.Query.F.$withinDistance = geoPostfix("ST_WithinDistance", [3]);
queries.Query.F.$within = geoPostfix("ST_Within", [1]);
queries.Query.F.$x = geoPostfix("ST_X", [0]);
queries.Query.F.$y = geoPostfix("ST_Y", [0]);
queries.Query.F.$xMax = geoPostfix("ST_XMax", [0]);
queries.Query.F.$xMin = geoPostfix("ST_XMin", [0]);
queries.Query.F.$yMax = geoPostfix("ST_YMax", [0]);
queries.Query.F.$yMin = geoPostfix("ST_YMin", [0]);
queries.Query.F.$startPoint = geoPostfix('ST_StartPoint', [0]);
queries.Query.F.$withinDistance = geoPostfix('ST_WithinDistance', [3]);
queries.Query.F.$within = geoPostfix('ST_Within', [1]);
queries.Query.F.$x = geoPostfix('ST_X', [0]);
queries.Query.F.$y = geoPostfix('ST_Y', [0]);
queries.Query.F.$xMax = geoPostfix('ST_XMax', [0]);
queries.Query.F.$xMin = geoPostfix('ST_XMin', [0]);
queries.Query.F.$yMax = geoPostfix('ST_YMax', [0]);
queries.Query.F.$yMin = geoPostfix('ST_YMin', [0]);
function geoPostfix(name, numArgs) {
return function(args) {
if (numArgs.indexOf(args.length-1) != -1) {
return Expr.$postOp(name, args);
}
else {
throw new Error("Invalid number of arguments")
}
};
return function(args) {
if (numArgs.indexOf(args.length-1) != -1) {
return Expr.$postOp(name, args);
} else {
throw new Error('Invalid number of arguments');
}
};
}
/*********************************************************************************
/** *******************************************************************************
* Spatial geometry from string representation

@@ -93,75 +92,73 @@ *********************************************************************************/

function geomFromEWKB(stringRep) {
return Expr.$geoPreOp("ST_GeomFromEWKB", stringRep);
return Expr.$geoPreOp('ST_GeomFromEWKB', stringRep);
}
function geomFromEWKT(stringRep) {
return Expr.$geoPreOp("ST_GeomFromEWKT", stringRep);
return Expr.$geoPreOp('ST_GeomFromEWKT', stringRep);
}
function geomFromText(stringRep, srid) {
if (typeof srid === 'undefined') {
return Expr.$geoPreOp("ST_GeomFromText", stringRep);
}
else {
return Expr.$geoPreOp("ST_GeomFromText", stringRep, srid);
}
if (typeof srid === 'undefined') {
return Expr.$geoPreOp('ST_GeomFromText', stringRep);
} else {
return Expr.$geoPreOp('ST_GeomFromText', stringRep, srid);
}
}
function geomFromWKB(stringRep, srid) {
if (typeof srid === 'undefined') {
return Expr.$geoPreOp("ST_GeomFromWKB", stringRep);
}
else {
return Expr.$geoPreOp("ST_GeomFromWKB", stringRep, srid);
}
if (typeof srid === 'undefined') {
return Expr.$geoPreOp('ST_GeomFromWKB', stringRep);
} else {
return Expr.$geoPreOp('ST_GeomFromWKB', stringRep, srid);
}
}
function geomFromWKT(stringRep, srid) {
if (typeof srid === 'undefined') {
return Expr.$geoPreOp("ST_GeomFromWKT", stringRep);
}
else {
return Expr.$geoPreOp("ST_GeomFromWKT", stringRep, srid);
}
if (typeof srid === 'undefined') {
return Expr.$geoPreOp('ST_GeomFromWKT', stringRep);
} else {
return Expr.$geoPreOp('ST_GeomFromWKT', stringRep, srid);
}
}
/*********************************************************************************
/** *******************************************************************************
* Spatial formats
*********************************************************************************/
queries.Query.F.$asBinary = geoFormat("Binary");
queries.Query.F.$asEWKB = geoFormat("EWKB");
queries.Query.F.$asEWKT = geoFormat("EWKT");
queries.Query.F.$asGeoJSON = geoFormat("GeoJSON");
queries.Query.F.$asSVG = geoFormat("SVG");
queries.Query.F.$asText = geoFormat("Text");
queries.Query.F.$asWKB = geoFormat("WKB");
queries.Query.F.$asWKT = geoFormat("WKT");
queries.Query.F.$asBinary = geoFormat('Binary');
queries.Query.F.$asEWKB = geoFormat('EWKB');
queries.Query.F.$asEWKT = geoFormat('EWKT');
queries.Query.F.$asGeoJSON = geoFormat('GeoJSON');
queries.Query.F.$asSVG = geoFormat('SVG');
queries.Query.F.$asText = geoFormat('Text');
queries.Query.F.$asWKB = geoFormat('WKB');
queries.Query.F.$asWKT = geoFormat('WKT');
function geoFormat(format) {
return function(args) {
return Expr.$postOp("ST_As" + format, args);
};
return function(args) {
return Expr.$postOp(`ST_As${ format}`, args);
};
};
/*********************************************************************************
/** *******************************************************************************
* Spatial queries
*********************************************************************************/
queries.Query.F.$query = function(args) {
return query([args[0]]);
return query([args[0]]);
};
function query(exprs) {
var q = new queries.Query(null, {t0: "DUMMY"}, "t0").$project({});
var fields = exprs.reduce(function(acc, e, i) {
acc["geo_" + i] = e;
return acc;
}, {});
var res = q.$addFields(fields);
return res;
const q = new queries.Query(null, {t0: 'DUMMY'}, 't0').$project({});
const fields = exprs.reduce(function(acc, e, i) {
acc[`geo_${ i}`] = e;
return acc;
}, {});
const res = q.$addFields(fields);
return res;
}
/*********************************************************************************
/** *******************************************************************************
* Spatial data types

@@ -180,306 +177,285 @@ *********************************************************************************/

function stType(constr, data, srid) {
if (typeof data === 'undefined') {
return Expr.$geoPreOp("NEW ST_" + constr);
}
if (typeof data === 'undefined') {
return Expr.$geoPreOp(`NEW ST_${ constr}`);
}
if (typeof srid === 'undefined') {
return Expr.$geoPreOp("NEW ST_" + constr, data);
}
if (typeof srid === 'undefined') {
return Expr.$geoPreOp(`NEW ST_${ constr}`, data);
}
return Expr.$geoPreOp("NEW ST_" + constr, data, srid);
return Expr.$geoPreOp(`NEW ST_${ constr}`, data, srid);
}
function point(p) {
return {
type: "Point",
coordinates : p
};
return {
type: 'Point',
coordinates: p,
};
}
function jsonPoints(points) {
return points.map(function(p){
return point(p);
});
return points.map(function(p) {
return point(p);
});
}
function jsonPoint(points) {
return point(points);
return point(points);
}
function polygon(points) {
return [{
type: "Polygon",
coordinates: points
}];
return [{
type: 'Polygon',
coordinates: points,
}];
}
function stLineString(points, srid) {
return stMultiplePoints("LineString", points, srid);
return stMultiplePoints('LineString', points, srid);
}
function stCircularString(points, srid) {
return stMultiplePoints("CircularString", points, srid);
return stMultiplePoints('CircularString', points, srid);
}
function stMultiLineString(points, srid) {
return stMultipleLines("MultiLineString", points, srid);
return stMultipleLines('MultiLineString', points, srid);
}
function stPolygon(points, srid) {
return stMultipleLines("Polygon", points, srid);
return stMultipleLines('Polygon', points, srid);
}
function stPoint(point, y) {
var textRep;
var srid;
let textRep;
let srid;
//GeoJson-Object as param
if (Object.prototype.toString.call(point) === "[object Object]") {
if(point.coordinates) {
point = point.coordinates;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
}
// GeoJson-Object as param
if (Object.prototype.toString.call(point) === '[object Object]') {
if (point.coordinates) {
point = point.coordinates;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
}
//point undefined --> empty constructor
if (typeof point === 'undefined') {
// point undefined --> empty constructor
if (typeof point === 'undefined') {
return stType('Point', textRep, srid);
} else if (typeof point === 'number') {
// constructor with 2 doubles is used and 2nd param 'srid' is abused for the 2nd double value
textRep = point;
srid = y;
} else if (typeof point === 'string') {
// --> wkb constructor
textRep = point;
} else {
// point is described in (well known) text format with optional srid parameter
if (!point.coordinates) {
point = jsonPoint(point);
}
textRep = `${'Point ' + '('}${ point.coordinates.join(' ') })`;
}
}
//point is a number --> constructor with 2 doubles is used and 2nd param 'srid' is abused for the 2nd double value
else if (typeof point === 'number') {
textRep = point;
srid = y;
}
//point is a string --> wkb constructor
else if (typeof point === 'string') {
textRep = point;
}
//point is described in (well known) text format with optional srid parameter
else {
if (!point.coordinates) {
point = jsonPoint(point);
}
textRep = "Point " + '(' + point.coordinates.join(" ") + ")";
}
return stType("Point", textRep, srid);
return stType('Point', textRep, srid);
}
function stMultiplePoints(type, points, srid) {
var textRep;
let textRep;
//GeoJson-Object as param
if (Object.prototype.toString.call(points) === "[object Object]") {
if(points.coordinates) {
points = points.coordinates;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
}
// GeoJson-Object as param
if (Object.prototype.toString.call(points) === '[object Object]') {
if (points.coordinates) {
points = points.coordinates;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
}
//points undefined --> empty constructor
if (typeof points === 'undefined') {
// points undefined --> empty constructor
if (typeof points === 'undefined') {
}
//points is a string --> wkb constructor
else if (typeof points === 'string') {
textRep = points;
}
//points are described in (well known) text format with optional srid parameter
else {
if (!points[0].coordinates) {
points = jsonPoints(points);
}
} else if (typeof points === 'string') {
// wkb constructor
textRep = points;
} else {
// points are described in (well known) text format with optional srid parameter
if (!points[0].coordinates) {
points = jsonPoints(points);
}
textRep = type+'(' + points.map(function(p) {
return p.coordinates.join(" ");
}).join(", ")+")";
}
textRep = `${type}(${ points.map(function(p) {
return p.coordinates.join(' ');
}).join(', ')})`;
}
return stType(type, textRep, srid);
return stType(type, textRep, srid);
}
function stMultipleLines(type, lines, srid) {
var textRep;
let textRep;
//GeoJson-Object as param
if (Object.prototype.toString.call(lines) === "[object Object]") {
if(lines.coordinates) {
lines = lines.coordinates;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
}
// GeoJson-Object as param
if (Object.prototype.toString.call(lines) === '[object Object]') {
if (lines.coordinates) {
lines = lines.coordinates;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
}
//lines undefined --> empty constructor
if (typeof lines === 'undefined') {
// lines undefined --> empty constructor
if (typeof lines === 'undefined') {
return stType(type, textRep, srid);
} else if (typeof lines === 'string') {
// wkb constructor
textRep = lines;
} else {
// lines are described in (well known) text format with optional srid parameter
if (!lines[0].coordinates) {
lines = polygon(lines);
}
}
//lines is a string --> wkb constructor
else if (typeof lines === 'string') {
textRep = lines;
}
//lines are described in (well known) text format with optional srid parameter
else {
if (!lines[0].coordinates) {
lines = polygon(lines);
}
textRep = type + lines.map(function(points) {
return `(${ points.coordinates.map(function(points0) {
return `(${ points0.map(function(p) {
return p.join(' ');
}).join(', ')})`;
}).join(', ')})`;
}).join(', ');
}
textRep = type + lines.map(function(points) {
return '(' + (points.coordinates.map(function(points0) {
return '(' +points0.map(function(p) {
return p.join(" ");
}).join(", ")+")";
}).join(", "))+")";
}).join(", ");
}
return stType(type, textRep, srid);
return stType(type, textRep, srid);
}
function stMultiPoint(points, srid) {
var textRep;
let textRep;
//GeoJson-Object as param
if (Object.prototype.toString.call(points) === "[object Object]") {
if(points.coordinates) {
points = points.coordinates;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
}
// GeoJson-Object as param
if (Object.prototype.toString.call(points) === '[object Object]') {
if (points.coordinates) {
points = points.coordinates;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
}
//points undefined --> empty constructor
if (typeof points === 'undefined') {
// points undefined --> empty constructor
if (typeof points === 'undefined') {
return stType('MultiPoint', textRep, srid);
} else if (typeof points === 'string') {
// wkb constructor
textRep = points;
} else {
// points are described in (well known) text format with optional srid parameter
if (!points[0].coordinates) {
points = jsonPoints(points);
}
}
//points is a string --> wkb constructor
else if (typeof points === 'string') {
textRep = points;
}
//points are described in (well known) text format with optional srid parameter
else {
if (!points[0].coordinates) {
points = jsonPoints(points);
}
textRep = `${'MultiPoint' + '('}${ points.map(function(p) {
return `(${p.coordinates.join(' ')})`;
}).join(', ')})`;
}
textRep = "MultiPoint" + '(' + points.map(function(p) {
return '('+p.coordinates.join(" ")+')';
}).join(", ")+")";
}
return stType("MultiPoint", textRep, srid);
return stType('MultiPoint', textRep, srid);
}
function stMultiPolygon(polygons, srid) {
var textRep;
let textRep;
//GeoJson-Object as param
if (Object.prototype.toString.call(polygons) === "[object Object]") {
if(polygons.coordinates) {
polygons = polygons.coordinates;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
}
// GeoJson-Object as param
if (Object.prototype.toString.call(polygons) === '[object Object]') {
if (polygons.coordinates) {
polygons = polygons.coordinates;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
}
//polygons undefined --> empty constructor
if (typeof polygons === 'undefined') {
// polygons undefined --> empty constructor
if (typeof polygons === 'undefined') {
return stType('MultiPolygon', textRep, srid);
} else if (typeof polygons === 'string') {
// wkb constructor
textRep = polygons;
} else {
// polygons are described in (well known) text format with optional srid parameter
textRep = `${'MultiPolygon' +'('}${ polygons.map(function(lines) {
if (!lines[0].coordinates) {
lines = polygon(lines);
}
}
//polygons is a string --> wkb constructor
else if (typeof polygons === 'string') {
textRep = polygons;
}
//polygons are described in (well known) text format with optional srid parameter
else {
textRep = "MultiPolygon" +'(' + polygons.map(function(lines) {
if (!lines[0].coordinates) {
lines = polygon(lines);
}
return (lines.map(function(points) {
return `(${ points.coordinates.map(function(points0) {
return `(${ points0.map(function(p) {
return p.join(' ');
}).join(', ')})`;
}).join(', ')})`;
}).join(', '));
}).join(', ')})`;
}
return (lines.map(function(points) {
return '(' + (points.coordinates.map(function(points0) {
return '(' +points0.map(function(p) {
return p.join(" ");
}).join(", ")+")";
}).join(", "))+")";
}).join(", "));
}).join(", ")+")";
}
return stType("MultiPolygon", textRep, srid);
return stType('MultiPolygon', textRep, srid);
}
function stGeometryCollection(geometries, srid) {
var textRep;
let textRep;
//GeoJson-Object as param
if (Object.prototype.toString.call(geometries) === "[object Object]") {
if(geometries.geometries) {
geometries = geometries.geometries;
}
else {
throw new Error("GeoJSON-Object has no \'position\' property.")
}
// GeoJson-Object as param
if (Object.prototype.toString.call(geometries) === '[object Object]') {
if (geometries.geometries) {
geometries = geometries.geometries;
} else {
throw new Error('GeoJSON-Object has no \'position\' property.');
}
geometries = geometries.map(function(jsonGeom) {
var geoConstr;
geometries = geometries.map(function(jsonGeom) {
let geoConstr;
switch(jsonGeom.type) {
case "Point":
geoConstr = stPoint(jsonGeom.coordinates);
break;
case "MultiPoint":
geoConstr = stMultiPoint(jsonGeom.coordinates);
break;
case "LineString":
geoConstr = stLineString(jsonGeom.coordinates);
break;
case "CircularString":
geoConstr = stCircularString(jsonGeom.coordinates);
break;
case "MultiLineString":
geoConstr = stMultiLineString(jsonGeom.coordinates);
break;
case "Polygon":
geoConstr = stPolygon(jsonGeom.coordinates);
break;
case "MultiPolygon":
geoConstr = stMultiPolygon(jsonGeom.coordinates);
break;
default:
//TODO error
break;
}
switch (jsonGeom.type) {
case 'Point':
geoConstr = stPoint(jsonGeom.coordinates);
break;
case 'MultiPoint':
geoConstr = stMultiPoint(jsonGeom.coordinates);
break;
case 'LineString':
geoConstr = stLineString(jsonGeom.coordinates);
break;
case 'CircularString':
geoConstr = stCircularString(jsonGeom.coordinates);
break;
case 'MultiLineString':
geoConstr = stMultiLineString(jsonGeom.coordinates);
break;
case 'Polygon':
geoConstr = stPolygon(jsonGeom.coordinates);
break;
case 'MultiPolygon':
geoConstr = stMultiPolygon(jsonGeom.coordinates);
break;
default:
// TODO error
break;
}
return geoConstr;
})
}
return geoConstr;
});
}
//geometries undefined --> empty constructor
if (typeof geometries === 'undefined') {
// geometries undefined --> empty constructor
if (typeof geometries === 'undefined') {
return stType('GeometryCollection', textRep, srid);
} else if (typeof geometries === 'string') {
// wkb constructor
textRep = geometries;
} else {
// take representations of the geometries (in whatever format they are) as constructor params
textRep = `${'GeometryCollection ' + '('}${ geometries.map(function(geo) {
return geo._geoPreOp[1];
}).join(', ') })`;
}
}
//geometries is a string --> wkb constructor
else if (typeof geometries === 'string') {
textRep = geometries;
}
//take representations of the geometries (in whatever format they are) as constructor parameters
else {
textRep = "GeometryCollection " + '(' + geometries.map(function(geo) {
return geo._geoPreOp[1];
}).join(', ') + ")"
}
//return stType(obj);
return stType("GeometryCollection", textRep, srid);
}
// return stType(obj);
return stType('GeometryCollection', textRep, srid);
}

@@ -1,74 +0,77 @@

var async = require('async');
const async = require('async');
var metadata = require('./metadata');
var manager = require('./manager');
var queries = require("./cds-queries");
var SqlQuery = queries.Query;
var transaction = require('./transaction');
const metadata = require('./metadata');
const manager = require('./manager');
const queries = require('./cds-queries');
const SqlQuery = queries.Query;
const transaction = require('./transaction');
const _hdbext = require('./_hdbext');
// transactions
// returns connected DB client
exports.$getTransaction = function (dbconn, callback) {
if (typeof callback === "undefined") {
callback = dbconn;
dbconn = null;
exports.$getTransaction = function(dbconn, callback) {
if (typeof callback === 'undefined') {
callback = dbconn;
dbconn = null;
}
transaction.getClient(dbconn, function(err, client) {
if (err) {
return callback(err);
}
transaction.getClient(dbconn, function(err, client) {
if (err)
return callback(err);
client.$get = function (entity, key, callback) {
manager._get(client, entity, key, callback);
};
client.$find = function (entity, condition, callback) {
manager._find(client, entity, condition, callback);
};
client.$save = function (instance, callback) {
manager._save(client, instance, callback);
};
client.$discard = function (instance, callback) {
manager._discard(client, instance, callback);
};
client.$getAll = function (refs, callback) {
var getter = function (ref, cb) {
manager._get(client, ref.$entity, ref, cb);
};
async.map(refs, getter, callback);
};
client.$findAll = function (refs, callback) {
var finder = function (ref, cb) {
manager._find(client, ref.$entity, ref, cb);
};
async.map(refs, finder, callback);
};
client.$saveAll = function (refs, callback) {
var saver = function(ref, cb) {
manager._save(client, ref, cb);
};
async.map(refs, saver, callback);
};
client.$discardAll = function (refs, callback) {
var discarder = function(ref, cb) {
manager._discard(client, ref, cb);
};
async.map(refs, discarder, function(err) { callback(err); });
};
client.$delete = function(entity, condition, callback) {
manager._delete(client, entity, condition, callback);
};
client.$setAutoCommit = function (auto) {
transaction._autoCommit(client, auto);
};
client.$commit = function (callback) {
transaction._commit(client, callback);
};
client.$rollback = function (callback) {
transaction._rollback(client, callback);
};
client.$close = function() {
transaction.releaseClient(client, dbconn);
};
callback(err, client);
});
client.$get = function(entity, key, callback) {
manager._get(client, entity, key, callback);
};
client.$find = function(entity, condition, callback) {
manager._find(client, entity, condition, callback);
};
client.$save = function(instance, callback) {
manager._save(client, instance, callback);
};
client.$discard = function(instance, callback) {
manager._discard(client, instance, callback);
};
client.$getAll = function(refs, callback) {
const getter = function(ref, cb) {
manager._get(client, ref.$entity, ref, cb);
};
async.map(refs, getter, callback);
};
client.$findAll = function(refs, callback) {
const finder = function(ref, cb) {
manager._find(client, ref.$entity, ref, cb);
};
async.map(refs, finder, callback);
};
client.$saveAll = function(refs, callback) {
const saver = function(ref, cb) {
manager._save(client, ref, cb);
};
async.map(refs, saver, callback);
};
client.$discardAll = function(refs, callback) {
const discarder = function(ref, cb) {
manager._discard(client, ref, cb);
};
async.map(refs, discarder, function(err) {
callback(err);
});
};
client.$delete = function(entity, condition, callback) {
manager._delete(client, entity, condition, callback);
};
client.$setAutoCommit = function(auto) {
transaction._autoCommit(client, auto);
};
client.$commit = function(callback) {
transaction._commit(client, callback);
};
client.$rollback = function(callback) {
transaction._rollback(client, callback);
};
client.$close = function() {
transaction.releaseClient(client, dbconn);
};
callback(err, client);
});
};

@@ -81,16 +84,18 @@

exports.$importEntities = function(refs, opts, callback) {
if (typeof callback === "undefined") {
callback = opts;
opts = {};
if (typeof callback === 'undefined') {
callback = opts;
opts = {};
}
metadata._import(refs, opts, function(error, entities) {
for (const name in entities) {
if (entities[name]) {
addXSInterface(entities[name]);
}
}
metadata._import(refs, opts, function (error, entities) {
for (var name in entities)
if (entities[name])
addXSInterface(entities[name]);
callback(error, entities);
});
}
callback(error, entities);
});
};
exports.$importEntity = function(ref, callback) {
exports.$importEntities([ref], callback);
exports.$importEntities([ref], callback);
};

@@ -101,9 +106,9 @@

exports.$getEntity = function(entityName, callback) {
metadata.getEntity(entityName, callback);
metadata.getEntity(entityName, callback);
};
exports.$getEntities = function(entityNames, callback) {
var getter = function (name, cb) {
exports.$getEntity(name, cb);
};
async.map(entityNames, getter, callback);
const getter = function(name, cb) {
exports.$getEntity(name, cb);
};
async.map(entityNames, getter, callback);
};

@@ -114,3 +119,3 @@

exports.$getEntitySync = function(entityName) {
return metadata.getEntitySync(entityName);
return metadata.getEntitySync(entityName);
};

@@ -122,13 +127,13 @@

function addXSInterface(entity) {
// batch operations
entity.$prepare = function(value) {
value.$entity = entity;
return value;
};
// general queries
entity.$query = function(client) {
var param = {};
param["t0"] = { entity: entity };
return new SqlQuery(client, param);
};
// batch operations
entity.$prepare = function(value) {
value.$entity = entity;
return value;
};
// general queries
entity.$query = function(client) {
const param = {};
param['t0'] = {entity: entity};
return new SqlQuery(client, param);
};
}

@@ -148,3 +153,3 @@

exports.$par = function(id) {
return new queries.Par(id);
return new queries.Par(id);
};

@@ -162,10 +167,12 @@

exports.xsjs = function(conn, callback) {
exports.$getTransaction(conn._client, function(err, tx) {
if (err) {
callback(err);
} else {
var xsjscds = require("./xsjs-cds").init(tx);
callback(null, xsjscds);
}
});
exports.$getTransaction(conn._client, function(err, tx) {
if (err) {
callback(err);
} else {
const xsjscds = require('./xsjs-cds').init(tx);
callback(null, xsjscds);
}
});
}.sync;
// Use at your own risk, middleware comes from @sap/hdbext@^4
exports.middleware = _hdbext.middleware;

@@ -1,2 +0,2 @@

var utils = require('./utils');
const utils = require('./utils');

@@ -6,5 +6,5 @@

exports.buildInstanceFilter = function(entity, condition) {
return function(instance) {
return evalExpression(entity, condition, instance);
};
return function(instance) {
return evalExpression(entity, condition, instance);
};
};

@@ -15,50 +15,57 @@

function evalExpression(entity, criteria, instance) {
var realcrit = criteria.$eq || criteria.$ne || criteria;
var match = true;
utils.forInstance(realcrit, entity.$_mapping, {
$column: function(p, f, v, m) {
if (v === null || typeof v[f] === "undefined")
return; // property not part of criteria
var value = utils.getPropPath(instance, p + f);
if (!evalCondition(v[f], value))
match = false;
},
$association: function(p, f, v, m) {
if (v === null || typeof v[f] === "undefined")
return; // property not part of criteria
var value = utils.getPropPath(instance, p + f);
// empty association
if (v[f] === null) { // short for { $none: true }
match = match && !value;
return;
} else if ("$none" in v[f]) {
match = match && (v[f].$none && isnone(value) || !v[f].$none && !isnone(value));
return;
} else if ("$empty" in v[f]) { // deprecated for "$none"
match = match && (v[f].$empty && isnone(value) || !v[f].$empty && !isnone(value));
return;
} else if ("$null" in v[f]) {
throw new Error("invalid $null operator on association");
} else if (!value) {
match = match && (v[f].$none || v[f].$empty);
return;
}
// non-empty association
if (utils.isArray(value))
throw new Error("invalid navigation in expression");
if (!("$_entity" in value))
throw new Error("*** ASSERT FAIL *** comparing against non-instance");
if ("$_entity" in v[f]) {
// shortcut for comparing two instances
if (v[f] === value)
return; // found match --> keep match unchanged
} else {
// follow association recursively
if (evalExpression(m[f].$association.$class, v[f], value))
return; // found match --> keep match unchanged
}
match = false;
}
});
return "$ne" in criteria ? !match : match;
const realcrit = criteria.$eq || criteria.$ne || criteria;
let match = true;
utils.forInstance(realcrit, entity.$_mapping, {
$column: function(p, f, v, m) {
if (v === null || typeof v[f] === 'undefined') {
return;
} // property not part of criteria
const value = utils.getPropPath(instance, p + f);
if (!evalCondition(v[f], value)) {
match = false;
}
},
$association: function(p, f, v, m) {
if (v === null || typeof v[f] === 'undefined') {
return;
} // property not part of criteria
const value = utils.getPropPath(instance, p + f);
// empty association
if (v[f] === null) { // short for { $none: true }
match = match && !value;
return;
} else if ('$none' in v[f]) {
match = match && (v[f].$none && isnone(value) || !v[f].$none && !isnone(value));
return;
} else if ('$empty' in v[f]) { // deprecated for "$none"
match = match && (v[f].$empty && isnone(value) || !v[f].$empty && !isnone(value));
return;
} else if ('$null' in v[f]) {
throw new Error('invalid $null operator on association');
} else if (!value) {
match = match && (v[f].$none || v[f].$empty);
return;
}
// non-empty association
if (utils.isArray(value)) {
throw new Error('invalid navigation in expression');
}
if (!('$_entity' in value)) {
throw new Error('*** ASSERT FAIL *** comparing against non-instance');
}
if ('$_entity' in v[f]) {
// shortcut for comparing two instances
if (v[f] === value) {
return;
} // found match --> keep match unchanged
} else {
// follow association recursively
if (evalExpression(m[f].$association.$class, v[f], value)) {
return;
} // found match --> keep match unchanged
}
match = false;
},
});
return '$ne' in criteria ? !match : match;
}

@@ -68,55 +75,56 @@

// evaluate condition for property value
var evalCondition = function(condition, value) {
// condition === expr
if (typeof condition !== 'object')
// NOTE: direct comparison between objects is always a bad idea
return defaultCompare(value, condition) === 0;
const evalCondition = function(condition, value) {
// condition === expr
// NOTE: direct comparison between objects is always a bad idea
if (typeof condition !== 'object') {
return defaultCompare(value, condition) === 0;
}
// use user-supplied comparison function?
var compare = "$using" in condition ? condition.$using : defaultCompare;
// use user-supplied comparison function?
const compare = '$using' in condition ? condition.$using : defaultCompare;
// condition === { op: expr, op: expr, ... }
for (var op in condition) {
var expr = condition[op];
switch (op) {
// NOTE: using !(...) to handle "undefined" values
case "$using":
break; // skip comparison function
case "$eq":
if (!(compare(value, expr) === 0)) return false;
break;
case "$ne":
var res = compare(value, expr);
if (!(res < 0 || res > 0)) return false;
break;
case "$lt":
if (!(compare(value, expr) < 0)) return false;
break;
case "$le":
if (!(compare(value, expr) <= 0)) return false;
break;
case "$gt":
if (!(compare(value, expr) > 0)) return false;
break;
case "$ge":
if (!(compare(value, expr) >= 0)) return false;
break;
case "$like":
if (!islike(expr, value)) return false;
break;
case "$unlike":
if (islike(expr, value) || typeof value === "undefined") return false;
break;
case "$null":
if (!(isnull(value) && expr || !isnull(value) && !expr)) return false;
break;
case "$empty":
throw new Error("invalid $empty operator on non-association");
case "$none":
throw new Error("invalid $none operator on non-association");
default:
throw new Error("invalid expression operator: " + op);
}
// condition === { op: expr, op: expr, ... }
for (const op in condition) {
const expr = condition[op];
switch (op) {
// NOTE: using !(...) to handle "undefined" values
case '$using':
break; // skip comparison function
case '$eq':
if (!(compare(value, expr) === 0)) return false;
break;
case '$ne':
const res = compare(value, expr);
if (!(res < 0 || res > 0)) return false;
break;
case '$lt':
if (!(compare(value, expr) < 0)) return false;
break;
case '$le':
if (!(compare(value, expr) <= 0)) return false;
break;
case '$gt':
if (!(compare(value, expr) > 0)) return false;
break;
case '$ge':
if (!(compare(value, expr) >= 0)) return false;
break;
case '$like':
if (!islike(expr, value)) return false;
break;
case '$unlike':
if (islike(expr, value) || typeof value === 'undefined') return false;
break;
case '$null':
if (!(isnull(value) && expr || !isnull(value) && !expr)) return false;
break;
case '$empty':
throw new Error('invalid $empty operator on non-association');
case '$none':
throw new Error('invalid $none operator on non-association');
default:
throw new Error(`invalid expression operator: ${ op}`);
}
return true;
}
return true;
};

@@ -127,3 +135,3 @@

function defaultCompare(lhs, rhs) {
return lhs < rhs ? -1 : lhs > rhs ? +1 : lhs == rhs ? 0 : undefined;
return lhs < rhs ? -1 : lhs > rhs ? +1 : lhs == rhs ? 0 : undefined;
};

@@ -134,4 +142,7 @@

function islike(pattern, value) {
var escaped = pattern.replace(/[-\\.^$*+?()|[\]{}]/g, '\\$&').replace(/%/g, ".*").replace(/_/g, ".");
return (new RegExp(escaped)).test(value);
const escaped = pattern
.replace(/[-\\.^$*+?()|[\]{}]/g, '\\$&')
.replace(/%/g, '.*')
.replace(/_/g, '.');
return (new RegExp(escaped)).test(value);
}

@@ -141,3 +152,3 @@

function isnull(value) {
return typeof value === "undefined" || value === null;
return typeof value === 'undefined' || value === null;
}

@@ -147,3 +158,3 @@

function isnone(value) {
return isnull(value) || utils.isArray(value) && value.length === 0;
return isnull(value) || utils.isArray(value) && value.length === 0;
}

@@ -1,28 +0,27 @@

var async = require("async");
const async = require('async');
var cds = require('./cds');
var metadata = require('./metadata');
var exprs = require('./exprs');
var utils = require('./utils');
var Queue = require('./util/Queue');
var ppp = utils.ppp; // @DEBUG
var transaction = require('./transaction');
const metadata = require('./metadata');
const exprs = require('./exprs');
const utils = require('./utils');
const Queue = require('./util/Queue');
const ppp = utils.ppp; // @DEBUG
const transaction = require('./transaction');
var logger = utils.logger;
const logger = utils.logger;
/// CRUD operations ////////////////////////////////////////////////////////////
// / CRUD operations ////////////////////////////////////////////////////////////
// cache for fully built entity instances; indexed by entityName x cacheId
var instanceCache = {};
var nextInstanceId = 1; // keeping track of objects @DEBUG
const instanceCache = {};
let nextInstanceId = 1; // keeping track of objects @DEBUG
exports.extensionPoints = {
instanceMethods: function(im){
return im;
}
instanceMethods: function(im) {
return im;
},
}; // hook for extenders
/// retrieve ///////////////////////////////////////////////////////////////////
// / retrieve ///////////////////////////////////////////////////////////////////

@@ -48,7 +47,7 @@ /*

// central request queue for serializing CRUD operations
var queue = new Queue.Queue();
const queue = new Queue.Queue();
exports._get = _get;
exports._find = function (client, entity, condition, callback) {
_get(client, entity, condition, callback, true);
exports._find = function(client, entity, condition, callback) {
_get(client, entity, condition, callback, true);
};

@@ -58,37 +57,43 @@

function _get(client, entity, key, callback, isFind) {
if (!("$_metadata" in entity))
return callback("not an entity");
if (!('$_metadata' in entity)) {
return callback('not an entity');
}
var entityName = entity.$_metadata.entityName;
var pool = {}; // work in progress
logger.debug("node-cds: getting " + entityName + " with key " +
JSON.stringify(projectOnKeys(entity, key)));
const entityName = entity.$_metadata.entityName;
const pool = {}; // work in progress
logger.debug(`node-cds: getting ${ entityName } with key ${
JSON.stringify(projectOnKeys(entity, key))}`);
if (entity.$_metadata.isUnmanaged)
return callback("Cannot get instance of unmanaged entity " + entityName);
if (entity.$_metadata.isUnmanaged) {
return callback(`Cannot get instance of unmanaged entity ${ entityName}`);
}
// convert to instances and update cache
function done(err) {
logger.debug("node-cds: " + entityName +
JSON.stringify(projectOnKeys(entity, key)) + " retrieved");
if (err)
return callback(err);
// convert to instances and update cache
function done(err) {
logger.debug(`node-cds: ${ entityName
}${JSON.stringify(projectOnKeys(entity, key)) } retrieved`);
if (err) {
return callback(err);
}
// add XS interface and wire up associations in wip skeletons, add to cache
for (var i in pool)
if (pool[i]) {
makeInstance(client, pool[i], pool, false);
}
// add XS interface and wire up associations in wip skeletons, add to cache
for (const i in pool) {
if (pool[i]) {
makeInstance(client, pool[i], pool, false);
}
}
// return result via cache
var result = isFind ?
// return result via cache
const result = isFind ?
findCached(client, entity, key) :
getCached(client, entity, key, true); // could be null
if (typeof result === "string") // indicates error
return callback(result);
else
return callback(null, result);
getCached(client, entity, key, true); // could be null
// indicates error
if (typeof result === 'string') {
return callback(result);
} else {
return callback(null, result);
}
}
fetchInstance(client, entity, key, pool, isFind, done);
fetchInstance(client, entity, key, pool, isFind, done);
}

@@ -101,136 +106,144 @@

function fetchInstance(client, entity, key, pool, isFind, callback) {
var entityName = entity.$_metadata.entityName;
const entityName = entity.$_metadata.entityName;
// request queue for fetching root and potential target instances
var todo = [];
// request queue for fetching root and potential target instances
const todo = [];
// initiate actual request for root instance
var debugId = entityName;
if (isFind) {
todo.push({entity: entity, key: key, isFind: true});
} else {
todo.push({entity: entity, key: projectOnKeys(entity, key)});
}
queue.push(start, callback, debugId);
// initiate actual request for root instance
const debugId = entityName;
if (isFind) {
todo.push({entity: entity, key: key, isFind: true});
} else {
todo.push({entity: entity, key: projectOnKeys(entity, key)});
}
queue.push(start, callback, debugId);
// main queue
// main queue
function start(callback) {
async.whilst(pending, loop, callback);
}
function start(callback) {
async.whilst(pending, loop, callback);
}
function pending() {
return todo.length > 0;
function pending() {
return todo.length > 0;
}
// main query loop
function loop(callback) {
const item = todo.shift();
if (!item) {
throw new Error('*** ASSERT FAIL *** empty todo list');
}
if (item.itemId && item.itemId in pool) {
return callback(null);
} // already retrieved
logger.debug(`node-cds fetch loop: processing ${ item.itemId}`);
// main query loop
function loop(callback) {
var item = todo.shift();
if (!item)
throw new Error("*** ASSERT FAIL *** empty todo list");
if (item.itemId && item.itemId in pool)
return callback(null); // already retrieved
logger.debug("node-cds fetch loop: processing " + item.itemId);
// check if cached
if (!item.isFind) {
var instance = getCached(client, entity, item.key);
if (instance && !instance.$_reload)
return callback(null);
}
// optimize: pre-compute at import
var relations = metadata.computeRelations(item.entity);
query(item.entity, item.key, relations, callback);
// check if cached
if (!item.isFind) {
const instance = getCached(client, entity, item.key);
if (instance && !instance.$_reload) {
return callback(null);
}
}
// queue items
// optimize: pre-compute at import
const relations = metadata.computeRelations(item.entity);
query(item.entity, item.key, relations, callback);
}
// query database for key
function query(entity, condition, relations, callback) {
if (condition.$_id)
throw new Error("*** ASSERT FAIL *** query condition is instance: " + ppp(condition));
// queue items
var q = entity.$query().$matching(condition).$project(relations.projection);
//logger.debug("node-cds SQL: " + q.$sql());
q.$execute(client, {$factorized: true, $noCommit: true}, function(err, skeletons) {
if (!err) {
logger.debug("node-cds: query returns " + skeletons.length + " results");
addSkeletonsToWipPool(entity, skeletons, relations.associations);
fetchRecursiveAssocs(skeletons, relations.cycles);
}
callback(err);
});
// query database for key
function query(entity, condition, relations, callback) {
if (condition.$_id) {
throw new Error(
`*** ASSERT FAIL *** query condition is instance: ${ ppp(condition)}`,
);
}
// add skeletons to work-in-progress pool
function addSkeletonsToWipPool(entity, skeletons, associations) {
const q = entity.$query().$matching(condition).$project(relations.projection);
// logger.debug("node-cds SQL: " + q.$sql());
q.$execute(client, {$factorized: true, $noCommit: true}, function(err, skeletons) {
if (!err) {
logger.debug(`node-cds: query returns ${ skeletons.length } results`);
addSkeletonsToWipPool(entity, skeletons, relations.associations);
fetchRecursiveAssocs(skeletons, relations.cycles);
}
callback(err);
});
}
var _add = function (entity, skeletons, index) {
var skeleton = skeletons[index];
if (skeleton === null)
return;
// add skeletons to work-in-progress pool
function addSkeletonsToWipPool(entity, skeletons, associations) {
const _add = function(entity, skeletons, index) {
const skeleton = skeletons[index];
if (skeleton === null) {
return;
}
var itemId = computeItemId(entity, skeleton);
if (itemId === null)
// NOTE: this may happen when (1) an association is key and
// (2) the foreign-key does not point to a valid instance
return;
const itemId = computeItemId(entity, skeleton);
// NOTE: this may happen when (1) an association is key and
// (2) the foreign-key does not point to a valid instance
if (itemId === null) {
return;
}
var instance = getCached(client, entity, skeleton);
if (instance && !instance.$_reload) {
if (!(itemId in pool))
pool[itemId] = instance; // add to wip pool to choke recursive gets
logger.debug("node-cds: added cached instance to pool: " + itemId);
} else {
if (itemId in pool) {
var existingSkeleton = pool[itemId];
skeletons[index] = existingSkeleton; // replace newcomer
logger.debug("node-cds: replaced skeleton in pool: " + itemId);
} else {
skeleton.$__entity = entity;
pool[itemId] = skeleton;
logger.debug("node-cds: added skeleton to pool: " + itemId);
}
}
};
const instance = getCached(client, entity, skeleton);
if (instance && !instance.$_reload) {
if (!(itemId in pool)) {
pool[itemId] = instance;
} // add to wip pool to choke recursive gets
logger.debug(`node-cds: added cached instance to pool: ${ itemId}`);
} else {
if (itemId in pool) {
const existingSkeleton = pool[itemId];
skeletons[index] = existingSkeleton; // replace newcomer
logger.debug(`node-cds: replaced skeleton in pool: ${ itemId}`);
} else {
skeleton.$__entity = entity;
pool[itemId] = skeleton;
logger.debug(`node-cds: added skeleton to pool: ${ itemId}`);
}
}
};
for (var i in skeletons) {
_add(entity, skeletons, i);
for (var a in associations) {
var assoc = associations[a];
var targetEntity = assoc.target;
var targetSkeletons = utils.getPropPathSet(skeletons[i], assoc.field);
if (!assoc.lazy) {
var hasTarget = false;
for (var j in targetSkeletons) {
_add(targetEntity, targetSkeletons, j);
hasTarget = true;
}
if (!hasTarget)
utils.setPropPath(skeletons[i], assoc.field, assoc.toMany ? [] : null);
}
}
for (const i in skeletons) {
_add(entity, skeletons, i);
for (const a in associations) {
const assoc = associations[a];
const targetEntity = assoc.target;
const targetSkeletons = utils.getPropPathSet(skeletons[i], assoc.field);
if (!assoc.lazy) {
let hasTarget = false;
for (const j in targetSkeletons) {
_add(targetEntity, targetSkeletons, j);
hasTarget = true;
}
if (!hasTarget) {
utils.setPropPath(skeletons[i], assoc.field, assoc.toMany ? [] : null);
}
}
}
}
}
// fetch targets of recursive associations
function fetchRecursiveAssocs(skeletons, cycles) {
for (var i in skeletons) {
for (var c in cycles) {
var entity = cycles[c].assoc.$class;
var links = utils.getPropPathSet(skeletons[i], cycles[c].field);
for (var f in links) {
var link = links[f];
if (link && !link.$_id) {
var itemId = computeItemId(entity, link);
todo.push({itemId: itemId, entity: entity, key: link});
logger.debug("node-cds: requesting recursive assoc " +
entity.$_metadata.entityName + JSON.stringify(link));
}
}
}
// fetch targets of recursive associations
function fetchRecursiveAssocs(skeletons, cycles) {
for (const i in skeletons) {
for (const c in cycles) {
const entity = cycles[c].assoc.$class;
const links = utils.getPropPathSet(skeletons[i], cycles[c].field);
for (const f in links) {
const link = links[f];
if (link && !link.$_id) {
const itemId = computeItemId(entity, link);
todo.push({itemId: itemId, entity: entity, key: link});
logger.debug(`node-cds: requesting recursive assoc ${
entity.$_metadata.entityName }${JSON.stringify(link)}`);
}
}
}
}
}
}

@@ -241,24 +254,25 @@

function makeInstance(client, skeleton, pool, needsDeepCopy) {
var entity = skeleton.$__entity || skeleton.$_entity; // || skeleton.$
var cacheId = computeKeyId(entity, skeleton);
const entity = skeleton.$__entity || skeleton.$_entity; // || skeleton.$
const cacheId = computeKeyId(entity, skeleton);
// check for cached instance
var instance = getCached(client, entity, skeleton);
if (instance)
return;
// check for cached instance
let instance = getCached(client, entity, skeleton);
if (instance) {
return;
}
// build instance from property skeleton
if (needsDeepCopy) {
instance = utils.deepCopy(skeleton);
} else {
instance = skeleton;
}
delete instance.$__entity;
// build instance from property skeleton
if (needsDeepCopy) {
instance = utils.deepCopy(skeleton);
} else {
instance = skeleton;
}
delete instance.$__entity;
logger.debug("node-cds: making instance for " + entity + " : " + cacheId);
makeAssociations(client, entity, instance, pool);
lockKeys(entity, instance);
addXSInterface(client, entity, instance);
logger.debug(`node-cds: making instance for ${ entity } : ${ cacheId}`);
makeAssociations(client, entity, instance, pool);
lockKeys(entity, instance);
addXSInterface(client, entity, instance);
addCache(client, instance, cacheId);
addCache(client, instance, cacheId);
}

@@ -268,78 +282,87 @@

function makeAssociations(client, entity, instance, pool) {
// load function for lazy associations
var load = function (entity, field, value) {
return function (cb) {
// load based on skeleton key information
client.$get(entity, value[field], function (err, target) {
value[field] = target;
cb(err, target);
});
};
// load function for lazy associations
const load = function(entity, field, value) {
return function(cb) {
// load based on skeleton key information
client.$get(entity, value[field], function(err, target) {
value[field] = target;
cb(err, target);
});
};
};
// find existing instance or wip item for skeleton
var getFromWip = function (entity, skeleton) {
var itemId = computeItemId(entity, skeleton);
if (!itemId)
return null; // missing target
return getCached(client, entity, skeleton) || pool[itemId] || null;
};
// find existing instance or wip item for skeleton
const getFromWip = function(entity, skeleton) {
const itemId = computeItemId(entity, skeleton);
if (!itemId) {
return null;
} // missing target
return getCached(client, entity, skeleton) || pool[itemId] || null;
};
// process all associations
utils.forInstance(instance, entity.$_mapping, {
$association: function (p, f, v, m) {
var a = m[f].$association;
var targetEntity = a.$class;
if (typeof v[f] === "undefined") {
var isToMany = utils.isToMany(a);
v[f] = isToMany ? [] : null; // @DESIGN
}
if (a.$lazy) {
utils.addInternalProp(v[f], "$load", load(targetEntity, f, v));
return;
}
if (utils.isArray(v[f])) {
var is = [];
for (var j in v[f])
is.push(getFromWip(targetEntity, v[f][j]));
v[f] = is;
} else {
v[f] = getFromWip(targetEntity, v[f]);
}
if (a.$viaBacklink || a.$on) {
utils.addInternalProp(v[f], "$reload", reloadInstance(client, entity, instance, p + f));
}
// process all associations
utils.forInstance(instance, entity.$_mapping, {
$association: function(p, f, v, m) {
const a = m[f].$association;
const targetEntity = a.$class;
if (typeof v[f] === 'undefined') {
const isToMany = utils.isToMany(a);
v[f] = isToMany ? [] : null; // @DESIGN
}
if (a.$lazy) {
utils.addInternalProp(v[f], '$load', load(targetEntity, f, v));
return;
}
if (utils.isArray(v[f])) {
const is = [];
for (const j in v[f]) {
is.push(getFromWip(targetEntity, v[f][j]));
}
});
v[f] = is;
} else {
v[f] = getFromWip(targetEntity, v[f]);
}
if (a.$viaBacklink || a.$on) {
utils.addInternalProp(
v[f],
'$reload',
reloadInstance(client, entity, instance, p + f),
);
}
},
});
}
// reload instance function, e.g., for unmanaged assocs
var reloadInstance = function (client, entity, instance, path) {
return function (cb) {
var reloadPool = {};
var key = projectOnKeys(entity, instance);
++instance.$_reload; // instance.$_reload = true;
const reloadInstance = function(client, entity, instance, path) {
return function(cb) {
const reloadPool = {};
const key = projectOnKeys(entity, instance);
++instance.$_reload; // instance.$_reload = true;
function done(err) {
if (err)
cb(err);
var itemId = computeItemId(entity, instance);
var reloadedInstance = reloadPool[itemId];
if (!reloadedInstance)
throw new Error("*** ASSERT FAIL *** missing reload instance");
makeAssociations(client, entity, reloadedInstance, reloadPool);
var targets = utils.getPropPath(reloadedInstance, path) || [];
utils.setPropPath(instance, path, targets);
utils.addInternalProp(targets, "$reload",
reloadInstance(client, entity, instance, path)); // rearm reloader
--instance.$_reload; //delete(instance.$_reload);
function done(err) {
if (err) {
cb(err);
}
const itemId = computeItemId(entity, instance);
const reloadedInstance = reloadPool[itemId];
if (!reloadedInstance) {
throw new Error('*** ASSERT FAIL *** missing reload instance');
}
makeAssociations(client, entity, reloadedInstance, reloadPool);
const targets = utils.getPropPath(reloadedInstance, path) || [];
utils.setPropPath(instance, path, targets);
utils.addInternalProp(targets, '$reload',
reloadInstance(client, entity, instance, path)); // rearm reloader
--instance.$_reload; // delete(instance.$_reload);
for (var i in reloadPool)
if (i !== itemId && reloadPool[i]) {
makeInstance(client, reloadPool[i], reloadPool, false);
}
cb(null, targets); // pass only reloaded assoc target to callback
};
fetchInstance(client, entity, key, reloadPool, false, done);
for (const i in reloadPool) {
if (i !== itemId && reloadPool[i]) {
makeInstance(client, reloadPool[i], reloadPool, false);
}
}
cb(null, targets); // pass only reloaded assoc target to callback
};
fetchInstance(client, entity, key, reloadPool, false, done);
};
};

@@ -349,16 +372,16 @@

function addXSInterface(client, entity, instance) {
Object.defineProperties(instance, exports.extensionPoints.instanceMethods({
$_id: {value: nextInstanceId++},
$_client: client,
$_entity: {value: entity},
$_tx: {value: 0, writable: true},
$_reload: {value: 0, writable: true},
}));
Object.defineProperties(instance, exports.extensionPoints.instanceMethods({
$_id: {value: nextInstanceId++},
$_client: client,
$_entity: {value: entity},
$_tx: {value: 0, writable: true},
$_reload: {value: 0, writable: true},
}));
}
/// create/update /////////////////////////////////////////////////////////
// / create/update /////////////////////////////////////////////////////////
exports._save = function(client, instance, callback) {
_save(client, instance.$_entity || instance.$entity, instance, callback);
_save(client, instance.$_entity || instance.$entity, instance, callback);
};

@@ -368,131 +391,160 @@

function _save(client, entity, instance, callback) {
_tx = Date.now();
var keys = [], saves = [], links = [], reloads = [];
collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads);
logger.debug("node-cds: saving instance " + instance.$_id + ", #keys=" +
keys.length + " #saves=" + saves.length + " #links=" + links.length +
" #reloads=" + reloads.length);
async.series([].concat(keys, saves, links, reloads), function(err) {
logger.debug("node-cds: saving instance " + instance.$_id + " done");
if (err)
return callback(err);
if (client.$_autoCommit)
client.$commit(function (err) { callback(err, instance); });
else
callback(err, instance);
});
const _tx = Date.now();
const keys = []; const saves = []; const links = []; const reloads = [];
collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads);
logger.debug(`node-cds: saving instance ${ instance.$_id }, #keys=${
keys.length } #saves=${ saves.length } #links=${ links.length
} #reloads=${ reloads.length}`);
async.series([].concat(keys, saves, links, reloads), function(err) {
logger.debug(`node-cds: saving instance ${ instance.$_id } done`);
if (err) {
return callback(err);
}
if (client.$_autoCommit) {
client.$commit(function(err) {
callback(err, instance);
});
} else {
callback(err, instance);
}
});
}
// (sync) recursively collect all instances that need to be updated
function collectInstancesToSave(client, entity, instance, _tx, keys, saves, links, reloads) {
if (instance.$_tx && instance.$_tx >= _tx)
return; // already saved or to be saved
if (instance.$load)
return; // don't attempt to save unresolved lazy associations
function collectInstancesToSave(
client, entity, instance, _tx, keys, saves, links, reloads,
) {
if (instance.$_tx && instance.$_tx >= _tx) {
return;
} // already saved or to be saved
if (instance.$load) {
return;
} // don't attempt to save unresolved lazy associations
// (async) insert or update single instance
function upsert(entity, instance, isCreate, callback) {
var cloneForSave = createInstance(client, entity, instance, isCreate);
var key = projectOnKeys(instance.$_entity, instance);
var query = entity.$query().$matching(key).$values(cloneForSave);
query = isCreate ? query.$insert() : query.$update();
query.$execute(client, {$noCommit: true}, callback);
// (async) insert or update single instance
function upsert(entity, instance, isCreate, callback) {
const cloneForSave = createInstance(client, entity, instance);
const key = projectOnKeys(instance.$_entity, instance);
let query = entity.$query().$matching(key).$values(cloneForSave);
query = isCreate ? query.$insert() : query.$update();
query.$execute(client, {$noCommit: true}, callback);
}
// update via entity links
const updateLinks = function(entity, instance, assoc, targets, linkEntity, callback) {
const qs = [];
const sourceKey = projectOnKeys(entity, instance);
// delete old links
qs.push(function(cb) {
const link = {};
link[assoc.$source] = sourceKey;
linkEntity
.$query()
.$matching(link)
.$discard()
.$execute(client, {$noCommit: true}, cb);
});
// create new links
for (let i = 0; i < targets.length; ++i) {
qs.push((function(t) {
return function(cb) {
const link = {}; // must create unique object in each loop iteration
link[assoc.$source] = sourceKey;
link[assoc.$target] = t;
linkEntity
.$query()
.$values(link)
.$insert()
.$execute(client, {$noCommit: true}, cb);
};
})(targets[i]));
}
async.series(qs, callback);
};
// update via entity links
var updateLinks = function (entity, instance, assoc, targets, linkEntity, callback) {
var qs = [];
var sourceKey = projectOnKeys(entity, instance);
// delete old links
qs.push(function (cb) {
var link = {};
link[assoc.$source] = sourceKey;
linkEntity.$query().$matching(link).$discard().$execute(client, {$noCommit: true}, cb);
const isCreate = typeof instance.$_id === 'undefined';
const isUnmanaged = entity.$_metadata.isUnmanaged;
if (isCreate) {
Object.defineProperty(instance, '$_tx', {value: 0, writable: true});
}
instance.$_tx = _tx;
// collect associated instances
utils.forInstance(instance, entity.$_mapping, {
$column: function(p, f, v, m) {
// generate missing keys
if (typeof m[f].$key === 'string' && v && typeof v[f] === 'undefined') {
// v[f] = { $key: utils.quoteTable(m[f].$key };
// @NOTE: The current architecture doesn't support the creation of keys
// during INSERT, as the application wouldn't know about the
// key values. We thus have to retrieve the keys manually from
// the database sequence.
keys.push(function(cb) {
querySequence(m[f].$key, function(err, seq) {
v[f] = seq;
cb(err);
});
});
// create new links
for (i = 0; i < targets.length; ++i) {
qs.push((function(t) { return function (cb) {
var link = {}; // must create unique object in each loop iteration
link[assoc.$source] = sourceKey;
link[assoc.$target] = t;
linkEntity.$query().$values(link).$insert().$execute(client, {$noCommit: true}, cb);
}; })(targets[i]));
}
},
$association: function(p, f, v, m, e) {
if (isUnmanaged || v[f] === null) {
return;
}
const assoc = m[f].$association;
// fix missing target property
if (typeof v[f] === 'undefined') {
v[f] = utils.isToMany(assoc) ? [] : null;
}
const targets = utils.isArray(v[f]) ? v[f] : [v[f]];
if (assoc.$viaEntity) {
const linkEntity = assoc.$viaClass;
links.push(function(cb) {
updateLinks(entity, instance, assoc, targets, linkEntity, cb);
});
} else if (assoc.$viaBacklink) {
// update backlink in children (thus adding children to association)
// only if instance is newly created -- updating backlinks unconditionally
// would pose potential conflict when updating multiple children in
// parallel (see spec text case "save backlinks/parallel child update")
if (isCreate) {
logger.debug(`node-cds: updating target backlinks for ${ instance.$_id}`);
const backlink = assoc.$viaBacklink;
for (const i in targets) {
utils.setPropPath(targets[i], backlink, instance);
}
}
async.series(qs, callback);
};
}
var isCreate = typeof instance.$_id === "undefined";
var isUnmanaged = entity.$_metadata.isUnmanaged;
if (isCreate)
Object.defineProperty(instance, "$_tx", {value: 0, writable: true});
instance.$_tx = _tx;
// add reload functions
if (assoc.$viaBacklink || assoc.$on) {
if (!v[f].$reload) {
utils.addInternalProp(
v[f], '$reload', reloadInstance(client, entity, instance, p + f),
);
}
// trigger reload of unmanaged associations
reloads.push(function(cb) {
if (v[f].$reload) {
v[f].$reload(cb);
} else {
logger.warn('node-cds: missing $reload function');
} // removed by user
});
}
// collect associated instances
utils.forInstance(instance, entity.$_mapping, {
$column: function (p, f, v, m) {
// generate missing keys
if (typeof m[f].$key === "string" && v && typeof v[f] === "undefined") {
//v[f] = { $key: utils.quoteTable(m[f].$key };
//@NOTE: The current architecture doesn't support the creation of keys
// during INSERT, as the application wouldn't know about the
// key values. We thus have to retrieve the keys manually from
// the database sequence.
keys.push(function (cb) {
querySequence(m[f].$key, function (err, seq) {
v[f] = seq;
cb(err);
});
});
}
},
$association: function(p, f, v, m, e) {
if (isUnmanaged || v[f] === null)
return;
var assoc = m[f].$association;
// fix missing target property
if (typeof v[f] === "undefined")
v[f] = utils.isToMany(assoc) ? [] : null;
// save target instances
for (const i in targets) {
collectInstancesToSave(
client, assoc.$class, targets[i], _tx, keys, saves, links, reloads,
);
}
},
});
var targets = utils.isArray(v[f]) ? v[f] : [v[f]];
if (assoc.$viaEntity) {
var linkEntity = assoc.$viaClass;
links.push(function(cb) {
updateLinks(entity, instance, assoc, targets, linkEntity, cb);
});
} else if (assoc.$viaBacklink) {
// update backlink in children (thus adding children to association)
// only if instance is newly created -- updating backlinks unconditionally
// would pose potential conflict when updating multiple children in
// parallel (see spec text case "save backlinks/parallel child update")
if (isCreate) {
logger.debug("node-cds: updating target backlinks for " + instance.$_id);
var backlink = assoc.$viaBacklink;
for (var i in targets)
utils.setPropPath(targets[i], backlink, instance);
}
}
// add reload functions
if (assoc.$viaBacklink || assoc.$on) {
if (!v[f].$reload)
utils.addInternalProp(v[f], "$reload", reloadInstance(client, entity, instance, p + f));
// trigger reload of unmanaged associations
reloads.push(function (cb) {
if (v[f].$reload)
v[f].$reload(cb);
else
logger.warn("node-cds: missing $reload function"); // removed by user
});
}
// save target instances
for (var i in targets)
collectInstancesToSave(client, assoc.$class, targets[i], _tx, keys, saves, links, reloads);
}
});
// register upsert of root instance
saves.push(function(cb) {
upsert(entity, instance, isCreate, cb);
});
// register upsert of root instance
saves.push(function(cb) {
upsert(entity, instance, isCreate, cb);
});
}

@@ -502,41 +554,45 @@

function createInstance(client, entity, skeleton) {
// missing target instance?
if (skeleton === undefined) // e.g., missing assoc target from get
return;
// missing target instance?
if (skeleton === undefined) {
// e.g., missing assoc target from get
return;
}
// clone instance for $query, which might get confused
var colValueClone = {};
utils.forInstance(skeleton, entity.$_mapping, {
$column: function (p, f, v, m) {
// clone column values
var x = v && f in v ? v[f] : undefined;
utils.setPropPath(colValueClone, p + f, x);
// lock keys
if (m[f].$key)
Object.defineProperty(v, f, {writable: false});
}
});
if (!skeleton.$_id) {
// add XS-style interface}
Object.defineProperties(skeleton, exports.extensionPoints.instanceMethods({
$_id: {value: nextInstanceId++},
$_client: client,
$_entity: {value: entity},
$_reload: {value: 0, writable: true},
}));
// clone instance for $query, which might get confused
const colValueClone = {};
utils.forInstance(skeleton, entity.$_mapping, {
$column: function(p, f, v, m) {
// clone column values
const x = v && f in v ? v[f] : undefined;
utils.setPropPath(colValueClone, p + f, x);
// lock keys
if (m[f].$key) {
Object.defineProperty(v, f, {writable: false});
}
},
});
// add to cache
var cacheId = computeKeyId(entity, skeleton);
addCache(client, skeleton, cacheId);
}
if (!skeleton.$_id) {
// add XS-style interface}
Object.defineProperties(skeleton, exports.extensionPoints.instanceMethods({
$_id: {value: nextInstanceId++},
$_client: client,
$_entity: {value: entity},
$_reload: {value: 0, writable: true},
}));
return colValueClone;
// add to cache
const cacheId = computeKeyId(entity, skeleton);
addCache(client, skeleton, cacheId);
}
return colValueClone;
}
/// discard /////////////////////////////////////////////////////////
// / discard /////////////////////////////////////////////////////////
exports._discard = function (client, instance, callback) {
_discard(client, instance.$_entity || instance.$entity, instance, callback);
exports._discard = function(client, instance, callback) {
_discard(client, instance.$_entity || instance.$entity, instance, callback);
};

@@ -546,95 +602,104 @@

function _discard(client, entity, instance, callback) {
_tx = Date.now();
var deletes = [];
var id = instance.$_id;
collectInstancesToDiscard(client, entity, instance, _tx, deletes);
logger.debug("node-cds: discarding instance " + id + ", #deletes=" + deletes.length);
async.series(deletes, function(err) {
logger.debug("node-cds: discarding of instance " + id + " complete");
if (!err && client.$_autoCommit)
client.$commit(callback);
else
callback(err); // strip result of series call
});
const _tx = Date.now();
const deletes = [];
const id = instance.$_id;
collectInstancesToDiscard(client, entity, instance, _tx, deletes);
logger.debug(`node-cds: discarding instance ${ id }, #deletes=${ deletes.length}`);
async.series(deletes, function(err) {
logger.debug(`node-cds: discarding of instance ${ id } complete`);
if (!err && client.$_autoCommit) {
client.$commit(callback);
} else {
callback(err);
} // strip result of series call
});
}
function collectInstancesToDiscard(client, entity, instance, _tx, deletes) {
if (instance.$_tx && instance.$_tx >= _tx)
return; // already marked for deletion
instance.$_tx = _tx;
if (instance.$_tx && instance.$_tx >= _tx) {
return;
} // already marked for deletion
instance.$_tx = _tx;
function discard(entity, key) {
deletes.push(function(cb) {
var query = entity.$query().$matching(key).$discard();
query.$execute(client, {$noCommit: true}, cb);
});
}
function discard(entity, key) {
deletes.push(function(cb) {
const query = entity.$query().$matching(key).$discard();
query.$execute(client, {$noCommit: true}, cb);
});
}
// discard root instance
removeCache(client, instance);
var key = projectOnKeys(entity, instance);
discard(entity, key);
// discard root instance
removeCache(client, instance);
const key = projectOnKeys(entity, instance);
discard(entity, key);
// cascade discard to associated instances
utils.forInstance(instance, entity.$_mapping, {
$association: function (p, f, v, m, e) {
// delete via entity links
if (m[f].$association.$viaEntity) {
var linkEntity = m[f].$association.$viaClass;
var link = {};
link[m[f].$association.$source] = key;
discard(linkEntity, link);
}
// cascade discard to associated instances
utils.forInstance(instance, entity.$_mapping, {
$association: function(p, f, v, m, e) {
// delete via entity links
let linkEntity;
let link;
if (m[f].$association.$viaEntity) {
linkEntity = m[f].$association.$viaClass;
link = {};
link[m[f].$association.$source] = key;
discard(linkEntity, link);
}
if (!m[f].$association.$cascadeDiscard)
return;
if (!m[f].$association.$cascadeDiscard) {
return;
}
// discard unresolved lazy associations
if (m[f].$association.$lazy && v[f].$load) {
deletes.push(function(cb) {
v[f].$load(function (err, targets) {
if (err)
return cb(err);
var ts = utils.isArray(targets) ? targets : [targets];
client.$discardAll(ts, cb);
});
});
return;
// discard unresolved lazy associations
if (m[f].$association.$lazy && v[f].$load) {
deletes.push(function(cb) {
v[f].$load(function(err, targets) {
if (err) {
return cb(err);
}
const ts = utils.isArray(targets) ? targets : [targets];
client.$discardAll(ts, cb);
});
});
return;
}
// discard target instances
var targetEntity = m[f].$association.$class;
var targets = utils.isArray(v[f]) ? v[f] : [v[f]];
for (var i in targets)
if (targets[i]) {
collectInstancesToDiscard(client, targetEntity, targets[i], _tx, deletes);
// discard target instances
const targetEntity = m[f].$association.$class;
const targets = utils.isArray(v[f]) ? v[f] : [v[f]];
for (const i in targets) {
if (targets[i]) {
collectInstancesToDiscard(client, targetEntity, targets[i], _tx, deletes);
// delete backlinking via entity entries for discarded targets
if (m[f].$association.$viaEntity) {
link = {};
var targetKey = projectOnKeys(targetEntity, targets[i]);
link[m[f].$association.$target] = targetKey;
discard(linkEntity, link);
}
}
// delete backlinking via entity entries for discarded targets
if (m[f].$association.$viaEntity) {
link = {};
const targetKey = projectOnKeys(targetEntity, targets[i]);
link[m[f].$association.$target] = targetKey;
discard(linkEntity, link);
}
}
});
}
},
});
}
/// unmanaged operations ///////////////////////////////////////////////////////
// / unmanaged operations ///////////////////////////////////////////////////////
// unmanaged delete: delete without associations, ignoring cache
exports._delete = function (client, entity, condition, callback) {
entity.$query().$matching(condition).$discard()
.$execute(client, function(err) { callback(err) });
exports._delete = function(client, entity, condition, callback) {
entity.$query().$matching(condition).$discard()
.$execute(client, function(err) {
callback(err);
});
};
/// internal functions /////////////////////////////////////////////////////////
// / internal functions /////////////////////////////////////////////////////////
// install new cache for given transaction
exports.openCache = function(tx) {
instanceCache[tx.$_tx_id] = {};
exports._clearCaches(tx);
instanceCache[tx.$_tx_id] = {};
exports._clearCaches(tx);
};

@@ -644,3 +709,3 @@

exports.closeCache = function(tx) {
delete(instanceCache[tx.$_tx_id]);
delete(instanceCache[tx.$_tx_id]);
};

@@ -650,5 +715,6 @@

exports._clearCaches = function(tx) {
var entities = metadata.getKnownEntities();
for (var name in entities)
instanceCache[tx.$_tx_id][name] = {};
const entities = metadata.getKnownEntities();
for (const name in entities) {
instanceCache[tx.$_tx_id][name] = {};
}
};

@@ -658,83 +724,96 @@

function getCached(tx, entity, props, reportErrors) {
var cacheId = computeKeyId(entity, props);
if (!cacheId)
return reportErrors ? "invalid key" : null;
var name = entity.$_metadata.entityName;
if (!(tx.$_tx_id in instanceCache))
throw new Error("*** ASSERT FAIL *** missing cache for tx " + tx.$_tx_id);
if (!(name in instanceCache[tx.$_tx_id]))
throw new Error("*** ASSERT FAIL *** unregistered entity " + name);
return instanceCache[tx.$_tx_id][name][cacheId] || null;
const cacheId = computeKeyId(entity, props);
if (!cacheId) {
return reportErrors ? 'invalid key' : null;
}
const name = entity.$_metadata.entityName;
if (!(tx.$_tx_id in instanceCache)) {
throw new Error(`*** ASSERT FAIL *** missing cache for tx ${ tx.$_tx_id}`);
}
if (!(name in instanceCache[tx.$_tx_id])) {
throw new Error(`*** ASSERT FAIL *** unregistered entity ${ name}`);
}
return instanceCache[tx.$_tx_id][name][cacheId] || null;
}
function findCached(tx, entity, condition) {
var name = entity.$_metadata.entityName;
var txid = tx.$_tx_id;
if (!(name in instanceCache[txid]))
throw new Error("*** ASSERT FAIL *** unregistered entity: " + name);
const name = entity.$_metadata.entityName;
const txid = tx.$_tx_id;
if (!(name in instanceCache[txid])) {
throw new Error(`*** ASSERT FAIL *** unregistered entity: ${ name}`);
}
// build filter function
var matches = exprs.buildInstanceFilter(entity, condition);
// build filter function
const matches = exprs.buildInstanceFilter(entity, condition);
// apply filter to cache (synchronous)
var result = [];
try {
for (var i in instanceCache[txid][name])
if (matches(instanceCache[txid][name][i]))
result.push(instanceCache[txid][name][i]);
} catch (e) {
return "Error: " + e; // return type string -> error
// apply filter to cache (synchronous)
const result = [];
try {
for (const i in instanceCache[txid][name]) {
if (matches(instanceCache[txid][name][i])) {
result.push(instanceCache[txid][name][i]);
}
}
} catch (e) {
return `Error: ${ e}`; // return type string -> error
}
return result;
return result;
}
function addCache(tx, instance, cacheId) {
var entity = instance.$_entity;
if (entity === undefined)
throw new Error("*** ASSERT FAIL *** not an entity instance");
var name = entity.$_metadata.entityName;
if (!cacheId)
logger.warn("node-cds: invalid key for instance of " + name);
else
instanceCache[tx.$_tx_id][name][cacheId] = instance;
const entity = instance.$_entity;
if (entity === undefined) {
throw new Error('*** ASSERT FAIL *** not an entity instance');
}
const name = entity.$_metadata.entityName;
if (!cacheId) {
logger.warn(`node-cds: invalid key for instance of ${ name}`);
} else {
instanceCache[tx.$_tx_id][name][cacheId] = instance;
}
}
function removeCache(tx, instance) {
var entity = instance.$_entity;
if (entity === undefined)
return; // not an instance, but discard by key
const entity = instance.$_entity;
if (entity === undefined) {
return;
} // not an instance, but discard by key
var name = entity.$_metadata.entityName;
var cacheId = computeKeyId(entity, instance);
if (!cacheId)
throw new Error("*** ASSERT FAIL *** invalid cache id");
delete instanceCache[tx.$_tx_id][name][cacheId];
const name = entity.$_metadata.entityName;
const cacheId = computeKeyId(entity, instance);
if (!cacheId) {
throw new Error('*** ASSERT FAIL *** invalid cache id');
}
delete instanceCache[tx.$_tx_id][name][cacheId];
}
function computeItemId(entity, properties) {
var cacheId = computeKeyId(entity, properties);
return cacheId ? entity.$_metadata.entityName + "##" + cacheId : null;
const cacheId = computeKeyId(entity, properties);
return cacheId ? `${entity.$_metadata.entityName }##${ cacheId}` : null;
}
function computeKeyId(entity, properties) {
var keyValues = getKeyValues(entity, properties);
if (!keyValues)
return null;
var id = "#";
for (var i in keyValues)
id += hash(keyValues[i]);
return id;
const keyValues = getKeyValues(entity, properties);
if (!keyValues) {
return null;
}
let id = '#';
for (const i in keyValues) {
id += hash(keyValues[i]);
}
return id;
}
function getKeyValues(entity, properties, relaxed) {
var keyValues = [];
var keyFields = entity.$_metadata.keyFields;
for (var k in keyFields) {
var v = utils.getPropPath(properties, k);
if (typeof v === "undefined" && !relaxed)
return null; // incomplete key yields null if not in relaxed mode
keyValues.push(v);
}
return keyValues;
const keyValues = [];
const keyFields = entity.$_metadata.keyFields;
for (const k in keyFields) {
const v = utils.getPropPath(properties, k);
if (typeof v === 'undefined' && !relaxed) {
return null;
} // incomplete key yields null if not in relaxed mode
keyValues.push(v);
}
return keyValues;
}

@@ -744,10 +823,11 @@

function projectOnKeys(entity, instance) {
var key = {};
utils.forInstance(instance, entity.$_mapping, {
$column: function(p, f, v, m) {
if (m[f].$key)
utils.setPropPath(key, p + f, v[f]);
}
});
return key;
const key = {};
utils.forInstance(instance, entity.$_mapping, {
$column: function(p, f, v, m) {
if (m[f].$key) {
utils.setPropPath(key, p + f, v[f]);
}
},
});
return key;
}

@@ -757,21 +837,23 @@

function lockKeys(entity, skeleton) {
utils.forInstance(skeleton, entity.$_mapping, {
$column: function (p, f, v, m) {
if (m[f].$key)
Object.defineProperty(v, f, {writable: false});
}
});
utils.forInstance(skeleton, entity.$_mapping, {
$column: function(p, f, v, m) {
if (m[f].$key) {
Object.defineProperty(v, f, {writable: false});
}
},
});
}
function querySequence(key, callback) {
logger.debug("node-cds: query sequence " + key);
transaction.getClient(null, function(err, client) {
if (err)
return callback(err);
client.exec("SELECT " + key + ".NEXTVAL AS V FROM DUMMY", function(err, result) {
transaction.releaseClient(client);
logger.debug("node-cds: received sequence value " + result[0].V);
callback(err, result[0].V);
});
logger.debug(`node-cds: query sequence ${ key}`);
transaction.getClient(null, function(err, client) {
if (err) {
return callback(err);
}
client.exec(`SELECT ${ key }.NEXTVAL AS V FROM DUMMY`, function(err, result) {
transaction.releaseClient(client);
logger.debug(`node-cds: received sequence value ${ result[0].V}`);
callback(err, result[0].V);
});
});
}

@@ -781,28 +863,28 @@

function hash(data) {
var dataView = new DataView(new ArrayBuffer(8));
switch (typeof data) {
case "undefined":
return "U";
case "number":
// see http://www.ecma-international.org/ecma-262/5.1/#sec-9.8.1
return "N" + data;
case "boolean":
return data ? "B1" : "B0";
case "string":
var str = data.toString(),
hash = 0;
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
hash = c + (hash << 6) + (hash << 16) - hash;
}
return "S" + hash;
case "object":
if (data === null)
return "Q";
if (data instanceof Date)
return "D" + data.getTime();
/* fall-through */
default:
}
throw new Error("*** ASSERT FAIL *** hash: invalid type: " + typeof data);
switch (typeof data) {
case 'undefined':
return 'U';
case 'number':
return `N${ data}`;
case 'boolean':
return data ? 'B1' : 'B0';
case 'string':
const str = data.toString();
let hash = 0;
for (let i = 0; i < str.length; i++) {
const c = str.charCodeAt(i);
hash = c + (hash << 6) + (hash << 16) - hash;
}
return `S${ hash}`;
case 'object':
if (data === null) {
return 'Q';
}
if (data instanceof Date) {
return `D${ data.getTime()}`;
}
/* fall-through */
default:
}
throw new Error(`*** ASSERT FAIL *** hash: invalid type: ${ typeof data}`);
}

@@ -1,24 +0,23 @@

var async = require('async');
const async = require('async');
var transaction = require('./transaction');
var manager = require('./manager');
var utils = require('./utils');
var Queue = require('./util/Queue');
var queries = require("./cds-queries");
var SqlQuery = queries.Query;
const transaction = require('./transaction');
const utils = require('./utils');
const Queue = require('./util/Queue');
const queries = require('./cds-queries');
const SqlQuery = queries.Query;
var logger = utils.logger;
const logger = utils.logger;
/// entity management ///////////////////////////////////////////////////////
// / entity management ///////////////////////////////////////////////////////
// known resolved entities
var knownEntities = {};
let knownEntities = {};
// pending callbacks for async getEntity()
var importNotifications = {};
const importNotifications = {};
// metadata cache
var sqlMetadata = {};
var cdsMetadata = {};
const sqlMetadata = {};
const cdsMetadata = {};

@@ -40,129 +39,143 @@

// central import request queue for serializing imports
var importQueue = new Queue.Queue();
const importQueue = new Queue.Queue();
exports._import = function (refs, opts, callback) {
// work in progress: entities and dependencies to import
var pool = {};
exports._import = function(refs, opts, callback) {
// work in progress: entities and dependencies to import
const pool = {};
// request import of dependent associated entities
function addDepImports(entity) {
utils.forStruct(entity.$_mapping, {
$association: function(s, f, p) {
var name = s[f].$association.$entity;
var schema = opts.$schema || s[f].$association.$schema;
todo.push({ $entity: name, $schema: schema,
$options: { $auto: true } });
logger.debug("node-cds import: added dependency " +
entity.$_metadata.entityName + " -> " + name);
if (s[f].$association.$viaEntity) {
name = s[f].$association.$viaEntity;
todo.push({$entity: name, $schema: schema,
$options: {$unmanaged: true, $auto: true}});
logger.debug("node-cds import: added dependency " +
entity.$_metadata.entityName + " -> " + name);
}
}
});
}
// request import of dependent associated entities
function addDepImports(entity) {
utils.forStruct(entity.$_mapping, {
$association: function(s, f, p) {
let name = s[f].$association.$entity;
const schema = opts.$schema || s[f].$association.$schema;
todo.push({$entity: name, $schema: schema,
$options: {$auto: true}});
logger.debug(`node-cds import: added dependency ${
entity.$_metadata.entityName } -> ${ name}`);
if (s[f].$association.$viaEntity) {
name = s[f].$association.$viaEntity;
todo.push({$entity: name, $schema: schema,
$options: {$unmanaged: true, $auto: true}});
logger.debug(`node-cds import: added dependency ${
entity.$_metadata.entityName } -> ${ name}`);
}
},
});
}
// main metadata query function
function query(entityName, ref, callback) {
var cdsFullname = ref.$entity;
var tableName = ref.$table || cdsFullname;
var schemaName = ref.$schema || ""; // '"' + ref.$schema + '".' : "";
var fields = ref.$fields || {};
var options = ref.$options || {};
// main metadata query function
function query(entityName, ref, callback) {
const cdsFullname = ref.$entity;
const tableName = ref.$table || cdsFullname;
const schemaName = ref.$schema || ''; // '"' + ref.$schema + '".' : "";
const fields = ref.$fields || {};
const options = ref.$options || {};
if (!entityName)
return callback("Missing entity name");
if (!tableName)
return callback("Unknown entity table");
if (ref.$table && !ref.$entity)
options.$noCds = true;
if (ref.$unmanaged)
options.$unmanaged = true;
// retrieve metadata and build entity
logger.debug("node-cds import: query metadata for " + schemaName + "." + tableName);
getSqlMetadata(tableName, schemaName, function(err, sqlMetadata) {
if (err)
return callback("Error importing " + cdsFullname + ": " + err);
getCdsMetadata(cdsFullname, schemaName, options, function(err, cdsMetadata) {
if (err)
return callback("Error importing " + cdsFullname + ": " + err);
if (entityName in pool)
throw new Error("*** ASSERT FAIL *** conflicting entity in pool");
var entity = makeEntity(entityName, tableName, schemaName, fields,
sqlMetadata, cdsMetadata, options);
if (typeof entity === "string")
return callback(entity); // actually, we got an error
pool[entityName] = entity;
logger.debug("node-cds import: added " + entityName + " to wip pool");
addDepImports(entity); // recursively import target entities
return callback(null);
});
});
if (!entityName) {
return callback('Missing entity name');
}
// top level: request queue handling
var todo = [];
for (var i in refs) {
var r = utils.shallowCopy(refs[i]);
for (var p in opts) // merge in global options
if (!(p in r))
r[p] = opts[p];
todo.push(r);
if (!tableName) {
return callback('Unknown entity table');
}
function start(callback) {
async.whilst(pending, loop, callback);
if (ref.$table && !ref.$entity) {
options.$noCds = true;
}
if (ref.$unmanaged) {
options.$unmanaged = true;
}
function pending() {
return todo.length > 0;
// retrieve metadata and build entity
logger.debug(`node-cds import: query metadata for ${ schemaName }.${ tableName}`);
getSqlMetadata(tableName, schemaName, function(err, sqlMetadata) {
if (err) {
return callback(`Error importing ${ cdsFullname }: ${ err}`);
}
getCdsMetadata(cdsFullname, schemaName, options, function(err, cdsMetadata) {
if (err) {
return callback(`Error importing ${ cdsFullname }: ${ err}`);
}
if (entityName in pool) {
throw new Error('*** ASSERT FAIL *** conflicting entity in pool');
}
const entity = makeEntity(entityName, tableName, schemaName, fields,
sqlMetadata, cdsMetadata, options);
if (typeof entity === 'string') {
return callback(entity);
} // actually, we got an error
pool[entityName] = entity;
logger.debug(`node-cds import: added ${ entityName } to wip pool`);
addDepImports(entity); // recursively import target entities
return callback(null);
});
});
}
// top level: request queue handling
const todo = [];
for (const i in refs) {
const r = utils.shallowCopy(refs[i]);
// merge in global options
for (const p in opts) {
if (!(p in r)) {
r[p] = opts[p];
}
}
todo.push(r);
}
function loop(callback) {
var item = todo.shift();
if (!item)
throw new Error("*** ASSERT FAIL *** empty todo list");
var entityName = item.$name || item.$entity || item.$table;
logger.debug("node-cds import loop: processing " + entityName);
function start(callback) {
async.whilst(pending, loop, callback);
}
// choke recursive imports
var previously = pool[entityName] || knownEntities[entityName];
if (previously) {
logger.debug("node-cds import: " + entityName + " already known");
if (!(entityName in pool))
pool[entityName] = previously; // present to callback
var options = item.$options || {};
if (!options.$auto) {
updateEntity(previously, item.$fields || {}, options);
addDepImports(previously);
}
return callback(null);
}
function pending() {
return todo.length > 0;
}
query(entityName, item, callback);
function loop(callback) {
const item = todo.shift();
if (!item) {
throw new Error('*** ASSERT FAIL *** empty todo list');
}
const entityName = item.$name || item.$entity || item.$table;
logger.debug(`node-cds import loop: processing ${ entityName}`);
function done(err) {
if (err)
return callback(err, []);
// choke recursive imports
const previously = pool[entityName] || knownEntities[entityName];
if (previously) {
logger.debug(`node-cds import: ${ entityName } already known`);
if (!(entityName in pool)) {
pool[entityName] = previously;
} // present to callback
const options = item.$options || {};
if (!options.$auto) {
updateEntity(previously, item.$fields || {}, options);
addDepImports(previously);
}
return callback(null);
}
// add XS interface and wire up associations in wip skeletons, add to cache
for (var i in pool) {
if (!pool[i].$_metadata.isResolved) {
resolveEntity(pool[i], pool);
registerEntity(pool[i]);
}
logger.debug("node-cds import: " + pool[i].$_metadata.entityName + " imported");
}
query(entityName, item, callback);
}
// return result via wip pool
return callback(null, pool);
function done(err) {
if (err) {
return callback(err, []);
}
importQueue.push(start, done);
// add XS interface and wire up associations in wip skeletons, add to cache
for (const i in pool) {
if (!pool[i].$_metadata.isResolved) {
resolveEntity(pool[i], pool);
registerEntity(pool[i]);
}
logger.debug(`node-cds import: ${ pool[i].$_metadata.entityName } imported`);
}
// return result via wip pool
return callback(null, pool);
}
importQueue.push(start, done);
};

@@ -172,55 +185,60 @@

// trigger import of all associated entities
function makeEntity(entityName, tableName, schemaName, fields, sqlMetadata, cdsMetadata, options) {
// prepare field mapping
var sqlMapping = buildSqlMapping(sqlMetadata);
var cdsMapping = buildCdsMapping(cdsMetadata);
var mapping = mergeMapping(sqlMapping, cdsMapping);
mapping = mergeMapping(mapping, fields);
var check = checkMapping(mapping);
if (check)
return check; // found error
function makeEntity(
entityName, tableName, schemaName, fields, sqlMetadata, cdsMetadata, options,
) {
// prepare field mapping
const sqlMapping = buildSqlMapping(sqlMetadata);
const cdsMapping = buildCdsMapping(cdsMetadata);
let mapping = mergeMapping(sqlMapping, cdsMapping);
mapping = mergeMapping(mapping, fields);
const check = checkMapping(mapping);
if (check) {
return check;
} // found error
// assemble entity metadata
var metadata = {
entityName: entityName,
tableName: tableName,
schemaName: schemaName,
sqlMetadata: sqlMetadata,
cdsMetadata: cdsMetadata,
isUnmanaged: options.$unmanaged || false,
isAutoImport: options.$auto || false,
isResolved: false // true iff mapping contains associated entiy objects
};
updateMetadata(metadata, mapping);
// assemble entity metadata
const metadata = {
entityName: entityName,
tableName: tableName,
schemaName: schemaName,
sqlMetadata: sqlMetadata,
cdsMetadata: cdsMetadata,
isUnmanaged: options.$unmanaged || false,
isAutoImport: options.$auto || false,
isResolved: false, // true iff mapping contains associated entiy objects
};
updateMetadata(metadata, mapping);
// build and register unresolved entity object
entity = {
$_metadata: metadata,
$_mapping: mapping
};
// build and register unresolved entity object
const entity = {
$_metadata: metadata,
$_mapping: mapping,
};
// verify entity (in particular, mapping)
var e = verifyEntity(entity);
if (e) return e;
// verify entity (in particular, mapping)
const e = verifyEntity(entity);
if (e) {
return e;
}
// add query interface
SqlQuery.addExpressionFunctions(entity, entity);
entity.$query = function (client) {
var param = {};
param["t0"] = {entity: this};
return new SqlQuery(client, param);
};
// add query interface
SqlQuery.addExpressionFunctions(entity, entity);
entity.$query = function(client) {
const param = {};
param['t0'] = {entity: this};
return new SqlQuery(client, param);
};
entity.$ref = function (id) {
return new queries.Ref(this, id);
};
entity.$ref = function(id) {
return new queries.Ref(this, id);
};
entity.$from = function (id) {
var param = {};
var e = this;
param[id] = {entity: e};
return new SqlQuery(null, param);
};
entity.$from = function(id) {
const param = {};
const e = this;
param[id] = {entity: e};
return new SqlQuery(null, param);
};
return entity;
return entity;
}

@@ -230,15 +248,17 @@

function verifyEntity(entity) {
var error = null;
utils.forStruct(entity.$_mapping, {
$association: function (s, f, p) {
var assoc = s[f].$association;
if (assoc.$viaEntity) {
if (!assoc.$source)
error = "Error: missing $source in viaEntity association";
if (!assoc.$target)
error = "Error: missing $target in viaEntity association";
}
let error = null;
utils.forStruct(entity.$_mapping, {
$association: function(s, f, p) {
const assoc = s[f].$association;
if (assoc.$viaEntity) {
if (!assoc.$source) {
error = 'Error: missing $source in viaEntity association';
}
});
return error;
if (!assoc.$target) {
error = 'Error: missing $target in viaEntity association';
}
}
},
});
return error;
}

@@ -248,10 +268,11 @@

function updateEntity(entity, fields, options) {
var entityName = entity.$_metadata.entityName;
logger.debug("node-cds import: updating entity " + entityName);
delete knownEntities[entityName]; // remove temporarily, still in wip pool
entity.$_mapping = mergeMapping(entity.$_mapping, fields);
entity.$_metadata.isResolved = false;
updateMetadata(entity.$_metadata, entity.$_mapping);
if (!options.$auto)
entity.$_metadata.isAutoImport = false;
const entityName = entity.$_metadata.entityName;
logger.debug(`node-cds import: updating entity ${ entityName}`);
delete knownEntities[entityName]; // remove temporarily, still in wip pool
entity.$_mapping = mergeMapping(entity.$_mapping, fields);
entity.$_metadata.isResolved = false;
updateMetadata(entity.$_metadata, entity.$_mapping);
if (!options.$auto) {
entity.$_metadata.isAutoImport = false;
}
}

@@ -261,28 +282,30 @@

function updateMetadata(metadata, mapping) {
var keys = {}, hasKeys = false;
utils.forStruct(mapping, {
$key: function(s, f, p) {
if (s[f].$key) {
var column = s[f].$column;
keys[p + f] = {
$seq: s[f].$key, //metadata.sqlMetadata[column].$key,
$type: metadata.sqlMetadata[column].$type
};
hasKeys = true;
}
}
});
if (!hasKeys && !metadata.isUnmanaged) // need key for managing instances
throw new Error("no key defined: " + metadata.entityName);
const keys = {}; let hasKeys = false;
utils.forStruct(mapping, {
$key: function(s, f, p) {
if (s[f].$key) {
const column = s[f].$column;
keys[p + f] = {
$seq: s[f].$key, // metadata.sqlMetadata[column].$key,
$type: metadata.sqlMetadata[column].$type,
};
hasKeys = true;
}
},
});
// need key for managing instances
if (!hasKeys && !metadata.isUnmanaged) {
throw new Error(`no key defined: ${ metadata.entityName}`);
}
var revMapping = {};
utils.forStruct(mapping, {
$column: function(s, f, p) {
revMapping[s[f].$column] = p + f;
}
});
const revMapping = {};
utils.forStruct(mapping, {
$column: function(s, f, p) {
revMapping[s[f].$column] = p + f;
},
});
metadata.keyFields = keys;
metadata.revMapping = revMapping;
//metadata.secondaryIndexes = [];
metadata.keyFields = keys;
metadata.revMapping = revMapping;
// metadata.secondaryIndexes = [];
}

@@ -292,47 +315,52 @@

function resolveEntity(entity, pool) {
logger.debug("node-cds import: resolving " + entity.$_metadata.entityName);
if (entity.$_metadata.isResolved)
throw new Error("*** ASSERT FAIL *** resolving resolved entity: " +
entity.$_metadata.entityName);
logger.debug(`node-cds import: resolving ${ entity.$_metadata.entityName}`);
if (entity.$_metadata.isResolved) {
throw new Error(`*** ASSERT FAIL *** resolving resolved entity: ${
entity.$_metadata.entityName}`);
}
function getByName(name) {
var e = pool[name] || knownEntities[name];
if (!e)
throw new Error("*** ASSERT FAIL *** missing entity: " + name);
return e;
function getByName(name) {
const e = pool[name] || knownEntities[name];
if (!e) {
throw new Error(`*** ASSERT FAIL *** missing entity: ${ name}`);
}
return e;
}
utils.forStruct(entity.$_mapping, {
$association: function(s, f, p) {
var name = s[f].$association.$entity;
s[f].$association.$class = getByName(name);
name = s[f].$association.$viaEntity;
if (name)
s[f].$association.$viaClass = getByName(name);
}
});
entity.$_metadata.isResolved = true;
utils.forStruct(entity.$_mapping, {
$association: function(s, f, p) {
let name = s[f].$association.$entity;
s[f].$association.$class = getByName(name);
name = s[f].$association.$viaEntity;
if (name) {
s[f].$association.$viaClass = getByName(name);
}
},
});
entity.$_metadata.isResolved = true;
};
/// other management function ///////////////////////////////////////
// / other management function ///////////////////////////////////////
// return previously imported entity, or save callback if not imported yet
exports.getEntity = function(name, callback) {
logger.debug("node-cds import: getting entity " + name);
if (name in knownEntities)
return callback(null, knownEntities[name]);
logger.debug("node-cds import: getEntity waiting for entity " + name);
if (!(name in importNotifications))
importNotifications[name] = [];
importNotifications[name].push(callback);
logger.debug(`node-cds import: getting entity ${ name}`);
if (name in knownEntities) {
return callback(null, knownEntities[name]);
}
logger.debug(`node-cds import: getEntity waiting for entity ${ name}`);
if (!(name in importNotifications)) {
importNotifications[name] = [];
}
importNotifications[name].push(callback);
};
// sync version (used by metadata import)
exports.getEntitySync = function (name) {
return knownEntities[name] || null;
exports.getEntitySync = function(name) {
return knownEntities[name] || null;
};
exports.getKnownEntities = function() {
return knownEntities;
return knownEntities;
};

@@ -346,20 +374,22 @@

function registerEntity(entity) {
var name = entity.$_metadata.entityName;
if (name in knownEntities)
throw new Error("*** ASSERT FAIL *** trying to register known entity " + name);
logger.debug("node-cds import: registering entity " + name);
const name = entity.$_metadata.entityName;
if (name in knownEntities) {
throw new Error(`*** ASSERT FAIL *** trying to register known entity ${ name}`);
}
logger.debug(`node-cds import: registering entity ${ name}`);
knownEntities[name] = entity;
knownEntities[name] = entity;
// check for pending requests
if (name in importNotifications) {
var notify = importNotifications[name];
for (var i in notify)
(notify[i])(null, entity);
delete importNotifications[name];
// check for pending requests
if (name in importNotifications) {
const notify = importNotifications[name];
for (const i in notify) {
(notify[i])(null, entity);
}
delete importNotifications[name];
}
};
/// support functions ////////////////////////////////////////////////////////////
// / support functions ////////////////////////////////////////////////////////////

@@ -370,43 +400,45 @@ // compute dependency graph for entity that shows

// (3) the projection for $query() that covers all non-recursive associations
exports.computeRelations = function (entity) {
var entityName = entity.$_metadata.entityName;
var projection = {}, assocs = [], cycles = [];
exports.computeRelations = function(entity) {
const entityName = entity.$_metadata.entityName;
const projection = {}; const assocs = []; const cycles = [];
var _build = function (entity, prefix, seen) {
utils.setPropPath(projection, prefix + "$all", true);
utils.forStruct(entity.$_mapping, {
$association: function (m, f, p) {
var target = m[f].$association.$entity;
var targetEntity = m[f].$association.$class;
if (typeof targetEntity === "undefined")
throw new Error("*** ASSERT FAIL *** missing target entity");
var isToMany = utils.isToMany(m[f].$association);
var isLazy = m[f].$association.$lazy;
if (seen.indexOf(target) >= 0) {
cycles.push({field: prefix + p + f, assoc: m[f].$association});
if (isToMany) {
// force projection, but without recursion
utils.setPropPath(projection, prefix + p + f, {$all: true});
}
} else {
assocs.push({
field: prefix + p + f, target: targetEntity, toMany: isToMany, lazy: isLazy
});
if (!isLazy)
_build(targetEntity, prefix + p + f + ".", seen.concat([target]));
}
}
});
}
_build(entity, "", [entityName]);
const _build = function(entity, prefix, seen) {
utils.setPropPath(projection, `${prefix }$all`, true);
utils.forStruct(entity.$_mapping, {
$association: function(m, f, p) {
const target = m[f].$association.$entity;
const targetEntity = m[f].$association.$class;
if (typeof targetEntity === 'undefined') {
throw new Error('*** ASSERT FAIL *** missing target entity');
}
const isToMany = utils.isToMany(m[f].$association);
const isLazy = m[f].$association.$lazy;
if (seen.indexOf(target) >= 0) {
cycles.push({field: prefix + p + f, assoc: m[f].$association});
if (isToMany) {
// force projection, but without recursion
utils.setPropPath(projection, prefix + p + f, {$all: true});
}
} else {
assocs.push({
field: prefix + p + f, target: targetEntity, toMany: isToMany, lazy: isLazy,
});
if (!isLazy) {
_build(targetEntity, `${prefix + p + f }.`, seen.concat([target]));
}
}
},
});
};
_build(entity, '', [entityName]);
logger.debug("node-cds import: analyzed " + entityName + ": proj = " +
JSON.stringify(projection) + ", " + assocs.length + " assocs" + cycles.length + " cycles");
logger.debug(`node-cds import: analyzed ${ entityName }: proj = ${
JSON.stringify(projection) }, ${ assocs.length } assocs${ cycles.length } cycles`);
return {
projection: projection,
associations: assocs,
cycles: cycles
};
}
return {
projection: projection,
associations: assocs,
cycles: cycles,
};
};

@@ -416,42 +448,46 @@

function getSqlMetadata(tableName, schemaName, callback) {
var sqlname = schemaName ? schemaName + "." + tableName : tableName;
if (sqlname in sqlMetadata) {
return callback(null, sqlMetadata[sqlname]);
const sqlname = schemaName ? `${schemaName }.${ tableName}` : tableName;
if (sqlname in sqlMetadata) {
return callback(null, sqlMetadata[sqlname]);
}
// get type information
const metadata = {};
const processResult = function(err, rows) {
if (err) {
return callback(err);
}
// get type information
var metadata = {};
var processResult = function(err, rows) {
if (err)
return callback(err);
if (rows.length == 0)
return callback("database table " + tableName + " not found");
for (var i = 0; i < rows.length; i++) {
var columnName = rows[i].COLUMN_NAME;
metadata[columnName] = {
$type: rows[i].DATA_TYPE_ID,
$csType: rows[i].CS_DATA_TYPE_ID,
$size: rows[i].SCALE,
$key: rows[i].IS_PRIMARY_KEY === "TRUE"
};
}
sqlMetadata[sqlname] = metadata;
return callback(null, metadata);
};
if (rows.length == 0) {
return callback(`database table ${ tableName } not found`);
}
for (let i = 0; i < rows.length; i++) {
const columnName = rows[i].COLUMN_NAME;
metadata[columnName] = {
$type: rows[i].DATA_TYPE_ID,
$csType: rows[i].CS_DATA_TYPE_ID,
$size: rows[i].SCALE,
$key: rows[i].IS_PRIMARY_KEY === 'TRUE',
};
}
sqlMetadata[sqlname] = metadata;
return callback(null, metadata);
};
transaction.getClient(null, function(err, client) {
if (err)
return callback(err);
var schema = schemaName ? "'" + schemaName + "'" : "CURRENT_SCHEMA";
client.exec(
"SELECT tc.COLUMN_NAME, tc.DATA_TYPE_ID, tc.CS_DATA_TYPE_ID, tc.SCALE, cs.IS_PRIMARY_KEY " +
"FROM SYS.TABLE_COLUMNS tc LEFT OUTER JOIN SYS.CONSTRAINTS cs " +
"ON tc.SCHEMA_NAME = cs.SCHEMA_NAME AND tc.TABLE_NAME = cs.TABLE_NAME " +
"AND tc.COLUMN_NAME = cs.COLUMN_NAME " +
"WHERE tc.SCHEMA_NAME = " + schema + " AND tc.TABLE_NAME = '" + tableName + "'",
function(err, result) {
transaction.releaseClient(client);
processResult(err, result);
});
});
transaction.getClient(null, function(err, client) {
if (err) {
return callback(err);
}
const schema = schemaName ? `'${ schemaName }'` : 'CURRENT_SCHEMA';
client.exec(
// eslint-disable-next-line max-len
`${'SELECT tc.COLUMN_NAME, tc.DATA_TYPE_ID, tc.CS_DATA_TYPE_ID, tc.SCALE, cs.IS_PRIMARY_KEY ' +
'FROM SYS.TABLE_COLUMNS tc LEFT OUTER JOIN SYS.CONSTRAINTS cs ' +
'ON tc.SCHEMA_NAME = cs.SCHEMA_NAME AND tc.TABLE_NAME = cs.TABLE_NAME ' +
'AND tc.COLUMN_NAME = cs.COLUMN_NAME ' +
'WHERE tc.SCHEMA_NAME = '}${ schema } AND tc.TABLE_NAME = '${ tableName }'`,
function(err, result) {
transaction.releaseClient(client);
processResult(err, result);
});
});
};

@@ -461,9 +497,11 @@

function getCdsMetadata(fullname, schema, options, callback) {
if (options.$noCds)
return callback(null, null);
if (fullname in cdsMetadata)
return callback(null, cdsMetadata[fullname]);
if (options.$noCds) {
return callback(null, null);
}
if (fullname in cdsMetadata) {
return callback(null, cdsMetadata[fullname]);
}
// prepare queries for retrieving assocs and structs
var sqlA =
// prepare queries for retrieving assocs and structs
const sqlA =
'SELECT e.ARTIFACT_NAME AS "n",' +

@@ -473,5 +511,5 @@ ' a.SCHEMA_NAME, e.SCHEMA_NAME,' +

' a.TARGET_ARTIFACT_NAME AS "tn",' +
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
' e.ELEMENT_NAME AS "cn",' + // component name
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
' e.ELEMENT_NAME AS "cn",' + // component name
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
' FROM SYS.CDS_ARTIFACT_DEFINITION(?, ?) AS e JOIN SYS.CDS_ASSOCIATIONS AS a' +

@@ -481,3 +519,3 @@ ' ON e.ARTIFACT_NAME = a.ASSOCIATION_NAME AND a.SCHEMA_NAME = e.SCHEMA_NAME' +

' (e.ARTIFACT_KIND = \'ASSOCIATION\' AND a.ASSOCIATION_KIND = \'UNMANAGED\')';
var sqlACS =
const sqlACS =
'SELECT e.ARTIFACT_NAME AS "n",' +

@@ -487,5 +525,6 @@ ' a.SCHEMA_NAME, e.SCHEMA_NAME,' +

' a.TARGET_ARTIFACT_NAME AS "tn",' +
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
' e.ELEMENT_NAME AS "cn",' + // component name
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
' a.JOIN_CONDITION AS "on",' + // unmanaged assoc
' e.ELEMENT_NAME AS "cn",' + // component name
' e.AUX_ELEMENT_INFO AS "fk"' + // alias name
// eslint-disable-next-line max-len
' FROM SYS.CDS_ARTIFACT_DEFINITION(CURRENT_SCHEMA, ?) AS e JOIN SYS.CDS_ASSOCIATIONS AS a' +

@@ -495,3 +534,3 @@ ' ON e.ARTIFACT_NAME = a.ASSOCIATION_NAME AND a.SCHEMA_NAME = e.SCHEMA_NAME' +

' (e.ARTIFACT_KIND = \'ASSOCIATION\' AND a.ASSOCIATION_KIND = \'UNMANAGED\')';
var sqlS =
const sqlS =
'SELECT ELEMENT_NAME AS "n",' +

@@ -502,3 +541,3 @@ ' USED_ARTIFACT_SCHEMA AS "ts",' +

' WHERE USED_ARTIFACT_KIND = \'STRUCTURED_TYPE\'';
var sqlSCS =
const sqlSCS =
'SELECT ELEMENT_NAME AS "n",' +

@@ -510,20 +549,30 @@ ' USED_ARTIFACT_SCHEMA AS "ts",' +

transaction.getClient(null, function(err, client) {
if (err)
return callback(err);
async.series([
function(cb) { client.prepare(sqlA, cb); },
function(cb) { client.prepare(sqlACS, cb); },
function(cb) { client.prepare(sqlS, cb); },
function(cb) { client.prepare(sqlSCS, cb); }
], function(err, stmts) {
if (err)
throw new Error("*** ASSERT FAIL *** invalid CDS metadata query");
getStructsAndAssocs(stmts, schema, fullname, function (err, data) {
transaction.releaseClient(client);
cdsMetadata[fullname] = data;
return callback(err, data);
});
});
transaction.getClient(null, function(err, client) {
if (err) {
return callback(err);
}
async.series([
function(cb) {
client.prepare(sqlA, cb);
},
function(cb) {
client.prepare(sqlACS, cb);
},
function(cb) {
client.prepare(sqlS, cb);
},
function(cb) {
client.prepare(sqlSCS, cb);
},
], function(err, stmts) {
if (err) {
throw new Error('*** ASSERT FAIL *** invalid CDS metadata query');
}
getStructsAndAssocs(stmts, schema, fullname, function(err, data) {
transaction.releaseClient(client);
cdsMetadata[fullname] = data;
return callback(err, data);
});
});
});
}

@@ -534,165 +583,177 @@

function getStructsAndAssocs(stmts, schema, fullname, callback) {
logger.debug("node-cds import: retrieve CDS assoc for " + fullname);
logger.debug(`node-cds import: retrieve CDS assoc for ${ fullname}`);
// retrieve association metadata from CDS tables
var getAssocs = function(callback) {
var s = schema ? stmts[0] : stmts[1];
var a = schema ? [schema, fullname] : [fullname];
s.exec(a, function(err, rows) {
var assocs = {};
if (err)
return callback("Error retrieving CDS association metadata: " + err, assocs);
for (var i = 0; i < rows.length; ++i) {
var fieldName = rows[i].n.slice(fullname.length + 1);
if (!(fieldName in assocs)) {
assocs[fieldName] = {
$association: rows[i].tn,
$schema: rows[i].ts
};
}
if (rows[i].on && rows[i].on.length) {
var cond = String.fromCharCode.apply(null, rows[i].on);
assocs[fieldName].$on = cond;
} else {
var foreignKey = rows[i].cn;
var foreignKeyAlias = rows[i].fk;
if (foreignKey !== foreignKeyAlias && foreignKeyAlias !== null) {
if (!("$aliases" in assocs[fieldName]))
assocs[fieldName].$aliases = {};
assocs[fieldName].$aliases[foreignKey] = foreignKeyAlias;
}
}
// retrieve association metadata from CDS tables
const getAssocs = function(callback) {
const s = schema ? stmts[0] : stmts[1];
const a = schema ? [schema, fullname] : [fullname];
s.exec(a, function(err, rows) {
const assocs = {};
if (err) {
return callback(`Error retrieving CDS association metadata: ${ err}`, assocs);
}
for (let i = 0; i < rows.length; ++i) {
const fieldName = rows[i].n.slice(fullname.length + 1);
if (!(fieldName in assocs)) {
assocs[fieldName] = {
$association: rows[i].tn,
$schema: rows[i].ts,
};
}
if (rows[i].on && rows[i].on.length) {
const cond = String.fromCharCode.apply(null, rows[i].on);
assocs[fieldName].$on = cond;
} else {
const foreignKey = rows[i].cn;
const foreignKeyAlias = rows[i].fk;
if (foreignKey !== foreignKeyAlias && foreignKeyAlias !== null) {
if (!('$aliases' in assocs[fieldName])) {
assocs[fieldName].$aliases = {};
}
return callback(null, assocs);
});
};
assocs[fieldName].$aliases[foreignKey] = foreignKeyAlias;
}
}
}
return callback(null, assocs);
});
};
// retrieve structure metadata from CDS tables
var getStructs = function(callback) {
var s = schema ? stmts[2] : stmts[3];
var a = schema ? [schema, fullname] : [fullname];
s.exec(a, function(err, rows) {
var structs = {};
if (err)
return callback("Error retrieving CDS type metadata: " + err, structs);
for (var i = 0; i < rows.length; ++i) {
var componentName = rows[i].n;
structs[componentName] = {
schema: rows[i].ts,
name: rows[i].tn
};
}
return callback(null, structs);
});
};
// retrieve structure metadata from CDS tables
const getStructs = function(callback) {
const s = schema ? stmts[2] : stmts[3];
const a = schema ? [schema, fullname] : [fullname];
s.exec(a, function(err, rows) {
const structs = {};
if (err) {
return callback(`Error retrieving CDS type metadata: ${ err}`, structs);
}
for (let i = 0; i < rows.length; ++i) {
const componentName = rows[i].n;
structs[componentName] = {
schema: rows[i].ts,
name: rows[i].tn,
};
}
return callback(null, structs);
});
};
// recursively get assocs and structs
getAssocs(function (err, assocs) {
if (err)
return callback(err, null);
getStructs(function (err, structs) {
if (err)
return callback(err, null);
var fns = [];
for (var s in structs) {
var fn = (function (result, field) {
return function (cb) {
getStructsAndAssocs(stmts, structs[field].schema, structs[field].name,
function (err, data) {
if (err)
return cb(err, null);
result[field] = data;
cb(null); // result is ignored
});
};
})(assocs, s);
fns.push(fn);
}
async.parallel(fns, function (err, ignored) {
callback(err, assocs);
});
});
// recursively get assocs and structs
getAssocs(function(err, assocs) {
if (err) {
return callback(err, null);
}
getStructs(function(err, structs) {
if (err) {
return callback(err, null);
}
const fns = [];
for (const s in structs) {
const fn = (function(result, field) {
return function(cb) {
getStructsAndAssocs(stmts, structs[field].schema, structs[field].name,
function(err, data) {
if (err) {
return cb(err, null);
}
result[field] = data;
cb(null); // result is ignored
});
};
})(assocs, s);
fns.push(fn);
}
async.parallel(fns, function(err, ignored) {
callback(err, assocs);
});
});
});
}
function buildSqlMapping(sqlMetadata, ignoredColumns) {
ignoredColumns = ignoredColumns || [];
// add all SQL columns as fields
var mapping = {};
for (var columnName in sqlMetadata) {
if (ignoredColumns.indexOf(columnName) >= 0)
continue;
var field = utils.mkPropPath(mapping, columnName);
field.$column = columnName;
if ("$key" in sqlMetadata[columnName])
field.$key = sqlMetadata[columnName].$key;
ignoredColumns = ignoredColumns || [];
// add all SQL columns as fields
const mapping = {};
for (const columnName in sqlMetadata) {
if (ignoredColumns.indexOf(columnName) >= 0) {
continue;
}
return mapping;
const field = utils.mkPropPath(mapping, columnName);
field.$column = columnName;
if ('$key' in sqlMetadata[columnName]) {
field.$key = sqlMetadata[columnName].$key;
}
}
return mapping;
}
function buildCdsMapping(cdsMetadata) {
// convert CDS association metadata into mapping format
var assocs = {};
utils.forStruct(cdsMetadata, {
$association: function(s, f, p) {
var a = utils.mkPropPath(assocs, p + f);
a.$association = {
$entity: s[f].$association,
$schema: s[f].$schema,
$lazy: false
};
if (s[f].$on)
a.$association.$on = s[f].$on;
if (s[f].$aliases)
a.$aliases = s[f].$aliases;
}
});
return assocs;
// convert CDS association metadata into mapping format
const assocs = {};
utils.forStruct(cdsMetadata, {
$association: function(s, f, p) {
const a = utils.mkPropPath(assocs, p + f);
a.$association = {
$entity: s[f].$association,
$schema: s[f].$schema,
$lazy: false,
};
if (s[f].$on) {
a.$association.$on = s[f].$on;
}
if (s[f].$aliases) {
a.$aliases = s[f].$aliases;
}
},
});
return assocs;
}
function mergeMapping(mapping, newFields) {
// recursively copy nested properties from src to dst
var copy = function(dst, src) {
// special handling for $association/$column overrides
if ("$column" in src && "$association" in dst ||
"$association" in src && "$column" in dst) {
for (var p in dst)
delete dst[p];
copy(dst, src); // redo
return;
// recursively copy nested properties from src to dst
const copy = function(dst, src) {
// special handling for $association/$column overrides
if ('$column' in src && '$association' in dst ||
'$association' in src && '$column' in dst) {
for (const p in dst) {
delete dst[p];
}
copy(dst, src); // redo
return;
}
// copy properties
for (const f in src) {
if (typeof src[f] === 'object') {
// merge props of src structure
if (!(f in dst)) {
dst[f] = {};
}
// copy properties
for (var f in src) {
if (typeof src[f] === "object") {
// merge props of src structure
if (!(f in dst))
dst[f] = {};
copy(dst[f], src[f]);
} else if (src[f] === false) {
// remove prop in dst
delete dst[f];
} else {
// copy prop from src to dst
dst[f] = src[f];
}
}
};
copy(dst[f], src[f]);
} else if (src[f] === false) {
// remove prop in dst
delete dst[f];
} else {
// copy prop from src to dst
dst[f] = src[f];
}
}
};
//var result = Entities.cloneJSON(mapping);
copy(mapping, newFields);
// var result = Entities.cloneJSON(mapping);
copy(mapping, newFields);
// aliases: rename fields by moving
utils.forStruct(mapping, {
$aliases: function (s, f, p) {
var aliases = s[f].$aliases;
for (var oldf in aliases) {
var newf = aliases[oldf];
utils.setPropPath(s[f], newf, s[f][oldf]);
delete s[f][oldf];
}
delete s[f].$aliases;
}
});
// aliases: rename fields by moving
utils.forStruct(mapping, {
$aliases: function(s, f, p) {
const aliases = s[f].$aliases;
for (const oldf in aliases) {
const newf = aliases[oldf];
utils.setPropPath(s[f], newf, s[f][oldf]);
delete s[f][oldf];
}
delete s[f].$aliases;
},
});
return mapping;
return mapping;
}

@@ -702,11 +763,12 @@

function checkMapping(mapping) {
var error = null;
utils.forStruct(mapping, {
$association: function (s, f, p) {
var assoc = s[f].$association;
if ("$on" in assoc && "$cascadeDiscard" in assoc)
error = "Cascade discard invalid for unmanaged associations";
}
});
return error;
let error = null;
utils.forStruct(mapping, {
$association: function(s, f, p) {
const assoc = s[f].$association;
if ('$on' in assoc && '$cascadeDiscard' in assoc) {
error = 'Cascade discard invalid for unmanaged associations';
}
},
});
return error;
}

@@ -718,4 +780,4 @@

exports._clearImports = function() {
logger.info("node-cds: reset imports");
knownEntities = {};
logger.info('node-cds: reset imports');
knownEntities = {};
};

@@ -1,1 +0,40 @@

{"dependencies":{"@sap/hdbext":"^4.3.0","@sap/xsenv":"^1.2.6","async":"1.5.0","winston":"1.1.2"},"description":"SAP HANA Core Data Services Client for node.js","devDependencies":{"expect":"^1.4.0","mocha":"4.0.1"},"engines":{"node":"^0.12.7 || ^4.4.0 || ^6.0.0","npm":"^2.11.x"},"keywords":["sap","hana","cds"],"main":"./cds.js","maintainers":[{"name":"https-support.sap.com","email":"do-not-reply@sap.com"}],"name":"@sap/cds","optionalDependencies":{},"readme":"node-cds: Core Data Services for node.js\n========================================\n\nImportant note: \n---------------\n The node-cds library is now considered feature complete. \n It will remain fully supported but will not receive further \n enhancements in future releases.\n\nAbstract\n--------\n\nThe *Core Data Services for node.js* (node-cds) are a JavaScript\nclient library for Core Data Services that allow node.js applications\nto consume CDS artifacts natively in node.js applications.\n\nnode-cds supports major CDS features, in particular entities, types,\nassociations, and views. The library offers a *managed mode* and an\n*unmanaged mode* that differ in the way that data is retrieved from\nthe database.\n\nThe node-cds project is the successor of the XS Data Services (XSDS)\nlibrary available for the HANA XS Engine.\n\n\nAPI Overview\n------------\n\n### Library Import\n\nTo use *node-cds*, require its main file:\n\n var cds = require('cds');\n\nOn Cloud Foundry, add a depencency on the latest node-cds release to\nyour `package.json`:\n\n \"dependencies\": {\n \"cds\": \"*\",\n ...\n }\n\t\t\n\n### CDS Import\n\nCDS entities are imported by name. The import function takes a callback that\nis invoked when all imports have completed. Additional fields and overrides\nmay be supplied for each entity.\n\n cds.importEntities([\n { $entity: \"xsds.test.cds::ds_test.e1\" },\n { $entity: \"xsds.test.cds::ds_test.e2\",\n $fields: {\n a: { $association: \"xsds.test.cds::ds_test.e2\",\n $viaBacklink: \"b\" }\n }\n }\n ], callback);\n\n function callback(error, entities) {\n var E1 = entities[\"xsds.test.cds::ds_test.e1\"];\n var E2 = entities[\"xsds.test.cds::ds_test.e2\"];\n // ...\n }\n\nNote that the import is a regular asynchronous node.js function. You may\nimport entities at any point in time, and in as many calls as you want.\n\nEntities may not be imported more than once. To retrieve an imported entity\nuse `$getEntity` or `$getEntities`:\n\n cds.$getEntities([\n \"xsds.test.cds::ds_test.e1\",\n \"xsds.test.cds::ds_test.e2\"\n ], function(err, entities) {\n var E1 = entities[\"xsds.test.cds::ds_test.e1\"];\n // ...\n });\n\nBoth functions will wait until the requested entity has been imported successfully.\nThere is also a synchronous version:\n\n var E1 = $getEntitySync(\"xsds.test.cds::ds_test.e1\");\n\nNote that `$getEntitySync` will return `null` if the import has not completed yet.\n\n\n### Database Connections and Transactions\n\nOpen new connection and transaction:\n\n cds.$getTransaction(function(error, tx) {\n tx.$get(...);\n // ...\n tx.$close();\n });\n\nReuse existing database connection `dbconn`, e.g., from `express` framework:\n\n cds.$getTransaction(dbconn, function(error, tx) {\n tx.$get(...);\n // ...\n tx.$close();\n });\n\nNote: We recommend `node-hdbext` for setting up the\nconnection to your HANA instance.\n\nTransaction management\n\n tx.$setAutoCommit(<boolean>);\n tx.$commit(callback);\n tx.$rollback(callback);\n\nBy default, transactions are in auto commit mode. Note that auto commit refers\nto node-cds operations, not database operations.\n\n\n### Managed Instances\n\nRetrieve entity instances by key:\n\n tx.$get(E1, { id1: 1, id2: 2, ... }, function(error, instance) {\n console.log(JSON.stringify(instance));\n });\n\nBatch retrieval for multiple instances:\n\n var requests = [\n { $entity: E1, id11: 1, id2: 2 },\n { $entity: E2, id: \"key\" }, ...\n ];\n tx.$getAll(requests, function(error, instances) {\n console.log(\"e1 = \" + JSON.stringify(instances[0]);\n console.log(\"e2 = \" + JSON.stringify(instances[1]);\n });\n\nSyntactic sugar for above:\n\n var requests = [\n E1.$prepare({ id1: 1, id2: 2 }),\n E2.$prepare({ id: \"key\" }), ...\n ];\n tx.$getAll(requests, ...);\n\nRetrieve entity instances by condition:\n\n tx.$find(E1, { value: { $gt: 69 } }, function(error, instances) {\n console.log(\"found \" + instance.length + \" instances\");\n });\n\nBatch retrieval by condition:\n\n tx.$findAll([\n { $entity: E1, { prop: { $eq: 1 } },\n { $entity: E2, { prop: { $ne: 2 } }\n ], callback);\n\nNote that the result of `$findAll` is an array of arrays; to flatten the result\nset you may use\n\n var flattenedInstanceArray = [].concat.apply([], findAllResult));\n\nFor complex data types you need to supply a comparison function using `$using` that\ncompares their values in JavaScript:\n\n tx.$find(E1, { prop: { $lt: \"1.0e-10\", $using: function(arg1, arg2) {\n return Math.sign(parseFloat(arg1) - parseFloat(arg2));\n } }, callback);\n\nA comparison function takes two arguments and returns values `< 0`, `== 0`, or `> 0`\ndepending on their relation to each other.\n\nCreate new instance:\n\n tx.$save({ $entity: E, key: 1, value: \"hello world\" }, function(error, instance) {\n if (!error)\n console.log(\"instance created\");\n });\n\nBatch creation:\n\n var newinsts = [\n { $entity: E1, id1: 1, value: 2 },\n E2.$prepare({ id: \"new\", value: 4 }), ...\n ];\n tx.$saveAll(newinsts, function(error, instances) {\n console.log(\"\" + instances.length + \" instances created\");\n });\n\nUpdate existing instance:\n\n instance.value++;\n tx.$save(instance, function (error, savedInstance) {\n if (!error)\n console.log(\"instance updated\");\n });\n\nBatch update:\n\n tx.$saveAll([ instance1, instance2, ... ], function (error, instances) {\n console.log(\"instances updated\");\n });\n\nDiscard entity instances:\n\n tx.$discard(instance, function(error) {\n if (error)\n console.error(\"Error discarding instance: \" + error);\n });\n\nBatch discard:\n\n tx.$discardAll([ instance1, instance2, ...], function(error) {\n if (error)\n console.error(\"Error discarding instances: \" + error);\n });\n\nUnmanaged delete:\n\n tx.$delete(entity, condition, callback);\n\n*CAUTION!* Unmanaged `delete`s bypass the cache and will not cascade to\ntarget instances! The `$delete` method is merely syntactic sugar for\n`$query().$matching().$delete()`!\n\n\n### Associations\n\nAdding via backlink 1:n associations:\n\n cds.importEntities([{\n $entity: \"cds.test::parent\",\n $fields: {\n BacklinkAssoc: {\n $association: {\n $entity: \"cds.test::target\",\n $viaBacklink: \"backassoc\"\n }\n }\n }\n }], callback);\n\nAdding via entity m:n associations:\n\n cds.importEntities([{\n $entity: \"cds.test::parent\",\n $fields: {\n BacklinkAssoc: {\n\t\t\t\t$association: {\n\t\t\t\t\t$entity: \"cds.test::target\",\n\t\t\t\t\t$viaEntity: \"cds.test::link\",\n\t\t\t\t\t$source: \"sourceassoc\",\n\t\t\t\t\t$target: \"targetassoc\"\n\t\t\t\t}\n }\n }\n }], callback);\n\nDeclaring lazy associations:\n\n cds.importEntities([{\n $entity: \"cds.test::parent\",\n $fields: {\n LazyAssoc: {\n $association: {\n\t\t\t\t\t$lazy: true\n\t\t\t\t}\n }\n }\n ], callback);\n\nLazy retrieval of lazy associations:\n\n instance.lazyAssoc.$load(function (error, targets) {\n // targets == instance.lazyAssoc\n console.log(\"\" + targets.length + \" targets retrieved\");\n });\n\nRe-syncing backlinking and unmanaged associations\n\n instance.unmanagedAssoc.$reload(function (error, targets) {\n // targets == instance.unmanagedAssoc\n console.log(\"association has \" + targets.length + \" targets\");\n });\n\n\n### Unmanaged Queries\n\nBasic query:\n\n E1.$query().$matching({ key: 1 })\n .$execute({}, function(error, result) {\n console.log(\"result = \" + JSON.stringify(result);\n });\n\nMore complex query conditions:\n\n E1.$query().$matching({ value1: { $lt: 42 }, value2: { $null: true } })\n .$execute({}, function(error, result) {\n console.log(\"result = \" + JSON.stringify(result);\n });\n\nProjection and navigation:\n\n E2.$query().$matching({ key: 1 })\n .$project({ value1: true, assoc: { value2: true, value3: true })\n .$execute({}, function(error, result) { ... });\n\nStream interface:\n\n E1.$query().$execute({ $stream: true }, function(error, stream) {\n stream.on('data', function(chunk) {\n console.log(chunk);\n }).on('end', function() {\n console.log(\"done\");\n });\n });\n","readmeFilename":"README.md","scripts":{"cleanBundle":"find $PWD -name package.json -exec node .filter/filter-package.js {} \\; ; rm -rf .filter init_sign.py","prepareRelease":"rm -rf doc/ test/ .gitignore .xmake.cfg && npm prune --production","test-disabled":"node node_modules/mocha/bin/mocha --recursive"},"version":"1.15.1","warnings":[{"code":"ENOTSUP","required":{"node":"^0.12.7 || ^4.4.0 || ^6.0.0","npm":"^2.11.x"},"pkgid":"@sap/cds@1.15.1"},{"code":"ENOTSUP","required":{"node":"^0.12.7 || ^4.4.0 || ^6.0.0","npm":"^2.11.x"},"pkgid":"@sap/cds@1.15.1"}],"license":"SEE LICENSE IN developer-license-3.1.txt"}
{
"dependencies": {
"@sap/e2e-trace": "^3.1.0",
"@sap/xsenv": "^3.3.2",
"accept-language": "2.0.16",
"async": "^2.6.4",
"debug": "^3.1.0",
"generic-pool": "^2.2.0",
"hdb": "^0.16.0",
"lodash": "^4.0.0",
"lru-cache": "^4.0.0",
"winston": "^2.4.6"
},
"license": "SEE LICENSE IN LICENSE",
"description": "SAP HANA Core Data Services Client for node.js",
"engines": {
"node": "^10.0.0 || ^12.0.0 || ^14.0.0 || ^16.0.0"
},
"files": [
"package.json",
"util",
"cds.js",
"_hdbext",
"npm-shrinkwrap.json",
"cds-queries-geo.js",
"cds-queries.js",
"exprs.js",
"manager.js",
"metadata.js",
"transaction.js",
"utils.js",
"xsjs-cds.js",
"README.md",
"CHANGELOG.md",
"LICENSE"
],
"main": "./cds.js",
"name": "@sap/cds",
"version": "1.17.0"
}

@@ -1,30 +0,18 @@

var xsconn = require('@sap/hdbext');
var xsenv = require('@sap/xsenv');
const xsconn = require('./_hdbext');
const xsenv = require('@sap/xsenv');
var manager = require('./manager');
var utils = require('./utils');
var logger = utils.logger;
const manager = require('./manager');
// transaction and database connection management
// transaction id for
var txId = 0;
let txId = 0;
// find HANA service binding
var hanaOptions = xsenv.getServices({
hana: process.env.HANA_SERVICE_NAME || { tag: 'hana' }
const hanaOptions = xsenv.getServices({
hana: process.env.HANA_SERVICE_NAME || {tag: 'hana'},
}).hana;
// manually provide HANA service binding
// var hanaOptions = {
// host : ...,
// port : 3xx15,
// user : ...,
// password : ...,
// schema : ...
// };
// control opening of new database connections
var proxy = xsconn.getPool(hanaOptions);
const proxy = xsconn.getPool(hanaOptions);

@@ -35,33 +23,39 @@

exports.getClient = function(dbconn, callback) {
var initClient = function(cl) {
cl.setAutoCommit(false); // auto commit handled by node-cds
exports._autoCommit(cl, true);
cl.$_tx_id = ++txId;
manager.openCache(cl);
};
if (dbconn) {
if (!dbconn.$_tx_id)
initClient(dbconn);
if (dbconn.readyState === "connected")
callback(null, dbconn);
else
dbconn.connect(function (err) { callback(err, dbconn); });
const initClient = function(cl) {
cl.setAutoCommit(false); // auto commit handled by node-cds
exports._autoCommit(cl, true);
cl.$_tx_id = ++txId;
manager.openCache(cl);
};
if (dbconn) {
if (!dbconn.$_tx_id) {
initClient(dbconn);
}
if (dbconn.readyState === 'connected') {
callback(null, dbconn);
} else {
proxy.acquire(null, function(err, client) {
if (!err)
initClient(client);
callback(err, client);
});
dbconn.connect(function(err) {
callback(err, dbconn);
});
}
} else {
proxy.acquire(null, function(err, client) {
if (!err) {
initClient(client);
}
callback(err, client);
});
}
};
exports.releaseClient = function(client, dbconn) {
if (dbconn) {
// don't touch connection, in particular keep it connected
// dbconn.disconnect(cb);
} else {
if (client.$_tx_id)
manager.closeCache(client);
proxy.release(client);
if (dbconn) {
// don't touch connection, in particular keep it connected
// dbconn.disconnect(cb);
} else {
if (client.$_tx_id) {
manager.closeCache(client);
}
proxy.release(client);
}
};

@@ -72,13 +66,13 @@

exports._autoCommit = function (client, auto) {
client.$_autoCommit = auto;
exports._autoCommit = function(client, auto) {
client.$_autoCommit = auto;
};
exports._commit = function (client, callback) {
client.commit(callback);
exports._commit = function(client, callback) {
client.commit(callback);
};
exports._rollback = function (client, callback) {
manager._clearCaches(client);
client.rollback(callback);
exports._rollback = function(client, callback) {
manager._clearCaches(client);
client.rollback(callback);
};

@@ -6,37 +6,38 @@ 'use strict';

function Queue() {
this.queue = [];
this.busy = false;
this.queue = [];
this.busy = false;
}
Object.defineProperty(Queue.prototype, 'empty', {
get: function isEmpty() {
return this.queue.length === 0;
}
get: function isEmpty() {
return this.queue.length === 0;
},
});
Queue.prototype.push = function push(worker, callback, name) {
var task = new Task(worker, callback, name);
this.queue.push(task);
this.run();
return this;
const task = new Task(worker, callback, name);
this.queue.push(task);
this.run();
return this;
};
Queue.prototype.run = function() {
var self = this;
const self = this;
function next(err, name) {
self.busy = false;
if (self.queue.length)
run();
function next(err, name) {
self.busy = false;
if (self.queue.length) {
run();
}
}
function run() {
if (!self.busy) {
self.busy = true;
var task = self.queue.shift();
task.run(next);
}
function run() {
if (!self.busy) {
self.busy = true;
const task = self.queue.shift();
task.run(next);
}
}
run();
run();
};

@@ -46,16 +47,17 @@

function Task(worker, callback, name) {
this.worker = worker;
this.callback = callback;
this.name = name;
this.worker = worker;
this.callback = callback;
this.name = name;
}
Task.prototype.run = function run(next) {
var self = this;
const self = this;
function done() {
self.callback.apply(null, arguments);
next(null, self.name);
}
function done() {
// eslint-disable-next-line prefer-rest-params
self.callback.apply(null, arguments);
next(null, self.name);
}
this.worker(done); // do NOT convert exceptions into errors!
this.worker(done); // do NOT convert exceptions into errors!
};

@@ -1,8 +0,8 @@

/// winston logger for node-cds ///////////////////////////////////
// / winston logger for node-cds ///////////////////////////////////
var winston = require("winston");
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)()
]
const winston = require('winston');
const logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)(),
],
});

@@ -13,3 +13,3 @@ logger.level = process.env.CDS_LOGLEVEL || 'warn';

/// Property access and manipulation //////////////////////////////
// / Property access and manipulation //////////////////////////////

@@ -19,15 +19,18 @@ // iterate over instance based on structure information

function forInstance(value, struct, fns, prefix) {
// fns: function(prefix, field, value, struct), ...
prefix = prefix || "";
for (var f in struct) {
if (typeof struct[f] === "object") {
for (var a in fns)
if (a in struct[f])
fns[a](prefix, f, value, struct);
if (value !== null && value[f] !== undefined && f[0] !== "$")
forInstance(value[f], struct[f], fns, prefix + f + ".");
} else if ("$default" in fns) {
fns.$default(prefix, f, value, struct);
// fns: function(prefix, field, value, struct), ...
prefix = prefix || '';
for (const f in struct) {
if (typeof struct[f] === 'object') {
for (const a in fns) {
if (a in struct[f]) {
fns[a](prefix, f, value, struct);
}
}
if (value !== null && value[f] !== undefined && f[0] !== '$') {
forInstance(value[f], struct[f], fns, `${prefix + f }.`);
}
} else if ('$default' in fns) {
fns.$default(prefix, f, value, struct);
}
}
}

@@ -37,15 +40,18 @@

function forStruct(struct, fns, prefix) {
// fns: function(struct, field, prefix), ...
prefix = prefix || "";
for (var f in struct) {
if (typeof struct[f] === "object") {
for (var a in fns)
if (a in struct[f])
fns[a](struct, f, prefix);
if (f[0] !== "$")
forStruct(struct[f], fns, prefix + f + ".");
} else if ("$default" in fns) {
fns.$default(struct, f, prefix);
// fns: function(struct, field, prefix), ...
prefix = prefix || '';
for (const f in struct) {
if (typeof struct[f] === 'object') {
for (const a in fns) {
if (a in struct[f]) {
fns[a](struct, f, prefix);
}
}
if (f[0] !== '$') {
forStruct(struct[f], fns, `${prefix + f }.`);
}
} else if ('$default' in fns) {
fns.$default(struct, f, prefix);
}
}
}

@@ -57,13 +63,15 @@

function mkPropPath(obj, dottedPath) {
if (dottedPath === "")
return obj;
var props = dottedPath.split(".");
var o = obj;
for (var i = 0; i < props.length; ++i) {
var p = props[i];
if (!(p in o) || typeof o[p] !== "object")
o[p] = {};
o = o[p];
if (dottedPath === '') {
return obj;
}
const props = dottedPath.split('.');
let o = obj;
for (let i = 0; i < props.length; ++i) {
const p = props[i];
if (!(p in o) || typeof o[p] !== 'object') {
o[p] = {};
}
return o;
o = o[p];
}
return o;
}

@@ -75,17 +83,20 @@

function setPropPath(obj, dottedPath, value) {
if (dottedPath === "")
return obj;
var props = dottedPath.split(".");
var o = obj;
for (var i = 0; i < props.length - 1; ++i) {
var p = props[i];
if (!(p in o) || typeof o[p] !== "object")
o[p] = {};
if (o[p] === null)
return o; // cannot set inside null instance
//throw new Error("*** ASSERT FAIL *** setting property of null value: " + p);
o = o[p];
if (dottedPath === '') {
return obj;
}
const props = dottedPath.split('.');
let o = obj;
for (let i = 0; i < props.length - 1; ++i) {
const p = props[i];
if (!(p in o) || typeof o[p] !== 'object') {
o[p] = {};
}
o[props.pop()] = value;
return o;
if (o[p] === null) {
return o;
} // cannot set inside null instance
// throw new Error("*** ASSERT FAIL *** setting property of null value: " + p);
o = o[p];
}
o[props.pop()] = value;
return o;
}

@@ -95,11 +106,11 @@

exports.mkNewPath = function(dottedPath) {
var props = dottedPath.split(".");
var o = {};
while (props.length > 0) {
var p = props.pop();
var t = {};
t[p] = o;
o = t;
}
return o;
const props = dottedPath.split('.');
let o = {};
while (props.length > 0) {
const p = props.pop();
const t = {};
t[p] = o;
o = t;
}
return o;
};

@@ -109,10 +120,11 @@

exports.getPropPath = function(obj, dottedPath) {
var props = dottedPath.split(".");
while (props.length > 0) {
if (typeof obj === "undefined" || obj === null)
return undefined; // missing property
var p = props.shift();
obj = obj[p];
}
return obj;
const props = dottedPath.split('.');
while (props.length > 0) {
if (typeof obj === 'undefined' || obj === null) {
return undefined;
} // missing property
const p = props.shift();
obj = obj[p];
}
return obj;
};

@@ -123,52 +135,57 @@

exports.getPropPathSet = function(obj, dottedPath) {
var props = dottedPath.split("."), values = { "": obj };
while (props.length > 0) {
var p = props.shift(), nexts = {};
for (var i in values) {
var v = values[i];
if (v && p in v) {
if (exports.isArray(v[p]))
for (var x in v[p])
nexts[(i ? i + "." + p : p) + "." + x] = v[p][x];
else
nexts[i ? i + "." + p : p] = v[p];
}
const props = dottedPath.split('.'); let values = {'': obj};
while (props.length > 0) {
const p = props.shift(); const nexts = {};
for (const i in values) {
const v = values[i];
if (v && p in v) {
if (exports.isArray(v[p])) {
for (const x in v[p]) {
nexts[`${i ? `${i }.${ p}` : p }.${ x}`] = v[p][x];
}
} else {
nexts[i ? `${i }.${ p}` : p] = v[p];
}
values = nexts;
}
}
return values;
values = nexts;
}
return values;
};
// check if to-many association
exports.isToMany = function (a) {
return a.$viaBacklink || a.$viaEntity || a.$on;
}
exports.isToMany = function(a) {
return a.$viaBacklink || a.$viaEntity || a.$on;
};
/// misc. utility functions
// / misc. utility functions
// check for Array
// NOTE: using the simpler "instanceof Array" seems to mess with Fibrous?!
exports.isArray = function (o) {
return Object.prototype.toString.call(o) === "[object Array]";
}
exports.isArray = function(o) {
return Object.prototype.toString.call(o) === '[object Array]';
};
// create shallow copy of object or array
exports.shallowCopy = function(o) {
if (typeof o === "object" && o !== null) {
var r;
if (exports.isArray(o)) {
r = [];
for (var i = 0; i < o.length; ++i)
r.push(o[i]);
} else {
r = {};
for (var p in o)
if (o.hasOwnProperty(p))
r[p] = o[p];
if (typeof o === 'object' && o !== null) {
let r;
if (exports.isArray(o)) {
r = [];
for (let i = 0; i < o.length; ++i) {
r.push(o[i]);
}
} else {
r = {};
for (const p in o) {
if (o.hasOwnProperty(p)) {
r[p] = o[p];
}
return r;
} else {
return o;
}
}
return r;
} else {
return o;
}
};

@@ -178,70 +195,76 @@

exports.deepCopy = function(obj) {
var seen = [];
var copy = function(o) {
if (o === null || typeof o !== "object")
return o;
if (o instanceof Date) {
return new Date(o.getTime());
const seen = [];
const copy = function(o) {
if (o === null || typeof o !== 'object') {
return o;
}
if (o instanceof Date) {
return new Date(o.getTime());
}
if (o instanceof ctypes.Int64) {
return ctypes.Int64.join(ctypes.Int64.hi(o), ctypes.Int64.lo(o));
}
if (seen.indexOf(o) >= 0) {
throw new TypeError('deep copy of recursive data structure');
}
let clone;
if (exports.isArray(o)) {
clone = [];
for (let i = 0; i < o.length; ++i) {
clone.push(copy(o[i]));
}
} else {
const j = seen.length;
seen.push(o);
clone = {};
for (const p in o) {
if (o.hasOwnProperty(p)) {
clone[p] = copy(o[p]);
}
if (o instanceof ctypes.Int64) {
return ctypes.Int64.join(ctypes.Int64.hi(o), ctypes.Int64.lo(o));
}
if (seen.indexOf(o) >= 0) {
throw new TypeError("deep copy of recursive data structure");
}
var clone;
if (exports.isArray(o)) {
clone = [];
for (var i = 0; i < o.length; ++i)
clone.push(copy(o[i]));
} else {
var j = seen.length;
seen.push(o);
clone = {};
for (var p in o)
if (o.hasOwnProperty(p))
clone[p] = copy(o[p]);
seen.splice(j);
}
return clone;
};
return copy(obj);
}
seen.splice(j);
}
return clone;
};
return copy(obj);
};
// add non-enumerable property to object
exports.addInternalProp = function (obj, prop, val) {
Object.defineProperty(obj, prop, {value: val, configurable: true});
}
exports.addInternalProp = function(obj, prop, val) {
Object.defineProperty(obj, prop, {value: val, configurable: true});
};
/// DB Stuff /////////////////////////////////////////////////////////
// / DB Stuff /////////////////////////////////////////////////////////
// properly quote table or schema + table name
exports.quoteTable = function (name) {
var parts = name.match(/^(?:("[^"]+"|[^".]+)\.)?(.*)$/);
var quotedName =
(parts[0] ? (parts[0][0] === '"' ? parts[0] : '"' + parts[0] + '"') : "") +
(parts[1][0] === '"' ? parts[1] : '"' + parts[1] + '"');
return quotedName;
}
exports.quoteTable = function(name) {
const parts = name.match(/^(?:("[^"]+"|[^".]+)\.)?(.*)$/);
const quotedName =
(parts[0] ? (parts[0][0] === '"' ? parts[0] : `"${ parts[0] }"`) : '') +
(parts[1][0] === '"' ? parts[1] : `"${ parts[1] }"`);
return quotedName;
};
/// Debugging ////////////////////////////////////////////////////////
// / Debugging ////////////////////////////////////////////////////////
// pretty-print non-internal properties
exports.ppp = function(obj, verbose) {
var seen = [];
function repl(k, v) {
if (typeof v === 'object') {
if (!verbose && k.substring(0, 2) === "$_")
return "#$";
if (v !== null && seen.indexOf(v) >= 0)
return "#obj"; //v instanceof Struct ? "#" + v : "#obj";
seen.push(v);
} else if (typeof v === 'function') {
return "#fn";
}
return v;
const seen = [];
function repl(k, v) {
if (typeof v === 'object') {
if (!verbose && k.substring(0, 2) === '$_') {
return '#$';
}
if (v !== null && seen.indexOf(v) >= 0) {
return '#obj';
} // v instanceof Struct ? "#" + v : "#obj";
seen.push(v);
} else if (typeof v === 'function') {
return '#fn';
}
return JSON.stringify(obj, repl, 4);
return v;
}
return JSON.stringify(obj, repl, 4);
};

@@ -1,248 +0,258 @@

var cds = require("./cds");
var origExecFct = cds.queries.Query.prototype.$execute;
/* eslint-disable prefer-rest-params */
const cds = require('./cds');
const origExecFct = cds.queries.Query.prototype.$execute;
exports.init = function(tx) {
function resolveEntityReferences(obj) {
if (typeof obj === 'object' || typeof obj === 'function') {
if (obj.$_metadata) {
return obj.$_metadata.entityName;
}
var result = {};
for (var p in obj) {
result[p] = resolveEntityReferences(obj[p]);
}
return result;
} else {
return obj;
}
function resolveEntityReferences(obj) {
if (typeof obj === 'object' || typeof obj === 'function') {
if (obj.$_metadata) {
return obj.$_metadata.entityName;
}
const result = {};
for (const p in obj) {
result[p] = resolveEntityReferences(obj[p]);
}
return result;
} else {
return obj;
}
}
function adaptImport(importSpec) {
var result = {
$entity: importSpec[0] + "::" + importSpec[1],
$fields: resolveEntityReferences(importSpec[2]),
$options: importSpec[3]
};
if (importSpec[3] && importSpec[3].$entityName) {
result.$name = importSpec[3].$entityName;
}
if (importSpec[3] && importSpec[3].$viaTable) {
result.$viaEntity = importSpec[3].$viaTable;
}
return [result];
function adaptImport(importSpec) {
const result = {
$entity: `${importSpec[0] }::${ importSpec[1]}`,
$fields: resolveEntityReferences(importSpec[2]),
$options: importSpec[3],
};
if (importSpec[3] && importSpec[3].$entityName) {
result.$name = importSpec[3].$entityName;
}
function adaptDefine(importSpec) {
var matches = /"(\w+)"\."([\w.:]+)"/g.exec(importSpec[1]);
var result = {
$name: importSpec[0],
$table: matches[2],
$schema: matches[1],
$fields: resolveEntityReferences(importSpec[2]),
$options: importSpec[3]
};
return [result];
if (importSpec[3] && importSpec[3].$viaTable) {
result.$viaEntity = importSpec[3].$viaTable;
}
return [result];
}
var wrapperRepo = {};
function adaptDefine(importSpec) {
const matches = /"(\w+)"\."([\w.:]+)"/g.exec(importSpec[1]);
const result = {
$name: importSpec[0],
$table: matches[2],
$schema: matches[1],
$fields: resolveEntityReferences(importSpec[2]),
$options: importSpec[3],
};
return [result];
}
function makeSkeleton(values, mapping) {
var res;
for (var p in mapping) {
if (p.substring(0, 1) === '$') continue;
if (!res) res = {};
if (values[p] && (values[p].$__entity || values[p].$_entity)) {
res[p] = values[p];
} else if (Object.prototype.toString.call(values[p]) === "[object Array]") {
var nextMapping = mapping[p].$association ? mapping[p].$association.$class.$_mapping : mapping[p];
res[p] = values[p].map(function (e) {
if (e.$__entity || e.$_entity) {
return e;
} else {
return makeSkeleton(e, nextMapping);
}
});
} else if (Object.prototype.toString.call(values[p]) === "[object Object]") {
var nextMapping = mapping[p].$association ? mapping[p].$association.$class.$_mapping : mapping[p];
res[p] = makeSkeleton(values[p], nextMapping);
} else if (typeof values[p] !== 'undefined') {
res[p] = values[p];
} else if (mapping[p].$association && (mapping[p].$viaBacklink || mapping[p].$viaEntity)) {
res[p] = [];
} else if (mapping[p].$association) {
res[p] = null;
} else {
res[p] = makeSkeleton({}, mapping[p]);
}
}
return res;
const wrapperRepo = {};
function makeSkeleton(values, mapping) {
let res;
let nextMapping;
for (const p in mapping) {
if (p.substring(0, 1) === '$') {
continue;
}
if (!res) {
res = {};
}
if (values[p] && (values[p].$__entity || values[p].$_entity)) {
res[p] = values[p];
} else if (Object.prototype.toString.call(values[p]) === '[object Array]') {
nextMapping = mapping[p].$association ?
mapping[p].$association.$class.$_mapping : mapping[p];
res[p] = values[p].map(function(e) {
if (e.$__entity || e.$_entity) {
return e;
} else {
return makeSkeleton(e, nextMapping);
}
});
} else if (Object.prototype.toString.call(values[p]) === '[object Object]') {
nextMapping = mapping[p].$association ?
mapping[p].$association.$class.$_mapping : mapping[p];
res[p] = makeSkeleton(values[p], nextMapping);
} else if (typeof values[p] !== 'undefined') {
res[p] = values[p];
} else if (
mapping[p].$association &&
(mapping[p].$viaBacklink || mapping[p].$viaEntity)
) {
res[p] = [];
} else if (mapping[p].$association) {
res[p] = null;
} else {
res[p] = makeSkeleton({}, mapping[p]);
}
}
return res;
}
function getWrapper(name, entity) {
if (!wrapperRepo[name]) {
var result = function (skeleton) { // constructor
var e = entity;
if (!skeleton.$__entity) {
skeleton = makeSkeleton(skeleton, e.$_mapping);
}
Object.defineProperty(skeleton, '$save',
{
value: function () {
return tx.$save.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true
});
Object.defineProperty(skeleton, '$persist',
{
value: function () {
return tx.$save.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true
});
Object.defineProperty(skeleton, '$discard',
{
value: function () {
return tx.$discard.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true
});
return skeleton;
};
function getWrapper(name, entity) {
if (!wrapperRepo[name]) {
const result = function(skeleton) { // constructor
const e = entity;
if (!skeleton.$__entity) {
skeleton = makeSkeleton(skeleton, e.$_mapping);
}
Object.defineProperty(skeleton, '$save',
{
value: function() {
return tx.$save.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true,
});
Object.defineProperty(skeleton, '$persist',
{
value: function() {
return tx.$save.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true,
});
Object.defineProperty(skeleton, '$discard',
{
value: function() {
return tx.$discard.sync(entity.$prepare(this));
},
enumerable: false,
configurable: true,
});
return skeleton;
};
for (var p in entity) {
result[p] = entity[p];
}
cds.queries.addExpressionFunctions(entity, result);
for (const p in entity) {
result[p] = entity[p];
}
cds.queries.addExpressionFunctions(entity, result);
result.$findAll = function (cond) {
if (arguments.length === 0) {
cond = {};
}
var r = entity.$findAll.sync.call(entity, cond);
return r;
};
result.$findAll = function(cond) {
if (arguments.length === 0) {
cond = {};
}
const r = entity.$findAll.sync.call(entity, cond);
return r;
};
result.$saveAll = function (instances) {
tx.$save(instances.map(function (inst) {
return entity.$prepare(inst);
}));
};
result.$saveAll = function(instances) {
tx.$save(instances.map(function(inst) {
return entity.$prepare(inst);
}));
};
result.$discardAll = function (instances) {
tx.$discard(instances.map(function (inst) {
return entity.$prepare(inst);
}));
};
result.$discardAll = function(instances) {
tx.$discard(instances.map(function(inst) {
return entity.$prepare(inst);
}));
};
result.$persistAll = result.$saveAll;
result.$persistAll = result.$saveAll;
result.$find = function (cond) {
if (arguments.length === 0) {
cond = {};
}
var r = entity.$findAll.sync.call(entity, cond)[0];
return r;
};
result.$find = function(cond) {
if (arguments.length === 0) {
cond = {};
}
const r = entity.$findAll.sync.call(entity, cond)[0];
return r;
};
result.$get = function (cond) {
if (arguments.length === 0) {
cond = {};
}
var r = entity.$get.sync.call(entity, cond);
return r;
};
result.$get = function(cond) {
if (arguments.length === 0) {
cond = {};
}
const r = entity.$get.sync.call(entity, cond);
return r;
};
result.$query = function () {
var q = entity.$query();
q.syncExecute = true;
return q;
}
result.$query = function() {
const q = entity.$query();
q.syncExecute = true;
return q;
};
wrapperRepo[name] = result;
}
return wrapperRepo[name];
wrapperRepo[name] = result;
}
return wrapperRepo[name];
}
var retrieveEntity = function (nodeImport) {
var alias = nodeImport[0].$name;
var entityName = nodeImport[0].$entity;
var imp = cds.$importEntities.sync(nodeImport);
var entity = imp[alias || entityName];
var name = entity.$_metadata.entityName;
var wrapped = getWrapper(name, entity);
return wrapped;
}
const retrieveEntity = function(nodeImport) {
const alias = nodeImport[0].$name;
const entityName = nodeImport[0].$entity;
const imp = cds.$importEntities.sync(nodeImport);
const entity = imp[alias || entityName];
const name = entity.$_metadata.entityName;
const wrapped = getWrapper(name, entity);
return wrapped;
};
exports.importEntity = function () {
var args = Array.prototype.slice.call(arguments);
return retrieveEntity.call(this, adaptImport(args));
};
exports.importEntity = function() {
const args = Array.prototype.slice.call(arguments);
return retrieveEntity.call(this, adaptImport(args));
};
exports.$importEntity = exports.importEntity;
exports.$importEntity = exports.importEntity;
exports.defineEntity = function () {
var args = Array.prototype.slice.call(arguments);
return retrieveEntity.call(this, adaptDefine(args));
};
exports.defineEntity = function() {
const args = Array.prototype.slice.call(arguments);
return retrieveEntity.call(this, adaptDefine(args));
};
exports.getEntity = function (name) {
return getWrapper(name, cds.$getEntitySync(name));
};
exports.getEntity = function(name) {
return getWrapper(name, cds.$getEntitySync(name));
};
cds.queries.Query.prototype.$execute = function () {
if (!this.syncExecute) {
return origExecFct.apply(this, arguments);
} else {
var args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
args = [{}];
}
return origExecFct.sync.apply(this, args);
}
cds.queries.Query.prototype.$execute = function() {
if (!this.syncExecute) {
return origExecFct.apply(this, arguments);
} else {
let args = Array.prototype.slice.call(arguments);
if (args.length === 0) {
args = [{}];
}
return origExecFct.sync.apply(this, args);
}
};
exports.cdsAsync = cds;
exports.Manager = {
clearCache: function () {
cds._clearCaches();
},
resetCaches: function () {
cds._clearCaches();
}
};
exports.cdsAsync = cds;
exports.Manager = {
clearCache: function() {
cds._clearCaches();
},
resetCaches: function() {
cds._clearCaches();
},
};
exports.Transaction = {
$commit: function (callback) {
tx.$commit(function(err) {
callback(err);
});
},
exports.Transaction = {
$commit: function(callback) {
tx.$commit(function(err) {
callback(err);
});
},
$close: function () {
tx.$close();
}
}
$close: function() {
tx.$close();
},
};
exports.Rename = {
$lowercase: "lowercase"
};
exports.Rename = {
$lowercase: 'lowercase',
};
exports.SqlQuery = cds.queries;
exports.Query = cds.createQuery;
exports.SqlQuery = cds.queries;
exports.Query = cds.createQuery;
cds.extensionPoints.instanceMethods = function (instance) {
instance.$save = function() {
tx.$save(instance);
}
instance.$persist = instance.$save;
instance.$discard = function() {
tx.$discard(instance);
}
return instance;
cds.extensionPoints.instanceMethods = function(instance) {
instance.$save = function() {
tx.$save(instance);
};
return exports;
}
instance.$persist = instance.$save;
instance.$discard = function() {
tx.$discard(instance);
};
return instance;
};
return exports;
};

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc