@sap/cds-compiler 2.12.0 → 2.15.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 +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -290
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
- package/lib/transform/universalCsnEnricher.js +0 -237
|
@@ -4,10 +4,10 @@ const { makeMessageFunction } = require('../base/messages');
|
|
|
4
4
|
const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
5
5
|
const transformUtils = require('./transformUtilsNew');
|
|
6
6
|
const { getUtils,
|
|
7
|
-
|
|
7
|
+
cloneCsnNonDict,
|
|
8
8
|
forEachDefinition,
|
|
9
9
|
forEachMemberRecursively,
|
|
10
|
-
|
|
10
|
+
applyTransformationsOnNonDictionary,
|
|
11
11
|
getArtifactDatabaseNameOf,
|
|
12
12
|
getElementDatabaseNameOf,
|
|
13
13
|
isAspect,
|
|
@@ -16,15 +16,14 @@ const { getUtils,
|
|
|
16
16
|
} = require('../model/csnUtils');
|
|
17
17
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
18
18
|
const validate = require('../checks/validator');
|
|
19
|
-
const { isArtifactInSomeService,
|
|
20
|
-
const ReferenceFlattener = require('./odata/referenceFlattener');
|
|
21
|
-
const { flattenCSN } = require('./odata/structureFlattener');
|
|
22
|
-
const generateForeignKeys = require('./odata/generateForeignKeyElements');
|
|
23
|
-
const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssociations');
|
|
19
|
+
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
|
|
24
20
|
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
25
21
|
const { timetrace } = require('../utils/timetrace');
|
|
26
|
-
const
|
|
27
|
-
const
|
|
22
|
+
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
23
|
+
const flattening = require('./db/flattening');
|
|
24
|
+
const associations = require('./db/associations')
|
|
25
|
+
const expansion = require('./db/expansion');
|
|
26
|
+
const generateDrafts = require('./draft/odata');
|
|
28
27
|
|
|
29
28
|
const { addLocalizationViews } = require('./localized');
|
|
30
29
|
|
|
@@ -73,10 +72,10 @@ module.exports = { transform4odataWithCsn };
|
|
|
73
72
|
function transform4odataWithCsn(inputModel, options) {
|
|
74
73
|
timetrace.start('OData transformation');
|
|
75
74
|
// copy the model as we don't want to change the input model
|
|
76
|
-
let csn =
|
|
75
|
+
let csn = cloneCsnNonDict(inputModel, options);
|
|
77
76
|
|
|
78
|
-
const { error, warning, info,
|
|
79
|
-
|
|
77
|
+
const { message, error, warning, info, throwWithAnyError } = makeMessageFunction(csn, options, 'for.odata');
|
|
78
|
+
throwWithAnyError();
|
|
80
79
|
|
|
81
80
|
// the new transformer works only with new CSN
|
|
82
81
|
checkCSNVersion(csn, options);
|
|
@@ -84,25 +83,18 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
84
83
|
const transformers = transformUtils.getTransformers(csn, options, '_');
|
|
85
84
|
const {
|
|
86
85
|
addDefaultTypeFacets,
|
|
87
|
-
createForeignKeyElement,
|
|
88
|
-
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
89
|
-
createAssociationElement, createAssociationPathComparison,
|
|
90
|
-
addElement, createAction, assignAction,
|
|
91
86
|
extractValidFromToKeyElement,
|
|
92
87
|
checkAssignment, checkMultipleAssignments,
|
|
93
|
-
recurseElements, setAnnotation,
|
|
88
|
+
recurseElements, setAnnotation, renameAnnotation,
|
|
94
89
|
expandStructsInExpression
|
|
95
90
|
} = transformers;
|
|
96
91
|
|
|
97
92
|
const csnUtils = getUtils(csn);
|
|
98
93
|
const {
|
|
99
94
|
getCsnDef,
|
|
100
|
-
getFinalType,
|
|
101
95
|
getServiceName,
|
|
102
|
-
hasAnnotationValue,
|
|
103
96
|
isAssocOrComposition,
|
|
104
97
|
isAssociation,
|
|
105
|
-
isStructured,
|
|
106
98
|
inspectRef,
|
|
107
99
|
artifactRef,
|
|
108
100
|
effectiveType,
|
|
@@ -132,13 +124,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
132
124
|
|
|
133
125
|
addLocalizationViews(csn, options, acceptLocalizedView);
|
|
134
126
|
|
|
135
|
-
validate.forOdata(csn, {
|
|
136
|
-
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
127
|
+
const cleanup = validate.forOdata(csn, {
|
|
128
|
+
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
137
129
|
});
|
|
138
130
|
|
|
139
131
|
|
|
140
132
|
// Throw exception in case of errors
|
|
141
|
-
|
|
133
|
+
throwWithAnyError();
|
|
142
134
|
|
|
143
135
|
// Semantic checks before flattening regarding temporal data
|
|
144
136
|
// TODO: Move in the validator
|
|
@@ -160,35 +152,38 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
160
152
|
// Check if structured elements and managed associations are compared in an expression
|
|
161
153
|
// and expand these structured elements. This tuple expansion allows all other
|
|
162
154
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
163
|
-
// If errors are detected,
|
|
155
|
+
// If errors are detected, throwWithAnyError() will return from further processing
|
|
164
156
|
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
165
157
|
|
|
166
|
-
// handles reference flattening
|
|
167
|
-
let referenceFlattener = new ReferenceFlattener();
|
|
168
|
-
referenceFlattener.resolveAllReferences(csn, inspectRef, isStructured);
|
|
169
|
-
attachPath(csn);
|
|
170
|
-
|
|
171
|
-
referenceFlattener.applyAliasesInOnCond(csn, inspectRef);
|
|
172
|
-
|
|
173
158
|
if (!structuredOData) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
159
|
+
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, { skipArtifact: isExternalServiceMember });
|
|
160
|
+
const resolved = new WeakMap();
|
|
161
|
+
// No refs with struct-steps exist anymore
|
|
162
|
+
flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
|
|
163
|
+
// No type references exist anymore
|
|
164
|
+
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
165
|
+
// OData doesn't resolve type chains after the first 'items'
|
|
166
|
+
flattening.resolveTypeReferences(csn, options, resolved, '_',
|
|
167
|
+
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'], skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
|
|
168
|
+
// No structured elements exists anymore
|
|
169
|
+
flattening.flattenElements(csn, options, '_', error,
|
|
170
|
+
{ skip: ['action', 'aspect', 'event', 'function', 'type'], skipArtifact: isExternalServiceMember,
|
|
171
|
+
skipStandard: { items: true }, // don't drill further into .items
|
|
172
|
+
skipDict: { actions: true } }); // don't drill further into .actions -> bound actions, action-artifacts are handled by skip
|
|
179
173
|
}
|
|
180
174
|
|
|
181
|
-
//
|
|
182
|
-
|
|
175
|
+
// TODO: add the generated foreign keys to the columns when we are in a view
|
|
176
|
+
// see db/views.js::addForeignKeysToColumns
|
|
177
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, { skipArtifact: isExternalServiceMember });
|
|
183
178
|
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
//
|
|
191
|
-
|
|
179
|
+
// Allow using managed associations as steps in on-conditions to access their fks
|
|
180
|
+
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
181
|
+
// since then the foreign keys of the managed assocs are part of the elements
|
|
182
|
+
if(!structuredOData)
|
|
183
|
+
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, '_'));
|
|
184
|
+
|
|
185
|
+
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
|
|
186
|
+
throwWithAnyError();
|
|
192
187
|
|
|
193
188
|
// Apply default type facets as set by options
|
|
194
189
|
// Flatten on-conditions in unmanaged associations
|
|
@@ -209,37 +204,26 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
209
204
|
// - structured types must not contain associations for OData V2
|
|
210
205
|
// - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
211
206
|
// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
212
|
-
|
|
213
|
-
forEachDefinition(csn, (def, defName) => {
|
|
214
|
-
if (def.kind === 'entity' || def.kind === 'view') {
|
|
215
|
-
// Generate artificial draft fields if requested
|
|
216
|
-
if (def['@odata.draft.enabled']) {
|
|
217
|
-
// Ignore if not part of a service
|
|
218
|
-
if (isArtifactInSomeService(defName, services)) {
|
|
219
|
-
generateDraftForOdata(def, defName, def, visitedArtifacts);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
visitedArtifacts[defName] = true;
|
|
224
|
-
}, { skipArtifact: isExternalServiceMember });
|
|
207
|
+
generateDrafts(csn, options, services)
|
|
225
208
|
|
|
226
209
|
// Deal with all kind of annotations manipulations here
|
|
210
|
+
const skipPersNameKinds = {'service':1, 'context':1, 'namespace':1, 'annotation':1, 'action':1, 'function':1};
|
|
227
211
|
forEachDefinition(csn, (def, defName) => {
|
|
228
212
|
// Resolve annotation shorthands for entities, types, annotations, ...
|
|
229
213
|
renameShorthandAnnotations(def);
|
|
230
214
|
|
|
231
215
|
// Annotate artifacts with their DB names if requested.
|
|
232
216
|
// Skip artifacts that have no DB equivalent anyway
|
|
233
|
-
if (options.toOdata.names && !
|
|
217
|
+
if (options.toOdata.names && !(def.kind in skipPersNameKinds))
|
|
234
218
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.toOdata.names, csn);
|
|
235
219
|
|
|
236
|
-
forEachMemberRecursively(def, (member, memberName, propertyName
|
|
220
|
+
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
237
221
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
238
222
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
239
223
|
// as they have no DB representation (although in views)
|
|
240
|
-
if (options.toOdata.names && typeof member === 'object' && !
|
|
224
|
+
if (options.toOdata.names && typeof member === 'object' && !(member.kind === 'action' || member.kind === 'function') && propertyName !== 'enum' && (!member.virtual || def.query)) {
|
|
241
225
|
// If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
|
|
242
|
-
member['@cds.persistence.name'] = getElementDatabaseNameOf(
|
|
226
|
+
member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.toOdata.names);
|
|
243
227
|
}
|
|
244
228
|
|
|
245
229
|
// Mark fields with @odata.on.insert/update as @Core.Computed
|
|
@@ -269,9 +253,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
269
253
|
}, { skipArtifact: isExternalServiceMember })
|
|
270
254
|
|
|
271
255
|
// Throw exception in case of errors
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (options.testMode) csn =
|
|
256
|
+
throwWithAnyError();
|
|
257
|
+
cleanup();
|
|
258
|
+
if (options.testMode) csn = cloneCsnNonDict(csn, options); // sort, keep hidden properties
|
|
275
259
|
timetrace.stop();
|
|
276
260
|
return csn;
|
|
277
261
|
|
|
@@ -342,7 +326,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
342
326
|
// but '@Core.Immutable' for everything else.
|
|
343
327
|
if (!(node['@readonly'] && node['@insertonly'])) {
|
|
344
328
|
if (name === '@readonly' && node[name] !== null) {
|
|
345
|
-
if (node.kind === 'entity'
|
|
329
|
+
if (node.kind === 'entity') {
|
|
346
330
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
347
331
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
348
332
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -352,7 +336,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
352
336
|
}
|
|
353
337
|
// @insertonly is effective on entities/queries only
|
|
354
338
|
else if (name === '@insertonly' && node[name] !== null) {
|
|
355
|
-
if (node.kind === 'entity'
|
|
339
|
+
if (node.kind === 'entity') {
|
|
356
340
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
357
341
|
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
|
|
358
342
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -423,151 +407,14 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
423
407
|
// removes leading $self in on-conditions's references
|
|
424
408
|
function removeLeadingDollarSelfInOnCondition(assoc) {
|
|
425
409
|
if (!assoc.on) return; // nothing to do
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
|
|
435
|
-
// into its transitively reachable composition targets, and into the model.
|
|
436
|
-
// 'rootArtifact' is the root artifact where composition traversal started.
|
|
437
|
-
|
|
438
|
-
// Constraints
|
|
439
|
-
// Draft Root: Exactly one PK of type UUID
|
|
440
|
-
// Draft Node: One PK of type UUID + 0..1 PK of another type
|
|
441
|
-
// Draft Node: Must not be reachable from multiple draft roots
|
|
442
|
-
function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {
|
|
443
|
-
// Sanity check
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
if (!isArtifactInSomeService(artifactName, services)) {
|
|
446
|
-
throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));
|
|
447
|
-
}
|
|
448
|
-
// Nothing to do if already draft-enabled (composition traversal may have circles)
|
|
449
|
-
if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction'])
|
|
450
|
-
&& artifact.actions && artifact.actions.draftPrepare) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
455
|
-
// @ts-ignore
|
|
456
|
-
let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
|
|
457
|
-
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
458
|
-
if (!draftAdminDataProjection) {
|
|
459
|
-
// @ts-ignore
|
|
460
|
-
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
|
|
461
|
-
}
|
|
462
|
-
// Report an error if it is not an entity or not what we expect
|
|
463
|
-
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
|
|
464
|
-
error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName },
|
|
465
|
-
`Generated entity $(NAME) conflicts with existing artifact`);
|
|
466
|
-
}
|
|
467
|
-
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
468
|
-
if (artifact == rootArtifact) {
|
|
469
|
-
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
|
|
470
|
-
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
|
|
471
|
-
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
472
|
-
} else {
|
|
473
|
-
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
artifact.elements && Object.values(artifact.elements).forEach( elem => {
|
|
477
|
-
// Make all non-key elements nullable
|
|
478
|
-
if (elem.notNull && elem.key !== true) {
|
|
479
|
-
delete elem.notNull;
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
// Generate the additional elements into the draft-enabled artifact
|
|
483
|
-
|
|
484
|
-
// key IsActiveEntity : Boolean default true
|
|
485
|
-
let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true, false);
|
|
486
|
-
isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
|
|
487
|
-
addElement(isActiveEntity, artifact, artifactName);
|
|
488
|
-
|
|
489
|
-
// HasActiveEntity : Boolean default false
|
|
490
|
-
let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false, true);
|
|
491
|
-
hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
|
|
492
|
-
addElement(hasActiveEntity, artifact, artifactName);
|
|
493
|
-
|
|
494
|
-
// HasDraftEntity : Boolean default false;
|
|
495
|
-
let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false, true);
|
|
496
|
-
hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
|
|
497
|
-
addElement(hasDraftEntity, artifact, artifactName);
|
|
498
|
-
|
|
499
|
-
// @odata.contained: true
|
|
500
|
-
// DraftAdministrativeData : Association to one DraftAdministrativeData;
|
|
501
|
-
let draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
|
|
502
|
-
draftAdministrativeData.DraftAdministrativeData.cardinality = { max: 1, };
|
|
503
|
-
draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
|
|
504
|
-
draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
|
|
505
|
-
addElement(draftAdministrativeData, artifact, artifactName);
|
|
506
|
-
|
|
507
|
-
// Note that we need to do the ODATA transformation steps for managed associations
|
|
508
|
-
// (foreign key field generation, generatedFieldName) by hand, because the corresponding
|
|
509
|
-
// transformation steps have already been done on all artifacts when we come here)
|
|
510
|
-
let uuidDraftKey = draftAdministrativeData.DraftAdministrativeData.keys.filter(key => key.ref && key.ref.length === 1 && key.ref[0] === 'DraftUUID');
|
|
511
|
-
if (uuidDraftKey && uuidDraftKey[0]) {
|
|
512
|
-
uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
|
|
513
|
-
let path = ['definitions', artifactName, 'elements', 'DraftAdministrativeData', 'keys', 0];
|
|
514
|
-
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName, path);
|
|
515
|
-
}
|
|
516
|
-
// SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)
|
|
517
|
-
let siblingEntity = createAssociationElement('SiblingEntity', artifactName, false);
|
|
518
|
-
siblingEntity.SiblingEntity.cardinality = { max: 1 };
|
|
519
|
-
addElement(siblingEntity, artifact, artifactName);
|
|
520
|
-
// ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
|
|
521
|
-
siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
|
|
522
|
-
|
|
523
|
-
// Iterate elements
|
|
524
|
-
artifact.elements && Object.entries(artifact.elements).forEach( ([elemName, elem]) => {
|
|
525
|
-
if (elemName !== 'IsActiveEntity' && elem.key) {
|
|
526
|
-
// Amend the ON-condition above:
|
|
527
|
-
// ... and SiblingEntity.<keyfield> = <keyfield> ... (for all key fields except 'IsActiveEntity')
|
|
528
|
-
let cond = createAssociationPathComparison('SiblingEntity', elemName, '=', elemName);
|
|
529
|
-
cond.push('and');
|
|
530
|
-
cond.push(...siblingEntity.SiblingEntity.on);
|
|
531
|
-
siblingEntity.SiblingEntity.on = cond;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Draft-enable the targets of composition elements (draft nodes), too
|
|
535
|
-
// TODO rewrite
|
|
536
|
-
if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
|
|
537
|
-
let draftNode = csn.definitions[elem.target];
|
|
538
|
-
|
|
539
|
-
// Ignore if that is our own draft root
|
|
540
|
-
if (draftNode != rootArtifact) {
|
|
541
|
-
// Report error when the draft node has @odata.draft.enabled itself
|
|
542
|
-
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
543
|
-
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
|
|
544
|
-
}
|
|
545
|
-
// Ignore composition if not part of a service or explicitly draft disabled
|
|
546
|
-
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
// Generate draft stuff into the target
|
|
551
|
-
generateDraftForOdata(draftNode, elem.target, rootArtifact, visitedArtifacts);
|
|
552
|
-
}
|
|
410
|
+
// TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
|
|
411
|
+
applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
|
|
412
|
+
ref: (node, prop, ref) => {
|
|
413
|
+
// remove leading $self when at the begining of a ref
|
|
414
|
+
if (ref.length > 1 && ref[0] === '$self')
|
|
415
|
+
node.ref.splice(0, 1);
|
|
553
416
|
}
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
558
|
-
|
|
559
|
-
// action draftPrepare (SideEffectsQualifier: String) return <artifact>;
|
|
560
|
-
let draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
561
|
-
assignAction(draftPrepare, artifact);
|
|
562
|
-
|
|
563
|
-
if (artifact == rootArtifact) {
|
|
564
|
-
// action draftActivate() return <artifact>;
|
|
565
|
-
let draftActivate = createAction('draftActivate', artifactName);
|
|
566
|
-
assignAction(draftActivate, artifact);
|
|
567
|
-
|
|
568
|
-
// action draftEdit (PreserveChanges: Boolean) return <artifact>;
|
|
569
|
-
let draftEdit = createAction('draftEdit', artifactName, 'PreserveChanges', 'cds.Boolean');
|
|
570
|
-
assignAction(draftEdit, artifact);
|
|
417
|
+
});
|
|
571
418
|
}
|
|
572
419
|
}
|
|
573
420
|
|
|
@@ -3,10 +3,10 @@
|
|
|
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('../model/csnUtils');
|
|
6
|
+
const { cloneCsnDictionary, applyDefinitionAnnotationsFromExtensions} = require('../model/csnUtils');
|
|
7
7
|
const { cleanSymbols } = require('../base/cleanSymbols.js');
|
|
8
8
|
const {
|
|
9
|
-
|
|
9
|
+
cloneCsnNonDict,
|
|
10
10
|
forEachDefinition,
|
|
11
11
|
forEachGeneric,
|
|
12
12
|
forAllQueries,
|
|
@@ -48,7 +48,7 @@ const _targetFor = Symbol('_targetFor');
|
|
|
48
48
|
* We have three kinds of localized convenience views:
|
|
49
49
|
*
|
|
50
50
|
* 1. "direct ones" using coalesce() for the table entities with localized
|
|
51
|
-
* elements: as projection on the original (created in
|
|
51
|
+
* elements: as projection on the original (created in extend.js)
|
|
52
52
|
* 2. for table entities with associations to entities which have a localized
|
|
53
53
|
* convenience views or redirections thereon: as projection on the original
|
|
54
54
|
* 3. for view entities with associations to entities which have a localized
|
|
@@ -74,11 +74,10 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
74
74
|
if (hasErrors(options.messages))
|
|
75
75
|
return csn;
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
const messageFunctions = makeMessageFunction(csn, options);
|
|
78
|
+
if (hasExistingLocalizationViews(csn, options, messageFunctions))
|
|
78
79
|
return csn;
|
|
79
80
|
|
|
80
|
-
const { info } = makeMessageFunction(csn, options);
|
|
81
|
-
|
|
82
81
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
83
82
|
options.localizedWithoutCoalesce);
|
|
84
83
|
|
|
@@ -87,6 +86,12 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
87
86
|
|
|
88
87
|
forEachDefinition(csn, definition => cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor));
|
|
89
88
|
|
|
89
|
+
// In case that the user tried to annotate `localized.*` artifacts, apply them.
|
|
90
|
+
applyDefinitionAnnotationsFromExtensions(csn, {
|
|
91
|
+
overwrite: true,
|
|
92
|
+
filter: (name) => name.startsWith('localized.')
|
|
93
|
+
});
|
|
94
|
+
|
|
90
95
|
sortCsnDefinitionsForTests(csn, options);
|
|
91
96
|
return csn;
|
|
92
97
|
|
|
@@ -102,7 +107,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
102
107
|
return;
|
|
103
108
|
|
|
104
109
|
if (isInLocalizedNamespace(artName))
|
|
105
|
-
// We already issued a warning for it in
|
|
110
|
+
// We already issued a warning for it in hasExistingLocalizationViews()
|
|
106
111
|
return;
|
|
107
112
|
|
|
108
113
|
const localized = getLocalizedTextElements( artName );
|
|
@@ -127,7 +132,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
127
132
|
|
|
128
133
|
if (csn.definitions[viewName]) {
|
|
129
134
|
// Already exists, skip creation.
|
|
130
|
-
info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
|
|
135
|
+
messageFunctions.info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
|
|
131
136
|
return;
|
|
132
137
|
}
|
|
133
138
|
|
|
@@ -142,9 +147,8 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
142
147
|
else
|
|
143
148
|
view = createLocalizedViewForEntity(art, artName, textElements);
|
|
144
149
|
|
|
150
|
+
copyPersistenceAnnotations(view, art);
|
|
145
151
|
csn.definitions[viewName] = view;
|
|
146
|
-
|
|
147
|
-
copyPersistenceAnnotations(csn.definitions[viewName], art);
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
/**
|
|
@@ -246,9 +250,9 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
246
250
|
};
|
|
247
251
|
|
|
248
252
|
if (view.query)
|
|
249
|
-
convenienceView.query =
|
|
253
|
+
convenienceView.query = cloneCsnNonDict(view.query, options);
|
|
250
254
|
else if (view.projection)
|
|
251
|
-
convenienceView.projection =
|
|
255
|
+
convenienceView.projection = cloneCsnNonDict(view.projection, options);
|
|
252
256
|
|
|
253
257
|
convenienceView.elements = cloneCsnDictionary(view.elements, options);
|
|
254
258
|
convenienceView[_isViewForView] = true;
|
|
@@ -339,7 +343,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
339
343
|
if (elem.key || elem.$key || elem.localized)
|
|
340
344
|
textElements.push( elemName );
|
|
341
345
|
|
|
342
|
-
// TODO: Already warned about in
|
|
346
|
+
// TODO: Already warned about in extend.js
|
|
343
347
|
// if (elem.key && isLocalized)
|
|
344
348
|
// warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );
|
|
345
349
|
}, artPath);
|
|
@@ -349,7 +353,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
349
353
|
return null;
|
|
350
354
|
|
|
351
355
|
if (!isEntityPreprocessed( art )) {
|
|
352
|
-
info( null, artPath, { name: artName },
|
|
356
|
+
messageFunctions.info( null, artPath, { name: artName },
|
|
353
357
|
'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );
|
|
354
358
|
return null;
|
|
355
359
|
}
|
|
@@ -358,13 +362,13 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
358
362
|
const textsEntity = csn.definitions[textsName];
|
|
359
363
|
|
|
360
364
|
if (!textsEntity) {
|
|
361
|
-
info( null, artPath, { name: artName },
|
|
365
|
+
messageFunctions.info( null, artPath, { name: artName },
|
|
362
366
|
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
|
|
363
367
|
return null;
|
|
364
368
|
}
|
|
365
369
|
|
|
366
370
|
if (!isValidTextsEntity( textsEntity )) {
|
|
367
|
-
info( null, [ 'definitions', textsName ], { name: artName },
|
|
371
|
+
messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
|
|
368
372
|
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
|
|
369
373
|
return null;
|
|
370
374
|
}
|
|
@@ -550,7 +554,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
550
554
|
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
|
|
551
555
|
return;
|
|
552
556
|
|
|
553
|
-
for (const prop of [ 'ref', 'target'
|
|
557
|
+
for (const prop of [ 'ref', 'target' ]) {
|
|
554
558
|
const val = obj[prop];
|
|
555
559
|
if (prop === 'ref') {
|
|
556
560
|
rewriteRefToLocalized(obj);
|
|
@@ -685,18 +689,32 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
685
689
|
*
|
|
686
690
|
* @param {CSN.Model} csn
|
|
687
691
|
* @param {CSN.Options} options
|
|
692
|
+
* @param {object} messageFunctions
|
|
688
693
|
*/
|
|
689
|
-
function hasExistingLocalizationViews(csn, options) {
|
|
694
|
+
function hasExistingLocalizationViews(csn, options, messageFunctions) {
|
|
690
695
|
if (!csn || !csn.definitions)
|
|
691
696
|
return false;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
697
|
+
|
|
698
|
+
let hasExistingViews = false;
|
|
699
|
+
let hasNonViews = false;
|
|
700
|
+
|
|
701
|
+
for (const name in csn.definitions) {
|
|
702
|
+
const art = csn.definitions[name];
|
|
703
|
+
if (isInLocalizedNamespace(name) || name === 'localized') {
|
|
704
|
+
if (!art.query && !art.projection) {
|
|
705
|
+
if (!name.endsWith('.texts')) {
|
|
706
|
+
hasNonViews = true;
|
|
707
|
+
messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
|
|
708
|
+
'The namespace "localized" is reserved for localization views');
|
|
709
|
+
}
|
|
710
|
+
} else if (!hasExistingViews) {
|
|
711
|
+
hasExistingViews = true;
|
|
712
|
+
messageFunctions.info( null, [ 'definitions', name ], {},
|
|
713
|
+
'Input CSN already contains localization views, no further ones will be created' );
|
|
714
|
+
}
|
|
715
|
+
}
|
|
698
716
|
}
|
|
699
|
-
return
|
|
717
|
+
return hasExistingViews || hasNonViews;
|
|
700
718
|
}
|
|
701
719
|
|
|
702
720
|
/**
|
|
@@ -728,9 +746,10 @@ function isEntityPreprocessed(entity) {
|
|
|
728
746
|
|
|
729
747
|
/**
|
|
730
748
|
* @param {string} name
|
|
749
|
+
* @returns {boolean}
|
|
731
750
|
*/
|
|
732
751
|
function isInLocalizedNamespace(name) {
|
|
733
|
-
return name.startsWith('localized.');
|
|
752
|
+
return name === 'localized' || name.startsWith('localized.');
|
|
734
753
|
}
|
|
735
754
|
|
|
736
755
|
/**
|
|
@@ -738,6 +757,7 @@ function isInLocalizedNamespace(name) {
|
|
|
738
757
|
*
|
|
739
758
|
* @param {CSN.Model} csn
|
|
740
759
|
* @param {string} artifactName
|
|
760
|
+
* @returns {boolean}
|
|
741
761
|
*/
|
|
742
762
|
function hasLocalizedConvenienceView(csn, artifactName) {
|
|
743
763
|
return !isInLocalizedNamespace(artifactName) && !!csn.definitions[`localized.${ artifactName }`];
|