@sap/cds-compiler 2.10.4 → 2.12.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 +136 -0
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +58 -35
- package/bin/cdsse.js +1 -0
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +16 -0
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +10 -36
- package/lib/api/options.js +17 -8
- package/lib/api/validate.js +30 -3
- package/lib/backends.js +12 -13
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +3 -2
- package/lib/base/message-registry.js +64 -11
- package/lib/base/messages.js +38 -18
- package/lib/base/model.js +6 -4
- package/lib/base/optionProcessorHelper.js +148 -86
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -0
- package/lib/checks/sql-snippets.js +93 -0
- package/lib/checks/unknownMagic.js +6 -3
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +14 -5
- package/lib/compiler/base.js +64 -0
- package/lib/compiler/builtins.js +62 -16
- package/lib/compiler/checks.js +34 -10
- package/lib/compiler/definer.js +91 -112
- package/lib/compiler/index.js +30 -30
- package/lib/compiler/propagator.js +8 -4
- package/lib/compiler/resolver.js +279 -63
- package/lib/compiler/shared.js +65 -230
- package/lib/compiler/utils.js +191 -0
- package/lib/edm/annotations/genericTranslation.js +35 -18
- package/lib/edm/annotations/preprocessAnnotations.js +1 -1
- package/lib/edm/csn2edm.js +4 -3
- package/lib/edm/edm.js +8 -8
- package/lib/edm/edmPreprocessor.js +61 -59
- package/lib/edm/edmUtils.js +14 -15
- package/lib/gen/Dictionary.json +82 -40
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +19 -1
- package/lib/gen/language.tokens +80 -73
- package/lib/gen/languageLexer.interp +27 -1
- package/lib/gen/languageLexer.js +925 -826
- package/lib/gen/languageLexer.tokens +72 -65
- package/lib/gen/languageParser.js +4817 -4102
- package/lib/json/from-csn.js +57 -26
- package/lib/json/to-csn.js +244 -51
- package/lib/language/antlrParser.js +12 -1
- package/lib/language/docCommentParser.js +1 -1
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +106 -30
- package/lib/language/language.g4 +200 -70
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +220 -21
- package/lib/main.js +6 -3
- package/lib/model/api.js +2 -2
- package/lib/model/csnRefs.js +218 -86
- package/lib/model/csnUtils.js +99 -178
- package/lib/model/enrichCsn.js +84 -43
- package/lib/model/revealInternalProperties.js +25 -8
- package/lib/model/sortViews.js +8 -1
- package/lib/modelCompare/compare.js +2 -1
- package/lib/optionProcessor.js +33 -18
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +2 -2
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +202 -82
- package/lib/render/toHdbcds.js +194 -135
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +91 -51
- package/lib/render/utils/common.js +24 -5
- package/lib/render/utils/sql.js +6 -4
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/applyTransformations.js +189 -0
- package/lib/transform/db/associations.js +389 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +275 -119
- package/lib/transform/db/draft.js +6 -4
- package/lib/transform/db/expansion.js +10 -9
- package/lib/transform/db/flattening.js +23 -8
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +106 -25
- package/lib/transform/db/views.js +485 -0
- package/lib/transform/forHanaNew.js +90 -1036
- package/lib/transform/forOdataNew.js +11 -3
- package/lib/transform/localized.js +5 -14
- package/lib/transform/odata/generateForeignKeyElements.js +2 -2
- package/lib/transform/transformUtilsNew.js +34 -20
- package/lib/transform/translateAssocsToJoins.js +15 -23
- package/lib/transform/universalCsnEnricher.js +217 -47
- package/lib/utils/file.js +13 -6
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +55 -27
- package/package.json +1 -1
- package/lib/transform/db/helpers.js +0 -58
|
@@ -1,25 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
|
-
const { getUtils, cloneCsn,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
9
|
-
isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
|
|
4
|
+
const { getUtils, cloneCsn,
|
|
5
|
+
forEachMemberRecursively, forEachRef, forAllQueries,
|
|
6
|
+
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
7
|
+
isAspect, walkCsnPath,
|
|
10
8
|
} = require('../model/csnUtils');
|
|
11
9
|
const { makeMessageFunction } = require('../base/messages');
|
|
12
10
|
const transformUtils = require('./transformUtilsNew');
|
|
13
11
|
const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
|
|
14
|
-
const { csnRefs, pathId
|
|
12
|
+
const { csnRefs, pathId } = require('../model/csnRefs');
|
|
15
13
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
16
14
|
const validate = require('../checks/validator');
|
|
15
|
+
const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
|
|
17
16
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
18
|
-
const timetrace = require('../utils/timetrace');
|
|
17
|
+
const { timetrace } = require('../utils/timetrace');
|
|
19
18
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
20
19
|
const { createDict } = require('../utils/objectUtils');
|
|
21
20
|
const handleExists = require('./db/transformExists');
|
|
22
|
-
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
|
|
23
21
|
const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
|
|
24
22
|
const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
25
23
|
const flattening = require('./db/flattening');
|
|
@@ -27,6 +25,10 @@ const expansion = require('./db/expansion');
|
|
|
27
25
|
const assertUnique = require('./db/assertUnique');
|
|
28
26
|
const generateDrafts = require('./db/draft');
|
|
29
27
|
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
28
|
+
const { getViewTransformer } = require('./db/views');
|
|
29
|
+
const cdsPersistence = require('./db/cdsPersistence');
|
|
30
|
+
const temporal = require('./db/temporal');
|
|
31
|
+
const associations = require('./db/associations')
|
|
30
32
|
|
|
31
33
|
// By default: Do not process non-entities/views
|
|
32
34
|
function forEachDefinition(csn, cb) {
|
|
@@ -100,7 +102,6 @@ function forEachDefinition(csn, cb) {
|
|
|
100
102
|
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
|
|
101
103
|
*/
|
|
102
104
|
function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
103
|
-
const columnClearer = [];
|
|
104
105
|
// copy the model as we don't want to change the input model
|
|
105
106
|
timetrace.start('HANA transformation');
|
|
106
107
|
/** @type {CSN.Model} */
|
|
@@ -115,16 +116,16 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
115
116
|
let error, warning, info; // message functions
|
|
116
117
|
/** @type {() => void} */
|
|
117
118
|
let throwWithError;
|
|
118
|
-
let artifactRef, inspectRef,
|
|
119
|
+
let artifactRef, inspectRef, effectiveType, // csnRefs
|
|
119
120
|
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
|
|
120
121
|
get$combined; // csnUtils
|
|
121
122
|
|
|
122
123
|
bindCsnReference();
|
|
123
124
|
|
|
124
125
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
127
|
-
enrichUniversalCsn(csn, options);
|
|
128
|
+
enrichUniversalCsn(csn, options);
|
|
128
129
|
bindCsnReference();
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -138,11 +139,16 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
138
139
|
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
139
140
|
});
|
|
140
141
|
|
|
142
|
+
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
143
|
+
handleExists(csn, options, error);
|
|
144
|
+
|
|
141
145
|
// Check if structured elements and managed associations are compared in an expression
|
|
142
146
|
// and expand these structured elements. This tuple expansion allows all other
|
|
143
147
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
144
148
|
// If errors are detected, throwWithError() will return from further processing
|
|
145
149
|
|
|
150
|
+
// If this function is ever undefined, we have a bug in our logic.
|
|
151
|
+
// @ts-ignore
|
|
146
152
|
expandStructsInExpression(csn, { drillRef: true });
|
|
147
153
|
|
|
148
154
|
throwWithError();
|
|
@@ -150,11 +156,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
150
156
|
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
151
157
|
const transformCsn = transformUtils.transformModel;
|
|
152
158
|
|
|
153
|
-
handleExists(csn, options, error);
|
|
154
159
|
|
|
155
160
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
156
161
|
// assoc2join eventually rewrites the table aliases
|
|
157
|
-
forEachDefinition(csn,
|
|
162
|
+
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
|
|
158
163
|
|
|
159
164
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
160
165
|
assertUnique.prepare(csn, options, error, info);
|
|
@@ -212,23 +217,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
212
217
|
}
|
|
213
218
|
});
|
|
214
219
|
|
|
215
|
-
// Must happen after A2J, as A2J needs $self to correctly resolve stuff
|
|
216
|
-
if(doA2J)
|
|
217
|
-
flattening.removeLeadingSelf(csn);
|
|
218
|
-
|
|
219
220
|
const {
|
|
220
221
|
flattenStructuredElement,
|
|
221
|
-
flattenStructStepsInRef,
|
|
222
|
+
flattenStructStepsInRef,
|
|
222
223
|
isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
223
|
-
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
|
|
224
|
-
recurseElements
|
|
225
224
|
} = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
226
225
|
|
|
227
226
|
const {
|
|
228
227
|
getCsnDef,
|
|
229
228
|
isAssocOrComposition,
|
|
230
|
-
isManagedAssociationElement,
|
|
231
|
-
isStructured,
|
|
232
229
|
addStringAnnotationTo,
|
|
233
230
|
cloneWithTransformations,
|
|
234
231
|
} = getUtils(csn);
|
|
@@ -273,28 +270,28 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
273
270
|
// (040) Ignore entities and views that are abstract or implemented
|
|
274
271
|
// or carry the annotation cds.persistence.skip/exists
|
|
275
272
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
276
|
-
forEachDefinition(csn,
|
|
273
|
+
forEachDefinition(csn, cdsPersistence.getAnnoProcessor(csn, options, {warning}));
|
|
277
274
|
|
|
278
275
|
|
|
279
276
|
// (050) Check @cds.valid.from/to only on entity
|
|
280
277
|
// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
|
|
281
278
|
// Temporal only in beta-mode
|
|
282
|
-
forEachDefinition(csn,
|
|
279
|
+
forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
|
|
283
280
|
|
|
284
281
|
handleManagedAssociationsAndCreateForeignKeys();
|
|
285
|
-
|
|
282
|
+
|
|
286
283
|
function handleManagedAssociationsAndCreateForeignKeys() {
|
|
287
|
-
forEachDefinition(csn, (
|
|
288
|
-
forEachDefinition(csn, (
|
|
284
|
+
forEachDefinition(csn, associations.getForeignKeyFlattener(csn, options, pathDelimiter));
|
|
285
|
+
forEachDefinition(csn, associations.getForeignKeyElementCreator(csn, options, pathDelimiter, { error }));
|
|
289
286
|
}
|
|
290
287
|
|
|
291
288
|
forEachDefinition(csn, flattenIndexes);
|
|
292
289
|
// Basic handling of associations in views and entities
|
|
293
|
-
forEachDefinition(csn,
|
|
290
|
+
forEachDefinition(csn, associations.getManagedAssociationTransformer(csn, options, pathDelimiter));
|
|
294
291
|
|
|
295
292
|
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
296
293
|
// and make them entities
|
|
297
|
-
forEachDefinition(csn,
|
|
294
|
+
forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, {error}));
|
|
298
295
|
|
|
299
296
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
300
297
|
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
@@ -311,6 +308,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
311
308
|
else
|
|
312
309
|
addLocalizationViews(csn, options);
|
|
313
310
|
|
|
311
|
+
forEachDefinition(csn, (definition, artName, prop, path) => {
|
|
312
|
+
if (definition.query) {
|
|
313
|
+
// reject managed association and structure publishing for to-hdbcds.hdbcds
|
|
314
|
+
const that = { csnUtils: getUtils(csn), options, error };
|
|
315
|
+
rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
314
319
|
// For generating DB stuff:
|
|
315
320
|
// - table-entity with parameters: not allowed
|
|
316
321
|
// - view with parameters: ok on HANA, not allowed otherwise
|
|
@@ -331,7 +336,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
331
336
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
332
337
|
forEachDefinition(csn, transformSelfInBacklinks);
|
|
333
338
|
|
|
334
|
-
if(
|
|
339
|
+
if(options.forHana){
|
|
335
340
|
/**
|
|
336
341
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
337
342
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
@@ -343,19 +348,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
343
348
|
if(validOptionsForConstraint())
|
|
344
349
|
createReferentialConstraints(csn, options);
|
|
345
350
|
}
|
|
346
|
-
|
|
351
|
+
// no constraints for drafts
|
|
347
352
|
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
348
353
|
|
|
349
354
|
// Set the final constraint paths and produce hana tc indexes if required
|
|
350
355
|
// See function comment for extensive information.
|
|
351
356
|
assertUnique.rewrite(csn, options, pathDelimiter);
|
|
352
357
|
|
|
353
|
-
// Associations that point to
|
|
354
|
-
forEachDefinition(csn,
|
|
358
|
+
// Associations that point to things marked with @cds.persistence.skip are removed
|
|
359
|
+
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
|
|
355
360
|
|
|
356
361
|
// Apply view-specific transformations
|
|
357
362
|
// (160) Projections now finally become views
|
|
358
363
|
// Replace managed association in group/order by with foreign keys
|
|
364
|
+
const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
|
|
359
365
|
forEachDefinition(csn, transformViews);
|
|
360
366
|
|
|
361
367
|
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
@@ -384,8 +390,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
384
390
|
checkConstraintIdentifiers,
|
|
385
391
|
/* (250) Remove all namespaces from definitions */
|
|
386
392
|
removeNamespaces,
|
|
387
|
-
/* (190 b) Replace enum types by their final base type */
|
|
388
|
-
replaceEnumsByBaseTypes,
|
|
389
393
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
390
394
|
checkTypeParameters,
|
|
391
395
|
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
@@ -394,6 +398,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
394
398
|
removeKeyPropInType,
|
|
395
399
|
]);
|
|
396
400
|
|
|
401
|
+
// Remove leading $self to keep renderer-diffs smaller
|
|
402
|
+
if(doA2J)
|
|
403
|
+
flattening.removeLeadingSelf(csn);
|
|
404
|
+
|
|
397
405
|
throwWithError();
|
|
398
406
|
|
|
399
407
|
timetrace.stop();
|
|
@@ -403,6 +411,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
403
411
|
}
|
|
404
412
|
|
|
405
413
|
const killers = {
|
|
414
|
+
// Used to ignore actions etc from processing and remove associations/elements
|
|
406
415
|
'_ignore': function (parent, a, b, path){
|
|
407
416
|
if(path.length > 2) {
|
|
408
417
|
const tail = path[path.length-1];
|
|
@@ -413,97 +422,35 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
413
422
|
delete parent._ignore;
|
|
414
423
|
}
|
|
415
424
|
},
|
|
416
|
-
|
|
417
|
-
'_effectiveType': killProp,
|
|
425
|
+
// Still used in flattenStructuredElements - in db/flattening.js
|
|
418
426
|
'_flatElementNameWithDots': killProp,
|
|
419
|
-
|
|
427
|
+
// Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
|
|
428
|
+
// to not copy the .length property if it was only set via default
|
|
420
429
|
'$default': killProp,
|
|
421
|
-
|
|
422
|
-
'$env': killProp,
|
|
423
|
-
'$fksgenerated': killProp,
|
|
424
|
-
'$lateFlattening': killProp,
|
|
425
|
-
'$path': killProp,
|
|
430
|
+
// Set when we turn UUID into String, checked during generateDraftForHana
|
|
426
431
|
'$renamed': killProp,
|
|
427
|
-
|
|
428
|
-
'$
|
|
432
|
+
// Set when we remove .key from temporal things, used in localized.js
|
|
433
|
+
'$key': killProp
|
|
429
434
|
}
|
|
430
435
|
|
|
431
436
|
applyTransformations(csn, killers, [], false);
|
|
432
437
|
|
|
433
438
|
redoProjections.forEach(fn => fn());
|
|
434
|
-
columnClearer.forEach(fn => fn());
|
|
435
439
|
|
|
436
440
|
return csn;
|
|
437
441
|
|
|
438
442
|
/* ----------------------------------- Functions start here -----------------------------------------------*/
|
|
439
443
|
|
|
440
|
-
/**
|
|
441
|
-
* Create the foreign key elements for managed associations.
|
|
442
|
-
* Create them in-place, right after the corresponding association.
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
* @param {CSN.Artifact} art
|
|
446
|
-
* @param {string} artName
|
|
447
|
-
*/
|
|
448
|
-
function createForeignKeyElements(art, artName) {
|
|
449
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
450
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
451
|
-
const elementsArray = [];
|
|
452
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
453
|
-
elementsArray.push([elemName, element]);
|
|
454
|
-
if (isManagedAssociationElement(element)) {
|
|
455
|
-
if (element.keys) {
|
|
456
|
-
for(let i = 0; i < element.keys.length; i++){
|
|
457
|
-
const foreignKey = element.keys[i];
|
|
458
|
-
const path = [...pathToElements, elemName, 'keys', i];
|
|
459
|
-
foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
|
|
460
|
-
const [fkName, fkElem] = getForeignKeyArtifact(element, elemName, foreignKey, path);
|
|
461
|
-
if(parent.elements[fkName]) {
|
|
462
|
-
error(null, [...pathToElements, elemName], { name: fkName, art: elemName },
|
|
463
|
-
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
464
|
-
} else {
|
|
465
|
-
elementsArray.push([fkName, fkElem]);
|
|
466
|
-
}
|
|
467
|
-
applyCachedAlias(foreignKey);
|
|
468
|
-
// join ref array as the struct / assoc steps are not necessary anymore
|
|
469
|
-
foreignKey.ref = [foreignKey.ref.join(pathDelimiter)]
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// Don't fake consistency of the model by adding empty elements {}
|
|
476
|
-
if(elementsArray.length === 0)
|
|
477
|
-
return;
|
|
478
|
-
|
|
479
|
-
parent.elements = elementsArray.reduce((previous, [name, element]) => {
|
|
480
|
-
previous[name] = element;
|
|
481
|
-
return previous;
|
|
482
|
-
}, Object.create(null));
|
|
483
|
-
|
|
484
|
-
})
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
function applyCachedAlias(foreignKey) {
|
|
488
|
-
// If we have a $ref use that - it resolves aliased FKs correctly
|
|
489
|
-
if (foreignKey.$ref) {
|
|
490
|
-
foreignKey.ref = foreignKey.$ref;
|
|
491
|
-
delete foreignKey.$ref;
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
444
|
function bindCsnReference(){
|
|
498
445
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
499
|
-
({ artifactRef, inspectRef,
|
|
446
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
500
447
|
({ getFinalBaseType, get$combined } = getUtils(csn));
|
|
501
448
|
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
502
449
|
}
|
|
503
450
|
|
|
504
451
|
function bindCsnReferenceOnly(){
|
|
505
452
|
// invalidate caches for CSN ref API
|
|
506
|
-
({ artifactRef, inspectRef,
|
|
453
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
507
454
|
}
|
|
508
455
|
|
|
509
456
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
@@ -619,20 +566,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
619
566
|
}
|
|
620
567
|
}
|
|
621
568
|
|
|
622
|
-
/**
|
|
623
|
-
* @param {CSN.Artifact} artifact
|
|
624
|
-
* @param {string} artifactName
|
|
625
|
-
*/
|
|
626
|
-
function replaceEnumsByBaseTypes(artifact, artifactName) {
|
|
627
|
-
replaceEnumByBaseType(artifact);
|
|
628
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
629
|
-
replaceEnumByBaseType(member);
|
|
630
|
-
if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
|
|
631
|
-
toFinalBaseType(member);
|
|
632
|
-
addDefaultTypeFacets(member);
|
|
633
|
-
}
|
|
634
|
-
}, [ 'definitions', artifactName ]);
|
|
635
|
-
}
|
|
636
569
|
|
|
637
570
|
/**
|
|
638
571
|
* @param {CSN.Artifact} artifact
|
|
@@ -641,7 +574,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
641
574
|
function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
|
|
642
575
|
// Fixme: For toHana mixins must be transformed, for toSql -d hana
|
|
643
576
|
// mixin elements must be transformed, why can't toSql also use mixins?
|
|
644
|
-
|
|
577
|
+
if(artifact.kind === 'entity' || artifact.query || (options.toHana && options.toHana.names === 'hdbcds' && artifact.kind == 'type'))
|
|
578
|
+
doit(artifact.elements, path.concat([ 'elements' ]));
|
|
645
579
|
if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
646
580
|
doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
|
|
647
581
|
|
|
@@ -699,60 +633,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
699
633
|
}, [ 'definitions', artifactName ]);
|
|
700
634
|
}
|
|
701
635
|
|
|
702
|
-
/**
|
|
703
|
-
*
|
|
704
|
-
* Generate foreign keys for managed associations
|
|
705
|
-
* Forbid aliases for foreign keys
|
|
706
|
-
*
|
|
707
|
-
* @param {CSN.Artifact} artifact
|
|
708
|
-
* @param {string} artifactName
|
|
709
|
-
*/
|
|
710
|
-
function handleAssociations(artifact, artifactName) {
|
|
711
|
-
// Do things specific for entities and views (pass 1)
|
|
712
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
713
|
-
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
714
|
-
for (const elemName in elements) {
|
|
715
|
-
const elem = elements[elemName];
|
|
716
|
-
// (140) Generate foreign key elements and ON-condition for managed associations
|
|
717
|
-
// (unless explicitly asked to keep assocs unchanged)
|
|
718
|
-
if (doA2J) {
|
|
719
|
-
if (isManagedAssociationElement(elem))
|
|
720
|
-
transformManagedAssociation(parent, artifactName, elem, elemName);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
})
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function fixBorkedElementsOfLocalized(elements, pathToElements){
|
|
728
|
-
const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
|
|
729
|
-
const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
for(const elementName in elements){
|
|
733
|
-
const element = elements[elementName];
|
|
734
|
-
const reference = nonLocalizedElements[elementName];
|
|
735
|
-
|
|
736
|
-
// if the declared element is an enum, these values are with priority
|
|
737
|
-
if (!element.enum && reference.enum)
|
|
738
|
-
Object.assign(element, { enum: reference.enum });
|
|
739
|
-
if (!element.length && reference.length && !reference.$default)
|
|
740
|
-
Object.assign(element, { length: reference.length });
|
|
741
|
-
if (!element.precision && reference.precision)
|
|
742
|
-
Object.assign(element, { precision: reference.precision });
|
|
743
|
-
if (!element.scale && reference.scale)
|
|
744
|
-
Object.assign(element, { scale: reference.scale });
|
|
745
|
-
if (!element.srid && reference.srid)
|
|
746
|
-
Object.assign(element, { srid: reference.srid });
|
|
747
|
-
if (!element.keys && reference.keys)
|
|
748
|
-
Object.assign(element, { keys: cloneCsn(reference.keys, options)})
|
|
749
|
-
if (!element.type && reference.type)
|
|
750
|
-
Object.assign(element, { type: reference.type})
|
|
751
|
-
if (!element.on && reference.on && !reference.keys)
|
|
752
|
-
Object.assign(element, {on: cloneCsn(reference.on, options)})
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
636
|
/**
|
|
757
637
|
* @param {CSN.Artifact} artifact
|
|
758
638
|
* @param {string} artifactName
|
|
@@ -784,48 +664,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
784
664
|
}
|
|
785
665
|
}
|
|
786
666
|
|
|
787
|
-
/**
|
|
788
|
-
* @param {CSN.Artifact} artifact
|
|
789
|
-
* @param {string} artifactName
|
|
790
|
-
*/
|
|
791
|
-
function handleQueryish(artifact, artifactName) {
|
|
792
|
-
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
793
|
-
|
|
794
|
-
if (stripQueryish) {
|
|
795
|
-
artifact.kind = 'entity';
|
|
796
|
-
delete artifact.query;
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
800
|
-
// All elements must have a type for this to work
|
|
801
|
-
if (stripQueryish && !member._ignore && !member.kind && !member.type)
|
|
802
|
-
error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
|
|
803
|
-
});
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
/**
|
|
807
|
-
* @param {CSN.Artifact} artifact
|
|
808
|
-
* @param {string} artifactName
|
|
809
|
-
*/
|
|
810
|
-
function handleCdsPersistence(artifact, artifactName) {
|
|
811
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
812
|
-
if (artifact.abstract
|
|
813
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.skip')
|
|
814
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.exists'))
|
|
815
|
-
artifact._ignore = true;
|
|
816
|
-
|
|
817
|
-
// issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
|
|
818
|
-
if (options.forHana.names === 'quoted' &&
|
|
819
|
-
hasAnnotationValue(artifact, '@cds.persistence.exists')) {
|
|
820
|
-
const firstPath = artifactName.split('.')[0];
|
|
821
|
-
const topParent = csn.definitions[firstPath];
|
|
822
|
-
// namespaces, contexts and services become contexts in HANA CDS
|
|
823
|
-
if (topParent && [ 'namespace', 'context', 'service' ].includes(topParent.kind))
|
|
824
|
-
warning(null, [ 'definitions', artifactName ], `"${ artifactName }": external definition belongs to ${ topParent.kind } "${ firstPath }"`);
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
667
|
function handleAssocToJoins() {
|
|
830
668
|
// With flattening errors, it makes little sense to continue.
|
|
831
669
|
throwWithError();
|
|
@@ -843,242 +681,11 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
843
681
|
if (art.technicalConfig)
|
|
844
682
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
845
683
|
|
|
846
|
-
const newArt = newCsn.definitions[artName];
|
|
847
|
-
|
|
848
|
-
// No need to loop/check artifacts that won't reach the DB anyways
|
|
849
|
-
if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
|
|
850
|
-
// Loop through the newCSN and add possible new _ignore mixin to the kill list
|
|
851
|
-
forAllQueries(newArt.query, (q, p) => {
|
|
852
|
-
if (q.SELECT && q.SELECT.mixin) {
|
|
853
|
-
for(let mixinName of Object.keys(q.SELECT.mixin)) {
|
|
854
|
-
const mixinElement = q.SELECT.mixin[mixinName];
|
|
855
|
-
if (mixinElement._ignore && options.toSql) {
|
|
856
|
-
columnClearer.push(() => {
|
|
857
|
-
const query = walkCsnPath(csn, p);
|
|
858
|
-
for(let i = query.columns.length-1; i > -1; i--){
|
|
859
|
-
const col = query.columns[i];
|
|
860
|
-
if(col && col.ref && col.ref[0] === mixinName){
|
|
861
|
-
query.columns.splice(i, 1);
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}, ['definitions', artName, 'query']);
|
|
869
|
-
}
|
|
870
684
|
});
|
|
871
685
|
csn = newCsn;
|
|
872
686
|
}
|
|
873
687
|
|
|
874
|
-
/**
|
|
875
|
-
* @param {CSN.Artifact} artifact
|
|
876
|
-
* @param {string} artifactName
|
|
877
|
-
*/
|
|
878
|
-
function handleTemporalAnnotations(artifact, artifactName) {
|
|
879
|
-
const validFrom = [];
|
|
880
|
-
const validTo = [];
|
|
881
|
-
const validKey = [];
|
|
882
|
-
|
|
883
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
884
|
-
const [ f, t, k ] = extractValidFromToKeyElement(member, path);
|
|
885
|
-
validFrom.push(...f);
|
|
886
|
-
validTo.push(...t);
|
|
887
|
-
validKey.push(...k);
|
|
888
|
-
});
|
|
889
|
-
|
|
890
|
-
if (artifact.kind === 'entity' && !artifact.query) {
|
|
891
|
-
validFrom.forEach(obj => checkAssignment('@cds.valid.from', obj.element, obj.path, artifact));
|
|
892
|
-
validTo.forEach(obj => checkAssignment('@cds.valid.to', obj.element, obj.path, artifact));
|
|
893
|
-
validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
|
|
894
|
-
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact, artifactName);
|
|
895
|
-
checkMultipleAssignments(validTo, '@cds.valid.to', artifact, artifactName, true);
|
|
896
|
-
checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// if there is an cds.valid.key, make this the only primary key
|
|
900
|
-
// otherwise add all cds.valid.from to primary key tuple
|
|
901
|
-
if (validKey.length) {
|
|
902
|
-
if (!validFrom.length || !validTo.length)
|
|
903
|
-
error(null, [ 'definitions', artifactName ],
|
|
904
|
-
'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
|
|
905
|
-
|
|
906
|
-
forEachMember(artifact, (member) => {
|
|
907
|
-
if (member.key) {
|
|
908
|
-
member.unique = true;
|
|
909
|
-
delete member.key;
|
|
910
|
-
// Remember that this element was a key in the original artifact.
|
|
911
|
-
// This is needed for localized convenience view generation.
|
|
912
|
-
setProp(member, '$key', true);
|
|
913
|
-
}
|
|
914
|
-
});
|
|
915
|
-
validKey.forEach((member) => {
|
|
916
|
-
member.element.key = true;
|
|
917
|
-
});
|
|
918
|
-
|
|
919
|
-
validFrom.forEach((member) => {
|
|
920
|
-
member.element.unique = true;
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
else {
|
|
924
|
-
validFrom.forEach((member) => {
|
|
925
|
-
member.element.key = true;
|
|
926
|
-
});
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
688
|
|
|
930
|
-
function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
|
|
931
|
-
let fromElement = elements[from.name];
|
|
932
|
-
let toElement = elements[to.name];
|
|
933
|
-
|
|
934
|
-
if(SELECT.columns) {
|
|
935
|
-
for(const col of SELECT.columns) {
|
|
936
|
-
if(col.ref) {
|
|
937
|
-
const implicitAlias = implicitAs(col.ref);
|
|
938
|
-
if(implicitAlias === from.name)
|
|
939
|
-
fromElement = elements[col.as || implicitAlias];
|
|
940
|
-
else if(implicitAlias === to.name)
|
|
941
|
-
toElement = elements[col.as || implicitAlias];
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
|
|
946
|
-
return val;
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
/**
|
|
950
|
-
* Add a where condition to views that
|
|
951
|
-
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
952
|
-
* - have only one @cds.valid.from and @cds.valid.to,
|
|
953
|
-
* - and both annotations come from the same entity
|
|
954
|
-
*
|
|
955
|
-
* If the view has one of the annotations but the other conditions are not met, an error will be raised.
|
|
956
|
-
*
|
|
957
|
-
* @param {CSN.Artifact} artifact
|
|
958
|
-
* @param {string} artifactName
|
|
959
|
-
*/
|
|
960
|
-
function addTemporalWhereConditionToView(artifact, artifactName) {
|
|
961
|
-
const normalizedQuery = getNormalizedQuery(artifact);
|
|
962
|
-
if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
|
|
963
|
-
// BLOCKER: We need information to handle $combined
|
|
964
|
-
// What we are trying to achieve by this:
|
|
965
|
-
// Forbid joining/selecting from two or more temporal entities
|
|
966
|
-
// Idea: Follow the query-tree and check each from
|
|
967
|
-
// Collect all source-entities and compute our own $combined
|
|
968
|
-
const $combined = get$combined(normalizedQuery.query);
|
|
969
|
-
const [ from, to ] = getFromToElements($combined);
|
|
970
|
-
// exactly one validFrom & validTo
|
|
971
|
-
if (from.length === 1 && to.length === 1) {
|
|
972
|
-
// and both are from the same origin
|
|
973
|
-
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
974
|
-
if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
|
|
975
|
-
const fromPath = {
|
|
976
|
-
ref: [
|
|
977
|
-
from[0].parent,
|
|
978
|
-
from[0].name,
|
|
979
|
-
],
|
|
980
|
-
};
|
|
981
|
-
|
|
982
|
-
const toPath = {
|
|
983
|
-
ref: [
|
|
984
|
-
to[0].parent,
|
|
985
|
-
to[0].name,
|
|
986
|
-
],
|
|
987
|
-
};
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
const atFrom = { ref: [ '$at', 'from' ] };
|
|
991
|
-
const atTo = { ref: [ '$at', 'to' ] };
|
|
992
|
-
|
|
993
|
-
const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
|
|
994
|
-
|
|
995
|
-
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
996
|
-
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
997
|
-
}
|
|
998
|
-
else {
|
|
999
|
-
normalizedQuery.query.SELECT.where = cond;
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
else {
|
|
1004
|
-
info(null, [ 'definitions', artifactName ], `No temporal WHERE clause added as "${ from[0].error_parent }"."${ from[0].name }" and "${ to[0].error_parent }"."${ to[0].name }" are not of same origin`);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
else if (from.length > 0 || to.length > 0) {
|
|
1008
|
-
const missingAnnotation = from.length > to.length ? '@cds.valid.to' : '@cds.valid.from';
|
|
1009
|
-
info(null, [ 'definitions', artifactName ],
|
|
1010
|
-
{ anno: missingAnnotation },
|
|
1011
|
-
'No temporal WHERE clause added because $(ANNO) is missing'
|
|
1012
|
-
)
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
/**
|
|
1018
|
-
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
1019
|
-
*
|
|
1020
|
-
* @param {any} combined union of all entities of the from-clause
|
|
1021
|
-
* @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
|
|
1022
|
-
*/
|
|
1023
|
-
function getFromToElements(combined) {
|
|
1024
|
-
const from = [];
|
|
1025
|
-
const to = [];
|
|
1026
|
-
for (const name in combined) {
|
|
1027
|
-
let elt = combined[name];
|
|
1028
|
-
if (!Array.isArray(elt))
|
|
1029
|
-
elt = [ elt ];
|
|
1030
|
-
elt.forEach((e) => {
|
|
1031
|
-
if (hasAnnotationValue(e.element, '@cds.valid.from'))
|
|
1032
|
-
from.push(e);
|
|
1033
|
-
|
|
1034
|
-
if (hasAnnotationValue(e.element, '@cds.valid.to'))
|
|
1035
|
-
to.push(e);
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
return [ from, to ];
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
/**
|
|
1043
|
-
* Associations that target a @cds.persistence.skip artifact must be removed
|
|
1044
|
-
* from the persistence model
|
|
1045
|
-
*
|
|
1046
|
-
* @param {CSN.Artifact} artifact
|
|
1047
|
-
* @param {string} artifactName
|
|
1048
|
-
* @param {string} prop
|
|
1049
|
-
* @param {CSN.Path} path
|
|
1050
|
-
*/
|
|
1051
|
-
function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
|
|
1052
|
-
if (isPersistedOnDatabase(artifact)) {
|
|
1053
|
-
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1054
|
-
if (artifact.query) {
|
|
1055
|
-
if (artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
1056
|
-
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1057
|
-
|
|
1058
|
-
else if (artifact.query.SET && artifact.query.SET.mixin)
|
|
1059
|
-
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1060
|
-
}
|
|
1061
|
-
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
1062
|
-
}
|
|
1063
|
-
function ignore(member, memberName, prop, path) {
|
|
1064
|
-
if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
1065
|
-
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
|
|
1066
|
-
info(null, path,
|
|
1067
|
-
{ target: member.target, anno: targetAnnotation },
|
|
1068
|
-
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
|
|
1069
|
-
);
|
|
1070
|
-
member._ignore = true;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
/**
|
|
1076
|
-
* @param {CSN.Artifact} art
|
|
1077
|
-
* @returns {boolean}
|
|
1078
|
-
*/
|
|
1079
|
-
function isUnreachableAssociationTarget(art) {
|
|
1080
|
-
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
|
|
1081
|
-
}
|
|
1082
689
|
|
|
1083
690
|
/**
|
|
1084
691
|
* Remove `localized` from elements and replace Enum symbols by their values.
|
|
@@ -1096,6 +703,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1096
703
|
|
|
1097
704
|
// (190 a) Replace enum symbols by their value (if found)
|
|
1098
705
|
replaceEnumSymbolsByValues(obj, path);
|
|
706
|
+
|
|
707
|
+
if (obj.enum)
|
|
708
|
+
delete obj.enum;
|
|
1099
709
|
}
|
|
1100
710
|
|
|
1101
711
|
// Change the names of those builtin types that have different names in HANA.
|
|
@@ -1129,309 +739,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1129
739
|
// }
|
|
1130
740
|
// }
|
|
1131
741
|
|
|
1132
|
-
/**
|
|
1133
|
-
* Strip of leading $self of the ref
|
|
1134
|
-
* @param {object} col A column
|
|
1135
|
-
*
|
|
1136
|
-
* @returns {object}
|
|
1137
|
-
*/
|
|
1138
|
-
function stripLeadingSelf(col) {
|
|
1139
|
-
if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
|
|
1140
|
-
col.ref = col.ref.slice(1);
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
return col;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
function isUnion(path){
|
|
1147
|
-
const subquery = path[path.length-1];
|
|
1148
|
-
const queryIndex = path[path.length-2]
|
|
1149
|
-
const args = path[path.length-3];
|
|
1150
|
-
const unionOperator = path[path.length-4];
|
|
1151
|
-
return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
function transformEntityOrViewPass2(query, artifact, artName, path) {
|
|
1155
|
-
const { elements } = queryOrMain(query, artifact);
|
|
1156
|
-
let hasNonAssocElements = false;
|
|
1157
|
-
const isSelect = query && query.SELECT;
|
|
1158
|
-
let isProjection = !!artifact.projection;
|
|
1159
|
-
const columnMap = Object.create(null);
|
|
1160
|
-
let isSelectStar = false;
|
|
1161
|
-
if (isSelect) {
|
|
1162
|
-
if (!query.SELECT.columns) {
|
|
1163
|
-
isProjection = true;
|
|
1164
|
-
}
|
|
1165
|
-
else {
|
|
1166
|
-
query.SELECT.columns.forEach((col) => {
|
|
1167
|
-
if (col === '*') {
|
|
1168
|
-
isSelectStar = true;
|
|
1169
|
-
}
|
|
1170
|
-
else if (col.as) {
|
|
1171
|
-
if (!columnMap[col.as])
|
|
1172
|
-
columnMap[col.as] = col;
|
|
1173
|
-
}
|
|
1174
|
-
else if (col.ref) {
|
|
1175
|
-
if (!columnMap[col.ref[col.ref.length - 1]])
|
|
1176
|
-
columnMap[col.ref[col.ref.length - 1]] = col;
|
|
1177
|
-
}
|
|
1178
|
-
else if (col.func) {
|
|
1179
|
-
columnMap[col.func] = col;
|
|
1180
|
-
}
|
|
1181
|
-
else if (!columnMap[col]) {
|
|
1182
|
-
columnMap[col] = col;
|
|
1183
|
-
}
|
|
1184
|
-
});
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
if (query && options.transformation === 'hdbcds') {
|
|
1188
|
-
// check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
|
|
1189
|
-
if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
|
|
1190
|
-
for (const elementName in elements) {
|
|
1191
|
-
const element = elements[elementName];
|
|
1192
|
-
if (element.target) {
|
|
1193
|
-
let colLocation;
|
|
1194
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1195
|
-
const col = query.SELECT.columns[i];
|
|
1196
|
-
if (col.ref && col.ref.length === 1) {
|
|
1197
|
-
if (!colLocation && col.ref[0] === elementName)
|
|
1198
|
-
colLocation = i;
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
if (col.as === elementName)
|
|
1202
|
-
colLocation = i;
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
if (colLocation) {
|
|
1206
|
-
const matchingCol = query.SELECT.columns[colLocation];
|
|
1207
|
-
const possibleMixinName = matchingCol.ref[0];
|
|
1208
|
-
const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
|
|
1209
|
-
if (element.target && isMixin)
|
|
1210
|
-
error(null, path.concat([ 'columns', colLocation ]),
|
|
1211
|
-
`Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// Second walk through the entity elements: Deal with associations (might also result in new elements)
|
|
1219
|
-
|
|
1220
|
-
// Will be initialized JIT inside the elements-loop
|
|
1221
|
-
let $combined;
|
|
1222
|
-
|
|
1223
|
-
for (const elemName in elements) {
|
|
1224
|
-
const elem = elements[elemName];
|
|
1225
|
-
if (isSelect) {
|
|
1226
|
-
if (!columnMap[elemName]) {
|
|
1227
|
-
// Prepend an alias if present
|
|
1228
|
-
let alias = (isProjection || isSelectStar) &&
|
|
1229
|
-
(query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
|
|
1230
|
-
// In case of * and no explicit alias
|
|
1231
|
-
// find the source of the col by looking at $combined and prepend it
|
|
1232
|
-
if (isSelectStar && !alias && !isProjection) {
|
|
1233
|
-
if (!$combined)
|
|
1234
|
-
$combined = get$combined(query);
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
const matchingCombined = $combined[elemName];
|
|
1238
|
-
// Internal errors - this should never happen!
|
|
1239
|
-
if (matchingCombined.length > 1) { // should already be caught by compiler
|
|
1240
|
-
throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
|
|
1241
|
-
}
|
|
1242
|
-
else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
|
|
1243
|
-
throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
|
|
1244
|
-
}
|
|
1245
|
-
alias = matchingCombined[0].parent;
|
|
1246
|
-
}
|
|
1247
|
-
if (alias)
|
|
1248
|
-
columnMap[elemName] = { ref: [ alias, elemName ] };
|
|
1249
|
-
else
|
|
1250
|
-
columnMap[elemName] = { ref: [ elemName ] };
|
|
1251
|
-
}
|
|
1252
|
-
|
|
1253
|
-
// For associations - make sure that the foreign keys have the same "style"
|
|
1254
|
-
// If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
|
|
1255
|
-
if (elem.keys && doA2J) {
|
|
1256
|
-
const assoc_col = columnMap[elemName];
|
|
1257
|
-
if (assoc_col && assoc_col.ref) {
|
|
1258
|
-
elem.keys.forEach((key) => {
|
|
1259
|
-
const ref = cloneCsn(assoc_col.ref, options);
|
|
1260
|
-
ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
|
|
1261
|
-
const result = {
|
|
1262
|
-
ref,
|
|
1263
|
-
};
|
|
1264
|
-
if (assoc_col.as)
|
|
1265
|
-
result.as = key.$generatedFieldName;
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (assoc_col.key)
|
|
1269
|
-
result.key = true;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const colName = result.as || ref[ref.length - 1];
|
|
1273
|
-
columnMap[colName] = result;
|
|
1274
|
-
});
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
// Add flattened structured things preserving aliases and refs with/without table alias
|
|
1278
|
-
// If we add them when we get to them in "elements", we cannot know what table alias was used...
|
|
1279
|
-
if (isStructured(elem) && doA2J) {
|
|
1280
|
-
const col = columnMap[elemName];
|
|
1281
|
-
const originalName = col.ref[col.ref.length - 1];
|
|
1282
|
-
const flatElements = flattenStructuredElement(elem, originalName, [], path);
|
|
1283
|
-
const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
|
|
1284
|
-
|
|
1285
|
-
Object.keys(flatElements).forEach((flatElemName, index ) => {
|
|
1286
|
-
const clone = cloneCsn(col, options);
|
|
1287
|
-
// For the ref, use the "original"
|
|
1288
|
-
if (clone.ref)
|
|
1289
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1290
|
-
|
|
1291
|
-
// If the column was aliased, use the alias-prefix for the flattened element
|
|
1292
|
-
if (originalName !== elemName)
|
|
1293
|
-
clone.as = aliasedFlatElements[index];
|
|
1294
|
-
|
|
1295
|
-
// Insert into map, giving precedence to the alias
|
|
1296
|
-
columnMap[clone.as || flatElemName] = clone;
|
|
1297
|
-
});
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
// Views must have at least one element that is not an unmanaged assoc
|
|
1301
|
-
if (!elem.on && !elem._ignore)
|
|
1302
|
-
hasNonAssocElements = true;
|
|
1303
|
-
|
|
1304
|
-
// (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
|
|
1305
|
-
// CDXCORE-585: Allow mixin associations to be used and published in parallel
|
|
1306
|
-
if (query !== undefined && elem.target) {
|
|
1307
|
-
if(isUnion(path) && options.transformation === 'hdbcds'){
|
|
1308
|
-
if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
|
|
1309
|
-
if(elem.keys) {
|
|
1310
|
-
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
|
|
1311
|
-
} else {
|
|
1312
|
-
info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
|
|
1313
|
-
}
|
|
1314
|
-
elem._ignore = true;
|
|
1315
|
-
}
|
|
1316
|
-
else {
|
|
1317
|
-
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
|
|
1318
|
-
}
|
|
1319
|
-
} else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
|
|
1320
|
-
error(null, path, { name: elemName },
|
|
1321
|
-
'Association $(NAME) can\'t be published in a subquery')
|
|
1322
|
-
} else {
|
|
1323
|
-
/* Old implementation:
|
|
1324
|
-
const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
|
|
1325
|
-
*/
|
|
1326
|
-
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
|
|
1327
|
-
const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
|
|
1328
|
-
if (isNotMixinByItself || mixinElement !== undefined) {
|
|
1329
|
-
// If the mixin is only published and not used, only display the __ clone. Ignore the "original".
|
|
1330
|
-
if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
|
|
1331
|
-
mixinElement._ignore = true;
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
delete elem._typeIsExplicit;
|
|
1335
|
-
// Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
|
|
1336
|
-
let mixinElemName = `___${ mixinName || elemName }`;
|
|
1337
|
-
while (elements[mixinElemName])
|
|
1338
|
-
mixinElemName = `_${ mixinElemName }`;
|
|
1339
|
-
|
|
1340
|
-
// Copy the association element to the MIXIN clause under its alias name
|
|
1341
|
-
// (shallow copy is sufficient, just fix name and value)
|
|
1342
|
-
const mixinElem = Object.assign({}, elem);
|
|
1343
|
-
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
|
|
1344
|
-
transformCommon(mixinElem, mixinElemName);
|
|
1345
|
-
// TODO: Can we rely on query.SELECT.mixin to check for mixins?
|
|
1346
|
-
// Yes, we can - only SELECT can have mixin. But:
|
|
1347
|
-
// - UNION
|
|
1348
|
-
// - JOINS
|
|
1349
|
-
// - Subqueries
|
|
1350
|
-
// Are currently (and in the old transformer) not handled!
|
|
1351
|
-
if (query.SELECT && !query.SELECT.mixin)
|
|
1352
|
-
query.SELECT.mixin = Object.create(null);
|
|
1353
|
-
|
|
1354
|
-
// Let the original association element use the newly generated MIXIN name as value and alias
|
|
1355
|
-
delete elem.viaAll;
|
|
1356
|
-
|
|
1357
|
-
// Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
|
|
1358
|
-
// and fixing the association alias just created
|
|
1359
|
-
|
|
1360
|
-
if (mixinElem.on) {
|
|
1361
|
-
mixinElem.on = cloneWithTransformations(mixinElem.on, {
|
|
1362
|
-
ref: (ref) => {
|
|
1363
|
-
// Clone the path, without any transformations
|
|
1364
|
-
const clonedPath = cloneWithTransformations(ref, {});
|
|
1365
|
-
// Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
|
|
1366
|
-
if (clonedPath[0] == elemName) {
|
|
1367
|
-
clonedPath[0] = mixinElemName;
|
|
1368
|
-
}
|
|
1369
|
-
else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
|
|
1370
|
-
const projectionId = '$projection';
|
|
1371
|
-
clonedPath.unshift(projectionId);
|
|
1372
|
-
}
|
|
1373
|
-
return clonedPath;
|
|
1374
|
-
},
|
|
1375
|
-
func: (func) => {
|
|
1376
|
-
// Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
|
|
1377
|
-
// above (no way to distinguish that in the callback for 'path' above). We can only pluck it
|
|
1378
|
-
// off again here ... sigh
|
|
1379
|
-
if (func.ref && func.ref[0] && func.ref[0] === '$projection')
|
|
1380
|
-
func.ref = func.ref.slice(1);
|
|
1381
|
-
|
|
1382
|
-
return func;
|
|
1383
|
-
},
|
|
1384
|
-
});
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
if (!mixinElem._ignore)
|
|
1388
|
-
columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
|
|
1389
|
-
|
|
1390
|
-
if (query.SELECT) {
|
|
1391
|
-
query.SELECT.mixin[mixinElemName] = mixinElem;
|
|
1392
|
-
}
|
|
1393
|
-
}
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
if (query && !hasNonAssocElements) {
|
|
1399
|
-
// Complain if there are no elements other than unmanaged associations
|
|
1400
|
-
// Allow with plain
|
|
1401
|
-
error(null, [ 'definitions', artName ], { $reviewed: true } ,
|
|
1402
|
-
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
if (isSelect) {
|
|
1406
|
-
// Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
|
|
1407
|
-
// If a select item of a cdx view contains an expression, the result type cannot be computed
|
|
1408
|
-
// but must be explicitly specified. This is important for the OData channel, which doesn't
|
|
1409
|
-
// work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
|
|
1410
|
-
// can compute the result type).
|
|
1411
|
-
// Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
|
|
1412
|
-
// diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
|
|
1413
|
-
// type in the HANA channel via an annotation.
|
|
1414
|
-
Object.keys(columnMap).forEach((value) => {
|
|
1415
|
-
const elem = elements[value];
|
|
1416
|
-
if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
|
|
1417
|
-
delete columnMap[value].cast;
|
|
1418
|
-
});
|
|
1419
|
-
|
|
1420
|
-
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
1421
|
-
// If following an association, explicitly set the implicit alias
|
|
1422
|
-
// due to an issue with HANA
|
|
1423
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1424
|
-
const col = query.SELECT.columns[i];
|
|
1425
|
-
if (!col.as && col.ref && col.ref.length > 1) {
|
|
1426
|
-
const { links } = inspectRef(path.concat([ 'columns', i ]));
|
|
1427
|
-
if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
|
|
1428
|
-
col.as = col.ref[col.ref.length - 1];
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
742
|
|
|
1436
743
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
1437
744
|
// if not found or not an enum type,
|
|
@@ -1448,7 +755,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1448
755
|
// Looks like it is always run?! But message says HANA CDS?!
|
|
1449
756
|
error(null, path, {
|
|
1450
757
|
$reviewed: true,
|
|
1451
|
-
name: `#${elem.default['#']}`
|
|
758
|
+
name: `#${ elem.default['#'] }`
|
|
1452
759
|
},
|
|
1453
760
|
'Expecting enum literal $(NAME) to be used with an enum type');
|
|
1454
761
|
}
|
|
@@ -1458,7 +765,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1458
765
|
if (!enumSymbol) {
|
|
1459
766
|
error(null, path, {
|
|
1460
767
|
$reviewed: true,
|
|
1461
|
-
name: `#${elem.default['#']}`
|
|
768
|
+
name: `#${ elem.default['#'] }`
|
|
1462
769
|
}, 'Enum literal $(NAME) is undefined in enumeration type');
|
|
1463
770
|
}
|
|
1464
771
|
else if (enumSymbol.val !== undefined) { // `val` may be `null`
|
|
@@ -1475,30 +782,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1475
782
|
}
|
|
1476
783
|
}
|
|
1477
784
|
|
|
1478
|
-
// If 'node' has an enum type, change node's type to be the enum's base type
|
|
1479
|
-
// and strip off the 'enum' property.
|
|
1480
|
-
function replaceEnumByBaseType(node) {
|
|
1481
|
-
if (node.items)
|
|
1482
|
-
replaceEnumByBaseType(node.items);
|
|
1483
|
-
|
|
1484
|
-
// (190 b) Replace enum types by their final base type (must happen after 190 a)
|
|
1485
|
-
/* Old implementation:
|
|
1486
|
-
if (node && node._finalType && (node.enum || node._finalType.enum)) {
|
|
1487
|
-
node.type = node._finalType.type
|
|
1488
|
-
// node.type = node._finalType.type._artifact._finalType.type;
|
|
1489
|
-
if (node._finalType.length) {
|
|
1490
|
-
node.length = node._finalType.length;
|
|
1491
|
-
}
|
|
1492
|
-
setProp(node, '_finalType', node.type._artifact);
|
|
1493
|
-
delete node.enum;
|
|
1494
|
-
}
|
|
1495
|
-
*/
|
|
1496
|
-
if (node && node.enum) {
|
|
1497
|
-
// toFinalBaseType(node);
|
|
1498
|
-
// addDefaultTypeFacets(node);
|
|
1499
|
-
delete node.enum;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
785
|
|
|
1503
786
|
// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
|
|
1504
787
|
// (in place) so that it
|
|
@@ -1538,14 +821,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1538
821
|
if (multipleExprs)
|
|
1539
822
|
result.push(')');
|
|
1540
823
|
i += 3;
|
|
1541
|
-
|
|
1542
|
-
if(elem.$selfOnCondition)
|
|
1543
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1544
|
-
else {
|
|
1545
|
-
setProp(elem, '$selfOnCondition', {
|
|
1546
|
-
backlinkName
|
|
1547
|
-
}) // important for the foreign key constraints
|
|
1548
|
-
}
|
|
824
|
+
attachBacklinkInformation(backlinkName);
|
|
1549
825
|
}
|
|
1550
826
|
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
1551
827
|
const assoc = inspectRef(path.concat([ i ])).art;
|
|
@@ -1556,14 +832,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1556
832
|
if (multipleExprs)
|
|
1557
833
|
result.push(')');
|
|
1558
834
|
i += 3;
|
|
1559
|
-
|
|
1560
|
-
if(elem.$selfOnCondition)
|
|
1561
|
-
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1562
|
-
else {
|
|
1563
|
-
setProp(elem, '$selfOnCondition', {
|
|
1564
|
-
backlinkName
|
|
1565
|
-
}) // important for the foreign key constraints
|
|
1566
|
-
}
|
|
835
|
+
attachBacklinkInformation(backlinkName);
|
|
1567
836
|
}
|
|
1568
837
|
// Otherwise take one (!) token unchanged
|
|
1569
838
|
else {
|
|
@@ -1583,6 +852,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1583
852
|
}
|
|
1584
853
|
}
|
|
1585
854
|
return result;
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* The knowledge whether an association was an `<up_>` association in a
|
|
858
|
+
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
|
|
859
|
+
* By the time we generate them, such on-conditions are already transformed
|
|
860
|
+
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
|
|
861
|
+
*
|
|
862
|
+
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
|
|
863
|
+
*/
|
|
864
|
+
function attachBacklinkInformation(backlinkName) {
|
|
865
|
+
if (elem.$selfOnCondition)
|
|
866
|
+
elem.$selfOnCondition.up_.push(backlinkName);
|
|
867
|
+
else {
|
|
868
|
+
setProp(elem, '$selfOnCondition', {
|
|
869
|
+
up_: [backlinkName]
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
}
|
|
1586
873
|
}
|
|
1587
874
|
|
|
1588
875
|
elem.on = processExpressionArgs(elem.on, pathToOn);
|
|
@@ -1653,14 +940,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1653
940
|
conditions.push([ a[0], '=', a[1] ]);
|
|
1654
941
|
});
|
|
1655
942
|
|
|
1656
|
-
|
|
943
|
+
return conditions.reduce((prev, current) => {
|
|
1657
944
|
if (prev.length === 0)
|
|
1658
945
|
return [ ...current ];
|
|
1659
946
|
|
|
1660
947
|
return [ ...prev, 'and', ...current ];
|
|
1661
948
|
}, []);
|
|
1662
|
-
|
|
1663
|
-
return result;
|
|
1664
949
|
}
|
|
1665
950
|
|
|
1666
951
|
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
@@ -1683,6 +968,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1683
968
|
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
1684
969
|
{
|
|
1685
970
|
ref.shift();
|
|
971
|
+
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
972
|
+
// We could also have a $self infront of the assoc name - so we would need to shift twice
|
|
973
|
+
ref.shift();
|
|
974
|
+
ref.shift();
|
|
1686
975
|
}
|
|
1687
976
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
1688
977
|
ref.unshift(elemName);
|
|
@@ -1696,26 +985,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1696
985
|
}
|
|
1697
986
|
}
|
|
1698
987
|
|
|
1699
|
-
/**
|
|
1700
|
-
* @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
|
|
1701
|
-
*
|
|
1702
|
-
* @param {CSN.Query} query
|
|
1703
|
-
* @param {object} columnMap
|
|
1704
|
-
* @param {CSN.Artifact} columnMap
|
|
1705
|
-
* @param {string} elementName
|
|
1706
|
-
*/
|
|
1707
|
-
function checkIsNotMixinByItself(query, columnMap, element, elementName) {
|
|
1708
|
-
if (query && query.SELECT && query.SELECT.mixin) {
|
|
1709
|
-
const col = columnMap[elementName];
|
|
1710
|
-
|
|
1711
|
-
const realName = col.ref[col.ref.length - 1];
|
|
1712
|
-
// If the element is not part of the mixin => True
|
|
1713
|
-
return query.SELECT.mixin[realName] == undefined;
|
|
1714
|
-
}
|
|
1715
|
-
// the artifact does not define any mixins, the element cannot be a mixin
|
|
1716
|
-
return true;
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
988
|
/**
|
|
1720
989
|
* @param {CSN.Artifact} artifact
|
|
1721
990
|
* @param {string} artifactName
|
|
@@ -1812,148 +1081,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1812
1081
|
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
|
|
1813
1082
|
}
|
|
1814
1083
|
|
|
1815
|
-
/**
|
|
1816
|
-
* Flatten and create the foreign key elements of managed associaitons
|
|
1817
|
-
*
|
|
1818
|
-
* @param {CSN.Artifact} art
|
|
1819
|
-
* @param {string} artName
|
|
1820
|
-
*/
|
|
1821
|
-
function handleManagedAssociationFKs(art, artName) {
|
|
1822
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
1823
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
1824
|
-
if(artName.startsWith('localized.') && pathToElements.length > 3) {
|
|
1825
|
-
// In subqueries, the elements of localized views are missing all the important bits and pieces...
|
|
1826
|
-
fixBorkedElementsOfLocalized(elements, pathToElements);
|
|
1827
|
-
}
|
|
1828
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
1829
|
-
if (isManagedAssociationElement(element)) {
|
|
1830
|
-
if (element.keys) {
|
|
1831
|
-
// replace foreign keys that are managed associations by their respective foreign keys
|
|
1832
|
-
flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
});
|
|
1836
|
-
})
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
/**
|
|
1842
|
-
* Flattens all foreign keys
|
|
1843
|
-
*
|
|
1844
|
-
* Structures will be resolved to individual elements with scalar types
|
|
1845
|
-
*
|
|
1846
|
-
* Associations will be replaced by their respective foreign keys
|
|
1847
|
-
*
|
|
1848
|
-
* If a structure contains an assoc, this will also be resolved and vice versa
|
|
1849
|
-
*
|
|
1850
|
-
* @param {*} assoc
|
|
1851
|
-
* @param {*} assocName
|
|
1852
|
-
* @param {*} path
|
|
1853
|
-
*/
|
|
1854
|
-
function flattenFKs(assoc, assocName, path) {
|
|
1855
|
-
let finished = false;
|
|
1856
|
-
while(!finished) {
|
|
1857
|
-
const newKeys = [];
|
|
1858
|
-
finished = processKeys(assoc, assocName, path, newKeys);
|
|
1859
|
-
assoc.keys = newKeys;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
function processKeys(assoc, assocName, path, collector) {
|
|
1863
|
-
let finished = true;
|
|
1864
|
-
for (let i = 0; i < assoc.keys.length; i++) {
|
|
1865
|
-
const pathToKey = path.concat([ 'keys', i ]);
|
|
1866
|
-
const { art } = inspectRef(pathToKey);
|
|
1867
|
-
const { ref } = assoc.keys[i];
|
|
1868
|
-
if (isStructured(art)) {
|
|
1869
|
-
finished = false;
|
|
1870
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1871
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1872
|
-
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
1873
|
-
Object.keys(flat).forEach((flatElemName) => {
|
|
1874
|
-
const key = assoc.keys[i];
|
|
1875
|
-
const clone = cloneCsn(assoc.keys[i], options);
|
|
1876
|
-
if (clone.as) {
|
|
1877
|
-
const lastRef = clone.ref[clone.ref.length - 1];
|
|
1878
|
-
// Cut off the last ref part from the beginning of the flat name
|
|
1879
|
-
const flatBaseName = flatElemName.slice(lastRef.length);
|
|
1880
|
-
// Join it to the existing table alias
|
|
1881
|
-
clone.as += flatBaseName;
|
|
1882
|
-
// do not loose the $ref for nested keys
|
|
1883
|
-
if(key.$ref){
|
|
1884
|
-
let aliasedLeaf = key.$ref[key.$ref.length - 1 ];
|
|
1885
|
-
aliasedLeaf += flatBaseName;
|
|
1886
|
-
setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
if (clone.ref) {
|
|
1890
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1891
|
-
// Now we need to properly flatten the whole ref
|
|
1892
|
-
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
|
|
1893
|
-
}
|
|
1894
|
-
if (!clone.as) {
|
|
1895
|
-
clone.as = flatElemName;
|
|
1896
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1897
|
-
setProp(clone, '$inferredAlias', true);
|
|
1898
|
-
}
|
|
1899
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1900
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1901
|
-
// Recursive solutions run into call stack issues
|
|
1902
|
-
collector.push(clone);
|
|
1903
|
-
});
|
|
1904
|
-
}
|
|
1905
|
-
else if (art.target) {
|
|
1906
|
-
finished = false;
|
|
1907
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1908
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1909
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1910
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1911
|
-
// Recursive solutions run into call stack issues
|
|
1912
|
-
art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
|
|
1913
|
-
}
|
|
1914
|
-
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
1915
|
-
setProp(assoc.keys[i], '$inferredAlias', true);
|
|
1916
|
-
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
|
|
1917
|
-
collector.push(assoc.keys[i]);
|
|
1918
|
-
} else {
|
|
1919
|
-
collector.push(assoc.keys[i]);
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
return finished;
|
|
1923
|
-
}
|
|
1924
|
-
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
|
-
function cloneAndExtendRef(key, base, ref) {
|
|
1928
|
-
const clone = cloneCsn(base, options);
|
|
1929
|
-
if (key.ref) {
|
|
1930
|
-
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
1931
|
-
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
1932
|
-
// Later on, after we know that these foreign key elements are created, we replace ref with this $ref
|
|
1933
|
-
let $ref;
|
|
1934
|
-
if(base.$ref){
|
|
1935
|
-
// if a base $ref is provided, use it to correctly resolve association chains
|
|
1936
|
-
const refChain = [base.$ref[base.$ref.length - 1]].concat(key.as || key.ref);
|
|
1937
|
-
$ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain)
|
|
1938
|
-
} else {
|
|
1939
|
-
$ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
|
|
1940
|
-
}
|
|
1941
|
-
setProp(clone, '$ref', $ref);
|
|
1942
|
-
clone.ref = clone.ref.concat(key.ref);
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
1946
|
-
clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1947
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1948
|
-
setProp(clone, '$inferredAlias', true);
|
|
1949
|
-
}
|
|
1950
|
-
else {
|
|
1951
|
-
clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
return clone;
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
1084
|
/**
|
|
1958
1085
|
* Flatten technical configuration stuff
|
|
1959
1086
|
*
|
|
@@ -2050,11 +1177,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2050
1177
|
const source = findSource(links, i - 1) || artifact;
|
|
2051
1178
|
// allow specifying managed assoc on the source side
|
|
2052
1179
|
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
2053
|
-
|
|
2054
1180
|
if(fks && fks.length >= 1){
|
|
2055
1181
|
const fk = fks[0];
|
|
2056
|
-
|
|
2057
|
-
|
|
1182
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
1183
|
+
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
|
|
1184
|
+
if(source && source.elements[fkName])
|
|
1185
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
2058
1186
|
}
|
|
2059
1187
|
}
|
|
2060
1188
|
}
|
|
@@ -2085,80 +1213,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2085
1213
|
return undefined;
|
|
2086
1214
|
}
|
|
2087
1215
|
}
|
|
2088
|
-
|
|
2089
|
-
/**
|
|
2090
|
-
* Create the foreign key elements for a managed association and build the on-condition
|
|
2091
|
-
*
|
|
2092
|
-
* @param {CSN.Artifact} artifact
|
|
2093
|
-
* @param {string} artifactName
|
|
2094
|
-
* @param {Object} elem The association to process
|
|
2095
|
-
* @param {string} elemName
|
|
2096
|
-
* @returns {void}
|
|
2097
|
-
*/
|
|
2098
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName) {
|
|
2099
|
-
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
2100
|
-
// of another association - see a few lines lower
|
|
2101
|
-
if (elem.$fksgenerated)
|
|
2102
|
-
return;
|
|
2103
|
-
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
2104
|
-
const onCondParts = [];
|
|
2105
|
-
let join_with_and = false;
|
|
2106
|
-
if(elem.keys.length === 0)
|
|
2107
|
-
elem._ignore = true;
|
|
2108
|
-
else {
|
|
2109
|
-
for (let i = 0; i < elem.keys.length; i++) {
|
|
2110
|
-
const foreignKey = elem.keys[i];
|
|
2111
|
-
|
|
2112
|
-
// Assemble left hand side of 'assoc.key = fkey'
|
|
2113
|
-
const assocKeyArg = {
|
|
2114
|
-
ref: [
|
|
2115
|
-
elemName,
|
|
2116
|
-
].concat(foreignKey.ref),
|
|
2117
|
-
};
|
|
2118
|
-
|
|
2119
|
-
const fKeyArg = {
|
|
2120
|
-
ref: [
|
|
2121
|
-
foreignKey.$generatedFieldName,
|
|
2122
|
-
],
|
|
2123
|
-
};
|
|
2124
|
-
|
|
2125
|
-
if (join_with_and) { // more than one FK
|
|
2126
|
-
onCondParts.push('and');
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
onCondParts.push(
|
|
2130
|
-
assocKeyArg
|
|
2131
|
-
);
|
|
2132
|
-
onCondParts.push('=');
|
|
2133
|
-
onCondParts.push(fKeyArg);
|
|
2134
|
-
|
|
2135
|
-
if (!join_with_and)
|
|
2136
|
-
join_with_and = true;
|
|
2137
|
-
}
|
|
2138
|
-
elem.on = onCondParts;
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
// If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
|
|
2142
|
-
// TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
|
|
2143
|
-
if (elem.key)
|
|
2144
|
-
delete elem.key;
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
// If the managed association has a 'not null' property => remove it
|
|
2148
|
-
if (elem.notNull)
|
|
2149
|
-
delete elem.notNull;
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
2153
|
-
// at all. But the processing of backlink associations below expects to have them, so
|
|
2154
|
-
// we don't delete them (but mark them as implicit so that toCdl does not render them)
|
|
2155
|
-
/* Skip for now - forHana adds this to elements, but it is not part of the resulting CSN
|
|
2156
|
-
forHanaNew -> Somehow ends up in the CSN?!
|
|
2157
|
-
elem.implicitForeignKeys = true;
|
|
2158
|
-
*/
|
|
2159
|
-
// Remember that we already processed this
|
|
2160
|
-
setProp(elem, '$fksgenerated', true);
|
|
2161
|
-
}
|
|
2162
1216
|
}
|
|
2163
1217
|
|
|
2164
1218
|
|