@sap/cds-compiler 4.8.0 → 4.9.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 +38 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +30 -17
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +38 -21
- package/lib/base/messages.js +51 -20
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +10 -3
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +38 -30
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +0 -2
- package/lib/compiler/propagator.js +23 -19
- package/lib/compiler/resolve.js +48 -29
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +72 -116
- package/lib/compiler/xpr-rewrite.js +762 -0
- package/lib/edm/annotations/edmJson.js +24 -7
- package/lib/edm/annotations/genericTranslation.js +81 -61
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +12 -12
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +23 -11
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +75 -28
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +3 -3
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +221 -124
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +47 -42
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
package/lib/optionProcessor.js
CHANGED
|
@@ -155,7 +155,8 @@ optionProcessor
|
|
|
155
155
|
manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
|
|
156
156
|
add / modify referential constraints.
|
|
157
157
|
inspect [options] <files...> (internal) Inspect the given CDS files.
|
|
158
|
-
toEffectiveCsn [options] <files...> (internal) Get an effective CSN
|
|
158
|
+
toEffectiveCsn [options] <files...> (internal) Get an effective CSN; requires beta mode
|
|
159
|
+
forSeal [options] <files...> (internal) Get a SEAL CSN
|
|
159
160
|
|
|
160
161
|
Environment variables
|
|
161
162
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
@@ -546,6 +547,8 @@ optionProcessor.command('toEffectiveCsn')
|
|
|
546
547
|
.option('-h, --help')
|
|
547
548
|
.option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )
|
|
548
549
|
.option('--resolve-projections <val>', { valid: ['true', 'false'] } )
|
|
550
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
551
|
+
.option('--keep-localized <val>', { valid: ['true', 'false'] } )
|
|
549
552
|
.positionalArgument('<files...>')
|
|
550
553
|
.help(`
|
|
551
554
|
Usage: cdsc toEffectiveCsn [options] <files...>
|
|
@@ -560,8 +563,30 @@ optionProcessor.command('toEffectiveCsn')
|
|
|
560
563
|
true: (default) resolve simple type references to their simple base type
|
|
561
564
|
false: do not resolve simple type references
|
|
562
565
|
--resolve-projections <val> Resolve projections:
|
|
563
|
-
true: (default)
|
|
566
|
+
true: (default) transform projections into ordinary views with SELECT
|
|
564
567
|
false: leave them as real projections
|
|
568
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
569
|
+
true: remap annotations
|
|
570
|
+
false:(default) leave them as is
|
|
571
|
+
--keep-localized <val> Keep '.localized' property in the CSN:
|
|
572
|
+
true: property is kept
|
|
573
|
+
false:(default) property is deleted
|
|
574
|
+
`);
|
|
575
|
+
|
|
576
|
+
optionProcessor.command('forSeal')
|
|
577
|
+
.option('-h, --help')
|
|
578
|
+
.option('--remap-odata-annotations <val>', { valid: ['true', 'false'] } )
|
|
579
|
+
.positionalArgument('<files...>')
|
|
580
|
+
.help(`
|
|
581
|
+
Usage: cdsc forSeal [options] <files...>
|
|
582
|
+
|
|
583
|
+
(internal): Get the SEAL CSN model compiled from the provided CDS files.
|
|
584
|
+
|
|
585
|
+
Options
|
|
586
|
+
-h, --help Show this help text
|
|
587
|
+
--remap-odata-annotations <val> Remap OData annotations to ABAP annotations:
|
|
588
|
+
true: (default) remap annotations
|
|
589
|
+
false: leave them as is
|
|
565
590
|
`);
|
|
566
591
|
|
|
567
592
|
module.exports = {
|
|
@@ -97,12 +97,12 @@ class DuplicateChecker {
|
|
|
97
97
|
else
|
|
98
98
|
namingMode = 'plain';
|
|
99
99
|
|
|
100
|
-
error(null, [ 'definitions', artifact.modelName ],
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
error(null, [ 'definitions', artifact.modelName ], {
|
|
101
|
+
name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std',
|
|
102
|
+
}, {
|
|
103
|
+
std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
104
|
+
dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
|
|
105
|
+
});
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
108
|
artifacts.forEach((artifact) => {
|
|
@@ -32,6 +32,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
|
|
|
32
32
|
warning(null, null, { prop: sqlDialect || 'plain' }, 'Referential Constraints are not available for sql dialect $(PROP)');
|
|
33
33
|
|
|
34
34
|
if (drop && alter)
|
|
35
|
+
// eslint-disable-next-line cds-compiler/message-no-quotes
|
|
35
36
|
error(null, null, 'Option “--drop” can\'t be combined with “--alter”');
|
|
36
37
|
|
|
37
38
|
// Of course, we want the database constraints
|
package/lib/render/toCdl.js
CHANGED
|
@@ -614,10 +614,12 @@ function csnToCdl( csn, options, msg ) {
|
|
|
614
614
|
else if (element['#'] !== undefined) { // enum symbol reference
|
|
615
615
|
result += ` = #${element['#']}`;
|
|
616
616
|
}
|
|
617
|
-
else if (!isCalcElement || !isDirectAssocOrComp(element.type)) {
|
|
617
|
+
else if (!isCalcElement || !isDirectAssocOrComp(element.type) && !element.$filtered && !element.$enclosed) {
|
|
618
618
|
// If the element is a calculated element _and_ a direct association or
|
|
619
619
|
// composition, we'd render `Association to F on (cond) = calcValue;` which
|
|
620
620
|
// would alter the ON-condition.
|
|
621
|
+
// If it is a calculated element _and_ an indirect association (via type chain),
|
|
622
|
+
// we'd get a cast to an association.
|
|
621
623
|
const props = renderTypeReferenceAndProps(element, env);
|
|
622
624
|
if (props !== '')
|
|
623
625
|
result += ` : ${props}`;
|
|
@@ -344,7 +344,40 @@ function transformExpression( parent, propName, transformers, path = [] ) {
|
|
|
344
344
|
return parent;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Merge an array of transformer-objects into a single one, set the this-value of every subfunction to "that"
|
|
349
|
+
*
|
|
350
|
+
* @param {object[]} transformers transformers
|
|
351
|
+
* @param {object} that Value for this
|
|
352
|
+
* @returns {object} Remapped transformers.
|
|
353
|
+
*/
|
|
354
|
+
function mergeTransformers( transformers, that ) {
|
|
355
|
+
const remapped = {};
|
|
356
|
+
for (const transformer of transformers) {
|
|
357
|
+
for (const [ n, fns ] of Object.entries(transformer)) {
|
|
358
|
+
if (!remapped[n])
|
|
359
|
+
remapped[n] = [];
|
|
360
|
+
|
|
361
|
+
if (Array.isArray(fns)) {
|
|
362
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
|
|
363
|
+
fn => fn.bind(that)(parent, name, prop, path, parentParent)
|
|
364
|
+
));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
for (const [ n, fns ] of Object.entries(remapped))
|
|
373
|
+
remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
return remapped;
|
|
377
|
+
}
|
|
378
|
+
|
|
347
379
|
module.exports = {
|
|
380
|
+
mergeTransformers,
|
|
348
381
|
transformExpression,
|
|
349
382
|
applyTransformations,
|
|
350
383
|
applyTransformationsOnNonDictionary,
|
|
@@ -116,7 +116,7 @@ function createReferentialConstraints( csn, options ) {
|
|
|
116
116
|
foreignKeyConstraintForAssociation(up_, dependent, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1] );
|
|
117
117
|
}
|
|
118
118
|
else if (!onCondition && composition.keys.length > 0) {
|
|
119
|
-
throw new CompilerAssertion('
|
|
119
|
+
throw new CompilerAssertion('Debug me, an on-condition was expected here, but only found keys');
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -187,28 +187,46 @@ function createReferentialConstraints( csn, options ) {
|
|
|
187
187
|
* @param {CSN.PathSegment | object} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
|
|
188
188
|
* it is used for a comment in the constraint, which is only printed out in test-mode
|
|
189
189
|
*/
|
|
190
|
-
function attachConstraintsToDependentKeys(
|
|
190
|
+
function attachConstraintsToDependentKeys(
|
|
191
|
+
dependentKeys,
|
|
192
|
+
parentKeys,
|
|
193
|
+
parentTable,
|
|
194
|
+
sourceAssociation,
|
|
195
|
+
upLinkFor = null
|
|
196
|
+
) {
|
|
191
197
|
while (dependentKeys.length > 0) {
|
|
192
198
|
const dependentKeyValuePair = dependentKeys.pop();
|
|
193
199
|
const dependentKey = dependentKeyValuePair[1];
|
|
194
200
|
// if it already has a dependent key assigned, do not overwrite it.
|
|
195
201
|
// this is the case for <up_> associations in on-conditions of compositions
|
|
196
|
-
|
|
202
|
+
const { $foreignKeyConstraint } = dependentKey;
|
|
203
|
+
// in contrast to foreign keys which stem from managed associations,
|
|
204
|
+
// a tenant foreign key column may have multiple parent keys as partners
|
|
205
|
+
const tenantForeignKey = isTenant && dependentKeyValuePair[0] === 'tenant';
|
|
206
|
+
if ($foreignKeyConstraint && (!tenantForeignKey || $foreignKeyConstraint.upLinkFor)) {
|
|
197
207
|
return;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
}
|
|
209
|
+
else if ($foreignKeyConstraint && tenantForeignKey) {
|
|
210
|
+
parentKeys.pop();
|
|
211
|
+
$foreignKeyConstraint.sourceAssociation.push(sourceAssociation);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
const parentKeyValuePair = parentKeys.pop();
|
|
215
|
+
const parentKeyName = parentKeyValuePair[0];
|
|
216
|
+
|
|
217
|
+
const constraint = {
|
|
218
|
+
parentKey: parentKeyName,
|
|
219
|
+
parentTable,
|
|
220
|
+
upLinkFor,
|
|
221
|
+
sourceAssociation: tenantForeignKey
|
|
222
|
+
? [ sourceAssociation ]
|
|
223
|
+
: sourceAssociation,
|
|
224
|
+
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
|
|
225
|
+
validated,
|
|
226
|
+
enforced,
|
|
227
|
+
};
|
|
228
|
+
dependentKey.$foreignKeyConstraint = constraint;
|
|
229
|
+
}
|
|
212
230
|
}
|
|
213
231
|
}
|
|
214
232
|
|
|
@@ -473,11 +491,11 @@ function createReferentialConstraints( csn, options ) {
|
|
|
473
491
|
}
|
|
474
492
|
|
|
475
493
|
/**
|
|
476
|
-
* Creates the final referential constraints from all dependent key <-> parent key pairs stemming from the same
|
|
494
|
+
* Creates the final referential constraints from all dependent key <-> parent key pairs stemming from the same sourceAssociation
|
|
477
495
|
* and attaches it to the given artifact.
|
|
478
496
|
*
|
|
479
497
|
* Go over all elements with $foreignKeyConstraint property:
|
|
480
|
-
* - Find all other elements in artifact with the same
|
|
498
|
+
* - Find all other elements in artifact with the same sourceAssociation
|
|
481
499
|
* - Create constraints with the information supplied by $parentKey, $parentTable and $onDelete
|
|
482
500
|
*
|
|
483
501
|
* @param {CSN.Artifact} artifact
|
|
@@ -485,6 +503,23 @@ function createReferentialConstraints( csn, options ) {
|
|
|
485
503
|
*/
|
|
486
504
|
function collectAndAttachReferentialConstraints( artifact, artifactName ) {
|
|
487
505
|
const referentialConstraints = Object.create(null);
|
|
506
|
+
|
|
507
|
+
// tenant foreign keys may have multiple parent keys
|
|
508
|
+
// process tenant foreign key first
|
|
509
|
+
if (isTenant && artifact.elements?.tenant) {
|
|
510
|
+
const element = artifact.elements.tenant;
|
|
511
|
+
if (element.$foreignKeyConstraint) {
|
|
512
|
+
const $foreignKeyConstraint = Object.assign({}, element.$foreignKeyConstraint);
|
|
513
|
+
delete element.$foreignKeyConstraint;
|
|
514
|
+
// create a foreign key constraint for the tenant column with each association in the dependent entity
|
|
515
|
+
$foreignKeyConstraint.sourceAssociation.forEach((sourceAssociation) => {
|
|
516
|
+
const copy = Object.assign({}, $foreignKeyConstraint);
|
|
517
|
+
copy.sourceAssociation = sourceAssociation;
|
|
518
|
+
createReferentialConstraints(copy, 'tenant');
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
488
523
|
for (const elementName in artifact.elements) {
|
|
489
524
|
const element = artifact.elements[elementName];
|
|
490
525
|
if (!element.$foreignKeyConstraint)
|
|
@@ -492,16 +527,34 @@ function createReferentialConstraints( csn, options ) {
|
|
|
492
527
|
// copy constraint property, and delete it from the element
|
|
493
528
|
const $foreignKeyConstraint = Object.assign({}, element.$foreignKeyConstraint);
|
|
494
529
|
delete element.$foreignKeyConstraint;
|
|
530
|
+
createReferentialConstraints($foreignKeyConstraint, elementName);
|
|
531
|
+
}
|
|
532
|
+
if (Object.keys(referentialConstraints).length) {
|
|
533
|
+
if (!('$tableConstraints' in artifact))
|
|
534
|
+
artifact.$tableConstraints = Object.create(null);
|
|
535
|
+
|
|
536
|
+
artifact.$tableConstraints.referential = referentialConstraints;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Creates referential constraints for database relationships. This function constructs constraints based on foreign key information and element names,
|
|
541
|
+
* and determines deletion rules based on the existing constraints and options. It manages dependencies and names for constraints dynamically during
|
|
542
|
+
* execution.
|
|
543
|
+
*
|
|
544
|
+
* @param {object} $foreignKeyConstraint - An object encapsulating details about the foreign key constraint
|
|
545
|
+
* @param {string} elementName - The name of the dependent element or table that is linked by the foreign key.
|
|
546
|
+
*/
|
|
547
|
+
function createReferentialConstraints($foreignKeyConstraint, elementName) {
|
|
495
548
|
const { parentTable } = $foreignKeyConstraint;
|
|
496
549
|
const parentKey = [ $foreignKeyConstraint.parentKey ];
|
|
497
550
|
const dependentKey = [ elementName ];
|
|
498
551
|
const onDeleteRules = new Set();
|
|
499
552
|
onDeleteRules.add($foreignKeyConstraint.onDelete);
|
|
500
553
|
forEach(artifact.elements, (foreignKeyName, foreignKey) => {
|
|
501
|
-
// find all other `$foreignKeyConstraint`s with same
|
|
554
|
+
// find all other `$foreignKeyConstraint`s with same `sourceAssociation` and same `parentTable`
|
|
502
555
|
const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
|
|
503
|
-
|
|
504
|
-
|
|
556
|
+
foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
|
|
557
|
+
foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
|
|
505
558
|
if (!matchingForeignKeyFound)
|
|
506
559
|
return;
|
|
507
560
|
|
|
@@ -536,12 +589,6 @@ function createReferentialConstraints( csn, options ) {
|
|
|
536
589
|
enforced: $foreignKeyConstraint.enforced,
|
|
537
590
|
};
|
|
538
591
|
}
|
|
539
|
-
if (Object.keys(referentialConstraints).length) {
|
|
540
|
-
if (!('$tableConstraints' in artifact))
|
|
541
|
-
artifact.$tableConstraints = Object.create(null);
|
|
542
|
-
|
|
543
|
-
artifact.$tableConstraints.referential = referentialConstraints;
|
|
544
|
-
}
|
|
545
592
|
}
|
|
546
593
|
}
|
|
547
594
|
|
|
@@ -98,7 +98,9 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
98
98
|
*/
|
|
99
99
|
if (rewritten.toMany.length > 0 && !options.toOdata) {
|
|
100
100
|
markAsToDummify(artifact, path[1]);
|
|
101
|
-
|
|
101
|
+
rewritten.toMany.forEach(({ art }) => {
|
|
102
|
+
error( null, art.$path || [ 'definitions', path[1] ], { name: `${art.$env || path[1]}:${art.ref.map(r => r.id || r)}` }, 'Unexpected .expand with to-many association $(NAME)');
|
|
103
|
+
});
|
|
102
104
|
}
|
|
103
105
|
else {
|
|
104
106
|
parent.columns = rewritten.columns;
|
|
@@ -175,8 +177,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
175
177
|
const target = art.target ? art.target : pathStep;
|
|
176
178
|
if (toDummify.indexOf(target) !== -1) {
|
|
177
179
|
error( null, obj.$path, {
|
|
178
|
-
id: pathStep,
|
|
179
|
-
|
|
180
|
+
id: pathStep,
|
|
181
|
+
elemref: obj,
|
|
182
|
+
name,
|
|
183
|
+
anno: '@cds.persistence.skip',
|
|
184
|
+
}, 'Unexpected $(ANNO) annotation on Association target $(NAME) of $(ID) in path $(ELEMREF) was skipped because of .expand in conjunction with to-many');
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
|
|
@@ -242,7 +242,7 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
|
|
|
242
242
|
// full path into target, uncomment this line and
|
|
243
243
|
// comment/remove setProp in expansion.js
|
|
244
244
|
// setProp(parent, '$structRef', parent.ref);
|
|
245
|
-
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
|
|
245
|
+
[ parent.ref ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
|
|
246
246
|
resolved.set(parent, { links, art, scope });
|
|
247
247
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
248
248
|
// TODO: Can this be done elegantly during expand phase already?
|
|
@@ -543,7 +543,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
543
543
|
if (clone.ref) {
|
|
544
544
|
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
545
545
|
// Now we need to properly flatten the whole ref
|
|
546
|
-
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
|
|
546
|
+
[ clone.ref ] = flattenStructStepsInRef(clone.ref, pathToKey);
|
|
547
547
|
}
|
|
548
548
|
if (!clone.as) {
|
|
549
549
|
clone.as = flatElemName;
|
|
@@ -28,8 +28,8 @@ function replaceAssociationsInGroupByOrderBy( inputQuery, options, inspectRef, e
|
|
|
28
28
|
// (230 c) If we keep associations as they are (hdbcds naming convention), we can't have associations in GROUP BY
|
|
29
29
|
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds') {
|
|
30
30
|
error(null, groupByPath,
|
|
31
|
-
{ $reviewed: true },
|
|
32
|
-
'Unexpected managed association in
|
|
31
|
+
{ $reviewed: true, keyword: 'GROUP BY', value: 'hdbcds' },
|
|
32
|
+
'Unexpected managed association in $(KEYWORD) for naming mode $(VALUE)');
|
|
33
33
|
continue;
|
|
34
34
|
}
|
|
35
35
|
const pathPrefix = query.groupBy[i].ref.slice(0, -1);
|
|
@@ -4,7 +4,7 @@ const {
|
|
|
4
4
|
getNormalizedQuery, hasAnnotationValue, forEachMember,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs } = require('../../model/csnRefs');
|
|
7
|
-
const { setProp } = require('../../base/model');
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
|
|
10
10
|
const validToString = '@cds.valid.to';
|
|
@@ -21,9 +21,10 @@ const validFromString = '@cds.valid.from';
|
|
|
21
21
|
* @param {object} messageFunctions
|
|
22
22
|
* @param {Function} messageFunctions.info
|
|
23
23
|
* @param {object} csnUtils
|
|
24
|
+
* @param {object} options
|
|
24
25
|
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition applying the where-condition to views.
|
|
25
26
|
*/
|
|
26
|
-
function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
27
|
+
function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
27
28
|
const { info } = messageFunctions;
|
|
28
29
|
const { get$combined } = csnUtils;
|
|
29
30
|
return addTemporalWhereConditionToView;
|
|
@@ -52,7 +53,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils ) {
|
|
|
52
53
|
if (from.length === 1 && to.length === 1) {
|
|
53
54
|
// and both are from the same origin
|
|
54
55
|
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
55
|
-
|
|
56
|
+
const omitWhereClause = isBetaEnabled(options, 'temporalRawProjection') &&
|
|
57
|
+
hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0]);
|
|
58
|
+
if (!omitWhereClause) {
|
|
56
59
|
const fromPath = {
|
|
57
60
|
ref: [
|
|
58
61
|
from[0].parent,
|
|
@@ -616,10 +616,10 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
|
|
|
616
616
|
else if (typeof xpr.$env === 'number') {
|
|
617
617
|
if (xpr.$scope === 'mixin')
|
|
618
618
|
return '';
|
|
619
|
-
return error(null, xpr.$path, '$env with number is not handled yet -
|
|
619
|
+
return error(null, xpr.$path, '$env with number is not handled yet - report this error!');
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
-
return error(null, xpr.$path, 'Boolean $env is not handled yet -
|
|
622
|
+
return error(null, xpr.$path, 'Boolean $env is not handled yet - report this error!');
|
|
623
623
|
}
|
|
624
624
|
else if (xpr.ref) {
|
|
625
625
|
throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CompilerAssertion } = require('../../base/error');
|
|
4
|
+
|
|
5
|
+
const directMappings = {
|
|
6
|
+
'@Common.IsDayOfCalendarMonth': replace('@Semantics.calendar.dayOfMonth'),
|
|
7
|
+
'@Common.IsDayOfCalendarYear': replace('@Semantics.calendar.dayOfYear'),
|
|
8
|
+
'@Common.IsCalendarWeek': replace('@Semantics.calendar.week'),
|
|
9
|
+
'@Common.IsCalendarMonth': replace('@Semantics.calendar.month'),
|
|
10
|
+
'@Common.IsCalendarQuarter': replace('@Semantics.calendar.quarter'),
|
|
11
|
+
'@Common.IsCalendarHalfyear': replace('@Semantics.calendar.halfyear'),
|
|
12
|
+
'@Common.IsCalendarYear': replace('@Semantics.calendar.year'),
|
|
13
|
+
'@Common.IsCalendarYearWeek': replace('@Semantics.calendar.yearWeek'),
|
|
14
|
+
'@Common.IsCalendarYearMonth': replace('@Semantics.calendar.yearMonth'),
|
|
15
|
+
'@Common.IsCalendarYearQuarter': replace('@Semantics.calendar.yearQuarter'),
|
|
16
|
+
'@Common.IsCalendarYearHalfyear': replace('@Semantics.calendar.yearHalfyear'),
|
|
17
|
+
'@Common.IsCalendarDate': replace('@Semantics.date'),
|
|
18
|
+
'@Common.IsFiscalYearVariant': replace('@Semantics.yearVariant'),
|
|
19
|
+
'@Common.IsFiscalPeriod': replace('@Semantics.period'),
|
|
20
|
+
'@Common.IsFiscalYear': replace('@Semantics.year'),
|
|
21
|
+
'@Common.IsFiscalYearPeriod': replace('@Semantics.yearPeriod'),
|
|
22
|
+
'@Common.IsFiscalQuarter': replace('@Semantics.quarter'),
|
|
23
|
+
'@Common.IsFiscalYearQuarter': replace('@Semantics.yearQuarter'),
|
|
24
|
+
'@Common.IsFiscalWeek': replace('@Semantics.week'),
|
|
25
|
+
'@Common.IsFiscalYearWeek': replace('@Semantics.yearWeek'),
|
|
26
|
+
'@Common.IsDayOfFiscalYear': replace('@Semantics.dayOfYear'),
|
|
27
|
+
'@Measures.ISOCurrency': (csn, artifact, element, oldAnno) => {
|
|
28
|
+
const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
29
|
+
if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
|
|
30
|
+
replace('@Semantics.amount.currencyCode')(csn, artifact, element, oldAnno);
|
|
31
|
+
if (targetElement && targetElement['@Semantics.currencyCode'] === undefined)
|
|
32
|
+
targetElement['@Semantics.currencyCode'] = true;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
'@Measures.Unit': (csn, artifact, element, oldAnno) => {
|
|
36
|
+
const { targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
37
|
+
if (refPointsToThisArtifact(csn, artifact, element, oldAnno)) {
|
|
38
|
+
replace('@Semantics.quantity.unitOfMeasure')(csn, artifact, element, oldAnno);
|
|
39
|
+
if (targetElement && targetElement['@Semantics.unitOfMeasure'] === undefined)
|
|
40
|
+
targetElement['@Semantics.unitOfMeasure'] = true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
'@UI.IsImageURL': replace('@Semantics.imageUrl'),
|
|
44
|
+
'@Common.ValueList.CollectionPath': (csn, artifact, element) => {
|
|
45
|
+
if (!element.target && element['@Consumption.valueHelpDefinition'] === undefined) {
|
|
46
|
+
if (element['@Common.ValueList.Parameters'] && Array.isArray(element['@Common.ValueList.Parameters'])) {
|
|
47
|
+
const InOutParameters = element['@Common.ValueList.Parameters'].filter(param => param.$Type === 'Common.ValueListParameterInOut');
|
|
48
|
+
|
|
49
|
+
if (InOutParameters.length === 1) {
|
|
50
|
+
element['@Consumption.valueHelpDefinition'] = [ {
|
|
51
|
+
name: element['@Common.ValueList.CollectionPath'],
|
|
52
|
+
} ];
|
|
53
|
+
|
|
54
|
+
delete element['@Common.ValueList.CollectionPath'];
|
|
55
|
+
delete element['@Common.ValueList.Label'];
|
|
56
|
+
|
|
57
|
+
element['@Consumption.valueHelpDefinition'][0].element = element['@Common.ValueList.Parameters'][0].ValueListProperty;
|
|
58
|
+
delete element['@Common.ValueList.Parameters'];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
'@Common.TextFor': replace('@Semantics.text', true),
|
|
64
|
+
'@Common.IsLanguageIdentifier': replaceIf('@Semantics.language', true, (csn, artifact, element, anno) => !!element[anno]),
|
|
65
|
+
// We need to set two different annos here, depending on the value -> need a custom replacer
|
|
66
|
+
'@Common.Text': (csn, artifact, element, oldAnno) => {
|
|
67
|
+
const { targetArtifact, targetElement } = getAnnoRefTarget(csn, artifact, element[oldAnno]);
|
|
68
|
+
if (targetArtifact === artifact && !element['@ObjectModel.text.element'] && !targetElement['@Semantics.text']) {
|
|
69
|
+
element['@ObjectModel.text.element'] = element[oldAnno];
|
|
70
|
+
if (targetElement['@Semantics.text'] === undefined)
|
|
71
|
+
targetElement['@Semantics.text'] = true;
|
|
72
|
+
delete element['@Common.Text'];
|
|
73
|
+
}
|
|
74
|
+
else if (targetArtifact && targetElement && !element['@ObjectModel.text.association'] && !targetElement['@Semantics.text']) {
|
|
75
|
+
element['@ObjectModel.text.association'] = element[oldAnno];
|
|
76
|
+
if (targetElement['@Semantics.text'] === undefined)
|
|
77
|
+
targetElement['@Semantics.text'] = true;
|
|
78
|
+
delete element['@Common.Text'];
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
*
|
|
85
|
+
* @param {CSN.Model} csn
|
|
86
|
+
* @param {CSN.Artifact} artifact
|
|
87
|
+
* @param {CSN.Element} element
|
|
88
|
+
* @param {object} anno
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
function refPointsToThisArtifact( csn, artifact, element, anno ) {
|
|
92
|
+
const { targetArtifact } = getAnnoRefTarget(csn, artifact, element[anno]);
|
|
93
|
+
return targetArtifact && targetArtifact === artifact;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Walk the possible annotation ref and return the artifact and element it points to
|
|
98
|
+
*
|
|
99
|
+
* @param {CSN.Model} csn
|
|
100
|
+
* @param {CSN.Artifact} startArtifact
|
|
101
|
+
* @param {object} annoValue
|
|
102
|
+
* @returns {object}
|
|
103
|
+
*/
|
|
104
|
+
function getAnnoRefTarget( csn, startArtifact, annoValue ) {
|
|
105
|
+
if (!annoValue || !annoValue['='])
|
|
106
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
107
|
+
|
|
108
|
+
const steps = annoValue['='].split('.');
|
|
109
|
+
let base = startArtifact;
|
|
110
|
+
let element;
|
|
111
|
+
for (const step of steps) {
|
|
112
|
+
if (!base.elements)
|
|
113
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
114
|
+
element = base.elements[step];
|
|
115
|
+
if (!element)
|
|
116
|
+
return { targetArtifact: undefined, targetElement: undefined };
|
|
117
|
+
if (element.target)
|
|
118
|
+
base = csn.definitions[element.target];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return { targetArtifact: base, targetElement: element };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the function to replace oldAnno with newAnno on carrier.
|
|
126
|
+
*
|
|
127
|
+
* - If available, use "replacement" as value.
|
|
128
|
+
* - Only do replacement if "condition" returns true
|
|
129
|
+
* - Possibly set additional annotations via "additional"
|
|
130
|
+
* @param {string} newAnno
|
|
131
|
+
* @param {any} replacement
|
|
132
|
+
* @param {Function} [condition]
|
|
133
|
+
* @param {Function} [additional]
|
|
134
|
+
* @returns {Function}
|
|
135
|
+
*/
|
|
136
|
+
function replace( newAnno, replacement, condition = () => true, additional = () => true ) {
|
|
137
|
+
return function replaceAnnotationPrefix(csn, artifact, carrier, oldAnno) {
|
|
138
|
+
if (carrier[newAnno] === undefined && condition(csn, artifact, carrier, oldAnno, newAnno)) {
|
|
139
|
+
carrier[newAnno] = replacement || carrier[oldAnno];
|
|
140
|
+
additional(carrier, oldAnno, newAnno);
|
|
141
|
+
delete carrier[oldAnno];
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the function to replace oldAnno with newAnno on carrier.
|
|
148
|
+
*
|
|
149
|
+
* - If available, use "replacement" as value.
|
|
150
|
+
* - Only do replacement if "condition" returns true
|
|
151
|
+
*
|
|
152
|
+
* @param {string} newAnno
|
|
153
|
+
* @param {any} replacement
|
|
154
|
+
* @param {Function} condition
|
|
155
|
+
* @returns {Function}
|
|
156
|
+
*/
|
|
157
|
+
function replaceIf( newAnno, replacement, condition ) {
|
|
158
|
+
return replace( newAnno, replacement, condition );
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
*
|
|
163
|
+
* @param {CSN.Model} csn
|
|
164
|
+
* @returns {object} Transfomer object for applyTransformations
|
|
165
|
+
*/
|
|
166
|
+
function remapODataAnnotations( csn ) {
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
* @param {CSN.Artifact} artifact
|
|
170
|
+
* @param {CSN.Element} element Element to process
|
|
171
|
+
*/
|
|
172
|
+
function remapAnnotationsOnElement( artifact, element ) {
|
|
173
|
+
if (element.elements && !element.$ignore) // We expect to only be called on flattened CSN - error if we encounter .elements!
|
|
174
|
+
throw new CompilerAssertion(`Expected a flat model. Found element with subelements: ${JSON.stringify(element)}`);
|
|
175
|
+
for (const prop in element) {
|
|
176
|
+
if (directMappings[prop])
|
|
177
|
+
directMappings[prop](csn, artifact, element, prop);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
elements: (parent, prop, elements, path) => {
|
|
183
|
+
const artifact = csn.definitions[path[1]];
|
|
184
|
+
if (artifact?.kind === 'entity') {
|
|
185
|
+
for (const elementName in elements)
|
|
186
|
+
remapAnnotationsOnElement(artifact, elements[elementName]);
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = {
|
|
193
|
+
remapODataAnnotations,
|
|
194
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const {
|
|
4
|
+
getUtils, isAspect, mergeTransformers, applyTransformations,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
6
|
const transformUtils = require('../transformUtils');
|
|
7
7
|
const flattening = require('../db/flattening');
|
|
8
8
|
const types = require('./types');
|
|
@@ -14,6 +14,7 @@ const associations = require('./associations');
|
|
|
14
14
|
const generateDrafts = require('../draft/db');
|
|
15
15
|
const handleExists = require('../db/transformExists');
|
|
16
16
|
const misc = require('./misc');
|
|
17
|
+
const annotations = require('./annotations');
|
|
17
18
|
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('../db/rewriteCalculatedElements');
|
|
18
19
|
const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
19
20
|
|
|
@@ -30,9 +31,6 @@ const { cloneFullCsn } = require('../../model/cloneCsn');
|
|
|
30
31
|
* @returns {CSN.Model}
|
|
31
32
|
*/
|
|
32
33
|
function effectiveCsn( model, options, messageFunctions ) {
|
|
33
|
-
if (!isBetaEnabled(options, 'effectiveCsn'))
|
|
34
|
-
throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
|
|
35
|
-
|
|
36
34
|
const csn = cloneFullCsn(model, options);
|
|
37
35
|
delete csn.vocabularies; // must not be set for effective CSN
|
|
38
36
|
messageFunctions.setModel(csn);
|
|
@@ -80,8 +78,8 @@ function effectiveCsn( model, options, messageFunctions ) {
|
|
|
80
78
|
associations.managedToUnmanaged(csn, options, csnUtils, messageFunctions);
|
|
81
79
|
associations.transformBacklinks(csn, options, csnUtils, messageFunctions);
|
|
82
80
|
generateDrafts(csn, options, '_', messageFunctions);
|
|
83
|
-
misc.attachPersistenceName(csn, options, csnUtils);
|
|
84
|
-
|
|
81
|
+
const transformers = mergeTransformers([ misc.attachPersistenceName(csn, options, csnUtils), options.remapOdataAnnotations ? annotations.remapODataAnnotations(csn) : {}, misc.removeDefinitionsAndProperties(csn, options) ], null);
|
|
82
|
+
applyTransformations(csn, transformers, [], { skipIgnore: false });
|
|
85
83
|
|
|
86
84
|
if (!options.resolveProjections)
|
|
87
85
|
redoProjections.forEach(fn => fn());
|