@sap/cds-compiler 3.5.4 → 3.6.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 +56 -2
- package/bin/cdsc.js +14 -6
- package/doc/CHANGELOG_ARCHIVE.md +10 -10
- package/doc/CHANGELOG_DEPRECATED.md +2 -2
- package/lib/api/main.js +32 -55
- package/lib/api/validate.js +5 -0
- package/lib/base/message-registry.js +104 -32
- package/lib/base/messages.js +277 -212
- package/lib/base/optionProcessorHelper.js +9 -2
- package/lib/base/shuffle.js +50 -0
- package/lib/checks/actionsFunctions.js +37 -20
- package/lib/checks/foreignKeys.js +13 -6
- package/lib/checks/nonexpandableStructured.js +1 -2
- package/lib/checks/onConditions.js +21 -19
- package/lib/checks/parameters.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -0
- package/lib/checks/types.js +16 -22
- package/lib/compiler/assert-consistency.js +31 -28
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +72 -63
- package/lib/compiler/define.js +396 -314
- package/lib/compiler/extend.js +55 -49
- package/lib/compiler/index.js +5 -0
- package/lib/compiler/populate.js +28 -11
- package/lib/compiler/propagator.js +2 -1
- package/lib/compiler/resolve.js +28 -13
- package/lib/compiler/shared.js +15 -10
- package/lib/compiler/utils.js +7 -7
- package/lib/edm/annotations/genericTranslation.js +51 -46
- package/lib/edm/annotations/preprocessAnnotations.js +37 -40
- package/lib/edm/csn2edm.js +69 -21
- package/lib/edm/edm.js +2 -2
- package/lib/edm/edmInboundChecks.js +6 -8
- package/lib/edm/edmPreprocessor.js +88 -80
- package/lib/edm/edmUtils.js +6 -15
- package/lib/gen/Dictionary.json +81 -13
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4680 -4484
- package/lib/inspect/inspectModelStatistics.js +2 -1
- package/lib/inspect/inspectPropagation.js +2 -1
- package/lib/json/from-csn.js +131 -78
- package/lib/json/to-csn.js +39 -23
- package/lib/language/antlrParser.js +0 -3
- package/lib/language/docCommentParser.js +7 -3
- package/lib/language/errorStrategy.js +3 -2
- package/lib/language/genericAntlrParser.js +96 -41
- package/lib/language/language.g4 +112 -128
- package/lib/language/multiLineStringParser.js +2 -1
- package/lib/main.d.ts +115 -2
- package/lib/main.js +16 -3
- package/lib/model/csnRefs.js +3 -3
- package/lib/model/csnUtils.js +109 -179
- package/lib/model/enrichCsn.js +13 -8
- package/lib/model/revealInternalProperties.js +4 -3
- package/lib/optionProcessor.js +19 -3
- package/lib/render/manageConstraints.js +11 -15
- package/lib/render/toCdl.js +144 -47
- package/lib/render/toHdbcds.js +22 -22
- package/lib/render/toRename.js +3 -4
- package/lib/render/toSql.js +29 -20
- package/lib/render/utils/delta.js +3 -1
- package/lib/render/utils/sql.js +2 -14
- package/lib/transform/db/associations.js +6 -6
- package/lib/transform/db/cdsPersistence.js +3 -3
- package/lib/transform/db/constraints.js +4 -6
- package/lib/transform/db/expansion.js +4 -4
- package/lib/transform/db/flattening.js +12 -15
- package/lib/transform/db/temporal.js +4 -3
- package/lib/transform/db/transformExists.js +2 -1
- package/lib/transform/draft/db.js +7 -7
- package/lib/transform/forOdataNew.js +15 -4
- package/lib/transform/forRelationalDB.js +53 -39
- package/lib/transform/odata/toFinalBaseType.js +106 -82
- package/lib/transform/odata/typesExposure.js +26 -17
- package/lib/transform/odata/utils.js +1 -1
- package/lib/transform/parseExpr.js +1 -1
- package/lib/transform/transformUtilsNew.js +33 -10
- package/lib/transform/translateAssocsToJoins.js +8 -7
- package/lib/transform/universalCsn/coreComputed.js +7 -5
- package/lib/transform/universalCsn/universalCsnEnricher.js +12 -4
- package/lib/utils/timetrace.js +2 -2
- package/package.json +1 -2
|
@@ -70,6 +70,7 @@ module.exports = { transform4odataWithCsn };
|
|
|
70
70
|
|
|
71
71
|
function transform4odataWithCsn(inputModel, options) {
|
|
72
72
|
timetrace.start('OData transformation');
|
|
73
|
+
|
|
73
74
|
// copy the model as we don't want to change the input model
|
|
74
75
|
let csn = cloneCsnNonDict(inputModel, options);
|
|
75
76
|
|
|
@@ -125,6 +126,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
125
126
|
|
|
126
127
|
addLocalizationViews(csn, options, { acceptLocalizedView, ignoreUnknownExtensions: true });
|
|
127
128
|
|
|
129
|
+
// replace all type refs to builtin types with direct type
|
|
130
|
+
transformUtils.rewriteBuiltinTypeRef(csn);
|
|
131
|
+
|
|
128
132
|
const cleanup = validate.forOdata(csn, {
|
|
129
133
|
message, error, warning, info, inspectRef, effectiveType, getFinalBaseTypeWithProps, artifactRef, csn, options, csnUtils, services, isAspect, isExternalServiceMember
|
|
130
134
|
});
|
|
@@ -148,7 +152,12 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
148
152
|
{ skipArtifact: isExternalServiceMember }
|
|
149
153
|
);
|
|
150
154
|
|
|
151
|
-
|
|
155
|
+
// All type refs must be resolved, including external APIs.
|
|
156
|
+
// OData has no 'type of' so 'real' imported OData APIs marked @cds.external are safe.
|
|
157
|
+
// If in the future 'other' APIs that might support type refs are imported, these refs must be
|
|
158
|
+
// resolved here, as this is the OData transformation and sets the foundation for subsequent EDM
|
|
159
|
+
// rendering which may has to publish external definitions
|
|
160
|
+
expandToFinalBaseType(csn, transformers, csnUtils, services, options);
|
|
152
161
|
|
|
153
162
|
// Check if structured elements and managed associations are compared in an expression
|
|
154
163
|
// and expand these structured elements. This tuple expansion allows all other
|
|
@@ -157,7 +166,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
157
166
|
expandStructsInExpression(csn, { skipArtifact: isExternalServiceMember, drillRef: true });
|
|
158
167
|
|
|
159
168
|
if (!structuredOData) {
|
|
160
|
-
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, { skipArtifact: isExternalServiceMember });
|
|
169
|
+
expansion.expandStructureReferences(csn, options, '_', { error, info, throwWithAnyError }, csnUtils, { skipArtifact: isExternalServiceMember });
|
|
161
170
|
const resolved = new WeakMap();
|
|
162
171
|
// No refs with struct-steps exist anymore
|
|
163
172
|
flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
|
|
@@ -177,13 +186,13 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
177
186
|
|
|
178
187
|
// TODO: add the generated foreign keys to the columns when we are in a view
|
|
179
188
|
// see db/views.js::addForeignKeysToColumns
|
|
180
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, { skipArtifact: isExternalServiceMember });
|
|
189
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
|
|
181
190
|
|
|
182
191
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
183
192
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
184
193
|
// since then the foreign keys of the managed assocs are part of the elements
|
|
185
194
|
if(!structuredOData)
|
|
186
|
-
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, '_'));
|
|
195
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, '_'));
|
|
187
196
|
|
|
188
197
|
// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
|
|
189
198
|
throwWithAnyError();
|
|
@@ -221,6 +230,8 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
221
230
|
def['@cds.persistence.name'] = getArtifactDatabaseNameOf(defName, options.sqlMapping, csn, 'hana'); // hana to allow naming mode "hdbcds"
|
|
222
231
|
|
|
223
232
|
forEachMemberRecursively(def, (member, memberName, propertyName) => {
|
|
233
|
+
if (memberName === '' && propertyName === 'params')
|
|
234
|
+
return; // ignore "returns" type
|
|
224
235
|
// Annotate elements, foreign keys, parameters, etc. with their DB names if requested
|
|
225
236
|
// Only these are actually required and don't annotate virtual elements in entities or types
|
|
226
237
|
// as they have no DB representation (although in views)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const {
|
|
4
|
+
const { cloneCsnNonDict,
|
|
5
5
|
forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
|
|
6
6
|
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
7
7
|
isAspect, walkCsnPath, isPersistedOnDatabase,
|
|
@@ -113,11 +113,26 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
113
113
|
|
|
114
114
|
const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
|
|
115
115
|
|
|
116
|
+
let csnUtils;
|
|
116
117
|
let message, error, warning, info; // message functions
|
|
117
118
|
/** @type {() => void} */
|
|
118
119
|
let throwWithAnyError;
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
// csnUtils
|
|
121
|
+
let artifactRef,
|
|
122
|
+
inspectRef,
|
|
123
|
+
effectiveType,
|
|
124
|
+
get$combined,
|
|
125
|
+
getCsnDef,
|
|
126
|
+
isAssocOrComposition,
|
|
127
|
+
addStringAnnotationTo,
|
|
128
|
+
cloneWithTransformations;
|
|
129
|
+
// transformUtils
|
|
130
|
+
let addDefaultTypeFacets,
|
|
131
|
+
expandStructsInExpression,
|
|
132
|
+
flattenStructuredElement,
|
|
133
|
+
flattenStructStepsInRef,
|
|
134
|
+
isAssociationOperand,
|
|
135
|
+
isDollarSelfOrProjectionOperand;
|
|
121
136
|
|
|
122
137
|
bindCsnReference();
|
|
123
138
|
|
|
@@ -133,10 +148,13 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
133
148
|
if (!doA2J)
|
|
134
149
|
forEachDefinition(csn, handleMixinOnConditions);
|
|
135
150
|
|
|
151
|
+
// replace all type refs to builtin types with direct type
|
|
152
|
+
transformUtils.rewriteBuiltinTypeRef(csn);
|
|
153
|
+
|
|
136
154
|
timetrace.start('Validate');
|
|
137
155
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
138
156
|
const cleanup = validate.forRelationalDB(csn, {
|
|
139
|
-
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils
|
|
157
|
+
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils, csn, options, isAspect
|
|
140
158
|
});
|
|
141
159
|
timetrace.stop('Validate');
|
|
142
160
|
|
|
@@ -161,7 +179,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
161
179
|
timetrace.start('temporal');
|
|
162
180
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
163
181
|
// assoc2join eventually rewrites the table aliases
|
|
164
|
-
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
|
|
182
|
+
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}, csnUtils));
|
|
165
183
|
timetrace.stop('temporal');
|
|
166
184
|
|
|
167
185
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
@@ -169,7 +187,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
169
187
|
|
|
170
188
|
if(doA2J) {
|
|
171
189
|
// Expand a structured thing in: keys, columns, order by, group by
|
|
172
|
-
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError});
|
|
190
|
+
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithAnyError}, csnUtils);
|
|
173
191
|
bindCsnReference();
|
|
174
192
|
}
|
|
175
193
|
|
|
@@ -220,20 +238,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
220
238
|
}
|
|
221
239
|
});
|
|
222
240
|
|
|
223
|
-
const {
|
|
224
|
-
flattenStructuredElement,
|
|
225
|
-
flattenStructStepsInRef,
|
|
226
|
-
isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
227
|
-
csnUtils,
|
|
228
|
-
} = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
229
|
-
|
|
230
|
-
const {
|
|
231
|
-
getCsnDef,
|
|
232
|
-
isAssocOrComposition,
|
|
233
|
-
addStringAnnotationTo,
|
|
234
|
-
cloneWithTransformations,
|
|
235
|
-
} = csnUtils;
|
|
236
|
-
|
|
237
241
|
timetrace.start('Transform CSN')
|
|
238
242
|
|
|
239
243
|
// (000) Rename primitive types, make UUID a String
|
|
@@ -260,11 +264,11 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
260
264
|
forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
|
|
261
265
|
|
|
262
266
|
// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
|
|
263
|
-
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
267
|
+
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
264
268
|
|
|
265
269
|
doA2J && forEachDefinition(csn, flattenIndexes);
|
|
266
270
|
// Managed associations get an on-condition - in views and entities
|
|
267
|
-
doA2J && associations.attachOnConditions(csn, pathDelimiter);
|
|
271
|
+
doA2J && associations.attachOnConditions(csn, csnUtils, pathDelimiter);
|
|
268
272
|
|
|
269
273
|
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
270
274
|
// and make them entities
|
|
@@ -274,7 +278,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
274
278
|
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
275
279
|
// are part of the elements
|
|
276
280
|
if (doA2J)
|
|
277
|
-
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, pathDelimiter));
|
|
281
|
+
forEachDefinition(csn, associations.getFKAccessFinalizer(csn, csnUtils, pathDelimiter));
|
|
278
282
|
|
|
279
283
|
// Create convenience views for localized entities/views.
|
|
280
284
|
// To be done after getFKAccessFinalizer because associations are
|
|
@@ -330,7 +334,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
330
334
|
assertUnique.rewrite(csn, options, pathDelimiter);
|
|
331
335
|
|
|
332
336
|
// Associations that point to things marked with @cds.persistence.skip are removed
|
|
333
|
-
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
|
|
337
|
+
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}, csnUtils));
|
|
334
338
|
|
|
335
339
|
// Apply view-specific transformations
|
|
336
340
|
// (160) Projections now finally become views
|
|
@@ -424,9 +428,25 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
424
428
|
|
|
425
429
|
function bindCsnReference(){
|
|
426
430
|
({ error, warning, info, message, throwWithAnyError } = makeMessageFunction(csn, options, moduleName));
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
431
|
+
|
|
432
|
+
({ flattenStructuredElement,
|
|
433
|
+
flattenStructStepsInRef,
|
|
434
|
+
isAssociationOperand,
|
|
435
|
+
isDollarSelfOrProjectionOperand,
|
|
436
|
+
addDefaultTypeFacets,
|
|
437
|
+
expandStructsInExpression,
|
|
438
|
+
csnUtils
|
|
439
|
+
} = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
440
|
+
|
|
441
|
+
({ artifactRef,
|
|
442
|
+
inspectRef,
|
|
443
|
+
effectiveType,
|
|
444
|
+
get$combined,
|
|
445
|
+
getCsnDef,
|
|
446
|
+
isAssocOrComposition,
|
|
447
|
+
addStringAnnotationTo,
|
|
448
|
+
cloneWithTransformations
|
|
449
|
+
} = csnUtils);
|
|
430
450
|
}
|
|
431
451
|
|
|
432
452
|
function bindCsnReferenceOnly(){
|
|
@@ -445,7 +465,7 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
445
465
|
.filter((c) => {
|
|
446
466
|
return c.ref && c.ref.length > 1;
|
|
447
467
|
})
|
|
448
|
-
.
|
|
468
|
+
.forEach((usedAssoc) => {
|
|
449
469
|
const assocName = pathId(usedAssoc.ref[0]);
|
|
450
470
|
const mixinAssociation = mixin[assocName];
|
|
451
471
|
if(mixinAssociation){
|
|
@@ -528,6 +548,8 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
528
548
|
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.sqlMapping, csn, options.sqlDialect), artifact);
|
|
529
549
|
|
|
530
550
|
forEachMemberRecursively(artifact, (member, memberName, property, path) => {
|
|
551
|
+
if (memberName === '' && property === 'params')
|
|
552
|
+
return; // ignore "returns" type
|
|
531
553
|
transformCommon(member, memberName, path);
|
|
532
554
|
// (240 b) Annotate elements, foreign keys, parameters etc with their DB names
|
|
533
555
|
// Virtual elements in entities and types are not annotated, as they have no DB representation.
|
|
@@ -874,9 +896,9 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
874
896
|
// a backlink association element from an entity, the forward link will point to the entity,
|
|
875
897
|
// not to the view).
|
|
876
898
|
// FIXME: This also means that corresponding key fields should be in the select list etc ...
|
|
877
|
-
if (!art.query && !art.projection && assoc.target && assoc.target
|
|
878
|
-
error(null, path, {
|
|
879
|
-
|
|
899
|
+
if (!art.query && !art.projection && assoc.target && assoc.target !== artifactName)
|
|
900
|
+
error( null, path, { id: '$self', name: artifactName, target: assoc.target },
|
|
901
|
+
'Expected association using $(ID) to point back to $(NAME) but found $(TARGET)' );
|
|
880
902
|
|
|
881
903
|
// Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
|
|
882
904
|
if (assoc.on) {
|
|
@@ -997,14 +1019,6 @@ function transformForRelationalDBWithCsn(inputModel, options, moduleName) {
|
|
|
997
1019
|
function _check(node, nodeName, model, path) {
|
|
998
1020
|
if (node.type) {
|
|
999
1021
|
const absolute = node.type;
|
|
1000
|
-
const parameters = node.parameters || [];
|
|
1001
|
-
// :FIXME: Is this dead code? node.parameters is always undefined...
|
|
1002
|
-
// forRelationalDB tested against the parameters of the type definition which is not available in CSN
|
|
1003
|
-
for (const name in parameters) {
|
|
1004
|
-
const param = parameters[name];
|
|
1005
|
-
if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
|
|
1006
|
-
error('missing-type-parameter', path, { name: param, id: absolute, $reviewed: false });
|
|
1007
|
-
}
|
|
1008
1022
|
switch (absolute) {
|
|
1009
1023
|
case 'cds.String':
|
|
1010
1024
|
case 'cds.Binary':
|
|
@@ -9,6 +9,7 @@ const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
|
9
9
|
|
|
10
10
|
function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
|
|
11
11
|
const isV4 = options.odataVersion === 'v4';
|
|
12
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
12
13
|
forEachDefinition(csn, (def, defName) => {
|
|
13
14
|
// Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
|
|
14
15
|
forEachMemberRecursively(def, (member) => {
|
|
@@ -100,109 +101,132 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
100
101
|
// EDMX at the moment and the reference in the OData CSN is fulfilled.
|
|
101
102
|
if (node.kind === 'event') return;
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// type
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
104
|
+
if(node.type && !isBuiltinType(node.type)) {
|
|
105
|
+
const finalBaseType = csnUtils.getFinalBaseTypeWithProps(node.type);
|
|
106
|
+
if(!finalBaseType) {
|
|
107
|
+
/*
|
|
108
|
+
type could not be resolved, set it to null
|
|
109
|
+
Today, all type refs must be resolvable,
|
|
110
|
+
input validations checkTypeDefinitionHasType, checkElementTypeDefinitionHasType
|
|
111
|
+
guarantee this. In the future this may change.
|
|
112
|
+
*/
|
|
113
|
+
node.type = null;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
if (isExpandable(finalBaseType) || node.kind === 'type') {
|
|
117
|
+
// 1. Get the final type of the node (resolve derived type chain)
|
|
118
|
+
if (finalBaseType.type !== special$self) {
|
|
119
|
+
// The type replacement depends on whether 'node' is a definition or a member[element].
|
|
120
|
+
if (node.kind) {
|
|
121
|
+
/*
|
|
122
|
+
It is a definition and we expand to builtin type and to elements
|
|
123
|
+
type T: S; --> Integer;
|
|
124
|
+
type S: X; --> Integer;
|
|
125
|
+
type X: Integer;
|
|
126
|
+
|
|
127
|
+
type A: B; -> {...}
|
|
128
|
+
type B: C; -> { ... }
|
|
129
|
+
type C { .... };
|
|
130
|
+
*/
|
|
131
|
+
if (isBuiltinType(finalBaseType.type)) {
|
|
132
|
+
/*
|
|
133
|
+
use transformUtilsNew::toFinalBaseType for the moment,
|
|
134
|
+
as it is collects along the chain of types
|
|
135
|
+
attributes that need to be propagated
|
|
136
|
+
enum, length, scale, etc.
|
|
137
|
+
*/
|
|
138
|
+
transformers.toFinalBaseType(node);
|
|
139
|
+
}
|
|
140
|
+
else if (csnUtils.isStructured(finalBaseType)) {
|
|
141
|
+
cloneElements(finalBaseType);
|
|
142
|
+
}
|
|
143
|
+
else if (node.type && node.items)
|
|
144
|
+
delete node.type;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
/*
|
|
148
|
+
this is a member and we expand to final base only if builtin
|
|
149
|
+
type T: S; --> Integer;
|
|
150
|
+
type S: X; --> Integer;
|
|
151
|
+
type X: Integer;
|
|
152
|
+
|
|
153
|
+
type {
|
|
154
|
+
struct_elt: many A; ---> stays the same
|
|
155
|
+
scalar_elt: T; ---> Integer;
|
|
156
|
+
type_ref_elt: type of struct_elt;
|
|
157
|
+
};
|
|
158
|
+
type A: B; -> {...}
|
|
159
|
+
type B: C; -> { ... }
|
|
160
|
+
type C { .... };
|
|
161
|
+
*/
|
|
162
|
+
if (isBuiltinType(finalBaseType.type)) {
|
|
163
|
+
/*
|
|
164
|
+
use transformUtilsNew::toFinalBaseType for the moment,
|
|
165
|
+
as it is collects along the chain of types
|
|
166
|
+
attributes that need to be propagated
|
|
167
|
+
enum, length, scale, etc.
|
|
168
|
+
*/
|
|
169
|
+
transformers.toFinalBaseType(node);
|
|
170
|
+
// node.type = finalType;
|
|
171
|
+
}
|
|
172
|
+
else if (node.type && node.type.ref) {
|
|
173
|
+
cloneElements(finalBaseType);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
128
176
|
}
|
|
129
|
-
else if (node.type && node.items)
|
|
130
|
-
delete node.type;
|
|
131
177
|
}
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// type_ref_elt: type of struct_elt;
|
|
142
|
-
// };
|
|
143
|
-
// type A: B; -> {...}
|
|
144
|
-
// type B: C; -> { ... }
|
|
145
|
-
// type C { .... };
|
|
146
|
-
if (isBuiltinType(finalType.type)) {
|
|
147
|
-
// use transformUtilsNew::toFinalBaseType for the moment,
|
|
148
|
-
// as it is collects along the chain of types
|
|
149
|
-
// attributes that need to be propagated
|
|
150
|
-
// enum, length, scale, etc.
|
|
151
|
-
transformers.toFinalBaseType(node);
|
|
152
|
-
// node.type = finalType;
|
|
153
|
-
}
|
|
154
|
-
else if (node.type && node.type.ref) {
|
|
155
|
-
cloneElements(finalType);
|
|
178
|
+
if (/*the resolved type is not built in*/ !isBuiltinType(node.type)) {
|
|
179
|
+
// handle array of defined via a named type
|
|
180
|
+
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
|
|
181
|
+
const currService = csnUtils.getServiceName(defName);
|
|
182
|
+
const isArrayOfBuiltin = finalBaseType.items &&
|
|
183
|
+
isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalBaseType.items.type)?.type)
|
|
184
|
+
if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
|
|
185
|
+
node.items = finalBaseType.items;
|
|
186
|
+
delete node.type;
|
|
156
187
|
}
|
|
157
188
|
}
|
|
158
189
|
}
|
|
159
190
|
}
|
|
160
|
-
if (node.type && !isBuiltinType(node.type)) {
|
|
161
|
-
// handle array of defined via a named type
|
|
162
|
-
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
|
|
163
|
-
const currService = csnUtils.getServiceName(defName);
|
|
164
|
-
const finalType = csnUtils.getFinalBaseTypeWithProps(node.type);
|
|
165
|
-
const isArrayOfBuiltin = finalType?.items && isBuiltinType(csnUtils.getFinalBaseTypeWithProps(finalType.items.type)?.type)
|
|
166
|
-
if (isArrayOfBuiltin && (!isArtifactInService(node.type, currService) || !isV4)) {
|
|
167
|
-
node.items = finalType.items;
|
|
168
|
-
delete node.type;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
191
|
|
|
172
|
-
function cloneElements(
|
|
192
|
+
function cloneElements(finalBaseType) {
|
|
173
193
|
// cloneCsn only works correctly if we start "from the top"
|
|
174
194
|
let clone;
|
|
175
195
|
// do the clone only if really needed
|
|
176
|
-
if((
|
|
177
|
-
(
|
|
178
|
-
clone = cloneCsnNonDict({ definitions: { 'TypeDef':
|
|
179
|
-
if (
|
|
196
|
+
if((finalBaseType.items && !node.items) ||
|
|
197
|
+
(finalBaseType.elements && !node.elements))
|
|
198
|
+
clone = cloneCsnNonDict({ definitions: { 'TypeDef': finalBaseType } }, options);
|
|
199
|
+
if (finalBaseType.items) {
|
|
180
200
|
delete node.type;
|
|
181
201
|
if(!node.items)
|
|
182
202
|
Object.assign(node, { items: clone.definitions.TypeDef.items });
|
|
183
203
|
}
|
|
184
|
-
if (
|
|
185
|
-
if(!
|
|
204
|
+
if (finalBaseType.elements) {
|
|
205
|
+
if(!finalBaseType.items)
|
|
186
206
|
delete node.type;
|
|
187
207
|
if(!node.elements)
|
|
188
208
|
Object.assign(node, { elements: clone.definitions.TypeDef.elements });
|
|
189
209
|
}
|
|
190
210
|
}
|
|
191
|
-
}
|
|
192
211
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
212
|
+
/*
|
|
213
|
+
Check, if a type needs to be expanded into the service
|
|
196
214
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
215
|
+
All types are expansion candidates except these in V4:
|
|
216
|
+
- it's a builtin
|
|
217
|
+
- it's an assoc
|
|
218
|
+
- the referred type is defined in the service
|
|
219
|
+
*/
|
|
220
|
+
function isExpandable(finalBaseType) {
|
|
221
|
+
// in V4 we should use TypeDefinitions whenever possible, thus in case the final type of a field is
|
|
222
|
+
// a builtin from the service - do not expand to the final base type
|
|
223
|
+
|
|
224
|
+
const currService = csnUtils.getServiceName(defName);
|
|
225
|
+
const isBuiltin = isBuiltinType(finalBaseType.type);
|
|
226
|
+
const isAssoc = node.target;
|
|
227
|
+
const isInCurServ = isArtifactInService(node.type, currService);
|
|
228
|
+
return !isV4 || !(isBuiltin && !isAssoc && isInCurServ);
|
|
229
|
+
}
|
|
206
230
|
}
|
|
207
231
|
}
|
|
208
232
|
|
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
const { setProp } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
11
|
const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
|
|
12
|
-
const { copyAnnotations } = require('../../model/csnUtils');
|
|
12
|
+
const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
|
|
13
13
|
const { isBetaEnabled } = require('../../base/model.js');
|
|
14
|
+
const { CompilerAssertion } = require('../../base/error');
|
|
14
15
|
|
|
15
16
|
function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackSchemaName, options, csnUtils, message) {
|
|
16
17
|
const { error } = message;
|
|
18
|
+
const special$self = !csn?.definitions?.$self && '$self';
|
|
17
19
|
// are we working with OData proxies or cross-service refs
|
|
18
20
|
const isMultiSchema = options.odataVersion === 'v4' && (options.odataProxies || options.odataXServiceRefs);
|
|
19
21
|
// collect in this variable all the newly exposed types
|
|
@@ -163,7 +165,7 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
163
165
|
const { isExposable, typeDef, typeName } = exposeTypeOf(newElem, isKey, elemName, defName, serviceName,
|
|
164
166
|
getNewTypeName(newElem, elemName, newTypeName, serviceName), path, fullQualifiedNewTypeName, isTermDef);
|
|
165
167
|
// if the type for the newElem was not exposed it may be a scalar type def from an external service that hasn't
|
|
166
|
-
// been
|
|
168
|
+
// been caught by expandToFinalBaseType() (forODataNew must not modify external imported services)
|
|
167
169
|
if(!isExposable && isBuiltinType(typeName) && !isBuiltinType((newElem.items?.type || newElem.type))) {
|
|
168
170
|
if(typeDef.items) {
|
|
169
171
|
newElem.items = typeDef.items;
|
|
@@ -214,16 +216,19 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
214
216
|
}
|
|
215
217
|
return { isExposable, typeDef, typeName, isAnonymous };
|
|
216
218
|
|
|
217
|
-
|
|
219
|
+
/**
|
|
218
220
|
* Check if the node's type can be exposed:
|
|
219
|
-
* 1) If it's an anonymous structured type (items.elements || elements)
|
|
221
|
+
* 1) If it's an anonymous, structured type (items.elements || elements)
|
|
220
222
|
* 2) If it's a named type resolve to the final type definition and
|
|
221
223
|
* check if this is a structured type
|
|
224
|
+
*
|
|
222
225
|
* Returns an object that indicates
|
|
223
|
-
* -
|
|
224
|
-
* -
|
|
225
|
-
* -
|
|
226
|
+
* - `isExposable`: whether the type needs exposure
|
|
227
|
+
* - `elements`: dictionary that needs to be cloned
|
|
228
|
+
* - `typeDef`: either the resolved type def or the node itself
|
|
229
|
+
* - `typeName`
|
|
226
230
|
* - if structured type was anonymously defined
|
|
231
|
+
*
|
|
227
232
|
* @returns {object} { isExposable, typeDef, typeName, elements, isAnonymous }
|
|
228
233
|
*/
|
|
229
234
|
function isTypeExposable() {
|
|
@@ -236,21 +241,22 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
236
241
|
// named type, resolve the type to inspect it
|
|
237
242
|
let type = node.items?.type || node.type;
|
|
238
243
|
if(type) {
|
|
239
|
-
typeName = (type.ref && csnUtils.
|
|
244
|
+
typeName = (type.ref && csnUtils.artifactRef(type)) || type;
|
|
240
245
|
const rc = { isExposable: true, typeDef, typeName, isAnonymous: false };
|
|
241
|
-
if(!isBuiltinType(typeName)) {
|
|
242
|
-
rc.typeDef = typeDef = csnUtils.
|
|
246
|
+
if(!isBuiltinType(typeName) && typeName !== special$self) {
|
|
247
|
+
rc.typeDef = typeDef = csnUtils.artifactRef(typeName, typeName);
|
|
243
248
|
if(!isArtifactInService(typeName, serviceName)) {
|
|
244
249
|
while(!isBuiltinType(typeName)) {
|
|
245
|
-
typeDef = csnUtils.
|
|
246
|
-
if(typeDef) {
|
|
250
|
+
typeDef = csnUtils.artifactRef(typeName, typeName);
|
|
251
|
+
if(typeDef !== typeName) {
|
|
252
|
+
// Implementation note: For `type S: T:struct;`, elements from `T:struct` were already propagated to `S`.
|
|
247
253
|
if((isTermDef && typeDef.enum) || (rc.elements = (typeDef.items?.elements || typeDef.elements)) !== undefined)
|
|
248
254
|
return rc;
|
|
249
255
|
type = typeDef.items?.type || typeDef.type;
|
|
250
|
-
typeName = (type.ref && csnUtils.
|
|
256
|
+
typeName = (type.ref && csnUtils.artifactRef(type)) || type;
|
|
251
257
|
}
|
|
252
258
|
else {
|
|
253
|
-
throw
|
|
259
|
+
throw new CompilerAssertion(`Please debug me: ${typeName} not found`);
|
|
254
260
|
}
|
|
255
261
|
}
|
|
256
262
|
}
|
|
@@ -285,11 +291,14 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
285
291
|
return `${newSchemaName}.${typePlainName.replace(/\./g, '_')}`;
|
|
286
292
|
} else {
|
|
287
293
|
const typeContext = csnUtils.getContextOfArtifact(typeName);
|
|
288
|
-
const typeNamespace =
|
|
294
|
+
const typeNamespace = getNamespace(csn, typeName);
|
|
289
295
|
const newSchemaName = `${serviceName}.${typeContext || typeNamespace || fallBackSchemaName}`;
|
|
290
296
|
// new type name without any prefixes
|
|
291
|
-
const typePlainName = typeContext
|
|
292
|
-
|
|
297
|
+
const typePlainName = typeContext
|
|
298
|
+
? defNameWithoutServiceOrContextName(typeName, typeContext)
|
|
299
|
+
: (typeNamespace
|
|
300
|
+
? typeName.replace(typeNamespace + '.', '')
|
|
301
|
+
: typeName);
|
|
293
302
|
createSchema(newSchemaName);
|
|
294
303
|
// return the new type name
|
|
295
304
|
return `${newSchemaName}.${typePlainName.replace(/\./g, '_')}`;
|
|
@@ -26,7 +26,7 @@ function getServiceOfArtifact(artName, services) {
|
|
|
26
26
|
* @param {string} service Name of the service
|
|
27
27
|
*/
|
|
28
28
|
function isArtifactInService(artName, service) {
|
|
29
|
-
return artName.startsWith(`${service}.`);
|
|
29
|
+
return typeof artName === 'string' ? artName.startsWith(`${service}.`) : false;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* nary: return n-ary or binary tree
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
function parseExpr(xpr, state = { anno: 0, array: true, nary:
|
|
42
|
+
function parseExpr(xpr, state = { anno: 0, array: true, nary: false }) {
|
|
43
43
|
// Notes:
|
|
44
44
|
// - Variables `s` and `e` are used as index variables into `xpr`s for start and end.
|
|
45
45
|
// - xpr's are our CSN expressions, see <https://pages.github.tools.sap/cap/docs/cds/cxn>
|