@sap/cds-compiler 2.15.2 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +66 -1590
  2. package/bin/cdsc.js +42 -46
  3. package/doc/CHANGELOG_ARCHIVE.md +1592 -0
  4. package/doc/CHANGELOG_BETA.md +3 -4
  5. package/doc/CHANGELOG_DEPRECATED.md +35 -1
  6. package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
  7. package/doc/Versioning.md +20 -1
  8. package/lib/api/.eslintrc.json +2 -2
  9. package/lib/api/main.js +312 -143
  10. package/lib/api/options.js +15 -85
  11. package/lib/api/validate.js +6 -10
  12. package/lib/base/keywords.js +280 -110
  13. package/lib/base/message-registry.js +80 -24
  14. package/lib/base/messages.js +103 -52
  15. package/lib/base/model.js +44 -2
  16. package/lib/base/optionProcessorHelper.js +53 -21
  17. package/lib/checks/actionsFunctions.js +7 -5
  18. package/lib/checks/annotationsOData.js +1 -1
  19. package/lib/checks/cdsPersistence.js +1 -0
  20. package/lib/checks/elements.js +6 -6
  21. package/lib/checks/invalidTarget.js +1 -1
  22. package/lib/checks/nonexpandableStructured.js +1 -1
  23. package/lib/checks/queryNoDbArtifacts.js +2 -1
  24. package/lib/checks/selectItems.js +5 -1
  25. package/lib/checks/types.js +4 -2
  26. package/lib/checks/utils.js +2 -2
  27. package/lib/checks/validator.js +2 -1
  28. package/lib/compiler/assert-consistency.js +15 -10
  29. package/lib/compiler/builtins.js +127 -10
  30. package/lib/compiler/define.js +6 -4
  31. package/lib/compiler/extend.js +63 -12
  32. package/lib/compiler/finalize-parse-cdl.js +20 -9
  33. package/lib/compiler/index.js +25 -11
  34. package/lib/compiler/moduleLayers.js +7 -0
  35. package/lib/compiler/populate.js +16 -14
  36. package/lib/compiler/propagator.js +3 -3
  37. package/lib/compiler/resolve.js +194 -222
  38. package/lib/compiler/shared.js +56 -76
  39. package/lib/compiler/tweak-assocs.js +9 -10
  40. package/lib/compiler/utils.js +7 -2
  41. package/lib/edm/annotations/genericTranslation.js +60 -6
  42. package/lib/edm/annotations/preprocessAnnotations.js +10 -11
  43. package/lib/edm/csn2edm.js +39 -41
  44. package/lib/edm/edm.js +22 -15
  45. package/lib/edm/edmPreprocessor.js +66 -69
  46. package/lib/edm/edmUtils.js +12 -62
  47. package/lib/gen/Dictionary.json +8 -6
  48. package/lib/gen/language.checksum +1 -1
  49. package/lib/gen/language.interp +8 -30
  50. package/lib/gen/language.tokens +105 -114
  51. package/lib/gen/languageLexer.interp +1 -34
  52. package/lib/gen/languageLexer.js +889 -1007
  53. package/lib/gen/languageLexer.tokens +95 -106
  54. package/lib/gen/languageParser.js +20717 -22376
  55. package/lib/json/from-csn.js +73 -68
  56. package/lib/json/to-csn.js +13 -10
  57. package/lib/language/antlrParser.js +2 -2
  58. package/lib/language/docCommentParser.js +61 -38
  59. package/lib/language/errorStrategy.js +52 -40
  60. package/lib/language/genericAntlrParser.js +333 -259
  61. package/lib/language/language.g4 +600 -645
  62. package/lib/language/multiLineStringParser.js +14 -42
  63. package/lib/language/textUtils.js +44 -0
  64. package/lib/main.d.ts +27 -42
  65. package/lib/main.js +104 -81
  66. package/lib/model/csnRefs.js +2 -1
  67. package/lib/model/csnUtils.js +183 -285
  68. package/lib/model/revealInternalProperties.js +32 -9
  69. package/lib/model/sortViews.js +32 -31
  70. package/lib/optionProcessor.js +64 -57
  71. package/lib/render/.eslintrc.json +1 -1
  72. package/lib/render/DuplicateChecker.js +4 -7
  73. package/lib/render/manageConstraints.js +70 -2
  74. package/lib/render/toCdl.js +334 -339
  75. package/lib/render/toHdbcds.js +20 -16
  76. package/lib/render/toRename.js +44 -22
  77. package/lib/render/toSql.js +60 -54
  78. package/lib/render/utils/common.js +15 -1
  79. package/lib/render/utils/sql.js +20 -19
  80. package/lib/sql-identifier.js +6 -0
  81. package/lib/transform/db/.eslintrc.json +3 -2
  82. package/lib/transform/db/cdsPersistence.js +5 -15
  83. package/lib/transform/db/constraints.js +1 -1
  84. package/lib/transform/db/expansion.js +7 -6
  85. package/lib/transform/db/flattening.js +18 -19
  86. package/lib/transform/db/views.js +3 -3
  87. package/lib/transform/draft/.eslintrc.json +2 -2
  88. package/lib/transform/draft/db.js +6 -6
  89. package/lib/transform/draft/odata.js +6 -7
  90. package/lib/transform/forHanaNew.js +19 -22
  91. package/lib/transform/forOdataNew.js +13 -15
  92. package/lib/transform/localized.js +35 -25
  93. package/lib/transform/odata/toFinalBaseType.js +11 -9
  94. package/lib/transform/odata/typesExposure.js +3 -3
  95. package/lib/transform/odata/utils.js +1 -38
  96. package/lib/transform/transformUtilsNew.js +63 -77
  97. package/lib/transform/translateAssocsToJoins.js +6 -2
  98. package/lib/transform/universalCsn/.eslintrc.json +2 -2
  99. package/lib/transform/universalCsn/coreComputed.js +11 -6
  100. package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
  101. package/lib/utils/file.js +31 -21
  102. package/lib/utils/timetrace.js +20 -21
  103. package/package.json +34 -4
  104. package/share/messages/syntax-expected-integer.md +9 -8
  105. package/doc/ApiMigration.md +0 -237
  106. package/doc/CommandLineMigration.md +0 -58
  107. package/doc/ErrorMessages.md +0 -175
  108. package/doc/FioriAnnotations.md +0 -94
  109. package/doc/ODataTransformation.md +0 -273
  110. package/lib/backends.js +0 -529
  111. package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/api/main.js CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  'use strict';
