@sap/cds-compiler 5.9.4 → 6.0.12
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 +117 -319
- package/README.md +1 -1
- package/bin/cds_update_identifiers.js +3 -5
- package/bin/cdsc.js +24 -9
- package/bin/cdshi.js +1 -1
- package/bin/cdsse.js +4 -4
- package/doc/CHANGELOG_BETA.md +11 -0
- package/doc/CHANGELOG_DEPRECATED.md +29 -0
- package/lib/api/main.js +8 -5
- package/lib/api/options.js +12 -10
- package/lib/base/builtins.js +1 -0
- package/lib/base/message-registry.js +191 -99
- package/lib/base/messages.js +35 -21
- package/lib/base/model.js +14 -24
- package/lib/checks/actionsFunctions.js +10 -20
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/elements.js +35 -10
- package/lib/checks/enums.js +31 -0
- package/lib/checks/foreignKeys.js +2 -2
- package/lib/checks/hasPersistedElements.js +5 -0
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/managedWithoutKeys.js +5 -4
- package/lib/checks/queryNoDbArtifacts.js +10 -8
- package/lib/checks/types.js +5 -5
- package/lib/checks/validator.js +6 -4
- package/lib/compiler/assert-consistency.js +13 -9
- package/lib/compiler/checks.js +20 -52
- package/lib/compiler/define.js +31 -6
- package/lib/compiler/extend.js +5 -1
- package/lib/compiler/generate.js +14 -17
- package/lib/compiler/populate.js +8 -31
- package/lib/compiler/propagator.js +21 -35
- package/lib/compiler/resolve.js +64 -29
- package/lib/compiler/shared.js +16 -4
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +1 -1
- package/lib/edm/annotations/edmJson.js +23 -20
- package/lib/edm/annotations/genericTranslation.js +12 -10
- package/lib/edm/csn2edm.js +50 -56
- package/lib/edm/edm.js +33 -28
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +54 -88
- package/lib/edm/edmUtils.js +9 -12
- package/lib/gen/BaseParser.js +63 -52
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1153 -1165
- package/lib/gen/Dictionary.json +21 -1
- package/lib/json/from-csn.js +70 -43
- package/lib/json/to-csn.js +6 -8
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/main.d.ts +58 -24
- package/lib/model/cloneCsn.js +3 -0
- package/lib/model/csnUtils.js +28 -39
- package/lib/model/xprAsTree.js +23 -9
- package/lib/modelCompare/compare.js +5 -4
- package/lib/optionProcessor.js +24 -17
- package/lib/parsers/AstBuildingParser.js +81 -25
- package/lib/parsers/XprTree.js +57 -3
- package/lib/parsers/identifiers.js +1 -1
- package/lib/parsers/index.js +0 -3
- package/lib/render/manageConstraints.js +25 -25
- package/lib/render/toCdl.js +173 -170
- package/lib/render/toHdbcds.js +126 -128
- package/lib/render/toRename.js +7 -7
- package/lib/render/toSql.js +128 -125
- package/lib/render/utils/common.js +47 -22
- package/lib/render/utils/delta.js +25 -25
- package/lib/render/utils/operators.js +2 -2
- package/lib/render/utils/pretty.js +5 -5
- package/lib/render/utils/sql.js +13 -13
- package/lib/render/utils/standardDatabaseFunctions.js +115 -103
- package/lib/render/utils/unique.js +4 -4
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/assertUnique.js +2 -2
- package/lib/transform/db/associations.js +6 -7
- package/lib/transform/db/assocsToQueries/utils.js +4 -5
- package/lib/transform/db/backlinks.js +12 -9
- package/lib/transform/db/cdsPersistence.js +8 -7
- package/lib/transform/db/constraints.js +13 -10
- package/lib/transform/db/expansion.js +7 -3
- package/lib/transform/db/flattening.js +4 -14
- package/lib/transform/db/processSqlServices.js +2 -1
- package/lib/transform/db/temporal.js +5 -7
- package/lib/transform/db/views.js +2 -4
- package/lib/transform/draft/db.js +8 -8
- package/lib/transform/draft/odata.js +10 -7
- package/lib/transform/forOdata.js +10 -5
- package/lib/transform/forRelationalDB.js +5 -75
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/createForeignKeys.js +11 -10
- package/lib/transform/odata/flattening.js +8 -4
- package/lib/transform/odata/foreignKeyRefsInXprAnnos.js +96 -0
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/transformUtils.js +4 -8
- package/lib/transform/translateAssocsToJoins.js +14 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +10 -4
- package/lib/utils/objectUtils.js +0 -17
- package/package.json +10 -13
- package/share/messages/def-upcoming-virtual-change.md +1 -1
- package/LICENSE +0 -37
- package/bin/cds_remove_invalid_whitespace.js +0 -138
- package/doc/CHANGELOG_ARCHIVE.md +0 -3604
- package/lib/gen/genericAntlrParser.js +0 -3
- package/lib/gen/language.checksum +0 -1
- package/lib/gen/language.interp +0 -456
- package/lib/gen/language.tokens +0 -180
- package/lib/gen/languageLexer.interp +0 -439
- package/lib/gen/languageLexer.js +0 -1483
- package/lib/gen/languageLexer.tokens +0 -167
- package/lib/gen/languageParser.js +0 -24941
- package/lib/language/antlrParser.js +0 -205
- package/lib/language/errorStrategy.js +0 -646
- package/lib/language/genericAntlrParser.js +0 -1572
- package/lib/parsers/CdlGrammar.g4 +0 -2070
|
@@ -14,9 +14,9 @@ function renderUniqueConstraintString( constraint, constraintName, tableName, qu
|
|
|
14
14
|
const c = constraint.paths;
|
|
15
15
|
const refs = c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ');
|
|
16
16
|
if (options.src === 'hdi')
|
|
17
|
-
return `UNIQUE INVERTED INDEX ${constraintName} ON ${tableName} (${refs})`;
|
|
17
|
+
return `UNIQUE INVERTED INDEX ${ constraintName } ON ${ tableName } (${ refs })`;
|
|
18
18
|
|
|
19
|
-
return `CONSTRAINT ${constraintName} UNIQUE (${refs})`;
|
|
19
|
+
return `CONSTRAINT ${ constraintName } UNIQUE (${ refs })`;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Render the "ALTER TABLE XY DROP CONSTRAINT <Z>;"
|
|
@@ -28,7 +28,7 @@ function renderUniqueConstraintString( constraint, constraintName, tableName, qu
|
|
|
28
28
|
* @returns {string}
|
|
29
29
|
*/
|
|
30
30
|
function renderUniqueConstraintDrop( constraint, constraintName, tableName, quoteSqlId ) {
|
|
31
|
-
return `ALTER TABLE ${tableName} DROP CONSTRAINT ${quoteSqlId(constraintName)};`;
|
|
31
|
+
return `ALTER TABLE ${ tableName } DROP CONSTRAINT ${ quoteSqlId(constraintName) };`;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
@@ -42,7 +42,7 @@ function renderUniqueConstraintDrop( constraint, constraintName, tableName, quot
|
|
|
42
42
|
* @returns {string}
|
|
43
43
|
*/
|
|
44
44
|
function renderUniqueConstraintAdd( constraint, constraintName, tableName, quoteSqlId, options ) {
|
|
45
|
-
return `ALTER TABLE ${tableName} ADD ${renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options)};`;
|
|
45
|
+
return `ALTER TABLE ${ tableName } ADD ${ renderUniqueConstraintString(constraint, constraintName, tableName, quoteSqlId, options) };`;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
module.exports = {
|
|
@@ -183,7 +183,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
183
183
|
csnPath.push( _prop );
|
|
184
184
|
for (const name of Object.getOwnPropertyNames( dict )) {
|
|
185
185
|
if (!isArtifactSkipped( dict, name )) {
|
|
186
|
-
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
|
|
186
|
+
artifactTransformers.forEach(fn => fn(dict, name, dict[name], [ 'definitions' ]));
|
|
187
187
|
dictEntry( dict, name, dict[name] );
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition
|
|
3
|
+
const { forEachDefinition } = require('../../model/csnUtils');
|
|
4
4
|
const { getTransformers } = require('../transformUtils');
|
|
5
5
|
const { pathName } = require('../../compiler/utils');
|
|
6
6
|
|
|
@@ -32,7 +32,7 @@ function processAssertUnique( csn, options, messageFunctions ) {
|
|
|
32
32
|
*/
|
|
33
33
|
function handleAssertUnique( artifact, artifactName ) {
|
|
34
34
|
// operate only on real entities that are not abstract
|
|
35
|
-
if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !
|
|
35
|
+
if (artifact.abstract || (artifact.kind !== 'entity' || (artifact.query || artifact.projection) && !artifact['@cds.persistence.table']))
|
|
36
36
|
return;
|
|
37
37
|
const constraintXrefs = Object.create(null);
|
|
38
38
|
const constraintDict = Object.create(null);
|
|
@@ -46,14 +46,14 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
|
|
|
46
46
|
// of another association - see a few lines lower
|
|
47
47
|
if (alreadyHandled.has(elem))
|
|
48
48
|
return;
|
|
49
|
-
|
|
50
|
-
const onCondParts = [];
|
|
51
|
-
let joinWithAnd = false;
|
|
52
|
-
if (elem.keys.length === 0 && options.transformation !== 'effective') { // TODO: really kill instead of $ignore?
|
|
49
|
+
if ((!elem.keys || elem.keys.length === 0) && options.transformation !== 'effective') { // TODO: really kill instead of $ignore?
|
|
53
50
|
elem.$ignore = true;
|
|
54
51
|
}
|
|
55
52
|
else {
|
|
56
|
-
|
|
53
|
+
// Assemble an ON-condition with the foreign keys created in earlier steps
|
|
54
|
+
const onCondParts = [];
|
|
55
|
+
let joinWithAnd = false;
|
|
56
|
+
for (const foreignKey of elem.keys || []) {
|
|
57
57
|
// Assemble left hand side of 'assoc.key = fkey'
|
|
58
58
|
const assocKeyArg = {
|
|
59
59
|
ref: [
|
|
@@ -70,9 +70,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter, iterateOptions = {},
|
|
|
70
70
|
],
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
if (joinWithAnd)
|
|
73
|
+
if (joinWithAnd) // more than one FK
|
|
74
74
|
onCondParts.push('and');
|
|
75
|
-
}
|
|
76
75
|
|
|
77
76
|
onCondParts.push(
|
|
78
77
|
assocKeyArg
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { getRealName } = require('../../../render/utils/common');
|
|
4
|
-
const { ModelError } = require('../../../base/error');
|
|
4
|
+
const { ModelError, CompilerAssertion } = require('../../../base/error');
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Some shared transformation utilities between exists rewriting and general assoc to subselect rewriting
|
|
@@ -74,10 +74,10 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
74
74
|
else if (typeof xpr.$env === 'number') {
|
|
75
75
|
if (xpr.$scope === 'mixin')
|
|
76
76
|
return '';
|
|
77
|
-
|
|
77
|
+
throw new CompilerAssertion(`$env with number is not handled yet - report this error! in ${ JSON.stringify(xpr.$path) }`);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
throw new CompilerAssertion(`Boolean $env is not handled yet - report this error! in ${ JSON.stringify(xpr.$path) }`);
|
|
81
81
|
}
|
|
82
82
|
else if (xpr.ref) {
|
|
83
83
|
throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
|
|
@@ -364,8 +364,7 @@ function getHelpers( csn, inspectRef, error ) {
|
|
|
364
364
|
}
|
|
365
365
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
366
366
|
// Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
|
|
367
|
-
error(
|
|
368
|
-
'An association that uses $(NAME) in its ON-condition can\'t be compared to "$self"');
|
|
367
|
+
error('type-invalid-self', part.$path, { name: '$self' });
|
|
369
368
|
}
|
|
370
369
|
else if (partInspect.art) { // source side - with local scope
|
|
371
370
|
where.push({ ref: [ target, ...assoc.ref.slice(1, -1), ...part.ref ] });
|
|
@@ -5,9 +5,9 @@ const {
|
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
|
|
7
7
|
const { setProp } = require('../../base/model');
|
|
8
|
-
const { ModelError } = require('../../base/error');
|
|
9
8
|
const { forEach } = require('../../utils/objectUtils');
|
|
10
9
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
10
|
+
const { ModelError } = require('../../base/error');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Get a function that transforms $self backlinks
|
|
@@ -188,25 +188,28 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
188
188
|
// Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
|
|
189
189
|
if (assoc.on) {
|
|
190
190
|
const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
|
|
191
|
+
if (containsDollarSelf)
|
|
192
|
+
messageFunctions.error('type-invalid-self', path, { name: '$self' });
|
|
193
|
+
}
|
|
191
194
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
195
|
+
if (!assoc.keys && !assoc.on) {
|
|
196
|
+
// Interpret no ON-condition/no keys like empty 'keys'.
|
|
197
|
+
if (options.transformation !== 'effective')
|
|
198
|
+
elem.$ignore = true;
|
|
199
|
+
return [];
|
|
196
200
|
}
|
|
197
201
|
|
|
198
|
-
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
199
202
|
if (assoc.keys) {
|
|
200
|
-
|
|
203
|
+
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
204
|
+
if (assoc.keys.length > 0)
|
|
201
205
|
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
202
206
|
|
|
203
207
|
if (options.transformation !== 'effective')
|
|
204
208
|
elem.$ignore = true;
|
|
205
209
|
return [];
|
|
206
210
|
}
|
|
207
|
-
|
|
208
|
-
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
|
|
209
211
|
else if (assoc.on) {
|
|
212
|
+
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
|
|
210
213
|
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
211
214
|
}
|
|
212
215
|
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
forEachGeneric,
|
|
4
|
+
forEachGeneric,
|
|
5
|
+
forEachMemberRecursively,
|
|
6
|
+
isPersistedOnDatabase,
|
|
7
|
+
hasPersistenceSkipAnnotation,
|
|
5
8
|
} = require('../../model/csnUtils');
|
|
6
9
|
const transformUtils = require('../transformUtils');
|
|
7
10
|
|
|
8
|
-
const exists = '@cds.persistence.exists';
|
|
9
|
-
|
|
10
11
|
/**
|
|
11
12
|
* Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
|
|
12
13
|
* with $ignore.
|
|
13
14
|
*
|
|
14
|
-
* @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
|
|
15
|
+
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback function for forEachDefinition
|
|
15
16
|
*/
|
|
16
17
|
function getAnnoProcessor() {
|
|
17
18
|
return handleCdsPersistence;
|
|
@@ -21,8 +22,8 @@ function getAnnoProcessor() {
|
|
|
21
22
|
function handleCdsPersistence( artifact ) {
|
|
22
23
|
const ignoreArtifact = (artifact.kind === 'entity') &&
|
|
23
24
|
(artifact.abstract ||
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
hasPersistenceSkipAnnotation(artifact) ||
|
|
26
|
+
artifact['@cds.persistence.exists']);
|
|
26
27
|
if (ignoreArtifact)
|
|
27
28
|
artifact.$ignore = true;
|
|
28
29
|
}
|
|
@@ -116,7 +117,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
|
|
|
116
117
|
* @param {string} artifactName
|
|
117
118
|
*/
|
|
118
119
|
function handleQueryish( artifact, artifactName ) {
|
|
119
|
-
const stripQueryish = artifact.query &&
|
|
120
|
+
const stripQueryish = artifact.query && artifact['@cds.persistence.table'];
|
|
120
121
|
|
|
121
122
|
if (stripQueryish) {
|
|
122
123
|
artifact.kind = 'entity';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { forEachDefinition } = require('../../base/model');
|
|
4
|
-
const { applyTransformations,
|
|
4
|
+
const { applyTransformations, getResultingName, hasPersistenceSkipAnnotation } = require('../../model/csnUtils');
|
|
5
5
|
const { forEach, forEachKey } = require('../../utils/objectUtils');
|
|
6
6
|
const { CompilerAssertion } = require('../../base/error');
|
|
7
7
|
|
|
@@ -292,10 +292,10 @@ function createReferentialConstraints( csn, options ) {
|
|
|
292
292
|
|
|
293
293
|
// no constraint if either dependent or parent is not persisted
|
|
294
294
|
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
hasPersistenceSkipAnnotation(parent) ||
|
|
296
|
+
hasPersistenceSkipAnnotation(dependent) ||
|
|
297
|
+
parent['@cds.persistence.exists'] ||
|
|
298
|
+
dependent['@cds.persistence.exists']
|
|
299
299
|
)
|
|
300
300
|
return true;
|
|
301
301
|
|
|
@@ -351,12 +351,12 @@ function createReferentialConstraints( csn, options ) {
|
|
|
351
351
|
*/
|
|
352
352
|
function assertForIndividual() {
|
|
353
353
|
if (isAssertIntegrityAnnotationSetTo(DB) || element[CREATE_FOR_UP]) {
|
|
354
|
-
// if this is
|
|
354
|
+
// if this is a $self comparison, the up_ link should then result in a constraint
|
|
355
355
|
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
|
|
356
356
|
return false;
|
|
357
357
|
}
|
|
358
358
|
if (options.assertIntegrityType === DB && isAssertIntegrityAnnotationSetTo(true)) {
|
|
359
|
-
// if this is
|
|
359
|
+
// if this is a $self comparison, the up_ link should then result in a constraint
|
|
360
360
|
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
|
|
361
361
|
return false;
|
|
362
362
|
}
|
|
@@ -397,14 +397,17 @@ function createReferentialConstraints( csn, options ) {
|
|
|
397
397
|
}
|
|
398
398
|
|
|
399
399
|
/**
|
|
400
|
-
*
|
|
401
|
-
* is the same as a given value
|
|
400
|
+
* Convenience to check if value of element's @assert.integrity annotation
|
|
401
|
+
* is the same as a given value. `@assert.integrity`-value checks do not use the "truthy"-semantics,
|
|
402
|
+
* since string values _and_ booleans are allowed, but are treated differently.
|
|
402
403
|
*
|
|
403
404
|
* @param {string|boolean} value
|
|
404
405
|
* @returns {boolean}
|
|
405
406
|
*/
|
|
406
407
|
function isAssertIntegrityAnnotationSetTo( value ) {
|
|
407
|
-
|
|
408
|
+
if (typeof element['@assert.integrity'] === 'string' && typeof value === 'string')
|
|
409
|
+
return element['@assert.integrity'].toUpperCase() === value.toUpperCase();
|
|
410
|
+
return element['@assert.integrity'] === value;
|
|
408
411
|
}
|
|
409
412
|
|
|
410
413
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
hasAnnotationValue,
|
|
5
4
|
applyTransformations,
|
|
6
5
|
setDependencies,
|
|
7
6
|
walkCsnPath,
|
|
@@ -39,7 +38,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
39
38
|
columns: (parent, name, columns, path) => {
|
|
40
39
|
const artifact = csn.definitions[path[1]];
|
|
41
40
|
csnUtils.initDefinition(artifact); // potentially not initialized, yet
|
|
42
|
-
if (!
|
|
41
|
+
if (!artifact['@cds.persistence.table']) {
|
|
43
42
|
const root = csnUtils.get$combined({ SELECT: parent });
|
|
44
43
|
// TODO: replace with the correct options.transformation?
|
|
45
44
|
// Do not expand the * in OData for a moment, not to introduce changes
|
|
@@ -87,7 +86,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
87
86
|
// (which is the thing inside SET/SELECT)
|
|
88
87
|
// We can directly use SELECT here, as only projections and SELECT can have .columns
|
|
89
88
|
const root = csnUtils.get$combined({ SELECT: parent });
|
|
90
|
-
if (!
|
|
89
|
+
if (!artifact['@cds.persistence.table']) {
|
|
91
90
|
// Make root look like normal .elements - we never cared about conflict afaik anyway
|
|
92
91
|
Object.keys(root).forEach((key) => {
|
|
93
92
|
root[key] = root[key][0].element;
|
|
@@ -395,6 +394,11 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
395
394
|
const stack = [ [ parent, ref ] ];
|
|
396
395
|
while (stack.length > 0) {
|
|
397
396
|
const [ current, currentRef ] = stack.pop();
|
|
397
|
+
|
|
398
|
+
// '*' can't be used in function inside expand/inline, as it's not rewritten.
|
|
399
|
+
if (current.func && current.args?.[0] === '*')
|
|
400
|
+
error('query-unsupported-asterisk', current.$path, { code: `${ current.func }(*)` });
|
|
401
|
+
|
|
398
402
|
if (current.xpr)
|
|
399
403
|
rewriteSingleExpressionArray(current.xpr, currentRef, stack);
|
|
400
404
|
if (current.args)
|
|
@@ -462,6 +462,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
462
462
|
* @param {*} path
|
|
463
463
|
*/
|
|
464
464
|
function flattenFKs( assoc, assocName, path ) {
|
|
465
|
+
if (!assoc.keys)
|
|
466
|
+
return; // managed to-many assoc
|
|
467
|
+
|
|
465
468
|
// TODO Depth first search and not iterate mark and sweep approach
|
|
466
469
|
let finished = false;
|
|
467
470
|
while (!finished) {
|
|
@@ -713,18 +716,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
713
716
|
return [];
|
|
714
717
|
|
|
715
718
|
if (finalElement.target && !finalElement.on) {
|
|
716
|
-
|
|
717
|
-
if (!hasKeys) {
|
|
718
|
-
const target = csn.definitions[finalElement.target];
|
|
719
|
-
|
|
720
|
-
setProp(finalElement, 'keys', [ ] );
|
|
721
|
-
if (target && target.elements) {
|
|
722
|
-
finalElement.keys = Object.entries(target.elements).filter(([ _n, e ]) => e.key)
|
|
723
|
-
. map(([ n, _e ]) => ({ ref: [ n ], as: n }));
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
// TODO: has managed assoc keys?
|
|
727
|
-
finalElement.keys.forEach((key, keyIndex) => {
|
|
719
|
+
finalElement.keys?.forEach((key, keyIndex) => {
|
|
728
720
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
729
721
|
const alias = key.as || implicitAs(key.ref);
|
|
730
722
|
const result = csnUtils.inspectRef(continuePath);
|
|
@@ -732,8 +724,6 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
732
724
|
ref: key.ref, as: key.as, $path: key.$path, $originalKeyRef: key.$originalKeyRef,
|
|
733
725
|
} : originalKey));
|
|
734
726
|
});
|
|
735
|
-
if (!hasKeys)
|
|
736
|
-
delete finalElement.keys;
|
|
737
727
|
}
|
|
738
728
|
// return if the toplevel element is not a managed association
|
|
739
729
|
else if (lvl === 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
4
|
+
const { hasPersistenceSkipAnnotation } = require('../../model/csnUtils');
|
|
4
5
|
|
|
5
6
|
const sqlServiceAnnotation = '@protocol';
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ function isDummyService(artifact, options) {
|
|
|
55
56
|
*/
|
|
56
57
|
function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
57
58
|
const result = { sqlServiceName: undefined, dummyServiceName: undefined };
|
|
58
|
-
if (artifact.kind !== 'entity' || !artifactName.includes('.') || artifact
|
|
59
|
+
if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
|
|
59
60
|
return result;
|
|
60
61
|
|
|
61
62
|
const nameParts = artifactName.split('.');
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
getNormalizedQuery,
|
|
4
|
+
getNormalizedQuery, forEachMember,
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { implicitAs } = require('../../model/csnRefs');
|
|
7
7
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
@@ -110,10 +110,9 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
|
110
110
|
if (!Array.isArray(elt))
|
|
111
111
|
elt = [ elt ];
|
|
112
112
|
elt.forEach((e) => {
|
|
113
|
-
if (
|
|
113
|
+
if (e.element[validFromString])
|
|
114
114
|
from.push(e);
|
|
115
|
-
|
|
116
|
-
if (hasAnnotationValue(e.element, validToString))
|
|
115
|
+
if (e.element[validToString])
|
|
117
116
|
to.push(e);
|
|
118
117
|
});
|
|
119
118
|
}
|
|
@@ -145,9 +144,8 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
|
145
144
|
}
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
|
-
return fromElement &&
|
|
149
|
-
|
|
150
|
-
hasAnnotationValue(toElement, validToString, false);
|
|
147
|
+
return fromElement && !fromElement[validFromString] &&
|
|
148
|
+
toElement && !toElement[validToString];
|
|
151
149
|
}
|
|
152
150
|
}
|
|
153
151
|
|
|
@@ -371,10 +371,8 @@ function getViewTransformer( csn, options, messageFunctions ) {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
if (query && !hasNonAssocElements) {
|
|
374
|
-
// Complain if there are no elements other than unmanaged associations
|
|
375
|
-
|
|
376
|
-
error(null, [ 'definitions', artName ], { $reviewed: true },
|
|
377
|
-
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
374
|
+
// Complain if there are no elements other than unmanaged associations or associations without keys.
|
|
375
|
+
error('def-missing-element', [ 'definitions', artName ], { '#': 'view' });
|
|
378
376
|
}
|
|
379
377
|
|
|
380
378
|
if (isSelect) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
getServiceNames, forEachDefinition,
|
|
5
5
|
getResultingName, forEachMemberRecursively, applyAnnotationsFromExtensions,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
|
-
const { setProp,
|
|
7
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
8
|
const { getTransformers } = require('../transformUtils');
|
|
9
9
|
const { ModelError } = require('../../base/error');
|
|
10
10
|
const { forEach } = require('../../utils/objectUtils');
|
|
@@ -34,7 +34,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
34
34
|
|
|
35
35
|
forEachDefinition(csn, generateDraft);
|
|
36
36
|
|
|
37
|
-
applyAnnotationsFromExtensions(csn, { filter: name => generatedArtifacts[name], applyToElements: false });
|
|
37
|
+
applyAnnotationsFromExtensions(csn, { filter: name => generatedArtifacts[name], applyToElements: false }, error);
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Generate the draft stuff for a given artifact
|
|
@@ -44,7 +44,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
44
44
|
*/
|
|
45
45
|
function generateDraft( artifact, artifactName ) {
|
|
46
46
|
if ((artifact.kind === 'entity') &&
|
|
47
|
-
|
|
47
|
+
artifact[draftAnnotation] &&
|
|
48
48
|
isPartOfService(artifactName)) {
|
|
49
49
|
// Determine the set of target draft nodes belonging to this draft root (the draft root
|
|
50
50
|
// itself plus all its transitively composition-reachable targets)
|
|
@@ -93,13 +93,13 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
93
93
|
continue;
|
|
94
94
|
}
|
|
95
95
|
// Barf if a draft node other than the root has @odata.draft.enabled itself
|
|
96
|
-
if (draftNode !== rootArtifact &&
|
|
96
|
+
if (draftNode !== rootArtifact && draftNode[draftAnnotation]) {
|
|
97
97
|
error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
|
|
98
98
|
delete draftNodes[draftNodeName];
|
|
99
99
|
continue;
|
|
100
100
|
}
|
|
101
|
-
// Recurse unless already known
|
|
102
|
-
if (
|
|
101
|
+
// Recurse unless already known. Check for explicit `false` on purpose.
|
|
102
|
+
if (draftNode[draftAnnotation] !== false && !draftNodes[draftNodeName])
|
|
103
103
|
collectDraftNodesInto(draftNode, draftNodeName, rootArtifact, draftNodes);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
@@ -193,7 +193,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
193
193
|
}
|
|
194
194
|
else {
|
|
195
195
|
let elem;
|
|
196
|
-
if (
|
|
196
|
+
if (!origElem.virtual)
|
|
197
197
|
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
|
|
198
198
|
if (elem) {
|
|
199
199
|
// Remove "virtual" - cap/issues 4956
|
|
@@ -36,7 +36,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
36
36
|
|
|
37
37
|
const { error, info } = messageFunctions;
|
|
38
38
|
const {
|
|
39
|
-
createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
|
|
39
|
+
createAndAddDraftAdminDataProjection, isValidDraftAdminDataMessagesType,
|
|
40
40
|
createScalarElement, createAssociationElement,
|
|
41
41
|
createAssociationPathComparison, addElement,
|
|
42
42
|
createAction, assignAction,
|
|
@@ -45,7 +45,6 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
45
45
|
} = getTransformers(csn, options, messageFunctions);
|
|
46
46
|
const {
|
|
47
47
|
getServiceName,
|
|
48
|
-
hasAnnotationValue,
|
|
49
48
|
getFinalTypeInfo,
|
|
50
49
|
} = csnUtils;
|
|
51
50
|
|
|
@@ -74,7 +73,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
74
73
|
generateDraftForOdata(def, defName, def);
|
|
75
74
|
}, { skipArtifact: isExternalServiceMember });
|
|
76
75
|
|
|
77
|
-
applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
|
|
76
|
+
applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] }, error);
|
|
78
77
|
rewriteDollarDraft();
|
|
79
78
|
|
|
80
79
|
return csn;
|
|
@@ -101,7 +100,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
101
100
|
|
|
102
101
|
if(!visitedArtifacts[artifactName])
|
|
103
102
|
visitedArtifacts[artifactName] = artifact;
|
|
104
|
-
|
|
103
|
+
|
|
105
104
|
const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
106
105
|
assignAction(draftPrepare, artifact);
|
|
107
106
|
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
@@ -124,6 +123,8 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
124
123
|
if (!draftAdminDataProjection) {
|
|
125
124
|
// @ts-ignore
|
|
126
125
|
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
|
|
126
|
+
filterDict[draftAdminDataProjectionName] = true;
|
|
127
|
+
filterDict['DRAFT.DraftAdministrativeData'] = true;
|
|
127
128
|
}
|
|
128
129
|
// Report an error if it is not an entity or not what we expect
|
|
129
130
|
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements.DraftUUID) {
|
|
@@ -217,11 +218,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
217
218
|
// Ignore if that is our own draft root
|
|
218
219
|
if (draftNode !== rootArtifact) {
|
|
219
220
|
// Report error when the draft node has @odata.draft.enabled itself
|
|
220
|
-
|
|
221
|
+
const draftEnabled = draftNode['@odata.draft.enabled'];
|
|
222
|
+
if (draftEnabled) {
|
|
221
223
|
error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
|
|
222
224
|
}
|
|
223
225
|
// Ignore composition if it's target is not part of a service or explicitly draft disabled
|
|
224
|
-
|
|
226
|
+
// Only for explicit `false` annotation value, not for `undefined` or `null`.
|
|
227
|
+
else if (!getServiceName(elem.target) || draftEnabled === false) {
|
|
225
228
|
return;
|
|
226
229
|
}
|
|
227
230
|
else {
|
|
@@ -247,7 +250,7 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
247
250
|
* After draft decoration, all visited artifacts are supposed to have the draft state elements
|
|
248
251
|
* Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
|
|
249
252
|
* in the core compiler builtins) needs to be translated into $self.<postfix>.
|
|
250
|
-
*
|
|
253
|
+
*
|
|
251
254
|
* It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
|
|
252
255
|
* some $draft path expressions.
|
|
253
256
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { isBetaEnabled } = require('../base/model');
|
|
4
4
|
const transformUtils = require('./transformUtils');
|
|
5
5
|
const { forEachDefinition,
|
|
6
6
|
forEachMemberRecursively,
|
|
@@ -28,6 +28,7 @@ const { addTenantFields } = require('./addTenantFields');
|
|
|
28
28
|
const { addLocalizationViews } = require('./localized');
|
|
29
29
|
const { cloneFullCsn } = require('../model/cloneCsn');
|
|
30
30
|
const { csnRefs } = require('../model/csnRefs');
|
|
31
|
+
const replaceForeignKeyRefsInExpressionAnnotations = require('./odata/foreignKeyRefsInXprAnnos');
|
|
31
32
|
|
|
32
33
|
// Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
|
|
33
34
|
// The result should be suitable for consumption by EDMX processors (annotations and metadata)
|
|
@@ -129,11 +130,9 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
129
130
|
if (options.tenantDiscriminator)
|
|
130
131
|
addTenantFields(csn, options);
|
|
131
132
|
|
|
132
|
-
const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');
|
|
133
|
-
|
|
134
133
|
function acceptLocalizedView(_name, parent) {
|
|
135
134
|
csn.definitions[parent].$localized = true;
|
|
136
|
-
return
|
|
135
|
+
return false; // don't keep the views
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
|
|
@@ -192,6 +191,12 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
192
191
|
|
|
193
192
|
createForeignKeyElements(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
194
193
|
|
|
194
|
+
// needs to be performed after creating foreign keys for the entire model,
|
|
195
|
+
// because of multiple managed associations in refs
|
|
196
|
+
replaceForeignKeyRefsInExpressionAnnotations(csn, options, messageFunctions, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
197
|
+
|
|
198
|
+
bindCsnReferenceOnly();
|
|
199
|
+
|
|
195
200
|
if (!structuredOData) {
|
|
196
201
|
const resolved = new WeakMap();
|
|
197
202
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
@@ -465,7 +470,7 @@ function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
|
465
470
|
// a cardinality property is set to the association member
|
|
466
471
|
// with the value { "min": 1 };
|
|
467
472
|
function setCardinalityToNotNullAssociations(member) {
|
|
468
|
-
if (member.target &&
|
|
473
|
+
if (member.target && !member.on) {
|
|
469
474
|
if (member.notNull) {
|
|
470
475
|
if (member.cardinality === undefined)
|
|
471
476
|
member.cardinality = {};
|