@sap/cds-compiler 2.11.2 → 2.13.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -1,19 +1,18 @@
|
|
|
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, forAllQueries, applyTransformationsOnNonDictionary,
|
|
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 { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
|
|
17
16
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
18
17
|
const { timetrace } = require('../utils/timetrace');
|
|
19
18
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
@@ -24,9 +23,13 @@ const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
|
24
23
|
const flattening = require('./db/flattening');
|
|
25
24
|
const expansion = require('./db/expansion');
|
|
26
25
|
const assertUnique = require('./db/assertUnique');
|
|
27
|
-
const generateDrafts = require('./db
|
|
28
|
-
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
26
|
+
const generateDrafts = require('./draft/db');
|
|
27
|
+
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
|
|
29
28
|
const { getViewTransformer } = require('./db/views');
|
|
29
|
+
const cdsPersistence = require('./db/cdsPersistence');
|
|
30
|
+
const temporal = require('./db/temporal');
|
|
31
|
+
const associations = require('./db/associations')
|
|
32
|
+
const { ModelError } = require("../base/error");
|
|
30
33
|
|
|
31
34
|
// By default: Do not process non-entities/views
|
|
32
35
|
function forEachDefinition(csn, cb) {
|
|
@@ -111,19 +114,19 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
111
114
|
|
|
112
115
|
const pathDelimiter = (options.forHana.names === 'hdbcds') ? '.' : '_';
|
|
113
116
|
|
|
114
|
-
let error, warning, info; // message functions
|
|
117
|
+
let message, error, warning, info; // message functions
|
|
115
118
|
/** @type {() => void} */
|
|
116
119
|
let throwWithError;
|
|
117
|
-
let artifactRef, inspectRef, effectiveType,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
let artifactRef, inspectRef, effectiveType, get$combined,
|
|
121
|
+
getFinalBaseType, // csnUtils (csnRefs)
|
|
122
|
+
addDefaultTypeFacets, expandStructsInExpression; // transformUtils
|
|
120
123
|
|
|
121
124
|
bindCsnReference();
|
|
122
125
|
|
|
123
126
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
124
|
-
|
|
127
|
+
|
|
125
128
|
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
126
|
-
enrichUniversalCsn(csn, options);
|
|
129
|
+
enrichUniversalCsn(csn, options);
|
|
127
130
|
bindCsnReference();
|
|
128
131
|
}
|
|
129
132
|
|
|
@@ -134,7 +137,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
134
137
|
|
|
135
138
|
// Run validations on CSN - each validator function has access to the message functions and the inspect ref via this
|
|
136
139
|
const cleanup = validate.forHana(csn, {
|
|
137
|
-
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
140
|
+
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
138
141
|
});
|
|
139
142
|
|
|
140
143
|
// Needs to happen before tuple expansion, so the newly generated WHERE-conditions have it applied
|
|
@@ -145,6 +148,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
145
148
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
146
149
|
// If errors are detected, throwWithError() will return from further processing
|
|
147
150
|
|
|
151
|
+
// If this function is ever undefined, we have a bug in our logic.
|
|
152
|
+
// @ts-ignore
|
|
148
153
|
expandStructsInExpression(csn, { drillRef: true });
|
|
149
154
|
|
|
150
155
|
throwWithError();
|
|
@@ -155,7 +160,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
155
160
|
|
|
156
161
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
157
162
|
// assoc2join eventually rewrites the table aliases
|
|
158
|
-
forEachDefinition(csn,
|
|
163
|
+
forEachDefinition(csn, temporal.getViewDecorator(csn, {info}));
|
|
159
164
|
|
|
160
165
|
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
161
166
|
assertUnique.prepare(csn, options, error, info);
|
|
@@ -213,57 +218,25 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
213
218
|
}
|
|
214
219
|
});
|
|
215
220
|
|
|
216
|
-
// Must happen after A2J, as A2J needs $self to correctly resolve stuff
|
|
217
|
-
if(doA2J)
|
|
218
|
-
flattening.removeLeadingSelf(csn);
|
|
219
|
-
|
|
220
221
|
const {
|
|
221
222
|
flattenStructuredElement,
|
|
222
|
-
flattenStructStepsInRef,
|
|
223
|
+
flattenStructStepsInRef,
|
|
223
224
|
isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
224
|
-
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
|
|
225
|
-
recurseElements
|
|
226
225
|
} = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
227
226
|
|
|
228
227
|
const {
|
|
229
228
|
getCsnDef,
|
|
230
229
|
isAssocOrComposition,
|
|
231
|
-
isManagedAssociationElement,
|
|
232
|
-
isStructured,
|
|
233
230
|
addStringAnnotationTo,
|
|
234
231
|
cloneWithTransformations,
|
|
235
232
|
} = getUtils(csn);
|
|
236
233
|
|
|
237
234
|
// (000) Rename primitive types, make UUID a String
|
|
238
235
|
transformCsn(csn, {
|
|
239
|
-
type: (val, node, key
|
|
240
|
-
// Resolve type-of chains
|
|
241
|
-
function fn() {
|
|
242
|
-
// val can be undefined: books as myBooks : redirected to Model.MyBooks
|
|
243
|
-
if (val && val.ref) {
|
|
244
|
-
const { art } = inspectRef(path);
|
|
245
|
-
if (art && art.type) {
|
|
246
|
-
val = art.type;
|
|
247
|
-
// This is somehow needed to update the ref so that inspectRef sees it
|
|
248
|
-
node[key] = val;
|
|
249
|
-
if (val.ref)
|
|
250
|
-
fn();
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
// Doesn't seem to ever ocurr
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
fn();
|
|
236
|
+
type: (val, node, key) => {
|
|
258
237
|
renamePrimitiveTypesAndUuid(val, node, key);
|
|
259
238
|
addDefaultTypeFacets(node);
|
|
260
239
|
},
|
|
261
|
-
cast: (val) => {
|
|
262
|
-
if (options.forHana.names === 'plain' || options.toSql )
|
|
263
|
-
toFinalBaseType(val);
|
|
264
|
-
renamePrimitiveTypesAndUuid(val.type, val, 'type');
|
|
265
|
-
addDefaultTypeFacets(val);
|
|
266
|
-
},
|
|
267
240
|
// HANA/SQLite do not support array-of - turn into CLOB/Text
|
|
268
241
|
items: (val, node) => {
|
|
269
242
|
node.type = 'cds.LargeString';
|
|
@@ -274,36 +247,32 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
274
247
|
// (040) Ignore entities and views that are abstract or implemented
|
|
275
248
|
// or carry the annotation cds.persistence.skip/exists
|
|
276
249
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
277
|
-
forEachDefinition(csn,
|
|
250
|
+
forEachDefinition(csn, cdsPersistence.getAnnoProcessor());
|
|
278
251
|
|
|
279
252
|
|
|
280
253
|
// (050) Check @cds.valid.from/to only on entity
|
|
281
254
|
// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
handleManagedAssociationsAndCreateForeignKeys();
|
|
286
|
-
|
|
287
|
-
function handleManagedAssociationsAndCreateForeignKeys() {
|
|
288
|
-
forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
|
|
289
|
-
forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
|
|
290
|
-
}
|
|
255
|
+
forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
|
|
256
|
+
|
|
257
|
+
// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
|
|
258
|
+
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, { allowArtifact: artifact => (artifact.kind === 'entity') });
|
|
291
259
|
|
|
292
|
-
forEachDefinition(csn, flattenIndexes);
|
|
293
|
-
//
|
|
294
|
-
|
|
260
|
+
doA2J && forEachDefinition(csn, flattenIndexes);
|
|
261
|
+
// Managed associations get an on-condition - in views and entities
|
|
262
|
+
doA2J && associations.attachOnConditions(csn, pathDelimiter);
|
|
295
263
|
|
|
296
264
|
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',
|
|
297
265
|
// and make them entities
|
|
298
|
-
forEachDefinition(csn,
|
|
266
|
+
forEachDefinition(csn, cdsPersistence.getPersistenceTableProcessor(csn, options, {error}));
|
|
299
267
|
|
|
300
268
|
// Allow using managed associations as steps in on-conditions to access their fks
|
|
301
269
|
// To be done after handleAssociations, since then the foreign keys of the managed assocs
|
|
302
270
|
// are part of the elements
|
|
303
|
-
|
|
271
|
+
if (doA2J)
|
|
272
|
+
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter));
|
|
304
273
|
|
|
305
274
|
// Create convenience views for localized entities/views.
|
|
306
|
-
// To be done after
|
|
275
|
+
// To be done after getManagedAssocStepsInOnConditionFinalizer because associations are
|
|
307
276
|
// handled and before handleDBChecks which removes the localized attribute.
|
|
308
277
|
// Association elements of localized convenience views do not have hidden properties
|
|
309
278
|
// like $managed set, so we cannot do this earlier on.
|
|
@@ -312,6 +281,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
312
281
|
else
|
|
313
282
|
addLocalizationViews(csn, options);
|
|
314
283
|
|
|
284
|
+
!doA2J && forEachDefinition(csn, (definition, artName, prop, path) => {
|
|
285
|
+
if (definition.query) {
|
|
286
|
+
// reject managed association and structure publishing for to-hdbcds.hdbcds
|
|
287
|
+
const that = { csnUtils: getUtils(csn), options, error };
|
|
288
|
+
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(that, definition, path)
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
315
292
|
// For generating DB stuff:
|
|
316
293
|
// - table-entity with parameters: not allowed
|
|
317
294
|
// - view with parameters: ok on HANA, not allowed otherwise
|
|
@@ -332,18 +309,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
332
309
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
333
310
|
forEachDefinition(csn, transformSelfInBacklinks);
|
|
334
311
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if(validOptionsForConstraint())
|
|
345
|
-
createReferentialConstraints(csn, options);
|
|
346
|
-
}
|
|
312
|
+
/**
|
|
313
|
+
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
314
|
+
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
315
|
+
* hence we do not generate the referential constraints for them.
|
|
316
|
+
*/
|
|
317
|
+
if((options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite') && doA2J)
|
|
318
|
+
createReferentialConstraints(csn, options);
|
|
319
|
+
|
|
347
320
|
// no constraints for drafts
|
|
348
321
|
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
349
322
|
|
|
@@ -351,8 +324,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
351
324
|
// See function comment for extensive information.
|
|
352
325
|
assertUnique.rewrite(csn, options, pathDelimiter);
|
|
353
326
|
|
|
354
|
-
// Associations that point to
|
|
355
|
-
forEachDefinition(csn,
|
|
327
|
+
// Associations that point to things marked with @cds.persistence.skip are removed
|
|
328
|
+
forEachDefinition(csn, cdsPersistence.getAssocToSkippedIgnorer(csn, options, {info}));
|
|
356
329
|
|
|
357
330
|
// Apply view-specific transformations
|
|
358
331
|
// (160) Projections now finally become views
|
|
@@ -366,34 +339,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
366
339
|
const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
|
|
367
340
|
assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
|
|
368
341
|
};
|
|
369
|
-
const removeNamespaces = (artifact, artifactName) => {
|
|
370
|
-
if (artifact.kind === 'namespace')
|
|
371
|
-
delete csn.definitions[artifactName];
|
|
372
|
-
};
|
|
373
|
-
const ignoreNonPersistedArtifactsWithAnonymousAspectComposition = (artifact) => {
|
|
374
|
-
if(artifact.kind === 'type' || artifact.kind === 'aspect' || artifact.kind === 'entity' && artifact.abstract){
|
|
375
|
-
if(artifact.elements && Object.keys(artifact.elements).some((elementName) => {
|
|
376
|
-
const element = artifact.elements[elementName];
|
|
377
|
-
return !element.target && element.targetAspect && typeof element.targetAspect !== 'string';
|
|
378
|
-
})) {
|
|
379
|
-
artifact._ignore = true;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
};
|
|
383
342
|
|
|
384
343
|
forEachDefinition(csn, [
|
|
385
344
|
/* assert that there will be no conflicting unique- and foreign key constraint identifiers */
|
|
386
345
|
checkConstraintIdentifiers,
|
|
387
|
-
/* (250) Remove all namespaces from definitions */
|
|
388
|
-
removeNamespaces,
|
|
389
346
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
390
347
|
checkTypeParameters,
|
|
391
|
-
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
392
|
-
ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
|
|
393
348
|
// (200) Strip 'key' property from type elements
|
|
394
349
|
removeKeyPropInType,
|
|
395
350
|
]);
|
|
396
351
|
|
|
352
|
+
// Remove leading $self to keep renderer-diffs smaller
|
|
353
|
+
if(doA2J)
|
|
354
|
+
flattening.removeLeadingSelf(csn);
|
|
355
|
+
|
|
397
356
|
throwWithError();
|
|
398
357
|
|
|
399
358
|
timetrace.stop();
|
|
@@ -425,7 +384,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
425
384
|
'$key': killProp
|
|
426
385
|
}
|
|
427
386
|
|
|
428
|
-
applyTransformations(csn, killers, [], false);
|
|
387
|
+
applyTransformations(csn, killers, [], { skipIgnore: false});
|
|
429
388
|
|
|
430
389
|
redoProjections.forEach(fn => fn());
|
|
431
390
|
|
|
@@ -433,68 +392,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
433
392
|
|
|
434
393
|
/* ----------------------------------- Functions start here -----------------------------------------------*/
|
|
435
394
|
|
|
436
|
-
/**
|
|
437
|
-
* Create the foreign key elements for managed associations.
|
|
438
|
-
* Create them in-place, right after the corresponding association.
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* @param {CSN.Artifact} art
|
|
442
|
-
* @param {string} artName
|
|
443
|
-
*/
|
|
444
|
-
function createForeignKeyElements(art, artName) {
|
|
445
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
446
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
447
|
-
const elementsArray = [];
|
|
448
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
449
|
-
elementsArray.push([elemName, element]);
|
|
450
|
-
if (isManagedAssociationElement(element)) {
|
|
451
|
-
if (element.keys) {
|
|
452
|
-
for(let i = 0; i < element.keys.length; i++){
|
|
453
|
-
const foreignKey = element.keys[i];
|
|
454
|
-
const path = [...pathToElements, elemName, 'keys', i];
|
|
455
|
-
foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
|
|
456
|
-
const [fkName, fkElem] = getForeignKeyArtifact(element, elemName, foreignKey, path);
|
|
457
|
-
if(parent.elements[fkName]) {
|
|
458
|
-
error(null, [...pathToElements, elemName], { name: fkName, art: elemName },
|
|
459
|
-
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
|
|
460
|
-
} else {
|
|
461
|
-
elementsArray.push([fkName, fkElem]);
|
|
462
|
-
}
|
|
463
|
-
applyCachedAlias(foreignKey);
|
|
464
|
-
// join ref array as the struct / assoc steps are not necessary anymore
|
|
465
|
-
foreignKey.ref = [foreignKey.ref.join(pathDelimiter)]
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
// Don't fake consistency of the model by adding empty elements {}
|
|
472
|
-
if(elementsArray.length === 0)
|
|
473
|
-
return;
|
|
474
|
-
|
|
475
|
-
parent.elements = elementsArray.reduce((previous, [name, element]) => {
|
|
476
|
-
previous[name] = element;
|
|
477
|
-
return previous;
|
|
478
|
-
}, Object.create(null));
|
|
479
|
-
|
|
480
|
-
})
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function applyCachedAlias(foreignKey) {
|
|
484
|
-
// If we have a $ref use that - it resolves aliased FKs correctly
|
|
485
|
-
if (foreignKey.$ref) {
|
|
486
|
-
foreignKey.ref = foreignKey.$ref;
|
|
487
|
-
delete foreignKey.$ref;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
395
|
function bindCsnReference(){
|
|
494
|
-
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
495
|
-
({ artifactRef, inspectRef, effectiveType } =
|
|
496
|
-
({
|
|
497
|
-
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
396
|
+
({ error, warning, info, message, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
397
|
+
({ artifactRef, inspectRef, effectiveType, getFinalBaseType, get$combined } = getUtils(csn));
|
|
398
|
+
({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
498
399
|
}
|
|
499
400
|
|
|
500
401
|
function bindCsnReferenceOnly(){
|
|
@@ -521,8 +422,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
521
422
|
}
|
|
522
423
|
})
|
|
523
424
|
}
|
|
524
|
-
}
|
|
525
|
-
|
|
425
|
+
}, [ 'definitions', artifactName, 'query' ]);
|
|
426
|
+
|
|
526
427
|
function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
|
|
527
428
|
const { inspectRef } = csnRefs(csn);
|
|
528
429
|
const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
|
|
@@ -573,7 +474,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
573
474
|
function transformViews(artifact, artifactName) {
|
|
574
475
|
if (!artifact._ignore) {
|
|
575
476
|
// Do things specific for entities and views (pass 2)
|
|
576
|
-
if ((artifact.kind === 'entity'
|
|
477
|
+
if ((artifact.kind === 'entity') && artifact.query) {
|
|
577
478
|
forAllQueries(artifact.query, (q, p) => {
|
|
578
479
|
transformEntityOrViewPass2(q, artifact, artifactName, p)
|
|
579
480
|
replaceAssociationsInGroupByOrderBy(q, options, inspectRef, error, p);
|
|
@@ -588,7 +489,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
588
489
|
*/
|
|
589
490
|
function recursivelyApplyCommon(artifact, artifactName) {
|
|
590
491
|
if (!artifact._ignore) {
|
|
591
|
-
if (
|
|
492
|
+
if (artifact.kind !== 'service' && artifact.kind !== 'context')
|
|
592
493
|
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.forHana.names, csn), artifact);
|
|
593
494
|
|
|
594
495
|
forEachMemberRecursively(artifact, (member, memberName, property, path) => {
|
|
@@ -607,9 +508,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
607
508
|
* @param {string} artifactName
|
|
608
509
|
*/
|
|
609
510
|
function removeKeyPropInType(artifact, artifactName) {
|
|
610
|
-
if (!artifact._ignore) {
|
|
511
|
+
if (!artifact._ignore && artifact.kind === 'type') {
|
|
611
512
|
forEachMemberRecursively(artifact, (member) => {
|
|
612
|
-
if (
|
|
513
|
+
if (member.key)
|
|
613
514
|
delete member.key;
|
|
614
515
|
}, [ 'definitions', artifactName ]);
|
|
615
516
|
}
|
|
@@ -623,7 +524,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
623
524
|
function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
|
|
624
525
|
// Fixme: For toHana mixins must be transformed, for toSql -d hana
|
|
625
526
|
// mixin elements must be transformed, why can't toSql also use mixins?
|
|
626
|
-
|
|
527
|
+
if(artifact.kind === 'entity' || artifact.query || (options.toHana && options.toHana.names === 'hdbcds' && artifact.kind == 'type'))
|
|
528
|
+
doit(artifact.elements, path.concat([ 'elements' ]));
|
|
627
529
|
if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
628
530
|
doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
|
|
629
531
|
|
|
@@ -657,91 +559,38 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
657
559
|
if (artifact.params) {
|
|
658
560
|
// HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
|
|
659
561
|
// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
|
|
660
|
-
|
|
562
|
+
message('def-unexpected-paramview-assoc', path, { '#': 'view' });
|
|
661
563
|
}
|
|
662
564
|
else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
663
565
|
// UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
|
|
664
|
-
|
|
566
|
+
const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
|
|
567
|
+
message('def-unexpected-calcview-assoc', path, { '#': 'entity-persistence', anno });
|
|
665
568
|
}
|
|
666
569
|
if (csn.definitions[member.target].params) {
|
|
667
570
|
// HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
|
|
668
571
|
// SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
|
|
669
|
-
|
|
572
|
+
message('def-unexpected-paramview-assoc', path, { '#': 'target' });
|
|
670
573
|
}
|
|
671
|
-
else if(csn.definitions[member.target]['@cds.persistence.udf'] ||
|
|
574
|
+
else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
|
|
672
575
|
// HANA won't check the assoc target but when querying an association with target UDF, this is the error:
|
|
673
576
|
// SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)
|
|
674
577
|
// CREATE TABLE F (id INTEGER NOT NULL);
|
|
675
578
|
// CREATE FUNCTION UDF RETURNS TABLE (ID INTEGER) LANGUAGE SQLSCRIPT SQL SECURITY DEFINER AS BEGIN RETURN SELECT ID FROM F; END;
|
|
676
579
|
// CREATE TABLE Y ( id INTEGER NOT NULL, toUDF_id INTEGER) WITH ASSOCIATIONS (MANY TO ONE JOIN UDF AS toUDF ON (toUDF.id = toUDF_id));
|
|
677
580
|
// CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
|
|
678
|
-
|
|
581
|
+
const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
|
|
582
|
+
message('def-unexpected-calcview-assoc', path, { '#': 'target-persistence', anno });
|
|
679
583
|
}
|
|
680
584
|
}
|
|
681
585
|
}, [ 'definitions', artifactName ]);
|
|
682
586
|
}
|
|
683
587
|
|
|
684
|
-
/**
|
|
685
|
-
*
|
|
686
|
-
* Generate foreign keys for managed associations
|
|
687
|
-
* Forbid aliases for foreign keys
|
|
688
|
-
*
|
|
689
|
-
* @param {CSN.Artifact} artifact
|
|
690
|
-
* @param {string} artifactName
|
|
691
|
-
*/
|
|
692
|
-
function handleAssociations(artifact, artifactName) {
|
|
693
|
-
// Do things specific for entities and views (pass 1)
|
|
694
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
695
|
-
const alreadyHandled = new WeakMap();
|
|
696
|
-
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
697
|
-
for (const elemName in elements) {
|
|
698
|
-
const elem = elements[elemName];
|
|
699
|
-
// (140) Generate foreign key elements and ON-condition for managed associations
|
|
700
|
-
// (unless explicitly asked to keep assocs unchanged)
|
|
701
|
-
if (doA2J) {
|
|
702
|
-
if (isManagedAssociationElement(elem))
|
|
703
|
-
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
})
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
function fixBorkedElementsOfLocalized(elements, pathToElements){
|
|
711
|
-
const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
|
|
712
|
-
const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
for(const elementName in elements){
|
|
716
|
-
const element = elements[elementName];
|
|
717
|
-
const reference = nonLocalizedElements[elementName];
|
|
718
|
-
|
|
719
|
-
// if the declared element is an enum, these values are with priority
|
|
720
|
-
if (!element.enum && reference.enum)
|
|
721
|
-
Object.assign(element, { enum: reference.enum });
|
|
722
|
-
if (!element.length && reference.length && !reference.$default)
|
|
723
|
-
Object.assign(element, { length: reference.length });
|
|
724
|
-
if (!element.precision && reference.precision)
|
|
725
|
-
Object.assign(element, { precision: reference.precision });
|
|
726
|
-
if (!element.scale && reference.scale)
|
|
727
|
-
Object.assign(element, { scale: reference.scale });
|
|
728
|
-
if (!element.srid && reference.srid)
|
|
729
|
-
Object.assign(element, { srid: reference.srid });
|
|
730
|
-
if (!element.keys && reference.keys)
|
|
731
|
-
Object.assign(element, { keys: cloneCsn(reference.keys, options)})
|
|
732
|
-
if (!element.type && reference.type)
|
|
733
|
-
Object.assign(element, { type: reference.type})
|
|
734
|
-
if (!element.on && reference.on && !reference.keys)
|
|
735
|
-
Object.assign(element, {on: cloneCsn(reference.on, options)})
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
588
|
/**
|
|
740
589
|
* @param {CSN.Artifact} artifact
|
|
741
590
|
* @param {string} artifactName
|
|
742
591
|
*/
|
|
743
592
|
function handleChecksForWithParameters(artifact, artifactName) {
|
|
744
|
-
if (!artifact._ignore && artifact.params && (artifact.kind === 'entity'
|
|
593
|
+
if (!artifact._ignore && artifact.params && (artifact.kind === 'entity')) {
|
|
745
594
|
if (!artifact.query) { // table entity with params
|
|
746
595
|
// Allow with plain
|
|
747
596
|
error(null, [ 'definitions', artifactName ], { '#': options.toSql ? 'sql' : 'std' }, {
|
|
@@ -767,48 +616,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
767
616
|
}
|
|
768
617
|
}
|
|
769
618
|
|
|
770
|
-
/**
|
|
771
|
-
* @param {CSN.Artifact} artifact
|
|
772
|
-
* @param {string} artifactName
|
|
773
|
-
*/
|
|
774
|
-
function handleQueryish(artifact, artifactName) {
|
|
775
|
-
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
776
|
-
|
|
777
|
-
if (stripQueryish) {
|
|
778
|
-
artifact.kind = 'entity';
|
|
779
|
-
delete artifact.query;
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
783
|
-
// All elements must have a type for this to work
|
|
784
|
-
if (stripQueryish && !member._ignore && !member.kind && !member.type)
|
|
785
|
-
error(null, path, 'Expecting element to have a type if view is annotated with “@cds.persistence.table“');
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* @param {CSN.Artifact} artifact
|
|
791
|
-
* @param {string} artifactName
|
|
792
|
-
*/
|
|
793
|
-
function handleCdsPersistence(artifact, artifactName) {
|
|
794
|
-
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
795
|
-
if (artifact.abstract
|
|
796
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.skip')
|
|
797
|
-
|| hasAnnotationValue(artifact, '@cds.persistence.exists'))
|
|
798
|
-
artifact._ignore = true;
|
|
799
|
-
|
|
800
|
-
// issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
|
|
801
|
-
if (options.forHana.names === 'quoted' &&
|
|
802
|
-
hasAnnotationValue(artifact, '@cds.persistence.exists')) {
|
|
803
|
-
const firstPath = artifactName.split('.')[0];
|
|
804
|
-
const topParent = csn.definitions[firstPath];
|
|
805
|
-
// namespaces, contexts and services become contexts in HANA CDS
|
|
806
|
-
if (topParent && [ 'namespace', 'context', 'service' ].includes(topParent.kind))
|
|
807
|
-
warning(null, [ 'definitions', artifactName ], `"${ artifactName }": external definition belongs to ${ topParent.kind } "${ firstPath }"`);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
|
|
812
619
|
function handleAssocToJoins() {
|
|
813
620
|
// With flattening errors, it makes little sense to continue.
|
|
814
621
|
throwWithError();
|
|
@@ -830,216 +637,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
830
637
|
csn = newCsn;
|
|
831
638
|
}
|
|
832
639
|
|
|
833
|
-
/**
|
|
834
|
-
* @param {CSN.Artifact} artifact
|
|
835
|
-
* @param {string} artifactName
|
|
836
|
-
*/
|
|
837
|
-
function handleTemporalAnnotations(artifact, artifactName) {
|
|
838
|
-
const validFrom = [];
|
|
839
|
-
const validTo = [];
|
|
840
|
-
const validKey = [];
|
|
841
|
-
|
|
842
|
-
recurseElements(artifact, [ 'definitions', artifactName ], (member, path) => {
|
|
843
|
-
const [ f, t, k ] = extractValidFromToKeyElement(member, path);
|
|
844
|
-
validFrom.push(...f);
|
|
845
|
-
validTo.push(...t);
|
|
846
|
-
validKey.push(...k);
|
|
847
|
-
});
|
|
848
640
|
|
|
849
|
-
if (artifact.kind === 'entity' && !artifact.query) {
|
|
850
|
-
validFrom.forEach(obj => checkAssignment('@cds.valid.from', obj.element, obj.path, artifact));
|
|
851
|
-
validTo.forEach(obj => checkAssignment('@cds.valid.to', obj.element, obj.path, artifact));
|
|
852
|
-
validKey.forEach(obj => checkAssignment('@cds.valid.key', obj.element, obj.path, artifact));
|
|
853
|
-
checkMultipleAssignments(validFrom, '@cds.valid.from', artifact, artifactName);
|
|
854
|
-
checkMultipleAssignments(validTo, '@cds.valid.to', artifact, artifactName, true);
|
|
855
|
-
checkMultipleAssignments(validKey, '@cds.valid.key', artifact, artifactName);
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
// if there is an cds.valid.key, make this the only primary key
|
|
859
|
-
// otherwise add all cds.valid.from to primary key tuple
|
|
860
|
-
if (validKey.length) {
|
|
861
|
-
if (!validFrom.length || !validTo.length)
|
|
862
|
-
error(null, [ 'definitions', artifactName ],
|
|
863
|
-
'Expecting “@cds.valid.from” and “@cds.valid.to” if “@cds.valid.key” is used');
|
|
864
|
-
|
|
865
|
-
forEachMember(artifact, (member) => {
|
|
866
|
-
if (member.key) {
|
|
867
|
-
member.unique = true;
|
|
868
|
-
delete member.key;
|
|
869
|
-
// Remember that this element was a key in the original artifact.
|
|
870
|
-
// This is needed for localized convenience view generation.
|
|
871
|
-
setProp(member, '$key', true);
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
validKey.forEach((member) => {
|
|
875
|
-
member.element.key = true;
|
|
876
|
-
});
|
|
877
|
-
|
|
878
|
-
validFrom.forEach((member) => {
|
|
879
|
-
member.element.unique = true;
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
validFrom.forEach((member) => {
|
|
884
|
-
member.element.key = true;
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
|
|
890
|
-
let fromElement = elements[from.name];
|
|
891
|
-
let toElement = elements[to.name];
|
|
892
|
-
|
|
893
|
-
if(SELECT.columns) {
|
|
894
|
-
for(const col of SELECT.columns) {
|
|
895
|
-
if(col.ref) {
|
|
896
|
-
const implicitAlias = implicitAs(col.ref);
|
|
897
|
-
if(implicitAlias === from.name)
|
|
898
|
-
fromElement = elements[col.as || implicitAlias];
|
|
899
|
-
else if(implicitAlias === to.name)
|
|
900
|
-
toElement = elements[col.as || implicitAlias];
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
|
|
905
|
-
return val;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Add a where condition to views that
|
|
910
|
-
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
911
|
-
* - have only one @cds.valid.from and @cds.valid.to,
|
|
912
|
-
* - and both annotations come from the same entity
|
|
913
|
-
*
|
|
914
|
-
* If the view has one of the annotations but the other conditions are not met, an error will be raised.
|
|
915
|
-
*
|
|
916
|
-
* @param {CSN.Artifact} artifact
|
|
917
|
-
* @param {string} artifactName
|
|
918
|
-
*/
|
|
919
|
-
function addTemporalWhereConditionToView(artifact, artifactName) {
|
|
920
|
-
const normalizedQuery = getNormalizedQuery(artifact);
|
|
921
|
-
if (normalizedQuery && normalizedQuery.query && normalizedQuery.query.SELECT) {
|
|
922
|
-
// BLOCKER: We need information to handle $combined
|
|
923
|
-
// What we are trying to achieve by this:
|
|
924
|
-
// Forbid joining/selecting from two or more temporal entities
|
|
925
|
-
// Idea: Follow the query-tree and check each from
|
|
926
|
-
// Collect all source-entities and compute our own $combined
|
|
927
|
-
const $combined = get$combined(normalizedQuery.query);
|
|
928
|
-
const [ from, to ] = getFromToElements($combined);
|
|
929
|
-
// exactly one validFrom & validTo
|
|
930
|
-
if (from.length === 1 && to.length === 1) {
|
|
931
|
-
// and both are from the same origin
|
|
932
|
-
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
933
|
-
if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
|
|
934
|
-
const fromPath = {
|
|
935
|
-
ref: [
|
|
936
|
-
from[0].parent,
|
|
937
|
-
from[0].name,
|
|
938
|
-
],
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
const toPath = {
|
|
942
|
-
ref: [
|
|
943
|
-
to[0].parent,
|
|
944
|
-
to[0].name,
|
|
945
|
-
],
|
|
946
|
-
};
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
const atFrom = { ref: [ '$at', 'from' ] };
|
|
950
|
-
const atTo = { ref: [ '$at', 'to' ] };
|
|
951
|
-
|
|
952
|
-
const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
|
|
953
|
-
|
|
954
|
-
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
955
|
-
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
956
|
-
}
|
|
957
|
-
else {
|
|
958
|
-
normalizedQuery.query.SELECT.where = cond;
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
else {
|
|
963
|
-
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`);
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
else if (from.length > 0 || to.length > 0) {
|
|
967
|
-
const missingAnnotation = from.length > to.length ? '@cds.valid.to' : '@cds.valid.from';
|
|
968
|
-
info(null, [ 'definitions', artifactName ],
|
|
969
|
-
{ anno: missingAnnotation },
|
|
970
|
-
'No temporal WHERE clause added because $(ANNO) is missing'
|
|
971
|
-
)
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
/**
|
|
977
|
-
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
978
|
-
*
|
|
979
|
-
* @param {any} combined union of all entities of the from-clause
|
|
980
|
-
* @returns {Array[]} Array where first field is array of elements with @cds.valid.from, second field is array of elements with @cds.valid.to.
|
|
981
|
-
*/
|
|
982
|
-
function getFromToElements(combined) {
|
|
983
|
-
const from = [];
|
|
984
|
-
const to = [];
|
|
985
|
-
for (const name in combined) {
|
|
986
|
-
let elt = combined[name];
|
|
987
|
-
if (!Array.isArray(elt))
|
|
988
|
-
elt = [ elt ];
|
|
989
|
-
elt.forEach((e) => {
|
|
990
|
-
if (hasAnnotationValue(e.element, '@cds.valid.from'))
|
|
991
|
-
from.push(e);
|
|
992
|
-
|
|
993
|
-
if (hasAnnotationValue(e.element, '@cds.valid.to'))
|
|
994
|
-
to.push(e);
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
return [ from, to ];
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
/**
|
|
1002
|
-
* Associations that target a @cds.persistence.skip artifact must be removed
|
|
1003
|
-
* from the persistence model
|
|
1004
|
-
*
|
|
1005
|
-
* @param {CSN.Artifact} artifact
|
|
1006
|
-
* @param {string} artifactName
|
|
1007
|
-
* @param {string} prop
|
|
1008
|
-
* @param {CSN.Path} path
|
|
1009
|
-
*/
|
|
1010
|
-
function ignoreAssociationToSkippedTarget(artifact, artifactName, prop, path) {
|
|
1011
|
-
if (isPersistedOnDatabase(artifact)) {
|
|
1012
|
-
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1013
|
-
if (artifact.query) {
|
|
1014
|
-
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
1015
|
-
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
1016
|
-
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
1017
|
-
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1018
|
-
|
|
1019
|
-
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
1020
|
-
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1021
|
-
}
|
|
1022
|
-
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
1023
|
-
}
|
|
1024
|
-
function ignore(member, memberName, prop, path) {
|
|
1025
|
-
if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
1026
|
-
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
|
|
1027
|
-
info(null, path,
|
|
1028
|
-
{ target: member.target, anno: targetAnnotation },
|
|
1029
|
-
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
|
|
1030
|
-
);
|
|
1031
|
-
member._ignore = true;
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
/**
|
|
1037
|
-
* @param {CSN.Artifact} art
|
|
1038
|
-
* @returns {boolean}
|
|
1039
|
-
*/
|
|
1040
|
-
function isUnreachableAssociationTarget(art) {
|
|
1041
|
-
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
|
|
1042
|
-
}
|
|
1043
641
|
|
|
1044
642
|
/**
|
|
1045
643
|
* Remove `localized` from elements and replace Enum symbols by their values.
|
|
@@ -1262,7 +860,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1262
860
|
else if (assoc.on)
|
|
1263
861
|
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
|
|
1264
862
|
|
|
1265
|
-
throw new
|
|
863
|
+
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
|
|
1266
864
|
}
|
|
1267
865
|
|
|
1268
866
|
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
@@ -1294,14 +892,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1294
892
|
conditions.push([ a[0], '=', a[1] ]);
|
|
1295
893
|
});
|
|
1296
894
|
|
|
1297
|
-
|
|
895
|
+
return conditions.reduce((prev, current) => {
|
|
1298
896
|
if (prev.length === 0)
|
|
1299
897
|
return [ ...current ];
|
|
1300
898
|
|
|
1301
899
|
return [ ...prev, 'and', ...current ];
|
|
1302
900
|
}, []);
|
|
1303
|
-
|
|
1304
|
-
return result;
|
|
1305
901
|
}
|
|
1306
902
|
|
|
1307
903
|
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
|
|
@@ -1319,18 +915,23 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1319
915
|
const newOnCond = cloneWithTransformations(assoc.on, {
|
|
1320
916
|
ref: (value) => cloneWithTransformations(value, {}),
|
|
1321
917
|
});
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
ref.
|
|
918
|
+
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
|
|
919
|
+
ref: (parent, prop, ref) => {
|
|
920
|
+
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
921
|
+
{
|
|
922
|
+
ref.shift();
|
|
923
|
+
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
924
|
+
// We could also have a $self infront of the assoc name - so we would need to shift twice
|
|
925
|
+
ref.shift();
|
|
926
|
+
ref.shift();
|
|
927
|
+
}
|
|
928
|
+
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
929
|
+
ref.unshift(elemName);
|
|
930
|
+
// if there was a $self identifier in the forwarding association onCond
|
|
931
|
+
// we do not need it any more, as we prepended in the previous step the back association's id
|
|
932
|
+
if (ref[1] === '$self')
|
|
933
|
+
ref.splice(1, 1);
|
|
934
|
+
}
|
|
1334
935
|
}
|
|
1335
936
|
});
|
|
1336
937
|
return newOnCond;
|
|
@@ -1433,148 +1034,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1433
1034
|
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
|
|
1434
1035
|
}
|
|
1435
1036
|
|
|
1436
|
-
/**
|
|
1437
|
-
* Flatten and create the foreign key elements of managed associaitons
|
|
1438
|
-
*
|
|
1439
|
-
* @param {CSN.Artifact} art
|
|
1440
|
-
* @param {string} artName
|
|
1441
|
-
*/
|
|
1442
|
-
function handleManagedAssociationFKs(art, artName) {
|
|
1443
|
-
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
1444
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
1445
|
-
if(artName.startsWith('localized.') && pathToElements.length > 3) {
|
|
1446
|
-
// In subqueries, the elements of localized views are missing all the important bits and pieces...
|
|
1447
|
-
fixBorkedElementsOfLocalized(elements, pathToElements);
|
|
1448
|
-
}
|
|
1449
|
-
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
1450
|
-
if (isManagedAssociationElement(element)) {
|
|
1451
|
-
if (element.keys) {
|
|
1452
|
-
// replace foreign keys that are managed associations by their respective foreign keys
|
|
1453
|
-
flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
});
|
|
1457
|
-
})
|
|
1458
|
-
}
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
/**
|
|
1463
|
-
* Flattens all foreign keys
|
|
1464
|
-
*
|
|
1465
|
-
* Structures will be resolved to individual elements with scalar types
|
|
1466
|
-
*
|
|
1467
|
-
* Associations will be replaced by their respective foreign keys
|
|
1468
|
-
*
|
|
1469
|
-
* If a structure contains an assoc, this will also be resolved and vice versa
|
|
1470
|
-
*
|
|
1471
|
-
* @param {*} assoc
|
|
1472
|
-
* @param {*} assocName
|
|
1473
|
-
* @param {*} path
|
|
1474
|
-
*/
|
|
1475
|
-
function flattenFKs(assoc, assocName, path) {
|
|
1476
|
-
let finished = false;
|
|
1477
|
-
while(!finished) {
|
|
1478
|
-
const newKeys = [];
|
|
1479
|
-
finished = processKeys(assoc, assocName, path, newKeys);
|
|
1480
|
-
assoc.keys = newKeys;
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
function processKeys(assoc, assocName, path, collector) {
|
|
1484
|
-
let finished = true;
|
|
1485
|
-
for (let i = 0; i < assoc.keys.length; i++) {
|
|
1486
|
-
const pathToKey = path.concat([ 'keys', i ]);
|
|
1487
|
-
const { art } = inspectRef(pathToKey);
|
|
1488
|
-
const { ref } = assoc.keys[i];
|
|
1489
|
-
if (isStructured(art)) {
|
|
1490
|
-
finished = false;
|
|
1491
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1492
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1493
|
-
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
|
|
1494
|
-
Object.keys(flat).forEach((flatElemName) => {
|
|
1495
|
-
const key = assoc.keys[i];
|
|
1496
|
-
const clone = cloneCsn(assoc.keys[i], options);
|
|
1497
|
-
if (clone.as) {
|
|
1498
|
-
const lastRef = clone.ref[clone.ref.length - 1];
|
|
1499
|
-
// Cut off the last ref part from the beginning of the flat name
|
|
1500
|
-
const flatBaseName = flatElemName.slice(lastRef.length);
|
|
1501
|
-
// Join it to the existing table alias
|
|
1502
|
-
clone.as += flatBaseName;
|
|
1503
|
-
// do not loose the $ref for nested keys
|
|
1504
|
-
if(key.$ref){
|
|
1505
|
-
let aliasedLeaf = key.$ref[key.$ref.length - 1 ];
|
|
1506
|
-
aliasedLeaf += flatBaseName;
|
|
1507
|
-
setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
if (clone.ref) {
|
|
1511
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1512
|
-
// Now we need to properly flatten the whole ref
|
|
1513
|
-
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
|
|
1514
|
-
}
|
|
1515
|
-
if (!clone.as) {
|
|
1516
|
-
clone.as = flatElemName;
|
|
1517
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1518
|
-
setProp(clone, '$inferredAlias', true);
|
|
1519
|
-
}
|
|
1520
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1521
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1522
|
-
// Recursive solutions run into call stack issues
|
|
1523
|
-
collector.push(clone);
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
else if (art.target) {
|
|
1527
|
-
finished = false;
|
|
1528
|
-
// Mark this element to filter it later - not needed after expansion
|
|
1529
|
-
setProp(assoc.keys[i], '$toDelete', true);
|
|
1530
|
-
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
|
|
1531
|
-
// Add the newly generated foreign keys to the end - they will be picked up later on
|
|
1532
|
-
// Recursive solutions run into call stack issues
|
|
1533
|
-
art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
|
|
1534
|
-
}
|
|
1535
|
-
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
|
|
1536
|
-
setProp(assoc.keys[i], '$inferredAlias', true);
|
|
1537
|
-
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
|
|
1538
|
-
collector.push(assoc.keys[i]);
|
|
1539
|
-
} else {
|
|
1540
|
-
collector.push(assoc.keys[i]);
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
return finished;
|
|
1544
|
-
}
|
|
1545
|
-
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
function cloneAndExtendRef(key, base, ref) {
|
|
1549
|
-
const clone = cloneCsn(base, options);
|
|
1550
|
-
if (key.ref) {
|
|
1551
|
-
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
1552
|
-
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
1553
|
-
// Later on, after we know that these foreign key elements are created, we replace ref with this $ref
|
|
1554
|
-
let $ref;
|
|
1555
|
-
if(base.$ref){
|
|
1556
|
-
// if a base $ref is provided, use it to correctly resolve association chains
|
|
1557
|
-
const refChain = [base.$ref[base.$ref.length - 1]].concat(key.as || key.ref);
|
|
1558
|
-
$ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain)
|
|
1559
|
-
} else {
|
|
1560
|
-
$ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
|
|
1561
|
-
}
|
|
1562
|
-
setProp(clone, '$ref', $ref);
|
|
1563
|
-
clone.ref = clone.ref.concat(key.ref);
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
1567
|
-
clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1568
|
-
// TODO: can we use $inferred? Does it have other weird side-effects?
|
|
1569
|
-
setProp(clone, '$inferredAlias', true);
|
|
1570
|
-
}
|
|
1571
|
-
else {
|
|
1572
|
-
clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
|
|
1573
|
-
}
|
|
1574
|
-
|
|
1575
|
-
return clone;
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
1037
|
/**
|
|
1579
1038
|
* Flatten technical configuration stuff
|
|
1580
1039
|
*
|
|
@@ -1584,7 +1043,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1584
1043
|
function flattenIndexes(art, artName) {
|
|
1585
1044
|
// Flatten structs in indexes (unless explicitly asked to keep structs)
|
|
1586
1045
|
const tc = art.technicalConfig;
|
|
1587
|
-
if (
|
|
1046
|
+
if (art.kind === 'entity') {
|
|
1588
1047
|
if (tc && tc[dialect]) {
|
|
1589
1048
|
// Secondary and fulltext indexes
|
|
1590
1049
|
for (const name in tc[dialect].indexes) {
|
|
@@ -1638,150 +1097,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1638
1097
|
}
|
|
1639
1098
|
}
|
|
1640
1099
|
}
|
|
1641
|
-
|
|
1642
|
-
/**
|
|
1643
|
-
* Loop over all elements and for all unmanaged associations translate
|
|
1644
|
-
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
|
|
1645
|
-
*
|
|
1646
|
-
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
|
|
1647
|
-
*
|
|
1648
|
-
* @param {CSN.Artifact} artifact Artifact to check
|
|
1649
|
-
* @param {string} artifactName Name of the artifact
|
|
1650
|
-
*/
|
|
1651
|
-
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
|
|
1652
|
-
for (const elemName in artifact.elements) {
|
|
1653
|
-
const elem = artifact.elements[elemName];
|
|
1654
|
-
if (doA2J) {
|
|
1655
|
-
// The association is an unmanaged on
|
|
1656
|
-
if (!elem.keys && elem.target && elem.on) {
|
|
1657
|
-
forEachRef(elem.on, (ref, refOwner, path) => {
|
|
1658
|
-
// [<assoc base>.]<managed assoc>.<field>
|
|
1659
|
-
if (ref.length > 1) {
|
|
1660
|
-
const { links } = inspectRef(path);
|
|
1661
|
-
if (links) {
|
|
1662
|
-
// eslint-disable-next-line for-direction
|
|
1663
|
-
for (let i = links.length - 1; i >= 0; i--) {
|
|
1664
|
-
const link = links[i];
|
|
1665
|
-
// We found the latest managed assoc path step
|
|
1666
|
-
if (link.art && link.art.target && link.art.keys) {
|
|
1667
|
-
// Doesn't work when ref-target (filter condition) or similar is used
|
|
1668
|
-
if (!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
1669
|
-
// We join the managed assoc with everything following it
|
|
1670
|
-
const sourceElementName = ref.slice(i).join(pathDelimiter);
|
|
1671
|
-
const source = findSource(links, i - 1) || artifact;
|
|
1672
|
-
// allow specifying managed assoc on the source side
|
|
1673
|
-
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
1674
|
-
if(fks && fks.length >= 1){
|
|
1675
|
-
const fk = fks[0];
|
|
1676
|
-
const managedAssocStepName = refOwner.ref[i];
|
|
1677
|
-
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
|
|
1678
|
-
if(source && source.elements[fkName])
|
|
1679
|
-
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
1680
|
-
}
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
}, [ 'definitions', artifactName, 'elements', elemName, 'on' ]);
|
|
1687
|
-
}
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
|
|
1691
|
-
/**
|
|
1692
|
-
* Find out where the managed association is
|
|
1693
|
-
*
|
|
1694
|
-
* @param {Array} links
|
|
1695
|
-
* @param {Number} startIndex
|
|
1696
|
-
* @returns {Object| undefined} CSN definition of the source of the managed association
|
|
1697
|
-
*
|
|
1698
|
-
*/
|
|
1699
|
-
function findSource(links, startIndex) {
|
|
1700
|
-
for (let i = startIndex; i >= 0; i--) {
|
|
1701
|
-
const link = links[i];
|
|
1702
|
-
// We found the latest assoc step - now check where that points to
|
|
1703
|
-
if (link.art && link.art.target)
|
|
1704
|
-
return csn.definitions[link.art.target];
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1707
|
-
return undefined;
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
/**
|
|
1712
|
-
* Create the foreign key elements for a managed association and build the on-condition
|
|
1713
|
-
*
|
|
1714
|
-
* @param {CSN.Artifact} artifact
|
|
1715
|
-
* @param {string} artifactName
|
|
1716
|
-
* @param {Object} elem The association to process
|
|
1717
|
-
* @param {string} elemName
|
|
1718
|
-
* @param {WeakMap} alreadyHandled To cache which elements were already processed
|
|
1719
|
-
* @returns {void}
|
|
1720
|
-
*/
|
|
1721
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
|
|
1722
|
-
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
1723
|
-
// of another association - see a few lines lower
|
|
1724
|
-
if (alreadyHandled.has(elem))
|
|
1725
|
-
return;
|
|
1726
|
-
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
1727
|
-
const onCondParts = [];
|
|
1728
|
-
let join_with_and = false;
|
|
1729
|
-
if(elem.keys.length === 0)
|
|
1730
|
-
elem._ignore = true;
|
|
1731
|
-
else {
|
|
1732
|
-
for (let i = 0; i < elem.keys.length; i++) {
|
|
1733
|
-
const foreignKey = elem.keys[i];
|
|
1734
|
-
|
|
1735
|
-
// Assemble left hand side of 'assoc.key = fkey'
|
|
1736
|
-
const assocKeyArg = {
|
|
1737
|
-
ref: [
|
|
1738
|
-
elemName,
|
|
1739
|
-
].concat(foreignKey.ref),
|
|
1740
|
-
};
|
|
1741
|
-
const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
|
|
1742
|
-
const fKeyArg = {
|
|
1743
|
-
ref: [
|
|
1744
|
-
fkName,
|
|
1745
|
-
],
|
|
1746
|
-
};
|
|
1747
|
-
|
|
1748
|
-
if (join_with_and) { // more than one FK
|
|
1749
|
-
onCondParts.push('and');
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
onCondParts.push(
|
|
1753
|
-
assocKeyArg
|
|
1754
|
-
);
|
|
1755
|
-
onCondParts.push('=');
|
|
1756
|
-
onCondParts.push(fKeyArg);
|
|
1757
|
-
|
|
1758
|
-
if (!join_with_and)
|
|
1759
|
-
join_with_and = true;
|
|
1760
|
-
}
|
|
1761
|
-
elem.on = onCondParts;
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
// If the managed association has a 'key' property => remove it as unmanaged assocs cannot be keys
|
|
1765
|
-
// TODO: Are there other modifiers (like 'key') that are valid for managed, but not valid for unmanaged assocs?
|
|
1766
|
-
if (elem.key)
|
|
1767
|
-
delete elem.key;
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
// If the managed association has a 'not null' property => remove it
|
|
1771
|
-
if (elem.notNull)
|
|
1772
|
-
delete elem.notNull;
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
1776
|
-
// at all. But the processing of backlink associations below expects to have them, so
|
|
1777
|
-
// we don't delete them (but mark them as implicit so that toCdl does not render them)
|
|
1778
|
-
/* Skip for now - forHana adds this to elements, but it is not part of the resulting CSN
|
|
1779
|
-
forHanaNew -> Somehow ends up in the CSN?!
|
|
1780
|
-
elem.implicitForeignKeys = true;
|
|
1781
|
-
*/
|
|
1782
|
-
// Remember that we already processed this
|
|
1783
|
-
alreadyHandled.set(elem, true);
|
|
1784
|
-
}
|
|
1785
1100
|
}
|
|
1786
1101
|
|
|
1787
1102
|
|