@sap/cds-compiler 5.0.6 → 5.1.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 +41 -0
- package/bin/cdsc.js +34 -8
- package/bin/cdshi.js +2 -1
- package/lib/api/main.js +10 -1
- package/lib/api/options.js +2 -3
- package/lib/base/message-registry.js +14 -0
- package/lib/base/messages.js +2 -1
- package/lib/base/meta.js +10 -0
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/compiler/assert-consistency.js +1 -0
- package/lib/compiler/define.js +4 -2
- package/lib/compiler/extend.js +43 -7
- package/lib/compiler/index.js +4 -4
- package/lib/compiler/populate.js +58 -11
- package/lib/compiler/propagator.js +9 -4
- package/lib/compiler/resolve.js +115 -79
- package/lib/compiler/shared.js +2 -1
- package/lib/compiler/tweak-assocs.js +29 -5
- package/lib/edm/edm.js +8 -0
- package/lib/edm/edmPreprocessor.js +7 -3
- package/lib/gen/Dictionary.json +37 -0
- package/lib/json/to-csn.js +2 -0
- package/lib/main.js +2 -5
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +2 -1
- package/lib/model/csnUtils.js +0 -12
- package/lib/model/revealInternalProperties.js +0 -1
- package/lib/modelCompare/compare.js +12 -10
- package/lib/optionProcessor.js +2 -0
- package/lib/render/toCdl.js +8 -7
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +44 -8
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/draft/odata.js +6 -1
- package/lib/transform/forRelationalDB.js +9 -0
- package/lib/utils/file.js +77 -4
- package/package.json +1 -1
package/lib/optionProcessor.js
CHANGED
|
@@ -23,6 +23,7 @@ optionProcessor
|
|
|
23
23
|
.option(' --options <file>')
|
|
24
24
|
.option('-w, --warning <level>', { valid: ['0', '1', '2', '3'] })
|
|
25
25
|
.option(' --quiet')
|
|
26
|
+
.option('-i, --stdin')
|
|
26
27
|
.option(' --show-message-id')
|
|
27
28
|
.option(' --no-message-id')
|
|
28
29
|
.option(' --no-message-context')
|
|
@@ -89,6 +90,7 @@ optionProcessor
|
|
|
89
90
|
--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
|
|
90
91
|
--module-lookup-directories <list> Comma separated list of directories to look
|
|
91
92
|
for CDS modules. Default is 'node_modules/'.
|
|
93
|
+
-i, --stdin Read input from stdin.
|
|
92
94
|
-- Indicate the end of options (helpful if source names start with "-")
|
|
93
95
|
|
|
94
96
|
Type options
|
package/lib/render/toCdl.js
CHANGED
|
@@ -13,11 +13,11 @@ const { typeParameters, specialFunctions } = require('../compiler/builtins');
|
|
|
13
13
|
const { isAnnotationExpression } = require('../base/builtins');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
15
|
const {
|
|
16
|
-
generatedByCompilerVersion,
|
|
17
16
|
getNormalizedQuery,
|
|
18
17
|
} = require('../model/csnUtils');
|
|
19
18
|
const { isBuiltinType } = require('../base/builtins');
|
|
20
19
|
const { cloneFullCsn } = require('../model/cloneCsn');
|
|
20
|
+
const { getKeysDict } = require('../model/csnRefs');
|
|
21
21
|
|
|
22
22
|
const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
23
23
|
const specialFunctionKeywords = Object.create(null);
|
|
@@ -62,7 +62,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
62
62
|
const hanaRequiresAbsolutePath = usings.available.includes('hana');
|
|
63
63
|
|
|
64
64
|
const cdlResult = Object.create(null);
|
|
65
|
-
cdlResult.model =
|
|
65
|
+
cdlResult.model = '';
|
|
66
66
|
|
|
67
67
|
const subelementAnnotates = [];
|
|
68
68
|
|
|
@@ -676,7 +676,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
676
676
|
artifact = artifact.items;
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
-
if (!artifact.elements && !artifact.enum)
|
|
679
|
+
if (!artifact.elements && !artifact.enum && !artifact.keys)
|
|
680
680
|
return null;
|
|
681
681
|
|
|
682
682
|
const annotate = { annotate: env.path[1] };
|
|
@@ -712,15 +712,16 @@ function csnToCdl( csn, options, msg ) {
|
|
|
712
712
|
*/
|
|
713
713
|
function collectAnnos( annotateObj, art ) {
|
|
714
714
|
if (!Object.hasOwnProperty.call(art, 'elements') &&
|
|
715
|
-
|
|
715
|
+
!Object.hasOwnProperty.call(art, 'enum') &&
|
|
716
|
+
!Object.hasOwnProperty.call(art, 'keys'))
|
|
716
717
|
return false;
|
|
717
718
|
|
|
718
|
-
const
|
|
719
|
-
// Use "elements" for
|
|
719
|
+
const dict = art.enum || art.keys && getKeysDict(art) || art.elements;
|
|
720
|
+
// Use "elements" for all. This is allowed in extensions.
|
|
720
721
|
const collected = { elements: Object.create(null) };
|
|
721
722
|
let hasAnnotation = false;
|
|
722
723
|
|
|
723
|
-
forEach(
|
|
724
|
+
forEach(dict, (elemName, element) => {
|
|
724
725
|
if (!collected.elements[elemName])
|
|
725
726
|
collected.elements[elemName] = { };
|
|
726
727
|
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
getLastPartOf, getLastPartOfRef,
|
|
5
|
-
hasValidSkipOrExists,
|
|
5
|
+
hasValidSkipOrExists, getNormalizedQuery,
|
|
6
6
|
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
|
|
7
7
|
pathName,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
@@ -167,7 +167,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
|
167
167
|
if (sourceStr !== '') {
|
|
168
168
|
const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
|
|
169
169
|
hdbcds[name] = [
|
|
170
|
-
!options.testMode ? `// ${generatedByCompilerVersion()} \n` : '',
|
|
171
170
|
renderNamespaceDeclaration(name, env),
|
|
172
171
|
renderUsings(name, env),
|
|
173
172
|
sourceStr,
|
package/lib/render/toSql.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
|
-
hasValidSkipOrExists,
|
|
6
|
+
hasValidSkipOrExists, getNormalizedQuery,
|
|
7
7
|
forEachDefinition, getResultingName,
|
|
8
8
|
getVariableReplacement, pathName,
|
|
9
9
|
} = require('../model/csnUtils');
|
|
@@ -186,8 +186,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
186
186
|
deletions: Object.create(null),
|
|
187
187
|
constraintDeletions: [],
|
|
188
188
|
migrations: Object.create(null),
|
|
189
|
+
hdbrole: Object.create(null),
|
|
189
190
|
};
|
|
190
191
|
|
|
192
|
+
const sqlServiceEntities = Object.create(null);
|
|
193
|
+
|
|
191
194
|
// Registries for artifact and element names per CSN section
|
|
192
195
|
const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
|
|
193
196
|
const deletionsDuplicateChecker = new DuplicateChecker();
|
|
@@ -252,6 +255,25 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
252
255
|
});
|
|
253
256
|
}
|
|
254
257
|
|
|
258
|
+
// Can only happen for HDI based deployment
|
|
259
|
+
// .hdbrole documentation: https://help.sap.com/docs/SAP_HANA_PLATFORM/3823b0f33420468ba5f1cf7f59bd6bd9/625d7733c30b4666b4a522d7fa68a550.html
|
|
260
|
+
Object.keys(sqlServiceEntities).forEach((sqlServiceName) => {
|
|
261
|
+
const accessRole = {
|
|
262
|
+
role: {
|
|
263
|
+
name: renderArtifactNameWithoutQuotes(`${sqlServiceName }.access`),
|
|
264
|
+
object_privileges: Object.entries(sqlServiceEntities[sqlServiceName]).map(([ name, entity ]) => ({
|
|
265
|
+
name: renderArtifactNameWithoutQuotes(name),
|
|
266
|
+
type: entity.query || entity.projection ? 'VIEW' : 'TABLE',
|
|
267
|
+
privileges: [ 'SELECT' ],
|
|
268
|
+
privileges_with_grant_option: [],
|
|
269
|
+
})),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (accessRole.role.object_privileges.length > 0)
|
|
274
|
+
mainResultObj.hdbrole[`${sqlServiceName }_access`] = JSON.stringify(accessRole, null, 2);
|
|
275
|
+
});
|
|
276
|
+
|
|
255
277
|
// trigger artifact and element name checks
|
|
256
278
|
definitionsDuplicateChecker.check(error, options);
|
|
257
279
|
extensionsDuplicateChecker.check(error);
|
|
@@ -264,7 +286,6 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
264
286
|
// (relying on the order of dictionaries above)
|
|
265
287
|
// FIXME: Should consider inter-view dependencies, too
|
|
266
288
|
const sql = Object.create(null);
|
|
267
|
-
const sqlVersionLine = `-- ${generatedByCompilerVersion()}\n`;
|
|
268
289
|
|
|
269
290
|
// Handle hdbKinds separately from alterTable case
|
|
270
291
|
const {
|
|
@@ -278,10 +299,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
278
299
|
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
279
300
|
if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
280
301
|
sourceString = sourceString.slice('COLUMN '.length);
|
|
281
|
-
sql[name] =
|
|
282
|
-
}
|
|
283
|
-
else if (!options.testMode && options.generatedByComment) {
|
|
284
|
-
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
302
|
+
sql[name] = `CREATE ${sourceString};`;
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
305
|
if (options.src === 'sql')
|
|
@@ -295,7 +313,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
295
313
|
|
|
296
314
|
forEachKey(alterStmts, (constraintName) => {
|
|
297
315
|
if (!csn.unchangedConstraints?.has(constraintName))
|
|
298
|
-
constraints[constraintName] = `${
|
|
316
|
+
constraints[constraintName] = `${alterStmts[constraintName]}`;
|
|
299
317
|
});
|
|
300
318
|
mainResultObj.constraints = constraints;
|
|
301
319
|
}
|
|
@@ -304,7 +322,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
304
322
|
mainResultObj.sql = sql;
|
|
305
323
|
|
|
306
324
|
for (const name in deletions)
|
|
307
|
-
deletions[name] = `${
|
|
325
|
+
deletions[name] = `${deletions[name]}`;
|
|
308
326
|
|
|
309
327
|
timetrace.stop('SQL rendering');
|
|
310
328
|
return mainResultObj;
|
|
@@ -325,6 +343,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
325
343
|
|
|
326
344
|
switch (art.kind) {
|
|
327
345
|
case 'entity':
|
|
346
|
+
if (art.$sqlService) { // collect entities that are in a sql service so we can render the .hdbrole later
|
|
347
|
+
sqlServiceEntities[art.$sqlService] ??= Object.create(null);
|
|
348
|
+
sqlServiceEntities[art.$sqlService][artifactName] = art;
|
|
349
|
+
}
|
|
328
350
|
if (art.query || art.projection) {
|
|
329
351
|
const result = renderView(artifactName, art, env);
|
|
330
352
|
if (result)
|
|
@@ -350,6 +372,20 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
350
372
|
}
|
|
351
373
|
}
|
|
352
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Render the given artifactName according to the sqlMapping, but
|
|
377
|
+
* - uppercased for plain
|
|
378
|
+
* - without enclosing " for quoted/hdbcds
|
|
379
|
+
*
|
|
380
|
+
* @param {string} artifactName
|
|
381
|
+
* @returns {string}
|
|
382
|
+
*/
|
|
383
|
+
function renderArtifactNameWithoutQuotes( artifactName ) {
|
|
384
|
+
if (options.sqlMapping === 'plain')
|
|
385
|
+
return renderArtifactName(artifactName).toUpperCase();
|
|
386
|
+
return renderArtifactName(artifactName).slice(1, -1); // trim leading/trailing "
|
|
387
|
+
}
|
|
388
|
+
|
|
353
389
|
/**
|
|
354
390
|
* Render an artifact extension into the appropriate dictionary of 'resultObj'.
|
|
355
391
|
* Only SAP HANA SQL is currently supported.
|
|
@@ -193,7 +193,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
193
193
|
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
194
194
|
if (assoc.keys) {
|
|
195
195
|
if (assoc.keys.length)
|
|
196
|
-
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
|
|
196
|
+
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
197
197
|
|
|
198
198
|
if (options.transformation !== 'effective')
|
|
199
199
|
elem.$ignore = true;
|
|
@@ -202,7 +202,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
202
202
|
|
|
203
203
|
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
|
|
204
204
|
else if (assoc.on) {
|
|
205
|
-
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
|
|
205
|
+
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${JSON.stringify(elem.on)}`);
|
|
@@ -220,9 +220,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
220
220
|
* @param {CSN.Element} assoc
|
|
221
221
|
* @param {string} originalAssocName
|
|
222
222
|
* @param {string} elemName
|
|
223
|
+
* @param {CSN.Artifact} art
|
|
224
|
+
* @param {CSN.Path} path
|
|
223
225
|
* @returns {Array} New on-condition
|
|
224
226
|
*/
|
|
225
|
-
function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
|
|
227
|
+
function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path) {
|
|
226
228
|
const conditions = [];
|
|
227
229
|
// if the element was structured then it was flattened => change of the delimiter from '.' to '_'
|
|
228
230
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
@@ -245,7 +247,9 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
245
247
|
if (prepend$self)
|
|
246
248
|
a[1].ref = [ '$self', ...a[1].ref ];
|
|
247
249
|
|
|
248
|
-
|
|
250
|
+
// Not without a2j so we can rely on a certain model state
|
|
251
|
+
if (doA2J && prepend$self && art.elements[k.ref[1]] || !prepend$self && !art.elements[k.ref[0]])
|
|
252
|
+
messageFunctions.message('ref-missing-self-counterpart', path, { prop: k.ref[0], name: assocName });
|
|
249
253
|
conditions.push([ a[0], '=', a[1] ]);
|
|
250
254
|
});
|
|
251
255
|
|
|
@@ -268,9 +272,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
268
272
|
* @param {CSN.Element} assoc
|
|
269
273
|
* @param {string} originalAssocName
|
|
270
274
|
* @param {string} elemName
|
|
275
|
+
* @param {CSN.Artifact} art
|
|
276
|
+
* @param {CSN.Path} path
|
|
271
277
|
* @returns {Array} New on-condition
|
|
272
278
|
*/
|
|
273
|
-
function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
|
|
279
|
+
function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path ) {
|
|
274
280
|
// if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
|
|
275
281
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
276
282
|
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
@@ -279,11 +285,14 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
279
285
|
const newOnCond = cloneCsnNonDict(assoc.on, options);
|
|
280
286
|
applyTransformationsOnNonDictionary({ on: newOnCond }, 'on', {
|
|
281
287
|
ref: (parent, prop, ref) => {
|
|
288
|
+
let sourceSide = false;
|
|
282
289
|
// we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
283
290
|
if (ref[0] === assocName) {
|
|
284
291
|
ref.shift();
|
|
285
292
|
if (prepend$self)
|
|
286
293
|
ref.unshift('$self');
|
|
294
|
+
|
|
295
|
+
sourceSide = true;
|
|
287
296
|
}
|
|
288
297
|
else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
289
298
|
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
@@ -291,6 +300,8 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
291
300
|
ref.shift();
|
|
292
301
|
if (prepend$self)
|
|
293
302
|
ref.unshift('$self');
|
|
303
|
+
|
|
304
|
+
sourceSide = true;
|
|
294
305
|
}
|
|
295
306
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
296
307
|
ref.unshift(elemName);
|
|
@@ -299,6 +310,10 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
299
310
|
if (ref[1] === '$self')
|
|
300
311
|
ref.splice(1, 1);
|
|
301
312
|
}
|
|
313
|
+
|
|
314
|
+
// Not without a2j so we can rely on a certain model state
|
|
315
|
+
if (doA2J && sourceSide && (prepend$self && !art.elements[ref[1]] || !prepend$self && !art.elements[ref[0]]))
|
|
316
|
+
messageFunctions.message('ref-missing-self-counterpart', path, { '#': 'unmanaged', prop: ref[0], name: assocName });
|
|
302
317
|
},
|
|
303
318
|
});
|
|
304
319
|
return newOnCond;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { sqlServiceAnnotation } = require('./processSqlServices');
|
|
4
|
+
|
|
3
5
|
const requiredAnnos = {
|
|
4
6
|
'@cds.persistence.skip': true,
|
|
5
7
|
'@cds.persistence.exists': true,
|
|
@@ -22,6 +24,7 @@ const requiredAnnos = {
|
|
|
22
24
|
'@cds.autoexposed': true,
|
|
23
25
|
'@cds.redirection.target': true,
|
|
24
26
|
'@Core.Computed': true,
|
|
27
|
+
[sqlServiceAnnotation]: true,
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setProp } = require('../../base/model');
|
|
4
|
+
|
|
5
|
+
const sqlServiceAnnotation = '@protocol';
|
|
6
|
+
// Problem: How can we clone a Symbol when sorting?
|
|
7
|
+
// const sqlServiceEntities = Symbol.for('SQL Service enabled entities');
|
|
8
|
+
/**
|
|
9
|
+
* Find all entities in SQL services and mark them with an annotation and
|
|
10
|
+
* remember them in a symbol property for easier processing in toSql-rendering.
|
|
11
|
+
*
|
|
12
|
+
* @param {CSN.Model} csn
|
|
13
|
+
* @returns {Function}
|
|
14
|
+
*/
|
|
15
|
+
function processSqlServices(csn) {
|
|
16
|
+
setProp(csn, '$sqlServiceEntities', Object.create(null));
|
|
17
|
+
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
18
|
+
const sqlServiceName = isEntityInSqlService(artifact, artifactName, csn);
|
|
19
|
+
if (sqlServiceName?.length > 0)
|
|
20
|
+
setProp(artifact, '$sqlService', sqlServiceName);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {CSN.Artifact} artifact
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isSqlService(artifact) {
|
|
30
|
+
return artifact.kind === 'service' && artifact[sqlServiceAnnotation] === 'sql';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {CSN.Artifact} artifact
|
|
35
|
+
* @param {string} artifactName
|
|
36
|
+
* @param {CSN.Model} csn
|
|
37
|
+
* @returns {string|null}
|
|
38
|
+
*/
|
|
39
|
+
function isEntityInSqlService(artifact, artifactName, csn) {
|
|
40
|
+
if (artifact.kind !== 'entity' || !artifactName.includes('.'))
|
|
41
|
+
return null;
|
|
42
|
+
|
|
43
|
+
const nameParts = artifactName.split('.');
|
|
44
|
+
for (let i = nameParts.length; i >= 0; i--) {
|
|
45
|
+
const possibleServiceName = nameParts.slice(0, i).join('.');
|
|
46
|
+
if (!csn.definitions[possibleServiceName])
|
|
47
|
+
continue;
|
|
48
|
+
|
|
49
|
+
const definition = csn.definitions[possibleServiceName];
|
|
50
|
+
if (isSqlService(definition))
|
|
51
|
+
return possibleServiceName;
|
|
52
|
+
|
|
53
|
+
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
54
|
+
if (definition.kind === 'service' || definition.kind === 'context')
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
processSqlServices, isSqlService, sqlServiceAnnotation,
|
|
63
|
+
};
|
|
@@ -230,14 +230,19 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
230
230
|
|
|
231
231
|
function $draft2$self(member) {
|
|
232
232
|
Object.keys(member).forEach(pn => {
|
|
233
|
-
if(pn[0] === '@')
|
|
233
|
+
if(pn[0] === '@') {
|
|
234
|
+
let refChanged = false;
|
|
234
235
|
transformExpression(member, pn,{
|
|
235
236
|
ref: (_parent, _prop, xpr, _path) => {
|
|
236
237
|
if(xpr[0] === '$draft') {
|
|
237
238
|
xpr[0] = '$self';
|
|
239
|
+
refChanged = true;
|
|
238
240
|
}
|
|
239
241
|
}
|
|
240
242
|
});
|
|
243
|
+
if (refChanged)
|
|
244
|
+
member[pn]['='] = true;
|
|
245
|
+
}
|
|
241
246
|
});
|
|
242
247
|
}
|
|
243
248
|
|
|
@@ -34,6 +34,7 @@ const backlinks = require('./db/backlinks');
|
|
|
34
34
|
const { getDefaultTypeLengths } = require('../render/utils/common');
|
|
35
35
|
const { featureFlags } = require('./db/featureFlags');
|
|
36
36
|
const { cloneCsnNonDict, cloneFullCsn } = require('../model/cloneCsn');
|
|
37
|
+
const { processSqlServices } = require('./db/processSqlServices');
|
|
37
38
|
|
|
38
39
|
// By default: Do not process non-entities/views
|
|
39
40
|
function forEachDefinition(csn, cb) {
|
|
@@ -368,6 +369,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
368
369
|
removeKeyPropInType,
|
|
369
370
|
]);
|
|
370
371
|
|
|
372
|
+
// TODO: Might have to do this earlier if we want special rendering for projections?
|
|
373
|
+
const findAndMarkSqlServiceArtifacts = options.sqlDialect === 'hana' && options.src === 'hdi' && csn.meta?.[featureFlags]?.$sqlService ? processSqlServices(csn): () => {}
|
|
371
374
|
|
|
372
375
|
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
373
376
|
applyTransformations(csn, {
|
|
@@ -387,6 +390,8 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
387
390
|
// Attach @cds.persistence.name to artifacts
|
|
388
391
|
if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
389
392
|
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
393
|
+
|
|
394
|
+
findAndMarkSqlServiceArtifacts(artifact, artifactName);
|
|
390
395
|
}], { allowArtifact: artifact => artifact.kind === 'entity'});
|
|
391
396
|
|
|
392
397
|
throwWithAnyError();
|
|
@@ -700,6 +705,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
700
705
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
701
706
|
|
|
702
707
|
});
|
|
708
|
+
|
|
709
|
+
// To ensure we preserve feature flags
|
|
710
|
+
newCsn.meta = csn.meta;
|
|
711
|
+
|
|
703
712
|
csn = newCsn;
|
|
704
713
|
}
|
|
705
714
|
|
package/lib/utils/file.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const util = require('util');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* Split the given source string into its lines. Respects Unix,
|
|
@@ -16,6 +18,74 @@ function splitLines( src ) {
|
|
|
16
18
|
return src.split(/\r\n?|\n/);
|
|
17
19
|
}
|
|
18
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Returns the file's normalized extension, e.g. for `file.CDS` -> `cds`
|
|
23
|
+
* Returns null if the given filename is not a string.
|
|
24
|
+
*
|
|
25
|
+
* @param filename
|
|
26
|
+
* @returns {null|string}
|
|
27
|
+
*/
|
|
28
|
+
function fileExtension( filename ) {
|
|
29
|
+
if (typeof filename === 'string')
|
|
30
|
+
return path.extname( filename ).slice(1).toLowerCase();
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create a temporary file path using the system's temporary folder and a filename
|
|
36
|
+
* consisting of the given name/extension and a random string.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} name
|
|
39
|
+
* @param {string} extension
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function tmpFilePath( name, extension ) {
|
|
43
|
+
const crypto = require('crypto');
|
|
44
|
+
const id = crypto.randomBytes(32).toString('hex');
|
|
45
|
+
const filename = `${ name }-${ id }.${ extension }`;
|
|
46
|
+
return path.join(os.tmpdir(), filename);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read input from the given stream.
|
|
51
|
+
* See https://nodejs.org/api/stream.html#readablereadsize
|
|
52
|
+
*
|
|
53
|
+
* @returns {Promise<string>}
|
|
54
|
+
*/
|
|
55
|
+
function readStream( stream ) {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
const chunks = [];
|
|
58
|
+
|
|
59
|
+
const listeners = {
|
|
60
|
+
__proto__: null,
|
|
61
|
+
data: onData,
|
|
62
|
+
error: onError,
|
|
63
|
+
end: onEnd,
|
|
64
|
+
};
|
|
65
|
+
for (const name in listeners)
|
|
66
|
+
stream.on(name, listeners[name]);
|
|
67
|
+
|
|
68
|
+
function onData( chunk ) {
|
|
69
|
+
chunks.push(chunk);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function onEnd() {
|
|
73
|
+
removeListeners();
|
|
74
|
+
resolve(chunks.join(''));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function onError( error ) {
|
|
78
|
+
removeListeners();
|
|
79
|
+
reject(error);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function removeListeners() {
|
|
83
|
+
for (const name in listeners)
|
|
84
|
+
stream.removeListener(name, listeners[name]);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
19
89
|
/**
|
|
20
90
|
* Returns filesystem utils readFile(), isFile(), realpath() for _CDS_ usage.
|
|
21
91
|
* This includes a trace as well as usage of a file cache.
|
|
@@ -61,18 +131,18 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
61
131
|
};
|
|
62
132
|
|
|
63
133
|
|
|
64
|
-
function realpathSync(
|
|
134
|
+
function realpathSync( filepath, cb ) {
|
|
65
135
|
try {
|
|
66
|
-
cb(null, fs.realpathSync(
|
|
136
|
+
cb(null, fs.realpathSync(filepath));
|
|
67
137
|
}
|
|
68
138
|
catch (err) {
|
|
69
139
|
cb(err, null);
|
|
70
140
|
}
|
|
71
141
|
}
|
|
72
142
|
|
|
73
|
-
function realpathSyncNative(
|
|
143
|
+
function realpathSyncNative( filepath, cb ) {
|
|
74
144
|
try {
|
|
75
|
-
cb(null, fs.realpathSync.native(
|
|
145
|
+
cb(null, fs.realpathSync.native(filepath));
|
|
76
146
|
}
|
|
77
147
|
catch (err) {
|
|
78
148
|
cb(err, null);
|
|
@@ -194,5 +264,8 @@ function cdsFs( fileCache, enableTrace ) {
|
|
|
194
264
|
|
|
195
265
|
module.exports = {
|
|
196
266
|
splitLines,
|
|
267
|
+
readStream,
|
|
268
|
+
fileExtension,
|
|
269
|
+
tmpFilePath,
|
|
197
270
|
cdsFs,
|
|
198
271
|
};
|