@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/bin/cdsse.js +1 -0
  3. package/bin/cdsv2m.js +2 -1
  4. package/doc/Versioning.md +4 -4
  5. package/lib/api/options.js +1 -0
  6. package/lib/base/builtins.js +2 -2
  7. package/lib/base/dictionaries.js +1 -2
  8. package/lib/base/keywords.js +3 -1
  9. package/lib/base/lazyload.js +1 -1
  10. package/lib/base/message-registry.js +169 -144
  11. package/lib/base/messages.js +69 -59
  12. package/lib/base/model.js +3 -3
  13. package/lib/base/node-helpers.js +17 -16
  14. package/lib/base/optionProcessorHelper.js +13 -14
  15. package/lib/base/shuffle.js +4 -1
  16. package/lib/checks/structuredAnnoExpressions.js +1 -1
  17. package/lib/compiler/assert-consistency.js +1 -1
  18. package/lib/compiler/builtins.js +2 -1
  19. package/lib/compiler/extend.js +20 -5
  20. package/lib/compiler/resolve.js +45 -9
  21. package/lib/compiler/shared.js +1 -0
  22. package/lib/edm/annotations/edmJson.js +3 -3
  23. package/lib/edm/annotations/genericTranslation.js +5 -1
  24. package/lib/edm/annotations/vocabularyDefinitions.js +2 -2
  25. package/lib/edm/edmUtils.js +2 -1
  26. package/lib/gen/BaseParser.js +32 -32
  27. package/lib/gen/CdlParser.js +2237 -2196
  28. package/lib/json/from-csn.js +2 -0
  29. package/lib/json/to-csn.js +13 -4
  30. package/lib/language/docCommentParser.js +11 -5
  31. package/lib/language/errorStrategy.js +3 -3
  32. package/lib/language/genericAntlrParser.js +2 -0
  33. package/lib/model/csnUtils.js +6 -1
  34. package/lib/optionProcessor.js +5 -1
  35. package/lib/parsers/AstBuildingParser.js +151 -72
  36. package/lib/parsers/CdlGrammar.g4 +125 -83
  37. package/lib/parsers/Lexer.js +5 -3
  38. package/lib/parsers/index.js +1 -1
  39. package/lib/render/toCdl.js +6 -5
  40. package/lib/render/toHdbcds.js +1 -1
  41. package/lib/render/toSql.js +5 -3
  42. package/lib/render/utils/common.js +19 -6
  43. package/lib/render/utils/standardDatabaseFunctions.js +576 -0
  44. package/lib/transform/addTenantFields.js +2 -1
  45. package/lib/transform/db/flattening.js +18 -77
  46. package/lib/transform/db/groupByOrderBy.js +2 -2
  47. package/lib/transform/db/rewriteCalculatedElements.js +14 -19
  48. package/lib/transform/db/temporal.js +2 -1
  49. package/lib/transform/odata/adaptAnnotationRefs.js +79 -0
  50. package/lib/transform/odata/createForeignKeys.js +4 -71
  51. package/lib/transform/odata/flattening.js +11 -1
  52. package/lib/transform/transformUtils.js +20 -85
  53. package/package.json +2 -1
  54. package/bin/cds_update_annotations.js +0 -180
@@ -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, 'hana', a => renderArgs(a, '=>', this.env));
1269
+ return renderFunc(funcName, x, a => renderArgs(a, '=>', this.env), { options });
1270
1270
  }
1271
1271
 
1272
1272
 
@@ -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
- // FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
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, options.sqlDialect, a => renderArgs(a, '=>', this.env, null));
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, dialect, renderArgs ) {
35
- if (funcWithoutParen( node, dialect ))
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
- if (!assoc || !(assoc = effectiveType( assoc )))
334
+ assoc = assoc ? effectiveType( assoc ) : assoc;
335
+ if (!assoc)
335
336
  return '';
336
337
  const assocDep = typeCache.get( assoc );
337
338
  if (assocDep != null)