@sap/cds-compiler 2.5.2 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -9
- package/bin/cdsc.js +44 -27
- package/bin/cdsse.js +1 -0
- package/doc/CHANGELOG_BETA.md +37 -3
- package/lib/api/.eslintrc.json +2 -0
- package/lib/api/main.js +37 -123
- package/lib/api/options.js +27 -15
- package/lib/api/validate.js +34 -9
- package/lib/backends.js +9 -89
- package/lib/base/dictionaries.js +2 -1
- package/lib/base/keywords.js +32 -2
- package/lib/base/message-registry.js +73 -11
- package/lib/base/messages.js +86 -30
- package/lib/base/model.js +6 -6
- package/lib/base/optionProcessorHelper.js +56 -22
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/foreignKeys.js +0 -6
- package/lib/checks/managedWithoutKeys.js +17 -0
- package/lib/checks/nonexpandableStructured.js +38 -0
- package/lib/checks/onConditions.js +9 -45
- package/lib/checks/queryNoDbArtifacts.js +25 -7
- package/lib/checks/selectItems.js +29 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +41 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +60 -7
- package/lib/compiler/assert-consistency.js +23 -7
- package/lib/compiler/base.js +65 -0
- package/lib/compiler/builtins.js +30 -1
- package/lib/compiler/checks.js +8 -5
- package/lib/compiler/definer.js +157 -133
- package/lib/compiler/index.js +89 -31
- package/lib/compiler/propagator.js +5 -2
- package/lib/compiler/resolver.js +375 -185
- package/lib/compiler/shared.js +49 -202
- package/lib/compiler/utils.js +173 -0
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +104 -108
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +388 -146
- package/lib/edm/edmUtils.js +104 -34
- package/lib/gen/Dictionary.json +22 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +28 -1
- package/lib/gen/language.tokens +79 -69
- package/lib/gen/languageLexer.interp +28 -1
- package/lib/gen/languageLexer.js +879 -805
- package/lib/gen/languageLexer.tokens +71 -62
- package/lib/gen/languageParser.js +5330 -4300
- package/lib/json/from-csn.js +110 -52
- package/lib/json/to-csn.js +434 -120
- package/lib/language/antlrParser.js +15 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +93 -26
- package/lib/language/language.g4 +172 -31
- package/lib/main.d.ts +216 -19
- package/lib/main.js +32 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +413 -149
- package/lib/model/csnUtils.js +286 -75
- package/lib/model/enrichCsn.js +50 -6
- package/lib/model/revealInternalProperties.js +22 -5
- package/lib/modelCompare/compare.js +39 -21
- package/lib/optionProcessor.js +35 -18
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +9 -6
- package/lib/render/toCdl.js +121 -36
- package/lib/render/toHdbcds.js +148 -98
- package/lib/render/toSql.js +114 -43
- package/lib/render/utils/common.js +8 -13
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/db/assertUnique.js +5 -6
- package/lib/transform/db/constraints.js +281 -106
- package/lib/transform/db/draft.js +11 -8
- package/lib/transform/db/expansion.js +584 -0
- package/lib/transform/db/flattening.js +341 -0
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/transformExists.js +345 -65
- package/lib/transform/db/views.js +438 -0
- package/lib/transform/forHanaNew.js +131 -793
- package/lib/transform/forOdataNew.js +30 -24
- package/lib/transform/localized.js +39 -10
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/generateForeignKeyElements.js +11 -10
- package/lib/transform/odata/referenceFlattener.js +60 -39
- package/lib/transform/odata/sortByAssociationDependency.js +2 -2
- package/lib/transform/odata/structuralPath.js +72 -0
- package/lib/transform/odata/structureFlattener.js +19 -18
- package/lib/transform/odata/typesExposure.js +22 -12
- package/lib/transform/transformUtilsNew.js +144 -78
- package/lib/transform/translateAssocsToJoins.js +22 -27
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +5 -14
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/term.js +65 -42
- package/lib/utils/timetrace.js +48 -26
- package/package.json +1 -1
- package/lib/json/walker.js +0 -26
- package/lib/transform/sqlite +0 -0
- package/lib/utils/string.js +0 -17
|
@@ -6,7 +6,7 @@ const { getUtils, cloneCsn, forEachGeneric,
|
|
|
6
6
|
forEachMemberRecursively, forEachRef,
|
|
7
7
|
forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
|
|
8
8
|
getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
9
|
-
isPersistedOnDatabase, getNormalizedQuery, isAspect,
|
|
9
|
+
isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
|
|
10
10
|
} = require('../model/csnUtils');
|
|
11
11
|
const { makeMessageFunction } = require('../base/messages');
|
|
12
12
|
const transformUtils = require('./transformUtilsNew');
|
|
@@ -15,15 +15,18 @@ const { csnRefs, pathId, implicitAs } = require('../model/csnRefs');
|
|
|
15
15
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
16
16
|
const validate = require('../checks/validator');
|
|
17
17
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
18
|
-
const timetrace = require('../utils/timetrace');
|
|
18
|
+
const { timetrace } = require('../utils/timetrace');
|
|
19
19
|
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
20
20
|
const { createDict } = require('../utils/objectUtils');
|
|
21
21
|
const handleExists = require('./db/transformExists');
|
|
22
|
-
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
|
|
23
22
|
const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
|
|
24
23
|
const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
24
|
+
const flattening = require('./db/flattening');
|
|
25
|
+
const expansion = require('./db/expansion');
|
|
25
26
|
const assertUnique = require('./db/assertUnique');
|
|
26
27
|
const generateDrafts = require('./db/draft');
|
|
28
|
+
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
29
|
+
const { getViewTransformer } = require('./db/views');
|
|
27
30
|
|
|
28
31
|
// By default: Do not process non-entities/views
|
|
29
32
|
function forEachDefinition(csn, cb) {
|
|
@@ -36,12 +39,6 @@ function forEachDefinition(csn, cb) {
|
|
|
36
39
|
* The behavior is controlled by the following options:
|
|
37
40
|
* options = {
|
|
38
41
|
* forHana.names // See the behavior of 'names' in toHana, toSql and toRename
|
|
39
|
-
* forHana.keepNamespaces // Do not transform namespaces to contexts (to be used for
|
|
40
|
-
* // producing HANA-CDS compatible names with 'toHana', 'toSql' ...)
|
|
41
|
-
* forHana.keepStructsAssocs // Do not flatten structs, do not convert managed assocs to
|
|
42
|
-
* // unmanaged ones, do not convert assocs to joins (to be used
|
|
43
|
-
* // for rendering strictly HANA-CDS compatible CDS source with
|
|
44
|
-
* // 'toHana')
|
|
45
42
|
* forHana.alwaysResolveDerivedTypes // Always resolve derived type chains (by default, this is only
|
|
46
43
|
* // done for 'quoted' names). FIXME: Should always be done in general.
|
|
47
44
|
* }
|
|
@@ -50,12 +47,11 @@ function forEachDefinition(csn, cb) {
|
|
|
50
47
|
* - (000) Some primitive type names are mapped to HANA type names (e.g. DateTime => UTCDateTime,
|
|
51
48
|
* Date => LocalDate, ...).The primitive type 'UUID' is renamed to 'String' (see also 060 below).
|
|
52
49
|
* - (001) Add a temporal where condition to views where applicable before assoc2join
|
|
53
|
-
* - (010) (not for
|
|
50
|
+
* - (010) (not for to.hdbcds with hdbcds names): Transform associations to joins
|
|
54
51
|
* - (015) Draft shadow entities are generated for entities/views annotated with '@odata.draft.enabled'.
|
|
55
52
|
* - (020) Check: in "plain" mode, quoted ids are not allowed.
|
|
56
53
|
* (a) check in namespace declarations
|
|
57
54
|
* (b) check in artifact/element definitions.
|
|
58
|
-
* - (030) For all elements, derived types are replaced by their final base type.
|
|
59
55
|
* - (040) Abstract entities and entities 'implemented in' something are ignored, as well
|
|
60
56
|
* as entities annotated with '@cds.persistence.skip' or '@cds.persistence.exists'.
|
|
61
57
|
* - (050) Checks on the hierarchical model (pre-flattening)
|
|
@@ -69,10 +65,9 @@ function forEachDefinition(csn, cb) {
|
|
|
69
65
|
* - (100) 'masked' is ignored (a), and attribute 'localized' is removed (b)
|
|
70
66
|
* - (110) Actions and functions (bound or unbound) are ignored.
|
|
71
67
|
* - (120) (a) Services become contexts.
|
|
72
|
-
*
|
|
73
|
-
* - (130) (not for 'keepStructsAssocs'): Elements having structured types are flattened into
|
|
68
|
+
* - (130) (not for to.hdbcds with hdbcds names): Elements having structured types are flattened into
|
|
74
69
|
* multiple elements (using '_' or '.' as name separator, depending on 'forHana.names').
|
|
75
|
-
* - (140) (not for
|
|
70
|
+
* - (140) (not for to.hdbcds with hdbcds names): Managed associations get explicit ON-conditions, with
|
|
76
71
|
* generated foreign key elements (also using '_' or '.' as name separator, depending on 'forHana.names').
|
|
77
72
|
* - (150) (a) Elements from inherited (included) entities are copied into the receiving entity
|
|
78
73
|
* (b) The 'include' property is removed from entities.
|
|
@@ -86,10 +81,10 @@ function forEachDefinition(csn, cb) {
|
|
|
86
81
|
* (a) enum constants in defaults are replaced by their values (assuming a matching enum as element type)
|
|
87
82
|
* (b) the enum-ness is stripped off (i.e. the enum type is replaced by its final base type).
|
|
88
83
|
* - (200) The 'key' property is removed from all elements of types.
|
|
89
|
-
* - (210) (not for
|
|
84
|
+
* - (210) (not for to.hdbcds with hdbcds names): Managed associations in GROUP BY and ORDER BY are
|
|
90
85
|
* replaced by by their foreign key fields.
|
|
91
86
|
* - (220) Contexts that contain no artifacts or only ignored artifacts are ignored.
|
|
92
|
-
* - (230) (only for
|
|
87
|
+
* - (230) (only for to.hdbcds with hdbcds names): The following are rejected in views
|
|
93
88
|
* (a) Structured elements
|
|
94
89
|
* (b) Managed association elements
|
|
95
90
|
* (c) Managed association entries in GROUP BY
|
|
@@ -105,7 +100,6 @@ function forEachDefinition(csn, cb) {
|
|
|
105
100
|
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
|
|
106
101
|
*/
|
|
107
102
|
function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
108
|
-
const columnClearer = [];
|
|
109
103
|
// copy the model as we don't want to change the input model
|
|
110
104
|
timetrace.start('HANA transformation');
|
|
111
105
|
/** @type {CSN.Model} */
|
|
@@ -120,15 +114,21 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
120
114
|
let error, warning, info; // message functions
|
|
121
115
|
/** @type {() => void} */
|
|
122
116
|
let throwWithError;
|
|
123
|
-
let artifactRef, inspectRef,
|
|
124
|
-
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType
|
|
117
|
+
let artifactRef, inspectRef, effectiveType, // csnRefs
|
|
118
|
+
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
|
|
119
|
+
get$combined; // csnUtils
|
|
125
120
|
|
|
126
121
|
bindCsnReference();
|
|
127
122
|
|
|
128
123
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
124
|
+
|
|
125
|
+
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
126
|
+
enrichUniversalCsn(csn, options);
|
|
127
|
+
bindCsnReference();
|
|
128
|
+
}
|
|
129
129
|
|
|
130
130
|
const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
|
|
131
|
-
const doA2J = !options.
|
|
131
|
+
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
132
132
|
if (!doA2J)
|
|
133
133
|
forEachDefinition(csn, handleMixinOnConditions);
|
|
134
134
|
|
|
@@ -142,25 +142,48 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
142
142
|
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
143
143
|
// If errors are detected, throwWithError() will return from further processing
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
expandStructsInExpression(csn, { drillRef: true });
|
|
146
146
|
|
|
147
147
|
throwWithError();
|
|
148
148
|
|
|
149
149
|
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
150
150
|
const transformCsn = transformUtils.transformModel;
|
|
151
151
|
|
|
152
|
-
handleExists(csn, error);
|
|
152
|
+
handleExists(csn, options, error);
|
|
153
153
|
|
|
154
154
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
155
155
|
// assoc2join eventually rewrites the table aliases
|
|
156
156
|
forEachDefinition(csn, addTemporalWhereConditionToView);
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
159
159
|
assertUnique.prepare(csn, options, error, info);
|
|
160
160
|
|
|
161
|
+
if(doA2J) {
|
|
162
|
+
// Expand a structured thing in: keys, columns, order by, group by
|
|
163
|
+
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithError});
|
|
164
|
+
bindCsnReference();
|
|
165
|
+
}
|
|
166
|
+
|
|
161
167
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
162
168
|
cleanup();
|
|
163
169
|
|
|
170
|
+
bindCsnReferenceOnly();
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if(doA2J) {
|
|
174
|
+
const resolved = new WeakMap();
|
|
175
|
+
// No refs with struct-steps exist anymore
|
|
176
|
+
flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
|
|
177
|
+
// No type references exist anymore
|
|
178
|
+
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
179
|
+
flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
|
|
180
|
+
// No structured elements exists anymore
|
|
181
|
+
flattening.flattenElements(csn, options, pathDelimiter, error);
|
|
182
|
+
} else {
|
|
183
|
+
// For to.hdbcds with naming mode hdbcds we also need to resolve the types
|
|
184
|
+
flattening.resolveTypeReferences(csn, options, undefined, pathDelimiter);
|
|
185
|
+
}
|
|
186
|
+
|
|
164
187
|
// (010) If requested, translate associations to joins
|
|
165
188
|
if (doA2J)
|
|
166
189
|
handleAssocToJoins();
|
|
@@ -188,6 +211,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
188
211
|
}
|
|
189
212
|
});
|
|
190
213
|
|
|
214
|
+
// Must happen after A2J, as A2J needs $self to correctly resolve stuff
|
|
215
|
+
if(doA2J)
|
|
216
|
+
flattening.removeLeadingSelf(csn);
|
|
217
|
+
|
|
191
218
|
const {
|
|
192
219
|
flattenStructuredElement,
|
|
193
220
|
flattenStructStepsInRef, getForeignKeyArtifact,
|
|
@@ -242,13 +269,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
242
269
|
},
|
|
243
270
|
}, true);
|
|
244
271
|
|
|
245
|
-
// (030) - For all elements, replace derived types by final base type
|
|
246
|
-
forEachDefinition(csn, (artifact) => {
|
|
247
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
248
|
-
toFinalBaseType(member);
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
|
|
252
272
|
// (040) Ignore entities and views that are abstract or implemented
|
|
253
273
|
// or carry the annotation cds.persistence.skip/exists
|
|
254
274
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
@@ -260,57 +280,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
260
280
|
// Temporal only in beta-mode
|
|
261
281
|
forEachDefinition(csn, handleTemporalAnnotations);
|
|
262
282
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// the following step can rely on the rest of the model having
|
|
266
|
-
// a certain structure, i.e. structs unfolded
|
|
267
|
-
if(!options.forHana.keepStructsAssocs) {
|
|
268
|
-
const flatteningOfStructured = [];
|
|
269
|
-
const adaptRefs = [];
|
|
270
|
-
// A2J resolves paths in queries to their flattened version,
|
|
271
|
-
// that is the foreign key of a managed assoc that will be generated in a later step
|
|
272
|
-
// we need to adapt the refs after the foreign keys are generated
|
|
273
|
-
const adaptQueryRefLater = [];
|
|
274
|
-
|
|
275
|
-
const fkRefs = new WeakMap();
|
|
276
|
-
|
|
277
|
-
applyTransformations(csn, {
|
|
278
|
-
keys: (parent, prop, keys) => {
|
|
279
|
-
keys.forEach(key => fkRefs.set(key.ref, true));
|
|
280
|
-
},
|
|
281
|
-
ref: (parent, prop, ref, path) => {
|
|
282
|
-
// Do not process fk refs - no need to adapt
|
|
283
|
-
if(fkRefs.has(ref))
|
|
284
|
-
return;
|
|
285
|
-
|
|
286
|
-
setProp(parent, '$path', [...path]);
|
|
287
|
-
const lastRef = ref[ref.length-1];
|
|
288
|
-
const fn = () => {
|
|
289
|
-
const scopedPath = [...parent.$path];
|
|
290
|
-
parent.ref = flattenStructStepsInRef(ref, scopedPath);
|
|
291
|
-
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
292
|
-
if (parent.ref[ref.length - 1] != lastRef && insideColumns(scopedPath) && !parent.as)
|
|
293
|
-
parent.as = lastRef;
|
|
294
|
-
};
|
|
295
|
-
// adapt queries later
|
|
296
|
-
const enclosingArtifact = csn.definitions[path[1]];
|
|
297
|
-
if(enclosingArtifact.query)
|
|
298
|
-
adaptQueryRefLater.push(fn);
|
|
299
|
-
else
|
|
300
|
-
adaptRefs.push(fn);
|
|
301
|
-
}
|
|
302
|
-
}, [(definitions, artifactName, artifact) => flatteningOfStructured.push(() => flattenStructuredElements(artifact, artifactName))]);
|
|
303
|
-
|
|
304
|
-
adaptRefs.forEach(fn => fn());
|
|
305
|
-
flatteningOfStructured.forEach(fn => fn());
|
|
306
|
-
handleManagedAssociationsAndCreateForeignKeys();
|
|
307
|
-
// now the foreign key references in queries are resolvable
|
|
308
|
-
bindCsnReferenceOnly();
|
|
309
|
-
adaptQueryRefLater.forEach(fn => fn());
|
|
310
|
-
}else {
|
|
311
|
-
handleManagedAssociationsAndCreateForeignKeys();
|
|
312
|
-
}
|
|
313
|
-
|
|
283
|
+
handleManagedAssociationsAndCreateForeignKeys();
|
|
284
|
+
|
|
314
285
|
function handleManagedAssociationsAndCreateForeignKeys() {
|
|
315
286
|
forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
|
|
316
287
|
forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
|
|
@@ -359,20 +330,19 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
359
330
|
// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
|
|
360
331
|
forEachDefinition(csn, transformSelfInBacklinks);
|
|
361
332
|
|
|
362
|
-
if(
|
|
333
|
+
if(options.forHana){
|
|
363
334
|
/**
|
|
364
335
|
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
|
|
365
336
|
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
|
|
366
337
|
* hence we do not generate the referential constraints for them.
|
|
367
338
|
*/
|
|
368
339
|
const validOptionsForConstraint = () => {
|
|
369
|
-
return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') &&
|
|
370
|
-
!(options.toHana && options.toHana.names === 'hdbcds')
|
|
340
|
+
return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
|
|
371
341
|
}
|
|
372
342
|
if(validOptionsForConstraint())
|
|
373
343
|
createReferentialConstraints(csn, options);
|
|
374
344
|
}
|
|
375
|
-
|
|
345
|
+
// no constraints for drafts
|
|
376
346
|
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
377
347
|
|
|
378
348
|
// Set the final constraint paths and produce hana tc indexes if required
|
|
@@ -385,17 +355,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
385
355
|
// Apply view-specific transformations
|
|
386
356
|
// (160) Projections now finally become views
|
|
387
357
|
// Replace managed association in group/order by with foreign keys
|
|
358
|
+
const transformEntityOrViewPass2 = getViewTransformer(csn, options, {error, info}, transformCommon);
|
|
388
359
|
forEachDefinition(csn, transformViews);
|
|
389
360
|
|
|
390
361
|
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
391
362
|
forEachDefinition(csn, recursivelyApplyCommon);
|
|
392
363
|
|
|
393
|
-
// (20 a) If we keep associations as they are (hdbcds naming convention), we cannot have structured
|
|
394
|
-
// view elements (we could enumerate the elements but we can't give them the names one would expect)
|
|
395
|
-
// Check foreign keys of redirected associations
|
|
396
|
-
// (200) Strip 'key' property from type elements
|
|
397
|
-
forEachDefinition(csn, recursiveChecks);
|
|
398
|
-
|
|
399
364
|
const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
|
|
400
365
|
assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
|
|
401
366
|
};
|
|
@@ -419,12 +384,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
419
384
|
checkConstraintIdentifiers,
|
|
420
385
|
/* (250) Remove all namespaces from definitions */
|
|
421
386
|
removeNamespaces,
|
|
422
|
-
/* (190 b) Replace enum types by their final base type */
|
|
423
|
-
replaceEnumsByBaseTypes,
|
|
424
387
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
425
388
|
checkTypeParameters,
|
|
426
389
|
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
427
|
-
ignoreNonPersistedArtifactsWithAnonymousAspectComposition
|
|
390
|
+
ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
|
|
391
|
+
// (200) Strip 'key' property from type elements
|
|
392
|
+
removeKeyPropInType,
|
|
428
393
|
]);
|
|
429
394
|
|
|
430
395
|
throwWithError();
|
|
@@ -436,34 +401,31 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
436
401
|
}
|
|
437
402
|
|
|
438
403
|
const killers = {
|
|
404
|
+
// Used to ignore actions etc from processing and remove associations/elements
|
|
439
405
|
'_ignore': function (parent, a, b, path){
|
|
440
406
|
if(path.length > 2) {
|
|
441
407
|
const tail = path[path.length-1];
|
|
442
|
-
const
|
|
408
|
+
const parentPath = path.slice(0, -1)
|
|
409
|
+
const parentParent = walkCsnPath(csn, parentPath);
|
|
443
410
|
delete parentParent[tail];
|
|
444
411
|
} else {
|
|
445
412
|
delete parent._ignore;
|
|
446
413
|
}
|
|
447
414
|
},
|
|
448
|
-
|
|
449
|
-
'_effectiveType': killProp,
|
|
415
|
+
// Still used in flattenStructuredElements - in db/flattening.js
|
|
450
416
|
'_flatElementNameWithDots': killProp,
|
|
451
|
-
|
|
417
|
+
// Set when setting default string/binary length - used in copyTypeProperties and fixBorkedElementsOfLocalized
|
|
418
|
+
// to not copy the .length property if it was only set via default
|
|
452
419
|
'$default': killProp,
|
|
453
|
-
|
|
454
|
-
'$env': killProp,
|
|
455
|
-
'$fksgenerated': killProp,
|
|
456
|
-
'$lateFlattening': killProp,
|
|
457
|
-
'$path': killProp,
|
|
420
|
+
// Set when we turn UUID into String, checked during generateDraftForHana
|
|
458
421
|
'$renamed': killProp,
|
|
459
|
-
|
|
460
|
-
'$
|
|
422
|
+
// Set when we remove .key from temporal things, used in localized.js
|
|
423
|
+
'$key': killProp
|
|
461
424
|
}
|
|
462
425
|
|
|
463
426
|
applyTransformations(csn, killers, [], false);
|
|
464
427
|
|
|
465
428
|
redoProjections.forEach(fn => fn());
|
|
466
|
-
columnClearer.forEach(fn => fn());
|
|
467
429
|
|
|
468
430
|
return csn;
|
|
469
431
|
|
|
@@ -478,7 +440,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
478
440
|
* @param {string} artName
|
|
479
441
|
*/
|
|
480
442
|
function createForeignKeyElements(art, artName) {
|
|
481
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
443
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
482
444
|
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
483
445
|
const elementsArray = [];
|
|
484
446
|
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
@@ -528,14 +490,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
528
490
|
|
|
529
491
|
function bindCsnReference(){
|
|
530
492
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
531
|
-
({ artifactRef, inspectRef,
|
|
532
|
-
({ getFinalBaseType } = getUtils(csn));
|
|
493
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
494
|
+
({ getFinalBaseType, get$combined } = getUtils(csn));
|
|
533
495
|
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
534
496
|
}
|
|
535
497
|
|
|
536
498
|
function bindCsnReferenceOnly(){
|
|
537
499
|
// invalidate caches for CSN ref API
|
|
538
|
-
({ artifactRef, inspectRef,
|
|
500
|
+
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
|
|
539
501
|
}
|
|
540
502
|
|
|
541
503
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
@@ -568,12 +530,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
568
530
|
if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
|
|
569
531
|
const { links } = inspectRef(path.concat(['on', i]));
|
|
570
532
|
if(links){
|
|
571
|
-
// no assocs in $projection / $self paths #5050
|
|
572
|
-
links.forEach((element, j) => {
|
|
573
|
-
const { art } = links[j];
|
|
574
|
-
if(art && art.target) // Fine for plain
|
|
575
|
-
error(null, path.concat(['on', j]), `Step "${onConditionPart.ref[j]}" in path "${onConditionPart.ref.join('.')}" must not be an association`);
|
|
576
|
-
});
|
|
577
533
|
columnToReplace = onConditionPart.ref[links.length - 1];
|
|
578
534
|
}
|
|
579
535
|
}
|
|
@@ -648,36 +604,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
648
604
|
* @param {CSN.Artifact} artifact
|
|
649
605
|
* @param {string} artifactName
|
|
650
606
|
*/
|
|
651
|
-
function
|
|
607
|
+
function removeKeyPropInType(artifact, artifactName) {
|
|
652
608
|
if (!artifact._ignore) {
|
|
653
|
-
forEachMemberRecursively(artifact, (member
|
|
654
|
-
if (options.forHana.keepStructsAssocs &&
|
|
655
|
-
artifact.query &&
|
|
656
|
-
isStructured(member)) {
|
|
657
|
-
error(null, path, `With "hdbcds" naming, structured elements can't be used in a view`);
|
|
658
|
-
return;
|
|
659
|
-
}
|
|
660
|
-
|
|
609
|
+
forEachMemberRecursively(artifact, (member) => {
|
|
661
610
|
if (artifact.kind === 'type' && member.key)
|
|
662
611
|
delete member.key;
|
|
663
612
|
}, [ 'definitions', artifactName ]);
|
|
664
613
|
}
|
|
665
614
|
}
|
|
666
615
|
|
|
667
|
-
/**
|
|
668
|
-
* @param {CSN.Artifact} artifact
|
|
669
|
-
* @param {string} artifactName
|
|
670
|
-
*/
|
|
671
|
-
function replaceEnumsByBaseTypes(artifact, artifactName) {
|
|
672
|
-
replaceEnumByBaseType(artifact);
|
|
673
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
674
|
-
replaceEnumByBaseType(member);
|
|
675
|
-
if (options.forHana.alwaysResolveDerivedTypes || options.forHana.names === 'plain') {
|
|
676
|
-
toFinalBaseType(member);
|
|
677
|
-
addDefaultTypeFacets(member);
|
|
678
|
-
}
|
|
679
|
-
}, [ 'definitions', artifactName ]);
|
|
680
|
-
}
|
|
681
616
|
|
|
682
617
|
/**
|
|
683
618
|
* @param {CSN.Artifact} artifact
|
|
@@ -720,7 +655,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
720
655
|
if (artifact.params) {
|
|
721
656
|
// HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
|
|
722
657
|
// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
|
|
723
|
-
error(null, path,
|
|
658
|
+
error(null, path, 'Unexpected association in parameterized view');
|
|
724
659
|
}
|
|
725
660
|
else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
726
661
|
// UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
|
|
@@ -729,7 +664,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
729
664
|
if (csn.definitions[member.target].params) {
|
|
730
665
|
// HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
|
|
731
666
|
// SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
|
|
732
|
-
error(null, path,
|
|
667
|
+
error(null, path, 'Unexpected parameterized association target');
|
|
733
668
|
}
|
|
734
669
|
else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
735
670
|
// HANA won't check the assoc target but when querying an association with target UDF, this is the error:
|
|
@@ -755,14 +690,15 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
755
690
|
function handleAssociations(artifact, artifactName) {
|
|
756
691
|
// Do things specific for entities and views (pass 1)
|
|
757
692
|
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
693
|
+
const alreadyHandled = new WeakMap();
|
|
758
694
|
forAllElements(artifact, artifactName, (parent, elements) => {
|
|
759
695
|
for (const elemName in elements) {
|
|
760
696
|
const elem = elements[elemName];
|
|
761
697
|
// (140) Generate foreign key elements and ON-condition for managed associations
|
|
762
698
|
// (unless explicitly asked to keep assocs unchanged)
|
|
763
|
-
if (
|
|
699
|
+
if (doA2J) {
|
|
764
700
|
if (isManagedAssociationElement(elem))
|
|
765
|
-
transformManagedAssociation(parent, artifactName, elem, elemName);
|
|
701
|
+
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
|
|
766
702
|
}
|
|
767
703
|
}
|
|
768
704
|
})
|
|
@@ -771,7 +707,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
771
707
|
|
|
772
708
|
function fixBorkedElementsOfLocalized(elements, pathToElements){
|
|
773
709
|
const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
|
|
774
|
-
const nonLocalizedElements = walkCsnPath(pathToNonLocalized);
|
|
710
|
+
const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
|
|
775
711
|
|
|
776
712
|
|
|
777
713
|
for(const elementName in elements){
|
|
@@ -818,7 +754,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
818
754
|
else {
|
|
819
755
|
for (const pname in artifact.params) {
|
|
820
756
|
if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
|
|
821
|
-
warning(null, [ 'definitions', artifactName, 'params', pname ], `
|
|
757
|
+
warning(null, [ 'definitions', artifactName, 'params', pname ], `Expecting regular SQL-Identifier`);
|
|
822
758
|
}
|
|
823
759
|
else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
|
|
824
760
|
warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
|
|
@@ -872,9 +808,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
872
808
|
}
|
|
873
809
|
|
|
874
810
|
function handleAssocToJoins() {
|
|
811
|
+
// With flattening errors, it makes little sense to continue.
|
|
812
|
+
throwWithError();
|
|
875
813
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
876
814
|
// simply make it invisible and copy it over to the result csn
|
|
877
815
|
forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
|
|
816
|
+
|
|
878
817
|
const newCsn = translateAssocsToJoinsCSN(csn, options);
|
|
879
818
|
|
|
880
819
|
// restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
|
|
@@ -885,30 +824,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
885
824
|
if (art.technicalConfig)
|
|
886
825
|
newCsn.definitions[artName].technicalConfig = art.technicalConfig;
|
|
887
826
|
|
|
888
|
-
const newArt = newCsn.definitions[artName];
|
|
889
|
-
|
|
890
|
-
// No need to loop/check artifacts that won't reach the DB anyways
|
|
891
|
-
if (art.query && newArt && newArt.query && isPersistedOnDatabase(newArt)) {
|
|
892
|
-
// Loop through the newCSN and add possible new _ignore mixin to the kill list
|
|
893
|
-
forAllQueries(newArt.query, (q, p) => {
|
|
894
|
-
if (q.SELECT && q.SELECT.mixin) {
|
|
895
|
-
for(let mixinName of Object.keys(q.SELECT.mixin)) {
|
|
896
|
-
const mixinElement = q.SELECT.mixin[mixinName];
|
|
897
|
-
if (mixinElement._ignore && options.toSql) {
|
|
898
|
-
columnClearer.push(() => {
|
|
899
|
-
const query = walkCsnPath(p);
|
|
900
|
-
for(let i = query.columns.length-1; i > -1; i--){
|
|
901
|
-
const col = query.columns[i];
|
|
902
|
-
if(col && col.ref && col.ref[0] === mixinName){
|
|
903
|
-
query.columns.splice(i, 1);
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
});
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}, ['definitions', artName, 'query']);
|
|
911
|
-
}
|
|
912
827
|
});
|
|
913
828
|
csn = newCsn;
|
|
914
829
|
}
|
|
@@ -1056,127 +971,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1056
971
|
}
|
|
1057
972
|
}
|
|
1058
973
|
|
|
1059
|
-
|
|
1060
|
-
/**
|
|
1061
|
-
* Compute and return $combined for the given query.
|
|
1062
|
-
*
|
|
1063
|
-
* @param {CSN.Query} query
|
|
1064
|
-
* @returns {object}
|
|
1065
|
-
*/
|
|
1066
|
-
function get$combined(query) {
|
|
1067
|
-
const sources = getSources(query);
|
|
1068
|
-
return sources;
|
|
1069
|
-
|
|
1070
|
-
/**
|
|
1071
|
-
* Get the union of all elements from the from clause
|
|
1072
|
-
* - descend into unions, following the lead query
|
|
1073
|
-
* - merge all queries in case of joins
|
|
1074
|
-
* - follow subqueries
|
|
1075
|
-
*
|
|
1076
|
-
* @param {CSN.Query} query Query to check
|
|
1077
|
-
* @returns {object} Map of sources
|
|
1078
|
-
*/
|
|
1079
|
-
function getSources(query) {
|
|
1080
|
-
// Remark CW: better just a while along query.SET.args[0]
|
|
1081
|
-
if (query.SET) {
|
|
1082
|
-
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
|
|
1083
|
-
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
|
|
1084
|
-
|
|
1085
|
-
return getSources(query.SET.args[0]);
|
|
1086
|
-
}
|
|
1087
|
-
else if (query.SELECT) {
|
|
1088
|
-
if (query.SELECT.from.args) {
|
|
1089
|
-
return walkArgs(query.SELECT.from.args);
|
|
1090
|
-
}
|
|
1091
|
-
else if (query.SELECT.from.ref) {
|
|
1092
|
-
const art = artifactRef(query.SELECT.from);
|
|
1093
|
-
return mergeElementsIntoMap(Object.create(null), art.elements, art.$location,
|
|
1094
|
-
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
|
|
1095
|
-
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
|
|
1096
|
-
}
|
|
1097
|
-
else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
|
|
1098
|
-
return getSources(query.SELECT.from);
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
function walkArgs(args) {
|
|
1103
|
-
let elements = Object.create(null);
|
|
1104
|
-
for (const arg of args) {
|
|
1105
|
-
if (arg.args) {
|
|
1106
|
-
elements = mergeElementMaps(elements, walkArgs(arg.args));
|
|
1107
|
-
}
|
|
1108
|
-
else if (arg.ref) {
|
|
1109
|
-
const art = artifactRef(arg);
|
|
1110
|
-
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
|
|
1111
|
-
}
|
|
1112
|
-
else if (arg.SELECT || arg.SET) {
|
|
1113
|
-
elements = mergeElementMaps(elements, getSources(arg));
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
return elements;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
return {};
|
|
1121
|
-
|
|
1122
|
-
/**
|
|
1123
|
-
* Merge two maps of elements together
|
|
1124
|
-
*
|
|
1125
|
-
* @param {object} mapA Map a - will be returned
|
|
1126
|
-
* @param {object} mapB Map b - will not be returned
|
|
1127
|
-
* @returns {object} mapA
|
|
1128
|
-
*/
|
|
1129
|
-
function mergeElementMaps(mapA, mapB) {
|
|
1130
|
-
for (const elementName in mapB) {
|
|
1131
|
-
if (!mapA[elementName])
|
|
1132
|
-
mapA[elementName] = [];
|
|
1133
|
-
|
|
1134
|
-
mapB[elementName].forEach(e => mapA[elementName].push(e));
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
return mapA;
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
/**
|
|
1141
|
-
* Merge elements into an existing map
|
|
1142
|
-
*
|
|
1143
|
-
* @param {any} existingMap map to merge into - will be returned
|
|
1144
|
-
* @param {object} elements elements to merge into the map
|
|
1145
|
-
* @param {CSN.Location} $location $location of the elements - where they come from
|
|
1146
|
-
* @param {any} [parent] Name of the parent of the elements, alias before ref
|
|
1147
|
-
* @param {any} [error_parent] Parent name to use for error messages, ref before alias
|
|
1148
|
-
* @returns {object} existingMap
|
|
1149
|
-
*/
|
|
1150
|
-
function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
|
|
1151
|
-
for (const elementName in elements) {
|
|
1152
|
-
const element = elements[elementName];
|
|
1153
|
-
if (!existingMap[elementName])
|
|
1154
|
-
existingMap[elementName] = [];
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
existingMap[elementName].push({
|
|
1158
|
-
element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
|
|
1159
|
-
});
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
return existingMap;
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
/**
|
|
1167
|
-
* Return the name part of the artifact name - no namespace etc.
|
|
1168
|
-
* @param {string|object} name Absolute name of the artifact
|
|
1169
|
-
*/
|
|
1170
|
-
function getBaseName(name) {
|
|
1171
|
-
if (!name)
|
|
1172
|
-
return name;
|
|
1173
|
-
|
|
1174
|
-
if (name.id)
|
|
1175
|
-
return name.id.substring( name.id.lastIndexOf('.')+1 );
|
|
1176
|
-
|
|
1177
|
-
return name.substring( name.lastIndexOf('.')+1 )
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
974
|
/**
|
|
1181
975
|
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
1182
976
|
*
|
|
@@ -1215,10 +1009,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1215
1009
|
if (isPersistedOnDatabase(artifact)) {
|
|
1216
1010
|
// TODO: structure in CSN is artifact.query.[SELECT/SET].mixin
|
|
1217
1011
|
if (artifact.query) {
|
|
1218
|
-
|
|
1012
|
+
// If we do A2J, we don't need to check the mixin. Either it is used -> a join
|
|
1013
|
+
// or published -> handled via elements/members. Unused mixins are removed anyway.
|
|
1014
|
+
if (!doA2J && artifact.query.SELECT && artifact.query.SELECT.mixin)
|
|
1219
1015
|
forEachGeneric(artifact.query.SELECT, 'mixin', ignore, path.concat([ 'query', 'SELECT' ]));
|
|
1220
1016
|
|
|
1221
|
-
else if (artifact.query.SET && artifact.query.SET.mixin)
|
|
1017
|
+
else if (!doA2J && artifact.query.SET && artifact.query.SET.mixin)
|
|
1222
1018
|
forEachGeneric(artifact.query.SET, 'mixin', ignore, path.concat([ 'query', 'SET' ]));
|
|
1223
1019
|
}
|
|
1224
1020
|
forEachMemberRecursively(artifact, ignore, [ 'definitions', artifactName ]);
|
|
@@ -1259,16 +1055,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1259
1055
|
|
|
1260
1056
|
// (190 a) Replace enum symbols by their value (if found)
|
|
1261
1057
|
replaceEnumSymbolsByValues(obj, path);
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
function walkCsnPath(path) {
|
|
1265
|
-
/** @type {object} */
|
|
1266
|
-
let obj = csn;
|
|
1267
|
-
for(let i = 0; i < path.length; i++){
|
|
1268
|
-
obj = obj[path[i]];
|
|
1269
|
-
}
|
|
1270
1058
|
|
|
1271
|
-
|
|
1059
|
+
if (obj.enum)
|
|
1060
|
+
delete obj.enum;
|
|
1272
1061
|
}
|
|
1273
1062
|
|
|
1274
1063
|
// Change the names of those builtin types that have different names in HANA.
|
|
@@ -1302,320 +1091,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1302
1091
|
// }
|
|
1303
1092
|
// }
|
|
1304
1093
|
|
|
1305
|
-
/**
|
|
1306
|
-
* Strip of leading $self of the ref
|
|
1307
|
-
* @param {object} col A column
|
|
1308
|
-
*
|
|
1309
|
-
* @returns {object}
|
|
1310
|
-
*/
|
|
1311
|
-
function stripLeadingSelf(col) {
|
|
1312
|
-
if (col.ref && col.ref.length > 1 && col.ref[0] === '$self')
|
|
1313
|
-
col.ref = col.ref.slice(1);
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
return col;
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
function isUnion(path){
|
|
1320
|
-
const subquery = path[path.length-1];
|
|
1321
|
-
const queryIndex = path[path.length-2]
|
|
1322
|
-
const args = path[path.length-3];
|
|
1323
|
-
const unionOperator = path[path.length-4];
|
|
1324
|
-
return path.length > 3 && (subquery === 'SET' || subquery === 'SELECT') && typeof queryIndex === 'number' && queryIndex >= 0 && args === 'args' && unionOperator === 'SET';
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
function transformEntityOrViewPass2(query, artifact, artName, path) {
|
|
1328
|
-
const { elements } = queryOrMain(query, artifact);
|
|
1329
|
-
let hasNonAssocElements = false;
|
|
1330
|
-
const isSelect = query && query.SELECT;
|
|
1331
|
-
let isProjection = !!artifact.projection;
|
|
1332
|
-
const columnMap = Object.create(null);
|
|
1333
|
-
let isSelectStar = false;
|
|
1334
|
-
if (isSelect) {
|
|
1335
|
-
if (!query.SELECT.columns) {
|
|
1336
|
-
isProjection = true;
|
|
1337
|
-
}
|
|
1338
|
-
else {
|
|
1339
|
-
query.SELECT.columns.forEach((col) => {
|
|
1340
|
-
if (col === '*') {
|
|
1341
|
-
isSelectStar = true;
|
|
1342
|
-
}
|
|
1343
|
-
else if (col.as) {
|
|
1344
|
-
if (!columnMap[col.as])
|
|
1345
|
-
columnMap[col.as] = col;
|
|
1346
|
-
}
|
|
1347
|
-
else if (col.ref) {
|
|
1348
|
-
if (!columnMap[col.ref[col.ref.length - 1]])
|
|
1349
|
-
columnMap[col.ref[col.ref.length - 1]] = col;
|
|
1350
|
-
}
|
|
1351
|
-
else if (col.func) {
|
|
1352
|
-
columnMap[col.func] = col;
|
|
1353
|
-
}
|
|
1354
|
-
else if (!columnMap[col]) {
|
|
1355
|
-
columnMap[col] = col;
|
|
1356
|
-
}
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
if (query && options.toHana) {
|
|
1361
|
-
// check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
|
|
1362
|
-
if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
|
|
1363
|
-
for (const elementName in elements) {
|
|
1364
|
-
const element = elements[elementName];
|
|
1365
|
-
if (element.target) {
|
|
1366
|
-
let colLocation;
|
|
1367
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1368
|
-
const col = query.SELECT.columns[i];
|
|
1369
|
-
if (col.ref && col.ref.length === 1) {
|
|
1370
|
-
if (!colLocation && col.ref[0] === elementName)
|
|
1371
|
-
colLocation = i;
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
if (col.as === elementName)
|
|
1375
|
-
colLocation = i;
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
if (colLocation) {
|
|
1379
|
-
const matchingCol = query.SELECT.columns[colLocation];
|
|
1380
|
-
const possibleMixinName = matchingCol.ref[0];
|
|
1381
|
-
const isMixin = query.SELECT.mixin[possibleMixinName] !== undefined;
|
|
1382
|
-
if (element.target && isMixin)
|
|
1383
|
-
error(null, path.concat([ 'columns', colLocation ]),
|
|
1384
|
-
`Element "${ elementName }" is a mixin association${ possibleMixinName !== elementName ? ` ("${ possibleMixinName }")` : '' } and can't be published in a UNION`);
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
|
|
1391
|
-
// Second walk through the entity elements: Deal with associations (might also result in new elements)
|
|
1392
|
-
|
|
1393
|
-
// Will be initialized JIT inside the elements-loop
|
|
1394
|
-
let $combined;
|
|
1395
|
-
|
|
1396
|
-
for (const elemName in elements) {
|
|
1397
|
-
const elem = elements[elemName];
|
|
1398
|
-
if (isSelect) {
|
|
1399
|
-
if (!columnMap[elemName]) {
|
|
1400
|
-
// Prepend an alias if present
|
|
1401
|
-
let alias = (isProjection || isSelectStar) &&
|
|
1402
|
-
(query.SELECT.from.as || (query.SELECT.from.ref && implicitAs(query.SELECT.from.ref)));
|
|
1403
|
-
// In case of * and no explicit alias
|
|
1404
|
-
// find the source of the col by looking at $combined and prepend it
|
|
1405
|
-
if (isSelectStar && !alias && !isProjection) {
|
|
1406
|
-
if (!$combined)
|
|
1407
|
-
$combined = get$combined(query);
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
const matchingCombined = $combined[elemName];
|
|
1411
|
-
// Internal errors - this should never happen!
|
|
1412
|
-
if (matchingCombined.length > 1) { // should already be caught by compiler
|
|
1413
|
-
throw new Error(`Ambiguous name - can't be resolved: ${ elemName }. Found in: ${ matchingCombined.map(o => o.parent) }`);
|
|
1414
|
-
}
|
|
1415
|
-
else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
|
|
1416
|
-
throw new Error(`No matching entry found in UNION of all elements for: ${ elemName }`);
|
|
1417
|
-
}
|
|
1418
|
-
alias = matchingCombined[0].parent;
|
|
1419
|
-
}
|
|
1420
|
-
if (alias)
|
|
1421
|
-
columnMap[elemName] = { ref: [ alias, elemName ] };
|
|
1422
|
-
else
|
|
1423
|
-
columnMap[elemName] = { ref: [ elemName ] };
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
// For associations - make sure that the foreign keys have the same "style"
|
|
1427
|
-
// If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
|
|
1428
|
-
if (elem.keys && !options.forHana.keepStructsAssocs) {
|
|
1429
|
-
const assoc_col = columnMap[elemName];
|
|
1430
|
-
if (assoc_col && assoc_col.ref) {
|
|
1431
|
-
elem.keys.forEach((key) => {
|
|
1432
|
-
const ref = cloneCsn(assoc_col.ref, options);
|
|
1433
|
-
ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
|
|
1434
|
-
const result = {
|
|
1435
|
-
ref,
|
|
1436
|
-
};
|
|
1437
|
-
if (assoc_col.as)
|
|
1438
|
-
result.as = key.$generatedFieldName;
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
if (assoc_col.key)
|
|
1442
|
-
result.key = true;
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
const colName = result.as || ref[ref.length - 1];
|
|
1446
|
-
columnMap[colName] = result;
|
|
1447
|
-
});
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
// Add flattened structured things preserving aliases and refs with/without table alias
|
|
1451
|
-
// If we add them when we get to them in "elements", we cannot know what table alias was used...
|
|
1452
|
-
if (isStructured(elem) && !options.forHana.keepStructsAssocs) {
|
|
1453
|
-
const col = columnMap[elemName];
|
|
1454
|
-
const originalName = col.ref[col.ref.length - 1];
|
|
1455
|
-
const flatElements = flattenStructuredElement(elem, originalName, [], path);
|
|
1456
|
-
const aliasedFlatElements = originalName !== elemName ? Object.keys(flattenStructuredElement(elem, elemName, [], path)) : [];
|
|
1457
|
-
|
|
1458
|
-
Object.keys(flatElements).forEach((flatElemName, index ) => {
|
|
1459
|
-
const clone = cloneCsn(col, options);
|
|
1460
|
-
// For the ref, use the "original"
|
|
1461
|
-
if (clone.ref)
|
|
1462
|
-
clone.ref[clone.ref.length - 1] = flatElemName;
|
|
1463
|
-
|
|
1464
|
-
// If the column was aliased, use the alias-prefix for the flattened element
|
|
1465
|
-
if (originalName !== elemName)
|
|
1466
|
-
clone.as = aliasedFlatElements[index];
|
|
1467
|
-
|
|
1468
|
-
// Insert into map, giving precedence to the alias
|
|
1469
|
-
columnMap[clone.as || flatElemName] = clone;
|
|
1470
|
-
});
|
|
1471
|
-
}
|
|
1472
|
-
}
|
|
1473
|
-
// Views must have at least one element that is not an unmanaged assoc
|
|
1474
|
-
if (!elem.on && !elem._ignore)
|
|
1475
|
-
hasNonAssocElements = true;
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
// (230 b) If we keep associations as they are (hdbcds naming convention), we cannot have managed associations
|
|
1479
|
-
// as view elements (their foreign keys cannot be addressed in the view)
|
|
1480
|
-
if (options.forHana.keepStructsAssocs &&
|
|
1481
|
-
query &&
|
|
1482
|
-
isAssocOrComposition(elem.type) &&
|
|
1483
|
-
!elem.on) {
|
|
1484
|
-
error(null, [ 'definitions', artName, 'elements', elemName ], `With "hdbcds" naming, managed association elements can't be used in a view`);
|
|
1485
|
-
continue;
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
// (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
|
|
1489
|
-
// CDXCORE-585: Allow mixin associations to be used and published in parallel
|
|
1490
|
-
if (query !== undefined && elem.target) {
|
|
1491
|
-
if(isUnion(path) && options.toHana){
|
|
1492
|
-
if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
|
|
1493
|
-
if(elem.keys) {
|
|
1494
|
-
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
|
|
1495
|
-
} else {
|
|
1496
|
-
info(null, path, `Association "${elemName}", published in a UNION, will be ignored`)
|
|
1497
|
-
}
|
|
1498
|
-
elem._ignore = true;
|
|
1499
|
-
}
|
|
1500
|
-
else {
|
|
1501
|
-
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
|
|
1502
|
-
}
|
|
1503
|
-
} else if(path.length > 4 && options.toHana){ // path.length > 4 -> is a subquery
|
|
1504
|
-
error(null, path, { name: elemName },
|
|
1505
|
-
'Association $(NAME) can\'t be published in a subquery')
|
|
1506
|
-
} else {
|
|
1507
|
-
/* Old implementation:
|
|
1508
|
-
const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
|
|
1509
|
-
*/
|
|
1510
|
-
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elem, elemName);
|
|
1511
|
-
const {mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
|
|
1512
|
-
if (isNotMixinByItself || mixinElement !== undefined) {
|
|
1513
|
-
// If the mixin is only published and not used, only display the __ clone. Ignore the "original".
|
|
1514
|
-
if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName)){
|
|
1515
|
-
mixinElement._ignore = true;
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1518
|
-
delete elem._typeIsExplicit;
|
|
1519
|
-
// Create an unused alias name for the MIXIN - use 3 _ to avoid collision with usings
|
|
1520
|
-
let mixinElemName = `___${ mixinName || elemName }`;
|
|
1521
|
-
while (elements[mixinElemName])
|
|
1522
|
-
mixinElemName = `_${ mixinElemName }`;
|
|
1523
|
-
|
|
1524
|
-
// Copy the association element to the MIXIN clause under its alias name
|
|
1525
|
-
// (shallow copy is sufficient, just fix name and value)
|
|
1526
|
-
const mixinElem = Object.assign({}, elem);
|
|
1527
|
-
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)
|
|
1528
|
-
transformCommon(mixinElem, mixinElemName);
|
|
1529
|
-
// TODO: Can we rely on query.SELECT.mixin to check for mixins?
|
|
1530
|
-
// Yes, we can - only SELECT can have mixin. But:
|
|
1531
|
-
// - UNION
|
|
1532
|
-
// - JOINS
|
|
1533
|
-
// - Subqueries
|
|
1534
|
-
// Are currently (and in the old transformer) not handled!
|
|
1535
|
-
if (query.SELECT && !query.SELECT.mixin)
|
|
1536
|
-
query.SELECT.mixin = Object.create(null);
|
|
1537
|
-
|
|
1538
|
-
// Let the original association element use the newly generated MIXIN name as value and alias
|
|
1539
|
-
delete elem.viaAll;
|
|
1540
|
-
|
|
1541
|
-
// Clone 'on'-condition, pre-pending '$projection' to paths where appropriate,
|
|
1542
|
-
// and fixing the association alias just created
|
|
1543
|
-
|
|
1544
|
-
if (mixinElem.on) {
|
|
1545
|
-
mixinElem.on = cloneWithTransformations(mixinElem.on, {
|
|
1546
|
-
ref: (ref) => {
|
|
1547
|
-
// Clone the path, without any transformations
|
|
1548
|
-
const clonedPath = cloneWithTransformations(ref, {});
|
|
1549
|
-
// Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
|
|
1550
|
-
if (clonedPath[0] == elemName) {
|
|
1551
|
-
clonedPath[0] = mixinElemName;
|
|
1552
|
-
}
|
|
1553
|
-
else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
|
|
1554
|
-
const projectionId = '$projection';
|
|
1555
|
-
clonedPath.unshift(projectionId);
|
|
1556
|
-
}
|
|
1557
|
-
return clonedPath;
|
|
1558
|
-
},
|
|
1559
|
-
func: (func) => {
|
|
1560
|
-
// Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
|
|
1561
|
-
// above (no way to distinguish that in the callback for 'path' above). We can only pluck it
|
|
1562
|
-
// off again here ... sigh
|
|
1563
|
-
if (func.ref && func.ref[0] && func.ref[0] === '$projection')
|
|
1564
|
-
func.ref = func.ref.slice(1);
|
|
1565
|
-
|
|
1566
|
-
return func;
|
|
1567
|
-
},
|
|
1568
|
-
});
|
|
1569
|
-
}
|
|
1570
|
-
|
|
1571
|
-
if (!mixinElem._ignore)
|
|
1572
|
-
columnMap[elemName] = { ref: [ mixinElemName ], as: elemName };
|
|
1573
|
-
|
|
1574
|
-
if (query.SELECT) {
|
|
1575
|
-
query.SELECT.mixin[mixinElemName] = mixinElem;
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
if (query && !hasNonAssocElements) {
|
|
1583
|
-
// Complain if there are no elements other than unmanaged associations
|
|
1584
|
-
// Allow with plain
|
|
1585
|
-
error(null, [ 'definitions', artName ], { $reviewed: true } ,
|
|
1586
|
-
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
1587
|
-
}
|
|
1588
|
-
|
|
1589
|
-
if (isSelect) {
|
|
1590
|
-
// Workaround for bugzilla 176495 FIXME FIXME FIXME: is this really still needed?
|
|
1591
|
-
// If a select item of a cdx view contains an expression, the result type cannot be computed
|
|
1592
|
-
// but must be explicitly specified. This is important for the OData channel, which doesn't
|
|
1593
|
-
// work if the type is missing (for HANA channel an explicit type is not required, as HANA CDS
|
|
1594
|
-
// can compute the result type).
|
|
1595
|
-
// Due to bug in HANA CDS, providing explicit type 'LargeString' or 'LargeBinary' causes a
|
|
1596
|
-
// diserver crash. Until a fix in HANA CDS is available, we allow to suppress the explicit
|
|
1597
|
-
// type in the HANA channel via an annotation.
|
|
1598
|
-
Object.keys(columnMap).forEach((value) => {
|
|
1599
|
-
const elem = elements[value];
|
|
1600
|
-
if (elem && elem['@cds.workaround.noExplicitTypeForHANA'])
|
|
1601
|
-
delete columnMap[value].cast;
|
|
1602
|
-
});
|
|
1603
|
-
|
|
1604
|
-
query.SELECT.columns = Object.keys(elements).filter(elem => !elements[elem]._ignore).map(key => stripLeadingSelf(columnMap[key]));
|
|
1605
|
-
// If following an association, explicitly set the implicit alias
|
|
1606
|
-
// due to an issue with HANA
|
|
1607
|
-
for (let i = 0; i < query.SELECT.columns.length; i++) {
|
|
1608
|
-
const col = query.SELECT.columns[i];
|
|
1609
|
-
if (!col.as && col.ref && col.ref.length > 1) {
|
|
1610
|
-
const { links } = inspectRef(path.concat([ 'columns', i ]));
|
|
1611
|
-
if (links && links.slice(0, -1).some(({ art }) => isAssocOrComposition(art && art.type || '')))
|
|
1612
|
-
col.as = col.ref[col.ref.length - 1];
|
|
1613
|
-
}
|
|
1614
|
-
}
|
|
1615
|
-
delete query.SELECT.excluding; // just to make the output of the new transformer the same as the old
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
1094
|
|
|
1620
1095
|
// If 'elem' has a default that is an enum constant, replace that by its value. Complain
|
|
1621
1096
|
// if not found or not an enum type,
|
|
@@ -1632,7 +1107,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1632
1107
|
// Looks like it is always run?! But message says HANA CDS?!
|
|
1633
1108
|
error(null, path, {
|
|
1634
1109
|
$reviewed: true,
|
|
1635
|
-
name: `#${elem.default['#']}`
|
|
1110
|
+
name: `#${ elem.default['#'] }`
|
|
1636
1111
|
},
|
|
1637
1112
|
'Expecting enum literal $(NAME) to be used with an enum type');
|
|
1638
1113
|
}
|
|
@@ -1642,7 +1117,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1642
1117
|
if (!enumSymbol) {
|
|
1643
1118
|
error(null, path, {
|
|
1644
1119
|
$reviewed: true,
|
|
1645
|
-
name: `#${elem.default['#']}`
|
|
1120
|
+
name: `#${ elem.default['#'] }`
|
|
1646
1121
|
}, 'Enum literal $(NAME) is undefined in enumeration type');
|
|
1647
1122
|
}
|
|
1648
1123
|
else if (enumSymbol.val !== undefined) { // `val` may be `null`
|
|
@@ -1659,30 +1134,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1659
1134
|
}
|
|
1660
1135
|
}
|
|
1661
1136
|
|
|
1662
|
-
// If 'node' has an enum type, change node's type to be the enum's base type
|
|
1663
|
-
// and strip off the 'enum' property.
|
|
1664
|
-
function replaceEnumByBaseType(node) {
|
|
1665
|
-
if (node.items)
|
|
1666
|
-
replaceEnumByBaseType(node.items);
|
|
1667
|
-
|
|
1668
|
-
// (190 b) Replace enum types by their final base type (must happen after 190 a)
|
|
1669
|
-
/* Old implementation:
|
|
1670
|
-
if (node && node._finalType && (node.enum || node._finalType.enum)) {
|
|
1671
|
-
node.type = node._finalType.type
|
|
1672
|
-
// node.type = node._finalType.type._artifact._finalType.type;
|
|
1673
|
-
if (node._finalType.length) {
|
|
1674
|
-
node.length = node._finalType.length;
|
|
1675
|
-
}
|
|
1676
|
-
setProp(node, '_finalType', node.type._artifact);
|
|
1677
|
-
delete node.enum;
|
|
1678
|
-
}
|
|
1679
|
-
*/
|
|
1680
|
-
if (node && node.enum) {
|
|
1681
|
-
// toFinalBaseType(node);
|
|
1682
|
-
// addDefaultTypeFacets(node);
|
|
1683
|
-
delete node.enum;
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
1137
|
|
|
1687
1138
|
// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
|
|
1688
1139
|
// (in place) so that it
|
|
@@ -1713,23 +1164,27 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1713
1164
|
const assoc = inspectRef(path.concat([ i + 2 ])).art;
|
|
1714
1165
|
if (multipleExprs)
|
|
1715
1166
|
result.push('(');
|
|
1167
|
+
const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
|
|
1716
1168
|
result.push(...transformDollarSelfComparison(xprArgs[i + 2],
|
|
1717
1169
|
assoc,
|
|
1718
|
-
|
|
1170
|
+
backlinkName,
|
|
1719
1171
|
elem, elemName, art, artName, path.concat([ i ])
|
|
1720
1172
|
));
|
|
1721
1173
|
if (multipleExprs)
|
|
1722
1174
|
result.push(')');
|
|
1723
1175
|
i += 3;
|
|
1176
|
+
attachBacklinkInformation(backlinkName);
|
|
1724
1177
|
}
|
|
1725
1178
|
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
1726
1179
|
const assoc = inspectRef(path.concat([ i ])).art;
|
|
1727
1180
|
if (multipleExprs)
|
|
1728
1181
|
result.push('(');
|
|
1729
|
-
|
|
1182
|
+
const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
|
|
1183
|
+
result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
|
|
1730
1184
|
if (multipleExprs)
|
|
1731
1185
|
result.push(')');
|
|
1732
1186
|
i += 3;
|
|
1187
|
+
attachBacklinkInformation(backlinkName);
|
|
1733
1188
|
}
|
|
1734
1189
|
// Otherwise take one (!) token unchanged
|
|
1735
1190
|
else {
|
|
@@ -1749,6 +1204,24 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1749
1204
|
}
|
|
1750
1205
|
}
|
|
1751
1206
|
return result;
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* The knowledge whether an association was an `<up_>` association in a
|
|
1210
|
+
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
|
|
1211
|
+
* By the time we generate them, such on-conditions are already transformed
|
|
1212
|
+
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
|
|
1213
|
+
*
|
|
1214
|
+
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
|
|
1215
|
+
*/
|
|
1216
|
+
function attachBacklinkInformation(backlinkName) {
|
|
1217
|
+
if (elem.$selfOnCondition)
|
|
1218
|
+
elem.$selfOnCondition.up_.push(backlinkName);
|
|
1219
|
+
else {
|
|
1220
|
+
setProp(elem, '$selfOnCondition', {
|
|
1221
|
+
up_: [backlinkName]
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1752
1225
|
}
|
|
1753
1226
|
|
|
1754
1227
|
elem.on = processExpressionArgs(elem.on, pathToOn);
|
|
@@ -1806,8 +1279,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1806
1279
|
assoc.keys.forEach((k) => {
|
|
1807
1280
|
// Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
|
|
1808
1281
|
// With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
|
|
1809
|
-
const keyName = k.as &&
|
|
1810
|
-
const fKeyPath =
|
|
1282
|
+
const keyName = k.as && doA2J ? [k.as] : k.ref;
|
|
1283
|
+
const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
|
|
1811
1284
|
// FIXME: _artifact to the args ???
|
|
1812
1285
|
const a = [
|
|
1813
1286
|
{
|
|
@@ -1862,30 +1335,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1862
1335
|
}
|
|
1863
1336
|
}
|
|
1864
1337
|
|
|
1865
|
-
/**
|
|
1866
|
-
* @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
|
|
1867
|
-
*
|
|
1868
|
-
* @param {CSN.Query} query
|
|
1869
|
-
* @param {object} columnMap
|
|
1870
|
-
* @param {CSN.Artifact} columnMap
|
|
1871
|
-
* @param {string} elementName
|
|
1872
|
-
*/
|
|
1873
|
-
function checkIsNotMixinByItself(query, columnMap, element, elementName) {
|
|
1874
|
-
if (query && query.SELECT && query.SELECT.mixin) {
|
|
1875
|
-
const col = columnMap[elementName];
|
|
1876
|
-
|
|
1877
|
-
const realName = col.ref[col.ref.length - 1];
|
|
1878
|
-
// If the element is not part of the mixin => True
|
|
1879
|
-
return query.SELECT.mixin[realName] == undefined;
|
|
1880
|
-
}
|
|
1881
|
-
// the artifact does not define any mixins, the element cannot be a mixin
|
|
1882
|
-
return true;
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
function insideColumns(path) {
|
|
1886
|
-
return path.length >= 3 && path[path.length - 3] === 'SELECT' && path[path.length - 2] === 'columns';
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
1338
|
/**
|
|
1890
1339
|
* @param {CSN.Artifact} artifact
|
|
1891
1340
|
* @param {string} artifactName
|
|
@@ -1982,119 +1431,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1982
1431
|
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
|
|
1983
1432
|
}
|
|
1984
1433
|
|
|
1985
|
-
/**
|
|
1986
|
-
* Get not just the leafs, but all the branches of a structured element
|
|
1987
|
-
*
|
|
1988
|
-
* @param {object} element Structured element
|
|
1989
|
-
* @param {string} elementName Name of the structured element
|
|
1990
|
-
* @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
|
|
1991
|
-
*/
|
|
1992
|
-
function getBranches(element, elementName){
|
|
1993
|
-
const branches = {};
|
|
1994
|
-
const subbranchNames = [];
|
|
1995
|
-
const subbranchElements = [];
|
|
1996
|
-
walkElements(element, elementName);
|
|
1997
|
-
function walkElements(e, name){
|
|
1998
|
-
if(isBuiltinType(e)){
|
|
1999
|
-
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
2000
|
-
} else {
|
|
2001
|
-
const eType = effectiveType(e)
|
|
2002
|
-
const subelements = e.elements || eType.elements;
|
|
2003
|
-
if(subelements){
|
|
2004
|
-
subbranchElements.push(e);
|
|
2005
|
-
subbranchNames.push(name);
|
|
2006
|
-
for(let [subelementName, subelement] of Object.entries(subelements)){
|
|
2007
|
-
walkElements(subelement, subelementName);
|
|
2008
|
-
}
|
|
2009
|
-
subbranchNames.pop();
|
|
2010
|
-
subbranchElements.pop();
|
|
2011
|
-
} else {
|
|
2012
|
-
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
2013
|
-
}
|
|
2014
|
-
|
|
2015
|
-
}
|
|
2016
|
-
}
|
|
2017
|
-
return branches;
|
|
2018
|
-
}
|
|
2019
|
-
|
|
2020
|
-
/**
|
|
2021
|
-
* Flatten structures
|
|
2022
|
-
*
|
|
2023
|
-
* @param {CSN.Artifact} art Artifact
|
|
2024
|
-
* @param {string} artName Artifact Name
|
|
2025
|
-
*/
|
|
2026
|
-
function flattenStructuredElements(art, artName) {
|
|
2027
|
-
if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
|
|
2028
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
2029
|
-
const elementsArray = [];
|
|
2030
|
-
for (const elemName in elements) {
|
|
2031
|
-
const pathToElement = pathToElements.concat([elemName])
|
|
2032
|
-
const elem = parent.elements[elemName];
|
|
2033
|
-
elementsArray.push([elemName, elem]);
|
|
2034
|
-
if (isStructured(elem)) {
|
|
2035
|
-
// Ignore the structured element, replace it by its flattened form
|
|
2036
|
-
// TODO: use $ignore - _ is for links
|
|
2037
|
-
elem._ignore = true;
|
|
2038
|
-
|
|
2039
|
-
const branches = getBranches(elem, elemName);
|
|
2040
|
-
const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
|
|
2041
|
-
|
|
2042
|
-
for (const flatElemName in flatElems) {
|
|
2043
|
-
if (parent.elements[flatElemName])
|
|
2044
|
-
error(null, pathToElement, `"${ artName }.${ elemName }": Flattened struct element name conflicts with existing element: "${ flatElemName }"`);
|
|
2045
|
-
|
|
2046
|
-
const flatElement = flatElems[flatElemName];
|
|
2047
|
-
|
|
2048
|
-
// Check if we have a valid notNull chain
|
|
2049
|
-
const branch = branches[flatElemName];
|
|
2050
|
-
if(flatElement.notNull !== false && !branch.some(s => !s.notNull)){
|
|
2051
|
-
flatElement.notNull = true;
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2054
|
-
if(flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on){
|
|
2055
|
-
// Make refs resolvable by fixing the first ref step
|
|
2056
|
-
for (let i = 0; i < flatElement.on.length; i++) {
|
|
2057
|
-
const onPart = flatElement.on[i];
|
|
2058
|
-
if (onPart.ref) {
|
|
2059
|
-
const firstRef = flatElement.on[i].ref[0];
|
|
2060
|
-
|
|
2061
|
-
/*
|
|
2062
|
-
when element is defined in the current name resolution scope, like
|
|
2063
|
-
entity E {
|
|
2064
|
-
key x: Integer;
|
|
2065
|
-
s : {
|
|
2066
|
-
y : Integer;
|
|
2067
|
-
a3 : association to E on a3.x = y;
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
2071
|
-
*/
|
|
2072
|
-
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,-1).join(pathDelimiter);
|
|
2073
|
-
const possibleFlatName = prefix + pathDelimiter + firstRef;
|
|
2074
|
-
|
|
2075
|
-
if (flatElems[possibleFlatName])
|
|
2076
|
-
flatElement.on[i].ref[0] = possibleFlatName;
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
}
|
|
2080
|
-
elementsArray.push([flatElemName, flatElement]);
|
|
2081
|
-
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
2082
|
-
parent.elements[flatElemName] = flatElement;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
// Don't fake consistency of the model by adding empty elements {}
|
|
2087
|
-
if(elementsArray.length === 0)
|
|
2088
|
-
return;
|
|
2089
|
-
|
|
2090
|
-
parent.elements = elementsArray.reduce((previous, [name, element]) => {
|
|
2091
|
-
previous[name] = element;
|
|
2092
|
-
return previous;
|
|
2093
|
-
}, Object.create(null));
|
|
2094
|
-
});
|
|
2095
|
-
}
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
1434
|
/**
|
|
2099
1435
|
* Flatten and create the foreign key elements of managed associaitons
|
|
2100
1436
|
*
|
|
@@ -2102,7 +1438,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2102
1438
|
* @param {string} artName
|
|
2103
1439
|
*/
|
|
2104
1440
|
function handleManagedAssociationFKs(art, artName) {
|
|
2105
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
1441
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
2106
1442
|
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
2107
1443
|
if(artName.startsWith('localized.') && pathToElements.length > 3) {
|
|
2108
1444
|
// In subqueries, the elements of localized views are missing all the important bits and pieces...
|
|
@@ -2246,7 +1582,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2246
1582
|
function flattenIndexes(art, artName) {
|
|
2247
1583
|
// Flatten structs in indexes (unless explicitly asked to keep structs)
|
|
2248
1584
|
const tc = art.technicalConfig;
|
|
2249
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
1585
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
2250
1586
|
if (tc && tc[dialect]) {
|
|
2251
1587
|
// Secondary and fulltext indexes
|
|
2252
1588
|
for (const name in tc[dialect].indexes) {
|
|
@@ -2313,7 +1649,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2313
1649
|
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
|
|
2314
1650
|
for (const elemName in artifact.elements) {
|
|
2315
1651
|
const elem = artifact.elements[elemName];
|
|
2316
|
-
if (
|
|
1652
|
+
if (doA2J) {
|
|
2317
1653
|
// The association is an unmanaged on
|
|
2318
1654
|
if (!elem.keys && elem.target && elem.on) {
|
|
2319
1655
|
forEachRef(elem.on, (ref, refOwner, path) => {
|
|
@@ -2333,11 +1669,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2333
1669
|
const source = findSource(links, i - 1) || artifact;
|
|
2334
1670
|
// allow specifying managed assoc on the source side
|
|
2335
1671
|
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
|
|
2336
|
-
|
|
2337
1672
|
if(fks && fks.length >= 1){
|
|
2338
1673
|
const fk = fks[0];
|
|
2339
|
-
|
|
2340
|
-
|
|
1674
|
+
const managedAssocStepName = refOwner.ref[i];
|
|
1675
|
+
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
|
|
1676
|
+
if(source && source.elements[fkName])
|
|
1677
|
+
refOwner.ref = [ ...ref.slice(0, i), fkName ];
|
|
2341
1678
|
}
|
|
2342
1679
|
}
|
|
2343
1680
|
}
|
|
@@ -2376,12 +1713,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2376
1713
|
* @param {string} artifactName
|
|
2377
1714
|
* @param {Object} elem The association to process
|
|
2378
1715
|
* @param {string} elemName
|
|
1716
|
+
* @param {WeakMap} alreadyHandled To cache which elements were already processed
|
|
2379
1717
|
* @returns {void}
|
|
2380
1718
|
*/
|
|
2381
|
-
function transformManagedAssociation(artifact, artifactName, elem, elemName) {
|
|
1719
|
+
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
|
|
2382
1720
|
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition
|
|
2383
1721
|
// of another association - see a few lines lower
|
|
2384
|
-
if (elem
|
|
1722
|
+
if (alreadyHandled.has(elem))
|
|
2385
1723
|
return;
|
|
2386
1724
|
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
|
|
2387
1725
|
const onCondParts = [];
|
|
@@ -2398,10 +1736,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2398
1736
|
elemName,
|
|
2399
1737
|
].concat(foreignKey.ref),
|
|
2400
1738
|
};
|
|
2401
|
-
|
|
1739
|
+
const fkName = `${ elemName }${ pathDelimiter }${ foreignKey.as }`;
|
|
2402
1740
|
const fKeyArg = {
|
|
2403
1741
|
ref: [
|
|
2404
|
-
|
|
1742
|
+
fkName,
|
|
2405
1743
|
],
|
|
2406
1744
|
};
|
|
2407
1745
|
|
|
@@ -2440,7 +1778,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2440
1778
|
elem.implicitForeignKeys = true;
|
|
2441
1779
|
*/
|
|
2442
1780
|
// Remember that we already processed this
|
|
2443
|
-
|
|
1781
|
+
alreadyHandled.set(elem, true);
|
|
2444
1782
|
}
|
|
2445
1783
|
}
|
|
2446
1784
|
|