@sap/cds-compiler 4.1.2 → 4.2.4

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 (74) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/bin/cdsc.js +6 -3
  3. package/doc/CHANGELOG_BETA.md +5 -0
  4. package/doc/CHANGELOG_DEPRECATED.md +15 -0
  5. package/lib/api/main.js +2 -2
  6. package/lib/api/options.js +2 -2
  7. package/lib/api/validate.js +24 -24
  8. package/lib/base/message-registry.js +41 -6
  9. package/lib/base/messages.js +7 -0
  10. package/lib/base/model.js +38 -8
  11. package/lib/checks/elements.js +11 -10
  12. package/lib/checks/manyNavigations.js +33 -0
  13. package/lib/checks/onConditions.js +5 -2
  14. package/lib/checks/queryNoDbArtifacts.js +2 -3
  15. package/lib/checks/selectItems.js +4 -55
  16. package/lib/checks/utils.js +3 -2
  17. package/lib/checks/validator.js +3 -1
  18. package/lib/compiler/.eslintrc.json +2 -1
  19. package/lib/compiler/assert-consistency.js +27 -24
  20. package/lib/compiler/base.js +6 -2
  21. package/lib/compiler/builtins.js +34 -34
  22. package/lib/compiler/checks.js +179 -208
  23. package/lib/compiler/classes.js +2 -2
  24. package/lib/compiler/cycle-detector.js +6 -6
  25. package/lib/compiler/define.js +66 -45
  26. package/lib/compiler/extend.js +81 -72
  27. package/lib/compiler/finalize-parse-cdl.js +26 -26
  28. package/lib/compiler/generate.js +61 -45
  29. package/lib/compiler/index.js +47 -49
  30. package/lib/compiler/kick-start.js +8 -7
  31. package/lib/compiler/moduleLayers.js +1 -1
  32. package/lib/compiler/populate.js +42 -35
  33. package/lib/compiler/propagator.js +6 -6
  34. package/lib/compiler/resolve.js +170 -126
  35. package/lib/compiler/shared.js +122 -45
  36. package/lib/compiler/tweak-assocs.js +93 -40
  37. package/lib/compiler/utils.js +15 -12
  38. package/lib/edm/.eslintrc.json +40 -1
  39. package/lib/edm/annotations/genericTranslation.js +721 -707
  40. package/lib/edm/annotations/preprocessAnnotations.js +88 -77
  41. package/lib/edm/csn2edm.js +389 -378
  42. package/lib/edm/edm.js +678 -772
  43. package/lib/edm/edmAnnoPreprocessor.js +132 -146
  44. package/lib/edm/edmInboundChecks.js +29 -27
  45. package/lib/edm/edmPreprocessor.js +686 -646
  46. package/lib/edm/edmUtils.js +277 -296
  47. package/lib/gen/language.checksum +1 -1
  48. package/lib/gen/language.interp +1 -1
  49. package/lib/gen/languageParser.js +1253 -1276
  50. package/lib/json/from-csn.js +34 -4
  51. package/lib/json/to-csn.js +4 -4
  52. package/lib/language/language.g4 +2 -5
  53. package/lib/main.d.ts +61 -1
  54. package/lib/model/csnUtils.js +31 -2
  55. package/lib/model/revealInternalProperties.js +1 -1
  56. package/lib/modelCompare/compare.js +37 -2
  57. package/lib/modelCompare/utils/filter.js +1 -1
  58. package/lib/optionProcessor.js +15 -3
  59. package/lib/render/toCdl.js +30 -4
  60. package/lib/render/toSql.js +5 -9
  61. package/lib/render/utils/common.js +8 -6
  62. package/lib/transform/db/applyTransformations.js +1 -1
  63. package/lib/transform/db/cdsPersistence.js +1 -1
  64. package/lib/transform/db/constraints.js +47 -17
  65. package/lib/transform/db/expansion.js +133 -50
  66. package/lib/transform/db/flattening.js +75 -7
  67. package/lib/transform/forOdata.js +4 -1
  68. package/lib/transform/forRelationalDB.js +80 -62
  69. package/lib/transform/localized.js +91 -54
  70. package/lib/transform/transformUtils.js +9 -10
  71. package/lib/utils/file.js +7 -7
  72. package/lib/utils/moduleResolve.js +210 -121
  73. package/lib/utils/objectUtils.js +1 -1
  74. package/package.json +5 -5
