@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.
Files changed (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. 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
- cloneCsn,
7
+ cloneCsnNonDict,
8
8
  forEachDefinition,
9
9
  forEachMemberRecursively,
10
- forEachRef,
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, getServiceOfArtifact, isLocalizedArtifactInService } = require('./odata/utils');
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 { attachPath } = require('./odata/attachPath');
27
- const enrichUniversalCsn = require('./universalCsnEnricher');
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 = cloneCsn(inputModel, options);
75
+ let csn = cloneCsnNonDict(inputModel, options);
77
76
 
78
- const { error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
79
- throwWithError();
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, resetAnnotation, renameAnnotation,
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
- throwWithError();
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, throwWithError() will return from further processing
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
- // flatten structures
175
- // @ts-ignore
176
- flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember);
177
- // flatten references
178
- referenceFlattener.flattenAllReferences(csn);
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
- // structure flattener reports errors, further processing is not safe -> throw exception in case of errors
182
- throwWithError();
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
- // Process associations
185
- // 1. In case we generate flat mode, expand the structured foreign keys.
186
- // This logic rewrites the 'ref' for such keys with the corresponding flattened
187
- // elements.
188
- if (!structuredOData)
189
- expandStructKeysInAssociations(csn, referenceFlattener, csnUtils, isExternalServiceMember);
190
- // 2. generate foreign keys for managed associations
191
- generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);
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
- let visitedArtifacts = Object.create(null);
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 && !['service', 'context', 'namespace', 'annotation', 'action', 'function'].includes(def.kind))
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, path) => {
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' && !['action', 'function'].includes(member.kind) && propertyName !== 'enum' && (!member.virtual || def.query)) {
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(referenceFlattener.getElementNameWithDots(path) || memberName, options.toOdata.names);
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
- throwWithError();
273
-
274
- if (options.testMode) csn = cloneCsn(csn, options); // sort, keep hidden properties
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' || node.kind === 'view') {
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' || node.kind === 'view') {
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
- forEachRef(assoc, (ref, node) => {
427
- // remove leading $self when at the begining of a ref
428
- if (ref.length > 1 && ref[0] === '$self')
429
- node.ref.splice(0, 1);
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
- cloneCsn,
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 definer.js)
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
- if (hasExistingLocalizationViews(csn, options))
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 warnAboutExistingLocalizationViews()
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 = cloneCsn(view.query, options);
253
+ convenienceView.query = cloneCsnNonDict(view.query, options);
250
254
  else if (view.projection)
251
- convenienceView.projection = cloneCsn(view.projection, options);
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 definer.js
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', 'on' ]) {
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
- const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
693
- if (firstLocalizedView) {
694
- const { info } = makeMessageFunction(csn, options);
695
- info( null, [ 'definitions', firstLocalizedView ], {},
696
- 'Input CSN already contains expansions for localized data' );
697
- return true;
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 false;
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 }`];