@sap/cds-compiler 4.0.0 → 4.1.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 (85) hide show
  1. package/CHANGELOG.md +115 -5
  2. package/bin/cdsc.js +12 -12
  3. package/doc/CHANGELOG_BETA.md +11 -0
  4. package/lib/api/main.js +60 -12
  5. package/lib/api/validate.js +1 -1
  6. package/lib/base/location.js +6 -7
  7. package/lib/base/message-registry.js +84 -38
  8. package/lib/base/messages.js +11 -10
  9. package/lib/base/model.js +6 -2
  10. package/lib/checks/defaultValues.js +6 -6
  11. package/lib/checks/foreignKeys.js +0 -5
  12. package/lib/checks/onConditions.js +17 -12
  13. package/lib/checks/queryNoDbArtifacts.js +132 -72
  14. package/lib/checks/sql-snippets.js +15 -4
  15. package/lib/checks/types.js +3 -3
  16. package/lib/checks/utils.js +1 -1
  17. package/lib/compiler/assert-consistency.js +44 -16
  18. package/lib/compiler/base.js +1 -0
  19. package/lib/compiler/builtins.js +7 -8
  20. package/lib/compiler/checks.js +274 -197
  21. package/lib/compiler/classes.js +62 -0
  22. package/lib/compiler/cycle-detector.js +3 -3
  23. package/lib/compiler/define.js +63 -50
  24. package/lib/compiler/extend.js +38 -20
  25. package/lib/compiler/finalize-parse-cdl.js +2 -1
  26. package/lib/compiler/generate.js +0 -8
  27. package/lib/compiler/index.js +9 -7
  28. package/lib/compiler/kick-start.js +2 -0
  29. package/lib/compiler/populate.js +139 -110
  30. package/lib/compiler/propagator.js +4 -3
  31. package/lib/compiler/resolve.js +157 -126
  32. package/lib/compiler/shared.js +706 -404
  33. package/lib/compiler/tweak-assocs.js +21 -10
  34. package/lib/compiler/utils.js +228 -36
  35. package/lib/edm/annotations/genericTranslation.js +30 -2
  36. package/lib/edm/edm.js +4 -1
  37. package/lib/edm/edmPreprocessor.js +12 -5
  38. package/lib/edm/edmUtils.js +2 -4
  39. package/lib/gen/Dictionary.json +34 -10
  40. package/lib/gen/language.checksum +1 -1
  41. package/lib/gen/language.interp +1 -1
  42. package/lib/gen/languageParser.js +3987 -3963
  43. package/lib/json/from-csn.js +43 -47
  44. package/lib/json/to-csn.js +11 -11
  45. package/lib/language/antlrParser.js +2 -1
  46. package/lib/language/genericAntlrParser.js +52 -43
  47. package/lib/language/language.g4 +59 -59
  48. package/lib/language/multiLineStringParser.js +2 -0
  49. package/lib/main.d.ts +5 -0
  50. package/lib/model/csnRefs.js +37 -19
  51. package/lib/model/csnUtils.js +20 -16
  52. package/lib/model/revealInternalProperties.js +29 -21
  53. package/lib/model/sortViews.js +4 -2
  54. package/lib/modelCompare/compare.js +112 -39
  55. package/lib/modelCompare/utils/filter.js +54 -24
  56. package/lib/optionProcessor.js +6 -6
  57. package/lib/render/manageConstraints.js +20 -17
  58. package/lib/render/toCdl.js +34 -20
  59. package/lib/render/toHdbcds.js +2 -2
  60. package/lib/render/toRename.js +4 -9
  61. package/lib/render/toSql.js +77 -26
  62. package/lib/render/utils/common.js +3 -3
  63. package/lib/render/utils/unique.js +52 -0
  64. package/lib/transform/db/applyTransformations.js +61 -20
  65. package/lib/transform/db/assertUnique.js +7 -8
  66. package/lib/transform/db/associations.js +2 -2
  67. package/lib/transform/db/cdsPersistence.js +8 -8
  68. package/lib/transform/db/expansion.js +17 -21
  69. package/lib/transform/db/flattening.js +23 -23
  70. package/lib/transform/db/rewriteCalculatedElements.js +20 -14
  71. package/lib/transform/db/temporal.js +1 -1
  72. package/lib/transform/db/transformExists.js +8 -7
  73. package/lib/transform/db/views.js +73 -33
  74. package/lib/transform/draft/db.js +11 -9
  75. package/lib/transform/draft/odata.js +1 -1
  76. package/lib/transform/{forOdataNew.js → forOdata.js} +56 -42
  77. package/lib/transform/forRelationalDB.js +69 -75
  78. package/lib/transform/localized.js +6 -5
  79. package/lib/transform/odata/toFinalBaseType.js +3 -3
  80. package/lib/transform/{transformUtilsNew.js → transformUtils.js} +4 -101
  81. package/lib/transform/translateAssocsToJoins.js +14 -28
  82. package/package.json +1 -1
  83. package/share/messages/check-proper-type-of.md +1 -1
  84. package/share/messages/{check-proper-type.md → def-missing-type.md} +3 -5
  85. package/share/messages/message-explanations.json +1 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- /**
3
+ /*
4
4
  * Module for general (partial) CSN looper functions, respecting dictionaries and allowing
5
5
  * to pass custom callbacks for certain properties like "ref".
6
6
  *
@@ -10,6 +10,8 @@
10
10
  *
11
11
  * @module lib/transform/db/applyTransformations
12
12
  */
