@sap/cds-compiler 6.0.14 → 6.2.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 +61 -0
- package/bin/cdsc.js +6 -2
- package/bin/cdsse.js +1 -1
- package/bin/cdsv2m.js +1 -1
- package/lib/api/main.js +29 -7
- package/lib/api/options.js +2 -0
- package/lib/base/builtins.js +9 -0
- package/lib/base/keywords.js +1 -1
- package/lib/base/message-registry.js +5 -3
- package/lib/base/messages.js +3 -3
- package/lib/base/model.js +1 -0
- package/lib/base/node-helpers.js +10 -2
- package/lib/base/optionProcessorHelper.js +7 -2
- package/lib/checks/assocOutsideService.js +3 -1
- package/lib/checks/featureFlags.js +4 -1
- package/lib/compiler/assert-consistency.js +3 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +1 -1
- package/lib/compiler/checks.js +38 -21
- package/lib/compiler/define.js +24 -5
- package/lib/compiler/extend.js +1 -1
- package/lib/compiler/finalize-parse-cdl.js +9 -1
- package/lib/compiler/generate.js +4 -4
- package/lib/compiler/index.js +10 -1
- package/lib/compiler/lsp-api.js +2 -0
- package/lib/compiler/populate.js +8 -8
- package/lib/compiler/propagator.js +1 -1
- package/lib/compiler/resolve.js +15 -14
- package/lib/compiler/shared.js +6 -7
- package/lib/compiler/tweak-assocs.js +6 -6
- package/lib/compiler/utils.js +9 -16
- package/lib/compiler/xpr-rewrite.js +2 -2
- package/lib/gen/BaseParser.js +43 -37
- package/lib/gen/CdlGrammar.checksum +1 -1
- package/lib/gen/CdlParser.js +1424 -1433
- package/lib/gen/Dictionary.json +1 -7
- package/lib/gen/cdlKeywords.json +26 -0
- package/lib/inspect/inspectPropagation.js +1 -1
- package/lib/json/from-csn.js +2 -2
- package/lib/json/to-csn.js +9 -5
- package/lib/language/multiLineStringParser.js +1 -1
- package/lib/main.d.ts +10 -2
- package/lib/model/cloneCsn.js +1 -0
- package/lib/optionProcessor.js +13 -7
- package/lib/parsers/AstBuildingParser.js +24 -21
- package/lib/parsers/identifiers.js +2 -30
- package/lib/render/toCdl.js +63 -9
- package/lib/render/toSql.js +127 -108
- package/lib/render/utils/sql.js +67 -0
- package/lib/transform/addTenantFields.js +4 -4
- package/lib/transform/db/killAnnotations.js +1 -0
- package/lib/transform/db/processSqlServices.js +20 -2
- package/lib/transform/draft/db.js +1 -1
- package/lib/transform/draft/odata.js +14 -4
- package/lib/transform/forOdata.js +91 -2
- package/lib/transform/forRelationalDB.js +1 -1
- package/lib/transform/odata/flattening.js +1 -1
- package/lib/transform/transformUtils.js +2 -2
- package/lib/transform/translateAssocsToJoins.js +2 -26
- package/lib/utils/moduleResolve.js +1 -1
- package/package.json +2 -2
package/lib/render/toSql.js
CHANGED
|
@@ -18,13 +18,13 @@ const {
|
|
|
18
18
|
getDeltaRenderer,
|
|
19
19
|
} = require('./utils/delta');
|
|
20
20
|
const {
|
|
21
|
-
renderReferentialConstraint, getIdentifierUtils,
|
|
21
|
+
renderReferentialConstraint, getIdentifierUtils, isProjectionView,
|
|
22
22
|
} = require('./utils/sql');
|
|
23
23
|
const DuplicateChecker = require('./DuplicateChecker');
|
|
24
24
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
25
25
|
const { timetrace } = require('../utils/timetrace');
|
|
26
26
|
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
27
|
-
const
|
|
27
|
+
const sqlIdentifiers = require('../sql-identifier');
|
|
28
28
|
const { sortCsn } = require('../model/cloneCsn');
|
|
29
29
|
const { manageConstraints, manageConstraint } = require('./manageConstraints');
|
|
30
30
|
const { renderUniqueConstraintString, renderUniqueConstraintDrop, renderUniqueConstraintAdd } = require('./utils/unique');
|
|
@@ -109,8 +109,6 @@ class SqlRenderEnvironment {
|
|
|
109
109
|
* @returns {object} Dictionary of artifact-type:artifacts, where artifacts is a dictionary of name:content
|
|
110
110
|
*/
|
|
111
111
|
function toSqlDdl( csn, options, messageFunctions ) {
|
|
112
|
-
const withHanaAssociations = options.withHanaAssociations && options.sqlDialect === 'hana';
|
|
113
|
-
|
|
114
112
|
timetrace.start('SQL rendering');
|
|
115
113
|
const {
|
|
116
114
|
error, warning, info, throwWithAnyError,
|
|
@@ -137,10 +135,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
137
135
|
return renderExpressionRef(x, this.env);
|
|
138
136
|
},
|
|
139
137
|
windowFunction( x) {
|
|
140
|
-
return renderWindowFunction(smartFuncId(prepareIdentifier(x.func)
|
|
138
|
+
return renderWindowFunction(smartFuncId(prepareIdentifier(x.func)), x, this.env);
|
|
141
139
|
},
|
|
142
140
|
func(x) {
|
|
143
|
-
return renderFunc(smartFuncId(prepareIdentifier(x.func)
|
|
141
|
+
return renderFunc(smartFuncId(prepareIdentifier(x.func)), x, a => renderArgs(a, '=>', this.env, null), { messageFunctions, options, path: this.env.path });
|
|
144
142
|
},
|
|
145
143
|
xpr(x) {
|
|
146
144
|
const env = this.env.withSubPath([ 'xpr' ]);
|
|
@@ -193,6 +191,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
193
191
|
hdbindex: Object.create(null),
|
|
194
192
|
hdbfulltextindex: Object.create(null),
|
|
195
193
|
hdbview: Object.create(null),
|
|
194
|
+
hdbprojectionview: Object.create(null),
|
|
196
195
|
hdbconstraint: Object.create(null),
|
|
197
196
|
deletions: Object.create(null),
|
|
198
197
|
constraintDeletions: [],
|
|
@@ -212,7 +211,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
212
211
|
const changeElementsDuplicateChecker = new DuplicateChecker();
|
|
213
212
|
|
|
214
213
|
// Render each artifact on its own
|
|
215
|
-
forEachDefinition((
|
|
214
|
+
forEachDefinition(sortCsnIfTestMode(csn), (artifact, artifactName) => {
|
|
216
215
|
renderDefinitionInto(artifactName, artifact, mainResultObj, new SqlRenderEnvironment());
|
|
217
216
|
});
|
|
218
217
|
|
|
@@ -220,10 +219,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
220
219
|
for (const artifactName in csn.deletions)
|
|
221
220
|
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], mainResultObj);
|
|
222
221
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (csn.changedPrimaryKeys && supportsSqlExtensions) {
|
|
226
|
-
csn.changedPrimaryKeys = options.testMode ? sortCsn(csn.changedPrimaryKeys) : csn.changedPrimaryKeys;
|
|
222
|
+
if (csn.changedPrimaryKeys && supportsSqlExtensions()) {
|
|
223
|
+
csn.changedPrimaryKeys = sortCsnIfTestMode(csn.changedPrimaryKeys);
|
|
227
224
|
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
228
225
|
const drop = render.dropKey(artifactName);
|
|
229
226
|
addMigration(mainResultObj, artifactName, true, render.concat(...drop));
|
|
@@ -233,8 +230,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
233
230
|
// Render each artifact extension
|
|
234
231
|
// Only SAP HANA SQL is currently supported.
|
|
235
232
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
236
|
-
if (csn.extensions && supportsSqlExtensions) {
|
|
237
|
-
csn.extensions =
|
|
233
|
+
if (csn.extensions && supportsSqlExtensions()) {
|
|
234
|
+
csn.extensions = sortCsnIfTestMode(csn.extensions);
|
|
238
235
|
for (let i = 0; i < csn.extensions.length; ++i) {
|
|
239
236
|
const extension = csn.extensions[i];
|
|
240
237
|
if (extension.extend) {
|
|
@@ -248,8 +245,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
248
245
|
|
|
249
246
|
// Render each artifact change
|
|
250
247
|
// Only SAP HANA SQL is currently supported.
|
|
251
|
-
if (csn.migrations && supportsSqlExtensions) {
|
|
252
|
-
csn.migrations =
|
|
248
|
+
if (csn.migrations && supportsSqlExtensions()) {
|
|
249
|
+
csn.migrations = sortCsnIfTestMode(csn.migrations);
|
|
253
250
|
for (const migration of csn.migrations) {
|
|
254
251
|
if (migration.migrate) {
|
|
255
252
|
const artifactName = migration.migrate;
|
|
@@ -261,8 +258,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
261
258
|
}
|
|
262
259
|
}
|
|
263
260
|
|
|
264
|
-
if (csn.changedPrimaryKeys && supportsSqlExtensions) {
|
|
265
|
-
csn.changedPrimaryKeys =
|
|
261
|
+
if (csn.changedPrimaryKeys && supportsSqlExtensions()) {
|
|
262
|
+
csn.changedPrimaryKeys = sortCsnIfTestMode(csn.changedPrimaryKeys);
|
|
266
263
|
csn.changedPrimaryKeys.forEach((artifactName) => {
|
|
267
264
|
const add = render.addKey(artifactName, csn.definitions[artifactName].elements);
|
|
268
265
|
addMigration(mainResultObj, artifactName, true, render.concat(...add));
|
|
@@ -318,26 +315,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
318
315
|
const sql = Object.create(null);
|
|
319
316
|
|
|
320
317
|
// Handle hdbKinds separately from alterTable case
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
deletions, constraintDeletions, migrations: _, ...hdbKinds
|
|
324
|
-
} = mainResultObj;
|
|
325
|
-
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
326
|
-
for (const name in mainResultObj[hdbKind]) {
|
|
327
|
-
if (options.src === 'sql') {
|
|
328
|
-
let sourceString = mainResultObj[hdbKind][name];
|
|
329
|
-
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
330
|
-
if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
331
|
-
sourceString = sourceString.slice('COLUMN '.length);
|
|
332
|
-
sql[name] = `CREATE ${ sourceString };`;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
if (options.src === 'sql')
|
|
336
|
-
delete mainResultObj[hdbKind];
|
|
337
|
-
}
|
|
318
|
+
if (options.src === 'sql')
|
|
319
|
+
adaptHdbKindsForSql();
|
|
338
320
|
|
|
339
|
-
|
|
340
|
-
if (!options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres' /* || options.sqlDialect === 'sqlite' */)) {
|
|
321
|
+
if (useAlterTableForConstraints()) {
|
|
341
322
|
const constraints = Object.create(null);
|
|
342
323
|
const alterStmts = manageConstraints(csn, options);
|
|
343
324
|
|
|
@@ -351,12 +332,31 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
351
332
|
if (options.src === 'sql')
|
|
352
333
|
mainResultObj.sql = sql;
|
|
353
334
|
|
|
354
|
-
for (const name in deletions)
|
|
355
|
-
deletions[name] = `${ deletions[name] }`;
|
|
356
|
-
|
|
357
335
|
timetrace.stop('SQL rendering');
|
|
358
336
|
return mainResultObj;
|
|
359
337
|
|
|
338
|
+
function adaptHdbKindsForSql() {
|
|
339
|
+
const {
|
|
340
|
+
hdbtable,
|
|
341
|
+
// eslint-disable-next-line no-unused-vars
|
|
342
|
+
deletions, constraintDeletions, migrations: _, ...hdbKinds
|
|
343
|
+
} = mainResultObj;
|
|
344
|
+
|
|
345
|
+
for (const name in hdbtable) {
|
|
346
|
+
let sourceString = hdbtable[name];
|
|
347
|
+
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
348
|
+
if (sourceString.startsWith('COLUMN '))
|
|
349
|
+
sourceString = sourceString.slice('COLUMN '.length);
|
|
350
|
+
sql[name] = `CREATE ${ sourceString };`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
354
|
+
for (const name in mainResultObj[hdbKind])
|
|
355
|
+
sql[name] = `CREATE ${ mainResultObj[hdbKind][name] };`;
|
|
356
|
+
delete mainResultObj[hdbKind];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
360
|
/**
|
|
361
361
|
* Render a definition into the appropriate dictionary of 'resultObj'.
|
|
362
362
|
*
|
|
@@ -373,7 +373,6 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
373
373
|
dummySqlServiceEntities[art.$dummyService] ??= Object.create(null);
|
|
374
374
|
dummySqlServiceEntities[art.$dummyService][artifactName] = art;
|
|
375
375
|
}
|
|
376
|
-
|
|
377
376
|
return;
|
|
378
377
|
}
|
|
379
378
|
|
|
@@ -384,14 +383,12 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
384
383
|
sqlServiceEntities[art.$sqlService][artifactName] = art;
|
|
385
384
|
}
|
|
386
385
|
|
|
387
|
-
if (art.query || art.projection)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
386
|
+
if (art.query || art.projection)
|
|
387
|
+
renderViewInto(artifactName, art, resultObj, env);
|
|
388
|
+
|
|
389
|
+
else
|
|
393
390
|
renderEntityInto(artifactName, art, resultObj, env);
|
|
394
|
-
|
|
391
|
+
|
|
395
392
|
break;
|
|
396
393
|
case 'type':
|
|
397
394
|
case 'context':
|
|
@@ -451,8 +448,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
451
448
|
function renderArtifactDeletionInto( artifactName, art, resultObj ) {
|
|
452
449
|
const tableName = renderArtifactName(artifactName);
|
|
453
450
|
deletionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
|
|
454
|
-
|
|
455
|
-
addDeletion(resultObj, artifactName, `-- [WARNING] this statement is lossy\nDROP TABLE ${ tableName }`);
|
|
451
|
+
resultObj.deletions[artifactName] = `-- [WARNING] this statement is lossy\nDROP TABLE ${ tableName }`;
|
|
456
452
|
}
|
|
457
453
|
|
|
458
454
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
@@ -534,13 +530,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
534
530
|
if (migration.removeConstraints[constraintType]) {
|
|
535
531
|
const entries = Object.entries(migration.removeConstraints[constraintType]);
|
|
536
532
|
const optionsWithDrop = { ...options, drop: true };
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
else
|
|
541
|
-
renderer = (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${ artifactName }_${ constraintName }`), tableName, quoteSqlId);
|
|
533
|
+
const renderer = (constraintType === 'referential')
|
|
534
|
+
? constraint => manageConstraint(constraint, csn, optionsWithDrop, '', quoteSqlId)
|
|
535
|
+
: (constraint, constraintName) => renderUniqueConstraintDrop(constraint, renderArtifactName(`${ artifactName }_${ constraintName }`), tableName, quoteSqlId);
|
|
542
536
|
entries.forEach(( [ constraintName, constraint ]) => {
|
|
543
|
-
|
|
537
|
+
resultObj.constraintDeletions.push(renderer(constraint, constraintName));
|
|
544
538
|
});
|
|
545
539
|
}
|
|
546
540
|
});
|
|
@@ -655,9 +649,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
655
649
|
if (primaryKeys !== '')
|
|
656
650
|
result += `,\n${ childEnv.indent }${ primaryKeys }`;
|
|
657
651
|
|
|
658
|
-
|
|
659
|
-
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
|
|
660
|
-
if (!constraintsAsAlter && art.$tableConstraints?.referential) {
|
|
652
|
+
if (!useAlterTableForConstraints() && art.$tableConstraints?.referential) {
|
|
661
653
|
const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
|
|
662
654
|
const referentialConstraints = {};
|
|
663
655
|
forEach(art.$tableConstraints.referential, ( fileName, referentialConstraint ) => {
|
|
@@ -691,7 +683,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
691
683
|
result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
|
|
692
684
|
|
|
693
685
|
|
|
694
|
-
if (
|
|
686
|
+
if (supportsHanaAssociations()) {
|
|
695
687
|
const associations = Object.keys(art.elements)
|
|
696
688
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
697
689
|
.filter(s => s !== '')
|
|
@@ -706,11 +698,8 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
706
698
|
if (options.sqlDialect === 'hana')
|
|
707
699
|
renderIndexesInto(art.technicalConfig?.hana?.indexes, artifactName, resultObj, env);
|
|
708
700
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (back)
|
|
713
|
-
result += back;
|
|
701
|
+
result += renderComment(art);
|
|
702
|
+
result += back;
|
|
714
703
|
|
|
715
704
|
resultObj.hdbtable[artifactName] = result;
|
|
716
705
|
}
|
|
@@ -753,24 +742,17 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
753
742
|
}
|
|
754
743
|
|
|
755
744
|
function addMigration( resultObj, artifactName, drop, sqlArray, description ) {
|
|
756
|
-
|
|
757
|
-
resultObj.migrations[artifactName] = [];
|
|
745
|
+
resultObj.migrations[artifactName] ??= [];
|
|
758
746
|
|
|
759
|
-
if (
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
747
|
+
if (sqlArray) {
|
|
748
|
+
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
749
|
+
resultObj.migrations[artifactName].push(...migrations);
|
|
750
|
+
}
|
|
751
|
+
else if (description) {
|
|
752
|
+
resultObj.migrations[artifactName].push({ description });
|
|
763
753
|
}
|
|
764
|
-
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
765
|
-
resultObj.migrations[artifactName].push(...migrations);
|
|
766
754
|
}
|
|
767
755
|
|
|
768
|
-
function addConstraintDeletion( resultObj, artifactName, deletionSql ) {
|
|
769
|
-
resultObj.constraintDeletions.push(deletionSql);
|
|
770
|
-
}
|
|
771
|
-
function addDeletion( resultObj, artifactName, deletionSql ) {
|
|
772
|
-
resultObj.deletions[artifactName] = deletionSql;
|
|
773
|
-
}
|
|
774
756
|
|
|
775
757
|
/**
|
|
776
758
|
* Retrieve the 'fzindex' (fuzzy index) property (if any) for element 'elemName' from hanaTc (if defined)
|
|
@@ -834,8 +816,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
834
816
|
const { back } = getSqlSnippets(options, elm);
|
|
835
817
|
result += back; // Needs to be rendered before the COMMENT
|
|
836
818
|
|
|
837
|
-
|
|
838
|
-
result += ` COMMENT ${ renderStringForSql(getHanaComment(elm), options.sqlDialect) }`;
|
|
819
|
+
result += renderComment(elm);
|
|
839
820
|
|
|
840
821
|
return result;
|
|
841
822
|
}
|
|
@@ -892,11 +873,9 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
892
873
|
*/
|
|
893
874
|
function renderTechnicalConfiguration( tc, env ) {
|
|
894
875
|
let result = '';
|
|
895
|
-
|
|
896
876
|
if (!tc)
|
|
897
877
|
return result;
|
|
898
878
|
|
|
899
|
-
|
|
900
879
|
// FIXME: How to deal with non-HANA technical configurations?
|
|
901
880
|
// This also affects renderIndexes
|
|
902
881
|
tc = tc.hana;
|
|
@@ -1213,17 +1192,15 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1213
1192
|
*
|
|
1214
1193
|
* @param {string} artifactName Name of the view
|
|
1215
1194
|
* @param {CSN.Artifact} art CSN view
|
|
1195
|
+
* @param {object} resultObj
|
|
1216
1196
|
* @param {SqlRenderEnvironment} env Render environment
|
|
1217
1197
|
* @returns {string} Rendered view
|
|
1218
1198
|
*/
|
|
1219
|
-
function
|
|
1199
|
+
function renderViewInto( artifactName, art, resultObj, env ) {
|
|
1220
1200
|
const viewName = renderArtifactName(artifactName);
|
|
1221
1201
|
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art?.$location, artifactName);
|
|
1222
|
-
let result =
|
|
1223
|
-
|
|
1224
|
-
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
1225
|
-
result += ` COMMENT ${ renderStringForSql(getHanaComment(art), options.sqlDialect) }`;
|
|
1226
|
-
|
|
1202
|
+
let result = `${ isProjectionView(csn, art, options) ? 'PROJECTION ' : '' }VIEW ${ viewName }`;
|
|
1203
|
+
result += renderComment(art);
|
|
1227
1204
|
result += renderParameterDefinitions(art.params, env);
|
|
1228
1205
|
result += ` AS ${ renderQuery(getNormalizedQuery(art).query,
|
|
1229
1206
|
env.withSubPath([ art.projection ? 'projection' : 'query' ]),
|
|
@@ -1235,17 +1212,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1235
1212
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
1236
1213
|
.filter(s => s !== '')
|
|
1237
1214
|
.join(',\n');
|
|
1238
|
-
if (associations !== '' &&
|
|
1215
|
+
if (associations !== '' && supportsHanaAssociations() && !isProjectionView(csn, art, options)) {
|
|
1239
1216
|
result += `${ env.indent }\nWITH ASSOCIATIONS (\n${ associations }\n`;
|
|
1240
1217
|
result += `${ env.indent })`;
|
|
1241
1218
|
}
|
|
1242
1219
|
|
|
1243
1220
|
// views can only have a @sql.append
|
|
1244
1221
|
const { back } = getSqlSnippets(options, art);
|
|
1245
|
-
|
|
1246
|
-
result += back;
|
|
1222
|
+
result += back;
|
|
1247
1223
|
|
|
1248
|
-
|
|
1224
|
+
if (result && isProjectionView(csn, art, options))
|
|
1225
|
+
resultObj.hdbprojectionview[artifactName] = result;
|
|
1226
|
+
else if (result)
|
|
1227
|
+
resultObj.hdbview[artifactName] = result;
|
|
1249
1228
|
}
|
|
1250
1229
|
|
|
1251
1230
|
/**
|
|
@@ -1517,16 +1496,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1517
1496
|
if (elm.scale !== undefined)
|
|
1518
1497
|
params.push(elm.scale);
|
|
1519
1498
|
|
|
1520
|
-
if (elm.srid !== undefined)
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
params.push(2000);
|
|
1524
|
-
else
|
|
1525
|
-
params.push(elm.srid);
|
|
1526
|
-
}
|
|
1499
|
+
if (elm.srid !== undefined)
|
|
1500
|
+
params.push(renderSRID(elm));
|
|
1501
|
+
|
|
1527
1502
|
return params.length === 0 ? '' : `(${ params.join(', ') })`;
|
|
1528
1503
|
}
|
|
1529
1504
|
|
|
1505
|
+
function renderSRID( element ) {
|
|
1506
|
+
// SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
|
|
1507
|
+
if (options.sqlDialect !== 'hana')
|
|
1508
|
+
return 2000;
|
|
1509
|
+
return element.srid;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1530
1512
|
function renderExpressionLiteral( x ) {
|
|
1531
1513
|
// Literal value, possibly with explicit 'literal' property
|
|
1532
1514
|
switch (x.literal || typeof x.val) {
|
|
@@ -1535,22 +1517,19 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1535
1517
|
case 'null':
|
|
1536
1518
|
// 17.42, NULL, TRUE
|
|
1537
1519
|
return String(x.val).toUpperCase();
|
|
1520
|
+
|
|
1538
1521
|
case 'x':
|
|
1539
1522
|
// x'f000'
|
|
1540
1523
|
return `${ x.literal }'${ x.val }'`;
|
|
1541
1524
|
case 'date':
|
|
1542
1525
|
case 'time':
|
|
1543
1526
|
case 'timestamp':
|
|
1544
|
-
|
|
1545
|
-
// simple string literal '2017-11-02'
|
|
1546
|
-
return `'${ x.val }'`;
|
|
1547
|
-
}
|
|
1548
|
-
// date'2017-11-02'
|
|
1549
|
-
return `${ x.literal }'${ x.val }'`;
|
|
1527
|
+
return renderTimeLiteral(x);
|
|
1550
1528
|
|
|
1551
1529
|
case 'string':
|
|
1552
1530
|
// 'foo', with proper escaping
|
|
1553
|
-
return
|
|
1531
|
+
return renderString(x.val);
|
|
1532
|
+
|
|
1554
1533
|
case 'object':
|
|
1555
1534
|
if (x.val === null)
|
|
1556
1535
|
return 'NULL';
|
|
@@ -1575,7 +1554,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1575
1554
|
function renderMagicVariable( ref, env ) {
|
|
1576
1555
|
const magicReplacement = getVariableReplacement(ref, options);
|
|
1577
1556
|
if (magicReplacement !== null)
|
|
1578
|
-
return
|
|
1557
|
+
return renderString(magicReplacement);
|
|
1579
1558
|
|
|
1580
1559
|
const name = pathName(ref);
|
|
1581
1560
|
const result = variableForDialect(options, name, env.isInDefault ? 'default' : null);
|
|
@@ -1592,7 +1571,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1592
1571
|
'Variable $(NAME) is not supported. Use option $(OPTION) to specify a value for $(NAME)');
|
|
1593
1572
|
}
|
|
1594
1573
|
|
|
1595
|
-
return
|
|
1574
|
+
return renderString(name);
|
|
1596
1575
|
}
|
|
1597
1576
|
|
|
1598
1577
|
/**
|
|
@@ -1690,6 +1669,46 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
1690
1669
|
function activateAlterMode( env, changeType ) {
|
|
1691
1670
|
return env.cloneWith({ alterMode: true, changeType });
|
|
1692
1671
|
}
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
function renderComment( art ) {
|
|
1675
|
+
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
1676
|
+
return ` COMMENT ${ renderString(getHanaComment(art)) }`;
|
|
1677
|
+
return '';
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function renderString( str ) {
|
|
1681
|
+
return renderStringForSql(str, options.sqlDialect);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function renderTimeLiteral( x ) {
|
|
1685
|
+
if (options.sqlDialect === 'sqlite') {
|
|
1686
|
+
// simple string literal '2017-11-02'
|
|
1687
|
+
return `'${ x.val }'`;
|
|
1688
|
+
}
|
|
1689
|
+
return `${ x.literal }'${ x.val }'`; // date'2017-11-02'
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function supportsHanaAssociations() {
|
|
1693
|
+
return options.withHanaAssociations && options.sqlDialect === 'hana';
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
function smartFuncId( name ) {
|
|
1697
|
+
return sqlIdentifiers.smartFuncId(name, options.sqlDialect);
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
function supportsSqlExtensions() {
|
|
1701
|
+
return options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions');
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function sortCsnIfTestMode( obj ) {
|
|
1705
|
+
return options?.testMode ? sortCsn(obj, options) : obj;
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
function useAlterTableForConstraints() {
|
|
1709
|
+
// for `to.sql` w/ dialect `hana` the constraints will be part of the alter statement
|
|
1710
|
+
return !options.constraintsInCreateTable && options.src === 'sql' && (options.sqlDialect === 'hana' || options.sqlDialect === 'postgres');
|
|
1711
|
+
}
|
|
1693
1712
|
}
|
|
1694
1713
|
|
|
1695
1714
|
/**
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
7
|
const { ModelError } = require('../../base/error');
|
|
8
|
+
const { isBetaEnabled, setProp } = require('../../base/model');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
@@ -142,8 +143,74 @@ function getIdentifierUtils( csn, options ) {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
const allowedHdbProjectionViewProperties = {
|
|
147
|
+
from: true,
|
|
148
|
+
mixin: true,
|
|
149
|
+
columns: true,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Determines if the given artifact is a projection view. Caches the information via
|
|
154
|
+
* non-enumerable property `$isProjectionView` on the artifact for non-trivial cases.
|
|
155
|
+
*
|
|
156
|
+
* Requires a for.hana processed model including A2J.
|
|
157
|
+
*
|
|
158
|
+
* @param {CSN.Model} csn - The Core Schema Notation (CSN) model.
|
|
159
|
+
* @param {CSN.Artifact} artifact - The artifact to check for being a projection view.
|
|
160
|
+
* @param {Model.Options} options - Compilation options, including feature flags and SQL dialect.
|
|
161
|
+
* @returns {boolean} `true` if the artifact is a projection view, otherwise `false`.
|
|
162
|
+
*/
|
|
163
|
+
function isProjectionView( csn, artifact, options ) {
|
|
164
|
+
if (!isBetaEnabled(options, 'projectionViews') || !artifact.projection || options.sqlDialect !== 'hana' || artifact.params || !artifact.$dataProductService)
|
|
165
|
+
return false;
|
|
166
|
+
|
|
167
|
+
if (artifact.$isProjectionView !== undefined)
|
|
168
|
+
return artifact.$isProjectionView;
|
|
169
|
+
|
|
170
|
+
setProp(artifact, '$isProjectionView', _artifactIsProjectionView(csn, artifact));
|
|
171
|
+
|
|
172
|
+
return artifact.$isProjectionView;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function _artifactIsProjectionView( csn, artifact ) {
|
|
176
|
+
for (const prop of Object.keys(artifact.projection)) {
|
|
177
|
+
if (!prop.startsWith('@') && !allowedHdbProjectionViewProperties[prop])
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const source = csn.definitions[artifact.projection.from.ref[0]];
|
|
182
|
+
if (!source)
|
|
183
|
+
return false;
|
|
184
|
+
|
|
185
|
+
// Source must be a table or a projection view. Only support table for now
|
|
186
|
+
if (source.kind !== 'entity' || source.query || source.projection)
|
|
187
|
+
return false;
|
|
188
|
+
|
|
189
|
+
const referencedElements = Object.create(null);
|
|
190
|
+
|
|
191
|
+
for (const column of artifact.projection.columns) {
|
|
192
|
+
if (!column.ref)
|
|
193
|
+
return false;
|
|
194
|
+
|
|
195
|
+
if (referencedElements[column.ref.at(-1)])
|
|
196
|
+
return false; // we have an element projected more than once, seems to not be supported
|
|
197
|
+
|
|
198
|
+
referencedElements[column.ref.at(-1)] = true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Full primary key needs to be projected according to HANA SQL spec
|
|
202
|
+
for (const elementName in source.elements) {
|
|
203
|
+
const element = source.elements[elementName];
|
|
204
|
+
if (element.key && !referencedElements[elementName])
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
145
211
|
|
|
146
212
|
module.exports = {
|
|
147
213
|
renderReferentialConstraint,
|
|
148
214
|
getIdentifierUtils,
|
|
215
|
+
isProjectionView,
|
|
149
216
|
};
|
|
@@ -85,7 +85,7 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
85
85
|
else if (!independent && independent != null) {
|
|
86
86
|
error( 'tenant-invalid-anno-value', msgLocations( csnPath ),
|
|
87
87
|
{ anno: annoTenantIndep, value: independent },
|
|
88
|
-
// eslint-disable-next-line @stylistic/
|
|
88
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
89
89
|
'Can\'t add $(ANNO) with value $(VALUE) to a non-entity, which is always tenant-independent' );
|
|
90
90
|
}
|
|
91
91
|
else if (art.includes) {
|
|
@@ -129,9 +129,9 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
129
129
|
.filter( name => isTenantDepEntity( csn.definitions[name] ) );
|
|
130
130
|
if (names.length) {
|
|
131
131
|
error( 'tenant-invalid-include', msgLocations( csnPath ), { names }, {
|
|
132
|
-
// eslint-disable-next-line @stylistic/
|
|
132
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
133
133
|
std: 'Can\'t include the tenant-dependent entities $(NAMES) into a tenant-independent definition',
|
|
134
|
-
// eslint-disable-next-line @stylistic/
|
|
134
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
135
135
|
one: 'Can\'t include the tenant-dependent entity $(NAMES) into a tenant-independent definition',
|
|
136
136
|
} );
|
|
137
137
|
}
|
|
@@ -221,7 +221,7 @@ function addTenantFields( csn, options, messageFunctions ) {
|
|
|
221
221
|
if (art[annoTenantIndep]) {
|
|
222
222
|
error( 'tenant-expecting-tenant-source', msgLocations( csnPath ), { art: query },
|
|
223
223
|
// TODO: better the final entity name of assoc navigation in FROM
|
|
224
|
-
// eslint-disable-next-line @stylistic/
|
|
224
|
+
// eslint-disable-next-line @stylistic/max-len
|
|
225
225
|
'Expecting the query source $(ART) to be tenant-dependent for a tenant-dependent query entity' );
|
|
226
226
|
}
|
|
227
227
|
return true;
|
|
@@ -26,6 +26,7 @@ const requiredAnnos = {
|
|
|
26
26
|
'@Core.Computed': true,
|
|
27
27
|
[sqlServiceAnnotation]: true,
|
|
28
28
|
'@cds.external': true, // for external ABAP SQL services and data products for now
|
|
29
|
+
'@DataIntegration.dataproduct.type': true, // for data product production
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -16,12 +16,15 @@ const sqlServiceAnnotation = '@protocol';
|
|
|
16
16
|
function processSqlServices(csn, options) {
|
|
17
17
|
setProp(csn, '$sqlServiceEntities', Object.create(null));
|
|
18
18
|
setProp(csn, '$dummyServiceEntities', Object.create(null));
|
|
19
|
+
setProp(csn, '$dataProductEntities', Object.create(null));
|
|
19
20
|
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
20
|
-
const { sqlServiceName, dummyServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
21
|
+
const { sqlServiceName, dummyServiceName, dataProductServiceName } = isEntityInSqlService(artifact, artifactName, csn, options);
|
|
21
22
|
if (sqlServiceName?.length > 0)
|
|
22
23
|
setProp(artifact, '$sqlService', sqlServiceName);
|
|
23
24
|
if (dummyServiceName?.length > 0)
|
|
24
25
|
setProp(artifact, '$dummyService', dummyServiceName);
|
|
26
|
+
if (dataProductServiceName?.length > 0)
|
|
27
|
+
setProp(artifact, '$dataProductService', dataProductServiceName);
|
|
25
28
|
};
|
|
26
29
|
}
|
|
27
30
|
|
|
@@ -55,7 +58,7 @@ function isDummyService(artifact, options) {
|
|
|
55
58
|
* @returns {object} An object containing the names of the SQL service and external ABAP SQL service, if found.
|
|
56
59
|
*/
|
|
57
60
|
function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
58
|
-
const result = { sqlServiceName: undefined, dummyServiceName: undefined };
|
|
61
|
+
const result = { sqlServiceName: undefined, dummyServiceName: undefined, dataProductServiceName: undefined };
|
|
59
62
|
if (artifact.kind !== 'entity' || !artifactName.includes('.') || hasPersistenceSkipAnnotation(artifact))
|
|
60
63
|
return result;
|
|
61
64
|
|
|
@@ -72,6 +75,9 @@ function isEntityInSqlService(artifact, artifactName, csn, options) {
|
|
|
72
75
|
if (isDummyService(definition, options))
|
|
73
76
|
result.dummyServiceName = possibleServiceName;
|
|
74
77
|
|
|
78
|
+
if (isDataProductService(definition, options))
|
|
79
|
+
result.dataProductServiceName = possibleServiceName;
|
|
80
|
+
|
|
75
81
|
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
76
82
|
if (definition.kind === 'service' || definition.kind === 'context')
|
|
77
83
|
return result;
|
|
@@ -108,10 +114,22 @@ function createServiceDummy(artifact, artifactName, csn, { error }) {
|
|
|
108
114
|
csn.definitions[`dummy.${ artifactName }`] = dummy;
|
|
109
115
|
}
|
|
110
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Determines if the given artifact is a data product service.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} artifact - The artifact to evaluate.
|
|
121
|
+
* @param {object} options - The options object containing feature flags.
|
|
122
|
+
* @returns {boolean} - Returns `true` if the artifact is a data product service, otherwise `false`.
|
|
123
|
+
*/
|
|
124
|
+
function isDataProductService(artifact, options) {
|
|
125
|
+
return isBetaEnabled(options, 'projectionViews') && artifact.kind === 'service' && artifact['@DataIntegration.dataproduct.type'] === 'primary';
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
module.exports = {
|
|
112
129
|
processSqlServices,
|
|
113
130
|
isSqlService,
|
|
114
131
|
isDummyService,
|
|
132
|
+
isDataProductService,
|
|
115
133
|
sqlServiceAnnotation,
|
|
116
134
|
createServiceDummy,
|
|
117
135
|
};
|
|
@@ -232,7 +232,7 @@ function generateDrafts( csn, options, pathDelimiter, messageFunctions ) {
|
|
|
232
232
|
draftAdministrativeData.DraftAdministrativeData.notNull = true;
|
|
233
233
|
addElement(draftAdministrativeData, draftsArtifact, artifactName);
|
|
234
234
|
|
|
235
|
-
if (isBetaEnabled(options, 'draftMessages')) {
|
|
235
|
+
if (isBetaEnabled(options, 'draftMessages') || options.draftMessages) {
|
|
236
236
|
const draftMessages = { DraftMessages: { '@Core.Computed': true, virtual: true, items: { type: 'DRAFT.DraftAdministrativeData_DraftMessage' } } };
|
|
237
237
|
addElement(draftMessages, draftsArtifact, artifactName);
|
|
238
238
|
}
|