@sap/cds-compiler 3.9.4 → 4.0.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 (95) hide show
  1. package/CHANGELOG.md +107 -4
  2. package/README.md +0 -1
  3. package/bin/cdsc.js +11 -23
  4. package/bin/cdsse.js +3 -3
  5. package/doc/API.md +5 -0
  6. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  7. package/doc/CHANGELOG_BETA.md +17 -1
  8. package/doc/CHANGELOG_DEPRECATED.md +28 -0
  9. package/lib/api/.eslintrc.json +1 -1
  10. package/lib/api/main.js +55 -9
  11. package/lib/api/options.js +2 -0
  12. package/lib/base/error.js +2 -0
  13. package/lib/base/message-registry.js +143 -64
  14. package/lib/base/messages.js +213 -107
  15. package/lib/base/model.js +11 -11
  16. package/lib/checks/.eslintrc.json +1 -1
  17. package/lib/checks/annotationsOData.js +2 -2
  18. package/lib/checks/elements.js +1 -1
  19. package/lib/checks/enricher.js +26 -3
  20. package/lib/checks/onConditions.js +67 -12
  21. package/lib/checks/queryNoDbArtifacts.js +106 -105
  22. package/lib/checks/sql-snippets.js +2 -0
  23. package/lib/checks/types.js +12 -6
  24. package/lib/checks/validator.js +2 -2
  25. package/lib/compiler/assert-consistency.js +10 -8
  26. package/lib/compiler/builtins.js +8 -2
  27. package/lib/compiler/checks.js +52 -35
  28. package/lib/compiler/define.js +31 -26
  29. package/lib/compiler/extend.js +120 -65
  30. package/lib/compiler/finalize-parse-cdl.js +12 -43
  31. package/lib/compiler/generate.js +16 -5
  32. package/lib/compiler/index.js +8 -5
  33. package/lib/compiler/kick-start.js +4 -3
  34. package/lib/compiler/populate.js +96 -95
  35. package/lib/compiler/propagator.js +7 -8
  36. package/lib/compiler/resolve.js +377 -103
  37. package/lib/compiler/shared.js +794 -517
  38. package/lib/compiler/tweak-assocs.js +8 -6
  39. package/lib/compiler/utils.js +44 -0
  40. package/lib/edm/annotations/genericTranslation.js +41 -5
  41. package/lib/edm/csn2edm.js +34 -32
  42. package/lib/edm/edm.js +34 -31
  43. package/lib/edm/edmAnnoPreprocessor.js +0 -23
  44. package/lib/edm/edmInboundChecks.js +7 -2
  45. package/lib/edm/edmPreprocessor.js +25 -18
  46. package/lib/edm/edmUtils.js +8 -4
  47. package/lib/gen/Dictionary.json +18 -0
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +4 -2
  50. package/lib/gen/languageParser.js +5006 -4582
  51. package/lib/json/from-csn.js +157 -112
  52. package/lib/json/to-csn.js +60 -89
  53. package/lib/language/antlrParser.js +17 -13
  54. package/lib/language/docCommentParser.js +11 -1
  55. package/lib/language/genericAntlrParser.js +13 -10
  56. package/lib/language/language.g4 +168 -97
  57. package/lib/main.d.ts +128 -36
  58. package/lib/main.js +1 -1
  59. package/lib/model/csnRefs.js +24 -5
  60. package/lib/model/csnUtils.js +9 -8
  61. package/lib/model/revealInternalProperties.js +7 -12
  62. package/lib/model/sortViews.js +4 -2
  63. package/lib/modelCompare/compare.js +1 -1
  64. package/lib/modelCompare/utils/filter.js +40 -2
  65. package/lib/optionProcessor.js +0 -3
  66. package/lib/render/toCdl.js +247 -214
  67. package/lib/render/toHdbcds.js +197 -181
  68. package/lib/render/toSql.js +325 -289
  69. package/lib/render/utils/common.js +42 -4
  70. package/lib/render/utils/delta.js +1 -1
  71. package/lib/render/utils/sql.js +3 -3
  72. package/lib/transform/braceExpression.js +2 -2
  73. package/lib/transform/db/.eslintrc.json +1 -1
  74. package/lib/transform/db/applyTransformations.js +3 -3
  75. package/lib/transform/db/associations.js +24 -12
  76. package/lib/transform/db/expansion.js +17 -18
  77. package/lib/transform/db/flattening.js +17 -21
  78. package/lib/transform/db/rewriteCalculatedElements.js +171 -64
  79. package/lib/transform/db/views.js +3 -4
  80. package/lib/transform/draft/db.js +21 -12
  81. package/lib/transform/draft/odata.js +4 -0
  82. package/lib/transform/forOdataNew.js +62 -47
  83. package/lib/transform/forRelationalDB.js +12 -7
  84. package/lib/transform/localized.js +4 -2
  85. package/lib/transform/odata/toFinalBaseType.js +5 -5
  86. package/lib/transform/odata/typesExposure.js +3 -3
  87. package/lib/transform/parseExpr.js +3 -0
  88. package/lib/transform/transformUtilsNew.js +43 -23
  89. package/lib/transform/translateAssocsToJoins.js +7 -6
  90. package/lib/transform/universalCsn/.eslintrc.json +1 -1
  91. package/lib/transform/universalCsn/coreComputed.js +7 -5
  92. package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
  93. package/package.json +2 -2
  94. package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
  95. package/share/messages/message-explanations.json +1 -1
