@sap/cds-compiler 2.11.4 → 2.13.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { forEachDefinition, getUtils, getServiceNames } = require('../../model/csnUtils');
|
|
4
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
5
|
+
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
6
|
+
const { getTransformers } = require('../transformUtilsNew');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
8
|
+
const { makeMessageFunction } = require('../../base/messages');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* - Generate artificial draft fields if requested
|
|
12
|
+
*
|
|
13
|
+
* - Check associations for:
|
|
14
|
+
* - exposed associations do not point to non-exposed targets
|
|
15
|
+
* - structured types must not contain associations for OData V2
|
|
16
|
+
* - Element must not be an 'array of' for OData V2 TODO: move to the validator
|
|
17
|
+
* - Perform checks for exposed non-abstract entities and views - check media type and key-ness
|
|
18
|
+
*
|
|
19
|
+
* @param {CSN.Model} csn
|
|
20
|
+
* @param {CSN.Options} options
|
|
21
|
+
* @param {Array} [services] Will be calculated JIT if not provided
|
|
22
|
+
* @returns {CSN.Model} Returns the transformed input model
|
|
23
|
+
* @todo should be done by the compiler - Check associations for valid foreign keys
|
|
24
|
+
* @todo check if needed at all: Remove '$projection' from paths in the element's ON-condition
|
|
25
|
+
*/
|
|
26
|
+
function generateDrafts(csn, options, services) {
|
|
27
|
+
const {
|
|
28
|
+
createForeignKeyElement,
|
|
29
|
+
createAndAddDraftAdminDataProjection, createScalarElement,
|
|
30
|
+
createAssociationElement, createAssociationPathComparison,
|
|
31
|
+
addElement, createAction, assignAction,
|
|
32
|
+
resetAnnotation,
|
|
33
|
+
} = getTransformers(csn, options);
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
getFinalType,
|
|
37
|
+
getServiceName,
|
|
38
|
+
hasAnnotationValue,
|
|
39
|
+
getFinalBaseType,
|
|
40
|
+
getFinalTypeDef,
|
|
41
|
+
} = getUtils(csn);
|
|
42
|
+
|
|
43
|
+
const { error, info } = makeMessageFunction(csn, options, 'for.odata');
|
|
44
|
+
|
|
45
|
+
if (!services)
|
|
46
|
+
services = getServiceNames(csn);
|
|
47
|
+
|
|
48
|
+
const visitedArtifacts = Object.create(null);
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
|
|
53
|
+
|
|
54
|
+
forEachDefinition(csn, (def, defName) => {
|
|
55
|
+
// Generate artificial draft fields for entities/views if requested, ignore if not part of a service
|
|
56
|
+
if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
|
|
57
|
+
generateDraftForOdata(def, defName, def);
|
|
58
|
+
|
|
59
|
+
visitedArtifacts[defName] = true;
|
|
60
|
+
}, { skipArtifact: isExternalServiceMember });
|
|
61
|
+
|
|
62
|
+
return csn;
|
|
63
|
+
/**
|
|
64
|
+
* Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
|
|
65
|
+
* into its transitively reachable composition targets, and into the model.
|
|
66
|
+
* 'rootArtifact' is the root artifact where composition traversal started.
|
|
67
|
+
*
|
|
68
|
+
* Constraints
|
|
69
|
+
* Draft Root: Exactly one PK of type UUID
|
|
70
|
+
* Draft Node: One PK of type UUID + 0..1 PK of another type
|
|
71
|
+
* Draft Node: Must not be reachable from multiple draft roots
|
|
72
|
+
*
|
|
73
|
+
* @param {CSN.Artifact} artifact
|
|
74
|
+
* @param {string} artifactName
|
|
75
|
+
* @param {CSN.Artifact} rootArtifact artifact where composition traversal started
|
|
76
|
+
*/
|
|
77
|
+
function generateDraftForOdata(artifact, artifactName, rootArtifact) {
|
|
78
|
+
// Sanity check
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
if (!isArtifactInSomeService(artifactName, services))
|
|
81
|
+
throw new ModelError(`Expecting artifact to be part of a service: ${JSON.stringify(artifact)}`);
|
|
82
|
+
|
|
83
|
+
// Nothing to do if already draft-enabled (composition traversal may have circles)
|
|
84
|
+
if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction']) &&
|
|
85
|
+
artifact.actions && artifact.actions.draftPrepare)
|
|
86
|
+
return;
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
90
|
+
// @ts-ignore
|
|
91
|
+
const draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
|
|
92
|
+
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
93
|
+
if (!draftAdminDataProjection) {
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
|
|
96
|
+
}
|
|
97
|
+
// Report an error if it is not an entity or not what we expect
|
|
98
|
+
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements.DraftUUID) {
|
|
99
|
+
error(null, [ 'definitions', draftAdminDataProjectionName ], { name: draftAdminDataProjectionName },
|
|
100
|
+
'Generated entity $(NAME) conflicts with existing artifact');
|
|
101
|
+
}
|
|
102
|
+
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
|
|
103
|
+
if (artifact === rootArtifact) {
|
|
104
|
+
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, [ 'definitions', draftAdminDataProjectionName ]);
|
|
105
|
+
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, [ 'definitions', draftAdminDataProjectionName ]);
|
|
106
|
+
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, [ 'definitions', draftAdminDataProjectionName ]);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, [ 'definitions', draftAdminDataProjectionName ]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Object.values(artifact.elements || {}).forEach( (elem) => {
|
|
113
|
+
// Make all non-key elements nullable
|
|
114
|
+
if (elem.notNull && elem.key !== true)
|
|
115
|
+
delete elem.notNull;
|
|
116
|
+
});
|
|
117
|
+
// Generate the additional elements into the draft-enabled artifact
|
|
118
|
+
|
|
119
|
+
// key IsActiveEntity : Boolean default true
|
|
120
|
+
const isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true, false);
|
|
121
|
+
isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
|
|
122
|
+
addElement(isActiveEntity, artifact, artifactName);
|
|
123
|
+
|
|
124
|
+
// HasActiveEntity : Boolean default false
|
|
125
|
+
const hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false, true);
|
|
126
|
+
hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
|
|
127
|
+
addElement(hasActiveEntity, artifact, artifactName);
|
|
128
|
+
|
|
129
|
+
// HasDraftEntity : Boolean default false;
|
|
130
|
+
const hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false, true);
|
|
131
|
+
hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
|
|
132
|
+
addElement(hasDraftEntity, artifact, artifactName);
|
|
133
|
+
|
|
134
|
+
// @odata.contained: true
|
|
135
|
+
// DraftAdministrativeData : Association to one DraftAdministrativeData;
|
|
136
|
+
const draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
|
|
137
|
+
draftAdministrativeData.DraftAdministrativeData.cardinality = { max: 1 };
|
|
138
|
+
draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
|
|
139
|
+
draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
|
|
140
|
+
addElement(draftAdministrativeData, artifact, artifactName);
|
|
141
|
+
|
|
142
|
+
// Note that we need to do the ODATA transformation steps for managed associations
|
|
143
|
+
// (foreign key field generation, generatedFieldName) by hand, because the corresponding
|
|
144
|
+
// transformation steps have already been done on all artifacts when we come here)
|
|
145
|
+
let uuidDraftKey = draftAdministrativeData.DraftAdministrativeData.keys.filter(key => key.ref && key.ref.length === 1 && key.ref[0] === 'DraftUUID');
|
|
146
|
+
if (uuidDraftKey && uuidDraftKey[0]) {
|
|
147
|
+
uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
|
|
148
|
+
const path = [ 'definitions', artifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
|
|
149
|
+
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName, path);
|
|
150
|
+
}
|
|
151
|
+
// SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)
|
|
152
|
+
const siblingEntity = createAssociationElement('SiblingEntity', artifactName, false);
|
|
153
|
+
siblingEntity.SiblingEntity.cardinality = { max: 1 };
|
|
154
|
+
addElement(siblingEntity, artifact, artifactName);
|
|
155
|
+
// ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
|
|
156
|
+
siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
|
|
157
|
+
|
|
158
|
+
// Iterate elements
|
|
159
|
+
// TODO: Iterative vs recursive? What is more likely: Super deep nesting or cycles via malicious CSN?
|
|
160
|
+
if (artifact.elements) {
|
|
161
|
+
// No need to reverse the stack, not order dependent
|
|
162
|
+
const stack = [ artifact ];
|
|
163
|
+
while (stack.length > 0) {
|
|
164
|
+
const { elements } = stack.pop();
|
|
165
|
+
forEach(elements, (elemName, elem) => {
|
|
166
|
+
if (elemName !== 'IsActiveEntity' && elem.key) {
|
|
167
|
+
// Amend the ON-condition above:
|
|
168
|
+
// ... and SiblingEntity.<keyfield> = <keyfield> ... (for all key fields except 'IsActiveEntity')
|
|
169
|
+
const cond = createAssociationPathComparison('SiblingEntity', elemName, '=', elemName);
|
|
170
|
+
cond.push('and');
|
|
171
|
+
cond.push(...siblingEntity.SiblingEntity.on);
|
|
172
|
+
siblingEntity.SiblingEntity.on = cond;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Draft-enable the targets of composition elements (draft nodes), too
|
|
176
|
+
// TODO rewrite
|
|
177
|
+
if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
|
|
178
|
+
const draftNode = csn.definitions[elem.target];
|
|
179
|
+
|
|
180
|
+
// Ignore if that is our own draft root
|
|
181
|
+
if (draftNode !== rootArtifact) {
|
|
182
|
+
// Report error when the draft node has @odata.draft.enabled itself
|
|
183
|
+
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
|
|
184
|
+
error('ref-unexpected-draft-enabled', [ 'definitions', artifactName, 'elements', elemName ], { anno: '@odata.draft.enabled' });
|
|
185
|
+
}
|
|
186
|
+
// Ignore composition if it's target is not part of a service or explicitly draft disabled
|
|
187
|
+
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
// Generate draft stuff into the target
|
|
192
|
+
generateDraftForOdata(draftNode, elem.target, rootArtifact);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if (elem.elements) { // anonymous structure
|
|
197
|
+
stack.push(elem);
|
|
198
|
+
}
|
|
199
|
+
else if (elem.type) { // types - possibly structured
|
|
200
|
+
const typeDef = elem.type.ref ? getFinalBaseType(elem.type) : getFinalTypeDef(elem.type);
|
|
201
|
+
if (typeDef.elements)
|
|
202
|
+
stack.push(typeDef);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
210
|
+
|
|
211
|
+
// action draftPrepare (SideEffectsQualifier: String) return <artifact>;
|
|
212
|
+
const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
213
|
+
assignAction(draftPrepare, artifact);
|
|
214
|
+
|
|
215
|
+
if (artifact === rootArtifact) {
|
|
216
|
+
// action draftActivate() return <artifact>;
|
|
217
|
+
const draftActivate = createAction('draftActivate', artifactName);
|
|
218
|
+
assignAction(draftActivate, artifact);
|
|
219
|
+
|
|
220
|
+
// action draftEdit (PreserveChanges: Boolean) return <artifact>;
|
|
221
|
+
const draftEdit = createAction('draftEdit', artifactName, 'PreserveChanges', 'cds.Boolean');
|
|
222
|
+
assignAction(draftEdit, artifact);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = generateDrafts;
|