@sap/cds-compiler 2.12.0 → 2.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +221 -15
- package/bin/cdsc.js +125 -50
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +47 -84
- package/lib/api/options.js +5 -6
- package/lib/api/validate.js +6 -11
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +114 -18
- package/lib/base/messages.js +101 -90
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +177 -123
- package/lib/checks/annotationsOData.js +12 -33
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +6 -11
- package/lib/compiler/assert-consistency.js +6 -3
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +19 -6
- package/lib/compiler/checks.js +23 -60
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1151 -0
- package/lib/compiler/extend.js +1000 -0
- package/lib/compiler/finalize-parse-cdl.js +237 -0
- package/lib/compiler/index.js +107 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1227 -0
- package/lib/compiler/propagator.js +114 -46
- package/lib/compiler/resolve.js +1521 -0
- package/lib/compiler/shared.js +126 -65
- package/lib/compiler/tweak-assocs.js +535 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -24
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +219 -100
- package/lib/edm/edm.js +302 -230
- package/lib/edm/edmPreprocessor.js +554 -419
- package/lib/edm/edmUtils.js +138 -44
- package/lib/gen/Dictionary.json +100 -19
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5765 -4480
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +15 -3
- package/lib/json/to-csn.js +126 -68
- package/lib/language/docCommentParser.js +4 -4
- package/lib/language/genericAntlrParser.js +123 -5
- package/lib/language/language.g4 +355 -156
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +486 -59
- package/lib/main.js +41 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +252 -156
- package/lib/model/csnUtils.js +384 -297
- package/lib/model/enrichCsn.js +71 -29
- package/lib/model/revealInternalProperties.js +29 -8
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +23 -18
- package/lib/optionProcessor.js +63 -26
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +897 -947
- package/lib/render/toHdbcds.js +205 -257
- package/lib/render/toSql.js +264 -225
- package/lib/render/utils/common.js +136 -25
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +3 -1
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +104 -306
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +58 -53
- package/lib/transform/db/expansion.js +60 -33
- package/lib/transform/db/flattening.js +582 -104
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +66 -13
- package/lib/transform/db/views.js +11 -7
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +109 -208
- package/lib/transform/forOdataNew.js +59 -212
- package/lib/transform/localized.js +46 -26
- package/lib/transform/odata/toFinalBaseType.js +85 -11
- package/lib/transform/odata/typesExposure.js +147 -199
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +44 -33
- package/lib/transform/translateAssocsToJoins.js +3 -20
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +172 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -290
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
- package/lib/transform/universalCsnEnricher.js +0 -237
package/lib/render/toSql.js
CHANGED
|
@@ -7,8 +7,8 @@ const {
|
|
|
7
7
|
forEachDefinition, getResultingName, getVariableReplacement,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const {
|
|
10
|
-
renderFunc,
|
|
11
|
-
getSqlSnippets,
|
|
10
|
+
renderFunc, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
11
|
+
getSqlSnippets, getExpressionRenderer,
|
|
12
12
|
} = require('./utils/common');
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint, getIdentifierUtils,
|
|
@@ -21,6 +21,7 @@ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
|
21
21
|
const { smartFuncId } = require('../sql-identifier');
|
|
22
22
|
const { sortCsn } = require('../json/to-csn');
|
|
23
23
|
const { manageConstraints } = require('./manageConstraints');
|
|
24
|
+
const { ModelError } = require('../base/error');
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -76,9 +77,37 @@ const { manageConstraints } = require('./manageConstraints');
|
|
|
76
77
|
function toSqlDdl(csn, options) {
|
|
77
78
|
timetrace.start('SQL rendering');
|
|
78
79
|
const {
|
|
79
|
-
error, warning, info,
|
|
80
|
+
error, warning, info, throwWithAnyError,
|
|
80
81
|
} = makeMessageFunction(csn, options, 'to.sql');
|
|
81
82
|
const { quoteSqlId, prepareIdentifier } = getIdentifierUtils(options);
|
|
83
|
+
const renderExpr = getExpressionRenderer({
|
|
84
|
+
finalize: x => String(x).toUpperCase(),
|
|
85
|
+
explicitTypeCast: (x, env) => {
|
|
86
|
+
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
87
|
+
return `CAST(${renderExpr(x, env)} AS ${typeRef})`;
|
|
88
|
+
},
|
|
89
|
+
val: renderExpressionLiteral,
|
|
90
|
+
enum: (x) => {
|
|
91
|
+
// TODO: Signal is not covered by tests + better location
|
|
92
|
+
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
|
|
93
|
+
error(null, x.$location, 'Enum values are not yet supported for conversion to SQL');
|
|
94
|
+
return '';
|
|
95
|
+
},
|
|
96
|
+
ref: renderExpressionRef,
|
|
97
|
+
aliasOnly(x, _env) {
|
|
98
|
+
return x.as;
|
|
99
|
+
},
|
|
100
|
+
windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, env),
|
|
101
|
+
func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.toSql.dialect), x, options.toSql.dialect, a => renderArgs(a, '=>', env, null)),
|
|
102
|
+
xpr(x, env) {
|
|
103
|
+
if (this.nestedExpr && !x.cast)
|
|
104
|
+
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
105
|
+
|
|
106
|
+
return renderExpr(x.xpr, env, this.inline, true);
|
|
107
|
+
},
|
|
108
|
+
SELECT: (x, env) => `(${renderQuery('<subselect>', x, increaseIndent(env))})`,
|
|
109
|
+
SET: (x, env) => `(${renderQuery('<union>', x, increaseIndent(env))})`,
|
|
110
|
+
});
|
|
82
111
|
|
|
83
112
|
// Utils to render SQL statements.
|
|
84
113
|
const render = {
|
|
@@ -88,7 +117,8 @@ function toSqlDdl(csn, options) {
|
|
|
88
117
|
*/
|
|
89
118
|
addColumns: {
|
|
90
119
|
fromElementStrings(tableName, eltStrings) {
|
|
91
|
-
|
|
120
|
+
const elts = options.sqlDialect === 'hana' ? `(${eltStrings.join(', ')})` : `${eltStrings.join(', ')}`;
|
|
121
|
+
return [ `ALTER TABLE ${tableName} ADD ${elts};` ];
|
|
92
122
|
},
|
|
93
123
|
fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker) {
|
|
94
124
|
// Only extend with 'ADD' for elements/associations
|
|
@@ -170,7 +200,13 @@ function toSqlDdl(csn, options) {
|
|
|
170
200
|
Render comment string.
|
|
171
201
|
*/
|
|
172
202
|
comment(comment) {
|
|
173
|
-
return comment &&
|
|
203
|
+
return comment && renderStringForSql(comment, options.sqlDialect) || 'NULL';
|
|
204
|
+
},
|
|
205
|
+
/*
|
|
206
|
+
Alter SQL snippet for entity.
|
|
207
|
+
*/
|
|
208
|
+
alterEntitySqlSnippet(tableName, snippet) {
|
|
209
|
+
return [ `ALTER TABLE ${tableName} ${snippet};` ];
|
|
174
210
|
},
|
|
175
211
|
/*
|
|
176
212
|
Concatenate multiple statements which are to be treated as one by the API caller.
|
|
@@ -226,7 +262,7 @@ function toSqlDdl(csn, options) {
|
|
|
226
262
|
// Render each artifact extension
|
|
227
263
|
// Only HANA SQL is currently supported.
|
|
228
264
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
229
|
-
if (csn.extensions && options.toSql.dialect === 'hana') {
|
|
265
|
+
if (csn.extensions && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
230
266
|
for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
|
|
231
267
|
if (extension.extend) {
|
|
232
268
|
const artifactName = extension.extend;
|
|
@@ -239,7 +275,7 @@ function toSqlDdl(csn, options) {
|
|
|
239
275
|
|
|
240
276
|
// Render each artifact change
|
|
241
277
|
// Only HANA SQL is currently supported.
|
|
242
|
-
if (csn.migrations && options.toSql.dialect === 'hana') {
|
|
278
|
+
if (csn.migrations && (options.toSql.dialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
243
279
|
for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
|
|
244
280
|
if (migration.migrate) {
|
|
245
281
|
const artifactName = migration.migrate;
|
|
@@ -256,7 +292,7 @@ function toSqlDdl(csn, options) {
|
|
|
256
292
|
deletionsDuplicateChecker.check(error);
|
|
257
293
|
|
|
258
294
|
// Throw exception in case of errors
|
|
259
|
-
|
|
295
|
+
throwWithAnyError();
|
|
260
296
|
|
|
261
297
|
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src === 'sql'
|
|
262
298
|
// (relying on the order of dictionaries above)
|
|
@@ -284,8 +320,8 @@ function toSqlDdl(csn, options) {
|
|
|
284
320
|
delete mainResultObj[hdbKind];
|
|
285
321
|
}
|
|
286
322
|
|
|
287
|
-
// add `ALTER TABLE ADD CONSTRAINT` statements
|
|
288
|
-
if (options.
|
|
323
|
+
// add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
|
|
324
|
+
if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
|
|
289
325
|
const alterStmts = manageConstraints(csn, options);
|
|
290
326
|
|
|
291
327
|
for ( const constraintName of Object.keys(alterStmts))
|
|
@@ -318,7 +354,6 @@ function toSqlDdl(csn, options) {
|
|
|
318
354
|
|
|
319
355
|
switch (art.kind) {
|
|
320
356
|
case 'entity':
|
|
321
|
-
case 'view':
|
|
322
357
|
if (getNormalizedQuery(art).query) {
|
|
323
358
|
const result = renderView(artifactName, art, env);
|
|
324
359
|
if (result)
|
|
@@ -340,7 +375,7 @@ function toSqlDdl(csn, options) {
|
|
|
340
375
|
// Ignore: not SQL-relevant
|
|
341
376
|
return;
|
|
342
377
|
default:
|
|
343
|
-
throw new
|
|
378
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
344
379
|
}
|
|
345
380
|
}
|
|
346
381
|
|
|
@@ -361,7 +396,7 @@ function toSqlDdl(csn, options) {
|
|
|
361
396
|
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
|
|
362
397
|
|
|
363
398
|
if (!artifactName)
|
|
364
|
-
throw new
|
|
399
|
+
throw new ModelError(`Undefined artifact name: ${artifactName}`);
|
|
365
400
|
}
|
|
366
401
|
|
|
367
402
|
// Render an artifact deletion into the appropriate dictionary of 'resultObj'.
|
|
@@ -401,12 +436,26 @@ function toSqlDdl(csn, options) {
|
|
|
401
436
|
? renderAssociationElement(eltName, defVariant, env)
|
|
402
437
|
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
403
438
|
}
|
|
404
|
-
function
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
439
|
+
function getEltStrNoProps(defVariant, eltName, ...props) {
|
|
440
|
+
const defNoProps = Object.assign({}, defVariant);
|
|
441
|
+
for (const prop of props)
|
|
442
|
+
delete defNoProps[prop];
|
|
443
|
+
return getEltStr(defNoProps, eltName);
|
|
444
|
+
}
|
|
445
|
+
function oldAnnoChangedIncompatibly(defOld, defNew) {
|
|
446
|
+
return typeof defOld === 'string' && defOld.trim().length && !(typeof defNew === 'string' && defNew.trim().startsWith(`${defOld.trim()} `));
|
|
447
|
+
}
|
|
448
|
+
function getUnknownSqlReason(anno, artName, defOld, defNew, eltName) {
|
|
449
|
+
const changeKind = defNew === undefined
|
|
450
|
+
? `removed (previous value: ${JSON.stringify(defOld)})`
|
|
451
|
+
: `changed from ${JSON.stringify(defOld)} to ${JSON.stringify(defNew)}`;
|
|
452
|
+
return eltName
|
|
453
|
+
? `annotation ${anno} of element ${artName}:${eltName} has been ${changeKind}`
|
|
454
|
+
: `annotation ${anno} of artifact ${artName} has been ${changeKind}`;
|
|
408
455
|
}
|
|
409
456
|
|
|
457
|
+
const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
|
|
458
|
+
|
|
410
459
|
const tableName = renderArtifactName(artifactName);
|
|
411
460
|
|
|
412
461
|
// Change entity properties
|
|
@@ -416,6 +465,15 @@ function toSqlDdl(csn, options) {
|
|
|
416
465
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
417
466
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
418
467
|
}
|
|
468
|
+
else if (sqlSnippetAnnos.includes(prop)) { // NOTE: @sql.replace may be supported in the future
|
|
469
|
+
if (oldAnnoChangedIncompatibly(def.old, def.new)) {
|
|
470
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
471
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(prop, artifactName, def.old, def.new));
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
addMigration(resultObj, artifactName, false, render.alterEntitySqlSnippet(tableName, def.new));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
419
477
|
}
|
|
420
478
|
}
|
|
421
479
|
|
|
@@ -450,9 +508,28 @@ function toSqlDdl(csn, options) {
|
|
|
450
508
|
if (eltStrNew === eltStrOld)
|
|
451
509
|
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
452
510
|
|
|
511
|
+
const annosIncompat = [];
|
|
512
|
+
sqlSnippetAnnos
|
|
513
|
+
.filter(anno => def.old[anno] !== def.new[anno])
|
|
514
|
+
.forEach((anno) => { // NOTE: @sql.replace may be supported in the future
|
|
515
|
+
if (oldAnnoChangedIncompatibly(def.old[anno], def.new[anno])) {
|
|
516
|
+
annosIncompat.push(anno);
|
|
517
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
518
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(anno, artifactName, def.old[anno], def.new[anno], eltName));
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
if (annosIncompat.length) {
|
|
523
|
+
const eltStrOldNoAnnos = getEltStrNoProps(def.old, eltName, ...annosIncompat);
|
|
524
|
+
const eltStrNewNoAnnos = getEltStrNoProps(def.new, eltName, ...annosIncompat);
|
|
525
|
+
if (eltStrOldNoAnnos === eltStrNewNoAnnos) { // only incompatibly-changed annos were modified
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
453
530
|
if (def.old.doc !== def.new.doc) {
|
|
454
|
-
const eltStrOldNoDoc =
|
|
455
|
-
const eltStrNewNoDoc =
|
|
531
|
+
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
532
|
+
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
456
533
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
457
534
|
const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
|
|
458
535
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
@@ -501,7 +578,7 @@ function toSqlDdl(csn, options) {
|
|
|
501
578
|
// Explicitly specified
|
|
502
579
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
503
580
|
}
|
|
504
|
-
else {
|
|
581
|
+
else if (!front) {
|
|
505
582
|
// in 'hdbtable' files, COLUMN or ROW is mandatory, and COLUMN is the default
|
|
506
583
|
result += 'COLUMN ';
|
|
507
584
|
}
|
|
@@ -524,7 +601,8 @@ function toSqlDdl(csn, options) {
|
|
|
524
601
|
if (primaryKeys !== '')
|
|
525
602
|
result += `,\n${childEnv.indent}${primaryKeys}`;
|
|
526
603
|
|
|
527
|
-
|
|
604
|
+
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
605
|
+
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
528
606
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
529
607
|
const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
|
|
530
608
|
const referentialConstraints = {};
|
|
@@ -575,8 +653,8 @@ function toSqlDdl(csn, options) {
|
|
|
575
653
|
if (options.toSql.dialect === 'hana')
|
|
576
654
|
renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
|
|
577
655
|
|
|
578
|
-
if (options.
|
|
579
|
-
result += ` COMMENT
|
|
656
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
657
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
580
658
|
|
|
581
659
|
if (back)
|
|
582
660
|
result += back;
|
|
@@ -612,10 +690,15 @@ function toSqlDdl(csn, options) {
|
|
|
612
690
|
}
|
|
613
691
|
}
|
|
614
692
|
|
|
615
|
-
function addMigration(resultObj, artifactName, drop, sqlArray) {
|
|
693
|
+
function addMigration(resultObj, artifactName, drop, sqlArray, description) {
|
|
616
694
|
if (!(artifactName in resultObj.migrations))
|
|
617
695
|
resultObj.migrations[artifactName] = [];
|
|
618
696
|
|
|
697
|
+
if (!sqlArray) {
|
|
698
|
+
if (description)
|
|
699
|
+
resultObj.migrations[artifactName].push({ description });
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
619
702
|
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
620
703
|
resultObj.migrations[artifactName].push(...migrations);
|
|
621
704
|
}
|
|
@@ -678,15 +761,15 @@ function toSqlDdl(csn, options) {
|
|
|
678
761
|
if (fzindex && options.toSql.dialect === 'hana')
|
|
679
762
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
680
763
|
|
|
681
|
-
if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
|
|
682
|
-
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
683
|
-
|
|
684
764
|
// (table) elements can only have a @sql.append
|
|
685
765
|
const { back } = getSqlSnippets(options, elm);
|
|
686
766
|
|
|
687
|
-
if (back !== '')
|
|
767
|
+
if (back !== '') // Needs to be rendered before the COMMENT
|
|
688
768
|
result += back;
|
|
689
769
|
|
|
770
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(elm, options))
|
|
771
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(elm), options.sqlDialect)}`;
|
|
772
|
+
|
|
690
773
|
return result;
|
|
691
774
|
}
|
|
692
775
|
|
|
@@ -751,7 +834,7 @@ function toSqlDdl(csn, options) {
|
|
|
751
834
|
// This also affects renderIndexes
|
|
752
835
|
tc = tc.hana;
|
|
753
836
|
if (!tc)
|
|
754
|
-
throw new
|
|
837
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
755
838
|
|
|
756
839
|
if (tc.tableSuffix) {
|
|
757
840
|
// Although we could just render the whole bandwurm as one stream of tokens, the
|
|
@@ -820,7 +903,7 @@ function toSqlDdl(csn, options) {
|
|
|
820
903
|
const i = index.indexOf('index');
|
|
821
904
|
const j = index.indexOf('(');
|
|
822
905
|
if (i > index.length - 2 || !index[i + 1].ref || j < i || j > index.length - 2)
|
|
823
|
-
throw new
|
|
906
|
+
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
824
907
|
|
|
825
908
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
826
909
|
if (options.toSql.names === 'plain')
|
|
@@ -876,7 +959,7 @@ function toSqlDdl(csn, options) {
|
|
|
876
959
|
|
|
877
960
|
// Sanity check
|
|
878
961
|
if (!source.ref)
|
|
879
|
-
throw new
|
|
962
|
+
throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
|
|
880
963
|
|
|
881
964
|
return renderAbsolutePathWithAlias(artifactName, source, env);
|
|
882
965
|
}
|
|
@@ -918,7 +1001,7 @@ function toSqlDdl(csn, options) {
|
|
|
918
1001
|
function renderAbsolutePathWithAlias(artifactName, path, env) {
|
|
919
1002
|
// This actually can't happen anymore because assoc2joins should have taken care of it
|
|
920
1003
|
if (path.ref[0].where)
|
|
921
|
-
throw new
|
|
1004
|
+
throw new ModelError(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
|
|
922
1005
|
|
|
923
1006
|
|
|
924
1007
|
// SQL needs a ':' after path.ref[0] to separate associations
|
|
@@ -956,7 +1039,7 @@ function toSqlDdl(csn, options) {
|
|
|
956
1039
|
function renderAbsolutePath(path, sep, env) {
|
|
957
1040
|
// Sanity checks
|
|
958
1041
|
if (!path.ref)
|
|
959
|
-
throw new
|
|
1042
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
960
1043
|
|
|
961
1044
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
962
1045
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
@@ -973,7 +1056,7 @@ function toSqlDdl(csn, options) {
|
|
|
973
1056
|
if (ref && ref.params) {
|
|
974
1057
|
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
975
1058
|
}
|
|
976
|
-
else if (
|
|
1059
|
+
else if (syntax === 'udf') {
|
|
977
1060
|
// if syntax is user defined function, render empty argument list
|
|
978
1061
|
// CV without parameters is called as simple view
|
|
979
1062
|
result += '()';
|
|
@@ -1004,15 +1087,15 @@ function toSqlDdl(csn, options) {
|
|
|
1004
1087
|
const args = node.args ? node.args : {};
|
|
1005
1088
|
// Positional arguments
|
|
1006
1089
|
if (Array.isArray(args))
|
|
1007
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1090
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
1008
1091
|
|
|
1009
1092
|
// Named arguments (object/dict)
|
|
1010
1093
|
else if (typeof args === 'object')
|
|
1011
1094
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1012
|
-
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1095
|
+
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
|
|
1013
1096
|
|
|
1014
1097
|
|
|
1015
|
-
throw new
|
|
1098
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1016
1099
|
|
|
1017
1100
|
|
|
1018
1101
|
/**
|
|
@@ -1035,13 +1118,14 @@ function toSqlDdl(csn, options) {
|
|
|
1035
1118
|
* Return the resulting source string (one line per column item, no CR).
|
|
1036
1119
|
*
|
|
1037
1120
|
* @param {object} col Column to render
|
|
1121
|
+
* @param {CSN.Elements} elements of leading or subquery
|
|
1038
1122
|
* @param {object} env Render environment
|
|
1039
1123
|
* @returns {string} Rendered column
|
|
1040
1124
|
*/
|
|
1041
|
-
function renderViewColumn(col, env) {
|
|
1125
|
+
function renderViewColumn(col, elements, env) {
|
|
1042
1126
|
let result = '';
|
|
1043
1127
|
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
1044
|
-
if (leaf &&
|
|
1128
|
+
if (leaf && elements[leaf] && elements[leaf].virtual) {
|
|
1045
1129
|
if (isDeprecatedEnabled(options, 'renderVirtualElements'))
|
|
1046
1130
|
// render a virtual column 'null as <alias>'
|
|
1047
1131
|
result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
|
|
@@ -1070,11 +1154,11 @@ function toSqlDdl(csn, options) {
|
|
|
1070
1154
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
|
|
1071
1155
|
let result = `VIEW ${viewName}`;
|
|
1072
1156
|
|
|
1073
|
-
if (options.
|
|
1074
|
-
result += ` COMMENT
|
|
1157
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
1158
|
+
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
1075
1159
|
|
|
1076
1160
|
result += renderParameterDefinitions(artifactName, art.params);
|
|
1077
|
-
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env)}`;
|
|
1161
|
+
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env, art.elements)}`;
|
|
1078
1162
|
|
|
1079
1163
|
const childEnv = increaseIndent(env);
|
|
1080
1164
|
const associations = Object.keys(art.elements).filter(name => !!art.elements[name].target)
|
|
@@ -1134,9 +1218,10 @@ function toSqlDdl(csn, options) {
|
|
|
1134
1218
|
* @param {string} artifactName Artifact containing the query
|
|
1135
1219
|
* @param {CSN.Query} query CSN query
|
|
1136
1220
|
* @param {object} env Render environment
|
|
1221
|
+
* @param {CSN.Elements} [elements] to override direct query elements - e.g. leading union should win
|
|
1137
1222
|
* @returns {string} Rendered query
|
|
1138
1223
|
*/
|
|
1139
|
-
function renderQuery(artifactName, query, env) {
|
|
1224
|
+
function renderQuery(artifactName, query, env, elements = null) {
|
|
1140
1225
|
let result = '';
|
|
1141
1226
|
// Set operator, like UNION, INTERSECT, ...
|
|
1142
1227
|
if (query.SET) {
|
|
@@ -1145,7 +1230,7 @@ function toSqlDdl(csn, options) {
|
|
|
1145
1230
|
// Wrap each query in the SET in parentheses that
|
|
1146
1231
|
// - is a SET itself (to preserve precedence between the different SET operations),
|
|
1147
1232
|
// - has an ORDER BY/LIMIT (because UNION etc. can't stand directly behind an ORDER BY)
|
|
1148
|
-
const queryString = renderQuery(artifactName, arg, env);
|
|
1233
|
+
const queryString = renderQuery(artifactName, arg, env, elements || query.SET.elements);
|
|
1149
1234
|
return (arg.SET || arg.SELECT && (arg.SELECT.orderBy || arg.SELECT.limit)) ? `(${queryString})` : queryString;
|
|
1150
1235
|
})
|
|
1151
1236
|
.join(`\n${env.indent}${query.SET.op && query.SET.op.toUpperCase()}${query.SET.all ? ' ALL ' : ' '}`);
|
|
@@ -1166,7 +1251,7 @@ function toSqlDdl(csn, options) {
|
|
|
1166
1251
|
}
|
|
1167
1252
|
// Otherwise must have a SELECT
|
|
1168
1253
|
else if (!query.SELECT) {
|
|
1169
|
-
throw new
|
|
1254
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
1170
1255
|
}
|
|
1171
1256
|
const select = query.SELECT;
|
|
1172
1257
|
const childEnv = increaseIndent(env);
|
|
@@ -1174,7 +1259,7 @@ function toSqlDdl(csn, options) {
|
|
|
1174
1259
|
// FIXME: We probably also need to consider `excluding` here ?
|
|
1175
1260
|
result += `\n${(select.columns || [ '*' ])
|
|
1176
1261
|
.filter(col => !(select.mixin || Object.create(null))[firstPathStepId(col.ref)]) // No mixin columns
|
|
1177
|
-
.map(col => renderViewColumn(col, childEnv))
|
|
1262
|
+
.map(col => renderViewColumn(col, elements || select.elements, childEnv))
|
|
1178
1263
|
.filter(s => s !== '')
|
|
1179
1264
|
.join(',\n')}\n`;
|
|
1180
1265
|
result += `${env.indent}FROM ${renderViewSource(artifactName, select.from, env)}`;
|
|
@@ -1182,7 +1267,7 @@ function toSqlDdl(csn, options) {
|
|
|
1182
1267
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
1183
1268
|
|
|
1184
1269
|
if (select.groupBy)
|
|
1185
|
-
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
1270
|
+
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
1186
1271
|
|
|
1187
1272
|
if (select.having)
|
|
1188
1273
|
result += `\n${env.indent}HAVING ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -1233,7 +1318,7 @@ function toSqlDdl(csn, options) {
|
|
|
1233
1318
|
* @returns {string} Rendered ORDER BY entry
|
|
1234
1319
|
*/
|
|
1235
1320
|
function renderOrderByEntry(entry, env) {
|
|
1236
|
-
let result = renderExpr(entry, env);
|
|
1321
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
1237
1322
|
if (entry.sort)
|
|
1238
1323
|
result += ` ${entry.sort.toUpperCase()}`;
|
|
1239
1324
|
|
|
@@ -1257,7 +1342,7 @@ function toSqlDdl(csn, options) {
|
|
|
1257
1342
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1258
1343
|
if (!elm.type) {
|
|
1259
1344
|
if (!elm.elements)
|
|
1260
|
-
throw new
|
|
1345
|
+
throw new ModelError(`Missing type of: ${elementName}`);
|
|
1261
1346
|
|
|
1262
1347
|
// TODO: Signal is not covered by tests + better location
|
|
1263
1348
|
error(null, [ 'definitions', artifactName, 'elements', elementName ],
|
|
@@ -1280,7 +1365,7 @@ function toSqlDdl(csn, options) {
|
|
|
1280
1365
|
result += renderBuiltinType(elm.type);
|
|
1281
1366
|
}
|
|
1282
1367
|
else {
|
|
1283
|
-
throw new
|
|
1368
|
+
throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
|
|
1284
1369
|
}
|
|
1285
1370
|
result += renderTypeParameters(elm);
|
|
1286
1371
|
return result;
|
|
@@ -1309,7 +1394,7 @@ function toSqlDdl(csn, options) {
|
|
|
1309
1394
|
* Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1310
1395
|
*
|
|
1311
1396
|
* @param {object} obj Object to render for
|
|
1312
|
-
* @param {boolean} treatKeyAsNotNull
|
|
1397
|
+
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1313
1398
|
* @returns {string} NULL/NOT NULL or ''
|
|
1314
1399
|
*/
|
|
1315
1400
|
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
@@ -1349,168 +1434,80 @@ function toSqlDdl(csn, options) {
|
|
|
1349
1434
|
return params.length === 0 ? '' : `(${params.join(', ')})`;
|
|
1350
1435
|
}
|
|
1351
1436
|
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
return
|
|
1372
|
-
return renderExprObject(expr);
|
|
1373
|
-
}
|
|
1374
|
-
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1375
|
-
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
1437
|
+
function renderExpressionLiteral(x) {
|
|
1438
|
+
// Literal value, possibly with explicit 'literal' property
|
|
1439
|
+
switch (x.literal || typeof x.val) {
|
|
1440
|
+
case 'number':
|
|
1441
|
+
case 'boolean':
|
|
1442
|
+
case 'null':
|
|
1443
|
+
// 17.42, NULL, TRUE
|
|
1444
|
+
return String(x.val).toUpperCase();
|
|
1445
|
+
case 'x':
|
|
1446
|
+
// x'f000'
|
|
1447
|
+
return `${x.literal}'${x.val}'`;
|
|
1448
|
+
case 'date':
|
|
1449
|
+
case 'time':
|
|
1450
|
+
case 'timestamp':
|
|
1451
|
+
if (options.toSql.dialect === 'sqlite') {
|
|
1452
|
+
// simple string literal '2017-11-02'
|
|
1453
|
+
return `'${x.val}'`;
|
|
1454
|
+
}
|
|
1455
|
+
// date'2017-11-02'
|
|
1456
|
+
return `${x.literal}'${x.val}'`;
|
|
1376
1457
|
|
|
1377
|
-
|
|
1458
|
+
case 'string':
|
|
1459
|
+
// 'foo', with proper escaping
|
|
1460
|
+
return renderStringForSql(x.val, options.sqlDialect);
|
|
1461
|
+
case 'object':
|
|
1462
|
+
if (x.val === null)
|
|
1463
|
+
return 'NULL';
|
|
1378
1464
|
|
|
1465
|
+
// otherwise fall through to
|
|
1466
|
+
default:
|
|
1467
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1379
1470
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
return `(${x.list.map(item => renderExpr(item)).join(', ')})`;
|
|
1388
|
-
}
|
|
1389
|
-
else if (x.val !== undefined) {
|
|
1390
|
-
return renderExpressionLiteral(x);
|
|
1391
|
-
}
|
|
1392
|
-
// Enum symbol
|
|
1393
|
-
else if (x['#']) {
|
|
1394
|
-
// #foo
|
|
1395
|
-
// TODO: Signal is not covered by tests + better location
|
|
1396
|
-
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
|
|
1397
|
-
error(null, x.$location, 'Enum values are not yet supported for conversion to SQL');
|
|
1398
|
-
return '';
|
|
1399
|
-
}
|
|
1400
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
1401
|
-
else if (x.ref) {
|
|
1402
|
-
return renderExpressionRef(x);
|
|
1403
|
-
}
|
|
1404
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
1405
|
-
else if (x.func) {
|
|
1406
|
-
const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
|
|
1407
|
-
if (x.xpr)
|
|
1408
|
-
return renderWindowFunction(funcName, x, env);
|
|
1409
|
-
return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));
|
|
1410
|
-
}
|
|
1411
|
-
// Nested expression
|
|
1412
|
-
else if (x.xpr) {
|
|
1413
|
-
if (nestedExpr && !x.cast)
|
|
1414
|
-
return `(${renderExpr(x.xpr, env, inline, true)})`;
|
|
1471
|
+
function renderExpressionRef(x, env) {
|
|
1472
|
+
if (!x.param && !x.global) {
|
|
1473
|
+
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1474
|
+
|
|
1475
|
+
if (x.ref[0] === '$user') {
|
|
1476
|
+
if (magicReplacement !== null)
|
|
1477
|
+
return `'${magicReplacement}'`;
|
|
1415
1478
|
|
|
1416
|
-
|
|
1479
|
+
const result = render$user();
|
|
1480
|
+
// Invalid second path step doesn't cause a return
|
|
1481
|
+
if (result)
|
|
1482
|
+
return result;
|
|
1417
1483
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
//
|
|
1421
|
-
|
|
1484
|
+
else if (x.ref[0] === '$at') {
|
|
1485
|
+
const result = render$at();
|
|
1486
|
+
// Invalid second path step doesn't cause a return
|
|
1487
|
+
if (result)
|
|
1488
|
+
return result;
|
|
1422
1489
|
}
|
|
1423
|
-
else if (x.
|
|
1424
|
-
|
|
1425
|
-
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1490
|
+
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1491
|
+
return `'${magicReplacement}'`;
|
|
1426
1492
|
}
|
|
1427
|
-
|
|
1428
|
-
throw new Error(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1429
1493
|
}
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
return r;
|
|
1494
|
+
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1495
|
+
// assume that it was not if the path has length 2 (
|
|
1496
|
+
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
|
|
1497
|
+
// Parameters must be uppercased and unquoted in SQL
|
|
1498
|
+
return `:${x.ref[1].toUpperCase()}`;
|
|
1436
1499
|
}
|
|
1500
|
+
if (x.param)
|
|
1501
|
+
return `:${x.ref[0].toUpperCase()}`;
|
|
1437
1502
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
case 'number':
|
|
1442
|
-
case 'boolean':
|
|
1443
|
-
case 'null':
|
|
1444
|
-
// 17.42, NULL, TRUE
|
|
1445
|
-
return String(x.val).toUpperCase();
|
|
1446
|
-
case 'x':
|
|
1447
|
-
// x'f000'
|
|
1448
|
-
return `${x.literal}'${x.val}'`;
|
|
1449
|
-
case 'date':
|
|
1450
|
-
case 'time':
|
|
1451
|
-
case 'timestamp':
|
|
1452
|
-
if (options.toSql.dialect === 'sqlite') {
|
|
1453
|
-
// simple string literal '2017-11-02'
|
|
1454
|
-
return `'${x.val}'`;
|
|
1455
|
-
}
|
|
1456
|
-
// date'2017-11-02'
|
|
1457
|
-
return `${x.literal}'${x.val}'`;
|
|
1458
|
-
|
|
1459
|
-
case 'string':
|
|
1460
|
-
// 'foo', with proper escaping
|
|
1461
|
-
return `'${x.val.replace(/'/g, '\'\'')}'`;
|
|
1462
|
-
case 'object':
|
|
1463
|
-
if (x.val === null)
|
|
1464
|
-
return 'NULL';
|
|
1465
|
-
|
|
1466
|
-
// otherwise fall through to
|
|
1467
|
-
default:
|
|
1468
|
-
throw new Error(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
function renderExpressionRef(x) {
|
|
1473
|
-
if (!x.param && !x.global) {
|
|
1474
|
-
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1475
|
-
|
|
1476
|
-
if (x.ref[0] === '$user') {
|
|
1477
|
-
if (magicReplacement !== null)
|
|
1478
|
-
return `'${magicReplacement}'`;
|
|
1479
|
-
|
|
1480
|
-
const result = render$user(x);
|
|
1481
|
-
// Invalid second path step doesn't cause a return
|
|
1482
|
-
if (result)
|
|
1483
|
-
return result;
|
|
1484
|
-
}
|
|
1485
|
-
else if (x.ref[0] === '$at') {
|
|
1486
|
-
const result = render$at(x);
|
|
1487
|
-
// Invalid second path step doesn't cause a return
|
|
1488
|
-
if (result)
|
|
1489
|
-
return result;
|
|
1490
|
-
}
|
|
1491
|
-
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1492
|
-
return `'${magicReplacement}'`;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
1496
|
-
// assume that it was not if the path has length 2 (
|
|
1497
|
-
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length === 2) {
|
|
1498
|
-
// Parameters must be uppercased and unquoted in SQL
|
|
1499
|
-
return `:${x.ref[1].toUpperCase()}`;
|
|
1500
|
-
}
|
|
1501
|
-
if (x.param)
|
|
1502
|
-
return `:${x.ref[0].toUpperCase()}`;
|
|
1503
|
-
|
|
1504
|
-
return x.ref.map(renderPathStep)
|
|
1505
|
-
.filter(s => s !== '')
|
|
1506
|
-
.join('.');
|
|
1507
|
-
}
|
|
1503
|
+
return x.ref.map(renderPathStep)
|
|
1504
|
+
.filter(s => s !== '')
|
|
1505
|
+
.join('.');
|
|
1508
1506
|
|
|
1509
1507
|
/**
|
|
1510
|
-
* @param {object} x
|
|
1511
1508
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1512
1509
|
*/
|
|
1513
|
-
function render$user(
|
|
1510
|
+
function render$user() {
|
|
1514
1511
|
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1515
1512
|
if (x.ref[1] === 'id') {
|
|
1516
1513
|
// Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
|
|
@@ -1538,18 +1535,17 @@ function toSqlDdl(csn, options) {
|
|
|
1538
1535
|
}
|
|
1539
1536
|
/**
|
|
1540
1537
|
* For a given reference starting with $at, render a 'current_timestamp' literal for plain.
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
function render$at(x) {
|
|
1538
|
+
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
|
|
1539
|
+
*
|
|
1540
|
+
*
|
|
1541
|
+
* For sqlite, we render the string-format-time (strftime) function.
|
|
1542
|
+
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
1543
|
+
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
1544
|
+
* --> Therefore the comparison in the temporal where clause doesn't work properly.
|
|
1545
|
+
*
|
|
1546
|
+
* @returns {string|null} Null in case of an invalid second path step
|
|
1547
|
+
*/
|
|
1548
|
+
function render$at() {
|
|
1553
1549
|
if (x.ref[1] === 'from') {
|
|
1554
1550
|
switch (options.toSql.dialect) {
|
|
1555
1551
|
case 'sqlite': {
|
|
@@ -1583,18 +1579,6 @@ function toSqlDdl(csn, options) {
|
|
|
1583
1579
|
return null;
|
|
1584
1580
|
}
|
|
1585
1581
|
|
|
1586
|
-
/**
|
|
1587
|
-
* Renders an explicit `cast()` inside an 'xpr'.
|
|
1588
|
-
*
|
|
1589
|
-
* @param {object} x Expression with cast
|
|
1590
|
-
* @param {string} value Value to cast
|
|
1591
|
-
* @returns {string} CAST statement
|
|
1592
|
-
*/
|
|
1593
|
-
function renderExplicitTypeCast(x, value) {
|
|
1594
|
-
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
1595
|
-
return `CAST(${value} AS ${typeRef})`;
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
1582
|
/**
|
|
1599
1583
|
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1600
1584
|
*
|
|
@@ -1628,7 +1612,7 @@ function toSqlDdl(csn, options) {
|
|
|
1628
1612
|
else if (typeof s === 'object') {
|
|
1629
1613
|
// Sanity check
|
|
1630
1614
|
if (!s.func && !s.id)
|
|
1631
|
-
throw new
|
|
1615
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1632
1616
|
|
|
1633
1617
|
// Not really a path step but an object-like function call
|
|
1634
1618
|
if (s.func)
|
|
@@ -1648,10 +1632,17 @@ function toSqlDdl(csn, options) {
|
|
|
1648
1632
|
return result;
|
|
1649
1633
|
}
|
|
1650
1634
|
|
|
1651
|
-
throw new
|
|
1635
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1652
1636
|
}
|
|
1653
1637
|
}
|
|
1654
1638
|
|
|
1639
|
+
function renderWindowFunction(funcName, node, fctEnv) {
|
|
1640
|
+
const suffix = node.xpr[0]; // OVER
|
|
1641
|
+
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
|
|
1642
|
+
r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
|
|
1643
|
+
return r;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1655
1646
|
/**
|
|
1656
1647
|
* Returns a copy of 'env' with increased indentation
|
|
1657
1648
|
*
|
|
@@ -1663,6 +1654,54 @@ function toSqlDdl(csn, options) {
|
|
|
1663
1654
|
}
|
|
1664
1655
|
}
|
|
1665
1656
|
|
|
1657
|
+
/**
|
|
1658
|
+
* Render the given string for SQL databases.
|
|
1659
|
+
*
|
|
1660
|
+
* @param {string} str
|
|
1661
|
+
* @param {string} sqlDialect
|
|
1662
|
+
* @return {string}
|
|
1663
|
+
*/
|
|
1664
|
+
function renderStringForSql(str, sqlDialect) {
|
|
1665
|
+
if (sqlDialect === 'hana' || sqlDialect === 'sqlite') {
|
|
1666
|
+
// SQLite
|
|
1667
|
+
// ======
|
|
1668
|
+
// SQLite's tokenizer available at
|
|
1669
|
+
// <https://www.sqlite.org/src/file?name=src/tokenize.c>.
|
|
1670
|
+
//
|
|
1671
|
+
// Note that NUL may have side effects, as explained on
|
|
1672
|
+
// <https://sqlite.org/nulinstr.html>.
|
|
1673
|
+
//
|
|
1674
|
+
//
|
|
1675
|
+
// HANA
|
|
1676
|
+
// ====
|
|
1677
|
+
// Respects the specification available at
|
|
1678
|
+
// <https://help.sap.com/doc/9b40bf74f8644b898fb07dabdd2a36ad/2.0.04/en-US/SAP_HANA_SQL_Reference_Guide_en.pdf>.
|
|
1679
|
+
//
|
|
1680
|
+
// <string_literal> ::= <single_quote>[<any_character>...]<single_quote>
|
|
1681
|
+
// <single_quote> ::= '
|
|
1682
|
+
//
|
|
1683
|
+
// and
|
|
1684
|
+
// > # Quotation marks
|
|
1685
|
+
// > Single quotation marks are used to delimit string literals.
|
|
1686
|
+
// > A single quotation mark itself can be represented using two single quotation marks.
|
|
1687
|
+
str = str.replace(/'/g, '\'\'')
|
|
1688
|
+
.replace(/\u{0}/ug, '\' || CHAR(0) || \'');
|
|
1689
|
+
}
|
|
1690
|
+
else {
|
|
1691
|
+
// Generic SQL databases
|
|
1692
|
+
// =====================
|
|
1693
|
+
// While escaping NUL may be useful to avoid the SQL file being identified as binary,
|
|
1694
|
+
// we can't escape it using `CHAR(0)`. This function is not available on e.g. PostgreSQL.
|
|
1695
|
+
// On top of this, PostgreSQL also has this limitation:
|
|
1696
|
+
// > chr(int) | text | Character with the given code. For UTF8 the argument is treated as a Unicode code point.
|
|
1697
|
+
// > | | For other multibyte encodings the argument must designate an ASCII character. The NULL (0)
|
|
1698
|
+
// > | | character is not allowed because text data types cannot store such bytes.
|
|
1699
|
+
// - <https://www.postgresql.org/docs/9.1/functions-string.html>
|
|
1700
|
+
str = str.replace(/'/g, '\'\'');
|
|
1701
|
+
}
|
|
1702
|
+
return `'${str}'`;
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1666
1705
|
module.exports = {
|
|
1667
1706
|
toSqlDdl,
|
|
1668
1707
|
};
|