@sap/cds-compiler 2.13.8 → 2.15.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.
- package/CHANGELOG.md +109 -4
- package/bin/cdsc.js +112 -37
- package/lib/api/main.js +20 -22
- package/lib/api/options.js +2 -3
- package/lib/api/validate.js +6 -6
- package/lib/base/message-registry.js +89 -14
- package/lib/base/messages.js +85 -64
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/validator.js +2 -4
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +11 -0
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +59 -11
- package/lib/compiler/extend.js +20 -3
- package/lib/compiler/finalize-parse-cdl.js +26 -20
- package/lib/compiler/index.js +75 -26
- package/lib/compiler/populate.js +6 -5
- package/lib/compiler/propagator.js +4 -1
- package/lib/compiler/resolve.js +104 -16
- package/lib/compiler/shared.js +61 -27
- package/lib/compiler/tweak-assocs.js +7 -1
- package/lib/edm/annotations/genericTranslation.js +33 -15
- package/lib/edm/csn2edm.js +216 -98
- package/lib/edm/edm.js +298 -225
- package/lib/edm/edmPreprocessor.js +486 -415
- package/lib/edm/edmUtils.js +22 -22
- package/lib/gen/Dictionary.json +90 -16
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +3 -1
- package/lib/gen/languageParser.js +4636 -4368
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +3 -2
- package/lib/json/to-csn.js +0 -2
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +47 -2
- package/lib/language/language.g4 +59 -27
- package/lib/main.d.ts +19 -1
- package/lib/main.js +6 -0
- package/lib/model/csnRefs.js +33 -6
- package/lib/model/csnUtils.js +193 -75
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +2 -2
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +62 -26
- package/lib/render/toCdl.js +844 -679
- package/lib/render/toHdbcds.js +189 -243
- package/lib/render/toSql.js +180 -198
- package/lib/render/utils/common.js +131 -15
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/constraints.js +3 -1
- package/lib/transform/db/expansion.js +15 -10
- package/lib/transform/db/flattening.js +94 -64
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +6 -3
- package/lib/transform/forHanaNew.js +43 -26
- package/lib/transform/forOdataNew.js +43 -42
- package/lib/transform/localized.js +12 -7
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +145 -197
- package/lib/transform/transformUtilsNew.js +9 -12
- package/lib/transform/translateAssocsToJoins.js +1 -1
- package/lib/transform/universalCsn/coreComputed.js +5 -3
- package/lib/transform/universalCsn/universalCsnEnricher.js +27 -5
- package/lib/utils/moduleResolve.js +13 -6
- package/package.json +1 -1
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- 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,13 @@ 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
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -56,9 +56,40 @@ function toHdbcdsSource(csn, options) {
|
|
|
56
56
|
const hdbcdsNames = options.sqlMapping === 'hdbcds';
|
|
57
57
|
|
|
58
58
|
const {
|
|
59
|
-
info, warning, error,
|
|
59
|
+
info, warning, error, throwWithAnyError,
|
|
60
60
|
} = makeMessageFunction(csn, options, 'to.hdbcds');
|
|
61
61
|
|
|
62
|
+
const renderExpr = getExpressionRenderer({
|
|
63
|
+
finalize: x => x,
|
|
64
|
+
explicitTypeCast(x, env) {
|
|
65
|
+
let typeRef = renderTypeReference(x.cast, env);
|
|
66
|
+
|
|
67
|
+
// inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
|
|
68
|
+
const hanaSqlType = cdsToSqlTypes.hana[x.cast.type] || cdsToSqlTypes.standard[x.cast.type];
|
|
69
|
+
if (hanaSqlType) {
|
|
70
|
+
const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
|
|
71
|
+
typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
|
|
72
|
+
}
|
|
73
|
+
return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
|
|
74
|
+
},
|
|
75
|
+
val: renderExpressionLiteral,
|
|
76
|
+
enum: x => `#${x['#']}`,
|
|
77
|
+
ref: renderExpressionRef,
|
|
78
|
+
aliasOnly(x, _env) {
|
|
79
|
+
return x.as;
|
|
80
|
+
},
|
|
81
|
+
windowFunction: renderExpressionFunc,
|
|
82
|
+
func: renderExpressionFunc,
|
|
83
|
+
xpr(x, env) {
|
|
84
|
+
if (this.nestedExpr && !x.cast)
|
|
85
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
86
|
+
|
|
87
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
88
|
+
},
|
|
89
|
+
SELECT: (x, env) => `(${renderQuery(x, false, increaseIndent(env))})`,
|
|
90
|
+
SET: (x, env) => `${renderQuery(x, false, increaseIndent(env))}`,
|
|
91
|
+
});
|
|
92
|
+
|
|
62
93
|
checkCSNVersion(csn, options);
|
|
63
94
|
|
|
64
95
|
const hdbcdsResult = Object.create(null);
|
|
@@ -117,7 +148,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
117
148
|
|
|
118
149
|
killList.forEach(fn => fn());
|
|
119
150
|
|
|
120
|
-
|
|
151
|
+
throwWithAnyError();
|
|
121
152
|
timetrace.stop();
|
|
122
153
|
return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
|
|
123
154
|
|
|
@@ -217,7 +248,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
217
248
|
*
|
|
218
249
|
* @param {string} containee Name of the contained artifact
|
|
219
250
|
* @param {string} contextName Name of the (grand?)parent context
|
|
220
|
-
* @returns {boolean} True if there is another context
|
|
251
|
+
* @returns {boolean} True if there is another context in between
|
|
221
252
|
*/
|
|
222
253
|
function isContainedInOtherContext(containee, contextName) {
|
|
223
254
|
const parts = containee.split('.');
|
|
@@ -280,7 +311,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
280
311
|
* Render a context or service. Return the resulting source string.
|
|
281
312
|
*
|
|
282
313
|
* If the context is shadowed by another entity, the context itself is not rendered,
|
|
283
|
-
* but any contained (and transitively contained)
|
|
314
|
+
* but any contained (and transitively contained) entities and views are.
|
|
284
315
|
*
|
|
285
316
|
* @param {string} artifactName Name of the context/service
|
|
286
317
|
* @param {CSN.Artifact} art Content of the context/service
|
|
@@ -687,18 +718,19 @@ function toHdbcdsSource(csn, options) {
|
|
|
687
718
|
* Return the resulting source string (no trailing LF).
|
|
688
719
|
*
|
|
689
720
|
* @param {object} col Column to render
|
|
721
|
+
* @param {CSN.Elements} elements where column exists
|
|
690
722
|
* @param {CdlRenderEnvironment} env Environment
|
|
691
|
-
* @param {CSN.Element} [element] Element (non-enum from subquery possibly) corresponding to the column ref
|
|
692
723
|
* @returns {string} Rendered column
|
|
693
724
|
*/
|
|
694
|
-
function renderViewColumn(col,
|
|
725
|
+
function renderViewColumn(col, elements, env ) {
|
|
695
726
|
// Annotations and column
|
|
696
727
|
let result = '';
|
|
697
728
|
|
|
698
|
-
const leaf = col.as || col.ref && col.ref[col.ref.length - 1];
|
|
699
|
-
|
|
729
|
+
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
730
|
+
const element = elements[leaf];
|
|
700
731
|
|
|
701
|
-
|
|
732
|
+
// Render 'null as <alias>' only for database and if element is virtual
|
|
733
|
+
if (element && element.virtual) {
|
|
702
734
|
if (isDeprecatedEnabled(options, 'renderVirtualElements'))
|
|
703
735
|
return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
|
|
704
736
|
}
|
|
@@ -803,12 +835,12 @@ function toHdbcdsSource(csn, options) {
|
|
|
803
835
|
// Set operator, like UNION, INTERSECT, ...
|
|
804
836
|
if (query.SET) {
|
|
805
837
|
// First arg may be leading query
|
|
806
|
-
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements)}`;
|
|
838
|
+
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, env, path.concat([ 'SET', 'args', 0 ]), elements || query.SET.elements)}`;
|
|
807
839
|
// FIXME: Clarify if set operators can be n-ary (assuming binary here)
|
|
808
840
|
if (query.SET.op) {
|
|
809
841
|
// Loop over all other arguments, i.e. for A UNION B UNION C UNION D ...
|
|
810
842
|
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 ]))}`;
|
|
843
|
+
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
844
|
}
|
|
813
845
|
result += ')';
|
|
814
846
|
// Set operation may also have an ORDER BY and LIMIT/OFFSET (in contrast to the ones belonging to
|
|
@@ -843,7 +875,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
843
875
|
result += select.distinct ? ' distinct' : '';
|
|
844
876
|
if (select.columns) {
|
|
845
877
|
result += ' {\n';
|
|
846
|
-
result += `${select.columns.map(col => renderViewColumn(col,
|
|
878
|
+
result += `${select.columns.map(col => renderViewColumn(col, elements || select.elements, childEnv))
|
|
847
879
|
.filter(s => s !== '')
|
|
848
880
|
.join(',\n')}\n`;
|
|
849
881
|
result += `${env.indent}}`;
|
|
@@ -1109,239 +1141,154 @@ function toHdbcdsSource(csn, options) {
|
|
|
1109
1141
|
}
|
|
1110
1142
|
|
|
1111
1143
|
/**
|
|
1112
|
-
* Render
|
|
1113
|
-
* (no trailing LF, don't indent if inline)
|
|
1144
|
+
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1114
1145
|
*
|
|
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
|
|
1146
|
+
* @param {string|object} s Path step
|
|
1147
|
+
* @param {number} idx Path position
|
|
1148
|
+
* @returns {string} Rendered path step
|
|
1122
1149
|
*/
|
|
1123
|
-
function
|
|
1124
|
-
//
|
|
1125
|
-
if (
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1150
|
+
function renderPathStep(s, idx, ref, env, inline) {
|
|
1151
|
+
// Simple id or absolute name
|
|
1152
|
+
if (typeof s === 'string') {
|
|
1153
|
+
// HANA-specific extra magic (should actually be in forHana)
|
|
1154
|
+
// In HANA, we replace leading $self by the absolute name of the current artifact
|
|
1155
|
+
// (see FIXME at renderArtifact)
|
|
1156
|
+
if (idx === 0 && s === $SELF) {
|
|
1157
|
+
// do not produce USING for $projection
|
|
1158
|
+
if (env.currentArtifactName === $PROJECTION)
|
|
1159
|
+
return env.currentArtifactName;
|
|
1160
|
+
|
|
1161
|
+
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
1162
|
+
: renderAbsoluteNameWithQuotes(env.currentArtifactName, env);
|
|
1163
|
+
}
|
|
1164
|
+
// HANA-specific translation of '$now' and '$user'
|
|
1165
|
+
if (s === '$now' && ref.length === 1)
|
|
1166
|
+
return 'CURRENT_TIMESTAMP';
|
|
1133
1167
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1168
|
+
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1169
|
+
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1136
1170
|
|
|
1171
|
+
// TODO: quote $parameters if it doesn't reference a parameter, this requires knowledge about the kind
|
|
1172
|
+
// Example: both views are correct in HANA CDS
|
|
1173
|
+
// entity E { key id: Integer; }
|
|
1174
|
+
// view EV with parameters P1: Integer as select from E { id, $parameters.P1 };
|
|
1175
|
+
// view EVp as select from E as "$parameters" { "$parameters".id };
|
|
1137
1176
|
|
|
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)})`;
|
|
1178
|
-
|
|
1179
|
-
return renderExpr(x.xpr, env, inline, true);
|
|
1180
|
-
}
|
|
1181
|
-
// Sub-select
|
|
1182
|
-
else if (x.SELECT) {
|
|
1183
|
-
// renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
|
|
1184
|
-
return `(${renderQuery(x, false, increaseIndent(env))})`;
|
|
1185
|
-
}
|
|
1186
|
-
else if (x.SET) {
|
|
1187
|
-
// renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
|
|
1188
|
-
return `${renderQuery(x, false, increaseIndent(env))}`;
|
|
1189
|
-
}
|
|
1177
|
+
if (idx === 0 &&
|
|
1178
|
+
[ $SELF, $PROJECTION, '$session' ].includes(s))
|
|
1179
|
+
return s;
|
|
1190
1180
|
|
|
1191
|
-
|
|
1181
|
+
return formatIdentifier(s);
|
|
1192
1182
|
}
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
// otherwise fall through to
|
|
1216
|
-
default:
|
|
1217
|
-
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1183
|
+
// ID with filters or parameters
|
|
1184
|
+
else if (typeof s === 'object') {
|
|
1185
|
+
// Sanity check
|
|
1186
|
+
if (!s.func && !s.id)
|
|
1187
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1188
|
+
|
|
1189
|
+
// Not really a path step but an object-like function call
|
|
1190
|
+
if (s.func)
|
|
1191
|
+
return `${s.func}(${renderArgs(s, '=>', env)})`;
|
|
1192
|
+
|
|
1193
|
+
// Path step, possibly with view parameters and/or filters
|
|
1194
|
+
let result = `${formatIdentifier(s.id)}`;
|
|
1195
|
+
if (s.args) {
|
|
1196
|
+
// View parameters
|
|
1197
|
+
result += `(${renderArgs(s, ':', env)})`;
|
|
1218
1198
|
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
* @param {object} x Expression with a ref property
|
|
1223
|
-
* @returns {string} Rendered expression
|
|
1224
|
-
* @todo no extra magic with x.param or x.global
|
|
1225
|
-
*/
|
|
1226
|
-
function renderExpressionRef(x) {
|
|
1227
|
-
if (!x.param && !x.global) {
|
|
1228
|
-
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1229
|
-
if (x.ref[0] === '$user') {
|
|
1230
|
-
if (magicReplacement !== null)
|
|
1231
|
-
return `'${magicReplacement}'`;
|
|
1232
|
-
|
|
1233
|
-
// Keep old way of solving this to remain backwards compatible
|
|
1234
|
-
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1235
|
-
if (x.ref[1] === 'id') {
|
|
1236
|
-
if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
|
|
1237
|
-
return `'${options.magicVars.user}'`;
|
|
1238
|
-
|
|
1239
|
-
else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
|
|
1240
|
-
return `'${options.magicVars.user.id}'`;
|
|
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\'))';
|
|
1251
|
-
|
|
1252
|
-
else if (x.ref[1] === 'to')
|
|
1253
|
-
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1254
|
-
}
|
|
1255
|
-
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1256
|
-
return `'${magicReplacement}'`;
|
|
1257
|
-
}
|
|
1199
|
+
if (s.where) {
|
|
1200
|
+
// Filter, possibly with cardinality
|
|
1201
|
+
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1258
1202
|
}
|
|
1259
|
-
return
|
|
1203
|
+
return result;
|
|
1260
1204
|
}
|
|
1261
1205
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
*
|
|
1265
|
-
* @param {string} value Value to cast
|
|
1266
|
-
* @returns {string} Rendered cast()
|
|
1267
|
-
*/
|
|
1268
|
-
function renderExplicitTypeCast(value) {
|
|
1269
|
-
let typeRef = renderTypeReference(expr.cast, env);
|
|
1206
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1207
|
+
}
|
|
1270
1208
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1209
|
+
/**
|
|
1210
|
+
* @param {object} x Expression with a val and/or literal property
|
|
1211
|
+
* @returns {string} Rendered expression
|
|
1212
|
+
*/
|
|
1213
|
+
function renderExpressionLiteral(x) {
|
|
1214
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1215
|
+
switch (x.literal || typeof x.val) {
|
|
1216
|
+
case 'number':
|
|
1217
|
+
case 'boolean':
|
|
1218
|
+
case 'null':
|
|
1219
|
+
return x.val;
|
|
1220
|
+
case 'x':
|
|
1221
|
+
case 'date':
|
|
1222
|
+
case 'time':
|
|
1223
|
+
case 'timestamp':
|
|
1224
|
+
return `${x.literal}'${x.val}'`;
|
|
1225
|
+
case 'string':
|
|
1226
|
+
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1227
|
+
case 'object':
|
|
1228
|
+
if (x.val === null)
|
|
1229
|
+
return 'null';
|
|
1230
|
+
|
|
1231
|
+
// otherwise fall through to
|
|
1232
|
+
default:
|
|
1233
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1278
1234
|
}
|
|
1235
|
+
}
|
|
1279
1236
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
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';
|
|
1237
|
+
/**
|
|
1238
|
+
* Render the given expression x - which has a .func property
|
|
1239
|
+
*
|
|
1240
|
+
* @param {object} x
|
|
1241
|
+
* @param {CdlRenderEnvironment} env
|
|
1242
|
+
* @returns {string}
|
|
1243
|
+
*/
|
|
1244
|
+
function renderExpressionFunc(x, env) {
|
|
1245
|
+
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
|
|
1246
|
+
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1247
|
+
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1248
|
+
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1249
|
+
warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
|
|
1250
|
+
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1251
|
+
}
|
|
1304
1252
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1253
|
+
/**
|
|
1254
|
+
* @param {object} x Expression with a ref property
|
|
1255
|
+
* @returns {string} Rendered expression
|
|
1256
|
+
* @todo no extra magic with x.param or x.global
|
|
1257
|
+
*/
|
|
1258
|
+
function renderExpressionRef(x, env) {
|
|
1259
|
+
if (!x.param && !x.global) {
|
|
1260
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1261
|
+
if (x.ref[0] === '$user') {
|
|
1262
|
+
if (magicReplacement !== null)
|
|
1263
|
+
return `'${magicReplacement}'`;
|
|
1307
1264
|
|
|
1308
|
-
//
|
|
1309
|
-
//
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1265
|
+
// Keep old way of solving this to remain backwards compatible
|
|
1266
|
+
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1267
|
+
if (x.ref[1] === 'id') {
|
|
1268
|
+
if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
|
|
1269
|
+
return `'${options.magicVars.user}'`;
|
|
1313
1270
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
return s;
|
|
1271
|
+
else if ((options.magicVars && options.magicVars.user && options.magicVars.user.id) && (typeof options.magicVars.user.id === 'string' || options.magicVars.user.id instanceof String))
|
|
1272
|
+
return `'${options.magicVars.user.id}'`;
|
|
1317
1273
|
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
// ID with filters or parameters
|
|
1321
|
-
else if (typeof s === 'object') {
|
|
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)})`;
|
|
1274
|
+
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1335
1275
|
}
|
|
1336
|
-
if (
|
|
1337
|
-
|
|
1338
|
-
result += `[${s.cardinality ? (`${s.cardinality.max}: `) : ''}${renderExpr(s.where, env, inline, true)}]`;
|
|
1276
|
+
else if (x.ref[1] === 'locale') {
|
|
1277
|
+
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1339
1278
|
}
|
|
1340
|
-
return result;
|
|
1341
1279
|
}
|
|
1280
|
+
else if (x.ref[0] === '$at') {
|
|
1281
|
+
if (x.ref[1] === 'from')
|
|
1282
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1342
1283
|
|
|
1343
|
-
|
|
1284
|
+
else if (x.ref[1] === 'to')
|
|
1285
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1286
|
+
}
|
|
1287
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1288
|
+
return `'${magicReplacement}'`;
|
|
1289
|
+
}
|
|
1344
1290
|
}
|
|
1291
|
+
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
|
|
1345
1292
|
}
|
|
1346
1293
|
|
|
1347
1294
|
/**
|
|
@@ -1349,7 +1296,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1349
1296
|
* using 'sep' as separator for positional parameters
|
|
1350
1297
|
*
|
|
1351
1298
|
* @param {object} node with `args` to render
|
|
1352
|
-
* @param {string} sep
|
|
1299
|
+
* @param {string} sep Separator between arguments
|
|
1353
1300
|
* @param {CdlRenderEnvironment} env Environment
|
|
1354
1301
|
* @returns {string} Rendered arguments
|
|
1355
1302
|
*/
|
|
@@ -1692,7 +1639,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1692
1639
|
}
|
|
1693
1640
|
|
|
1694
1641
|
/**
|
|
1695
|
-
* Returns a copy of 'env' with increased indentation
|
|
1642
|
+
* Returns a copy of 'env' with increased indentation - also resets the name prefix
|
|
1696
1643
|
*
|
|
1697
1644
|
* @param {CdlRenderEnvironment} env Current environment
|
|
1698
1645
|
* @returns {CdlRenderEnvironment} New environment with increased indent
|
|
@@ -1713,15 +1660,15 @@ function toHdbcdsSource(csn, options) {
|
|
|
1713
1660
|
}
|
|
1714
1661
|
|
|
1715
1662
|
/**
|
|
1716
|
-
* Return an absolute path '
|
|
1663
|
+
* Return an absolute path 'absPath', with '::' inserted if required by naming strategy 'hdbcds',
|
|
1717
1664
|
* with appropriate "-quotes
|
|
1718
1665
|
*
|
|
1719
|
-
* @param {string}
|
|
1666
|
+
* @param {string} absPath Absolute path to quote
|
|
1720
1667
|
* @returns {string} Quoted path
|
|
1721
1668
|
*/
|
|
1722
|
-
function quoteAbsolutePathString(
|
|
1723
|
-
const namespace = getNamespace(csn,
|
|
1724
|
-
const resultingName = getResultingName(csn, options.sqlMapping,
|
|
1669
|
+
function quoteAbsolutePathString(absPath) {
|
|
1670
|
+
const namespace = getNamespace(csn, absPath);
|
|
1671
|
+
const resultingName = getResultingName(csn, options.sqlMapping, absPath);
|
|
1725
1672
|
|
|
1726
1673
|
if (hdbcdsNames && namespace)
|
|
1727
1674
|
return `${quotePathString(namespace)}::${quotePathString(resultingName.slice(namespace.length + 2))}`;
|
|
@@ -1738,14 +1685,13 @@ function toHdbcdsSource(csn, options) {
|
|
|
1738
1685
|
function quoteId(id) {
|
|
1739
1686
|
// Should only ever be called for real IDs (i.e. no dots inside)
|
|
1740
1687
|
if (id.indexOf('.') !== -1)
|
|
1741
|
-
throw new ModelError(id);
|
|
1688
|
+
throw new ModelError(`HDBCDS: Tried to quote id with dot: ${id}`);
|
|
1742
1689
|
|
|
1743
1690
|
|
|
1744
|
-
switch (options.
|
|
1691
|
+
switch (options.sqlMapping) {
|
|
1745
1692
|
case 'plain':
|
|
1746
1693
|
return smartId(id, 'hdbcds');
|
|
1747
1694
|
case 'quoted':
|
|
1748
|
-
return delimitedId(id, 'hdbcds');
|
|
1749
1695
|
case 'hdbcds':
|
|
1750
1696
|
return delimitedId(id, 'hdbcds');
|
|
1751
1697
|
default:
|
|
@@ -1754,17 +1700,17 @@ function toHdbcdsSource(csn, options) {
|
|
|
1754
1700
|
}
|
|
1755
1701
|
|
|
1756
1702
|
/*
|
|
1757
|
-
* Return an absolute name '
|
|
1703
|
+
* Return an absolute name 'absName', with '::' inserted if required by naming strategy 'hdbcds', quoted
|
|
1758
1704
|
* as if it was a single identifier (required only for native USINGs)
|
|
1759
1705
|
*
|
|
1760
|
-
* @param {string}
|
|
1761
|
-
* @returns {string} Correctly quoted
|
|
1706
|
+
* @param {string} absName Absolute name
|
|
1707
|
+
* @returns {string} Correctly quoted absName
|
|
1762
1708
|
*/
|
|
1763
|
-
function quoteAbsoluteNameAsId(
|
|
1764
|
-
const resultingName = getResultingName(csn, options.sqlMapping,
|
|
1709
|
+
function quoteAbsoluteNameAsId(absName) {
|
|
1710
|
+
const resultingName = getResultingName(csn, options.sqlMapping, absName);
|
|
1765
1711
|
|
|
1766
1712
|
if (hdbcdsNames) {
|
|
1767
|
-
const namespace = getNamespace(csn,
|
|
1713
|
+
const namespace = getNamespace(csn, absName);
|
|
1768
1714
|
if (namespace)
|
|
1769
1715
|
return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
|
|
1770
1716
|
}
|
|
@@ -1778,7 +1724,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1778
1724
|
* @returns {string} Quoted/uppercased id
|
|
1779
1725
|
*/
|
|
1780
1726
|
function formatIdentifier(id) {
|
|
1781
|
-
id =
|
|
1727
|
+
id = plainNames ? id.toUpperCase() : id;
|
|
1782
1728
|
return quoteId(id);
|
|
1783
1729
|
}
|
|
1784
1730
|
|