@sap/cds-compiler 4.5.0 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -7
- package/bin/cdsc.js +13 -11
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +256 -115
- package/lib/api/options.js +8 -0
- package/lib/base/message-registry.js +17 -4
- package/lib/base/messages.js +15 -3
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +2 -3
- package/lib/compiler/assert-consistency.js +2 -1
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +30 -6
- package/lib/compiler/define.js +31 -9
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/resolve.js +26 -21
- package/lib/compiler/shared.js +19 -9
- package/lib/compiler/tweak-assocs.js +82 -107
- package/lib/compiler/utils.js +2 -1
- package/lib/edm/annotations/edmJson.js +23 -22
- package/lib/edm/annotations/genericTranslation.js +14 -4
- package/lib/edm/csn2edm.js +24 -10
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +11 -9
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +3 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -1
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +5253 -5214
- package/lib/json/to-csn.js +7 -1
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +21 -4
- package/lib/language/genericAntlrParser.js +9 -11
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +12 -7
- package/lib/optionProcessor.js +21 -19
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +18 -15
- package/lib/render/toHdbcds.js +59 -28
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +57 -82
- package/lib/render/utils/common.js +17 -0
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +27 -31
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/flattening.js +68 -69
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +13 -9
- package/lib/transform/forRelationalDB.js +36 -17
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +33 -8
- package/package.json +2 -2
|
@@ -17,14 +17,12 @@ const backlinks = require('../db/backlinks');
|
|
|
17
17
|
* @param {CSN.Model} csn Input CSN - will not be transformed
|
|
18
18
|
* @param {CSN.Options} options
|
|
19
19
|
* @param {object} csnUtils
|
|
20
|
-
* @param {object} messageFunctions
|
|
21
|
-
* @param {Function} messageFunctions.error
|
|
22
|
-
* @param {Function} messageFunctions.warning
|
|
20
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
23
21
|
* @todo Remove .keys afterwards
|
|
24
22
|
* @todo Add created foreign keys into .columns in case of a query?
|
|
25
23
|
* @returns {CSN.Model}
|
|
26
24
|
*/
|
|
27
|
-
function turnAssociationsIntoUnmanaged( csn, options, csnUtils,
|
|
25
|
+
function turnAssociationsIntoUnmanaged( csn, options, csnUtils, messageFunctions ) {
|
|
28
26
|
// TODO: Do we really need this?
|
|
29
27
|
forEachDefinition(csn, (artifact, artifactName) => {
|
|
30
28
|
setProp(artifact, '$path', [ 'definitions', artifactName ]);
|
|
@@ -33,7 +31,7 @@ function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning
|
|
|
33
31
|
}, [ 'definitions', artifactName ]);
|
|
34
32
|
});
|
|
35
33
|
// Flatten out the fks and create the corresponding elements
|
|
36
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options,
|
|
34
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
|
|
37
35
|
|
|
38
36
|
// Add the foreign keys also to the columns if the association itself was explicitly selected
|
|
39
37
|
// TODO: Extend the expansion to also expand managed to their foreign
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const { isBetaEnabled } = require('../../base/model');
|
|
4
4
|
const { CompilerAssertion } = require('../../base/error');
|
|
5
|
-
const { makeMessageFunction } = require('../../base/messages');
|
|
6
5
|
const { cloneCsnNonDict, getUtils, isAspect } = require('../../model/csnUtils');
|
|
7
6
|
const transformUtils = require('../transformUtils');
|
|
8
7
|
const flattening = require('../db/flattening');
|
|
@@ -26,21 +25,21 @@ const { rewriteCalculatedElementsInViews, processCalculatedElementsInEntities }
|
|
|
26
25
|
* @private
|
|
27
26
|
* @param {CSN.Model} model Input CSN - will not be transformed
|
|
28
27
|
* @param {CSN.Options} options
|
|
28
|
+
* @param {object} messageFunctions
|
|
29
29
|
* @returns {CSN.Model}
|
|
30
30
|
*/
|
|
31
|
-
function effectiveCsn( model, options ) {
|
|
31
|
+
function effectiveCsn( model, options, messageFunctions ) {
|
|
32
32
|
if (!isBetaEnabled(options, 'effectiveCsn'))
|
|
33
33
|
throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');
|
|
34
34
|
|
|
35
35
|
const csn = cloneCsnNonDict(model, options);
|
|
36
|
-
|
|
37
36
|
delete csn.vocabularies; // must not be set for effective CSN
|
|
37
|
+
messageFunctions.setModel(csn);
|
|
38
38
|
|
|
39
|
-
const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
|
|
39
|
+
const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
|
|
40
40
|
queries.projectionToSELECTAndAddColumns(csn);
|
|
41
41
|
|
|
42
42
|
let csnUtils = getUtils(csn, 'init-all');
|
|
43
|
-
const messageFunctions = makeMessageFunction(csn, options, 'for.effective');
|
|
44
43
|
|
|
45
44
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
46
45
|
const cleanup = validate.forRelationalDB(csn, {
|
|
@@ -65,13 +64,11 @@ function effectiveCsn( model, options ) {
|
|
|
65
64
|
|
|
66
65
|
const resolveTypesInActionsAfterFlattening = types.resolve(csn, csnUtils);
|
|
67
66
|
|
|
68
|
-
csnUtils = getUtils(csn, 'init-all');
|
|
69
|
-
|
|
70
67
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
71
68
|
cleanup();
|
|
72
69
|
|
|
73
|
-
flattening.flattenAllStructStepsInRefs(csn, options, new WeakMap(), '_');
|
|
74
|
-
flattening.flattenElements(csn, options, '_'
|
|
70
|
+
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, new WeakMap(), '_');
|
|
71
|
+
flattening.flattenElements(csn, options, messageFunctions, '_');
|
|
75
72
|
|
|
76
73
|
resolveTypesInActionsAfterFlattening();
|
|
77
74
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
4
3
|
const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');
|
|
5
4
|
const transformUtils = require('./transformUtils');
|
|
6
5
|
const { cloneCsnNonDict,
|
|
@@ -24,6 +23,7 @@ const associations = require('./db/associations')
|
|
|
24
23
|
const expansion = require('./db/expansion');
|
|
25
24
|
const generateDrafts = require('./draft/odata');
|
|
26
25
|
|
|
26
|
+
const { addTenantFields } = require('./addTenantFields');
|
|
27
27
|
const { addLocalizationViews } = require('./localized');
|
|
28
28
|
|
|
29
29
|
// Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.
|
|
@@ -68,19 +68,20 @@ const { addLocalizationViews } = require('./localized');
|
|
|
68
68
|
// (Linter check candidate)
|
|
69
69
|
module.exports = { transform4odataWithCsn };
|
|
70
70
|
|
|
71
|
-
function transform4odataWithCsn(inputModel, options) {
|
|
71
|
+
function transform4odataWithCsn(inputModel, options, messageFunctions) {
|
|
72
72
|
timetrace.start('OData transformation');
|
|
73
73
|
|
|
74
74
|
// copy the model as we don't want to change the input model
|
|
75
75
|
let csn = cloneCsnNonDict(inputModel, options);
|
|
76
|
+
messageFunctions.setModel(csn);
|
|
76
77
|
|
|
77
|
-
const { message, error, warning, info, throwWithAnyError } =
|
|
78
|
+
const { message, error, warning, info, throwWithAnyError } = messageFunctions;
|
|
78
79
|
throwWithAnyError();
|
|
79
80
|
|
|
80
81
|
// the new transformer works only with new CSN
|
|
81
82
|
checkCSNVersion(csn, options);
|
|
82
83
|
|
|
83
|
-
const transformers = transformUtils.getTransformers(csn, options, '_');
|
|
84
|
+
const transformers = transformUtils.getTransformers(csn, options, messageFunctions, '_');
|
|
84
85
|
const {
|
|
85
86
|
addDefaultTypeFacets, checkMultipleAssignments,
|
|
86
87
|
recurseElements, setAnnotation, renameAnnotation,
|
|
@@ -117,6 +118,9 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
117
118
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn'))
|
|
118
119
|
enrichUniversalCsn(csn, options);
|
|
119
120
|
|
|
121
|
+
if (options.tenantAsColumn)
|
|
122
|
+
addTenantFields(csn, options);
|
|
123
|
+
|
|
120
124
|
const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');
|
|
121
125
|
|
|
122
126
|
function acceptLocalizedView(_name, parent) {
|
|
@@ -178,15 +182,15 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
178
182
|
const resolved = new WeakMap();
|
|
179
183
|
// No refs with struct-steps exist anymore
|
|
180
184
|
|
|
181
|
-
flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
|
|
185
|
+
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, '_', { skipArtifact: isExternalServiceMember });
|
|
182
186
|
// No type references exist anymore
|
|
183
187
|
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
184
188
|
// OData doesn't resolve type chains after the first 'items'
|
|
185
|
-
flattening.resolveTypeReferences(csn, options, resolved, '_',
|
|
189
|
+
flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, '_',
|
|
186
190
|
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'],
|
|
187
191
|
skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
|
|
188
192
|
// No structured elements exists anymore
|
|
189
|
-
flattening.flattenElements(csn, options, '_',
|
|
193
|
+
flattening.flattenElements(csn, options, messageFunctions, '_',
|
|
190
194
|
{ skip: ['action', 'aspect', 'event', 'function', 'type'],
|
|
191
195
|
skipArtifact: isExternalServiceMember,
|
|
192
196
|
skipStandard: { items: true }, // don't drill further into .items
|
|
@@ -195,7 +199,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
195
199
|
|
|
196
200
|
// TODO: add the generated foreign keys to the columns when we are in a view
|
|
197
201
|
// see db/views.js::addForeignKeysToColumns
|
|
198
|
-
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options,
|
|
202
|
+
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
|
|
199
203
|
|
|
200
204
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
201
205
|
// To be done after handleManagedAssociationsAndCreateForeignKeys,
|
|
@@ -321,7 +325,7 @@ function transform4odataWithCsn(inputModel, options) {
|
|
|
321
325
|
return;
|
|
322
326
|
// Rename according to map above
|
|
323
327
|
const renamePrefix = (name in renameMappings)
|
|
324
|
-
? name
|
|
328
|
+
? name
|
|
325
329
|
: renameShortCuts.find(p => name.startsWith(p + '.'));
|
|
326
330
|
if(renamePrefix) {
|
|
327
331
|
const mapping = renameMappings[renamePrefix];
|
|
@@ -6,13 +6,13 @@ const { cloneCsnNonDict,
|
|
|
6
6
|
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
7
7
|
isAspect, walkCsnPath, isPersistedOnDatabase
|
|
8
8
|
} = require('../model/csnUtils');
|
|
9
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
10
9
|
const transformUtils = require('./transformUtils');
|
|
11
10
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
12
11
|
const { csnRefs, pathId, traverseQuery, columnAlias} = require('../model/csnRefs');
|
|
13
12
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
14
13
|
const validate = require('../checks/validator');
|
|
15
14
|
const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
|
|
15
|
+
const { addTenantFields } = require('../transform/addTenantFields');
|
|
16
16
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
17
17
|
const { timetrace } = require('../utils/timetrace');
|
|
18
18
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
@@ -100,9 +100,9 @@ function forEachDefinition(csn, cb) {
|
|
|
100
100
|
*
|
|
101
101
|
* @param {CSN.Model} csn
|
|
102
102
|
* @param {CSN.Options} options
|
|
103
|
-
* @param {
|
|
103
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
104
104
|
*/
|
|
105
|
-
function transformForRelationalDBWithCsn(csn, options,
|
|
105
|
+
function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
|
|
106
106
|
// copy the model as we don't want to change the input model
|
|
107
107
|
timetrace.start('HANA transformation');
|
|
108
108
|
|
|
@@ -111,6 +111,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
111
111
|
csn = cloneCsnNonDict(csn, options);
|
|
112
112
|
timetrace.stop('Clone CSN');
|
|
113
113
|
|
|
114
|
+
if (options.tenantAsColumn)
|
|
115
|
+
addTenantFields(csn, options);
|
|
116
|
+
|
|
114
117
|
checkCSNVersion(csn, options);
|
|
115
118
|
|
|
116
119
|
const pathDelimiter = (options.sqlMapping === 'hdbcds') ? '.' : '_';
|
|
@@ -120,12 +123,11 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
120
123
|
/** @type {object} */
|
|
121
124
|
let csnUtils;
|
|
122
125
|
/** @type {object} */
|
|
123
|
-
let
|
|
124
|
-
let message, error, warning, info; // message functions
|
|
126
|
+
let message, error, warning; // message functions
|
|
125
127
|
/** @type {() => void} */
|
|
126
128
|
let throwWithAnyError;
|
|
127
129
|
// transformUtils
|
|
128
|
-
let addDefaultTypeFacets,
|
|
130
|
+
let addDefaultTypeFacets,
|
|
129
131
|
expandStructsInExpression,
|
|
130
132
|
flattenStructuredElement,
|
|
131
133
|
flattenStructStepsInRef;
|
|
@@ -180,7 +182,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
180
182
|
// assoc2join eventually rewrites the table aliases
|
|
181
183
|
temporal.getViewDecorator(csn, messageFunctions, csnUtils),
|
|
182
184
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
183
|
-
assertUnique.prepare(csn, options,
|
|
185
|
+
assertUnique.prepare(csn, options, messageFunctions)
|
|
184
186
|
]);
|
|
185
187
|
|
|
186
188
|
if(doA2J) {
|
|
@@ -204,15 +206,15 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
204
206
|
if(doA2J) {
|
|
205
207
|
const resolved = new WeakMap();
|
|
206
208
|
// No refs with struct-steps exist anymore
|
|
207
|
-
flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
|
|
209
|
+
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, pathDelimiter);
|
|
208
210
|
// No type references exist anymore
|
|
209
211
|
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
210
|
-
flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
|
|
212
|
+
flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, pathDelimiter);
|
|
211
213
|
// No structured elements exists anymore
|
|
212
|
-
flattening.flattenElements(csn, options,
|
|
214
|
+
flattening.flattenElements(csn, options, messageFunctions, pathDelimiter);
|
|
213
215
|
} else {
|
|
214
216
|
// For to.hdbcds with naming mode hdbcds we also need to resolve the types
|
|
215
|
-
flattening.resolveTypeReferences(csn, options, new WeakMap(), pathDelimiter);
|
|
217
|
+
flattening.resolveTypeReferences(csn, options, messageFunctions, new WeakMap(), pathDelimiter);
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
// With flattening errors, it makes little sense to continue.
|
|
@@ -285,7 +287,7 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
285
287
|
]);
|
|
286
288
|
|
|
287
289
|
// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
|
|
288
|
-
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options,
|
|
290
|
+
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
289
291
|
|
|
290
292
|
doA2J && forEachDefinition(csn, flattenIndexes);
|
|
291
293
|
// Managed associations get an on-condition - in views and entities
|
|
@@ -358,6 +360,9 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
358
360
|
// Associations that point to things marked with @cds.persistence.skip are removed
|
|
359
361
|
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, messageFunctions, csnUtils));
|
|
360
362
|
|
|
363
|
+
// some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
|
|
364
|
+
messageFunctions.throwWithError();
|
|
365
|
+
|
|
361
366
|
// Apply view-specific transformations
|
|
362
367
|
// (160) Projections now finally become views
|
|
363
368
|
// Replace managed association in group/order by with foreign keys
|
|
@@ -449,15 +454,15 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
449
454
|
/* ----------------------------------- Functions start here -----------------------------------------------*/
|
|
450
455
|
|
|
451
456
|
function bindCsnReference(){
|
|
452
|
-
messageFunctions
|
|
453
|
-
({ error, warning,
|
|
457
|
+
messageFunctions.setModel(csn);
|
|
458
|
+
({ error, warning, message, throwWithAnyError } = messageFunctions);
|
|
454
459
|
|
|
455
460
|
({ flattenStructuredElement,
|
|
456
461
|
flattenStructStepsInRef,
|
|
457
462
|
addDefaultTypeFacets,
|
|
458
463
|
expandStructsInExpression,
|
|
459
464
|
csnUtils
|
|
460
|
-
} = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
465
|
+
} = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter));
|
|
461
466
|
}
|
|
462
467
|
|
|
463
468
|
function bindCsnReferenceOnly(){
|
|
@@ -614,10 +619,10 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
614
619
|
// (100 a) Ignore the property 'masked' itself (but not its effect on projections)
|
|
615
620
|
if (member.masked)
|
|
616
621
|
delete member.masked;
|
|
617
|
-
//
|
|
622
|
+
// Report an error on
|
|
618
623
|
// - view with parameters that has an element of type association/composition
|
|
619
624
|
// - association that points to entity with parameters
|
|
620
|
-
if (
|
|
625
|
+
if (member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
|
|
621
626
|
if (artifact.params) {
|
|
622
627
|
// HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
|
|
623
628
|
// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
|
|
@@ -862,6 +867,20 @@ function transformForRelationalDBWithCsn(csn, options, moduleName) {
|
|
|
862
867
|
checkTypeParamValue(node, 'srid', { max: Number.MAX_SAFE_INTEGER }, path);
|
|
863
868
|
break;
|
|
864
869
|
}
|
|
870
|
+
case 'cds.Vector': {
|
|
871
|
+
if (options.sqlDialect !== 'hana') {
|
|
872
|
+
error('ref-unsupported-type', path, {
|
|
873
|
+
'#': 'hana', type: node.type, value: 'hana',
|
|
874
|
+
othervalue: options.sqlDialect
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
else if (options.transformation === 'hdbcds') {
|
|
878
|
+
error('ref-unsupported-type', path, {
|
|
879
|
+
'#': 'hdbcds', type: node.type, value: options.sqlDialect
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
break;
|
|
883
|
+
}
|
|
865
884
|
}
|
|
866
885
|
}
|
|
867
886
|
|
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
} = require('../../model/csnUtils');
|
|
8
8
|
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
9
9
|
|
|
10
|
-
function expandToFinalBaseType(csn, transformers, csnUtils, services, options
|
|
10
|
+
function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
|
|
11
11
|
const isV4 = options.odataVersion === 'v4';
|
|
12
12
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
13
13
|
forEachDefinition(csn, (def, defName) => {
|
|
@@ -63,7 +63,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
63
63
|
if (def.kind === 'type' && def.items && isArtifactInSomeService(defName, services)) {
|
|
64
64
|
expandFirstLevelOfArrayed(def);
|
|
65
65
|
}
|
|
66
|
-
}
|
|
66
|
+
});
|
|
67
67
|
|
|
68
68
|
if(isBetaEnabled(options, 'odataTerms')) {
|
|
69
69
|
forEachGeneric(csn, 'vocabularies', (def, defName) => {
|
|
@@ -75,7 +75,7 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
75
75
|
|
|
76
76
|
expandToFinalBaseType(def, defName);
|
|
77
77
|
expandToFinalBaseType(def.items, defName);
|
|
78
|
-
}, []
|
|
78
|
+
}, []);
|
|
79
79
|
}
|
|
80
80
|
// In case we have in the model something like:
|
|
81
81
|
// type Foo: array of Bar; type Bar: { qux: Integer };
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* @module typesExposure
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const { setProp } = require('../../base/model');
|
|
9
|
+
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
10
10
|
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const { getNamespace, copyAnnotations, cloneCsnNonDict,
|
|
12
|
+
isBuiltinType, isAnnotationExpression,
|
|
13
|
+
forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
|
|
14
14
|
const { CompilerAssertion } = require('../../base/error');
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -219,7 +219,16 @@ function typesExposure(csn, whatsMyServiceName, requestedServiceNames, fallBackS
|
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
});
|
|
222
|
-
|
|
222
|
+
|
|
223
|
+
// expression annos and their sub annotations are not propagated to type
|
|
224
|
+
let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
|
|
225
|
+
if (pn[0] === '@')
|
|
226
|
+
acc[isAnnotationExpression(typeDef[pn]) ? 0 : 1].push(pn);
|
|
227
|
+
return acc;
|
|
228
|
+
}, [ [], [] ]);
|
|
229
|
+
nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
|
|
230
|
+
copyAnnotations(typeDef, newType, false, {}, nxprANames);
|
|
231
|
+
|
|
223
232
|
// if the origin type had items, add items to exposed type
|
|
224
233
|
if(typeDef.kind === 'type') {
|
|
225
234
|
if(typeDef.items) {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
// different backends.
|
|
5
5
|
// The sibling of model/transform/TransformUtil.js which works with compacted new CSN.
|
|
6
6
|
|
|
7
|
-
const { makeMessageFunction } = require('../base/messages');
|
|
8
7
|
const { setProp } = require('../base/model');
|
|
9
8
|
|
|
10
9
|
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
|
|
@@ -20,8 +19,8 @@ const RelationalOperators = ['=', '!=', '<>', 'is' /*, 'like'*/,...RestrictedOpe
|
|
|
20
19
|
// 'model' is compacted new style CSN
|
|
21
20
|
// TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
|
|
22
21
|
// TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
|
|
23
|
-
function getTransformers(model, options, pathDelimiter = '_') {
|
|
24
|
-
const { message, error, warning, info } =
|
|
22
|
+
function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
23
|
+
const { message, error, warning, info } = msgFunctions;
|
|
25
24
|
const csnUtils = getUtils(model);
|
|
26
25
|
const {
|
|
27
26
|
getCsnDef,
|
|
@@ -321,48 +320,62 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
321
320
|
* @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
|
|
322
321
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
323
322
|
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
323
|
+
* @param {bool} [refParentIsItems] relative ref has an items root (suspend flattening by caller)
|
|
324
324
|
* @returns {string[]}
|
|
325
325
|
*/
|
|
326
|
-
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
|
|
326
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), refParentIsItems=false) {
|
|
327
327
|
// Refs of length 1 cannot contain steps - no need to check
|
|
328
328
|
if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
|
|
329
329
|
return ref;
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
const result = scope === '$self' ? [ref[0]] : [];
|
|
333
|
+
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
|
|
334
|
+
if(!links && !scope) { // calculate JIT if not supplied
|
|
335
|
+
const res = inspectRef(path);
|
|
336
|
+
links = res.links;
|
|
337
|
+
scope = res.scope;
|
|
338
|
+
}
|
|
339
|
+
if (scope === '$magic')
|
|
340
|
+
return ref;
|
|
333
341
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
ref[i].id = result[result.length-1];
|
|
355
|
-
result[result.length-1] = ref[i];
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
result.push(ref[i]);
|
|
342
|
+
// Don't process a leading $self - it will a .art with .elements!
|
|
343
|
+
let i = scope === '$self' ? 1 : 0;
|
|
344
|
+
|
|
345
|
+
// read property from resolved path link
|
|
346
|
+
const art = (propName) =>
|
|
347
|
+
(links[i].art?.[propName] ||
|
|
348
|
+
effectiveType(links[i].art)[propName] ||
|
|
349
|
+
(resolvedLinkTypes.get(links[i])||{})[propName]);
|
|
350
|
+
|
|
351
|
+
let flattenStep = false;
|
|
352
|
+
let nextIsItems = !!art('items') || (refParentIsItems && i === 0);
|
|
353
|
+
for(; i < links.length; i++) {
|
|
354
|
+
|
|
355
|
+
if (flattenStep && !nextIsItems) {
|
|
356
|
+
result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
|
|
357
|
+
// if we had a filter or args, we had an assoc so this step is done
|
|
358
|
+
// we then keep along the filter/args by updating the id of the current ref
|
|
359
|
+
if(ref[i].id) {
|
|
360
|
+
ref[i].id = result[result.length-1];
|
|
361
|
+
result[result.length-1] = ref[i];
|
|
360
362
|
}
|
|
361
|
-
|
|
363
|
+
// suspend flattening if the next path step has some 'items'
|
|
364
|
+
nextIsItems = !!art('items');
|
|
362
365
|
}
|
|
363
|
-
|
|
364
|
-
|
|
366
|
+
else {
|
|
367
|
+
result.push(ref[i]);
|
|
368
|
+
}
|
|
369
|
+
// revoke items suspension for next assoc step
|
|
370
|
+
if(nextIsItems && art('target'))
|
|
371
|
+
nextIsItems = false;
|
|
372
|
+
|
|
373
|
+
flattenStep = !links[i].art?.kind &&
|
|
374
|
+
!links[i].art?.SELECT &&
|
|
375
|
+
!links[i].art?.from &&
|
|
376
|
+
art('elements');
|
|
365
377
|
}
|
|
378
|
+
return result;
|
|
366
379
|
}
|
|
367
380
|
|
|
368
381
|
/**
|
|
@@ -47,8 +47,6 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
47
47
|
deduplicateMessages( options.messages );
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// If A2J reports error - end! Continuing with a broken CSN makes no sense
|
|
51
|
-
makeMessageFunction(model, options).throwWithAnyError();
|
|
52
50
|
// FIXME: Move this somewhere more appropriate
|
|
53
51
|
const newCsn = compactModel(model, compileOptions);
|
|
54
52
|
//require('fs').writeFileSync('./csnoutput_a2j.json', JSON.stringify(newCsn, null,2))
|
|
@@ -57,7 +55,7 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
57
55
|
|
|
58
56
|
function translateAssocsToJoins(model, inputOptions = {})
|
|
59
57
|
{
|
|
60
|
-
const { error, warning } = makeMessageFunction(model, inputOptions);
|
|
58
|
+
const { error, warning, throwWithError } = makeMessageFunction(model, inputOptions);
|
|
61
59
|
|
|
62
60
|
const options = model.options || inputOptions;
|
|
63
61
|
|
|
@@ -70,6 +68,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
70
68
|
forEachDefinition(model, prepareAssociations);
|
|
71
69
|
forEachDefinition(model, transformQueries);
|
|
72
70
|
|
|
71
|
+
// If A2J reports error - end! Continuing with a broken model makes no sense
|
|
72
|
+
throwWithError();
|
|
73
|
+
|
|
73
74
|
return model;
|
|
74
75
|
|
|
75
76
|
function prepareAssociations(art)
|
|
@@ -316,7 +317,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
316
317
|
// Paths without _navigation in ORDER BY are select item aliases, they must
|
|
317
318
|
// be rendered verbatim
|
|
318
319
|
let [head, ...tail] = pathNode.path;
|
|
319
|
-
if((env.location === 'OrderBy' && !head._navigation)||
|
|
320
|
+
if((env.location === 'OrderBy' && !head._navigation)||
|
|
320
321
|
env.location === 'UnionOuterOrderBy' && (!head._navigation || ['$self', '$projection'].includes(head.id)))
|
|
321
322
|
return;
|
|
322
323
|
|
|
@@ -494,7 +495,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
494
495
|
let srcTableAlias = constructTableAliasPathStep(assocSourceQA);
|
|
495
496
|
let tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);
|
|
496
497
|
|
|
497
|
-
node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias);
|
|
498
|
+
node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias, options.tenantAsColumn);
|
|
498
499
|
|
|
499
500
|
if(assocQAT._filter)
|
|
500
501
|
{
|
|
@@ -564,7 +565,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
564
565
|
return xsnCard;
|
|
565
566
|
}
|
|
566
567
|
// produce the ON condition for a given association
|
|
567
|
-
function createOnCondition(assoc, srcAlias, tgtAlias)
|
|
568
|
+
function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants)
|
|
568
569
|
{
|
|
569
570
|
let prefixes = [ assoc.name.id ];
|
|
570
571
|
/* This is no art and can be removed once ON cond for published
|
|
@@ -599,7 +600,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
599
600
|
Put all src/tgt path siblings into the EQ term and create the proper path objects
|
|
600
601
|
with the src/tgt table alias path steps in front.
|
|
601
602
|
*/
|
|
602
|
-
let args = [];
|
|
603
|
+
let args = []; // TODO: tenant comparison?
|
|
603
604
|
for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
|
|
604
605
|
{
|
|
605
606
|
args.push({op: {val: '=' },
|
|
@@ -660,7 +661,25 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
660
661
|
env.assocStack.push(assoc);
|
|
661
662
|
const onCond = cloneOnCondition(assoc.on);
|
|
662
663
|
env.assocStack.pop();
|
|
663
|
-
return onCond;
|
|
664
|
+
return compareTenants ? addTenantComparison(assoc, onCond) : onCond;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Add tenant comparison
|
|
668
|
+
function addTenantComparison(assoc, cond) {
|
|
669
|
+
// It is enough to test whether the target is tenant-dependent. If it is,
|
|
670
|
+
// the current query must also be (check in addTenantFields). If we allow
|
|
671
|
+
// assocs from tenant-independent entities to tenant-dependent ones, we
|
|
672
|
+
// also need to use the current query = `env.lead`.
|
|
673
|
+
if(annotationVal(assoc.target._artifact['@cds.tenant.independent']))
|
|
674
|
+
return cond;
|
|
675
|
+
|
|
676
|
+
const args = [ constructPathNode([ srcAlias ]), constructPathNode([ tgtAlias ]) ];
|
|
677
|
+
args[0].path.push({ id: 'tenant' }); // no need for _artifact
|
|
678
|
+
args[1].path.push({ id: 'tenant' }); // no need for _artifact
|
|
679
|
+
const comparison = { op: {val: '=' }, args };
|
|
680
|
+
if (!cond) // for managed assoc
|
|
681
|
+
return comparison;
|
|
682
|
+
return { op: { val: 'and' }, args: [ comparison, parenthesise(cond) ] };
|
|
664
683
|
}
|
|
665
684
|
|
|
666
685
|
// make foreign key absolute to its main entity
|
|
@@ -1047,6 +1066,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1047
1066
|
return node;
|
|
1048
1067
|
}
|
|
1049
1068
|
|
|
1069
|
+
// Remark CW: why boolean and not just truthy/falsy as usual? See annotationVal() below
|
|
1050
1070
|
function isBooleanAnnotation(prop, val=true) {
|
|
1051
1071
|
return prop && prop.val !== undefined && prop.val === val && prop.literal === 'boolean';
|
|
1052
1072
|
}
|
|
@@ -1905,4 +1925,9 @@ function walkPath(node, env)
|
|
|
1905
1925
|
return path;
|
|
1906
1926
|
}
|
|
1907
1927
|
|
|
1928
|
+
function annotationVal( anno ) {
|
|
1929
|
+
// XSN TODO: also set `val:true` but no location for anno short form
|
|
1930
|
+
return anno && (anno.val === undefined || anno.val);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1908
1933
|
module.exports = { translateAssocsToJoinsCSN };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-compiler",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.6.2",
|
|
4
4
|
"description": "CDS (Core Data Services) compiler and backends",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"deployGitDiffs": "CDS_COMPILER_DEPLOY_HANA=1 mocha --reporter-option maxDiffSize=0 test3/test.deploy.git-diffs.js",
|
|
28
28
|
"gentest3": "cross-env MAKEREFS=${MAKEREFS:-'true'} mocha --reporter-option maxDiffSize=0 test3/testRefFiles.js",
|
|
29
29
|
"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
|
|
30
|
-
"coverage:piper": "cross-env nyc mocha --reporter
|
|
30
|
+
"coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
|
|
31
31
|
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",
|
|
32
32
|
"tslint": "tsc --pretty -p .",
|
|
33
33
|
"updateVocs": "node scripts/odataAnnotations/generateDictMain.js && npm run generateAllRefs",
|