@sap/cds-compiler 3.4.2 → 3.5.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 +80 -0
- package/README.md +1 -0
- package/bin/cds_update_identifiers.js +5 -5
- package/bin/cdsc.js +15 -16
- package/bin/cdshi.js +19 -6
- package/doc/CHANGELOG_ARCHIVE.md +2 -2
- package/doc/CHANGELOG_BETA.md +9 -1
- package/doc/CHANGELOG_DEPRECATED.md +2 -0
- package/lib/api/main.js +61 -59
- package/lib/api/options.js +4 -2
- package/lib/api/validate.js +2 -2
- package/lib/base/cleanSymbols.js +2 -3
- package/lib/base/dictionaries.js +6 -6
- package/lib/base/error.js +2 -2
- package/lib/base/keywords.js +6 -6
- package/lib/base/location.js +11 -12
- package/lib/base/message-registry.js +177 -58
- package/lib/base/messages.js +252 -180
- package/lib/base/model.js +14 -11
- package/lib/base/node-helpers.js +9 -10
- package/lib/base/optionProcessorHelper.js +138 -129
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +5 -5
- package/lib/checks/annotationsOData.js +4 -4
- package/lib/checks/arrayOfs.js +1 -1
- package/lib/checks/cdsPersistence.js +1 -1
- package/lib/checks/checkForTypes.js +3 -3
- package/lib/checks/defaultValues.js +3 -3
- package/lib/checks/elements.js +7 -7
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +1 -1
- package/lib/checks/invalidTarget.js +4 -4
- package/lib/checks/managedInType.js +1 -1
- package/lib/checks/managedWithoutKeys.js +1 -1
- package/lib/checks/nonexpandableStructured.js +5 -3
- package/lib/checks/nullableKeys.js +1 -1
- package/lib/checks/onConditions.js +5 -6
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -2
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +4 -4
- package/lib/checks/types.js +7 -7
- package/lib/checks/utils.js +4 -4
- package/lib/checks/validator.js +16 -13
- package/lib/compiler/.eslintrc.json +4 -1
- package/lib/compiler/assert-consistency.js +8 -7
- package/lib/compiler/builtins.js +14 -14
- package/lib/compiler/checks.js +123 -48
- package/lib/compiler/define.js +12 -13
- package/lib/compiler/extend.js +266 -60
- package/lib/compiler/finalize-parse-cdl.js +10 -5
- package/lib/compiler/index.js +17 -14
- package/lib/compiler/populate.js +14 -6
- package/lib/compiler/propagator.js +2 -0
- package/lib/compiler/resolve.js +2 -15
- package/lib/compiler/shared.js +27 -16
- package/lib/compiler/tweak-assocs.js +5 -6
- package/lib/compiler/utils.js +20 -0
- package/lib/edm/annotations/genericTranslation.js +604 -358
- package/lib/edm/annotations/preprocessAnnotations.js +39 -35
- package/lib/edm/csn2edm.js +275 -222
- package/lib/edm/edm.js +17 -3
- package/lib/edm/edmAnnoPreprocessor.js +6 -6
- package/lib/edm/edmInboundChecks.js +2 -2
- package/lib/edm/edmPreprocessor.js +107 -77
- package/lib/edm/edmUtils.js +44 -5
- package/lib/gen/Dictionary.json +210 -8
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +67 -63
- package/lib/gen/language.tokens +81 -81
- package/lib/gen/languageLexer.interp +4 -10
- package/lib/gen/languageLexer.js +854 -869
- package/lib/gen/languageLexer.tokens +79 -81
- package/lib/gen/languageParser.js +14309 -13832
- package/lib/inspect/inspectModelStatistics.js +2 -2
- package/lib/inspect/inspectPropagation.js +6 -6
- package/lib/inspect/inspectUtils.js +2 -2
- package/lib/json/from-csn.js +102 -55
- package/lib/json/to-csn.js +119 -198
- package/lib/language/antlrParser.js +5 -2
- package/lib/language/docCommentParser.js +6 -6
- package/lib/language/errorStrategy.js +43 -23
- package/lib/language/genericAntlrParser.js +113 -133
- package/lib/language/language.g4 +1550 -1506
- package/lib/language/multiLineStringParser.js +3 -3
- package/lib/language/textUtils.js +2 -2
- package/lib/main.js +3 -3
- package/lib/model/csnRefs.js +5 -0
- package/lib/model/csnUtils.js +130 -122
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/model/sortViews.js +4 -6
- package/lib/modelCompare/compare.js +2 -2
- package/lib/modelCompare/utils/.eslintrc.json +22 -0
- package/lib/modelCompare/utils/filter.js +100 -0
- package/lib/optionProcessor.js +5 -0
- package/lib/render/.eslintrc.json +1 -0
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +12 -12
- package/lib/render/toCdl.js +311 -276
- package/lib/render/toHdbcds.js +97 -94
- package/lib/render/toRename.js +5 -5
- package/lib/render/toSql.js +127 -223
- package/lib/render/utils/common.js +141 -108
- package/lib/render/utils/delta.js +227 -0
- package/lib/render/utils/sql.js +22 -6
- package/lib/render/utils/stringEscapes.js +3 -3
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/assertUnique.js +13 -12
- package/lib/transform/db/associations.js +5 -5
- package/lib/transform/db/cdsPersistence.js +10 -8
- package/lib/transform/db/constraints.js +14 -14
- package/lib/transform/db/expansion.js +20 -22
- package/lib/transform/db/flattening.js +24 -42
- package/lib/transform/db/groupByOrderBy.js +3 -3
- package/lib/transform/db/temporal.js +6 -6
- package/lib/transform/db/transformExists.js +23 -23
- package/lib/transform/db/views.js +16 -16
- package/lib/transform/draft/.eslintrc.json +1 -35
- package/lib/transform/draft/db.js +10 -10
- package/lib/transform/draft/odata.js +2 -2
- package/lib/transform/forOdataNew.js +8 -29
- package/lib/transform/forRelationalDB.js +16 -6
- package/lib/transform/localized.js +11 -10
- package/lib/transform/odata/toFinalBaseType.js +41 -27
- package/lib/transform/odata/typesExposure.js +113 -47
- package/lib/transform/parseExpr.js +209 -106
- package/lib/transform/transformUtilsNew.js +17 -10
- package/lib/transform/translateAssocsToJoins.js +24 -19
- package/lib/transform/universalCsn/coreComputed.js +10 -10
- package/lib/transform/universalCsn/universalCsnEnricher.js +26 -26
- package/lib/transform/universalCsn/utils.js +3 -3
- package/lib/utils/file.js +5 -5
- package/lib/utils/moduleResolve.js +13 -13
- package/lib/utils/objectUtils.js +6 -6
- package/lib/utils/term.js +5 -2
- package/lib/utils/timetrace.js +51 -24
- package/package.json +5 -8
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/message-explanations.json +1 -1
- package/share/messages/redirected-to-complex.md +4 -4
- package/share/messages/{syntax-expecting-integer.md → syntax-expecting-unsigned-int.md} +7 -4
- package/lib/modelCompare/filter.js +0 -83
|
@@ -31,7 +31,7 @@ const { implicitAs } = require('../../model/csnRefs');
|
|
|
31
31
|
* @param {(a: string) => string} renderArgs Function to render function arguments
|
|
32
32
|
* @returns {string} Function string
|
|
33
33
|
*/
|
|
34
|
-
function renderFunc( funcName, node, dialect, renderArgs) {
|
|
34
|
+
function renderFunc( funcName, node, dialect, renderArgs ) {
|
|
35
35
|
if (funcWithoutParen( node, dialect ))
|
|
36
36
|
return funcName;
|
|
37
37
|
return `${funcName}(${renderArgs( node )})`;
|
|
@@ -60,7 +60,7 @@ function funcWithoutParen( node, dialect ) {
|
|
|
60
60
|
*
|
|
61
61
|
* @returns {string} The rendered xpr
|
|
62
62
|
*/
|
|
63
|
-
function beautifyExprArray(tokens) {
|
|
63
|
+
function beautifyExprArray( tokens ) {
|
|
64
64
|
// Simply concatenate array parts with spaces (with a tiny bit of beautification)
|
|
65
65
|
let result = '';
|
|
66
66
|
for (let i = 0; i < tokens.length; i++) {
|
|
@@ -72,7 +72,6 @@ function beautifyExprArray(tokens) {
|
|
|
72
72
|
return result;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
75
|
/**
|
|
77
76
|
* Get the part that is really the name of this artifact and not just prefix caused by a context/service
|
|
78
77
|
*
|
|
@@ -80,13 +79,12 @@ function beautifyExprArray(tokens) {
|
|
|
80
79
|
* @param {string} artifactName Artifact name to use
|
|
81
80
|
* @returns {string} non-prefix part of the artifact name
|
|
82
81
|
*/
|
|
83
|
-
function getRealName(csn, artifactName) {
|
|
82
|
+
function getRealName( csn, artifactName ) {
|
|
84
83
|
const parts = artifactName.split('.');
|
|
85
84
|
// Length of 1 -> There can be no prefix
|
|
86
85
|
if (parts.length === 1)
|
|
87
86
|
return artifactName;
|
|
88
87
|
|
|
89
|
-
|
|
90
88
|
const namespace = getNamespace(csn, artifactName);
|
|
91
89
|
const startIndex = namespace ? namespace.split('.').length : 0;
|
|
92
90
|
let indexOfLastParent = startIndex;
|
|
@@ -120,7 +118,7 @@ function getRealName(csn, artifactName) {
|
|
|
120
118
|
* @param {string} artifactName Name of the artifact to check for
|
|
121
119
|
* @returns {string | null} Name of the topmost context or null
|
|
122
120
|
*/
|
|
123
|
-
function getParentContextName(csn, artifactName) {
|
|
121
|
+
function getParentContextName( csn, artifactName ) {
|
|
124
122
|
const parts = artifactName.split('.');
|
|
125
123
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
126
124
|
const name = parts.slice(0, i).join('.');
|
|
@@ -129,7 +127,6 @@ function getParentContextName(csn, artifactName) {
|
|
|
129
127
|
if (art && (art.kind === 'context' || art.kind === 'service'))
|
|
130
128
|
return name;
|
|
131
129
|
}
|
|
132
|
-
|
|
133
130
|
return null;
|
|
134
131
|
}
|
|
135
132
|
|
|
@@ -140,7 +137,7 @@ function getParentContextName(csn, artifactName) {
|
|
|
140
137
|
*
|
|
141
138
|
* @param {Function[]} killList Array to add cleanup functions to
|
|
142
139
|
*/
|
|
143
|
-
function addContextMarkers(csn, killList) {
|
|
140
|
+
function addContextMarkers( csn, killList ) {
|
|
144
141
|
const contextsToCreate = Object.create(null);
|
|
145
142
|
forEachDefinition(csn, (art, artifactName) => {
|
|
146
143
|
const namespace = getNamespace(csn, artifactName);
|
|
@@ -175,7 +172,7 @@ function addContextMarkers(csn, killList) {
|
|
|
175
172
|
* @param {string} artifactName Name of the current context
|
|
176
173
|
* @returns {string[]} All possible context names inbetween
|
|
177
174
|
*/
|
|
178
|
-
function getIntermediateContextNames(csn, parentName, artifactName) {
|
|
175
|
+
function getIntermediateContextNames( csn, parentName, artifactName ) {
|
|
179
176
|
const parentLength = parentName.split('.').length;
|
|
180
177
|
const parts = artifactName.split('.');
|
|
181
178
|
const names = [];
|
|
@@ -197,7 +194,7 @@ function getIntermediateContextNames(csn, parentName, artifactName) {
|
|
|
197
194
|
* @param {string} artifactName
|
|
198
195
|
* @param {Function[]} killList Array to add cleanup functions to
|
|
199
196
|
*/
|
|
200
|
-
function addMissingChildContexts(csn, artifactName, killList) {
|
|
197
|
+
function addMissingChildContexts( csn, artifactName, killList ) {
|
|
201
198
|
// Get all other definitions sharing the same prefix, sorted by shortest first
|
|
202
199
|
const possibleNames = Object.keys(csn.definitions).filter(name => name.startsWith(`${artifactName}.`)).sort((a, b) => a.length - b.length);
|
|
203
200
|
for (const name of possibleNames) {
|
|
@@ -206,7 +203,7 @@ function addMissingChildContexts(csn, artifactName, killList) {
|
|
|
206
203
|
addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
|
|
207
204
|
}
|
|
208
205
|
|
|
209
|
-
function addPossibleGaps(possibleGaps, gapArtifactName) {
|
|
206
|
+
function addPossibleGaps( possibleGaps, gapArtifactName ) {
|
|
210
207
|
for (const gap of possibleGaps) {
|
|
211
208
|
gapArtifactName += `.${gap}`;
|
|
212
209
|
if (!csn.definitions[gapArtifactName]) {
|
|
@@ -317,7 +314,7 @@ const cdsToHdbcdsTypes = {
|
|
|
317
314
|
* @param {CSN.Column} column Column from the same query
|
|
318
315
|
* @returns {CSN.Element}
|
|
319
316
|
*/
|
|
320
|
-
function findElement(elements, column) {
|
|
317
|
+
function findElement( elements, column ) {
|
|
321
318
|
if (!elements)
|
|
322
319
|
return undefined;
|
|
323
320
|
if (column.as)
|
|
@@ -336,7 +333,7 @@ function findElement(elements, column) {
|
|
|
336
333
|
*
|
|
337
334
|
* @param {Function[]} killList Array to add cleanup functions to
|
|
338
335
|
*/
|
|
339
|
-
function addIntermediateContexts(csn, killList) {
|
|
336
|
+
function addIntermediateContexts( csn, killList ) {
|
|
340
337
|
for (const artifactName in csn.definitions) {
|
|
341
338
|
const artifact = csn.definitions[artifactName];
|
|
342
339
|
if ((artifact.kind === 'context') && !artifact._ignore) {
|
|
@@ -368,7 +365,7 @@ function addIntermediateContexts(csn, killList) {
|
|
|
368
365
|
* @param {CSN.Options} options To check for `disableHanaComments`
|
|
369
366
|
* @returns {boolean}
|
|
370
367
|
*/
|
|
371
|
-
function hasHanaComment(obj, options) {
|
|
368
|
+
function hasHanaComment( obj, options ) {
|
|
372
369
|
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
373
370
|
}
|
|
374
371
|
/**
|
|
@@ -380,7 +377,7 @@ function hasHanaComment(obj, options) {
|
|
|
380
377
|
* @param {CSN.Artifact|CSN.Element} obj
|
|
381
378
|
* @returns {string}
|
|
382
379
|
*/
|
|
383
|
-
function getHanaComment(obj) {
|
|
380
|
+
function getHanaComment( obj ) {
|
|
384
381
|
return obj.doc.split('\n\n')[0].trim();
|
|
385
382
|
}
|
|
386
383
|
|
|
@@ -392,7 +389,7 @@ function getHanaComment(obj) {
|
|
|
392
389
|
* @param {object} obj
|
|
393
390
|
* @returns {object} object with .front and .back
|
|
394
391
|
*/
|
|
395
|
-
function getSqlSnippets(options, obj) {
|
|
392
|
+
function getSqlSnippets( options, obj ) {
|
|
396
393
|
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
|
|
397
394
|
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
398
395
|
|
|
@@ -404,8 +401,7 @@ function getSqlSnippets(options, obj) {
|
|
|
404
401
|
*
|
|
405
402
|
* @callback renderPart
|
|
406
403
|
* @param {object|array} expression
|
|
407
|
-
* @
|
|
408
|
-
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
404
|
+
* @this {ExpressionRenderer}
|
|
409
405
|
* @returns {string}
|
|
410
406
|
*/
|
|
411
407
|
|
|
@@ -415,7 +411,27 @@ function getSqlSnippets(options, obj) {
|
|
|
415
411
|
*
|
|
416
412
|
* @typedef {object} ExpressionConfiguration
|
|
417
413
|
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
418
|
-
* @property {renderPart}
|
|
414
|
+
* @property {renderPart} typeCast
|
|
415
|
+
* @property {renderPart} val
|
|
416
|
+
* @property {renderPart} enum
|
|
417
|
+
* @property {renderPart} ref
|
|
418
|
+
* @property {renderPart} aliasOnly
|
|
419
|
+
* @property {renderPart} windowFunction
|
|
420
|
+
* @property {renderPart} func
|
|
421
|
+
* @property {renderPart} xpr
|
|
422
|
+
* @property {renderPart} SELECT
|
|
423
|
+
* @property {renderPart} SET
|
|
424
|
+
* @property {Function} [visitExpr]
|
|
425
|
+
* @property {Function} [renderExpr]
|
|
426
|
+
* @property {Function} [renderSubExpr]
|
|
427
|
+
* @property {boolean} [isNestedXpr]
|
|
428
|
+
* @property {object} [env]
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @typedef {object} ExpressionRenderer
|
|
433
|
+
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
434
|
+
* @property {renderPart} typeCast
|
|
419
435
|
* @property {renderPart} val
|
|
420
436
|
* @property {renderPart} enum
|
|
421
437
|
* @property {renderPart} ref
|
|
@@ -425,114 +441,130 @@ function getSqlSnippets(options, obj) {
|
|
|
425
441
|
* @property {renderPart} xpr
|
|
426
442
|
* @property {renderPart} SELECT
|
|
427
443
|
* @property {renderPart} SET
|
|
428
|
-
* @property {
|
|
429
|
-
* @property {
|
|
444
|
+
* @property {Function} visitExpr
|
|
445
|
+
* @property {Function} renderExpr
|
|
446
|
+
* @property {Function} renderSubExpr
|
|
447
|
+
* @property {boolean} isNestedXpr
|
|
448
|
+
* @property {object} env
|
|
449
|
+
*/
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* If `xpr` has a `cast` property, return a copy without it, otherwise return `xpr`.
|
|
453
|
+
* Useful for removing e.g. top-level CDL-style casts that should not be rendered as CAST().
|
|
454
|
+
*
|
|
455
|
+
* @param xpr
|
|
430
456
|
*/
|
|
457
|
+
function withoutCast( xpr ) {
|
|
458
|
+
return !xpr.cast ? xpr : { ...xpr, cast: undefined };
|
|
459
|
+
}
|
|
431
460
|
|
|
432
461
|
/**
|
|
433
462
|
* Render an expression (including paths and values) or condition 'x'.
|
|
434
463
|
* (no trailing LF, don't indent if inline)
|
|
435
464
|
*
|
|
436
|
-
* @param {ExpressionConfiguration}
|
|
437
|
-
* @returns {
|
|
465
|
+
* @param {ExpressionConfiguration} rendererBase
|
|
466
|
+
* @returns {ExpressionRenderer} Expression rendering utility
|
|
438
467
|
*/
|
|
439
|
-
function
|
|
468
|
+
function createExpressionRenderer( rendererBase ) {
|
|
469
|
+
const renderer = Object.create(rendererBase);
|
|
470
|
+
renderer.visitExpr = visitExpr;
|
|
440
471
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
*
|
|
444
|
-
* @todo Reuse this with toCdl
|
|
445
|
-
* @param {Array|object|string} expr Expression to render
|
|
446
|
-
* @param {object} env Render environment
|
|
447
|
-
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
448
|
-
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
449
|
-
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
450
|
-
* Note: This is a hack for casts() inside groupBy.
|
|
451
|
-
* @returns {string} Rendered expression
|
|
472
|
+
* @param {any} x
|
|
473
|
+
* @param {object} env
|
|
452
474
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return renderExprObject(expr);
|
|
463
|
-
}
|
|
464
|
-
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
465
|
-
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
466
|
-
return renderer.finalize.call({ inline, nestedExpr }, expr, env);
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Various special cases represented as objects
|
|
471
|
-
*
|
|
472
|
-
* @param {object} x Expression
|
|
473
|
-
* @returns {string} String representation of the expression
|
|
474
|
-
*/
|
|
475
|
-
function renderExprObject(x) {
|
|
476
|
-
if (x.list) { // TODO: Does this still exist?
|
|
477
|
-
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
478
|
-
}
|
|
479
|
-
else if (x.val !== undefined) {
|
|
480
|
-
return renderer.val.call({ inline, nestedExpr }, x, env);
|
|
481
|
-
}
|
|
482
|
-
// Enum symbol
|
|
483
|
-
else if (x['#']) {
|
|
484
|
-
return renderer.enum.call({ inline, nestedExpr }, x, env);
|
|
485
|
-
}
|
|
486
|
-
// Reference: Array of path steps, possibly preceded by ':'
|
|
487
|
-
else if (x.ref) {
|
|
488
|
-
return renderer.ref.call({ inline, nestedExpr }, x, env);
|
|
489
|
-
}
|
|
490
|
-
// Function call, possibly with args (use '=>' for named args)
|
|
491
|
-
else if (x.func) {
|
|
492
|
-
if (x.xpr)
|
|
493
|
-
return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
|
|
494
|
-
return renderer.func.call({ inline, nestedExpr }, x, env);
|
|
495
|
-
}
|
|
496
|
-
// Nested expression
|
|
497
|
-
else if (x.xpr) {
|
|
498
|
-
return renderer.xpr.call({ inline, nestedExpr }, x, env);
|
|
499
|
-
}
|
|
500
|
-
// Sub-select
|
|
501
|
-
else if (x.SELECT) {
|
|
502
|
-
return renderer.SELECT.call({ inline, nestedExpr }, x, env);
|
|
503
|
-
}
|
|
504
|
-
else if (x.SET) {
|
|
505
|
-
return renderer.SET.call({ inline, nestedExpr }, x, env);
|
|
506
|
-
}
|
|
507
|
-
else if (x.as && x.cast && x.cast.type && x.cast.target) {
|
|
508
|
-
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
512
|
-
}
|
|
475
|
+
renderer.renderExpr = function renderExpr(x, env) {
|
|
476
|
+
/** @type {ExpressionRenderer} */
|
|
477
|
+
const renderObj = Object.create(renderer);
|
|
478
|
+
renderObj.env = env || this?.env;
|
|
479
|
+
// The outermost expression is not nested. All `.xpr` inside `expr`
|
|
480
|
+
// are nested. This information is used for adding parentheses around
|
|
481
|
+
// expressions (see `this.xpr()`).
|
|
482
|
+
renderObj.isNestedXpr = false;
|
|
483
|
+
return renderObj.visitExpr(x);
|
|
513
484
|
};
|
|
485
|
+
/**
|
|
486
|
+
* @param {any} x
|
|
487
|
+
* @param {object} env
|
|
488
|
+
*/
|
|
489
|
+
renderer.renderSubExpr = function renderSubExpr(x, env) {
|
|
490
|
+
/** @type {ExpressionRenderer} */
|
|
491
|
+
const renderObj = Object.create(renderer);
|
|
492
|
+
renderObj.env = env || this?.env;
|
|
493
|
+
renderObj.isNestedXpr = true;
|
|
494
|
+
return renderObj.visitExpr(x);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
return renderer;
|
|
514
498
|
}
|
|
515
499
|
|
|
500
|
+
|
|
516
501
|
/**
|
|
517
|
-
*
|
|
502
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
503
|
+
* (no trailing LF, don't indent if inline)
|
|
504
|
+
*
|
|
505
|
+
* `this` must refer to an object of type `ExpressionRenderer`, see
|
|
506
|
+
* `createExpressionRenderer()`
|
|
518
507
|
*
|
|
519
|
-
* @
|
|
520
|
-
* @property {CSN.Path} [path] CSN path to the current artifact
|
|
521
|
-
* @property {string} [currentArtifactName] Name of the current artifact
|
|
522
|
-
* @property {{[name: string]: {
|
|
523
|
-
quotedName: string,
|
|
524
|
-
quotedAlias: string
|
|
525
|
-
}}} topLevelAliases Dictionary of aliases for used artifact names
|
|
508
|
+
* @param {any} x (Sub-)Expression to render
|
|
526
509
|
*
|
|
527
|
-
* @
|
|
528
|
-
* @
|
|
529
|
-
* @property {CSN.Artifact} [_artifact] The original view artifact, used when rendering queries
|
|
510
|
+
* @this ExpressionRenderer
|
|
511
|
+
* @returns {string} Rendered expression
|
|
530
512
|
*/
|
|
513
|
+
function visitExpr( x ) {
|
|
514
|
+
if (Array.isArray(x)) {
|
|
515
|
+
// Compound expression, e.g. for on- or where-conditions.
|
|
516
|
+
// If xpr is part of an array, it's always a nested xpr,
|
|
517
|
+
// e.g. CSN for `(1=1 or 2=2) and 3=3`.
|
|
518
|
+
const tokens = x.map(item => this.renderSubExpr(item));
|
|
519
|
+
return beautifyExprArray(tokens);
|
|
520
|
+
}
|
|
521
|
+
else if (typeof x !== 'object' || x === null) {
|
|
522
|
+
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
523
|
+
return this.finalize(x);
|
|
524
|
+
}
|
|
525
|
+
else if (x.cast?.type && !x.cast.target) {
|
|
526
|
+
return this.typeCast(x);
|
|
527
|
+
}
|
|
528
|
+
else if (x.list) {
|
|
529
|
+
// Render as non-nested expr.
|
|
530
|
+
return `(${x.list.map(item => this.renderExpr(item)).join(', ')})`;
|
|
531
|
+
}
|
|
532
|
+
else if (x.val !== undefined) {
|
|
533
|
+
return this.val(x);
|
|
534
|
+
}
|
|
535
|
+
else if (x['#']) {
|
|
536
|
+
// Enum symbol
|
|
537
|
+
return this.enum(x);
|
|
538
|
+
}
|
|
539
|
+
else if (x.ref) {
|
|
540
|
+
// Reference: Array of path steps, possibly preceded by ':'
|
|
541
|
+
return this.ref(x);
|
|
542
|
+
}
|
|
543
|
+
else if (x.func) {
|
|
544
|
+
// Function call, possibly with args (use '=>' for named args)
|
|
545
|
+
if (x.xpr)
|
|
546
|
+
return this.windowFunction(x);
|
|
547
|
+
return this.func(x);
|
|
548
|
+
}
|
|
549
|
+
else if (x.xpr) {
|
|
550
|
+
return this.xpr(x);
|
|
551
|
+
}
|
|
552
|
+
else if (x.SELECT) {
|
|
553
|
+
return this.SELECT(x);
|
|
554
|
+
}
|
|
555
|
+
else if (x.SET) {
|
|
556
|
+
return this.SET(x);
|
|
557
|
+
}
|
|
558
|
+
else if (x.as) {
|
|
559
|
+
return this.aliasOnly(x);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
throw new ModelError(`renderExpr(): Unknown expression: ${JSON.stringify(x)}`);
|
|
563
|
+
}
|
|
531
564
|
|
|
532
565
|
module.exports = {
|
|
533
566
|
renderFunc,
|
|
534
|
-
|
|
535
|
-
beautifyExprArray,
|
|
567
|
+
createExpressionRenderer,
|
|
536
568
|
getNamespace,
|
|
537
569
|
getRealName,
|
|
538
570
|
addIntermediateContexts,
|
|
@@ -544,4 +576,5 @@ module.exports = {
|
|
|
544
576
|
findElement,
|
|
545
577
|
funcWithoutParen,
|
|
546
578
|
getSqlSnippets,
|
|
579
|
+
withoutCast,
|
|
547
580
|
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Encapsulate all the functions needed to render SQL ALTER/DROP/ADD statements.
|
|
5
|
+
*/
|
|
6
|
+
class DeltaRenderer {
|
|
7
|
+
constructor(options, scopedFunctions) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.scopedFunctions = scopedFunctions;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render column additions as SQL. Checks for duplicate elements.
|
|
14
|
+
*/
|
|
15
|
+
addColumnsFromElementStrings(artifactName, eltStrings) {
|
|
16
|
+
return eltStrings.map(eltString => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${eltString};`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Render column additions as SQL. Checks for duplicate elements.
|
|
21
|
+
*/
|
|
22
|
+
addColumnsFromElementsObj(artifactName, elementsObj, env, duplicateChecker) {
|
|
23
|
+
// Only extend with 'ADD' for elements/associations
|
|
24
|
+
// TODO: May also include 'RENAME' at a later stage
|
|
25
|
+
const alterEnv = this.scopedFunctions.activateAlterMode(env);
|
|
26
|
+
const elements = Object.entries(elementsObj)
|
|
27
|
+
.map(([ name, elt ]) => this.scopedFunctions.renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
|
|
28
|
+
.filter(s => s !== '');
|
|
29
|
+
|
|
30
|
+
if (elements.length)
|
|
31
|
+
return this.addColumnsFromElementStrings(artifactName, elements);
|
|
32
|
+
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* By default, we don't support rendering association-alters - only for HANA
|
|
38
|
+
*/
|
|
39
|
+
addAssociations(_artifactName, _extElements, _env) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Render key addition as SQL.
|
|
45
|
+
*/
|
|
46
|
+
addKey(artifactName, elementsObj) {
|
|
47
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ${this.primaryKey(elementsObj)};` ];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Render column removals as SQL.
|
|
52
|
+
*/
|
|
53
|
+
dropColumns(artifactName, sqlIds) {
|
|
54
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlIds.join(', ')};` ];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* No associations by default - only for HANA.
|
|
59
|
+
*/
|
|
60
|
+
dropAssociation(_artifactName, _sqlId) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Render primary-key removals as SQL.
|
|
66
|
+
*/
|
|
67
|
+
dropKey(artifactName) {
|
|
68
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP PRIMARY KEY;` ];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Render column modifications as SQL.
|
|
73
|
+
*/
|
|
74
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
75
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER (${definitionsStr});` ];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render primary keys as SQL.
|
|
80
|
+
*/
|
|
81
|
+
primaryKey(elementsObj) {
|
|
82
|
+
const primaryKeys = Object.keys(elementsObj)
|
|
83
|
+
.filter(name => elementsObj[name].key && !elementsObj[name].virtual)
|
|
84
|
+
.map(name => this.scopedFunctions.quoteSqlId(name))
|
|
85
|
+
.join(', ');
|
|
86
|
+
return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render entity-comment modifications as SQL.
|
|
91
|
+
*/
|
|
92
|
+
alterEntityComment(artifactName, comment) {
|
|
93
|
+
return [ `COMMENT ON TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} IS ${this.comment(comment)};` ];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Render column-comment modifications as SQL.
|
|
98
|
+
*/
|
|
99
|
+
alterColumnComment(artifactName, columnName, comment) {
|
|
100
|
+
return [ `COMMENT ON COLUMN ${this.scopedFunctions.renderArtifactName(artifactName)}.${columnName} IS ${this.comment(comment)};` ];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Render comment string.
|
|
105
|
+
*/
|
|
106
|
+
comment(comment) {
|
|
107
|
+
return comment && this.scopedFunctions.renderStringForSql(this.scopedFunctions.getHanaComment({ doc: comment }), this.options.sqlDialect) || 'NULL';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Alter SQL snippet for entity.
|
|
112
|
+
*/
|
|
113
|
+
alterEntitySqlSnippet(artifactName, snippet) {
|
|
114
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ${snippet};` ];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Concatenate multiple statements which are to be treated as one by the API caller.
|
|
119
|
+
*/
|
|
120
|
+
concat(...statements) {
|
|
121
|
+
return [ statements.join('\n') ];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
class DeltaRendererHana extends DeltaRenderer {
|
|
126
|
+
/**
|
|
127
|
+
* Render column additions as HANA SQL. Checks for duplicate elements.
|
|
128
|
+
*/
|
|
129
|
+
addColumnsFromElementStrings(artifactName, eltStrings) {
|
|
130
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD (${eltStrings.join(', ')});` ];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Render association additions as HANA SQL.
|
|
135
|
+
* TODO duplicity check
|
|
136
|
+
*/
|
|
137
|
+
addAssociations(artifactName, elementsObj, env) {
|
|
138
|
+
return Object.entries(elementsObj)
|
|
139
|
+
.map(([ name, elt ]) => this.scopedFunctions.renderAssociationElement(name, elt, env))
|
|
140
|
+
.filter(s => s !== '')
|
|
141
|
+
.map(eltStr => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ADD ASSOCIATION (${eltStr});`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Render column removals as HANA SQL.
|
|
146
|
+
*/
|
|
147
|
+
dropColumns(artifactName, sqlIds) {
|
|
148
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP (${sqlIds.join(', ')});` ];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render association removals as HANA SQL.
|
|
153
|
+
*/
|
|
154
|
+
dropAssociation(artifactName, sqlId) {
|
|
155
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ASSOCIATION ${sqlId};` ];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
class DeltaRendererPostgres extends DeltaRenderer {
|
|
160
|
+
/**
|
|
161
|
+
* Render primary-key removals as SQL.
|
|
162
|
+
* @todo tableName is escaped - we cannot simply add _pkey
|
|
163
|
+
*/
|
|
164
|
+
dropKey(artifactName) {
|
|
165
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP CONSTRAINT ${this.scopedFunctions.renderArtifactName(`${artifactName}_pkey`)};` ];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Render column removals as SQL.
|
|
170
|
+
*/
|
|
171
|
+
dropColumns(artifactName, sqlIds) {
|
|
172
|
+
return sqlIds.map(sqlId => `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} DROP ${sqlId};`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Render column modifications as Postgres SQL - no ( ), special NOT NULL.
|
|
177
|
+
*/
|
|
178
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
179
|
+
const sqls = [];
|
|
180
|
+
if (delta.new.notNull === true || delta.new.key === true)
|
|
181
|
+
definitionsStr = definitionsStr.replace(' NOT NULL', ''); // TODO: Is this robust enough?
|
|
182
|
+
else if (delta.new.notNull === false || delta.new.$notNull === false)
|
|
183
|
+
definitionsStr = definitionsStr.replace(' NULL', ''); // TODO: Is this robust enough?
|
|
184
|
+
|
|
185
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};`);
|
|
186
|
+
if (delta.new.notNull && !delta.old.notNull)
|
|
187
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} SET NOT NULL;`);
|
|
188
|
+
else if (delta.old.notNull && !delta.new.notNull)
|
|
189
|
+
sqls.push(`ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${columnName} DROP NOT NULL;`);
|
|
190
|
+
|
|
191
|
+
return sqls;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
class DeltaRendererH2 extends DeltaRenderer {
|
|
196
|
+
/**
|
|
197
|
+
* Render column modifications as H2 SQL - no ().
|
|
198
|
+
*/
|
|
199
|
+
alterColumns(artifactName, columnName, delta, definitionsStr) {
|
|
200
|
+
return [ `ALTER TABLE ${this.scopedFunctions.renderArtifactName(artifactName)} ALTER ${definitionsStr};` ];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Return an object encapsulating the render-functions for ALTER/DROP for a given db dialect.
|
|
206
|
+
*
|
|
207
|
+
* @param {CSN.Options} options
|
|
208
|
+
* @param {object} scopedFunctions
|
|
209
|
+
* @returns {DeltaRenderer}
|
|
210
|
+
*/
|
|
211
|
+
function getDeltaRenderer( options, scopedFunctions ) {
|
|
212
|
+
switch (options.sqlDialect) {
|
|
213
|
+
case 'hana':
|
|
214
|
+
return new DeltaRendererHana(options, scopedFunctions);
|
|
215
|
+
case 'h2':
|
|
216
|
+
return new DeltaRendererH2(options, scopedFunctions);
|
|
217
|
+
case 'postgres':
|
|
218
|
+
return new DeltaRendererPostgres(options, scopedFunctions);
|
|
219
|
+
default:
|
|
220
|
+
return new DeltaRenderer(options, scopedFunctions);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
module.exports = {
|
|
226
|
+
getDeltaRenderer,
|
|
227
|
+
};
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -18,7 +18,7 @@ const { ModelError } = require('../../base/error');
|
|
|
18
18
|
*
|
|
19
19
|
* @returns {string} SQL statement which can be used to create the referential constraint on the db.
|
|
20
20
|
*/
|
|
21
|
-
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint = false) {
|
|
21
|
+
function renderReferentialConstraint( constraint, indent, toUpperCase, csn, options, alterConstraint = false ) {
|
|
22
22
|
let quoteId;
|
|
23
23
|
// for to.hana we can't utilize the sql identifier utils
|
|
24
24
|
if (options.transformation === 'hdbcds') {
|
|
@@ -29,7 +29,7 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
-
quoteId = getIdentifierUtils(options).quoteSqlId;
|
|
32
|
+
quoteId = getIdentifierUtils(csn, options).quoteSqlId;
|
|
33
33
|
}
|
|
34
34
|
if (toUpperCase) {
|
|
35
35
|
constraint.identifier = constraint.identifier.toUpperCase();
|
|
@@ -81,8 +81,8 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
81
81
|
* @param {CSN.Options} options
|
|
82
82
|
* @returns quoteSqlId and prepareIdentifier function
|
|
83
83
|
*/
|
|
84
|
-
function getIdentifierUtils(options) {
|
|
85
|
-
return { quoteSqlId, prepareIdentifier };
|
|
84
|
+
function getIdentifierUtils( csn, options ) {
|
|
85
|
+
return { quoteSqlId, prepareIdentifier, renderArtifactName };
|
|
86
86
|
/**
|
|
87
87
|
* Return 'name' with appropriate "-quotes.
|
|
88
88
|
* Additionally perform the following conversions on 'name'
|
|
@@ -95,7 +95,7 @@ function getIdentifierUtils(options) {
|
|
|
95
95
|
* @param {string} name Identifier to quote
|
|
96
96
|
* @returns {string} Quoted identifier
|
|
97
97
|
*/
|
|
98
|
-
function quoteSqlId(name) {
|
|
98
|
+
function quoteSqlId( name ) {
|
|
99
99
|
name = prepareIdentifier(name);
|
|
100
100
|
|
|
101
101
|
switch (options.sqlMapping) {
|
|
@@ -120,7 +120,7 @@ function getIdentifierUtils(options) {
|
|
|
120
120
|
* @param {string} name Identifier to prepare
|
|
121
121
|
* @returns {string} Identifier prepared for quoting
|
|
122
122
|
*/
|
|
123
|
-
function prepareIdentifier(name) {
|
|
123
|
+
function prepareIdentifier( name ) {
|
|
124
124
|
// Sanity check
|
|
125
125
|
if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
|
|
126
126
|
throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
|
|
@@ -137,6 +137,22 @@ function getIdentifierUtils(options) {
|
|
|
137
137
|
throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Given the following artifact name: namespace.prefix.entity.with.dot, render the following,
|
|
143
|
+
* depending on the naming mode:
|
|
144
|
+
* - plain: NAMESPACE_PREFIX_ENTITY_WITH_DOT
|
|
145
|
+
* - quoted: namespace.prefix.entity_with_dot
|
|
146
|
+
* - hdbcds: namespace::prefix.entity_with_dot
|
|
147
|
+
*
|
|
148
|
+
*
|
|
149
|
+
* @param {string} artifactName Artifact name to render
|
|
150
|
+
*
|
|
151
|
+
* @returns {string} Artifact name
|
|
152
|
+
*/
|
|
153
|
+
function renderArtifactName( artifactName ) {
|
|
154
|
+
return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
|
|
155
|
+
}
|
|
140
156
|
}
|
|
141
157
|
|
|
142
158
|
|