@sap/cds-compiler 5.6.0 → 5.7.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 +31 -0
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +2 -1
- package/doc/Versioning.md +4 -4
- package/lib/api/options.js +1 -0
- package/lib/base/builtins.js +2 -2
- package/lib/base/dictionaries.js +1 -2
- package/lib/base/keywords.js +3 -1
- package/lib/base/lazyload.js +1 -1
- package/lib/base/message-registry.js +169 -144
- package/lib/base/messages.js +69 -59
- package/lib/base/model.js +3 -3
- package/lib/base/node-helpers.js +17 -16
- package/lib/base/optionProcessorHelper.js +13 -14
- package/lib/base/shuffle.js +4 -1
- package/lib/checks/structuredAnnoExpressions.js +1 -1
- package/lib/compiler/assert-consistency.js +1 -1
- package/lib/compiler/builtins.js +2 -1
- package/lib/compiler/extend.js +20 -5
- package/lib/compiler/resolve.js +45 -9
- package/lib/compiler/shared.js +1 -0
- package/lib/edm/annotations/edmJson.js +3 -3
- package/lib/edm/annotations/genericTranslation.js +5 -1
- package/lib/edm/annotations/vocabularyDefinitions.js +2 -2
- package/lib/edm/edmUtils.js +2 -1
- package/lib/gen/BaseParser.js +32 -32
- package/lib/gen/CdlParser.js +2237 -2196
- package/lib/json/from-csn.js +2 -0
- package/lib/json/to-csn.js +13 -4
- package/lib/language/docCommentParser.js +11 -5
- package/lib/language/errorStrategy.js +3 -3
- package/lib/language/genericAntlrParser.js +2 -0
- package/lib/model/csnUtils.js +6 -1
- package/lib/optionProcessor.js +5 -1
- package/lib/parsers/AstBuildingParser.js +151 -72
- package/lib/parsers/CdlGrammar.g4 +125 -83
- package/lib/parsers/Lexer.js +5 -3
- package/lib/parsers/index.js +1 -1
- package/lib/render/toCdl.js +6 -5
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +5 -3
- package/lib/render/utils/common.js +19 -6
- package/lib/render/utils/standardDatabaseFunctions.js +576 -0
- package/lib/transform/addTenantFields.js +2 -1
- package/lib/transform/db/flattening.js +18 -77
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/rewriteCalculatedElements.js +14 -19
- package/lib/transform/db/temporal.js +2 -1
- package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
- package/lib/transform/odata/createForeignKeys.js +4 -71
- package/lib/transform/odata/flattening.js +11 -1
- package/lib/transform/transformUtils.js +20 -85
- package/package.json +2 -1
- package/bin/cds_update_annotations.js +0 -180
package/lib/render/toHdbcds.js
CHANGED
|
@@ -1266,7 +1266,7 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
|
1266
1266
|
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1267
1267
|
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1268
1268
|
warning(null, this.env.path, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1269
|
-
return renderFunc(funcName, x,
|
|
1269
|
+
return renderFunc(funcName, x, a => renderArgs(a, '=>', this.env), { options });
|
|
1270
1270
|
}
|
|
1271
1271
|
|
|
1272
1272
|
|
package/lib/render/toSql.js
CHANGED
|
@@ -122,8 +122,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
122
122
|
return `CAST(${this.renderExpr(withoutCast(x))} AS ${typeRef})`;
|
|
123
123
|
},
|
|
124
124
|
val: renderExpressionLiteral,
|
|
125
|
-
enum() {
|
|
126
|
-
//
|
|
125
|
+
enum(x) {
|
|
126
|
+
// visitExpr first checks for `#`, then `val`:
|
|
127
|
+
if (x.val !== undefined)
|
|
128
|
+
return renderExpressionLiteral(x);
|
|
127
129
|
error('expr-unexpected-enum', this.env.path, 'Enum values are not yet supported for conversion to SQL');
|
|
128
130
|
return '';
|
|
129
131
|
},
|
|
@@ -135,7 +137,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
135
137
|
return renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, this.env);
|
|
136
138
|
},
|
|
137
139
|
func(x) {
|
|
138
|
-
return renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x,
|
|
140
|
+
return renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, a => renderArgs(a, '=>', this.env, null), { messageFunctions, options, path: this.env.path });
|
|
139
141
|
},
|
|
140
142
|
xpr(x) {
|
|
141
143
|
const env = this.env.withSubPath([ 'xpr' ]);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
const { ModelError } = require('../../base/error');
|
|
6
|
+
const { standardDatabaseFunctions } = require('./standardDatabaseFunctions');
|
|
6
7
|
|
|
7
8
|
const functionsWithoutParams = {
|
|
8
9
|
hana: {
|
|
@@ -27,13 +28,25 @@ const { implicitAs } = require('../../model/csnRefs');
|
|
|
27
28
|
*
|
|
28
29
|
* @param {string} funcName Name of the function
|
|
29
30
|
* @param {object} node Content of the function
|
|
30
|
-
* @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
|
|
31
31
|
* @param {(a: string) => string} renderArgs Function to render function arguments
|
|
32
|
+
* @param {object} utils Utility object containing options, path, and message functions
|
|
32
33
|
* @returns {string} Function string
|
|
33
34
|
*/
|
|
34
|
-
function renderFunc( funcName, node,
|
|
35
|
-
|
|
35
|
+
function renderFunc( funcName, node, renderArgs, utils ) {
|
|
36
|
+
const { options, path, messageFunctions } = utils || {};
|
|
37
|
+
const { sqlDialect } = options;
|
|
38
|
+
if (funcWithoutParen( node, sqlDialect ))
|
|
36
39
|
return funcName;
|
|
40
|
+
const rewriteStandardFunctions = options.transformation !== 'hdbcds' && sqlDialect !== 'plain' && options.standardDatabaseFunctions;
|
|
41
|
+
if (rewriteStandardFunctions) {
|
|
42
|
+
// we check function arguments for correctness
|
|
43
|
+
const { error } = messageFunctions;
|
|
44
|
+
const that = { renderArgs, error, path };
|
|
45
|
+
if (standardDatabaseFunctions[sqlDialect]?.[funcName])
|
|
46
|
+
return standardDatabaseFunctions[sqlDialect][funcName].call(that, node);
|
|
47
|
+
else if (standardDatabaseFunctions.common[funcName])
|
|
48
|
+
return standardDatabaseFunctions.common[funcName].call(that, node);
|
|
49
|
+
}
|
|
37
50
|
return `${funcName}(${renderArgs( node )})`;
|
|
38
51
|
}
|
|
39
52
|
|
|
@@ -682,13 +695,13 @@ function visitExpr( x ) {
|
|
|
682
695
|
return result;
|
|
683
696
|
}).join(', ')})`;
|
|
684
697
|
}
|
|
685
|
-
else if (x.val !== undefined) {
|
|
686
|
-
return this.val(x);
|
|
687
|
-
}
|
|
688
698
|
else if (x['#']) {
|
|
689
699
|
// Enum symbol
|
|
690
700
|
return this.enum(x);
|
|
691
701
|
}
|
|
702
|
+
else if (x.val !== undefined) {
|
|
703
|
+
return this.val(x);
|
|
704
|
+
}
|
|
692
705
|
else if (x.ref) {
|
|
693
706
|
// Reference: Array of path steps, possibly preceded by ':'
|
|
694
707
|
return this.ref(x);
|
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* List of functions for which we provide a mapping to the respective SQL dialect.
|
|
5
|
+
* All functions are lowercase, the caller may treat the function name case-insensitive.
|
|
6
|
+
*
|
|
7
|
+
* The `this` context within the functions hold the `renderArgs` function.
|
|
8
|
+
*/
|
|
9
|
+
const oDataFunctions = {
|
|
10
|
+
// https://www.sqlite.org/lang_corefunc.html
|
|
11
|
+
sqlite: {
|
|
12
|
+
contains(signature) {
|
|
13
|
+
const { args } = signature;
|
|
14
|
+
checkArgs.call(this, 'contains', args, 2);
|
|
15
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
16
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
17
|
+
return `(ifnull(instr(${x}, ${y}),0) <> 0)`;
|
|
18
|
+
},
|
|
19
|
+
startswith(signature) {
|
|
20
|
+
const { args } = signature;
|
|
21
|
+
checkArgs.call(this, 'startswith', args, 2);
|
|
22
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
23
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
24
|
+
return `coalesce((instr(${x}, ${y}) = 1), false)`;
|
|
25
|
+
}, // instr is 1 indexed
|
|
26
|
+
endswith(signature) {
|
|
27
|
+
const { args } = signature;
|
|
28
|
+
checkArgs.call(this, 'endswith', args, 2);
|
|
29
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
30
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
31
|
+
return `coalesce((substr(${x}, length(${x}) + 1 - length(${y})) = ${y}), false)`;
|
|
32
|
+
},
|
|
33
|
+
indexof(signature) {
|
|
34
|
+
const { args } = signature;
|
|
35
|
+
checkArgs.call(this, 'indexof', args, 2);
|
|
36
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
37
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
38
|
+
return `(instr(${x}, ${y}) - 1)`; // instr is 1 indexed
|
|
39
|
+
},
|
|
40
|
+
matchespattern(signature) {
|
|
41
|
+
const { args } = signature;
|
|
42
|
+
checkArgs.call(this, 'matchespattern', args, 2);
|
|
43
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
44
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
45
|
+
return `cast((${x} regexp ${y}) as INTEGER)`; // this is a udf, sqlite always returns a REAL w/o the cast
|
|
46
|
+
},
|
|
47
|
+
year(signature) {
|
|
48
|
+
const { args } = signature;
|
|
49
|
+
checkArgs.call(this, 'year', args, 1);
|
|
50
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
51
|
+
return `cast(strftime('%Y', ${x}) as Integer)`;
|
|
52
|
+
},
|
|
53
|
+
month(signature) {
|
|
54
|
+
const { args } = signature;
|
|
55
|
+
checkArgs.call(this, 'month', args, 1);
|
|
56
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
57
|
+
return `cast(strftime('%m', ${x}) as Integer)`;
|
|
58
|
+
},
|
|
59
|
+
day(signature) {
|
|
60
|
+
const { args } = signature;
|
|
61
|
+
checkArgs.call(this, 'day', args, 1);
|
|
62
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
63
|
+
return `cast(strftime('%d', ${x}) as Integer)`;
|
|
64
|
+
},
|
|
65
|
+
hour(signature) {
|
|
66
|
+
const { args } = signature;
|
|
67
|
+
checkArgs.call(this, 'hour', args, 1);
|
|
68
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
69
|
+
return `cast(strftime('%H', ${x}) as Integer)`;
|
|
70
|
+
},
|
|
71
|
+
minute(signature) {
|
|
72
|
+
const { args } = signature;
|
|
73
|
+
checkArgs.call(this, 'minute', args, 1);
|
|
74
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
75
|
+
return `cast(strftime('%M', ${x}) as Integer)`;
|
|
76
|
+
},
|
|
77
|
+
second(signature) {
|
|
78
|
+
const { args } = signature;
|
|
79
|
+
checkArgs.call(this, 'second', args, 1);
|
|
80
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
81
|
+
return `cast(strftime('%S', ${x}) as Integer)`;
|
|
82
|
+
},
|
|
83
|
+
// REVISIT: currently runtimes normalize to milliseconds
|
|
84
|
+
// we could allow this to be more precise
|
|
85
|
+
fractionalseconds(signature) {
|
|
86
|
+
const { args } = signature;
|
|
87
|
+
checkArgs.call(this, 'fractionalseconds', args, 1);
|
|
88
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
89
|
+
return `cast(substr(strftime('%f', ${x}), length(strftime('%f', ${x})) - 3) as REAL)`;
|
|
90
|
+
},
|
|
91
|
+
// The date(), time(), and datetime() functions all return text, and so their strftime() equivalents are exact.
|
|
92
|
+
time(signature) {
|
|
93
|
+
const { args } = signature;
|
|
94
|
+
checkArgs.call(this, 'time', args, 1);
|
|
95
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
96
|
+
return `time(${x})`;
|
|
97
|
+
},
|
|
98
|
+
date(signature) {
|
|
99
|
+
const { args } = signature;
|
|
100
|
+
checkArgs.call(this, 'date', args, 1);
|
|
101
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
102
|
+
return `date(${x}) `;
|
|
103
|
+
},
|
|
104
|
+
// this could also be a negative number
|
|
105
|
+
// also, parts of the EDM.duration are optional which complicates
|
|
106
|
+
// the implementation on SQL level. As the parameter may be an element
|
|
107
|
+
// reference, we must do splitting and casting in the SQL as well as
|
|
108
|
+
// considering the case where the duration is negative.
|
|
109
|
+
// --> We do not support this function.
|
|
110
|
+
// totalseconds(signature) {
|
|
111
|
+
// const { args } = signature;
|
|
112
|
+
// checkArgs.call(this, 'totalseconds', args, 1);
|
|
113
|
+
// let x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
114
|
+
// const isNegative = x.startsWith("'-"); // Check for leading '-'
|
|
115
|
+
// x = isNegative ? x.replace('-', '') : x; // remove for easier processing
|
|
116
|
+
// const sql = `((cast(substr(${x},2,instr(${x},'DT') - 2) as Integer) + (julianday('-4713-11-25T' || replace(replace(replace(substr(${x},instr(${x},'DT') + 2),'H',':'),'M',':'),'S','Z')) - 0.5)) * 86400)`;
|
|
117
|
+
// return isNegative ? `-(${sql})` : sql;
|
|
118
|
+
// },
|
|
119
|
+
},
|
|
120
|
+
// https://www.postgresql.org/docs/current/functions-string.html
|
|
121
|
+
// https://www.postgresql.org/docs/current/functions-math.html
|
|
122
|
+
postgres: {
|
|
123
|
+
contains(signature) {
|
|
124
|
+
const { args } = signature;
|
|
125
|
+
checkArgs.call(this, 'contains', args, 2);
|
|
126
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
127
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
128
|
+
return `(coalesce(strpos(${x}, ${y}),0) > 0)`;
|
|
129
|
+
},
|
|
130
|
+
startswith(signature) {
|
|
131
|
+
const { args } = signature;
|
|
132
|
+
checkArgs.call(this, 'startswith', args, 2);
|
|
133
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
134
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
135
|
+
return `coalesce((strpos(${x}, ${y}) = 1), false)`; // strpos is 1 indexed
|
|
136
|
+
},
|
|
137
|
+
endswith(signature) {
|
|
138
|
+
const { args } = signature;
|
|
139
|
+
checkArgs.call(this, 'endswith', args, 2);
|
|
140
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
141
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
142
|
+
return `coalesce((substr(${x}, (length(${x}) + 1) - length(${y})) = ${y}), false)`;
|
|
143
|
+
},
|
|
144
|
+
indexof(signature) {
|
|
145
|
+
const { args } = signature;
|
|
146
|
+
checkArgs.call(this, 'indexof', args, 2);
|
|
147
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
148
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
149
|
+
return `(strpos(${x}, ${y}) - 1)`; // strpos is 1 indexed
|
|
150
|
+
},
|
|
151
|
+
matchespattern(signature) {
|
|
152
|
+
const { args } = signature;
|
|
153
|
+
checkArgs.call(this, 'matchespattern', args, 2);
|
|
154
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
155
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
156
|
+
return `regexp_like(${x}, ${y})`;
|
|
157
|
+
},
|
|
158
|
+
// TODO: PG docu recommends to use the "EXTRACT" function for improved precision
|
|
159
|
+
// https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT
|
|
160
|
+
year(signature) {
|
|
161
|
+
const { args } = signature;
|
|
162
|
+
checkArgs.call(this, 'year', args, 1);
|
|
163
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
164
|
+
return `cast(date_part('year', ${x}) as Integer)`;
|
|
165
|
+
},
|
|
166
|
+
month(signature) {
|
|
167
|
+
const { args } = signature;
|
|
168
|
+
checkArgs.call(this, 'month', args, 1);
|
|
169
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
170
|
+
return `cast(date_part('month', ${x}) as Integer)`;
|
|
171
|
+
},
|
|
172
|
+
day(signature) {
|
|
173
|
+
const { args } = signature;
|
|
174
|
+
checkArgs.call(this, 'day', args, 1);
|
|
175
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
176
|
+
return `cast(date_part('day', ${x}) as Integer)`;
|
|
177
|
+
},
|
|
178
|
+
hour(signature) {
|
|
179
|
+
const { args } = signature;
|
|
180
|
+
checkArgs.call(this, 'hour', args, 1);
|
|
181
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
182
|
+
return `cast(date_part('hour', ${x}) as Integer)`;
|
|
183
|
+
},
|
|
184
|
+
minute(signature) {
|
|
185
|
+
const { args } = signature;
|
|
186
|
+
checkArgs.call(this, 'minute', args, 1);
|
|
187
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
188
|
+
return `cast(date_part('minute', ${x}) as Integer)`;
|
|
189
|
+
},
|
|
190
|
+
second(signature) {
|
|
191
|
+
const { args } = signature;
|
|
192
|
+
checkArgs.call(this, 'second', args, 1);
|
|
193
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
194
|
+
return `cast(floor(date_part('second', ${x})) as Integer)`;
|
|
195
|
+
},
|
|
196
|
+
// REVISIT: currently runtimes normalize to milliseconds
|
|
197
|
+
// we could allow this to be more precise
|
|
198
|
+
fractionalseconds(signature) {
|
|
199
|
+
const { args } = signature;
|
|
200
|
+
checkArgs.call(this, 'fractionalseconds', args, 1);
|
|
201
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
202
|
+
return `CAST(date_part('second', ${x}) - floor(date_part('second', ${x})) AS DECIMAL(3,3))`;
|
|
203
|
+
},
|
|
204
|
+
time(signature) {
|
|
205
|
+
const { args } = signature;
|
|
206
|
+
checkArgs.call(this, 'time', args, 1);
|
|
207
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
208
|
+
return `to_char(${x}, 'HH24:MI:SS')`;
|
|
209
|
+
},
|
|
210
|
+
date(signature) {
|
|
211
|
+
const { args } = signature;
|
|
212
|
+
checkArgs.call(this, 'date', args, 1);
|
|
213
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
214
|
+
return `to_char(${x}, 'YYYY-MM-DD')`;
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
// https://help.sap.com/docs/HANA_SERVICE_CF/7c78579ce9b14a669c1f3295b0d8ca16/f12b86a6284c4aeeb449e57eb5dd3ebd.html?locale=en-US
|
|
218
|
+
hana: {
|
|
219
|
+
contains(signature) {
|
|
220
|
+
const { args } = signature;
|
|
221
|
+
checkArgs.call(this, 'contains', args, 2, 3);
|
|
222
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
223
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
224
|
+
if (signature.args.length > 2) {
|
|
225
|
+
const z = this.renderArgs({ ...signature, args: [ args[2] ] });
|
|
226
|
+
// While CONTAINS() looks like a function because of its syntax,
|
|
227
|
+
// it is classified as a predicate because it is designed to evaluate a condition
|
|
228
|
+
// and return a Boolean result.
|
|
229
|
+
return `CONTAINS(${x}, ${y}, ${z})`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return `(CASE WHEN coalesce(locate(${this.renderArgs(signature)}),0)>0 THEN TRUE ELSE FALSE END)`;
|
|
233
|
+
},
|
|
234
|
+
startswith(signature) {
|
|
235
|
+
const { args } = signature;
|
|
236
|
+
checkArgs.call(this, 'startswith', args, 2);
|
|
237
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
238
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
239
|
+
return `(CASE WHEN locate(${x}, ${y}) = 1 THEN TRUE ELSE FALSE END)`;
|
|
240
|
+
}, // locate is 1 indexed
|
|
241
|
+
endswith(signature) {
|
|
242
|
+
const { args } = signature;
|
|
243
|
+
checkArgs.call(this, 'endswith', args, 2);
|
|
244
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
245
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
246
|
+
return `(CASE WHEN substring(${x}, (length(${x}) + 1) - length(${y})) = ${y} THEN TRUE ELSE FALSE END)`;
|
|
247
|
+
},
|
|
248
|
+
indexof(signature) {
|
|
249
|
+
const { args } = signature;
|
|
250
|
+
checkArgs.call(this, 'indexof', args, 2);
|
|
251
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
252
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
253
|
+
return `(locate(${x}, ${y}) - 1)`; // locate is 1 indexed
|
|
254
|
+
},
|
|
255
|
+
matchespattern(signature) {
|
|
256
|
+
// case … when only works as column expression (not in where)
|
|
257
|
+
// in the where clause, only "${x} LIKE_REGEXPR ${y}" works
|
|
258
|
+
const { args } = signature;
|
|
259
|
+
checkArgs.call(this, 'matchespattern', args, 2);
|
|
260
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
261
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
262
|
+
return `(CASE WHEN ${x} LIKE_REGEXPR ${y} THEN TRUE ELSE FALSE END)`;
|
|
263
|
+
},
|
|
264
|
+
year(signature) {
|
|
265
|
+
const { args } = signature;
|
|
266
|
+
checkArgs.call(this, 'year', args, 1);
|
|
267
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
268
|
+
return `year(${x})`;
|
|
269
|
+
},
|
|
270
|
+
month(signature) {
|
|
271
|
+
const { args } = signature;
|
|
272
|
+
checkArgs.call(this, 'month', args, 1);
|
|
273
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
274
|
+
return `month(${x})`;
|
|
275
|
+
},
|
|
276
|
+
day(signature) {
|
|
277
|
+
const { args } = signature;
|
|
278
|
+
checkArgs.call(this, 'day', args, 1);
|
|
279
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
280
|
+
return `dayofmonth(${x})`;
|
|
281
|
+
},
|
|
282
|
+
hour(signature) {
|
|
283
|
+
const { args } = signature;
|
|
284
|
+
checkArgs.call(this, 'hour', args, 1);
|
|
285
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
286
|
+
return `hour(${x})`;
|
|
287
|
+
},
|
|
288
|
+
minute(signature) {
|
|
289
|
+
const { args } = signature;
|
|
290
|
+
checkArgs.call(this, 'minute', args, 1);
|
|
291
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
292
|
+
return `minute(${x})`;
|
|
293
|
+
},
|
|
294
|
+
second(signature) {
|
|
295
|
+
const { args } = signature;
|
|
296
|
+
checkArgs.call(this, 'second', args, 1);
|
|
297
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
298
|
+
return `to_integer(second(${x}))`;
|
|
299
|
+
},
|
|
300
|
+
// REVISIT: currently runtimes normalize to milliseconds
|
|
301
|
+
// we could allow this to be more precise
|
|
302
|
+
fractionalseconds(signature) {
|
|
303
|
+
const { args } = signature;
|
|
304
|
+
checkArgs.call(this, 'fractionalseconds', args, 1);
|
|
305
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
306
|
+
return `(to_decimal(second(${x}),5,3) - to_integer(second(${x})))`;
|
|
307
|
+
},
|
|
308
|
+
time(signature) {
|
|
309
|
+
const { args } = signature;
|
|
310
|
+
checkArgs.call(this, 'time', args, 1);
|
|
311
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
312
|
+
return `cast(to_time(${x}) AS NVARCHAR)`;
|
|
313
|
+
},
|
|
314
|
+
date(signature) {
|
|
315
|
+
const { args } = signature;
|
|
316
|
+
checkArgs.call(this, 'date', args, 1);
|
|
317
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
318
|
+
return `cast(to_date(${x}) AS NVARCHAR)`;
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
// https://www.h2database.com/html/functions.html
|
|
322
|
+
h2: {
|
|
323
|
+
contains(signature) {
|
|
324
|
+
const args = [ ...signature.args ];
|
|
325
|
+
checkArgs.call(this, 'contains', args, 2);
|
|
326
|
+
// defined as { LOCATE(searchString, string [, startInt]) }
|
|
327
|
+
args.reverse();
|
|
328
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
329
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
330
|
+
return `(coalesce(locate(${x}, ${y}),0) > 0)`;
|
|
331
|
+
},
|
|
332
|
+
startswith(signature) {
|
|
333
|
+
const args = [ ...signature.args ];
|
|
334
|
+
checkArgs.call(this, 'startswith', args, 2);
|
|
335
|
+
// defined as { LOCATE(searchString, string [, startInt]) }
|
|
336
|
+
args.reverse();
|
|
337
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
338
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
339
|
+
return `coalesce((locate(${x}, ${y}) = 1), false)`; // locate is 1 indexed
|
|
340
|
+
},
|
|
341
|
+
endswith(signature) {
|
|
342
|
+
const { args } = signature;
|
|
343
|
+
checkArgs.call(this, 'endswith', args, 2);
|
|
344
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
345
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
346
|
+
return `coalesce((substring(${x} FROM (char_length(${x}) + 1) - char_length(${y})) = ${y}), false)`;
|
|
347
|
+
},
|
|
348
|
+
substring(signature) {
|
|
349
|
+
const { args } = signature;
|
|
350
|
+
checkArgs.call(this, 'substring', args, 2, 3);
|
|
351
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
352
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
353
|
+
const z = args[2]
|
|
354
|
+
? this.renderArgs({ ...signature, args: [ args[2] ] })
|
|
355
|
+
: null;
|
|
356
|
+
return z
|
|
357
|
+
? `substring(${x} FROM CASE WHEN ${y} < 0 THEN char_length(${x}) + ${y} + 1 ELSE ${y} + 1 END FOR ${z})`
|
|
358
|
+
: `substring(${x} FROM CASE WHEN ${y} < 0 THEN char_length(${x}) + ${y} + 1 ELSE ${y} + 1 END)`;
|
|
359
|
+
},
|
|
360
|
+
// char_length is preferred over length -> REVISIT: returns a BIGINT, is this ok?
|
|
361
|
+
// https://www.h2database.com/html/functions.html#char_length
|
|
362
|
+
length(signature) {
|
|
363
|
+
const { args } = signature;
|
|
364
|
+
checkArgs.call(this, 'length', args, 1);
|
|
365
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
366
|
+
return `cast(char_length(${x}) as Integer)`;
|
|
367
|
+
},
|
|
368
|
+
indexof(signature) {
|
|
369
|
+
const args = [ ...signature.args ];
|
|
370
|
+
checkArgs.call(this, 'indexof', args, 2);
|
|
371
|
+
// defined as { LOCATE(searchString, string [, startInt]) }
|
|
372
|
+
args.reverse();
|
|
373
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
374
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
375
|
+
return `(locate(${x}, ${y}) - 1)`; // locate is 1 indexed
|
|
376
|
+
},
|
|
377
|
+
matchespattern(signature) {
|
|
378
|
+
const { args } = signature;
|
|
379
|
+
checkArgs.call(this, 'matchespattern', args, 2);
|
|
380
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
381
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
382
|
+
return `regexp_like(${x}, ${y})`;
|
|
383
|
+
},
|
|
384
|
+
year(signature) {
|
|
385
|
+
const { args } = signature;
|
|
386
|
+
checkArgs.call(this, 'year', args, 1);
|
|
387
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
388
|
+
return `extract(YEAR FROM ${x})`;
|
|
389
|
+
},
|
|
390
|
+
month(signature) {
|
|
391
|
+
const { args } = signature;
|
|
392
|
+
checkArgs.call(this, 'month', args, 1);
|
|
393
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
394
|
+
return `extract(MONTH FROM ${x})`;
|
|
395
|
+
},
|
|
396
|
+
day(signature) {
|
|
397
|
+
const { args } = signature;
|
|
398
|
+
checkArgs.call(this, 'day', args, 1);
|
|
399
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
400
|
+
return `extract(DAY FROM ${x})`;
|
|
401
|
+
},
|
|
402
|
+
hour(signature) {
|
|
403
|
+
const { args } = signature;
|
|
404
|
+
checkArgs.call(this, 'hour', args, 1);
|
|
405
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
406
|
+
return `extract(HOUR FROM ${x})`;
|
|
407
|
+
},
|
|
408
|
+
minute(signature) {
|
|
409
|
+
const { args } = signature;
|
|
410
|
+
checkArgs.call(this, 'minute', args, 1);
|
|
411
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
412
|
+
return `extract(MINUTE FROM ${x})`;
|
|
413
|
+
},
|
|
414
|
+
second(signature) {
|
|
415
|
+
const { args } = signature;
|
|
416
|
+
checkArgs.call(this, 'second', args, 1);
|
|
417
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
418
|
+
return `extract(SECOND FROM ${x})`;
|
|
419
|
+
},
|
|
420
|
+
// REVISIT: currently runtimes normalize to milliseconds
|
|
421
|
+
// we could allow this to be more precise
|
|
422
|
+
fractionalseconds(signature) {
|
|
423
|
+
const { args } = signature;
|
|
424
|
+
checkArgs.call(this, 'fractionalseconds', args, 1);
|
|
425
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
426
|
+
return `cast(extract(MILLISECOND FROM ${x}) / 1000.0 AS NUMERIC(3,3))`;
|
|
427
|
+
},
|
|
428
|
+
time(signature) {
|
|
429
|
+
const { args } = signature;
|
|
430
|
+
checkArgs.call(this, 'time', args, 1);
|
|
431
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
432
|
+
return `cast(cast(${x} AS TIME) AS VARCHAR)`;
|
|
433
|
+
},
|
|
434
|
+
date(signature) {
|
|
435
|
+
const { args } = signature;
|
|
436
|
+
checkArgs.call(this, 'date', args, 1);
|
|
437
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
438
|
+
return `cast(cast(${x} AS DATE) AS VARCHAR)`;
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
common: {
|
|
442
|
+
concat(signature) {
|
|
443
|
+
const separator = '||';
|
|
444
|
+
const args = signature.args.reduce((acc, current, index) => {
|
|
445
|
+
if (index > 0)
|
|
446
|
+
acc.push(separator);
|
|
447
|
+
|
|
448
|
+
acc.push(current);
|
|
449
|
+
return acc;
|
|
450
|
+
}, []);
|
|
451
|
+
const res = this.renderArgs({ signature, ...{ args: [ args ] } });
|
|
452
|
+
return `(${res})`;
|
|
453
|
+
},
|
|
454
|
+
ceiling(signature) {
|
|
455
|
+
const { args } = signature;
|
|
456
|
+
checkArgs.call(this, 'ceiling', args, 1);
|
|
457
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
458
|
+
return `ceil(${x})`;
|
|
459
|
+
},
|
|
460
|
+
floor(signature) {
|
|
461
|
+
const { args } = signature;
|
|
462
|
+
checkArgs.call(this, 'floor', args, 1);
|
|
463
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
464
|
+
return `floor(${x})`;
|
|
465
|
+
},
|
|
466
|
+
trim(signature) {
|
|
467
|
+
const { args } = signature;
|
|
468
|
+
checkArgs.call(this, 'trim', args, 1);
|
|
469
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
470
|
+
return `trim(${x})`;
|
|
471
|
+
},
|
|
472
|
+
// SAP HANA, sqlite and postgres share the same implementation
|
|
473
|
+
substring(signature) {
|
|
474
|
+
const { args } = signature;
|
|
475
|
+
checkArgs.call(this, 'substring', args, 2, 3);
|
|
476
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
477
|
+
const y = this.renderArgs({ ...signature, args: [ args[1] ] });
|
|
478
|
+
const z = args[2]
|
|
479
|
+
? this.renderArgs({ ...signature, args: [ args[2] ] })
|
|
480
|
+
: null;
|
|
481
|
+
return z
|
|
482
|
+
? `substr(${x}, CASE WHEN ${y} < 0 THEN length(${x}) + ${y} + 1 ELSE ${y} + 1 END, ${z})`
|
|
483
|
+
: `substr(${x}, CASE WHEN ${y} < 0 THEN length(${x}) + ${y} + 1 ELSE ${y} + 1 END)`;
|
|
484
|
+
},
|
|
485
|
+
min(signature) {
|
|
486
|
+
const { args } = signature;
|
|
487
|
+
checkArgs.call(this, 'min', args, 1);
|
|
488
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
489
|
+
return `min(${x})`;
|
|
490
|
+
},
|
|
491
|
+
max(signature) {
|
|
492
|
+
const { args } = signature;
|
|
493
|
+
checkArgs.call(this, 'max', args, 1);
|
|
494
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
495
|
+
return `max(${x})`;
|
|
496
|
+
},
|
|
497
|
+
sum(signature) {
|
|
498
|
+
const { args } = signature;
|
|
499
|
+
checkArgs.call(this, 'sum', args, 1);
|
|
500
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
501
|
+
return `sum(${x})`;
|
|
502
|
+
},
|
|
503
|
+
count(signature) {
|
|
504
|
+
const { args } = signature;
|
|
505
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
506
|
+
return `count(${x || '*'})`;
|
|
507
|
+
},
|
|
508
|
+
countdistinct(signature) {
|
|
509
|
+
const { args } = signature;
|
|
510
|
+
return `count(distinct ${args.length > 0 ? this.renderArgs(signature) : "'*'"})`;
|
|
511
|
+
},
|
|
512
|
+
average(signature) {
|
|
513
|
+
const { args } = signature;
|
|
514
|
+
checkArgs.call(this, 'average', args, 1);
|
|
515
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
516
|
+
return `avg(${x})`;
|
|
517
|
+
},
|
|
518
|
+
length(signature) {
|
|
519
|
+
const { args } = signature;
|
|
520
|
+
checkArgs.call(this, 'length', args, 1);
|
|
521
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
522
|
+
return `length(${x})`;
|
|
523
|
+
},
|
|
524
|
+
tolower(signature) {
|
|
525
|
+
const { args } = signature;
|
|
526
|
+
checkArgs.call(this, 'tolower', args, 1);
|
|
527
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
528
|
+
return `lower(${x})`;
|
|
529
|
+
},
|
|
530
|
+
toupper(signature) {
|
|
531
|
+
const { args } = signature;
|
|
532
|
+
checkArgs.call(this, 'toupper', args, 1);
|
|
533
|
+
const x = this.renderArgs({ ...signature, args: [ args[0] ] });
|
|
534
|
+
return `upper(${x})`;
|
|
535
|
+
},
|
|
536
|
+
// eslint-disable-next-line no-unused-vars
|
|
537
|
+
maxdatetime(signature) {
|
|
538
|
+
return "'9999-12-31T23:59:59.999Z'";
|
|
539
|
+
},
|
|
540
|
+
// eslint-disable-next-line no-unused-vars
|
|
541
|
+
mindatetime(signature) {
|
|
542
|
+
return "'0001-01-01T00:00:00.000Z'";
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// TODO: add support for the common SAP HANA Functions
|
|
548
|
+
const hanaFunctions = {
|
|
549
|
+
sqlite: {},
|
|
550
|
+
postgres: {},
|
|
551
|
+
hana: { /* no-op */ },
|
|
552
|
+
h2: {},
|
|
553
|
+
common: {},
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
function checkArgs( funcName, receivedArgs, expectedLength, alternativeLength = null ) {
|
|
557
|
+
const expectedMismatch = receivedArgs.length < expectedLength;
|
|
558
|
+
const alternativeMismatch = expectedMismatch && (!alternativeLength || alternativeLength && receivedArgs.length < alternativeLength);
|
|
559
|
+
if (expectedMismatch && alternativeMismatch) {
|
|
560
|
+
this.error('def-missing-argument', [ ...this.path, 'args' ], {
|
|
561
|
+
'#': alternativeLength ? 'alternative' : 'std',
|
|
562
|
+
n: expectedLength,
|
|
563
|
+
m: alternativeLength,
|
|
564
|
+
literal: receivedArgs.length,
|
|
565
|
+
name: funcName,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
module.exports.standardDatabaseFunctions = {
|
|
571
|
+
sqlite: { ...oDataFunctions.sqlite, ...hanaFunctions.sqlite },
|
|
572
|
+
postgres: { ...oDataFunctions.postgres, ...hanaFunctions.postgres },
|
|
573
|
+
hana: { ...oDataFunctions.hana, ...hanaFunctions.hana },
|
|
574
|
+
h2: { ...oDataFunctions.h2, ...hanaFunctions.h2 },
|
|
575
|
+
common: { ...oDataFunctions.common },
|
|
576
|
+
};
|
|
@@ -331,7 +331,8 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
331
331
|
* Type references are followed, but only without sibling `elements` or `items`.
|
|
332
332
|
*/
|
|
333
333
|
function typeDependency( assoc ) {
|
|
334
|
-
|
|
334
|
+
assoc = assoc ? effectiveType( assoc ) : assoc;
|
|
335
|
+
if (!assoc)
|
|
335
336
|
return '';
|
|
336
337
|
const assocDep = typeCache.get( assoc );
|
|
337
338
|
if (assocDep != null)
|