Comparing version 1.15.1 to 1.17.0
@@ -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); | ||
} |
215
cds.js
@@ -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; |
225
exprs.js
@@ -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; | ||
} |
1244
manager.js
@@ -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}`); | ||
} |
1094
metadata.js
@@ -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! | ||
}; |
341
utils.js
@@ -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); | ||
}; |
438
xsjs-cds.js
@@ -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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
NPM Shrinkwrap
Supply chain riskPackage contains a shrinkwrap file. This may allow the package to bypass normal install procedures.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
0
26
6672
259541
10
1
6
+ Added@sap/e2e-trace@^3.1.0
+ Addedaccept-language@2.0.16
+ Addeddebug@^3.1.0
+ Addedgeneric-pool@^2.2.0
+ Addedhdb@^0.16.0
+ Addedlodash@^4.0.0
+ Addedlru-cache@^4.0.0
+ Added@sap/e2e-trace@3.2.0(transitive)
+ Added@sap/xsenv@3.4.0(transitive)
+ Addedasync@2.6.4(transitive)
+ Addedclone@2.1.2(transitive)
+ Addeddebug@3.2.74.3.3(transitive)
+ Addedgeneric-pool@2.5.4(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedlru-cache@4.1.5(transitive)
+ Addedms@2.1.2(transitive)
+ Addednode-cache@5.1.2(transitive)
+ Addedwinston@2.4.7(transitive)
- Removed@sap/hdbext@^4.3.0
- Removed@sap/e2e-trace@1.4.1(transitive)
- Removed@sap/hdbext@4.7.5(transitive)
- Removed@sap/xsenv@1.3.0(transitive)
- Removedasync@1.0.01.5.02.4.1(transitive)
- Removeddebug@3.1.0(transitive)
- Removedgeneric-pool@2.2.0(transitive)
- Removedlodash@4.17.11(transitive)
- Removedlru-cache@4.0.0(transitive)
- Removedms@2.0.0(transitive)
- Removedpkginfo@0.3.1(transitive)
- Removedwinston@1.1.2(transitive)
Updated@sap/xsenv@^3.3.2
Updatedasync@^2.6.4
Updatedwinston@^2.4.6