4
4
 
5
- const prepareOptions = require('./options');
6
- const backends = require('../backends');
7
- const { setProp } = require('../base/model');
8
- const { emptyLocation } = require('../base/location');
9
- const { CompilationError, makeMessageFunction } = require('../base/messages');
10
- const { recompileX } = require('../compiler/index');
11
- const { compactModel, sortCsn } = require('../json/to-csn');
12
- const { transform4odataWithCsn } = require('../transform/forOdataNew.js');
13
- const { toSqlDdl } = require('../render/toSql');
14
- const { compareModels } = require('../modelCompare/compare');
15
- const sortViews = require('../model/sortViews');
16
- const { getResultingName } = require('../model/csnUtils');
17
- const { timetrace } = require('../utils/timetrace');
18
- const { transformForHanaWithCsn } = require('../transform/forHanaNew');
5
+ const prepareOptions = lazyload('./options');
6
+ const baseModel = lazyload('../base/model');
7
+ const location = lazyload('../base/location');
8
+ const messages = lazyload('../base/messages');
9
+ const compiler = lazyload('../compiler/index');
10
+ const toCsn = lazyload('../json/to-csn');
11
+ const forOdataNew = lazyload('../transform/forOdataNew.js');
12
+ const toSql = lazyload('../render/toSql');
13
+ const toCdl = require('../render/toCdl');
14
+ const modelCompare = lazyload('../modelCompare/compare');
15
+ const sortViews = lazyload('../model/sortViews');
16
+ const csnUtils = lazyload('../model/csnUtils');
17
+ const timetrace = lazyload('../utils/timetrace');
18
+ const forHanaNew = lazyload('../transform/forHanaNew');
19
19
 
20
20
  /**
21
21
  * Return the artifact name for use for the hdbresult object
@@ -26,17 +26,15 @@ const { transformForHanaWithCsn } = require('../transform/forHanaNew');
26
26
  * @returns {string} Name with . replaced as _ in some places
27
27
  */
28
28
  function getFileName(artifactName, csn) {
29
- return getResultingName(csn, 'quoted', artifactName);
29
+ return csnUtils.getResultingName(csn, 'quoted', artifactName);
30
30
  }
31
31
 
32
- const propertyToCheck = {
33
- odata: 'toOdata',
34
- };
35
-
36
32
  const { cloneCsnNonDict } = require('../model/csnUtils');
37
33
  const { toHdbcdsSource } = require('../render/toHdbcds');
38
34
  const { ModelError } = require('../base/error');
39
- const { forEach } = require('../utils/objectUtils');
35
+ const { forEach, forEachKey } = require('../utils/objectUtils');
36
+ const { checkRemovedDeprecatedFlags } = require('../base/model');
37
+ const { csn2edm, csn2edmAll } = require('../edm/csn2edm');
40
38
 
41
39
  const relevantGeneralOptions = [ /* for future generic options */ ];
42
40
  const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];
