@sap/cds-compiler 3.6.2 → 3.8.0

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 (89) hide show
  1. package/CHANGELOG.md +109 -1
  2. package/README.md +3 -0
  3. package/bin/cdsc.js +12 -5
  4. package/doc/CHANGELOG_ARCHIVE.md +6 -6
  5. package/doc/CHANGELOG_BETA.md +35 -2
  6. package/doc/CHANGELOG_DEPRECATED.md +2 -2
  7. package/doc/DeprecatedOptions_v2.md +1 -1
  8. package/doc/NameResolution.md +1 -1
  9. package/lib/api/main.js +63 -23
  10. package/lib/api/options.js +1 -0
  11. package/lib/api/validate.js +5 -0
  12. package/lib/base/dictionaries.js +15 -3
  13. package/lib/base/keywords.js +2 -0
  14. package/lib/base/message-registry.js +120 -34
  15. package/lib/base/messages.js +51 -27
  16. package/lib/base/model.js +4 -2
  17. package/lib/base/shuffle.js +2 -1
  18. package/lib/checks/arrayOfs.js +1 -1
  19. package/lib/checks/defaultValues.js +1 -1
  20. package/lib/checks/elements.js +29 -1
  21. package/lib/checks/{emptyOrOnlyVirtual.js → hasPersistedElements.js} +10 -6
  22. package/lib/checks/invalidTarget.js +1 -1
  23. package/lib/checks/nonexpandableStructured.js +1 -1
  24. package/lib/checks/onConditions.js +15 -9
  25. package/lib/checks/sql-snippets.js +2 -2
  26. package/lib/checks/types.js +5 -1
  27. package/lib/checks/validator.js +7 -3
  28. package/lib/compiler/assert-consistency.js +42 -26
  29. package/lib/compiler/base.js +50 -4
  30. package/lib/compiler/builtins.js +17 -8
  31. package/lib/compiler/checks.js +241 -246
  32. package/lib/compiler/define.js +113 -146
  33. package/lib/compiler/extend.js +889 -383
  34. package/lib/compiler/finalize-parse-cdl.js +5 -58
  35. package/lib/compiler/index.js +1 -1
  36. package/lib/compiler/kick-start.js +7 -8
  37. package/lib/compiler/populate.js +297 -293
  38. package/lib/compiler/propagator.js +27 -18
  39. package/lib/compiler/resolve.js +146 -463
  40. package/lib/compiler/shared.js +36 -79
  41. package/lib/compiler/tweak-assocs.js +30 -28
  42. package/lib/compiler/utils.js +31 -5
  43. package/lib/edm/annotations/genericTranslation.js +131 -59
  44. package/lib/edm/annotations/preprocessAnnotations.js +3 -0
  45. package/lib/edm/csn2edm.js +22 -5
  46. package/lib/edm/edm.js +6 -4
  47. package/lib/edm/edmAnnoPreprocessor.js +1 -0
  48. package/lib/edm/edmPreprocessor.js +42 -26
  49. package/lib/gen/Dictionary.json +38 -2
  50. package/lib/gen/language.checksum +1 -1
  51. package/lib/gen/language.interp +3 -1
  52. package/lib/gen/languageLexer.js +1 -1
  53. package/lib/gen/languageParser.js +4828 -4472
  54. package/lib/inspect/inspectPropagation.js +20 -34
  55. package/lib/json/from-csn.js +140 -44
  56. package/lib/json/to-csn.js +114 -122
  57. package/lib/language/errorStrategy.js +2 -0
  58. package/lib/language/genericAntlrParser.js +156 -36
  59. package/lib/language/language.g4 +100 -58
  60. package/lib/language/textUtils.js +13 -0
  61. package/lib/main.d.ts +43 -3
  62. package/lib/main.js +4 -2
  63. package/lib/model/csnRefs.js +15 -3
  64. package/lib/model/csnUtils.js +12 -74
  65. package/lib/model/revealInternalProperties.js +4 -2
  66. package/lib/modelCompare/compare.js +2 -1
  67. package/lib/optionProcessor.js +3 -0
  68. package/lib/render/manageConstraints.js +5 -2
  69. package/lib/render/toCdl.js +216 -104
  70. package/lib/render/toHdbcds.js +2 -9
  71. package/lib/render/toRename.js +14 -51
  72. package/lib/render/toSql.js +4 -3
  73. package/lib/render/utils/common.js +9 -5
  74. package/lib/transform/braceExpression.js +6 -0
  75. package/lib/transform/db/assertUnique.js +2 -1
  76. package/lib/transform/db/expansion.js +2 -0
  77. package/lib/transform/db/flattening.js +37 -36
  78. package/lib/transform/db/rewriteCalculatedElements.js +600 -0
  79. package/lib/transform/db/transformExists.js +4 -0
  80. package/lib/transform/db/views.js +40 -37
  81. package/lib/transform/forOdataNew.js +20 -15
  82. package/lib/transform/forRelationalDB.js +58 -41
  83. package/lib/transform/odata/typesExposure.js +50 -15
  84. package/lib/transform/parseExpr.js +16 -8
  85. package/lib/transform/transformUtilsNew.js +42 -14
  86. package/lib/transform/translateAssocsToJoins.js +60 -37
  87. package/lib/transform/universalCsn/coreComputed.js +15 -7
  88. package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
  89. package/package.json +2 -1
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { isPersistedOnDatabase } = require('../model/csnUtils.js');
4
3
  // Only to be used with validator.js - a correct this value needs to be provided!