@@ -51,7 +51,7 @@ const { addLocalizationViews } = require('./localized');
51
51
  // -- exposed associations do not point to non-exposed targets
52
52
  // -- structured types must not contain associations for OData V2
53
53
  // - Element must not be an 'array of' for OData V2 TODO: move to the validator
54
- // (Linter Candiate, move as hard error into EdmPreproc on V2 generation)
54
+ // (Linter Candidate, move as hard error into EdmPreproc on V2 generation)
55
55
  // - Perform checks for exposed non-abstract entities and views - check media type and
56
56
  // key-ness (requires that containers have been identified) (Linter candidate, scenario check)
57
57
  // Annotations related:
@@ -201,7 +201,7 @@ function transform4odataWithCsn(inputModel, options) {
201
201
  // Flatten on-conditions in unmanaged associations
202
202
  /* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
203
203
  We should not remove $self prefixes in structured OData to not
204
- interfer with path resolution
204
+ interfere with path resolution
205
205
  */
206
206
  // This must be done before all the draft logic as all
207
207
  // composition targets are annotated with @odata.draft.enabled in this step
@@ -230,12 +230,13 @@ function transform4odataWithCsn(inputModel, options) {
230
230
  def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
231
231
 
232
232
  forEachMemberRecursively(def, (member, memberName, propertyName) => {
233
- if (memberName === '' && propertyName === 'params')
234
- return; // ignore "returns" type
235
233
  // Annotate elements, foreign keys, parameters, etc. with their DB names if requested
236
234
  // Only these are actually required and don't annotate virtual elements in entities or types
237
235
  // as they have no DB representation (although in views)
238
- if (options.sqlMapping && typeof member === 'object' && !(member.kind === 'action' || member.kind === 'function') && propertyName !== 'enum' && (!member.virtual || def.query)) {
236
+ if (options.sqlMapping && typeof member === 'object' &&
237
+ !(member.kind === 'action' || member.kind === 'function') &&
238
+ !(propertyName === 'enum' || propertyName === 'returns') &&
239
+ (!member.virtual || def.query)) {
239
240
  // If we have a 'preserved dotted name' (i.e. we are a result of flattening), use that for the @cds.persistence.name annotation
240
241
  member['@cds.persistence.name'] = getElementDatabaseNameOf(member._flatElementNameWithDots || memberName, options.sqlMapping, 'hana'); // hana to allow "hdbcds"
241
242
  }
@@ -311,88 +312,102 @@ function transform4odataWithCsn(inputModel, options) {
311
312
  node['@Core.Computed'] = true;
312
313
  }
313
314
 
314
- // Rename shorthand annotations within artifact or element 'node' according to a builtin
315
- // list.
315
+ // Rename shorthand annotations within artifact or element 'node' according to a builtin list
316
316
  function renameShorthandAnnotations(node) {
317
- // FIXME: Verify this list - are they all still required? Do we need any more?
318
317
  const setMappings = {
319
318
  '@label': '@Common.Label',
320
319
  '@title': '@Common.Label',
321
320
  '@description': '@Core.Description',
322
321
  };
323
322
  const renameMappings = {
324
- '@ValueList.entity': '@Common.ValueList.entity',
325
- '@ValueList.type': '@Common.ValueList.type',
326
- '@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable',
327
- '@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable',
328
- '@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
329
- '@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
323
+ '@ValueList.entity': { val: '@Common.ValueList', op: 'entity' },
324
+ '@ValueList.type': { val: '@Common.ValueList', op: 'type' },
325
+ '@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' },
326
+ '@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' },
327
+ '@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' },
328
+ '@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' }
330
329
  };
331
330
 
332
331
  const setShortCuts = Object.keys(setMappings);
333
332
  const renameShortCuts = Object.keys(renameMappings);
333
+
334
+ // Capabilities shortcuts have precedence over @readonly/@insertonly
334
335
  Object.keys(node).forEach( name => {
335
336
  if (!name.startsWith('@'))
336
337
  return;
337
338
  // Rename according to map above
338
- const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
339
+ const renamePrefix = (name in renameMappings)
340
+ ? name
341
+ : renameShortCuts.find(p => name.startsWith(p + '.'));
339
342
  if(renamePrefix) {
340
- renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
341
- } else {
343
+ const mapping = renameMappings[renamePrefix];
344
+ renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`));
345
+ }
346
+ else {
342
347
  // The two mappings have no overlap, so no need to check for second map if first matched.
343
348
  // Rename according to map above
344
- const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
349
+ const setPrefix = (name in setMappings)
350
+ ? name
351
+ : setShortCuts.find(p => name.startsWith(p + '.') || name.startsWith(p + '#'));
345
352
  if(setPrefix) {
346
353
  setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
347
354
  }
348
355
  }
356
+ });
357
+
358
+ // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
359
+ // but '@Core.Computed' for everything else.
349
360
 
350
- // Special case: '@readonly' becomes a triplet of capability restrictions for entities,
351
- // but '@Core.Immutable' for everything else.
352
- if (!(node['@readonly'] && node['@insertonly'])) {
353
- if (name === '@readonly' && node[name]) {
361
+ // only if not both readonly/insertonly are true do the mapping
362
+ if(!(node['@readonly'] && node['@insertonly'])) {
363
+ if(node['@readonly']) {
364
+ const setRO = (qualifier) => {
354
365
  if (node.kind === 'entity' || node.kind === 'aspect') {
355
- setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
356
- setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
357
- setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
366
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
367
+ setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false);
368
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
358
369
  } else {
359
370
  setAnnotation(node, '@Core.Computed', true);
360
371
  }
361
- }
362
- // @insertonly is effective on entities/queries only
363
- else if (name === '@insertonly' && node[name]) {
364
- if (node.kind === 'entity' || node.kind === 'aspect') {
365
- setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
366
- setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
367
- setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
368
- }
369
- }
372
+ };
373
+ setRO(undefined);
370
374
  }
371
- // Only on element level: translate @mandatory
372
- if (name === '@mandatory' && node[name] &&
373
- node.kind === undefined && node['@Common.FieldControl'] === undefined) {
374
- setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
375
+ // @insertonly is effective on entities/queries only
376
+ if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) {
377
+ const setIO = (qualifier) => {
378
+ setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false);
379
+ setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifier ? '#' + qualifier : ''}.Readable`, false);
380
+ setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false);
381
+ }
382
+ setIO(undefined);
375
383
  }
384
+ }
376
385
 
