@sap/cds-compiler 4.7.6 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -2
- package/bin/cdsc.js +15 -1
- package/bin/cdshi.js +13 -3
- package/doc/CHANGELOG_BETA.md +5 -1
- package/lib/api/main.js +61 -23
- package/lib/api/options.js +40 -0
- package/lib/base/builtins.js +89 -0
- package/lib/base/keywords.js +5 -1
- package/lib/base/location.js +91 -14
- package/lib/base/message-registry.js +50 -33
- package/lib/base/messages.js +71 -16
- package/lib/base/model.js +0 -2
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/elements.js +2 -1
- package/lib/checks/enricher.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +6 -22
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/builtins.js +0 -74
- package/lib/compiler/checks.js +61 -11
- package/lib/compiler/define.js +3 -3
- package/lib/compiler/extend.js +2 -2
- package/lib/compiler/index.js +9 -9
- package/lib/compiler/populate.js +13 -5
- package/lib/compiler/propagator.js +3 -0
- package/lib/compiler/resolve.js +6 -20
- package/lib/compiler/shared.js +1 -1
- package/lib/compiler/tweak-assocs.js +2 -2
- package/lib/compiler/utils.js +3 -3
- package/lib/compiler/{classes.js → xsn-model.js} +0 -16
- package/lib/edm/annotations/edmJson.js +7 -5
- package/lib/edm/annotations/genericTranslation.js +113 -55
- package/lib/edm/csn2edm.js +25 -9
- package/lib/edm/edm.js +3 -3
- package/lib/edm/edmInboundChecks.js +24 -5
- package/lib/edm/edmPreprocessor.js +46 -20
- package/lib/edm/edmUtils.js +3 -16
- package/lib/gen/Dictionary.json +9 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1941 -1850
- package/lib/json/csnVersion.js +7 -4
- package/lib/json/from-csn.js +8 -7
- package/lib/json/to-csn.js +12 -7
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +9 -10
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/main.js +8 -1
- package/lib/model/cloneCsn.js +15 -6
- package/lib/model/csnRefs.js +141 -35
- package/lib/model/csnUtils.js +1 -4
- package/lib/model/enrichCsn.js +1 -1
- package/lib/modelCompare/compare.js +106 -92
- package/lib/optionProcessor.js +23 -1
- package/lib/render/toCdl.js +3 -2
- package/lib/render/toHdbcds.js +4 -48
- package/lib/render/toSql.js +6 -3
- package/lib/transform/addTenantFields.js +58 -35
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/db/flattening.js +71 -46
- package/lib/transform/db/views.js +1 -4
- package/lib/transform/effective/main.js +6 -3
- package/lib/transform/effective/misc.js +18 -8
- package/lib/transform/effective/types.js +4 -3
- package/lib/transform/forOdata.js +8 -7
- package/lib/transform/forRelationalDB.js +103 -112
- package/lib/transform/odata/flattening.js +82 -44
- package/lib/transform/odata/toFinalBaseType.js +9 -25
- package/lib/transform/odata/typesExposure.js +28 -15
- package/lib/transform/parseExpr.js +0 -3
- package/lib/transform/transformUtils.js +12 -8
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/coreComputed.js +2 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -1
- package/package.json +2 -2
- package/share/messages/README.md +4 -0
- package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/def-duplicate-autoexposed.md +1 -1
- package/share/messages/extend-repeated-intralayer.md +3 -16
- package/share/messages/extend-unrelated-layer.md +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/redirected-to-ambiguous.md +1 -1
- package/share/messages/redirected-to-complex.md +1 -1
- package/share/messages/redirected-to-unrelated.md +1 -1
- package/share/messages/rewrite-not-supported.md +1 -1
- package/share/messages/syntax-expecting-unsigned-int.md +2 -2
- package/share/messages/type-missing-enum-value.md +59 -0
- package/share/messages/wildcard-excluding-one.md +1 -1
|
@@ -6,9 +6,9 @@ const {
|
|
|
6
6
|
forEachDefinition,
|
|
7
7
|
forEachGeneric,
|
|
8
8
|
forEachMemberRecursively,
|
|
9
|
-
|
|
10
|
-
findAnnotationExpression,
|
|
9
|
+
findAnnotationExpression,
|
|
11
10
|
} = require('../../model/csnUtils');
|
|
11
|
+
const { isBuiltinType } = require('../../base/builtins');
|
|
12
12
|
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
13
13
|
const { cloneCsnDict, cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
14
14
|
|
|
@@ -98,12 +98,6 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
|
|
|
98
98
|
|
|
99
99
|
function expandToFinalBaseType(node, defName) {
|
|
100
100
|
if (!node) return;
|
|
101
|
-
// TODO: Clarify how should events be handled?
|
|
102
|
-
// They are not treated by the transformUtils::toFinalBaseType function
|
|
103
|
-
// in the same manner as named types, because the elements of structured events are not
|
|
104
|
-
// propagated as it is with types.
|
|
105
|
-
// It is ok to skip the expansion to the final base type for now as events are not rendered in
|
|
106
|
-
// EDMX at the moment and the reference in the OData CSN is fulfilled.
|
|
107
101
|
if (node.kind === 'event') return;
|
|
108
102
|
|
|
109
103
|
if(node.type && !isBuiltinType(node.type)) {
|
|
@@ -199,7 +193,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
|
|
|
199
193
|
// do the clone only if really needed
|
|
200
194
|
if((finalBaseType.items && !node.items) ||
|
|
201
195
|
(finalBaseType.elements && !node.elements)) {
|
|
202
|
-
//
|
|
196
|
+
// clone the definition not another clone
|
|
203
197
|
let _type = node._type;
|
|
204
198
|
while(_type._type && !_type.items && !_type.elements)
|
|
205
199
|
_type = _type._type;
|
|
@@ -227,15 +221,10 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
|
|
|
227
221
|
const typeRefStr = f(node.type.ref);
|
|
228
222
|
const typeRefRootPath = $path2path(typeRefCsnPath);
|
|
229
223
|
forEachMemberRecursively(clone, (elt, eltName, prop, location) => {
|
|
230
|
-
const [ xprANames /*nxprANames */ ] = Object.keys(elt).reduce((acc, pn) => {
|
|
231
|
-
if (pn[0] === '@')
|
|
232
|
-
acc[findAnnotationExpression(elt, pn) ? 0 : 1].push(pn);
|
|
233
|
-
return acc;
|
|
234
|
-
}, [ [], [] ]);
|
|
235
224
|
const usingPositionStr = f($path2path(location));
|
|
236
225
|
const eltRootPath = $path2path(elt.$path);
|
|
237
|
-
|
|
238
|
-
|
|
226
|
+
|
|
227
|
+
Object.keys(elt).filter(pn => pn[0] === '@' && findAnnotationExpression(elt, pn)).forEach(xprAName => {
|
|
239
228
|
transformExpression(elt, xprAName, {
|
|
240
229
|
ref: (parent, prop, xpr, csnPath) => {
|
|
241
230
|
let prefixMatch = true;
|
|
@@ -245,23 +234,18 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, e
|
|
|
245
234
|
prefixMatch = (xpr[i].id || xpr[i]) === typeRefRootPath[i];
|
|
246
235
|
}
|
|
247
236
|
if(prefixMatch && xpr.length > typeRefRootPath.length) {
|
|
248
|
-
|
|
237
|
+
if(xpr.length >= eltRootPath.length)
|
|
238
|
+
parent[prop] = [ ...xpr.slice(eltRootPath.length-1)];
|
|
239
|
+
else
|
|
240
|
+
parent[prop] = [ '$self', ...xpr.slice(typeRefRootPath.length)];
|
|
249
241
|
}
|
|
250
242
|
else {
|
|
251
243
|
error('odata-anno-xpr-ref', csnPath,
|
|
252
244
|
{ anno: xprAName, elemref: xpr.join('.'), name: usingPositionStr, code: typeRefStr });
|
|
253
|
-
//isSubTreeSpan = false;
|
|
254
245
|
}
|
|
255
246
|
}
|
|
256
247
|
},
|
|
257
248
|
}, elt.$path);
|
|
258
|
-
// if(!isSubTreeSpan) {
|
|
259
|
-
// // remove annotation and sub annotations from cloned element
|
|
260
|
-
// delete elt[xprAName];
|
|
261
|
-
// nxprANames.filter(an => an.startsWith(`${xprAName}.`)).forEach((nxprAName) => {
|
|
262
|
-
// delete elt[nxprAName];
|
|
263
|
-
// });
|
|
264
|
-
// }
|
|
265
249
|
});
|
|
266
250
|
setProp(elt, '$path', location);
|
|
267
251
|
}, node.$path);
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
|
-
const { getNamespace, copyAnnotations,
|
|
11
|
+
const { getNamespace, copyAnnotations, findAnnotationExpression,
|
|
12
12
|
forEachDefinition, forEachMember, forEachGeneric, isEdmPropertyRendered } = require('../../model/csnUtils');
|
|
13
|
+
const { isBuiltinType } = require('../../base/builtins');
|
|
13
14
|
const { CompilerAssertion } = require('../../base/error');
|
|
14
15
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
15
16
|
|
|
@@ -78,7 +79,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
78
79
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
79
80
|
if (propertyName === 'elements' || propertyName === 'params') {
|
|
80
81
|
const newTypeName = getNewTypeName(element, elementName, defName, serviceName);
|
|
81
|
-
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, newTypeName, path);
|
|
82
|
+
exposeTypeOf(element, element.key || propertyName === 'params', elementName, defName, serviceName, newTypeName, defName, path);
|
|
82
83
|
}
|
|
83
84
|
}, path);
|
|
84
85
|
}
|
|
@@ -105,7 +106,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
105
106
|
csn.definitions[defName] = def;
|
|
106
107
|
const artificialName = `term_${defName.replace(/\./g, '_')}`;//_${paramName}`;
|
|
107
108
|
const newTypeName = getNewTypeName(undefined, undefined, artificialName, serviceName);
|
|
108
|
-
exposeTypeOf(def, false, defName, defName, serviceName, newTypeName, path.concat(['vocabularies', defName]), undefined, true);
|
|
109
|
+
exposeTypeOf(def, false, defName, defName, serviceName, newTypeName, defName, path.concat(['vocabularies', defName]), undefined, true);
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
});
|
|
@@ -125,13 +126,13 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
125
126
|
if (action.returns) {
|
|
126
127
|
const artificialName = `return_${actionName.replace(/\./g, '_')}`;
|
|
127
128
|
const newTypeName = getNewTypeName(action.returns, undefined, artificialName, serviceName);
|
|
128
|
-
exposeTypeOf(action.returns, false, actionName, defName, serviceName, newTypeName, path.concat(['returns']));
|
|
129
|
+
exposeTypeOf(action.returns, false, actionName, defName, serviceName, newTypeName, defName, path.concat(['returns']));
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
action.params && Object.entries(action.params).forEach(([paramName, param]) => {
|
|
132
133
|
const artificialName = `param_${actionName.replace(/\./g, '_')}`;//_${paramName}`;
|
|
133
134
|
const newTypeName = getNewTypeName(param, paramName, artificialName, serviceName);
|
|
134
|
-
exposeTypeOf(param, false, actionName, defName, serviceName, newTypeName, path.concat(['params', paramName]));
|
|
135
|
+
exposeTypeOf(param, false, actionName, defName, serviceName, newTypeName, defName, path.concat(['params', paramName]));
|
|
135
136
|
});
|
|
136
137
|
}
|
|
137
138
|
|
|
@@ -144,7 +145,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
144
145
|
* @param {String} serviceName
|
|
145
146
|
* @param {String} newTypeName
|
|
146
147
|
*/
|
|
147
|
-
function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, path, parentName, isTermDef=false, ignoreInAPI=false) {
|
|
148
|
+
function exposeTypeOf(node, isKey, memberName, defName, serviceName, newTypeName, lastNonAnonymousFQDefName, path, parentName, isTermDef=false, ignoreInAPI=false) {
|
|
148
149
|
const { isExposable, typeDef, typeName, elements, isAnonymous } = isTypeExposable();
|
|
149
150
|
if (isExposable) {
|
|
150
151
|
// this is the name used to register the new type in csn.definitions
|
|
@@ -164,6 +165,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
164
165
|
// expose the type as a new one not changing the original definition.
|
|
165
166
|
if(elements && !!node['@open'] !== !!typeDef['@open'])
|
|
166
167
|
fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
|
|
168
|
+
lastNonAnonymousFQDefName = fullQualifiedNewTypeName;
|
|
167
169
|
}
|
|
168
170
|
// check if that type is already defined
|
|
169
171
|
let newType = csn.definitions[fullQualifiedNewTypeName];
|
|
@@ -197,10 +199,12 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
197
199
|
newType.elements && Object.entries(newType.elements).forEach(([elemName, newElem]) => {
|
|
198
200
|
if (node.elements && node.elements[elemName].$location)
|
|
199
201
|
setProp(newElem, '$location', node.elements[elemName].$location);
|
|
202
|
+
if (newElem.$path)
|
|
203
|
+
newElem.$path[1] = lastNonAnonymousFQDefName;
|
|
200
204
|
defName = typeDef.kind === 'type' ? typeName : defName;
|
|
201
205
|
{
|
|
202
206
|
const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
|
|
203
|
-
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef, ignoreInAPI);
|
|
207
|
+
getNewTypeName(newElem, elemName, newTypeName, serviceName), lastNonAnonymousFQDefName, path, fullQualifiedNewTypeName, isTermDef, ignoreInAPI);
|
|
204
208
|
// if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
|
|
205
209
|
// been caught by expandToFinalBaseType() (forODataNew must not modify external imported services)
|
|
206
210
|
if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
|
|
@@ -222,14 +226,23 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
222
226
|
}
|
|
223
227
|
});
|
|
224
228
|
|
|
225
|
-
//
|
|
226
|
-
|
|
227
|
-
if
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
229
|
+
// TODO: remove the this if and the else clause for the V5 release
|
|
230
|
+
if (isBetaEnabled(options, 'v5preview')) {
|
|
231
|
+
// We need this check, as we only need to copy the annotions if the type
|
|
232
|
+
// is user defined structured type outside of the service
|
|
233
|
+
if (!isAnonymous) {
|
|
234
|
+
copyAnnotations(typeDef, newType);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
// expression annos and their sub annotations are not propagated to type
|
|
238
|
+
let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
|
|
239
|
+
if (pn[0] === '@')
|
|
240
|
+
acc[findAnnotationExpression(typeDef, pn) ? 0 : 1].push(pn);
|
|
241
|
+
return acc;
|
|
242
|
+
}, [ [], [] ]);
|
|
243
|
+
nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
|
|
244
|
+
copyAnnotations(typeDef, newType, false, {}, nxprANames);
|
|
245
|
+
}
|
|
233
246
|
|
|
234
247
|
// if the origin type had items, add items to exposed type
|
|
235
248
|
if(typeDef.kind === 'type') {
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict'
|
|
4
4
|
|
|
5
|
-
// const funkyfuncs = Object.keys(require('../compiler/builtins.js').
|
|
6
|
-
// specialFunctions).filter(n => n.length).map(n=>n.toLowerCase());
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
6
|
* parseExpr() accepts any JSON object and tries to convert a token stream expression
|
|
10
7
|
* array into an AST like expression with CDL operator precedence.
|
|
@@ -8,10 +8,12 @@ const { setProp } = require('../base/model');
|
|
|
8
8
|
|
|
9
9
|
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
|
|
10
10
|
const { getUtils } = require('../model/csnUtils');
|
|
11
|
-
const { typeParameters
|
|
11
|
+
const { typeParameters } = require('../compiler/builtins');
|
|
12
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
12
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
13
14
|
const { forEach } = require('../utils/objectUtils');
|
|
14
15
|
const { cloneCsnNonDict, cloneCsnDict } = require('../model/cloneCsn');
|
|
16
|
+
const { addTenantFieldToArt } = require('./addTenantFields');
|
|
15
17
|
|
|
16
18
|
const RestrictedOperators = ['<', '>', '>=', '<='];
|
|
17
19
|
const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
|
|
@@ -321,10 +323,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
321
323
|
* @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
|
|
322
324
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
323
325
|
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
324
|
-
* @param {bool} [
|
|
326
|
+
* @param {bool} [suspend] suspend flattening by caller until association path step
|
|
325
327
|
* @returns {string[]}
|
|
326
328
|
*/
|
|
327
|
-
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(),
|
|
329
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0) {
|
|
328
330
|
// Refs of length 1 cannot contain steps - no need to check
|
|
329
331
|
if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
|
|
330
332
|
return ref;
|
|
@@ -350,10 +352,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
350
352
|
(resolvedLinkTypes.get(links[i])||{})[propName]);
|
|
351
353
|
|
|
352
354
|
let flattenStep = false;
|
|
353
|
-
|
|
355
|
+
suspend = !!art('items') || (suspend && i <= suspendPos);
|
|
354
356
|
for(; i < links.length; i++) {
|
|
355
357
|
|
|
356
|
-
if (flattenStep && !
|
|
358
|
+
if (flattenStep && !suspend) {
|
|
357
359
|
result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
|
|
358
360
|
// if we had a filter or args, we had an assoc so this step is done
|
|
359
361
|
// we then keep along the filter/args by updating the id of the current ref
|
|
@@ -362,14 +364,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
362
364
|
result[result.length-1] = ref[i];
|
|
363
365
|
}
|
|
364
366
|
// suspend flattening if the next path step has some 'items'
|
|
365
|
-
|
|
367
|
+
suspend = !!art('items');
|
|
366
368
|
}
|
|
367
369
|
else {
|
|
368
370
|
result.push(ref[i]);
|
|
369
371
|
}
|
|
370
372
|
// revoke items suspension for next assoc step
|
|
371
|
-
if(
|
|
372
|
-
|
|
373
|
+
if(suspend && art('target'))
|
|
374
|
+
suspend = false;
|
|
373
375
|
|
|
374
376
|
flattenStep = !links[i].art?.kind &&
|
|
375
377
|
!links[i].art?.SELECT &&
|
|
@@ -522,6 +524,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
522
524
|
if (!draftAdminDataEntity) {
|
|
523
525
|
draftAdminDataEntity = createAndAddDraftAdminDataEntity();
|
|
524
526
|
model.definitions['DRAFT.DraftAdministrativeData'] = draftAdminDataEntity;
|
|
527
|
+
if(options.tenantDiscriminator && options.transformation !== 'odata')
|
|
528
|
+
addTenantFieldToArt(model.definitions['DRAFT.DraftAdministrativeData'], options);
|
|
525
529
|
}
|
|
526
530
|
|
|
527
531
|
// Create a projection within this service
|
|
@@ -600,7 +600,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
600
600
|
Put all src/tgt path siblings into the EQ term and create the proper path objects
|
|
601
601
|
with the src/tgt table alias path steps in front.
|
|
602
602
|
*/
|
|
603
|
-
let args = compareTenants
|
|
603
|
+
let args = compareTenants && addTenantComparison(assoc) || [];
|
|
604
604
|
for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
|
|
605
605
|
{
|
|
606
606
|
args.push({op: {val: '=' },
|
|
@@ -678,7 +678,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
678
678
|
args[1].path.push({ id: 'tenant' }); // no need for _artifact
|
|
679
679
|
const comparison = { op: {val: '=' }, args };
|
|
680
680
|
if (!cond) // for managed assoc
|
|
681
|
-
return comparison;
|
|
681
|
+
return [ comparison ];
|
|
682
682
|
return { op: { val: 'and' }, args: [ comparison, parenthesise(cond) ] };
|
|
683
683
|
}
|
|
684
684
|
|
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
} = require('../../model/csnUtils');
|
|
6
6
|
const { setAnnotationIfNotDefined } = require('./utils');
|
|
7
7
|
const { CompilerAssertion } = require('../../base/error');
|
|
8
|
+
const { isMagicVariable } = require('../../base/builtins');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Set @Core.Computed on the elements of views (and projections) as well
|
|
@@ -157,7 +158,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
|
|
|
157
158
|
(
|
|
158
159
|
column.xpr || column.list || column.func || column.val !== undefined || column['#'] !== undefined || column.param ||
|
|
159
160
|
column.SELECT || column.SET ||
|
|
160
|
-
column.ref && [
|
|
161
|
+
column.ref && (isMagicVariable(column.ref[0]) || column.ref[0] === '$parameters')
|
|
161
162
|
);
|
|
162
163
|
}
|
|
163
164
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
|
|
29
29
|
"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
|
|
30
30
|
"coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
|
|
31
|
-
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
|
|
31
|
+
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint . && cd ../../ && node scripts/check-changelog.js",
|
|
32
32
|
"tslint": "tsc --pretty -p .",
|
|
33
33
|
"updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
|
|
34
34
|
"updateTocs": "node scripts/update-toc.js",
|
package/share/messages/README.md
CHANGED
|
@@ -9,30 +9,17 @@ They form a cyclic connection through their dependencies
|
|
|
9
9
|
|
|
10
10
|
## Example
|
|
11
11
|
|
|
12
|
-
Erroneous code example with
|
|
13
|
-
|
|
14
|
-
```cds
|
|
15
|
-
entity FooBar { }
|
|
16
|
-
|
|
17
|
-
extend FooBar { foo : Integer; }
|
|
18
|
-
extend FooBar { bar : Integer; }
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
Due to multiple extensions in the example above, the order of `foo` and `bar`
|
|
22
|
-
inside `FooBar` may not be stable. You therefore can’t depend on it.
|
|
23
|
-
|
|
24
|
-
It's also possible to trigger this warning with multiple files.
|
|
25
|
-
Look at the following example:
|
|
12
|
+
Erroneous code example with multiple CDL files:
|
|
26
13
|
|
|
27
14
|
```cds
|
|
28
15
|
// (1) Definition.cds
|
|
29
16
|
using from './Extension.cds';
|
|
30
17
|
entity FooBar { };
|
|
31
|
-
extend FooBar { foo: Integer; };
|
|
18
|
+
extend FooBar { foo: Integer; }; // ❌
|
|
32
19
|
|
|
33
20
|
// (2) Extension.cds
|
|
34
21
|
using from './Definition.cds';
|
|
35
|
-
extend FooBar { bar: Integer; }
|
|
22
|
+
extend FooBar { bar: Integer; }; // ❌
|
|
36
23
|
```
|
|
37
24
|
|
|
38
25
|
Here we have a cyclic dependency between (1) and (2). Together they form one
|
|
@@ -17,8 +17,8 @@ Erroneous code example:
|
|
|
17
17
|
|
|
18
18
|
<!-- cds-mode: ignore -->
|
|
19
19
|
```cds
|
|
20
|
-
type LengthIsUnsafe : String(9007199254740992);
|
|
21
|
-
type NotAnInteger : String(42.1);
|
|
20
|
+
type LengthIsUnsafe : String(9007199254740992); // ❌
|
|
21
|
+
type NotAnInteger : String(42.1); // ❌
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
In the erroneous example, the string length for the type `LengthIsUnsafe` is
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# type-missing-enum-value
|
|
2
|
+
|
|
3
|
+
An enum definition is missing explicit values for one or more of its entries.
|
|
4
|
+
|
|
5
|
+
Enum definitions that aren't based on string-types do not get implicit values.
|
|
6
|
+
They have therefore to be defined explicitly in the model.
|
|
7
|
+
|
|
8
|
+
The message’s severity is `Warning` and is raised by the compiler. You need
|
|
9
|
+
to adapt your model to fix the warning.
|
|
10
|
+
|
|
11
|
+
## Example
|
|
12
|
+
|
|
13
|
+
Erroneous code example:
|
|
14
|
+
|
|
15
|
+
```cds
|
|
16
|
+
entity Books {
|
|
17
|
+
// …
|
|
18
|
+
category: Integer enum {
|
|
19
|
+
Fiction; // ❌
|
|
20
|
+
Action; // ❌
|
|
21
|
+
// …
|
|
22
|
+
} default #Action;
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Both entries `#Fiction` and `#Action` of the enum `category` are missing an
|
|
27
|
+
explicit value. Because the base type `Integer` is not a string, no implicit
|
|
28
|
+
values are defined for them.
|
|
29
|
+
|
|
30
|
+
## How to Fix
|
|
31
|
+
|
|
32
|
+
Explicitly assign a value or change the type to a string if the values are not
|
|
33
|
+
important in your model. The erroneous example above can be changed to:
|
|
34
|
+
|
|
35
|
+
```cds
|
|
36
|
+
entity Books {
|
|
37
|
+
// …
|
|
38
|
+
category: Integer enum {
|
|
39
|
+
Fiction = 1;
|
|
40
|
+
Action = 2;
|
|
41
|
+
// …
|
|
42
|
+
} default #Action;
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Background
|
|
47
|
+
|
|
48
|
+
Many languages support implicit values for integer-like enums. However,
|
|
49
|
+
CAP CDS does not have this feature, because otherwise, if values are persisted,
|
|
50
|
+
adding a new entry in-between existing ones would lead to issues during
|
|
51
|
+
deserialization later on.
|
|
52
|
+
|
|
53
|
+
Assume that CAP would assign implicit values for integer enums. If a new value
|
|
54
|
+
were to be added between `Fiction` and `Action` in the erroneous example above,
|
|
55
|
+
then the generated SQL statement for entity `Books` would change:
|
|
56
|
+
Instead of default value `2`, value `3` would be persisted. Without data
|
|
57
|
+
migration, existing action books would have changed their category.
|
|
58
|
+
|
|
59
|
+
To avoid this scenario, always add explicit values to enums.
|