@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.
Files changed (106) hide show
  1. package/CHANGELOG.md +241 -1
  2. package/bin/.eslintrc.json +17 -0
  3. package/bin/cds_update_identifiers.js +8 -7
  4. package/bin/cdsc.js +180 -132
  5. package/bin/cdshi.js +18 -11
  6. package/bin/cdsse.js +38 -32
  7. package/bin/cdsv2m.js +8 -7
  8. package/doc/CHANGELOG_BETA.md +36 -1
  9. package/lib/api/main.js +81 -100
  10. package/lib/api/options.js +17 -11
  11. package/lib/api/validate.js +12 -8
  12. package/lib/backends.js +0 -81
  13. package/lib/base/keywords.js +32 -2
  14. package/lib/base/location.js +2 -2
  15. package/lib/base/message-registry.js +66 -4
  16. package/lib/base/messages.js +84 -27
  17. package/lib/base/model.js +2 -61
  18. package/lib/checks/arrayOfs.js +0 -1
  19. package/lib/checks/defaultValues.js +27 -2
  20. package/lib/checks/elements.js +1 -6
  21. package/lib/checks/enricher.js +8 -2
  22. package/lib/checks/foreignKeys.js +0 -6
  23. package/lib/checks/managedWithoutKeys.js +17 -0
  24. package/lib/checks/nonexpandableStructured.js +38 -0
  25. package/lib/checks/onConditions.js +9 -45
  26. package/lib/checks/queryNoDbArtifacts.js +27 -9
  27. package/lib/checks/selectItems.js +25 -2
  28. package/lib/checks/types.js +26 -2
  29. package/lib/checks/unknownMagic.js +38 -0
  30. package/lib/checks/utils.js +61 -0
  31. package/lib/checks/validator.js +66 -13
  32. package/lib/compiler/assert-consistency.js +24 -12
  33. package/lib/compiler/builtins.js +2 -0
  34. package/lib/compiler/checks.js +6 -4
  35. package/lib/compiler/definer.js +101 -39
  36. package/lib/compiler/index.js +88 -59
  37. package/lib/compiler/resolver.js +455 -209
  38. package/lib/compiler/shared.js +57 -33
  39. package/lib/edm/annotations/genericTranslation.js +183 -187
  40. package/lib/edm/csn2edm.js +128 -99
  41. package/lib/edm/edm.js +18 -21
  42. package/lib/edm/edmPreprocessor.js +361 -127
  43. package/lib/edm/edmUtils.js +103 -33
  44. package/lib/gen/Dictionary.json +74 -28
  45. package/lib/gen/language.checksum +1 -1
  46. package/lib/gen/language.interp +18 -4
  47. package/lib/gen/language.tokens +124 -118
  48. package/lib/gen/languageLexer.interp +13 -1
  49. package/lib/gen/languageLexer.js +870 -839
  50. package/lib/gen/languageLexer.tokens +116 -111
  51. package/lib/gen/languageParser.js +5894 -5614
  52. package/lib/json/from-csn.js +152 -67
  53. package/lib/json/to-csn.js +334 -135
  54. package/lib/language/antlrParser.js +4 -3
  55. package/lib/language/errorStrategy.js +1 -0
  56. package/lib/language/genericAntlrParser.js +24 -14
  57. package/lib/language/language.g4 +188 -128
  58. package/lib/main.d.ts +435 -0
  59. package/lib/main.js +31 -7
  60. package/lib/model/api.js +78 -0
  61. package/lib/model/csnRefs.js +463 -187
  62. package/lib/model/csnUtils.js +280 -136
  63. package/lib/model/enrichCsn.js +75 -4
  64. package/lib/model/revealInternalProperties.js +2 -1
  65. package/lib/modelCompare/compare.js +70 -25
  66. package/lib/optionProcessor.js +13 -10
  67. package/lib/render/.eslintrc.json +4 -1
  68. package/lib/render/DuplicateChecker.js +8 -5
  69. package/lib/render/toCdl.js +123 -40
  70. package/lib/render/toHdbcds.js +156 -65
  71. package/lib/render/toSql.js +87 -11
  72. package/lib/render/utils/common.js +55 -9
  73. package/lib/render/utils/sql.js +3 -3
  74. package/lib/sql-identifier.js +6 -1
  75. package/lib/transform/{sql → db}/.eslintrc.json +0 -0
  76. package/lib/transform/{sql → db}/assertUnique.js +7 -8
  77. package/lib/transform/{sql → db}/constraints.js +35 -20
  78. package/lib/transform/db/draft.js +353 -0
  79. package/lib/transform/db/expansion.js +582 -0
  80. package/lib/transform/db/flattening.js +325 -0
  81. package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
  82. package/lib/transform/{sql → db}/helpers.js +0 -0
  83. package/lib/transform/{sql → db}/transformExists.js +256 -60
  84. package/lib/transform/forHanaNew.js +216 -765
  85. package/lib/transform/forOdataNew.js +60 -56
  86. package/lib/transform/localized.js +48 -26
  87. package/lib/transform/odata/attachPath.js +19 -4
  88. package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
  89. package/lib/transform/odata/generateForeignKeyElements.js +13 -12
  90. package/lib/transform/odata/referenceFlattener.js +60 -36
  91. package/lib/transform/odata/sortByAssociationDependency.js +4 -4
  92. package/lib/transform/odata/structuralPath.js +76 -0
  93. package/lib/transform/odata/structureFlattener.js +21 -22
  94. package/lib/transform/odata/toFinalBaseType.js +5 -5
  95. package/lib/transform/odata/typesExposure.js +27 -17
  96. package/lib/transform/odata/utils.js +2 -2
  97. package/lib/transform/transformUtilsNew.js +141 -77
  98. package/lib/transform/translateAssocsToJoins.js +17 -14
  99. package/lib/transform/universalCsnEnricher.js +67 -0
  100. package/lib/utils/file.js +0 -11
  101. package/lib/utils/moduleResolve.js +6 -8
  102. package/lib/utils/timetrace.js +6 -1
  103. package/package.json +2 -1
  104. package/lib/base/deepCopy.js +0 -66
  105. package/lib/json/walker.js +0 -26
  106. package/lib/utils/string.js +0 -17
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { handleMessages, makeMessageFunction } = require('../base/messages');
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, hasLocalizedConvenienceView, isInLocalizedNamespace } = require('./localized');
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
- expandStructsInOnConditions
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
- hasBoolAnnotation,
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
- let services = getServiceNames(csn);
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
- handleMessages(csn, options);
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 ON condition
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 ON conditions.
154
- // If errors are detected, handleMessages will return from further processing
155
- forEachDefinition(csn, expandStructsInOnConditions);
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
- flattenCSN(csn, csnUtils, options, referenceFlattener, error);
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
- handleMessages(inputModel, options);
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 (!isArtifactInSomeService(defName, services)) {
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
- if (member._flatElementNameWithDots) {
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
- handleMessages(csn, options);
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['@Common.DraftRoot.ActivationAction'] = 'draftActivate';
456
- artifact['@Common.DraftRoot.EditAction'] = 'draftEdit';
457
- artifact['@Common.DraftRoot.PreparationAction'] = 'draftPrepare';
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['@Common.DraftNode.PreparationAction'] = 'draftPrepare';
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
- // Barf if the draft node has @odata.draft.enabled itself
528
- if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', true)) {
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('../json/to-csn');
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, definition => cleanSymbols(definition, _hasLocalizedView, _isViewForEntity, _isViewForView, _targetFor));
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
- csn.definitions[viewName] = createLocalizedViewForView(art);
152
+ view = createLocalizedViewForView(art);
130
153
  else
131
- csn.definitions[viewName] = createLocalizedViewForEntity(art, artName, textElements);
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, false, options),
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', false );
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', false ),
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, false, options);
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, false, options);
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
- `Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements` );
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
- `Skipped creation of convenience view for $(NAME) because its texts entity could not be found` );
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
- `Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid` );
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, withEnv = true) {
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
- const column = { ref: [ entityName, elementName ] }
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.startsWith('@cds.persistence.skip'))
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) func(obj[name], path.concat(name));
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
- let currElementsNames = Object.keys(parent.elements);
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 (parent.elements[foreignKeyName]) {
142
- if (!(parent.elements[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(parent.elements[foreignKeyName], foreignKey))) {
143
- const path = parent.elements[foreignKeyName].$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.elements = currElementsNames.reduce((previous, name) => {
155
- previous[name] = parent.elements[name] || foreignKeyElements[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}`;