@sap/cds-compiler 4.7.6 → 4.8.0
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 +37 -2
- package/bin/cdsc.js +15 -1
- package/bin/cdshi.js +13 -3
- package/doc/CHANGELOG_BETA.md +5 -1
- package/lib/api/main.js +61 -23
- package/lib/api/options.js +40 -0
- package/lib/base/builtins.js +89 -0
- package/lib/base/keywords.js +5 -1
- package/lib/base/location.js +91 -14
- package/lib/base/message-registry.js +50 -33
- package/lib/base/messages.js +71 -16
- package/lib/base/model.js +0 -2
- package/lib/checks/actionsFunctions.js +1 -1
- package/lib/checks/elements.js +2 -1
- package/lib/checks/enricher.js +2 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/utils.js +1 -1
- package/lib/checks/validator.js +6 -22
- package/lib/compiler/assert-consistency.js +3 -5
- package/lib/compiler/builtins.js +0 -74
- package/lib/compiler/checks.js +61 -11
- package/lib/compiler/define.js +3 -3
- package/lib/compiler/extend.js +2 -2
- package/lib/compiler/index.js +9 -9
- package/lib/compiler/populate.js +13 -5
- package/lib/compiler/propagator.js +3 -0
- package/lib/compiler/resolve.js +6 -20
- package/lib/compiler/shared.js +1 -1
- package/lib/compiler/tweak-assocs.js +2 -2
- package/lib/compiler/utils.js +3 -3
- package/lib/compiler/{classes.js → xsn-model.js} +0 -16
- package/lib/edm/annotations/edmJson.js +7 -5
- package/lib/edm/annotations/genericTranslation.js +113 -55
- package/lib/edm/csn2edm.js +25 -9
- package/lib/edm/edm.js +3 -3
- package/lib/edm/edmInboundChecks.js +24 -5
- package/lib/edm/edmPreprocessor.js +46 -20
- package/lib/edm/edmUtils.js +3 -16
- package/lib/gen/Dictionary.json +9 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1941 -1850
- package/lib/json/csnVersion.js +7 -4
- package/lib/json/from-csn.js +8 -7
- package/lib/json/to-csn.js +12 -7
- package/lib/language/antlrParser.js +1 -1
- package/lib/language/genericAntlrParser.js +9 -10
- package/lib/language/multiLineStringParser.js +2 -2
- package/lib/language/textUtils.js +1 -1
- package/lib/main.d.ts +23 -0
- package/lib/main.js +8 -1
- package/lib/model/cloneCsn.js +15 -6
- package/lib/model/csnRefs.js +141 -35
- package/lib/model/csnUtils.js +1 -4
- package/lib/model/enrichCsn.js +1 -1
- package/lib/modelCompare/compare.js +106 -92
- package/lib/optionProcessor.js +23 -1
- package/lib/render/toCdl.js +3 -2
- package/lib/render/toHdbcds.js +4 -48
- package/lib/render/toSql.js +6 -3
- package/lib/transform/addTenantFields.js +58 -35
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/expansion.js +3 -0
- package/lib/transform/db/flattening.js +71 -46
- package/lib/transform/db/views.js +1 -4
- package/lib/transform/effective/main.js +6 -3
- package/lib/transform/effective/misc.js +18 -8
- package/lib/transform/effective/types.js +4 -3
- package/lib/transform/forOdata.js +8 -7
- package/lib/transform/forRelationalDB.js +103 -112
- package/lib/transform/odata/flattening.js +82 -44
- package/lib/transform/odata/toFinalBaseType.js +9 -25
- package/lib/transform/odata/typesExposure.js +28 -15
- package/lib/transform/parseExpr.js +0 -3
- package/lib/transform/transformUtils.js +12 -8
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/coreComputed.js +2 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +1 -1
- package/package.json +2 -2
- package/share/messages/README.md +4 -0
- package/share/messages/anno-duplicate-unrelated-layer.md +1 -1
- package/share/messages/check-proper-type-of.md +1 -1
- package/share/messages/def-duplicate-autoexposed.md +1 -1
- package/share/messages/extend-repeated-intralayer.md +3 -16
- package/share/messages/extend-unrelated-layer.md +1 -1
- package/share/messages/message-explanations.json +1 -0
- package/share/messages/redirected-to-ambiguous.md +1 -1
- package/share/messages/redirected-to-complex.md +1 -1
- package/share/messages/redirected-to-unrelated.md +1 -1
- package/share/messages/rewrite-not-supported.md +1 -1
- package/share/messages/syntax-expecting-unsigned-int.md +2 -2
- package/share/messages/type-missing-enum-value.md +59 -0
- package/share/messages/wildcard-excluding-one.md +1 -1
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
|
|
5
|
-
getArtifactDatabaseNameOf, getElementDatabaseNameOf,
|
|
5
|
+
getArtifactDatabaseNameOf, getElementDatabaseNameOf, applyTransformations,
|
|
6
6
|
isAspect, walkCsnPath, isPersistedOnDatabase
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
|
+
const { isBuiltinType } = require('../base/builtins');
|
|
8
9
|
const transformUtils = require('./transformUtils');
|
|
9
10
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
10
11
|
const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
|
|
@@ -256,25 +257,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
256
257
|
//
|
|
257
258
|
// First, gather all nodes that are arrayed: Don't replace inline, or getFinalTypeInfo()
|
|
258
259
|
// may not return `.items` for types that were already processed.
|
|
260
|
+
// TODO: Do this in resolveTypeReferences?
|
|
259
261
|
{
|
|
260
|
-
const removeItems = new Set();
|
|
261
262
|
applyTransformations(csn, {
|
|
262
263
|
type: (node) => {
|
|
263
|
-
if (node.items || node.type && csnUtils.getFinalTypeInfo(node.type)?.items)
|
|
264
|
-
removeItems.add(node);
|
|
265
264
|
renamePrimitiveTypesAndUuid(node.type, node, 'type');
|
|
266
265
|
addDefaultTypeFacets(node, implicitDefaultLengths);
|
|
267
266
|
},
|
|
268
|
-
items: (node) => removeItems.add(node),
|
|
269
267
|
});
|
|
270
|
-
// no support for array-of - turn into CLOB/Text
|
|
271
|
-
// must be done after A2J or compiler checks could change
|
|
272
|
-
// (e.g. annotation def checks for arrayed types)
|
|
273
|
-
for (const node of removeItems) {
|
|
274
|
-
node.type = 'cds.LargeString';
|
|
275
|
-
delete node.items;
|
|
276
|
-
}
|
|
277
|
-
removeItems.clear();
|
|
278
268
|
}
|
|
279
269
|
|
|
280
270
|
forEachDefinition(csn, [
|
|
@@ -294,15 +284,17 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
294
284
|
// Managed associations get an on-condition - in views and entities
|
|
295
285
|
doA2J && associations.attachOnConditions(csn, csnUtils, pathDelimiter);
|
|
296
286
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
287
|
+
{
|
|
288
|
+
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
289
|
+
// and make them entities
|
|
290
|
+
const fns = [cdsPersistence.getPersistenceTableProcessor(csn, options, messageFunctions)];
|
|
291
|
+
// Allow using managed associations as steps in on-conditions to access their fks
|
|
292
|
+
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
293
|
+
// are part of the elements
|
|
294
|
+
if(doA2J) fns.push(associations.getFKAccessFinalizer(csn, csnUtils, pathDelimiter));
|
|
295
|
+
|
|
296
|
+
forEachDefinition(csn, fns);
|
|
297
|
+
}
|
|
306
298
|
|
|
307
299
|
// Create convenience views for localized entities/views.
|
|
308
300
|
// To be done after getFKAccessFinalizer because associations are
|
|
@@ -328,21 +320,15 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
328
320
|
// - view with parameters: ok on HANA, not allowed otherwise
|
|
329
321
|
// (don't complain about action/function with parameters)
|
|
330
322
|
handleChecksForWithParameters,
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
|
|
323
|
+
checkAssocsWithParams,
|
|
324
|
+
// (170) Transform '$self' in backlink associations to appropriate key comparisons
|
|
325
|
+
// Must happen before draft processing because the artificial ON-conditions in generated
|
|
326
|
+
// draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
|
|
327
|
+
// But it must also happen after flattenForeignKeys has been called for all artifacts,
|
|
328
|
+
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
329
|
+
backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J)
|
|
337
330
|
]);
|
|
338
331
|
|
|
339
|
-
// (170) Transform '$self' in backlink associations to appropriate key comparisons
|
|
340
|
-
// Must happen before draft processing because the artificial ON-conditions in generated
|
|
341
|
-
// draft shadow entities have crooked '_artifact' links, confusing the backlink processing.
|
|
342
|
-
// But it must also happen after flattenForeignKeys has been called for all artifacts,
|
|
343
|
-
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
344
|
-
forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J));
|
|
345
|
-
|
|
346
332
|
/**
|
|
347
333
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
348
334
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
@@ -367,16 +353,14 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
367
353
|
// Apply view-specific transformations
|
|
368
354
|
// (160) Projections now finally become views
|
|
369
355
|
// Replace managed association in group/order by with foreign keys
|
|
370
|
-
const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions
|
|
356
|
+
const transformEntityOrViewPass2 = getViewTransformer(csn, options, messageFunctions);
|
|
371
357
|
forEachDefinition(csn, transformViews);
|
|
372
358
|
|
|
373
|
-
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
374
|
-
forEachDefinition(csn, recursivelyApplyCommon);
|
|
375
|
-
|
|
376
359
|
const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
|
|
377
360
|
assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
|
|
378
361
|
};
|
|
379
362
|
|
|
363
|
+
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
380
364
|
forEachDefinition(csn, [
|
|
381
365
|
/* assert that there will be no conflicting unique- and foreign key constraint identifiers */
|
|
382
366
|
checkConstraintIdentifiers,
|
|
@@ -386,9 +370,26 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
386
370
|
removeKeyPropInType,
|
|
387
371
|
]);
|
|
388
372
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
373
|
+
|
|
374
|
+
// TODO: Could we maybe merge this with the final applyTransformations?
|
|
375
|
+
applyTransformations(csn, {
|
|
376
|
+
elements: (parent, prop, elements, path) => {
|
|
377
|
+
// Attach @cds.persistence.name to elements, replace enums by their base value
|
|
378
|
+
const artifact = csn.definitions[path[1]];
|
|
379
|
+
forEach(elements, (name, element) => {
|
|
380
|
+
replaceEnums(element, name, path.concat(['elements', name]));
|
|
381
|
+
if ((!element.virtual || artifact.query))
|
|
382
|
+
csnUtils.addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(name, options.sqlMapping, options.sqlDialect), element);
|
|
383
|
+
});
|
|
384
|
+
// Remove leading $self to keep renderer-diffs smaller
|
|
385
|
+
if(doA2J && options.transformation === 'hdbcds')
|
|
386
|
+
flattening.removeLeadingSelf(parent, prop, elements);
|
|
387
|
+
}
|
|
388
|
+
}, [(definitions, artifactName, artifact) => {
|
|
389
|
+
// Attach @cds.persistence.name to artifacts
|
|
390
|
+
if (!artifact.$ignore && artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
391
|
+
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
392
|
+
}], { allowArtifact: artifact => artifact.kind === 'entity'});
|
|
392
393
|
|
|
393
394
|
throwWithAnyError();
|
|
394
395
|
|
|
@@ -434,7 +435,10 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
434
435
|
}
|
|
435
436
|
}
|
|
436
437
|
},
|
|
437
|
-
|
|
438
|
+
includes: killProp,
|
|
439
|
+
masked: killProp,
|
|
440
|
+
localized: killProp,
|
|
441
|
+
enum: killProp
|
|
438
442
|
}
|
|
439
443
|
|
|
440
444
|
if(options.sqlDialect === 'postgres') {
|
|
@@ -449,8 +453,25 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
449
453
|
killers.target = killParent;
|
|
450
454
|
}
|
|
451
455
|
|
|
456
|
+
const killTypes = [];
|
|
457
|
+
|
|
458
|
+
if(doA2J) { // replace types and aspects with dummies to shrink overall CSN size
|
|
459
|
+
killers.kind = (parent, prop, kind, path) => {
|
|
460
|
+
if(kind === 'type' || kind === 'aspect') {
|
|
461
|
+
const artifactName = path[1];
|
|
462
|
+
killTypes.push(() => {
|
|
463
|
+
csn.definitions[artifactName] = {
|
|
464
|
+
kind,
|
|
465
|
+
type: 'cds.Integer'
|
|
466
|
+
};
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
452
472
|
applyTransformations(csn, killers, [], { skipIgnore: false });
|
|
453
473
|
|
|
474
|
+
killTypes.forEach(fn => fn());
|
|
454
475
|
redoProjections.forEach(fn => fn());
|
|
455
476
|
|
|
456
477
|
timetrace.stop('Transform CSN');
|
|
@@ -577,34 +598,12 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
577
598
|
}
|
|
578
599
|
}
|
|
579
600
|
|
|
580
|
-
/**
|
|
581
|
-
* @param {CSN.Artifact} artifact
|
|
582
|
-
* @param {string} artifactName
|
|
583
|
-
*/
|
|
584
|
-
function recursivelyApplyCommon(artifact, artifactName) {
|
|
585
|
-
if (!artifact.$ignore) {
|
|
586
|
-
if (artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
587
|
-
csnUtils.addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
588
|
-
|
|
589
|
-
forEachMemberRecursively(artifact, (member, memberName, property, path) => {
|
|
590
|
-
if (property === 'returns')
|
|
591
|
-
return; // ignore "returns" type
|
|
592
|
-
transformCommon(member, memberName, path);
|
|
593
|
-
// (240 b) Annotate elements, foreign keys, parameters etc with their DB names
|
|
594
|
-
// Virtual elements in entities and types are not annotated, as they have no DB representation.
|
|
595
|
-
// In views they are, as we generate a null expression for them (null as <colname>)
|
|
596
|
-
if ((!member.virtual || artifact.query))
|
|
597
|
-
csnUtils.addStringAnnotationTo('@cds.persistence.name', getElementDatabaseNameOf(memberName, options.sqlMapping, options.sqlDialect), member);
|
|
598
|
-
}, [ 'definitions', artifactName ]);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
601
|
/**
|
|
603
602
|
* @param {CSN.Artifact} artifact
|
|
604
603
|
* @param {string} artifactName
|
|
605
604
|
*/
|
|
606
605
|
function removeKeyPropInType(artifact, artifactName) {
|
|
607
|
-
if (!artifact.$ignore && artifact.kind === 'type') {
|
|
606
|
+
if (!doA2J && !artifact.$ignore && artifact.kind === 'type') {
|
|
608
607
|
forEachMemberRecursively(artifact, (member) => {
|
|
609
608
|
if (member.key)
|
|
610
609
|
delete member.key;
|
|
@@ -616,47 +615,42 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
616
615
|
* @param {CSN.Artifact} artifact
|
|
617
616
|
* @param {string} artifactName
|
|
618
617
|
*/
|
|
619
|
-
function
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
// CREATE TABLE F (id INTEGER NOT NULL);
|
|
652
|
-
// CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
|
|
653
|
-
// CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
|
|
654
|
-
// CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
|
|
655
|
-
const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
|
|
656
|
-
message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
|
|
618
|
+
function checkAssocsWithParams(artifact, artifactName) {
|
|
619
|
+
if(options.transformation === 'hdbcds') {
|
|
620
|
+
forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
|
|
621
|
+
// Report an error on
|
|
622
|
+
// - view with parameters that has an element of type association/composition
|
|
623
|
+
// - association that points to entity with parameters
|
|
624
|
+
if (member.target && csnUtils.isAssocOrComposition(member)) {
|
|
625
|
+
if (artifact.params) {
|
|
626
|
+
// HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
|
|
627
|
+
// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
|
|
628
|
+
message('def-unexpected-paramview-assoc', path, { '#': 'source' });
|
|
629
|
+
}
|
|
630
|
+
else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
631
|
+
// UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
|
|
632
|
+
const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
|
|
633
|
+
message('def-unexpected-calcview-assoc', path, { '#': 'source', anno });
|
|
634
|
+
}
|
|
635
|
+
if (csn.definitions[member.target].params) {
|
|
636
|
+
// HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
|
|
637
|
+
// SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
|
|
638
|
+
message('def-unexpected-paramview-assoc', path, { '#': 'target' });
|
|
639
|
+
}
|
|
640
|
+
else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
|
|
641
|
+
// HANA won't check the assoc target but when querying an association with target UDF, this is the error:
|
|
642
|
+
// SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
|
|
643
|
+
// CREATE TABLE F (id INTEGER NOT NULL);
|
|
644
|
+
// CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
|
|
645
|
+
// CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
|
|
646
|
+
// CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
|
|
647
|
+
const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
|
|
648
|
+
message('def-unexpected-calcview-assoc', path, { '#': 'target', anno });
|
|
649
|
+
}
|
|
657
650
|
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
651
|
+
}, [ 'definitions', artifactName ]);
|
|
652
|
+
}
|
|
653
|
+
|
|
660
654
|
}
|
|
661
655
|
|
|
662
656
|
/**
|
|
@@ -731,7 +725,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
731
725
|
|
|
732
726
|
|
|
733
727
|
/**
|
|
734
|
-
*
|
|
728
|
+
* Replace Enum symbols by their values.
|
|
735
729
|
*
|
|
736
730
|
* Only applies to elements.
|
|
737
731
|
*
|
|
@@ -739,11 +733,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
739
733
|
* @param {String} objName
|
|
740
734
|
* @param {CSN.Path} path
|
|
741
735
|
*/
|
|
742
|
-
function
|
|
743
|
-
// (100 b) Remove attribute 'localized'
|
|
744
|
-
if (obj.localized)
|
|
745
|
-
delete obj.localized;
|
|
746
|
-
|
|
736
|
+
function replaceEnums(obj, objName, path) {
|
|
747
737
|
// (190 a) Replace enum symbols by their value (if found)
|
|
748
738
|
replaceEnumSymbolsByValues(obj, path);
|
|
749
739
|
|
|
@@ -832,6 +822,7 @@ function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
|
832
822
|
/**
|
|
833
823
|
* @param {CSN.Artifact} artifact
|
|
834
824
|
* @param {string} artifactName
|
|
825
|
+
* @todo can we do this earlier? Together with another forEachMemberRecursively?
|
|
835
826
|
*/
|
|
836
827
|
function checkTypeParameters(artifact, artifactName) {
|
|
837
828
|
forEachMemberRecursively(artifact, (member, memberName, prop, path) => {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { forEachDefinition,
|
|
4
4
|
copyAnnotations, forEachMemberRecursively,
|
|
5
5
|
transformExpression, findAnnotationExpression } = require('../../model/csnUtils');
|
|
6
|
+
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
6
7
|
const transformUtils = require('../transformUtils');
|
|
7
8
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
8
9
|
const { applyTransformationsOnDictionary,
|
|
@@ -26,25 +27,32 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
26
27
|
const csnPath = ['definitions', defName, dictName];
|
|
27
28
|
const orderedElementList = [];
|
|
28
29
|
|
|
29
|
-
forEach(dict, (
|
|
30
|
-
const location = [ ...csnPath,
|
|
31
|
-
|
|
30
|
+
forEach(dict, (childName, child) => {
|
|
31
|
+
const location = [ ...csnPath, childName ];
|
|
32
|
+
let rootPrefix = [ defName ];
|
|
33
|
+
let resolvedElt = child;
|
|
34
|
+
let typeIdx = 0;
|
|
35
|
+
if(child.type && !child.elements) {
|
|
36
|
+
resolvedElt = csnUtils.getFinalTypeInfo(child.type);
|
|
37
|
+
if(resolvedElt.elements)
|
|
38
|
+
typeIdx = rootPrefix.length + 1;
|
|
39
|
+
}
|
|
32
40
|
if(resolvedElt.elements) {
|
|
33
|
-
const flattenedSubTree = recurseIntoElement(dictName,
|
|
34
|
-
!!
|
|
41
|
+
const flattenedSubTree = recurseIntoElement(dictName, child, resolvedElt,
|
|
42
|
+
!!child.notNull, location, [ ...rootPrefix, childName ], typeIdx);
|
|
35
43
|
|
|
36
44
|
flattenedSubTree.forEach(([flatEltName, flatElt]) => {
|
|
37
45
|
if (dict[flatEltName] || orderedElementList.some(elt => elt[0] === flatEltName))
|
|
38
46
|
error('name-duplicate-element', location,
|
|
39
47
|
{ '#': 'flatten-element-exist', name: flatEltName });
|
|
40
|
-
propagateToFlatElem(
|
|
48
|
+
propagateToFlatElem(child, flatElt);
|
|
41
49
|
rewriteOnCondition(flatElt, flattenedSubTree);
|
|
42
50
|
orderedElementList.push([flatEltName, flatElt]);
|
|
43
51
|
});
|
|
44
52
|
}
|
|
45
53
|
else {
|
|
46
|
-
const flatElt = cloneElt(dictName,
|
|
47
|
-
orderedElementList.push([
|
|
54
|
+
const flatElt = cloneElt(dictName, child, location, [ defName, childName ]);
|
|
55
|
+
orderedElementList.push([childName, flatElt]);
|
|
48
56
|
}
|
|
49
57
|
});
|
|
50
58
|
|
|
@@ -60,7 +68,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
60
68
|
elements[flatEltName] = flatElt;
|
|
61
69
|
return elements;
|
|
62
70
|
}, Object.create(null));
|
|
63
|
-
setProp(def,
|
|
71
|
+
setProp(def, `$flat${dictName}`, flatDict);
|
|
64
72
|
}
|
|
65
73
|
});
|
|
66
74
|
const flatAnnos = Object.create(null);
|
|
@@ -91,7 +99,7 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
91
99
|
}
|
|
92
100
|
flattenedSubTree = flattenedSubTree.concat(recurseIntoElement(scope, child, resolvedElt,
|
|
93
101
|
!!(child.notNull && rootPathIsNotNull),
|
|
94
|
-
[... location, 'elements', childName],
|
|
102
|
+
[... location, 'elements', childName],
|
|
95
103
|
[ ...rootPrefix, childName ], typeIdx));
|
|
96
104
|
});
|
|
97
105
|
// 1) rename, 2) filter duplicates and finalize new elements
|
|
@@ -178,11 +186,11 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
178
186
|
!isODataV4BuiltinFromService(elt.type, location)
|
|
179
187
|
&& !isItemsType(elt.type)) {
|
|
180
188
|
let resolvedType = csnUtils.getFinalTypeInfo(elt);
|
|
181
|
-
|
|
189
|
+
|
|
182
190
|
delete resolvedType.kind;
|
|
183
191
|
if(resolvedType.items)
|
|
184
192
|
delete resolvedType.type;
|
|
185
|
-
|
|
193
|
+
|
|
186
194
|
if(elt.items) {
|
|
187
195
|
if(resolvedType.items) {
|
|
188
196
|
elt.items = resolvedType;
|
|
@@ -200,16 +208,16 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
200
208
|
elt.type = resolvedType;
|
|
201
209
|
}
|
|
202
210
|
}
|
|
203
|
-
|
|
211
|
+
|
|
204
212
|
function isItemsType(typeName) {
|
|
205
213
|
let typeDef = csn.definitions[typeName];
|
|
206
214
|
return !!typeDef?.items;
|
|
207
215
|
}
|
|
208
|
-
|
|
216
|
+
|
|
209
217
|
function isODataV4BuiltinFromService( typeName, path ) {
|
|
210
218
|
if (options.odataVersion === 'v2' || typeof typeName !== 'string')
|
|
211
219
|
return false;
|
|
212
|
-
|
|
220
|
+
|
|
213
221
|
const typeServiceName = csnUtils.getServiceName(typeName);
|
|
214
222
|
let finalBaseType = csnUtils.getFinalTypeInfo(typeName).type;
|
|
215
223
|
if(!isBuiltinType(finalBaseType)) {
|
|
@@ -224,20 +232,19 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
224
232
|
}
|
|
225
233
|
|
|
226
234
|
// The path rewriting must be done with the current CSN path of that exact annotation location
|
|
227
|
-
// in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
|
|
235
|
+
// in the element tree and done exactly after the initial copy, (otherwise, csnRefs is not able
|
|
228
236
|
// to locate the relative location of the path expression in this element).
|
|
229
237
|
// At this time both annotations and values must be rewritten.
|
|
230
238
|
//
|
|
231
239
|
// Later, the query can/must be rewritten as long as a flat OData CSN is published
|
|
232
240
|
// but this then operates on the entity/view which has all struct infos available
|
|
233
241
|
function flattenAndPrefixExprPaths(carrier, propNames, csnPath, rootPrefix, typeIdx, refParentIsItems = false) {
|
|
234
|
-
refFlattener.$refParentIsItems = refParentIsItems;
|
|
235
242
|
const refCheck = {
|
|
236
|
-
ref: (
|
|
237
|
-
const { art } = inspectRef(path);
|
|
243
|
+
ref: (elemref, prop, xpr, path) => {
|
|
244
|
+
const { art } = (elemref._art ? { art: elemref._art } : inspectRef(path));
|
|
238
245
|
const ft = csnUtils.getFinalTypeInfo(art?.type);
|
|
239
246
|
if(!isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
|
|
240
|
-
error('odata-anno-xpr-ref', path, {
|
|
247
|
+
error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, name: refCheck.eltLocationStr, '#': 'flatten_builtin' });
|
|
241
248
|
}
|
|
242
249
|
}
|
|
243
250
|
}
|
|
@@ -250,9 +257,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
250
257
|
const absolutifier = {
|
|
251
258
|
ref : (parent, prop, xpr) => {
|
|
252
259
|
const head = xpr[0].id || xpr[0];
|
|
253
|
-
if(typeIdx
|
|
260
|
+
if(typeIdx < rootPrefix.length && head === '$self' && !isMagicVariable(head)) {
|
|
254
261
|
const [xprHead, ...xprTail] = xpr.slice(1, xpr.length);
|
|
255
|
-
|
|
262
|
+
if(xprHead.id) {
|
|
263
|
+
xprHead.id = rootPrefix.slice(1, typeIdx).concat(xprHead.id).join('_');
|
|
264
|
+
parent[prop] = [ xprHead, ...xprTail ];
|
|
265
|
+
}
|
|
266
|
+
else
|
|
267
|
+
parent[prop] = [ rootPrefix.slice(1, typeIdx).concat(xprHead).join('_'), ...xprTail];
|
|
256
268
|
if(carrier.$scope === 'params')
|
|
257
269
|
parent.param = true;
|
|
258
270
|
else
|
|
@@ -260,8 +272,14 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
260
272
|
}
|
|
261
273
|
else if(rootPrefix.length > 2 && head !== '$self' && !parent.param && !isMagicVariable(head)) {
|
|
262
274
|
const [xprHead, ...xprTail] = xpr;
|
|
263
|
-
if(!refParentIsItems)
|
|
264
|
-
|
|
275
|
+
if(!refParentIsItems) {
|
|
276
|
+
if(xprHead.id) {
|
|
277
|
+
xprHead.id = rootPrefix.slice(1, -1).concat(xprHead.id).join('_');
|
|
278
|
+
parent[prop] = [ xprHead, ...xprTail ];
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
parent[prop] = [ rootPrefix.slice(1, -1).concat(xprHead).join('_'), ...xprTail];
|
|
282
|
+
}
|
|
265
283
|
else
|
|
266
284
|
parent[prop] = [ ...rootPrefix.slice(0, rootPrefix.length-1), ...xpr];
|
|
267
285
|
if(carrier.$scope === 'params')
|
|
@@ -271,14 +289,13 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
271
289
|
}
|
|
272
290
|
}
|
|
273
291
|
}
|
|
274
|
-
|
|
292
|
+
|
|
275
293
|
propNames.forEach(pn => {
|
|
276
294
|
refCheck.anno = pn;
|
|
277
295
|
transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
|
|
278
296
|
});
|
|
279
|
-
adaptRefs.forEach(fn => fn());
|
|
297
|
+
adaptRefs.forEach(fn => fn(refParentIsItems));
|
|
280
298
|
adaptRefs.length = 0;
|
|
281
|
-
refFlattener.$refParentIsItems = false;
|
|
282
299
|
propNames.forEach(pn => {
|
|
283
300
|
transformExpression(carrier, pn, absolutifier, csnPath)
|
|
284
301
|
})
|
|
@@ -303,17 +320,10 @@ function allInOneFlattening(csn, refFlattener, adaptRefs, inspectRef, isExternal
|
|
|
303
320
|
}
|
|
304
321
|
}
|
|
305
322
|
|
|
306
|
-
|
|
307
|
-
* Rewrite all structured references in the model such that it matches their flat counterparts
|
|
308
|
-
* @param {CSN.Model} csn
|
|
309
|
-
* @param {CSN.Options} options
|
|
310
|
-
* @param {WeakMap} resolved Cache for resolved refs
|
|
311
|
-
* @param {string} pathDelimiter
|
|
312
|
-
* @param {object} iterateOptions
|
|
313
|
-
*/
|
|
314
|
-
function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptions = {} ) {
|
|
323
|
+
function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, inspectRef, effectiveType, csnUtils, error, options, iterateOptions = {} ) {
|
|
315
324
|
|
|
316
325
|
// All anno path flattening is already done, don't do it on locations where we don't want it!
|
|
326
|
+
const typeNames = [];
|
|
317
327
|
forEachDefinition(csn, (def, defName) => {
|
|
318
328
|
if(def.kind === 'entity') {
|
|
319
329
|
['query', 'projection'].forEach(dictName => {
|
|
@@ -322,16 +332,45 @@ function flattenAllStructStepsInRefs( csn, refFlattener, adaptRefs, iterateOptio
|
|
|
322
332
|
if(csn.definitions[defName].actions)
|
|
323
333
|
applyTransformationsOnDictionary(csn.definitions[defName].actions, refFlattener, iterateOptions, [ 'definitions', defName, 'actions' ]);
|
|
324
334
|
}
|
|
325
|
-
|
|
326
|
-
TODO: In real hybrid flat/struct OData transformation, ref rewriting in types must not be done
|
|
327
|
-
together with non-rewriting ON condition refs during foreign key creation
|
|
328
|
-
*/
|
|
335
|
+
|
|
329
336
|
if(['type'].includes(def.kind)) {
|
|
337
|
+
typeNames.push(defName);
|
|
330
338
|
applyTransformationsOnNonDictionary(csn.definitions, defName, refFlattener, iterateOptions, [ 'definitions' ]);
|
|
331
339
|
}
|
|
332
340
|
});
|
|
333
341
|
adaptRefs.forEach(fn => fn());
|
|
334
342
|
adaptRefs.length = 0;
|
|
343
|
+
if(isBetaEnabled(options, 'odataPathsInAnnotationExpressions')) {
|
|
344
|
+
const refCheck = {
|
|
345
|
+
ref: (elemref, prop, xpr, path) => {
|
|
346
|
+
const { links, art } = (elemref._links && elemref._art ? { links: elemref._links, art: elemref._art } : inspectRef(path) );
|
|
347
|
+
|
|
348
|
+
let i = links.length-2;
|
|
349
|
+
const getProp = (propName) =>
|
|
350
|
+
(links[i].art?.[propName] ||
|
|
351
|
+
effectiveType(links[i].art)[propName]);
|
|
352
|
+
|
|
353
|
+
const ft = csnUtils.getFinalTypeInfo(art?.type);
|
|
354
|
+
let target = undefined;
|
|
355
|
+
for(; i >= 0 && !getProp('items') && !target; i--) {
|
|
356
|
+
target = getProp('target');
|
|
357
|
+
}
|
|
358
|
+
if(target && csn.definitions[target].$flatelements && !isBuiltinType(ft?.type) && refCheck.anno !== 'value') {
|
|
359
|
+
error('odata-anno-xpr-ref', path, { anno: refCheck.anno, elemref, '#': 'flatten_builtin_type' });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
typeNames.forEach(tn => {
|
|
364
|
+
forEachMemberRecursively(csn.definitions[tn], (member, memberName, prop, csnPath) => {
|
|
365
|
+
Object.keys(member).filter(pn => pn[0] === '@').forEach(pn => {
|
|
366
|
+
refCheck.anno = pn;
|
|
367
|
+
transformExpression(member, pn, [ refCheck, refFlattener ], csnPath);
|
|
368
|
+
});
|
|
369
|
+
}, [ 'definitions', tn ]);
|
|
370
|
+
})
|
|
371
|
+
adaptRefs.forEach(fn => fn(true, 1));
|
|
372
|
+
adaptRefs.length = 0;
|
|
373
|
+
}
|
|
335
374
|
}
|
|
336
375
|
|
|
337
376
|
function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, options, resolved, pathDelimiter) {
|
|
@@ -355,19 +394,18 @@ function getStructRefFlatteningTransformer(csn, inspectRef, effectiveType, optio
|
|
|
355
394
|
}
|
|
356
395
|
const adaptRefs = [];
|
|
357
396
|
const transformer = {
|
|
358
|
-
$refParentIsItems: false,
|
|
359
397
|
ref: (parent, prop, ref, path) => {
|
|
360
398
|
const { links, art, scope } = inspectRef(path);
|
|
361
399
|
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
362
400
|
setProp(parent, '$path', [ ...path ]);
|
|
363
401
|
const lastRef = ref[ref.length - 1];
|
|
364
|
-
const fn = () => {
|
|
402
|
+
const fn = (suspend=false, suspendPos=0) => {
|
|
365
403
|
const scopedPath = [ ...parent.$path ];
|
|
366
404
|
// TODO: If foreign key annotations should be assigned via
|
|
367
405
|
// full path into target, uncomment this line and
|
|
368
406
|
// comment/remove setProp in expansion.js
|
|
369
407
|
// setProp(parent, '$structRef', parent.ref);
|
|
370
|
-
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes,
|
|
408
|
+
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos);
|
|
371
409
|
resolved.set(parent, { links, art, scope });
|
|
372
410
|
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
373
411
|
// TODO: Can this be done elegantly during expand phase already?
|