@sap/cds-compiler 3.6.2 → 3.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 +109 -1
- package/README.md +3 -0
- package/bin/cdsc.js +12 -5
- package/doc/CHANGELOG_ARCHIVE.md +6 -6
- package/doc/CHANGELOG_BETA.md +35 -2
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/doc/DeprecatedOptions_v2.md +1 -1
- package/doc/NameResolution.md +1 -1
- package/lib/api/main.js +63 -23
- package/lib/api/options.js +1 -0
- package/lib/api/validate.js +5 -0
- package/lib/base/dictionaries.js +15 -3
- package/lib/base/keywords.js +2 -0
- package/lib/base/message-registry.js +120 -34
- package/lib/base/messages.js +51 -27
- package/lib/base/model.js +4 -2
- package/lib/base/shuffle.js +2 -1
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/defaultValues.js +1 -1
- package/lib/checks/elements.js +29 -1
- package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/onConditions.js +15 -9
- package/lib/checks/sql-snippets.js +2 -2
- package/lib/checks/types.js +5 -1
- package/lib/checks/validator.js +7 -3
- package/lib/compiler/assert-consistency.js +42 -26
- package/lib/compiler/base.js +50 -4
- package/lib/compiler/builtins.js +17 -8
- package/lib/compiler/checks.js +241 -246
- package/lib/compiler/define.js +113 -146
- package/lib/compiler/extend.js +889 -383
- package/lib/compiler/finalize-parse-cdl.js +5 -58
- package/lib/compiler/index.js +1 -1
- package/lib/compiler/kick-start.js +7 -8
- package/lib/compiler/populate.js +297 -293
- package/lib/compiler/propagator.js +27 -18
- package/lib/compiler/resolve.js +146 -463
- package/lib/compiler/shared.js +36 -79
- package/lib/compiler/tweak-assocs.js +30 -28
- package/lib/compiler/utils.js +31 -5
- package/lib/edm/annotations/genericTranslation.js +131 -59
- package/lib/edm/annotations/preprocessAnnotations.js +3 -0
- package/lib/edm/csn2edm.js +22 -5
- package/lib/edm/edm.js +6 -4
- package/lib/edm/edmAnnoPreprocessor.js +1 -0
- package/lib/edm/edmPreprocessor.js +42 -26
- package/lib/gen/Dictionary.json +38 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageLexer.js +1 -1
- package/lib/gen/languageParser.js +4828 -4472
- package/lib/inspect/inspectPropagation.js +20 -34
- package/lib/json/from-csn.js +140 -44
- package/lib/json/to-csn.js +114 -122
- package/lib/language/errorStrategy.js +2 -0
- package/lib/language/genericAntlrParser.js +156 -36
- package/lib/language/language.g4 +100 -58
- package/lib/language/textUtils.js +13 -0
- package/lib/main.d.ts +43 -3
- package/lib/main.js +4 -2
- package/lib/model/csnRefs.js +15 -3
- package/lib/model/csnUtils.js +12 -74
- package/lib/model/revealInternalProperties.js +4 -2
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +3 -0
- package/lib/render/manageConstraints.js +5 -2
- package/lib/render/toCdl.js +216 -104
- package/lib/render/toHdbcds.js +2 -9
- package/lib/render/toRename.js +14 -51
- package/lib/render/toSql.js +4 -3
- package/lib/render/utils/common.js +9 -5
- package/lib/transform/braceExpression.js +6 -0
- package/lib/transform/db/assertUnique.js +2 -1
- package/lib/transform/db/expansion.js +2 -0
- package/lib/transform/db/flattening.js +37 -36
- package/lib/transform/db/rewriteCalculatedElements.js +600 -0
- package/lib/transform/db/transformExists.js +4 -0
- package/lib/transform/db/views.js +40 -37
- package/lib/transform/forOdataNew.js +20 -15
- package/lib/transform/forRelationalDB.js +58 -41
- package/lib/transform/odata/typesExposure.js +50 -15
- package/lib/transform/parseExpr.js +16 -8
- package/lib/transform/transformUtilsNew.js +42 -14
- package/lib/transform/translateAssocsToJoins.js +60 -37
- package/lib/transform/universalCsn/coreComputed.js +15 -7
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +2 -1
|
@@ -60,7 +60,7 @@ const { addLocalizationViews } = require('./localized');
|
|
|
60
60
|
// - Mark fields with @odata.on.insert/update as @Core.Computed
|
|
61
61
|
// (EdmPreproc candidate, check with RT if @Core.Computed required by them)
|
|
62
62
|
// - Rename shorthand annotations according to a builtin list (EdmPreproc Candidate)
|
|
63
|
-
// e.g. @label -> @Common.Label
|
|
63
|
+
// e.g. @label -> @Common.Label
|
|
64
64
|
// - If the association target is annotated with @cds.odata.valuelist, annotate the
|
|
65
65
|
// association with @Common.ValueList.viaAssociation (EdmPreproc Candidate)
|
|
66
66
|
// - Check for @Analytics.Measure and @Aggregation.default (Linter check candidate, remove)
|
|
@@ -315,31 +315,36 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
315
315
|
// list.
|
|
316
316
|
function renameShorthandAnnotations(node) {
|
|
317
317
|
// FIXME: Verify this list - are they all still required? Do we need any more?
|
|
318
|
-
const
|
|
318
|
+
const setMappings = {
|
|
319
319
|
'@label': '@Common.Label',
|
|
320
320
|
'@title': '@Common.Label',
|
|
321
321
|
'@description': '@Core.Description',
|
|
322
|
+
};
|
|
323
|
+
const renameMappings = {
|
|
322
324
|
'@ValueList.entity': '@Common.ValueList.entity',
|
|
323
325
|
'@ValueList.type': '@Common.ValueList.type',
|
|
324
326
|
'@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
|
|
325
327
|
'@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
|
|
326
328
|
'@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
|
|
327
329
|
'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
|
|
328
|
-
}
|
|
330
|
+
};
|
|
329
331
|
|
|
330
|
-
const
|
|
332
|
+
const setShortCuts = Object.keys(setMappings);
|
|
333
|
+
const renameShortCuts = Object.keys(renameMappings);
|
|
331
334
|
Object.keys(node).forEach( name => {
|
|
335
|
+
if (!name.startsWith('@'))
|
|
336
|
+
return;
|
|
332
337
|
// Rename according to map above
|
|
333
|
-
const
|
|
334
|
-
if(
|
|
335
|
-
renameAnnotation(node, name, name.replace(
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
|
|
339
|
+
if(renamePrefix) {
|
|
340
|
+
renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
|
|
341
|
+
} else {
|
|
342
|
+
// The two mappings have no overlap, so no need to check for second map if first matched.
|
|
343
|
+
// Rename according to map above
|
|
344
|
+
const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
|
|
345
|
+
if(setPrefix) {
|
|
346
|
+
setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
|
|
347
|
+
}
|
|
343
348
|
}
|
|
344
349
|
|
|
345
350
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
@@ -351,7 +356,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
351
356
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
352
357
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
353
358
|
} else {
|
|
354
|
-
|
|
359
|
+
setAnnotation(node, '@Core.Computed', true);
|
|
355
360
|
}
|
|
356
361
|
}
|
|
357
362
|
// @insertonly is effective on entities/queries only
|
|
@@ -9,7 +9,7 @@ const { cloneCsnNonDict,
|
|
|
9
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
10
10
|
const transformUtils = require('./transformUtilsNew');
|
|
11
11
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
12
|
-
const { csnRefs, pathId } = require('../model/csnRefs');
|
|
12
|
+
const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
|
|
13
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
14
14
|
const validate = require('../checks/validator');
|
|
15
15
|
const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
|
|
@@ -18,6 +18,7 @@ const { timetrace } = require('../utils/timetrace');
|
|
|
18
18
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
19
19
|
const { createDict, forEach } = require('../utils/objectUtils');
|
|
20
20
|
const handleExists = require('./db/transformExists');
|
|
21
|
+
const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities } = require('./db/rewriteCalculatedElements');
|
|
21
22
|
const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
|
|
22
23
|
const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
23
24
|
const flattening = require('./db/flattening');
|
|
@@ -126,8 +127,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
126
127
|
get$combined,
|
|
127
128
|
getCsnDef,
|
|
128
129
|
isAssocOrComposition,
|
|
129
|
-
addStringAnnotationTo
|
|
130
|
-
cloneWithTransformations;
|
|
130
|
+
addStringAnnotationTo;
|
|
131
131
|
// transformUtils
|
|
132
132
|
let addDefaultTypeFacets,
|
|
133
133
|
expandStructsInExpression,
|
|
@@ -174,18 +174,17 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
174
174
|
|
|
175
175
|
throwWithAnyError();
|
|
176
176
|
|
|
177
|
-
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
178
177
|
const transformCsn = transformUtils.transformModel;
|
|
179
178
|
|
|
179
|
+
forEachDefinition(csn, [
|
|
180
|
+
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
181
|
+
// assoc2join eventually rewrites the table aliases
|
|
182
|
+
temporal.getViewDecorator(csn, {info}, csnUtils),
|
|
183
|
+
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
184
|
+
assertUnique.prepare(csn, options, error, info)
|
|
185
|
+
]);
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
183
|
-
// assoc2join eventually rewrites the table aliases
|
|
184
|
-
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}, csnUtils));
|
|
185
|
-
timetrace.stop('temporal');
|
|
186
|
-
|
|
187
|
-
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
188
|
-
assertUnique.prepare(csn, options, error, info);
|
|
187
|
+
rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
|
|
189
188
|
|
|
190
189
|
if(doA2J) {
|
|
191
190
|
// Expand a structured thing in: keys, columns, order by, group by
|
|
@@ -199,6 +198,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
199
198
|
bindCsnReferenceOnly();
|
|
200
199
|
|
|
201
200
|
|
|
201
|
+
// TODO: Instead of 3 separate applyTransformations, we could have each of them just return the "listeners", merge them into
|
|
202
|
+
// one big listener that then gets passed into one single applyTransformations. Each listener would then have to return an array of callbacks to call.
|
|
203
|
+
// With that, we could still ensure the processing order (assuming we don't run into problems with scoping).
|
|
204
|
+
// To analyze: Increased memory vs. saved cycles
|
|
205
|
+
// Looked at it with AFC: This is only a small part of the overall processing time, enrich step of validator is just as expensive
|
|
202
206
|
if(doA2J) {
|
|
203
207
|
const resolved = new WeakMap();
|
|
204
208
|
// No refs with struct-steps exist anymore
|
|
@@ -240,6 +244,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
240
244
|
}
|
|
241
245
|
});
|
|
242
246
|
|
|
247
|
+
processCalculatedElementsInEntities(csn, options);
|
|
248
|
+
|
|
243
249
|
timetrace.start('Transform CSN')
|
|
244
250
|
|
|
245
251
|
// (000) Rename primitive types, make UUID a String
|
|
@@ -255,15 +261,15 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
255
261
|
},
|
|
256
262
|
}, true);
|
|
257
263
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
forEachDefinition(csn, [
|
|
265
|
+
// (040) Ignore entities and views that are abstract or implemented
|
|
266
|
+
// or carry the annotation cds.persistence.skip/exists
|
|
267
|
+
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
268
|
+
cdsPersistence.getAnnoProcessor(),
|
|
269
|
+
// (050) Check @cds.valid.from/to only on entity
|
|
270
|
+
// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
|
|
271
|
+
temporal.getAnnotationHandler(csn, options, pathDelimiter, {error})
|
|
272
|
+
]);
|
|
267
273
|
|
|
268
274
|
// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
|
|
269
275
|
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
@@ -300,18 +306,19 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
300
306
|
}
|
|
301
307
|
});
|
|
302
308
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
309
|
+
forEachDefinition(csn, [
|
|
310
|
+
// For generating DB stuff:
|
|
311
|
+
// - table-entity with parameters: not allowed
|
|
312
|
+
// - view with parameters: ok on HANA, not allowed otherwise
|
|
313
|
+
// (don't complain about action/function with parameters)
|
|
314
|
+
handleChecksForWithParameters,
|
|
315
|
+
// Remove .masked
|
|
316
|
+
// Check that keys are not explicitly nullable
|
|
317
|
+
// Check that Associations are not used in entities/views with parameters
|
|
318
|
+
// (150 b) Strip inheritance
|
|
319
|
+
// Note that this should happen after implicit redirection, because includes are required for that
|
|
320
|
+
handleDBChecks,
|
|
321
|
+
]);
|
|
315
322
|
|
|
316
323
|
// (170) Transform '$self' in backlink associations to appropriate key comparisons
|
|
317
324
|
// Must happen before draft processing because the artificial ON-conditions in generated
|
|
@@ -449,7 +456,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
449
456
|
getCsnDef,
|
|
450
457
|
isAssocOrComposition,
|
|
451
458
|
addStringAnnotationTo,
|
|
452
|
-
cloneWithTransformations
|
|
453
459
|
} = csnUtils);
|
|
454
460
|
}
|
|
455
461
|
|
|
@@ -530,6 +536,20 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
530
536
|
if (!artifact._ignore) {
|
|
531
537
|
// Do things specific for entities and views (pass 2)
|
|
532
538
|
if ((artifact.kind === 'entity') && artifact.query) {
|
|
539
|
+
|
|
540
|
+
// First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
|
|
541
|
+
// for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
|
|
542
|
+
// have already been reported.
|
|
543
|
+
let selectDepth = 0;
|
|
544
|
+
traverseQuery(artifact.query, null, null, (query, fromSelect) => {
|
|
545
|
+
if (!query.ref && !query.as && fromSelect) {
|
|
546
|
+
// Use +1; for UNION, it's the next select, for SELECT, it's increased later.
|
|
547
|
+
query.as = `$_select_${selectDepth + 1}__`;
|
|
548
|
+
}
|
|
549
|
+
if (query.SELECT)
|
|
550
|
+
++selectDepth;
|
|
551
|
+
});
|
|
552
|
+
|
|
533
553
|
const process = (parent, prop, query, path) => {
|
|
534
554
|
transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))
|
|
535
555
|
replaceAssociationsInGroupByOrderBy(parent, options, inspectRef, error, path.concat(prop));
|
|
@@ -977,25 +997,22 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
977
997
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
978
998
|
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
979
999
|
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
|
|
980
|
-
// clone the onCond for later use in the path transformation
|
|
981
|
-
|
|
982
|
-
const newOnCond = cloneWithTransformations(assoc.on, {
|
|
983
|
-
ref: (value) => cloneWithTransformations(value, {}),
|
|
984
|
-
});
|
|
1000
|
+
// clone the onCond for later use in the path transformation
|
|
1001
|
+
const newOnCond = cloneCsnNonDict(assoc.on, options);
|
|
985
1002
|
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
|
|
986
1003
|
ref: (parent, prop, ref) => {
|
|
987
1004
|
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
988
1005
|
{
|
|
989
1006
|
ref.shift();
|
|
990
1007
|
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
991
|
-
// We could also have a $self
|
|
1008
|
+
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
992
1009
|
ref.shift();
|
|
993
1010
|
ref.shift();
|
|
994
1011
|
}
|
|
995
1012
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
996
1013
|
ref.unshift(elemName);
|
|
997
1014
|
// if there was a $self identifier in the forwarding association onCond
|
|
998
|
-
// we do not need it
|
|
1015
|
+
// we do not need it anymore, as we prepended in the previous step the back association's id
|
|
999
1016
|
if (ref[1] === '$self')
|
|
1000
1017
|
ref.splice(1, 1);
|
|
1001
1018
|
}
|
|
@@ -13,21 +13,66 @@ const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
|
|
|
13
13
|
const { isBetaEnabled } = require('../../base/model.js');
|
|
14
14
|
const { CompilerAssertion } = require('../../base/error');
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A given CDS model is a set of n definitions D = {v_1, ..., v_n } spanning a type dependency
|
|
18
|
+
* graph T(D) with vertices v_r, v_d (representing the referrer (using) and defining node of a type)
|
|
19
|
+
* and edges e(v_r, v_d).
|
|
20
|
+
*
|
|
21
|
+
* S may be a proper subset of D and is defined by v_s. Up to n S_i may exist.
|
|
22
|
+
* v is element of S_i, if name(v) starts with name(v_s). Therefore any v can only be member
|
|
23
|
+
* of exactly one S_i and v_s is always member of its own S_i.
|
|
24
|
+
*
|
|
25
|
+
* A complete service type dependency graph Tc(S) is defined as a set of vertices v_r, v_d
|
|
26
|
+
* and edges e(v_r, v_d) such that all v_r, v_d are elements of S (v_rs, v_ds) and with that
|
|
27
|
+
* all edges are { e(v_rs, v_ds) }.
|
|
28
|
+
*
|
|
29
|
+
* The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
|
|
30
|
+
* of S_i (v_dns).
|
|
31
|
+
*
|
|
32
|
+
* The aim of this algorithm is to produce Tc's for all requested S_i by 'filling' up the missing
|
|
33
|
+
* vertices v_ds and rewriting all e(v_rs, v_dns) to e(v_rs, v_ds).
|
|
34
|
+
*
|
|
35
|
+
* This can be done pretty easily by (recursively) iterating over all requested v_rs and
|
|
36
|
+
* follow e(v_r, v_d) until a v_dns is found. v_dns is cloned and added to S via
|
|
37
|
+
* name(v_ds) = name(v_s) + '.' + name(v_dns). If v_dns is an anonymous definition, an
|
|
38
|
+
* artificial name representing the path to that node is being used.
|
|
39
|
+
*
|
|
40
|
+
* The algorithm has a beneficial side effect: it creates new (sub) schemas on the fly
|
|
41
|
+
* which are required for the construction of the EDM intermediate representation later on.
|
|
42
|
+
*
|
|
43
|
+
* An OData service contains at least one schema. Only (OData) schemas may contain definitions.
|
|
44
|
+
* By default, the CDS service v_s represents the default (OData) schema.
|
|
45
|
+
* However, there are situations where { v_dns } must be partitioned into (sub) schemas in order to
|
|
46
|
+
* maintain their original name prefixes and to be compatible with later service definitions.
|
|
47
|
+
*
|
|
48
|
+
* If name(v_dns) is made up of segments separated by a dot '.', the first n-1 segments represent
|
|
49
|
+
* the sub schema: name(schema) = name(v_s) + '.' + concat(1,n-1, segments(name(v_dns)), '.')
|
|
50
|
+
*
|
|
51
|
+
* If name(v_dns) has no prefix segments, the fallback schema name is prepended instead:
|
|
52
|
+
* name(v_ds) = name(v_s) + '.' + fallbackschema + name(v_dns);
|
|
53
|
+
*
|
|
54
|
+
* @param {CSN.Model} csn
|
|
55
|
+
* @param {function} whatsMyServiceName
|
|
56
|
+
* @param {string[]} requestedServiceNames
|
|
57
|
+
* @param {String} fallBackSchemaName
|
|
58
|
+
* @param {Object} options
|
|
59
|
+
* @param {Object} csnUtils
|
|
60
|
+
* @param {Object} message
|
|
61
|
+
* @returns {Object} schemas dictionary of (sub) schemas for all requested services
|
|
62
|
+
*/
|
|
16
63
|
function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
|
|
17
64
|
const { error } = message;
|
|
18
65
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
19
66
|
// are we working with OData proxies or cross-service refs
|
|
20
67
|
const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
|
|
21
|
-
//
|
|
68
|
+
// service sub schemas as return value
|
|
22
69
|
const schemas = Object.create(null);
|
|
70
|
+
// exposed types register
|
|
23
71
|
const exposedTypes = Object.create(null);
|
|
24
|
-
// walk through the definitions of the given CSN and expose types where needed
|
|
25
72
|
forEachDefinition(csn, (def, defName, propertyName, path) => {
|
|
26
|
-
// we do expose types only for definition from inside services
|
|
27
73
|
const serviceName = whatsMyServiceName(defName, false);
|
|
28
74
|
// run type exposure only on requested services if not in multi schema mode
|
|
29
75
|
// multi schema mode requires a proper type exposure for all services as a prerequisite
|
|
30
|
-
// for the proxy exposure
|
|
31
76
|
if (serviceName && requestedServiceNames.includes(serviceName)) {
|
|
32
77
|
if (def.kind === 'type' || def.kind === 'entity') {
|
|
33
78
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
@@ -38,13 +83,9 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
38
83
|
}, path);
|
|
39
84
|
}
|
|
40
85
|
|
|
41
|
-
// For exposed actions and functions that use non-exposed or anonymous structured types, create
|
|
42
|
-
// artificial exposing types.
|
|
43
|
-
// unbound actions
|
|
44
86
|
if (def.kind === 'action' || def.kind === 'function') {
|
|
45
87
|
exposeTypesOfAction(def, defName, defName, serviceName, path);
|
|
46
88
|
}
|
|
47
|
-
// bound actions
|
|
48
89
|
def.actions && Object.entries(def.actions).forEach(([actionName, action]) => {
|
|
49
90
|
exposeTypesOfAction(action, `${defName}_${actionName}`, defName, serviceName, path.concat(['actions', actionName]));
|
|
50
91
|
});
|
|
@@ -53,19 +94,13 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
53
94
|
|
|
54
95
|
if(isBetaEnabled(options, 'odataTerms')) {
|
|
55
96
|
forEachGeneric(csn, 'vocabularies', (def, defName, _propertyName, path) => {
|
|
56
|
-
// we do expose types only for definition from inside services
|
|
57
97
|
const serviceName = whatsMyServiceName(defName, false);
|
|
58
|
-
// run type exposure only on requested services if not in multi schema mode
|
|
59
|
-
// multi schema mode requires a proper type exposure for all services as a prerequisite
|
|
60
|
-
// for the proxy exposure
|
|
61
98
|
if (serviceName && requestedServiceNames.includes(serviceName)) {
|
|
62
99
|
if(csn.definitions[defName]) {
|
|
63
|
-
// error, duplicate definitions not allowed!
|
|
64
|
-
// TODO: Use path as error location as soon as refs outside definitions are supported
|
|
65
100
|
error('odata-definition-exists', ['vocabularies', defName], { anno: defName, '#':'anno' });
|
|
66
101
|
}
|
|
67
102
|
else {
|
|
68
|
-
|
|
103
|
+
// link def into definitions for later use
|
|
69
104
|
def.kind = 'annotation';
|
|
70
105
|
csn.definitions[defName] = def;
|
|
71
106
|
const artificialName = `term_${defName.replace(/\./g, '_')}`;//_${paramName}`;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Unary: 'is [not] null', 'not'
|
|
20
20
|
* Conditional: 'case [when then]+ [else]? end', 'and', 'or'
|
|
21
21
|
*
|
|
22
|
-
*
|
|
22
|
+
* stand-alone token: 'new'
|
|
23
23
|
*
|
|
24
24
|
* This is not an optimized LL(1) parser but a token 'sniffer'. A stream is
|
|
25
25
|
* cracked up in sub streams and passed down to the next higher function.
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
* This parser intentionally does no error handling. If a clause is malformed, it is accepted as is.
|
|
34
34
|
*
|
|
35
35
|
* @param {any} xpr A JSON object.
|
|
36
|
-
* @param {Object} state
|
|
37
|
-
* anno: Don't eliminate arrays with single entry in statetations as they are collections
|
|
36
|
+
* @param {Object} state Object
|
|
37
|
+
* anno: Don't eliminate arrays with single entry in statetations (TODO?) as they are collections
|
|
38
38
|
* array: Bias AST representation.
|
|
39
39
|
* nary: return n-ary or binary tree
|
|
40
40
|
*/
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
43
43
|
// Notes:
|
|
44
44
|
// - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
|
|
45
|
-
// - xpr's are our CSN expressions, see <https://
|
|
45
|
+
// - xpr's are our CSN expressions, see <https://cap.cloud.sap/docs/cds/cxn>
|
|
46
46
|
|
|
47
47
|
return parseExprInt(xpr, state);
|
|
48
48
|
|
|
@@ -82,7 +82,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
82
82
|
*/
|
|
83
83
|
function rewriteCaseBlock(casePos, endPos) {
|
|
84
84
|
const caseTree = state.array ? [ 'case' ] : { 'case': [] };
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
let elsePos = endPos;
|
|
87
87
|
let whenPos = casePos;
|
|
88
88
|
|
|
@@ -144,7 +144,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
144
144
|
function conditionOR(xpr, s, e, state) {
|
|
145
145
|
return binaryExpr(xpr, ['or'], conditionAnd, s, e, state);
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
function conditionAnd(xpr, s, e, state) {
|
|
149
149
|
return binaryExpr(xpr, (xpr, s, e) => {
|
|
150
150
|
let a = s-1;
|
|
@@ -283,7 +283,8 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
283
283
|
// return xpr;
|
|
284
284
|
for(let n in xpr) {
|
|
285
285
|
const x = xpr[n];
|
|
286
|
-
|
|
286
|
+
const isAnno = n[0] === '@' && isSimpleAnnoValue(x);
|
|
287
|
+
if(isAnno)
|
|
287
288
|
state.anno++;
|
|
288
289
|
if(Array.isArray(x)) {
|
|
289
290
|
if(csnarray.includes(n) || state.anno !== 0)
|
|
@@ -295,7 +296,7 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
295
296
|
}
|
|
296
297
|
else
|
|
297
298
|
xpr[n] = parseExprInt(x, state);
|
|
298
|
-
if(
|
|
299
|
+
if(isAnno)
|
|
299
300
|
state.anno--;
|
|
300
301
|
}
|
|
301
302
|
}
|
|
@@ -343,6 +344,13 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
|
343
344
|
|
|
344
345
|
}
|
|
345
346
|
|
|
347
|
+
function isSimpleAnnoValue(val) {
|
|
348
|
+
// Expressions as annotation values always have a `=` and another property.
|
|
349
|
+
// TODO: There must be at least one known expression property, otherwise
|
|
350
|
+
// it could be `type: 'unchecked'`.
|
|
351
|
+
return !val?.['='] || Object.keys(val) < 2;
|
|
352
|
+
}
|
|
353
|
+
|
|
346
354
|
module.exports = {
|
|
347
355
|
parseExpr,
|
|
348
356
|
};
|
|
@@ -12,7 +12,6 @@ const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnU
|
|
|
12
12
|
const { typeParameters, isBuiltinType } = require('../compiler/builtins');
|
|
13
13
|
const { ModelError, CompilerAssertion} = require('../base/error');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
|
-
const { pathName } = require('../compiler/utils');
|
|
16
15
|
|
|
17
16
|
const RestrictedOperators = ['<', '>', '>=', '<='];
|
|
18
17
|
const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOperators];
|
|
@@ -321,9 +320,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
321
320
|
*/
|
|
322
321
|
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
|
|
323
322
|
// Refs of length 1 cannot contain steps - no need to check
|
|
324
|
-
if (ref.length < 2) {
|
|
325
|
-
return ref;
|
|
326
|
-
} else if(scope === '$self' && ref.length === 2) {
|
|
323
|
+
if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
|
|
327
324
|
return ref;
|
|
328
325
|
}
|
|
329
326
|
|
|
@@ -1136,7 +1133,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1136
1133
|
const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
|
|
1137
1134
|
|
|
1138
1135
|
// lhs & rhs must be expandable types (structures or managed associations)
|
|
1139
|
-
// if ever lhs should be
|
|
1136
|
+
// if ever lhs should be allowed to be a value uncomment this
|
|
1140
1137
|
if(!(lhsIsVal /*&& rhsIsVal*/) &&
|
|
1141
1138
|
!(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs)) &&
|
|
1142
1139
|
RelationalOperators.includes(op) &&
|
|
@@ -1162,26 +1159,41 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1162
1159
|
const xrefvalues = Object.values(xref);
|
|
1163
1160
|
let cont = true;
|
|
1164
1161
|
|
|
1162
|
+
const prefix = (lhs, op, rhs) => {
|
|
1163
|
+
return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
|
|
1164
|
+
}
|
|
1165
1165
|
if(op === 'like' && xrefvalues.reduce((a, v) => {
|
|
1166
1166
|
return (v.lhs && v.rhs) ? a + 1: a;
|
|
1167
1167
|
}, 0) === 0) {
|
|
1168
1168
|
// error if intersection of paths is zero
|
|
1169
|
-
error(null, location,
|
|
1170
|
-
|
|
1169
|
+
error(null, location,
|
|
1170
|
+
{
|
|
1171
|
+
prefix: prefix(lhs, op, rhs)
|
|
1172
|
+
},
|
|
1173
|
+
'Expected compatible types for $(PREFIX)');
|
|
1171
1174
|
cont = false;
|
|
1172
1175
|
}
|
|
1173
1176
|
|
|
1174
1177
|
cont && xrefkeys.forEach(xn => {
|
|
1175
1178
|
const x = xref[xn];
|
|
1176
|
-
const prefix = `${pathName(lhs.ref)} ${op} ${pathName(rhs.ref)}`;
|
|
1177
1179
|
// do the paths match?
|
|
1178
1180
|
if(op !== 'like' && !(x.lhs && x.rhs)) {
|
|
1179
1181
|
if(xn.length) {
|
|
1180
|
-
error(null, location,
|
|
1182
|
+
error(null, location,
|
|
1183
|
+
{
|
|
1184
|
+
prefix: prefix(lhs, op, rhs),
|
|
1185
|
+
name: xn,
|
|
1186
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1187
|
+
},
|
|
1181
1188
|
'$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
|
|
1182
1189
|
}
|
|
1183
1190
|
else {
|
|
1184
|
-
error(null, location,
|
|
1191
|
+
error(null, location,
|
|
1192
|
+
{
|
|
1193
|
+
prefix: prefix(lhs, op, rhs),
|
|
1194
|
+
name: (x.lhs ? lhs : rhs).ref.join('.'),
|
|
1195
|
+
alias: (x.lhs ? rhs : lhs).ref.join('.')
|
|
1196
|
+
},
|
|
1185
1197
|
'$(PREFIX): Path $(NAME) does not match $(ALIAS)');
|
|
1186
1198
|
}
|
|
1187
1199
|
cont = false;
|
|
@@ -1189,23 +1201,38 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1189
1201
|
// lhs && rhs are present, consistency checks that affect both ends
|
|
1190
1202
|
else {
|
|
1191
1203
|
// is lhs scalar?
|
|
1204
|
+
// eslint-disable-next-line sonarjs/no-gratuitous-expressions
|
|
1192
1205
|
if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
|
|
1193
|
-
error(null, location,
|
|
1206
|
+
error(null, location,
|
|
1207
|
+
{
|
|
1208
|
+
prefix: prefix(lhs, op, rhs),
|
|
1209
|
+
name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
|
|
1210
|
+
},
|
|
1194
1211
|
'$(PREFIX): Path $(NAME) must end on a scalar type')
|
|
1195
1212
|
cont = false;
|
|
1196
1213
|
}
|
|
1197
1214
|
// is rhs scalar?
|
|
1198
1215
|
if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
|
|
1199
|
-
error(null, location,
|
|
1216
|
+
error(null, location,
|
|
1217
|
+
{
|
|
1218
|
+
prefix: prefix(lhs, op, rhs),
|
|
1219
|
+
name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
|
|
1220
|
+
},
|
|
1200
1221
|
'$(PREFIX): Path $(NAME) must end on a scalar type');
|
|
1201
1222
|
cont = false;
|
|
1202
1223
|
}
|
|
1203
1224
|
// info about type incompatibility if no other errors occurred
|
|
1225
|
+
// eslint-disable-next-line sonarjs/no-gratuitous-expressions
|
|
1204
1226
|
if(!(lhsIsVal || rhsIsVal) && x.lhs && x.rhs && xn && cont) {
|
|
1205
1227
|
const lhst = getType(x.lhs._art);
|
|
1206
1228
|
const rhst = getType(x.rhs._art);
|
|
1207
1229
|
if(lhst !== rhst) {
|
|
1208
|
-
info(null, location,
|
|
1230
|
+
info(null, location,
|
|
1231
|
+
{
|
|
1232
|
+
prefix: prefix(lhs, op, rhs),
|
|
1233
|
+
name: xn
|
|
1234
|
+
},
|
|
1235
|
+
'$(PREFIX): Types for sub path $(NAME) don\'t match');
|
|
1209
1236
|
}
|
|
1210
1237
|
}
|
|
1211
1238
|
}
|
|
@@ -1215,6 +1242,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1215
1242
|
return expr;
|
|
1216
1243
|
|
|
1217
1244
|
// if lhs and rhs are refs set operator from 'like' to '='
|
|
1245
|
+
// eslint-disable-next-line sonarjs/no-gratuitous-expressions
|
|
1218
1246
|
if(op === 'like' && !(lhsIsVal || rhsIsVal)) {
|
|
1219
1247
|
op = '=';
|
|
1220
1248
|
}
|
|
@@ -1419,7 +1447,7 @@ function rewriteBuiltinTypeRef(csn) {
|
|
|
1419
1447
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
1420
1448
|
applyTransformations(csn, {
|
|
1421
1449
|
type: (parent, _prop, type) => {
|
|
1422
|
-
if(type
|
|
1450
|
+
if(type?.ref && (
|
|
1423
1451
|
isBuiltinType(type.ref[0]) ||
|
|
1424
1452
|
type.ref[0] === special$self)
|
|
1425
1453
|
) {
|