@sap/cds-compiler 2.5.0 → 2.10.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 +191 -9
- package/bin/cdsc.js +2 -2
- package/doc/CHANGELOG_BETA.md +33 -3
- package/lib/api/main.js +29 -101
- package/lib/api/options.js +15 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +63 -9
- package/lib/base/messages.js +63 -21
- package/lib/base/model.js +2 -3
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- 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 +25 -7
- 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 +60 -7
- package/lib/compiler/assert-consistency.js +16 -7
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +99 -42
- package/lib/compiler/index.js +73 -27
- package/lib/compiler/resolver.js +288 -157
- package/lib/compiler/shared.js +31 -11
- package/lib/edm/annotations/genericTranslation.js +182 -186
- package/lib/edm/csn2edm.js +103 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -114
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +12 -1
- package/lib/gen/language.tokens +57 -53
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +770 -744
- package/lib/gen/languageLexer.tokens +49 -46
- package/lib/gen/languageParser.js +4713 -4279
- package/lib/json/from-csn.js +103 -45
- package/lib/json/to-csn.js +296 -117
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +21 -12
- package/lib/language/language.g4 +99 -31
- package/lib/main.d.ts +81 -3
- package/lib/main.js +30 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +329 -142
- package/lib/model/csnUtils.js +235 -58
- package/lib/model/enrichCsn.js +18 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +37 -20
- package/lib/optionProcessor.js +9 -3
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +112 -33
- package/lib/render/toHdbcds.js +134 -64
- package/lib/render/toSql.js +91 -38
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +29 -13
- package/lib/transform/db/draft.js +8 -6
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +284 -63
- package/lib/transform/forHanaNew.js +98 -381
- package/lib/transform/forOdataNew.js +21 -22
- package/lib/transform/localized.js +37 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +134 -78
- 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/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
package/lib/model/csnUtils.js
CHANGED
|
@@ -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,
|
|
@@ -58,8 +58,137 @@ function getUtils(model) {
|
|
|
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
|
|
@@ -493,12 +631,12 @@ function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterate
|
|
|
493
631
|
// Backends rely on the fact that `forEachElement` also goes through all
|
|
494
632
|
// `elements` of the return type (if structured).
|
|
495
633
|
// TODO: `returns` should be handled like a parameter just like XSN (maybe with different prop name)
|
|
496
|
-
if (construct.returns) {
|
|
634
|
+
if (construct.returns && !iterateOptions.elementsOnly) {
|
|
497
635
|
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
|
|
498
636
|
}
|
|
499
637
|
|
|
500
638
|
path = [...path]; // Copy
|
|
501
|
-
const propsWithMembers = ['elements', 'enum', 'foreignKeys', 'actions', 'params'];
|
|
639
|
+
const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'foreignKeys', 'actions', 'params']);
|
|
502
640
|
propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));
|
|
503
641
|
}
|
|
504
642
|
|
|
@@ -922,11 +1060,12 @@ function getElementDatabaseNameOf(elemName, namingConvention) {
|
|
|
922
1060
|
*
|
|
923
1061
|
* @param {object} csn CSN to enrich in-place
|
|
924
1062
|
* @param {object} customTransformers Map of prop to transform and function to apply
|
|
925
|
-
* @param {Function[]} artifactTransformers Transformations to run on the artifacts, like forEachDefinition
|
|
926
|
-
* @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
|
|
927
1066
|
* @returns {object} CSN with transformations applied
|
|
928
1067
|
*/
|
|
929
|
-
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true ) {
|
|
1068
|
+
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true, options = {} ) {
|
|
930
1069
|
const transformers = {
|
|
931
1070
|
elements: dictionary,
|
|
932
1071
|
definitions: dictionary,
|
|
@@ -959,7 +1098,7 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
959
1098
|
for (let name of Object.getOwnPropertyNames( node )) {
|
|
960
1099
|
const trans = transformers[name] || standard;
|
|
961
1100
|
if(customTransformers[name])
|
|
962
|
-
customTransformers[name](node, name, node[name], csnPath);
|
|
1101
|
+
customTransformers[name](node, name, node[name], csnPath, parent, prop);
|
|
963
1102
|
|
|
964
1103
|
trans( node, name, node[name], csnPath );
|
|
965
1104
|
}
|
|
@@ -968,6 +1107,9 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
968
1107
|
}
|
|
969
1108
|
|
|
970
1109
|
function dictionary( node, prop, dict ) {
|
|
1110
|
+
// Allow skipping dicts like actions in forHanaNew
|
|
1111
|
+
if(options.skipDict && options.skipDict[prop])
|
|
1112
|
+
return;
|
|
971
1113
|
csnPath.push( prop );
|
|
972
1114
|
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
973
1115
|
standard( dict, name, dict[name] );
|
|
@@ -980,8 +1122,11 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
980
1122
|
function definitions( node, prop, dict ) {
|
|
981
1123
|
csnPath.push( prop );
|
|
982
1124
|
for (let name of Object.getOwnPropertyNames( dict )) {
|
|
983
|
-
|
|
984
|
-
|
|
1125
|
+
const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
|
|
1126
|
+
if(!skip) {
|
|
1127
|
+
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
1128
|
+
standard( dict, name, dict[name] );
|
|
1129
|
+
}
|
|
985
1130
|
}
|
|
986
1131
|
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
|
|
987
1132
|
setProp(node, '$' + prop, dict);
|
|
@@ -994,10 +1139,14 @@ function applyTransformations( csn, customTransformers={}, artifactTransformers=
|
|
|
994
1139
|
path.forEach( function step( s, i ) {
|
|
995
1140
|
if (s && typeof s === 'object') {
|
|
996
1141
|
csnPath.push( i );
|
|
997
|
-
if
|
|
998
|
-
standard(
|
|
999
|
-
|
|
1000
|
-
|
|
1142
|
+
if(options.drillRef) {
|
|
1143
|
+
standard(path, i, s);
|
|
1144
|
+
} else {
|
|
1145
|
+
if (s.args)
|
|
1146
|
+
standard( s, 'args', s.args );
|
|
1147
|
+
if (s.where)
|
|
1148
|
+
standard( s, 'where', s.where );
|
|
1149
|
+
}
|
|
1001
1150
|
csnPath.pop();
|
|
1002
1151
|
}
|
|
1003
1152
|
} );
|
|
@@ -1405,6 +1554,32 @@ function getServiceNames(csn) {
|
|
|
1405
1554
|
return result;
|
|
1406
1555
|
}
|
|
1407
1556
|
|
|
1557
|
+
/**
|
|
1558
|
+
* Check wether the artifact is @cds.persistence.skip
|
|
1559
|
+
*
|
|
1560
|
+
* @param {CSN.Artifact} artifact
|
|
1561
|
+
* @returns {Boolean}
|
|
1562
|
+
*/
|
|
1563
|
+
function isSkipped(artifact) {
|
|
1564
|
+
return hasAnnotationValue(artifact, '@cds.persistence.skip', true)
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
/**
|
|
1568
|
+
* Walk path in the CSN and return the result.
|
|
1569
|
+
*
|
|
1570
|
+
* @param {CSN.Model} csn
|
|
1571
|
+
* @param {CSN.Path} path
|
|
1572
|
+
* @returns {object} Whatever is at the end of path
|
|
1573
|
+
*/
|
|
1574
|
+
function walkCsnPath(csn, path) {
|
|
1575
|
+
/** @type {object} */
|
|
1576
|
+
let obj = csn;
|
|
1577
|
+
for(let i = 0; i < path.length; i++){
|
|
1578
|
+
obj = obj[path[i]];
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
return obj;
|
|
1582
|
+
}
|
|
1408
1583
|
|
|
1409
1584
|
module.exports = {
|
|
1410
1585
|
getUtils,
|
|
@@ -1444,4 +1619,6 @@ module.exports = {
|
|
|
1444
1619
|
getNamespace,
|
|
1445
1620
|
sortCsnDefinitionsForTests,
|
|
1446
1621
|
getServiceNames,
|
|
1622
|
+
isSkipped,
|
|
1623
|
+
walkCsnPath,
|
|
1447
1624
|
};
|
package/lib/model/enrichCsn.js
CHANGED
|
@@ -44,11 +44,13 @@ function enrichCsn( csn, options = {} ) {
|
|
|
44
44
|
targetAspect: simpleRef,
|
|
45
45
|
target: simpleRef,
|
|
46
46
|
includes: simpleRef,
|
|
47
|
+
$origin,
|
|
47
48
|
// TODO: excluding
|
|
48
49
|
'@': () => { /* ignore annotations */ },
|
|
49
50
|
}
|
|
50
51
|
setLocations( csn, false, null );
|
|
51
|
-
const { inspectRef, artifactRef, __getCache_forEnrichCsnDebugging } =
|
|
52
|
+
const { inspectRef, artifactRef, getOrigin, __getCache_forEnrichCsnDebugging } =
|
|
53
|
+
csnRefs( csn );
|
|
52
54
|
let $$cacheObjectNumber = 0; // for debugging
|
|
53
55
|
const csnPath = [];
|
|
54
56
|
if (csn.definitions)
|
|
@@ -119,6 +121,21 @@ function enrichCsn( csn, options = {} ) {
|
|
|
119
121
|
// parent['_' + prop] = e.toString(); }
|
|
120
122
|
}
|
|
121
123
|
|
|
124
|
+
function $origin( parent, prop, ref ) {
|
|
125
|
+
if (options.testMode) {
|
|
126
|
+
if (Array.isArray( ref )) // $origin: […], not $origin: {…}
|
|
127
|
+
parent._origin = refLocation( getOrigin( parent ) );
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
try {
|
|
131
|
+
if (Array.isArray( ref )) // $origin: […], not $origin: {…}
|
|
132
|
+
parent._origin = refLocation( getOrigin( parent ) );
|
|
133
|
+
} catch (e) {
|
|
134
|
+
parent._origin = e.toString();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
122
139
|
function pathRef( parent, prop, path ) {
|
|
123
140
|
const { links, art, scope, $env } = (() => {
|
|
124
141
|
if (options.testMode)
|
|
@@ -76,6 +76,7 @@ function revealInternalProperties( model, name ) {
|
|
|
76
76
|
_annotate: reveal,
|
|
77
77
|
_deps: dependencyInfo,
|
|
78
78
|
_status: primOrString, // is a string anyway
|
|
79
|
+
$messageFunctions: () => '‹some functions›',
|
|
79
80
|
}
|
|
80
81
|
unique_id = 1;
|
|
81
82
|
return revealXsnPath(name, model);
|
|
@@ -159,7 +160,7 @@ function revealInternalProperties( model, name ) {
|
|
|
159
160
|
|
|
160
161
|
function columns( nodes, query ) {
|
|
161
162
|
// If we will have specified elements, we need another test to see columns in --parse-cdl
|
|
162
|
-
return nodes && nodes.map( c => (c._parent && c._parent.elements
|
|
163
|
+
return nodes && nodes.map( c => (c._parent && c._parent.elements)
|
|
163
164
|
? artifactIdentifier( c, query )
|
|
164
165
|
: reveal( c, nodes ) );
|
|
165
166
|
}
|
|
@@ -22,17 +22,17 @@ function compareModels(beforeModel, afterModel, options) {
|
|
|
22
22
|
|
|
23
23
|
const deletedEntities = Object.create(null);
|
|
24
24
|
const elementAdditions = [];
|
|
25
|
-
const
|
|
25
|
+
const migrations = []; // element changes/removals or changes of entity properties
|
|
26
26
|
|
|
27
27
|
// There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
|
|
28
|
-
forEachDefinition(afterModel, getArtifactComparator(beforeModel, null, null, elementAdditions,
|
|
29
|
-
forEachDefinition(beforeModel, getArtifactComparator(afterModel, null, deletedEntities, null, null));
|
|
28
|
+
forEachDefinition(afterModel, getArtifactComparator(beforeModel, null, null, elementAdditions, migrations));
|
|
29
|
+
forEachDefinition(beforeModel, getArtifactComparator(afterModel, null, deletedEntities, null, null));
|
|
30
30
|
|
|
31
31
|
const returnObj = Object.create(null);
|
|
32
32
|
returnObj.definitions = afterModel.definitions;
|
|
33
33
|
returnObj.deletions = deletedEntities;
|
|
34
34
|
returnObj.extensions = elementAdditions;
|
|
35
|
-
returnObj.migrations =
|
|
35
|
+
returnObj.migrations = migrations;
|
|
36
36
|
return returnObj;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -59,35 +59,45 @@ function validateCsnVersions(beforeModel, afterModel, options) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function getArtifactComparator(otherModel, addedEntities, deletedEntities, elementAdditions,
|
|
63
|
-
return function compareArtifacts(artifact, name) {
|
|
62
|
+
function getArtifactComparator(otherModel, addedEntities, deletedEntities, elementAdditions, migrations) {
|
|
63
|
+
return function compareArtifacts(artifact, name) {
|
|
64
64
|
function addElements() {
|
|
65
65
|
const elements = {};
|
|
66
66
|
forEachMember(artifact, getElementComparator(otherArtifact, elements));
|
|
67
67
|
if (Object.keys(elements).length > 0) {
|
|
68
|
-
elementAdditions.push(
|
|
69
|
-
extend: name,
|
|
70
|
-
elements: elements
|
|
71
|
-
});
|
|
68
|
+
elementAdditions.push(addedElements(name, elements));
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
|
-
function
|
|
71
|
+
function changePropsOrRemoveOrChangeElements() {
|
|
72
|
+
const relevantProperties = ['doc'];
|
|
73
|
+
const changedProperties = {};
|
|
74
|
+
|
|
75
75
|
const removedElements = {};
|
|
76
76
|
const changedElements = {};
|
|
77
|
-
|
|
77
|
+
|
|
78
|
+
const migration = { migrate: name };
|
|
79
|
+
|
|
80
|
+
relevantProperties.forEach(prop => {
|
|
81
|
+
if (artifact[prop] !== otherArtifact[prop]) {
|
|
82
|
+
changedProperties[prop] = changedElement(artifact[prop], otherArtifact[prop] || null);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (Object.keys(changedProperties).length > 0) {
|
|
86
|
+
migration.properties = changedProperties;
|
|
87
|
+
}
|
|
78
88
|
|
|
79
89
|
forEachMember(otherArtifact, getElementComparator(artifact, removedElements));
|
|
80
90
|
if (Object.keys(removedElements).length > 0) {
|
|
81
|
-
|
|
91
|
+
migration.remove = removedElements;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
94
|
forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements));
|
|
85
95
|
if (Object.keys(changedElements).length > 0) {
|
|
86
|
-
|
|
96
|
+
migration.change = changedElements;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
99
|
+
if (migration.properties || migration.remove || migration.change) {
|
|
100
|
+
migrations.push(migration);
|
|
91
101
|
}
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -124,8 +134,8 @@ function getArtifactComparator(otherModel, addedEntities, deletedEntities, eleme
|
|
|
124
134
|
if (elementAdditions) {
|
|
125
135
|
addElements();
|
|
126
136
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
137
|
+
if (migrations) {
|
|
138
|
+
changePropsOrRemoveOrChangeElements();
|
|
129
139
|
}
|
|
130
140
|
};
|
|
131
141
|
}
|
|
@@ -139,8 +149,8 @@ function isPersistedAsTable(artifact) {
|
|
|
139
149
|
&& !hasAnnotationValue(artifact, '@cds.persistence.exists');
|
|
140
150
|
}
|
|
141
151
|
|
|
142
|
-
function getElementComparator(otherArtifact, addedElements = null, changedElements = null) {
|
|
143
|
-
return function compareElements(element, name) {
|
|
152
|
+
function getElementComparator(otherArtifact, addedElements = null, changedElements = null) {
|
|
153
|
+
return function compareElements(element, name) {
|
|
144
154
|
if (element._ignore) {
|
|
145
155
|
return;
|
|
146
156
|
}
|
|
@@ -209,6 +219,13 @@ function typeParametersChanged(element, otherElement) {
|
|
|
209
219
|
return !deepEqual(element, otherElement, (key, depth) => !(depth === 0 && key === 'type'));
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
function addedElements(entity, elements) {
|
|
223
|
+
return {
|
|
224
|
+
extend: entity,
|
|
225
|
+
elements
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
212
229
|
function changedElement(element, otherElement) {
|
|
213
230
|
return {
|
|
214
231
|
old: otherElement,
|
package/lib/optionProcessor.js
CHANGED
|
@@ -37,8 +37,10 @@ optionProcessor
|
|
|
37
37
|
.option(' --test-mode')
|
|
38
38
|
.option(' --test-sort-csn')
|
|
39
39
|
.option(' --doc-comment')
|
|
40
|
+
.option(' --add-texts-language-assoc')
|
|
40
41
|
.option(' --localized-without-coalesce')
|
|
41
42
|
.option(' --defaultStringLength <length>')
|
|
43
|
+
.option(' --no-recompile')
|
|
42
44
|
.positionalArgument('<files...>')
|
|
43
45
|
.help(`
|
|
44
46
|
Usage: cdsc <command> [options] <files...>
|
|
@@ -88,12 +90,12 @@ optionProcessor
|
|
|
88
90
|
--beta-mode Enable all unsupported, incomplete (beta) features
|
|
89
91
|
--beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
|
|
90
92
|
Valid values are:
|
|
91
|
-
keylessManagedAssoc
|
|
92
93
|
foreignKeyConstraints
|
|
93
94
|
addTextsLanguageAssoc
|
|
94
95
|
hanaAssocRealCardinality
|
|
95
96
|
mapAssocToJoinCardinality
|
|
96
97
|
ignoreAssocPublishingInUnion
|
|
98
|
+
windowFunctions
|
|
97
99
|
--constraints-not-enforced If this option is supplied, referential constraints are NOT ENFORCED
|
|
98
100
|
This option is also applied to result of "cdsc manageConstraints"
|
|
99
101
|
--constraints-not-validated If this option is supplied, referential constraints are NOT VALIDATED
|
|
@@ -124,8 +126,11 @@ optionProcessor
|
|
|
124
126
|
OData CSN, CDL order and more. When --test-mode is enabled, this
|
|
125
127
|
option is implicitly enabled as well.
|
|
126
128
|
--doc-comment Preserve /** */ comments at annotation positions as doc property in CSN
|
|
129
|
+
--add-texts-language-assoc In generated texts entities, add association "language"
|
|
130
|
+
to "sap.common.Languages" if it exists
|
|
127
131
|
--localized-without-coalesce Omit coalesce in localized convenience views
|
|
128
|
-
|
|
132
|
+
--no-recompile Don't recompile in case of internal errors
|
|
133
|
+
|
|
129
134
|
Commands
|
|
130
135
|
H, toHana [options] <files...> Generate HANA CDS source files
|
|
131
136
|
O, toOdata [options] <files...> Generate ODATA metadata and annotations
|
|
@@ -335,7 +340,7 @@ optionProcessor.command('manageConstraints')
|
|
|
335
340
|
|
|
336
341
|
optionProcessor.command('toCsn')
|
|
337
342
|
.option('-h, --help')
|
|
338
|
-
.option('-f, --flavor <flavor>', ['client', 'gensrc'])
|
|
343
|
+
.option('-f, --flavor <flavor>', ['client', 'gensrc', 'universal'])
|
|
339
344
|
.option(' --with-localized')
|
|
340
345
|
.help(`
|
|
341
346
|
Usage: cdsc toCsn [options] <files...>
|
|
@@ -350,6 +355,7 @@ optionProcessor.command('toCsn')
|
|
|
350
355
|
combination with additional "extend" or "annotate"
|
|
351
356
|
statements, but not suitable for consumption by clients or
|
|
352
357
|
backends
|
|
358
|
+
universal: in development (BETA)
|
|
353
359
|
|
|
354
360
|
Internal options (for testing only, may be changed/removed at any time)
|
|
355
361
|
--with-localized Add localized convenience views to the CSN output.
|
|
@@ -7,8 +7,11 @@
|
|
|
7
7
|
"prefer-template": "error",
|
|
8
8
|
"no-trailing-spaces": "error",
|
|
9
9
|
"sonarjs/cognitive-complexity": "off",
|
|
10
|
+
"sonarjs/no-duplicate-string": ["off"],
|
|
11
|
+
"sonarjs/no-nested-template-literals": "off",
|
|
10
12
|
"template-curly-spacing":["error", "never"],
|
|
11
|
-
|
|
13
|
+
// Who cares - just very whiny and in the way
|
|
14
|
+
"complexity": "off",
|
|
12
15
|
"max-len": "off",
|
|
13
16
|
// We should enable this
|
|
14
17
|
"no-shadow": "off"
|