@sap/cds-compiler 3.4.2 → 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.
- package/CHANGELOG.md +8 -0
- package/bin/cdsc.js +3 -4
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/lib/api/main.js +6 -3
- package/lib/base/message-registry.js +60 -37
- package/lib/base/messages.js +7 -3
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -6
- package/lib/compiler/builtins.js +13 -13
- package/lib/compiler/checks.js +50 -33
- package/lib/compiler/define.js +9 -6
- package/lib/compiler/extend.js +71 -45
- package/lib/compiler/finalize-parse-cdl.js +3 -3
- package/lib/compiler/populate.js +16 -5
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +4 -4
- package/lib/compiler/utils.js +14 -0
- package/lib/edm/annotations/genericTranslation.js +68 -56
- package/lib/edm/csn2edm.js +214 -174
- package/lib/edm/edmAnnoPreprocessor.js +5 -5
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +1 -1
- package/lib/edm/edmUtils.js +3 -3
- package/lib/gen/Dictionary.json +176 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4776 -4513
- package/lib/json/from-csn.js +21 -16
- package/lib/json/to-csn.js +37 -41
- package/lib/language/.eslintrc.json +4 -1
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +54 -95
- package/lib/language/language.g4 +92 -66
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +2 -2
- package/lib/model/csnRefs.js +5 -0
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +99 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/toCdl.js +96 -127
- package/lib/render/toHdbcds.js +38 -35
- package/lib/render/toSql.js +75 -161
- package/lib/render/utils/common.js +133 -83
- package/lib/render/utils/delta.js +227 -0
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/forOdataNew.js +26 -19
- package/lib/transform/localized.js +9 -8
- package/lib/transform/odata/typesExposure.js +26 -4
- package/lib/transform/transformUtilsNew.js +15 -8
- package/package.json +2 -3
- package/lib/modelCompare/filter.js +0 -83
package/lib/model/csnRefs.js
CHANGED
|
@@ -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",
|
package/lib/render/toCdl.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
759
|
-
|
|
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
|
-
|
|
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
|
|
768
|
-
if (col.as && !col.inline && !col.expand
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
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
|
-
'
|
|
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
|
-
'
|
|
1866
|
+
'IS', 'IN', 'NOT', 'NULL', 'EXISTS',
|
|
1898
1867
|
// Antlr rule 'predicate'
|
|
1899
|
-
'
|
|
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.
|
|
1895
|
+
(additionalAllowedKeywords.includes(val.toUpperCase()) ||
|
|
1896
|
+
!functionExpressionOperatorsRequireParentheses.includes(val.toUpperCase())));
|
|
1928
1897
|
}
|
|
1929
1898
|
|
|
1930
1899
|
/**
|