@sap/cds-compiler 3.6.2 → 3.8.0
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 +109 -1
- package/README.md +3 -0
- package/bin/cdsc.js +12 -5
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +35 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +63 -23
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +15 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +120 -34
- package/lib/base/messages.js +51 -27
- package/lib/base/model.js +4 -2
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +15 -9
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +5 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +42 -26
- package/lib/compiler/base.js +50 -4
- package/lib/compiler/builtins.js +17 -8
- package/lib/compiler/checks.js +241 -246
- package/lib/compiler/define.js +113 -146
- package/lib/compiler/extend.js +889 -383
- package/lib/compiler/finalize-parse-cdl.js +5 -58
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +7 -8
- package/lib/compiler/populate.js +297 -293
- package/lib/compiler/propagator.js +27 -18
- package/lib/compiler/resolve.js +146 -463
- package/lib/compiler/shared.js +36 -79
- package/lib/compiler/tweak-assocs.js +30 -28
- package/lib/compiler/utils.js +31 -5
- package/lib/edm/annotations/genericTranslation.js +131 -59
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +22 -5
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +42 -26
- package/lib/gen/Dictionary.json +38 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4828 -4472
- package/lib/inspect/inspectPropagation.js +20 -34
- package/lib/json/from-csn.js +140 -44
- package/lib/json/to-csn.js +114 -122
- package/lib/language/errorStrategy.js +2 -0
- package/lib/language/genericAntlrParser.js +156 -36
- package/lib/language/language.g4 +100 -58
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +15 -3
- package/lib/model/csnUtils.js +12 -74
- package/lib/model/revealInternalProperties.js +4 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +216 -104
- package/lib/render/toHdbcds.js +2 -9
- package/lib/render/toRename.js +14 -51
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +600 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +58 -41
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +16 -8
- package/lib/transform/transformUtilsNew.js +42 -14
- package/lib/transform/translateAssocsToJoins.js +60 -37
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
4
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
5
4
|
// not relevant for odata - entities need to be checked at the end of the transformation
|
|
5
|
+
|
|
6
|
+
const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
7
|
+
|
|
6
8
|
/**
|
|
7
9
|
* Ensure that empty/only virtual entities do not reach the db.
|
|
8
10
|
*
|
|
@@ -11,22 +13,24 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
|
11
13
|
* @param {string} prop Property being looped over
|
|
12
14
|
* @param {CSN.Path} path Path to the artifact
|
|
13
15
|
*/
|
|
14
|
-
function
|
|
16
|
+
function validateHasPersistedElements( artifact, artifactName, prop, path ) {
|
|
15
17
|
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
|
|
16
18
|
if (!artifact.elements || !hasRealElements(artifact.elements))
|
|
17
|
-
|
|
19
|
+
// TODO: Maybe check if there are only calc elements and adapt the message?
|
|
20
|
+
this.error('def-missing-element', path, { '#': ( artifact.query || artifact.projection ) ? 'view' : 'std' });
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
* Check if the provided elements contain elements that will be created on the
|
|
25
|
+
* Check if the provided elements contain elements that will be created on the database.
|
|
26
|
+
* This includes virtual and calculated elements.
|
|
23
27
|
*
|
|
24
28
|
* @param {CSN.Elements} elements Elements to look through
|
|
25
29
|
* @returns {boolean} True if something would be created on the db from these elements.
|
|
26
30
|
*/
|
|
27
31
|
function hasRealElements( elements ) {
|
|
28
32
|
for (const element of Object.values(elements)) {
|
|
29
|
-
if (!element.virtual) {
|
|
33
|
+
if (!element.virtual && !element.value) {
|
|
30
34
|
if (element.elements) {
|
|
31
35
|
if (hasRealElements(element.elements))
|
|
32
36
|
return true;
|
|
@@ -41,4 +45,4 @@ function hasRealElements( elements ) {
|
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
|
|
44
|
-
module.exports =
|
|
48
|
+
module.exports = validateHasPersistedElements;
|
|
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression( parent, name, expression ) {
|
|
|
21
21
|
if (_art) {
|
|
22
22
|
_art = resolveArtifactType.call(this, _art);
|
|
23
23
|
// Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
|
|
24
|
-
if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
|
|
24
|
+
if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && ($scope !== '$self' || $scope === '$self' && ref.length > 1)) { // TODO: Use $self to navigate to struct
|
|
25
25
|
this.error('ref-unexpected-structured',
|
|
26
26
|
name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
|
|
27
27
|
{ '#': 'std', elemref: { ref } } );
|
|
@@ -83,16 +83,18 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
83
83
|
if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
|
|
84
84
|
if (_links[j].art.on) {
|
|
85
85
|
// It's an unmanaged association - traversal is always forbidden
|
|
86
|
-
this.error(
|
|
86
|
+
this.error('ref-unexpected-navigation', csnPath, { '#': 'unmanaged', id, elemref });
|
|
87
87
|
}
|
|
88
88
|
else {
|
|
89
89
|
// It's a managed association - access of the foreign keys is allowed
|
|
90
90
|
const nextRef = ref[j + 1].id || ref[j + 1];
|
|
91
|
-
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
|
|
92
|
-
this.error(
|
|
91
|
+
if (!_links[j].art.keys.some(r => r.ref[0] === nextRef)) {
|
|
92
|
+
this.error('ref-unexpected-navigation', csnPath, {
|
|
93
|
+
'#': 'std', id, elemref, name: nextRef,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
}
|
|
95
|
-
|
|
96
98
|
if (_links[j].art.virtual)
|
|
97
99
|
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
|
|
98
100
|
|
|
@@ -113,11 +115,11 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
113
115
|
// 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
|
|
114
116
|
|
|
115
117
|
// If this path ends structured or on an association, perform the check:
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
if (
|
|
119
|
+
((type.target && type.keys || type.elements) && validStructuredElement ||
|
|
120
|
+
(type.target && validDollarSelf)) && !type.virtual
|
|
121
|
+
) {
|
|
122
|
+
// Do nothing - handled by lib/checks/nonexpandableStructured.js
|
|
121
123
|
}
|
|
122
124
|
else if (type.items && !type.virtual) {
|
|
123
125
|
this.error(null, onPath, { elemref: { ref } },
|
|
@@ -127,6 +129,10 @@ function validateOnCondition( member, memberName, property, path ) {
|
|
|
127
129
|
this.error(null, onPath, { elemref: { ref } },
|
|
128
130
|
'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
|
|
129
131
|
}
|
|
132
|
+
else if (type.on) {
|
|
133
|
+
// Path leaf is an unmanaged association, can't use an unmanaged assoc as operand
|
|
134
|
+
this.error('ref-unexpected-navigation', onPath, { '#': 'unmanagedleaf', id: logReady(ref[ref.length - 1]), elemref: { ref } });
|
|
135
|
+
}
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
}
|
|
@@ -18,7 +18,7 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
|
|
|
18
18
|
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
|
|
19
19
|
|
|
20
20
|
if (member['@sql.append']) {
|
|
21
|
-
if (this.artifact.query)
|
|
21
|
+
if (this.artifact.query || this.artifact.projection)
|
|
22
22
|
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
|
|
23
23
|
else if (this.csnUtils.isStructured(member))
|
|
24
24
|
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
|
|
@@ -57,7 +57,7 @@ function checkSqlAnnotationOnArtifact( artifact, artifactName ) {
|
|
|
57
57
|
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
|
|
58
58
|
}
|
|
59
59
|
else if (artifact['@sql.prepend']) {
|
|
60
|
-
if (artifact.query)
|
|
60
|
+
if (artifact.query || artifact.projection)
|
|
61
61
|
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
|
|
62
62
|
else
|
|
63
63
|
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
package/lib/checks/types.js
CHANGED
|
@@ -51,7 +51,11 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
|
|
|
51
51
|
// Computed elements, e.g. "1+1 as foo" in a view don't have a valid type and
|
|
52
52
|
// are skipped here. References to such columns are checked further below.
|
|
53
53
|
const parent = this.csn.definitions[path[1]];
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
// should only happen with csn input, not in cdl
|
|
56
|
+
// calculated elements may not have a .type (requires beta flag)
|
|
57
|
+
if (!member.value &&
|
|
58
|
+
!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
|
|
55
59
|
errorAboutMissingType(this.error, path, memberName, true);
|
|
56
60
|
return;
|
|
57
61
|
}
|
package/lib/checks/validator.js
CHANGED
|
@@ -12,7 +12,7 @@ const { validateSelectItems } = require('./selectItems');
|
|
|
12
12
|
const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
|
|
13
13
|
const validateCdsPersistenceAnnotation = require('./cdsPersistence');
|
|
14
14
|
const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
|
|
15
|
-
const
|
|
15
|
+
const validateHasPersistedElements = require('./hasPersistedElements');
|
|
16
16
|
const checkForHanaTypes = require('./checkForTypes');
|
|
17
17
|
const checkForParams = require('./parameters');
|
|
18
18
|
// forOdata
|
|
@@ -30,7 +30,8 @@ const {
|
|
|
30
30
|
checkTypeIsScalar, checkDecimalScale,
|
|
31
31
|
} = require('./types');
|
|
32
32
|
const {
|
|
33
|
-
checkPrimaryKey, checkVirtualElement, checkManagedAssoc,
|
|
33
|
+
checkPrimaryKey, checkVirtualElement, checkManagedAssoc,
|
|
34
|
+
checkRecursiveTypeUsage, rejectAnnotationsOnCalcElement,
|
|
34
35
|
} = require('./elements');
|
|
35
36
|
const checkForInvalidTarget = require('./invalidTarget');
|
|
36
37
|
const { validateAssociationsInItems } = require('./arrayOfs');
|
|
@@ -54,6 +55,8 @@ const forRelationalDBMemberValidators
|
|
|
54
55
|
warnAboutDefaultOnAssociationForHanaCds,
|
|
55
56
|
// sql.prepend/append
|
|
56
57
|
checkSqlAnnotationOnElement,
|
|
58
|
+
// no temporal annotations on calc elements
|
|
59
|
+
rejectAnnotationsOnCalcElement,
|
|
57
60
|
];
|
|
58
61
|
|
|
59
62
|
const forRelationalDBArtifactValidators
|
|
@@ -61,7 +64,7 @@ const forRelationalDBArtifactValidators
|
|
|
61
64
|
// @cds.persistence has no impact on odata
|
|
62
65
|
validateCdsPersistenceAnnotation,
|
|
63
66
|
// virtual items are not persisted on the db
|
|
64
|
-
|
|
67
|
+
validateHasPersistedElements,
|
|
65
68
|
// sql.prepend/append
|
|
66
69
|
checkSqlAnnotationOnArtifact,
|
|
67
70
|
];
|
|
@@ -254,6 +257,7 @@ function forOdata( csn, that ) {
|
|
|
254
257
|
}
|
|
255
258
|
}
|
|
256
259
|
),
|
|
260
|
+
// eslint-disable-next-line sonarjs/no-empty-collection
|
|
257
261
|
forOdataQueryValidators.concat(commonQueryValidators),
|
|
258
262
|
{
|
|
259
263
|
skipArtifact: this.isExternalServiceMember,
|
|
@@ -72,7 +72,7 @@ const { locationString, hasErrors } = require('../base/messages');
|
|
|
72
72
|
// Properties that can appear where a type can have type arguments.
|
|
73
73
|
const typeProperties = [
|
|
74
74
|
'type', '$typeArgs', 'length', 'precision', 'scale', 'srid',
|
|
75
|
-
'_effectiveType',
|
|
75
|
+
'_effectiveType', '$effectiveSeqNo',
|
|
76
76
|
];
|
|
77
77
|
|
|
78
78
|
class InternalConsistencyError extends Error {
|
|
@@ -191,8 +191,8 @@ function assertConsistency( model, stage ) {
|
|
|
191
191
|
test: isDictionary( definition ),
|
|
192
192
|
requires: [ 'kind', 'name' ],
|
|
193
193
|
optional: [
|
|
194
|
-
'elements', '$autoElement', '$uncheckedElements',
|
|
195
|
-
'$requireElementAccess', '_effectiveType', '_deps',
|
|
194
|
+
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
195
|
+
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
196
196
|
],
|
|
197
197
|
schema: {
|
|
198
198
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -250,18 +250,17 @@ function assertConsistency( model, stage ) {
|
|
|
250
250
|
schema: { args: { inherits: 'query', test: isArray( query ) } },
|
|
251
251
|
requires: [ 'op', 'location', 'args' ],
|
|
252
252
|
optional: [
|
|
253
|
-
'quantifier', 'orderBy', 'limit', '
|
|
254
|
-
'
|
|
253
|
+
'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
|
|
254
|
+
'_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM
|
|
255
255
|
],
|
|
256
256
|
},
|
|
257
257
|
select: { // sub query
|
|
258
258
|
requires: [ 'op', 'location', 'from' ],
|
|
259
259
|
optional: [
|
|
260
260
|
'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
|
|
261
|
-
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
262
|
-
'_projections', '
|
|
261
|
+
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '_origin', '_block',
|
|
262
|
+
'_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
|
|
263
263
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
264
|
-
'_extension', // for unapplied extensions
|
|
265
264
|
],
|
|
266
265
|
},
|
|
267
266
|
none: { optional: () => true }, // parse error
|
|
@@ -285,7 +284,7 @@ function assertConsistency( model, stage ) {
|
|
|
285
284
|
'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
|
|
286
285
|
'$parens', '_status', // TODO: only in from
|
|
287
286
|
'scope', '_artifact', '$inferred', 'kind',
|
|
288
|
-
'_effectiveType', // TODO:check this
|
|
287
|
+
'_effectiveType', '$effectiveSeqNo', // TODO:check this
|
|
289
288
|
'$duplicates', // In JOIN if both sides are the same.
|
|
290
289
|
],
|
|
291
290
|
},
|
|
@@ -293,9 +292,10 @@ function assertConsistency( model, stage ) {
|
|
|
293
292
|
requires: [ 'query', 'location' ],
|
|
294
293
|
optional: [
|
|
295
294
|
'$parens',
|
|
296
|
-
'kind', 'name', '_block', '_parent', '_main',
|
|
297
|
-
'_effectiveType', '
|
|
295
|
+
'kind', 'name', '_block', '_parent', '_main', 'elements',
|
|
296
|
+
'_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
|
|
298
297
|
'$duplicates', // duplicate query in FROM clause
|
|
298
|
+
'$inferred', // table alias with $inferred: '$internal'
|
|
299
299
|
],
|
|
300
300
|
},
|
|
301
301
|
none: { optional: () => true }, // parse error
|
|
@@ -340,7 +340,7 @@ function assertConsistency( model, stage ) {
|
|
|
340
340
|
optional: [
|
|
341
341
|
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
342
342
|
'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
|
|
343
|
-
'_effectiveType',
|
|
343
|
+
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
|
|
344
344
|
],
|
|
345
345
|
},
|
|
346
346
|
target: {
|
|
@@ -373,7 +373,7 @@ function assertConsistency( model, stage ) {
|
|
|
373
373
|
// required to be set by Core Compiler even with parse errors
|
|
374
374
|
test: isString,
|
|
375
375
|
enum: [
|
|
376
|
-
'context', 'service', 'entity', 'type', 'aspect', '
|
|
376
|
+
'context', 'service', 'entity', 'type', 'aspect', 'annotation',
|
|
377
377
|
'element', 'enum', 'action', 'function', 'param', 'key', 'event',
|
|
378
378
|
'annotate', 'extend', '$column',
|
|
379
379
|
'select', '$join', 'mixin',
|
|
@@ -389,14 +389,18 @@ function assertConsistency( model, stage ) {
|
|
|
389
389
|
kind: [ 'entity', 'view', 'type', 'aspect' ],
|
|
390
390
|
test: isString, // CSN parser should check for 'entity', 'view', 'projection'
|
|
391
391
|
},
|
|
392
|
+
$tokenTexts: {
|
|
393
|
+
parser: true,
|
|
394
|
+
test: isString,
|
|
395
|
+
},
|
|
392
396
|
value: {
|
|
393
397
|
optional: [
|
|
394
398
|
'location', '$inferred', 'sort', 'nulls',
|
|
395
399
|
'param', 'scope', // for dynamic parameter '?'
|
|
396
|
-
//
|
|
397
|
-
|
|
398
|
-
//
|
|
399
|
-
'
|
|
400
|
+
// A2J wrongly propagates the following into a CAST of the CSN passed to compileX:
|
|
401
|
+
'elements', 'items', 'enum',
|
|
402
|
+
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
403
|
+
'args', 'op', 'func', 'suffix',
|
|
400
404
|
],
|
|
401
405
|
|
|
402
406
|
kind: true,
|
|
@@ -446,6 +450,8 @@ function assertConsistency( model, stage ) {
|
|
|
446
450
|
requires: [ 'location' ],
|
|
447
451
|
optional: [
|
|
448
452
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
453
|
+
// expressions as annotation values
|
|
454
|
+
'$tokenTexts', 'op', 'args', 'func',
|
|
449
455
|
],
|
|
450
456
|
// TODO: restrict path to #simplePath
|
|
451
457
|
},
|
|
@@ -473,7 +479,13 @@ function assertConsistency( model, stage ) {
|
|
|
473
479
|
'@': {
|
|
474
480
|
kind: true,
|
|
475
481
|
inherits: 'value',
|
|
476
|
-
optional: [
|
|
482
|
+
optional: [
|
|
483
|
+
'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
|
|
484
|
+
// annotation values
|
|
485
|
+
'$tokenTexts',
|
|
486
|
+
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
487
|
+
'args', 'op', 'func', 'suffix',
|
|
488
|
+
],
|
|
477
489
|
// TODO: name requires if not in parser?
|
|
478
490
|
},
|
|
479
491
|
$priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
|
|
@@ -497,7 +509,7 @@ function assertConsistency( model, stage ) {
|
|
|
497
509
|
action: { test: isString },
|
|
498
510
|
param: { test: TODO },
|
|
499
511
|
alias: { test: isString },
|
|
500
|
-
expectedKind: { kind: [ 'extend' ],
|
|
512
|
+
expectedKind: { kind: [ 'extend' ], test: locationVal( isString ) },
|
|
501
513
|
virtual: { kind: true, test: locationVal() },
|
|
502
514
|
key: { kind: true, test: locationVal(), also: [ null, undefined ] },
|
|
503
515
|
masked: { kind: true, test: locationVal() },
|
|
@@ -515,9 +527,9 @@ function assertConsistency( model, stage ) {
|
|
|
515
527
|
optional: [
|
|
516
528
|
'enum',
|
|
517
529
|
'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
|
|
518
|
-
'_outer', '_effectiveType', 'notNull', '_parent',
|
|
530
|
+
'_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
|
|
519
531
|
'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
|
|
520
|
-
'$syntax',
|
|
532
|
+
'$syntax', '_extensions',
|
|
521
533
|
'_status', '_redirected',
|
|
522
534
|
...typeProperties,
|
|
523
535
|
],
|
|
@@ -550,6 +562,7 @@ function assertConsistency( model, stage ) {
|
|
|
550
562
|
_artifact: { test: TODO },
|
|
551
563
|
_navigation: { test: TODO },
|
|
552
564
|
_effectiveType: { kind: true, test: TODO },
|
|
565
|
+
$effectiveSeqNo: { kind: true, test: isNumber },
|
|
553
566
|
_joinParent: { test: TODO },
|
|
554
567
|
$joinArgsIndex: { test: isNumber },
|
|
555
568
|
_outer: { test: TODO }, // for returns/items
|
|
@@ -565,24 +578,25 @@ function assertConsistency( model, stage ) {
|
|
|
565
578
|
'$tableAliases', '$inlines',
|
|
566
579
|
],
|
|
567
580
|
optional: [
|
|
568
|
-
'_effectiveType', '$parens',
|
|
581
|
+
'_effectiveType', '$effectiveSeqNo', '$parens',
|
|
569
582
|
'_deps', '$expand',
|
|
570
583
|
// query specific
|
|
571
584
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
572
585
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
573
|
-
'limit', '_status',
|
|
574
|
-
'_extension', // for unapplied extensions
|
|
586
|
+
'limit', '_status', '_origin', '_effectiveType', '$effectiveSeqNo',
|
|
575
587
|
],
|
|
576
588
|
},
|
|
577
589
|
_leadingQuery: { kind: true, test: TODO },
|
|
578
590
|
$replacement: { kind: true, test: TODO }, // for smart * in queries
|
|
579
|
-
_origin: { kind:
|
|
591
|
+
_origin: { kind: true, test: TODO },
|
|
592
|
+
_calcOrigin: { kind: true, test: TODO },
|
|
580
593
|
_pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
|
|
581
|
-
_from: { kind: true, test: TODO }, //
|
|
594
|
+
_from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
|
|
582
595
|
// array of $tableAlias (or includes) for explicit and implicit redirection:
|
|
583
596
|
_redirected: { kind: true, test: TODO },
|
|
584
597
|
// ...array of table aliases for targets from orig to new
|
|
585
598
|
_$next: { kind: true, test: TODO }, // next lexical search environment for values
|
|
599
|
+
_extensions: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
586
600
|
_extend: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
587
601
|
_annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
588
602
|
_extension: { kind: true, test: TODO }, // on artifact to its "super extend/annotate" statement
|
|
@@ -612,6 +626,7 @@ function assertConsistency( model, stage ) {
|
|
|
612
626
|
parser: true,
|
|
613
627
|
kind: true,
|
|
614
628
|
test: isOneOf([
|
|
629
|
+
'', // constructed “super annotate” statement
|
|
615
630
|
// Uppercase values are used in logic, lowercase value are "just for us", i.e.
|
|
616
631
|
// debugging or to add properties such as $generated in Universal CSN.
|
|
617
632
|
// However, that is no longer true. For example, `autoexposed` is used in populate.js
|
|
@@ -623,6 +638,7 @@ function assertConsistency( model, stage ) {
|
|
|
623
638
|
|
|
624
639
|
'$autoElement', // for magicVars: $user is automatically changed to $user.id
|
|
625
640
|
'$generated', // compiler generated annotations, e.g. @Core.Computed
|
|
641
|
+
'$internal', // compiler internal; must not reach CSN output
|
|
626
642
|
'*', // inferred from query wildcard
|
|
627
643
|
'as', // query alias name
|
|
628
644
|
'aspect-composition',
|
package/lib/compiler/base.js
CHANGED
|
@@ -29,28 +29,31 @@ const kindProperties = {
|
|
|
29
29
|
type: { elements: propExists, enum: propExists, include: true },
|
|
30
30
|
aspect: { elements: propExists, actions: true, include: true },
|
|
31
31
|
annotation: { elements: propExists, enum: propExists },
|
|
32
|
-
enum: { normalized: 'element' },
|
|
32
|
+
enum: { normalized: 'element', dict: 'enum' },
|
|
33
33
|
element: { elements: propExists, enum: propExists, dict: 'elements' },
|
|
34
34
|
mixin: { normalized: 'alias' },
|
|
35
35
|
action: {
|
|
36
36
|
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
|
|
37
37
|
}, // no extend params, only annotate
|
|
38
38
|
function: {
|
|
39
|
-
params: () => false,
|
|
39
|
+
params: () => false,
|
|
40
|
+
elements: () => false,
|
|
41
|
+
enum: () => false,
|
|
42
|
+
normalized: 'action',
|
|
43
|
+
dict: 'actions',
|
|
40
44
|
}, // no extend params, only annotate
|
|
41
45
|
key: { normalized: 'element' },
|
|
42
46
|
param: { elements: () => false, enum: () => false, dict: 'params' },
|
|
43
47
|
source: { artifacts: true }, // TODO -> $source
|
|
44
48
|
using: {},
|
|
45
49
|
extend: {
|
|
46
|
-
isExtension: true,
|
|
47
50
|
noDep: 'special',
|
|
48
51
|
elements: true, /* only for parse-cdl */
|
|
49
52
|
actions: true, /* only for parse-cdl */
|
|
50
53
|
enum: true, /* only for parse-cdl */
|
|
51
54
|
},
|
|
52
55
|
annotate: {
|
|
53
|
-
|
|
56
|
+
noDep: 'special', elements: true, enum: true, actions: true, params: true,
|
|
54
57
|
},
|
|
55
58
|
builtin: {}, // = CURRENT_DATE, TODO: improve
|
|
56
59
|
$parameters: {}, // $parameters in query entities
|
|
@@ -61,7 +64,50 @@ function propExists( prop, parent ) {
|
|
|
61
64
|
return (obj.items || obj.targetAspect || obj)[prop];
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
// Return the "old style" name structure with `absolute`, `action`, `param`,
|
|
68
|
+
// `element`. Later we also need to add `select` and `alias`.
|
|
69
|
+
// (Currently not needed, as only used for extend and annotate statements.)
|
|
70
|
+
function getArtifactName( art ) {
|
|
71
|
+
if (!art.name || art.name.absolute)
|
|
72
|
+
return art.name;
|
|
73
|
+
// extend and annotate statement already have "sparse" names → calculate old one
|
|
74
|
+
const link = art.name._artifact;
|
|
75
|
+
const namePath = [];
|
|
76
|
+
while (art._main && !art.name.absolute) { // until we hit an old-style name or the main artifact
|
|
77
|
+
namePath.push( art );
|
|
78
|
+
art = art._parent;
|
|
79
|
+
}
|
|
80
|
+
namePath.reverse();
|
|
81
|
+
const name = { ...art.name };
|
|
82
|
+
for (const np of namePath) {
|
|
83
|
+
const prop = getMemberNameProp( np, np.kind );
|
|
84
|
+
name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
|
|
85
|
+
name.id = np.name.id;
|
|
86
|
+
name.location = np.name.location;
|
|
87
|
+
}
|
|
88
|
+
if (link !== undefined)
|
|
89
|
+
Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// TODO: probably store this prop in name
|
|
94
|
+
function getMemberNameProp( elem, kind ) {
|
|
95
|
+
if (kind !== 'annotate' && kind !== 'extend')
|
|
96
|
+
return kindProperties[kind]?.normalized || kind;
|
|
97
|
+
let obj = elem._parent;
|
|
98
|
+
if (obj.params || obj.returns)
|
|
99
|
+
return 'param';
|
|
100
|
+
if (obj.actions)
|
|
101
|
+
return 'action';
|
|
102
|
+
while (obj.items)
|
|
103
|
+
obj = obj.items;
|
|
104
|
+
if (obj.elements || obj.enum)
|
|
105
|
+
return 'element';
|
|
106
|
+
return 'id';
|
|
107
|
+
}
|
|
108
|
+
|
|
64
109
|
module.exports = {
|
|
65
110
|
dictKinds,
|
|
66
111
|
kindProperties,
|
|
112
|
+
getArtifactName,
|
|
67
113
|
};
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const { builtinLocation } = require('../base/location');
|
|
11
11
|
const { setLink: setProp } = require('./utils');
|
|
12
12
|
|
|
13
|
+
// TODO: make type parameters a dict
|
|
13
14
|
const core = {
|
|
14
15
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
15
16
|
LargeString: { category: 'string' },
|
|
@@ -199,7 +200,16 @@ const magicVariables = {
|
|
|
199
200
|
},
|
|
200
201
|
};
|
|
201
202
|
|
|
202
|
-
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP
|
|
203
|
+
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
|
|
204
|
+
|
|
205
|
+
const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
|
|
206
|
+
// YYYY - MM - dd
|
|
207
|
+
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
208
|
+
// T HH : mm : ss TZD
|
|
209
|
+
// eslint-disable-next-line max-len
|
|
210
|
+
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
211
|
+
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
212
|
+
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
|
|
203
213
|
|
|
204
214
|
/**
|
|
205
215
|
* Patterns for literal token tests and creation. The value is a map from the
|
|
@@ -227,7 +237,7 @@ const quotedLiteralPatterns = {
|
|
|
227
237
|
test_variant: 'time',
|
|
228
238
|
test_fn: (x) => {
|
|
229
239
|
// Leading `T` allowed in ISO 8601.
|
|
230
|
-
const match = x.match(
|
|
240
|
+
const match = x.match( timeRegEx );
|
|
231
241
|
return match !== null && checkTime( match[1], match[2], match[3] );
|
|
232
242
|
},
|
|
233
243
|
json_type: 'string',
|
|
@@ -235,7 +245,7 @@ const quotedLiteralPatterns = {
|
|
|
235
245
|
date: {
|
|
236
246
|
test_variant: 'date',
|
|
237
247
|
test_fn: (x) => {
|
|
238
|
-
const match = x.match(
|
|
248
|
+
const match = x.match( dateRegEx );
|
|
239
249
|
return match !== null && checkDate( match[1], match[2], match[3] );
|
|
240
250
|
},
|
|
241
251
|
json_type: 'string',
|
|
@@ -243,8 +253,7 @@ const quotedLiteralPatterns = {
|
|
|
243
253
|
timestamp: {
|
|
244
254
|
test_variant: 'timestamp',
|
|
245
255
|
test_fn: (x) => {
|
|
246
|
-
|
|
247
|
-
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
|
|
256
|
+
const match = x.match( timestampRegEx );
|
|
248
257
|
return match !== null && checkDate( match[1], match[2], match[3] ) &&
|
|
249
258
|
checkTime( match[4], match[5], match[6] );
|
|
250
259
|
},
|
|
@@ -259,7 +268,7 @@ const quotedLiteralPatterns = {
|
|
|
259
268
|
},
|
|
260
269
|
number: {
|
|
261
270
|
test_variant: 'number',
|
|
262
|
-
test_fn: (x =>
|
|
271
|
+
test_fn: (x => numberRegEx.test( x )),
|
|
263
272
|
json_type: 'number',
|
|
264
273
|
secondary_json_type: 'string',
|
|
265
274
|
},
|
|
@@ -447,11 +456,11 @@ function initBuiltins( model ) {
|
|
|
447
456
|
const absolute = prefix + name;
|
|
448
457
|
// TODO: reconsider whether to set a type to itself - looks wrong
|
|
449
458
|
const art = {
|
|
450
|
-
kind: 'type', builtin: true, name: { absolute },
|
|
459
|
+
kind: 'type', builtin: true, name: { absolute },
|
|
451
460
|
};
|
|
452
|
-
setProp( art.type, '_artifact', art );
|
|
453
461
|
if (parent)
|
|
454
462
|
parent._subArtifacts[name] = art;
|
|
463
|
+
setProp( art, '_origin', '' );
|
|
455
464
|
setProp( art, '_effectiveType', art );
|
|
456
465
|
setProp( art, '_deps', [] );
|
|
457
466
|
Object.assign( art, builtins[name] );
|