@sap/cds-compiler 2.4.4 → 2.10.2
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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
package/lib/model/csnUtils.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { setProp } = require('../base/model');
|
|
4
4
|
const { csnRefs } = require('../model/csnRefs');
|
|
5
|
-
const { sortCsn } = require('../json/to-csn');
|
|
5
|
+
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
|
|
6
6
|
const version = require('../../package.json').version;
|
|
7
7
|
|
|
8
8
|
// Low-level utility functions to work with compact CSN.
|
|
@@ -36,7 +36,7 @@ const version = require('../../package.json').version;
|
|
|
36
36
|
* @param {CSN.Model} model (Compact) CSN model
|
|
37
37
|
*/
|
|
38
38
|
function getUtils(model) {
|
|
39
|
-
const { artifactRef, inspectRef, effectiveType } = csnRefs(model);
|
|
39
|
+
const { artifactRef, inspectRef, effectiveType, getOrigin } = csnRefs(model);
|
|
40
40
|
|
|
41
41
|
return {
|
|
42
42
|
getCsnDef,
|
|
@@ -52,14 +52,143 @@ function getUtils(model) {
|
|
|
52
52
|
getContextOfArtifact,
|
|
53
53
|
addStringAnnotationTo,
|
|
54
54
|
getServiceName,
|
|
55
|
-
|
|
55
|
+
hasAnnotationValue,
|
|
56
56
|
cloneWithTransformations,
|
|
57
57
|
getFinalBaseType,
|
|
58
58
|
inspectRef,
|
|
59
59
|
artifactRef,
|
|
60
60
|
effectiveType,
|
|
61
|
+
get$combined,
|
|
62
|
+
getOrigin,
|
|
61
63
|
};
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Compute and return $combined for the given query.
|
|
67
|
+
*
|
|
68
|
+
* @param {CSN.Query} query
|
|
69
|
+
* @returns {object}
|
|
70
|
+
*/
|
|
71
|
+
function get$combined(query) {
|
|
72
|
+
const sources = getSources(query);
|
|
73
|
+
return sources;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get the union of all elements from the from clause
|
|
77
|
+
* - descend into unions, following the lead query
|
|
78
|
+
* - merge all queries in case of joins
|
|
79
|
+
* - follow subqueries
|
|
80
|
+
*
|
|
81
|
+
* @param {CSN.Query} query Query to check
|
|
82
|
+
* @returns {object} Map of sources
|
|
83
|
+
*/
|
|
84
|
+
function getSources(query, isSubquery=false) {
|
|
85
|
+
// Remark CW: better just a while along query.SET.args[0]
|
|
86
|
+
if (query.SET) {
|
|
87
|
+
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
|
|
88
|
+
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
|
|
89
|
+
|
|
90
|
+
return getSources(query.SET.args[0], isSubquery);
|
|
91
|
+
}
|
|
92
|
+
else if (query.SELECT) {
|
|
93
|
+
if (query.SELECT.from.args) {
|
|
94
|
+
return walkArgs(query.SELECT.from.args);
|
|
95
|
+
}
|
|
96
|
+
else if (query.SELECT.from.ref) {
|
|
97
|
+
let art = artifactRef(query.SELECT.from);
|
|
98
|
+
|
|
99
|
+
if(art.target)
|
|
100
|
+
art = artifactRef(art.target);
|
|
101
|
+
|
|
102
|
+
if(isSubquery && !query.SELECT.elements)
|
|
103
|
+
throw new Error('Expected subquery to have .elements');
|
|
104
|
+
|
|
105
|
+
return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
|
|
106
|
+
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
|
|
107
|
+
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
|
|
108
|
+
}
|
|
109
|
+
else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
|
|
110
|
+
return getSources(query.SELECT.from, true);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function walkArgs(args) {
|
|
115
|
+
let elements = Object.create(null);
|
|
116
|
+
for (const arg of args) {
|
|
117
|
+
if (arg.args) {
|
|
118
|
+
elements = mergeElementMaps(elements, walkArgs(arg.args));
|
|
119
|
+
}
|
|
120
|
+
else if (arg.ref) {
|
|
121
|
+
const art = artifactRef(arg);
|
|
122
|
+
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
|
|
123
|
+
}
|
|
124
|
+
else if (arg.SELECT || arg.SET) {
|
|
125
|
+
elements = mergeElementMaps(elements, getSources(arg));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return elements;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Merge two maps of elements together
|
|
136
|
+
*
|
|
137
|
+
* @param {object} mapA Map a - will be returned
|
|
138
|
+
* @param {object} mapB Map b - will not be returned
|
|
139
|
+
* @returns {object} mapA
|
|
140
|
+
*/
|
|
141
|
+
function mergeElementMaps(mapA, mapB) {
|
|
142
|
+
for (const elementName in mapB) {
|
|
143
|
+
if (!mapA[elementName])
|
|
144
|
+
mapA[elementName] = [];
|
|
145
|
+
|
|
146
|
+
mapB[elementName].forEach(e => mapA[elementName].push(e));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return mapA;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Merge elements into an existing map
|
|
154
|
+
*
|
|
155
|
+
* @param {any} existingMap map to merge into - will be returned
|
|
156
|
+
* @param {object} elements elements to merge into the map
|
|
157
|
+
* @param {CSN.Location} $location $location of the elements - where they come from
|
|
158
|
+
* @param {any} [parent] Name of the parent of the elements, alias before ref
|
|
159
|
+
* @param {any} [error_parent] Parent name to use for error messages, ref before alias
|
|
160
|
+
* @returns {object} existingMap
|
|
161
|
+
*/
|
|
162
|
+
function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
|
|
163
|
+
for (const elementName in elements) {
|
|
164
|
+
const element = elements[elementName];
|
|
165
|
+
if (!existingMap[elementName])
|
|
166
|
+
existingMap[elementName] = [];
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
existingMap[elementName].push({
|
|
170
|
+
element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return existingMap;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Return the name part of the artifact name - no namespace etc.
|
|
180
|
+
* @param {string|object} name Absolute name of the artifact
|
|
181
|
+
*/
|
|
182
|
+
function getBaseName(name) {
|
|
183
|
+
if (!name)
|
|
184
|
+
return name;
|
|
185
|
+
|
|
186
|
+
if (name.id)
|
|
187
|
+
return name.id.substring( name.id.lastIndexOf('.')+1 );
|
|
188
|
+
|
|
189
|
+
return name.substring( name.lastIndexOf('.')+1 )
|
|
190
|
+
}
|
|
191
|
+
}
|
|
63
192
|
|
|
64
193
|
|
|
65
194
|
/**
|
|
@@ -328,45 +457,54 @@ function getUtils(model) {
|
|
|
328
457
|
return resultNode;
|
|
329
458
|
}
|
|
330
459
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Resolve to the final type of a type, that means follow type chains, references to other types or
|
|
464
|
+
* elements a.s.o
|
|
465
|
+
* Works for all kinds of types, strings as well as type objects. Strings need to be absolute type names.
|
|
466
|
+
* Returns the final type as string (if it has a name, which is not always the case, think of embedded structures),
|
|
467
|
+
* else the type object itself is returned. If a type is structured, you can navigate into it by providing a path,
|
|
468
|
+
* e.g. given the following model
|
|
469
|
+
* type bar: S.foo;
|
|
470
|
+
* type s1 {
|
|
471
|
+
* s: s2;
|
|
472
|
+
* };
|
|
473
|
+
* type s2 {
|
|
474
|
+
* u: type of S.e:t;
|
|
475
|
+
* }
|
|
476
|
+
* service S {
|
|
477
|
+
* type foo: type of S.e:i.j1;
|
|
478
|
+
* entity e {
|
|
479
|
+
* key i: { j1: Integer };
|
|
480
|
+
* t: bar;
|
|
481
|
+
* v: s1;
|
|
482
|
+
* x: blutz.s.u;
|
|
483
|
+
* };
|
|
484
|
+
* type blutz: S.e.v;
|
|
485
|
+
* view V as select from e {
|
|
486
|
+
* 1+1 as i: bar,
|
|
487
|
+
* };
|
|
488
|
+
* type tt: type of V:i;
|
|
489
|
+
* }
|
|
490
|
+
* the following calls will all return 'cds.Integer'
|
|
491
|
+
* getFinalBaseType('S.tt')
|
|
492
|
+
* getFinalBaseType('S.e',['i','j1'])
|
|
493
|
+
* getFinalBaseType('S.e',['t'])
|
|
494
|
+
* getFinalBaseType('S.e',['x'])
|
|
495
|
+
* getFinalBaseType('S.blutz',['s', 'u'])
|
|
496
|
+
* Types are always resolved as far as possible. A type name which has no further definition is simply returned.
|
|
497
|
+
* Composed types (structures, entities, views, ...) are returned as type objects, if not drilled down into
|
|
498
|
+
* the elements. Path steps that have no corresponding element lead to 'undefined'. Refs to something that has
|
|
499
|
+
* no type (e.g. expr in a view without explicit type) returns 'null'
|
|
500
|
+
*
|
|
501
|
+
* @param {string|object} type Type - either string or ref
|
|
502
|
+
* @param {CSN.Path} path
|
|
503
|
+
* @param {WeakMap} [resolved=new WeakMap()] WeakMap containing already resolved refs - if a ref is not cached, it will be resolved JIT
|
|
504
|
+
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
|
|
505
|
+
* @returns
|
|
506
|
+
*/
|
|
507
|
+
function getFinalBaseType(type, path = [], resolved = new WeakMap(), cycleCheck = undefined) {
|
|
370
508
|
if (!type)
|
|
371
509
|
return type;
|
|
372
510
|
if (typeof(type) === 'string') {
|
|
@@ -384,24 +522,24 @@ function getUtils(model) {
|
|
|
384
522
|
}
|
|
385
523
|
let definedType = model.definitions[type];
|
|
386
524
|
if (definedType && definedType.type)
|
|
387
|
-
return getFinalBaseType(definedType.type, path, cycleCheck);
|
|
525
|
+
return getFinalBaseType(definedType.type, path, resolved, cycleCheck);
|
|
388
526
|
else
|
|
389
|
-
return getFinalBaseType(definedType, path, cycleCheck);
|
|
527
|
+
return getFinalBaseType(definedType, path, resolved, cycleCheck);
|
|
390
528
|
}
|
|
391
529
|
else if (typeof(type) === 'object') {
|
|
392
530
|
if (type.ref) {
|
|
393
531
|
// assert type.ref instanceof Array && type.ref.length >= 1
|
|
394
|
-
const ref = artifactRef(type);
|
|
395
|
-
return getFinalBaseType(ref, path, cycleCheck);
|
|
532
|
+
const ref = resolved.has(type) ? resolved.get(type).art : artifactRef(type);
|
|
533
|
+
return getFinalBaseType(ref, path, resolved, cycleCheck);
|
|
396
534
|
}
|
|
397
535
|
else if (type.elements) {
|
|
398
536
|
if (path.length) {
|
|
399
537
|
let [e, ...p] = path;
|
|
400
|
-
return getFinalBaseType(type.elements[e], p, cycleCheck);
|
|
538
|
+
return getFinalBaseType(type.elements[e], p, resolved, cycleCheck);
|
|
401
539
|
}
|
|
402
540
|
}
|
|
403
541
|
else if (type.type)
|
|
404
|
-
return (getFinalBaseType(type.type, path, cycleCheck));
|
|
542
|
+
return (getFinalBaseType(type.type, path, resolved, cycleCheck));
|
|
405
543
|
else if (type.items)
|
|
406
544
|
return type;
|
|
407
545
|
else
|
|
@@ -426,15 +564,30 @@ function isBuiltinType(type) {
|
|
|
426
564
|
|
|
427
565
|
/**
|
|
428
566
|
* Deeply clone the given CSN model and return it.
|
|
429
|
-
* In testMode, definitions are sorted.
|
|
567
|
+
* In testMode (or with testSortCsn), definitions are sorted.
|
|
568
|
+
* Note that annotations are only copied shallowly.
|
|
430
569
|
*
|
|
431
|
-
* @param {object} csn
|
|
570
|
+
* @param {object} csn Top-level CSN. You can pass non-dictionary values.
|
|
432
571
|
* @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`
|
|
433
572
|
*/
|
|
434
573
|
function cloneCsn(csn, options) {
|
|
435
574
|
return sortCsn(csn, options);
|
|
436
575
|
}
|
|
437
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Deeply clone the given CSN dictionary and return it.
|
|
579
|
+
* Note that annotations are only copied shallowly.
|
|
580
|
+
* This function does _not_ sort the given dictionary.
|
|
581
|
+
* See cloneCsn() if you want sorted definitions.
|
|
582
|
+
*
|
|
583
|
+
* @param {object} csn
|
|
584
|
+
* @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
|
|
585
|
+
* used and cloneOptions are passed to sort().
|
|
586
|
+
*/
|
|
587
|
+
function cloneCsnDictionary(csn, options) {
|
|
588
|
+
return _cloneCsnDictionary(csn, options);
|
|
589
|
+
}
|
|
590
|
+
|
|
438
591
|
/**
|
|
439
592
|
* Apply function `callback` to all artifacts in dictionary
|
|
440
593
|
* `model.definitions`. See function `forEachGeneric` for details.
|
|
@@ -478,12 +631,12 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
|
|
|
478
631
|
// Backends rely on the fact that `forEachElement` also goes through all
|
|
479
632
|
// `elements` of the return type (if structured).
|
|
480
633
|
// TODO: `returns` should be handled like a parameter just like XSN (maybe with different prop name)
|
|
481
|
-
if (construct.returns) {
|
|
634
|
+
if (construct.returns && !iterateOptions.elementsOnly) {
|
|
482
635
|
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
|
|
483
636
|
}
|
|
484
637
|
|
|
485
638
|
path = [...path]; // Copy
|
|
486
|
-
const propsWithMembers = ['elements', 'enum', 'foreignKeys', 'actions', 'params'];
|
|
639
|
+
const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'foreignKeys', 'actions', 'params']);
|
|
487
640
|
propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));
|
|
488
641
|
}
|
|
489
642
|
|
|
@@ -526,7 +679,9 @@ function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) {
|
|
|
526
679
|
if (!Object.prototype.hasOwnProperty.call(dict, name))
|
|
527
680
|
continue;
|
|
528
681
|
const dictObj = dict[name];
|
|
529
|
-
if(iterateOptions.skip && iterateOptions.skip.includes(dictObj.kind))
|
|
682
|
+
if((iterateOptions.skip && iterateOptions.skip.includes(dictObj.kind))
|
|
683
|
+
|| (iterateOptions.skipArtifact && typeof iterateOptions.skipArtifact === 'function'
|
|
684
|
+
&& iterateOptions.skipArtifact(dictObj, name)))
|
|
530
685
|
continue;
|
|
531
686
|
cb( dictObj, name );
|
|
532
687
|
}
|
|
@@ -682,15 +837,30 @@ function forAllElements(artifact, artifactName, cb){
|
|
|
682
837
|
}
|
|
683
838
|
|
|
684
839
|
/**
|
|
685
|
-
*
|
|
840
|
+
* Compare a given annotation value with an expectation value and return
|
|
841
|
+
*
|
|
842
|
+
* | Expected
|
|
843
|
+
* | true | false | null | arb val
|
|
844
|
+
* Anno Val |-------|-------|-------|--------
|
|
845
|
+
* true | true | false | false | false
|
|
846
|
+
* false | false | true | false | false
|
|
847
|
+
* null | false | true | true | false
|
|
848
|
+
* arb val | false | false | false | true/false
|
|
849
|
+
*
|
|
850
|
+
* If the annotation value is 'null', 'true' is returned for the expectation values
|
|
851
|
+
* 'null' and 'false'. Expecting 'null' for an annotation value 'false' returns
|
|
852
|
+
* 'false'.
|
|
686
853
|
*
|
|
687
854
|
* @param {CSN.Artifact} artifact
|
|
688
855
|
* @param {string} annotationName Name of the annotation (including the at-sign)
|
|
689
|
-
* @param {any}
|
|
856
|
+
* @param {any} expected
|
|
690
857
|
* @returns {boolean}
|
|
691
858
|
*/
|
|
692
|
-
function
|
|
693
|
-
|
|
859
|
+
function hasAnnotationValue(artifact, annotationName, expected = true) {
|
|
860
|
+
if(expected === false)
|
|
861
|
+
return artifact[annotationName] === expected || artifact[annotationName] === null;
|
|
862
|
+
else
|
|
863
|
+
return artifact[annotationName] === expected;
|
|
694
864
|
}
|
|
695
865
|
|
|
696
866
|
/**
|
|
@@ -890,11 +1060,12 @@ function getElementDatabaseNameOf(elemName, namingConvention) {
|
|
|
890
1060
|
*
|
|
891
1061
|
* @param {object} csn CSN to enrich in-place
|
|
892
1062
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
893
|
-
* @param {Function[]} artifactTransformers Transformations to run on the artifacts, like forEachDefinition
|
|
894
|
-
* @param {Boolean} skipIgnore Wether to skip _ignore elements or not
|
|
1063
|
+
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
|
|
1064
|
+
* @param {Boolean} [skipIgnore=true] Wether to skip _ignore elements or not
|
|
1065
|
+
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
|
|
895
1066
|
* @returns {object} CSN with transformations applied
|
|
896
1067
|
*/
|
|
897
|
-
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true ) {
|
|
1068
|
+
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true, options = {} ) {
|
|
898
1069
|
const transformers = {
|
|
899
1070
|
elements: dictionary,
|
|
900
1071
|
definitions: dictionary,
|
|
@@ -927,7 +1098,7 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
927
1098
|
for (let name of Object.getOwnPropertyNames( node )) {
|
|
928
1099
|
const trans = transformers[name] || standard;
|
|
929
1100
|
if(customTransformers[name])
|
|
930
|
-
customTransformers[name](node, name, node[name], csnPath);
|
|
1101
|
+
customTransformers[name](node, name, node[name], csnPath, parent, prop);
|
|
931
1102
|
|
|
932
1103
|
trans( node, name, node[name], csnPath );
|
|
933
1104
|
}
|
|
@@ -948,8 +1119,11 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
948
1119
|
function definitions( node, prop, dict ) {
|
|
949
1120
|
csnPath.push( prop );
|
|
950
1121
|
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
951
|
-
|
|
952
|
-
|
|
1122
|
+
const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
|
|
1123
|
+
if(!skip) {
|
|
1124
|
+
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
1125
|
+
standard( dict, name, dict[name] );
|
|
1126
|
+
}
|
|
953
1127
|
}
|
|
954
1128
|
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
|
|
955
1129
|
setProp(node, '$' + prop, dict);
|
|
@@ -962,10 +1136,14 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
962
1136
|
path.forEach( function step( s, i ) {
|
|
963
1137
|
if (s && typeof s === 'object') {
|
|
964
1138
|
csnPath.push( i );
|
|
965
|
-
if
|
|
966
|
-
standard(
|
|
967
|
-
|
|
968
|
-
|
|
1139
|
+
if(options.drillRef) {
|
|
1140
|
+
standard(path, i, s);
|
|
1141
|
+
} else {
|
|
1142
|
+
if (s.args)
|
|
1143
|
+
standard( s, 'args', s.args );
|
|
1144
|
+
if (s.where)
|
|
1145
|
+
standard( s, 'where', s.where );
|
|
1146
|
+
}
|
|
969
1147
|
csnPath.pop();
|
|
970
1148
|
}
|
|
971
1149
|
} );
|
|
@@ -1047,7 +1225,7 @@ function setDependencies( csn ) {
|
|
|
1047
1225
|
* @returns {boolean}
|
|
1048
1226
|
*/
|
|
1049
1227
|
function isPersistedOnDatabase(art) {
|
|
1050
|
-
return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract ||
|
|
1228
|
+
return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
|
|
1051
1229
|
}
|
|
1052
1230
|
|
|
1053
1231
|
/**
|
|
@@ -1160,6 +1338,7 @@ function mergeOptions(...optionsObjects) {
|
|
|
1160
1338
|
// Will return the artifact 'name' if it is a top-level artifact itself, and 'undefined'
|
|
1161
1339
|
// if there is no artifact surrounding 'name' in the model
|
|
1162
1340
|
// TODO: to be checked by author: still intended behaviour with 'cds' prefix?
|
|
1341
|
+
// TODO: Can this be replaced by getRootArtifactName? Or maybe not rely on namespace-hacking...
|
|
1163
1342
|
// FIXME: This only works with namespace-hacking, i.e. adding them as artifacts...
|
|
1164
1343
|
function getTopLevelArtifactNameOf(name, model) {
|
|
1165
1344
|
let dotIdx = name.indexOf('.');
|
|
@@ -1267,22 +1446,13 @@ function copyAnnotations(fromNode, toNode, overwrite=false) {
|
|
|
1267
1446
|
if (!Object.hasOwnProperty.call( fromNode, prop ))
|
|
1268
1447
|
continue;
|
|
1269
1448
|
if (prop.startsWith('@')) {
|
|
1270
|
-
if (toNode[prop]
|
|
1449
|
+
if (toNode[prop] === undefined || overwrite) {
|
|
1271
1450
|
toNode[prop] = fromNode[prop];
|
|
1272
1451
|
}
|
|
1273
1452
|
}
|
|
1274
1453
|
}
|
|
1275
1454
|
}
|
|
1276
1455
|
|
|
1277
|
-
// Return true if 'type' is a composition type
|
|
1278
|
-
function isComposition(type) {
|
|
1279
|
-
if (!type)
|
|
1280
|
-
return type;
|
|
1281
|
-
if (type._artifact)
|
|
1282
|
-
type = type._artifact.name;
|
|
1283
|
-
return type.absolute === 'cds.Composition';
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
1456
|
function isAspect(node) {
|
|
1287
1457
|
return node && node.kind === 'aspect';
|
|
1288
1458
|
}
|
|
@@ -1305,59 +1475,7 @@ function forEachPath(node, callback) {
|
|
|
1305
1475
|
}
|
|
1306
1476
|
}
|
|
1307
1477
|
|
|
1308
|
-
// Add an annotation with absolute name 'absoluteName' (including '@') and string value 'theValue' to 'node'
|
|
1309
|
-
function addBoolAnnotationTo(absoluteName, theValue, node) {
|
|
1310
|
-
// Sanity check
|
|
1311
|
-
if (!absoluteName.startsWith('@')) {
|
|
1312
|
-
throw Error('Annotation name should start with "@": ' + absoluteName);
|
|
1313
|
-
}
|
|
1314
|
-
// Assemble the annotation
|
|
1315
|
-
if(isAnnotationAssignable(node, absoluteName)) {
|
|
1316
|
-
node[absoluteName] = {
|
|
1317
|
-
name: {
|
|
1318
|
-
absolute: absoluteName.substring(1),
|
|
1319
|
-
location: node.location, // inherit location from main element
|
|
1320
|
-
},
|
|
1321
|
-
val: theValue,
|
|
1322
|
-
literal: 'boolean',
|
|
1323
|
-
location: node.location, // inherit location from main element
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
1478
|
|
|
1328
|
-
// Add an annotation with absolute name 'absoluteName' (including '@') and array 'theValue' to 'node'
|
|
1329
|
-
function addArrayAnnotationTo(absoluteName, theValue, node) {
|
|
1330
|
-
// Sanity check
|
|
1331
|
-
if (!absoluteName.startsWith('@')) {
|
|
1332
|
-
throw Error(`Annotation name should start with "@": ${ absoluteName}`);
|
|
1333
|
-
}
|
|
1334
|
-
// Assemble the annotation
|
|
1335
|
-
if(isAnnotationAssignable(node, absoluteName)) {
|
|
1336
|
-
node[absoluteName] = {
|
|
1337
|
-
name: {
|
|
1338
|
-
absolute: absoluteName.substring(1),
|
|
1339
|
-
location: node.location, // inherit location from main element
|
|
1340
|
-
},
|
|
1341
|
-
val: theValue,
|
|
1342
|
-
literal: 'array',
|
|
1343
|
-
location: node.location, // inherit location from main element
|
|
1344
|
-
};
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
/**
|
|
1349
|
-
* Check wether an annotation can be assigned or not
|
|
1350
|
-
* (must be either undefined or null value)
|
|
1351
|
-
* @param {object} node Assignee
|
|
1352
|
-
* @param {string} name Annotation name
|
|
1353
|
-
* @returns {boolean}
|
|
1354
|
-
*/
|
|
1355
|
-
function isAnnotationAssignable(node, name) {
|
|
1356
|
-
if (!name.startsWith('@')) {
|
|
1357
|
-
throw Error(`Annotation name should start with "@": ${ name}`);
|
|
1358
|
-
}
|
|
1359
|
-
return (node[name] === undefined || node[name] && node[name].val === null);
|
|
1360
|
-
}
|
|
1361
1479
|
/**
|
|
1362
1480
|
* Return true if the artifact has a valid, truthy persistence.exists/skip annotation
|
|
1363
1481
|
*
|
|
@@ -1366,7 +1484,7 @@ function isAnnotationAssignable(node, name) {
|
|
|
1366
1484
|
*/
|
|
1367
1485
|
function hasValidSkipOrExists(artifact) {
|
|
1368
1486
|
return (artifact.kind === 'entity' || artifact.kind === 'view') &&
|
|
1369
|
-
(
|
|
1487
|
+
(hasAnnotationValue(artifact, '@cds.persistence.exists', true) || hasAnnotationValue(artifact, '@cds.persistence.skip', true))
|
|
1370
1488
|
|
|
1371
1489
|
}
|
|
1372
1490
|
|
|
@@ -1420,7 +1538,7 @@ function sortCsnDefinitionsForTests(csn, options) {
|
|
|
1420
1538
|
/**
|
|
1421
1539
|
* Return an array of non-abstract service names contained in CSN
|
|
1422
1540
|
*
|
|
1423
|
-
* @param {CSN.Model} csn
|
|
1541
|
+
* @param {CSN.Model} csn
|
|
1424
1542
|
* @returns {CSN.Service[]}
|
|
1425
1543
|
*/
|
|
1426
1544
|
function getServiceNames(csn) {
|
|
@@ -1433,10 +1551,37 @@ function getServiceNames(csn) {
|
|
|
1433
1551
|
return result;
|
|
1434
1552
|
}
|
|
1435
1553
|
|
|
1554
|
+
/**
|
|
1555
|
+
* Check wether the artifact is @cds.persistence.skip
|
|
1556
|
+
*
|
|
1557
|
+
* @param {CSN.Artifact} artifact
|
|
1558
|
+
* @returns {Boolean}
|
|
1559
|
+
*/
|
|
1560
|
+
function isSkipped(artifact) {
|
|
1561
|
+
return hasAnnotationValue(artifact, '@cds.persistence.skip', true)
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Walk path in the CSN and return the result.
|
|
1566
|
+
*
|
|
1567
|
+
* @param {CSN.Model} csn
|
|
1568
|
+
* @param {CSN.Path} path
|
|
1569
|
+
* @returns {object} Whatever is at the end of path
|
|
1570
|
+
*/
|
|
1571
|
+
function walkCsnPath(csn, path) {
|
|
1572
|
+
/** @type {object} */
|
|
1573
|
+
let obj = csn;
|
|
1574
|
+
for(let i = 0; i < path.length; i++){
|
|
1575
|
+
obj = obj[path[i]];
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
return obj;
|
|
1579
|
+
}
|
|
1436
1580
|
|
|
1437
1581
|
module.exports = {
|
|
1438
1582
|
getUtils,
|
|
1439
1583
|
cloneCsn,
|
|
1584
|
+
cloneCsnDictionary,
|
|
1440
1585
|
isBuiltinType,
|
|
1441
1586
|
assignAll,
|
|
1442
1587
|
forEachGeneric,
|
|
@@ -1446,7 +1591,7 @@ module.exports = {
|
|
|
1446
1591
|
forEachRef,
|
|
1447
1592
|
forAllQueries,
|
|
1448
1593
|
forAllElements,
|
|
1449
|
-
|
|
1594
|
+
hasAnnotationValue,
|
|
1450
1595
|
isEdmPropertyRendered,
|
|
1451
1596
|
getArtifactDatabaseNameOf,
|
|
1452
1597
|
getResultingName,
|
|
@@ -1465,13 +1610,12 @@ module.exports = {
|
|
|
1465
1610
|
getParentNameOf,
|
|
1466
1611
|
getLastPartOf,
|
|
1467
1612
|
copyAnnotations,
|
|
1468
|
-
isComposition,
|
|
1469
1613
|
isAspect,
|
|
1470
1614
|
forEachPath,
|
|
1471
|
-
addBoolAnnotationTo,
|
|
1472
|
-
addArrayAnnotationTo,
|
|
1473
1615
|
hasValidSkipOrExists,
|
|
1474
1616
|
getNamespace,
|
|
1475
1617
|
sortCsnDefinitionsForTests,
|
|
1476
1618
|
getServiceNames,
|
|
1619
|
+
isSkipped,
|
|
1620
|
+
walkCsnPath,
|
|
1477
1621
|
};
|