@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
package/lib/render/toRename.js
CHANGED
|
@@ -70,22 +70,19 @@ function toRenameDdl(csn, options) {
|
|
|
70
70
|
|
|
71
71
|
resultStr += Object.keys(art.elements).map((name) => {
|
|
72
72
|
const e = art.elements[name];
|
|
73
|
-
let
|
|
73
|
+
let str = '';
|
|
74
74
|
|
|
75
75
|
const beforeColumnName = quoteSqlId(name);
|
|
76
76
|
const afterColumnName = plainSqlId(name);
|
|
77
77
|
|
|
78
78
|
if (!e._ignore) {
|
|
79
|
-
if (e.target)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
resultStr += ' ';
|
|
85
|
-
result = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
|
|
86
|
-
}
|
|
79
|
+
if (e.target)
|
|
80
|
+
str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
|
|
81
|
+
|
|
82
|
+
else if (beforeColumnName !== afterColumnName)
|
|
83
|
+
str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
|
|
87
84
|
}
|
|
88
|
-
return
|
|
85
|
+
return str;
|
|
89
86
|
}).join('');
|
|
90
87
|
}
|
|
91
88
|
return resultStr;
|
package/lib/render/toSql.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
9
|
const {
|
|
10
10
|
renderFunc, beautifyExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
|
|
11
|
+
getSqlSnippets,
|
|
11
12
|
} = require('./utils/common');
|
|
12
13
|
const {
|
|
13
14
|
renderReferentialConstraint, getIdentifierUtils,
|
|
@@ -19,6 +20,8 @@ const { timetrace } = require('../utils/timetrace');
|
|
|
19
20
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
20
21
|
const { smartFuncId } = require('../sql-identifier');
|
|
21
22
|
const { sortCsn } = require('../json/to-csn');
|
|
23
|
+
const { manageConstraints } = require('./manageConstraints');
|
|
24
|
+
const { ModelError } = require('../base/error');
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -170,6 +173,12 @@ function toSqlDdl(csn, options) {
|
|
|
170
173
|
comment(comment) {
|
|
171
174
|
return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
|
|
172
175
|
},
|
|
176
|
+
/*
|
|
177
|
+
Alter SQL snippet for entity.
|
|
178
|
+
*/
|
|
179
|
+
alterEntitySqlSnippet(tableName, snippet) {
|
|
180
|
+
return [ `ALTER TABLE ${tableName} ${snippet};` ];
|
|
181
|
+
},
|
|
173
182
|
/*
|
|
174
183
|
Concatenate multiple statements which are to be treated as one by the API caller.
|
|
175
184
|
*/
|
|
@@ -185,10 +194,10 @@ function toSqlDdl(csn, options) {
|
|
|
185
194
|
checkCSNVersion(csn, options);
|
|
186
195
|
|
|
187
196
|
// The final result in hdb-kind-specific form, without leading CREATE, without trailing newlines
|
|
188
|
-
// (note that the order here is relevant for transmission into '
|
|
197
|
+
// (note that the order here is relevant for transmission into 'mainResultObj.sql' below and that
|
|
189
198
|
// the attribute names must be the HDI plugin names for --src hdi)
|
|
190
199
|
// The result object may have a `sql` dictionary for `toSql`.
|
|
191
|
-
const
|
|
200
|
+
const mainResultObj = {
|
|
192
201
|
hdbtabletype: Object.create(null),
|
|
193
202
|
hdbtable: Object.create(null),
|
|
194
203
|
hdbindex: Object.create(null),
|
|
@@ -214,13 +223,12 @@ function toSqlDdl(csn, options) {
|
|
|
214
223
|
// Current indentation string
|
|
215
224
|
indent: '',
|
|
216
225
|
};
|
|
217
|
-
renderArtifactInto(artifactName, artifact,
|
|
226
|
+
renderArtifactInto(artifactName, artifact, mainResultObj, env);
|
|
218
227
|
});
|
|
219
228
|
|
|
220
229
|
// Render each deleted artifact
|
|
221
230
|
for (const artifactName in csn.deletions)
|
|
222
|
-
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName],
|
|
223
|
-
|
|
231
|
+
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
|
|
224
232
|
|
|
225
233
|
// Render each artifact extension
|
|
226
234
|
// Only HANA SQL is currently supported.
|
|
@@ -231,7 +239,7 @@ function toSqlDdl(csn, options) {
|
|
|
231
239
|
const artifactName = extension.extend;
|
|
232
240
|
const _artifact = csn.definitions[artifactName];
|
|
233
241
|
const env = { indent: '', _artifact };
|
|
234
|
-
renderArtifactExtensionInto(artifactName, _artifact, extension,
|
|
242
|
+
renderArtifactExtensionInto(artifactName, _artifact, extension, mainResultObj, env);
|
|
235
243
|
}
|
|
236
244
|
}
|
|
237
245
|
}
|
|
@@ -244,7 +252,7 @@ function toSqlDdl(csn, options) {
|
|
|
244
252
|
const artifactName = migration.migrate;
|
|
245
253
|
const _artifact = csn.definitions[artifactName];
|
|
246
254
|
const env = { indent: '', _artifact };
|
|
247
|
-
renderArtifactMigrationInto(artifactName, migration,
|
|
255
|
+
renderArtifactMigrationInto(artifactName, migration, mainResultObj, env);
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
}
|
|
@@ -265,34 +273,42 @@ function toSqlDdl(csn, options) {
|
|
|
265
273
|
|
|
266
274
|
// Handle hdbKinds separately from alterTable case
|
|
267
275
|
// eslint-disable-next-line no-unused-vars
|
|
268
|
-
const { deletions, migrations, ...hdbKinds } =
|
|
276
|
+
const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
|
|
269
277
|
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
270
|
-
for (const name in
|
|
278
|
+
for (const name in mainResultObj[hdbKind]) {
|
|
271
279
|
if (options.toSql.src === 'sql') {
|
|
272
|
-
let sourceString =
|
|
280
|
+
let sourceString = mainResultObj[hdbKind][name];
|
|
273
281
|
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
274
282
|
if (options.toSql.dialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
275
283
|
sourceString = sourceString.slice('COLUMN '.length);
|
|
276
|
-
|
|
277
284
|
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
|
|
278
285
|
}
|
|
279
286
|
else if (!options.testMode) {
|
|
280
|
-
|
|
287
|
+
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
281
288
|
}
|
|
282
289
|
}
|
|
283
290
|
if (options.toSql.src === 'sql')
|
|
284
|
-
delete
|
|
291
|
+
delete mainResultObj[hdbKind];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
|
|
295
|
+
if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
|
|
296
|
+
const alterStmts = manageConstraints(csn, options);
|
|
297
|
+
|
|
298
|
+
for ( const constraintName of Object.keys(alterStmts))
|
|
299
|
+
sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
|
|
300
|
+
mainResultObj.sql = sql;
|
|
285
301
|
}
|
|
286
302
|
|
|
287
303
|
if (options.toSql.src === 'sql')
|
|
288
|
-
|
|
304
|
+
mainResultObj.sql = sql;
|
|
289
305
|
|
|
290
306
|
for (const name in deletions)
|
|
291
307
|
deletions[name] = `${options.testMode ? '' : sqlVersionLine}${deletions[name]}`;
|
|
292
308
|
|
|
293
309
|
|
|
294
310
|
timetrace.stop();
|
|
295
|
-
return
|
|
311
|
+
return mainResultObj;
|
|
296
312
|
|
|
297
313
|
/**
|
|
298
314
|
* Render an artifact into the appropriate dictionary of 'resultObj'.
|
|
@@ -309,7 +325,6 @@ function toSqlDdl(csn, options) {
|
|
|
309
325
|
|
|
310
326
|
switch (art.kind) {
|
|
311
327
|
case 'entity':
|
|
312
|
-
case 'view':
|
|
313
328
|
if (getNormalizedQuery(art).query) {
|
|
314
329
|
const result = renderView(artifactName, art, env);
|
|
315
330
|
if (result)
|
|
@@ -331,7 +346,7 @@ function toSqlDdl(csn, options) {
|
|
|
331
346
|
// Ignore: not SQL-relevant
|
|
332
347
|
return;
|
|
333
348
|
default:
|
|
334
|
-
throw new
|
|
349
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
335
350
|
}
|
|
336
351
|
}
|
|
337
352
|
|
|
@@ -352,7 +367,7 @@ function toSqlDdl(csn, options) {
|
|
|
352
367
|
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
|
|
353
368
|
|
|
354
369
|
if (!artifactName)
|
|
355
|
-
throw new
|
|
370
|
+
throw new ModelError(`Undefined artifact name: ${artifactName}`);
|
|
356
371
|
}
|
|
357
372
|
|
|
358
373
|
// Render an artifact deletion into the appropriate dictionary of 'resultObj'.
|
|
@@ -392,12 +407,26 @@ function toSqlDdl(csn, options) {
|
|
|
392
407
|
? renderAssociationElement(eltName, defVariant, env)
|
|
393
408
|
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
394
409
|
}
|
|
395
|
-
function
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
410
|
+
function getEltStrNoProps(defVariant, eltName, ...props) {
|
|
411
|
+
const defNoProps = Object.assign({}, defVariant);
|
|
412
|
+
for (const prop of props)
|
|
413
|
+
delete defNoProps[prop];
|
|
414
|
+
return getEltStr(defNoProps, eltName);
|
|
415
|
+
}
|
|
416
|
+
function oldAnnoChangedIncompatibly(defOld, defNew) {
|
|
417
|
+
return typeof defOld === 'string' && defOld.trim().length && !(typeof defNew === 'string' && defNew.trim().startsWith(`${defOld.trim()} `));
|
|
418
|
+
}
|
|
419
|
+
function getUnknownSqlReason(anno, artifactName, defOld, defNew, eltName) {
|
|
420
|
+
const changeKind = defNew === undefined
|
|
421
|
+
? `removed (previous value: ${JSON.stringify(defOld)})`
|
|
422
|
+
: `changed from ${JSON.stringify(defOld)} to ${JSON.stringify(defNew)}`;
|
|
423
|
+
return eltName
|
|
424
|
+
? `annotation ${anno} of element ${artifactName}:${eltName} has been ${changeKind}`
|
|
425
|
+
: `annotation ${anno} of artifact ${artifactName} has been ${changeKind}`;
|
|
399
426
|
}
|
|
400
427
|
|
|
428
|
+
const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
|
|
429
|
+
|
|
401
430
|
const tableName = renderArtifactName(artifactName);
|
|
402
431
|
|
|
403
432
|
// Change entity properties
|
|
@@ -407,6 +436,15 @@ function toSqlDdl(csn, options) {
|
|
|
407
436
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
408
437
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
409
438
|
}
|
|
439
|
+
else if (sqlSnippetAnnos.includes(prop)) { // NOTE: @sql.replace may be supported in the future
|
|
440
|
+
if (oldAnnoChangedIncompatibly(def.old, def.new)) {
|
|
441
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
442
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(prop, artifactName, def.old, def.new));
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
addMigration(resultObj, artifactName, false, render.alterEntitySqlSnippet(tableName, def.new));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
410
448
|
}
|
|
411
449
|
}
|
|
412
450
|
|
|
@@ -441,9 +479,28 @@ function toSqlDdl(csn, options) {
|
|
|
441
479
|
if (eltStrNew === eltStrOld)
|
|
442
480
|
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
443
481
|
|
|
482
|
+
const annosIncompat = [];
|
|
483
|
+
sqlSnippetAnnos
|
|
484
|
+
.filter(anno => def.old[anno] !== def.new[anno])
|
|
485
|
+
.forEach((anno) => { // NOTE: @sql.replace may be supported in the future
|
|
486
|
+
if (oldAnnoChangedIncompatibly(def.old[anno], def.new[anno])) {
|
|
487
|
+
annosIncompat.push(anno);
|
|
488
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
489
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(anno, artifactName, def.old[anno], def.new[anno], eltName));
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
if (annosIncompat.length) {
|
|
494
|
+
const eltStrOldNoAnnos = getEltStrNoProps(def.old, eltName, ...annosIncompat);
|
|
495
|
+
const eltStrNewNoAnnos = getEltStrNoProps(def.new, eltName, ...annosIncompat);
|
|
496
|
+
if (eltStrOldNoAnnos === eltStrNewNoAnnos) { // only incompatibly-changed annos were modified
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
444
501
|
if (def.old.doc !== def.new.doc) {
|
|
445
|
-
const eltStrOldNoDoc =
|
|
446
|
-
const eltStrNewNoDoc =
|
|
502
|
+
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
503
|
+
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
447
504
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
448
505
|
const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
|
|
449
506
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
@@ -483,14 +540,16 @@ function toSqlDdl(csn, options) {
|
|
|
483
540
|
env._artifact = art;
|
|
484
541
|
const childEnv = increaseIndent(env);
|
|
485
542
|
const hanaTc = art.technicalConfig && art.technicalConfig.hana;
|
|
486
|
-
|
|
543
|
+
// tables can have @sql.prepend and @sql.append
|
|
544
|
+
const { front, back } = getSqlSnippets(options, art);
|
|
545
|
+
let result = front;
|
|
487
546
|
// Only HANA has row/column tables
|
|
488
547
|
if (options.toSql.dialect === 'hana') {
|
|
489
548
|
if (hanaTc && hanaTc.storeType) {
|
|
490
549
|
// Explicitly specified
|
|
491
550
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
492
551
|
}
|
|
493
|
-
else {
|
|
552
|
+
else if (!front) {
|
|
494
553
|
// in 'hdbtable' files, COLUMN or ROW is mandatory, and COLUMN is the default
|
|
495
554
|
result += 'COLUMN ';
|
|
496
555
|
}
|
|
@@ -500,13 +559,9 @@ function toSqlDdl(csn, options) {
|
|
|
500
559
|
result += `TABLE ${tableName}`;
|
|
501
560
|
result += ' (\n';
|
|
502
561
|
const elements = Object.keys(art.elements).map(eltName => renderElement(artifactName, eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv)).filter(s => s !== '').join(',\n');
|
|
503
|
-
if (elements !== '')
|
|
562
|
+
if (elements !== '')
|
|
504
563
|
result += elements;
|
|
505
|
-
|
|
506
|
-
else {
|
|
507
|
-
// TODO: Already be handled by 'empty-entity' reclassification; better location
|
|
508
|
-
error(null, [ 'definitions', artifactName ], 'Entities must have at least one element that is non-virtual');
|
|
509
|
-
}
|
|
564
|
+
|
|
510
565
|
const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)
|
|
511
566
|
.map(name => quoteSqlId(name))
|
|
512
567
|
.join(', ');
|
|
@@ -517,7 +572,9 @@ function toSqlDdl(csn, options) {
|
|
|
517
572
|
if (primaryKeys !== '')
|
|
518
573
|
result += `,\n${childEnv.indent}${primaryKeys}`;
|
|
519
574
|
|
|
520
|
-
|
|
575
|
+
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
576
|
+
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
577
|
+
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
521
578
|
const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
|
|
522
579
|
const referentialConstraints = {};
|
|
523
580
|
Object.entries(art.$tableConstraints.referential)
|
|
@@ -570,6 +627,9 @@ function toSqlDdl(csn, options) {
|
|
|
570
627
|
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
|
|
571
628
|
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
572
629
|
|
|
630
|
+
if (back)
|
|
631
|
+
result += back;
|
|
632
|
+
|
|
573
633
|
resultObj.hdbtable[artifactName] = result;
|
|
574
634
|
}
|
|
575
635
|
|
|
@@ -601,16 +661,21 @@ function toSqlDdl(csn, options) {
|
|
|
601
661
|
}
|
|
602
662
|
}
|
|
603
663
|
|
|
604
|
-
function addMigration(resultObj, artifactName, drop, sqlArray) {
|
|
664
|
+
function addMigration(resultObj, artifactName, drop, sqlArray, description) {
|
|
605
665
|
if (!(artifactName in resultObj.migrations))
|
|
606
666
|
resultObj.migrations[artifactName] = [];
|
|
607
667
|
|
|
608
|
-
|
|
668
|
+
if (!sqlArray) {
|
|
669
|
+
if (description)
|
|
670
|
+
resultObj.migrations[artifactName].push({ description });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
609
674
|
resultObj.migrations[artifactName].push(...migrations);
|
|
610
675
|
}
|
|
611
676
|
|
|
612
|
-
function addDeletion(resultObj, artifactName,
|
|
613
|
-
resultObj.deletions[artifactName] =
|
|
677
|
+
function addDeletion(resultObj, artifactName, deletionSql) {
|
|
678
|
+
resultObj.deletions[artifactName] = deletionSql;
|
|
614
679
|
}
|
|
615
680
|
|
|
616
681
|
/**
|
|
@@ -667,7 +732,13 @@ function toSqlDdl(csn, options) {
|
|
|
667
732
|
if (fzindex && options.toSql.dialect === 'hana')
|
|
668
733
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
669
734
|
|
|
670
|
-
|
|
735
|
+
// (table) elements can only have a @sql.append
|
|
736
|
+
const { back } = getSqlSnippets(options, elm);
|
|
737
|
+
|
|
738
|
+
if (back !== '') // Needs to be rendered before the COMMENT
|
|
739
|
+
result += back;
|
|
740
|
+
|
|
741
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
|
|
671
742
|
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
672
743
|
|
|
673
744
|
return result;
|
|
@@ -734,7 +805,7 @@ function toSqlDdl(csn, options) {
|
|
|
734
805
|
// This also affects renderIndexes
|
|
735
806
|
tc = tc.hana;
|
|
736
807
|
if (!tc)
|
|
737
|
-
throw new
|
|
808
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
738
809
|
|
|
739
810
|
if (tc.tableSuffix) {
|
|
740
811
|
// Although we could just render the whole bandwurm as one stream of tokens, the
|
|
@@ -803,7 +874,7 @@ function toSqlDdl(csn, options) {
|
|
|
803
874
|
const i = index.indexOf('index');
|
|
804
875
|
const j = index.indexOf('(');
|
|
805
876
|
if (i > index.length - 2 || !index[i + 1].ref || j < i || j > index.length - 2)
|
|
806
|
-
throw new
|
|
877
|
+
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
807
878
|
|
|
808
879
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
809
880
|
if (options.toSql.names === 'plain')
|
|
@@ -859,7 +930,7 @@ function toSqlDdl(csn, options) {
|
|
|
859
930
|
|
|
860
931
|
// Sanity check
|
|
861
932
|
if (!source.ref)
|
|
862
|
-
throw new
|
|
933
|
+
throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
|
|
863
934
|
|
|
864
935
|
return renderAbsolutePathWithAlias(artifactName, source, env);
|
|
865
936
|
}
|
|
@@ -901,7 +972,7 @@ function toSqlDdl(csn, options) {
|
|
|
901
972
|
function renderAbsolutePathWithAlias(artifactName, path, env) {
|
|
902
973
|
// This actually can't happen anymore because assoc2joins should have taken care of it
|
|
903
974
|
if (path.ref[0].where)
|
|
904
|
-
throw new
|
|
975
|
+
throw new ModelError(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
|
|
905
976
|
|
|
906
977
|
|
|
907
978
|
// SQL needs a ':' after path.ref[0] to separate associations
|
|
@@ -939,7 +1010,7 @@ function toSqlDdl(csn, options) {
|
|
|
939
1010
|
function renderAbsolutePath(path, sep, env) {
|
|
940
1011
|
// Sanity checks
|
|
941
1012
|
if (!path.ref)
|
|
942
|
-
throw new
|
|
1013
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
943
1014
|
|
|
944
1015
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
945
1016
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
@@ -956,7 +1027,7 @@ function toSqlDdl(csn, options) {
|
|
|
956
1027
|
if (ref && ref.params) {
|
|
957
1028
|
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
958
1029
|
}
|
|
959
|
-
else if (
|
|
1030
|
+
else if (syntax === 'udf') {
|
|
960
1031
|
// if syntax is user defined function, render empty argument list
|
|
961
1032
|
// CV without parameters is called as simple view
|
|
962
1033
|
result += '()';
|
|
@@ -979,7 +1050,7 @@ function toSqlDdl(csn, options) {
|
|
|
979
1050
|
* @param {object} node with `args` to render
|
|
980
1051
|
* @param {string} sep Separator between args
|
|
981
1052
|
* @param {object} env Render environment
|
|
982
|
-
* @param {string} syntax Some magic A2J
|
|
1053
|
+
* @param {string|null} syntax Some magic A2J parameter - for calcview parameter rendering
|
|
983
1054
|
* @returns {string} Rendered arguments
|
|
984
1055
|
* @throws Throws if args is not an array or object.
|
|
985
1056
|
*/
|
|
@@ -987,29 +1058,28 @@ function toSqlDdl(csn, options) {
|
|
|
987
1058
|
const args = node.args ? node.args : {};
|
|
988
1059
|
// Positional arguments
|
|
989
1060
|
if (Array.isArray(args))
|
|
990
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1061
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
991
1062
|
|
|
992
1063
|
// Named arguments (object/dict)
|
|
993
1064
|
else if (typeof args === 'object')
|
|
994
1065
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
995
|
-
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1066
|
+
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
|
|
996
1067
|
|
|
997
1068
|
|
|
998
|
-
throw new
|
|
1069
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
999
1070
|
|
|
1000
1071
|
|
|
1001
1072
|
/**
|
|
1002
1073
|
* Render the given argument/parameter correctly.
|
|
1003
1074
|
*
|
|
1004
1075
|
* @param {string} arg Argument to render
|
|
1005
|
-
* @param {string}
|
|
1076
|
+
* @param {string|null} parameterSyntax Some magic A2J parameter - for calcview parameter rendering
|
|
1006
1077
|
* @returns {string} Rendered argument
|
|
1007
1078
|
*/
|
|
1008
|
-
function decorateParameter(arg,
|
|
1009
|
-
if (
|
|
1079
|
+
function decorateParameter(arg, parameterSyntax) {
|
|
1080
|
+
if (parameterSyntax === 'calcview')
|
|
1010
1081
|
return `PLACEHOLDER."$$${arg}$$"`;
|
|
1011
1082
|
|
|
1012
|
-
|
|
1013
1083
|
return quoteSqlId(arg);
|
|
1014
1084
|
}
|
|
1015
1085
|
}
|
|
@@ -1070,6 +1140,11 @@ function toSqlDdl(csn, options) {
|
|
|
1070
1140
|
result += `${env.indent})`;
|
|
1071
1141
|
}
|
|
1072
1142
|
|
|
1143
|
+
// views can only have a @sql.append
|
|
1144
|
+
const { back } = getSqlSnippets(options, art);
|
|
1145
|
+
if (back)
|
|
1146
|
+
result += back;
|
|
1147
|
+
|
|
1073
1148
|
return result;
|
|
1074
1149
|
}
|
|
1075
1150
|
|
|
@@ -1077,7 +1152,7 @@ function toSqlDdl(csn, options) {
|
|
|
1077
1152
|
* Render the parameter definition of a view if any. Return the parameters in parentheses, or an empty string
|
|
1078
1153
|
*
|
|
1079
1154
|
* @param {string} artifactName Name of the view
|
|
1080
|
-
* @param {
|
|
1155
|
+
* @param {Object} params Dictionary of parameters
|
|
1081
1156
|
* @returns {string} Rendered parameters
|
|
1082
1157
|
*/
|
|
1083
1158
|
function renderParameterDefinitions(artifactName, params) {
|
|
@@ -1087,7 +1162,7 @@ function toSqlDdl(csn, options) {
|
|
|
1087
1162
|
for (const pn in params) {
|
|
1088
1163
|
const p = params[pn];
|
|
1089
1164
|
if (p.notNull === true || p.notNull === false)
|
|
1090
|
-
info(
|
|
1165
|
+
info('query-ignoring-param-nullability', [ 'definitions', artifactName, 'params', pn ], { '#': 'sql' });
|
|
1091
1166
|
// do not quote parameter identifiers for naming mode "quoted" / "hdbcds"
|
|
1092
1167
|
// this would be an incompatible change, as non-uppercased, quoted identifiers
|
|
1093
1168
|
// are rejected by the HANA compiler.
|
|
@@ -1145,7 +1220,7 @@ function toSqlDdl(csn, options) {
|
|
|
1145
1220
|
}
|
|
1146
1221
|
// Otherwise must have a SELECT
|
|
1147
1222
|
else if (!query.SELECT) {
|
|
1148
|
-
throw new
|
|
1223
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
1149
1224
|
}
|
|
1150
1225
|
const select = query.SELECT;
|
|
1151
1226
|
const childEnv = increaseIndent(env);
|
|
@@ -1161,7 +1236,7 @@ function toSqlDdl(csn, options) {
|
|
|
1161
1236
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
1162
1237
|
|
|
1163
1238
|
if (select.groupBy)
|
|
1164
|
-
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
1239
|
+
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
1165
1240
|
|
|
1166
1241
|
if (select.having)
|
|
1167
1242
|
result += `\n${env.indent}HAVING ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -1212,7 +1287,7 @@ function toSqlDdl(csn, options) {
|
|
|
1212
1287
|
* @returns {string} Rendered ORDER BY entry
|
|
1213
1288
|
*/
|
|
1214
1289
|
function renderOrderByEntry(entry, env) {
|
|
1215
|
-
let result = renderExpr(entry, env);
|
|
1290
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
1216
1291
|
if (entry.sort)
|
|
1217
1292
|
result += ` ${entry.sort.toUpperCase()}`;
|
|
1218
1293
|
|
|
@@ -1236,7 +1311,7 @@ function toSqlDdl(csn, options) {
|
|
|
1236
1311
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1237
1312
|
if (!elm.type) {
|
|
1238
1313
|
if (!elm.elements)
|
|
1239
|
-
throw new
|
|
1314
|
+
throw new ModelError(`Missing type of: ${elementName}`);
|
|
1240
1315
|
|
|
1241
1316
|
// TODO: Signal is not covered by tests + better location
|
|
1242
1317
|
error(null, [ 'definitions', artifactName, 'elements', elementName ],
|
|
@@ -1259,7 +1334,7 @@ function toSqlDdl(csn, options) {
|
|
|
1259
1334
|
result += renderBuiltinType(elm.type);
|
|
1260
1335
|
}
|
|
1261
1336
|
else {
|
|
1262
|
-
throw new
|
|
1337
|
+
throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
|
|
1263
1338
|
}
|
|
1264
1339
|
result += renderTypeParameters(elm);
|
|
1265
1340
|
return result;
|
|
@@ -1288,7 +1363,7 @@ function toSqlDdl(csn, options) {
|
|
|
1288
1363
|
* Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1289
1364
|
*
|
|
1290
1365
|
* @param {object} obj Object to render for
|
|
1291
|
-
* @param {boolean} treatKeyAsNotNull
|
|
1366
|
+
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1292
1367
|
* @returns {string} NULL/NOT NULL or ''
|
|
1293
1368
|
*/
|
|
1294
1369
|
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
@@ -1333,35 +1408,38 @@ function toSqlDdl(csn, options) {
|
|
|
1333
1408
|
* (no trailing LF, don't indent if inline)
|
|
1334
1409
|
*
|
|
1335
1410
|
* @todo Reuse this with toCdl
|
|
1336
|
-
* @param {Array|object|string}
|
|
1411
|
+
* @param {Array|object|string} expr Expression to render
|
|
1337
1412
|
* @param {object} env Render environment
|
|
1338
|
-
* @param {boolean} inline
|
|
1339
|
-
* @param {boolean} nestedExpr
|
|
1413
|
+
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
1414
|
+
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
1415
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
1416
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
1340
1417
|
* @returns {string} Rendered expression
|
|
1341
1418
|
*/
|
|
1342
|
-
function renderExpr(
|
|
1419
|
+
function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
1343
1420
|
// Compound expression
|
|
1344
|
-
if (Array.isArray(
|
|
1345
|
-
const tokens =
|
|
1421
|
+
if (Array.isArray(expr)) {
|
|
1422
|
+
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
1346
1423
|
return beautifyExprArray(tokens);
|
|
1347
1424
|
}
|
|
1348
|
-
else if (typeof
|
|
1349
|
-
if (nestedExpr &&
|
|
1350
|
-
return renderExplicitTypeCast(renderExprObject());
|
|
1351
|
-
return renderExprObject();
|
|
1425
|
+
else if (typeof expr === 'object' && expr !== null) {
|
|
1426
|
+
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
|
|
1427
|
+
return renderExplicitTypeCast(expr, renderExprObject(expr));
|
|
1428
|
+
return renderExprObject(expr);
|
|
1352
1429
|
}
|
|
1353
1430
|
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1354
1431
|
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
1355
1432
|
|
|
1356
|
-
return String(
|
|
1433
|
+
return String(expr).toUpperCase();
|
|
1357
1434
|
|
|
1358
1435
|
|
|
1359
1436
|
/**
|
|
1360
1437
|
* Various special cases represented as objects
|
|
1361
1438
|
*
|
|
1439
|
+
* @param {object} x Expression
|
|
1362
1440
|
* @returns {string} String representation of the expression
|
|
1363
1441
|
*/
|
|
1364
|
-
function renderExprObject() {
|
|
1442
|
+
function renderExprObject(x) {
|
|
1365
1443
|
if (x.list) {
|
|
1366
1444
|
return `(${x.list.map(item => renderExpr(item)).join(', ')})`;
|
|
1367
1445
|
}
|
|
@@ -1404,13 +1482,13 @@ function toSqlDdl(csn, options) {
|
|
|
1404
1482
|
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1405
1483
|
}
|
|
1406
1484
|
|
|
1407
|
-
throw new
|
|
1485
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1408
1486
|
}
|
|
1409
1487
|
|
|
1410
|
-
function renderWindowFunction(funcName, node,
|
|
1411
|
-
const suffix = node.xpr
|
|
1412
|
-
let r = `${funcName}(${renderArgs(node, '=>',
|
|
1413
|
-
r += ` ${suffix} (${renderExpr(node.xpr,
|
|
1488
|
+
function renderWindowFunction(funcName, node, fctEnv) {
|
|
1489
|
+
const suffix = node.xpr[0]; // OVER
|
|
1490
|
+
let r = `${funcName}(${renderArgs(node, '=>', fctEnv, null)})`;
|
|
1491
|
+
r += ` ${suffix} (${renderExpr(node.xpr.slice(1), fctEnv)})`; // do not pass suffix in renderExpr
|
|
1414
1492
|
return r;
|
|
1415
1493
|
}
|
|
1416
1494
|
|
|
@@ -1444,7 +1522,7 @@ function toSqlDdl(csn, options) {
|
|
|
1444
1522
|
|
|
1445
1523
|
// otherwise fall through to
|
|
1446
1524
|
default:
|
|
1447
|
-
throw new
|
|
1525
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1448
1526
|
}
|
|
1449
1527
|
}
|
|
1450
1528
|
|
|
@@ -1500,7 +1578,7 @@ function toSqlDdl(csn, options) {
|
|
|
1500
1578
|
return `'${options.toSql.user.id}'`;
|
|
1501
1579
|
|
|
1502
1580
|
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
|
|
1503
|
-
warning(null, null, 'The "$user" variable is not supported. Use
|
|
1581
|
+
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1504
1582
|
return '\'$user.id\'';
|
|
1505
1583
|
}
|
|
1506
1584
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
@@ -1565,10 +1643,11 @@ function toSqlDdl(csn, options) {
|
|
|
1565
1643
|
/**
|
|
1566
1644
|
* Renders an explicit `cast()` inside an 'xpr'.
|
|
1567
1645
|
*
|
|
1646
|
+
* @param {object} x Expression with cast
|
|
1568
1647
|
* @param {string} value Value to cast
|
|
1569
1648
|
* @returns {string} CAST statement
|
|
1570
1649
|
*/
|
|
1571
|
-
function renderExplicitTypeCast(value) {
|
|
1650
|
+
function renderExplicitTypeCast(x, value) {
|
|
1572
1651
|
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
1573
1652
|
return `CAST(${value} AS ${typeRef})`;
|
|
1574
1653
|
}
|
|
@@ -1606,7 +1685,7 @@ function toSqlDdl(csn, options) {
|
|
|
1606
1685
|
else if (typeof s === 'object') {
|
|
1607
1686
|
// Sanity check
|
|
1608
1687
|
if (!s.func && !s.id)
|
|
1609
|
-
throw new
|
|
1688
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1610
1689
|
|
|
1611
1690
|
// Not really a path step but an object-like function call
|
|
1612
1691
|
if (s.func)
|
|
@@ -1626,7 +1705,7 @@ function toSqlDdl(csn, options) {
|
|
|
1626
1705
|
return result;
|
|
1627
1706
|
}
|
|
1628
1707
|
|
|
1629
|
-
throw new
|
|
1708
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1630
1709
|
}
|
|
1631
1710
|
}
|
|
1632
1711
|
|