@sap/cds-compiler 3.9.4 → 4.0.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 +107 -4
- package/README.md +0 -1
- package/bin/cdsc.js +11 -23
- package/bin/cdsse.js +3 -3
- package/doc/API.md +5 -0
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +17 -1
- package/doc/CHANGELOG_DEPRECATED.md +28 -0
- package/lib/api/.eslintrc.json +1 -1
- package/lib/api/main.js +55 -9
- package/lib/api/options.js +2 -0
- package/lib/base/error.js +2 -0
- package/lib/base/message-registry.js +143 -64
- package/lib/base/messages.js +213 -107
- package/lib/base/model.js +11 -11
- package/lib/checks/.eslintrc.json +1 -1
- package/lib/checks/annotationsOData.js +2 -2
- package/lib/checks/elements.js +1 -1
- package/lib/checks/enricher.js +26 -3
- package/lib/checks/onConditions.js +67 -12
- package/lib/checks/queryNoDbArtifacts.js +106 -105
- package/lib/checks/sql-snippets.js +2 -0
- package/lib/checks/types.js +12 -6
- package/lib/checks/validator.js +2 -2
- package/lib/compiler/assert-consistency.js +10 -8
- package/lib/compiler/builtins.js +8 -2
- package/lib/compiler/checks.js +52 -35
- package/lib/compiler/define.js +31 -26
- package/lib/compiler/extend.js +120 -65
- package/lib/compiler/finalize-parse-cdl.js +12 -43
- package/lib/compiler/generate.js +16 -5
- package/lib/compiler/index.js +8 -5
- package/lib/compiler/kick-start.js +4 -3
- package/lib/compiler/populate.js +96 -95
- package/lib/compiler/propagator.js +7 -8
- package/lib/compiler/resolve.js +377 -103
- package/lib/compiler/shared.js +794 -517
- package/lib/compiler/tweak-assocs.js +8 -6
- package/lib/compiler/utils.js +44 -0
- package/lib/edm/annotations/genericTranslation.js +41 -5
- package/lib/edm/csn2edm.js +34 -32
- package/lib/edm/edm.js +34 -31
- package/lib/edm/edmAnnoPreprocessor.js +0 -23
- package/lib/edm/edmInboundChecks.js +7 -2
- package/lib/edm/edmPreprocessor.js +25 -18
- package/lib/edm/edmUtils.js +8 -4
- package/lib/gen/Dictionary.json +18 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -2
- package/lib/gen/languageParser.js +5006 -4582
- package/lib/json/from-csn.js +157 -112
- package/lib/json/to-csn.js +60 -89
- package/lib/language/antlrParser.js +17 -13
- package/lib/language/docCommentParser.js +11 -1
- package/lib/language/genericAntlrParser.js +13 -10
- package/lib/language/language.g4 +168 -97
- package/lib/main.d.ts +128 -36
- package/lib/main.js +1 -1
- package/lib/model/csnRefs.js +24 -5
- package/lib/model/csnUtils.js +9 -8
- package/lib/model/revealInternalProperties.js +7 -12
- package/lib/model/sortViews.js +4 -2
- package/lib/modelCompare/compare.js +1 -1
- package/lib/modelCompare/utils/filter.js +40 -2
- package/lib/optionProcessor.js +0 -3
- package/lib/render/toCdl.js +247 -214
- package/lib/render/toHdbcds.js +197 -181
- package/lib/render/toSql.js +325 -289
- package/lib/render/utils/common.js +42 -4
- package/lib/render/utils/delta.js +1 -1
- package/lib/render/utils/sql.js +3 -3
- package/lib/transform/braceExpression.js +2 -2
- package/lib/transform/db/.eslintrc.json +1 -1
- package/lib/transform/db/applyTransformations.js +3 -3
- package/lib/transform/db/associations.js +24 -12
- package/lib/transform/db/expansion.js +17 -18
- package/lib/transform/db/flattening.js +17 -21
- package/lib/transform/db/rewriteCalculatedElements.js +171 -64
- package/lib/transform/db/views.js +3 -4
- package/lib/transform/draft/db.js +21 -12
- package/lib/transform/draft/odata.js +4 -0
- package/lib/transform/forOdataNew.js +62 -47
- package/lib/transform/forRelationalDB.js +12 -7
- package/lib/transform/localized.js +4 -2
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +3 -3
- package/lib/transform/parseExpr.js +3 -0
- package/lib/transform/transformUtilsNew.js +43 -23
- package/lib/transform/translateAssocsToJoins.js +7 -6
- package/lib/transform/universalCsn/.eslintrc.json +1 -1
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -12
- package/package.json +2 -2
- package/share/messages/{duplicate-autoexposed.md → def-duplicate-autoexposed.md} +5 -1
- package/share/messages/message-explanations.json +1 -1
package/lib/render/toSql.js
CHANGED
|
@@ -27,6 +27,27 @@ const { sortCsn } = require('../json/to-csn');
|
|
|
27
27
|
const { manageConstraints } = require('./manageConstraints');
|
|
28
28
|
const { ModelError, CompilerAssertion } = require('../base/error');
|
|
29
29
|
|
|
30
|
+
class SqlRenderEnvironment {
|
|
31
|
+
indent = '';
|
|
32
|
+
path = null;
|
|
33
|
+
alterMode = false;
|
|
34
|
+
changeType = null;
|
|
35
|
+
|
|
36
|
+
constructor(values) {
|
|
37
|
+
Object.assign(this, values);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
withIncreasedIndent() {
|
|
41
|
+
return new SqlRenderEnvironment({ ...this, indent: ` ${this.indent}` });
|
|
42
|
+
}
|
|
43
|
+
withSubPath(path) {
|
|
44
|
+
return new SqlRenderEnvironment({ ...this, path: [ ...this.path, ...path ] });
|
|
45
|
+
}
|
|
46
|
+
cloneWith(values) {
|
|
47
|
+
return Object.assign(new SqlRenderEnvironment(this), values);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
|
|
31
52
|
/**
|
|
32
53
|
* Render the CSN model 'model' to SQL DDL statements. One statement is created
|
|
@@ -93,14 +114,14 @@ function toSqlDdl( csn, options ) {
|
|
|
93
114
|
return `CAST(${this.renderExpr(withoutCast(x))} AS ${typeRef})`;
|
|
94
115
|
},
|
|
95
116
|
val: renderExpressionLiteral,
|
|
96
|
-
enum(
|
|
97
|
-
// TODO: better location; callers should set env.$path
|
|
117
|
+
enum() {
|
|
98
118
|
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
|
|
99
|
-
|
|
100
|
-
error('expr-unexpected-enum', [ loc, null ], 'Enum values are not yet supported for conversion to SQL');
|
|
119
|
+
error('expr-unexpected-enum', this.env.path, 'Enum values are not yet supported for conversion to SQL');
|
|
101
120
|
return '';
|
|
102
121
|
},
|
|
103
|
-
ref
|
|
122
|
+
ref(x) {
|
|
123
|
+
return renderExpressionRef(x, this.env);
|
|
124
|
+
},
|
|
104
125
|
aliasOnly: x => x.as,
|
|
105
126
|
windowFunction( x) {
|
|
106
127
|
return renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, this.env);
|
|
@@ -109,17 +130,18 @@ function toSqlDdl( csn, options ) {
|
|
|
109
130
|
return renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, options.sqlDialect, a => renderArgs(a, '=>', this.env, null));
|
|
110
131
|
},
|
|
111
132
|
xpr(x) {
|
|
133
|
+
const env = this.env.withSubPath([ 'xpr' ]);
|
|
112
134
|
if (this.isNestedXpr && !x.cast)
|
|
113
|
-
return `(${this.renderSubExpr(x.xpr)})`;
|
|
114
|
-
return this.renderSubExpr(x.xpr);
|
|
135
|
+
return `(${this.renderSubExpr(x.xpr, env)})`;
|
|
136
|
+
return this.renderSubExpr(x.xpr, env);
|
|
115
137
|
},
|
|
116
138
|
SELECT( x) {
|
|
117
|
-
return `(${renderQuery(
|
|
139
|
+
return `(${renderQuery(x, this.env.withIncreasedIndent())})`;
|
|
118
140
|
},
|
|
119
141
|
SET( x) {
|
|
120
|
-
return `(${renderQuery(
|
|
142
|
+
return `(${renderQuery(x, this.env.withIncreasedIndent())})`;
|
|
121
143
|
},
|
|
122
|
-
});
|
|
144
|
+
}, true);
|
|
123
145
|
|
|
124
146
|
function renderExpr( x, env ) {
|
|
125
147
|
return exprRenderer.renderExpr(x, env);
|
|
@@ -137,7 +159,7 @@ function toSqlDdl( csn, options ) {
|
|
|
137
159
|
|
|
138
160
|
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect
|
|
139
161
|
if (!options.forHana && !isBetaEnabled(options, 'sqlExtensions'))
|
|
140
|
-
throw new CompilerAssertion('
|
|
162
|
+
throw new CompilerAssertion('to.sql can currently only be used with SAP HANA preprocessing');
|
|
141
163
|
|
|
142
164
|
checkCSNVersion(csn, options);
|
|
143
165
|
|
|
@@ -165,13 +187,7 @@ function toSqlDdl( csn, options ) {
|
|
|
165
187
|
|
|
166
188
|
// Render each artifact on its own
|
|
167
189
|
forEachDefinition((options && options.testMode) ? sortCsn(csn, options) : csn, (artifact, artifactName) => {
|
|
168
|
-
|
|
169
|
-
// indentation issues
|
|
170
|
-
const env = {
|
|
171
|
-
// Current indentation string
|
|
172
|
-
indent: '',
|
|
173
|
-
};
|
|
174
|
-
renderArtifactInto(artifactName, artifact, mainResultObj, env);
|
|
190
|
+
renderDefinitionInto(artifactName, artifact, mainResultObj, new SqlRenderEnvironment());
|
|
175
191
|
});
|
|
176
192
|
|
|
177
193
|
// Render each deleted artifact
|
|
@@ -179,27 +195,31 @@ function toSqlDdl( csn, options ) {
|
|
|
179
195
|
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
|
|
180
196
|
|
|
181
197
|
// Render each artifact extension
|
|
182
|
-
// Only HANA SQL is currently supported.
|
|
198
|
+
// Only SAP HANA SQL is currently supported.
|
|
183
199
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
184
200
|
if (csn.extensions && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
185
|
-
|
|
201
|
+
csn.extensions = options.testMode ? sortCsn(csn.extensions) : csn.extensions;
|
|
202
|
+
for (let i = 0; i < csn.extensions.length; ++i) {
|
|
203
|
+
const extension = csn.extensions[i];
|
|
186
204
|
if (extension.extend) {
|
|
187
205
|
const artifactName = extension.extend;
|
|
188
|
-
const
|
|
189
|
-
const env = {
|
|
190
|
-
renderArtifactExtensionInto(artifactName,
|
|
206
|
+
const artifact = csn.definitions[artifactName];
|
|
207
|
+
const env = new SqlRenderEnvironment({ path: [ 'extensions', i ] });
|
|
208
|
+
renderArtifactExtensionInto(artifactName, artifact, extension, mainResultObj, env);
|
|
191
209
|
}
|
|
192
210
|
}
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
// Render each artifact change
|
|
196
|
-
// Only HANA SQL is currently supported.
|
|
214
|
+
// Only SAP HANA SQL is currently supported.
|
|
197
215
|
if (csn.migrations && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
198
|
-
|
|
216
|
+
csn.migrations = options.testMode ? sortCsn(csn.migrations) : csn.migrations;
|
|
217
|
+
for (const migration of csn.migrations) {
|
|
199
218
|
if (migration.migrate) {
|
|
200
219
|
const artifactName = migration.migrate;
|
|
201
|
-
|
|
202
|
-
|
|
220
|
+
// There is no "migrations" property in client CSN, so for better locations, use
|
|
221
|
+
// a path to the definition.
|
|
222
|
+
const env = new SqlRenderEnvironment({ path: [ 'definitions', artifactName ] });
|
|
203
223
|
renderArtifactMigrationInto(artifactName, migration, mainResultObj, env);
|
|
204
224
|
}
|
|
205
225
|
}
|
|
@@ -260,21 +280,22 @@ function toSqlDdl( csn, options ) {
|
|
|
260
280
|
return mainResultObj;
|
|
261
281
|
|
|
262
282
|
/**
|
|
263
|
-
* Render
|
|
283
|
+
* Render a definition into the appropriate dictionary of 'resultObj'.
|
|
264
284
|
*
|
|
265
285
|
* @param {string} artifactName Name of the artifact to render
|
|
266
286
|
* @param {CSN.Artifact} art Artifact to render
|
|
267
287
|
* @param {object} resultObj Result collector
|
|
268
|
-
* @param {
|
|
288
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
269
289
|
*/
|
|
270
|
-
function
|
|
290
|
+
function renderDefinitionInto( artifactName, art, resultObj, env ) {
|
|
291
|
+
env.path = [ 'definitions', artifactName ];
|
|
271
292
|
// Ignore whole artifacts if forRelationalDB says so
|
|
272
293
|
if (art.abstract || hasValidSkipOrExists(art))
|
|
273
294
|
return;
|
|
274
295
|
|
|
275
296
|
switch (art.kind) {
|
|
276
297
|
case 'entity':
|
|
277
|
-
if (
|
|
298
|
+
if (art.query || art.projection) {
|
|
278
299
|
const result = renderView(artifactName, art, env);
|
|
279
300
|
if (result)
|
|
280
301
|
resultObj.hdbview[artifactName] = result;
|
|
@@ -301,13 +322,13 @@ function toSqlDdl( csn, options ) {
|
|
|
301
322
|
|
|
302
323
|
/**
|
|
303
324
|
* Render an artifact extension into the appropriate dictionary of 'resultObj'.
|
|
304
|
-
* Only HANA SQL is currently supported.
|
|
325
|
+
* Only SAP HANA SQL is currently supported.
|
|
305
326
|
*
|
|
306
327
|
* @param {string} artifactName Name of the artifact to render
|
|
307
328
|
* @param {CSN.Artifact} artifact The complete artifact
|
|
308
329
|
* @param {CSN.Artifact} ext Extension to render
|
|
309
330
|
* @param {object} resultObj Result collector
|
|
310
|
-
* @param {
|
|
331
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
311
332
|
*/
|
|
312
333
|
function renderArtifactExtensionInto( artifactName, artifact, ext, resultObj, env ) {
|
|
313
334
|
// Property kind is always omitted for elements and can be omitted for
|
|
@@ -328,7 +349,7 @@ function toSqlDdl( csn, options ) {
|
|
|
328
349
|
}
|
|
329
350
|
|
|
330
351
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
331
|
-
// Only HANA SQL is currently supported.
|
|
352
|
+
// Only SAP HANA SQL is currently supported.
|
|
332
353
|
function renderArtifactMigrationInto( artifactName, migration, resultObj, env ) {
|
|
333
354
|
function reducesTypeSize( def ) {
|
|
334
355
|
// HANA does not allow decreasing the value of any of those type parameters.
|
|
@@ -338,7 +359,7 @@ function toSqlDdl( csn, options ) {
|
|
|
338
359
|
function getEltStr( defVariant, eltName, changeType = 'extension' ) {
|
|
339
360
|
return defVariant.target
|
|
340
361
|
? renderAssociationElement(eltName, defVariant, env)
|
|
341
|
-
: renderElement(
|
|
362
|
+
: renderElement(eltName, defVariant, null, null, activateAlterMode(env, changeType));
|
|
342
363
|
}
|
|
343
364
|
function getEltStrNoProps( defVariant, eltName, ...props ) {
|
|
344
365
|
const defNoProps = Object.assign({}, defVariant);
|
|
@@ -467,16 +488,15 @@ function toSqlDdl( csn, options ) {
|
|
|
467
488
|
* @param {string} artifactName Name of the artifact to render
|
|
468
489
|
* @param {CSN.Artifact} art Artifact to render
|
|
469
490
|
* @param {object} resultObj Result collector
|
|
470
|
-
* @param {
|
|
491
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
471
492
|
*/
|
|
472
493
|
function renderEntityInto( artifactName, art, resultObj, env ) {
|
|
473
|
-
|
|
474
|
-
const childEnv = increaseIndent(env);
|
|
494
|
+
const childEnv = env.withIncreasedIndent();
|
|
475
495
|
const hanaTc = art.technicalConfig && art.technicalConfig.hana;
|
|
476
496
|
// tables can have @sql.prepend and @sql.append
|
|
477
497
|
const { front, back } = getSqlSnippets(options, art);
|
|
478
498
|
let result = front;
|
|
479
|
-
// Only HANA has row/column tables
|
|
499
|
+
// Only SAP HANA has row/column tables
|
|
480
500
|
if (options.sqlDialect === 'hana') {
|
|
481
501
|
if (hanaTc && hanaTc.storeType) {
|
|
482
502
|
// Explicitly specified
|
|
@@ -491,9 +511,10 @@ function toSqlDdl( csn, options ) {
|
|
|
491
511
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art.$location, artifactName);
|
|
492
512
|
result += `TABLE ${tableName}`;
|
|
493
513
|
result += ' (\n';
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
514
|
+
result += Object.keys(art.elements)
|
|
515
|
+
.map(eltName => renderElement(eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv))
|
|
516
|
+
.filter(s => s !== '')
|
|
517
|
+
.join(',\n');
|
|
497
518
|
|
|
498
519
|
const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)
|
|
499
520
|
.map(name => quoteSqlId(name))
|
|
@@ -507,7 +528,7 @@ function toSqlDdl( csn, options ) {
|
|
|
507
528
|
|
|
508
529
|
// for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
|
|
509
530
|
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
|
|
510
|
-
if (
|
|
531
|
+
if (!constraintsAsAlter && art.$tableConstraints?.referential) {
|
|
511
532
|
const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
|
|
512
533
|
const referentialConstraints = {};
|
|
513
534
|
forEach(art.$tableConstraints.referential, ( fileName, referentialConstraint ) => {
|
|
@@ -546,12 +567,15 @@ function toSqlDdl( csn, options ) {
|
|
|
546
567
|
result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
|
|
547
568
|
|
|
548
569
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
570
|
+
if (options.sqlDialect === 'hana') {
|
|
571
|
+
const associations = Object.keys(art.elements)
|
|
572
|
+
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
573
|
+
.filter(s => s !== '')
|
|
574
|
+
.join(',\n');
|
|
575
|
+
if (associations !== '') {
|
|
576
|
+
result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
|
|
577
|
+
result += `${env.indent})`;
|
|
578
|
+
}
|
|
555
579
|
}
|
|
556
580
|
// Only HANA has indices
|
|
557
581
|
// FIXME: Really? We should provide a DB-agnostic way to specify that
|
|
@@ -570,13 +594,13 @@ function toSqlDdl( csn, options ) {
|
|
|
570
594
|
|
|
571
595
|
/**
|
|
572
596
|
* Render an extended entity into the appropriate dictionaries of 'resultObj'.
|
|
573
|
-
* Only HANA SQL is currently supported.
|
|
597
|
+
* Only SAP HANA SQL is currently supported.
|
|
574
598
|
*
|
|
575
599
|
* @param {string} artifactName Name of the artifact to render
|
|
576
600
|
* @param {object} artifactElements Elements comprising the artifact
|
|
577
601
|
* @param {object} extElements Elements comprising the extension
|
|
578
602
|
* @param {object} resultObj Result collector
|
|
579
|
-
* @param {
|
|
603
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
580
604
|
* @param {DuplicateChecker} duplicateChecker
|
|
581
605
|
*/
|
|
582
606
|
function renderExtendInto( artifactName, artifactElements, extElements, resultObj, env, duplicateChecker ) {
|
|
@@ -638,40 +662,37 @@ function toSqlDdl( csn, options ) {
|
|
|
638
662
|
* projection or view), optionally with corresponding fuzzy index 'fzindex' from the
|
|
639
663
|
* technical configuration.
|
|
640
664
|
* Ignore association elements (those are rendered later by renderAssociationElement).
|
|
641
|
-
* Use 'artifactName' only for error output.
|
|
642
665
|
* Return the resulting source string (no trailing LF).
|
|
643
666
|
*
|
|
644
|
-
* @param {string} artifactName Name of the artifact containing the element
|
|
645
667
|
* @param {string} elementName Name of the element to render
|
|
646
668
|
* @param {CSN.Element} elm CSN element
|
|
647
669
|
* @param {DuplicateChecker} duplicateChecker Utility for detecting duplicates
|
|
648
670
|
* @param {object} fzindex Fzindex object for the element
|
|
649
|
-
* @param {
|
|
671
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
650
672
|
* @returns {string} Rendered element
|
|
651
673
|
*/
|
|
652
|
-
function renderElement(
|
|
674
|
+
function renderElement( elementName, elm, duplicateChecker, fzindex, env ) {
|
|
653
675
|
if (elm.virtual || elm.target)
|
|
654
676
|
return '';
|
|
655
|
-
|
|
677
|
+
env = env.withSubPath([ 'elements', elementName ]);
|
|
656
678
|
const isPostgresAlterColumn = env.alterMode && env.changeType === 'migration' && options.sqlDialect === 'postgres';
|
|
657
679
|
const quotedElementName = quoteSqlId(elementName);
|
|
658
680
|
if (duplicateChecker)
|
|
659
681
|
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
|
|
660
682
|
|
|
661
|
-
let result = `${env.indent + quotedElementName}${isPostgresAlterColumn ? ' TYPE' : ''} ${renderTypeReference(
|
|
683
|
+
let result = `${env.indent + quotedElementName}${isPostgresAlterColumn ? ' TYPE' : ''} ${renderTypeReference(elm, env)
|
|
662
684
|
}${renderNullability(elm, true, env.alterMode)}`;
|
|
663
|
-
|
|
664
|
-
|
|
685
|
+
// calculated elements (on write) can't have a default; ignore it
|
|
686
|
+
if (elm.default && !elm.value)
|
|
687
|
+
result += ` DEFAULT ${renderExpr(elm.default, env.withSubPath([ 'default' ]))}`;
|
|
665
688
|
|
|
666
|
-
// Only HANA has fuzzy indices
|
|
689
|
+
// Only SAP HANA has fuzzy indices
|
|
667
690
|
if (fzindex && options.sqlDialect === 'hana')
|
|
668
691
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
669
692
|
|
|
670
693
|
// (table) elements can only have a @sql.append
|
|
671
694
|
const { back } = getSqlSnippets(options, elm);
|
|
672
|
-
|
|
673
|
-
if (back !== '') // Needs to be rendered before the COMMENT
|
|
674
|
-
result += back;
|
|
695
|
+
result += back; // Needs to be rendered before the COMMENT
|
|
675
696
|
|
|
676
697
|
if (options.sqlDialect === 'hana' && hasHanaComment(elm, options))
|
|
677
698
|
result += ` COMMENT ${renderStringForSql(getHanaComment(elm), options.sqlDialect)}`;
|
|
@@ -690,10 +711,11 @@ function toSqlDdl( csn, options ) {
|
|
|
690
711
|
* @todo Duplicate check
|
|
691
712
|
* @param {string} elementName Name of the element to render
|
|
692
713
|
* @param {CSN.Element} elm CSN element
|
|
693
|
-
* @param {
|
|
714
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
694
715
|
* @returns {string} Rendered association element
|
|
695
716
|
*/
|
|
696
717
|
function renderAssociationElement( elementName, elm, env ) {
|
|
718
|
+
env = env.withSubPath([ 'elements', elementName ]);
|
|
697
719
|
let result = '';
|
|
698
720
|
if (elm.target) {
|
|
699
721
|
result += env.indent;
|
|
@@ -713,7 +735,7 @@ function toSqlDdl( csn, options ) {
|
|
|
713
735
|
}
|
|
714
736
|
result += ' JOIN ';
|
|
715
737
|
result += `${renderArtifactName(elm.target)} AS ${quoteSqlId(elementName)} ON (`;
|
|
716
|
-
result += `${renderExpr(elm.on, env)})`;
|
|
738
|
+
result += `${renderExpr(elm.on, env.withSubPath([ 'on' ]))})`;
|
|
717
739
|
}
|
|
718
740
|
return result;
|
|
719
741
|
}
|
|
@@ -726,7 +748,7 @@ function toSqlDdl( csn, options ) {
|
|
|
726
748
|
* Return the resulting source string.
|
|
727
749
|
*
|
|
728
750
|
* @param {object} tc Technical configuration
|
|
729
|
-
* @param {
|
|
751
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
730
752
|
* @returns {string} Rendered technical configuration
|
|
731
753
|
*/
|
|
732
754
|
function renderTechnicalConfiguration( tc, env ) {
|
|
@@ -740,15 +762,15 @@ function toSqlDdl( csn, options ) {
|
|
|
740
762
|
// This also affects renderIndexes
|
|
741
763
|
tc = tc.hana;
|
|
742
764
|
if (!tc)
|
|
743
|
-
throw new ModelError('Expecting a HANA technical configuration');
|
|
765
|
+
throw new ModelError('Expecting a SAP HANA technical configuration');
|
|
744
766
|
|
|
745
767
|
if (tc.tableSuffix) {
|
|
746
768
|
// Although we could just render the whole bandwurm as one stream of tokens, the
|
|
747
769
|
// compactor has kindly stored each part (e.g. `migration enabled` `row store`, ...)
|
|
748
770
|
// in its own `xpr` (for the benefit of the `toCdl` renderer, which needs semicolons
|
|
749
|
-
// between parts). We use this here for putting each one
|
|
771
|
+
// between parts). We use this here for putting each one line)
|
|
750
772
|
|
|
751
|
-
//
|
|
773
|
+
// This array contains technical configurations that are illegal in HANA SQL
|
|
752
774
|
const ignore = [
|
|
753
775
|
'PARTITION BY KEEPING EXISTING LAYOUT',
|
|
754
776
|
'ROW STORE',
|
|
@@ -771,7 +793,7 @@ function toSqlDdl( csn, options ) {
|
|
|
771
793
|
* @param {object} indexes Indices to render
|
|
772
794
|
* @param {string} artifactName Artifact to render indices for
|
|
773
795
|
* @param {object} resultObj Result collector
|
|
774
|
-
* @param {
|
|
796
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
775
797
|
*/
|
|
776
798
|
function renderIndexesInto( indexes, artifactName, resultObj, env ) {
|
|
777
799
|
// Indices and full-text indices
|
|
@@ -826,20 +848,19 @@ function toSqlDdl( csn, options ) {
|
|
|
826
848
|
|
|
827
849
|
/**
|
|
828
850
|
* Render the source of a query, which may be a path reference, possibly with an alias,
|
|
829
|
-
* or a
|
|
851
|
+
* or a sub-select, or a join operation.
|
|
830
852
|
*
|
|
831
853
|
* Returns the source as a string.
|
|
832
854
|
*
|
|
833
855
|
* @todo Misleading name, should be something like 'renderQueryFrom'. All the query parts should probably also be rearranged.
|
|
834
|
-
* @param {string} artifactName Name of the artifact containing the query
|
|
835
856
|
* @param {object} source Query source
|
|
836
|
-
* @param {
|
|
857
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
837
858
|
* @returns {string} Rendered view source
|
|
838
859
|
*/
|
|
839
|
-
function renderViewSource(
|
|
860
|
+
function renderViewSource( source, env ) {
|
|
840
861
|
// Sub-SELECT
|
|
841
862
|
if (source.SELECT || source.SET) {
|
|
842
|
-
let result = `(${renderQuery(
|
|
863
|
+
let result = `(${renderQuery(source, env.withIncreasedIndent())})`;
|
|
843
864
|
if (source.as)
|
|
844
865
|
result += ` AS ${quoteSqlId(source.as)}`;
|
|
845
866
|
|
|
@@ -848,14 +869,14 @@ function toSqlDdl( csn, options ) {
|
|
|
848
869
|
// JOIN
|
|
849
870
|
else if (source.join) {
|
|
850
871
|
// One join operation, possibly with ON-condition
|
|
851
|
-
let result = `${renderViewSource(
|
|
872
|
+
let result = `${renderViewSource(source.args[0], env.withSubPath([ 'args', 0 ]))}`;
|
|
852
873
|
for (let i = 1; i < source.args.length; i++) {
|
|
853
874
|
result = `(${result} ${source.join.toUpperCase()} `;
|
|
854
875
|
if (options.sqlDialect === 'hana')
|
|
855
876
|
result += renderJoinCardinality(source.cardinality);
|
|
856
|
-
result += `JOIN ${renderViewSource(
|
|
877
|
+
result += `JOIN ${renderViewSource(source.args[i], env.withSubPath([ 'args', i ]))}`;
|
|
857
878
|
if (source.on)
|
|
858
|
-
result += ` ON ${renderExpr(source.on, env)}`;
|
|
879
|
+
result += ` ON ${renderExpr(source.on, env.withSubPath([ 'on' ]))}`;
|
|
859
880
|
|
|
860
881
|
result += ')';
|
|
861
882
|
}
|
|
@@ -867,7 +888,7 @@ function toSqlDdl( csn, options ) {
|
|
|
867
888
|
if (!source.ref)
|
|
868
889
|
throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
|
|
869
890
|
|
|
870
|
-
return renderAbsolutePathWithAlias(
|
|
891
|
+
return renderAbsolutePathWithAlias(source, env);
|
|
871
892
|
}
|
|
872
893
|
|
|
873
894
|
/**
|
|
@@ -896,19 +917,17 @@ function toSqlDdl( csn, options ) {
|
|
|
896
917
|
* Render a path that starts with an absolute name (as used for the source of a query),
|
|
897
918
|
* possibly with an alias, with plain or quoted names, depending on options. Expects an object 'path' that has a
|
|
898
919
|
* 'ref' and (in case of an alias) an 'as'. If necessary, an artificial alias
|
|
899
|
-
* is created to the original implicit name.
|
|
920
|
+
* is created to the original implicit name.
|
|
900
921
|
* Returns the name and alias as a string.
|
|
901
922
|
*
|
|
902
|
-
* @param {string} artifactName Name of the artifact containing the path - used for error output
|
|
903
923
|
* @param {object} path Path to render
|
|
904
|
-
* @param {
|
|
924
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
905
925
|
* @returns {string} Rendered path
|
|
906
926
|
*/
|
|
907
|
-
function renderAbsolutePathWithAlias(
|
|
927
|
+
function renderAbsolutePathWithAlias( path, env ) {
|
|
908
928
|
// This actually can't happen anymore because assoc2joins should have taken care of it
|
|
909
929
|
if (path.ref[0].where)
|
|
910
|
-
throw new ModelError(`"${
|
|
911
|
-
|
|
930
|
+
throw new ModelError(`"At ${JSON.stringify(env.path)}": Filters in FROM are not supported for conversion to SQL (path: ${JSON.stringify(path)})`);
|
|
912
931
|
|
|
913
932
|
// SQL needs a ':' after path.ref[0] to separate associations
|
|
914
933
|
let result = renderAbsolutePath(path, ':', env);
|
|
@@ -939,7 +958,7 @@ function toSqlDdl( csn, options ) {
|
|
|
939
958
|
*
|
|
940
959
|
* @param {object} path Path to render
|
|
941
960
|
* @param {string} sep Separator between path steps
|
|
942
|
-
* @param {
|
|
961
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
943
962
|
* @returns {string} Rendered path
|
|
944
963
|
*/
|
|
945
964
|
function renderAbsolutePath( path, sep, env ) {
|
|
@@ -960,7 +979,9 @@ function toSqlDdl( csn, options ) {
|
|
|
960
979
|
// An empty actual parameter list is rendered as `()`.
|
|
961
980
|
const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
|
|
962
981
|
if (ref && ref.params) {
|
|
963
|
-
result +=
|
|
982
|
+
result += path.ref[0]?.args
|
|
983
|
+
? `(${renderArgs(path.ref[0], '=>', env.withSubPath([ 'ref', 0 ]), syntax)})`
|
|
984
|
+
: '()';
|
|
964
985
|
}
|
|
965
986
|
else if (syntax === 'udf') {
|
|
966
987
|
// if syntax is user defined function, render empty argument list
|
|
@@ -969,7 +990,7 @@ function toSqlDdl( csn, options ) {
|
|
|
969
990
|
}
|
|
970
991
|
if (path.ref[0].where) {
|
|
971
992
|
const cardinality = path.ref[0].cardinality ? (`${path.ref[0].cardinality.max}: `) : '';
|
|
972
|
-
result += `[${cardinality}${renderExpr(path.ref[0].where, env)}]`;
|
|
993
|
+
result += `[${cardinality}${renderExpr(path.ref[0].where, env.withSubPath([ 'ref', 0, 'where' ]))}]`;
|
|
973
994
|
}
|
|
974
995
|
|
|
975
996
|
// Add any path steps (possibly with parameters and filters) that may follow after that
|
|
@@ -986,24 +1007,25 @@ function toSqlDdl( csn, options ) {
|
|
|
986
1007
|
*
|
|
987
1008
|
* @param {object} node with `args` to render
|
|
988
1009
|
* @param {string} sep Separator between args
|
|
989
|
-
* @param {
|
|
1010
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
990
1011
|
* @param {string|null} syntax Some magic A2J parameter - for calcview parameter rendering
|
|
991
1012
|
* @returns {string} Rendered arguments
|
|
992
1013
|
* @throws Throws if args is not an array or object.
|
|
993
1014
|
*/
|
|
994
1015
|
function renderArgs( node, sep, env, syntax ) {
|
|
995
|
-
|
|
1016
|
+
if (!node.args)
|
|
1017
|
+
return '';
|
|
996
1018
|
// Positional arguments
|
|
997
|
-
if (Array.isArray(args))
|
|
998
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1019
|
+
if (Array.isArray(node.args))
|
|
1020
|
+
return node.args.map((arg, i) => renderExpr(arg, env.withSubPath([ 'args', i ]))).join(', ');
|
|
999
1021
|
|
|
1000
1022
|
// Named arguments (object/dict)
|
|
1001
|
-
else if (typeof args === 'object')
|
|
1023
|
+
else if (typeof node.args === 'object')
|
|
1002
1024
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1003
|
-
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1025
|
+
return Object.keys(node.args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(node.args[key], env.withSubPath([ 'args', key ]))}`).join(', ');
|
|
1004
1026
|
|
|
1005
1027
|
|
|
1006
|
-
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1028
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(node.args)}`);
|
|
1007
1029
|
|
|
1008
1030
|
|
|
1009
1031
|
/**
|
|
@@ -1027,7 +1049,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1027
1049
|
*
|
|
1028
1050
|
* @param {object} col Column to render
|
|
1029
1051
|
* @param {CSN.Elements} elements of leading or subquery
|
|
1030
|
-
* @param {
|
|
1052
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1031
1053
|
* @returns {string} Rendered column
|
|
1032
1054
|
*/
|
|
1033
1055
|
function renderViewColumn( col, elements, env ) {
|
|
@@ -1053,11 +1075,10 @@ function toSqlDdl( csn, options ) {
|
|
|
1053
1075
|
*
|
|
1054
1076
|
* @param {string} artifactName Name of the view
|
|
1055
1077
|
* @param {CSN.Artifact} art CSN view
|
|
1056
|
-
* @param {
|
|
1078
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1057
1079
|
* @returns {string} Rendered view
|
|
1058
1080
|
*/
|
|
1059
1081
|
function renderView( artifactName, art, env ) {
|
|
1060
|
-
env._artifact = art;
|
|
1061
1082
|
const viewName = renderArtifactName(artifactName);
|
|
1062
1083
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
|
|
1063
1084
|
let result = `VIEW ${viewName}`;
|
|
@@ -1065,11 +1086,14 @@ function toSqlDdl( csn, options ) {
|
|
|
1065
1086
|
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
1066
1087
|
result += ` COMMENT ${renderStringForSql(getHanaComment(art), options.sqlDialect)}`;
|
|
1067
1088
|
|
|
1068
|
-
result += renderParameterDefinitions(
|
|
1069
|
-
result += ` AS ${renderQuery(
|
|
1089
|
+
result += renderParameterDefinitions(art.params, env);
|
|
1090
|
+
result += ` AS ${renderQuery(getNormalizedQuery(art).query,
|
|
1091
|
+
env.withSubPath([ art.projection ? 'projection' : 'query' ]),
|
|
1092
|
+
art.elements, !!art.projection)}`;
|
|
1070
1093
|
|
|
1071
|
-
const childEnv =
|
|
1072
|
-
const associations = Object.keys(art.elements)
|
|
1094
|
+
const childEnv = env.withIncreasedIndent();
|
|
1095
|
+
const associations = Object.keys(art.elements)
|
|
1096
|
+
.filter(name => !!art.elements[name].target)
|
|
1073
1097
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
1074
1098
|
.filter(s => s !== '')
|
|
1075
1099
|
.join(',\n');
|
|
@@ -1089,18 +1113,19 @@ function toSqlDdl( csn, options ) {
|
|
|
1089
1113
|
/**
|
|
1090
1114
|
* Render the parameter definition of a view if any. Return the parameters in parentheses, or an empty string
|
|
1091
1115
|
*
|
|
1092
|
-
* @param {string} artifactName Name of the view
|
|
1093
1116
|
* @param {Object} params Dictionary of parameters
|
|
1117
|
+
* @param {SqlRenderEnvironment} env
|
|
1094
1118
|
* @returns {string} Rendered parameters
|
|
1095
1119
|
*/
|
|
1096
|
-
function renderParameterDefinitions(
|
|
1120
|
+
function renderParameterDefinitions( params, env ) {
|
|
1097
1121
|
let result = '';
|
|
1098
1122
|
if (params) {
|
|
1099
1123
|
const parray = [];
|
|
1100
1124
|
for (const pn in params) {
|
|
1125
|
+
const paramEnv = env.withSubPath([ 'params', pn ]);
|
|
1101
1126
|
const p = params[pn];
|
|
1102
1127
|
if (p.notNull === true || p.notNull === false)
|
|
1103
|
-
info('query-ignoring-param-nullability',
|
|
1128
|
+
info('query-ignoring-param-nullability', paramEnv.path, { '#': 'sql' });
|
|
1104
1129
|
// do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
|
|
1105
1130
|
// this would be an incompatible change, as non-uppercased, quoted identifiers
|
|
1106
1131
|
// are rejected by the HANA compiler.
|
|
@@ -1109,9 +1134,9 @@ function toSqlDdl( csn, options ) {
|
|
|
1109
1134
|
pIdentifier = prepareIdentifier(pn);
|
|
1110
1135
|
else
|
|
1111
1136
|
pIdentifier = quoteSqlId(pn);
|
|
1112
|
-
let pstr = `IN ${pIdentifier} ${renderTypeReference(
|
|
1137
|
+
let pstr = `IN ${pIdentifier} ${renderTypeReference(p, paramEnv)}`;
|
|
1113
1138
|
if (p.default)
|
|
1114
|
-
pstr += ` DEFAULT ${renderExpr(p.default, { indent: '' })}`;
|
|
1139
|
+
pstr += ` DEFAULT ${renderExpr(p.default, new SqlRenderEnvironment({ ...env, indent: '' }))}`;
|
|
1115
1140
|
|
|
1116
1141
|
parray.push(pstr);
|
|
1117
1142
|
}
|
|
@@ -1121,24 +1146,26 @@ function toSqlDdl( csn, options ) {
|
|
|
1121
1146
|
}
|
|
1122
1147
|
|
|
1123
1148
|
/**
|
|
1124
|
-
* Render a query 'query', i.e. a select statement with where-condition etc.
|
|
1149
|
+
* Render a query 'query', i.e. a select statement with where-condition etc.
|
|
1125
1150
|
*
|
|
1126
|
-
* @param {string} artifactName Artifact containing the query
|
|
1127
1151
|
* @param {CSN.Query} query CSN query
|
|
1128
|
-
* @param {
|
|
1152
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1129
1153
|
* @param {CSN.Elements} [elements] to override direct query elements - e.g. leading union should win
|
|
1154
|
+
* @param {boolean} [isProjection]
|
|
1130
1155
|
* @returns {string} Rendered query
|
|
1131
1156
|
*/
|
|
1132
|
-
function renderQuery(
|
|
1157
|
+
function renderQuery( query, env, elements = null, isProjection = false ) {
|
|
1133
1158
|
let result = '';
|
|
1134
1159
|
// Set operator, like UNION, INTERSECT, ...
|
|
1135
1160
|
if (query.SET) {
|
|
1161
|
+
env = env.withSubPath([ 'SET' ]);
|
|
1136
1162
|
result += query.SET.args
|
|
1137
|
-
.map((arg) => {
|
|
1163
|
+
.map((arg, index) => {
|
|
1138
1164
|
// Wrap each query in the SET in parentheses that
|
|
1139
1165
|
// - is a SET itself (to preserve precedence between the different SET operations),
|
|
1140
1166
|
// - has an ORDER BY/LIMIT (because UNION etc. can't stand directly behind an ORDER BY)
|
|
1141
|
-
const
|
|
1167
|
+
const argEnv = env.withSubPath([ 'args', index ]);
|
|
1168
|
+
const queryString = renderQuery( arg, argEnv, elements || query.SET.elements, false);
|
|
1142
1169
|
return (arg.SET || arg.SELECT && (arg.SELECT.orderBy || arg.SELECT.limit)) ? `(${queryString})` : queryString;
|
|
1143
1170
|
})
|
|
1144
1171
|
.join(`\n${env.indent}${query.SET.op && query.SET.op.toUpperCase()}${query.SET.all ? ' ALL ' : ' '}`);
|
|
@@ -1150,10 +1177,10 @@ function toSqlDdl( csn, options ) {
|
|
|
1150
1177
|
if (query.SET.orderBy || query.SET.limit) {
|
|
1151
1178
|
result = `(${result})`;
|
|
1152
1179
|
if (query.SET.orderBy)
|
|
1153
|
-
result += `\n${env.indent}ORDER BY ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
1180
|
+
result += `\n${env.indent}ORDER BY ${query.SET.orderBy.map(entry => renderOrderByEntry(entry, env.withSubPath([ 'orderBy' ]))).join(', ')}`;
|
|
1154
1181
|
|
|
1155
1182
|
if (query.SET.limit)
|
|
1156
|
-
result += `\n${env.indent}${renderLimit(query.SET.limit, env)}`;
|
|
1183
|
+
result += `\n${env.indent}${renderLimit(query.SET.limit, env.withSubPath([ 'limit' ]))}`;
|
|
1157
1184
|
}
|
|
1158
1185
|
return result;
|
|
1159
1186
|
}
|
|
@@ -1161,30 +1188,37 @@ function toSqlDdl( csn, options ) {
|
|
|
1161
1188
|
else if (!query.SELECT) {
|
|
1162
1189
|
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
1163
1190
|
}
|
|
1191
|
+
if (!isProjection)
|
|
1192
|
+
env = env.withSubPath([ 'SELECT' ]);
|
|
1164
1193
|
const select = query.SELECT;
|
|
1165
|
-
const childEnv =
|
|
1194
|
+
const childEnv = env.withIncreasedIndent();
|
|
1166
1195
|
result += `SELECT${select.distinct ? ' DISTINCT' : ''}`;
|
|
1167
1196
|
// FIXME: We probably also need to consider `excluding` here ?
|
|
1168
1197
|
result += `\n${(select.columns || [ '*' ])
|
|
1169
|
-
.
|
|
1170
|
-
|
|
1198
|
+
.map((col, index) => {
|
|
1199
|
+
if (!select.mixin?.[firstPathStepId(col.ref)]) {
|
|
1200
|
+
const colEnv = select.columns ? childEnv.withSubPath([ 'columns', index ]) : childEnv;
|
|
1201
|
+
return renderViewColumn(col, elements || select.elements, colEnv);
|
|
1202
|
+
}
|
|
1203
|
+
return ''; // No mixin columns
|
|
1204
|
+
})
|
|
1171
1205
|
.filter(s => s !== '')
|
|
1172
1206
|
.join(',\n')}\n`;
|
|
1173
|
-
result += `${env.indent}FROM ${renderViewSource(
|
|
1207
|
+
result += `${env.indent}FROM ${renderViewSource( select.from, env.withSubPath([ 'from' ]))}`;
|
|
1174
1208
|
if (select.where)
|
|
1175
|
-
result += `\n${env.indent}WHERE ${renderExpr(select.where, env)}`;
|
|
1209
|
+
result += `\n${env.indent}WHERE ${renderExpr(select.where, env.withSubPath([ 'where' ]))}`;
|
|
1176
1210
|
|
|
1177
1211
|
if (select.groupBy)
|
|
1178
|
-
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
1212
|
+
result += `\n${env.indent}GROUP BY ${select.groupBy.map((expr, i) => renderExpr(expr, env.withSubPath([ 'groupBy', i ]))).join(', ')}`;
|
|
1179
1213
|
|
|
1180
1214
|
if (select.having)
|
|
1181
|
-
result += `\n${env.indent}HAVING ${renderExpr(select.having, env)}`;
|
|
1215
|
+
result += `\n${env.indent}HAVING ${renderExpr(select.having, env.withSubPath([ 'having' ]))}`;
|
|
1182
1216
|
|
|
1183
1217
|
if (select.orderBy)
|
|
1184
|
-
result += `\n${env.indent}ORDER BY ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
1218
|
+
result += `\n${env.indent}ORDER BY ${select.orderBy.map((entry, i) => renderOrderByEntry(entry, env.withSubPath([ 'orderBy', i ]))).join(', ')}`;
|
|
1185
1219
|
|
|
1186
1220
|
if (select.limit)
|
|
1187
|
-
result += `\n${env.indent}${renderLimit(select.limit, env)}`;
|
|
1221
|
+
result += `\n${env.indent}${renderLimit(select.limit, env.withSubPath([ 'limit' ]))}`;
|
|
1188
1222
|
|
|
1189
1223
|
return result;
|
|
1190
1224
|
}
|
|
@@ -1203,17 +1237,17 @@ function toSqlDdl( csn, options ) {
|
|
|
1203
1237
|
* Render a query's LIMIT clause, which may also have OFFSET.
|
|
1204
1238
|
*
|
|
1205
1239
|
* @param {CSN.QueryLimit} limit Limit clause
|
|
1206
|
-
* @param {
|
|
1240
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1207
1241
|
* @returns {string} Rendered LIMIT clause
|
|
1208
1242
|
*/
|
|
1209
1243
|
function renderLimit( limit, env ) {
|
|
1210
1244
|
let result = '';
|
|
1211
1245
|
if (limit.rows !== undefined)
|
|
1212
|
-
result += `LIMIT ${renderExpr(limit.rows, env)}`;
|
|
1246
|
+
result += `LIMIT ${renderExpr(limit.rows, env.withSubPath([ 'rows' ]))}`;
|
|
1213
1247
|
|
|
1214
1248
|
if (limit.offset !== undefined) {
|
|
1215
1249
|
const indent = result !== '' ? `\n${env.indent}` : '';
|
|
1216
|
-
result += `${indent}OFFSET ${renderExpr(limit.offset, env)}`;
|
|
1250
|
+
result += `${indent}OFFSET ${renderExpr(limit.offset, env.withSubPath([ 'offset' ]))}`;
|
|
1217
1251
|
}
|
|
1218
1252
|
|
|
1219
1253
|
return result;
|
|
@@ -1224,7 +1258,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1224
1258
|
* have a 'sort' property for ASC/DESC and a 'nulls' for FIRST/LAST
|
|
1225
1259
|
*
|
|
1226
1260
|
* @param {object} entry Part of an ORDER BY
|
|
1227
|
-
* @param {
|
|
1261
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1228
1262
|
* @returns {string} Rendered ORDER BY entry
|
|
1229
1263
|
*/
|
|
1230
1264
|
function renderOrderByEntry( entry, env ) {
|
|
@@ -1239,45 +1273,46 @@ function toSqlDdl( csn, options ) {
|
|
|
1239
1273
|
}
|
|
1240
1274
|
|
|
1241
1275
|
/**
|
|
1242
|
-
* Render a reference to the type used by 'elm'
|
|
1276
|
+
* Render a reference to the type used by 'elm'. env.path must point to the element/param.
|
|
1243
1277
|
*
|
|
1244
|
-
* @param {string} artifactName Artifact containing the element
|
|
1245
|
-
* @param {string} elementName Element referencing the type
|
|
1246
1278
|
* @param {CSN.Element} elm CSN element
|
|
1279
|
+
* @param {SqlRenderEnvironment} env
|
|
1247
1280
|
* @returns {string} Rendered type reference
|
|
1248
1281
|
*/
|
|
1249
|
-
function renderTypeReference(
|
|
1282
|
+
function renderTypeReference( elm, env ) {
|
|
1250
1283
|
let result = '';
|
|
1251
1284
|
|
|
1252
1285
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1253
1286
|
if (!elm.type && !elm.value) {
|
|
1254
1287
|
if (!elm.elements)
|
|
1255
|
-
throw new ModelError(`Missing type of: ${
|
|
1288
|
+
throw new ModelError(`to.sql(): Missing type of: ${JSON.stringify(env.path)}`);
|
|
1256
1289
|
|
|
1257
|
-
// TODO: Signal is not covered by tests
|
|
1258
|
-
error(null,
|
|
1290
|
+
// TODO: Signal is not covered by tests
|
|
1291
|
+
error(null, env.path,
|
|
1259
1292
|
'Anonymous structured types are not supported for conversion to SQL');
|
|
1260
1293
|
return result;
|
|
1261
1294
|
}
|
|
1262
1295
|
|
|
1263
1296
|
// Association type
|
|
1264
1297
|
if (elm.target) {
|
|
1265
|
-
// TODO: Signal is not covered by tests
|
|
1298
|
+
// TODO: Signal is not covered by tests
|
|
1266
1299
|
// We can't do associations yet
|
|
1267
|
-
error(null,
|
|
1300
|
+
error(null, env.path,
|
|
1268
1301
|
'Association and composition types are not yet supported for conversion to SQL');
|
|
1269
1302
|
return result;
|
|
1270
1303
|
}
|
|
1271
1304
|
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1305
|
+
if (elm.type) {
|
|
1306
|
+
// If we get here, it must be a primitive (i.e. builtin) type
|
|
1307
|
+
if (isBuiltinType(elm.type)) {
|
|
1308
|
+
// cds.Integer => render as INTEGER (no quotes)
|
|
1309
|
+
result += renderBuiltinType(elm.type);
|
|
1310
|
+
result += renderTypeParameters(elm);
|
|
1311
|
+
}
|
|
1312
|
+
else {
|
|
1313
|
+
throw new ModelError(`Unexpected non-primitive type of: ${JSON.stringify(env.path)}`);
|
|
1314
|
+
}
|
|
1279
1315
|
}
|
|
1280
|
-
result += renderTypeParameters(elm);
|
|
1281
1316
|
|
|
1282
1317
|
if (elm.value) {
|
|
1283
1318
|
if (!elm.value.stored)
|
|
@@ -1286,7 +1321,7 @@ function toSqlDdl( csn, options ) {
|
|
|
1286
1321
|
// The SQL standard 2016 describes the syntax in section 11.3 - 11.4
|
|
1287
1322
|
// of the SQL Foundation spec (for 2003 in 5WD-02-Foundation-2003-09.pdf). Summarized:
|
|
1288
1323
|
// <generation clause> ::= GENERATED ALWAYS AS '(' <value expression> ')'
|
|
1289
|
-
result += ` GENERATED ALWAYS AS (${renderExpr(elm.value)})`;
|
|
1324
|
+
result += ` GENERATED ALWAYS AS (${renderExpr(elm.value, env.withSubPath([ 'value' ]))})`;
|
|
1290
1325
|
// However, it appears many databases require a trailing "STORED".
|
|
1291
1326
|
if (options.sqlDialect === 'sqlite' || options.sqlDialect === 'postgres')
|
|
1292
1327
|
result += ' STORED';
|
|
@@ -1327,7 +1362,6 @@ function toSqlDdl( csn, options ) {
|
|
|
1327
1362
|
return obj.$notNull ? ' NOT NULL' : ' NULL';
|
|
1328
1363
|
}
|
|
1329
1364
|
|
|
1330
|
-
|
|
1331
1365
|
if (obj.notNull === undefined && !(obj.key && treatKeyAsNotNull)) {
|
|
1332
1366
|
// Attribute not set at all
|
|
1333
1367
|
return '';
|
|
@@ -1398,6 +1432,11 @@ function toSqlDdl( csn, options ) {
|
|
|
1398
1432
|
}
|
|
1399
1433
|
}
|
|
1400
1434
|
|
|
1435
|
+
/**
|
|
1436
|
+
* @param {object} x
|
|
1437
|
+
* @param {object} env
|
|
1438
|
+
* @return {string}
|
|
1439
|
+
*/
|
|
1401
1440
|
function renderExpressionRef( x, env ) {
|
|
1402
1441
|
if (!x.param && !x.global) {
|
|
1403
1442
|
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
@@ -1406,13 +1445,13 @@ function toSqlDdl( csn, options ) {
|
|
|
1406
1445
|
if (magicReplacement !== null)
|
|
1407
1446
|
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1408
1447
|
|
|
1409
|
-
const result = render$user();
|
|
1448
|
+
const result = render$user(x);
|
|
1410
1449
|
// Invalid second path step doesn't cause a return
|
|
1411
1450
|
if (result)
|
|
1412
1451
|
return result;
|
|
1413
1452
|
}
|
|
1414
1453
|
else if (x.ref[0] === '$at' || x.ref[0] === '$valid') {
|
|
1415
|
-
const result = render$at();
|
|
1454
|
+
const result = render$at(x);
|
|
1416
1455
|
// Invalid second path step doesn't cause a return
|
|
1417
1456
|
if (result)
|
|
1418
1457
|
return result;
|
|
@@ -1444,174 +1483,171 @@ function toSqlDdl( csn, options ) {
|
|
|
1444
1483
|
if (x.param)
|
|
1445
1484
|
return `:${x.ref[0].toUpperCase()}`;
|
|
1446
1485
|
|
|
1447
|
-
return x.ref.map(renderPathStep)
|
|
1486
|
+
return x.ref.map((step, i) => renderPathStep(step, i, env.withSubPath([ 'ref', i ])))
|
|
1448
1487
|
.filter(s => s !== '')
|
|
1449
1488
|
.join('.');
|
|
1489
|
+
}
|
|
1450
1490
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
else if (x.ref[1] === 'locale') {
|
|
1467
|
-
if (options.sqlDialect === 'hana')
|
|
1468
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1469
|
-
else if (options.sqlDialect === 'postgres')
|
|
1470
|
-
return 'current_setting(\'CAP.LOCALE\')';
|
|
1471
|
-
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1472
|
-
return 'session_context( \'$user.locale\' )';
|
|
1473
|
-
return '\'en\''; // default language
|
|
1474
|
-
}
|
|
1475
|
-
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
1476
|
-
return null;
|
|
1491
|
+
/**
|
|
1492
|
+
* @param {object} x
|
|
1493
|
+
* @returns {string|null} Null in case of an invalid second path step
|
|
1494
|
+
*/
|
|
1495
|
+
function render$user( x ) {
|
|
1496
|
+
// FIXME: this is all not enough: we might need an explicit select item alias (?)
|
|
1497
|
+
if (x.ref[1] === 'id') {
|
|
1498
|
+
if (options.sqlDialect === 'hana')
|
|
1499
|
+
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1500
|
+
else if (options.sqlDialect === 'postgres')
|
|
1501
|
+
return 'current_setting(\'CAP.APPLICATIONUSER\')';
|
|
1502
|
+
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1503
|
+
return 'session_context( \'$user.id\' )';
|
|
1504
|
+
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1505
|
+
return '\'$user.id\'';
|
|
1477
1506
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1507
|
+
else if (x.ref[1] === 'locale') {
|
|
1508
|
+
if (options.sqlDialect === 'hana')
|
|
1509
|
+
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1510
|
+
else if (options.sqlDialect === 'postgres')
|
|
1511
|
+
return 'current_setting(\'CAP.LOCALE\')';
|
|
1512
|
+
else if (options.betterSqliteSessionVariables && options.sqlDialect === 'sqlite')
|
|
1513
|
+
return 'session_context( \'$user.locale\' )';
|
|
1514
|
+
return '\'en\''; // default language
|
|
1515
|
+
}
|
|
1516
|
+
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* For a given reference starting with $at, render a 'current_timestamp' literal for plain.
|
|
1522
|
+
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
|
|
1523
|
+
*
|
|
1524
|
+
*
|
|
1525
|
+
* For sqlite, we render the string-format-time (strftime) function.
|
|
1526
|
+
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
1527
|
+
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
1528
|
+
* --> Therefore the comparison in the temporal where clause doesn't work properly.
|
|
1529
|
+
*
|
|
1530
|
+
* @param {object} x
|
|
1531
|
+
* @returns {string|null} Null in case of an invalid second path step
|
|
1532
|
+
*/
|
|
1533
|
+
function render$at( x ) {
|
|
1534
|
+
if (x.ref[1] === 'from') {
|
|
1535
|
+
switch (options.sqlDialect) {
|
|
1536
|
+
case 'sqlite': {
|
|
1537
|
+
if (options.betterSqliteSessionVariables)
|
|
1538
|
+
return 'session_context( \'$valid.from\' )';
|
|
1539
|
+
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
|
|
1540
|
+
return `strftime('${dateFromFormat}', 'now')`;
|
|
1508
1541
|
}
|
|
1542
|
+
case 'hana':
|
|
1543
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1544
|
+
case 'postgres':
|
|
1545
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_FROM\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1546
|
+
case 'h2':
|
|
1547
|
+
case 'plain':
|
|
1548
|
+
return 'current_timestamp';
|
|
1549
|
+
default:
|
|
1550
|
+
break;
|
|
1509
1551
|
}
|
|
1552
|
+
}
|
|
1510
1553
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
}
|
|
1520
|
-
case 'hana':
|
|
1521
|
-
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1522
|
-
case 'postgres':
|
|
1523
|
-
return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1524
|
-
case 'h2':
|
|
1525
|
-
case 'plain':
|
|
1526
|
-
return 'current_timestamp';
|
|
1527
|
-
default:
|
|
1528
|
-
break;
|
|
1554
|
+
if (x.ref[1] === 'to') {
|
|
1555
|
+
switch (options.sqlDialect) {
|
|
1556
|
+
case 'sqlite': {
|
|
1557
|
+
if (options.betterSqliteSessionVariables)
|
|
1558
|
+
return 'session_context( \'$valid.to\' )';
|
|
1559
|
+
// + 1ms compared to $at.from
|
|
1560
|
+
const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
|
|
1561
|
+
return `strftime('${dateToFormat}', 'now')`;
|
|
1529
1562
|
}
|
|
1563
|
+
case 'hana':
|
|
1564
|
+
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1565
|
+
case 'postgres':
|
|
1566
|
+
return '(to_timestamp(current_setting(\'CAP.VALID_TO\'), \'YYYY-MM-DD HH24:MI:SS.FF6\') at time zone \'UTC\')';
|
|
1567
|
+
case 'h2':
|
|
1568
|
+
case 'plain':
|
|
1569
|
+
return 'current_timestamp';
|
|
1570
|
+
default:
|
|
1571
|
+
break;
|
|
1530
1572
|
}
|
|
1531
|
-
return null;
|
|
1532
1573
|
}
|
|
1574
|
+
return null;
|
|
1575
|
+
}
|
|
1533
1576
|
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
return quoteSqlId(s);
|
|
1577
|
+
/**
|
|
1578
|
+
* Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
|
|
1579
|
+
*
|
|
1580
|
+
* @param {string|object} s Path step to render
|
|
1581
|
+
* @param {number} idx index of the path step in the overall path
|
|
1582
|
+
* @param {object} env
|
|
1583
|
+
* @returns {string} Rendered path step
|
|
1584
|
+
*/
|
|
1585
|
+
function renderPathStep( s, idx, env ) {
|
|
1586
|
+
// Simple id or absolute name
|
|
1587
|
+
if (typeof (s) === 'string') {
|
|
1588
|
+
// TODO: When is this actually executed and not handled already in renderExpr?
|
|
1589
|
+
const magicForHana = {
|
|
1590
|
+
'$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
|
|
1591
|
+
'$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
|
|
1592
|
+
};
|
|
1593
|
+
// Some magic for first path steps
|
|
1594
|
+
if (idx === 0) {
|
|
1595
|
+
// HANA-specific translation of '$now' and '$user'
|
|
1596
|
+
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1597
|
+
if (options.sqlDialect === 'hana' && magicForHana[s])
|
|
1598
|
+
return magicForHana[s];
|
|
1599
|
+
|
|
1600
|
+
// Ignore initial $projection and initial $self
|
|
1601
|
+
if (s === '$projection' || s === '$self')
|
|
1602
|
+
return '';
|
|
1561
1603
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
// Filter, possibly with cardinality
|
|
1580
|
-
// FIXME: Does SQL understand filter cardinalities?
|
|
1581
|
-
const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
|
|
1582
|
-
result += `[${cardinality}${renderExpr(s.where, env)}]`;
|
|
1583
|
-
}
|
|
1584
|
-
return result;
|
|
1604
|
+
return quoteSqlId(s);
|
|
1605
|
+
}
|
|
1606
|
+
// ID with filters or parameters
|
|
1607
|
+
else if (typeof s === 'object') {
|
|
1608
|
+
// Sanity check
|
|
1609
|
+
if (!s.func && !s.id)
|
|
1610
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1611
|
+
|
|
1612
|
+
// Not really a path step but an object-like function call
|
|
1613
|
+
if (s.func)
|
|
1614
|
+
return `${s.func}(${renderArgs(s, '=>', env, null)})`;
|
|
1615
|
+
|
|
1616
|
+
// Path step, possibly with view parameters and/or filters
|
|
1617
|
+
let result = `${quoteSqlId(s.id)}`;
|
|
1618
|
+
if (s.args) {
|
|
1619
|
+
// View parameters
|
|
1620
|
+
result += `(${renderArgs(s, '=>', env, null)})`;
|
|
1585
1621
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1622
|
+
if (s.where) {
|
|
1623
|
+
// Filter, possibly with cardinality
|
|
1624
|
+
// FIXME: Does SQL understand filter cardinalities?
|
|
1625
|
+
const cardinality = s.cardinality ? (`${s.cardinality.max}: `) : '';
|
|
1626
|
+
result += `[${cardinality}${renderExpr(s.where, env.withSubPath([ 'where' ]))}]`;
|
|
1627
|
+
}
|
|
1628
|
+
return result;
|
|
1588
1629
|
}
|
|
1630
|
+
|
|
1631
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1589
1632
|
}
|
|
1590
1633
|
|
|
1634
|
+
|
|
1591
1635
|
function renderWindowFunction( funcName, node, fctEnv ) {
|
|
1592
1636
|
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)}) `;
|
|
1593
|
-
r += renderExpr(node.xpr, fctEnv); // xpr[0] is 'over'
|
|
1637
|
+
r += renderExpr(node.xpr, fctEnv.withSubPath([ 'xpr' ])); // xpr[0] is 'over'
|
|
1594
1638
|
return r;
|
|
1595
1639
|
}
|
|
1596
1640
|
|
|
1597
|
-
|
|
1598
|
-
* Returns a copy of 'env' with increased indentation
|
|
1599
|
-
*
|
|
1600
|
-
* @param {object} env Render environment
|
|
1601
|
-
* @returns {object} Render environment with increased indent
|
|
1602
|
-
*/
|
|
1603
|
-
function increaseIndent( env ) {
|
|
1604
|
-
return Object.assign({}, env, { indent: `${env.indent} ` });
|
|
1605
|
-
}
|
|
1641
|
+
|
|
1606
1642
|
/**
|
|
1607
1643
|
* Returns a copy of 'env' with alterMode set to true
|
|
1608
1644
|
*
|
|
1609
|
-
* @param {
|
|
1645
|
+
* @param {SqlRenderEnvironment} env Render environment
|
|
1610
1646
|
* @param {string} changeType 'extension' or 'migration'
|
|
1611
1647
|
* @returns {object} Render environment with alterMode
|
|
1612
1648
|
*/
|
|
1613
1649
|
function activateAlterMode( env, changeType ) {
|
|
1614
|
-
return
|
|
1650
|
+
return env.cloneWith({ alterMode: true, changeType });
|
|
1615
1651
|
}
|
|
1616
1652
|
}
|
|
1617
1653
|
|