@sap/cds-compiler 3.1.2 → 3.4.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 +101 -3
- package/bin/cdsc.js +4 -2
- package/doc/CHANGELOG_BETA.md +35 -0
- package/lib/api/main.js +153 -29
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +106 -24
- package/lib/base/message-registry.js +177 -79
- package/lib/base/messages.js +78 -57
- package/lib/base/model.js +2 -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 +53 -0
- package/lib/checks/defaultValues.js +4 -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 +23 -0
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/selectItems.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/types.js +2 -2
- package/lib/checks/utils.js +17 -7
- package/lib/checks/validator.js +36 -14
- package/lib/compiler/assert-consistency.js +21 -13
- package/lib/compiler/builtins.js +8 -0
- package/lib/compiler/checks.js +57 -40
- package/lib/compiler/define.js +139 -69
- package/lib/compiler/extend.js +319 -50
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/kick-start.js +2 -35
- package/lib/compiler/populate.js +111 -68
- package/lib/compiler/propagator.js +5 -3
- package/lib/compiler/resolve.js +71 -108
- package/lib/compiler/shared.js +82 -54
- package/lib/compiler/tweak-assocs.js +26 -14
- package/lib/compiler/utils.js +13 -2
- package/lib/edm/annotations/genericTranslation.js +10 -7
- package/lib/edm/csn2edm.js +11 -11
- package/lib/edm/edm.js +17 -9
- package/lib/edm/edmPreprocessor.js +53 -30
- package/lib/edm/edmUtils.js +7 -2
- 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 +4312 -4186
- package/lib/inspect/inspectModelStatistics.js +1 -1
- package/lib/inspect/inspectPropagation.js +23 -9
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +161 -172
- package/lib/json/to-csn.js +70 -10
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +8 -11
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +140 -93
- package/lib/language/language.g4 +57 -33
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.d.ts +3 -6
- package/lib/main.js +1 -0
- package/lib/model/.eslintrc.json +13 -0
- package/lib/model/api.js +4 -2
- package/lib/model/csnRefs.js +78 -50
- package/lib/model/csnUtils.js +272 -222
- package/lib/model/enrichCsn.js +41 -31
- package/lib/model/revealInternalProperties.js +61 -57
- package/lib/model/sortViews.js +35 -31
- package/lib/modelCompare/compare.js +52 -18
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +10 -1
- package/lib/render/manageConstraints.js +11 -7
- package/lib/render/toCdl.js +151 -106
- package/lib/render/toHdbcds.js +8 -6
- package/lib/render/toRename.js +4 -4
- package/lib/render/toSql.js +17 -7
- package/lib/render/utils/common.js +27 -9
- package/lib/render/utils/sql.js +5 -5
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/applyTransformations.js +32 -3
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +92 -41
- package/lib/transform/db/flattening.js +1 -1
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +8 -2
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forOdataNew.js +10 -7
- package/lib/transform/{forHanaNew.js → forRelationalDB.js} +18 -12
- package/lib/transform/localized.js +29 -20
- package/lib/transform/odata/toFinalBaseType.js +8 -11
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +122 -51
- package/lib/transform/translateAssocsToJoins.js +17 -16
- package/lib/utils/moduleResolve.js +5 -5
- package/lib/utils/objectUtils.js +3 -3
- package/lib/utils/term.js +5 -5
- 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 → syntax-expecting-integer.md} +4 -4
- package/share/messages/wildcard-excluding-one.md +37 -0
|
@@ -247,14 +247,14 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
|
|
|
247
247
|
if (isUnion(queryPath) && options.transformation === 'hdbcds') {
|
|
248
248
|
if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
|
|
249
249
|
if (elem.keys)
|
|
250
|
-
info(null, queryPath,
|
|
250
|
+
info(null, queryPath, { name: elemName }, 'Managed association $(NAME), published in a UNION, will be ignored');
|
|
251
251
|
else
|
|
252
|
-
info(null, queryPath,
|
|
252
|
+
info(null, queryPath, { name: elemName }, 'Association $(NAME), published in a UNION, will be ignored');
|
|
253
253
|
|
|
254
254
|
elem._ignore = true;
|
|
255
255
|
}
|
|
256
256
|
else {
|
|
257
|
-
error(null, queryPath,
|
|
257
|
+
error(null, queryPath, { name: elemName }, 'Association $(NAME) can\'t be published in a SAP HANA CDS UNION');
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
else if (queryPath.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
|
|
@@ -325,17 +325,40 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
|
|
|
325
325
|
* due to an issue with HANA - only for hdbcds-hdbcds, I assume flattening
|
|
326
326
|
* takes care of this for the other cases already
|
|
327
327
|
*
|
|
328
|
-
* @param {CSN.
|
|
328
|
+
* @param {CSN.Column} col
|
|
329
329
|
* @param {CSN.Path} path
|
|
330
330
|
*/
|
|
331
|
-
function addImplicitAliasWithAssoc(
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
function addImplicitAliasWithAssoc(col, path) {
|
|
332
|
+
if (!col.as && col.ref && col.ref.length > 1) {
|
|
333
|
+
const { links } = inspectRef(path);
|
|
334
|
+
if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
|
|
335
|
+
col.as = getLastRefStepString(col.ref);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* If simply selecting from a param like `:param`, we need to add an implicit alias like `:param as param`
|
|
341
|
+
* due to an issue with HANA
|
|
342
|
+
*
|
|
343
|
+
* @param {CSN.Column} col
|
|
344
|
+
*/
|
|
345
|
+
function addImplicitAliasWithLonelyParam(col) {
|
|
346
|
+
if (!col.as && col.param)
|
|
347
|
+
col.as = getLastRefStepString(col.ref);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Loop over the columns and call all of the given functions with the column and the path
|
|
353
|
+
*
|
|
354
|
+
* @param {Function[]} functions
|
|
355
|
+
* @param {CSN.Column[]} columns
|
|
356
|
+
* @param {CSN.Path} path
|
|
357
|
+
*/
|
|
358
|
+
function processColumns(functions, columns, path) {
|
|
359
|
+
for (let i = 0; i < columns.length; i++) {
|
|
360
|
+
const col = columns[i];
|
|
361
|
+
functions.forEach(fn => fn(col, path.concat(i)));
|
|
339
362
|
}
|
|
340
363
|
}
|
|
341
364
|
|
|
@@ -401,8 +424,14 @@ function getViewTransformer(csn, options, messageFunctions, transformCommon) {
|
|
|
401
424
|
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
402
425
|
// If following an association, explicitly set the implicit alias
|
|
403
426
|
// due to an issue with HANA - this seems to only have an effect on ref files with hdbcds-hdbcds, so only run then
|
|
427
|
+
const columnProcessors = [];
|
|
428
|
+
if (options.transformation === 'hdbcds' || options.transformation === 'sql' && options.sqlDialect === 'hana')
|
|
429
|
+
columnProcessors.push(addImplicitAliasWithLonelyParam);
|
|
404
430
|
if (options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')
|
|
405
|
-
addImplicitAliasWithAssoc
|
|
431
|
+
columnProcessors.push(addImplicitAliasWithAssoc);
|
|
432
|
+
|
|
433
|
+
if (columnProcessors.length > 0)
|
|
434
|
+
processColumns(columnProcessors, query.SELECT.columns, path.concat('columns'));
|
|
406
435
|
|
|
407
436
|
delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
|
|
408
437
|
|
|
@@ -165,8 +165,8 @@ function generateDrafts(csn, options, pathDelimiter, messageFunctions) {
|
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
else {
|
|
168
|
-
error(null, [ 'definitions', draftRootName ], { name: persistenceName },
|
|
169
|
-
|
|
168
|
+
error(null, [ 'definitions', draftRootName ], { name: persistenceName, alias: definingDraftRoot },
|
|
169
|
+
'Entity $(NAME) already generated by draft root $(ALIAS)');
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
return;
|
|
@@ -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' && options.sqlDialect !== 'h2' && 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
|
}
|
|
@@ -625,12 +625,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
625
625
|
}
|
|
626
626
|
else if (options.sqlDialect === 'sqlite') { // view with params
|
|
627
627
|
// Allow with plain
|
|
628
|
-
error(null, [ 'definitions', artifactName ],
|
|
628
|
+
error(null, [ 'definitions', artifactName ], 'SQLite does not support entities with parameters');
|
|
629
629
|
}
|
|
630
630
|
else {
|
|
631
631
|
for (const pname in artifact.params) {
|
|
632
632
|
if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
|
|
633
|
-
warning(null, [ 'definitions', artifactName, 'params', pname ],
|
|
633
|
+
warning(null, [ 'definitions', artifactName, 'params', pname ], 'Expecting regular SQL-Identifier');
|
|
634
634
|
}
|
|
635
635
|
else if (options.sqlMapping !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
|
|
636
636
|
warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.sqlMapping },
|
|
@@ -704,6 +704,11 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
704
704
|
node.length = 36;
|
|
705
705
|
setProp(node, '$renamed', 'cds.UUID');
|
|
706
706
|
}
|
|
707
|
+
|
|
708
|
+
if(options.sqlDialect === 'h2' && val === 'cds.Decimal' && !node.scale) {
|
|
709
|
+
node[key] = 'cds.DecimalFloat'; // cds.Decimal and cds.Decimal(p) should map do DECFLOAT for h2
|
|
710
|
+
}
|
|
711
|
+
|
|
707
712
|
// Length/Precision/Scale is done in addDefaultTypeFacets
|
|
708
713
|
}
|
|
709
714
|
|
|
@@ -860,7 +865,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
860
865
|
// not to the view).
|
|
861
866
|
// FIXME: This also means that corresponding key fields should be in the select list etc ...
|
|
862
867
|
if (!art.query && !art.projection && assoc.target && assoc.target != artifactName)
|
|
863
|
-
error(null, path,
|
|
868
|
+
error(null, path, { name: '$self' }, 'Only an association that points back to this artifact can be compared to $(NAME)');
|
|
864
869
|
|
|
865
870
|
|
|
866
871
|
// Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
|
|
@@ -868,7 +873,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
868
873
|
const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
|
|
869
874
|
|
|
870
875
|
if (containsDollarSelf)
|
|
871
|
-
error(null, path,
|
|
876
|
+
error(null, path, { name: '$self' },
|
|
877
|
+
'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
|
|
872
878
|
}
|
|
873
879
|
|
|
874
880
|
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
@@ -983,7 +989,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
983
989
|
const absolute = node.type;
|
|
984
990
|
const parameters = node.parameters || [];
|
|
985
991
|
// :FIXME: Is this dead code? node.parameters is always undefined...
|
|
986
|
-
//
|
|
992
|
+
// forRelationalDB tested against the parameters of the type definition which is not available in CSN
|
|
987
993
|
for (const name in parameters) {
|
|
988
994
|
const param = parameters[name];
|
|
989
995
|
if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
|
|
@@ -1086,7 +1092,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1086
1092
|
else if (art.elements) {
|
|
1087
1093
|
// The reference is structured
|
|
1088
1094
|
if (isFulltextIndex)
|
|
1089
|
-
error(null, path,
|
|
1095
|
+
error(null, path, { name: artName }, 'A fulltext index can\'t be defined on a structured element $(NAME)');
|
|
1090
1096
|
// First, compute the name from the path, e.g ['s', 's1', 's2' ] will result in 'S_s1_s2' ...
|
|
1091
1097
|
const refPath = flattenStructStepsInRef(val.ref, path);
|
|
1092
1098
|
// ... and take this as the prefix for all elements
|
|
@@ -1124,5 +1130,5 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1124
1130
|
|
|
1125
1131
|
|
|
1126
1132
|
module.exports = {
|
|
1127
|
-
|
|
1133
|
+
transformForRelationalDBWithCsn,
|
|
1128
1134
|
};
|
|
@@ -73,7 +73,7 @@ const _targetFor = Symbol('_targetFor');
|
|
|
73
73
|
*/
|
|
74
74
|
function _addLocalizationViews(csn, options, useJoins, config) {
|
|
75
75
|
// Don't try to create convenience views with errors.
|
|
76
|
-
if (hasErrors(options.messages))
|
|
76
|
+
if (hasErrors(options.messages)) // TODO: this is actually wrong, consider --test-mode
|
|
77
77
|
return csn;
|
|
78
78
|
|
|
79
79
|
const messageFunctions = makeMessageFunction(csn, options);
|
|
@@ -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,8 @@ 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, { type: fullQualifiedNewTypeName, name: memberName },
|
|
112
|
+
'Can\'t create artificial type $(TYPE) for $(NAME) because the name is already used');
|
|
112
113
|
return;
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* parseExpr accepts any JSON object and tries to convert a token stream expression
|
|
5
|
+
* array into an AST like expression with CDL operator precedence.
|
|
6
|
+
*
|
|
7
|
+
* The following operators are supported:
|
|
8
|
+
*
|
|
9
|
+
* Multiplication/Division: '*', '/'
|
|
10
|
+
* Addition/Subtraction: '+', '-'
|
|
11
|
+
* Concatenation: '||'
|
|
12
|
+
* Relational: '=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in', 'exists', 'between and'
|
|
13
|
+
* Unary: 'is [not] null', 'not'
|
|
14
|
+
* Conditional: 'case [when then]+ [else]? end', 'and', 'or'
|
|
15
|
+
*
|
|
16
|
+
* Not yet implmemented: 'new'
|
|
17
|
+
*
|
|
18
|
+
* This is not an optimized LL(1) parser but a token 'sniffer'. A stream is
|
|
19
|
+
* cracked up in sub streams and passed down to the next higher function.
|
|
20
|
+
*
|
|
21
|
+
* Complex aggregates like case/when/else/end and between are parsed first to pass down the
|
|
22
|
+
* resulting sub expressions and avoiding 'and' ambiguities.
|
|
23
|
+
*
|
|
24
|
+
* Sub expressions are grouped as arrays, the final AST is an array of nested arrays.
|
|
25
|
+
* Alternatively, an object like AST can be produced by setting argument 'array' to false.
|
|
26
|
+
*
|
|
27
|
+
* This parser intentionally does no error handling. If a clause is malformed, it is accepted as is.
|
|
28
|
+
*
|
|
29
|
+
* @param {any} xpr A JSON object.
|
|
30
|
+
* @param {Boolean} array Bias AST representation.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
function parseExpr(xpr, array=true) {
|
|
34
|
+
return parseExprInt(xpr);
|
|
35
|
+
|
|
36
|
+
function parseExprInt(xpr) {
|
|
37
|
+
return conditionOR(...CaseWhen(xpr));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function CaseWhen(xpr) {
|
|
41
|
+
if(Array.isArray(xpr))
|
|
42
|
+
inner(xpr);
|
|
43
|
+
return [xpr, 0, Array.isArray(xpr) ? xpr.length : 1];
|
|
44
|
+
|
|
45
|
+
// replace case/end from inner to outer
|
|
46
|
+
function inner(pxpr, lvl=0) {
|
|
47
|
+
const s = pxpr.findIndex(t => t === 'case');
|
|
48
|
+
if(s >= 0) {
|
|
49
|
+
let e = findLastIndex(pxpr, 'end');
|
|
50
|
+
pxpr = pxpr.slice(s+1, e);
|
|
51
|
+
const dist = inner(pxpr, lvl+1);
|
|
52
|
+
e -= dist;
|
|
53
|
+
if(dist > 0)
|
|
54
|
+
pxpr = xpr.slice(s+1, e+1);
|
|
55
|
+
const caseTree = array ? [ 'case' ] : { 'case': [] };
|
|
56
|
+
let i = pxpr.findIndex(t => t === 'else');
|
|
57
|
+
let elseCond = undefined;
|
|
58
|
+
if(i >= 0) {
|
|
59
|
+
elseCond = pxpr.slice(i+1);
|
|
60
|
+
pxpr = pxpr.slice(0, i);
|
|
61
|
+
}
|
|
62
|
+
i = pxpr.findIndex(t => t === 'when');
|
|
63
|
+
while(i >= 0) {
|
|
64
|
+
pxpr = pxpr.slice(i+1);
|
|
65
|
+
const when = { 'when': [] };
|
|
66
|
+
if(array)
|
|
67
|
+
caseTree.push('when');
|
|
68
|
+
else
|
|
69
|
+
caseTree.case.push(when);
|
|
70
|
+
i = pxpr.findIndex(t => t === 'then');
|
|
71
|
+
if(i >= 0) {
|
|
72
|
+
const arg = pxpr.slice(0, i);
|
|
73
|
+
if(array)
|
|
74
|
+
caseTree.push(arg);
|
|
75
|
+
else
|
|
76
|
+
when.when.push(arg.length === 1 ? arg[0] : arg);
|
|
77
|
+
}
|
|
78
|
+
pxpr = pxpr.slice(i+1);
|
|
79
|
+
i = pxpr.findIndex(t => t === 'when');
|
|
80
|
+
const arg = ((i >= 0) ? pxpr.slice(0, i) : pxpr);
|
|
81
|
+
if(array)
|
|
82
|
+
caseTree.push('then', arg);
|
|
83
|
+
else
|
|
84
|
+
when.when.push(arg.length === 1 ? arg[0] : arg);
|
|
85
|
+
}
|
|
86
|
+
if(elseCond) {
|
|
87
|
+
if(array)
|
|
88
|
+
caseTree.push('else', elseCond);
|
|
89
|
+
else
|
|
90
|
+
caseTree.case.push(elseCond.length === 1 ? elseCond[0] : elseCond);
|
|
91
|
+
}
|
|
92
|
+
if(array)
|
|
93
|
+
caseTree.push('end');
|
|
94
|
+
if(lvl > 0)
|
|
95
|
+
xpr.splice(s+1, e-s+1, caseTree);
|
|
96
|
+
else {
|
|
97
|
+
xpr = caseTree;
|
|
98
|
+
}
|
|
99
|
+
return e-s+1;
|
|
100
|
+
}
|
|
101
|
+
else
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function findLastIndex(expr, token, l=expr.length-1) {
|
|
106
|
+
while(l >= 0 && expr[l] !== token) l--;
|
|
107
|
+
return l;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function conditionOR(xpr, s, e) {
|
|
112
|
+
return binaryExpr(xpr, ['or'], conditionAnd, s, e);
|
|
113
|
+
}
|
|
114
|
+
function conditionAnd(xpr, s, e) {
|
|
115
|
+
return binaryExpr(xpr, (xpr, s, e) => {
|
|
116
|
+
let a = s-1;
|
|
117
|
+
let b;
|
|
118
|
+
// scan for 'and', skip 'between/and'
|
|
119
|
+
do {
|
|
120
|
+
b = false;
|
|
121
|
+
for(a++; xpr[a] !== 'and' && a < e; a++) {
|
|
122
|
+
if(xpr[a] === 'between')
|
|
123
|
+
b = true;
|
|
124
|
+
}
|
|
125
|
+
} while(b && a < e)
|
|
126
|
+
|
|
127
|
+
if(!b && a < e)
|
|
128
|
+
return [1, a]
|
|
129
|
+
else
|
|
130
|
+
return [1, -1];
|
|
131
|
+
}, conditionTerm, s, e);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function conditionTerm(xpr, s, e) {
|
|
135
|
+
if(Array.isArray(xpr)) {
|
|
136
|
+
if(xpr.length >= 3 && xpr[s+1] === 'is') {
|
|
137
|
+
if(xpr[s+2] === 'null')
|
|
138
|
+
return array ? [ conditionOR(xpr[s]), 'is', 'null' ] : { 'isNull': conditionOR(xpr[s]) };
|
|
139
|
+
else if(xpr[s+2] === 'not' && xpr[s+3] === 'null')
|
|
140
|
+
return array ? [ conditionOR(xpr[s]), 'is', 'not', 'null' ] : { 'isNotNull': conditionOR(xpr[s]) };
|
|
141
|
+
}
|
|
142
|
+
if(xpr[s] === 'not')
|
|
143
|
+
return array ? [ 'not', conditionTerm(xpr, s+1, e) ] : { 'not': conditionTerm(xpr, s+1, e) };
|
|
144
|
+
if(xpr[s] === 'exists')
|
|
145
|
+
return array ? [ 'exists', conditionOR(xpr[s+1]) ] : { 'exists': conditionOR(xpr[s+1]) };
|
|
146
|
+
}
|
|
147
|
+
return compareTerm(xpr, s, e);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function compareTerm(xpr, s, e) {
|
|
151
|
+
if(Array.isArray(xpr)) {
|
|
152
|
+
let i = s;
|
|
153
|
+
while(i < e && xpr[i] !== 'between') i++;
|
|
154
|
+
const b = i < e ? i : -1;
|
|
155
|
+
while(i < e && xpr[i] !== 'and') i++;
|
|
156
|
+
const a = i < e ? i : -1;
|
|
157
|
+
if(b >= 0) {
|
|
158
|
+
const expr = expression(xpr, s, b);
|
|
159
|
+
const between = array ? [ expr, 'between' ] : { 'between': [ expr ] };
|
|
160
|
+
if(a >= 0) {
|
|
161
|
+
const lower = expression(xpr, b+1, a);
|
|
162
|
+
const upper = expression(xpr, a+1, e);
|
|
163
|
+
if(array)
|
|
164
|
+
between.push(lower, 'and', upper);
|
|
165
|
+
else {
|
|
166
|
+
between.between.push(lower, upper);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const unspec = expression(xpr, b+1, e);
|
|
171
|
+
if(array)
|
|
172
|
+
between.push(unspec);
|
|
173
|
+
else
|
|
174
|
+
between.between.push(unspec);
|
|
175
|
+
}
|
|
176
|
+
return between;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return binaryExpr(xpr, ['=', '<>', '>', '>=', '<', '<=', '!=', 'like', 'in'], expression, s, e);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function expression(xpr, s, e) {
|
|
183
|
+
return binaryExpr(xpr, ['||'], exprAddSub, s, e);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function exprAddSub(xpr, s, e) {
|
|
187
|
+
return binaryExpr(xpr, ['+', '-'], exprMulDiv, s, e);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function exprMulDiv(xpr, s, e) {
|
|
191
|
+
return binaryExpr(xpr, ['*', '/'], terminal, s, e);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function terminal(xpr, s, e) {
|
|
195
|
+
if(Array.isArray(xpr) && xpr.length > 0) {
|
|
196
|
+
if(e-s <= 1)
|
|
197
|
+
return parseExprInt(xpr[e-1]);
|
|
198
|
+
else
|
|
199
|
+
return xpr.slice(s, e).map(parseExprInt);
|
|
200
|
+
}
|
|
201
|
+
if (typeof xpr === 'object') {
|
|
202
|
+
for(let n in xpr) {
|
|
203
|
+
xpr[n] = parseExprInt(xpr[n]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return xpr;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function binaryExpr(xpr, token, next, s, e) {
|
|
210
|
+
if (Array.isArray(xpr)) {
|
|
211
|
+
let [tl, p] = findToken(s, e);
|
|
212
|
+
if (p >= 0) {
|
|
213
|
+
let lhs = next(xpr, s, p);
|
|
214
|
+
let op = xpr[p];
|
|
215
|
+
s = p+tl;
|
|
216
|
+
[tl, p] = findToken(s, e);
|
|
217
|
+
while(p >= 0) {
|
|
218
|
+
let rhs = next(xpr, s, p);
|
|
219
|
+
lhs = array ? [ lhs, op, rhs ] : { [op]: [lhs, rhs] };
|
|
220
|
+
op = xpr[p];
|
|
221
|
+
s = p+tl;
|
|
222
|
+
[tl, p] = findToken(s, e);
|
|
223
|
+
}
|
|
224
|
+
return array ? [ lhs, op, next(xpr, s, e) ] : { [op]: [lhs, next(xpr, s, e)] };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return next(xpr, s, e);
|
|
228
|
+
|
|
229
|
+
function findToken(s, e) {
|
|
230
|
+
if(typeof token === 'function')
|
|
231
|
+
return token(xpr, s, e);
|
|
232
|
+
else {
|
|
233
|
+
while(s < e && !token.includes(xpr[s])) s++;
|
|
234
|
+
if(s < e)
|
|
235
|
+
return [1, s];
|
|
236
|
+
}
|
|
237
|
+
return [1, -1];
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
parseExpr,
|
|
245
|
+
};
|