@@ -54,15 +52,14 @@ const warnAboutMismatchOdata = [ 'odataVersion' ];
54
52
  function attachTransformerCharacteristics(csn, transformation, options,
55
53
  relevantOptionNames, optionalOptionNames = []) {
56
54
  const relevant = {};
57
- const propName = propertyToCheck[transformation];
58
55
  for (const name of relevantOptionNames ) {
59
- if (options[propName][name] !== undefined)
60
- relevant[name] = options[propName][name];
56
+ if (options[name] !== undefined)
57
+ relevant[name] = options[name];
61
58
  }
62
59
 
63
60
  for (const name of optionalOptionNames ) {
64
- if (options[propName][name] !== undefined)
65
- relevant[name] = options[propName][name];
61
+ if (options[name] !== undefined)
62
+ relevant[name] = options[name];
66
63
  }
67
64
 
68
65
  for (const name of relevantGeneralOptions ) {
@@ -70,10 +67,10 @@ function attachTransformerCharacteristics(csn, transformation, options,
70
67
  relevant[name] = options[name];
71
68
  }
72
69
  if (!csn.meta)
73
- setProp(csn, 'meta', {});
70
+ baseModel.setProp(csn, 'meta', {});
74
71
 
75
- setProp(csn.meta, 'options', relevant);
76
- setProp(csn.meta, 'transformation', transformation);
72
+ baseModel.setProp(csn.meta, 'options', relevant);
73
+ baseModel.setProp(csn.meta, 'transformation', transformation);
77
74
  }
78
75
 
79
76
  /**
@@ -92,7 +89,7 @@ function checkPreTransformedCsn(csn, options, relevantOptionNames, warnAboutMism
92
89
  // Not able to check
93
90
  return;
94
91
  }
95
- const { error, warning, throwWithAnyError } = makeMessageFunction(csn, options, module);
92
+ const { error, warning, throwWithAnyError } = messages.makeMessageFunction(csn, options, module);
96
93
 
97
94
  for (const name of relevantOptionNames ) {
98
95
  if (options[name] !== csn.meta.options[name])
@@ -128,7 +125,7 @@ function isPreTransformed(csn, transformation) {
128
125
  * @returns {object} Return an oData-pre-processed CSN
129
126
  */
130
127
  function odataInternal(csn, internalOptions) {
131
- const oDataCsn = transform4odataWithCsn(csn, internalOptions);
128
+ const oDataCsn = forOdataNew.transform4odataWithCsn(csn, internalOptions);
132
129
  attachTransformerCharacteristics(oDataCsn, 'odata', internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
133
130
  return oDataCsn;
134
131
  }
@@ -150,13 +147,13 @@ function odata(csn, options = {}) {
150
147
  *
151
148
  * @param {object} csn CSN to process
152
149
  * @param {object} [externalOptions={}] Options
153
- * @returns {CDL} { <artifactName>: <CDL representation>, ...}
150
+ * @returns {object} { model: string, namespace: string, unappliedExtensions: string }
154
151
  */
155
152
  function cdl(csn, externalOptions = {}) {
156
153
  const internalOptions = prepareOptions.to.cdl(externalOptions);
157
- const { result } = backends.toCdlWithCsn(cloneCsnNonDict(csn, internalOptions), internalOptions);
158
- return result;
154
+ return toCdl.csnToCdl(cloneCsnNonDict(csn, internalOptions), internalOptions);
159
155
  }
156
+
160
157
  /**
161
158
  * Transform a CSN like to.sql
162
159
  *
@@ -168,8 +165,9 @@ function cdl(csn, externalOptions = {}) {
168
165
  function forSql(csn, options = {}) {
169
166
  const internalOptions = prepareOptions.to.sql(options);
170
167
  internalOptions.transformation = 'sql';
171
- internalOptions.toSql.csn = true;
172
- return backends.toSqlWithCsn(csn, internalOptions).csn;
168
+ const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.sql');
169
+
170
+ return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
173
171
  }
174
172
  /**
175
173
  * Transform a CSN like to.hdi
@@ -181,8 +179,10 @@ function forSql(csn, options = {}) {
181
179
  */
182
180
  function forHdi(csn, options = {}) {
183
181
  const internalOptions = prepareOptions.to.hdi(options);
184
- internalOptions.toSql.csn = true;
185
- return backends.toSqlWithCsn(csn, internalOptions).csn;
182
+ internalOptions.transformation = 'sql';
183
+ const transformedCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdi');
184
+
185
+ return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;
186
186
  }
187
187
  /**
188
188
  * Transform a CSN like to.hdbcds
@@ -196,9 +196,9 @@ function forHdbcds(csn, options = {}) {
196
196
  const internalOptions = prepareOptions.to.hdbcds(options);
197
197
  internalOptions.transformation = 'hdbcds';
198
198
 
199
- const hanaCsn = transformForHanaWithCsn(csn, internalOptions, 'to.hdbcds');
199
+ const hanaCsn = forHanaNew.transformForHanaWithCsn(csn, internalOptions, 'to.hdbcds');
200
200
 
201
- return internalOptions.testMode ? sortCsn(hanaCsn, internalOptions) : hanaCsn;
201
+ return internalOptions.testMode ? toCsn.sortCsn(hanaCsn, internalOptions) : hanaCsn;
202
202
  }
203
203
 
204
204
  /**
@@ -212,16 +212,64 @@ function sql(csn, options = {}) {
212
212
  const internalOptions = prepareOptions.to.sql(options);
213
213
  internalOptions.transformation = 'sql';
214
214
 
215
- // we need the CSN for view sorting
216
- internalOptions.toSql.csn = true;
215
+ const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
216
+
217
+ if (internalOptions.sqlDialect === 'postgres' && !baseModel.isBetaEnabled(internalOptions, 'postgres'))
218
+ error(null, null, 'sqlDialect: \'postgres\' is only supported with beta-flag \'postgres\'');
217
219
 
218
- const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);
220
+ // we need the CSN for view sorting
221
+ const transformedCsn = forSql(csn, options);
222
+ const sqls = toSql.toSqlDdl(transformedCsn, internalOptions);
219
223
 
220
- const result = sortViews(intermediateResult);
224
+ const result = sortViews({ csn: transformedCsn, sql: sqls.sql });
221
225
 
222
226
  return result.map(obj => obj.sql).filter(create => create);
223
227
  }
224
228
 
229
+ /**
230
+ * Render the given deltaCSN as SQL.
231
+ *
232
+ * @param {CSN.Model} csn A clean input CSN
233
+ * @param {CSN.Model} deltaCsn A CSN representing new entities and extensions
234
+ * @param {SqlOptions} [options={}] Options
235
+ * @returns {object} - definitions: An array of objects with all artifacts in the after-image. Each object specifies
236
+ * the artifact filename, the suffix, and the corresponding SQL statement to create
237
+ * the artifact.
238
+ * - deletions: An array of objects with the deleted artifacts. Each object specifies the artifact
239
+ * filename and the suffix.
240
+ * - migrations: An array of objects with the changed (migrated) artifacts. Each object specifies the
241
+ * artifact filename, the suffix, and the changeset (an array of changes, each specifying
242
+ * whether it incurs potential data loss, and its respective SQL statement(s), with
243
+ * multiple statements concatenated as a multi-line string in case the change e.g.
244
+ * consists of a column drop and add).
245
+ */
246
+ function mtx(csn, deltaCsn, options = {}) {
247
+ if (!baseModel.isBetaEnabled(options, 'to.mtx'))
248
+ throw new Error('to.mtx is only available with beta flag `to.mtx`');
249
+
250
+ const internalOptions = prepareOptions.to.sql(options);
251
+ // TODO: Use compiler.compileSources() when this function is moved to lib/main.js
252
+ const merged = toCsn.compactModel(compiler.compileSourcesX({ 'base.csn': csn, 'delta.csn': deltaCsn }), options);
253
+ const baseSql = forSql(csn, options);
254
+ const mergedSql = forSql(merged, options);
255
+ const diff = modelCompare.compareModels(baseSql, mergedSql, options);
256
+ // Delete artifacts that are already present in csn
257
+ Object.keys(baseSql.definitions).forEach((artifactName) => {
258
+ if (diff.definitions[artifactName]) // don't render again, but need info for primary key extension
259
+ diff.definitions[artifactName]['@cds.persistence.skip'] = true;
260
+ });
261
+
262
+ internalOptions.forHana = true;
263
+ internalOptions.beta.sqlExtensions = true;
264
+ const { deletions, migrations, ...additions } = toSql.toSqlDdl(diff, internalOptions);
265
+
266
+ return {
267
+ additions: createSqlDefinitions(additions, mergedSql),
268
+ deletions: createSqlDeletions(deletions, baseSql),
269
+ migrations: createSqlMigrations(migrations, mergedSql),
270
+ };
271
+ }
272
+
225
273
  /**
226
274
  * Process the given CSN into HDI artifacts.
227
275
  *
@@ -233,12 +281,8 @@ function hdi(csn, options = {}) {
233
281
  const internalOptions = prepareOptions.to.hdi(options);
234
282
 
235
283
  // we need the CSN for view sorting
236
- internalOptions.toSql.csn = true;
237
-
238
- const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);
239
-
240
- const sqlCSN = intermediateResult.csn;
241
- delete intermediateResult.csn;
284
+ const sqlCSN = forHdi(csn, options);
285
+ const sqls = toSql.toSqlDdl(sqlCSN, internalOptions);
242
286
 
243
287
  if (internalOptions.testMode) {
244
288
  // All this mapping is needed because sortViews crossmatches
@@ -246,7 +290,7 @@ function hdi(csn, options = {}) {
246
290
  // But we also need to return it with the correct file ending in the end
247
291
  // so remember and do lot's of mapping here.
248
292
 
249
- const flat = flattenResultStructure(intermediateResult);
293
+ const flat = flattenResultStructure(sqls);
250
294
 
251
295
  const nameMapping = Object.create(null);
252
296
  const sqlArtifactsWithCSNNamesToSort = Object.create(null);
@@ -277,7 +321,7 @@ function hdi(csn, options = {}) {
277
321
  return sorted;
278
322
  }
279
323
 
280
- return remapNames(flattenResultStructure(intermediateResult), sqlCSN, k => !k.endsWith('.hdbindex'));
324
+ return remapNames(flattenResultStructure(sqls), sqlCSN, k => !k.endsWith('.hdbindex'));
281
325
  }
282
326
  /**
283
327
  * Remap names so that they stay consistent between v1 and v2
@@ -343,91 +387,69 @@ function remapName(key, csn, filter = () => true) {
343
387
  * consists of a column drop and add).
344
388
  */
345
389
  function hdiMigration(csn, options, beforeImage) {
346
- /**
347
- * Swap arguments in case of inverted argument order.
348
- * This is for backward compatibility with @sap/cds@4.5.(2…3).
349
- *
350
- * @todo Remove in cds-compiler@2.x
351
- * @param {HdiOptions|CSN.Model} inputOptions Options or CSN image
352
- * @param {HdiOptions|CSN.Model} inputBeforeImage CSN image or options
353
- * @returns {Array} Array where the real options come first
354
- */
355
- function backwardCompatible(inputOptions, inputBeforeImage) {
356
- /**
357
- * Check whether the given argument is a CSN
358
- *
359
- * @param {object} arg Argument to verify
360
- * @returns {boolean} True if it is a CSN
361
- */
362
- function isBeforeImage(arg) {
363
- return arg === null || [ 'definitions', 'meta', '$version' ].some(key => key in arg);
364
- }
365
- return isBeforeImage(inputBeforeImage)
366
- ? [ inputOptions, inputBeforeImage ]
367
- : [ inputBeforeImage, inputOptions ];
368
- }
369
- [ options, beforeImage ] = backwardCompatible(options, beforeImage);
370
-
371
390
  const internalOptions = prepareOptions.to.hdi(options);
372
- internalOptions.toSql.csn = true;
373
391
 
374
392
  // Prepare after-image.
375
- // FIXME: Is this needed?
376
- // cloneCsnMessages(csn, options, internalOptions);
377
- const afterImage = backends.toSqlWithCsn(csn, internalOptions).csn;
393
+ const afterImage = forHdi(csn, options);
378
394
 
379
395
  // Compare both images.
380
- const diff = compareModels(beforeImage || afterImage, afterImage, internalOptions);
396
+ const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
381
397
 
382
398
  // Convert the diff to SQL.
383
399
  internalOptions.forHana = true; // Make it pass the SQL rendering
384
- const { deletions, migrations, ...hdbkinds } = toSqlDdl(diff, internalOptions);
400
+ const { deletions, migrations, ...hdbkinds } = toSql.toSqlDdl(diff, internalOptions);
385
401
 
386
402
  return {
387
403
  afterImage,
388
- definitions: createDefinitions(),
389
- deletions: createDeletions(),
390
- migrations: createMigrations(),
404
+ definitions: createSqlDefinitions(hdbkinds, afterImage),
405
+ deletions: createSqlDeletions(deletions, beforeImage),
406
+ migrations: createSqlMigrations(migrations, afterImage),
391
407
  };
408
+ }
392
409
 
393
- /**
394
- * From the given HDI artifacts, create the the correct result structure.
395
- *
396
- * @returns {object[]} Array of objects, each having: name, suffix and sql
397
- */
398
- function createDefinitions() {
399
- const result = [];
400
- forEach(hdbkinds, (kind, artifacts) => {
401
- const suffix = `.${ kind }`;
402
- forEach(artifacts, (name, sqlStatement) => {
403
- if ( kind !== 'hdbindex' )
404
- result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
405
- else
406
- result.push({ name, suffix, sql: sqlStatement });
407
- });
410
+ /**
411
+ * From the given SQLs, create the the correct result structure.
412
+ *
413
+ * @param {object} hdbkinds
414
+ * @param {CSN.Model} afterImage
415
+ * @returns {object[]} Array of objects, each having: name, suffix and sql
416
+ */
417
+ function createSqlDefinitions(hdbkinds, afterImage) {
418
+ const result = [];
419
+ forEach(hdbkinds, (kind, artifacts) => {
420
+ const suffix = `.${ kind }`;
421
+ forEach(artifacts, (name, sqlStatement) => {
422
+ if ( kind !== 'hdbindex' )
423
+ result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement });
424
+ else
425
+ result.push({ name, suffix, sql: sqlStatement });
408
426
  });
409
- return result;
410
- }
411
- /**
412
- * From the given deletions, create the correct result structure.
413
- *
414
- * @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
415
- */
416
- function createDeletions() {
417
- const result = [];
418
- forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
419
- return result;
420
- }
421
- /**
422
- * From the given migrations, create the correct result structure.
423
- *
424
- * @returns {object[]} Array of objects, each having: name, suffix and changeset.
425
- */
426
- function createMigrations() {
427
- const result = [];
428
- forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
429
- return result;
430
- }
427
+ });
428
+ return result;
429
+ }
430
+ /**
431
+ * From the given deletions, create the correct result structure.
432
+ *
433
+ * @param {object} deletions
434
+ * @param {CSN.Model} beforeImage
435
+ * @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
436
+ */
437
+ function createSqlDeletions(deletions, beforeImage) {
438
+ const result = [];
439
+ forEach(deletions, name => result.push({ name: getFileName(name, beforeImage), suffix: '.hdbtable' }));
440
+ return result;
441
+ }
442
+ /**
443
+ * From the given migrations, create the correct result structure.
444
+ *
445
+ * @param {object} migrations
446
+ * @param {CSN.Model} afterImage
447
+ * @returns {object[]} Array of objects, each having: name, suffix and changeset.
448
+ */
449
+ function createSqlMigrations(migrations, afterImage) {
450
+ const result = [];
451
+ forEach(migrations, (name, changeset) => result.push({ name: getFileName(name, afterImage), suffix: '.hdbmigrationtable', changeset }));
452
+ return result;
431
453
  }
432
454
 
433
455
  hdi.migration = hdiMigration;
@@ -440,14 +462,14 @@ hdi.migration = hdiMigration;
440
462
  * @returns {HDBCDS} { <filename>:<content>, ...}
441
463
  */
442
464
  function hdbcds(csn, options = {}) {
443
- timetrace.start('to.hdbcds');
465
+ timetrace.timetrace.start('to.hdbcds');
444
466
  const internalOptions = prepareOptions.to.hdbcds(options);
445
467
  internalOptions.transformation = 'hdbcds';
446
468
 
447
469
  const hanaCsn = forHdbcds(csn, internalOptions);
448
470
 
449
471
  const result = flattenResultStructure(toHdbcdsSource(hanaCsn, internalOptions));
450
- timetrace.stop();
472
+ timetrace.timetrace.stop();
451
473
  return result;
452
474
  }
453
475
  /**
@@ -469,11 +491,11 @@ function edm(csn, options = {}) {
469
491
  let servicesEdmj;
470
492
  if (isPreTransformed(csn, 'odata')) {
471
493
  checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
472
- servicesEdmj = backends.preparedCsnToEdm(csn, service, internalOptions);
494
+ servicesEdmj = preparedCsnToEdm(csn, service, internalOptions);
473
495
  }
474
496
  else {
475
497
  const oDataCsn = odataInternal(csn, internalOptions);
476
- servicesEdmj = backends.preparedCsnToEdm(oDataCsn, service, internalOptions);
498
+ servicesEdmj = preparedCsnToEdm(oDataCsn, service, internalOptions);
477
499
  }
478
500
  return servicesEdmj.edmj;
479
501
  }
@@ -489,9 +511,9 @@ edm.all = edmall;
489
511
  */
490
512
  function edmall(csn, options = {}) {
491
513
  const internalOptions = prepareOptions.to.edm(options);
492
- const { error } = makeMessageFunction(csn, internalOptions, 'for.odata');
514
+ const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
493
515
 
494
- if (internalOptions.version === 'v2')
516
+ if (internalOptions.odataVersion === 'v2')
495
517
  error(null, null, 'OData JSON output is not available for OData V2');
496
518
 
497
519
  const result = {};
@@ -503,13 +525,11 @@ function edmall(csn, options = {}) {
503
525
  else
504
526
  oDataCsn = odataInternal(csn, internalOptions);
505
527
 
506
- const servicesJson = backends.preparedCsnToEdmAll(oDataCsn, internalOptions);
528
+ const servicesJson = preparedCsnToEdmAll(oDataCsn, internalOptions);
507
529
  const services = servicesJson.edmj;
508
- for (const serviceName in services) {
509
- const lEdm = services[serviceName];
510
- // FIXME: Why only metadata_json - isn't this rather a 'combined_json' ? If so, rename it!
511
- result[serviceName] = lEdm;
512
- }
530
+ for (const serviceName in services)
531
+ result[serviceName] = services[serviceName];
532
+
513
533
  return result;
514
534
  }
515
535
  /**
@@ -531,11 +551,11 @@ function edmx(csn, options = {}) {
531
551
  let services;
532
552
  if (isPreTransformed(csn, 'odata')) {
533
553
  checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
534
- services = backends.preparedCsnToEdmx(csn, service, internalOptions);
554
+ services = preparedCsnToEdmx(csn, service, internalOptions);
535
555
  }
536
556
  else {
537
557
  const oDataCsn = odataInternal(csn, internalOptions);
538
- services = backends.preparedCsnToEdmx(oDataCsn, service, internalOptions);
558
+ services = preparedCsnToEdmx(oDataCsn, service, internalOptions);
539
559
  }
540
560
 
541
561
  return services.edmx;
@@ -562,7 +582,7 @@ function edmxall(csn, options = {}) {
562
582
  else
563
583
  oDataCsn = odataInternal(csn, internalOptions);
564
584
 
565
- const servicesEdmx = backends.preparedCsnToEdmxAll(oDataCsn, internalOptions);
585
+ const servicesEdmx = preparedCsnToEdmxAll(oDataCsn, internalOptions);
566
586
  const services = servicesEdmx.edmx;
567
587
  // Create annotations and metadata once per service
568
588
  for (const serviceName in services) {
@@ -573,6 +593,79 @@ function edmxall(csn, options = {}) {
573
593
  return result;
574
594
  }
575
595
 
596
+ /**
597
+ * Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
598
+ * using 'options'
599
+ *
600
+ * @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
601
+ * @param {string} service Service name to use. If you want all services, use preparedCsnToEdmxAll()
602
+ * @param {ODataOptions} options OData / EDMX specific options.
603
+ * @returns {object} Rendered EDMX string for the given service.
604
+ */
605
+ function preparedCsnToEdmx(csn, service, options) {
606
+ const e = csn2edm(csn, service, options);
607
+ return {
608
+ edmx: (e ? e.toXML('all') : undefined),
609
+ };
610
+ }
611
+
612
+ /**
613
+ * Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)
614
+ * using 'options'.
615
+ *
616
+ * @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
617
+ * @param {ODataOptions} options OData / EDMX specific options.
618
+ * @returns {object} Dictionary of rendered EDMX strings for each service.
619
+ */
620
+ function preparedCsnToEdmxAll(csn, options) {
621
+ const edmxResult = csn2edmAll(csn, options);
622
+ for (const service in edmxResult)
623
+ edmxResult[service] = edmxResult[service].toXML('all');
624
+
625
+ return {
626
+ edmx: edmxResult,
627
+ };
628
+ }
629
+
630
+ /**
631
+ * Generate edm-json for given 'service' based on 'csn' (new-style compact, already prepared for OData)
632
+ * using 'options'
633
+ *
634
+ * @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
635
+ * @param {string} service Service name for which EDMX should be rendered.
636
+ * @param {ODataOptions} options OData / EDMX specific options.
637
+ * @returns {object} Rendered EDM JSON object for of the given service.
638
+ */
639
+ function preparedCsnToEdm(csn, service, options) {
640
+ // Override OData version as edm json is always v4
641
+ options.odataVersion = 'v4';
642
+ const e = csn2edm(csn, service, options);
643
+ return {
644
+ edmj: (e ? e.toJSON() : undefined),
645
+ };
646
+ }
647
+
648
+ /**
649
+ * Generate edm-json for given 'service' based on 'csn' (new-style compact, already prepared for OData)
650
+ * using 'options'
651
+ *
652
+ * @param {CSN.Model} csn Input CSN model. Must be OData transformed CSN.
653
+ * @param {ODataOptions} options OData / EDMX specific options.
654
+ * @returns {object} Dictionary of rendered EDM JSON objects for each service.
655
+ */
656
+ function preparedCsnToEdmAll(csn, options) {
657
+ // Override OData version as edm json is always v4
658
+ options.odataVersion = 'v4';
659
+ const edmj = csn2edmAll(csn, options);
660
+ for (const service in edmj)
661
+ edmj[service] = edmj[service].toJSON();
662
+
663
+ return {
664
+ edmj,
665
+ };
666
+ }
667
+
668
+
576
669
  /**
577
670
  * Flatten the result structure to a flat map.
578
671
  *
@@ -606,7 +699,11 @@ module.exports = {
606
699
  for_sql: publishCsnProcessor(forSql, 'for.sql'),
607
700
  for_hdi: publishCsnProcessor(forHdi, 'for.hdi'),
608
701
  for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
609
- /** */
702
+ /** beta - WIP */
703
+ mtx: publishCsnProcessor(mtx, 'to.mtx'),
704
+ /** Deprecated, will be removed in cds-compiler@v4 */
705
+ preparedCsnToEdmx,
706
+ preparedCsnToEdm,
610
707
  };
611
708
 
612
709
 
@@ -639,29 +736,101 @@ function publishCsnProcessor( processor, _name ) {
639
736
  */
640
737
  function api( csn, options = {}, ...args ) {
641
738
  try {
739
+ if (options.deprecated) {
740
+ const messageFunctions = messages.makeMessageFunction(csn, options, 'api');
741
+ checkRemovedDeprecatedFlags( options, messageFunctions );
742
+ }
743
+ checkOutdatedOptions( options );
642
744
  return processor( csn, options, ...args );
643
745
  }
644
746
  catch (err) {
645
- if (err instanceof CompilationError || options.noRecompile || isPreTransformed(csn, 'odata')) // we cannot recompile a pre-transformed CSN
747
+ if (err instanceof messages.CompilationError || options.noRecompile || isPreTransformed(csn, 'odata')) // we cannot recompile a pre-transformed CSN
646
748
  throw err;
647
749
 
648
750
  if (options.testMode && !(err instanceof TypeError) &&
649
751
  !(err instanceof ModelError))
650
752
  throw err;
651
753
 
652
- const { info } = makeMessageFunction( csn, options, 'compile' );
653
- const msg = info( 'api-recompiled-csn', emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
754
+ const { info } = messages.makeMessageFunction( csn, options, 'compile' );
755
+ const msg = info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
654
756
  if (options.internalMsg)
655
757
  msg.error = err; // Attach original error
656
758
 
657
759
  // next line to be replaced by CSN parser call which reads the CSN object
658
- const xsn = recompileX(csn, options);
659
- const recompiledCsn = compactModel(xsn);
760
+ const xsn = compiler.recompileX(csn, options);
761
+ const recompiledCsn = toCsn.compactModel(xsn);
660
762
  return processor( recompiledCsn, options, ...args );
661
763
  }
662
764
  }
663
765
  }
664
766
 
767
+ // Note: No toCsn, because @sap/cds may still use it (2022-06-15)
768
+ const oldBackendOptionNames = [ 'toSql', 'toOdata', 'toHana', 'forHana' ];
769
+ /**
770
+ * Checks if outdated options are used and if so, throw a compiler error.
771
+ * These include:
772
+ * - magicVars (now variableReplacements)
773
+ * - toOdata/toSql/toHana/forHana -> now flat options
774
+ *
775
+ * @param {CSN.Options} options Backend options
776
+ */
777
+ function checkOutdatedOptions(options) {
778
+ const { error, throwWithError } = messages.makeMessageFunction(null, options, 'api');
779
+
780
+ // This error has been emitted once, we don't need to emit it again.
781
+ if (options.messages?.some(m => m.messageId === 'api-invalid-option')) {
782
+ throwWithError();
783
+ return;
784
+ }
785
+
786
+ for (const name of oldBackendOptionNames) {
787
+ if (typeof options[name] === 'object') // may be a boolean due to internal options
788
+ error('api-invalid-option', null, { '#': 'std', name });
789
+ }
790
+
791
+ if (options.magicVars)
792
+ error('api-invalid-option', null, { '#': 'magicVars' });
793
+
794
+ // Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
795
+ // forgot about user -> $user and locale -> $user.locale
796
+ if (options.variableReplacements?.user)
797
+ error('api-invalid-option', null, { '#': 'user' });
798
+ if (options.variableReplacements?.locale)
799
+ error('api-invalid-option', null, { '#': 'locale' });
800
+
801
+ forEachKey(options.variableReplacements || {}, (name) => {
802
+ if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
803
+ error('api-invalid-option', null, { '#': 'noDollar', name });
804
+ });
805
+
806
+ throwWithError();
807
+ }
808
+
809
+ /**
810
+ * Load the module on-demand and not immediately.
811
+ *
812
+ * @param {string} moduleName Name of the module to load - like with require
813
+ * @returns {object} A Proxy that handles the on-demand loading
814
+ */
815
+ function lazyload(moduleName) {
816
+ let module;
817
+ return new Proxy(((...args) => {
818
+ if (!module) // eslint-disable-next-line global-require
819
+ module = require(moduleName);
820
+
821
+ if (module.apply && typeof module.apply === 'function')
822
+ return module.apply(this, args);
823
+ return module; // for destructured calls
824
+ }), {
825
+ get(target, name) {
826
+ if (!module) // eslint-disable-next-line global-require
827
+ module = require(moduleName);
828
+
829
+ return module[name];
830
+ },
831
+ });
832
+ }
833
+
665
834
 
666
835
  /**
667
836
  * Option format used by the old API, where they are grouped thematically.