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