@sap/cds-compiler 2.12.0 → 2.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -15
- package/bin/cdsc.js +13 -13
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +28 -63
- package/lib/api/options.js +3 -3
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +25 -4
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +158 -123
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +4 -7
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +14 -3
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +32 -13
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +111 -46
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +64 -37
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +5 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +9 -8
- package/lib/edm/edm.js +11 -12
- package/lib/edm/edmPreprocessor.js +137 -73
- package/lib/edm/edmUtils.js +116 -22
- package/lib/gen/Dictionary.json +10 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5282 -4265
- package/lib/json/from-csn.js +12 -1
- package/lib/json/to-csn.js +126 -66
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +76 -3
- package/lib/language/language.g4 +297 -130
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +468 -59
- package/lib/main.js +35 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +225 -156
- package/lib/model/csnUtils.js +192 -223
- package/lib/model/enrichCsn.js +70 -29
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +5 -4
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +73 -288
- package/lib/render/toHdbcds.js +25 -23
- package/lib/render/toSql.js +98 -41
- package/lib/render/utils/common.js +5 -10
- package/lib/render/utils/sql.js +4 -3
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +103 -305
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +55 -52
- package/lib/transform/db/expansion.js +46 -24
- package/lib/transform/db/flattening.js +553 -102
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +59 -6
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +67 -183
- package/lib/transform/forOdataNew.js +17 -171
- package/lib/transform/localized.js +34 -19
- package/lib/transform/odata/generateForeignKeyElements.js +1 -1
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +36 -22
- package/lib/transform/translateAssocsToJoins.js +2 -19
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/universalCsnEnricher.js +0 -237
|
@@ -7,7 +7,7 @@ const { getUtils,
|
|
|
7
7
|
cloneCsn,
|
|
8
8
|
forEachDefinition,
|
|
9
9
|
forEachMemberRecursively,
|
|
10
|
-
|
|
10
|
+
applyTransformationsOnNonDictionary,
|
|
11
11
|
getArtifactDatabaseNameOf,
|
|
12
12
|
getElementDatabaseNameOf,
|
|
13
13
|
isAspect,
|
|
@@ -16,7 +16,7 @@ const { getUtils,
|
|
|
16
16
|
} = require('../model/csnUtils');
|
|
17
17
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
18
18
|
const validate = require('../checks/validator');
|
|
19
|
-
const { isArtifactInSomeService,
|
|
19
|
+
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
|
|
20
20
|
const ReferenceFlattener = require('./odata/referenceFlattener');
|
|
21
21
|
const { flattenCSN } = require('./odata/structureFlattener');
|
|
22
22
|
const generateForeignKeys = require('./odata/generateForeignKeyElements');
|
|
@@ -24,7 +24,8 @@ const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssoci
|
|
|
24
24
|
const expandToFinalBaseType = require('./odata/toFinalBaseType');
|
|
25
25
|
const { timetrace } = require('../utils/timetrace');
|
|
26
26
|
const { attachPath } = require('./odata/attachPath');
|
|
27
|
-
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
27
|
+
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
28
|
+
const generateDrafts = require('./draft/odata');
|
|
28
29
|
|
|
29
30
|
const { addLocalizationViews } = require('./localized');
|
|
30
31
|
|
|
@@ -75,7 +76,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
75
76
|
// copy the model as we don't want to change the input model
|
|
76
77
|
let csn = cloneCsn(inputModel, options);
|
|
77
78
|
|
|
78
|
-
const { error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
|
|
79
|
+
const { message, error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
|
|
79
80
|
throwWithError();
|
|
80
81
|
|
|
81
82
|
// the new transformer works only with new CSN
|
|
@@ -84,22 +85,16 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
84
85
|
const transformers = transformUtils.getTransformers(csn, options, '_');
|
|
85
86
|
const {
|
|
86
87
|
addDefaultTypeFacets,
|
|
87
|
-
createForeignKeyElement,
|
|
88
|
-
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
89
|
-
createAssociationElement, createAssociationPathComparison,
|
|
90
|
-
addElement, createAction, assignAction,
|
|
91
88
|
extractValidFromToKeyElement,
|
|
92
89
|
checkAssignment, checkMultipleAssignments,
|
|
93
|
-
recurseElements, setAnnotation,
|
|
90
|
+
recurseElements, setAnnotation, renameAnnotation,
|
|
94
91
|
expandStructsInExpression
|
|
95
92
|
} = transformers;
|
|
96
93
|
|
|
97
94
|
const csnUtils = getUtils(csn);
|
|
98
95
|
const {
|
|
99
96
|
getCsnDef,
|
|
100
|
-
getFinalType,
|
|
101
97
|
getServiceName,
|
|
102
|
-
hasAnnotationValue,
|
|
103
98
|
isAssocOrComposition,
|
|
104
99
|
isAssociation,
|
|
105
100
|
isStructured,
|
|
@@ -133,7 +128,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
133
128
|
addLocalizationViews(csn, options, acceptLocalizedView);
|
|
134
129
|
|
|
135
130
|
validate.forOdata(csn, {
|
|
136
|
-
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
131
|
+
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
|
|
137
132
|
});
|
|
138
133
|
|
|
139
134
|
|
|
@@ -209,19 +204,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
209
204
|
// - structured types must not contain associations for OData V2
|
|
210
205
|
// - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
211
206
|
// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
212
|
-
|
|
213
|
-
forEachDefinition(csn, (def, defName) => {
|
|
214
|
-
if (def.kind === 'entity' || def.kind === 'view') {
|
|
215
|
-
// Generate artificial draft fields if requested
|
|
216
|
-
if (def['@odata.draft.enabled']) {
|
|
217
|
-
// Ignore if not part of a service
|
|
218
|
-
if (isArtifactInSomeService(defName, services)) {
|
|
219
|
-
generateDraftForOdata(def, defName, def, visitedArtifacts);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
visitedArtifacts[defName] = true;
|
|
224
|
-
}, { skipArtifact: isExternalServiceMember });
|
|
207
|
+
generateDrafts(csn, options, services)
|
|
225
208
|
|
|
226
209
|
// Deal with all kind of annotations manipulations here
|
|
227
210
|
forEachDefinition(csn, (def, defName) => {
|
|
@@ -342,7 +325,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
342
325
|
// but '@Core.Immutable' for everything else.
|
|
343
326
|
if (!(node['@readonly'] && node['@insertonly'])) {
|
|
344
327
|
if (name === '@readonly' && node[name] !== null) {
|
|
345
|
-
if (node.kind === 'entity'
|
|
328
|
+
if (node.kind === 'entity') {
|
|
346
329
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
347
330
|
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);
|
|
348
331
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -352,7 +335,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
352
335
|
}
|
|
353
336
|
// @insertonly is effective on entities/queries only
|
|
354
337
|
else if (name === '@insertonly' && node[name] !== null) {
|
|
355
|
-
if (node.kind === 'entity'
|
|
338
|
+
if (node.kind === 'entity') {
|
|
356
339
|
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);
|
|
357
340
|
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);
|
|
358
341
|
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false);
|
|
@@ -423,151 +406,14 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
423
406
|
// removes leading $self in on-conditions's references
|
|
424
407
|
function removeLeadingDollarSelfInOnCondition(assoc) {
|
|
425
408
|
if (!assoc.on) return; // nothing to do
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
|
|
435
|
-
// into its transitively reachable composition targets, and into the model.
|
|
436
|
-
// 'rootArtifact' is the root artifact where composition traversal started.
|
|
437
|
-
|
|
438
|
-
// Constraints
|
|
439
|
-
// Draft Root: Exactly one PK of type UUID
|
|
440
|
-
// Draft Node: One PK of type UUID + 0..1 PK of another type
|
|
441
|
-
// Draft Node: Must not be reachable from multiple draft roots
|
|
442
|
-
function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {
|
|
443
|
-
// Sanity check
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
if (!isArtifactInSomeService(artifactName, services)) {
|
|
446
|
-
throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));
|
|
447
|
-
}
|
|
448
|
-
// Nothing to do if already draft-enabled (composition traversal may have circles)
|
|
449
|
-
if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction'])
|
|
450
|
-
&& artifact.actions && artifact.actions.draftPrepare) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
455
|
-
// @ts-ignore
|
|
456
|
-
let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
|
|
457
|
-
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
458
|
-
if (!draftAdminDataProjection) {
|
|
459
|
-
// @ts-ignore
|
|
460
|
-
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
|
|
461
|
-
}
|
|
462
|
-
// Report an error if it is not an entity or not what we expect
|
|
463
|
-
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
|
|
464
|
-
error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName },
|
|
465
|
-
`Generated entity $(NAME) conflicts with existing artifact`);
|
|
466
|
-
}
|
|
467
|
-
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
468
|
-
if (artifact == rootArtifact) {
|
|
469
|
-
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
|
|
470
|
-
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
|
|
471
|
-
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
472
|
-
} else {
|
|
473
|
-
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
artifact.elements && Object.values(artifact.elements).forEach( elem => {
|
|
477
|
-
// Make all non-key elements nullable
|
|
478
|
-
if (elem.notNull && elem.key !== true) {
|
|
479
|
-
delete elem.notNull;
|
|
480
|
-
}
|
|
481
|
-
});
|
|
482
|
-
// Generate the additional elements into the draft-enabled artifact
|
|
483
|
-
|
|
484
|
-
// key IsActiveEntity : Boolean default true
|
|
485
|
-
let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true, false);
|
|
486
|
-
isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
|
|
487
|
-
addElement(isActiveEntity, artifact, artifactName);
|
|
488
|
-
|
|
489
|
-
// HasActiveEntity : Boolean default false
|
|
490
|
-
let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false, true);
|
|
491
|
-
hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
|
|
492
|
-
addElement(hasActiveEntity, artifact, artifactName);
|
|
493
|
-
|
|
494
|
-
// HasDraftEntity : Boolean default false;
|
|
495
|
-
let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false, true);
|
|
496
|
-
hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
|
|
497
|
-
addElement(hasDraftEntity, artifact, artifactName);
|
|
498
|
-
|
|
499
|
-
// @odata.contained: true
|
|
500
|
-
// DraftAdministrativeData : Association to one DraftAdministrativeData;
|
|
501
|
-
let draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
|
|
502
|
-
draftAdministrativeData.DraftAdministrativeData.cardinality = { max: 1, };
|
|
503
|
-
draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
|
|
504
|
-
draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
|
|
505
|
-
addElement(draftAdministrativeData, artifact, artifactName);
|
|
506
|
-
|
|
507
|
-
// Note that we need to do the ODATA transformation steps for managed associations
|
|
508
|
-
// (foreign key field generation, generatedFieldName) by hand, because the corresponding
|
|
509
|
-
// transformation steps have already been done on all artifacts when we come here)
|
|
510
|
-
let uuidDraftKey = draftAdministrativeData.DraftAdministrativeData.keys.filter(key => key.ref && key.ref.length === 1 && key.ref[0] === 'DraftUUID');
|
|
511
|
-
if (uuidDraftKey && uuidDraftKey[0]) {
|
|
512
|
-
uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
|
|
513
|
-
let path = ['definitions', artifactName, 'elements', 'DraftAdministrativeData', 'keys', 0];
|
|
514
|
-
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName, path);
|
|
515
|
-
}
|
|
516
|
-
// SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)
|
|
517
|
-
let siblingEntity = createAssociationElement('SiblingEntity', artifactName, false);
|
|
518
|
-
siblingEntity.SiblingEntity.cardinality = { max: 1 };
|
|
519
|
-
addElement(siblingEntity, artifact, artifactName);
|
|
520
|
-
// ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
|
|
521
|
-
siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
|
|
522
|
-
|
|
523
|
-
// Iterate elements
|
|
524
|
-
artifact.elements && Object.entries(artifact.elements).forEach( ([elemName, elem]) => {
|
|
525
|
-
if (elemName !== 'IsActiveEntity' && elem.key) {
|
|
526
|
-
// Amend the ON-condition above:
|
|
527
|
-
// ... and SiblingEntity.<keyfield> = <keyfield> ... (for all key fields except 'IsActiveEntity')
|
|
528
|
-
let cond = createAssociationPathComparison('SiblingEntity', elemName, '=', elemName);
|
|
529
|
-
cond.push('and');
|
|
530
|
-
cond.push(...siblingEntity.SiblingEntity.on);
|
|
531
|
-
siblingEntity.SiblingEntity.on = cond;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Draft-enable the targets of composition elements (draft nodes), too
|
|
535
|
-
// TODO rewrite
|
|
536
|
-
if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
|
|
537
|
-
let draftNode = csn.definitions[elem.target];
|
|
538
|
-
|
|
539
|
-
// Ignore if that is our own draft root
|
|
540
|
-
if (draftNode != rootArtifact) {
|
|
541
|
-
// Report error when the draft node has @odata.draft.enabled itself
|
|
542
|
-
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
543
|
-
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
|
|
544
|
-
}
|
|
545
|
-
// Ignore composition if not part of a service or explicitly draft disabled
|
|
546
|
-
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
else {
|
|
550
|
-
// Generate draft stuff into the target
|
|
551
|
-
generateDraftForOdata(draftNode, elem.target, rootArtifact, visitedArtifacts);
|
|
552
|
-
}
|
|
409
|
+
// TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
|
|
410
|
+
applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
|
|
411
|
+
ref: (node, prop, ref) => {
|
|
412
|
+
// remove leading $self when at the begining of a ref
|
|
413
|
+
if (ref.length > 1 && ref[0] === '$self')
|
|
414
|
+
node.ref.splice(0, 1);
|
|
553
415
|
}
|
|
554
|
-
}
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
558
|
-
|
|
559
|
-
// action draftPrepare (SideEffectsQualifier: String) return <artifact>;
|
|
560
|
-
let draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
561
|
-
assignAction(draftPrepare, artifact);
|
|
562
|
-
|
|
563
|
-
if (artifact == rootArtifact) {
|
|
564
|
-
// action draftActivate() return <artifact>;
|
|
565
|
-
let draftActivate = createAction('draftActivate', artifactName);
|
|
566
|
-
assignAction(draftActivate, artifact);
|
|
567
|
-
|
|
568
|
-
// action draftEdit (PreserveChanges: Boolean) return <artifact>;
|
|
569
|
-
let draftEdit = createAction('draftEdit', artifactName, 'PreserveChanges', 'cds.Boolean');
|
|
570
|
-
assignAction(draftEdit, artifact);
|
|
416
|
+
});
|
|
571
417
|
}
|
|
572
418
|
}
|
|
573
419
|
|
|
@@ -48,7 +48,7 @@ const _targetFor = Symbol('_targetFor');
|
|
|
48
48
|
* We have three kinds of localized convenience views:
|
|
49
49
|
*
|
|
50
50
|
* 1. "direct ones" using coalesce() for the table entities with localized
|
|
51
|
-
* elements: as projection on the original (created in
|
|
51
|
+
* elements: as projection on the original (created in extend.js)
|
|
52
52
|
* 2. for table entities with associations to entities which have a localized
|
|
53
53
|
* convenience views or redirections thereon: as projection on the original
|
|
54
54
|
* 3. for view entities with associations to entities which have a localized
|
|
@@ -74,11 +74,10 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
74
74
|
if (hasErrors(options.messages))
|
|
75
75
|
return csn;
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
const messageFunctions = makeMessageFunction(csn, options);
|
|
78
|
+
if (hasExistingLocalizationViews(csn, options, messageFunctions))
|
|
78
79
|
return csn;
|
|
79
80
|
|
|
80
|
-
const { info } = makeMessageFunction(csn, options);
|
|
81
|
-
|
|
82
81
|
const noCoalesce = (options.localizedLanguageFallback === 'none' ||
|
|
83
82
|
options.localizedWithoutCoalesce);
|
|
84
83
|
|
|
@@ -102,7 +101,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
102
101
|
return;
|
|
103
102
|
|
|
104
103
|
if (isInLocalizedNamespace(artName))
|
|
105
|
-
// We already issued a warning for it in
|
|
104
|
+
// We already issued a warning for it in hasExistingLocalizationViews()
|
|
106
105
|
return;
|
|
107
106
|
|
|
108
107
|
const localized = getLocalizedTextElements( artName );
|
|
@@ -127,7 +126,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
127
126
|
|
|
128
127
|
if (csn.definitions[viewName]) {
|
|
129
128
|
// Already exists, skip creation.
|
|
130
|
-
info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
|
|
129
|
+
messageFunctions.info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
|
|
131
130
|
return;
|
|
132
131
|
}
|
|
133
132
|
|
|
@@ -339,7 +338,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
339
338
|
if (elem.key || elem.$key || elem.localized)
|
|
340
339
|
textElements.push( elemName );
|
|
341
340
|
|
|
342
|
-
// TODO: Already warned about in
|
|
341
|
+
// TODO: Already warned about in extend.js
|
|
343
342
|
// if (elem.key && isLocalized)
|
|
344
343
|
// warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );
|
|
345
344
|
}, artPath);
|
|
@@ -349,7 +348,7 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
349
348
|
return null;
|
|
350
349
|
|
|
351
350
|
if (!isEntityPreprocessed( art )) {
|
|
352
|
-
info( null, artPath, { name: artName },
|
|
351
|
+
messageFunctions.info( null, artPath, { name: artName },
|
|
353
352
|
'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );
|
|
354
353
|
return null;
|
|
355
354
|
}
|
|
@@ -358,13 +357,13 @@ function _addLocalizationViews(csn, options, useJoins, acceptLocalizedView = nul
|
|
|
358
357
|
const textsEntity = csn.definitions[textsName];
|
|
359
358
|
|
|
360
359
|
if (!textsEntity) {
|
|
361
|
-
info( null, artPath, { name: artName },
|
|
360
|
+
messageFunctions.info( null, artPath, { name: artName },
|
|
362
361
|
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
|
|
363
362
|
return null;
|
|
364
363
|
}
|
|
365
364
|
|
|
366
365
|
if (!isValidTextsEntity( textsEntity )) {
|
|
367
|
-
info( null, [ 'definitions', textsName ], { name: artName },
|
|
366
|
+
messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
|
|
368
367
|
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
|
|
369
368
|
return null;
|
|
370
369
|
}
|
|
@@ -685,18 +684,32 @@ function copyPersistenceAnnotations(target, source) {
|
|
|
685
684
|
*
|
|
686
685
|
* @param {CSN.Model} csn
|
|
687
686
|
* @param {CSN.Options} options
|
|
687
|
+
* @param {object} messageFunctions
|
|
688
688
|
*/
|
|
689
|
-
function hasExistingLocalizationViews(csn, options) {
|
|
689
|
+
function hasExistingLocalizationViews(csn, options, messageFunctions) {
|
|
690
690
|
if (!csn || !csn.definitions)
|
|
691
691
|
return false;
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
692
|
+
|
|
693
|
+
let hasExistingViews = false;
|
|
694
|
+
let hasNonViews = false;
|
|
695
|
+
|
|
696
|
+
for (const name in csn.definitions) {
|
|
697
|
+
const art = csn.definitions[name];
|
|
698
|
+
if (isInLocalizedNamespace(name) || name === 'localized') {
|
|
699
|
+
if (!art.query && !art.projection) {
|
|
700
|
+
if (!name.endsWith('.texts')) {
|
|
701
|
+
hasNonViews = true;
|
|
702
|
+
messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
|
|
703
|
+
'The namespace "localized" is reserved for localization views');
|
|
704
|
+
}
|
|
705
|
+
} else if (!hasExistingViews) {
|
|
706
|
+
hasExistingViews = true;
|
|
707
|
+
messageFunctions.info( null, [ 'definitions', name ], {},
|
|
708
|
+
'Input CSN already contains localization views, no further ones will be created' );
|
|
709
|
+
}
|
|
710
|
+
}
|
|
698
711
|
}
|
|
699
|
-
return
|
|
712
|
+
return hasExistingViews || hasNonViews;
|
|
700
713
|
}
|
|
701
714
|
|
|
702
715
|
/**
|
|
@@ -728,9 +741,10 @@ function isEntityPreprocessed(entity) {
|
|
|
728
741
|
|
|
729
742
|
/**
|
|
730
743
|
* @param {string} name
|
|
744
|
+
* @returns {boolean}
|
|
731
745
|
*/
|
|
732
746
|
function isInLocalizedNamespace(name) {
|
|
733
|
-
return name.startsWith('localized.');
|
|
747
|
+
return name === 'localized' || name.startsWith('localized.');
|
|
734
748
|
}
|
|
735
749
|
|
|
736
750
|
/**
|
|
@@ -738,6 +752,7 @@ function isInLocalizedNamespace(name) {
|
|
|
738
752
|
*
|
|
739
753
|
* @param {CSN.Model} csn
|
|
740
754
|
* @param {string} artifactName
|
|
755
|
+
* @returns {boolean}
|
|
741
756
|
*/
|
|
742
757
|
function hasLocalizedConvenienceView(csn, artifactName) {
|
|
743
758
|
return !isInLocalizedNamespace(artifactName) && !!csn.definitions[`localized.${ artifactName }`];
|
|
@@ -37,7 +37,7 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error, is
|
|
|
37
37
|
sortedAssociations.forEach(item => {
|
|
38
38
|
const { definitionName, structuralNodeName, elementName, element, parent, path } = item;
|
|
39
39
|
|
|
40
|
-
if (csnUtils.
|
|
40
|
+
if (csnUtils.isManagedAssociation(element) && element.keys) {
|
|
41
41
|
if (flatKeys) // tackling the ref value in assoc.keys
|
|
42
42
|
takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath);
|
|
43
43
|
// TODO: move in separate function
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { applyTransformations } = require('../../model/csnUtils');
|
|
2
2
|
const { setProp } = require('../../base/model');
|
|
3
3
|
const { implicitAs } = require('../../model/csnRefs');
|
|
4
4
|
const { structuralPath } = require('./structuralPath');
|
|
@@ -52,32 +52,34 @@ class ReferenceFlattener {
|
|
|
52
52
|
* @param {*} isStructured Callback function checking of an artifact is a structured element.
|
|
53
53
|
*/
|
|
54
54
|
resolveAllReferences(csn, inspectRef, isStructured) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!resolved)
|
|
64
|
-
return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
|
|
65
|
-
if (!resolved.links)
|
|
66
|
-
return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
|
|
67
|
-
let paths = [];
|
|
68
|
-
resolved.links.forEach((element) => {
|
|
69
|
-
if (!element.art)
|
|
70
|
-
paths = undefined; // not resolved -> no paths
|
|
71
|
-
if (paths) {
|
|
72
|
-
paths.push(element.art.$path);
|
|
55
|
+
applyTransformations(csn, {
|
|
56
|
+
ref: (node, prop, _ref, path) => {
|
|
57
|
+
if (!path) return;
|
|
58
|
+
let resolved;
|
|
59
|
+
try {
|
|
60
|
+
resolved = inspectRef(path);
|
|
61
|
+
} catch (ex) {
|
|
62
|
+
return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
|
|
73
63
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
64
|
+
if (!resolved)
|
|
65
|
+
return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
|
|
66
|
+
if (!resolved.links)
|
|
67
|
+
return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
|
|
68
|
+
let paths = [];
|
|
69
|
+
resolved.links.forEach((element) => {
|
|
70
|
+
if (!element.art)
|
|
71
|
+
paths = undefined; // not resolved -> no paths
|
|
72
|
+
if (paths) {
|
|
73
|
+
paths.push(element.art.$path);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (paths)
|
|
77
|
+
setProp(node, '$paths', paths);
|
|
78
|
+
// cache if structured or not
|
|
79
|
+
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
|
|
80
|
+
this.structuredReference[path.join('/')] = structured;
|
|
81
|
+
}
|
|
82
|
+
})
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
/**
|
|
@@ -172,80 +174,84 @@ class ReferenceFlattener {
|
|
|
172
174
|
* @param {*} csn
|
|
173
175
|
*/
|
|
174
176
|
flattenAllReferences(csn) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
ref[i].id
|
|
191
|
-
|
|
177
|
+
applyTransformations(csn, {
|
|
178
|
+
ref: (node, prop, ref, path) => {
|
|
179
|
+
if (node.$paths) {
|
|
180
|
+
let newRef = []; // flattened reference
|
|
181
|
+
let flattenWithPrevious = false;
|
|
182
|
+
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
|
|
183
|
+
node.$paths.forEach((path, i) => {
|
|
184
|
+
if (path === undefined || !ref[i]) return;
|
|
185
|
+
let spath = path.join('/');
|
|
186
|
+
let movedTo = this.elementTransitions[spath]; // detect element transition
|
|
187
|
+
let flattened = this.flattenedElementPaths[spath];
|
|
188
|
+
if (flattenWithPrevious) {
|
|
189
|
+
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
|
|
190
|
+
// if we have a filter or args in an assoc, it needs to be kept, therefore
|
|
191
|
+
// the id of the ref is updated with the flattened version
|
|
192
|
+
if (ref[i].id) {
|
|
193
|
+
ref[i].id = newRef[newRef.length - 1];
|
|
194
|
+
newRef[newRef.length - 1] = ref[i];
|
|
195
|
+
}
|
|
196
|
+
lastFlattenedID = i;
|
|
197
|
+
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
|
|
198
|
+
let movedToElementName = movedTo[movedTo.length-1];
|
|
199
|
+
newRef.push(movedToElementName);
|
|
200
|
+
lastFlattenedID = i;
|
|
201
|
+
} else {
|
|
202
|
+
newRef.push(ref[i]);
|
|
192
203
|
}
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
setProp(newRef, '$path', path.concat('ref'));
|
|
211
|
-
if (!node.as) {
|
|
212
|
-
if (isPartOfKeysStructure(structural))
|
|
213
|
-
node.as = implicitAs(node.ref);
|
|
214
|
-
else
|
|
215
|
-
setProp(node, 'as', implicitAs(node.ref))
|
|
204
|
+
flattenWithPrevious = flattened;
|
|
205
|
+
});
|
|
206
|
+
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
|
|
207
|
+
// check if this is a column and add alias if missing
|
|
208
|
+
let structural = structuralPath(csn, path);
|
|
209
|
+
if (isColumnInSelectOrProjection(structural)) {
|
|
210
|
+
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
|
|
211
|
+
node.as = node.ref[node.ref.length - 1];
|
|
212
|
+
}
|
|
213
|
+
setProp(newRef, '$path', path.concat('ref'));
|
|
214
|
+
if (!node.as) {
|
|
215
|
+
if (isPartOfKeysStructure(structural))
|
|
216
|
+
node.as = implicitAs(node.ref);
|
|
217
|
+
else
|
|
218
|
+
setProp(node, 'as', implicitAs(node.ref))
|
|
219
|
+
}
|
|
220
|
+
node.ref = newRef;
|
|
216
221
|
}
|
|
217
|
-
node.ref = newRef;
|
|
218
222
|
}
|
|
219
223
|
}
|
|
220
224
|
})
|
|
221
225
|
}
|
|
222
226
|
|
|
223
227
|
applyAliasesInOnCond(csn, inspectRef) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
228
|
+
applyTransformations(csn, {
|
|
229
|
+
ref: (node, prop, ref, path) => {
|
|
230
|
+
// Process only on-conditions of associations
|
|
231
|
+
let structural = structuralPath(csn, path);
|
|
232
|
+
if(!isOnCondition(structural)) return;
|
|
233
|
+
let { links } = inspectRef(path);
|
|
234
|
+
if(!links) return; // $user not resolvable
|
|
235
|
+
let keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
236
|
+
|
|
237
|
+
let aliasedRef = [...ref];
|
|
238
|
+
|
|
239
|
+
for (let idx = 0; idx < ref.length; idx++) {
|
|
240
|
+
const currArt = links[idx].art;
|
|
241
|
+
if (keysOfPreviousStepWhenManagedAssoc) {
|
|
242
|
+
const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
|
|
243
|
+
if (usedKey && usedKey.as) {
|
|
244
|
+
aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
|
|
245
|
+
idx += usedKey.ref.length - 1;
|
|
246
|
+
keysOfPreviousStepWhenManagedAssoc = undefined;
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
keysOfPreviousStepWhenManagedAssoc =
|
|
250
|
+
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
|
|
242
251
|
}
|
|
243
|
-
} else {
|
|
244
|
-
keysOfPreviousStepWhenManagedAssoc =
|
|
245
|
-
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
|
|
246
252
|
}
|
|
253
|
+
node.ref = aliasedRef;
|
|
247
254
|
}
|
|
248
|
-
node.ref = aliasedRef;
|
|
249
255
|
})
|
|
250
256
|
}
|
|
251
257
|
}
|
|
@@ -71,7 +71,7 @@ function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExterna
|
|
|
71
71
|
* @param {Function} error
|
|
72
72
|
*/
|
|
73
73
|
function flattenDefinition(definition, definitionPath, csnUtils, options, referenceFlattener, error) {
|
|
74
|
-
if (definition.kind !== 'entity'
|
|
74
|
+
if (definition.kind !== 'entity')
|
|
75
75
|
return;
|
|
76
76
|
|
|
77
77
|
let { newFlatElements } = flattenStructure(definition.elements, definitionPath, csnUtils, options, error, referenceFlattener);
|