377
- if (name === '@assert.format' && node[name] !== null)
378
- setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
386
+ // @Validation.Pattern is applicable to "Term" => node.kind === annotation
387
+ if (node['@assert.format'] != null)
388
+ setAnnotation(node, '@Validation.Pattern', node['@assert.format']);
379
389
 
380
- if (name === '@assert.range' && node[name] !== null) {
390
+ // Only on element level
391
+ if(node.kind == null) {
392
+ if (node['@mandatory']&& node['@Common.FieldControl'] === undefined) {
393
+ setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' });
394
+ }
395
+ if (node['@assert.range'] != null) {
381
396
  if (Array.isArray(node['@assert.range']) && node['@assert.range'].length === 2) {
382
397
  setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]);
383
398
  setAnnotation(node, '@Validation.Maximum', node['@assert.range'][1]);
384
399
  }
385
400
  }
386
- });
401
+ }
387
402
  }
388
403
 
389
404
  // Apply default type facets to each type definition and every member
390
- // But do not apply default string length 5000 (as in DB)
405
+ // But do not apply default string length (as in DB)
391
406
  function setDefaultTypeFacets(def) {
392
- addDefaultTypeFacets(def.items || def, false)
393
- forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, false));
407
+ addDefaultTypeFacets(def.items || def, null)
408
+ forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, null));
394
409
  if(def.returns)
