@sap/cds-compiler 3.1.0 → 3.3.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 +90 -3
- package/bin/cdsc.js +1 -1
- package/doc/CHANGELOG_BETA.md +18 -0
- package/lib/api/main.js +8 -13
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +2 -24
- package/lib/base/message-registry.js +43 -14
- package/lib/base/messages.js +20 -10
- package/lib/base/model.js +1 -1
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/arrayOfs.js +15 -7
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +48 -0
- package/lib/checks/defaultValues.js +2 -2
- package/lib/checks/elements.js +81 -6
- package/lib/checks/foreignKeys.js +12 -13
- package/lib/checks/invalidTarget.js +10 -11
- package/lib/checks/managedInType.js +21 -15
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +9 -9
- package/lib/checks/parameters.js +21 -0
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +26 -14
- package/lib/compiler/assert-consistency.js +13 -6
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +40 -33
- package/lib/compiler/define.js +50 -44
- package/lib/compiler/extend.js +303 -37
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +83 -62
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +61 -104
- package/lib/compiler/shared.js +16 -6
- package/lib/compiler/tweak-assocs.js +25 -12
- package/lib/compiler/utils.js +2 -2
- package/lib/edm/annotations/genericTranslation.js +15 -5
- package/lib/edm/csn2edm.js +10 -10
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +82 -42
- package/lib/edm/edmUtils.js +18 -16
- package/lib/gen/Dictionary.json +14 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -2
- package/lib/gen/languageParser.js +4205 -4100
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +26 -19
- package/lib/json/to-csn.js +47 -5
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +29 -13
- package/lib/language/language.g4 +28 -8
- package/lib/main.d.ts +3 -6
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +74 -47
- package/lib/model/csnUtils.js +236 -218
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +31 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +5 -0
- package/lib/render/manageConstraints.js +2 -2
- package/lib/render/toCdl.js +31 -44
- package/lib/render/toHdbcds.js +7 -5
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +11 -5
- package/lib/render/utils/common.js +20 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/expansion.js +81 -37
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/db/transformExists.js +1 -1
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +7 -7
- package/lib/transform/localized.js +28 -19
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +1 -1
- package/lib/transform/transformUtilsNew.js +101 -39
- package/lib/transform/translateAssocsToJoins.js +5 -4
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/package.json +2 -2
- package/share/messages/anno-duplicate-unrelated-layer.md +6 -6
- package/share/messages/check-proper-type-of.md +4 -4
- package/share/messages/check-proper-type.md +2 -2
- package/share/messages/duplicate-autoexposed.md +4 -4
- package/share/messages/extend-repeated-intralayer.md +4 -5
- package/share/messages/extend-unrelated-layer.md +4 -4
- package/share/messages/message-explanations.json +3 -1
- package/share/messages/redirected-to-ambiguous.md +7 -6
- package/share/messages/redirected-to-complex.md +63 -0
- package/share/messages/redirected-to-unrelated.md +6 -5
- package/share/messages/rewrite-not-supported.md +4 -4
- package/share/messages/syntax-expected-integer.md +3 -3
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -124,7 +124,7 @@ function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOpt
|
|
|
124
124
|
}
|
|
125
125
|
},
|
|
126
126
|
}, [ (definitions, artifactName, artifact) => {
|
|
127
|
-
// Replace events, actions and functions with simple dummies - they don't have effect on
|
|
127
|
+
// Replace events, actions and functions with simple dummies - they don't have effect on forRelationalDB stuff
|
|
128
128
|
// and that way they contain no references and don't hurt.
|
|
129
129
|
|
|
130
130
|
// Do not do for OData
|
|
@@ -81,7 +81,7 @@ function getViewDecorator(csn, messageFunctions) {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].
|
|
84
|
+
info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${from[0].errorParent}"."${from[0].name}" and "${to[0].errorParent}"."${to[0].name}" are not of same origin`);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
else if (from.length > 0 || to.length > 0) {
|
|
@@ -799,7 +799,7 @@ function handleExists(csn, options, error) {
|
|
|
799
799
|
where.push({ ref: [ base, ...part.ref.slice(1) ] });
|
|
800
800
|
}
|
|
801
801
|
else if (part.$scope === '$self') { // source side - "absolute" scope
|
|
802
|
-
// Same message as in
|
|
802
|
+
// Same message as in forRelationalDB/transformDollarSelfComparisonWithUnmanagedAssoc
|
|
803
803
|
error(null, part.$path, 'An association that uses "$self" in its ON-condition can\'t be compared to "$self"');
|
|
804
804
|
}
|
|
805
805
|
else if (partInspect.art) { // source side - with local scope
|
|
@@ -97,6 +97,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
97
97
|
inspectRef,
|
|
98
98
|
artifactRef,
|
|
99
99
|
effectiveType,
|
|
100
|
+
getFinalBaseTypeWithProps
|
|
100
101
|
} = csnUtils;
|
|
101
102
|
|
|
102
103
|
// are we working with structured OData or not
|
|
@@ -123,7 +124,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
123
124
|
addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
|
|
124
125
|
|
|
125
126
|
const cleanup = validate.forOdata(csn, {
|
|
126
|
-
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
|
|
127
|
+
message, error, warning, info, inspectRef, effectiveType, getFinalBaseTypeWithProps, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
|
|
127
128
|
});
|
|
128
129
|
|
|
129
130
|
|
|
@@ -307,11 +308,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
307
308
|
'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
|
|
308
309
|
}
|
|
309
310
|
|
|
311
|
+
const shortCuts = Object.keys(mappings);
|
|
310
312
|
Object.keys(node).forEach( name => {
|
|
311
313
|
// Rename according to map above
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
314
|
+
const prefix = shortCuts.find(p => name.startsWith(p + '.') || name === p);
|
|
315
|
+
if(prefix) {
|
|
316
|
+
renameAnnotation(node, name, name.replace(prefix, mappings[prefix]));
|
|
317
|
+
}
|
|
315
318
|
// Special case: '@important: [true|false]' becomes '@UI.Importance: [#High|#Low]'
|
|
316
319
|
if (name === '@important') {
|
|
317
320
|
renameAnnotation(node, name, '@UI.Importance');
|
|
@@ -323,7 +326,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
323
326
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
324
327
|
// but '@Core.Immutable' for everything else.
|
|
325
328
|
if (!(node['@readonly'] && node['@insertonly'])) {
|
|
326
|
-
if (name === '@readonly' && node[name]
|
|
329
|
+
if (name === '@readonly' && node[name]) {
|
|
327
330
|
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
328
331
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
329
332
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
@@ -333,7 +336,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
333
336
|
}
|
|
334
337
|
}
|
|
335
338
|
// @insertonly is effective on entities/queries only
|
|
336
|
-
else if (name === '@insertonly' && node[name]
|
|
339
|
+
else if (name === '@insertonly' && node[name]) {
|
|
337
340
|
if (node.kind === 'entity' || node.kind === 'aspect') {
|
|
338
341
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
339
342
|
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
|
|
@@ -342,7 +345,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
// Only on element level: translate @mandatory
|
|
345
|
-
if (name === '@mandatory' && node[name]
|
|
348
|
+
if (name === '@mandatory' && node[name] &&
|
|
346
349
|
node.kind === undefined && node['@Common.FieldControl'] === undefined) {
|
|
347
350
|
setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
|
|
348
351
|
}
|
|
@@ -93,14 +93,14 @@ function forEachDefinition(csn, cb) {
|
|
|
93
93
|
* - (240) All artifacts (a), elements, foreign keys, parameters (b) that have a DB representation are annotated
|
|
94
94
|
* with their database name (as '@cds.persistence.name') according to the naming convention chosen
|
|
95
95
|
* in 'options.sqlMapping'.
|
|
96
|
-
* - (250) Remove name space definitions again (only in
|
|
96
|
+
* - (250) Remove name space definitions again (only in forRelationalDB). Maybe we can omit inserting namespace definitions
|
|
97
97
|
* completely (TODO)
|
|
98
98
|
*
|
|
99
99
|
* @param {CSN.Model} inputModel
|
|
100
100
|
* @param {CSN.Options} options
|
|
101
101
|
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
|
|
102
102
|
*/
|
|
103
|
-
function
|
|
103
|
+
function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
104
104
|
// copy the model as we don't want to change the input model
|
|
105
105
|
timetrace.start('HANA transformation');
|
|
106
106
|
/** @type {CSN.Model} */
|
|
@@ -131,7 +131,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
131
131
|
forEachDefinition(csn, handleMixinOnConditions);
|
|
132
132
|
|
|
133
133
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
134
|
-
const cleanup = validate.
|
|
134
|
+
const cleanup = validate.forRelationalDB(csn, {
|
|
135
135
|
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, isAspect
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -310,7 +310,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
310
310
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
311
311
|
* hence we do not generate the referential constraints for them.
|
|
312
312
|
*/
|
|
313
|
-
if(
|
|
313
|
+
if(options.sqlDialect !== 'plain' && doA2J)
|
|
314
314
|
createReferentialConstraints(csn, options);
|
|
315
315
|
|
|
316
316
|
// no constraints for drafts
|
|
@@ -398,7 +398,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
398
398
|
|
|
399
399
|
if(options.sqlDialect === 'postgres') {
|
|
400
400
|
killers.length = (parent) => {
|
|
401
|
-
if (parent.type === 'cds.Binary'
|
|
401
|
+
if (parent.type === 'cds.Binary') {
|
|
402
402
|
delete parent.length;
|
|
403
403
|
}
|
|
404
404
|
}
|
|
@@ -983,7 +983,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
983
983
|
const absolute = node.type;
|
|
984
984
|
const parameters = node.parameters || [];
|
|
985
985
|
// :FIXME: Is this dead code? node.parameters is always undefined...
|
|
986
|
-
//
|
|
986
|
+
// forRelationalDB tested against the parameters of the type definition which is not available in CSN
|
|
987
987
|
for (const name in parameters) {
|
|
988
988
|
const param = parameters[name];
|
|
989
989
|
if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
|
|
@@ -1124,5 +1124,5 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1124
1124
|
|
|
1125
1125
|
|
|
1126
1126
|
module.exports = {
|
|
1127
|
-
|
|
1127
|
+
transformForRelationalDBWithCsn,
|
|
1128
1128
|
};
|
|
@@ -86,19 +86,8 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
86
86
|
|
|
87
87
|
createDirectConvenienceViews(); // 1
|
|
88
88
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// In case that the user tried to annotate `localized.*` artifacts, apply them.
|
|
93
|
-
applyAnnotationsFromExtensions(csn, {
|
|
94
|
-
override: true,
|
|
95
|
-
filter: (name) => name.startsWith('localized.'),
|
|
96
|
-
notFound(name, index) {
|
|
97
|
-
if (!ignoreUnknownExtensions)
|
|
98
|
-
messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
|
|
89
|
+
cleanDefinitionSymbols();
|
|
90
|
+
applyAnnotationsForLocalizedViews();
|
|
102
91
|
sortCsnDefinitionsForTests(csn, options);
|
|
103
92
|
return csn;
|
|
104
93
|
|
|
@@ -196,7 +185,7 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
196
185
|
|
|
197
186
|
for (const originalElement of textElements) {
|
|
198
187
|
const elem = entity.elements[originalElement];
|
|
199
|
-
// Note: $key is used by
|
|
188
|
+
// Note: $key is used by forRelationalDB.js to indicate that this element was a key in the original,
|
|
200
189
|
// user's entity. Keys may have been changed by the backends (e.g. by `@cds.valid.key`)
|
|
201
190
|
if (!elem.key && !elem.$key)
|
|
202
191
|
columns.push( createColumnLocalizedElement( originalElement, shouldUseJoin ) );
|
|
@@ -605,6 +594,27 @@ function _addLocalizationViews(csn, options, useJoins, config) {
|
|
|
605
594
|
// We can assume, that the element exists. This is checked in isEntityPreprocessed()
|
|
606
595
|
return csn.definitions[artName].elements.texts.target;
|
|
607
596
|
}
|
|
597
|
+
|
|
598
|
+
function cleanDefinitionSymbols() {
|
|
599
|
+
forEachDefinition(csn, function cleanDefinition(definition) {
|
|
600
|
+
cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* In case that the user tried to annotate `localized.*` artifacts, apply them.
|
|
606
|
+
*/
|
|
607
|
+
function applyAnnotationsForLocalizedViews() {
|
|
608
|
+
applyAnnotationsFromExtensions(csn, {
|
|
609
|
+
override: true,
|
|
610
|
+
filter: (name) => name.startsWith('localized.'),
|
|
611
|
+
notFound(name, index) {
|
|
612
|
+
if (!ignoreUnknownExtensions)
|
|
613
|
+
messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
|
|
608
618
|
}
|
|
609
619
|
|
|
610
620
|
/**
|
|
@@ -636,7 +646,7 @@ function addLocalizationViewsWithJoins(csn, options, config = {}) {
|
|
|
636
646
|
* @param {string} [as] Alias for path.
|
|
637
647
|
* @return {CSN.Column}
|
|
638
648
|
*/
|
|
639
|
-
function createColumnRef(ref, as
|
|
649
|
+
function createColumnRef(ref, as) {
|
|
640
650
|
const column = { ref };
|
|
641
651
|
if (as)
|
|
642
652
|
column.as = as;
|
|
@@ -708,10 +718,9 @@ function hasExistingLocalizationViews(csn, options, messageFunctions) {
|
|
|
708
718
|
let hasExistingViews = false;
|
|
709
719
|
let hasNonViews = false;
|
|
710
720
|
|
|
711
|
-
|
|
712
|
-
const art = csn.definitions[name];
|
|
721
|
+
forEachDefinition(csn, (def, name) => {
|
|
713
722
|
if (isInLocalizedNamespace(name) || name === 'localized') {
|
|
714
|
-
if (!
|
|
723
|
+
if (!def.query && !def.projection) {
|
|
715
724
|
if (!name.endsWith('.texts')) {
|
|
716
725
|
hasNonViews = true;
|
|
717
726
|
messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
|
|
@@ -723,7 +732,7 @@ function hasExistingLocalizationViews(csn, options, messageFunctions) {
|
|
|
723
732
|
'Input CSN already contains localization views, no further ones will be created' );
|
|
724
733
|
}
|
|
725
734
|
}
|
|
726
|
-
}
|
|
735
|
+
});
|
|
727
736
|
return hasExistingViews || hasNonViews;
|
|
728
737
|
}
|
|
729
738
|
|
|
@@ -68,9 +68,9 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
68
68
|
// type Foo: array of { qux: Integer };
|
|
69
69
|
function expandFirstLevelOfArrayed(def) {
|
|
70
70
|
if (def.items.type && !isBuiltinType(def.items.type)) {
|
|
71
|
-
let
|
|
72
|
-
if (
|
|
73
|
-
def.items.elements = cloneCsnDictionary(
|
|
71
|
+
let finalBaseType = csnUtils.getFinalBaseTypeWithProps(def.items.type);
|
|
72
|
+
if (finalBaseType?.elements) {
|
|
73
|
+
def.items.elements = cloneCsnDictionary(finalBaseType.elements, options);
|
|
74
74
|
delete def.items.type;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -147,14 +147,11 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
147
147
|
// handle array of defined via a named type
|
|
148
148
|
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
|
|
149
149
|
const currService = csnUtils.getServiceName(defName);
|
|
150
|
-
const finalType = csnUtils.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
node.items = finalType.items;
|
|
156
|
-
delete node.type;
|
|
157
|
-
}
|
|
150
|
+
const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
|
|
151
|
+
const isArrayOfBuiltin = finalType?.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)
|
|
152
|
+
if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
|
|
153
|
+
node.items = finalType.items;
|
|
154
|
+
delete node.type;
|
|
158
155
|
}
|
|
159
156
|
}
|
|
160
157
|
|
|
@@ -108,7 +108,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
108
108
|
if (newType) {
|
|
109
109
|
// error, if it was not exposed by us
|
|
110
110
|
if (!exposedTypes[fullQualifiedNewTypeName]) {
|
|
111
|
-
error(null, path, `
|
|
111
|
+
error(null, path, `Can't create artificial type "${fullQualifiedNewTypeName}" for "${memberName}" because the name is already used`);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -13,13 +13,15 @@ const { typeParameters, isBuiltinType } = require('../compiler/builtins');
|
|
|
13
13
|
const { ModelError } = require("../base/error");
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
15
|
|
|
16
|
+
const RestrictedOperators = ['<', '>', '>=', '<='];
|
|
17
|
+
const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
|
|
16
18
|
// Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
|
|
17
19
|
// Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
|
|
18
20
|
// 'model' is compacted new style CSN
|
|
19
21
|
// TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
|
|
20
22
|
// TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
|
|
21
23
|
function getTransformers(model, options, pathDelimiter = '_') {
|
|
22
|
-
const { error, warning, info } = makeMessageFunction(model, options);
|
|
24
|
+
const { message, error, warning, info } = makeMessageFunction(model, options);
|
|
23
25
|
const csnUtils = getUtils(model);
|
|
24
26
|
const {
|
|
25
27
|
getCsnDef,
|
|
@@ -500,7 +502,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
500
502
|
* Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData'
|
|
501
503
|
* in service 'service' and add it to the model.
|
|
502
504
|
*
|
|
503
|
-
* For
|
|
505
|
+
* For forRelationalDB, use String(36) instead of UUID and UTCTimestamp instead of Timestamp
|
|
504
506
|
*
|
|
505
507
|
* @param {string} service
|
|
506
508
|
* @param {boolean} [hanaMode=false] Turn UUID into String(36)
|
|
@@ -1113,51 +1115,64 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1113
1115
|
for(let i = 0; i < expr.length; i++)
|
|
1114
1116
|
{
|
|
1115
1117
|
if(Array.isArray(expr[i]))
|
|
1116
|
-
rc.push(expr[i].map(expand, location));
|
|
1118
|
+
rc.push(expr[i].map(e => expand(e, location)));
|
|
1117
1119
|
|
|
1118
1120
|
if(i < expr.length-2)
|
|
1119
1121
|
{
|
|
1120
|
-
|
|
1122
|
+
let [lhs, op, not, rhs] = expr.slice(i);
|
|
1123
|
+
if(not !== 'not') {
|
|
1124
|
+
rhs = not;
|
|
1125
|
+
not = false;
|
|
1126
|
+
}
|
|
1127
|
+
if(lhs === undefined || op === undefined || rhs === undefined)
|
|
1128
|
+
return expr;
|
|
1121
1129
|
|
|
1122
1130
|
// we might have to ad-hoc resolve a ref, since handleExists is run before hand and generates new refs.
|
|
1123
1131
|
const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
|
|
1124
1132
|
const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
|
|
1125
|
-
|
|
1126
|
-
if
|
|
1127
|
-
|
|
1128
|
-
isExpandable(lhsArt) && isExpandable(rhsArt) &&
|
|
1129
|
-
['=', '<', '>', '>=', '<=', '!=', '<>'].includes(op) &&
|
|
1130
|
-
!(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs))) {
|
|
1133
|
+
const lhsIsVal = (lhs.val !== undefined);
|
|
1134
|
+
// if ever rhs should be alowed to be a value uncomment this
|
|
1135
|
+
const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
|
|
1131
1136
|
|
|
1137
|
+
// lhs & rhs must be expandable types (structures or managed associations)
|
|
1138
|
+
// if ever lhs should be alowed to be a value uncomment this
|
|
1139
|
+
if(!(lhsIsVal /*&& rhsIsVal*/) &&
|
|
1140
|
+
!(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) &&
|
|
1141
|
+
RelationalOperators.includes(op) &&
|
|
1142
|
+
(lhsIsVal || (lhsArt && lhs.ref && isExpandable(lhsArt))) &&
|
|
1143
|
+
(rhsIsVal || (rhsArt && rhs.ref && isExpandable(rhsArt)))
|
|
1144
|
+
) {
|
|
1145
|
+
|
|
1146
|
+
if(RestrictedOperators.includes(op)) {
|
|
1147
|
+
message('expr-unexpected-operator', location, { op }, 'Unexpected operator $(OP) in structural comparison');
|
|
1148
|
+
}
|
|
1132
1149
|
// if path is scalar and no assoc or has no type (@Core.Computed) use original expression
|
|
1133
1150
|
// only do the expansion on (managed) assocs and (items.)elements, array of check in ON cond is done elsewhere
|
|
1134
|
-
const lhspaths =
|
|
1135
|
-
const rhspaths =
|
|
1151
|
+
const lhspaths = lhsIsVal ? [] : flattenPath({ _art: lhsArt, ref: lhs.ref }, false, true );
|
|
1152
|
+
const rhspaths = rhsIsVal ? [] : flattenPath({ _art: rhsArt, ref: rhs.ref }, false, true );
|
|
1136
1153
|
|
|
1137
1154
|
// mapping dict for lhs/rhs for mismatch check
|
|
1138
1155
|
// strip lhs/rhs prefix from flattened paths to check remaining common trailing path
|
|
1139
1156
|
// if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
|
|
1140
1157
|
// key is then empty string on both sides '' (=> equality)
|
|
1141
1158
|
// Path matches if lhs/rhs are available
|
|
1142
|
-
const xref = lhspaths
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
}, Object.create(null));
|
|
1146
|
-
|
|
1147
|
-
rhspaths.forEach(v => {
|
|
1148
|
-
const k = v.ref.slice(rhs.ref.length).join('.');
|
|
1149
|
-
if(xref[k])
|
|
1150
|
-
xref[k].rhs = v;
|
|
1151
|
-
else
|
|
1152
|
-
xref[k] = { rhs: v };
|
|
1153
|
-
});
|
|
1154
|
-
|
|
1159
|
+
const xref = createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal);
|
|
1160
|
+
const xrefkeys = Object.keys(xref);
|
|
1161
|
+
const xrefvalues = Object.values(xref);
|
|
1155
1162
|
let cont = true;
|
|
1156
|
-
for(const xn in xref) {
|
|
1157
|
-
const x = xref[xn];
|
|
1158
1163
|
|
|
1164
|
+
if(op === 'like' && xrefvalues.reduce((a, v) => {
|
|
1165
|
+
return (v.lhs && v.rhs) ? a + 1: a;
|
|
1166
|
+
}, 0) === 0) {
|
|
1167
|
+
// error if intersection of paths is zero
|
|
1168
|
+
error(null, location, `Expected compatible types for '${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}'`);
|
|
1169
|
+
cont = false;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
cont && xrefkeys.forEach(xn => {
|
|
1173
|
+
const x = xref[xn];
|
|
1159
1174
|
// do the paths match?
|
|
1160
|
-
if(!(x.lhs && x.rhs)) {
|
|
1175
|
+
if(op !== 'like' && !(x.lhs && x.rhs)) {
|
|
1161
1176
|
if(xn.length)
|
|
1162
1177
|
error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Sub path '${xn}' not found in ${((x.lhs ? rhs : lhs).ref.join('.'))}`)
|
|
1163
1178
|
else
|
|
@@ -1167,17 +1182,17 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1167
1182
|
// lhs && rhs are present, consistency checks that affect both ends
|
|
1168
1183
|
else {
|
|
1169
1184
|
// is lhs scalar?
|
|
1170
|
-
if(!isScalarOrNoType(x.lhs._art)) {
|
|
1185
|
+
if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
|
|
1171
1186
|
error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`)
|
|
1172
1187
|
cont = false;
|
|
1173
1188
|
}
|
|
1174
1189
|
// is rhs scalar?
|
|
1175
|
-
if(!isScalarOrNoType(x.rhs._art)) {
|
|
1190
|
+
if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
|
|
1176
1191
|
error(null, location, `'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`)
|
|
1177
1192
|
cont = false;
|
|
1178
1193
|
}
|
|
1179
1194
|
// info about type incompatibility if no other errors occured
|
|
1180
|
-
if(xn && cont) {
|
|
1195
|
+
if(!(lhsIsVal || rhsIsVal) && x.lhs && x.rhs && xn && cont) {
|
|
1181
1196
|
const lhst = getType(x.lhs._art);
|
|
1182
1197
|
const rhst = getType(x.rhs._art);
|
|
1183
1198
|
if(lhst !== rhst) {
|
|
@@ -1185,20 +1200,29 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1185
1200
|
}
|
|
1186
1201
|
}
|
|
1187
1202
|
}
|
|
1188
|
-
}
|
|
1203
|
+
});
|
|
1189
1204
|
// don't continue if there are path errors
|
|
1190
1205
|
if(!cont)
|
|
1191
1206
|
return expr;
|
|
1192
1207
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1208
|
+
// if lhs and rhs are refs set operator from 'like' to '='
|
|
1209
|
+
if(op === 'like' && !(lhsIsVal || rhsIsVal)) {
|
|
1210
|
+
op = '=';
|
|
1211
|
+
}
|
|
1212
|
+
// t_0 OR ... OR t_n with t = (a <not equal> b)
|
|
1213
|
+
const bop = (op === 'is' && not) || op === '!=' || op === '<>' ? 'or' : 'and';
|
|
1214
|
+
rc.push('(');
|
|
1215
|
+
xrefvalues.filter(x => x.lhs && x.rhs).forEach((x,i) => {
|
|
1195
1216
|
if(i>0)
|
|
1196
|
-
rc.push(
|
|
1217
|
+
rc.push(bop);
|
|
1197
1218
|
rc.push(x.lhs);
|
|
1198
1219
|
rc.push(op);
|
|
1220
|
+
if(not)
|
|
1221
|
+
rc.push('not')
|
|
1199
1222
|
rc.push(x.rhs);
|
|
1200
1223
|
});
|
|
1201
|
-
|
|
1224
|
+
rc.push(')');
|
|
1225
|
+
i += not ? 3 : 2;
|
|
1202
1226
|
}
|
|
1203
1227
|
else
|
|
1204
1228
|
rc.push(expr[i]);
|
|
@@ -1208,6 +1232,44 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1208
1232
|
}
|
|
1209
1233
|
return rc;
|
|
1210
1234
|
|
|
1235
|
+
function createXRef(lhspaths, rhspaths, lhs, rhs, lhsIsVal, rhsIsVal) {
|
|
1236
|
+
// mapping dict for lhs/rhs for mismatch check
|
|
1237
|
+
// strip lhs/rhs prefix from flattened paths to check remaining common trailing path
|
|
1238
|
+
// if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
|
|
1239
|
+
// key is then empty string on both sides '' (=> equality)
|
|
1240
|
+
// Path matches if lhs/rhs are available
|
|
1241
|
+
let xref;
|
|
1242
|
+
if(!lhsIsVal) {
|
|
1243
|
+
xref = lhspaths.reduce((a, v) => {
|
|
1244
|
+
a[v.ref.slice(lhs.ref.length).join('.')] = rhsIsVal ? { lhs: v, rhs } : { lhs: v };
|
|
1245
|
+
return a;
|
|
1246
|
+
}, Object.create(null));
|
|
1247
|
+
|
|
1248
|
+
rhspaths.forEach(v => {
|
|
1249
|
+
const k = v.ref.slice(rhs.ref.length).join('.');
|
|
1250
|
+
if(xref[k])
|
|
1251
|
+
xref[k].rhs = v;
|
|
1252
|
+
else
|
|
1253
|
+
xref[k] = { rhs: v };
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
else if(!rhsIsVal) {
|
|
1257
|
+
xref = rhspaths.reduce((a, v) => {
|
|
1258
|
+
a[v.ref.slice(rhs.ref.length).join('.')] = lhsIsVal ? { lhs, rhs: v } : { rhs: v };
|
|
1259
|
+
return a;
|
|
1260
|
+
}, Object.create(null));
|
|
1261
|
+
|
|
1262
|
+
lhspaths.forEach(v => {
|
|
1263
|
+
const k = v.ref.slice(lhs.ref.length).join('.');
|
|
1264
|
+
if(xref[k])
|
|
1265
|
+
xref[k].lhs = v;
|
|
1266
|
+
else
|
|
1267
|
+
xref[k] = { lhs: v };
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
return xref;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1211
1273
|
function getType(art) {
|
|
1212
1274
|
const effart = effectiveType(art);
|
|
1213
1275
|
return Object.keys(effart).length ? effart : art.type;
|
|
@@ -1218,7 +1280,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1218
1280
|
if(art) {
|
|
1219
1281
|
// items in ON conds are illegal but this should be checked elsewhere
|
|
1220
1282
|
const elements = art.elements || (art.items && art.items.elements);
|
|
1221
|
-
return (elements || art.target && art.keys)
|
|
1283
|
+
return !!(elements || art.target && art.keys)
|
|
1222
1284
|
}
|
|
1223
1285
|
return false;
|
|
1224
1286
|
}
|
|
@@ -1241,7 +1303,6 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1241
1303
|
|
|
1242
1304
|
}
|
|
1243
1305
|
|
|
1244
|
-
|
|
1245
1306
|
/**
|
|
1246
1307
|
* Modify the given CSN/artifact in-place, applying the given customTransformations.
|
|
1247
1308
|
* Dictionaries are correctly handled - a "type" transformer will not be called on an entity called "type".
|
|
@@ -1333,5 +1394,6 @@ function transformModel(csn, customTransformations, transformNonEnumerableElemen
|
|
|
1333
1394
|
module.exports = {
|
|
1334
1395
|
// This function retrieves the actual exports
|
|
1335
1396
|
getTransformers,
|
|
1336
|
-
transformModel
|
|
1397
|
+
transformModel,
|
|
1398
|
+
RelationalOperators
|
|
1337
1399
|
};
|
|
@@ -9,7 +9,7 @@ const { deduplicateMessages } = require('../base/messages');
|
|
|
9
9
|
const { timetrace } = require('../utils/timetrace');
|
|
10
10
|
// Paths that start with an artifact of protected kind are special
|
|
11
11
|
// either ignore them in QAT building or in path rewriting
|
|
12
|
-
const internalArtifactKinds = ['builtin'
|
|
12
|
+
const internalArtifactKinds = ['builtin', '$parameters', 'param'];
|
|
13
13
|
|
|
14
14
|
function translateAssocsToJoinsCSN(csn, options){
|
|
15
15
|
timetrace.start('Recompiling model');
|
|
@@ -444,7 +444,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
444
444
|
}
|
|
445
445
|
else if(art.target) { // it's not an artifact, so it should be an assoc step
|
|
446
446
|
if(joinTree === undefined)
|
|
447
|
-
throw Error('
|
|
447
|
+
throw Error('Can\'t follow Associations without starting Entity');
|
|
448
448
|
|
|
449
449
|
if(!childQat.$QA)
|
|
450
450
|
childQat.$QA = createQA(env, art.target._artifact, art.name.id, childQat._namedArgs);
|
|
@@ -762,7 +762,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
762
762
|
|
|
763
763
|
let [head, ...tail] = path;
|
|
764
764
|
|
|
765
|
-
|
|
765
|
+
// don't rewrite path
|
|
766
|
+
if(internalArtifactKinds.includes(head._artifact.kind))
|
|
766
767
|
return pathNode;
|
|
767
768
|
|
|
768
769
|
// strip the absolute path indicators
|
|
@@ -1536,7 +1537,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1536
1537
|
( // not leaf
|
|
1537
1538
|
i < tail.length-1 &&
|
|
1538
1539
|
// path terminates on a scalar type
|
|
1539
|
-
// _effectiveType.elements can be removed if
|
|
1540
|
+
// _effectiveType.elements can be removed if forRelationalDB can expand fk paths correctly
|
|
1540
1541
|
!(tail[tail.length-1]._artifact._effectiveType.elements || tail[tail.length-1]._artifact._effectiveType.target) &&
|
|
1541
1542
|
// association is managed
|
|
1542
1543
|
art.foreignKeys &&
|
|
@@ -173,13 +173,13 @@ function _errorFileNotFound(dep, options, { error }) {
|
|
|
173
173
|
resolved = resolved.replace( /\\/g, '/' );
|
|
174
174
|
for (const from of dep.usingFroms) {
|
|
175
175
|
error( 'file-not-readable', from.location, { file: resolved },
|
|
176
|
-
'
|
|
176
|
+
'Can\'t read file $(FILE)' );
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
else if (isLocalFile( dep.module ) ) {
|
|
180
180
|
for (const from of dep.usingFroms) {
|
|
181
181
|
error( 'file-unknown-local', from.location, { file: dep.module },
|
|
182
|
-
'
|
|
182
|
+
'Can\'t find local module $(FILE)' );
|
|
183
183
|
}
|
|
184
184
|
}
|
|
185
185
|
else {
|
|
@@ -187,8 +187,8 @@ function _errorFileNotFound(dep, options, { error }) {
|
|
|
187
187
|
for (const from of dep.usingFroms) {
|
|
188
188
|
error( 'file-unknown-package', from.location,
|
|
189
189
|
{ file: dep.module, '#': internal }, {
|
|
190
|
-
std: '
|
|
191
|
-
internal: '
|
|
190
|
+
std: 'Can\'t find package $(FILE)',
|
|
191
|
+
internal: 'Can\'t find package module $(FILE)',
|
|
192
192
|
} );
|
|
193
193
|
}
|
|
194
194
|
}
|
|
@@ -417,7 +417,7 @@ function resolveCDS(moduleName, options, callback) {
|
|
|
417
417
|
* @returns {Error}
|
|
418
418
|
*/
|
|
419
419
|
function makeNotFoundError() {
|
|
420
|
-
const moduleError = new Error(`
|
|
420
|
+
const moduleError = new Error(`Can't find module '${ moduleName }' from '${ options.basedir }'`);
|
|
421
421
|
// eslint-disable-next-line
|
|
422
422
|
moduleError['code'] = 'MODULE_NOT_FOUND';
|
|
423
423
|
return moduleError;
|
package/lib/utils/objectUtils.js
CHANGED
|
@@ -37,7 +37,7 @@ function createDict(obj) {
|
|
|
37
37
|
*/
|
|
38
38
|
function forEach(obj, callback) {
|
|
39
39
|
for (const key in obj) {
|
|
40
|
-
if (Object.hasOwnProperty.call(obj, key))
|
|
40
|
+
if (Object.prototype.hasOwnProperty.call(obj, key))
|
|
41
41
|
callback(key, obj[key]);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -51,7 +51,7 @@ function forEach(obj, callback) {
|
|
|
51
51
|
*/
|
|
52
52
|
function forEachValue(o, callback) {
|
|
53
53
|
for (const key in o) {
|
|
54
|
-
if (Object.hasOwnProperty.call(o, key))
|
|
54
|
+
if (Object.prototype.hasOwnProperty.call(o, key))
|
|
55
55
|
callback(o[key]);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -65,7 +65,7 @@ function forEachValue(o, callback) {
|
|
|
65
65
|
*/
|
|
66
66
|
function forEachKey(o, callback) {
|
|
67
67
|
for (const key in o) {
|
|
68
|
-
if (Object.hasOwnProperty.call(o, key))
|
|
68
|
+
if (Object.prototype.hasOwnProperty.call(o, key))
|
|
69
69
|
callback(key);
|
|
70
70
|
}
|
|
71
71
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.2",
|
|
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)",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"download": "node scripts/downloadANTLR.js",
|
|
18
18
|
"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
|
|
19
|
-
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.
|
|
19
|
+
"xmakeBeforeInstall": "echo \"Due to binary mirror, use sqlite 5.1.1 explicitly\" && npm install --save --save-exact --no-package-lock sqlite3@5.1.1",
|
|
20
20
|
"xmakeAfterInstall": "npm run gen",
|
|
21
21
|
"xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",
|
|
22
22
|
"test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
|