@@ -72,8 +72,9 @@
72
72
  * What "kind" values are possible in a definition. The root "definitions" properties allows
73
73
  * more kinds than e.g. definitions inside "elements".
74
74
  *
75
- * @property {string|string[]} [onlyWith]
76
- * Defines that the property *must* be used with these properties.
75
+ * @property {string|string[]|Object} [onlyWith]
76
+ * Defines that the property *must* be used with one of these properties.
77
+ * If an object, it maps the kind value to a string or array of strings.
77
78
  *
78
79
  * @property {number} [minLength]
79
80
  * Minimum number of elements that an array must have.
@@ -255,12 +256,14 @@ const schema = compileSchema( {
255
256
  requires: [ 'extend', 'annotate' ],
256
257
  },
257
258
  enum: {
259
+ type: enumDict,
258
260
  dictionaryOf: definition,
259
261
  defaultKind: 'enum',
260
262
  validKinds: [ 'enum' ],
261
263
  inKind: [ 'element', 'type', 'param', 'annotation', 'annotate', 'extend' ],
262
264
  },
263
265
  elements: {
266
+ type: elementsDict,
264
267
  dictionaryOf: definition,
265
268
  defaultKind: 'element',
266
269
  validKinds: [ 'element' ],
@@ -280,6 +283,7 @@ const schema = compileSchema( {
280
283
  dictionaryOf: actions,
281
284
  defaultKind: 'action',
282
285
  validKinds: [ 'action', 'function' ],
286
+ onlyWith: { aspect: 'elements' },
283
287
  inKind: [ 'entity', 'aspect', 'annotate', 'extend' ],
284
288
  },
285
289
  params: {
@@ -656,7 +660,7 @@ const schema = compileSchema( {
656
660
  '-expr': { // '-expr' and '-' must not exist top-level
657
661
  prop: '@‹anno›',
658
662
  type: object,
659
- optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args' ],
663
+ optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args', 'param' ],
660
664
  schema: {
661
665
  '=': {
662
666
  type: renameTo( '$tokenTexts', string ),
@@ -1038,7 +1042,7 @@ function definition( def, spec, xsn, csn, name ) {
1038
1042
  // for an 'annotate', both 'annotate' and the "host" kind must be expected
1039
1043
  (!inExtensions || s.inKind.includes( inExtensions ) ||
1040
1044
  // extending elements in returns can be without 'returns' in CSN
1041
- // TODO: with warning/info?
1045
+ // see function elementsDict() for detail, TODO: remove finally
1042
1046
  inExtensions === 'action' && p === 'elements');
1043
1047
  }
1044
1048
  }
@@ -1121,6 +1125,27 @@ function returnsDefinition( def, spec, xsn, csn ) {
1121
1125
  return definition( def, spec, xsn, csn, '' );
1122
1126
  }
1123
1127
 
1128
+ // Temporary function as long as the message below is not a hard error
1129
+ function elementsDict( def, spec, xsn ) {
1130
+ const elements = dictionaryOf( definition )( def, spec );
1131
+ if (inExtensions !== 'action')
1132
+ return elements;
1133
+ warning( 'syntax-expecting-returns', elements[$location],
1134
+ { prop: 'elements', parentprop: 'returns' },
1135
+ // eslint-disable-next-line max-len
1136
+ 'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
1137
+ xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
1138
+ return undefined;
1139
+ }
1140
+
1141
+ function enumDict( def, spec, xsn ) {
1142
+ const dict = dictionaryOf( definition )( def, spec );
1143
+ if (!inExtensions)
1144
+ return dict;
1145
+ xsn.elements = dict; // normalize to `elements` for `annotate`
1146
+ return undefined;
1147
+ }
1148
+
1124
1149
  // For v1 CSNs with annotation definitions
1125
1150
  function attachVocabInDefinitions( csn ) {
1126
1151
  if (!csn.vocabularies) {
@@ -1827,6 +1852,11 @@ function calculateKind( def, spec ) {
1827
1852
  function onlyWith( spec, need, csn, prop, xor, expected ) {
1828
1853
  if (!need)
1829
1854
  return spec;
1855
+ if (typeof need === 'object' && !Array.isArray( need )) {
1856
+ need = need[csn.kind];
1857
+ if (!need)
1858
+ return spec;
1859
+ }
1830
1860
  if (typeof need === 'string') {
1831
1861
  if (need in csn) // TODO: enumerable ?
1832
1862
  return spec;
@@ -115,7 +115,6 @@ const transformers = {
115
115
  offset: expression,
116
116
  on: onCondition,
117
117
  // definitions, extensions, members ----------------------------------------
118
- returns, // storing the return type of actions
119
118
  notNull: value,
120
119
  default: expression,
121
120
  // targetElement: ignore, // special display of foreign key, renameTo: select
@@ -123,6 +122,7 @@ const transformers = {
123
122
  query,
124
123
  elements,
125
124
  actions, // TODO: just normal dictionary
125
+ returns, // storing the return type of actions
126
126
  // special: top-level, cardinality -----------------------------------------
127
127
  sources,
128
128
  definitions: sortedDict,
@@ -465,13 +465,13 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
465
465
  attachAnnotations( sub, 'actions', entry.actions, inf );
466
466
  else if (entry.params)
467
467
  attachAnnotations( sub, 'params', entry.params, inf );
468
- const obj = entry.returns || entry; // TODO: create returns !
468
+ const obj = entry.returns || entry;
469
469
  const many = obj.items || obj;
470
470
  const elems = (many.targetAspect || many).elements;
471
471
  if (elems)
472
472
  attachAnnotations( sub, 'elements', elems, inf, entry.returns );
473
- if (many.enum)
474
- attachAnnotations( sub, 'enum', many.enum, inf );
473
+ else if (many.enum) // make 'enum' annotations appear in 'elements' annotate
474
+ attachAnnotations( sub, 'elements', many.enum, inf, entry.returns );
475
475
  }
476
476
  if (Object.keys( sub ).length)
477
477
  annoDict[name] = sub;
@@ -2010,11 +2010,8 @@ orderByClause[ inQuery ] returns [ query ]
2010
2010
  limitClause[ inQuery ] returns [ query ]
2011
2011
  :
2012
2012
  limkw=LIMIT { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
2013
- ( lim=Number { $query.limit = { rows: this.numberLiteral( $lim, '' ) }; }
2014
- | limnull=NULL { $query.limit = { rows: {
2015
- literal: 'null', val: null, location: this.tokenLocation($limnull) } }; }
2016
- )
2017
- ( OFFSET off=Number { $query.limit.offset = this.numberLiteral( $off ); } )? // unsigned integer
2013
+ lim=expression { $query.limit = { rows: $lim.expr }; }
2014
+ ( OFFSET off=expression { $query.limit.offset = $off.expr; } )? // unsigned integer
2018
2015
  ;
2019
2016
 
2020
2017
  orderBySpec returns[ ob ]
package/lib/main.d.ts CHANGED
@@ -70,6 +70,18 @@ declare namespace compiler {
70
70
  * - universal : In development (BETA)
71
71
  */
72
72
  csnFlavor?: string | 'client' | 'gensrc' | 'universal'
73
+ /**
74
+ * If set, backends will not create localized convenience views for those views,
75
+ * that only have an association to a localized entity/view. Views will only get
76
+ * a convenience view, if they themselves contain localized elements (i.e. either
77
+ * have simple projection on localized elements and CDL-casts to a localized element).
78
+ *
79
+ * The OData backend will not set `$localized: true` markers for such cases.
80
+ *
81
+ * Does not work for backends to.hdi(), to.hdbcds() or to.sql() with `sqlDialect: 'hana'`,
82
+ * since in all those dialects, associations still exist in generated artifacts.
83
+ */
84
+ fewerLocalizedViews?: boolean
73
85
  }
74
86
 
75
87
  /**
@@ -86,6 +98,30 @@ declare namespace compiler {
86
98
  * the prefix for SAP CDS packages / CDS files.
87
99
  */
88
100
  cdsHome?: string
101
+ /**
102
+ * "Doc comments" (documentation comments) are those comments starting with `/**` in CDL
103
+ * or the `doc` property in CSN. This option is an _output_ option, which can have three
104
+ * values:
105
+ *
106
+ * - `true`:
107
+ * Doc comments will appear in the compiled CSN. Basic sanity checks are performed:
108
+ * In CDL, if a doc comment appears at a not-defined position, where it has no impact,
109
+ * an info message is emitted. For CSN input, it is checked that the `doc` property
110
+ * is a string or `null`.
111
+ *
112
+ * - `false`:
113
+ * Doc comments will not be parsed for CDL, and will be stripped from input CSN,
114
+ * i.e. the compiled CSN (output) does not contain `doc` properties. No checks
115
+ * are performed on doc comments.
116
+ *
117
+ * - `undefined`:
118
+ * Doc comments are checked (see value `true`). For CDL, doc comments are not parsed,
119
+ * i.e. will not appear in the compiled CSN (output).
120
+ * For CSN input, all `doc` properties remain in the CSN.
121
+ *
122
+ * The CDL equivalent of the CSN value `doc: null`, is an empty doc comment.
123
+ */
124
+ docComment?: boolean
89
125
  /**
90
126
  * When set to `true`, and the model contains an entity `sap.common.Languages`
91
127
  * with an element `code`, all generated texts entities additionally contain
@@ -95,6 +131,17 @@ declare namespace compiler {
95
131
  * @since v2.8.0
96
132
  */
97
133
  addTextsLanguageAssoc?: boolean
134
+ /**
135
+ * An array of directory names that are used for CDS module lookups.
136
+ * Lookup directory `node_modules/` is appended if not set explicitly.
137
+ *
138
+ * All directories in this array follow the same lookup-pattern as `node_modules/`.
139
+ *
140
+ * See <https://cap.cloud.sap/docs/cds/cdl#model-resolution>
141
+ *
142
+ * @since v4.2.0
143
+ */
144
+ moduleLookupDirectories?: string[]
98
145
  /**
99
146
  * Option for {@link compileSources}. If set, all objects inside the
100
147
  * provided sources dictionary are interpreted as XSN structures instead
@@ -468,9 +515,10 @@ declare namespace compiler {
468
515
  constructor(messages: CompileMessage[], model?: any, text?: string, ...args: any[]);
469
516
  /**
470
517
  * String to identify this class. Can be used instead of relying on `instanceof`.
518
+ * Always `ERR_CDS_COMPILATION_FAILURE`.
471
519
  * @since v4.0.0
472
520
  */
473
- code = 'ERR_CDS_COMPILATION_FAILURE';
521
+ code: string;
474
522
  messages: CompileMessage[];
475
523
  toString(): string;
476
524
  /**
@@ -803,6 +851,9 @@ declare namespace compiler {
803
851
  * identifier in brackets.
804
852
  * Otherwise, return the name without brackets.
805
853
  *
854
+ * NOTE: If `name` contains newline characters, the resulting delimited identifier
855
+ * will not be parsable by the compiler!
856
+ *
806
857
  * Example:
807
858
  * ```js
808
859
  * to.cdl.smartId('with ![brackets]')
@@ -817,6 +868,9 @@ declare namespace compiler {
817
868
  *
818
869
  * @param name
819
870
  * @param [insideFunction=null]
871
+ * Inside special functions such as SAP HANA's `OCCURRENCES_REGEXPR`, there are more
872
+ * keywords than in other places. Set this value to a function name, if you want to
873
+ * handle those additional keywords as well.
820
874
  */
821
875
  function smartId(name: string, insideFunction?: string|null) : string;
822
876
  /**
@@ -824,6 +878,9 @@ declare namespace compiler {
824
878
  * function identifier in brackets for CDL.
825
879
  * Otherwise, return the function name without brackets.
826
880
  *
881
+ * NOTE: If `name` contains newline characters, the resulting delimited identifier
882
+ * will not be parsable by the compiler!
883
+ *
827
884
  * Example:
828
885
  * ```js
829
886
  * to.cdl.smartFunctionId('with ![brackets]')
@@ -839,6 +896,9 @@ declare namespace compiler {
839
896
  * Escapes the given name according to the CDL language and puts it
840
897
  * into `![` and `]`, properly escaping all `]` in the identifier.
841
898
  *
899
+ * NOTE: If `name` contains newline characters, the resulting delimited identifier
900
+ * will not be parsable by the compiler!
901
+ *
842
902
  * Example:
843
903
  * ```js
844
904
  * to.cdl.delimitedId('with ![brackets]')
@@ -1081,6 +1081,9 @@ function getLastPartOfRef( ref ) {
1081
1081
  *
1082
1082
  * Overwrite existing ones only if 'overwrite' is true.
1083
1083
  *
1084
+ * IMPORTANT: Consider using copyAnnotationsAndDoc() instead!
1085
+ * Don't forget about doc comments!
1086
+ *
1084
1087
  * @param {object} fromNode
1085
1088
  * @param {object} toNode
1086
1089
  * @param {boolean} [overwrite]
@@ -1124,6 +1127,32 @@ function copyAnnotationsAndDoc( fromNode, toNode, overwrite = false ) {
1124
1127
  }
1125
1128
  }
1126
1129
 
1130
+ /**
1131
+ * Same as `copyAnnotationsAndDoc()` but deletes the annotations on source
1132
+ * side after copying them. Useful when applying annotations from `cds.extensions`.
1133
+ *
1134
+ * Overwrite existing ones only if 'overwrite' is true.
1135
+ *
1136
+ * @param {object} sourceNode
1137
+ * @param {object} targetNode
1138
+ * @param {boolean} [overwrite]
1139
+ */
1140
+ function moveAnnotationsAndDoc( sourceNode, targetNode, overwrite = false ) {
1141
+ // Ignore if no targetNode (in case of errors)
1142
+ if (!targetNode)
1143
+ return;
1144
+
1145
+ const annotations = Object.keys(sourceNode)
1146
+ .filter(key => key.startsWith('@') || key === 'doc');
1147
+
1148
+ for (const anno of annotations) {
1149
+ if (targetNode[anno] === undefined || overwrite) {
1150
+ targetNode[anno] = sourceNode[anno];
1151
+ delete sourceNode[anno];
1152
+ }
1153
+ }
1154
+ }
1155
+
1127
1156
  /**
1128
1157
  * Applies annotations from `csn.extensions` to definitions and their elements.
1129
1158
  *
@@ -1151,7 +1180,7 @@ function applyAnnotationsFromExtensions( csn, config ) {
1151
1180
  if (name && filter(name)) {
1152
1181
  const def = csn.definitions[name];
1153
1182
  if (def) {
1154
- copyAnnotationsAndDoc(ext, def, config.override);
1183
+ moveAnnotationsAndDoc(ext, def, config.override);
1155
1184
  applyAnnotationsToElements(ext, def);
1156
1185
  }
1157
1186
  else if (config.notFound) {
@@ -1173,7 +1202,7 @@ function applyAnnotationsFromExtensions( csn, config ) {
1173
1202
  forEach(ext.elements, (key, sourceElem) => {
1174
1203
  const targetElem = def.elements[key];
1175
1204
  if (targetElem) {
1176
- copyAnnotationsAndDoc(sourceElem, targetElem, config.override);
1205
+ moveAnnotationsAndDoc(sourceElem, targetElem, config.override);
1177
1206
  applyAnnotationsToElements(sourceElem, targetElem);
1178
1207
  }
1179
1208
  });
@@ -17,7 +17,7 @@ const { CompilerAssertion } = require('../base/error');
17
17
  const $inferred = Symbol.for('cds.$inferred');
18
18
  const $location = Symbol.for('cds.$location');
19
19
 
20
- class NOT_A_DICTIONARY {} // used for consol.log display
20
+ class NOT_A_DICTIONARY {} // used for console.log display
21
21
 
22
22
  function locationString( loc ) {
23
23
  if (Array.isArray(loc))
@@ -249,7 +249,42 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
249
249
  }
250
250
 
251
251
  function relevantTypeChange(type, otherType) {
252
- return otherType !== type && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
252
+ return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
253
+ }
254
+
255
+ const superflousTypeChanges = {
256
+ // turn it into a real dict
257
+ __proto__: null,
258
+ // We used to put these types into the CSN, although they are just internal
259
+ // so we need to be robust against them now.
260
+ 'cds.UTCDateTime': 'cds.DateTime',
261
+ 'cds.UTCTimestamp': 'cds.Timestamp',
262
+ 'cds.LocalDate': 'cds.Date',
263
+ 'cds.LocalTime': 'cds.Time' ,
264
+ };
265
+
266
+ /**
267
+ * We removed some old SAP HANA types from the CSN and we now need to not
268
+ * detect them as changes.
269
+ *
270
+ * @param {string} before Type before
271
+ * @param {string} after Type after
272
+ * @returns {boolean}
273
+ */
274
+ function isSuperflousHanaTypeChange( before, after ) {
275
+ return superflousTypeChanges[before] ? superflousTypeChanges[before] === after : false;
276
+ }
277
+
278
+ /**
279
+ * If the element has one of the superflous types, do the change so we don't accidentally
280
+ * pass such an old type into the SQL renderer.
281
+ * @param {CSN.Element} element
282
+ * @returns {CSN.Element}
283
+ */
284
+ function remapType( element ) {
285
+ if(element?.type && superflousTypeChanges[element.type])
286
+ element.type = superflousTypeChanges[element.type];
287
+ return element;
253
288
  }
254
289
 
255
290
  /**
@@ -310,7 +345,7 @@ function addedConstraint(entity, constraint, constraintName, constraintType) {
310
345
 
311
346
  function changedElement(element, otherElement) {
312
347
  return {
313
- old: otherElement,
348
+ old: remapType(otherElement),
314
349
  new: element
315
350
  };
316
351
  }
@@ -159,7 +159,7 @@ function filterCsn( csn ) {
159
159
  },
160
160
  }, [ (artifact) => {
161
161
  forEachKey(artifact, (key) => {
162
- if (key.startsWith('@') && !annosToKeep[key])
162
+ if (key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key])
163
163
  delete artifact[key];
164
164
  });
165
165
  } ]);
@@ -21,7 +21,7 @@ optionProcessor
21
21
  .option(' --color <mode>', ['auto', 'always', 'never'])
22
22
  .option('-o, --out <dir>')
23
23
  .option(' --cds-home <dir>')
24
- .option(' --fuzzy-csn-error')
24
+ .option(' --module-lookup-directories <list>')
25
25
  .option(' --trace-fs')
26
26
  .option(' --error <id-list>')
27
27
  .option(' --warn <id-list>')
@@ -78,7 +78,8 @@ optionProcessor
78
78
  never:
79
79
  -o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
80
80
  --cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
81
- --fuzzy-csn-error Report free-style CSN properties as errors
81
+ --module-lookup-directories <list> Comma separated list of directories to look
82
+ for CDS modules. Default is 'node_modules/'.
82
83
  -- Indicate the end of options (helpful if source names start with "-")
83
84
 
84
85
  Type options
@@ -228,6 +229,7 @@ optionProcessor.command('O, toOdata')
228
229
  .option('-f, --odata-format <format>', ['flat', 'structured'])
229
230
  .option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
230
231
  .option('-s, --service-names <list>')
232
+ .option(' --fewer-localized-views')
231
233
  .help(`
232
234
  Usage: cdsc toOdata [options] <files...>
233
235
 
@@ -264,6 +266,8 @@ optionProcessor.command('O, toOdata')
264
266
  source (like "quoted", but using element names with dots)
265
267
  -s, --service-names <list> List of comma-separated service names to be rendered
266
268
  (default) empty, all services are rendered
269
+ --fewer-localized-views If set, the backends will not create localized convenience views for
270
+ those views, that only have an association to a localized entity/view.
267
271
  `);
268
272
 
269
273
  optionProcessor.command('C, toCdl')
@@ -296,6 +300,7 @@ optionProcessor.command('Q, toSql')
296
300
  .option(' --disable-hana-comments')
297
301
  .option(' --generated-by-comment')
298
302
  .option(' --better-sqlite-session-variables')
303
+ .option(' --fewer-localized-views')
299
304
  .help(`
300
305
  Usage: cdsc toSql [options] <files...>
301
306
 
@@ -347,7 +352,10 @@ optionProcessor.command('Q, toSql')
347
352
  --pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
348
353
  --disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
349
354
  --generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
350
- --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only active if sqlDialect is \`sqlite\`
355
+ --better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
356
+ active if sqlDialect is \`sqlite\`
357
+ --fewer-localized-views If set, the backends will not create localized convenience views for
358
+ those views, that only have an association to a localized entity/view.
351
359
 
352
360
  `);
353
361
 
@@ -425,6 +433,7 @@ optionProcessor.command('toCsn')
425
433
  .option(' --with-localized')
426
434
  .option(' --with-locations')
427
435
  .option(' --struct-xpr')
436
+ .option(' --fewer-localized-views')
428
437
  .help(`
429
438
  Usage: cdsc toCsn [options] <files...>
430
439
 
@@ -441,6 +450,9 @@ optionProcessor.command('toCsn')
441
450
  universal: in development (BETA)
442
451
  --with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
443
452
  $location is an object with 'file', 'line' and 'col' properties.
453
+ --fewer-localized-views If --with-locations and this option are set, the backends
454
+ will not create localized convenience views for those views,
455
+ that only have an association to a localized entity/view.
444
456
 
445
457
  Internal options (for testing only, may be changed/removed at any time)
446
458
  --with-localized Add localized convenience views to the CSN output.
@@ -303,6 +303,19 @@ function csnToCdl( csn, options ) {
303
303
  * @return {string}
304
304
  */
305
305
  function renderAnnotateStatement( ext, env ) {
306
+ // Special case: Super annotate has both "returns" and "elements".
307
+ // Render as separate `annotate`s, but keep the order.
308
+ if (ext.elements && ext.returns) {
309
+ const [ , second ] = Object.keys(ext).filter(key => key === 'elements' || key === 'returns');
310
+
311
+ // The first of 'elements' or 'returns' gets all other properties as well.
312
+ // The second only gets one property (itself).
313
+ let result = renderAnnotateStatement({ ...ext, [second]: undefined }, env);
314
+ result += renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
315
+
316
+ return result;
317
+ }
318
+
306
319
  // Top-level annotations of the artifact
307
320
  let result = renderAnnotationAssignmentsAndDocComment(ext, env);
308
321
  // Note: Not renderDefinitionReference, because we don't care if there
@@ -1236,10 +1249,14 @@ function csnToCdl( csn, options ) {
1236
1249
  function renderTypeOrAnnotation( artifactName, art, env, artType ) {
1237
1250
  let result = renderAnnotationAssignmentsAndDocComment(art, env);
1238
1251
  result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
1239
- if (art.includes)
1240
- result += renderIncludes(art.includes, env);
1241
1252
 
1242
1253
  const type = renderTypeReferenceAndProps(art, env);
1254
+ const isDirectStruct = type?.startsWith('{');
1255
+ if (art.includes?.length && isDirectStruct)
1256
+ // We can only render includes, if the type is directly structured. Otherwise, we would
1257
+ // render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
1258
+ result += renderIncludes(art.includes, env);
1259
+
1243
1260
  if (type) {
1244
1261
  // For nicer output, no colon if unnamed structure is used.
1245
1262
  result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
@@ -1248,7 +1265,15 @@ function csnToCdl( csn, options ) {
1248
1265
  msg.warning('syntax-missing-type', env.path, { name: artifactName },
1249
1266
  'Missing type for definition $(NAME); can\'t be represented in CDL');
1250
1267
  }
1268
+
1251
1269
  result += ';\n';
1270
+
1271
+ if (art.includes?.length && !isDirectStruct) {
1272
+ // If we're not a directly structured type, render the `includes` as `extend`
1273
+ // statements directly below the type definition.
1274
+ result += renderExtendStatement(artifactName, { includes: art.includes }, env);
1275
+ }
1276
+
1252
1277
  return result;
1253
1278
  }
1254
1279
 
@@ -1259,8 +1284,9 @@ function csnToCdl( csn, options ) {
1259
1284
  *
1260
1285
  * @param {CSN.Artifact} artifact
1261
1286
  * @param {CdlRenderEnvironment} env
1262
- * @param {object} [config={}] - `typeRefOnly` Whether to only render type defs, no arrayed/structured/enum.
1263
- * - `noAnnoCollect` Do not collect annotations of sub-elements.
1287
+ * @param {object} [config={}]
1288
+ * @param {boolean} [config.typeRefOnly] Whether to only render type defs, no arrayed/structured/enum.
1289
+ * @param {boolean} [config.noAnnoCollect] Do not collect annotations of sub-elements.
1264
1290
  * @return {string}
1265
1291
  */
1266
1292
  function renderTypeReferenceAndProps( artifact, env, config = {} ) {
@@ -459,7 +459,7 @@ function toSqlDdl( csn, options ) {
459
459
  const eltStrOld = getEltStr(def.old, eltName, 'migration');
460
460
  const eltStrNew = getEltStr(def.new, eltName, 'migration');
461
461
  if (eltStrNew === eltStrOld)
462
- return; // Prevent spurious migrations, where the column DDL does not change.
462
+ continue; // Prevent spurious migrations, where the column DDL does not change.
463
463
 
464
464
  const annosIncompat = [];
465
465
  sqlSnippetAnnos
@@ -1379,15 +1379,11 @@ function toSqlDdl( csn, options ) {
1379
1379
  * @returns {string} Rendered type
1380
1380
  */
1381
1381
  function renderBuiltinType( typeName ) {
1382
- const forHanaRenamesToEarly = {
1383
- 'cds.UTCDateTime': 'cds.DateTime',
1384
- 'cds.UTCTimestamp': 'cds.Timestamp',
1385
- 'cds.LocalDate': 'cds.Date',
1386
- 'cds.LocalTime': 'cds.Time',
1387
- };
1388
- const tName = forHanaRenamesToEarly[typeName] || typeName;
1389
1382
  const types = cdsToSqlTypes[options.sqlDialect];
1390
- return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
1383
+ const result = types && types[typeName] || cdsToSqlTypes.standard[typeName];
1384
+ if (!result && options.testMode)
1385
+ throw new CompilerAssertion(`Expected to find a type mapping for ${typeName}`);
1386
+ return result || 'CHAR';
1391
1387
  }
1392
1388
 
1393
1389
  /**
@@ -258,16 +258,14 @@ const cdsToSqlTypes = {
258
258
  'cds.DateTime': 'TIMESTAMP', // cds-compiler#2758
259
259
  'cds.Timestamp': 'TIMESTAMP',
260
260
  'cds.Boolean': 'BOOLEAN',
261
- 'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
262
261
  // (TODO: do it later; TODO: why not CHAR or at least VARCHAR?)
262
+ 'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
263
+ 'cds.hana.ST_POINT': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
264
+ 'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
263
265
  },
264
266
  hana: {
265
267
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
266
- 'cds.LocalDate': 'DATE',
267
- 'cds.LocalTime': 'TIME',
268
268
  'cds.DateTime': 'SECONDDATE',
269
- 'cds.UTCDateTime': 'SECONDDATE',
270
- 'cds.UTCTimestamp': 'TIMESTAMP',
271
269
  'cds.hana.ST_POINT': 'ST_POINT',
272
270
  'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
273
271
  },
@@ -275,7 +273,7 @@ const cdsToSqlTypes = {
275
273
  'cds.Date': 'DATE_TEXT',
276
274
  'cds.Time': 'TIME_TEXT',
277
275
  'cds.Timestamp': 'TIMESTAMP_TEXT',
278
- 'cds.DateTime': 'TIMESTAMP_TEXT',
276
+ 'cds.DateTime': 'DATETIME_TEXT',
279
277
  'cds.Binary': 'BINARY_BLOB',
280
278
  'cds.hana.BINARY': 'BINARY_BLOB',
281
279
  'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
@@ -310,6 +308,10 @@ const cdsToHdbcdsTypes = {
310
308
  'cds.Int16': 'cds.hana.SMALLINT',
311
309
  'cds.Int32': 'cds.Integer',
312
310
  'cds.Int64': 'cds.Integer64',
311
+ 'cds.Timestamp': 'cds.UTCTimestamp',
312
+ 'cds.DateTime': 'cds.UTCDateTime',
313
+ 'cds.Date': 'cds.LocalDate',
314
+ 'cds.Time': 'cds.LocalTime',
313
315
  };
314
316
 
315
317
  /**
@@ -61,7 +61,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
61
61
  * The customTransformers are applied here (and only here).
62
62
  *
63
63
  * @param {object | Array} _parent the thing that has _prop
64
- * @param {string|number} _prop the name of the current property
64
+ * @param {string|number} _prop the name of the current property or index
65
65
  * @param {object} node The value of node[_prop]
66
66
  */
67
67
  function standard( _parent, _prop, node ) {
@@ -86,7 +86,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
86
86
  !isPersistedOnDatabase(csn.definitions[member.target])) {
87
87
  info(null, path,
88
88
  { target: member.target, anno: '@cds.persistence.skip' },
89
- 'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
89
+ 'Association has been removed, as its target $(TARGET) is annotated with $(ANNO) and can\'t be rendered in SAP HANA SQL');
90
90
  member.$ignore = true;
91
91
  }
92
92
  }