@sap/cds-compiler 2.13.8 → 3.0.0
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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/render/toHdbcds.js
CHANGED
|
@@ -7,8 +7,8 @@ const {
|
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const keywords = require('../base/keywords');
|
|
9
9
|
const {
|
|
10
|
-
renderFunc,
|
|
11
|
-
hasHanaComment, getHanaComment,
|
|
10
|
+
renderFunc, getExpressionRenderer, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
|
|
11
|
+
hasHanaComment, getHanaComment, funcWithoutParen, getSqlSnippets,
|
|
12
12
|
} = require('./utils/common');
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint,
|
|
@@ -26,13 +26,23 @@ const $PROJECTION = '$projection';
|
|
|
26
26
|
const $SELF = '$self';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Get the comment and in addition escape \n so HANA CDS can handle it.
|
|
29
|
+
* Get the comment and in addition escape \n and `'` so SAP HANA CDS can handle it.
|
|
30
30
|
*
|
|
31
31
|
* @param {CSN.Artifact} obj
|
|
32
32
|
* @returns {string}
|
|
33
33
|
*/
|
|
34
34
|
function getEscapedHanaComment(obj) {
|
|
35
|
-
return getHanaComment(obj).replace(/\n/g, '\\n');
|
|
35
|
+
return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Render a string for HDBCDS, i.e. put it in quotes and escape single quotes.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} str
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function renderStringForHdbcds(str) {
|
|
45
|
+
return `'${str.replace(/'/g, '\'\'')}'`;
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
/**
|
|
@@ -56,9 +66,40 @@ function toHdbcdsSource(csn, options) {
|
|
|
56
66
|
const hdbcdsNames = options.sqlMapping === 'hdbcds';
|
|
57
67
|
|
|
58
68
|
const {
|
|
59
|
-
info, warning, error,
|
|
69
|
+
info, warning, error, throwWithAnyError,
|
|
60
70
|
} = makeMessageFunction(csn, options, 'to.hdbcds');
|
|
61
71
|
|
|
72
|
+
const renderExpr = getExpressionRenderer({
|
|
73
|
+
finalize: x => x,
|
|
74
|
+
explicitTypeCast(x, env) {
|
|
75
|
+
let typeRef = renderTypeReference(x.cast, env);
|
|
76
|
+
|
|
77
|
+
// inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
|
|
78
|
+
const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
|
|
79
|
+
if (hanaSqlType) {
|
|
80
|
+
const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
|
|
81
|
+
typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
|
|
82
|
+
}
|
|
83
|
+
return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
|
|
84
|
+
},
|
|
85
|
+
val: renderExpressionLiteral,
|
|
86
|
+
enum: x => `#${x['#']}`,
|
|
87
|
+
ref: renderExpressionRef,
|
|
88
|
+
aliasOnly(x, _env) {
|
|
89
|
+
return x.as;
|
|
90
|
+
},
|
|
91
|
+
windowFunction: renderExpressionFunc,
|
|
92
|
+
func: renderExpressionFunc,
|
|
93
|
+
xpr(x, env) {
|
|
94
|
+
if (this.nestedExpr && !x.cast)
|
|
95
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
96
|
+
|
|
97
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
98
|
+
},
|
|
99
|
+
SELECT: (x, env) => `(${renderQuery(x, false, increaseIndent(env))})`,
|
|
100
|
+
SET: (x, env) => `${renderQuery(x, false, increaseIndent(env))}`,
|
|
101
|
+
});
|
|
102
|
+
|
|
62
103
|
checkCSNVersion(csn, options);
|
|
63
104
|
|
|
64
105
|
const hdbcdsResult = Object.create(null);
|
|
@@ -117,7 +158,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
117
158
|
|
|
118
159
|
killList.forEach(fn => fn());
|
|
119
160
|
|
|
120
|
-
|
|
161
|
+
throwWithAnyError();
|
|
121
162
|
timetrace.stop();
|
|
122
163
|
return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
|
|
123
164
|
|
|
@@ -217,7 +258,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
217
258
|
*
|
|
218
259
|
* @param {string} containee Name of the contained artifact
|
|
219
260
|
* @param {string} contextName Name of the (grand?)parent context
|
|
220
|
-
* @returns {boolean} True if there is another context
|
|
261
|
+
* @returns {boolean} True if there is another context in between
|
|
221
262
|
*/
|
|
222
263
|
function isContainedInOtherContext(containee, contextName) {
|
|
223
264
|
const parts = containee.split('.');
|
|
@@ -280,7 +321,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
280
321
|
* Render a context or service. Return the resulting source string.
|
|
281
322
|
*
|
|
282
323
|
* If the context is shadowed by another entity, the context itself is not rendered,
|
|
283
|
-
* but any contained (and transitively contained)
|
|
324
|
+
* but any contained (and transitively contained) entities and views are.
|
|
284
325
|
*
|
|
285
326
|
* @param {string} artifactName Name of the context/service
|
|
286
327
|
* @param {CSN.Artifact} art Content of the context/service
|
|
@@ -687,19 +728,20 @@ function toHdbcdsSource(csn, options) {
|
|
|
687
728
|
* Return the resulting source string (no trailing LF).
|
|
688
729
|
*
|
|
689
730
|
* @param {object} col Column to render
|
|
731
|
+
* @param {CSN.Elements} elements where column exists
|
|
690
732
|
* @param {CdlRenderEnvironment} env Environment
|
|
691
|
-
* @param {CSN.Element} [element] Element (non-enum from subquery possibly) corresponding to the column ref
|
|
692
733
|
* @returns {string} Rendered column
|
|
693
734
|
*/
|
|
694
|
-
function renderViewColumn(col,
|
|
735
|
+
function renderViewColumn(col, elements, env ) {
|
|
695
736
|
// Annotations and column
|
|
696
737
|
let result = '';
|
|
697
738
|
|
|
698
|
-
const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
|
|
699
|
-
|
|
739
|
+
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
740
|
+
const element = elements[leaf];
|
|
700
741
|
|
|
701
|
-
|
|
702
|
-
|
|
742
|
+
// Render 'null as <alias>' only for database and if element is virtual
|
|
743
|
+
if (element && element.virtual) {
|
|
744
|
+
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
703
745
|
return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
|
|
704
746
|
}
|
|
705
747
|
else {
|
|
@@ -803,12 +845,12 @@ function toHdbcdsSource(csn, options) {
|
|
|
803
845
|
// Set operator, like UNION, INTERSECT, ...
|
|
804
846
|
if (query.SET) {
|
|
805
847
|
// First arg may be leading query
|
|
806
|
-
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements)}`;
|
|
848
|
+
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
|
|
807
849
|
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
808
850
|
if (query.SET.op) {
|
|
809
851
|
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
810
852
|
for (let i = 1; i < query.SET.args.length; i++)
|
|
811
|
-
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env, path.concat([ 'SET', 'args', i ]))}`;
|
|
853
|
+
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, env, path.concat([ 'SET', 'args', i ]), elements || query.SET.elements)}`;
|
|
812
854
|
}
|
|
813
855
|
result += ')';
|
|
814
856
|
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
@@ -843,7 +885,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
843
885
|
result += select.distinct ? ' distinct' : '';
|
|
844
886
|
if (select.columns) {
|
|
845
887
|
result += ' {\n';
|
|
846
|
-
result += `${select.columns.map(col => renderViewColumn(col,
|
|
888
|
+
result += `${select.columns.map(col => renderViewColumn(col, elements || select.elements, childEnv))
|
|
847
889
|
.filter(s => s !== '')
|
|
848
890
|
.join(',\n')}\n`;
|
|
849
891
|
result += `${env.indent}}`;
|
|
@@ -1109,239 +1151,148 @@ function toHdbcdsSource(csn, options) {
|
|
|
1109
1151
|
}
|
|
1110
1152
|
|
|
1111
1153
|
/**
|
|
1112
|
-
* Render
|
|
1113
|
-
* (no trailing LF, don't indent if inline)
|
|
1154
|
+
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1114
1155
|
*
|
|
1115
|
-
* @param {
|
|
1116
|
-
* @param {
|
|
1117
|
-
* @
|
|
1118
|
-
* @param {boolean} [inExpr=false] Whether the expression is already inside another expression
|
|
1119
|
-
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
1120
|
-
* Note: This is a hack for casts() inside groupBy.
|
|
1121
|
-
* @returns {string} Rendered expression
|
|
1156
|
+
* @param {string|object} s Path step
|
|
1157
|
+
* @param {number} idx Path position
|
|
1158
|
+
* @returns {string} Rendered path step
|
|
1122
1159
|
*/
|
|
1123
|
-
function
|
|
1124
|
-
//
|
|
1125
|
-
if (
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1160
|
+
function renderPathStep(s, idx, ref, env, inline) {
|
|
1161
|
+
// Simple id or absolute name
|
|
1162
|
+
if (typeof s === 'string') {
|
|
1163
|
+
// HANA-specific extra magic (should actually be in forHana)
|
|
1164
|
+
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1165
|
+
// (see FIXME at renderArtifact)
|
|
1166
|
+
if (idx === 0 && s === $SELF) {
|
|
1167
|
+
// do not produce USING for $projection
|
|
1168
|
+
if (env.currentArtifactName === $PROJECTION)
|
|
1169
|
+
return env.currentArtifactName;
|
|
1170
|
+
|
|
1171
|
+
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
1172
|
+
: renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
|
|
1173
|
+
}
|
|
1174
|
+
// HANA-specific translation of '$now' and '$user'
|
|
1175
|
+
if (s === '$now' && ref.length === 1)
|
|
1176
|
+
return 'CURRENT_TIMESTAMP';
|
|
1133
1177
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1178
|
+
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1179
|
+
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1136
1180
|
|
|
1181
|
+
// TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
|
|
1182
|
+
// Example: both views are correct in HANA CDS
|
|
1183
|
+
// entity E { key id: Integer; }
|
|
1184
|
+
// view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
|
|
1185
|
+
// view EVp as select from E as "$parameters" { "$parameters".id };
|
|
1137
1186
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
* @param {object} x Expression
|
|
1142
|
-
* @returns {string} Rendered expression object
|
|
1143
|
-
*/
|
|
1144
|
-
function renderExprObject(x) {
|
|
1145
|
-
if (x.list) {
|
|
1146
|
-
// set "inExpr" to false: treat list elements as new expressions
|
|
1147
|
-
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
1148
|
-
}
|
|
1149
|
-
else if (x.val !== undefined) {
|
|
1150
|
-
return renderExpressionLiteral(x);
|
|
1151
|
-
}
|
|
1152
|
-
// Enum symbol
|
|
1153
|
-
else if (x['#']) {
|
|
1154
|
-
return `#${x['#']}`;
|
|
1155
|
-
}
|
|
1156
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
1157
|
-
else if (x.ref) {
|
|
1158
|
-
return renderExpressionRef(x);
|
|
1159
|
-
}
|
|
1160
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
1161
|
-
else if (x.func) {
|
|
1162
|
-
// test for non-regular HANA identifier that needs to be quoted
|
|
1163
|
-
// identifier {letter}({letter_or_digit}|[#$])*
|
|
1164
|
-
// letter [A-Za-z_]
|
|
1165
|
-
// letter_or_digit [A-Za-z_0-9]
|
|
1166
|
-
|
|
1167
|
-
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1168
|
-
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1169
|
-
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1170
|
-
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1171
|
-
warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
|
|
1172
|
-
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1173
|
-
}
|
|
1174
|
-
// Nested expression
|
|
1175
|
-
else if (x.xpr) {
|
|
1176
|
-
if (inExpr && !x.cast)
|
|
1177
|
-
return `(${renderExpr(x.xpr, env, inline, true)})`;
|
|
1187
|
+
if (idx === 0 &&
|
|
1188
|
+
[ $SELF, $PROJECTION, '$session' ].includes(s))
|
|
1189
|
+
return s;
|
|
1178
1190
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1191
|
+
return formatIdentifier(s);
|
|
1192
|
+
}
|
|
1193
|
+
// ID with filters or parameters
|
|
1194
|
+
else if (typeof s === 'object') {
|
|
1195
|
+
// Sanity check
|
|
1196
|
+
if (!s.func && !s.id)
|
|
1197
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1198
|
+
|
|
1199
|
+
// Not really a path step but an object-like function call
|
|
1200
|
+
if (s.func)
|
|
1201
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1202
|
+
|
|
1203
|
+
// Path step, possibly with view parameters and/or filters
|
|
1204
|
+
let result = `${formatIdentifier(s.id)}`;
|
|
1205
|
+
if (s.args) {
|
|
1206
|
+
// View parameters
|
|
1207
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1185
1208
|
}
|
|
1186
|
-
|
|
1187
|
-
//
|
|
1188
|
-
|
|
1209
|
+
if (s.where) {
|
|
1210
|
+
// Filter, possibly with cardinality
|
|
1211
|
+
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1189
1212
|
}
|
|
1190
|
-
|
|
1191
|
-
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1213
|
+
return result;
|
|
1192
1214
|
}
|
|
1193
|
-
/**
|
|
1194
|
-
* @param {object} x Expression with a val and/or literal property
|
|
1195
|
-
* @returns {string} Rendered expression
|
|
1196
|
-
*/
|
|
1197
|
-
function renderExpressionLiteral(x) {
|
|
1198
|
-
// Literal value, possibly with explicit 'literal' property
|
|
1199
|
-
switch (x.literal || typeof x.val) {
|
|
1200
|
-
case 'number':
|
|
1201
|
-
case 'boolean':
|
|
1202
|
-
case 'null':
|
|
1203
|
-
return x.val;
|
|
1204
|
-
case 'x':
|
|
1205
|
-
case 'date':
|
|
1206
|
-
case 'time':
|
|
1207
|
-
case 'timestamp':
|
|
1208
|
-
return `${x.literal}'${x.val}'`;
|
|
1209
|
-
case 'string':
|
|
1210
|
-
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1211
|
-
case 'object':
|
|
1212
|
-
if (x.val === null)
|
|
1213
|
-
return 'null';
|
|
1214
1215
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1216
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1217
|
+
}
|
|
1220
1218
|
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1243
|
-
}
|
|
1244
|
-
else if (x.ref[1] === 'locale') {
|
|
1245
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
else if (x.ref[0] === '$at') {
|
|
1249
|
-
if (x.ref[1] === 'from')
|
|
1250
|
-
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1219
|
+
/**
|
|
1220
|
+
* @param {object} x Expression with a val and/or literal property
|
|
1221
|
+
* @returns {string} Rendered expression
|
|
1222
|
+
*/
|
|
1223
|
+
function renderExpressionLiteral(x) {
|
|
1224
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1225
|
+
switch (x.literal || typeof x.val) {
|
|
1226
|
+
case 'number':
|
|
1227
|
+
case 'boolean':
|
|
1228
|
+
case 'null':
|
|
1229
|
+
return x.val;
|
|
1230
|
+
case 'x':
|
|
1231
|
+
case 'date':
|
|
1232
|
+
case 'time':
|
|
1233
|
+
case 'timestamp':
|
|
1234
|
+
return `${x.literal}'${x.val}'`;
|
|
1235
|
+
case 'string':
|
|
1236
|
+
return renderStringForHdbcds(x.val);
|
|
1237
|
+
case 'object':
|
|
1238
|
+
if (x.val === null)
|
|
1239
|
+
return 'null';
|
|
1251
1240
|
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
}
|
|
1255
|
-
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1256
|
-
return `'${magicReplacement}'`;
|
|
1257
|
-
}
|
|
1258
|
-
}
|
|
1259
|
-
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref)).join('.')}`;
|
|
1241
|
+
// otherwise fall through to
|
|
1242
|
+
default:
|
|
1243
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1260
1244
|
}
|
|
1245
|
+
}
|
|
1261
1246
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
return `CAST(${value} AS ${typeRef})`;
|
|
1278
|
-
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Render the given expression x - which has a .func property
|
|
1249
|
+
*
|
|
1250
|
+
* @param {object} x
|
|
1251
|
+
* @param {CdlRenderEnvironment} env
|
|
1252
|
+
* @returns {string}
|
|
1253
|
+
*/
|
|
1254
|
+
function renderExpressionFunc(x, env) {
|
|
1255
|
+
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1256
|
+
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1257
|
+
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1258
|
+
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1259
|
+
warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
|
|
1260
|
+
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1261
|
+
}
|
|
1279
1262
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1292
|
-
// (see FIXME at renderArtifact)
|
|
1293
|
-
if (idx === 0 && s === $SELF) {
|
|
1294
|
-
// do not produce USING for $projection
|
|
1295
|
-
if (env.currentArtifactName === $PROJECTION)
|
|
1296
|
-
return env.currentArtifactName;
|
|
1297
|
-
|
|
1298
|
-
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
1299
|
-
: renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
|
|
1300
|
-
}
|
|
1301
|
-
// HANA-specific translation of '$now' and '$user'
|
|
1302
|
-
if (s === '$now' && ref.length === 1)
|
|
1303
|
-
return 'CURRENT_TIMESTAMP';
|
|
1263
|
+
/**
|
|
1264
|
+
* @param {object} x Expression with a ref property
|
|
1265
|
+
* @returns {string} Rendered expression
|
|
1266
|
+
* @todo no extra magic with x.param or x.global
|
|
1267
|
+
*/
|
|
1268
|
+
function renderExpressionRef(x, env) {
|
|
1269
|
+
if (!x.param && !x.global) {
|
|
1270
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1271
|
+
if (x.ref[0] === '$user') {
|
|
1272
|
+
if (magicReplacement !== null)
|
|
1273
|
+
return renderStringForHdbcds(magicReplacement);
|
|
1304
1274
|
|
|
1305
|
-
//
|
|
1306
|
-
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1275
|
+
// Note: The compiler already transforms $user into $user.id.
|
|
1307
1276
|
|
|
1308
|
-
//
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
// view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
|
|
1312
|
-
// view EVp as select from E as "$parameters" { "$parameters".id };
|
|
1277
|
+
// FIXME: this is all not enough: we might need an explicit select item alias (?)
|
|
1278
|
+
if (x.ref[1] === 'id')
|
|
1279
|
+
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1313
1280
|
|
|
1314
|
-
if (
|
|
1315
|
-
|
|
1316
|
-
|
|
1281
|
+
else if (x.ref[1] === 'locale')
|
|
1282
|
+
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1283
|
+
}
|
|
1284
|
+
else if (x.ref[0] === '$at') {
|
|
1285
|
+
if (x.ref[1] === 'from')
|
|
1286
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1317
1287
|
|
|
1318
|
-
|
|
1288
|
+
else if (x.ref[1] === 'to')
|
|
1289
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1319
1290
|
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
// Sanity check
|
|
1323
|
-
if (!s.func && !s.id)
|
|
1324
|
-
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1325
|
-
|
|
1326
|
-
// Not really a path step but an object-like function call
|
|
1327
|
-
if (s.func)
|
|
1328
|
-
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1329
|
-
|
|
1330
|
-
// Path step, possibly with view parameters and/or filters
|
|
1331
|
-
let result = `${formatIdentifier(s.id)}`;
|
|
1332
|
-
if (s.args) {
|
|
1333
|
-
// View parameters
|
|
1334
|
-
result += `(${renderArgs(s, ':', env)})`;
|
|
1335
|
-
}
|
|
1336
|
-
if (s.where) {
|
|
1337
|
-
// Filter, possibly with cardinality
|
|
1338
|
-
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1339
|
-
}
|
|
1340
|
-
return result;
|
|
1291
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1292
|
+
return renderStringForHdbcds(magicReplacement);
|
|
1341
1293
|
}
|
|
1342
|
-
|
|
1343
|
-
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1344
1294
|
}
|
|
1295
|
+
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
|
|
1345
1296
|
}
|
|
1346
1297
|
|
|
1347
1298
|
/**
|
|
@@ -1349,7 +1300,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1349
1300
|
* using 'sep' as separator for positional parameters
|
|
1350
1301
|
*
|
|
1351
1302
|
* @param {object} node with `args` to render
|
|
1352
|
-
* @param {string} sep
|
|
1303
|
+
* @param {string} sep Separator between arguments
|
|
1353
1304
|
* @param {CdlRenderEnvironment} env Environment
|
|
1354
1305
|
* @returns {string} Rendered arguments
|
|
1355
1306
|
*/
|
|
@@ -1692,7 +1643,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1692
1643
|
}
|
|
1693
1644
|
|
|
1694
1645
|
/**
|
|
1695
|
-
* Returns a copy of 'env' with increased indentation
|
|
1646
|
+
* Returns a copy of 'env' with increased indentation - also resets the name prefix
|
|
1696
1647
|
*
|
|
1697
1648
|
* @param {CdlRenderEnvironment} env Current environment
|
|
1698
1649
|
* @returns {CdlRenderEnvironment} New environment with increased indent
|
|
@@ -1713,15 +1664,15 @@ function toHdbcdsSource(csn, options) {
|
|
|
1713
1664
|
}
|
|
1714
1665
|
|
|
1715
1666
|
/**
|
|
1716
|
-
* Return an absolute path '
|
|
1667
|
+
* Return an absolute path 'absPath', with '::' inserted if required by naming strategy 'hdbcds',
|
|
1717
1668
|
* with appropriate "-quotes
|
|
1718
1669
|
*
|
|
1719
|
-
* @param {string}
|
|
1670
|
+
* @param {string} absPath Absolute path to quote
|
|
1720
1671
|
* @returns {string} Quoted path
|
|
1721
1672
|
*/
|
|
1722
|
-
function quoteAbsolutePathString(
|
|
1723
|
-
const namespace = getNamespace(csn,
|
|
1724
|
-
const resultingName = getResultingName(csn, options.sqlMapping,
|
|
1673
|
+
function quoteAbsolutePathString(absPath) {
|
|
1674
|
+
const namespace = getNamespace(csn, absPath);
|
|
1675
|
+
const resultingName = getResultingName(csn, options.sqlMapping, absPath);
|
|
1725
1676
|
|
|
1726
1677
|
if (hdbcdsNames && namespace)
|
|
1727
1678
|
return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
|
|
@@ -1738,14 +1689,13 @@ function toHdbcdsSource(csn, options) {
|
|
|
1738
1689
|
function quoteId(id) {
|
|
1739
1690
|
// Should only ever be called for real IDs (i.e. no dots inside)
|
|
1740
1691
|
if (id.indexOf('.') !== -1)
|
|
1741
|
-
throw new ModelError(id);
|
|
1692
|
+
throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
|
|
1742
1693
|
|
|
1743
1694
|
|
|
1744
|
-
switch (options.
|
|
1695
|
+
switch (options.sqlMapping) {
|
|
1745
1696
|
case 'plain':
|
|
1746
1697
|
return smartId(id, 'hdbcds');
|
|
1747
1698
|
case 'quoted':
|
|
1748
|
-
return delimitedId(id, 'hdbcds');
|
|
1749
1699
|
case 'hdbcds':
|
|
1750
1700
|
return delimitedId(id, 'hdbcds');
|
|
1751
1701
|
default:
|
|
@@ -1754,17 +1704,17 @@ function toHdbcdsSource(csn, options) {
|
|
|
1754
1704
|
}
|
|
1755
1705
|
|
|
1756
1706
|
/*
|
|
1757
|
-
* Return an absolute name '
|
|
1707
|
+
* Return an absolute name 'absName', with '::' inserted if required by naming strategy 'hdbcds', quoted
|
|
1758
1708
|
* as if it was a single identifier (required only for native USINGs)
|
|
1759
1709
|
*
|
|
1760
|
-
* @param {string}
|
|
1761
|
-
* @returns {string} Correctly quoted
|
|
1710
|
+
* @param {string} absName Absolute name
|
|
1711
|
+
* @returns {string} Correctly quoted absName
|
|
1762
1712
|
*/
|
|
1763
|
-
function quoteAbsoluteNameAsId(
|
|
1764
|
-
const resultingName = getResultingName(csn, options.sqlMapping,
|
|
1713
|
+
function quoteAbsoluteNameAsId(absName) {
|
|
1714
|
+
const resultingName = getResultingName(csn, options.sqlMapping, absName);
|
|
1765
1715
|
|
|
1766
1716
|
if (hdbcdsNames) {
|
|
1767
|
-
const namespace = getNamespace(csn,
|
|
1717
|
+
const namespace = getNamespace(csn, absName);
|
|
1768
1718
|
if (namespace)
|
|
1769
1719
|
return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
|
|
1770
1720
|
}
|
|
@@ -1778,7 +1728,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1778
1728
|
* @returns {string} Quoted/uppercased id
|
|
1779
1729
|
*/
|
|
1780
1730
|
function formatIdentifier(id) {
|
|
1781
|
-
id =
|
|
1731
|
+
id = plainNames ? id.toUpperCase() : id;
|
|
1782
1732
|
return quoteId(id);
|
|
1783
1733
|
}
|
|
1784
1734
|
|