395
- addDefaultTypeFacets(def.returns.items || def.returns, false);
410
+ addDefaultTypeFacets(def.returns.items || def.returns, null);
396
411
  }
397
412
 
398
413
  // Handles on-conditions in unmanaged associations
@@ -409,7 +424,7 @@ function transform4odataWithCsn(inputModel, options) {
409
424
  // TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
410
425
  applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
411
426
  ref: (node, prop, ref) => {
412
- // remove leading $self when at the begining of a ref
427
+ // remove leading $self when at the beginning of a ref
413
428
  if (ref.length > 1 && ref[0] === '$self')
414
429
  node.ref.splice(0, 1);
415
430
  }
@@ -31,6 +31,7 @@ const cdsPersistence = require('./db/cdsPersistence');
31
31
  const temporal = require('./db/temporal');
32
32
  const associations = require('./db/associations')
33
33
  const { ModelError } = require('../base/error');
34
+ const { getDefaultTypeLengths } = require('../render/utils/common');
34
35
 
35
36
  // By default: Do not process non-entities/views
36
37
  function forEachDefinition(csn, cb) {
@@ -61,7 +62,7 @@ function forEachDefinition(csn, cb) {
61
62
  * - (045) The query is stripped from entities that are annotated with '@cds.persistence.table',
62
63
  * essentially converting views to entities.
63
64
  * - (060) Users of primitive type 'UUID' (which is renamed to 'String' in 000) get length 36'.
64
- * - (070) Default length 5000 is supplied for strings if not specified.
65
+ * - (070) Default length N is supplied for strings if not specified.
65
66
  * - (080) Annotation definitions are ignored (note that annotation assignments are filtered out by toCdl).
66
67
  * - (090) Compositions become associations.
67
68
  * - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
@@ -113,6 +114,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
113
114
  checkCSNVersion(csn, options);
114
115
 
115
116
  const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
117
+ // There is also an explicit default length via options.defaultStringLength
118
+ const implicitDefaultLengths = getDefaultTypeLengths(options.sqlDialect);
116
119
 
117
120
  let csnUtils;
118
121
  let message, error, warning, info; // message functions
@@ -160,6 +163,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
160
163
  });
161
164
  timetrace.stop('Validate');
162
165
 
166
+ rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
167
+
163
168
  // Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
164
169
  handleExists(csn, options, error, inspectRef, initDefinition, dropDefinitionCache);
165
170
 
@@ -184,8 +189,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
184
189
  assertUnique.prepare(csn, options, error, info)
185
190
  ]);
186
191
 
187
- rewriteCalculatedElementsInViews(csn, options, pathDelimiter, error);
188
-
189
192
  if(doA2J) {
190
193
  // Expand a structured thing in: keys, columns, order by, group by
191
194
  expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError}, csnUtils);