5
4
  // not relevant for odata - entities need to be checked at the end of the transformation
5
+
6
+ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
7
+
6
8
  /**
7
9
  * Ensure that empty/only virtual entities do not reach the db.
8
10
  *
@@ -11,22 +13,24 @@ const { isPersistedOnDatabase } = require('../model/csnUtils.js');
11
13
  * @param {string} prop Property being looped over
12
14
  * @param {CSN.Path} path Path to the artifact
13
15
  */
14
- function validateEmptyOrOnlyVirtual( artifact, artifactName, prop, path ) {
16
+ function validateHasPersistedElements( artifact, artifactName, prop, path ) {
15
17
  if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
16
18
  if (!artifact.elements || !hasRealElements(artifact.elements))
17
- this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
19
+ // TODO: Maybe check if there are only calc elements and adapt the message?
20
+ this.error('def-missing-element', path, { '#': ( artifact.query || artifact.projection ) ? 'view' : 'std' });
18
21
  }
19
22
  }
20
23
 
21
24
  /**
22
- * Check if the provided elements contain elements that will be created on the db.
25
+ * Check if the provided elements contain elements that will be created on the database.
26
+ * This includes virtual and calculated elements.
23
27
  *
24
28
  * @param {CSN.Elements} elements Elements to look through
25
29
  * @returns {boolean} True if something would be created on the db from these elements.
26
30
  */
