@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -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 +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- 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/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/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- 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 +33 -14
- 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 +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -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 +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- 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 +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- 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 +35 -12
- 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 +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- 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 +94 -801
- 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 +10 -27
- 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 +2 -1
- 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 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
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,
|
|
@@ -20,6 +21,7 @@ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
|
20
21
|
const { smartFuncId } = require('../sql-identifier');
|
|
21
22
|
const { sortCsn } = require('../json/to-csn');
|
|
22
23
|
const { manageConstraints } = require('./manageConstraints');
|
|
24
|
+
const { ModelError } = require('../base/error');
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -171,6 +173,12 @@ function toSqlDdl(csn, options) {
|
|
|
171
173
|
comment(comment) {
|
|
172
174
|
return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
|
|
173
175
|
},
|
|
176
|
+
/*
|
|
177
|
+
Alter SQL snippet for entity.
|
|
178
|
+
*/
|
|
179
|
+
alterEntitySqlSnippet(tableName, snippet) {
|
|
180
|
+
return [ `ALTER TABLE ${tableName} ${snippet};` ];
|
|
181
|
+
},
|
|
174
182
|
/*
|
|
175
183
|
Concatenate multiple statements which are to be treated as one by the API caller.
|
|
176
184
|
*/
|
|
@@ -186,10 +194,10 @@ function toSqlDdl(csn, options) {
|
|
|
186
194
|
checkCSNVersion(csn, options);
|
|
187
195
|
|
|
188
196
|
// The final result in hdb-kind-specific form, without leading CREATE, without trailing newlines
|
|
189
|
-
// (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
|
|
190
198
|
// the attribute names must be the HDI plugin names for --src hdi)
|
|
191
199
|
// The result object may have a `sql` dictionary for `toSql`.
|
|
192
|
-
const
|
|
200
|
+
const mainResultObj = {
|
|
193
201
|
hdbtabletype: Object.create(null),
|
|
194
202
|
hdbtable: Object.create(null),
|
|
195
203
|
hdbindex: Object.create(null),
|
|
@@ -215,12 +223,12 @@ function toSqlDdl(csn, options) {
|
|
|
215
223
|
// Current indentation string
|
|
216
224
|
indent: '',
|
|
217
225
|
};
|
|
218
|
-
renderArtifactInto(artifactName, artifact,
|
|
226
|
+
renderArtifactInto(artifactName, artifact, mainResultObj, env);
|
|
219
227
|
});
|
|
220
228
|
|
|
221
229
|
// Render each deleted artifact
|
|
222
230
|
for (const artifactName in csn.deletions)
|
|
223
|
-
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName],
|
|
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,42 +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
284
|
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
|
|
277
285
|
}
|
|
278
286
|
else if (!options.testMode) {
|
|
279
|
-
|
|
287
|
+
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
280
288
|
}
|
|
281
289
|
}
|
|
282
290
|
if (options.toSql.src === 'sql')
|
|
283
|
-
delete
|
|
291
|
+
delete mainResultObj[hdbKind];
|
|
284
292
|
}
|
|
285
293
|
|
|
286
|
-
// add `ALTER TABLE ADD CONSTRAINT` statements
|
|
287
|
-
if (options.
|
|
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') {
|
|
288
296
|
const alterStmts = manageConstraints(csn, options);
|
|
289
297
|
|
|
290
298
|
for ( const constraintName of Object.keys(alterStmts))
|
|
291
299
|
sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
|
|
292
|
-
|
|
300
|
+
mainResultObj.sql = sql;
|
|
293
301
|
}
|
|
294
302
|
|
|
295
303
|
if (options.toSql.src === 'sql')
|
|
296
|
-
|
|
304
|
+
mainResultObj.sql = sql;
|
|
297
305
|
|
|
298
306
|
for (const name in deletions)
|
|
299
307
|
deletions[name] = `${options.testMode ? '' : sqlVersionLine}${deletions[name]}`;
|
|
300
308
|
|
|
301
309
|
|
|
302
310
|
timetrace.stop();
|
|
303
|
-
return
|
|
311
|
+
return mainResultObj;
|
|
304
312
|
|
|
305
313
|
/**
|
|
306
314
|
* Render an artifact into the appropriate dictionary of 'resultObj'.
|
|
@@ -317,7 +325,6 @@ function toSqlDdl(csn, options) {
|
|
|
317
325
|
|
|
318
326
|
switch (art.kind) {
|
|
319
327
|
case 'entity':
|
|
320
|
-
case 'view':
|
|
321
328
|
if (getNormalizedQuery(art).query) {
|
|
322
329
|
const result = renderView(artifactName, art, env);
|
|
323
330
|
if (result)
|
|
@@ -339,7 +346,7 @@ function toSqlDdl(csn, options) {
|
|
|
339
346
|
// Ignore: not SQL-relevant
|
|
340
347
|
return;
|
|
341
348
|
default:
|
|
342
|
-
throw new
|
|
349
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
343
350
|
}
|
|
344
351
|
}
|
|
345
352
|
|
|
@@ -360,7 +367,7 @@ function toSqlDdl(csn, options) {
|
|
|
360
367
|
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
|
|
361
368
|
|
|
362
369
|
if (!artifactName)
|
|
363
|
-
throw new
|
|
370
|
+
throw new ModelError(`Undefined artifact name: ${artifactName}`);
|
|
364
371
|
}
|
|
365
372
|
|
|
366
373
|
// Render an artifact deletion into the appropriate dictionary of 'resultObj'.
|
|
@@ -400,11 +407,25 @@ function toSqlDdl(csn, options) {
|
|
|
400
407
|
? renderAssociationElement(eltName, defVariant, env)
|
|
401
408
|
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
402
409
|
}
|
|
403
|
-
function
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
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);
|
|
407
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}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
|
|
408
429
|
|
|
409
430
|
const tableName = renderArtifactName(artifactName);
|
|
410
431
|
|
|
@@ -415,6 +436,15 @@ function toSqlDdl(csn, options) {
|
|
|
415
436
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
416
437
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
417
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
|
+
}
|
|
418
448
|
}
|
|
419
449
|
}
|
|
420
450
|
|
|
@@ -449,9 +479,28 @@ function toSqlDdl(csn, options) {
|
|
|
449
479
|
if (eltStrNew === eltStrOld)
|
|
450
480
|
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
451
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
|
+
|
|
452
501
|
if (def.old.doc !== def.new.doc) {
|
|
453
|
-
const eltStrOldNoDoc =
|
|
454
|
-
const eltStrNewNoDoc =
|
|
502
|
+
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
503
|
+
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
455
504
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
456
505
|
const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
|
|
457
506
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
@@ -491,14 +540,16 @@ function toSqlDdl(csn, options) {
|
|
|
491
540
|
env._artifact = art;
|
|
492
541
|
const childEnv = increaseIndent(env);
|
|
493
542
|
const hanaTc = art.technicalConfig && art.technicalConfig.hana;
|
|
494
|
-
|
|
543
|
+
// tables can have @sql.prepend and @sql.append
|
|
544
|
+
const { front, back } = getSqlSnippets(options, art);
|
|
545
|
+
let result = front;
|
|
495
546
|
// Only HANA has row/column tables
|
|
496
547
|
if (options.toSql.dialect === 'hana') {
|
|
497
548
|
if (hanaTc && hanaTc.storeType) {
|
|
498
549
|
// Explicitly specified
|
|
499
550
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
500
551
|
}
|
|
501
|
-
else {
|
|
552
|
+
else if (!front) {
|
|
502
553
|
// in 'hdbtable' files, COLUMN or ROW is mandatory, and COLUMN is the default
|
|
503
554
|
result += 'COLUMN ';
|
|
504
555
|
}
|
|
@@ -521,7 +572,8 @@ function toSqlDdl(csn, options) {
|
|
|
521
572
|
if (primaryKeys !== '')
|
|
522
573
|
result += `,\n${childEnv.indent}${primaryKeys}`;
|
|
523
574
|
|
|
524
|
-
|
|
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';
|
|
525
577
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
526
578
|
const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
|
|
527
579
|
const referentialConstraints = {};
|
|
@@ -575,6 +627,9 @@ function toSqlDdl(csn, options) {
|
|
|
575
627
|
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options))
|
|
576
628
|
result += ` COMMENT '${getHanaComment(art)}'`;
|
|
577
629
|
|
|
630
|
+
if (back)
|
|
631
|
+
result += back;
|
|
632
|
+
|
|
578
633
|
resultObj.hdbtable[artifactName] = result;
|
|
579
634
|
}
|
|
580
635
|
|
|
@@ -606,16 +661,21 @@ function toSqlDdl(csn, options) {
|
|
|
606
661
|
}
|
|
607
662
|
}
|
|
608
663
|
|
|
609
|
-
function addMigration(resultObj, artifactName, drop, sqlArray) {
|
|
664
|
+
function addMigration(resultObj, artifactName, drop, sqlArray, description) {
|
|
610
665
|
if (!(artifactName in resultObj.migrations))
|
|
611
666
|
resultObj.migrations[artifactName] = [];
|
|
612
667
|
|
|
613
|
-
|
|
668
|
+
if (!sqlArray) {
|
|
669
|
+
if (description)
|
|
670
|
+
resultObj.migrations[artifactName].push({ description });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
614
674
|
resultObj.migrations[artifactName].push(...migrations);
|
|
615
675
|
}
|
|
616
676
|
|
|
617
|
-
function addDeletion(resultObj, artifactName,
|
|
618
|
-
resultObj.deletions[artifactName] =
|
|
677
|
+
function addDeletion(resultObj, artifactName, deletionSql) {
|
|
678
|
+
resultObj.deletions[artifactName] = deletionSql;
|
|
619
679
|
}
|
|
620
680
|
|
|
621
681
|
/**
|
|
@@ -672,7 +732,13 @@ function toSqlDdl(csn, options) {
|
|
|
672
732
|
if (fzindex && options.toSql.dialect === 'hana')
|
|
673
733
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
674
734
|
|
|
675
|
-
|
|
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))
|
|
676
742
|
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
677
743
|
|
|
678
744
|
return result;
|
|
@@ -739,7 +805,7 @@ function toSqlDdl(csn, options) {
|
|
|
739
805
|
// This also affects renderIndexes
|
|
740
806
|
tc = tc.hana;
|
|
741
807
|
if (!tc)
|
|
742
|
-
throw new
|
|
808
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
743
809
|
|
|
744
810
|
if (tc.tableSuffix) {
|
|
745
811
|
// Although we could just render the whole bandwurm as one stream of tokens, the
|
|
@@ -808,7 +874,7 @@ function toSqlDdl(csn, options) {
|
|
|
808
874
|
const i = index.indexOf('index');
|
|
809
875
|
const j = index.indexOf('(');
|
|
810
876
|
if (i > index.length - 2 || !index[i + 1].ref || j < i || j > index.length - 2)
|
|
811
|
-
throw new
|
|
877
|
+
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
812
878
|
|
|
813
879
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
814
880
|
if (options.toSql.names === 'plain')
|
|
@@ -864,7 +930,7 @@ function toSqlDdl(csn, options) {
|
|
|
864
930
|
|
|
865
931
|
// Sanity check
|
|
866
932
|
if (!source.ref)
|
|
867
|
-
throw new
|
|
933
|
+
throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
|
|
868
934
|
|
|
869
935
|
return renderAbsolutePathWithAlias(artifactName, source, env);
|
|
870
936
|
}
|
|
@@ -906,7 +972,7 @@ function toSqlDdl(csn, options) {
|
|
|
906
972
|
function renderAbsolutePathWithAlias(artifactName, path, env) {
|
|
907
973
|
// This actually can't happen anymore because assoc2joins should have taken care of it
|
|
908
974
|
if (path.ref[0].where)
|
|
909
|
-
throw new
|
|
975
|
+
throw new ModelError(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
|
|
910
976
|
|
|
911
977
|
|
|
912
978
|
// SQL needs a ':' after path.ref[0] to separate associations
|
|
@@ -944,7 +1010,7 @@ function toSqlDdl(csn, options) {
|
|
|
944
1010
|
function renderAbsolutePath(path, sep, env) {
|
|
945
1011
|
// Sanity checks
|
|
946
1012
|
if (!path.ref)
|
|
947
|
-
throw new
|
|
1013
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
948
1014
|
|
|
949
1015
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
950
1016
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
@@ -961,7 +1027,7 @@ function toSqlDdl(csn, options) {
|
|
|
961
1027
|
if (ref && ref.params) {
|
|
962
1028
|
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
963
1029
|
}
|
|
964
|
-
else if (
|
|
1030
|
+
else if (syntax === 'udf') {
|
|
965
1031
|
// if syntax is user defined function, render empty argument list
|
|
966
1032
|
// CV without parameters is called as simple view
|
|
967
1033
|
result += '()';
|
|
@@ -984,7 +1050,7 @@ function toSqlDdl(csn, options) {
|
|
|
984
1050
|
* @param {object} node with `args` to render
|
|
985
1051
|
* @param {string} sep Separator between args
|
|
986
1052
|
* @param {object} env Render environment
|
|
987
|
-
* @param {string} syntax Some magic A2J
|
|
1053
|
+
* @param {string|null} syntax Some magic A2J parameter - for calcview parameter rendering
|
|
988
1054
|
* @returns {string} Rendered arguments
|
|
989
1055
|
* @throws Throws if args is not an array or object.
|
|
990
1056
|
*/
|
|
@@ -992,29 +1058,28 @@ function toSqlDdl(csn, options) {
|
|
|
992
1058
|
const args = node.args ? node.args : {};
|
|
993
1059
|
// Positional arguments
|
|
994
1060
|
if (Array.isArray(args))
|
|
995
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1061
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
996
1062
|
|
|
997
1063
|
// Named arguments (object/dict)
|
|
998
1064
|
else if (typeof args === 'object')
|
|
999
1065
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1000
|
-
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(', ');
|
|
1001
1067
|
|
|
1002
1068
|
|
|
1003
|
-
throw new
|
|
1069
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1004
1070
|
|
|
1005
1071
|
|
|
1006
1072
|
/**
|
|
1007
1073
|
* Render the given argument/parameter correctly.
|
|
1008
1074
|
*
|
|
1009
1075
|
* @param {string} arg Argument to render
|
|
1010
|
-
* @param {string}
|
|
1076
|
+
* @param {string|null} parameterSyntax Some magic A2J parameter - for calcview parameter rendering
|
|
1011
1077
|
* @returns {string} Rendered argument
|
|
1012
1078
|
*/
|
|
1013
|
-
function decorateParameter(arg,
|
|
1014
|
-
if (
|
|
1079
|
+
function decorateParameter(arg, parameterSyntax) {
|
|
1080
|
+
if (parameterSyntax === 'calcview')
|
|
1015
1081
|
return `PLACEHOLDER."$$${arg}$$"`;
|
|
1016
1082
|
|
|
1017
|
-
|
|
1018
1083
|
return quoteSqlId(arg);
|
|
1019
1084
|
}
|
|
1020
1085
|
}
|
|
@@ -1075,6 +1140,11 @@ function toSqlDdl(csn, options) {
|
|
|
1075
1140
|
result += `${env.indent})`;
|
|
1076
1141
|
}
|
|
1077
1142
|
|
|
1143
|
+
// views can only have a @sql.append
|
|
1144
|
+
const { back } = getSqlSnippets(options, art);
|
|
1145
|
+
if (back)
|
|
1146
|
+
result += back;
|
|
1147
|
+
|
|
1078
1148
|
return result;
|
|
1079
1149
|
}
|
|
1080
1150
|
|
|
@@ -1082,7 +1152,7 @@ function toSqlDdl(csn, options) {
|
|
|
1082
1152
|
* Render the parameter definition of a view if any. Return the parameters in parentheses, or an empty string
|
|
1083
1153
|
*
|
|
1084
1154
|
* @param {string} artifactName Name of the view
|
|
1085
|
-
* @param {
|
|
1155
|
+
* @param {Object} params Dictionary of parameters
|
|
1086
1156
|
* @returns {string} Rendered parameters
|
|
1087
1157
|
*/
|
|
1088
1158
|
function renderParameterDefinitions(artifactName, params) {
|
|
@@ -1150,7 +1220,7 @@ function toSqlDdl(csn, options) {
|
|
|
1150
1220
|
}
|
|
1151
1221
|
// Otherwise must have a SELECT
|
|
1152
1222
|
else if (!query.SELECT) {
|
|
1153
|
-
throw new
|
|
1223
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
1154
1224
|
}
|
|
1155
1225
|
const select = query.SELECT;
|
|
1156
1226
|
const childEnv = increaseIndent(env);
|
|
@@ -1166,7 +1236,7 @@ function toSqlDdl(csn, options) {
|
|
|
1166
1236
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
1167
1237
|
|
|
1168
1238
|
if (select.groupBy)
|
|
1169
|
-
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(', ')}`;
|
|
1170
1240
|
|
|
1171
1241
|
if (select.having)
|
|
1172
1242
|
result += `\n${env.indent}HAVING ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -1217,7 +1287,7 @@ function toSqlDdl(csn, options) {
|
|
|
1217
1287
|
* @returns {string} Rendered ORDER BY entry
|
|
1218
1288
|
*/
|
|
1219
1289
|
function renderOrderByEntry(entry, env) {
|
|
1220
|
-
let result = renderExpr(entry, env);
|
|
1290
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
1221
1291
|
if (entry.sort)
|
|
1222
1292
|
result += ` ${entry.sort.toUpperCase()}`;
|
|
1223
1293
|
|
|
@@ -1241,7 +1311,7 @@ function toSqlDdl(csn, options) {
|
|
|
1241
1311
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1242
1312
|
if (!elm.type) {
|
|
1243
1313
|
if (!elm.elements)
|
|
1244
|
-
throw new
|
|
1314
|
+
throw new ModelError(`Missing type of: ${elementName}`);
|
|
1245
1315
|
|
|
1246
1316
|
// TODO: Signal is not covered by tests + better location
|
|
1247
1317
|
error(null, [ 'definitions', artifactName, 'elements', elementName ],
|
|
@@ -1264,7 +1334,7 @@ function toSqlDdl(csn, options) {
|
|
|
1264
1334
|
result += renderBuiltinType(elm.type);
|
|
1265
1335
|
}
|
|
1266
1336
|
else {
|
|
1267
|
-
throw new
|
|
1337
|
+
throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
|
|
1268
1338
|
}
|
|
1269
1339
|
result += renderTypeParameters(elm);
|
|
1270
1340
|
return result;
|
|
@@ -1293,7 +1363,7 @@ function toSqlDdl(csn, options) {
|
|
|
1293
1363
|
* Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1294
1364
|
*
|
|
1295
1365
|
* @param {object} obj Object to render for
|
|
1296
|
-
* @param {boolean} treatKeyAsNotNull
|
|
1366
|
+
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1297
1367
|
* @returns {string} NULL/NOT NULL or ''
|
|
1298
1368
|
*/
|
|
1299
1369
|
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
@@ -1338,35 +1408,38 @@ function toSqlDdl(csn, options) {
|
|
|
1338
1408
|
* (no trailing LF, don't indent if inline)
|
|
1339
1409
|
*
|
|
1340
1410
|
* @todo Reuse this with toCdl
|
|
1341
|
-
* @param {Array|object|string}
|
|
1411
|
+
* @param {Array|object|string} expr Expression to render
|
|
1342
1412
|
* @param {object} env Render environment
|
|
1343
|
-
* @param {boolean} inline
|
|
1344
|
-
* @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.
|
|
1345
1417
|
* @returns {string} Rendered expression
|
|
1346
1418
|
*/
|
|
1347
|
-
function renderExpr(
|
|
1419
|
+
function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
1348
1420
|
// Compound expression
|
|
1349
|
-
if (Array.isArray(
|
|
1350
|
-
const tokens =
|
|
1421
|
+
if (Array.isArray(expr)) {
|
|
1422
|
+
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
1351
1423
|
return beautifyExprArray(tokens);
|
|
1352
1424
|
}
|
|
1353
|
-
else if (typeof
|
|
1354
|
-
if (nestedExpr &&
|
|
1355
|
-
return renderExplicitTypeCast(renderExprObject());
|
|
1356
|
-
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);
|
|
1357
1429
|
}
|
|
1358
1430
|
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1359
1431
|
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
1360
1432
|
|
|
1361
|
-
return String(
|
|
1433
|
+
return String(expr).toUpperCase();
|
|
1362
1434
|
|
|
1363
1435
|
|
|
1364
1436
|
/**
|
|
1365
1437
|
* Various special cases represented as objects
|
|
1366
1438
|
*
|
|
1439
|
+
* @param {object} x Expression
|
|
1367
1440
|
* @returns {string} String representation of the expression
|
|
1368
1441
|
*/
|
|
1369
|
-
function renderExprObject() {
|
|
1442
|
+
function renderExprObject(x) {
|
|
1370
1443
|
if (x.list) {
|
|
1371
1444
|
return `(${x.list.map(item => renderExpr(item)).join(', ')})`;
|
|
1372
1445
|
}
|
|
@@ -1409,13 +1482,13 @@ function toSqlDdl(csn, options) {
|
|
|
1409
1482
|
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1410
1483
|
}
|
|
1411
1484
|
|
|
1412
|
-
throw new
|
|
1485
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1413
1486
|
}
|
|
1414
1487
|
|
|
1415
|
-
function renderWindowFunction(funcName, node,
|
|
1416
|
-
const suffix = node.xpr
|
|
1417
|
-
let r = `${funcName}(${renderArgs(node, '=>',
|
|
1418
|
-
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
|
|
1419
1492
|
return r;
|
|
1420
1493
|
}
|
|
1421
1494
|
|
|
@@ -1449,7 +1522,7 @@ function toSqlDdl(csn, options) {
|
|
|
1449
1522
|
|
|
1450
1523
|
// otherwise fall through to
|
|
1451
1524
|
default:
|
|
1452
|
-
throw new
|
|
1525
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1453
1526
|
}
|
|
1454
1527
|
}
|
|
1455
1528
|
|
|
@@ -1570,10 +1643,11 @@ function toSqlDdl(csn, options) {
|
|
|
1570
1643
|
/**
|
|
1571
1644
|
* Renders an explicit `cast()` inside an 'xpr'.
|
|
1572
1645
|
*
|
|
1646
|
+
* @param {object} x Expression with cast
|
|
1573
1647
|
* @param {string} value Value to cast
|
|
1574
1648
|
* @returns {string} CAST statement
|
|
1575
1649
|
*/
|
|
1576
|
-
function renderExplicitTypeCast(value) {
|
|
1650
|
+
function renderExplicitTypeCast(x, value) {
|
|
1577
1651
|
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
|
|
1578
1652
|
return `CAST(${value} AS ${typeRef})`;
|
|
1579
1653
|
}
|
|
@@ -1611,7 +1685,7 @@ function toSqlDdl(csn, options) {
|
|
|
1611
1685
|
else if (typeof s === 'object') {
|
|
1612
1686
|
// Sanity check
|
|
1613
1687
|
if (!s.func && !s.id)
|
|
1614
|
-
throw new
|
|
1688
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1615
1689
|
|
|
1616
1690
|
// Not really a path step but an object-like function call
|
|
1617
1691
|
if (s.func)
|
|
@@ -1631,7 +1705,7 @@ function toSqlDdl(csn, options) {
|
|
|
1631
1705
|
return result;
|
|
1632
1706
|
}
|
|
1633
1707
|
|
|
1634
|
-
throw new
|
|
1708
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1635
1709
|
}
|
|
1636
1710
|
}
|
|
1637
1711
|
|
|
@@ -20,7 +20,6 @@ const {
|
|
|
20
20
|
|
|
21
21
|
const { implicitAs } = require('../../model/csnRefs');
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
24
|
* Render the given function
|
|
26
25
|
*
|
|
@@ -37,7 +36,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
/**
|
|
40
|
-
* Checks
|
|
39
|
+
* Checks whether the given function is to be rendered without parentheses
|
|
41
40
|
*
|
|
42
41
|
* @param {object} node Content of the function
|
|
43
42
|
* @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
|
|
@@ -205,12 +204,11 @@ function addMissingChildContexts(csn, artifactName, killList) {
|
|
|
205
204
|
addPossibleGaps(name.slice(artifactName.length + 1).split('.'), artifactName);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
function addPossibleGaps(possibleGaps,
|
|
209
|
-
let possibleGap = artifactName;
|
|
207
|
+
function addPossibleGaps(possibleGaps, gapArtifactName) {
|
|
210
208
|
for (const gap of possibleGaps) {
|
|
211
|
-
|
|
212
|
-
if (!csn.definitions[
|
|
213
|
-
const contextName =
|
|
209
|
+
gapArtifactName += `.${gap}`;
|
|
210
|
+
if (!csn.definitions[gapArtifactName]) {
|
|
211
|
+
const contextName = gapArtifactName;
|
|
214
212
|
csn.definitions[contextName] = {
|
|
215
213
|
kind: 'context',
|
|
216
214
|
};
|
|
@@ -332,7 +330,7 @@ function addIntermediateContexts(csn, killList) {
|
|
|
332
330
|
}
|
|
333
331
|
|
|
334
332
|
/**
|
|
335
|
-
* Check
|
|
333
|
+
* Check whether the given artifact or element has a comment that needs to be rendered.
|
|
336
334
|
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
|
|
337
335
|
*
|
|
338
336
|
* @param {CSN.Artifact} obj
|
|
@@ -355,6 +353,21 @@ function getHanaComment(obj) {
|
|
|
355
353
|
return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
|
|
356
354
|
}
|
|
357
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Get the @sql.prepend/append if set - already add a space after/before.
|
|
358
|
+
* If no value is set, use '';
|
|
359
|
+
*
|
|
360
|
+
* @param {CSN.Options} options
|
|
361
|
+
* @param {object} obj
|
|
362
|
+
* @returns {object} object with .front and .back
|
|
363
|
+
*/
|
|
364
|
+
function getSqlSnippets(options, obj) {
|
|
365
|
+
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
|
|
366
|
+
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
367
|
+
|
|
368
|
+
return { front, back };
|
|
369
|
+
}
|
|
370
|
+
|
|
358
371
|
/**
|
|
359
372
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
360
373
|
*
|
|
@@ -383,4 +396,5 @@ module.exports = {
|
|
|
383
396
|
getHanaComment,
|
|
384
397
|
findElement,
|
|
385
398
|
funcWithoutParen,
|
|
399
|
+
getSqlSnippets,
|
|
386
400
|
};
|