@@ -252,9 +255,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
252
255
  transformCsn(csn, {
253
256
  type: (val, node, key) => {
254
257
  renamePrimitiveTypesAndUuid(val, node, key);
255
- addDefaultTypeFacets(node);
258
+ addDefaultTypeFacets(node, implicitDefaultLengths);
256
259
  },
257
- // HANA/SQLite do not support array-of - turn into CLOB/Text
260
+ // no support for array-of - turn into CLOB/Text
261
+ // must be done after A2J or compiler checks could change
262
+ // (e.g. annotation def checks for arrayed types)
258
263
  items: (val, node) => {
259
264
  node.type = 'cds.LargeString';
260
265
  delete node.items;
@@ -572,7 +577,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
572
577
  addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
573
578
 
574
579
  forEachMemberRecursively(artifact, (member, memberName, property, path) => {
575
- if (memberName === '' && property === 'params')
580
+ if (property === 'returns')
576
581
  return; // ignore "returns" type
577
582
  transformCommon(member, memberName, path);
578
583
  // (240 b) Annotate elements, foreign keys, parameters etc with their DB names
@@ -1146,7 +1151,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
1146
1151
  // if it's not the first entry, add a ',' ...
1147
1152
  if (i)
1148
1153
  flattenedIndex.push(',');
1149
- // ... then add the flattend element name as a single ref
1154
+ // ... then add the flattened element name as a single ref
1150
1155
  flattenedIndex.push({ ref: [ elem ] });
1151
1156
  // ... then check if we have to propagate a 'asc'/'desc', omitting the last, which will be copied automatically
1152
1157
  if ((idx + 1) < index.length && (index[idx + 1] === 'asc' || index[idx + 1] === 'desc') && i < elems.length - 1)
@@ -610,8 +610,10 @@ function _addLocalizationViews(csn, options, useJoins, config) {
610
610
  override: true,
611
611
  filter: (name) => name.startsWith('localized.'),
612
612
  notFound(name, index) {
613
- if (!ignoreUnknownExtensions)
614
- messageFunctions.message('anno-undefined-art', [ 'extensions', index ], { name })
613
+ if (!ignoreUnknownExtensions) {
614
+ messageFunctions.message('anno-undefined-art', [ 'extensions', index ],
615
+ { art: name });
616
+ }
615
617
  },
616
618
  });
617
619
  }
@@ -103,14 +103,14 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
103
103
 
104
104
  if(node.type && !isBuiltinType(node.type)) {
105
105
  const finalBaseType = csnUtils.getFinalTypeInfo(node.type);
106
- if(!finalBaseType) {
106
+ if(finalBaseType == null) {
107
107
  /*
108
- type could not be resolved, set it to null
109
- Today, all type refs must be resolvable,
110
- input validations checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
108
+ type could not be resolved, delete type property to be equal to a typeless element
109
+ definition. Today, all type refs must be resolvable, input validations
110
+ checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
111
111
  guarantee this. In the future this may change.
112
112
  */
113
- node.type = null;
113
+ delete node.type;
114
114
  }
115
115
  else {
116
116
  if (isExpandable(finalBaseType) || node.kind === 'type') {
@@ -26,7 +26,7 @@ const { CompilerAssertion } = require('../../base/error');
26
26
  * and edges e(v_r, v_d) such that all v_r, v_d are elements of S (v_rs, v_ds) and with that
27
27
  * all edges are { e(v_rs, v_ds) }.
28
28
  *
29
- * The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
29
+ * The input CSN may contain edges e(v_rs, v_dns) with v_r element of S_i (v_rs) and v_d not element
30
30
  * of S_i (v_dns).
31
31
  *
32
32
  * The aim of this algorithm is to produce Tc's for all requested S_i by 'filling' up the missing
@@ -50,7 +50,7 @@ const { CompilerAssertion } = require('../../base/error');
50
50
  *
51
51
  * If name(v_dns) has no prefix segments, the fallback schema name is prepended instead:
52
52
  * name(v_ds) = name(v_s) + '.' + fallbackschema + name(v_dns);
53
- *
53
+ *
54
54
  * @param {CSN.Model} csn
55
55
  * @param {function} whatsMyServiceName
56
56
  * @param {string[]} requestedServiceNames
@@ -160,7 +160,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
160
160
  // we're no longer in a key def => don't set notNull:true on named types
161
161
  if(isKey)
162
162
  isKey = false;
163
- // in case this was a named type and if the openess does not match the type definition
163
+ // in case this was a named type and if the openness does not match the type definition
164
164
  // expose the type as a new one not changing the original definition.
165
165
  if(elements && !!node['@open'] !== !!typeDef['@open'])
166
166
  fullQualifiedNewTypeName += node['@open'] ? '_open' : '_closed';
@@ -282,6 +282,9 @@ function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
282
282
  // if(xpr?.func && funkyfuncs.includes(xpr?.func))
283
283
  // return xpr;
284
284
  for(let n in xpr) {
285
+ // xpr could be an array with polluted prototype
286
+ if (!Object.hasOwnProperty.call(xpr, n))
287
+ continue;
285
288
  const x = xpr[n];
286
289
  const isAnno = n[0] === '@' && isSimpleAnnoValue(x);
287
290
  if(isAnno)
@@ -66,28 +66,35 @@ function getTransformers(model, options, pathDelimiter = '_') {
66
66
  expandStructsInExpression,
67
67
  };
68
68
 
69
- // Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
70
- // If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default 5000.
71
- // if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
72
- function addDefaultTypeFacets(element, defStrLen5k=true) {
69
+ /**
70
+ * Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
71
+ * If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default internalDefaultLengths[type].
72
+ * if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
73
+ *
74
+ * @param {CSN.Element} element
75
+ * @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
76
+ * */
77
+ function addDefaultTypeFacets(element, internalDefaultLengths = null) {
73
78
  if (!element || !element.type)
74
79
  return;
75
80
 
76
81
  if (element.type === 'cds.String' && element.length === undefined) {
77
- if(options.defaultStringLength) {
82
+ if (options.defaultStringLength) {
78
83
  element.length = options.defaultStringLength;
79
84
  setProp(element, '$default', true);
80
85
  }
81
- else if(defStrLen5k)
82
- element.length = 5000;
86
+ else if (internalDefaultLengths !== null) {
87
+ element.length = internalDefaultLengths[element.type];
88
+ }
83
89
  }
84
90
  if (element.type === 'cds.Binary' && element.length === undefined) {
85
- if(options.defaultBinaryLength) {
91
+ if (options.defaultBinaryLength) {
86
92
  element.length = options.defaultBinaryLength;
87
93
  setProp(element, '$default', true);
88
94
  }
89
- else if(defStrLen5k)
90
- element.length = 5000;
95
+ else if(internalDefaultLengths !== null) {
96
+ element.length = internalDefaultLengths[element.type];
97
+ }
91
98
  }
92
99
  /*
93
100
  if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {
@@ -273,7 +280,22 @@ function getTransformers(model, options, pathDelimiter = '_') {
273
280
  // This has historic reasons. We don't copy doc-comments because copying annotations
274
281
  // is questionable to begin with. Only selected annotations should have been copied,
275
282
  // if at all.
276
- copyAnnotations(elem, flatElem, false);
283
+ // When flattening structured elements for OData don't propagate the odata.Type annotations
284
+ // as these would falsify the flattened elements. Type facets must be aligned with
285
+ // EdmTypeFacetMap defined in edm.js
286
+ const excludes = options.toOdata ?
287
+ {
288
+ '@odata.Type': 1,
289
+ '@odata.Scale': 1,
290
+ '@odata.Precision': 1,
291
+ '@odata.MaxLength': 1,
292
+ '@odata.SRID': 1,
293
+ '@odata.FixedLength': 1,
294
+ '@odata.Collation': 1,
295
+ '@odata.Unicode': 1,
296
+ } : {};
297
+ copyAnnotations(elem, flatElem, false, excludes);
298
+
277
299
  // Copy selected type properties
278
300
  const props = ['key', 'virtual', 'masked', 'viaAll'];
279
301
  // 'localized' is needed for OData
@@ -413,14 +435,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
413
435
  // Copy elements/items and we're finished. No need to look up actual base type,
414
436
  // since it must also be structured and must contain at least as many elements,
415
437
  // if not more (in client style CSN).
416
- if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
438
+ if (typeRef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds'))
417
439
  nodeWithType.elements = cloneCsnDictionary(typeRef.elements, options);
418
- delete nodeWithType.type;
419
- }
420
- if (typeRef.items) {
440
+ else if (typeRef.items)
421
441
  nodeWithType.items = cloneCsnNonDict(typeRef.items, options);
422
- delete nodeWithType.type;
423
- }
442
+
424
443
  return;
425
444
  }
426
445
 
@@ -450,7 +469,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
450
469
  art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
451
470
  let elem = Object.assign({}, artElem);
452
471
  // Transfer xrefs, that are redirected to the projection
453
- // TODO: shall we remove the transfered elements from the original?
472
+ // TODO: shall we remove the transferred elements from the original?
454
473
  // if (artElem._xref) {
455
474
  // setProp(elem, '_xref', artElem._xref.filter(xref => xref.user && xref.user._main && xref.user._main._service == service));
456
475
  // }
@@ -470,7 +489,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
470
489
  // Assemble the projection itself and add it into the model
471
490
  let projection = {
472
491
  'kind': 'entity',
473
- projection: query.SELECT, // it is important that projetion and query refer to the same object!
492
+ projection: query.SELECT, // it is important that projection and query refer to the same object!
474
493
  elements
475
494
  };
476
495
  // copy annotations from art to projection
@@ -749,6 +768,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
749
768
  if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
750
769
  throw new ModelError('Expecting valid return type name: ' + returnTypeName);
751
770
  action.returns = { type: returnTypeName };
771
+ // TODO: What about annotation propagation from return type to `returns`?
752
772
  }
753
773
 
754
774
  // Add parameter if provided
@@ -918,7 +938,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
918
938
 
919
939
  /**
920
940
  * Assigns unconditionally annotation to a node, which means it overwrites already existing annotation assignment.
921
- * Overwritting is when the assignment differs from undefined and null, also when differs from the already set value.
941
+ * Overwriting is when the assignment differs from undefined and null, also when differs from the already set value.
922
942
  * Setting new assignment results false as return value and overwriting - true.
923
943
  *
924
944
  * @param {object} node Assignee
@@ -1114,7 +1134,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
1114
1134
  const lhsArt = lhs._art || lhs.ref && !lhs.$scope && inspectRef(location.concat(i)).art;
1115
1135
  const rhsArt = rhs._art || rhs.ref && !rhs.$scope && inspectRef(location.concat(i+2)).art;
1116
1136
  const lhsIsVal = (lhs.val !== undefined);
1117
- // if ever rhs should be alowed to be a value uncomment this
1137
+ // if ever rhs should be allowed to be a value uncomment this
1118
1138
  const rhsIsVal = (rhs === 'null' /*|| rhs.val !== undefined*/);
1119
1139
 
1120
1140
  // lhs & rhs must be expandable types (structures or managed associations)
@@ -1300,8 +1320,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
1300
1320
  }
1301
1321
 
1302
1322
  function getType(art) {
1303
- const effart = effectiveType(art);
1304
- return Object.keys(effart).length ? effart : art.type;
1323
+ const effArt = effectiveType(art);
1324
+ return Object.keys(effArt).length ? effArt : art.type;
1305
1325
  }
1306
1326
 
1307
1327
  function isExpandable(art) {
@@ -230,7 +230,7 @@ function translateAssocsToJoins(model, inputOptions = {})
230
230
 
231
231
  /*
232
232
  Add an artificial QA for each mixin definition. This QA completes the QAT
233
- datastructure that requires a QA at the rootQat before starting the join generation.
233
+ data-structure that requires a QA at the rootQat before starting the join generation.
234
234
  This QA is marked as 'mixin' which indicates that the paths of the ON condition must
235
235
  not receive the usual source and target table alias (which is used for generic associations)
236
236
  but instead just use the rootQA of the individual ON condition paths. These paths are
@@ -684,7 +684,7 @@ function translateAssocsToJoins(model, inputOptions = {})
684
684
  //env.assocStack.includes(fwdAssoc) => recursion
685
685
  if(env.assocStack.length === 2) {
686
686
  // reuse (ugly) error message from forHana
687
- error(null, env.assocStack[0].location,
687
+ error(null, [env.assocStack[0].location,env.assocStack[0]],
688
688
  { name: '$self', id: '$self' },
689
689
  'An association that uses $(NAME) in its ON-condition can\'t be compared to $(ID)');
690
690
  // don't check these paths again
@@ -706,6 +706,7 @@ function translateAssocsToJoins(model, inputOptions = {})
706
706
  }
707
707
 
708
708
  function cloneOnCondExprTree(expr) {
709
+ // TODO: This function is not covered by an tests, only cloneOnCondExprStream is.
709
710
  // keep parentheses intact
710
711
  if(Array.isArray(expr))
711
712
  return expr.map(cloneOnCondition);
@@ -1126,7 +1127,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1126
1127
  for(let fkn in element.foreignKeys)
1127
1128
  {
1128
1129
  let fk = element.foreignKeys[fkn];
1129
- // once a fk is to be followed, treat all sub patsh as srcSide, this will add fk.name.id only
1130
+ // once a fk is to be followed, treat all sub-paths as srcSide, this will add fk.name.id only
1130
1131
  if(srcSide)
1131
1132
  paths = paths.concat(flattenElement(fk.targetElement._artifact, true, fk.name.id, fk.targetElement.path.map(ps => ps.id).join(pathDelimiter)));
1132
1133
  else
@@ -1199,7 +1200,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1199
1200
  /*
1200
1201
  Munch path steps and append them to a path string until an
1201
1202
  assoc step is found. The assoc path step is also appended
1202
- to the path string. If no assoc path step has occured, all
1203
+ to the path string. If no assoc path step has occurred, all
1203
1204
  path steps are added to the path string and tail is empty.
1204
1205
 
1205
1206
  Return assocPathStep, the remaining tail path and the path string
@@ -1219,7 +1220,7 @@ function translateAssocsToJoins(model, inputOptions = {})
1219
1220
 
1220
1221
  /*
1221
1222
  Substitute the n first path steps of a given path against a FK alias name.
1222
- Resolve a foreign key of a managaged association by following the n first
1223
+ Resolve a foreign key of a managed association by following the n first
1223
1224
  path steps. Longest path matches:
1224
1225
  Example: fk tuple { a.b, a.b.c, a.b.e },
1225
1226
  path: a.b.c.d.e.f: FK a.b.c is found, even if FK a.b is one level higher in the prefix tree.
@@ -1866,7 +1867,7 @@ function walkPath(node, env)
1866
1867
  /*
1867
1868
  NOTE: As long as association path steps are not allowed in filters,
1868
1869
  it is not required to walk over filter expressions.
1869
- Simple filter paths are rewritten inin createJoinTree (first filter)
1870
+ Simple filter paths are rewritten in createJoinTree (first filter)
1870
1871
  and createJoinQA (subsequent one that belong to the ON condition).
1871
1872
 
1872
1873
  If the filter becomes JOIN relevant, default FILTERS (part of the
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "root": true,
3
3
  "plugins": ["sonarjs", "jsdoc"],
4
- "extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
4
+ "extends": ["plugin:jsdoc/recommended", "../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
5
5
  "rules": {
6
6
  "prefer-const": "error",
7
7
  "quotes": ["error", "single", "avoid-escape"],
@@ -27,7 +27,9 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
27
27
  }
28
28
  else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
29
29
  forEachMemberRecursively(artifact, (element) => {
30
- if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
30
+ // Calculated elements, but simple references are ignored for on-read.
31
+ // casts() are also computed. In CSN, they appear next to a `.ref`.
32
+ if (element.value && (!element.value.ref || element.value.cast || element.value.stored))
31
33
  setAnnotationIfNotDefined(element, '@Core.Computed', true);
32
34
  }, path);
33
35
  }
@@ -95,7 +97,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
95
97
  return getAncestor(base.SELECT.elements[name], name, base.SELECT);
96
98
  }
97
99
  else if (base.ref) {
98
- let artifact = artifactRef(base);
100
+ let artifact = artifactRef.from(base);
99
101
  if (artifact.target)
100
102
  artifact = artifactRef(artifact.target);
101
103
  return artifact.elements[name];
@@ -145,7 +147,7 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
145
147
  }
146
148
 
147
149
  /**
148
- * Return whether the given columns element needs to be marked with @Core.Computed.
150
+ * Returns true, if the given columns element needs to be annotated with @Core.Computed.
149
151
  *
150
152
  * @param {CSN.Column} column
151
153
  * @returns {boolean}
@@ -153,9 +155,9 @@ function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
153
155
  function needsCoreComputed( column ) {
154
156
  return column &&
155
157
  (
156
- column.xpr || column.list || column.func || column.val !== undefined || column.param ||
158
+ column.xpr || column.list || column.func || column.val !== undefined || column['#'] !== undefined || column.param ||
157
159
  column.SELECT || column.SET ||
158
- column.ref && [ '$at', '$valid', '$now', '$user', '$session' ].includes(column.ref[0])
160
+ column.ref && [ '$at', '$valid', '$now', '$user', '$session', '$parameters' ].includes(column.ref[0])
159
161
  );
160
162
  }
161
163