@sap/cds-compiler 4.0.2 → 4.2.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 +200 -5
- package/bin/cdsc.js +18 -15
- package/doc/CHANGELOG_BETA.md +16 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +33 -13
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +25 -25
- package/lib/base/location.js +6 -7
- package/lib/base/message-registry.js +123 -42
- package/lib/base/messages.js +18 -10
- package/lib/base/model.js +43 -10
- package/lib/checks/defaultValues.js +6 -6
- package/lib/checks/elements.js +11 -10
- package/lib/checks/foreignKeys.js +0 -5
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +22 -14
- package/lib/checks/queryNoDbArtifacts.js +132 -73
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/sql-snippets.js +15 -4
- package/lib/checks/types.js +3 -3
- package/lib/checks/utils.js +4 -3
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +71 -40
- package/lib/compiler/base.js +7 -2
- package/lib/compiler/builtins.js +40 -41
- package/lib/compiler/checks.js +415 -367
- package/lib/compiler/classes.js +62 -0
- package/lib/compiler/cycle-detector.js +9 -9
- package/lib/compiler/define.js +124 -90
- package/lib/compiler/extend.js +115 -88
- package/lib/compiler/finalize-parse-cdl.js +26 -25
- package/lib/compiler/generate.js +57 -49
- package/lib/compiler/index.js +56 -56
- package/lib/compiler/kick-start.js +10 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +180 -144
- package/lib/compiler/propagator.js +10 -9
- package/lib/compiler/resolve.js +321 -246
- package/lib/compiler/shared.js +812 -433
- package/lib/compiler/tweak-assocs.js +114 -50
- package/lib/compiler/utils.js +241 -46
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +679 -770
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +689 -648
- package/lib/edm/edmUtils.js +279 -300
- package/lib/gen/Dictionary.json +34 -10
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +2857 -2856
- package/lib/json/from-csn.js +77 -51
- package/lib/json/to-csn.js +15 -15
- package/lib/language/antlrParser.js +2 -1
- package/lib/language/genericAntlrParser.js +52 -43
- package/lib/language/language.g4 +61 -64
- package/lib/language/multiLineStringParser.js +2 -0
- package/lib/main.d.ts +65 -0
- package/lib/model/csnRefs.js +37 -19
- package/lib/model/csnUtils.js +51 -18
- package/lib/model/revealInternalProperties.js +30 -22
- package/lib/modelCompare/compare.js +149 -41
- package/lib/modelCompare/utils/filter.js +55 -25
- package/lib/optionProcessor.js +21 -9
- package/lib/render/manageConstraints.js +20 -17
- package/lib/render/toCdl.js +63 -23
- package/lib/render/toHdbcds.js +2 -2
- package/lib/render/toRename.js +4 -9
- package/lib/render/toSql.js +82 -35
- package/lib/render/utils/common.js +11 -9
- package/lib/render/utils/unique.js +52 -0
- package/lib/transform/db/applyTransformations.js +62 -21
- package/lib/transform/db/assertUnique.js +7 -8
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +9 -9
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +138 -68
- package/lib/transform/db/flattening.js +98 -30
- package/lib/transform/db/rewriteCalculatedElements.js +20 -14
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +8 -7
- package/lib/transform/db/views.js +73 -33
- package/lib/transform/draft/db.js +11 -9
- package/lib/transform/draft/odata.js +1 -1
- package/lib/transform/{forOdataNew.js → forOdata.js} +10 -7
- package/lib/transform/forRelationalDB.js +148 -136
- package/lib/transform/localized.js +92 -54
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/{transformUtilsNew.js → transformUtils.js} +13 -111
- package/lib/transform/translateAssocsToJoins.js +14 -28
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
- package/share/messages/message-explanations.json +1 -1
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
forAllQueries,
|
|
14
14
|
sortCsnDefinitionsForTests,
|
|
15
15
|
} = require('../model/csnUtils');
|
|
16
|
+
const {CompilerAssertion} = require('../base/error');
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Indicator that a definition is localized and has a convenience view.
|
|
@@ -34,10 +35,11 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
|
|
|
34
35
|
* Used to transitively create convenience views.
|
|
35
36
|
*/
|
|
36
37
|
const _targetFor = Symbol('_targetFor');
|
|
38
|
+
const annoPersistenceSkip = '@cds.persistence.skip';
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
41
|
* Callback function returning `true` if the localization view should be created.
|
|
40
|
-
* @callback
|
|
42
|
+
* @callback AcceptLocalizedViewCallback
|
|
41
43
|
* @param {string} viewName localization view name
|
|
42
44
|
* @param {string} originalName Artifact name of the original view
|
|
43
45
|
*/
|
|
@@ -45,20 +47,22 @@ const _targetFor = Symbol('_targetFor');
|
|
|
45
47
|
/**
|
|
46
48
|
* Create transitive localized convenience views.
|
|
47
49
|
*
|
|
48
|
-
* A convenience view is created if the entity/view has a localized element
|
|
50
|
+
* A convenience view is created if the entity/view has a localized element[^1]
|
|
49
51
|
* or if it exposes an association leading to a localized-tagged target.
|
|
50
52
|
*
|
|
51
53
|
* INTERNALS:
|
|
52
54
|
* We have three kinds of localized convenience views:
|
|
53
55
|
*
|
|
54
56
|
* 1. "direct ones" using coalesce() for the table entities with localized
|
|
55
|
-
* elements: as projection on the original (created in extend.js)
|
|
56
|
-
* 2. for
|
|
57
|
-
* convenience
|
|
58
|
-
* 3. for
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* convenience view variant if present
|
|
57
|
+
* elements[^1]: as projection on the original and '.texts' entity (created in extend.js)
|
|
58
|
+
* 2. for _table_ entities with associations to entities which have a localized
|
|
59
|
+
* convenience: as projection on the original
|
|
60
|
+
* 3. for _view_ entities with either localized elements[^1] or associations
|
|
61
|
+
* to entities which have a localized convenience view:
|
|
62
|
+
* as view using a copy of the original query, but replacing all sources by
|
|
63
|
+
* their localized convenience view variant if present
|
|
64
|
+
*
|
|
65
|
+
* [^1]: That is, the element has `localized: true`.
|
|
62
66
|
*
|
|
63
67
|
* First, all "direct ones" are built (1). Then we build all 2 and 3
|
|
64
68
|
* transitively (i.e. as long as an entity has an association which directly or
|
|
@@ -68,21 +72,46 @@ const _targetFor = Symbol('_targetFor');
|
|
|
68
72
|
* variant if present.
|
|
69
73
|
*
|
|
70
74
|
* @param {CSN.Model} csn
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
75
|
+
* Input CSN model. Should not have existing convenience views.
|
|
76
|
+
*
|
|
77
|
+
* @param {object} options
|
|
78
|
+
* CSN options. Only few options are used, see below for important ones.
|
|
79
|
+
* Options such as `testMode` or `testSortCsn` can also be set.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} [options.localizedLanguageFallback]
|
|
82
|
+
* Valid values (if set): 'none', 'coalesce' (default)
|
|
83
|
+
* Whether to use a `coalesce()` function when selecting from `.texts` entities.
|
|
84
|
+
* If not set, untranslated strings may not return any value. If 'coalesce'
|
|
85
|
+
* is used, it will fall back to the original string.
|
|
86
|
+
*
|
|
87
|
+
* @param {boolean} [options.localizedWithoutCoalesce]
|
|
88
|
+
* Deprecated version of localizedLanguageFallback. Do not use.
|
|
89
|
+
*
|
|
90
|
+
* @param {boolean} [options.fewerLocalizedViews]
|
|
91
|
+
*
|
|
74
92
|
* @param {object} config
|
|
93
|
+
* Configuration for creating convenience views. Non-user visible options.
|
|
94
|
+
*
|
|
95
|
+
* @param {boolean} [config.useJoins]
|
|
96
|
+
* If true, rewrite the "localized" association to a join in direct convenience views.
|
|
97
|
+
*
|
|
98
|
+
* @param {AcceptLocalizedViewCallback} [config.acceptLocalizedView]
|
|
99
|
+
* A callback that can be used to suppress the creation of localized convenience views
|
|
100
|
+
* if desired. For example, if you want to know which definitions get a convenience view
|
|
101
|
+
* but don't actually want to create them.
|
|
102
|
+
*
|
|
103
|
+
* @param {boolean} [config.ignoreUnknownExtensions]
|
|
104
|
+
* If true, do not emit a warning for annotations on unknown `localized.*` views.
|
|
75
105
|
*/
|
|
76
|
-
function _addLocalizationViews(csn, options,
|
|
106
|
+
function _addLocalizationViews(csn, options, config) {
|
|
77
107
|
const messageFunctions = makeMessageFunction(csn, options);
|
|
78
|
-
if (checkExistingLocalizationViews(csn, options, messageFunctions))
|
|
79
|
-
messageFunctions.throwWithError();
|
|
108
|
+
if (checkExistingLocalizationViews(csn, options, messageFunctions))
|
|
80
109
|
return csn;
|
|
81
|
-
}
|
|
82
110
|
|
|
83
|
-
const { acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
111
|
+
const { useJoins, acceptLocalizedView, ignoreUnknownExtensions } = config;
|
|
84
112
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
85
113
|
options.localizedWithoutCoalesce);
|
|
114
|
+
const ignoreAssocToLocalized = !!options.fewerLocalizedViews;
|
|
86
115
|
|
|
87
116
|
createDirectConvenienceViews(); // 1
|
|
88
117
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
@@ -179,10 +208,10 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
179
208
|
copyLocation(convenienceView.query, entity);
|
|
180
209
|
|
|
181
210
|
if (shouldUseJoin)
|
|
182
|
-
// Expand elements
|
|
211
|
+
// Expand elements; (variant 1)
|
|
183
212
|
columns.push( ...columnsForEntityWithExcludeList( entity, 'L_0', textElements ) )
|
|
184
213
|
else
|
|
185
|
-
columns.push( '*' );
|
|
214
|
+
columns.push( '*' ); // (variant 2)
|
|
186
215
|
|
|
187
216
|
for (const originalElement of textElements) {
|
|
188
217
|
const elem = entity.elements[originalElement];
|
|
@@ -330,8 +359,8 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
330
359
|
let keyCount = 0;
|
|
331
360
|
let textElements = [];
|
|
332
361
|
|
|
333
|
-
forEachGeneric(art, 'elements', (elem, elemName
|
|
334
|
-
if (elem
|
|
362
|
+
forEachGeneric(art, 'elements', (elem, elemName , _prop) => {
|
|
363
|
+
if (elem.$ignore) // from SAP HANA backend
|
|
335
364
|
return;
|
|
336
365
|
|
|
337
366
|
if (elem.key || elem.$key)
|
|
@@ -339,10 +368,6 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
339
368
|
|
|
340
369
|
if (elem.key || elem.$key || elem.localized)
|
|
341
370
|
textElements.push( elemName );
|
|
342
|
-
|
|
343
|
-
// TODO: Already warned about in extend.js
|
|
344
|
-
// if (elem.key && isLocalized)
|
|
345
|
-
// warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );
|
|
346
371
|
}, artPath);
|
|
347
372
|
|
|
348
373
|
if (textElements.length <= keyCount || keyCount <= 0)
|
|
@@ -363,12 +388,17 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
363
388
|
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
|
|
364
389
|
return null;
|
|
365
390
|
}
|
|
366
|
-
|
|
367
391
|
if (!isValidTextsEntity( textsEntity )) {
|
|
368
392
|
messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
|
|
369
393
|
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
|
|
370
394
|
return null;
|
|
371
395
|
}
|
|
396
|
+
if (!art[annoPersistenceSkip] && textsEntity[annoPersistenceSkip]) {
|
|
397
|
+
messageFunctions.message( 'anno-unexpected-localized-skip', artPath,
|
|
398
|
+
{ name: textsName, art: artName, anno: annoPersistenceSkip },
|
|
399
|
+
'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped' );
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
372
402
|
|
|
373
403
|
// There may be keys in the original artifact that were added by the core compiler,
|
|
374
404
|
// for example elements that are marked @cds.valid.from.
|
|
@@ -390,22 +420,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
390
420
|
|
|
391
421
|
/**
|
|
392
422
|
* Transitively create convenience views for entities/views that have
|
|
393
|
-
* associations to localized entities
|
|
394
|
-
* a dependency.
|
|
423
|
+
* associations to localized entities, views that themselves have such
|
|
424
|
+
* a dependency or views that contain projections on localized elements.
|
|
395
425
|
*
|
|
396
426
|
* The algorithm is as follows:
|
|
397
427
|
*
|
|
398
|
-
* 1. For each view
|
|
399
|
-
*
|
|
400
|
-
*
|
|
428
|
+
* 1. For each view with elements that have `localized: true` markers:
|
|
429
|
+
* => add view to array `entities`
|
|
430
|
+
* For each view/entity with associations:
|
|
431
|
+
* - If target is NOT localized => add view/entity to target's `_targetFor` property
|
|
432
|
+
* - If target is localized => add view/entity to array `entities`
|
|
401
433
|
* 2. As long as `entities` has entries:
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
* c. Clear `nextEntities`.
|
|
434
|
+
* a. For each entry in `entities`
|
|
435
|
+
* - Create a convenience view
|
|
436
|
+
* - If the entry has a `_targetFor` property, add its entries to `nextEntities`
|
|
437
|
+
* because they now have a transitive dependency on a localized view.
|
|
438
|
+
* b. Copy all entries from `nextEntities` to `entities`.
|
|
439
|
+
* c. Clear `nextEntities`.
|
|
409
440
|
* 3. Rewrite all references to the localized variants.
|
|
410
441
|
*/
|
|
411
442
|
function createTransitiveConvenienceViews() {
|
|
@@ -447,10 +478,9 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
447
478
|
// if the artifact is an entity (already processed in (1))
|
|
448
479
|
entities.push(artName);
|
|
449
480
|
}
|
|
450
|
-
else if (elem.target) {
|
|
481
|
+
else if (!ignoreAssocToLocalized && elem.target) {
|
|
451
482
|
// If the target has a localized view then we are localized as well.
|
|
452
483
|
const def = csn.definitions[elem.target];
|
|
453
|
-
// TODO: What if elem.target cannot be found? Could this happen after flattening, ...?
|
|
454
484
|
if (!def)
|
|
455
485
|
continue;
|
|
456
486
|
|
|
@@ -489,7 +519,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
489
519
|
|
|
490
520
|
addLocalizedView(artName);
|
|
491
521
|
|
|
492
|
-
if (art[_targetFor])
|
|
522
|
+
if (!ignoreAssocToLocalized && art[_targetFor])
|
|
493
523
|
nextEntities.push(...art[_targetFor]);
|
|
494
524
|
delete art[_targetFor];
|
|
495
525
|
}
|
|
@@ -577,14 +607,22 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
577
607
|
if (!obj || !obj.ref)
|
|
578
608
|
return;
|
|
579
609
|
const ref = Array.isArray(obj.ref) ? obj.ref[0] : obj.ref;
|
|
580
|
-
if (typeof ref
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
else
|
|
610
|
+
if (typeof ref === 'string') {
|
|
611
|
+
const def = csn.definitions[ref];
|
|
612
|
+
if (def && def[_hasLocalizedView]) {
|
|
613
|
+
if (Array.isArray(obj.ref))
|
|
614
|
+
obj.ref[0] = def[_hasLocalizedView];
|
|
615
|
+
else
|
|
587
616
|
obj.ref = def[_hasLocalizedView];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
} else if (ref.id) {
|
|
620
|
+
const def = csn.definitions[ref.id];
|
|
621
|
+
if (def && def[_hasLocalizedView])
|
|
622
|
+
obj.ref[0].id = def[_hasLocalizedView];
|
|
623
|
+
|
|
624
|
+
} else if (options.testMode) {
|
|
625
|
+
throw new CompilerAssertion('Debug me: Unhandled reference during localized-rewrite!');
|
|
588
626
|
}
|
|
589
627
|
}
|
|
590
628
|
|
|
@@ -592,7 +630,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
592
630
|
* @param {string} artName
|
|
593
631
|
*/
|
|
594
632
|
function textsEntityName(artName) {
|
|
595
|
-
// We can assume
|
|
633
|
+
// We can assume that the element exists. This is checked in isEntityPreprocessed().
|
|
596
634
|
return csn.definitions[artName].elements.texts.target;
|
|
597
635
|
}
|
|
598
636
|
|
|
@@ -625,23 +663,23 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
625
663
|
*
|
|
626
664
|
* @param {CSN.Model} csn
|
|
627
665
|
* @param {CSN.Options} options
|
|
628
|
-
* @param [config]
|
|
666
|
+
* @param [config]
|
|
629
667
|
*/
|
|
630
668
|
function addLocalizationViews(csn, options, config = {}) {
|
|
631
|
-
return _addLocalizationViews(csn, options,
|
|
669
|
+
return _addLocalizationViews(csn, options, { ...config, useJoins: false });
|
|
632
670
|
}
|
|
633
671
|
|
|
634
672
|
/**
|
|
635
673
|
* Create transitive localized convenience views to the given CSN but
|
|
636
674
|
* rewrite the "localized" association to joins in direct entity convenience
|
|
637
|
-
* views. This is needed
|
|
675
|
+
* views. This is needed e.g. by SQL for SQLite where A2J is used.
|
|
638
676
|
*
|
|
639
677
|
* @param {CSN.Model} csn
|
|
640
678
|
* @param {CSN.Options} options
|
|
641
|
-
* @param [config]
|
|
679
|
+
* @param [config]
|
|
642
680
|
*/
|
|
643
681
|
function addLocalizationViewsWithJoins(csn, options, config = {}) {
|
|
644
|
-
return _addLocalizationViews(csn, options,
|
|
682
|
+
return _addLocalizationViews(csn, options, { ...config, useJoins: true });
|
|
645
683
|
}
|
|
646
684
|
|
|
647
685
|
/**
|
|
@@ -94,7 +94,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
94
94
|
function expandToFinalBaseType(node, defName) {
|
|
95
95
|
if (!node) return;
|
|
96
96
|
// TODO: Clarify how should events be handled?
|
|
97
|
-
// They are not treated by the
|
|
97
|
+
// They are not treated by the transformUtils::toFinalBaseType function
|
|
98
98
|
// in the same manner as named types, because the elements of structured events are not
|
|
99
99
|
// propagated as it is with types.
|
|
100
100
|
// It is ok to skip the expansion to the final base type for now as events are not rendered in
|
|
@@ -130,7 +130,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
130
130
|
*/
|
|
131
131
|
if (isBuiltinType(finalBaseType.type)) {
|
|
132
132
|
/*
|
|
133
|
-
use
|
|
133
|
+
use transformUtils::toFinalBaseType for the moment,
|
|
134
134
|
as it is collects along the chain of types
|
|
135
135
|
attributes that need to be propagated
|
|
136
136
|
enum, length, scale, etc.
|
|
@@ -161,7 +161,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
161
161
|
*/
|
|
162
162
|
if (isBuiltinType(finalBaseType.type)) {
|
|
163
163
|
/*
|
|
164
|
-
use
|
|
164
|
+
use transformUtils::toFinalBaseType for the moment,
|
|
165
165
|
as it is collects along the chain of types
|
|
166
166
|
attributes that need to be propagated
|
|
167
167
|
enum, length, scale, etc.
|
|
@@ -265,7 +265,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
265
265
|
} else {
|
|
266
266
|
// Primitive child - clone it and restore its cross references
|
|
267
267
|
let flatElemName = elemName + pathDelimiter + childName;
|
|
268
|
-
let flatElem = cloneCsnNonDict(childElem, options);
|
|
268
|
+
let flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
|
|
269
269
|
// Don't take over notNull from leaf elements
|
|
270
270
|
delete flatElem.notNull;
|
|
271
271
|
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
|
|
@@ -420,17 +420,10 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
420
420
|
if (typeof type === 'string' && isBuiltinType(type))
|
|
421
421
|
return;
|
|
422
422
|
|
|
423
|
-
|
|
424
|
-
if (resolved.has(type)) {
|
|
425
|
-
typeRef = resolved.get(type)?.art
|
|
426
|
-
// The cached entry may not be resolved, yet.
|
|
427
|
-
if (typeRef.type && !isBuiltinType(typeRef.type))
|
|
428
|
-
typeRef = getFinalTypeInfo(typeRef.type);
|
|
429
|
-
} else {
|
|
430
|
-
typeRef = getFinalTypeInfo(type);
|
|
431
|
-
}
|
|
423
|
+
const typeRef = getFinalTypeInfo(type, (t) => resolved.get(t)?.art || csnUtils.artifactRef(t));
|
|
432
424
|
if(!typeRef)
|
|
433
425
|
return;
|
|
426
|
+
|
|
434
427
|
if (typeRef.elements || typeRef.items) {
|
|
435
428
|
// Copy elements/items and we're finished. No need to look up actual base type,
|
|
436
429
|
// since it must also be structured and must contain at least as many elements,
|
|
@@ -543,7 +536,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
543
536
|
addElement(draftUuid, artifact, artifactName);
|
|
544
537
|
|
|
545
538
|
// CreationDateTime : Timestamp;
|
|
546
|
-
const creationDateTime = createScalarElement('CreationDateTime',
|
|
539
|
+
const creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp');
|
|
547
540
|
creationDateTime.CreationDateTime['@Common.Label'] = '{i18n>Draft_CreationDateTime}';
|
|
548
541
|
addElement(creationDateTime, artifact, artifactName);
|
|
549
542
|
|
|
@@ -560,7 +553,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
560
553
|
addElement(draftIsCreatedByMe, artifact, artifactName);
|
|
561
554
|
|
|
562
555
|
// LastChangeDateTime : Timestamp;
|
|
563
|
-
const lastChangeDateTime = createScalarElement('LastChangeDateTime',
|
|
556
|
+
const lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp');
|
|
564
557
|
lastChangeDateTime.LastChangeDateTime['@Common.Label'] = '{i18n>Draft_LastChangeDateTime}';
|
|
565
558
|
addElement(lastChangeDateTime, artifact, artifactName);
|
|
566
559
|
|
|
@@ -909,7 +902,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
909
902
|
if (annotation === undefined) {
|
|
910
903
|
throw new CompilerAssertion('Annotation ' + fromName + ' not found in ' + JSON.stringify(node));
|
|
911
904
|
}
|
|
912
|
-
if(node[toName]
|
|
905
|
+
if(node[toName] == null) {
|
|
913
906
|
delete node[fromName];
|
|
914
907
|
node[toName] = annotation;
|
|
915
908
|
}
|
|
@@ -931,9 +924,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
931
924
|
if (value === undefined) {
|
|
932
925
|
throw new CompilerAssertion('Annotation value must not be undefined');
|
|
933
926
|
}
|
|
934
|
-
|
|
935
|
-
if(node[name] === undefined || node[name] === null)
|
|
936
|
-
node[name] = value;
|
|
927
|
+
node[name] ??= value;
|
|
937
928
|
}
|
|
938
929
|
|
|
939
930
|
/**
|
|
@@ -1184,13 +1175,12 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1184
1175
|
// do the paths match?
|
|
1185
1176
|
if(op !== 'like' && !(x.lhs && x.rhs)) {
|
|
1186
1177
|
if(xn.length) {
|
|
1187
|
-
error(
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
'$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
|
|
1178
|
+
error('expr-invalid-expansion', location, {
|
|
1179
|
+
value: prefix(lhs, op, rhs),
|
|
1180
|
+
name: xn,
|
|
1181
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1182
|
+
},
|
|
1183
|
+
'Missing sub path $(NAME) in $(ALIAS) for tuple expansion of $(VALUE); both sides must expand to the same sub paths');
|
|
1194
1184
|
}
|
|
1195
1185
|
else {
|
|
1196
1186
|
error(null, location,
|
|
@@ -1352,93 +1342,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1352
1342
|
|
|
1353
1343
|
}
|
|
1354
1344
|
|
|
1355
|
-
/**
|
|
1356
|
-
* Modify the given CSN/artifact in-place, applying the given customTransformations.
|
|
1357
|
-
* Dictionaries are correctly handled - a "type" transformer will not be called on an entity called "type".
|
|
1358
|
-
*
|
|
1359
|
-
* A custom transformation function has the following signature:
|
|
1360
|
-
* (any, object, string, CSN.Path) => undefined
|
|
1361
|
-
*
|
|
1362
|
-
* Given that we have a custom transformation for "type" and stumble upon a thing like below:
|
|
1363
|
-
*
|
|
1364
|
-
* {
|
|
1365
|
-
* type: "cds.String",
|
|
1366
|
-
* anotherProp: 1
|
|
1367
|
-
* }
|
|
1368
|
-
*
|
|
1369
|
-
* The input for the function would be:
|
|
1370
|
-
*
|
|
1371
|
-
* ("cds.String", { type: <>, anotherProp: <>}, "type", [xy, "type"])
|
|
1372
|
-
*
|
|
1373
|
-
* @param {CSN.Model} csn
|
|
1374
|
-
* @param {object} customTransformations Dictionary of functions to apply - if the property matches a key in this dict, it will be called
|
|
1375
|
-
* @param {boolean} [transformNonEnumerableElements=false] Transform non-enumerable elements to work with cds linked...
|
|
1376
|
-
* @returns {CSN.Model|CSN.Artifact} Return the CSN/artifact
|
|
1377
|
-
*/
|
|
1378
|
-
function transformModel(csn, customTransformations, transformNonEnumerableElements=false){
|
|
1379
|
-
const transformers = {
|
|
1380
|
-
elements: dictionary,
|
|
1381
|
-
definitions: dictionary,
|
|
1382
|
-
actions: dictionary,
|
|
1383
|
-
params: dictionary,
|
|
1384
|
-
enum: dictionary,
|
|
1385
|
-
mixin: dictionary,
|
|
1386
|
-
args: dictionary
|
|
1387
|
-
};
|
|
1388
|
-
|
|
1389
|
-
const csnPath = [];
|
|
1390
|
-
if (csn.definitions)
|
|
1391
|
-
dictionary( csn, 'definitions', csn.definitions );
|
|
1392
|
-
else {
|
|
1393
|
-
// fake it till you make it
|
|
1394
|
-
const obj = { definitions: Object.create(null)};
|
|
1395
|
-
obj.definitions.thing = csn;
|
|
1396
|
-
dictionary(obj, 'definitions', obj.definitions);
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
return csn;
|
|
1400
|
-
|
|
1401
|
-
function standard( parent, prop, node ) {
|
|
1402
|
-
// checking for .kind and .type is safe because annotations with such properties, are already flattened out
|
|
1403
|
-
const isAnnotation = () => (typeof prop === 'string' && prop.startsWith('@') && !node.kind && !node.type);
|
|
1404
|
-
if (!node || node._ignore || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || isAnnotation())
|
|
1405
|
-
return;
|
|
1406
|
-
|
|
1407
|
-
csnPath.push( prop );
|
|
1408
|
-
|
|
1409
|
-
if (Array.isArray(node)) {
|
|
1410
|
-
node.forEach( (n, i) => standard( node, i, n ) );
|
|
1411
|
-
}
|
|
1412
|
-
else {
|
|
1413
|
-
const iterateOver = Object.getOwnPropertyNames( node );
|
|
1414
|
-
// cds-linked resolves types and add's them to elements as non-enum - need to be processed
|
|
1415
|
-
if(transformNonEnumerableElements && node.elements && !Object.prototype.propertyIsEnumerable.call(node, 'elements')){
|
|
1416
|
-
iterateOver.push('elements');
|
|
1417
|
-
}
|
|
1418
|
-
for (const name of iterateOver) {
|
|
1419
|
-
if(customTransformations[name])
|
|
1420
|
-
customTransformations[name](node[name], node, name, csnPath.concat(name))
|
|
1421
|
-
|
|
1422
|
-
const trans = transformers[name] || standard;
|
|
1423
|
-
trans( node, name, node[name] );
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
csnPath.pop();
|
|
1427
|
-
}
|
|
1428
|
-
function dictionary( node, prop, dict ) {
|
|
1429
|
-
csnPath.push( prop );
|
|
1430
|
-
|
|
1431
|
-
if (Array.isArray(dict)) {
|
|
1432
|
-
dict.forEach( (n, i) => standard(dict, i, n))
|
|
1433
|
-
} else {
|
|
1434
|
-
for (const name of Object.getOwnPropertyNames( dict ))
|
|
1435
|
-
standard( dict, name, dict[name] );
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
csnPath.pop();
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
|
|
1442
1345
|
/**
|
|
1443
1346
|
* Mandatory input transformation for all backends:
|
|
1444
1347
|
* Replace
|
|
@@ -1465,7 +1368,6 @@ function rewriteBuiltinTypeRef(csn) {
|
|
|
1465
1368
|
module.exports = {
|
|
1466
1369
|
// This function retrieves the actual exports
|
|
1467
1370
|
getTransformers,
|
|
1468
|
-
transformModel,
|
|
1469
1371
|
RelationalOperators,
|
|
1470
1372
|
rewriteBuiltinTypeRef,
|
|
1471
1373
|
};
|
|
@@ -1124,9 +1124,14 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1124
1124
|
// get paths of managed assocs (unmanaged assocs are not allowed in FK paths)
|
|
1125
1125
|
if(element.foreignKeys)
|
|
1126
1126
|
{
|
|
1127
|
-
for(
|
|
1127
|
+
for(const fkn in element.foreignKeys)
|
|
1128
1128
|
{
|
|
1129
|
-
|
|
1129
|
+
const fk = element.foreignKeys[fkn];
|
|
1130
|
+
// ignore an unmanaged association
|
|
1131
|
+
if(fk.targetElement._artifact.target &&
|
|
1132
|
+
fk.targetElement._artifact.on &&
|
|
1133
|
+
!fk.targetElement._artifact.foreignKeys)
|
|
1134
|
+
continue;
|
|
1130
1135
|
// once a fk is to be followed, treat all sub-paths as srcSide, this will add fk.name.id only
|
|
1131
1136
|
if(srcSide)
|
|
1132
1137
|
paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
|
|
@@ -1146,9 +1151,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1146
1151
|
// get paths of plain structured elements
|
|
1147
1152
|
else if(element.elements)
|
|
1148
1153
|
{
|
|
1149
|
-
for(
|
|
1154
|
+
for(const n in element.elements)
|
|
1150
1155
|
{
|
|
1151
|
-
|
|
1156
|
+
const elt = element.elements[n];
|
|
1152
1157
|
paths = paths.concat(flattenElement(elt, true, elt.name.id, elt.name.id));
|
|
1153
1158
|
}
|
|
1154
1159
|
}
|
|
@@ -1384,7 +1389,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1384
1389
|
|
|
1385
1390
|
All prefix trees are put underneath the $tableAlias structure with attribute $qat or $fqat.
|
|
1386
1391
|
Each path step appears exactly once for a given filter condition in the prefix tree and
|
|
1387
|
-
has a link to
|
|
1392
|
+
has a link to its definition (origin). The default filter is an empty string ''.
|
|
1388
1393
|
|
|
1389
1394
|
A special note on paths in filter conditions. Filter paths are treated like postfix
|
|
1390
1395
|
paths to an association path step, meaning, they are inserted into the assoc's $qat or $fqat
|
|
@@ -1409,32 +1414,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1409
1414
|
|
|
1410
1415
|
let [head, ...tail] = path;
|
|
1411
1416
|
|
|
1412
|
-
if(['$projection', '$self'].includes(head.id) && tail.length && head._navigation.kind === '$self') {
|
|
1413
|
-
// make sure not to truncate tail
|
|
1414
|
-
if(tail.length > 1)
|
|
1415
|
-
[head, ...tail] = tail;
|
|
1416
|
-
else
|
|
1417
|
-
head = tail[0];
|
|
1418
|
-
/*
|
|
1419
|
-
if the head is a path (it better be;) then use it as
|
|
1420
|
-
anchor for _navigation and just merge the tail into that QAT
|
|
1421
|
-
example:
|
|
1422
|
-
entity E { key id: Integer; toE: association to E; toF: association to F;}
|
|
1423
|
-
entity F { key id: Integer; toE: association to E; }
|
|
1424
|
-
view V as select from E { toE, $projection.toE.toF.id };
|
|
1425
|
-
*/
|
|
1426
|
-
let value = env.lead.elements[head.id].value;
|
|
1427
|
-
if(value.path) {
|
|
1428
|
-
head = value.path[0];
|
|
1429
|
-
}
|
|
1430
|
-
else // value is another expression, don't consider it
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
1417
|
// qatParent is the node where the starting qat is attached to
|
|
1436
1418
|
let qatParent = undefined;
|
|
1437
1419
|
|
|
1420
|
+
// Note: If head is $self, we would need to resolve it if the path follows associations.
|
|
1421
|
+
// However, that is already rejected by SQL backend checks. For example $self paths
|
|
1422
|
+
// can't be "$self.assoc.foo.bar".
|
|
1423
|
+
|
|
1438
1424
|
// FROM and filter paths do not have a _navigation, but for filter paths
|
|
1439
1425
|
// the corresponding path step (to where the filter was attached to) is in env.pathStep
|
|
1440
1426
|
if(!head._navigation)
|
package/lib/utils/file.js
CHANGED
|
@@ -101,22 +101,22 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
101
101
|
if (body && body.stack && body.message) {
|
|
102
102
|
// NOTE: checks for instanceof Error are not reliable if error
|
|
103
103
|
// created in different execution env
|
|
104
|
-
traceFS( 'READFILE:cache-
|
|
104
|
+
traceFS( 'READFILE:cache-err:', filename, body.message );
|
|
105
105
|
cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
|
|
106
106
|
}
|
|
107
107
|
else {
|
|
108
|
-
traceFS( 'READFILE:cache:', filename, body );
|
|
108
|
+
traceFS( 'READFILE:cache: ', filename, body );
|
|
109
109
|
cb( null, body );
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
else {
|
|
113
|
-
traceFS( 'READFILE:start:', filename );
|
|
113
|
+
traceFS( 'READFILE:start: ', filename );
|
|
114
114
|
// TODO: set cache directly to some "delay" - store error differently?
|
|
115
115
|
// e.g. an error of callback functions!
|
|
116
116
|
try {
|
|
117
117
|
reader(filename, enc, (err, data) => {
|
|
118
118
|
fileCache[filename] = err || data;
|
|
119
|
-
traceFS('READFILE:data:', filename, err || data);
|
|
119
|
+
traceFS('READFILE:data: ', filename, err || data);
|
|
120
120
|
cb(err, data);
|
|
121
121
|
});
|
|
122
122
|
}
|
|
@@ -137,14 +137,14 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
137
137
|
return ( filename, cb ) => {
|
|
138
138
|
let body = fileCache[filename];
|
|
139
139
|
if (body !== undefined) {
|
|
140
|
-
traceFS( 'ISFILE:cache:', filename, body );
|
|
140
|
+
traceFS( 'ISFILE:cache: ', filename, body );
|
|
141
141
|
if (body instanceof Error)
|
|
142
142
|
cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve
|
|
143
143
|
else // body could be empty string
|
|
144
144
|
cb( null, !!body || typeof body === 'string');
|
|
145
145
|
}
|
|
146
146
|
else {
|
|
147
|
-
traceFS( 'ISFILE:start:', filename, body );
|
|
147
|
+
traceFS( 'ISFILE:start: ', filename, body );
|
|
148
148
|
// in the future (if we do module resolve ourselves with just readFile),
|
|
149
149
|
// we avoid parallel readFile by storing having an array of `cb`s in
|
|
150
150
|
// fileCache[ filename ] before starting fs.readFile().
|
|
@@ -156,7 +156,7 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
156
156
|
body = !!(stat.isFile() || stat.isFIFO());
|
|
157
157
|
if (fileCache[filename] === undefined) // parallel readFile() has been processed
|
|
158
158
|
fileCache[filename] = body;
|
|
159
|
-
traceFS('ISFILE:data:', filename, body);
|
|
159
|
+
traceFS('ISFILE:data: ', filename, body);
|
|
160
160
|
if (body instanceof Error)
|
|
161
161
|
cb(err);
|
|
162
162
|
else
|