13
+
14
+
13
15
  const { setProp } = require('../../base/model');
14
16
 
15
17
 
@@ -18,11 +20,17 @@ const { setProp } = require('../../base/model');
18
20
  * @param {string} prop The property of parent to start at
19
21
  * @param {object} customTransformers Map of prop to transform and function to apply
20
22
  * @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
21
- * @param {applyTransformationsOptions} [options={}]
23
+ * @param {applyTransformationsOptions} [_options={}]
22
24
  * @param {CSN.Path} path Path to parent
23
25
  * @returns {object} parent with transformations applied
24
26
  */
25
- function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, options, path = [] ) {
27
+ function applyTransformationsInternal( parent, prop, customTransformers, artifactTransformers, _options, path = [] ) {
28
+ const options = { ..._options };
29
+ if (!options.skipStandard)
30
+ options.skipStandard = { $tableConstraints: true };
31
+ else if (options.skipStandard.$tableConstraints === undefined)
32
+ options.skipStandard = { ...options.skipStandard, ...{ $tableConstraints: true } };
33
+
26
34
  const transformers = {
27
35
  elements: dictionary,
28
36
  definitions: dictionary,
@@ -43,12 +51,13 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
43
51
  standard( parent, name, parent[name] );
44
52
  }
45
53
  else {
46
- standard(parent, prop, parent[prop]);
54
+ standard( parent, prop, parent[prop] );
47
55
  }
48
56
  return parent;
49
57
 
50
58
  /**
51
- * Default transformer for things that are not dictionaries, like "type" or "keys".
59
+ * Default transformer for things that are not dictionaries or dictionary entries,
60
+ * such as "type" or "keys".
52
61
  * The customTransformers are applied here (and only here).
53
62
  *
54
63
  * @param {object | Array} _parent the thing that has _prop
@@ -59,7 +68,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
59
68
  if (!node || typeof node !== 'object' ||
60
69
  !{}.propertyIsEnumerable.call( _parent, _prop ) ||
61
70
  (typeof _prop === 'string' && _prop.startsWith('@')) ||
62
- (options.skipIgnore && node._ignore) ||
71
+ (options.skipIgnore && node.$ignore) ||
63
72
  options.skipStandard?.[_prop]
64
73
  )
65
74
  return;
@@ -75,13 +84,35 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
75
84
  const trans = transformers[name] || standard;
76
85
  if (customTransformers[name])
77
86
  customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
78
-
79
87
  trans( node, name, node[name], csnPath );
80
88
  }
81
89
  }
82
90
  csnPath.pop();
83
91
  }
84
92
 
93
+ /**
94
+ * Transformer for dictionary entries. Similar to standard(), but does not filter
95
+ * based on the given name. Otherwise, `options.skipStandards` could accidentally skip
96
+ * dictionary entries (e.g. entity called `@Name`).
97
+ *
98
+ * @param {object | Array} dict the thing that has _prop
99
+ * @param {string|number} entryName the name of the current property
100
+ * @param {object} node The value of node[_prop]
101
+ */
102
+ function dictEntry( dict, entryName, node ) {
103
+ if (!node || typeof node !== 'object' || (options.skipIgnore && node.$ignore))
104
+ return;
105
+
106
+ csnPath.push( entryName );
107
+ for (const name of Object.getOwnPropertyNames( node )) {
108
+ const trans = transformers[name] || standard;
109
+ if (customTransformers[name])
110
+ customTransformers[name](node, name, node[name], csnPath, dict);
111
+ trans( node, name, node[name], csnPath );
112
+ }
113
+ csnPath.pop();
114
+ }
115
+
85
116
  /**
86
117
  * Transformer for things that are dictionaries - like "elements".
87
118
  *
@@ -90,12 +121,12 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
90
121
  * @param {object} dict The value of node[_prop]
91
122
  */
92
123
  function dictionary( node, _prop, dict ) {
93
- // Allow skipping dicts like actions in forRelationalDB
94
- if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
124
+ // Allow skipping dictionaries like actions in forRelationalDB
125
+ if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
95
126
  return;
96
127
  csnPath.push( _prop );
97
128
  for (const name of Object.getOwnPropertyNames( dict ))
98
- standard( dict, name, dict[name] );
129
+ dictEntry( dict, name, dict[name] );
99
130
 
100
131
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
101
132
  setProp(node, `$${_prop}`, dict);
@@ -112,13 +143,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
112
143
  function definitions( node, _prop, dict ) {
113
144
  csnPath.push( _prop );
114
145
  for (const name of Object.getOwnPropertyNames( dict )) {
115
- const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
116
- (options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
117
- (options && options.skip && options.skip.includes(dict[name].kind)) ||
118
- false;
119
- if (!skip) {
146
+ if (!isArtifactSkipped( dict, name )) {
120
147
  artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
121
- standard( dict, name, dict[name] );
148
+ dictEntry( dict, name, dict[name] );
122
149
  }
123
150
  }
124
151
  if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
@@ -126,6 +153,20 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
126
153
  csnPath.pop();
127
154
  }
128
155
 
156
+ /**
157
+ * Whether the given artifact `dict[name]` is skipped via options.
158
+ *
159
+ * @param {object} dict
160
+ * @param {string} name
161
+ * @returns {boolean}
162
+ */
163
+ function isArtifactSkipped( dict, name ) {
164
+ return options && ((options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
165
+ (options.skipArtifact && options.skipArtifact(dict[name], name)) ||
166
+ (options.skip?.includes(dict[name].kind))) ||
167
+ false;
168
+ }
169
+
129
170
  /**
130
171
  * Keep looping through the pathRef - because in a .ref we can have .args and .where
131
172
  *
@@ -173,7 +214,7 @@ function applyTransformations( csn, customTransformers = {}, artifactTransformer
173
214
  if (options.skipIgnore === undefined)
174
215
  options.skipIgnore = true;
175
216
 
176
- if (csn && csn.definitions)
217
+ if (csn?.definitions)
177
218
  return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
178
219
  return csn;
179
220
  }
@@ -230,12 +271,12 @@ module.exports = {
230
271
 
231
272
  /**
232
273
  * @typedef {object} applyTransformationsOptions
233
- * @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
234
- * @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
274
+ * @property {(artifact: CSN.Artifact, name: string) => boolean} [allowArtifact] to only allow certain artifacts
275
+ * @property {(artifact: CSN.Artifact, name: string) => boolean} [skipArtifact] to skip certain artifacts
235
276
  * @property {boolean} [drillRef] whether to drill into infix/args
236
277
  * @property {string[]} [skip] skip definitions from certain kind
237
278
  * @property {object} [skipStandard] stop drill-down on certain "standard" props
238
279
  * @property {object} [skipDict] stop drill-down on certain "dictionary" props
239
- * @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
280
+ * @property {boolean} [skipIgnore=true] Whether to skip $ignore elements or not
240
281
  * @property {boolean} [directDict=false] Implicitly set via applyTransformationsOnDictionary
241
282
  */
@@ -1,8 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { forEachDefinition, hasAnnotationValue } = require('../../model/csnUtils');
4
- const { getTransformers } = require('../transformUtilsNew');
5
- const { setProp } = require('../../base/model');
4
+ const { getTransformers } = require('../transformUtils');
6
5
  const { pathName } = require('../../compiler/utils');
7
6
 
8
7
 
@@ -97,7 +96,7 @@ function processAssertUnique( csn, options, error, info ) {
97
96
  }
98
97
  // 10) Store remaining paths (if any) in constraint dictionary
99
98
  if (flattenedPathObjects.length)
100
- constraintDict[constraintName.join('.')] = flattenedPathObjects;
99
+ constraintDict[constraintName.join('.')] = { paths: flattenedPathObjects, parentTable: artifactName };
101
100
  }
102
101
  }
103
102
 
@@ -110,9 +109,9 @@ function processAssertUnique( csn, options, error, info ) {
110
109
  // preserve dictionary in '$tableConstraints' on the artifact for path rewriting and rendering
111
110
  if (Object.keys(constraintDict).length) {
112
111
  if (!('$tableConstraints' in artifact))
113
- setProp(artifact, '$tableConstraints', Object.create(null));
112
+ artifact.$tableConstraints = Object.create(null);
114
113
 
115
- setProp(artifact.$tableConstraints, 'unique', constraintDict);
114
+ artifact.$tableConstraints.unique = constraintDict;
116
115
  }
117
116
 
118
117
  /**
@@ -286,7 +285,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
286
285
  }
287
286
  for (const uniqueConstraint in artifact.$tableConstraints.unique) {
288
287
  // iterate over each constraint
289
- const c = uniqueConstraints[uniqueConstraint];
288
+ const c = uniqueConstraints[uniqueConstraint].paths;
290
289
  const rewrittenPaths = [];
291
290
  // and inspect each path of the constraint
292
291
  c.forEach((cpath) => {
@@ -319,7 +318,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
319
318
  }
320
319
  });
321
320
  // preserve the rewritten and filtered paths for toSql
322
- uniqueConstraints[uniqueConstraint] = rewrittenPaths;
321
+ uniqueConstraints[uniqueConstraint] = { paths: rewrittenPaths, parentTable: uniqueConstraints[uniqueConstraint].parentTable };
323
322
 
324
323
  // now add the index for HANA CDS
325
324
  if (options.transformation === 'hdbcds') {
@@ -335,7 +334,7 @@ function rewriteUniqueConstraints( csn, options, pathDelimiter ) {
335
334
  artifact.technicalConfig.hana.indexes[uniqueConstraint] = index;
336
335
  }
337
336
  }
338
- setProp(artifact.$tableConstraints, 'unique', uniqueConstraints);
337
+ artifact.$tableConstraints.unique = uniqueConstraints;
339
338
  }
340
339
  }
341
340
  }
@@ -48,8 +48,8 @@ function attachOnConditions( csn, csnUtils, pathDelimiter ) {
48
48
  // Assemble an ON-condition with the foreign keys created in earlier steps
49
49
  const onCondParts = [];
50
50
  let joinWithAnd = false;
51
- if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
52
- elem._ignore = true;
51
+ if (elem.keys.length === 0) { // TODO: really kill instead of $ignore?
52
+ elem.$ignore = true;
53
53
  }
54
54
  else {
55
55
  for (const foreignKey of elem.keys) {
@@ -3,13 +3,13 @@
3
3
  const {
4
4
  forEachGeneric, forEachMemberRecursively, hasAnnotationValue, isPersistedOnDatabase,
5
5
  } = require('../../model/csnUtils');
6
- const transformUtils = require('../transformUtilsNew');
6
+ const transformUtils = require('../transformUtils');
7
7
 
8
8
  const exists = '@cds.persistence.exists';
9
9
 
10
10
  /**
11
11
  * Return a callback function for forEachDefinition that marks artifacts that are abstract or @cds.persistence.exists/skip
12
- * with _ignore.
12
+ * with $ignore.
13
13
  *
14
14
  * @returns {(artifact: CSN.Artifact, artifactName) => void} Callback function for forEachDefinition
15
15
  */
@@ -24,12 +24,12 @@ function getAnnoProcessor() {
24
24
  hasAnnotationValue(artifact, '@cds.persistence.skip') ||
25
25
  hasAnnotationValue(artifact, exists));
26
26
  if (ignoreArtifact)
27
- artifact._ignore = true;
27
+ artifact.$ignore = true;
28
28
  }
29
29
  }
30
30
 
31
31
  /**
32
- * Return a callback function for forEachDefinition that marks associations with _ignore
32
+ * Return a callback function for forEachDefinition that marks associations with $ignore
33
33
  * if their target does not reach the database, i.e. marked with @cds.persistence.skip or is abstract
34
34
  *
35
35
  * @param {CSN.Model} csn
@@ -72,7 +72,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
72
72
  }
73
73
 
74
74
  /**
75
- * Mark the given member with _ignore if it is an association/composition and it's target is unreachable.
75
+ * Mark the given member with $ignore if it is an association/composition and its target is unreachable.
76
76
  *
77
77
  * @param {CSN.Element} member
78
78
  * @param {string} memberName
@@ -81,13 +81,13 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
81
81
  */
82
82
  function ignore( member, memberName, prop, path ) {
83
83
  if (options.sqlDialect === 'hana' &&
84
- !member._ignore && member.target &&
84
+ !member.$ignore && member.target &&
85
85
  isAssocOrComposition(member) &&
86
86
  !isPersistedOnDatabase(csn.definitions[member.target])) {
87
87
  info(null, path,
88
88
  { target: member.target, anno: '@cds.persistence.skip' },
89
89
  'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
90
- member._ignore = true;
90
+ member.$ignore = true;
91
91
  }
92
92
  }
93
93
  }
@@ -124,7 +124,7 @@ function getPersistenceTableProcessor( csn, options, messageFunctions ) {
124
124
 
125
125
  recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
126
126
  // All elements must have a type for this to work
127
- if (!member._ignore && !member.kind && !member.type) {
127
+ if (!member.$ignore && !member.kind && !member.type) {
128
128
  error(null, path, { anno: '@cds.persistence.table' },
129
129
  'Expecting element to have a type if view is annotated with $(ANNO)');
130
130
  }
@@ -274,15 +274,8 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
274
274
  return { columns, toMany: [] };
275
275
 
276
276
  for (const col of columns) {
277
- if (col.expand) {
278
- // TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
279
- const { expanded, toManys } = expandInline(root, col, col.ref || [], [ dbName(col) ]);
280
-
281
- allToMany.push(...toManys);
282
- newThing.push(...expanded);
283
- }
284
- else if (col.inline) {
285
- const { expanded, toManys } = expandInline(root, col, col.ref || [], []);
277
+ if (col.expand || col.inline) {
278
+ const { expanded, toManys } = expandInline(root, col, col.ref || [], col.expand ? [ dbName(col) ] : []);
286
279
 
287
280
  allToMany.push(...toManys);
288
281
  newThing.push(...expanded);
@@ -335,18 +328,21 @@ function expandStructureReferences( csn, options, pathDelimiter, { error, info,
335
328
  });
336
329
  toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });
337
330
  }
338
- else if (current.expand) {
339
- current.expand = replaceStar(nextBase(current, base), current.expand, current.excluding);
340
- for (let i = current.expand.length - 1; i >= 0; i--) {
341
- const sub = current.expand[i];
342
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
343
- }
344
- }
345
- else if (current.inline) {
346
- current.inline = replaceStar(nextBase(current, base), current.inline, current.excluding);
347
- for (let i = current.inline.length - 1; i >= 0; i--) {
348
- const sub = current.inline[i];
349
- stack.push([ nextBase(current, base), sub, sub.ref ? currentRef.concat(sub.ref) : currentRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
331
+ else if (current.expand || current.inline) {
332
+ const withoutStar = replaceStar(nextBase(current, base), current.expand || current.inline, current.excluding);
333
+ current[current.expand ? 'expand' : 'inline'] = withoutStar;
334
+ for (let i = withoutStar.length - 1; i >= 0; i--) {
335
+ const sub = withoutStar[i];
336
+ let subRef;
337
+ if (sub.ref) {
338
+ // Each expand/inline can introduce another layer of $self/$projection. Since $self is
339
+ // a path-breakout, we can simply use the ref without outer expand/inline-references.
340
+ subRef = (sub.$scope === '$self') ? sub.ref : currentRef.concat(sub.ref);
341
+ }
342
+ else {
343
+ subRef = currentRef;
344
+ }
345
+ stack.push([ nextBase(current, base), sub, subRef, !sub.inline ? currentAlias.concat(dbName(sub)) : currentAlias ]);
350
346
  }
351
347
  }
352
348
  else if (current.xpr || current.args) {
@@ -5,7 +5,7 @@ const {
5
5
  isBuiltinType, cloneCsnNonDict,
6
6
  copyAnnotations, implicitAs, isDeepEqual,
7
7
  } = require('../../model/csnUtils');
8
- const transformUtils = require('../transformUtilsNew');
8
+ const transformUtils = require('../transformUtils');
9
9
  const { csnRefs } = require('../../model/csnRefs');
10
10
  const { setProp } = require('../../base/model');
11
11
  const { forEach } = require('../../utils/objectUtils');
@@ -79,24 +79,23 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
79
79
  const ignoreOdataKinds = { aspect: 1, event: 1, type: 1 };
80
80
  const replaceWithDummyKinds = { action: 1, function: 1, event: 1 };
81
81
  applyTransformations(csn, {
82
- cast: (parent, prop, cast, path) => {
83
- // Resolve cast already - we otherwise lose .localized
84
- if (cast.type && !isBuiltinType(cast.type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(cast.type, path) && !isODataItems(cast.type)))
85
- toFinalBaseType(parent.cast, resolved, true);
86
- },
87
- // @ts-ignore
88
- type: (parent, prop, type, path) => {
89
- if (options.toOdata && parent.kind && parent.kind in ignoreOdataKinds)
82
+ type: (node, prop, type, path, parent, parentProp) => {
83
+ if (options.toOdata && node.kind && node.kind in ignoreOdataKinds)
90
84
  return;
85
+ if (parentProp === 'cast') {
86
+ const e = csnUtils.getFinalTypeInfo(type, t => resolved.get(t)?.art || csnUtils.artifactRef(t));
87
+ if (!e || e.items || e.elements)
88
+ return;
89
+ }
91
90
  if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type, path) && !isODataItems(type))) {
92
- toFinalBaseType(parent, resolved, true);
91
+ toFinalBaseType(node, resolved, true);
93
92
 
94
- if (parent.items) // items could have unresolved types
95
- toFinalBaseType(parent.items, resolved, true);
93
+ if (node.items) // items could have unresolved types
94
+ toFinalBaseType(node.items, resolved, true);
96
95
 
97
96
  // structured types might not have the child-types replaced.
98
97
  // Drill down to ensure this.
99
- let nextElements = parent.elements || parent.items?.elements;
98
+ let nextElements = node.elements || node.items?.elements;
100
99
  if (nextElements) {
101
100
  const stack = [ nextElements ];
102
101
  while (stack.length > 0) {
@@ -111,9 +110,9 @@ function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOp
111
110
  }
112
111
  }
113
112
 
114
- const directLocalized = parent.localized || false;
113
+ const directLocalized = node.localized || false;
115
114
  if (!directLocalized && !options.toOdata)
116
- removeLocalized(parent);
115
+ removeLocalized(node);
117
116
  }
118
117
  },
119
118
  }, [ (definitions, artifactName, artifact) => {
@@ -284,8 +283,7 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
284
283
  forEach(dict, (elementName, element) => {
285
284
  if (element.elements) {
286
285
  // Ignore the structured element, replace it by its flattened form
287
- // TODO: use $ignore - _ is for links
288
- element._ignore = true;
286
+ element.$ignore = true;
289
287
 
290
288
  const branches = getBranches(element, elementName, effectiveType, pathDelimiter);
291
289
  const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
@@ -308,6 +306,8 @@ function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {
308
306
 
309
307
 
310
308
  if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
309
+ // unmanaged relations can't be primary key
310
+ delete flatElement.key;
311
311
  // Make refs resolvable by fixing the first ref step
312
312
  for (const onPart of flatElement.on) {
313
313
  if (onPart.ref) {
@@ -441,6 +441,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
441
441
  * @param {*} path
442
442
  */
443
443
  function flattenFKs( assoc, assocName, path ) {
444
+ // TODO Depth first search and not iterate mark and sweep approach
444
445
  let finished = false;
445
446
  while (!finished) {
446
447
  const newKeys = [];
@@ -465,8 +466,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
465
466
  const { ref } = assoc.keys[i];
466
467
  if (isStructured(art)) {
467
468
  done = false;
468
- // Mark this element to filter it later - not needed after expansion
469
- setProp(assoc.keys[i], '$toDelete', true);
470
469
  const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
471
470
  Object.keys(flat).forEach((flatElemName) => {
472
471
  const key = assoc.keys[i];
@@ -502,12 +501,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
502
501
  }
503
502
  else if (art.target) {
504
503
  done = false;
505
- // Mark this element to filter it later - not needed after expansion
506
- setProp(assoc.keys[i], '$toDelete', true);
507
504
  // Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
508
505
  // Add the newly generated foreign keys to the end - they will be picked up later on
509
506
  // Recursive solutions run into call stack issues
510
- art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
507
+ art.keys?.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
511
508
  }
512
509
  else if (assoc.keys[i].ref && !assoc.keys[i].as) {
513
510
  setProp(assoc.keys[i], inferredAlias, true);
@@ -523,7 +520,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
523
520
  }
524
521
  return done;
525
522
  }
526
- assoc.keys = assoc.keys.filter(o => !o.$toDelete);
527
523
 
528
524
  /**
529
525
  * Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
@@ -612,6 +608,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, war
612
608
  return acc;
613
609
  }, Object.create(null));
614
610
 
611
+ // set default for single foreign key from association (if available)
612
+ if (element.default?.val !== undefined && fks.length === 1)
613
+ fks[0][1].default = element.default;
614
+
615
615
  // check for duplicate foreign keys
616
616
  Object.entries(refCount).forEach(([ name, occ ]) => {
617
617
  if (occ > 1)
@@ -3,12 +3,16 @@
3
3
  const { setProp } = require('../../base/model');
4
4
  const { CompilerAssertion } = require('../../base/error');
5
5
  const {
6
- forEachDefinition, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary, implicitAs, cloneCsnNonDict, getUtils,
6
+ forEachDefinition,
7
+ applyTransformationsOnNonDictionary,
8
+ applyTransformationsOnDictionary,
9
+ implicitAs,
10
+ cloneCsnNonDict,
7
11
  forEachMemberRecursively,
8
12
  } = require('../../model/csnUtils');
9
13
  const { getBranches } = require('./flattening');
10
14
  const { getColumnMap } = require('./views');
11
- const { checkForeignKeyAccess } = require('../../checks/onConditions');
15
+ const { requireForeignKeyAccess } = require('../../checks/onConditions');
12
16
 
13
17
  const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };
14
18
 
@@ -19,11 +23,13 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '
19
23
  *
20
24
  * @param {CSN.Model} csn
21
25
  * @param {CSN.Options} options
26
+ * @param {object} csnUtils
22
27
  * @param {string} pathDelimiter
23
- * @param {Function} error
28
+ * @param {object} messageFunctions
24
29
  */
25
- function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
26
- const { inspectRef, effectiveType } = getUtils(csn, 'init-all');
30
+ function rewriteCalculatedElementsInViews( csn, options, csnUtils, pathDelimiter, messageFunctions ) {
31
+ const { inspectRef, effectiveType } = csnUtils;
32
+ const { error } = messageFunctions;
27
33
 
28
34
  const views = [];
29
35
  const entities = [];
@@ -121,7 +127,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
121
127
  }
122
128
  else {
123
129
  // It's a managed association - access of the foreign keys is allowed
124
- checkForeignKeyAccess(parent, i, csnPath, (errorIndex) => {
130
+ requireForeignKeyAccess(parent, i, (errorIndex) => {
125
131
  error('ref-unexpected-navigation', csnPath, {
126
132
  '#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
127
133
  });
@@ -412,7 +418,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
412
418
  * @param {CSN.QuerySelect} SELECT
413
419
  * @returns {object}
414
420
  */
415
- function getDirectlyAdressableElements( SELECT ) {
421
+ function getDirectlyAddressableElements( SELECT ) {
416
422
  const { from } = SELECT;
417
423
  if (from.ref) {
418
424
  return from._art.elements;
@@ -422,7 +428,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
422
428
  }
423
429
  else if (from.SET) {
424
430
  // args[0] could be SELECT or UNION
425
- return getDirectlyAdressableElements({ from: from.SET.args[0] });
431
+ return getDirectlyAddressableElements({ from: from.SET.args[0] });
426
432
  }
427
433
  else if (from.args) {
428
434
  const mergedElements = Object.create(null);
@@ -432,7 +438,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
432
438
  mergedElements[elementName] = arg._art.elements[elementName];
433
439
  }
434
440
  else if (arg.SET) {
435
- return getDirectlyAdressableElements({ from: arg.SET.args[0] });
441
+ return getDirectlyAddressableElements({ from: arg.SET.args[0] });
436
442
  }
437
443
  else if (arg.SELECT) { // TODO: UNION
438
444
  for (const elementName in arg.SELECT.elements)
@@ -440,7 +446,7 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
440
446
  }
441
447
  else if (arg.args) { // TODO: Is it safe to do recursion here?
442
448
  for (const subarg of arg.args) {
443
- const elements = getDirectlyAdressableElements({ from: subarg });
449
+ const elements = getDirectlyAddressableElements({ from: subarg });
444
450
  for (const elementName in elements)
445
451
  mergedElements[elementName] = elements[elementName];
446
452
  }
@@ -465,8 +471,8 @@ function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error )
465
471
  */
466
472
  function makeAllCalculatedElementsExplicitColumns( elements, SELECT, containsExpandInline ) {
467
473
  const cleanupCallbacks = [];
468
- const root = getDirectlyAdressableElements(SELECT);
469
- const columnMap = getColumnMap( { SELECT });
474
+ const root = getDirectlyAddressableElements(SELECT);
475
+ const columnMap = getColumnMap( { SELECT }, csnUtils );
470
476
  const hasStar = SELECT.columns.includes('*');
471
477
  const unfoldingMap = {};
472
478
  let starContainsCalculated = false;
@@ -699,9 +705,9 @@ function processCalculatedElementsInEntities( csn ) {
699
705
  */
700
706
  function removeDummyValueInEntity( artifact, path ) {
701
707
  applyTransformationsOnDictionary(artifact.elements, {
702
- value: (parent, prop, value, p, root) => {
708
+ value: (parent, prop, value, p, elements) => {
703
709
  if (!value.stored)
704
- delete root[p[p.length - 1]];
710
+ delete elements[p[p.length - 1]];
705
711
  },
706
712
  }, {}, path.concat( 'elements' ));
707
713
  }
@@ -5,7 +5,7 @@ const {
5
5
  } = require('../../model/csnUtils');
6
6
  const { implicitAs } = require('../../model/csnRefs');
7
7
  const { setProp } = require('../../base/model');
8
- const { getTransformers } = require('../transformUtilsNew');
8
+ const { getTransformers } = require('../transformUtils');
9
9
 
10
10
  const validToString = '@cds.valid.to';
11
11
  const validFromString = '@cds.valid.from';
@@ -53,22 +53,23 @@ const { ModelError } = require('../../base/error');
53
53
  function handleExists( csn, options, error, inspectRef, initDefinition, dropDefinitionCache ) {
54
54
  const generatedExists = new WeakMap();
55
55
  forEachDefinition(csn, (artifact, artifactName) => {
56
+ // drop cache: Otherwise, the projection/query hack below won't work, because csnRefs
57
+ // thinks that the artifact was already initialized (including all queries).
58
+ dropDefinitionCache(artifact);
56
59
  if (artifact.projection) // do the same hack we do for the other stuff...
57
60
  artifact.query = { SELECT: artifact.projection };
58
61
 
59
62
  if (artifact.query) {
60
- forAllQueries(artifact.query, (query, path) => {
63
+ forAllQueries(artifact.query, function handleExistsQuery(query, path) {
61
64
  if (!generatedExists.has(query)) {
62
65
  const toProcess = []; // Collect all expressions we need to process here
63
- if (query.SELECT && query.SELECT.where && query.SELECT.where.length > 1)
66
+ if (query.SELECT?.where?.length > 1)
64
67
  toProcess.push([ path.slice(0, -1), path.concat('where') ]);
65
68
 
66
-
67
- if (query.SELECT && query.SELECT.columns)
69
+ if (query.SELECT?.columns)
68
70
  toProcess.push([ path.slice(0, -1), path.concat('columns') ]);
69
71
 
70
-
71
- if (query.SELECT && query.SELECT.from.on )
72
+ if (query.SELECT?.from.on)
72
73
  toProcess.push([ path.slice(0, -1), path.concat([ 'from', 'on' ]) ]);
73
74
 
74
75
  for (const [ , exprPath ] of toProcess) {
@@ -268,7 +269,7 @@ function handleExists( csn, options, error, inspectRef, initDefinition, dropDefi
268
269
 
269
270
  const { links } = inspectRef(exprPath.concat(i));
270
271
 
271
- const assocs = links.filter(link => link.art && link.art.target).map(link => current.ref[link.idx]);
272
+ const assocs = links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
272
273
 
273
274
  checkForInvalidAssoc(assocs);
274
275
  }