@sap/cds-compiler 2.13.8 → 3.0.0
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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- 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
package/lib/model/csnUtils.js
CHANGED
|
@@ -5,6 +5,8 @@ const { applyTransformations, applyTransformationsOnNonDictionary } = require('.
|
|
|
5
5
|
const { isBuiltinType } = require('../compiler/builtins.js')
|
|
6
6
|
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
|
|
7
7
|
const { ModelError } = require("../base/error");
|
|
8
|
+
const { typeParameters } = require("../compiler/builtins");
|
|
9
|
+
const { forEach } = require('../utils/objectUtils');
|
|
8
10
|
const version = require('../../package.json').version;
|
|
9
11
|
|
|
10
12
|
// Low-level utility functions to work with compact CSN.
|
|
@@ -34,11 +36,14 @@ const version = require('../../package.json').version;
|
|
|
34
36
|
*/
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
* Get utility functions for a given CSN.
|
|
39
|
+
* Get utility functions for a given CSN. Re-exports functions of `csnRefs()`.
|
|
38
40
|
* @param {CSN.Model} model (Compact) CSN model
|
|
39
41
|
*/
|
|
40
42
|
function getUtils(model, universalReady) {
|
|
41
|
-
const
|
|
43
|
+
const _csnRefs = csnRefs(model, universalReady);
|
|
44
|
+
const { artifactRef } = _csnRefs;
|
|
45
|
+
/** Cache for getFinalBaseTypeWithProps(). Specific to the current model. */
|
|
46
|
+
const finalBaseTypeCache = Object.create(null);
|
|
42
47
|
|
|
43
48
|
return {
|
|
44
49
|
getCsnDef,
|
|
@@ -56,17 +61,10 @@ function getUtils(model, universalReady) {
|
|
|
56
61
|
getServiceName,
|
|
57
62
|
hasAnnotationValue,
|
|
58
63
|
cloneWithTransformations,
|
|
59
|
-
|
|
60
|
-
inspectRef,
|
|
61
|
-
artifactRef,
|
|
62
|
-
effectiveType,
|
|
64
|
+
getFinalBaseTypeWithProps,
|
|
63
65
|
get$combined,
|
|
64
|
-
getOrigin,
|
|
65
66
|
getQueryPrimarySource,
|
|
66
|
-
|
|
67
|
-
getColumn,
|
|
68
|
-
getElement,
|
|
69
|
-
initDefinition
|
|
67
|
+
..._csnRefs,
|
|
70
68
|
};
|
|
71
69
|
|
|
72
70
|
/**
|
|
@@ -261,7 +259,7 @@ function getUtils(model, universalReady) {
|
|
|
261
259
|
*/
|
|
262
260
|
function isStructured(obj) {
|
|
263
261
|
return obj.elements ||
|
|
264
|
-
(obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref &&
|
|
262
|
+
(obj.type && ((getFinalTypeDef(obj.type).elements) || (obj.type.ref && getFinalBaseTypeWithProps(obj.type)?.elements)));
|
|
265
263
|
}
|
|
266
264
|
|
|
267
265
|
/**
|
|
@@ -447,13 +445,13 @@ function getUtils(model, universalReady) {
|
|
|
447
445
|
* The transformer functions are called with the following signature:
|
|
448
446
|
* transformer(value, node, resultNode, key)
|
|
449
447
|
*
|
|
450
|
-
* @param {any}
|
|
448
|
+
* @param {any} rootNode Node to transform
|
|
451
449
|
* @param {any} transformers Object defining transformer functions
|
|
452
450
|
* @returns {object}
|
|
453
451
|
*/
|
|
454
|
-
function cloneWithTransformations(
|
|
452
|
+
function cloneWithTransformations(rootNode, transformers) {
|
|
455
453
|
|
|
456
|
-
return transformNode(
|
|
454
|
+
return transformNode(rootNode);
|
|
457
455
|
|
|
458
456
|
// This general transformation function will be applied to each node recursively
|
|
459
457
|
function transformNode(node) {
|
|
@@ -486,97 +484,122 @@ function getUtils(model, universalReady) {
|
|
|
486
484
|
}
|
|
487
485
|
}
|
|
488
486
|
|
|
487
|
+
function _normalizeTypeRef(type) {
|
|
488
|
+
if (type && typeof type === 'object' && type.ref?.length === 1)
|
|
489
|
+
type = type.ref[0]; // simplify type: no element -> simple string can be used
|
|
490
|
+
return type;
|
|
491
|
+
}
|
|
489
492
|
|
|
490
493
|
/**
|
|
491
|
-
* Resolve to the final type of a type, that means follow type chains, references
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* Returns
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
* type s1 {
|
|
499
|
-
* s: s2;
|
|
500
|
-
* };
|
|
501
|
-
* type s2 {
|
|
502
|
-
* u: type of S.e:t;
|
|
503
|
-
* }
|
|
504
|
-
* service S {
|
|
505
|
-
* type foo: type of S.e:i.j1;
|
|
506
|
-
* entity e {
|
|
507
|
-
* key i: { j1: Integer };
|
|
508
|
-
* t: bar;
|
|
509
|
-
* v: s1;
|
|
510
|
-
* x: blutz.s.u;
|
|
511
|
-
* };
|
|
512
|
-
* type blutz: S.e.v;
|
|
513
|
-
* view V as select from e {
|
|
514
|
-
* 1+1 as i: bar,
|
|
515
|
-
* };
|
|
516
|
-
* type tt: type of V:i;
|
|
517
|
-
* }
|
|
518
|
-
* the following calls will all return 'cds.Integer'
|
|
519
|
-
* getFinalBaseType('S.tt')
|
|
520
|
-
* getFinalBaseType('S.e',['i','j1'])
|
|
521
|
-
* getFinalBaseType('S.e',['t'])
|
|
522
|
-
* getFinalBaseType('S.e',['x'])
|
|
523
|
-
* getFinalBaseType('S.blutz',['s', 'u'])
|
|
524
|
-
* Types are always resolved as far as possible. A type name which has no further definition is simply returned.
|
|
525
|
-
* Composed types (structures, entities, views, ...) are returned as type objects, if not drilled down into
|
|
526
|
-
* the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
|
|
527
|
-
* no type (e.g. expr in a view without explicit type) returns 'null'
|
|
494
|
+
* Resolve to the final type of a type, that means follow type chains, references, etc.
|
|
495
|
+
* Input is a type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.
|
|
496
|
+
*
|
|
497
|
+
* Returns `null` if the type can't be resolved or if the referenced element has no type,
|
|
498
|
+
* e.g. `typeof V:calculated`.
|
|
499
|
+
* Otherwise, if scalar, returns an object that has a `type` property and all collected type
|
|
500
|
+
* properties, or the type object with `elements` or `items` property if structured/arrayed.
|
|
528
501
|
*
|
|
529
|
-
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
* @param {object}
|
|
533
|
-
* @returns
|
|
502
|
+
* Caches type lookups. If the CSN changes drastically, you will need to re-call `csnUtils()`
|
|
503
|
+
* and use the newly returned `getFinalBaseTypeWithProps()`.
|
|
504
|
+
*
|
|
505
|
+
* @param {string|object} type Type as string or type ref, i.e. `{ ref: [...] }`
|
|
506
|
+
* @returns {object|null}
|
|
534
507
|
*/
|
|
535
|
-
function
|
|
508
|
+
function getFinalBaseTypeWithProps(type) {
|
|
509
|
+
type = _normalizeTypeRef(type);
|
|
536
510
|
if (!type)
|
|
537
|
-
return
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
511
|
+
return null;
|
|
512
|
+
|
|
513
|
+
// Nothing to copy from builtin type name.
|
|
514
|
+
if (typeof type === 'string' && isBuiltinType( type )) {
|
|
515
|
+
return { type };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// We differentiate between ref and type to avoid collisions due to dict key.
|
|
519
|
+
// Delimiter chosen arbitrarily; just one that is rarely used.
|
|
520
|
+
const resolvedKey = (typeof type === 'object') ? `ref:${type.ref.join('\\')}` : `type:${type}`;
|
|
521
|
+
|
|
522
|
+
if (finalBaseTypeCache[resolvedKey]) {
|
|
523
|
+
if (finalBaseTypeCache[resolvedKey] === true)
|
|
524
|
+
throw new ModelError(`Detected circular type reference; can't resolve: ${resolvedKey}`);
|
|
525
|
+
return finalBaseTypeCache[resolvedKey];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
let typeRef = artifactRef(type); // throws if not found
|
|
529
|
+
const isNonScalar = _cacheNonScalar(typeRef);
|
|
530
|
+
if (isNonScalar)
|
|
531
|
+
return finalBaseTypeCache[resolvedKey];
|
|
532
|
+
|
|
533
|
+
const props = {};
|
|
534
|
+
_copyTypeProps(props, typeRef);
|
|
535
|
+
|
|
536
|
+
// If the resolved type is a builtin, stop and use its type arguments.
|
|
537
|
+
type = _normalizeTypeRef(typeRef.type);
|
|
538
|
+
if (typeof type === 'string' && isBuiltinType(type))
|
|
539
|
+
return _cacheResolved(props);
|
|
540
|
+
|
|
541
|
+
// Set to true (before the recursive call) to avoid cyclic issues.
|
|
542
|
+
finalBaseTypeCache[resolvedKey] = true;
|
|
543
|
+
|
|
544
|
+
// Continue the search
|
|
545
|
+
const finalBase = getFinalBaseTypeWithProps(type);
|
|
546
|
+
if (!finalBase) // Reference has no proper type, e.g. due to `type of View:calculated`.
|
|
547
|
+
return _cacheResolved(null);
|
|
548
|
+
const nonScalar = _cacheNonScalar(finalBase);
|
|
549
|
+
if (nonScalar)
|
|
550
|
+
return finalBaseTypeCache[resolvedKey];
|
|
551
|
+
|
|
552
|
+
// If not a non-scalar, must be resolved type.
|
|
553
|
+
_copyTypeProps(props, finalBase);
|
|
554
|
+
_cacheResolved(props);
|
|
555
|
+
return props;
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Cache/Store the type props under the current `resolvedKey` in the `resolved` cache.
|
|
559
|
+
*
|
|
560
|
+
* @param {object} typeProps
|
|
561
|
+
*/
|
|
562
|
+
function _cacheResolved(typeProps) {
|
|
563
|
+
finalBaseTypeCache[resolvedKey] = typeProps;
|
|
564
|
+
return typeProps;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Structured or arrayed types are not followed further, so cache them.
|
|
569
|
+
*
|
|
570
|
+
* @param obj
|
|
571
|
+
* @returns {boolean} True, if structured/arrayed/invalid, false if scalar.
|
|
572
|
+
*/
|
|
573
|
+
function _cacheNonScalar(obj) {
|
|
574
|
+
if (!obj) { // Reference has no proper type, e.g. due to `type of View:calculated`.
|
|
575
|
+
_cacheResolved(null);
|
|
576
|
+
return true;
|
|
547
577
|
}
|
|
548
|
-
|
|
549
|
-
|
|
578
|
+
if (obj.elements || obj.items) {
|
|
579
|
+
_cacheResolved(obj);
|
|
580
|
+
return true;
|
|
550
581
|
}
|
|
551
|
-
|
|
552
|
-
if (definedType && definedType.type)
|
|
553
|
-
return getFinalBaseType(definedType.type, path, resolved, cycleCheck);
|
|
554
|
-
else
|
|
555
|
-
return getFinalBaseType(definedType, path, resolved, cycleCheck);
|
|
582
|
+
return false;
|
|
556
583
|
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Copy type properties from source to target. Also copies `type`, `enum`,
|
|
587
|
+
* and `localized` (if keepLocalized is true). Only copies from source,
|
|
588
|
+
* if target does not have them.
|
|
589
|
+
*
|
|
590
|
+
* @param {object} target
|
|
591
|
+
* @param {object} source
|
|
592
|
+
*/
|
|
593
|
+
function _copyTypeProps(target, source) {
|
|
594
|
+
target.type = source.type;
|
|
595
|
+
const typeProps = [ ...typeParameters.list, 'enum', 'default', 'localized' ];
|
|
596
|
+
for (const param of typeProps) {
|
|
597
|
+
if (target[param] === undefined && source[param] !== undefined) {
|
|
598
|
+
target[param] = source[param];
|
|
567
599
|
}
|
|
568
600
|
}
|
|
569
|
-
|
|
570
|
-
return (getFinalBaseType(type.type, path, resolved, cycleCheck));
|
|
571
|
-
else if (type.items)
|
|
572
|
-
return type;
|
|
573
|
-
else
|
|
574
|
-
// TODO: this happens if we don't have a type, e.g. an expression in a select list
|
|
575
|
-
// in a view without explicit type. Instead of returning null we might want to return
|
|
576
|
-
// the object instead?
|
|
577
|
-
return null;
|
|
601
|
+
return target;
|
|
578
602
|
}
|
|
579
|
-
return type;
|
|
580
603
|
}
|
|
581
604
|
}
|
|
582
605
|
|
|
@@ -588,7 +611,7 @@ function getUtils(model, universalReady) {
|
|
|
588
611
|
* @param {object} csn Top-level CSN. You can pass non-dictionary values.
|
|
589
612
|
* @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`
|
|
590
613
|
*/
|
|
591
|
-
function
|
|
614
|
+
function cloneCsnNonDict(csn, options) {
|
|
592
615
|
return sortCsn(csn, options);
|
|
593
616
|
}
|
|
594
617
|
|
|
@@ -596,7 +619,7 @@ function cloneCsn(csn, options) {
|
|
|
596
619
|
* Deeply clone the given CSN dictionary and return it.
|
|
597
620
|
* Note that annotations are only copied shallowly.
|
|
598
621
|
* This function does _not_ sort the given dictionary.
|
|
599
|
-
* See
|
|
622
|
+
* See cloneCsnNonDict() if you want sorted definitions.
|
|
600
623
|
*
|
|
601
624
|
* @param {object} csn
|
|
602
625
|
* @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
|
|
@@ -632,8 +655,10 @@ function forEachDefinition( csn, callback, iterateOptions = {} ) {
|
|
|
632
655
|
* @param {CSN.Path} [path]
|
|
633
656
|
* @param {boolean} [ignoreIgnore]
|
|
634
657
|
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
658
|
+
* @param constructCallback
|
|
635
659
|
*/
|
|
636
|
-
function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}
|
|
660
|
+
function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
|
|
661
|
+
constructCallback = (_construct, _prop, _path) => {}) {
|
|
637
662
|
// Allow processing _ignored elements if requested
|
|
638
663
|
if (ignoreIgnore && construct._ignore) {
|
|
639
664
|
return;
|
|
@@ -641,8 +666,7 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
|
|
|
641
666
|
|
|
642
667
|
// `items` itself is a structure that can contain "elements", and more.
|
|
643
668
|
if (construct.items) {
|
|
644
|
-
|
|
645
|
-
forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions );
|
|
669
|
+
forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions, constructCallback );
|
|
646
670
|
}
|
|
647
671
|
|
|
648
672
|
// Unlike XSN, we don't make "returns" a "params" in the callback.
|
|
@@ -650,12 +674,43 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
|
|
|
650
674
|
// `elements` of the return type (if structured).
|
|
651
675
|
// TODO: `returns` should be handled like a parameter just like XSN (maybe with different prop name)
|
|
652
676
|
if (construct.returns && !iterateOptions.elementsOnly) {
|
|
653
|
-
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
|
|
677
|
+
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions, constructCallback );
|
|
654
678
|
}
|
|
655
679
|
|
|
656
680
|
path = [...path]; // Copy
|
|
657
681
|
const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'actions', 'params']);
|
|
658
|
-
propsWithMembers.forEach((prop) =>
|
|
682
|
+
propsWithMembers.forEach((prop) => {
|
|
683
|
+
forEachGeneric( construct, prop, callback, path, iterateOptions );
|
|
684
|
+
if (construct[prop]) {
|
|
685
|
+
if (Array.isArray(constructCallback))
|
|
686
|
+
constructCallback.forEach(cb => cb(construct, prop, path));
|
|
687
|
+
else
|
|
688
|
+
constructCallback(construct, prop, path);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Call `forEachMember` and then apply `forEachMember` on queries.
|
|
695
|
+
*
|
|
696
|
+
* @param {CSN.Artifact} construct
|
|
697
|
+
* @param {genericCallback|genericCallback[]} callback
|
|
698
|
+
* @param {CSN.Path} [path]
|
|
699
|
+
* @param {boolean} [ignoreIgnore]
|
|
700
|
+
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
701
|
+
* @param {constructCallback|constructCallback[]} callback
|
|
702
|
+
*/
|
|
703
|
+
function forEachMemberWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
|
|
704
|
+
constructCallback = (_construct, _prop, _path) => {}) {
|
|
705
|
+
forEachMember(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
706
|
+
if (construct.query) {
|
|
707
|
+
forAllQueries(construct.query, (q, p) => {
|
|
708
|
+
const s = q.SELECT;
|
|
709
|
+
if(s) {
|
|
710
|
+
forEachMember(s, callback, p, ignoreIgnore, iterateOptions);
|
|
711
|
+
}
|
|
712
|
+
}, [ ...path, 'query' ]);
|
|
713
|
+
}
|
|
659
714
|
}
|
|
660
715
|
|
|
661
716
|
/**
|
|
@@ -667,16 +722,43 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
|
|
|
667
722
|
* @param {CSN.Path} [path]
|
|
668
723
|
* @param {boolean} [ignoreIgnore]
|
|
669
724
|
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
725
|
+
* @param {constructCallback|constructCallback[]} callback
|
|
670
726
|
*/
|
|
671
|
-
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}
|
|
672
|
-
|
|
727
|
+
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
|
|
728
|
+
constructCallback = (_construct, _prop, _path) => {}) {
|
|
729
|
+
forEachMember( construct, ( member, memberName, prop, subpath, parent ) => {
|
|
673
730
|
if(Array.isArray(callback))
|
|
674
|
-
callback.forEach(cb => cb( member, memberName, prop, subpath,
|
|
731
|
+
callback.forEach(cb => cb( member, memberName, prop, subpath, parent ));
|
|
675
732
|
else
|
|
676
|
-
callback( member, memberName, prop, subpath,
|
|
733
|
+
callback( member, memberName, prop, subpath, parent );
|
|
677
734
|
// Descend into nested members, too
|
|
678
|
-
forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions);
|
|
679
|
-
}, path, ignoreIgnore, iterateOptions);
|
|
735
|
+
forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions, constructCallback);
|
|
736
|
+
}, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Apply function `callback(member, memberName)` to each member in `construct`,
|
|
741
|
+
* recursively (i.e. also for sub-elements of elements).
|
|
742
|
+
* Recursively iterate over elements of `construct` query.
|
|
743
|
+
*
|
|
744
|
+
* @param {CSN.Artifact} construct
|
|
745
|
+
* @param {genericCallback|genericCallback[]} callback
|
|
746
|
+
* @param {CSN.Path} [path]
|
|
747
|
+
* @param {boolean} [ignoreIgnore]
|
|
748
|
+
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
749
|
+
* @param {constructCallback|constructCallback[]} callback
|
|
750
|
+
*/
|
|
751
|
+
function forEachMemberRecursivelyWithQuery( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {},
|
|
752
|
+
constructCallback = (_construct, _prop, _path) => {}) {
|
|
753
|
+
forEachMemberRecursively(construct, callback, path, ignoreIgnore, iterateOptions, constructCallback);
|
|
754
|
+
if(construct.query) {
|
|
755
|
+
forAllQueries(construct.query, (q, p) => {
|
|
756
|
+
const s = q.SELECT;
|
|
757
|
+
if(s) {
|
|
758
|
+
forEachMemberRecursively(s, callback, p, ignoreIgnore, iterateOptions);
|
|
759
|
+
}
|
|
760
|
+
}, [ ...path, 'query' ]);
|
|
761
|
+
}
|
|
680
762
|
}
|
|
681
763
|
|
|
682
764
|
/**
|
|
@@ -685,14 +767,14 @@ function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=tr
|
|
|
685
767
|
* the following arguments: the object, the name, and -if it is a duplicate-
|
|
686
768
|
* the array index and the array containing all duplicates.
|
|
687
769
|
*
|
|
688
|
-
* @param {object}
|
|
770
|
+
* @param {object} construct
|
|
689
771
|
* @param {string} prop
|
|
690
772
|
* @param {genericCallback|genericCallback[]} callback
|
|
691
773
|
* @param {CSN.Path} path
|
|
692
774
|
* @param {object} iterateOptions can be used to skip certain kinds from being iterated
|
|
693
775
|
*/
|
|
694
|
-
function forEachGeneric(
|
|
695
|
-
const dict =
|
|
776
|
+
function forEachGeneric( construct, prop, callback, path = [], iterateOptions = {}) {
|
|
777
|
+
const dict = construct[prop];
|
|
696
778
|
for (const name in dict) {
|
|
697
779
|
if (!Object.prototype.hasOwnProperty.call(dict, name))
|
|
698
780
|
continue;
|
|
@@ -701,98 +783,79 @@ function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) {
|
|
|
701
783
|
|| (iterateOptions.skipArtifact && typeof iterateOptions.skipArtifact === 'function'
|
|
702
784
|
&& iterateOptions.skipArtifact(dictObj, name)))
|
|
703
785
|
continue;
|
|
704
|
-
|
|
786
|
+
executeCallbacks( dictObj, name );
|
|
705
787
|
}
|
|
706
|
-
function
|
|
788
|
+
function executeCallbacks(o, name ) {
|
|
707
789
|
if (Array.isArray(callback))
|
|
708
|
-
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));
|
|
790
|
+
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name]), construct ));
|
|
709
791
|
else
|
|
710
|
-
callback( o, name, prop, path.concat([prop, name]))
|
|
792
|
+
callback( o, name, prop, path.concat([prop, name]), construct )
|
|
711
793
|
}
|
|
712
794
|
}
|
|
713
795
|
|
|
714
|
-
// Like Object.assign() but copies also non enumerable properties
|
|
715
|
-
function assignAll(target, ...sources) {
|
|
716
|
-
sources.forEach(source => {
|
|
717
|
-
let descriptors = Object.getOwnPropertyNames(source).reduce((descriptors, key) => {
|
|
718
|
-
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
|
|
719
|
-
return descriptors;
|
|
720
|
-
}, {});
|
|
721
|
-
// by default, Object.assign copies enumerable Symbols too
|
|
722
|
-
Object.getOwnPropertySymbols(source).forEach(sym => {
|
|
723
|
-
let descriptor = Object.getOwnPropertyDescriptor(source, sym);
|
|
724
|
-
if (descriptor.enumerable) {
|
|
725
|
-
descriptors[sym] = descriptor;
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
Object.defineProperties(target, descriptors);
|
|
729
|
-
});
|
|
730
|
-
return target;
|
|
731
|
-
}
|
|
732
|
-
|
|
733
796
|
/**
|
|
734
|
-
* @param {CSN.Query}
|
|
735
|
-
* @param {queryCallback|queryCallback[]}
|
|
797
|
+
* @param {CSN.Query} mainQuery
|
|
798
|
+
* @param {queryCallback|queryCallback[]} queryCallback
|
|
736
799
|
* @param {CSN.Path} path
|
|
737
800
|
*/
|
|
738
|
-
function forAllQueries(
|
|
739
|
-
return traverseQuery(
|
|
740
|
-
function traverseQuery(
|
|
741
|
-
if (
|
|
801
|
+
function forAllQueries(mainQuery, queryCallback, path = []){
|
|
802
|
+
return traverseQuery(mainQuery, queryCallback, path);
|
|
803
|
+
function traverseQuery( query, callback, queryPath ) {
|
|
804
|
+
if (query.SELECT) {
|
|
742
805
|
// The projection is turned into a normalized query - there
|
|
743
806
|
// is no real SELECT, it is fake
|
|
744
807
|
if(!(path.length === 3 && path[2] === 'projection'))
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
808
|
+
queryPath.push('SELECT');
|
|
809
|
+
executeCallbacks();
|
|
810
|
+
query = query.SELECT;
|
|
748
811
|
}
|
|
749
|
-
else if (
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
812
|
+
else if (query.SET) {
|
|
813
|
+
queryPath.push('SET');
|
|
814
|
+
executeCallbacks();
|
|
815
|
+
query = query.SET;
|
|
753
816
|
}
|
|
754
817
|
|
|
755
|
-
if (
|
|
756
|
-
traverseFrom(
|
|
818
|
+
if (query.from)
|
|
819
|
+
traverseFrom( query.from, callback, queryPath.concat(['from']) );
|
|
757
820
|
|
|
758
821
|
for (const prop of ['args', 'xpr', 'columns', 'where', 'having']) {
|
|
759
822
|
// all properties which could have sub queries (directly or indirectly)
|
|
760
|
-
const expr =
|
|
823
|
+
const expr = query[prop];
|
|
761
824
|
if (expr && typeof expr === 'object') {
|
|
762
825
|
if(Array.isArray(expr)){
|
|
763
826
|
for(let i = 0; i < expr.length; i++){
|
|
764
|
-
traverseQuery(expr[i], callback,
|
|
827
|
+
traverseQuery(expr[i], callback, queryPath.concat([prop, i]));
|
|
765
828
|
}
|
|
766
829
|
} else {
|
|
767
830
|
for(const argName of Object.keys( expr )){
|
|
768
|
-
traverseQuery(expr[argName], callback,
|
|
831
|
+
traverseQuery(expr[argName], callback, queryPath.concat([prop, argName]))
|
|
769
832
|
}
|
|
770
833
|
}
|
|
771
834
|
}
|
|
772
835
|
}
|
|
773
|
-
function
|
|
836
|
+
function executeCallbacks() {
|
|
774
837
|
if(Array.isArray(callback))
|
|
775
|
-
callback.forEach(cb => cb(
|
|
838
|
+
callback.forEach(cb => cb( query, queryPath ));
|
|
776
839
|
else
|
|
777
|
-
callback(
|
|
840
|
+
callback( query, queryPath );
|
|
778
841
|
}
|
|
779
842
|
}
|
|
780
843
|
|
|
781
844
|
/**
|
|
782
845
|
* @param {CSN.QueryFrom} from
|
|
783
846
|
* @param {Function} callback
|
|
784
|
-
* @param {CSN.Path}
|
|
847
|
+
* @param {CSN.Path} csnPath
|
|
785
848
|
*/
|
|
786
|
-
function traverseFrom( from, callback,
|
|
849
|
+
function traverseFrom( from, callback, csnPath = [] ) {
|
|
787
850
|
if (from.ref) // ignore
|
|
788
851
|
return;
|
|
789
852
|
else if (from.args){ // join
|
|
790
853
|
for(let i = 0; i < from.args.length; i++){
|
|
791
|
-
traverseFrom(from.args[i], callback,
|
|
854
|
+
traverseFrom(from.args[i], callback, csnPath.concat(['args', i]));
|
|
792
855
|
}
|
|
793
856
|
}
|
|
794
857
|
else
|
|
795
|
-
traverseQuery( from, callback,
|
|
858
|
+
traverseQuery( from, callback, csnPath ); // sub query in FROM
|
|
796
859
|
}
|
|
797
860
|
}
|
|
798
861
|
|
|
@@ -837,12 +900,10 @@ function hasAnnotationValue(artifact, annotationName, expected = true, caseInsen
|
|
|
837
900
|
* @param {ODataOptions} options EDM specific options
|
|
838
901
|
*/
|
|
839
902
|
function isEdmPropertyRendered(elementCsn, options) {
|
|
840
|
-
if(options.toOdata)
|
|
841
|
-
options = options.toOdata;
|
|
842
903
|
// FKs are rendered in
|
|
843
904
|
// V2/V4 flat: always on
|
|
844
905
|
// V4 struct: on/off
|
|
845
|
-
const renderForeignKey = (options.
|
|
906
|
+
const renderForeignKey = (options.odataVersion === 'v4' && options.odataFormat === 'structured') ? !!options.odataForeignKeys : true;
|
|
846
907
|
const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
|
|
847
908
|
const isNavigable = elementCsn.target ?
|
|
848
909
|
(elementCsn['@odata.navigable'] === undefined ||
|
|
@@ -865,16 +926,19 @@ function isEdmPropertyRendered(elementCsn, options) {
|
|
|
865
926
|
* - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
|
|
866
927
|
* - For the 'quoted' naming mode, this means correctly replacing some '.' with '_'.
|
|
867
928
|
*
|
|
868
|
-
*
|
|
869
|
-
*
|
|
929
|
+
* The above rules might differ for different SQL dialects.
|
|
930
|
+
* Exceptions will be listed below.
|
|
870
931
|
*
|
|
871
932
|
* @param {string} artifactName The fully qualified name of the artifact
|
|
872
|
-
* @param {
|
|
873
|
-
* @param {CSN.Model
|
|
933
|
+
* @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
|
|
934
|
+
* @param {CSN.Model} csn
|
|
935
|
+
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
874
936
|
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
|
|
875
937
|
*/
|
|
876
|
-
|
|
877
|
-
|
|
938
|
+
// eslint-disable-next-line no-unused-vars
|
|
939
|
+
function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn, sqlDialect='plain') {
|
|
940
|
+
if(csn && typeof csn === 'object' && csn.definitions) {
|
|
941
|
+
isValidMappingDialectCombi(sqlDialect, sqlMapping)
|
|
878
942
|
if (sqlMapping === 'quoted' || sqlMapping === 'hdbcds') {
|
|
879
943
|
return getResultingName(csn, sqlMapping, artifactName);
|
|
880
944
|
}
|
|
@@ -883,24 +947,8 @@ function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
|
|
|
883
947
|
} else {
|
|
884
948
|
throw new Error('Unknown naming mode: ' + sqlMapping);
|
|
885
949
|
}
|
|
886
|
-
else {
|
|
887
|
-
|
|
888
|
-
if (sqlMapping === 'hdbcds') {
|
|
889
|
-
if (csn) {
|
|
890
|
-
const namespace = String(csn);
|
|
891
|
-
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
|
|
892
|
-
}
|
|
893
|
-
return artifactName;
|
|
894
|
-
}
|
|
895
|
-
else if (sqlMapping === 'plain') {
|
|
896
|
-
return artifactName.replace(/\./g, '_').toUpperCase();
|
|
897
|
-
}
|
|
898
|
-
else if (sqlMapping === 'quoted') {
|
|
899
|
-
return artifactName;
|
|
900
|
-
}
|
|
901
|
-
else {
|
|
902
|
-
throw new Error('Unknown naming mode: ' + sqlMapping);
|
|
903
|
-
}
|
|
950
|
+
} else {
|
|
951
|
+
throw new Error('A valid CSN model is required to correctly calculate the database name of an artifact.');
|
|
904
952
|
}
|
|
905
953
|
}
|
|
906
954
|
|
|
@@ -967,7 +1015,7 @@ function getUnderscoredName(startIndex, parts, csn) {
|
|
|
967
1015
|
|
|
968
1016
|
return result;
|
|
969
1017
|
} else if(art && art.kind === 'service') {
|
|
970
|
-
// inside services, we
|
|
1018
|
+
// inside services, we immediately turn . into _
|
|
971
1019
|
const prefix = parts.slice(0, i).join('.');
|
|
972
1020
|
const suffix = parts.slice(i).join('_');
|
|
973
1021
|
const result = [];
|
|
@@ -983,6 +1031,12 @@ function getUnderscoredName(startIndex, parts, csn) {
|
|
|
983
1031
|
return null;
|
|
984
1032
|
}
|
|
985
1033
|
|
|
1034
|
+
function isValidMappingDialectCombi(sqlDialect, sqlMapping) {
|
|
1035
|
+
if(sqlMapping === 'hdbcds' && sqlDialect !== 'hana')
|
|
1036
|
+
throw new Error(`sqlMapping "hdbcds" must only be used with sqlDialect "hana" - found: ${sqlDialect}`);
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
986
1040
|
|
|
987
1041
|
/**
|
|
988
1042
|
* Return the resulting database element name for 'elemName', depending on the current
|
|
@@ -992,11 +1046,17 @@ function getUnderscoredName(startIndex, parts, csn) {
|
|
|
992
1046
|
* - For the 'quoted' naming mode, it means converting all '.' to '_'.
|
|
993
1047
|
* No other naming modes are accepted!
|
|
994
1048
|
*
|
|
1049
|
+
* The above rules might differ for different SQL dialects.
|
|
1050
|
+
* Exceptions will be listed below.
|
|
1051
|
+
*
|
|
995
1052
|
* @param {string} elemName The name of the element
|
|
996
|
-
* @param {
|
|
1053
|
+
* @param {'plain'|'quoted'|'hdbcds'|string} sqlMapping The naming mode to use
|
|
1054
|
+
* @param {('sqlite'|'hana'|'plain'|string)} [sqlDialect='plain'] The SQL dialect to use
|
|
997
1055
|
* @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
|
|
998
1056
|
*/
|
|
999
|
-
|
|
1057
|
+
// eslint-disable-next-line no-unused-vars
|
|
1058
|
+
function getElementDatabaseNameOf(elemName, sqlMapping, sqlDialect='plain') {
|
|
1059
|
+
isValidMappingDialectCombi(sqlMapping, sqlDialect)
|
|
1000
1060
|
if (sqlMapping === 'hdbcds') {
|
|
1001
1061
|
return elemName;
|
|
1002
1062
|
}
|
|
@@ -1111,85 +1171,6 @@ function getNormalizedQuery(art) {
|
|
|
1111
1171
|
return art;
|
|
1112
1172
|
}
|
|
1113
1173
|
|
|
1114
|
-
/**
|
|
1115
|
-
* Merge multiple 'options' objects (from right to left, i.e. rightmost wins). Structured option values are
|
|
1116
|
-
* merged deeply. Structured option value from the right may override corresponding bool options on the left,
|
|
1117
|
-
* but no other combination of struct/scalar values is allowed. Array options are not merged, i.e. their
|
|
1118
|
-
* content is treated like scalars.
|
|
1119
|
-
* Returns a new options object.
|
|
1120
|
-
*
|
|
1121
|
-
* @param {...CSN.Options} optionsObjects
|
|
1122
|
-
* @return {CSN.Options}
|
|
1123
|
-
*/
|
|
1124
|
-
function mergeOptions(...optionsObjects) {
|
|
1125
|
-
let result = {};
|
|
1126
|
-
for (const options of optionsObjects) {
|
|
1127
|
-
if (options)
|
|
1128
|
-
result = mergeTwo(result, options, 'options');
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// Reverse the array to ensure that the rightmost option has priority
|
|
1132
|
-
const reversedOptions = [...optionsObjects].reverse(); // de-structure and create a new array, so reverse doesn't impact optionsObject
|
|
1133
|
-
const msgOptions = reversedOptions.find(opt => opt && Array.isArray(opt.messages));
|
|
1134
|
-
if (msgOptions) {
|
|
1135
|
-
result.messages = msgOptions.messages;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
return result;
|
|
1139
|
-
|
|
1140
|
-
// Recursively used for scalars, too
|
|
1141
|
-
function mergeTwo(left, right, name) {
|
|
1142
|
-
let result;
|
|
1143
|
-
// Copy left as far as required
|
|
1144
|
-
if (Array.isArray(left)) {
|
|
1145
|
-
// Shallow-copy left array
|
|
1146
|
-
result = left.slice();
|
|
1147
|
-
} else if (isObject(left)) {
|
|
1148
|
-
// Deep-copy left object (unless empty)
|
|
1149
|
-
result = Object.keys(left).length ? mergeTwo({}, left, name) : {};
|
|
1150
|
-
} else {
|
|
1151
|
-
// Just use left scalar
|
|
1152
|
-
result = left;
|
|
1153
|
-
}
|
|
1154
|
-
// Check against improper overwriting
|
|
1155
|
-
if (isObject(left) && !Array.isArray(left) && (Array.isArray(right) || isScalar(right))) {
|
|
1156
|
-
throw new ModelError(`Cannot overwrite structured option "${name}" with array or scalar value`);
|
|
1157
|
-
}
|
|
1158
|
-
if ((isScalar(left) && typeof left !== 'boolean' || Array.isArray(left)) && isObject(right) && !Array.isArray(right)) {
|
|
1159
|
-
throw new ModelError(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// Copy or overwrite properties from right to left
|
|
1163
|
-
if (Array.isArray(right)) {
|
|
1164
|
-
// Shallow-copy right array
|
|
1165
|
-
result = right.slice();
|
|
1166
|
-
} else if (isObject(right)) {
|
|
1167
|
-
// Object overwrites undefined, scalars and arrays
|
|
1168
|
-
if (result === undefined || isScalar(result) || Array.isArray(result)) {
|
|
1169
|
-
result = {};
|
|
1170
|
-
}
|
|
1171
|
-
// Deep-copy right object into result
|
|
1172
|
-
for (let key of Object.keys(right)) {
|
|
1173
|
-
result[key] = mergeTwo(result[key], right[key], `${name}.${key}`);
|
|
1174
|
-
}
|
|
1175
|
-
} else {
|
|
1176
|
-
// Right scalar wins (unless undefined)
|
|
1177
|
-
result = (right !== undefined) ? right : result;
|
|
1178
|
-
}
|
|
1179
|
-
return result;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Return true if 'o' is a non-null object or array
|
|
1183
|
-
function isObject(o) {
|
|
1184
|
-
return typeof o === 'object' && o !== null
|
|
1185
|
-
}
|
|
1186
|
-
|
|
1187
|
-
// Return true if 'o' is a non-undefined scalar
|
|
1188
|
-
function isScalar(o) {
|
|
1189
|
-
return o !== undefined && !isObject(o);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
1174
|
/**
|
|
1194
1175
|
* If the artifact with the name given is part of a context (or multiple), return the top-most context.
|
|
1195
1176
|
* Else, return the artifact itself. Namespaces are not of concern here.
|
|
@@ -1241,68 +1222,103 @@ function getLastPartOfRef(ref) {
|
|
|
1241
1222
|
return getLastPartOf(lastPathStep.id || lastPathStep);
|
|
1242
1223
|
}
|
|
1243
1224
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1225
|
+
/**
|
|
1226
|
+
* Copy all annotations from 'fromNode' to 'toNode'.
|
|
1227
|
+
*
|
|
1228
|
+
* Overwrite existing ones only if 'overwrite' is true.
|
|
1229
|
+
*
|
|
1230
|
+
* @param {object} fromNode
|
|
1231
|
+
* @param {object} toNode
|
|
1232
|
+
* @param {boolean} [overwrite]
|
|
1233
|
+
*/
|
|
1234
|
+
function copyAnnotations(fromNode, toNode, overwrite = false) {
|
|
1235
|
+
// Ignore if no toNode (in case of errors)
|
|
1236
|
+
if (!toNode)
|
|
1237
|
+
return;
|
|
1249
1238
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
function getParentNamesOf(name) {
|
|
1257
|
-
let remainder = name.slice(0, -getLastPartOf(name).length);
|
|
1258
|
-
if (remainder.endsWith('.')) {
|
|
1259
|
-
let parentName = remainder.slice(0, -1);
|
|
1260
|
-
return [parentName, ...getParentNamesOf(parentName)];
|
|
1261
|
-
} else {
|
|
1262
|
-
return [];
|
|
1239
|
+
const annotations = Object.keys(fromNode).filter(key => key.startsWith('@'));
|
|
1240
|
+
|
|
1241
|
+
for (const anno of annotations) {
|
|
1242
|
+
if (toNode[anno] === undefined || overwrite) {
|
|
1243
|
+
toNode[anno] = fromNode[anno];
|
|
1244
|
+
}
|
|
1263
1245
|
}
|
|
1264
1246
|
}
|
|
1265
1247
|
|
|
1266
1248
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1249
|
+
/**
|
|
1250
|
+
* Same as `copyAnnotations()` but also copies the
|
|
1251
|
+
* annotation-like property `doc`.
|
|
1252
|
+
*
|
|
1253
|
+
* Overwrite existing ones only if 'overwrite' is true.
|
|
1254
|
+
*
|
|
1255
|
+
* @param {object} fromNode
|
|
1256
|
+
* @param {object} toNode
|
|
1257
|
+
* @param {boolean} [overwrite]
|
|
1258
|
+
*/
|
|
1259
|
+
function copyAnnotationsAndDoc(fromNode, toNode, overwrite = false) {
|
|
1269
1260
|
// Ignore if no toNode (in case of errors)
|
|
1270
|
-
if (!toNode)
|
|
1261
|
+
if (!toNode)
|
|
1271
1262
|
return;
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1263
|
+
|
|
1264
|
+
const annotations = Object.keys(fromNode)
|
|
1265
|
+
.filter(key => key.startsWith('@') || key === 'doc');
|
|
1266
|
+
|
|
1267
|
+
for (const anno of annotations) {
|
|
1268
|
+
if (toNode[anno] === undefined || overwrite) {
|
|
1269
|
+
toNode[anno] = fromNode[anno];
|
|
1280
1270
|
}
|
|
1281
1271
|
}
|
|
1282
1272
|
}
|
|
1283
1273
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1274
|
+
/**
|
|
1275
|
+
* Applies annotations from `csn.extensions` to definitions and their elements.
|
|
1276
|
+
*
|
|
1277
|
+
* `config.filter` can be used to only copy annotations for those definitions,
|
|
1278
|
+
* for which the filter returns true.
|
|
1279
|
+
*
|
|
1280
|
+
* @todo Does _not_ apply param/action/... annotations.
|
|
1281
|
+
*
|
|
1282
|
+
* @param {CSN.Model} csn
|
|
1283
|
+
* @param {{overwrite?: boolean, filter?: (name: string) => boolean}} config
|
|
1284
|
+
*/
|
|
1285
|
+
function applyAnnotationsFromExtensions(csn, config) {
|
|
1286
|
+
if (!csn.extensions)
|
|
1292
1287
|
return;
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
if (name
|
|
1299
|
-
|
|
1288
|
+
|
|
1289
|
+
const filter = config.filter || ((_name) => true);
|
|
1290
|
+
for (const ext of csn.extensions) {
|
|
1291
|
+
const name = ext.annotate || ext.extend;
|
|
1292
|
+
const def = csn.definitions[name];
|
|
1293
|
+
if (name && def && filter(name)) {
|
|
1294
|
+
copyAnnotationsAndDoc(ext, def, config.overwrite);
|
|
1295
|
+
applyAnnotationsToElements(ext, def);
|
|
1300
1296
|
}
|
|
1301
|
-
|
|
1302
|
-
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function applyAnnotationsToElements(ext, def) {
|
|
1300
|
+
// Only the definition is arrayed but the extension is not since
|
|
1301
|
+
// `items` is not expected in `extensions` by the CSN frontend and not
|
|
1302
|
+
// generated by the CDL parser for `annotate E:arrayed.elem`.
|
|
1303
|
+
if (def.items)
|
|
1304
|
+
def = def.items;
|
|
1305
|
+
|
|
1306
|
+
if (!ext.elements || !def.elements)
|
|
1307
|
+
return;
|
|
1308
|
+
|
|
1309
|
+
forEach(ext.elements, (key, sourceElem) => {
|
|
1310
|
+
const targetElem = def.elements[key];
|
|
1311
|
+
if (targetElem) {
|
|
1312
|
+
copyAnnotationsAndDoc(sourceElem, targetElem, config.overwrite);
|
|
1313
|
+
applyAnnotationsToElements(sourceElem, targetElem);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1303
1316
|
}
|
|
1304
1317
|
}
|
|
1305
1318
|
|
|
1319
|
+
function isAspect(node) {
|
|
1320
|
+
return node && node.kind === 'aspect';
|
|
1321
|
+
}
|
|
1306
1322
|
|
|
1307
1323
|
/**
|
|
1308
1324
|
* Return true if the artifact has a valid, truthy persistence.exists/skip annotation
|
|
@@ -1379,16 +1395,6 @@ function getServiceNames(csn) {
|
|
|
1379
1395
|
return result;
|
|
1380
1396
|
}
|
|
1381
1397
|
|
|
1382
|
-
/**
|
|
1383
|
-
* Check whether the artifact is @cds.persistence.skip
|
|
1384
|
-
*
|
|
1385
|
-
* @param {CSN.Artifact} artifact
|
|
1386
|
-
* @returns {Boolean}
|
|
1387
|
-
*/
|
|
1388
|
-
function isSkipped(artifact) {
|
|
1389
|
-
return hasAnnotationValue(artifact, '@cds.persistence.skip', true)
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
1398
|
/**
|
|
1393
1399
|
* Walk path in the CSN and return the result.
|
|
1394
1400
|
*
|
|
@@ -1442,7 +1448,7 @@ function getVariableReplacement(ref, options) {
|
|
|
1442
1448
|
* @returns {boolean} returns equality
|
|
1443
1449
|
*
|
|
1444
1450
|
* noExtendedProps remove '$', '_' and '@' properties from
|
|
1445
|
-
* the
|
|
1451
|
+
* the comparison. This eliminates false negatives such as
|
|
1446
1452
|
* mismatching $locations or @odata.foreignKey4.
|
|
1447
1453
|
*/
|
|
1448
1454
|
function isDeepEqual(obj, other, noExtendedProps) {
|
|
@@ -1472,14 +1478,17 @@ function isDeepEqual(obj, other, noExtendedProps) {
|
|
|
1472
1478
|
|
|
1473
1479
|
module.exports = {
|
|
1474
1480
|
getUtils,
|
|
1475
|
-
cloneCsn,
|
|
1481
|
+
cloneCsn: cloneCsnNonDict, // Umbrella relies on this name
|
|
1482
|
+
cloneCsnNonDict,
|
|
1476
1483
|
cloneCsnDictionary,
|
|
1477
1484
|
isBuiltinType,
|
|
1478
|
-
|
|
1485
|
+
applyAnnotationsFromExtensions,
|
|
1479
1486
|
forEachGeneric,
|
|
1480
1487
|
forEachDefinition,
|
|
1481
1488
|
forEachMember,
|
|
1489
|
+
forEachMemberWithQuery,
|
|
1482
1490
|
forEachMemberRecursively,
|
|
1491
|
+
forEachMemberRecursivelyWithQuery,
|
|
1483
1492
|
forAllQueries,
|
|
1484
1493
|
hasAnnotationValue,
|
|
1485
1494
|
isEdmPropertyRendered,
|
|
@@ -1493,20 +1502,16 @@ module.exports = {
|
|
|
1493
1502
|
isPersistedOnDatabase,
|
|
1494
1503
|
generatedByCompilerVersion,
|
|
1495
1504
|
getNormalizedQuery,
|
|
1496
|
-
mergeOptions,
|
|
1497
1505
|
getRootArtifactName,
|
|
1498
1506
|
getLastPartOfRef,
|
|
1499
|
-
getParentNamesOf,
|
|
1500
|
-
getParentNameOf,
|
|
1501
1507
|
getLastPartOf,
|
|
1502
1508
|
copyAnnotations,
|
|
1509
|
+
copyAnnotationsAndDoc,
|
|
1503
1510
|
isAspect,
|
|
1504
|
-
forEachPath,
|
|
1505
1511
|
hasValidSkipOrExists,
|
|
1506
1512
|
getNamespace,
|
|
1507
1513
|
sortCsnDefinitionsForTests,
|
|
1508
1514
|
getServiceNames,
|
|
1509
|
-
isSkipped,
|
|
1510
1515
|
walkCsnPath,
|
|
1511
1516
|
getVariableReplacement,
|
|
1512
1517
|
implicitAs,
|