@sap/cds-compiler 2.13.6 → 2.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +92 -17
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +499 -423
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +95 -68
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +8 -6
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +5 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { ModelError } = require('../../base/error');
|
|
6
|
+
|
|
5
7
|
const functionsWithoutParams = {
|
|
6
8
|
hana: {
|
|
7
9
|
CURRENT_CONNECTION: {},
|
|
@@ -280,12 +282,12 @@ const cdsToSqlTypes = {
|
|
|
280
282
|
};
|
|
281
283
|
|
|
282
284
|
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
285
|
+
* Get the element matching the column
|
|
286
|
+
*
|
|
287
|
+
* @param {CSN.Elements} elements Elements of a query
|
|
288
|
+
* @param {CSN.Column} column Column from the same query
|
|
289
|
+
* @returns {CSN.Element}
|
|
290
|
+
*/
|
|
289
291
|
function findElement(elements, column) {
|
|
290
292
|
if (!elements)
|
|
291
293
|
return undefined;
|
|
@@ -341,16 +343,17 @@ function hasHanaComment(obj, options) {
|
|
|
341
343
|
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
342
344
|
}
|
|
343
345
|
/**
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
346
|
+
* Return the comment of the given artifact or element.
|
|
347
|
+
* Uses the first block (everything up to the first empty line (double \n)).
|
|
348
|
+
* Remove leading/trailing whitespace.
|
|
349
|
+
* Does not escape any characters.
|
|
350
|
+
*
|
|
351
|
+
* @param {CSN.Artifact|CSN.Element} obj
|
|
352
|
+
* @returns {string}
|
|
353
|
+
* @todo Warning/info to user?
|
|
354
|
+
*/
|
|
352
355
|
function getHanaComment(obj) {
|
|
353
|
-
return obj.doc.split('\n\n')[0].trim()
|
|
356
|
+
return obj.doc.split('\n\n')[0].trim();
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
/**
|
|
@@ -368,6 +371,118 @@ function getSqlSnippets(options, obj) {
|
|
|
368
371
|
return { front, back };
|
|
369
372
|
}
|
|
370
373
|
|
|
374
|
+
/**
|
|
375
|
+
* A function used to render a certain part of an expression object
|
|
376
|
+
*
|
|
377
|
+
* @callback renderPart
|
|
378
|
+
* @param {object||array} expression
|
|
379
|
+
* @param {CdlRenderEnvironment} env
|
|
380
|
+
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
381
|
+
* @returns {string}
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* The object containing the concrete rendering functions for the different parts
|
|
386
|
+
* of an expression
|
|
387
|
+
*
|
|
388
|
+
* @typedef {object} ExpressionConfiguration
|
|
389
|
+
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
390
|
+
* @property {renderPart} explicitTypeCast
|
|
391
|
+
* @property {renderPart} val
|
|
392
|
+
* @property {renderPart} enum
|
|
393
|
+
* @property {renderPart} ref
|
|
394
|
+
* @property {renderPart} aliasOnly
|
|
395
|
+
* @property {renderPart} windowFunction
|
|
396
|
+
* @property {renderPart} func
|
|
397
|
+
* @property {renderPart} xpr
|
|
398
|
+
* @property {renderPart} SELECT
|
|
399
|
+
* @property {renderPart} SET
|
|
400
|
+
*/
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
404
|
+
* (no trailing LF, don't indent if inline)
|
|
405
|
+
*
|
|
406
|
+
* @param {ExpressionConfiguration} renderer
|
|
407
|
+
* @returns {Function} Rendered expression
|
|
408
|
+
*/
|
|
409
|
+
function getExpressionRenderer(renderer) {
|
|
410
|
+
/**
|
|
411
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
412
|
+
* (no trailing LF, don't indent if inline)
|
|
413
|
+
*
|
|
414
|
+
* @todo Reuse this with toCdl
|
|
415
|
+
* @param {Array|object|string} expr Expression to render
|
|
416
|
+
* @param {object} env Render environment
|
|
417
|
+
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
418
|
+
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
419
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
420
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
421
|
+
* @returns {string} Rendered expression
|
|
422
|
+
*/
|
|
423
|
+
return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
424
|
+
// Compound expression
|
|
425
|
+
if (Array.isArray(expr)) {
|
|
426
|
+
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
427
|
+
return beautifyExprArray(tokens);
|
|
428
|
+
}
|
|
429
|
+
else if (typeof expr === 'object' && expr !== null) {
|
|
430
|
+
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
|
|
431
|
+
return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
|
|
432
|
+
return renderExprObject(expr);
|
|
433
|
+
}
|
|
434
|
+
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
435
|
+
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
436
|
+
return renderer.finalize.call({ inline, nestedExpr }, expr, env);
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Various special cases represented as objects
|
|
441
|
+
*
|
|
442
|
+
* @param {object} x Expression
|
|
443
|
+
* @returns {string} String representation of the expression
|
|
444
|
+
*/
|
|
445
|
+
function renderExprObject(x) {
|
|
446
|
+
if (x.list) { // TODO: Does this still exist?
|
|
447
|
+
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
448
|
+
}
|
|
449
|
+
else if (x.val !== undefined) {
|
|
450
|
+
return renderer.val.call({ inline, nestedExpr }, x, env);
|
|
451
|
+
}
|
|
452
|
+
// Enum symbol
|
|
453
|
+
else if (x['#']) {
|
|
454
|
+
return renderer.enum.call({ inline, nestedExpr }, x, env);
|
|
455
|
+
}
|
|
456
|
+
// Reference: Array of path steps, possibly preceded by ':'
|
|
457
|
+
else if (x.ref) {
|
|
458
|
+
return renderer.ref.call({ inline, nestedExpr }, x, env);
|
|
459
|
+
}
|
|
460
|
+
// Function call, possibly with args (use '=>' for named args)
|
|
461
|
+
else if (x.func) {
|
|
462
|
+
if (x.xpr)
|
|
463
|
+
return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
|
|
464
|
+
return renderer.func.call({ inline, nestedExpr }, x, env);
|
|
465
|
+
}
|
|
466
|
+
// Nested expression
|
|
467
|
+
else if (x.xpr) {
|
|
468
|
+
return renderer.xpr.call({ inline, nestedExpr }, x, env);
|
|
469
|
+
}
|
|
470
|
+
// Sub-select
|
|
471
|
+
else if (x.SELECT) {
|
|
472
|
+
return renderer.SELECT.call({ inline, nestedExpr }, x, env);
|
|
473
|
+
}
|
|
474
|
+
else if (x.SET) {
|
|
475
|
+
return renderer.SET.call({ inline, nestedExpr }, x, env);
|
|
476
|
+
}
|
|
477
|
+
else if (x.as && x.cast && x.cast.type && x.cast.target) {
|
|
478
|
+
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
371
486
|
/**
|
|
372
487
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
373
488
|
*
|
|
@@ -386,6 +501,7 @@ function getSqlSnippets(options, obj) {
|
|
|
386
501
|
|
|
387
502
|
module.exports = {
|
|
388
503
|
renderFunc,
|
|
504
|
+
getExpressionRenderer,
|
|
389
505
|
beautifyExprArray,
|
|
390
506
|
getNamespace,
|
|
391
507
|
getRealName,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"prefer-template": "error",
|
|
9
9
|
"no-trailing-spaces": "error",
|
|
10
10
|
"template-curly-spacing":["error", "never"],
|
|
11
|
-
"complexity": ["warn",
|
|
11
|
+
"complexity": ["warn", 40],
|
|
12
12
|
"max-len": "off",
|
|
13
13
|
// Don't enforce stupid descriptions
|
|
14
14
|
"jsdoc/require-param-description": "off",
|
|
@@ -37,7 +37,7 @@ function attachOnConditions(csn, pathDelimiter) {
|
|
|
37
37
|
/**
|
|
38
38
|
* Create the foreign key elements for a managed association and build the on-condition
|
|
39
39
|
*
|
|
40
|
-
* @param {
|
|
40
|
+
* @param {object} elem The association to process
|
|
41
41
|
* @param {string} elemName
|
|
42
42
|
* @returns {void}
|
|
43
43
|
*/
|
|
@@ -167,7 +167,7 @@ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
|
167
167
|
*
|
|
168
168
|
* @param {Array} links
|
|
169
169
|
* @param {number} startIndex
|
|
170
|
-
* @returns {
|
|
170
|
+
* @returns {object | undefined} CSN definition of the source of the managed association
|
|
171
171
|
*/
|
|
172
172
|
function findSource(links, startIndex) {
|
|
173
173
|
for (let i = startIndex; i >= 0; i--) {
|
|
@@ -277,7 +277,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
277
277
|
// no constraint if either dependent or parent is not persisted
|
|
278
278
|
if (
|
|
279
279
|
hasAnnotationValue(parent, '@cds.persistence.skip') ||
|
|
280
|
-
hasAnnotationValue(dependent, '@cds.persistence.skip')
|
|
280
|
+
hasAnnotationValue(dependent, '@cds.persistence.skip') ||
|
|
281
|
+
hasAnnotationValue(parent, '@cds.persistence.exists') ||
|
|
282
|
+
hasAnnotationValue(dependent, '@cds.persistence.exists')
|
|
281
283
|
)
|
|
282
284
|
return true;
|
|
283
285
|
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
} = require('../../model/csnUtils');
|
|
9
9
|
const { csnRefs, implicitAs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* For keys, columns, groupBy and orderBy, expand structured things.
|
|
@@ -19,10 +20,10 @@ const { setProp, isBetaEnabled } = require('../../base/model');
|
|
|
19
20
|
* @param {object} messageFunctions
|
|
20
21
|
* @param {Function} messageFunctions.error
|
|
21
22
|
* @param {Function} messageFunctions.info
|
|
22
|
-
* @param {Function} messageFunctions.
|
|
23
|
+
* @param {Function} messageFunctions.throwWithAnyError
|
|
23
24
|
* @param {object} iterateOptions
|
|
24
25
|
*/
|
|
25
|
-
function expandStructureReferences(csn, options, pathDelimiter, { error, info,
|
|
26
|
+
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
|
|
26
27
|
const {
|
|
27
28
|
isStructured, get$combined, getFinalBaseType, getServiceName,
|
|
28
29
|
} = getUtils(csn);
|
|
@@ -182,7 +183,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
182
183
|
}
|
|
183
184
|
|
|
184
185
|
// We would be broken if we continue with assoc usage to now skipped
|
|
185
|
-
|
|
186
|
+
throwWithAnyError();
|
|
186
187
|
|
|
187
188
|
|
|
188
189
|
for (const {
|
|
@@ -211,7 +212,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
211
212
|
while (stack.length > 0) {
|
|
212
213
|
const [ a, n ] = stack.pop();
|
|
213
214
|
if (a[_dependents]) {
|
|
214
|
-
|
|
215
|
+
forEach(a[_dependents], (dependentName, dependent) => {
|
|
215
216
|
stack.push([ dependent, dependentName ]);
|
|
216
217
|
});
|
|
217
218
|
}
|
|
@@ -253,7 +254,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
253
254
|
/**
|
|
254
255
|
* Rewrite expand and inline to "normal" refs
|
|
255
256
|
*
|
|
256
|
-
* @param {CSN.Artifact} root All elements visible
|
|
257
|
+
* @param {CSN.Artifact} root All elements visible from the query source ($combined)
|
|
257
258
|
* @param {CSN.Column[]} columns
|
|
258
259
|
* @param {string[]} excluding
|
|
259
260
|
* @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
|
|
@@ -261,8 +262,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
261
262
|
function rewrite(root, columns, excluding) {
|
|
262
263
|
const allToMany = [];
|
|
263
264
|
const newThing = [];
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
const containsExpandInline = columns.some(col => col.expand || col.inline);
|
|
266
|
+
if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
|
|
267
|
+
columns = replaceStar(root, columns, excluding);
|
|
268
|
+
else
|
|
269
|
+
return { columns, toMany: [] };
|
|
270
|
+
|
|
266
271
|
for (const col of columns) {
|
|
267
272
|
if (col.expand) {
|
|
268
273
|
// TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
|
|
@@ -402,8 +407,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
402
407
|
* @returns {string|null} Name of any entity
|
|
403
408
|
*/
|
|
404
409
|
function findAnEntity() {
|
|
405
|
-
for (const
|
|
406
|
-
if (
|
|
410
|
+
for (const name in csn.definitions) {
|
|
411
|
+
if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
407
412
|
return name;
|
|
408
413
|
}
|
|
409
414
|
return null;
|
|
@@ -544,7 +549,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
544
549
|
/**
|
|
545
550
|
* Replace the star and correctly put shadowed things in the right place.
|
|
546
551
|
*
|
|
547
|
-
* @param {
|
|
552
|
+
* @param {object} base The raw set of things a * can expand to
|
|
548
553
|
* @param {Array} subs Things - the .expand/.inline or .columns
|
|
549
554
|
* @param {string[]} [excluding=[]]
|
|
550
555
|
* @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
const {
|
|
4
4
|
getUtils, walkCsnPath,
|
|
5
5
|
applyTransformations, applyTransformationsOnNonDictionary,
|
|
6
|
-
isBuiltinType,
|
|
6
|
+
isBuiltinType, cloneCsnNonDict,
|
|
7
7
|
copyAnnotations, implicitAs, isDeepEqual,
|
|
8
8
|
} = require('../../model/csnUtils');
|
|
9
9
|
const transformUtils = require('../transformUtilsNew');
|
|
10
10
|
const { csnRefs } = require('../../model/csnRefs');
|
|
11
11
|
const { setProp } = require('../../base/model');
|
|
12
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Strip off leading $self from refs where applicable
|
|
@@ -74,17 +75,20 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
74
75
|
iterateOptions.skipDict.actions = true;
|
|
75
76
|
else
|
|
76
77
|
iterateOptions.skipDict = { actions: true };
|
|
78
|
+
|
|
79
|
+
const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
|
|
80
|
+
const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
|
|
77
81
|
applyTransformations(csn, {
|
|
78
|
-
cast: (parent) => {
|
|
82
|
+
cast: (parent, prop, cast, path) => {
|
|
79
83
|
// Resolve cast already - we otherwise lose .localized
|
|
80
|
-
if (
|
|
84
|
+
if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
|
|
81
85
|
toFinalBaseType(parent.cast, resolved, true);
|
|
82
86
|
},
|
|
83
87
|
// @ts-ignore
|
|
84
|
-
type: (parent, prop, type,
|
|
85
|
-
if (options.toOdata && parent.kind &&
|
|
88
|
+
type: (parent, prop, type, path) => {
|
|
89
|
+
if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
|
|
86
90
|
return;
|
|
87
|
-
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type) && !isODataItems(type))) {
|
|
91
|
+
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
|
|
88
92
|
toFinalBaseType(parent, resolved);
|
|
89
93
|
// structured types might not have the child-types replaced.
|
|
90
94
|
// Drill down to ensure this.
|
|
@@ -110,40 +114,6 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
110
114
|
parent.type = 'cds.LargeString';
|
|
111
115
|
delete parent.items;
|
|
112
116
|
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* OData V4 only:
|
|
116
|
-
* Do not replace a type ref if:
|
|
117
|
-
* The type definition is terminating on a scalar type (that can also be a derived type chain)
|
|
118
|
-
* AND the typeName (that is the start of that (derived) type chain is defined within the same
|
|
119
|
-
* service as the artifact from which the type reference has to be resolved.
|
|
120
|
-
*
|
|
121
|
-
* @param {string} typeName
|
|
122
|
-
* @returns {boolean}
|
|
123
|
-
*/
|
|
124
|
-
function isODataV4BuiltinFromService(typeName) {
|
|
125
|
-
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2'))
|
|
126
|
-
return false;
|
|
127
|
-
|
|
128
|
-
const typeServiceName = getServiceName(typeName);
|
|
129
|
-
const finalBaseType = getFinalBaseType(typeName);
|
|
130
|
-
// we need the service of the current definition
|
|
131
|
-
const currDefServiceName = getServiceName(csnPath[1]);
|
|
132
|
-
|
|
133
|
-
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* OData stops replacing types @ 'items', if the type ref is a user defined type
|
|
138
|
-
* AND that type has items, don't do toFinalBaseType
|
|
139
|
-
*
|
|
140
|
-
* @param {string} typeName
|
|
141
|
-
* @returns {boolean}
|
|
142
|
-
*/
|
|
143
|
-
function isODataItems(typeName) {
|
|
144
|
-
const typeDef = csn.definitions[typeName];
|
|
145
|
-
return !!(options.toOdata && typeDef && typeDef.items);
|
|
146
|
-
}
|
|
147
117
|
},
|
|
148
118
|
// HANA/SQLite do not support array-of - turn into CLOB/Text
|
|
149
119
|
items: (parent) => {
|
|
@@ -159,8 +129,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
159
129
|
|
|
160
130
|
// Do not do for OData
|
|
161
131
|
// TODO:factor out somewhere else
|
|
162
|
-
if (!options.toOdata &&
|
|
163
|
-
([ 'action', 'function', 'event' ].includes(artifact.kind))) {
|
|
132
|
+
if (!options.toOdata && artifact.kind in replaceWithDummyKinds) {
|
|
164
133
|
const dummy = { kind: artifact.kind };
|
|
165
134
|
if (artifact.$location)
|
|
166
135
|
setProp(dummy, '$location', artifact.$location);
|
|
@@ -169,6 +138,42 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
169
138
|
}
|
|
170
139
|
// TODO: skipDict options as default function arguments not via Object.assign
|
|
171
140
|
} ], iterateOptions);
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* OData V4 only:
|
|
145
|
+
* Do not replace a type ref if:
|
|
146
|
+
* The type definition is terminating on a scalar type (that can also be a derived type chain)
|
|
147
|
+
* AND the typeName (that is the start of that (derived) type chain is defined within the same
|
|
148
|
+
* service as the artifact from which the type reference has to be resolved.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} typeName
|
|
151
|
+
* @param {CSN.Path} path
|
|
152
|
+
* @returns {boolean}
|
|
153
|
+
*/
|
|
154
|
+
function isODataV4BuiltinFromService(typeName, path) {
|
|
155
|
+
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2') || typeof typeName !== 'string')
|
|
156
|
+
return false;
|
|
157
|
+
|
|
158
|
+
const typeServiceName = getServiceName(typeName);
|
|
159
|
+
const finalBaseType = getFinalBaseType(typeName);
|
|
160
|
+
// we need the service of the current definition
|
|
161
|
+
const currDefServiceName = getServiceName(path[1]);
|
|
162
|
+
|
|
163
|
+
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* OData stops replacing types @ 'items', if the type ref is a user defined type
|
|
168
|
+
* AND that type has items, don't do toFinalBaseType
|
|
169
|
+
*
|
|
170
|
+
* @param {string} typeName
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
function isODataItems(typeName) {
|
|
174
|
+
const typeDef = csn.definitions[typeName];
|
|
175
|
+
return !!(options.toOdata && typeDef && typeDef.items);
|
|
176
|
+
}
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
/**
|
|
@@ -258,7 +263,7 @@ function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter, iter
|
|
|
258
263
|
* @param {CSN.Options} options
|
|
259
264
|
* @param {string} pathDelimiter
|
|
260
265
|
* @param {Function} error
|
|
261
|
-
* @param {
|
|
266
|
+
* @param {object} iterateOptions
|
|
262
267
|
*/
|
|
263
268
|
function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {
|
|
264
269
|
const { isAssocOrComposition } = getUtils(csn);
|
|
@@ -284,7 +289,7 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
|
|
|
284
289
|
function flatten(parent, prop, dict, path) {
|
|
285
290
|
if (!parent[prop].$orderedElements)
|
|
286
291
|
setProp(parent[prop], '$orderedElements', []);
|
|
287
|
-
|
|
292
|
+
forEach(dict, (elementName, element) => {
|
|
288
293
|
if (element.elements) {
|
|
289
294
|
// Ignore the structured element, replace it by its flattened form
|
|
290
295
|
// TODO: use $ignore - _ is for links
|
|
@@ -295,9 +300,9 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
|
|
|
295
300
|
|
|
296
301
|
for (const flatElemName in flatElems) {
|
|
297
302
|
if (parent[prop][flatElemName])
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
// TODO: combine message ID with generated FK duplicate
|
|
304
|
+
// do the duplicate check in the consruct callback, requires to mark generated flat elements,
|
|
305
|
+
// check: Error location should be the existing element like @odata.foreignKey4
|
|
301
306
|
error(null, path.concat([ 'elements', elementName ]), `"${path[1]}.${elementName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
|
|
302
307
|
|
|
303
308
|
const flatElement = flatElems[flatElemName];
|
|
@@ -315,16 +320,16 @@ function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}
|
|
|
315
320
|
const firstRef = onPart.ref[0];
|
|
316
321
|
|
|
317
322
|
/*
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
323
|
+
when element is defined in the current name resolution scope, like
|
|
324
|
+
entity E {
|
|
325
|
+
key x: Integer;
|
|
326
|
+
s : {
|
|
327
|
+
y : Integer;
|
|
328
|
+
a3 : association to E on a3.x = y;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
332
|
+
*/
|
|
328
333
|
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
|
|
329
334
|
const possibleFlatName = prefix + pathDelimiter + firstRef;
|
|
330
335
|
|
|
@@ -418,11 +423,11 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
418
423
|
}
|
|
419
424
|
});
|
|
420
425
|
},
|
|
421
|
-
}, [], {
|
|
426
|
+
}, [], Object.assign({
|
|
422
427
|
skipIgnore: false,
|
|
423
428
|
allowArtifact: artifact => (artifact.kind === 'entity' || artifact.kind === 'type'),
|
|
424
429
|
skipDict: { actions: true },
|
|
425
|
-
});
|
|
430
|
+
}, iterateOptions));
|
|
426
431
|
}
|
|
427
432
|
createForeignKeyElements();
|
|
428
433
|
|
|
@@ -469,7 +474,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
469
474
|
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
470
475
|
Object.keys(flat).forEach((flatElemName) => {
|
|
471
476
|
const key = assoc.keys[i];
|
|
472
|
-
const clone =
|
|
477
|
+
const clone = cloneCsnNonDict(assoc.keys[i], options);
|
|
473
478
|
if (clone.as) {
|
|
474
479
|
const lastRef = clone.ref[clone.ref.length - 1];
|
|
475
480
|
// Cut off the last ref part from the beginning of the flat name
|
|
@@ -533,7 +538,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
533
538
|
* @returns {object} The clone of base
|
|
534
539
|
*/
|
|
535
540
|
function cloneAndExtendRef(key, base, ref) {
|
|
536
|
-
const clone =
|
|
541
|
+
const clone = cloneCsnNonDict(base, options);
|
|
537
542
|
if (key.ref) {
|
|
538
543
|
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
539
544
|
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
@@ -574,10 +579,7 @@ function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, path
|
|
|
574
579
|
if (options.toOdata)
|
|
575
580
|
transformers.params = createFks;
|
|
576
581
|
|
|
577
|
-
|
|
578
|
-
// artifacts that shall still be maintained from outside, filter them away in the rename script)
|
|
579
|
-
iterateOptions.skipIgnore = !(options.toOdata || options.toRename);
|
|
580
|
-
applyTransformations(csn, transformers, [], iterateOptions);
|
|
582
|
+
applyTransformations(csn, transformers, [], Object.assign({ skipIgnore: false }, iterateOptions));
|
|
581
583
|
|
|
582
584
|
/**
|
|
583
585
|
* Process a given .elements or .params dictionary and create foreign key elements
|
|
@@ -685,17 +687,21 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
|
|
|
685
687
|
return fks;
|
|
686
688
|
|
|
687
689
|
let finalElement = element;
|
|
690
|
+
let finalTypeName; // TODO: Find a way to not rely on $path?
|
|
688
691
|
// TODO: effectiveType's return value is 'path' for the next inspectRef
|
|
689
692
|
if (element.type && !isBuiltinType(element.type)) {
|
|
690
693
|
const tmpElt = effectiveType(element);
|
|
691
694
|
// effective type resolves to structs and enums only but not scalars
|
|
692
695
|
if (Object.keys(tmpElt).length) {
|
|
693
696
|
finalElement = tmpElt;
|
|
697
|
+
finalTypeName = finalElement.$path[1];
|
|
694
698
|
}
|
|
695
699
|
else {
|
|
696
700
|
// unwind a derived type chain to a scalar type
|
|
697
|
-
while (finalElement.type && !isBuiltinType(finalElement.type))
|
|
701
|
+
while (finalElement.type && !isBuiltinType(finalElement.type)) {
|
|
702
|
+
finalTypeName = finalElement.type;
|
|
698
703
|
finalElement = csn.definitions[finalElement.type];
|
|
704
|
+
}
|
|
699
705
|
}
|
|
700
706
|
}
|
|
701
707
|
|
|
@@ -712,7 +718,7 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
|
|
|
712
718
|
}
|
|
713
719
|
// TODO: has managed assoc keys?
|
|
714
720
|
finalElement.keys.forEach((key, keyIndex) => {
|
|
715
|
-
const continuePath =
|
|
721
|
+
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
716
722
|
const alias = key.as || implicitAs(key.ref);
|
|
717
723
|
const result = inspectRef(continuePath);
|
|
718
724
|
fks = fks.concat(createForeignKeysInternal(result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
|
|
@@ -738,12 +744,33 @@ function createForeignKeysInternal(path, element, prefix, csn, options, pathDeli
|
|
|
738
744
|
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
|
|
739
745
|
// Skip already produced foreign keys
|
|
740
746
|
if (!elem['@odata.foreignKey4']) {
|
|
741
|
-
const continuePath =
|
|
747
|
+
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
742
748
|
fks = fks.concat(createForeignKeysInternal(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
743
749
|
}
|
|
744
750
|
});
|
|
745
751
|
}
|
|
746
752
|
|
|
753
|
+
/**
|
|
754
|
+
* Get the path to continue resolving references
|
|
755
|
+
*
|
|
756
|
+
* If we are currently inside of a type, we need to start our path fresh from that given type.
|
|
757
|
+
* Otherwise, we would try to resolve .elements on a thing that does not exist.
|
|
758
|
+
*
|
|
759
|
+
* We also respect if we have a previous inspectRef result as our base.
|
|
760
|
+
*
|
|
761
|
+
* @param {Array} additions
|
|
762
|
+
* @returns {CSN.Path}
|
|
763
|
+
*/
|
|
764
|
+
function getContinuePath(additions) {
|
|
765
|
+
if (csn.definitions[finalElement.type])
|
|
766
|
+
return [ 'definitions', finalElement.type, ...additions ];
|
|
767
|
+
else if (finalTypeName)
|
|
768
|
+
return [ 'definitions', finalTypeName, ...additions ];
|
|
769
|
+
else if (isInspectRefResult)
|
|
770
|
+
return [ path, ...additions ];
|
|
771
|
+
return [ ...path, ...additions ];
|
|
772
|
+
}
|
|
773
|
+
|
|
747
774
|
fks.forEach((fk) => {
|
|
748
775
|
// prepend current prefix
|
|
749
776
|
fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
|
|
@@ -110,8 +110,8 @@ function handleExists(csn, options, error) {
|
|
|
110
110
|
sources[join.as] = join.as;
|
|
111
111
|
}
|
|
112
112
|
else if (join.args) {
|
|
113
|
-
const
|
|
114
|
-
sources = Object.assign(sources,
|
|
113
|
+
const subSources = getJoinSources(join.args);
|
|
114
|
+
sources = Object.assign(sources, subSources);
|
|
115
115
|
}
|
|
116
116
|
else if (join.ref) {
|
|
117
117
|
sources[join.ref[join.ref.length - 1]] = join.ref[join.ref.length - 1];
|
|
@@ -196,7 +196,7 @@ function handleExists(csn, options, error) {
|
|
|
196
196
|
const stack = [ [ null, startAssoc, startRest, startIndex ] ];
|
|
197
197
|
const { links } = inspectRef(path);
|
|
198
198
|
while (stack.length > 0) {
|
|
199
|
-
// previous: to nest "up" if the previous assoc did not
|
|
199
|
+
// previous: to nest "up" if the previous assoc did not originally have a filter
|
|
200
200
|
// assoc: the assoc path step
|
|
201
201
|
// rest: path steps after assoc
|
|
202
202
|
// index: index of after-assoc in the overall ref-array - so we know where to start looking for the next assoc
|
|
@@ -411,14 +411,14 @@ function handleExists(csn, options, error) {
|
|
|
411
411
|
* Translate an `EXISTS <managed assoc>` into a part of a WHERE condition.
|
|
412
412
|
*
|
|
413
413
|
* For each of the foreign keys, do:
|
|
414
|
-
* + build the target side by prefixing `target`
|
|
414
|
+
* + build the target side by prefixing `target` in front of the ref
|
|
415
415
|
* + build the source side by prefixing `base` (if not already part of `current`)
|
|
416
|
-
* and the assoc name itself (current)
|
|
416
|
+
* and the assoc name itself (current) in front of the ref
|
|
417
417
|
* + Compare source and target with `=`
|
|
418
418
|
*
|
|
419
419
|
* If there is more than one foreign key, join with `and`.
|
|
420
420
|
*
|
|
421
|
-
* The new tokens are
|
|
421
|
+
* The new tokens are immediately added to the WHERE of the subselect
|
|
422
422
|
*
|
|
423
423
|
* @param {CSN.Element} root
|
|
424
424
|
* @param {string} target
|
|
@@ -752,7 +752,7 @@ function handleExists(csn, options, error) {
|
|
|
752
752
|
*
|
|
753
753
|
* @param {string} target
|
|
754
754
|
* @param {TokenStream} where
|
|
755
|
-
* @returns {TokenStream} The input-where with the refs
|
|
755
|
+
* @returns {TokenStream} The input-where with the refs transformed to absolute ones
|
|
756
756
|
*/
|
|
757
757
|
function remapExistingWhere(target, where) {
|
|
758
758
|
return where.map((part) => {
|