27
31
  function hasRealElements( elements ) {
28
32
  for (const element of Object.values(elements)) {
29
- if (!element.virtual) {
33
+ if (!element.virtual && !element.value) {
30
34
  if (element.elements) {
31
35
  if (hasRealElements(element.elements))
32
36
  return true;
@@ -41,4 +45,4 @@ function hasRealElements( elements ) {
41
45
  }
42
46
 
43
47
 
44
- module.exports = validateEmptyOrOnlyVirtual;
48
+ module.exports = validateHasPersistedElements;
@@ -55,7 +55,7 @@ function invalidTarget( member ) {
55
55
 
56
56
  if (
57
57
  this.artifact &&
58
- (this.artifact.kind === 'entity' || this.artifact.query) &&
58
+ this.artifact.kind === 'entity' &&
59
59
  member.$path[2] === 'elements'
60
60
  )
61
61
  checkForInvalidTarget(member);
@@ -21,7 +21,7 @@ function nonexpandableStructuredInExpression( parent, name, expression ) {
21
21
  if (_art) {
22
22
  _art = resolveArtifactType.call(this, _art);
23
23
  // Paths of an expression may end on a structured element only if both operands in the expression end on a structured element
24
- if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && $scope !== '$self') { // TODO: Use $self to navigate to struct
24
+ if ((_art?.elements || _art?.keys && (i === 0 || expression[i - 1] !== 'exists')) && !validStructuredElement && ($scope !== '$self' || $scope === '$self' && ref.length > 1)) { // TODO: Use $self to navigate to struct
25
25
  this.error('ref-unexpected-structured',
26
26
  name === 'on' ? [ ...parent.$path, name, i ] : expression[i].$path,
27
27
  { '#': 'std', elemref: { ref } } );
@@ -83,16 +83,18 @@ function validateOnCondition( member, memberName, property, path ) {
83
83
  if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
84
84
  if (_links[j].art.on) {
85
85
  // It's an unmanaged association - traversal is always forbidden
86
- this.error(null, csnPath, { id, elemref }, 'ON-conditions can\'t follow unmanaged associations, step $(ID) of path $(ELEMREF)');
86
+ this.error('ref-unexpected-navigation', csnPath, { '#': 'unmanaged', id, elemref });
87
87
  }
88
88
  else {
89
89
  // It's a managed association - access of the foreign keys is allowed
90
90
  const nextRef = ref[j + 1].id || ref[j + 1];
91
- if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
92
- this.error(null, csnPath, { id, elemref }, 'ON-conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(ELEMREF)');
91
+ if (!_links[j].art.keys.some(r => r.ref[0] === nextRef)) {
92
+ this.error('ref-unexpected-navigation', csnPath, {
93
+ '#': 'std', id, elemref, name: nextRef,
94
+ });
95
+ }
93
96
  }
94
97
  }
95
-
96
98
  if (_links[j].art.virtual)
97
99
  this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');
98
100
 
@@ -113,11 +115,11 @@ function validateOnCondition( member, memberName, property, path ) {
113
115
  // 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
114
116
 
115
117
  // If this path ends structured or on an association, perform the check:
116
- if ((type.target) &&
117
- !( /* 1) */ (type.target && type.keys) && validStructuredElement ||
118
- /* 2) */ (type.target && validDollarSelf)) &&
119
- !type.virtual) {
120
- // Do nothing - handled by lib/checks/nonexpandableStructured.js
118
+ if (
119
+ ((type.target && type.keys || type.elements) && validStructuredElement ||
120
+ (type.target && validDollarSelf)) && !type.virtual
121
+ ) {
122
+ // Do nothing - handled by lib/checks/nonexpandableStructured.js
121
123
  }
122
124
  else if (type.items && !type.virtual) {
123
125
  this.error(null, onPath, { elemref: { ref } },
@@ -127,6 +129,10 @@ function validateOnCondition( member, memberName, property, path ) {
127
129
  this.error(null, onPath, { elemref: { ref } },
128
130
  'Virtual elements can\'t be used in ON-conditions, path $(ELEMREF)');
129
131
  }
132
+ else if (type.on) {
133
+ // Path leaf is an unmanaged association, can't use an unmanaged assoc as operand
134
+ this.error('ref-unexpected-navigation', onPath, { '#': 'unmanagedleaf', id: logReady(ref[ref.length - 1]), elemref: { ref } });
135
+ }
130
136
  }
131
137
  }
132
138
  }
@@ -18,7 +18,7 @@ function checkSqlAnnotationOnElement( member, memberName, prop, path ) {
18
18
  this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, 'Annotation $(ANNO) can\'t be used on elements' );
19
19
 
20
20
  if (member['@sql.append']) {
21
- if (this.artifact.query)
21
+ if (this.artifact.query || this.artifact.projection)
22
22
  this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );
23
23
  else if (this.csnUtils.isStructured(member))
24
24
  this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on structured elements' );
@@ -57,7 +57,7 @@ function checkSqlAnnotationOnArtifact( artifact, artifactName ) {
57
57
  this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, 'Annotation $(NAME) can\'t be used on an artifact of kind $(KIND)' );
58
58
  }
59
59
  else if (artifact['@sql.prepend']) {
60
- if (artifact.query)
60
+ if (artifact.query || artifact.projection)
61
61
  this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );
62
62
  else
63
63
  checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
@@ -51,7 +51,11 @@ function checkElementTypeDefinitionHasType( member, memberName, prop, path ) {
51
51
  // Computed elements, e.g. "1+1 as foo" in a view don't have a valid type and
52
52
  // are skipped here. References to such columns are checked further below.
53
53
  const parent = this.csn.definitions[path[1]];
54
- if (!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
54
+
55
+ // should only happen with csn input, not in cdl
56
+ // calculated elements may not have a .type (requires beta flag)
57
+ if (!member.value &&
58
+ !parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {
55
59
  errorAboutMissingType(this.error, path, memberName, true);
56
60
  return;
57
61
  }
@@ -12,7 +12,7 @@ const { validateSelectItems } = require('./selectItems');
12
12
  const { rejectParamDefaultsInHanaCds, warnAboutDefaultOnAssociationForHanaCds } = require('./defaultValues');
13
13
  const validateCdsPersistenceAnnotation = require('./cdsPersistence');
14
14
  const checkUsedTypesForAnonymousAspectComposition = require('./managedInType');
15
- const checkForEmptyOrOnlyVirtual = require('./emptyOrOnlyVirtual');
15
+ const validateHasPersistedElements = require('./hasPersistedElements');
16
16
  const checkForHanaTypes = require('./checkForTypes');
17
17
  const checkForParams = require('./parameters');
18
18
  // forOdata
@@ -30,7 +30,8 @@ const {
30
30
  checkTypeIsScalar, checkDecimalScale,
31
31
  } = require('./types');
32
32
  const {
33
- checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkRecursiveTypeUsage,
33
+ checkPrimaryKey, checkVirtualElement, checkManagedAssoc,
34
+ checkRecursiveTypeUsage, rejectAnnotationsOnCalcElement,
34
35
  } = require('./elements');
35
36
  const checkForInvalidTarget = require('./invalidTarget');
36
37
  const { validateAssociationsInItems } = require('./arrayOfs');
@@ -54,6 +55,8 @@ const forRelationalDBMemberValidators
54
55
  warnAboutDefaultOnAssociationForHanaCds,
55
56
  // sql.prepend/append
56
57
  checkSqlAnnotationOnElement,
58
+ // no temporal annotations on calc elements
59
+ rejectAnnotationsOnCalcElement,
57
60
  ];
58
61
 
59
62
  const forRelationalDBArtifactValidators
@@ -61,7 +64,7 @@ const forRelationalDBArtifactValidators
61
64
  // @cds.persistence has no impact on odata
62
65
  validateCdsPersistenceAnnotation,
63
66
  // virtual items are not persisted on the db
64
- checkForEmptyOrOnlyVirtual,
67
+ validateHasPersistedElements,
65
68
  // sql.prepend/append
66
69
  checkSqlAnnotationOnArtifact,
67
70
  ];
@@ -254,6 +257,7 @@ function forOdata( csn, that ) {
254
257
  }
255
258
  }
256
259
  ),
260
+ // eslint-disable-next-line sonarjs/no-empty-collection
257
261
  forOdataQueryValidators.concat(commonQueryValidators),
258
262
  {
259
263
  skipArtifact: this.isExternalServiceMember,
@@ -72,7 +72,7 @@ const { locationString, hasErrors } = require('../base/messages');
72
72
  // Properties that can appear where a type can have type arguments.
73
73
  const typeProperties = [
74
74
  'type', '$typeArgs', 'length', 'precision', 'scale', 'srid',
75
- '_effectiveType',
75
+ '_effectiveType', '$effectiveSeqNo',
76
76
  ];
77
77
 
78
78
  class InternalConsistencyError extends Error {
@@ -191,8 +191,8 @@ function assertConsistency( model, stage ) {
191
191
  test: isDictionary( definition ),
192
192
  requires: [ 'kind', 'name' ],
193
193
  optional: [
194
- 'elements', '$autoElement', '$uncheckedElements',
195
- '$requireElementAccess', '_effectiveType', '_deps',
194
+ 'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
195
+ '$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
196
196
  ],
197
197
  schema: {
198
198
  kind: { test: isString, enum: [ 'builtin' ] },
@@ -250,18 +250,17 @@ function assertConsistency( model, stage ) {
250
250
  schema: { args: { inherits: 'query', test: isArray( query ) } },
251
251
  requires: [ 'op', 'location', 'args' ],
252
252
  optional: [
253
- 'quantifier', 'orderBy', 'limit', '_leadingQuery',
254
- 'name', '$parens', 'kind', '_parent', '_main', '_effectiveType', // in FROM
253
+ 'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
254
+ '_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM
255
255
  ],
256
256
  },
257
257
  select: { // sub query
258
258
  requires: [ 'op', 'location', 'from' ],
259
259
  optional: [
260
260
  'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
261
- 'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit',
262
- '_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
261
+ 'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '_origin', '_block',
262
+ '_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
263
263
  '$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
264
- '_extension', // for unapplied extensions
265
264
  ],
266
265
  },
267
266
  none: { optional: () => true }, // parse error
@@ -285,7 +284,7 @@ function assertConsistency( model, stage ) {
285
284
  'elements', '_origin', '_joinParent', '$joinArgsIndex', '$syntax',
286
285
  '$parens', '_status', // TODO: only in from
287
286
  'scope', '_artifact', '$inferred', 'kind',
288
- '_effectiveType', // TODO:check this
287
+ '_effectiveType', '$effectiveSeqNo', // TODO:check this
289
288
  '$duplicates', // In JOIN if both sides are the same.
290
289
  ],
291
290
  },
@@ -293,9 +292,10 @@ function assertConsistency( model, stage ) {
293
292
  requires: [ 'query', 'location' ],
294
293
  optional: [
295
294
  '$parens',
296
- 'kind', 'name', '_block', '_parent', '_main',
297
- '_effectiveType', 'elements', '_origin', '_joinParent', '$joinArgsIndex',
295
+ 'kind', 'name', '_block', '_parent', '_main', 'elements',
296
+ '_effectiveType', '$effectiveSeqNo', '_origin', '_joinParent', '$joinArgsIndex',
298
297
  '$duplicates', // duplicate query in FROM clause
298
+ '$inferred', // table alias with $inferred: '$internal'
299
299
  ],
300
300
  },
301
301
  none: { optional: () => true }, // parse error
@@ -340,7 +340,7 @@ function assertConsistency( model, stage ) {
340
340
  optional: [
341
341
  'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
342
342
  'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
343
- '_effectiveType', // by propagation
343
+ '_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
344
344
  ],
345
345
  },
346
346
  target: {
@@ -373,7 +373,7 @@ function assertConsistency( model, stage ) {
373
373
  // required to be set by Core Compiler even with parse errors
374
374
  test: isString,
375
375
  enum: [
376
- 'context', 'service', 'entity', 'type', 'aspect', 'const', 'annotation',
376
+ 'context', 'service', 'entity', 'type', 'aspect', 'annotation',
377
377
  'element', 'enum', 'action', 'function', 'param', 'key', 'event',
378
378
  'annotate', 'extend', '$column',
379
379
  'select', '$join', 'mixin',
@@ -389,14 +389,18 @@ function assertConsistency( model, stage ) {
389
389
  kind: [ 'entity', 'view', 'type', 'aspect' ],
390
390
  test: isString, // CSN parser should check for 'entity', 'view', 'projection'
391
391
  },
392
+ $tokenTexts: {
393
+ parser: true,
394
+ test: isString,
395
+ },
392
396
  value: {
393
397
  optional: [
394
398
  'location', '$inferred', 'sort', 'nulls',
395
399
  'param', 'scope', // for dynamic parameter '?'
396
- // through cast() with enum through CSN->XSN
397
- // TODO: re-check #9225, this should be directly in the query element,
398
- // not inside value, no `enum` inside `cast`!
399
- 'elements', 'items', 'enum', '$expand', 'target',
400
+ // A2J wrongly propagates the following into a CAST of the CSN passed to compileX:
401
+ 'elements', 'items', 'enum',
402
+ // CSN parser may let these properties slip through to XSN, even if input is invalid.
403
+ 'args', 'op', 'func', 'suffix',
400
404
  ],
401
405
 
402
406
  kind: true,
@@ -446,6 +450,8 @@ function assertConsistency( model, stage ) {
446
450
  requires: [ 'location' ],
447
451
  optional: [
448
452
  'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
453
+ // expressions as annotation values
454
+ '$tokenTexts', 'op', 'args', 'func',
449
455
  ],
450
456
  // TODO: restrict path to #simplePath
451
457
  },
@@ -473,7 +479,13 @@ function assertConsistency( model, stage ) {
473
479
  '@': {
474
480
  kind: true,
475
481
  inherits: 'value',
476
- optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
482
+ optional: [
483
+ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
484
+ // annotation values
485
+ '$tokenTexts',
486
+ // CSN parser may let these properties slip through to XSN, even if input is invalid.
487
+ 'args', 'op', 'func', 'suffix',
488
+ ],
477
489
  // TODO: name requires if not in parser?
478
490
  },
479
491
  $priority: { test: isOneOf([ undefined, false, 'extend', 'annotate' ]) },
@@ -497,7 +509,7 @@ function assertConsistency( model, stage ) {
497
509
  action: { test: isString },
498
510
  param: { test: TODO },
499
511
  alias: { test: isString },
500
- expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
512
+ expectedKind: { kind: [ 'extend' ], test: locationVal( isString ) },
501
513
  virtual: { kind: true, test: locationVal() },
502
514
  key: { kind: true, test: locationVal(), also: [ null, undefined ] },
503
515
  masked: { kind: true, test: locationVal() },
@@ -515,9 +527,9 @@ function assertConsistency( model, stage ) {
515
527
  optional: [
516
528
  'enum',
517
529
  'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
518
- '_outer', '_effectiveType', 'notNull', '_parent',
530
+ '_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
519
531
  '_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
520
- '$syntax',
532
+ '$syntax', '_extensions',
521
533
  '_status', '_redirected',
522
534
  ...typeProperties,
523
535
  ],
@@ -550,6 +562,7 @@ function assertConsistency( model, stage ) {
550
562
  _artifact: { test: TODO },
551
563
  _navigation: { test: TODO },
552
564
  _effectiveType: { kind: true, test: TODO },
565
+ $effectiveSeqNo: { kind: true, test: isNumber },
553
566
  _joinParent: { test: TODO },
554
567
  $joinArgsIndex: { test: isNumber },
555
568
  _outer: { test: TODO }, // for returns/items
@@ -565,24 +578,25 @@ function assertConsistency( model, stage ) {
565
578
  '$tableAliases', '$inlines',
566
579
  ],
567
580
  optional: [
568
- '_effectiveType', '$parens',
581
+ '_effectiveType', '$effectiveSeqNo', '$parens',
569
582
  '_deps', '$expand',
570
583
  // query specific
571
584
  'where', 'columns', 'mixin', 'quantifier', 'offset',
572
585
  'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
573
- 'limit', '_status',
574
- '_extension', // for unapplied extensions
586
+ 'limit', '_status', '_origin', '_effectiveType', '$effectiveSeqNo',
575
587
  ],
576
588
  },
577
589
  _leadingQuery: { kind: true, test: TODO },
578
590
  $replacement: { kind: true, test: TODO }, // for smart * in queries
579
- _origin: { kind: [ 'entity' ], test: TODO },
591
+ _origin: { kind: true, test: TODO },
592
+ _calcOrigin: { kind: true, test: TODO },
580
593
  _pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
581
- _from: { kind: true, test: TODO }, // all table refs necessary to compute elements
594
+ _from: { kind: true, test: TODO }, // TODO: not necessary anymore ?
582
595
  // array of $tableAlias (or includes) for explicit and implicit redirection:
583
596
  _redirected: { kind: true, test: TODO },
584
597
  // ...array of table aliases for targets from orig to new
585
598
  _$next: { kind: true, test: TODO }, // next lexical search environment for values
599
+ _extensions: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
586
600
  _extend: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
587
601
  _annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
588
602
  _extension: { kind: true, test: TODO }, // on artifact to its "super extend/annotate" statement
@@ -612,6 +626,7 @@ function assertConsistency( model, stage ) {
612
626
  parser: true,
613
627
  kind: true,
614
628
  test: isOneOf([
629
+ '', // constructed “super annotate” statement
615
630
  // Uppercase values are used in logic, lowercase value are "just for us", i.e.
616
631
  // debugging or to add properties such as $generated in Universal CSN.
617
632
  // However, that is no longer true. For example, `autoexposed` is used in populate.js
@@ -623,6 +638,7 @@ function assertConsistency( model, stage ) {
623
638
 
624
639
  '$autoElement', // for magicVars: $user is automatically changed to $user.id
625
640
  '$generated', // compiler generated annotations, e.g. @Core.Computed
641
+ '$internal', // compiler internal; must not reach CSN output
626
642
  '*', // inferred from query wildcard
627
643
  'as', // query alias name
628
644
  'aspect-composition',
@@ -29,28 +29,31 @@ const kindProperties = {
29
29
  type: { elements: propExists, enum: propExists, include: true },
30
30
  aspect: { elements: propExists, actions: true, include: true },
31
31
  annotation: { elements: propExists, enum: propExists },
32
- enum: { normalized: 'element' },
32
+ enum: { normalized: 'element', dict: 'enum' },
33
33
  element: { elements: propExists, enum: propExists, dict: 'elements' },
34
34
  mixin: { normalized: 'alias' },
35
35
  action: {
36
36
  params: () => false, elements: () => false, enum: () => false, dict: 'actions',
37
37
  }, // no extend params, only annotate
38
38
  function: {
39
- params: () => false, elements: () => false, enum: () => false, normalized: 'action',
39
+ params: () => false,
40
+ elements: () => false,
41
+ enum: () => false,
42
+ normalized: 'action',
43
+ dict: 'actions',
40
44
  }, // no extend params, only annotate
41
45
  key: { normalized: 'element' },
42
46
  param: { elements: () => false, enum: () => false, dict: 'params' },
43
47
  source: { artifacts: true }, // TODO -> $source
44
48
  using: {},
45
49
  extend: {
46
- isExtension: true,
47
50
  noDep: 'special',
48
51
  elements: true, /* only for parse-cdl */
49
52
  actions: true, /* only for parse-cdl */
50
53
  enum: true, /* only for parse-cdl */
51
54
  },
52
55
  annotate: {
53
- isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
56
+ noDep: 'special', elements: true, enum: true, actions: true, params: true,
54
57
  },
55
58
  builtin: {}, // = CURRENT_DATE, TODO: improve
56
59
  $parameters: {}, // $parameters in query entities
@@ -61,7 +64,50 @@ function propExists( prop, parent ) {
61
64
  return (obj.items || obj.targetAspect || obj)[prop];
62
65
  }
63
66
 
67
+ // Return the "old style" name structure with `absolute`, `action`, `param`,
68
+ // `element`. Later we also need to add `select` and `alias`.
69
+ // (Currently not needed, as only used for extend and annotate statements.)
70
+ function getArtifactName( art ) {
71
+ if (!art.name || art.name.absolute)
72
+ return art.name;
73
+ // extend and annotate statement already have "sparse" names → calculate old one
74
+ const link = art.name._artifact;
75
+ const namePath = [];
76
+ while (art._main && !art.name.absolute) { // until we hit an old-style name or the main artifact
77
+ namePath.push( art );
78
+ art = art._parent;
79
+ }
80
+ namePath.reverse();
81
+ const name = { ...art.name };
82
+ for (const np of namePath) {
83
+ const prop = getMemberNameProp( np, np.kind );
84
+ name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
85
+ name.id = np.name.id;
86
+ name.location = np.name.location;
87
+ }
88
+ if (link !== undefined)
89
+ Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
90
+ return name;
91
+ }
92
+
93
+ // TODO: probably store this prop in name
94
+ function getMemberNameProp( elem, kind ) {
95
+ if (kind !== 'annotate' && kind !== 'extend')
96
+ return kindProperties[kind]?.normalized || kind;
97
+ let obj = elem._parent;
98
+ if (obj.params || obj.returns)
99
+ return 'param';
100
+ if (obj.actions)
101
+ return 'action';
102
+ while (obj.items)
103
+ obj = obj.items;
104
+ if (obj.elements || obj.enum)
105
+ return 'element';
106
+ return 'id';
107
+ }
108
+
64
109
  module.exports = {
65
110
  dictKinds,
66
111
  kindProperties,
112
+ getArtifactName,
67
113
  };
@@ -10,6 +10,7 @@
10
10
  const { builtinLocation } = require('../base/location');
11
11
  const { setLink: setProp } = require('./utils');
12
12
 
13
+ // TODO: make type parameters a dict
13
14
  const core = {
14
15
  String: { parameters: [ 'length' ], category: 'string' },
15
16
  LargeString: { category: 'string' },
@@ -199,7 +200,16 @@ const magicVariables = {
199
200
  },
200
201
  };
201
202
 
202
- // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
203
+ // see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
204
+
205
+ const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
206
+ // YYYY - MM - dd
207
+ const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
208
+ // T HH : mm : ss TZD
209
+ // eslint-disable-next-line max-len
210
+ const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
211
+ // YYYY - MM - dd T HH : mm : ss . fraction TZD
212
+ const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
203
213
 
204
214
  /**
205
215
  * Patterns for literal token tests and creation. The value is a map from the
@@ -227,7 +237,7 @@ const quotedLiteralPatterns = {
227
237
  test_variant: 'time',
228
238
  test_fn: (x) => {
229
239
  // Leading `T` allowed in ISO 8601.
230
- const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
240
+ const match = x.match( timeRegEx );
231
241
  return match !== null && checkTime( match[1], match[2], match[3] );
232
242
  },
233
243
  json_type: 'string',
@@ -235,7 +245,7 @@ const quotedLiteralPatterns = {
235
245
  date: {
236
246
  test_variant: 'date',
237
247
  test_fn: (x) => {
238
- const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
248
+ const match = x.match( dateRegEx );
239
249
  return match !== null && checkDate( match[1], match[2], match[3] );
240
250
  },
241
251
  json_type: 'string',
@@ -243,8 +253,7 @@ const quotedLiteralPatterns = {
243
253
  timestamp: {
244
254
  test_variant: 'timestamp',
245
255
  test_fn: (x) => {
246
- // eslint-disable-next-line max-len
247
- const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
256
+ const match = x.match( timestampRegEx );
248
257
  return match !== null && checkDate( match[1], match[2], match[3] ) &&
249
258
  checkTime( match[4], match[5], match[6] );
250
259
  },
@@ -259,7 +268,7 @@ const quotedLiteralPatterns = {
259
268
  },
260
269
  number: {
261
270
  test_variant: 'number',
262
- test_fn: (x => /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i.test( x )),
271
+ test_fn: (x => numberRegEx.test( x )),
263
272
  json_type: 'number',
264
273
  secondary_json_type: 'string',
265
274
  },
@@ -447,11 +456,11 @@ function initBuiltins( model ) {
447
456
  const absolute = prefix + name;
448
457
  // TODO: reconsider whether to set a type to itself - looks wrong
449
458
  const art = {
450
- kind: 'type', builtin: true, name: { absolute }, type: { path: [ { id: absolute } ] },
459
+ kind: 'type', builtin: true, name: { absolute },
451
460
  };
452
- setProp( art.type, '_artifact', art );
453
461
  if (parent)
454
462
  parent._subArtifacts[name] = art;
463
+ setProp( art, '_origin', '' );
455
464
  setProp( art, '_effectiveType', art );
456
465
  setProp( art, '_deps', [] );
457
466
  Object.assign( art, builtins[name] );