@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
function validateCdsPersistenceAnnotation(artifact, artifactName, prop, path) {
|
|
14
14
|
if (artifact.kind === 'entity') {
|
|
15
15
|
// filter for 'table', 'udf', 'calcview' === true
|
|
16
|
-
const
|
|
16
|
+
const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
|
|
17
|
+
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
|
|
17
18
|
if (TableUdfCv.length > 1)
|
|
18
19
|
this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);
|
|
19
20
|
}
|
|
@@ -12,9 +12,9 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
|
|
|
12
12
|
* @param {CSN.Path} path Path to the artifact
|
|
13
13
|
*/
|
|
14
14
|
function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
|
|
15
|
-
if (artifact.kind === 'entity' &&
|
|
15
|
+
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
|
|
16
16
|
if (!artifact.elements || !hasRealElements(artifact.elements))
|
|
17
|
-
this.error(
|
|
17
|
+
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
package/lib/checks/enricher.js
CHANGED
|
@@ -30,6 +30,7 @@ function enrichCsn( csn ) {
|
|
|
30
30
|
type: simpleRef,
|
|
31
31
|
target: simpleRef,
|
|
32
32
|
includes: simpleRef,
|
|
33
|
+
columns,
|
|
33
34
|
// Annotations are ignored.
|
|
34
35
|
'@': () => { /* ignore annotations */ },
|
|
35
36
|
};
|
|
@@ -40,7 +41,7 @@ function enrichCsn( csn ) {
|
|
|
40
41
|
cleanupCallbacks = [];
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
const { inspectRef, artifactRef } = csnRefs( csn );
|
|
44
|
+
const { inspectRef, artifactRef, getElement } = csnRefs( csn );
|
|
44
45
|
const csnPath = [];
|
|
45
46
|
if (csn.definitions)
|
|
46
47
|
dictionary( csn, 'definitions', csn.definitions );
|
|
@@ -82,6 +83,21 @@ function enrichCsn( csn ) {
|
|
|
82
83
|
csnPath.pop();
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
87
|
+
function columns( parent, prop, node ) {
|
|
88
|
+
// Establish the link relationships
|
|
89
|
+
parent[prop].forEach((column) => {
|
|
90
|
+
const element = getElement(column);
|
|
91
|
+
if (element) {
|
|
92
|
+
setProp(column, '_element', element);
|
|
93
|
+
cleanupCallbacks.push(() => delete column._element);
|
|
94
|
+
setProp(element, '_column', column);
|
|
95
|
+
cleanupCallbacks.push(() => delete element._column);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
standard(parent, prop, node);
|
|
99
|
+
}
|
|
100
|
+
|
|
85
101
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
86
102
|
function simpleRef( node, prop ) {
|
|
87
103
|
setProp(node, '$path', [ ...csnPath ]);
|
|
@@ -18,12 +18,12 @@ function validateForeignKeys(member) {
|
|
|
18
18
|
|
|
19
19
|
// Declared as arrow-function to keep scope the same (this value)
|
|
20
20
|
const handleAssociation = (mem) => {
|
|
21
|
-
for (
|
|
22
|
-
if (
|
|
23
|
-
if (!
|
|
21
|
+
for (const key of mem.keys) {
|
|
22
|
+
if (key.ref) {
|
|
23
|
+
if (!key._art)
|
|
24
24
|
continue;
|
|
25
25
|
// eslint-disable-next-line no-use-before-define
|
|
26
|
-
checkForItems(
|
|
26
|
+
checkForItems(key._art);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
};
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
4
|
|
|
5
|
+
const { ModelError } = require('../base/error');
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Assert that targets of associations and compositions are entities.
|
|
7
9
|
*
|
|
@@ -22,7 +24,7 @@ function invalidTarget(member) {
|
|
|
22
24
|
if (mem.target) {
|
|
23
25
|
const target = this.csn.definitions[mem.target];
|
|
24
26
|
if (!target)
|
|
25
|
-
throw new
|
|
27
|
+
throw new ModelError(`Expected target ${ mem.target }`);
|
|
26
28
|
if (target.kind !== 'entity') {
|
|
27
29
|
const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';
|
|
28
30
|
this.error(
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
function checkUsedTypesForAnonymousAspectComposition(member) {
|
|
14
14
|
// Declared as arrow-function to keep scope the same (this value)
|
|
15
15
|
const handleAssociation = (mem, fn) => {
|
|
16
|
-
for (
|
|
17
|
-
if (
|
|
18
|
-
if (!
|
|
16
|
+
for (const key of mem.keys) {
|
|
17
|
+
if (key.ref) {
|
|
18
|
+
if (!key._art)
|
|
19
19
|
continue;
|
|
20
|
-
fn(
|
|
20
|
+
fn(key._art);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
const { ModelError } = require('../base/error');
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Trigger a recompilation in case of an association without .keys and without .on
|
|
6
8
|
*
|
|
@@ -10,7 +12,7 @@
|
|
|
10
12
|
*/
|
|
11
13
|
function managedWithoutKeys(member, memberName, prop) {
|
|
12
14
|
if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
|
|
13
|
-
throw new
|
|
15
|
+
throw new ModelError('Expected association to have either an on-condition or foreign keys.');
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -124,10 +124,8 @@ function checkQueryForNoDBArtifacts(query) {
|
|
|
124
124
|
for (const prop of generalQueryProperties) {
|
|
125
125
|
const queryPart = (query.SELECT || query.SET)[prop];
|
|
126
126
|
if (Array.isArray(queryPart)) {
|
|
127
|
-
for (
|
|
128
|
-
const part = queryPart[i];
|
|
127
|
+
for (const part of queryPart)
|
|
129
128
|
checkRef(part, prop === 'columns');
|
|
130
|
-
}
|
|
131
129
|
}
|
|
132
130
|
else if (typeof queryPart === 'object') {
|
|
133
131
|
checkRef(queryPart, prop === 'columns');
|
|
@@ -29,7 +29,7 @@ function validateSelectItems(query) {
|
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
// .call() with 'this' to ensure we have access to the options
|
|
32
|
-
|
|
32
|
+
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
|
|
@@ -41,10 +41,10 @@ function validateSelectItems(query) {
|
|
|
41
41
|
* @param {CSN.Artifact} queryArtifact the query artifact which should be checked
|
|
42
42
|
* @param {CSN.Path} artifactPath the path to that artifact
|
|
43
43
|
*/
|
|
44
|
-
function
|
|
44
|
+
function rejectManagedAssociationsAndStructuresForHdbcdsNames(queryArtifact, artifactPath) {
|
|
45
45
|
if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
|
|
46
46
|
forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
|
|
47
|
-
if (this.csnUtils.
|
|
47
|
+
if (this.csnUtils.isManagedAssociation(selectItem))
|
|
48
48
|
this.error('query-unexpected-assoc-hdbcds', elementPath);
|
|
49
49
|
if (this.csnUtils.isStructured(selectItem))
|
|
50
50
|
this.error('query-unexpected-structure-hdbcds', elementPath);
|
|
@@ -52,4 +52,4 @@ function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, arti
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
module.exports = { validateSelectItems,
|
|
55
|
+
module.exports = { validateSelectItems, rejectManagedAssociationsAndStructuresForHdbcdsNames };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Only to be used with validator.js - a correct this value needs to be provided!
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check that @sql.prepend annotation is not used on any elements and @sql.append is not used on elements in views.
|
|
7
|
+
*
|
|
8
|
+
* @param {CSN.Element} member
|
|
9
|
+
* @param {string} memberName
|
|
10
|
+
* @param {string} prop
|
|
11
|
+
* @param {CSN.Path} path
|
|
12
|
+
* @returns {void}
|
|
13
|
+
*/
|
|
14
|
+
function checkSqlAnnotationOnElement(member, memberName, prop, path) {
|
|
15
|
+
if (member['@sql.replace'])
|
|
16
|
+
this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
17
|
+
if (member['@sql.prepend'])
|
|
18
|
+
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
|
|
19
|
+
|
|
20
|
+
if (member['@sql.append']) {
|
|
21
|
+
if (this.artifact.query)
|
|
22
|
+
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
|
|
23
|
+
else if (this.csnUtils.isStructured(member))
|
|
24
|
+
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
|
|
25
|
+
else
|
|
26
|
+
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {object} carrier element which has the annotation
|
|
32
|
+
* @param {string} annotation
|
|
33
|
+
* @param {CSN.Path} path
|
|
34
|
+
* @param {Function} error
|
|
35
|
+
* @param {CSN.Options} options
|
|
36
|
+
*/
|
|
37
|
+
function checkValidAnnoValue(carrier, annotation, path, error, options) {
|
|
38
|
+
if (carrier[annotation] !== undefined && carrier[annotation] !== null) {
|
|
39
|
+
if (typeof carrier[annotation] !== 'string')
|
|
40
|
+
error(null, path, { anno: annotation.slice(1), type: typeof carrier[annotation] }, `Annotation $(ANNO) must be a string, found $(TYPE)` );
|
|
41
|
+
else if (options.transformation === 'sql') // HDI and HDBCDS do their own checks
|
|
42
|
+
guardAgainstInjection(annotation, carrier[annotation], path, error);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check that @sql.prepend is not used on views - only supported for entities (tables)
|
|
48
|
+
*
|
|
49
|
+
* @param {CSN.Artifact} artifact
|
|
50
|
+
* @param {string} artifactName
|
|
51
|
+
*/
|
|
52
|
+
function checkSqlAnnotationOnArtifact(artifact, artifactName) {
|
|
53
|
+
if (artifact.kind !== 'entity') {
|
|
54
|
+
if (artifact['@sql.prepend'])
|
|
55
|
+
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, `Annotation $(NAME) can't be used on an artifact of kind $(KIND)` );
|
|
56
|
+
if (artifact['@sql.append'])
|
|
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
|
+
}
|
|
59
|
+
else if (artifact['@sql.prepend']) {
|
|
60
|
+
if (artifact.query)
|
|
61
|
+
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
|
|
62
|
+
else
|
|
63
|
+
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if (artifact['@sql.replace'])
|
|
68
|
+
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
|
|
69
|
+
|
|
70
|
+
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Anything that could terminate the "old" statement and start a new one basically.
|
|
74
|
+
const invalidInSnippet = [ ';', '--', '/*', '*/' ];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check that the common characters used to terminate the current statement and start a fresh one are not used.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} annoName
|
|
80
|
+
* @param {string} annoValue
|
|
81
|
+
* @param {CSN.Path} path
|
|
82
|
+
* @param {Function} error
|
|
83
|
+
*/
|
|
84
|
+
function guardAgainstInjection(annoName, annoValue, path, error) {
|
|
85
|
+
for (const invalid of invalidInSnippet) {
|
|
86
|
+
if (annoValue.indexOf(invalid) !== -1) // These should probably not be configurable, right?
|
|
87
|
+
error(null, path, { name: annoName, prop: invalid }, 'Annotation $(NAME) must not contain $(PROP)');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
checkSqlAnnotationOnArtifact,
|
|
93
|
+
checkSqlAnnotationOnElement,
|
|
94
|
+
};
|
package/lib/checks/types.js
CHANGED
|
@@ -19,7 +19,7 @@ function checkDecimalScale(member, memberName, prop, path) {
|
|
|
19
19
|
// skip is already filtered in validator, here for completeness
|
|
20
20
|
hasAnnotationValue(this.artifact, '@cds.persistence.skip'))
|
|
21
21
|
return;
|
|
22
|
-
if (member.scale &&
|
|
22
|
+
if (member.scale && (member.scale === 'variable' || member.scale === 'floating'))
|
|
23
23
|
this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -32,7 +32,7 @@ function unknownMagicVariable(parent, name, ref) {
|
|
|
32
32
|
const magicVariable = magicVariables[head];
|
|
33
33
|
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
|
|
34
34
|
getVariableReplacement(ref, this.options) === null)
|
|
35
|
-
this.error(
|
|
35
|
+
this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
package/lib/checks/validator.js
CHANGED
|
@@ -35,6 +35,10 @@ const checkExplicitlyNullableKeys = require('./nullableKeys');
|
|
|
35
35
|
const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
|
|
36
36
|
const unknownMagic = require('./unknownMagic');
|
|
37
37
|
const managedWithoutKeys = require('./managedWithoutKeys');
|
|
38
|
+
const {
|
|
39
|
+
checkSqlAnnotationOnArtifact,
|
|
40
|
+
checkSqlAnnotationOnElement,
|
|
41
|
+
} = require('./sql-snippets');
|
|
38
42
|
|
|
39
43
|
const forHanaMemberValidators
|
|
40
44
|
= [
|
|
@@ -45,6 +49,8 @@ const forHanaMemberValidators
|
|
|
45
49
|
checkExplicitlyNullableKeys,
|
|
46
50
|
managedWithoutKeys,
|
|
47
51
|
warnAboutDefaultOnAssociationForHanaCds,
|
|
52
|
+
// sql.prepend/append
|
|
53
|
+
checkSqlAnnotationOnElement,
|
|
48
54
|
];
|
|
49
55
|
|
|
50
56
|
const forHanaArtifactValidators
|
|
@@ -53,6 +59,8 @@ const forHanaArtifactValidators
|
|
|
53
59
|
validateCdsPersistenceAnnotation,
|
|
54
60
|
// virtual items are not persisted on the db
|
|
55
61
|
checkForEmptyOrOnlyVirtual,
|
|
62
|
+
// sql.prepend/append
|
|
63
|
+
checkSqlAnnotationOnArtifact,
|
|
56
64
|
];
|
|
57
65
|
|
|
58
66
|
const forHanaCsnValidators = [ nonexpandableStructuredInExpression, unknownMagic ];
|
|
@@ -121,7 +129,7 @@ function _validate(csn, that,
|
|
|
121
129
|
iterateOptions = {}) {
|
|
122
130
|
const { cleanup } = enrich(csn);
|
|
123
131
|
|
|
124
|
-
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [],
|
|
132
|
+
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
|
|
125
133
|
|
|
126
134
|
forEachDefinition(csn, (artifact, artifactName, prop, path) => {
|
|
127
135
|
artifactValidators.forEach((artifactValidator) => {
|
|
@@ -196,12 +204,9 @@ function forHana(csn, that) {
|
|
|
196
204
|
),
|
|
197
205
|
forHanaQueryValidators.concat(commonQueryValidators),
|
|
198
206
|
{
|
|
199
|
-
skipArtifact: artifact => artifact.abstract ||
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
'function',
|
|
203
|
-
'event',
|
|
204
|
-
],
|
|
207
|
+
skipArtifact: artifact => artifact.abstract ||
|
|
208
|
+
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
|
|
209
|
+
[ 'action', 'function', 'event' ].includes(artifact.kind),
|
|
205
210
|
});
|
|
206
211
|
}
|
|
207
212
|
|
|
@@ -249,7 +249,7 @@ function assertConsistency( model, stage ) {
|
|
|
249
249
|
'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
|
|
250
250
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
|
|
251
251
|
'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
|
|
252
|
-
'$tableAliases', 'kind', '_$next', '_combined', '$inlines',
|
|
252
|
+
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
253
253
|
],
|
|
254
254
|
},
|
|
255
255
|
none: { optional: () => true }, // parse error
|
|
@@ -298,6 +298,7 @@ function assertConsistency( model, stage ) {
|
|
|
298
298
|
},
|
|
299
299
|
expand: { kind: [ 'element' ], inherits: 'columns' },
|
|
300
300
|
inline: { kind: [ 'element' ], inherits: 'columns' },
|
|
301
|
+
$noOrigin: { kind: [ 'element' ], test: TODO },
|
|
301
302
|
excludingDict: {
|
|
302
303
|
kind: 'element',
|
|
303
304
|
test: isDictionary( definition ), // definition since redef
|
|
@@ -325,8 +326,8 @@ function assertConsistency( model, stage ) {
|
|
|
325
326
|
kind: true,
|
|
326
327
|
requires: [ 'location' ],
|
|
327
328
|
optional: [
|
|
328
|
-
'path', 'elements', '_outer',
|
|
329
|
-
'scope', '_artifact', '$inferred', '$expand',
|
|
329
|
+
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
|
|
330
|
+
'scope', '_artifact', '$inferred', '$expand', '$tableAliases', '_$next',
|
|
330
331
|
'_effectiveType', // by propagation
|
|
331
332
|
],
|
|
332
333
|
},
|
|
@@ -424,9 +425,12 @@ function assertConsistency( model, stage ) {
|
|
|
424
425
|
val: {
|
|
425
426
|
test: isVal, // the following for array/struct value
|
|
426
427
|
requires: [ 'location' ],
|
|
427
|
-
optional: [
|
|
428
|
+
optional: [
|
|
429
|
+
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicate', 'upTo',
|
|
430
|
+
],
|
|
428
431
|
// TODO: restrict path to #simplePath
|
|
429
432
|
},
|
|
433
|
+
upTo: { test: TODO },
|
|
430
434
|
struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
|
|
431
435
|
args: {
|
|
432
436
|
inherits: 'value',
|
|
@@ -542,7 +546,7 @@ function assertConsistency( model, stage ) {
|
|
|
542
546
|
// query specific
|
|
543
547
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
544
548
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
545
|
-
'limit',
|
|
549
|
+
'limit', '_status',
|
|
546
550
|
],
|
|
547
551
|
},
|
|
548
552
|
_leadingQuery: { kind: true, test: TODO },
|
|
@@ -638,7 +642,8 @@ function assertConsistency( model, stage ) {
|
|
|
638
642
|
* and `localized` namespaces.
|
|
639
643
|
*/
|
|
640
644
|
function builtin( node, parent, prop, spec, name ) {
|
|
641
|
-
|
|
645
|
+
const type = typeof node;
|
|
646
|
+
if (type !== 'string' && type !== 'boolean')
|
|
642
647
|
throw new Error(`Property '${ prop }' must be a boolean or string but was '${ typeof node }'${ at( [ node, parent ], prop, name ) }` );
|
|
643
648
|
|
|
644
649
|
if (parent.kind !== 'namespace')
|
|
@@ -792,8 +797,7 @@ function assertConsistency( model, stage ) {
|
|
|
792
797
|
}
|
|
793
798
|
|
|
794
799
|
function at( nodes, prop, name ) {
|
|
795
|
-
|
|
796
|
-
const n = name ? (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) : '';
|
|
800
|
+
const n = name && (typeof name === 'number' ? ` for index ${ name }` : ` for "${ name }"`) || '';
|
|
797
801
|
const loc = nodes.find( o => o && typeof o === 'object' && (o.location || o.start) );
|
|
798
802
|
const f = (prop) ? `${ n } in property '${ prop }'` : n;
|
|
799
803
|
const l = locationString( loc && loc.location || loc || model.location );
|
package/lib/compiler/base.js
CHANGED
package/lib/compiler/builtins.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// The builtin artifacts of CDS
|
|
2
2
|
|
|
3
|
+
// TODO: split this file
|
|
4
|
+
// - in base/: common definitions
|
|
5
|
+
// - in compiler/: XSN-specific
|
|
6
|
+
// - in ?: CSN-specific
|
|
7
|
+
|
|
3
8
|
'use strict';
|
|
4
9
|
|
|
5
|
-
const { forEachInDict } = require('../base/dictionaries');
|
|
6
10
|
const { builtinLocation } = require('../base/location');
|
|
7
|
-
const { setProp } = require('./utils');
|
|
11
|
+
const { setLink: setProp } = require('./utils');
|
|
8
12
|
|
|
9
13
|
const core = {
|
|
10
14
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
@@ -57,7 +61,6 @@ const coreHana = {
|
|
|
57
61
|
* (do not add more - make it part of the SQL renderer to remove parentheses for
|
|
58
62
|
* other funny SQL functions like CURRENT_UTCTIMESTAMP).
|
|
59
63
|
*/
|
|
60
|
-
|
|
61
64
|
const functionsWithoutParens = [
|
|
62
65
|
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
|
63
66
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
@@ -82,18 +85,19 @@ const specialFunctions = {
|
|
|
82
85
|
*/
|
|
83
86
|
const magicVariables = {
|
|
84
87
|
$user: {
|
|
88
|
+
// id and locale are always available
|
|
85
89
|
elements: { id: {}, locale: {} },
|
|
86
90
|
// Allow $user.<any>
|
|
87
91
|
$uncheckedElements: true,
|
|
88
92
|
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
|
|
89
93
|
$autoElement: 'id',
|
|
90
|
-
},
|
|
91
|
-
$at: {
|
|
94
|
+
},
|
|
95
|
+
$at: { // CDS-specific, not part of SQL
|
|
92
96
|
elements: {
|
|
93
97
|
from: {}, to: {},
|
|
94
98
|
},
|
|
95
99
|
},
|
|
96
|
-
$now: {},
|
|
100
|
+
$now: {}, // Dito
|
|
97
101
|
$session: {
|
|
98
102
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
99
103
|
// the pseudo variable $session.
|
|
@@ -176,7 +180,8 @@ function isRelationTypeName(typeName) {
|
|
|
176
180
|
function isInReservedNamespace(absolute) {
|
|
177
181
|
return absolute.startsWith( 'cds.') &&
|
|
178
182
|
!absolute.match(/^cds\.foundation(\.|$)/) &&
|
|
179
|
-
!absolute.match(/^cds\.outbox(\.|$)/)
|
|
183
|
+
!absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
|
|
184
|
+
!absolute.match(/^cds\.xt(\.|$)/); // Requested by Mtx
|
|
180
185
|
}
|
|
181
186
|
|
|
182
187
|
/**
|
|
@@ -199,6 +204,7 @@ function isBuiltinType(type) {
|
|
|
199
204
|
* @param {XSN.Model} model XSN model without CDS builtins
|
|
200
205
|
*/
|
|
201
206
|
function initBuiltins( model ) {
|
|
207
|
+
const { options } = model;
|
|
202
208
|
setMagicVariables( magicVariables );
|
|
203
209
|
// namespace:"cds" stores the builtins ---
|
|
204
210
|
const cds = createNamespace( 'cds', 'reserved' );
|
|
@@ -213,9 +219,6 @@ function initBuiltins( model ) {
|
|
|
213
219
|
model.$builtins.hana = hana;
|
|
214
220
|
cds._subArtifacts.hana = hana;
|
|
215
221
|
env( coreHana, 'cds.hana.', hana );
|
|
216
|
-
// namespace:"localized" stores localized convenience views ---
|
|
217
|
-
const localized = createNamespace( 'localized', true );
|
|
218
|
-
model.definitions.localized = localized;
|
|
219
222
|
model.$internal = { $frontend: '$internal' };
|
|
220
223
|
return;
|
|
221
224
|
|
|
@@ -266,27 +269,45 @@ function initBuiltins( model ) {
|
|
|
266
269
|
for (const name in builtins) {
|
|
267
270
|
const magic = builtins[name];
|
|
268
271
|
// TODO: rename to $builtinFunction
|
|
269
|
-
const art = { kind: 'builtin', name: {
|
|
272
|
+
const art = { kind: 'builtin', name: { element: name, id: name } };
|
|
270
273
|
artifacts[name] = art;
|
|
271
|
-
|
|
272
|
-
art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
|
|
274
|
+
|
|
273
275
|
if (magic.$autoElement)
|
|
274
276
|
art.$autoElement = magic.$autoElement;
|
|
275
277
|
if (magic.$uncheckedElements)
|
|
276
278
|
art.$uncheckedElements = magic.$uncheckedElements;
|
|
279
|
+
|
|
280
|
+
createMagicElements( art, magic.elements );
|
|
281
|
+
if (options.variableReplacements)
|
|
282
|
+
createMagicElements( art, options.variableReplacements[name] );
|
|
277
283
|
// setProp( art, '_effectiveType', art );
|
|
278
284
|
}
|
|
279
285
|
model.$magicVariables = { kind: '$magicVariables', artifacts };
|
|
280
286
|
}
|
|
281
287
|
|
|
282
|
-
function
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
288
|
+
function createMagicElements( art, elements ) {
|
|
289
|
+
if (!elements)
|
|
290
|
+
return;
|
|
291
|
+
|
|
292
|
+
const names = Object.keys(elements);
|
|
293
|
+
if (names.length > 0 && !art.elements)
|
|
294
|
+
art.elements = Object.create(null);
|
|
295
|
+
|
|
296
|
+
for (const n of names) {
|
|
297
|
+
const magic = {
|
|
298
|
+
kind: 'builtin',
|
|
299
|
+
name: { id: n, element: `${ art.name.element }.${ n }` },
|
|
300
|
+
};
|
|
301
|
+
// Propagate this property so that it is available for sub-elements.
|
|
302
|
+
if (art.$uncheckedElements)
|
|
303
|
+
magic.$uncheckedElements = art.$uncheckedElements;
|
|
304
|
+
setProp( magic, '_parent', art );
|
|
305
|
+
// setProp( magic, '_effectiveType', magic );
|
|
306
|
+
if (elements[n] && typeof elements[n] === 'object')
|
|
307
|
+
createMagicElements(magic, elements[n]);
|
|
308
|
+
|
|
309
|
+
art.elements[n] = magic;
|
|
310
|
+
}
|
|
290
311
|
}
|
|
291
312
|
}
|
|
292
313
|
|
package/lib/compiler/checks.js
CHANGED
|
@@ -87,6 +87,17 @@ function check( model ) { // = XSN
|
|
|
87
87
|
'Keyword “localized” may only be used in combination with string types');
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// "key" keyword at localized element in SELECT list.
|
|
91
|
+
// TODO: This check should be moved to localized.js
|
|
92
|
+
if (elem.key && elem.key.val && elem._main && elem._main.query) {
|
|
93
|
+
// original element is localized but not key, as that would have
|
|
94
|
+
// already resulted in a warning
|
|
95
|
+
if (elem._origin && elem._origin.localized && elem._origin.localized.val &&
|
|
96
|
+
( !elem._origin.key || !elem._origin.key.val)) {
|
|
97
|
+
warning('localized-key', [ elem.key.location, elem ], { keyword: 'localized' },
|
|
98
|
+
'Keyword $(KEYWORD) is ignored for primary keys');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
function checkQuery( query ) {
|
|
@@ -334,11 +345,17 @@ function check( model ) { // = XSN
|
|
|
334
345
|
// Max cardinalities must be a positive number or '*'
|
|
335
346
|
for (const prop of [ 'sourceMax', 'targetMax' ]) {
|
|
336
347
|
if (elem.cardinality[prop]) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
348
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
349
|
+
if (!(literal === 'number' && val > 0 ||
|
|
350
|
+
literal === 'string' && val === '*')) {
|
|
351
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
352
|
+
// eslint-disable-next-line max-len
|
|
353
|
+
std: 'Value $(CODE) is invalid for maximum cardinality, expecting a positive number or ‘*’',
|
|
354
|
+
// eslint-disable-next-line max-len
|
|
355
|
+
sourceMax: 'Value $(CODE) is invalid for maximum source cardinality, expecting a positive number or ‘*’',
|
|
356
|
+
// eslint-disable-next-line max-len
|
|
357
|
+
targetMax: 'Value $(CODE) is invalid for maximum target cardinality, expecting a positive number or ‘*’',
|
|
358
|
+
});
|
|
342
359
|
}
|
|
343
360
|
}
|
|
344
361
|
}
|
|
@@ -348,10 +365,16 @@ function check( model ) { // = XSN
|
|
|
348
365
|
// from-csn.json (expected non-negative number)
|
|
349
366
|
for (const prop of [ 'sourceMin', 'targetMin' ]) {
|
|
350
367
|
if (elem.cardinality[prop]) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
368
|
+
const { literal, val, location } = elem.cardinality[prop];
|
|
369
|
+
if (!(literal === 'number' && val >= 0)) {
|
|
370
|
+
error('invalid-cardinality', [ location, elem ], { '#': prop, code: val }, {
|
|
371
|
+
// eslint-disable-next-line max-len
|
|
372
|
+
std: 'Value $(CODE) is invalid for minimum cardinality, expecting a non-negative number',
|
|
373
|
+
// eslint-disable-next-line max-len
|
|
374
|
+
targetMin: 'Value $(CODE) is invalid for minimum target cardinality, expecting a non-negative number',
|
|
375
|
+
// eslint-disable-next-line max-len
|
|
376
|
+
sourceMin: 'Value $(CODE) is invalid for minimum source cardinality, expecting a non-negative number',
|
|
377
|
+
});
|
|
355
378
|
}
|
|
356
379
|
}
|
|
357
380
|
}
|
|
@@ -436,6 +459,10 @@ function check( model ) { // = XSN
|
|
|
436
459
|
* Check whether the argument count of the given path expression matches its artifact.
|
|
437
460
|
* If there is a mismatch, an error is issued.
|
|
438
461
|
*
|
|
462
|
+
* TODO: remove this function - it also checks for parameter in type
|
|
463
|
+
* references. We could have a warning, see also configurable errors
|
|
464
|
+
* 'args-no-params', 'args-undefined-param'.
|
|
465
|
+
*
|
|
439
466
|
* @param {object} pathStep The expression to check
|
|
440
467
|
*/
|
|
441
468
|
function checkPathForMissingArguments(pathStep) {
|
|
@@ -471,8 +498,15 @@ function check( model ) { // = XSN
|
|
|
471
498
|
|
|
472
499
|
const missingArgs = [];
|
|
473
500
|
for (const fAName in formalArgs) {
|
|
474
|
-
if (!actualArgs[fAName]
|
|
475
|
-
|
|
501
|
+
if (!actualArgs[fAName]) {
|
|
502
|
+
// Note: _effectiveType points to cds.String for `type T : DefaultString`.
|
|
503
|
+
// And `default` may appear at any `type` in the hierarchy.
|
|
504
|
+
let fArg = formalArgs[fAName];
|
|
505
|
+
while (fArg.type && !fArg.default)
|
|
506
|
+
fArg = fArg.type._artifact;
|
|
507
|
+
if (!fArg.default)
|
|
508
|
+
missingArgs.push(fAName);
|
|
509
|
+
}
|
|
476
510
|
}
|
|
477
511
|
|
|
478
512
|
if (missingArgs.length) {
|
|
@@ -612,7 +646,7 @@ function check( model ) { // = XSN
|
|
|
612
646
|
return checkTreeLikeExpression(xpr, allowAssocTail);
|
|
613
647
|
}
|
|
614
648
|
/**
|
|
615
|
-
* Check
|
|
649
|
+
* Check whether the supplied argument is a virtual element
|
|
616
650
|
*
|
|
617
651
|
* TO CLARIFY: do we want the "no virtual element" check for virtual elements/columns, too?
|
|
618
652
|
*
|