@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -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 +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- 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/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/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- 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 +33 -14
- 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 +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -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 +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- 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 +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- 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 +35 -12
- 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 +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- 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 +94 -801
- 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 +10 -27
- 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 +2 -1
- 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 -2340
- package/lib/compiler/resolver.js +0 -2988
- 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
|
}
|
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
|
|
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
|
|
|
@@ -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',
|
|
@@ -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,9 +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
10
|
const { builtinLocation } = require('../base/location');
|
|
6
|
-
const { setProp } = require('./utils');
|
|
11
|
+
const { setLink: setProp } = require('./utils');
|
|
7
12
|
|
|
8
13
|
const core = {
|
|
9
14
|
String: { parameters: [ 'length' ], category: 'string' },
|
|
@@ -56,7 +61,6 @@ const coreHana = {
|
|
|
56
61
|
* (do not add more - make it part of the SQL renderer to remove parentheses for
|
|
57
62
|
* other funny SQL functions like CURRENT_UTCTIMESTAMP).
|
|
58
63
|
*/
|
|
59
|
-
|
|
60
64
|
const functionsWithoutParens = [
|
|
61
65
|
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
|
|
62
66
|
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
|
|
@@ -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
|
/**
|
|
@@ -214,9 +219,6 @@ function initBuiltins( model ) {
|
|
|
214
219
|
model.$builtins.hana = hana;
|
|
215
220
|
cds._subArtifacts.hana = hana;
|
|
216
221
|
env( coreHana, 'cds.hana.', hana );
|
|
217
|
-
// namespace:"localized" stores localized convenience views ---
|
|
218
|
-
const localized = createNamespace( 'localized', true );
|
|
219
|
-
model.definitions.localized = localized;
|
|
220
222
|
model.$internal = { $frontend: '$internal' };
|
|
221
223
|
return;
|
|
222
224
|
|
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
|
*
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
'use strict';
|
|
18
18
|
|
|
19
|
-
const { setProp } = require('
|
|
19
|
+
const { setLink: setProp } = require('./utils'); // check enum/non-enum
|
|
20
20
|
|
|
21
21
|
// Detect cyclic dependencies between all nodes reachable from `definitions`.
|
|
22
22
|
// If such a dependency is found, call `reportCycle` with arguments `dep.art`
|