@sap/cds-compiler 3.4.0 → 3.4.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 (60) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/bin/cdsc.js +3 -4
  3. package/bin/cdshi.js +19 -6
  4. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  5. package/lib/api/main.js +6 -3
  6. package/lib/base/message-registry.js +61 -38
  7. package/lib/base/messages.js +7 -3
  8. package/lib/checks/.eslintrc.json +2 -0
  9. package/lib/compiler/.eslintrc.json +4 -1
  10. package/lib/compiler/assert-consistency.js +8 -6
  11. package/lib/compiler/builtins.js +13 -13
  12. package/lib/compiler/checks.js +50 -33
  13. package/lib/compiler/define.js +9 -6
  14. package/lib/compiler/extend.js +83 -55
  15. package/lib/compiler/finalize-parse-cdl.js +3 -3
  16. package/lib/compiler/populate.js +16 -5
  17. package/lib/compiler/propagator.js +22 -29
  18. package/lib/compiler/resolve.js +2 -15
  19. package/lib/compiler/shared.js +4 -4
  20. package/lib/compiler/utils.js +14 -0
  21. package/lib/edm/annotations/genericTranslation.js +68 -56
  22. package/lib/edm/csn2edm.js +214 -174
  23. package/lib/edm/edmAnnoPreprocessor.js +5 -5
  24. package/lib/edm/edmInboundChecks.js +2 -2
  25. package/lib/edm/edmPreprocessor.js +1 -1
  26. package/lib/edm/edmUtils.js +3 -3
  27. package/lib/gen/Dictionary.json +176 -8
  28. package/lib/gen/language.checksum +1 -1
  29. package/lib/gen/language.interp +2 -1
  30. package/lib/gen/languageParser.js +4776 -4513
  31. package/lib/json/from-csn.js +21 -16
  32. package/lib/json/to-csn.js +37 -41
  33. package/lib/language/.eslintrc.json +4 -1
  34. package/lib/language/antlrParser.js +5 -2
  35. package/lib/language/docCommentParser.js +6 -6
  36. package/lib/language/errorStrategy.js +43 -23
  37. package/lib/language/genericAntlrParser.js +54 -95
  38. package/lib/language/language.g4 +92 -66
  39. package/lib/language/multiLineStringParser.js +2 -2
  40. package/lib/language/textUtils.js +2 -2
  41. package/lib/model/csnRefs.js +5 -0
  42. package/lib/modelCompare/compare.js +2 -2
  43. package/lib/modelCompare/utils/.eslintrc.json +22 -0
  44. package/lib/modelCompare/utils/filter.js +99 -0
  45. package/lib/render/.eslintrc.json +1 -0
  46. package/lib/render/toCdl.js +96 -127
  47. package/lib/render/toHdbcds.js +38 -35
  48. package/lib/render/toSql.js +75 -161
  49. package/lib/render/utils/common.js +133 -83
  50. package/lib/render/utils/delta.js +227 -0
  51. package/lib/transform/db/.eslintrc.json +2 -0
  52. package/lib/transform/draft/.eslintrc.json +1 -35
  53. package/lib/transform/forOdataNew.js +33 -22
  54. package/lib/transform/forRelationalDB.js +1 -1
  55. package/lib/transform/localized.js +9 -8
  56. package/lib/transform/odata/typesExposure.js +26 -4
  57. package/lib/transform/transformUtilsNew.js +15 -8
  58. package/lib/transform/universalCsn/universalCsnEnricher.js +1 -0
  59. package/package.json +2 -3
  60. package/lib/modelCompare/filter.js +0 -83
@@ -204,6 +204,7 @@ const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
204
204
  // * 'target': always follow target, including last ref item
205
205
  // * other (& not provided) = follow target if not last ref item
