@sap/cds-compiler 3.3.2 → 3.4.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 +21 -0
- package/bin/cdsc.js +3 -1
- package/doc/CHANGELOG_BETA.md +17 -0
- package/lib/api/main.js +147 -18
- package/lib/api/validate.js +8 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/keywords.js +104 -0
- package/lib/base/message-registry.js +136 -67
- package/lib/base/messages.js +59 -48
- package/lib/base/model.js +1 -0
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +13 -8
- package/lib/checks/defaultValues.js +3 -1
- package/lib/checks/elements.js +1 -1
- package/lib/checks/parameters.js +4 -2
- package/lib/checks/queryNoDbArtifacts.js +1 -1
- package/lib/checks/sql-snippets.js +12 -10
- package/lib/checks/validator.js +14 -4
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/checks.js +30 -20
- package/lib/compiler/define.js +89 -25
- package/lib/compiler/extend.js +21 -18
- package/lib/compiler/finalize-parse-cdl.js +14 -9
- package/lib/compiler/populate.js +30 -8
- package/lib/compiler/propagator.js +4 -2
- package/lib/compiler/resolve.js +11 -5
- package/lib/compiler/shared.js +66 -48
- package/lib/compiler/tweak-assocs.js +2 -3
- package/lib/compiler/utils.js +11 -0
- package/lib/edm/annotations/genericTranslation.js +7 -4
- package/lib/edm/csn2edm.js +1 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +3565 -3544
- package/lib/json/csnVersion.js +13 -13
- package/lib/json/from-csn.js +140 -158
- package/lib/json/to-csn.js +23 -5
- package/lib/language/.eslintrc.json +4 -0
- package/lib/language/antlrParser.js +7 -10
- package/lib/language/docCommentParser.js +1 -2
- package/lib/language/errorStrategy.js +54 -27
- package/lib/language/genericAntlrParser.js +115 -84
- package/lib/language/language.g4 +29 -25
- package/lib/language/multiLineStringParser.js +75 -63
- package/lib/main.js +1 -0
- package/lib/model/csnRefs.js +4 -3
- package/lib/model/csnUtils.js +39 -7
- package/lib/model/sortViews.js +7 -3
- package/lib/modelCompare/compare.js +49 -15
- package/lib/modelCompare/filter.js +83 -0
- package/lib/optionProcessor.js +5 -1
- package/lib/render/manageConstraints.js +9 -5
- package/lib/render/toCdl.js +120 -62
- package/lib/render/toHdbcds.js +1 -1
- package/lib/render/toSql.js +6 -2
- package/lib/render/utils/common.js +7 -0
- package/lib/sql-identifier.js +7 -0
- package/lib/transform/db/assertUnique.js +27 -38
- package/lib/transform/db/expansion.js +11 -4
- package/lib/transform/db/temporal.js +3 -1
- package/lib/transform/db/transformExists.js +7 -1
- package/lib/transform/db/views.js +42 -13
- package/lib/transform/draft/db.js +2 -2
- package/lib/transform/forRelationalDB.js +12 -6
- package/lib/transform/localized.js +1 -1
- package/lib/transform/odata/typesExposure.js +2 -1
- package/lib/transform/parseExpr.js +245 -0
- package/lib/transform/transformUtilsNew.js +23 -14
- package/lib/transform/translateAssocsToJoins.js +12 -12
- package/lib/utils/term.js +5 -5
- package/package.json +2 -2
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/{syntax-expected-integer.md → syntax-expecting-integer.md} +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Each db has some changes that it can and cannot represent, or that cause problems only on that specific db
|
|
2
|
+
// In this file, we define rules for each db-dialect to detect and act on these cases.
|
|
3
|
+
|
|
4
|
+
const { forEach } = require("../utils/objectUtils");
|
|
5
|
+
const { isPersistedAsTable } = require('../model/csnUtils');
|
|
6
|
+
|
|
7
|
+
function isKey(element) {
|
|
8
|
+
return element.key;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
sqlite: getFilterObject(
|
|
13
|
+
'sqlite',
|
|
14
|
+
(extend, name, element, error) => {
|
|
15
|
+
if(isKey(element)) { // Key must not be extended
|
|
16
|
+
error(null, ['definitions', extend, 'elements', name], {id: name, name: 'sqlite'}, "Added element $(ID) is a primary key change and will not work with $(NAME)")
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
(migrate, name, migration, change, error) => {
|
|
20
|
+
const newIsKey = isKey(migration.new);
|
|
21
|
+
const oldIsKey = isKey(migration.old);
|
|
22
|
+
if((newIsKey || oldIsKey) && oldIsKey !== newIsKey) { // Turned into key or key was removed
|
|
23
|
+
error(null, ['definitions', migrate, 'elements', name], {id: name, name: 'sqlite'}, "Changed element $(ID) is a primary key change and will not work with $(NAME)")
|
|
24
|
+
} else { // Ignore simple migrations
|
|
25
|
+
delete change[name];
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getFilterObject(dialect, extensionCallback, migrationCallback) {
|
|
31
|
+
return {
|
|
32
|
+
// will be called with a simple Array.forEach
|
|
33
|
+
extension: ({ elements, extend }, error) => {
|
|
34
|
+
forEach(elements, (name, element) => {
|
|
35
|
+
extensionCallback(extend, name, element, error);
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
// will be called with a Array.map, as we need to filter "change" for SQLite
|
|
39
|
+
migration: ({ change, migrate, remove }, error) => {
|
|
40
|
+
forEach(remove, (name) => {
|
|
41
|
+
error(null, ['definitions', migrate, 'elements', name], {}, "Dropping elements is not supported")
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
forEach(change, (name, migration) => {
|
|
45
|
+
if(migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type)) {
|
|
46
|
+
error(null, ['definitions', migrate, 'elements', name], { id: name, name: migration.old.type, type: migration.new.type }, "Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported")
|
|
47
|
+
} else if(migration.new.length < migration.old.length) {
|
|
48
|
+
error(null, ['definitions', migrate, 'elements', name], { id: name }, "Changed element $(ID) is a length reduction and is not supported")
|
|
49
|
+
} else {
|
|
50
|
+
migrationCallback(migrate, name, migration, change, error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// TODO: precision/scale growth
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
deletion: ([artifactName, artifact ], error) => {
|
|
57
|
+
if(isPersistedAsTable(artifact))
|
|
58
|
+
error(null, ['definitions', artifactName], "Dropping tables is not supported");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const baseMatrix = {
|
|
64
|
+
// Integer types
|
|
65
|
+
'cds.hana.tinyint':['cds.UInt8', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
|
|
66
|
+
'cds.UInt8': ['cds.hana.tinyint', 'cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
|
|
67
|
+
'cds.Int16': ['cds.hana.smallint', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
|
|
68
|
+
'cds.hana.smallint':['cds.Int16', 'cds.Int32', 'cds.Integer', 'cds.Int64', 'cds.Integer64'],
|
|
69
|
+
'cds.Int32': ['cds.Integer', 'cds.Int64', 'cds.Integer64'],
|
|
70
|
+
'cds.Integer': ['cds.Int32', 'cds.Int64', 'cds.Integer64'],
|
|
71
|
+
'cds.Integer64': ['cds.Int64'],
|
|
72
|
+
'cds.Int64': ['cds.Integer64']
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const allowedTypeChanges = {
|
|
76
|
+
'sqlite': baseMatrix
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function typeChangeIsNotCompatible(dialect, before, after) {
|
|
80
|
+
if(allowedTypeChanges[dialect])
|
|
81
|
+
return allowedTypeChanges[dialect][before]?.indexOf(after) === -1;
|
|
82
|
+
return true;
|
|
83
|
+
}
|
package/lib/optionProcessor.js
CHANGED
|
@@ -102,6 +102,9 @@ optionProcessor
|
|
|
102
102
|
hanaAssocRealCardinality
|
|
103
103
|
mapAssocToJoinCardinality
|
|
104
104
|
ignoreAssocPublishingInUnion
|
|
105
|
+
enableUniversalCsn
|
|
106
|
+
postgres
|
|
107
|
+
aspectWithoutElements
|
|
105
108
|
odataOpenType
|
|
106
109
|
optionalActionFunctionParameters
|
|
107
110
|
--deprecated <list> Comma separated list of deprecated options.
|
|
@@ -262,7 +265,7 @@ optionProcessor.command('C, toCdl')
|
|
|
262
265
|
optionProcessor.command('Q, toSql')
|
|
263
266
|
.option('-h, --help')
|
|
264
267
|
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
|
|
265
|
-
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres'], { aliases: [ '--dialect' ] })
|
|
268
|
+
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
|
|
266
269
|
.option(' --render-virtual')
|
|
267
270
|
.option(' --joinfk')
|
|
268
271
|
.option('-u, --user <user>')
|
|
@@ -300,6 +303,7 @@ optionProcessor.command('Q, toSql')
|
|
|
300
303
|
hana : SQL with HANA specific language features
|
|
301
304
|
sqlite : Common SQL for sqlite
|
|
302
305
|
postgres : Common SQL for postgres - beta-feature
|
|
306
|
+
h2 : Common SQL for h2
|
|
303
307
|
-u, --user <user> Value for the "$user" variable
|
|
304
308
|
-l, --locale <locale> Value for the "$user.locale" variable in "sqlite"/"plain" dialect
|
|
305
309
|
-s, --src <style> Generate SQL source files as <artifact>.<suffix>
|
|
@@ -40,8 +40,10 @@ function alterConstraintsWithCsn(csn, options) {
|
|
|
40
40
|
const transformedOptions = _transformSqlOptions(csn, options);
|
|
41
41
|
const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
|
|
42
42
|
|
|
43
|
-
if (violations && src && src !== 'sql')
|
|
44
|
-
error(null, null,
|
|
43
|
+
if (violations && src && src !== 'sql') {
|
|
44
|
+
error(null, null, { value: '--violations', othervalue: src },
|
|
45
|
+
'Option $(VALUE) can\'t be combined with source style $(OTHERVALUE)');
|
|
46
|
+
}
|
|
45
47
|
|
|
46
48
|
let intermediateResult;
|
|
47
49
|
if (violations)
|
|
@@ -67,12 +69,14 @@ function _transformSqlOptions(model, options) {
|
|
|
67
69
|
|
|
68
70
|
if (options.sqlDialect !== 'hana') {
|
|
69
71
|
// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
|
|
70
|
-
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
|
|
71
|
-
error(null, null,
|
|
72
|
+
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds') {
|
|
73
|
+
error(null, null, { value: options.sqlDialect, othervalue: options.sqlMapping },
|
|
74
|
+
'Option sqlDialect: $(VALUE) can\'t be combined with sqlMapping: $(OTHERVALUE)');
|
|
75
|
+
}
|
|
72
76
|
|
|
73
77
|
// No non-HANA SQL for HDI
|
|
74
78
|
if (options.src === 'hdi')
|
|
75
|
-
error(null, null,
|
|
79
|
+
error(null, null, { value: options.sqlDialect }, 'Option sqlDialect: $(VALUE) can\'t be used for SAP HANA HDI');
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
return options;
|
package/lib/render/toCdl.js
CHANGED
|
@@ -351,13 +351,14 @@ function csnToCdl(csn, options) {
|
|
|
351
351
|
if (art.query || art.projection)
|
|
352
352
|
return renderView(artifactName, art, env);
|
|
353
353
|
return renderEntity(artifactName, art, env);
|
|
354
|
+
case 'aspect':
|
|
355
|
+
return renderAspect(artifactName, art, env);
|
|
354
356
|
|
|
355
357
|
case 'context':
|
|
356
358
|
case 'service':
|
|
357
359
|
return renderContextOrService(artifactName, art, env);
|
|
358
360
|
|
|
359
361
|
case 'type':
|
|
360
|
-
case 'aspect':
|
|
361
362
|
case 'annotation': // annotation in 'csn.definitions' for compiler v1 compatibility
|
|
362
363
|
return renderTypeOrAnnotation(artifactName, art, env);
|
|
363
364
|
|
|
@@ -380,7 +381,6 @@ function csnToCdl(csn, options) {
|
|
|
380
381
|
*/
|
|
381
382
|
function renderEvent(artifactName, art, env) {
|
|
382
383
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
383
|
-
const childEnv = increaseIndent(env);
|
|
384
384
|
const normalizedArtifactName = renderArtifactName(artifactName);
|
|
385
385
|
result += `${env.indent}event ${normalizedArtifactName}`;
|
|
386
386
|
if (art.includes)
|
|
@@ -396,11 +396,7 @@ function csnToCdl(csn, options) {
|
|
|
396
396
|
result += ` : ${renderTypeReferenceAndProps(art, env)};\n`;
|
|
397
397
|
}
|
|
398
398
|
else if (art.elements) {
|
|
399
|
-
result +=
|
|
400
|
-
for (const name in art.elements)
|
|
401
|
-
result += renderElement(name, art.elements[name], childEnv);
|
|
402
|
-
|
|
403
|
-
result += `${env.indent}}`;
|
|
399
|
+
result += ` ${renderElements(art, env)};\n`;
|
|
404
400
|
}
|
|
405
401
|
return result;
|
|
406
402
|
}
|
|
@@ -432,21 +428,57 @@ function csnToCdl(csn, options) {
|
|
|
432
428
|
|
|
433
429
|
if (art.params)
|
|
434
430
|
result += renderParameters(art, env);
|
|
431
|
+
if (art.includes)
|
|
432
|
+
result += renderIncludes(art.includes);
|
|
433
|
+
result += ` ${renderElements(art, env)}`;
|
|
434
|
+
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
435
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Render an aspect. Return the resulting source string.
|
|
440
|
+
* Behaves very similar to renderEntity, _except_ that aspects are
|
|
441
|
+
* allowed to _not_ have elements, e.g. `aspect A;`.
|
|
442
|
+
*
|
|
443
|
+
* @param {string} artifactName
|
|
444
|
+
* @param {CSN.Artifact} art
|
|
445
|
+
* @param {CdlRenderEnvironment} env
|
|
446
|
+
* @return {string}
|
|
447
|
+
*/
|
|
448
|
+
function renderAspect(artifactName, art, env) {
|
|
449
|
+
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
450
|
+
result += `${env.indent}aspect ${renderArtifactName(artifactName)}`;
|
|
436
451
|
if (art.includes)
|
|
437
452
|
result += renderIncludes(art.includes);
|
|
438
|
-
result += ' {\n';
|
|
439
|
-
const childEnv = increaseIndent(env);
|
|
440
|
-
for (const name in art.elements) {
|
|
441
|
-
const element = art.elements[name];
|
|
442
|
-
result += renderElement(name, element, childEnv);
|
|
443
|
-
}
|
|
444
453
|
|
|
445
|
-
|
|
454
|
+
if (art.elements)
|
|
455
|
+
result += ` ${renderElements(art, env)}`;
|
|
456
|
+
else if (art.actions)
|
|
457
|
+
// if there are no elements, but actions, CDL syntax requires braces.
|
|
458
|
+
result += ' { }';
|
|
459
|
+
|
|
446
460
|
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
447
461
|
return result;
|
|
448
462
|
}
|
|
449
463
|
|
|
464
|
+
/**
|
|
465
|
+
* Render a list of elements enclosed in braces. If the list is empty, returns `{ }`.
|
|
466
|
+
*
|
|
467
|
+
* @param {object} artifact Artifact with `elements` property.
|
|
468
|
+
* @param {CdlRenderEnvironment} env
|
|
469
|
+
* @return {string}
|
|
470
|
+
*/
|
|
471
|
+
function renderElements(artifact, env) {
|
|
472
|
+
let elements = '';
|
|
473
|
+
const childEnv = increaseIndent(env);
|
|
474
|
+
for (const name in artifact.elements)
|
|
475
|
+
elements += renderElement(name, artifact.elements[name], childEnv, null);
|
|
476
|
+
|
|
477
|
+
if (elements === '')
|
|
478
|
+
return '{ }';
|
|
479
|
+
return `{\n${elements}${env.indent}}`;
|
|
480
|
+
}
|
|
481
|
+
|
|
450
482
|
/**
|
|
451
483
|
* Render an element (of an entity, type or annotation, not a projection or view).
|
|
452
484
|
* Return the resulting source string.
|
|
@@ -651,7 +683,7 @@ function csnToCdl(csn, options) {
|
|
|
651
683
|
|
|
652
684
|
// Even the first step might have parameters and/or a filter
|
|
653
685
|
if (path.ref[0].args)
|
|
654
|
-
result += `(${
|
|
686
|
+
result += `(${renderArguments(path.ref[0], ':', env)})`;
|
|
655
687
|
|
|
656
688
|
if (path.ref[0].where) {
|
|
657
689
|
const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
|
|
@@ -841,6 +873,8 @@ function csnToCdl(csn, options) {
|
|
|
841
873
|
result += renderActionsAndFunctions(art, env);
|
|
842
874
|
result += ';\n';
|
|
843
875
|
result += renderQueryElementAndEnumAnnotations(artifactName, art, env);
|
|
876
|
+
if (art.includes)
|
|
877
|
+
result += renderExtension({ extend: artifactName, includes: art.includes }, env);
|
|
844
878
|
return result;
|
|
845
879
|
}
|
|
846
880
|
|
|
@@ -1093,8 +1127,7 @@ function csnToCdl(csn, options) {
|
|
|
1093
1127
|
result += ` ${renderTypeReferenceAndProps(art, env)}`;
|
|
1094
1128
|
else
|
|
1095
1129
|
result += ` : ${renderTypeReferenceAndProps(art, env)}`;
|
|
1096
|
-
|
|
1097
|
-
result += `${renderActionsAndFunctions(art, env)};\n`;
|
|
1130
|
+
result += ';\n';
|
|
1098
1131
|
return result;
|
|
1099
1132
|
}
|
|
1100
1133
|
|
|
@@ -1130,12 +1163,7 @@ function csnToCdl(csn, options) {
|
|
|
1130
1163
|
}
|
|
1131
1164
|
|
|
1132
1165
|
if (!artifact.type && artifact.elements) {
|
|
1133
|
-
result +=
|
|
1134
|
-
const childEnv = envAddPath(increaseIndent(env), 'items');
|
|
1135
|
-
for (const name in artifact.elements)
|
|
1136
|
-
result += renderElement(name, artifact.elements[name], childEnv, null);
|
|
1137
|
-
|
|
1138
|
-
result += `${env.indent}}`;
|
|
1166
|
+
result += renderElements(artifact, env);
|
|
1139
1167
|
if (!isTypeDef)
|
|
1140
1168
|
result += renderNullability(artifact);
|
|
1141
1169
|
// structured default not possible at the moment
|
|
@@ -1159,12 +1187,7 @@ function csnToCdl(csn, options) {
|
|
|
1159
1187
|
}
|
|
1160
1188
|
else if (elements) {
|
|
1161
1189
|
// anonymous aspect, either parseCdl or client CSN.
|
|
1162
|
-
|
|
1163
|
-
result += '{\n';
|
|
1164
|
-
for (const name in elements)
|
|
1165
|
-
result += renderElement(name, elements[name], childEnv);
|
|
1166
|
-
|
|
1167
|
-
result += `${env.indent}}`;
|
|
1190
|
+
result += renderElements({ elements }, env);
|
|
1168
1191
|
}
|
|
1169
1192
|
else {
|
|
1170
1193
|
throw new ModelError('Association/Composition is missing its target! Throwing exception to trigger recompilation.');
|
|
@@ -1350,6 +1373,8 @@ function csnToCdl(csn, options) {
|
|
|
1350
1373
|
*
|
|
1351
1374
|
* @param {string|object} s
|
|
1352
1375
|
* @param {number} idx
|
|
1376
|
+
* @param {boolean} inline
|
|
1377
|
+
* @param {object} env
|
|
1353
1378
|
* @returns {string}
|
|
1354
1379
|
*/
|
|
1355
1380
|
function renderPathStep(s, idx, inline, env) {
|
|
@@ -1357,12 +1382,9 @@ function csnToCdl(csn, options) {
|
|
|
1357
1382
|
if (typeof s === 'string') {
|
|
1358
1383
|
// In first path position, do not quote $projection and magic $-variables like CURRENT_DATE, $now etc.
|
|
1359
1384
|
// FIXME: We should rather explicitly recognize quoting somehow
|
|
1360
|
-
|
|
1361
|
-
if (idx === 0 &&
|
|
1362
|
-
s.startsWith('$'))
|
|
1385
|
+
if (idx === 0 && s.startsWith('$'))
|
|
1363
1386
|
return s;
|
|
1364
|
-
|
|
1365
|
-
return quoteIdIfRequired(s);
|
|
1387
|
+
return quoteIdIfRequired(s, env.additionalKeywords);
|
|
1366
1388
|
}
|
|
1367
1389
|
// ID with filters or parameters
|
|
1368
1390
|
else if (typeof s === 'object') {
|
|
@@ -1372,13 +1394,13 @@ function csnToCdl(csn, options) {
|
|
|
1372
1394
|
|
|
1373
1395
|
// Not really a path step but an object-like function call
|
|
1374
1396
|
if (s.func)
|
|
1375
|
-
return `${s.func}(${
|
|
1397
|
+
return `${s.func}(${renderArguments(s, '=>', env)})`;
|
|
1376
1398
|
|
|
1377
1399
|
// Path step, possibly with view parameters and/or filters
|
|
1378
|
-
let result = `${quoteIdIfRequired(s.id)}`;
|
|
1400
|
+
let result = `${quoteIdIfRequired(s.id, env.additionalKeywords)}`;
|
|
1379
1401
|
if (s.args) {
|
|
1380
1402
|
// View parameters
|
|
1381
|
-
result += `(${
|
|
1403
|
+
result += `(${renderArguments(s, ':', env)})`;
|
|
1382
1404
|
}
|
|
1383
1405
|
if (s.where) {
|
|
1384
1406
|
// Filter, possibly with cardinality
|
|
@@ -1402,24 +1424,48 @@ function csnToCdl(csn, options) {
|
|
|
1402
1424
|
* @param {CdlRenderEnvironment} env
|
|
1403
1425
|
* @returns {string}
|
|
1404
1426
|
*/
|
|
1405
|
-
function
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1427
|
+
function renderArguments(node, sep, env) {
|
|
1428
|
+
if (!node.args)
|
|
1429
|
+
return '';
|
|
1430
|
+
else if (Array.isArray(node.args))
|
|
1431
|
+
return renderPositionalArguments(node, env);
|
|
1432
|
+
else if (typeof node.args === 'object')
|
|
1433
|
+
return renderNamedArguments(node, sep, env);
|
|
1434
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}; expected array/object`);
|
|
1435
|
+
}
|
|
1413
1436
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1437
|
+
/**
|
|
1438
|
+
* Render named function arguments or view parameters,
|
|
1439
|
+
* using 'sep' as separator.
|
|
1440
|
+
*
|
|
1441
|
+
* @param {object} node with `args` to render
|
|
1442
|
+
* @param {string} separator
|
|
1443
|
+
* @param {CdlRenderEnvironment} env
|
|
1444
|
+
* @returns {string}
|
|
1445
|
+
*/
|
|
1446
|
+
function renderNamedArguments(node, separator, env) {
|
|
1447
|
+
return Object.keys(node.args).map(function renderNamedArgument(key) {
|
|
1448
|
+
return `${quoteIdIfRequired(key, env.additionalKeywords)} ${separator} ${renderArgument(node.args[key], env)}`;
|
|
1449
|
+
}).join(', ');
|
|
1450
|
+
}
|
|
1416
1451
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1452
|
+
/**
|
|
1453
|
+
* Render a comma separated list of positional function arguments.
|
|
1454
|
+
*
|
|
1455
|
+
* @param {object} node with `args` to render
|
|
1456
|
+
* @param {CdlRenderEnvironment} env
|
|
1457
|
+
* @returns {string}
|
|
1458
|
+
*/
|
|
1459
|
+
function renderPositionalArguments(node, env) {
|
|
1460
|
+
if (!node.args)
|
|
1461
|
+
return '';
|
|
1462
|
+
const func = node.func?.toUpperCase();
|
|
1463
|
+
if (func) {
|
|
1464
|
+
return node.args.map(function renderFunctionArg(arg, i) {
|
|
1465
|
+
return renderArgument(arg, env, getKeywordsForSpecialFunctionArgument(func, i));
|
|
1466
|
+
}).join(', ');
|
|
1420
1467
|
}
|
|
1421
|
-
|
|
1422
|
-
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1468
|
+
return node.args.map(arg => renderArgument(arg, env)).join(', ');
|
|
1423
1469
|
}
|
|
1424
1470
|
|
|
1425
1471
|
/**
|
|
@@ -1428,13 +1474,14 @@ function csnToCdl(csn, options) {
|
|
|
1428
1474
|
*
|
|
1429
1475
|
* @param {any} arg
|
|
1430
1476
|
* @param {CdlRenderEnvironment} env
|
|
1431
|
-
* @param {string[]}
|
|
1477
|
+
* @param {string[]} additionalKeywords
|
|
1432
1478
|
* @return {string}
|
|
1433
1479
|
*/
|
|
1434
|
-
function renderArgument(arg, env,
|
|
1480
|
+
function renderArgument(arg, env, additionalKeywords = []) {
|
|
1435
1481
|
// If the argument is a xpr with e.g. `=`, it may require parentheses.
|
|
1436
1482
|
// For nested xpr, `renderExpr()` will already add parentheses.
|
|
1437
|
-
|
|
1483
|
+
env = { ...env, additionalKeywords };
|
|
1484
|
+
return renderExpr(arg, env, true, !isSimpleFunctionExpression(arg && arg.xpr, additionalKeywords), true);
|
|
1438
1485
|
}
|
|
1439
1486
|
|
|
1440
1487
|
/**
|
|
@@ -1639,7 +1686,7 @@ function csnToCdl(csn, options) {
|
|
|
1639
1686
|
if (keywords.cdl_functions.includes(obj.func.toUpperCase()))
|
|
1640
1687
|
return obj.func;
|
|
1641
1688
|
const name = identifierRegex.test(obj.func) ? obj.func : quote(obj.func);
|
|
1642
|
-
return `${name}(${
|
|
1689
|
+
return `${name}(${renderArguments( obj, '=>', env )})`;
|
|
1643
1690
|
}
|
|
1644
1691
|
|
|
1645
1692
|
/**
|
|
@@ -1762,27 +1809,33 @@ function increaseIndent(env) {
|
|
|
1762
1809
|
* Quote the path steps with `![]` if necessary. For simple ids such as
|
|
1763
1810
|
* `elem` use `quoteIdIfRequired` instead.
|
|
1764
1811
|
*
|
|
1812
|
+
* In contrast to quoteIdIfRequired, does not handle additional keywords,
|
|
1813
|
+
* because it was not required, yet.
|
|
1814
|
+
*
|
|
1765
1815
|
* @param {string} path
|
|
1766
1816
|
* @returns {string}
|
|
1767
1817
|
*
|
|
1768
1818
|
* @todo For paths such as `E.key`, `key` does not have to be in quotes.
|
|
1769
1819
|
*/
|
|
1770
1820
|
function quotePathIfRequired(path) {
|
|
1771
|
-
return path.split('.').map(quoteIdIfRequired).join('.');
|
|
1821
|
+
return path.split('.').map(step => quoteIdIfRequired(step)).join('.');
|
|
1772
1822
|
}
|
|
1773
1823
|
|
|
1774
1824
|
/**
|
|
1775
1825
|
* Quote the id with `![]` if necessary. For paths such as `E.key` use
|
|
1776
1826
|
* `quotePathIfRequired` instead.
|
|
1777
1827
|
*
|
|
1828
|
+
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1829
|
+
* that also need quoting, e.g. in special functions.
|
|
1830
|
+
*
|
|
1778
1831
|
* @param {string} id
|
|
1832
|
+
* @param {string[]} [additionalKeywords]
|
|
1779
1833
|
* @return {string}
|
|
1780
1834
|
*/
|
|
1781
|
-
function quoteIdIfRequired(id) {
|
|
1835
|
+
function quoteIdIfRequired(id, additionalKeywords) {
|
|
1782
1836
|
// Quote if required for CDL
|
|
1783
|
-
if (requiresQuotingForCdl(id))
|
|
1837
|
+
if (requiresQuotingForCdl(id, additionalKeywords || []))
|
|
1784
1838
|
return quote(id);
|
|
1785
|
-
|
|
1786
1839
|
return id;
|
|
1787
1840
|
}
|
|
1788
1841
|
|
|
@@ -1818,13 +1871,18 @@ function quote(id) {
|
|
|
1818
1871
|
* does not match the first part of the `Identifier` rule of `language.g4`
|
|
1819
1872
|
* or if 'id' is a reserved keyword.
|
|
1820
1873
|
*
|
|
1874
|
+
* Set additionalKeywords to an array of UPPERCASE keywords
|
|
1875
|
+
* that also need quoting, e.g. in special functions.
|
|
1876
|
+
*
|
|
1821
1877
|
* @param {string} id
|
|
1878
|
+
* @param {string[]} [additionalKeywords]
|
|
1822
1879
|
* @return {boolean}
|
|
1823
1880
|
*/
|
|
1824
|
-
function requiresQuotingForCdl(id) {
|
|
1881
|
+
function requiresQuotingForCdl(id, additionalKeywords) {
|
|
1825
1882
|
return !identifierRegex.test(id) ||
|
|
1826
1883
|
keywords.cdl.includes(id.toUpperCase()) ||
|
|
1827
|
-
keywords.cdl_functions.includes(id.toUpperCase())
|
|
1884
|
+
keywords.cdl_functions.includes(id.toUpperCase()) ||
|
|
1885
|
+
additionalKeywords.includes(id.toUpperCase());
|
|
1828
1886
|
}
|
|
1829
1887
|
|
|
1830
1888
|
const functionExpressionOperatorsRequireParentheses = [
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -1258,7 +1258,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1258
1258
|
const funcName = regex.test(x.func) ? x.func : quoteId(x.func);
|
|
1259
1259
|
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1260
1260
|
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1261
|
-
warning(null, x.$location,
|
|
1261
|
+
warning(null, x.$location, { id: uppercaseAndUnderscore(funcName) }, 'The identifier $(ID) is a SAP HANA keyword');
|
|
1262
1262
|
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1263
1263
|
}
|
|
1264
1264
|
|
package/lib/render/toSql.js
CHANGED
|
@@ -157,7 +157,7 @@ function toSqlDdl(csn, options) {
|
|
|
157
157
|
Render column removals as HANA SQL.
|
|
158
158
|
*/
|
|
159
159
|
dropColumns(tableName, sqlIds) {
|
|
160
|
-
return [ `ALTER TABLE ${tableName} DROP (${sqlIds.join(', ')});` ];
|
|
160
|
+
return [ `ALTER TABLE ${tableName} DROP ${options.sqlDialect === 'hana' ? '(' : ''}${sqlIds.join(', ')}${options.sqlDialect === 'hana' ? ')' : ''};` ];
|
|
161
161
|
},
|
|
162
162
|
/*
|
|
163
163
|
Render association removals as HANA SQL.
|
|
@@ -221,7 +221,7 @@ function toSqlDdl(csn, options) {
|
|
|
221
221
|
};
|
|
222
222
|
|
|
223
223
|
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
|
|
224
|
-
if (!options.forHana)
|
|
224
|
+
if (!options.forHana && !isBetaEnabled(options, 'sqlExtensions'))
|
|
225
225
|
throw new Error('toSql can currently only be used with HANA preprocessing');
|
|
226
226
|
|
|
227
227
|
checkCSNVersion(csn, options);
|
|
@@ -1506,6 +1506,8 @@ function toSqlDdl(csn, options) {
|
|
|
1506
1506
|
case 'sqlite':
|
|
1507
1507
|
case 'hana':
|
|
1508
1508
|
return 'CURRENT_TIMESTAMP';
|
|
1509
|
+
case 'h2':
|
|
1510
|
+
return 'current_timestamp';
|
|
1509
1511
|
case 'postgres':
|
|
1510
1512
|
return '(current_timestamp at time zone \'UTC\')';
|
|
1511
1513
|
default:
|
|
@@ -1572,6 +1574,7 @@ function toSqlDdl(csn, options) {
|
|
|
1572
1574
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1573
1575
|
case 'postgres':
|
|
1574
1576
|
return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1577
|
+
case 'h2':
|
|
1575
1578
|
case 'plain':
|
|
1576
1579
|
return 'current_timestamp';
|
|
1577
1580
|
default:
|
|
@@ -1590,6 +1593,7 @@ function toSqlDdl(csn, options) {
|
|
|
1590
1593
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1591
1594
|
case 'postgres':
|
|
1592
1595
|
return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1596
|
+
case 'h2':
|
|
1593
1597
|
case 'plain':
|
|
1594
1598
|
return 'current_timestamp';
|
|
1595
1599
|
default:
|
|
@@ -283,6 +283,13 @@ const cdsToSqlTypes = {
|
|
|
283
283
|
'cds.hana.BINARY': 'BINARY',
|
|
284
284
|
'cds.hana.SMALLDECIMAL': 'DECIMAL',
|
|
285
285
|
},
|
|
286
|
+
h2: {
|
|
287
|
+
'cds.Binary': 'VARBINARY', // same as for plain
|
|
288
|
+
'cds.LargeBinary': 'BINARY LARGE OBJECT', // BLOB would require a length!
|
|
289
|
+
'cds.DecimalFloat': 'DECFLOAT', // Decimal and Decimal(p) is mapped to cds.DecimalFloat
|
|
290
|
+
'cds.DateTime': 'TIMESTAMP(0)',
|
|
291
|
+
'cds.Timestamp': 'TIMESTAMP(7)',
|
|
292
|
+
},
|
|
286
293
|
postgres: {
|
|
287
294
|
// See <https://www.postgresql.org/docs/current/datatype.html>
|
|
288
295
|
'cds.String': 'VARCHAR',
|
package/lib/sql-identifier.js
CHANGED
|
@@ -39,6 +39,13 @@ const keywords = require( './base/keywords' );
|
|
|
39
39
|
|
|
40
40
|
const sqlDialects = {
|
|
41
41
|
plain: {},
|
|
42
|
+
h2: {
|
|
43
|
+
// See http://www.h2database.com/html/grammar.html#name
|
|
44
|
+
regularRegex: /^[A-Za-z_][A-Za-z_0-9]*$/,
|
|
45
|
+
reservedWords: keywords.h2,
|
|
46
|
+
effectiveName: name => name.toUpperCase(),
|
|
47
|
+
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
48
|
+
},
|
|
42
49
|
sqlite: {
|
|
43
50
|
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
|
|
44
51
|
reservedWords: keywords.sqlite,
|