@sap/cds-compiler 2.4.4 → 2.10.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 +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
4
|
-
const { isDeprecatedEnabled } = require('../base/model');
|
|
3
|
+
const { makeMessageFunction } = require('../base/messages');
|
|
4
|
+
const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
5
5
|
const transformUtils = require('./transformUtilsNew');
|
|
6
6
|
const { getUtils,
|
|
7
7
|
cloneCsn,
|
|
@@ -24,8 +24,9 @@ const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssoci
|
|
|
24
24
|
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
25
25
|
const timetrace = require('../utils/timetrace');
|
|
26
26
|
const { attachPath } = require('./odata/attachPath');
|
|
27
|
+
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
27
28
|
|
|
28
|
-
const { addLocalizationViews
|
|
29
|
+
const { addLocalizationViews } = require('./localized');
|
|
29
30
|
|
|
30
31
|
// Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
|
|
31
32
|
// The result should be suitable for consumption by EDMX processors (annotations and metadata)
|
|
@@ -77,15 +78,6 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
77
78
|
const { error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
|
|
78
79
|
throwWithError();
|
|
79
80
|
|
|
80
|
-
addLocalizationViews(csn, options);
|
|
81
|
-
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
|
|
82
|
-
forEachDefinition(csn, (artifact, artifactName) => {
|
|
83
|
-
if (hasLocalizedConvenienceView(csn, artifactName))
|
|
84
|
-
artifact.$localized = true;
|
|
85
|
-
else if (!keepLocalizedViews && isInLocalizedNamespace(artifactName))
|
|
86
|
-
delete csn.definitions[artifactName];
|
|
87
|
-
});
|
|
88
|
-
|
|
89
81
|
// the new transformer works only with new CSN
|
|
90
82
|
checkCSNVersion(csn, options);
|
|
91
83
|
|
|
@@ -98,8 +90,8 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
98
90
|
addElement, createAction, assignAction,
|
|
99
91
|
extractValidFromToKeyElement,
|
|
100
92
|
checkAssignment, checkMultipleAssignments,
|
|
101
|
-
recurseElements, setAnnotation, renameAnnotation,
|
|
102
|
-
|
|
93
|
+
recurseElements, setAnnotation, resetAnnotation, renameAnnotation,
|
|
94
|
+
expandStructsInExpression
|
|
103
95
|
} = transformers;
|
|
104
96
|
|
|
105
97
|
const csnUtils = getUtils(csn);
|
|
@@ -107,7 +99,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
107
99
|
getCsnDef,
|
|
108
100
|
getFinalType,
|
|
109
101
|
getServiceName,
|
|
110
|
-
|
|
102
|
+
hasAnnotationValue,
|
|
111
103
|
isAssocOrComposition,
|
|
112
104
|
isAssociation,
|
|
113
105
|
isStructured,
|
|
@@ -122,15 +114,31 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
122
114
|
|
|
123
115
|
// collect all declared non-abstract services from the model
|
|
124
116
|
// use the array when there is a need to identify if an artifact is in a service or not
|
|
125
|
-
|
|
117
|
+
const services = getServiceNames(csn);
|
|
118
|
+
// @ts-ignore
|
|
119
|
+
const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
|
|
122
|
+
|
|
123
|
+
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
124
|
+
enrichUniversalCsn(csn, options);
|
|
125
|
+
|
|
126
|
+
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
|
|
127
|
+
|
|
128
|
+
function acceptLocalizedView(_name, parent) {
|
|
129
|
+
csn.definitions[parent].$localized = true;
|
|
130
|
+
return keepLocalizedViews && !isExternalServiceMember(undefined, parent);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
addLocalizationViews(csn, options, acceptLocalizedView);
|
|
126
134
|
|
|
127
135
|
validate.forOdata(csn, {
|
|
128
|
-
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect
|
|
136
|
+
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
129
137
|
});
|
|
130
138
|
|
|
131
139
|
|
|
132
140
|
// Throw exception in case of errors
|
|
133
|
-
|
|
141
|
+
throwWithError();
|
|
134
142
|
|
|
135
143
|
// Semantic checks before flattening regarding temporal data
|
|
136
144
|
// TODO: Move in the validator
|
|
@@ -143,16 +151,17 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
143
151
|
if (def.kind === 'entity' && def.projection) {
|
|
144
152
|
def.query = { SELECT: def.projection };
|
|
145
153
|
}
|
|
146
|
-
}]
|
|
154
|
+
}],
|
|
155
|
+
{ skipArtifact: isExternalServiceMember }
|
|
147
156
|
);
|
|
148
157
|
|
|
149
|
-
expandToFinalBaseType(csn, transformers, csnUtils, services, options);
|
|
158
|
+
expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember);
|
|
150
159
|
|
|
151
|
-
// Check if structured elements and managed associations are compared in an
|
|
160
|
+
// Check if structured elements and managed associations are compared in an expression
|
|
152
161
|
// and expand these structured elements. This tuple expansion allows all other
|
|
153
|
-
// subsequent procession steps (especially a2j) to see plain paths in
|
|
154
|
-
// If errors are detected,
|
|
155
|
-
|
|
162
|
+
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
163
|
+
// If errors are detected, throwWithError() will return from further processing
|
|
164
|
+
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
156
165
|
|
|
157
166
|
// handles reference flattening
|
|
158
167
|
let referenceFlattener = new ReferenceFlattener();
|
|
@@ -163,28 +172,29 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
163
172
|
|
|
164
173
|
if (!structuredOData) {
|
|
165
174
|
// flatten structures
|
|
166
|
-
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember);
|
|
167
177
|
// flatten references
|
|
168
178
|
referenceFlattener.flattenAllReferences(csn);
|
|
169
179
|
}
|
|
170
180
|
|
|
171
181
|
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
|
|
172
|
-
|
|
182
|
+
throwWithError();
|
|
173
183
|
|
|
174
184
|
// Process associations
|
|
175
185
|
// 1. In case we generate flat mode, expand the structured foreign keys.
|
|
176
186
|
// This logic rewrites the 'ref' for such keys with the corresponding flattened
|
|
177
187
|
// elements.
|
|
178
188
|
if (!structuredOData)
|
|
179
|
-
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils);
|
|
189
|
+
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils, isExternalServiceMember);
|
|
180
190
|
// 2. generate foreign keys for managed associations
|
|
181
|
-
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error);
|
|
191
|
+
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
|
|
182
192
|
|
|
183
193
|
// Apply default type facets as set by options
|
|
184
194
|
// Flatten on-conditions in unmanaged associations
|
|
185
195
|
// This must be done before all the draft logic as all
|
|
186
196
|
// composition targets are annotated with @odata.draft.enabled in this step
|
|
187
|
-
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ]);
|
|
197
|
+
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });
|
|
188
198
|
|
|
189
199
|
// Now all artificially generated things are in place
|
|
190
200
|
// - Generate artificial draft fields if requested
|
|
@@ -201,17 +211,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
201
211
|
// Generate artificial draft fields if requested
|
|
202
212
|
if (def['@odata.draft.enabled']) {
|
|
203
213
|
// Ignore if not part of a service
|
|
204
|
-
if (
|
|
205
|
-
warning(null, ['definitions', defName], { art: defName },
|
|
206
|
-
'Ignoring annotation “@odata.draft.enabled” because artifact $(ART) is not part of a service');
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
214
|
+
if (isArtifactInSomeService(defName, services)) {
|
|
209
215
|
generateDraftForOdata(def, defName, def, visitedArtifacts);
|
|
210
216
|
}
|
|
211
217
|
}
|
|
212
218
|
}
|
|
213
219
|
visitedArtifacts[defName] = true;
|
|
214
|
-
});
|
|
220
|
+
}, { skipArtifact: isExternalServiceMember });
|
|
215
221
|
|
|
216
222
|
// Deal with all kind of annotations manipulations here
|
|
217
223
|
forEachDefinition(csn, (def, defName) => {
|
|
@@ -223,16 +229,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
223
229
|
if (options.toOdata.names && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
|
|
224
230
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
|
|
225
231
|
|
|
226
|
-
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
232
|
+
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
|
|
227
233
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
228
234
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
229
235
|
// as they have no DB representation (although in views)
|
|
230
236
|
if (options.toOdata.names && typeof member === 'object' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
|
|
231
237
|
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
|
|
232
|
-
|
|
233
|
-
memberName = member._flatElementNameWithDots;
|
|
234
|
-
}
|
|
235
|
-
member['@cds.persistence.name'] = getElementDatabaseNameOf(memberName, options.toOdata.names);
|
|
238
|
+
member['@cds.persistence.name'] = getElementDatabaseNameOf(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
|
|
236
239
|
}
|
|
237
240
|
|
|
238
241
|
// Mark fields with @odata.on.insert/update as @Core.Computed
|
|
@@ -244,6 +247,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
244
247
|
// - If the association target is annotated with @cds.odata.valuelist, annotate the
|
|
245
248
|
// association with @Common.ValueList.viaAssociation
|
|
246
249
|
// - Check for @Analytics.Measure and @Aggregation.default
|
|
250
|
+
// @ts-ignore
|
|
247
251
|
if (isArtifactInSomeService(defName, services) || isLocalizedArtifactInService(defName, services)) {
|
|
248
252
|
// If the member is an association and the target is annotated with @cds.odata.valuelist,
|
|
249
253
|
// annotate the association with @Common.ValueList.viaAssociation (but only for service member artifacts
|
|
@@ -258,16 +262,17 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
258
262
|
if (def.kind === 'entity' && def.query && def.query && def.projection) {
|
|
259
263
|
delete def.query;
|
|
260
264
|
}
|
|
261
|
-
})
|
|
265
|
+
}, { skipArtifact: isExternalServiceMember })
|
|
262
266
|
|
|
263
267
|
// Throw exception in case of errors
|
|
264
|
-
|
|
268
|
+
throwWithError();
|
|
265
269
|
|
|
266
270
|
if (options.testMode) csn = cloneCsn(csn, options); // sort, keep hidden properties
|
|
267
271
|
timetrace.stop();
|
|
268
272
|
return csn;
|
|
269
273
|
|
|
270
274
|
// TODO: Move this to checks?
|
|
275
|
+
// @ts-ignore
|
|
271
276
|
function checkTemporalAnnotationsAssignment(artifact, artifactName, propertyName, path) {
|
|
272
277
|
// Gather all element names with @cds.valid.from/to/key
|
|
273
278
|
let validFrom = [], validTo = [], validKey = [];
|
|
@@ -325,7 +330,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
325
330
|
renameAnnotation(node, name, '@UI.Importance');
|
|
326
331
|
let annotation = node['@UI.Importance'];
|
|
327
332
|
if (annotation !== null)
|
|
328
|
-
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' }
|
|
333
|
+
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
|
|
329
334
|
}
|
|
330
335
|
|
|
331
336
|
// Special case: '@readonly' becomes a triplet of capability restrictions for entities,
|
|
@@ -404,6 +409,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
404
409
|
// Handles on-conditions in unmanaged associations
|
|
405
410
|
function processOnCond(def) {
|
|
406
411
|
forEachMemberRecursively(def, (member) => {
|
|
412
|
+
// @ts-ignore
|
|
407
413
|
if (member.type && isAssocOrComposition(member.type) && member.on) {
|
|
408
414
|
removeLeadingDollarSelfInOnCondition(member);
|
|
409
415
|
}
|
|
@@ -430,6 +436,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
430
436
|
// Draft Node: Must not be reachable from multiple draft roots
|
|
431
437
|
function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {
|
|
432
438
|
// Sanity check
|
|
439
|
+
// @ts-ignore
|
|
433
440
|
if (!isArtifactInSomeService(artifactName, services)) {
|
|
434
441
|
throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));
|
|
435
442
|
}
|
|
@@ -440,9 +447,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
440
447
|
}
|
|
441
448
|
|
|
442
449
|
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
450
|
+
// @ts-ignore
|
|
443
451
|
let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
|
|
444
452
|
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
445
453
|
if (!draftAdminDataProjection) {
|
|
454
|
+
// @ts-ignore
|
|
446
455
|
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
|
|
447
456
|
}
|
|
448
457
|
// Report an error if it is not an entity or not what we expect
|
|
@@ -452,11 +461,11 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
452
461
|
}
|
|
453
462
|
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
454
463
|
if (artifact == rootArtifact) {
|
|
455
|
-
artifact
|
|
456
|
-
artifact
|
|
457
|
-
artifact
|
|
464
|
+
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
|
|
465
|
+
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
|
|
466
|
+
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
458
467
|
} else {
|
|
459
|
-
artifact
|
|
468
|
+
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
460
469
|
}
|
|
461
470
|
|
|
462
471
|
artifact.elements && Object.values(artifact.elements).forEach( elem => {
|
|
@@ -524,17 +533,12 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
524
533
|
|
|
525
534
|
// Ignore if that is our own draft root
|
|
526
535
|
if (draftNode != rootArtifact) {
|
|
527
|
-
//
|
|
528
|
-
if (
|
|
536
|
+
// Report error when the draft node has @odata.draft.enabled itself
|
|
537
|
+
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
529
538
|
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
|
|
530
539
|
}
|
|
531
|
-
// Ignore composition if not part of a service
|
|
532
|
-
else if (!getServiceName(elem.target)) {
|
|
533
|
-
warning(null, ['definitions', artifactName, 'elements', elemName], { target: elem.target },
|
|
534
|
-
'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
else if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', false)) {
|
|
540
|
+
// Ignore composition if not part of a service or explicitly draft disabled
|
|
541
|
+
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
|
|
538
542
|
return;
|
|
539
543
|
}
|
|
540
544
|
else {
|
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
const { makeMessageFunction } = require('../base/messages');
|
|
4
4
|
const { setProp } = require('../base/model');
|
|
5
5
|
const { hasErrors } = require('../base/messages');
|
|
6
|
-
const { cloneCsnDictionary } = require('../
|
|
6
|
+
const { cloneCsnDictionary } = require('../model/csnUtils');
|
|
7
7
|
const { cleanSymbols } = require('../base/cleanSymbols.js');
|
|
8
|
+
const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
|
|
8
9
|
const {
|
|
9
10
|
cloneCsn,
|
|
10
11
|
forEachDefinition,
|
|
11
12
|
forEachGeneric,
|
|
12
13
|
forAllQueries,
|
|
13
14
|
sortCsnDefinitionsForTests,
|
|
15
|
+
getUtils,
|
|
14
16
|
} = require('../model/csnUtils');
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -34,6 +36,13 @@ const _isViewForEntity = Symbol('_isViewForEntity'); // $inferred = 'LOCALIZED-H
|
|
|
34
36
|
*/
|
|
35
37
|
const _targetFor = Symbol('_targetFor');
|
|
36
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Callback function returning `true` if the localization view should be created.
|
|
41
|
+
* @callback acceptLocalizedView
|
|
42
|
+
* @param {string} viewName localization view name
|
|
43
|
+
* @param {string} originalName Artifact name of the original view
|
|
44
|
+
*/
|
|
45
|
+
|
|
37
46
|
/**
|
|
38
47
|
* Create transitive localized convenience views
|
|
39
48
|
*
|
|
@@ -60,8 +69,9 @@ const _targetFor = Symbol('_targetFor');
|
|
|
60
69
|
* @param {CSN.Options} options
|
|
61
70
|
* @param {boolean} useJoins If true, rewrite the "localized" association to a
|
|
62
71
|
* join in direct convenience views.
|
|
72
|
+
* @param {acceptLocalizedView} [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
63
73
|
*/
|
|
64
|
-
function _addLocalizationViews(csn, options, useJoins) {
|
|
74
|
+
function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = null) {
|
|
65
75
|
// Don't try to create convenience views with errors.
|
|
66
76
|
if (hasErrors(options.messages))
|
|
67
77
|
return csn;
|
|
@@ -69,7 +79,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
69
79
|
if (hasExistingLocalizationViews(csn, options))
|
|
70
80
|
return csn;
|
|
71
81
|
|
|
72
|
-
const { info } = makeMessageFunction(csn, options);
|
|
82
|
+
const { info, error } = makeMessageFunction(csn, options);
|
|
73
83
|
|
|
74
84
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
75
85
|
options.localizedWithoutCoalesce);
|
|
@@ -77,7 +87,16 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
77
87
|
createDirectConvenienceViews(); // 1
|
|
78
88
|
createTransitiveConvenienceViews(); // 2 + 3
|
|
79
89
|
|
|
80
|
-
forEachDefinition(csn,
|
|
90
|
+
forEachDefinition(csn, (definition, artName, prop, path) => {
|
|
91
|
+
cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor)
|
|
92
|
+
if(definition.query) {
|
|
93
|
+
// reject managed association and structure publishing for to-hdbcds.hdbcds
|
|
94
|
+
const that = { csnUtils: getUtils(csn), options, error };
|
|
95
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
|
|
81
100
|
|
|
82
101
|
sortCsnDefinitionsForTests(csn, options);
|
|
83
102
|
return csn;
|
|
@@ -125,10 +144,16 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
125
144
|
|
|
126
145
|
art[_hasLocalizedView] = viewName;
|
|
127
146
|
|
|
147
|
+
if(acceptLocalizedView && !acceptLocalizedView(viewName, artName))
|
|
148
|
+
return;
|
|
149
|
+
|
|
150
|
+
let view;
|
|
128
151
|
if (art.query || art.projection)
|
|
129
|
-
|
|
152
|
+
view = createLocalizedViewForView(art);
|
|
130
153
|
else
|
|
131
|
-
|
|
154
|
+
view = createLocalizedViewForEntity(art, artName, textElements);
|
|
155
|
+
|
|
156
|
+
csn.definitions[viewName] = view;
|
|
132
157
|
|
|
133
158
|
copyPersistenceAnnotations(csn.definitions[viewName], art);
|
|
134
159
|
}
|
|
@@ -157,7 +182,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
157
182
|
columns,
|
|
158
183
|
},
|
|
159
184
|
},
|
|
160
|
-
elements: cloneCsnDictionary(entity.elements,
|
|
185
|
+
elements: cloneCsnDictionary(entity.elements, options),
|
|
161
186
|
[_isViewForEntity]: true,
|
|
162
187
|
};
|
|
163
188
|
copyLocation(convenienceView, entity);
|
|
@@ -187,13 +212,13 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
187
212
|
|
|
188
213
|
function createFromClauseForEntity() {
|
|
189
214
|
if (!shouldUseJoin) {
|
|
190
|
-
return createColumnRef( [ entityName ], 'L'
|
|
215
|
+
return createColumnRef( [ entityName ], 'L');
|
|
191
216
|
}
|
|
192
217
|
|
|
193
218
|
const from = {
|
|
194
219
|
join: 'left',
|
|
195
220
|
args: [
|
|
196
|
-
createColumnRef( [ entityName ], 'L_0'
|
|
221
|
+
createColumnRef( [ entityName ], 'L_0'),
|
|
197
222
|
createColumnRef( [ textsEntityName(entityName) ], 'localized_1' ),
|
|
198
223
|
],
|
|
199
224
|
on: []
|
|
@@ -236,7 +261,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
236
261
|
else if (view.projection)
|
|
237
262
|
convenienceView.projection = cloneCsn(view.projection, options);
|
|
238
263
|
|
|
239
|
-
convenienceView.elements = cloneCsnDictionary(view.elements,
|
|
264
|
+
convenienceView.elements = cloneCsnDictionary(view.elements, options);
|
|
240
265
|
convenienceView[_isViewForView] = true;
|
|
241
266
|
copyLocation(convenienceView, view);
|
|
242
267
|
|
|
@@ -245,7 +270,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
245
270
|
});
|
|
246
271
|
|
|
247
272
|
if (view.params)
|
|
248
|
-
convenienceView.params = cloneCsnDictionary(view.params,
|
|
273
|
+
convenienceView.params = cloneCsnDictionary(view.params, options);
|
|
249
274
|
|
|
250
275
|
return convenienceView;
|
|
251
276
|
}
|
|
@@ -336,7 +361,7 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
336
361
|
|
|
337
362
|
if (!isEntityPreprocessed( art )) {
|
|
338
363
|
info( null, artPath, { name: artName },
|
|
339
|
-
|
|
364
|
+
'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );
|
|
340
365
|
return null;
|
|
341
366
|
}
|
|
342
367
|
|
|
@@ -345,13 +370,13 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
345
370
|
|
|
346
371
|
if (!textsEntity) {
|
|
347
372
|
info( null, artPath, { name: artName },
|
|
348
|
-
|
|
373
|
+
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
|
|
349
374
|
return null;
|
|
350
375
|
}
|
|
351
376
|
|
|
352
377
|
if (!isValidTextsEntity( textsEntity )) {
|
|
353
378
|
info( null, [ 'definitions', textsName ], { name: artName },
|
|
354
|
-
|
|
379
|
+
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
|
|
355
380
|
return null;
|
|
356
381
|
}
|
|
357
382
|
|
|
@@ -587,9 +612,10 @@ function _addLocalizationViews(csn, options, useJoins) {
|
|
|
587
612
|
*
|
|
588
613
|
* @param {CSN.Model} csn
|
|
589
614
|
* @param {CSN.Options} options
|
|
615
|
+
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
590
616
|
*/
|
|
591
|
-
function addLocalizationViews(csn, options) {
|
|
592
|
-
return _addLocalizationViews(csn, options, false);
|
|
617
|
+
function addLocalizationViews(csn, options, acceptLocalizedView = null) {
|
|
618
|
+
return _addLocalizationViews(csn, options, false, acceptLocalizedView);
|
|
593
619
|
}
|
|
594
620
|
|
|
595
621
|
/**
|
|
@@ -599,21 +625,19 @@ function addLocalizationViews(csn, options) {
|
|
|
599
625
|
*
|
|
600
626
|
* @param {CSN.Model} csn
|
|
601
627
|
* @param {CSN.Options} options
|
|
628
|
+
* @param [acceptLocalizedView] optional callback function returning true if the localized view name and its parent name provided as parameter should be created
|
|
602
629
|
*/
|
|
603
|
-
function addLocalizationViewsWithJoins(csn, options) {
|
|
604
|
-
return _addLocalizationViews(csn, options, true);
|
|
630
|
+
function addLocalizationViewsWithJoins(csn, options, acceptLocalizedView = null) {
|
|
631
|
+
return _addLocalizationViews(csn, options, true, acceptLocalizedView);
|
|
605
632
|
}
|
|
606
633
|
|
|
607
634
|
/**
|
|
608
635
|
* @param {string[]} ref Reference path
|
|
609
636
|
* @param {string} [as] Alias for path.
|
|
610
|
-
* @param {boolean} [withEnv=true] If true, add `$env:1`
|
|
611
637
|
* @return {CSN.Column}
|
|
612
638
|
*/
|
|
613
|
-
function createColumnRef(ref, as = null
|
|
639
|
+
function createColumnRef(ref, as = null) {
|
|
614
640
|
const column = { ref };
|
|
615
|
-
if (withEnv)
|
|
616
|
-
setProp(column, '$env', 1);
|
|
617
641
|
if (as)
|
|
618
642
|
column.as = as;
|
|
619
643
|
// @ts-ignore
|
|
@@ -634,9 +658,7 @@ function columnsForEntityWithExcludeList(entity, entityName, excludeList) {
|
|
|
634
658
|
return Object.keys(entity.elements)
|
|
635
659
|
.filter(elementName => !excludeList.includes(elementName))
|
|
636
660
|
.map(elementName => {
|
|
637
|
-
|
|
638
|
-
setProp(column, '$env', 1);
|
|
639
|
-
return column;
|
|
661
|
+
return { ref: [ entityName, elementName ] };
|
|
640
662
|
});
|
|
641
663
|
}
|
|
642
664
|
|
|
@@ -664,7 +686,7 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
664
686
|
// Do NOT copy ".exists" at the moment. ".exists" is not propagated
|
|
665
687
|
// and this would lead to some localization views referencing not-existing
|
|
666
688
|
// "localized.XYZ" views.
|
|
667
|
-
if (anno
|
|
689
|
+
if (anno === '@cds.persistence.skip')
|
|
668
690
|
target[anno] = source[anno];
|
|
669
691
|
});
|
|
670
692
|
}
|
|
@@ -24,6 +24,8 @@ const structuralNodeHandlers = {
|
|
|
24
24
|
groupBy: traverseArray,
|
|
25
25
|
having: traverseArray,
|
|
26
26
|
xpr: traverseArray,
|
|
27
|
+
expand: traverseArray,
|
|
28
|
+
inline: traverseArray,
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
function attachPath(csn) {
|
|
@@ -38,6 +40,7 @@ function attachPathOnPartialCSN(csnPart, pathPrefix) {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
function traverseRef(obj, path) {
|
|
43
|
+
if(!obj) return;
|
|
41
44
|
setPath(obj, path);
|
|
42
45
|
traverseArray(obj, path);
|
|
43
46
|
}
|
|
@@ -48,7 +51,7 @@ function traverseArray(obj, path) {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
function traverseDict(obj, path) {
|
|
51
|
-
if(typeof obj !== 'object') return;
|
|
54
|
+
if(!obj || typeof obj !== 'object') return;
|
|
52
55
|
forAllEnumerableProperties(obj, name => {
|
|
53
56
|
const ipath = path.concat(name);
|
|
54
57
|
setPath(obj[name], ipath);
|
|
@@ -56,17 +59,29 @@ function traverseDict(obj, path) {
|
|
|
56
59
|
})
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
function traverseDictArray(obj, path) {
|
|
63
|
+
if(!obj || typeof obj !== 'object') return;
|
|
64
|
+
forAllEnumerableProperties(obj, name => {
|
|
65
|
+
const ipath = path.concat(name);
|
|
66
|
+
setPath(obj[name], ipath);
|
|
67
|
+
traverseArray(obj[name], ipath);
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
function traverseTyped(obj, path) {
|
|
60
|
-
if(!obj) return;
|
|
72
|
+
if(!obj || typeof obj !== 'object') return;
|
|
61
73
|
forAllEnumerableProperties(obj, name => {
|
|
62
74
|
if(name[0]==='@') return; // skip annotations
|
|
63
75
|
const func = structuralNodeHandlers[name];
|
|
64
|
-
if(func)
|
|
76
|
+
if(func)
|
|
77
|
+
func(obj[name], path.concat(name));
|
|
78
|
+
else if(path[path.length-2] === 'columns')
|
|
79
|
+
traverseDictArray(obj[name], path.concat(name)); // for columns
|
|
65
80
|
})
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
function setPath(obj, path) {
|
|
69
|
-
if(typeof obj !== 'object') return;
|
|
84
|
+
if(!obj || typeof obj !== 'object') return;
|
|
70
85
|
if(path.length>0)
|
|
71
86
|
Object.defineProperty( obj, '$path', { value: path, configurable: true, writable: true, enumerable: false } );
|
|
72
87
|
}
|
|
@@ -17,13 +17,13 @@ const { attachPath, attachPathOnPartialCSN } = require('./attachPath');
|
|
|
17
17
|
* }
|
|
18
18
|
* after expand -> keys:[ { ref: ['stru_subid'] } ]
|
|
19
19
|
*/
|
|
20
|
-
module.exports = function (csn, referenceFlattener, csnUtils) {
|
|
20
|
+
module.exports = function (csn, referenceFlattener, csnUtils, isExternalServiceMember) {
|
|
21
21
|
|
|
22
22
|
forEachManagedAssociation(csn, (element) => {
|
|
23
23
|
if (element.keys) {
|
|
24
24
|
expandStructuredKeysForAssociation(element, referenceFlattener);
|
|
25
25
|
}
|
|
26
|
-
});
|
|
26
|
+
}, isExternalServiceMember);
|
|
27
27
|
|
|
28
28
|
// update paths and resolve references
|
|
29
29
|
attachPath(csn);
|
|
@@ -18,13 +18,13 @@ const { implicitAs } = require('../../model/csnRefs');
|
|
|
18
18
|
* @param {*} csnUtils
|
|
19
19
|
* @param {object} error;
|
|
20
20
|
*/
|
|
21
|
-
module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
21
|
+
module.exports = function (csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember) {
|
|
22
22
|
|
|
23
23
|
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';
|
|
24
24
|
const flatKeys = !structuredOData || (structuredOData && options.toOdata.odataForeignKeys);
|
|
25
25
|
|
|
26
26
|
// sort all associations by their dependencies
|
|
27
|
-
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener);
|
|
27
|
+
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener, isExternalServiceMember);
|
|
28
28
|
|
|
29
29
|
// generate foreign keys
|
|
30
30
|
processSortedAssociations(sortedAssociations, flatKeys);
|
|
@@ -35,7 +35,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
35
35
|
let generatedForeignKeyNamesForPath = Object.create(null); // map<path,[key-name]>
|
|
36
36
|
|
|
37
37
|
sortedAssociations.forEach(item => {
|
|
38
|
-
const { definitionName, elementName, element, parent, path } = item;
|
|
38
|
+
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
39
|
|
|
40
40
|
if (csnUtils.isManagedAssociationElement(element) && element.keys) {
|
|
41
41
|
if (flatKeys) // tackling the ref value in assoc.keys
|
|
@@ -44,7 +44,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
44
44
|
fixCardinality(element);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, elementName, element, parent, path);
|
|
47
|
+
let arrayOfGeneratedForeignKeyNames = generateForeignKeys(definitionName, structuralNodeName, elementName, element, parent, path);
|
|
48
48
|
generatedForeignKeyNamesForPath[item.path.join('/')] = arrayOfGeneratedForeignKeyNames;
|
|
49
49
|
})
|
|
50
50
|
|
|
@@ -111,7 +111,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
111
111
|
/**
|
|
112
112
|
* Generates foreign keys and returns their names as an array
|
|
113
113
|
*/
|
|
114
|
-
function generateForeignKeys(definitionName, assocName, assoc, parent, path) {
|
|
114
|
+
function generateForeignKeys(definitionName, structuralNodeName, assocName, assoc, parent, path) {
|
|
115
115
|
let foreignKeyElements = Object.create(null);
|
|
116
116
|
|
|
117
117
|
// First, loop over the keys array of the association and generate the FKs.
|
|
@@ -134,13 +134,14 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
134
134
|
if (parent.returns)
|
|
135
135
|
parent = parent.returns.items || parent.returns;
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
const dictionary = parent[structuralNodeName];
|
|
138
|
+
let currElementsNames = Object.keys(parent[structuralNodeName]);
|
|
138
139
|
for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
|
|
139
140
|
copyAnnotations(assoc, foreignKey, true);
|
|
140
141
|
// Insert artificial element into artifact, with all cross-links
|
|
141
|
-
if (
|
|
142
|
-
if (!(
|
|
143
|
-
const path =
|
|
142
|
+
if (dictionary[foreignKeyName]) {
|
|
143
|
+
if (!(dictionary[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(dictionary[foreignKeyName], foreignKey))) {
|
|
144
|
+
const path = dictionary[foreignKeyName].$path;
|
|
144
145
|
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
145
146
|
}
|
|
146
147
|
}
|
|
@@ -151,8 +152,8 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
151
152
|
// if (flatKeys)
|
|
152
153
|
currElementsNames.splice(assocIndex + 1, 0, ...Object.keys(foreignKeyElements));
|
|
153
154
|
|
|
154
|
-
parent
|
|
155
|
-
previous[name] =
|
|
155
|
+
parent[structuralNodeName] = currElementsNames.reduce((previous, name) => {
|
|
156
|
+
previous[name] = dictionary[name] || foreignKeyElements[name];
|
|
156
157
|
return previous;
|
|
157
158
|
}, Object.create(null));
|
|
158
159
|
|
|
@@ -180,7 +181,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
|
|
|
180
181
|
|
|
181
182
|
function processStucturedKey(fkArtifact, assocName, foreignKeyRef) {
|
|
182
183
|
const subStruct = fkArtifact.elements ? fkArtifact : csnUtils.getFinalBaseType(fkArtifact.type);
|
|
183
|
-
const flatElements = flattenStructure(subStruct, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
|
+
const flatElements = flattenStructure(subStruct.elements, subStruct.$path, csnUtils, options, error, undefined, fkArtifact.$path.slice(-1) || []).newFlatElements;
|
|
184
185
|
for (const [flatElemName, flatElem] of Object.entries(flatElements)) {
|
|
185
186
|
const foreignKeyElementName =
|
|
186
187
|
`${assocName.replace(/\./g, '_')}_${foreignKeyRef.as ? flatElemName.replace(implicitAs(foreignKeyRef.ref), foreignKeyRef.as) : flatElemName}`;
|