206
206
  const referenceSemantics = {
207
+ $init: { $initOnly: true },
207
208
  type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458
208
209
  includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
209
210
  target: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
@@ -532,6 +533,8 @@ function csnRefs( csn, universalReady ) {
532
533
  return resolvePath( path, main.params[head], main, 'param' );
533
534
 
534
535
  const semantics = referenceSemantics[refCtx] || {};
536
+ if (semantics.$initOnly)
537
+ return undefined;
535
538
  if (semantics.dynamic === 'global' || ref.global)
536
539
  return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc );
537
540
 
@@ -979,6 +982,8 @@ function analyseCsnPath( csnPath, csn, resolve ) {
979
982
  else if (artifactProperties.includes( String(prop) )) {
980
983
  if (refCtx === 'target' || refCtx === 'targetAspect') { // with 'elements'
981
984
  // $self refers to the anonymous aspect
985
+ if (resolve)
986
+ resolve( '', '$init', main );
982
987
  main = obj;
983
988
  art = obj;
984
989
  parent = null;
@@ -49,12 +49,12 @@ function validateCsnVersions(beforeModel, afterModel, options) {
49
49
 
50
50
  if (!beforeVersionParts || beforeVersionParts.length < 2) {
51
51
  const { error, throwWithAnyError } = makeMessageFunction(beforeModel, options, 'modelCompare');
52
- error(null, null, { version: beforeVersion }, 'Invalid CSN version: $(VERSION)');
52
+ error(null, null, { version: beforeVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
53
53
  throwWithAnyError();
54
54
  }
55
55
  if (!afterVersionParts || afterVersionParts.length < 2) {
56
56
  const { error, throwWithAnyError } = makeMessageFunction(afterModel, options, 'modelCompare');
57
- error(null, null, { version: afterVersion }, 'Invalid CSN version: $(VERSION)');
57
+ error(null, null, { version: afterVersion || 'undefined' }, 'Invalid CSN version: $(VERSION)');
58
58
  throwWithAnyError();
59
59
  }
60
60
  if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
@@ -0,0 +1,22 @@
1
+ {
2
+ "plugins": ["sonarjs"],
3
+ "extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended"],
4
+ "rules": {
5
+ "prefer-const": "error",
6
+ "quotes": ["error", "single", "avoid-escape"],
7
+ "prefer-template": "error",
8
+ "no-trailing-spaces": "error",
9
+ "sonarjs/cognitive-complexity": "off",
10
+ "sonarjs/no-duplicate-string": ["off"],
11
+ "sonarjs/no-nested-template-literals": "off",
12
+ "template-curly-spacing":["error", "never"],
13
+ "class-methods-use-this": "off",
14
+ // Who cares - just very whiny and in the way
15
+ "complexity": "off",
16
+ "max-len": "off",
17
+ "no-shadow": "warn"
18
+ },
19
+ "env": {
20
+ "es2020": true
21
+ }
22
+ }
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ // Each db has some changes that it can and cannot represent, or that cause problems only on that specific db
4
+ // In this file, we define rules for each db-dialect to detect and act on these cases.
5
+
6
+ const { forEach } = require('../../utils/objectUtils');
7
+ const { isPersistedAsTable } = require('../../model/csnUtils');
8
+
9
+ function isKey(element) {
10
+ return element.key;
11
+ }
12
+
13
+ module.exports = {
14
+ sqlite: getFilterObject(
15
+ 'sqlite',
16
+ function forEachExtension(extend, name, element, error) {
17
+ if (isKey(element)) { // Key must not be extended
18
+ error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite' }, 'Added element $(ID) is a primary key change and will not work with dialect $(NAME)');
19
+ }
20
+ },
21
+ function forEachMigration(migrate, name, migration, change, error) {
22
+ const newIsKey = isKey(migration.new);
23
+ const oldIsKey = isKey(migration.old);
24
+ if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) { // Turned into key or key was removed
25
+ error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite' }, 'Changed element $(ID) is a primary key change and will not work with dialect $(NAME)');
26
+ }
27
+ else { // Ignore simple migrations
28
+ delete change[name];
29
+ }
30
+ }
31
+ ),
32
+ postgres: getFilterObject('postgres'),
33
+ h2: getFilterObject('h2'),
34
+ };
35
+
36
+ function getFilterObject(dialect, extensionCallback, migrationCallback) {
37
+ return {
38
+ // will be called with a simple Array.forEach
39
+ extension: ({ elements, extend }, error) => {
40
+ if (extensionCallback) {
41
+ forEach(elements, (name, element) => {
42
+ extensionCallback(extend, name, element, error);
43
+ });
44
+ }
45
+ },
46
+ // will be called with a Array.map, as we need to filter "change" for SQLite
47
+ migration: ({ change, migrate, remove }, error) => {
48
+ forEach(remove, (name) => {
49
+ error('def-unsupported-element-drop', [ 'definitions', migrate, 'elements', name ], {}, 'Dropping elements is not supported');
50
+ });
51
+
52
+ forEach(change, (name, migration) => {
53
+ const loc = [ 'definitions', migrate, 'elements', name ];
54
+ if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
55
+ error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported');
56
+ else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale)
57
+ error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported');
58
+ else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale)
59
+ error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
60
+ else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
61
+ error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported');
62
+ else if (migrationCallback)
63
+ migrationCallback(migrate, name, migration, change, error);
64
+
65
+ // TODO: precision/scale growth
66
+ });
67
+ },
68
+ deletion: ([ artifactName, artifact ], error) => {
69
+ if (isPersistedAsTable(artifact))
70
+ error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported');
71
+ },
72
+ };
73
+ }
74
+
75
+ const defaultAllowedTypeChanges = {
76
+ // Integer types
77
+ 'cds.hana.tinyint': [ 'cds.UInt8', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64' ],
78
+ 'cds.UInt8': [ 'cds.hana.tinyint', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64' ],
79
+ 'cds.Int16': [ 'cds.hana.smallint', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64' ],
80
+ 'cds.hana.smallint': [ 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64' ],
81
+ 'cds.Int32': [ 'cds.Integer', 'cds.Int64', 'cds.Integer64' ],
82
+ 'cds.Integer': [ 'cds.Int32', 'cds.Int64', 'cds.Integer64' ],
83
+ 'cds.Integer64': [ 'cds.Int64' ],
84
+ 'cds.Int64': [ 'cds.Integer64' ],
85
+ };
86
+
87
+ const allowedTypeChanges = {
88
+ sqlite: defaultAllowedTypeChanges,
89
+ postgres: defaultAllowedTypeChanges,
90
+ h2: defaultAllowedTypeChanges,
91
+ };
92
+
93
+ function typeChangeIsNotCompatible(dialect, before, after) {
94
+ if (allowedTypeChanges[dialect]) {
95
+ const map = allowedTypeChanges[dialect];
96
+ return map[before] ? !map[before].includes(after) : true;
97
+ }
98
+ return true;
99
+ }
@@ -10,6 +10,7 @@
10
10
  "sonarjs/no-duplicate-string": ["off"],
11
11
  "sonarjs/no-nested-template-literals": "off",
12
12
  "template-curly-spacing":["error", "never"],
13
+ "class-methods-use-this": "off",
13
14
  // Who cares - just very whiny and in the way
14
15
  "complexity": "off",
15
16
  "max-len": "off",
@@ -2,7 +2,7 @@
2
2
 
3
3
  const keywords = require('../base/keywords');
4
4
  const { isBuiltinType, generatedByCompilerVersion, getNormalizedQuery } = require('../model/csnUtils');
5
- const { findElement, getExpressionRenderer } = require('./utils/common');
5
+ const { findElement, createExpressionRenderer, withoutCast } = require('./utils/common');
6
6
  const { escapeString, hasUnpairedUnicodeSurrogate } = require('./utils/stringEscapes');
7
7
  const { checkCSNVersion } = require('../json/csnVersion');
8
8
  const { timetrace } = require('../utils/timetrace');
@@ -26,7 +26,7 @@ const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
26
26
  */
27
27
  function csnToCdl(csn, options) {
28
28
  timetrace.start('CDL rendering');
29
- let _renderExpr = null;
29
+ const exprRenderer = createCdlExpressionRenderer();
30
30
 
31
31
  if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
32
32
  enrichUniversalCsn(csn, options);
@@ -635,7 +635,7 @@ function csnToCdl(csn, options) {
635
635
  result += renderJoinCardinality(source.cardinality);
636
636
  result += `join ${renderViewSource(source.args[i], env)}`;
637
637
  if (source.on)
638
- result += ` on ${renderExpr(source.on, env, true, true)}`;
638
+ result += ` on ${exprRenderer.renderExpr(source.on, env)}`;
639
639
  }
640
640
  result += ')';
641
641
  return result;
@@ -687,13 +687,13 @@ function csnToCdl(csn, options) {
687
687
 
688
688
  if (path.ref[0].where) {
689
689
  const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
690
- const expr = renderExpr(path.ref[0].where, env, true, true);
690
+ const expr = exprRenderer.renderExpr(path.ref[0].where, env);
691
691
  result += `[${cardinality}${expr}]`;
692
692
  }
693
693
 
694
694
  // Add any path steps (possibly with parameters and filters) that may follow after that
695
695
  if (path.ref.length > 1)
696
- result += `:${renderExpr({ ref: path.ref.slice(1) }, env)}`;
696
+ result += `:${exprRenderer.renderExpr({ ref: path.ref.slice(1) }, env)}`;
697
697
 
698
698
  return result;
699
699
  }
@@ -755,17 +755,19 @@ function csnToCdl(csn, options) {
755
755
  result += env.indent;
756
756
 
757
757
  // only if column is virtual, keyword virtual was present in the source text
758
- if (col.virtual)
759
- result += 'virtual ';
758
+ result += col.virtual ? 'virtual ' : '';
759
+ result += col.key ? 'key ' : '';
760
760
 
761
- const key = col.key ? 'key ' : '';
762
761
  // Use special rendering for .expand/.inline - renderExpr cannot easily handle some cases
763
- result += key + ((col.expand || col.inline) ? renderInlineExpand(col, env) : renderExpr(col, env, true));
762
+ if (col.expand || col.inline)
763
+ result += renderInlineExpand(col, env);
764
+ else
765
+ result += exprRenderer.renderExpr(withoutCast(col), env);
764
766
 
765
767
  // Alias for inline/expand is already handled by renderInlineExpand
766
768
  // A new association (cast with `type` and `target`) uses `as` as its primary name, not alias.
767
- const isNewAssociation = col.cast && col.cast.type && col.cast.target;
768
- if (col.as && !col.inline && !col.expand && !isNewAssociation)
769
+ const isNewAssociation = col.cast?.type && col.cast.target;
770
+ if (!isNewAssociation && col.as && !col.inline && !col.expand)
769
771
  result += ` as ${quoteIdIfRequired(col.as)}`;
770
772
 
771
773
  // Explicit type provided for the view element?
@@ -789,7 +791,7 @@ function csnToCdl(csn, options) {
789
791
  */
790
792
  function renderInlineExpand(obj, env) {
791
793
  // No expression to render for { * } as alias
792
- let result = (obj.as && obj.expand && !obj.ref) ? '' : renderExpr(obj, env);
794
+ let result = (obj.as && obj.expand && !obj.ref) ? '' : exprRenderer.renderExpr(withoutCast(obj), env);
793
795
 
794
796
  // s as alias { * }
795
797
  if (obj.as && (obj.ref || obj.xpr || obj.val !== undefined || obj.func !== undefined))
@@ -937,13 +939,13 @@ function csnToCdl(csn, options) {
937
939
  result += renderActionsAndFunctions(query, env);
938
940
 
939
941
  if (select.where)
940
- result += `${continueIndent(result, env)}where ${renderExpr(select.where, env, true, true)}`;
942
+ result += `${continueIndent(result, env)}where ${exprRenderer.renderExpr(select.where, env)}`;
941
943
 
942
944
  if (select.groupBy)
943
- result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
945
+ result += `${continueIndent(result, env)}group by ${select.groupBy.map(expr => exprRenderer.renderExpr(expr, env)).join(', ')}`;
944
946
 
945
947
  if (select.having)
946
- result += `${continueIndent(result, env)}having ${renderExpr(select.having, env, true, true)}`;
948
+ result += `${continueIndent(result, env)}having ${exprRenderer.renderExpr(select.having, env)}`;
947
949
 
948
950
  if (select.orderBy)
949
951
  result += `${continueIndent(result, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
@@ -979,11 +981,11 @@ function csnToCdl(csn, options) {
979
981
  function renderLimit(limit, limitEnv) {
980
982
  let limitStr = '';
981
983
  if (limit.rows !== undefined)
982
- limitStr += `limit ${renderExpr(limit.rows, limitEnv)}`;
984
+ limitStr += `limit ${exprRenderer.renderExpr(limit.rows, limitEnv)}`;
983
985
 
984
986
  if (limit.offset !== undefined) {
985
987
  const offsetIndent = (limitStr === '') ? '' : `\n${increaseIndent(limitEnv).indent}`;
986
- limitStr += `${offsetIndent}offset ${renderExpr(limit.offset, limitEnv)}`;
988
+ limitStr += `${offsetIndent}offset ${exprRenderer.renderExpr(limit.offset, limitEnv)}`;
987
989
  }
988
990
  return limitStr;
989
991
  }
@@ -1021,7 +1023,7 @@ function csnToCdl(csn, options) {
1021
1023
  * @return {string}
1022
1024
  */
1023
1025
  function renderOrderByEntry(entry, env) {
1024
- let result = renderAnnotationAssignmentsAndDocComment(entry, env) + renderExpr(entry, env, true, false, true);
1026
+ let result = renderAnnotationAssignmentsAndDocComment(entry, env) + exprRenderer.renderExpr(entry, env);
1025
1027
  if (entry.sort)
1026
1028
  result += ` ${entry.sort}`;
1027
1029
 
@@ -1195,8 +1197,7 @@ function csnToCdl(csn, options) {
1195
1197
 
1196
1198
  // ON-condition (if any)
1197
1199
  if (artifact.on)
1198
- result += ` on ${renderExpr(artifact.on, env, true, true)}`;
1199
-
1200
+ result += ` on ${exprRenderer.renderExpr(artifact.on, env)}`;
1200
1201
 
1201
1202
  // Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
1202
1203
  if (artifact.keys && !artifact.on)
@@ -1237,7 +1238,7 @@ function csnToCdl(csn, options) {
1237
1238
  if (!isTypeDef) // NOT NULL not possible for not-arrayed type definitions
1238
1239
  result += renderNullability(artifact);
1239
1240
  if (artifact.default)
1240
- result += ` default ${renderExpr(artifact.default, env)}`;
1241
+ result += ` default ${exprRenderer.renderExpr(artifact.default, env)}`;
1241
1242
 
1242
1243
  return result;
1243
1244
  }
@@ -1252,7 +1253,7 @@ function csnToCdl(csn, options) {
1252
1253
  function renderRedirectedTo(art, env) {
1253
1254
  let result = `redirected to ${quotePathIfRequired(art.target)}`;
1254
1255
  if (art.on)
1255
- result += ` on ${renderExpr(art.on, env, true, true)}`;
1256
+ result += ` on ${exprRenderer.renderExpr(art.on, env)}`;
1256
1257
  else if (art.keys)
1257
1258
  result += ` { ${Object.keys(art.keys).map(name => renderForeignKey(art.keys[name], env)).join(', ')} }`;
1258
1259
  return result;
@@ -1312,7 +1313,7 @@ function csnToCdl(csn, options) {
1312
1313
  result += renderAnnotationAssignmentsAndDocComment(enumValue, env);
1313
1314
  result += env.indent + quoteIdIfRequired(name);
1314
1315
  if (enumValue.val !== undefined)
1315
- result += ` = ${renderExpr(enumValue, env)}`;
1316
+ result += ` = ${exprRenderer.renderExpr(enumValue, env)}`;
1316
1317
  else if (enumValue['#'] !== undefined)
1317
1318
  result += ` = #${enumValue['#']}`;
1318
1319
  result += ';\n';
@@ -1373,11 +1374,10 @@ function csnToCdl(csn, options) {
1373
1374
  *
1374
1375
  * @param {string|object} s
1375
1376
  * @param {number} idx
1376
- * @param {boolean} inline
1377
1377
  * @param {object} env
1378
1378
  * @returns {string}
1379
1379
  */
1380
- function renderPathStep(s, idx, inline, env) {
1380
+ function renderPathStep(s, idx, env) {
1381
1381
  // Simple id or absolute name
1382
1382
  if (typeof s === 'string') {
1383
1383
  // In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
@@ -1405,7 +1405,7 @@ function csnToCdl(csn, options) {
1405
1405
  if (s.where) {
1406
1406
  // Filter, possibly with cardinality
1407
1407
  const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
1408
- const expr = renderExpr(s.where, env, inline, true);
1408
+ const expr = exprRenderer.renderExpr(s.where, env);
1409
1409
  result += `[${cardinality}${expr}]`;
1410
1410
  }
1411
1411
 
@@ -1479,9 +1479,11 @@ function csnToCdl(csn, options) {
1479
1479
  */
1480
1480
  function renderArgument(arg, env, additionalKeywords = []) {
1481
1481
  // If the argument is a xpr with e.g. `=`, it may require parentheses.
1482
- // For nested xpr, `renderExpr()` will already add parentheses.
1482
+ // For nested xpr, `exprRenderer.renderExpr()` will already add parentheses.
1483
1483
  env = { ...env, additionalKeywords };
1484
- return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords), true);
1484
+ if (isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords))
1485
+ return exprRenderer.renderExpr(arg, env);
1486
+ return exprRenderer.renderSubExpr(arg, env);
1485
1487
  }
1486
1488
 
1487
1489
  /**
@@ -1575,7 +1577,7 @@ function csnToCdl(csn, options) {
1575
1577
  */
1576
1578
  function renderForeignKey(fKey, env) {
1577
1579
  const alias = fKey.as ? (` as ${fKey.as}`) : '';
1578
- return renderExpr(fKey, env) + alias;
1580
+ return exprRenderer.renderExpr(fKey, env) + alias;
1579
1581
  }
1580
1582
 
1581
1583
  /**
@@ -1675,100 +1677,67 @@ function csnToCdl(csn, options) {
1675
1677
  return quotePathIfRequired(artifactName);
1676
1678
  }
1677
1679
 
1678
- /**
1679
- * Render a function expression.
1680
- *
1681
- * @param {object} obj Object with .func and optionally .args
1682
- * @param {CdlRenderEnvironment} env
1683
- * @returns {string}
1684
- */
1685
- function renderFuncExpr( obj, env ) {
1686
- if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
1687
- return obj.func;
1688
- const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
1689
- return `${name}(${renderArguments( obj, '=>', env )})`;
1690
- }
1691
-
1692
- /**
1693
- * Render an expression.
1694
- *
1695
- * @param {any} expr
1696
- * @param {CdlRenderEnvironment} exprEnv
1697
- * @param {boolean} [isInline]
1698
- * @param {boolean} [isNestedExpr]
1699
- * @param {boolean} [alwaysRenderCast]
1700
- * @returns {string}
1701
- */
1702
- function renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast) {
1703
- if (!_renderExpr) {
1704
- _renderExpr = getExpressionRenderer({
1705
- finalize(x) {
1706
- return x;
1707
- },
1708
- explicitTypeCast(x, env) {
1709
- const typeRef = renderTypeReferenceAndProps(x.cast, env, { typeRefOnly: true, noAnnoCollect: true });
1710
- const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1711
- return `cast(${renderArgument(arg, env)} as ${typeRef})`;
1712
- },
1713
- val(x, env) {
1714
- // Literal value, possibly with explicit 'literal' property
1715
- switch (x.literal || typeof x.val) {
1716
- case 'number':
1717
- case 'boolean':
1718
- case 'null':
1719
- return x.val;
1720
- case 'x':
1721
- case 'date':
1722
- case 'time':
1723
- case 'timestamp':
1724
- return `${x.literal}'${x.val}'`;
1725
- case 'string':
1726
- return renderString(x.val, env);
1727
- case 'object':
1728
- if (x.val === null)
1729
- return 'null';
1680
+ function createCdlExpressionRenderer() {
1681
+ return createExpressionRenderer({
1682
+ finalize: x => x,
1683
+ typeCast(x) {
1684
+ const typeRef = renderTypeReferenceAndProps(x.cast, this.env, { typeRefOnly: true, noAnnoCollect: true });
1685
+ const arg = { ...x, cast: null }; // "arg" without cast to avoid recursion.
1686
+ return `cast(${renderArgument(arg, this.env)} as ${typeRef})`;
1687
+ },
1688
+ val(x) {
1689
+ // Literal value, possibly with explicit 'literal' property
1690
+ switch (x.literal || typeof x.val) {
1691
+ case 'number':
1692
+ case 'boolean':
1693
+ case 'null':
1694
+ return x.val;
1695
+ case 'x':
1696
+ case 'date':
1697
+ case 'time':
1698
+ case 'timestamp':
1699
+ return `${x.literal}'${x.val}'`;
1700
+ case 'string':
1701
+ return renderString(x.val, this.env);
1702
+ case 'object':
1703
+ if (x.val === null)
1704
+ return 'null';
1730
1705
  // otherwise fall through to
1731
- default:
1732
- throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1733
- }
1734
- },
1735
- aliasOnly(x, _env) {
1736
- return x.as;
1737
- },
1738
- enum(x) {
1739
- return `#${x['#']}`;
1740
- },
1741
- ref(x, env) {
1742
- const { inline } = this;
1743
- return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, inline, env)).join('.')}`;
1744
- },
1745
- windowFunction(x, env) {
1746
- const funcDef = renderFuncExpr(x, env);
1747
- const windowFunctionOperator = x.xpr.shift(); // OVER ...
1748
- return `${funcDef} ${windowFunctionOperator} ( ${renderExpr(x.xpr, env, true)} )`;
1749
- },
1750
- func(x, env) {
1751
- return renderFuncExpr(x, env);
1752
- },
1753
- xpr(x, env) {
1754
- if (this.nestedExpr && !x.cast || x.xpr.some(s => s === 'exists'))
1755
- return `(${renderExpr(x.xpr, env, this.inline, true)})`;
1756
- return renderExpr(x.xpr, env, this.inline, true);
1757
- },
1758
- // Sub-queries in expressions need to be in parentheses, otherwise
1759
- // left-associativity of UNIONS may result in different results.
1760
- // For example: `select from E where id in (select from E union select from E);`:
1761
- // Without parentheses, it would be different query.
1762
- SET(x, env) {
1763
- return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1764
- },
1765
- SELECT(x, env) {
1766
- return `(${renderQuery(x, false, 'view', increaseIndent(env))})`;
1767
- },
1768
- });
1769
- }
1770
-
1771
- return _renderExpr(expr, exprEnv, isInline, isNestedExpr, alwaysRenderCast);
1706
+ default:
1707
+ throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
1708
+ }
1709
+ },
1710
+ aliasOnly: x => x.as,
1711
+ enum: x => `#${x['#']}`,
1712
+ ref(x) {
1713
+ return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, this.env)).join('.')}`;
1714
+ },
1715
+ windowFunction(x) {
1716
+ const funcDef = this.func(x);
1717
+ return `${funcDef} ${this.renderExpr(x.xpr)}`; // xpr[0] is 'over'
1718
+ },
1719
+ func(x) {
1720
+ if (keywords.cdl_functions.includes(x.func.toUpperCase()))
1721
+ return x.func;
1722
+ const name = identifierRegex.test(x.func) ? x.func : quote(x.func);
1723
+ return `${name}(${renderArguments( x, '=>', this.env )})`;
1724
+ },
1725
+ xpr(x) {
1726
+ if (this.isNestedXpr && !x.cast || x.xpr.some(s => s === 'exists'))
1727
+ return `(${this.renderExpr(x.xpr)})`;
1728
+ return this.renderExpr(x.xpr);
1729
+ },
1730
+ // Sub-queries in expressions need to be in parentheses, otherwise
1731
+ // left-associativity of UNIONS may result in different results.
1732
+ // For example: `select from E where id in (select from E union select from E);`:
1733
+ // Without parentheses, it would be different query.
1734
+ SET(x) {
1735
+ return `(${renderQuery(x, false, 'view', increaseIndent(this.env))})`;
1736
+ },
1737
+ SELECT(x) {
1738
+ return `(${renderQuery(x, false, 'view', increaseIndent(this.env))})`;
1739
+ },
1740
+ });
1772
1741
  }
1773
1742
  }
1774
1743
 
@@ -1887,16 +1856,16 @@ function requiresQuotingForCdl(id, additionalKeywords) {
1887
1856
 
1888
1857
  const functionExpressionOperatorsRequireParentheses = [
1889
1858
  // Antlr rule 'condition', 'conditionAnd'
1890
- 'and', 'or',
1859
+ 'AND', 'OR',
1891
1860
 
1892
1861
  // Antlr rule 'conditionTerm'
1893
1862
  '=', '<>', '>', '>=', '<', '<=', '!=',
1894
1863
  // These are not forbidden, since they must be preceded by one of the comparators above.
1895
1864
  // 'any', 'some', 'all',
1896
1865
 
1897
- 'is', 'in', 'not', 'null', 'exists',
1866
+ 'IS', 'IN', 'NOT', 'NULL', 'EXISTS',
1898
1867
  // Antlr rule 'predicate'
1899
- 'between', 'like', 'escape',
1868
+ 'BETWEEN', 'LIKE', 'ESCAPE',
1900
1869
  ];
1901
1870
 
1902
1871
  /**
@@ -1923,8 +1892,8 @@ const functionExpressionOperatorsRequireParentheses = [
1923
1892
  */
1924
1893
  function isSimpleFunctionExpression(xpr, additionalAllowedKeywords = []) {
1925
1894
  return !xpr || xpr.every(val => typeof val !== 'string' ||
1926
- (additionalAllowedKeywords.includes(val) ||
1927
- !functionExpressionOperatorsRequireParentheses.includes(val.toLowerCase())));
1895
+ (additionalAllowedKeywords.includes(val.toUpperCase()) ||
1896
+ !functionExpressionOperatorsRequireParentheses.includes(val.toUpperCase())));
1928
1897
  }
1929
1898
 
1930
1899
  /**