@sap/cds-compiler 5.8.2 → 5.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/bin/cds_remove_invalid_whitespace.js +5 -3
- package/bin/cds_update_identifiers.js +9 -6
- package/bin/cdsc.js +79 -59
- package/bin/cdsse.js +14 -10
- package/bin/cdsv2m.js +3 -1
- package/lib/api/options.js +28 -6
- package/lib/base/message-registry.js +15 -4
- package/lib/checks/validator.js +3 -0
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/checks.js +70 -50
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/generate.js +8 -2
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/lsp-api.js +1 -1
- package/lib/compiler/propagator.js +2 -2
- package/lib/compiler/resolve.js +78 -31
- package/lib/compiler/shared.js +3 -3
- package/lib/compiler/tweak-assocs.js +1 -1
- package/lib/compiler/utils.js +10 -0
- package/lib/compiler/xpr-rewrite.js +1 -1
- package/lib/edm/annotations/edmJson.js +42 -39
- package/lib/edm/annotations/genericTranslation.js +55 -55
- package/lib/edm/annotations/preprocessAnnotations.js +5 -5
- package/lib/edm/csn2edm.js +16 -16
- package/lib/edm/edm.js +62 -62
- package/lib/edm/edmAnnoPreprocessor.js +2 -2
- package/lib/edm/edmInboundChecks.js +1 -1
- package/lib/edm/edmPreprocessor.js +32 -32
- package/lib/edm/edmUtils.js +8 -8
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +77 -81
- package/lib/gen/Dictionary.json +3062 -3072
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1238 -1236
- package/lib/json/from-csn.js +1 -1
- package/lib/json/to-csn.js +30 -3
- package/lib/language/genericAntlrParser.js +16 -0
- package/lib/main.d.ts +79 -1
- package/lib/model/csnRefs.js +12 -5
- package/lib/model/xprAsTree.js +71 -0
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +46 -32
- package/lib/parsers/CdlGrammar.g4 +33 -28
- package/lib/parsers/Lexer.js +1 -1
- package/lib/parsers/XprTree.js +25 -16
- package/lib/render/toCdl.js +902 -414
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +8 -0
- package/lib/render/utils/common.js +2 -2
- package/lib/render/utils/operators.js +160 -0
- package/lib/render/utils/pretty.js +337 -0
- package/lib/sql-identifier.js +7 -9
- package/lib/transform/addTenantFields.js +39 -41
- package/lib/transform/db/applyTransformations.js +4 -4
- package/lib/transform/db/assertUnique.js +6 -5
- package/lib/transform/db/associations.js +3 -3
- package/lib/transform/db/assocsToQueries/transformExists.js +13 -13
- package/lib/transform/db/assocsToQueries/utils.js +8 -0
- package/lib/transform/db/backlinks.js +19 -14
- package/lib/transform/db/constraints.js +6 -6
- package/lib/transform/db/expansion.js +1 -1
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +1 -1
- package/lib/transform/db/processSqlServices.js +3 -3
- package/lib/transform/db/rewriteCalculatedElements.js +2 -2
- package/lib/transform/db/temporal.js +7 -9
- package/lib/transform/db/views.js +6 -6
- package/lib/transform/draft/odata.js +2 -0
- package/lib/transform/effective/annotations.js +1 -1
- package/lib/transform/effective/associations.js +1 -1
- package/lib/transform/effective/main.js +1 -0
- package/lib/transform/effective/service.js +2 -2
- package/lib/transform/forRelationalDB.js +11 -5
- package/lib/transform/localized.js +2 -0
- package/lib/transform/odata/adaptAnnotationRefs.js +10 -9
- package/lib/transform/parseExpr.js +2 -2
- package/lib/transform/transformUtils.js +9 -7
- package/lib/transform/translateAssocsToJoins.js +0 -2
- package/lib/transform/universalCsn/coreComputed.js +2 -2
- package/lib/utils/moduleResolve.js +7 -5
- package/package.json +1 -1
- package/share/messages/def-upcoming-virtual-change.md +55 -0
- package/share/messages/file-unexpected-case-mismatch.md +61 -0
- package/share/messages/message-explanations.json +2 -0
- package/lib/transform/braceExpression.js +0 -77
|
@@ -167,14 +167,17 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
167
167
|
if(csn.meta?.[featureFlags]?.$calculatedElements)
|
|
168
168
|
rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);
|
|
169
169
|
|
|
170
|
+
timetrace.start('Where-Exists handling');
|
|
170
171
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
171
172
|
handleExists(csn, options, error, csnUtils.inspectRef, csnUtils.initDefinition, csnUtils.dropDefinitionCache);
|
|
173
|
+
timetrace.stop('Where-Exists handling');
|
|
172
174
|
|
|
173
175
|
// Check if structured elements and managed associations are compared in an expression
|
|
174
176
|
// and expand these structured elements. This tuple expansion allows all other
|
|
175
177
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
176
178
|
// If errors are detected, throwWithAnyError() will return from further processing
|
|
177
179
|
|
|
180
|
+
timetrace.start('Expand Structures (expressions + refs)');
|
|
178
181
|
// If this function is ever undefined, we have a bug in our logic.
|
|
179
182
|
// @ts-ignore
|
|
180
183
|
expandStructsInExpression(csn, { drillRef: true });
|
|
@@ -194,6 +197,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
194
197
|
bindCsnReference();
|
|
195
198
|
}
|
|
196
199
|
|
|
200
|
+
timetrace.stop('Expand Structures (expressions + refs)');
|
|
197
201
|
|
|
198
202
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
199
203
|
applyTransformations(csn, {
|
|
@@ -211,6 +215,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
211
215
|
|
|
212
216
|
bindCsnReferenceOnly();
|
|
213
217
|
|
|
218
|
+
timetrace.start('Flattening (refs + elements)');
|
|
214
219
|
// TODO: Instead of 3 separate applyTransformations, we could have each of them just return the "listeners", merge them into
|
|
215
220
|
// one big listener that then gets passed into one single applyTransformations. Each listener would then have to return an array of callbacks to call.
|
|
216
221
|
// With that, we could still ensure the processing order (assuming we don't run into problems with scoping).
|
|
@@ -229,6 +234,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
229
234
|
// For to.hdbcds with naming mode hdbcds we also need to resolve the types
|
|
230
235
|
flattening.resolveTypeReferences(csn, options, messageFunctions, new WeakMap(), pathDelimiter);
|
|
231
236
|
}
|
|
237
|
+
timetrace.stop('Flattening (refs + elements)');
|
|
232
238
|
|
|
233
239
|
// With flattening errors, it makes little sense to continue.
|
|
234
240
|
throwWithAnyError();
|
|
@@ -635,6 +641,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
635
641
|
}
|
|
636
642
|
|
|
637
643
|
function handleAssocToJoins() {
|
|
644
|
+
timetrace.start('A2J');
|
|
638
645
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
639
646
|
// simply make it invisible and copy it over to the result csn
|
|
640
647
|
forEachDefinition(csn, (art) => {
|
|
@@ -650,18 +657,18 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
650
657
|
|
|
651
658
|
// restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
|
|
652
659
|
forEachDefinition(csn, (art, artName) => {
|
|
653
|
-
if(art['$tableConstraints']) {
|
|
660
|
+
if (art['$tableConstraints']) {
|
|
654
661
|
newCsn.definitions[artName].$tableConstraints = art['$tableConstraints'];
|
|
655
662
|
}
|
|
656
663
|
if (art.technicalConfig)
|
|
657
664
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
658
|
-
|
|
659
665
|
});
|
|
660
666
|
|
|
661
667
|
// To ensure we preserve feature flags
|
|
662
668
|
newCsn.meta = csn.meta;
|
|
663
669
|
|
|
664
670
|
csn = newCsn;
|
|
671
|
+
timetrace.stop('A2J');
|
|
665
672
|
}
|
|
666
673
|
|
|
667
674
|
|
|
@@ -768,9 +775,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
768
775
|
* Check that required actual parameters on 'node.type' are set, that their values are in the correct range etc.
|
|
769
776
|
|
|
770
777
|
* @param {*} node
|
|
771
|
-
* @param {
|
|
772
|
-
* @param {
|
|
773
|
-
* @param {*} path
|
|
778
|
+
* @param {CSN.Artifact} artifact
|
|
779
|
+
* @param {CSN.Path} path
|
|
774
780
|
*/
|
|
775
781
|
function checkTypeParameters(node, artifact, path) {
|
|
776
782
|
if (node.type && !node.virtual) {
|
|
@@ -93,6 +93,8 @@ const annoPersistenceSkip = '@cds.persistence.skip';
|
|
|
93
93
|
* @param {boolean} [options.fewerLocalizedViews]
|
|
94
94
|
* Default: true
|
|
95
95
|
*
|
|
96
|
+
* @param {boolean} [options.testMode]
|
|
97
|
+
*
|
|
96
98
|
* @param {object} config
|
|
97
99
|
* Configuration for creating convenience views. Non-user visible options.
|
|
98
100
|
*
|
|
@@ -8,20 +8,21 @@ const { transformAnnotationExpression, implicitAs, } = require('../../model/csnU
|
|
|
8
8
|
* key declared in the scope, we replace it with referencing the foreign key itself. If a reference is a $self reference,
|
|
9
9
|
* we do nothing and if a ref points to a structure/managed association, an error is thrown
|
|
10
10
|
*
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {object} csnUtils
|
|
13
|
-
* @param {object} messageFunctions
|
|
11
|
+
* @param {object[]|string[][]} generatedForeignKeys
|
|
12
|
+
* @param {object} csnUtils
|
|
13
|
+
* @param {object} messageFunctions
|
|
14
|
+
* @param {CSN.Path} elementPath
|
|
14
15
|
*/
|
|
15
16
|
function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, elementPath) {
|
|
16
|
-
if(Array.isArray(generatedForeignKeys
|
|
17
|
+
if(Array.isArray(generatedForeignKeys?.[0])) {
|
|
17
18
|
// ensure we are always called with an array of objects. TODO: Cleanup fk creation in for.effective to create array of objects
|
|
18
|
-
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
|
|
19
|
+
adaptAnnotationsRefs(remapToArrayOfObjects(generatedForeignKeys), csnUtils, { error }, elementPath);
|
|
19
20
|
} else {
|
|
20
21
|
const reportedErrorsForAnnoPath = {};
|
|
21
22
|
generatedForeignKeys.forEach((gfk, index) => {
|
|
22
23
|
Object.entries(gfk.foreignKey).forEach(([key, value]) => {
|
|
23
24
|
if (key[0] !== '@') return;
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
transformAnnotationExpression(gfk.foreignKey, key, {
|
|
26
27
|
ref: (_parent, _prop, ref, path, _p, _ppn, ctx) => {
|
|
27
28
|
// if the reference is a $self reference, we do nothing,
|
|
@@ -30,8 +31,8 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
30
31
|
// if annotation was not propagated from the keys array during foreign keys creation,
|
|
31
32
|
// means that it is not a candidate for foreign key substitution
|
|
32
33
|
if (gfk.keyAnnotations !== null && !gfk.keyAnnotations.includes(key)) return;
|
|
33
|
-
|
|
34
|
-
const art = gfk.originalKey._art ||
|
|
34
|
+
|
|
35
|
+
const art = gfk.originalKey._art ||
|
|
35
36
|
csnUtils.inspectRef(elementPath ? path : getOriginatingKeyPath(gfk, path)).art; // OData uses getOriginatingKeyPath - as it relies on $path
|
|
36
37
|
if (csnUtils.isManagedAssociation(art)) {
|
|
37
38
|
if (!reportedErrorsForAnnoPath[path]) {
|
|
@@ -42,7 +43,7 @@ function adaptAnnotationsRefs(generatedForeignKeys, csnUtils, { error }, element
|
|
|
42
43
|
const gfkForRef = findGeneratedForeignKeyForKeyRef(generatedForeignKeys, ref);
|
|
43
44
|
if (gfkForRef.length === 1) {
|
|
44
45
|
ref[0] = gfkForRef[0].prefix;
|
|
45
|
-
|
|
46
|
+
|
|
46
47
|
if (ctx?.annoExpr?.['=']) {
|
|
47
48
|
ctx.annoExpr['='] = true;
|
|
48
49
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Multiplication/Division: '*', '/'
|
|
13
13
|
* Addition/Subtraction: '+', '-'
|
|
14
14
|
* Concatenation: '||'
|
|
15
|
-
* Relational: '=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in', 'exists', 'between and'
|
|
15
|
+
* Relational: '=', '<>', '>', '>=', '<', '<=', '==', '!=', 'like', 'in', 'exists', 'between and'
|
|
16
16
|
* Unary: 'is [not] null', 'not'
|
|
17
17
|
* Conditional: 'case [when then]+ [else]? end', 'and', 'or'
|
|
18
18
|
*
|
|
@@ -245,7 +245,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
return binaryExpr(xpr, (xpr, s, e) => {
|
|
248
|
-
const token = ['=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in'];
|
|
248
|
+
const token = ['=', '<>', '>', '>=', '<', '<=', '==', '!=', 'like', 'in'];
|
|
249
249
|
while(s < e && !token.includes(xpr[s])) s++;
|
|
250
250
|
if(s < e) {
|
|
251
251
|
if(xpr[s-1] === 'not' && (xpr[s] === 'in' || xpr[s] === 'like'))
|
|
@@ -16,7 +16,7 @@ const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
|
|
|
16
16
|
const { addTenantFieldToArt } = require('./addTenantFields');
|
|
17
17
|
|
|
18
18
|
const RestrictedOperators = ['<', '>', '>=', '<='];
|
|
19
|
-
const RelationalOperators = ['=', '
|
|
19
|
+
const RelationalOperators = ['=', '<>', '==', '!=', 'is' /*, 'like'*/,...RestrictedOperators];
|
|
20
20
|
// Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
|
|
21
21
|
// Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
|
|
22
22
|
// 'model' is compacted new style CSN
|
|
@@ -196,7 +196,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
196
196
|
'@odata.Collation': 1,
|
|
197
197
|
'@odata.Unicode': 1,
|
|
198
198
|
} : {};
|
|
199
|
-
|
|
199
|
+
|
|
200
200
|
copyAnnotations(elem, flatElem, false, excludes);
|
|
201
201
|
|
|
202
202
|
// Copy selected type properties
|
|
@@ -220,15 +220,15 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
220
220
|
* [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
|
|
221
221
|
* [ (Entity), (struct1_struct2_assoc), (elem) ]
|
|
222
222
|
*
|
|
223
|
-
* @param {
|
|
223
|
+
* @param {CSN.Ref} ref
|
|
224
224
|
* @param {CSN.Path} path CSN path to the ref
|
|
225
225
|
* @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
|
|
226
226
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
227
227
|
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
228
|
-
* @param {
|
|
229
|
-
* @param {
|
|
230
|
-
* @param {
|
|
231
|
-
* @param {
|
|
228
|
+
* @param {boolean} [suspend] suspend flattening by caller until association path step
|
|
229
|
+
* @param {number} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
|
|
230
|
+
* @param {boolean} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
|
|
231
|
+
* @param {boolean} [flattenParameters] Whether to flatten references into structured parameters. OData flattens parameters, SQL/for.effective does not.
|
|
232
232
|
*
|
|
233
233
|
* @todo: Refactor to take config object instead of N boolean arguments.
|
|
234
234
|
* @returns [string[], bool]
|
|
@@ -1028,6 +1028,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1028
1028
|
Flattening stops on all non-structured types.
|
|
1029
1029
|
*/
|
|
1030
1030
|
function expand(expr, location) {
|
|
1031
|
+
if (!Array.isArray(expr))
|
|
1032
|
+
return expr; // don't traverse strings, etc.
|
|
1031
1033
|
const rc = [];
|
|
1032
1034
|
for(let i = 0; i < expr.length; i++)
|
|
1033
1035
|
{
|
|
@@ -25,7 +25,6 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
25
25
|
timetrace.start('A2J: Translating associations to joins');
|
|
26
26
|
translateAssocsToJoins(model, options);
|
|
27
27
|
timetrace.stop('A2J: Translating associations to joins');
|
|
28
|
-
timetrace.start('A2J: Post-processing columns');
|
|
29
28
|
// Use the effective elements list as columns
|
|
30
29
|
forEachDefinition(model, art => {
|
|
31
30
|
if (art.$queries) {
|
|
@@ -40,7 +39,6 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
});
|
|
43
|
-
timetrace.stop('A2J: Post-processing columns');
|
|
44
42
|
|
|
45
43
|
if (options.messages) {
|
|
46
44
|
// Make sure that we don't complain twice about the same things
|
|
@@ -81,7 +81,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
81
81
|
const origin = getOrigin(element);
|
|
82
82
|
if (origin)
|
|
83
83
|
return origin;
|
|
84
|
-
throw new CompilerAssertion(`Could not find ancestor for ${JSON.stringify(element)} named ${name}`);
|
|
84
|
+
throw new CompilerAssertion(`Could not find ancestor for ${ JSON.stringify(element) } named ${ name }`);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
@@ -114,7 +114,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
114
114
|
return checkJoinSources(base.args, name);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
throw new CompilerAssertion(`Element “${name}” not found in: ${JSON.stringify(base)}`);
|
|
117
|
+
throw new CompilerAssertion(`Element “${ name }” not found in: ${ JSON.stringify(base) }`);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
/**
|
|
@@ -567,9 +567,9 @@ function packageCdsMain( pkg ) {
|
|
|
567
567
|
* @param {object} dep
|
|
568
568
|
* @param {string} realpath
|
|
569
569
|
* @param {string} nativeRealpath
|
|
570
|
-
* @param {
|
|
570
|
+
* @param {object} messageFunctions
|
|
571
571
|
*/
|
|
572
|
-
function checkFileCase( dep, realpath, nativeRealpath,
|
|
572
|
+
function checkFileCase( dep, realpath, nativeRealpath, messageFunctions ) {
|
|
573
573
|
if (realpath === nativeRealpath)
|
|
574
574
|
return;
|
|
575
575
|
if (realpath.toLowerCase() !== nativeRealpath.toLowerCase()) {
|
|
@@ -578,6 +578,7 @@ function checkFileCase( dep, realpath, nativeRealpath, { warning } ) {
|
|
|
578
578
|
return;
|
|
579
579
|
}
|
|
580
580
|
for (const using of dep.usingFroms) {
|
|
581
|
+
const { warning } = messageFunctions;
|
|
581
582
|
warning('file-unexpected-case-mismatch', [ using.location, using ], {},
|
|
582
583
|
// eslint-disable-next-line @stylistic/js/max-len
|
|
583
584
|
'The imported filename differs on the filesystem; ensure that capitalization matches the actual file\'s name');
|
|
@@ -589,9 +590,10 @@ function checkFileCase( dep, realpath, nativeRealpath, { warning } ) {
|
|
|
589
590
|
* @property {string[]} lookupDirs
|
|
590
591
|
* Directories to look in for modules, e.g. node_modules/.
|
|
591
592
|
* @property {string[]} extensions
|
|
592
|
-
* @property {(path: string, callback: (err, foundAndIsFile) => void) => void} isFile
|
|
593
|
-
* @property {(path: string, encoding, callback:
|
|
594
|
-
*
|
|
593
|
+
* @property {(path: string, callback: (err, foundAndIsFile: boolean) => void) => void} isFile
|
|
594
|
+
* @property {(path: string, encoding: string, callback:
|
|
595
|
+
* (err, content: string) => void) => void} readFile
|
|
596
|
+
* @property {(path: string, callback: (err, realpath: string) => void) => void} realpath
|
|
595
597
|
* used to read `package.json` files.
|
|
596
598
|
*/
|
|
597
599
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# def-upcoming-virtual-change
|
|
2
|
+
|
|
3
|
+
The behavior of `@sap/cds-compiler` v6 will change for a selected element.
|
|
4
|
+
|
|
5
|
+
## Example
|
|
6
|
+
|
|
7
|
+
Erroneous code example:
|
|
8
|
+
|
|
9
|
+
```cds
|
|
10
|
+
entity Source {
|
|
11
|
+
key ID : String;
|
|
12
|
+
a : String;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
entity Proj as projection on Source {
|
|
16
|
+
ID,
|
|
17
|
+
virtual a, // ❌ behavior will change in v6
|
|
18
|
+
};
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
In `@sap/cds-compiler` v5 and earlier, element `Proj:a` is a reference
|
|
22
|
+
to element `Source:a`, which was marked virtual.
|
|
23
|
+
|
|
24
|
+
In `@sap/cds-compiler` v6 and later, it will instead of a _new_ element,
|
|
25
|
+
without any reference to `Source:a`.
|
|
26
|
+
|
|
27
|
+
This may or may not affect your runtime coding, hence the warning.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## How to Fix
|
|
31
|
+
|
|
32
|
+
If the v6 behavior works for you, there is nothing you need to do.
|
|
33
|
+
|
|
34
|
+
However, if you want to keep a reference to `Source:a` in CSN, for example
|
|
35
|
+
because you use the reference at runtime, then you can keep the old behavior
|
|
36
|
+
by either:
|
|
37
|
+
|
|
38
|
+
1. prepending a table alias to the reference
|
|
39
|
+
2. adding a column alias
|
|
40
|
+
|
|
41
|
+
```cds
|
|
42
|
+
// (1) prepend a table alias
|
|
43
|
+
entity V as projection on E {
|
|
44
|
+
ID,
|
|
45
|
+
virtual E.a, // ok
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```cds
|
|
50
|
+
// (2) add an alias
|
|
51
|
+
entity V as projection on E {
|
|
52
|
+
ID,
|
|
53
|
+
virtual a as a, // ok
|
|
54
|
+
};
|
|
55
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# file-unexpected-case-mismatch
|
|
2
|
+
|
|
3
|
+
The filename of a `using` statement does not match
|
|
4
|
+
the file's actual name on disk.
|
|
5
|
+
|
|
6
|
+
To avoid operating-system dependent issues, the compiler checks if the name of
|
|
7
|
+
an imported file matches the name of the file in the filesystem / on disk.
|
|
8
|
+
For example, by default macOS uses a case-insensitive file system.
|
|
9
|
+
Hence, a file named `model.cds` will also be loaded by `using from './Model.cds'`
|
|
10
|
+
on such systems.
|
|
11
|
+
|
|
12
|
+
However, on other filesystems that are case-sensitive, e.g. when building your
|
|
13
|
+
application in another environment, the file will not be found.
|
|
14
|
+
|
|
15
|
+
Hence, the `using` statement needs to be adapted.
|
|
16
|
+
|
|
17
|
+
## Example
|
|
18
|
+
|
|
19
|
+
Erroneous code example:
|
|
20
|
+
|
|
21
|
+
```cds
|
|
22
|
+
// index.cds
|
|
23
|
+
using from './Model';
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
using following directory tree:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
├── index.cds
|
|
30
|
+
└── model.cds
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
On case-insensitive systems, the file can be loaded, but the compiler will warn
|
|
34
|
+
about the mismatch.
|
|
35
|
+
On case-sensitive file systems, compilation will fail, as the imported file
|
|
36
|
+
can't be found.
|
|
37
|
+
|
|
38
|
+
While in this case, compilation will fail on case-sensitive systems, it could
|
|
39
|
+
instead end up with semantic changes, too.
|
|
40
|
+
Given the same `index.cds`, but a different directory tree:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
├── index.cds
|
|
44
|
+
├── Model
|
|
45
|
+
│ └── index.cds
|
|
46
|
+
└── model.cds
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
On case-sensitive systems, `./Model/index.cds` will be loaded.
|
|
50
|
+
On case-insensitive systems, however, `model.cds` will be loaded,
|
|
51
|
+
as the compiler first tries to load `Model.cds`, before looking for
|
|
52
|
+
`Model/index.cds`.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## How to Fix
|
|
56
|
+
|
|
57
|
+
Adapt the filename in your `using` statement.
|
|
58
|
+
|
|
59
|
+
If you have both `model.cds` and `Model/index.cds`, but don't want to use
|
|
60
|
+
a `.cds` suffix, use `using from './Model/'`, i.e. add a trailing slash to
|
|
61
|
+
indicate that you want to load from the folder `Model`.
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
"check-proper-type-of",
|
|
7
7
|
"def-duplicate-autoexposed",
|
|
8
8
|
"def-missing-type",
|
|
9
|
+
"def-upcoming-virtual-change",
|
|
9
10
|
"extend-repeated-intralayer",
|
|
10
11
|
"extend-unrelated-layer",
|
|
12
|
+
"file-unexpected-case-mismatch",
|
|
11
13
|
"redirected-to-ambiguous",
|
|
12
14
|
"redirected-to-complex",
|
|
13
15
|
"redirected-to-unrelated",
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// Currently unused, but may become useful again if HDBCDS -> HDBTABLE
|
|
2
|
-
// handover becomes more prominent. Historically used with the no longer
|
|
3
|
-
// existent option `--compatibility`.
|
|
4
|
-
// If necessary, more complex expressions could be parsed with parseExpr.js
|
|
5
|
-
// and then stringified with parentheses again.
|
|
6
|
-
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
function isAlreadyBraced(expression, start, end){
|
|
10
|
-
return start - 1 > -1 &&
|
|
11
|
-
end + 1 < expression.length &&
|
|
12
|
-
expression[start-1] === '(' &&
|
|
13
|
-
expression[end+1] === ')';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function binarycomparison(expression, token, index){
|
|
17
|
-
if(!isAlreadyBraced(expression, index-1, index+1)){
|
|
18
|
-
expression.splice(index+2 > expression.length ? expression.length : index +2 ,0,')');
|
|
19
|
-
expression.splice(index-1 > -1 ? index - 1 : 0,0,'(');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return index + 3;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function between(expression, token, index){
|
|
26
|
-
let start = index-1, end = index+4;
|
|
27
|
-
if(expression[index-1] === 'not'){
|
|
28
|
-
start -= 1;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if(!isAlreadyBraced(expression, start, end)){
|
|
32
|
-
expression.splice(end > expression.length ? expression.length : end ,0,')');
|
|
33
|
-
expression.splice(start > -1 ? start : 0,0,'(');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return index + 4;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function like(expression, token, index){
|
|
40
|
-
let start = index-1, end = index+2;
|
|
41
|
-
if(expression[index-1] === 'not'){
|
|
42
|
-
start -= 1;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if(!isAlreadyBraced(expression, start, end)){
|
|
46
|
-
expression.splice(end > expression.length ? expression.length : end ,0,')');
|
|
47
|
-
expression.splice(start > -1 ? start : 0,0,'(');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return index + 3;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const bracers = {
|
|
54
|
-
'=' : binarycomparison,
|
|
55
|
-
'>' : binarycomparison,
|
|
56
|
-
'<' : binarycomparison,
|
|
57
|
-
'>=': binarycomparison,
|
|
58
|
-
'<=': binarycomparison,
|
|
59
|
-
'!=': binarycomparison,
|
|
60
|
-
'between': between,
|
|
61
|
-
'like': like
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function braceExpression(expr){
|
|
65
|
-
for(let i = 0; i < expr.length; i++){
|
|
66
|
-
const token = expr[i];
|
|
67
|
-
if(token && token.xpr){
|
|
68
|
-
token.xpr = braceExpression(token.xpr);
|
|
69
|
-
}
|
|
70
|
-
if(bracers[token]){
|
|
71
|
-
i = bracers[token](expr, token, i);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return expr;
|
|
76
|
-
}
|
|
77
|
-
module.exports = braceExpression;
|