@sap/cds-compiler 2.4.4 → 2.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +241 -1
- package/bin/.eslintrc.json +17 -0
- package/bin/cds_update_identifiers.js +8 -7
- package/bin/cdsc.js +180 -132
- package/bin/cdshi.js +18 -11
- package/bin/cdsse.js +38 -32
- package/bin/cdsv2m.js +8 -7
- package/doc/CHANGELOG_BETA.md +36 -1
- package/lib/api/main.js +81 -100
- package/lib/api/options.js +17 -11
- package/lib/api/validate.js +12 -8
- package/lib/backends.js +0 -81
- package/lib/base/keywords.js +32 -2
- package/lib/base/location.js +2 -2
- package/lib/base/message-registry.js +66 -4
- package/lib/base/messages.js +84 -27
- package/lib/base/model.js +2 -61
- package/lib/checks/arrayOfs.js +0 -1
- package/lib/checks/defaultValues.js +27 -2
- package/lib/checks/elements.js +1 -6
- package/lib/checks/enricher.js +8 -2
- 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 +27 -9
- package/lib/checks/selectItems.js +25 -2
- package/lib/checks/types.js +26 -2
- package/lib/checks/unknownMagic.js +38 -0
- package/lib/checks/utils.js +61 -0
- package/lib/checks/validator.js +66 -13
- package/lib/compiler/assert-consistency.js +24 -12
- package/lib/compiler/builtins.js +2 -0
- package/lib/compiler/checks.js +6 -4
- package/lib/compiler/definer.js +101 -39
- package/lib/compiler/index.js +88 -59
- package/lib/compiler/resolver.js +455 -209
- package/lib/compiler/shared.js +57 -33
- package/lib/edm/annotations/genericTranslation.js +183 -187
- package/lib/edm/csn2edm.js +128 -99
- package/lib/edm/edm.js +18 -21
- package/lib/edm/edmPreprocessor.js +361 -127
- package/lib/edm/edmUtils.js +103 -33
- package/lib/gen/Dictionary.json +74 -28
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +18 -4
- package/lib/gen/language.tokens +124 -118
- package/lib/gen/languageLexer.interp +13 -1
- package/lib/gen/languageLexer.js +870 -839
- package/lib/gen/languageLexer.tokens +116 -111
- package/lib/gen/languageParser.js +5894 -5614
- package/lib/json/from-csn.js +152 -67
- package/lib/json/to-csn.js +334 -135
- package/lib/language/antlrParser.js +4 -3
- package/lib/language/errorStrategy.js +1 -0
- package/lib/language/genericAntlrParser.js +24 -14
- package/lib/language/language.g4 +188 -128
- package/lib/main.d.ts +435 -0
- package/lib/main.js +31 -7
- package/lib/model/api.js +78 -0
- package/lib/model/csnRefs.js +463 -187
- package/lib/model/csnUtils.js +280 -136
- package/lib/model/enrichCsn.js +75 -4
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/modelCompare/compare.js +70 -25
- package/lib/optionProcessor.js +13 -10
- package/lib/render/.eslintrc.json +4 -1
- package/lib/render/DuplicateChecker.js +8 -5
- package/lib/render/toCdl.js +123 -40
- package/lib/render/toHdbcds.js +156 -65
- package/lib/render/toSql.js +87 -11
- package/lib/render/utils/common.js +55 -9
- package/lib/render/utils/sql.js +3 -3
- package/lib/sql-identifier.js +6 -1
- package/lib/transform/{sql → db}/.eslintrc.json +0 -0
- package/lib/transform/{sql → db}/assertUnique.js +7 -8
- package/lib/transform/{sql → db}/constraints.js +35 -20
- package/lib/transform/db/draft.js +353 -0
- package/lib/transform/db/expansion.js +582 -0
- package/lib/transform/db/flattening.js +325 -0
- package/lib/transform/{sql → db}/groupByOrderBy.js +8 -16
- package/lib/transform/{sql → db}/helpers.js +0 -0
- package/lib/transform/{sql → db}/transformExists.js +256 -60
- package/lib/transform/forHanaNew.js +216 -765
- package/lib/transform/forOdataNew.js +60 -56
- package/lib/transform/localized.js +48 -26
- package/lib/transform/odata/attachPath.js +19 -4
- package/lib/transform/odata/expandStructKeysInAssociations.js +2 -2
- package/lib/transform/odata/generateForeignKeyElements.js +13 -12
- package/lib/transform/odata/referenceFlattener.js +60 -36
- package/lib/transform/odata/sortByAssociationDependency.js +4 -4
- package/lib/transform/odata/structuralPath.js +76 -0
- package/lib/transform/odata/structureFlattener.js +21 -22
- package/lib/transform/odata/toFinalBaseType.js +5 -5
- package/lib/transform/odata/typesExposure.js +27 -17
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +141 -77
- package/lib/transform/translateAssocsToJoins.js +17 -14
- package/lib/transform/universalCsnEnricher.js +67 -0
- package/lib/utils/file.js +0 -11
- package/lib/utils/moduleResolve.js +6 -8
- package/lib/utils/timetrace.js +6 -1
- package/package.json +2 -1
- package/lib/base/deepCopy.js +0 -66
- package/lib/json/walker.js +0 -26
- package/lib/utils/string.js +0 -17
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { setProp, isBetaEnabled
|
|
3
|
+
const { setProp, isBetaEnabled } = require('../base/model');
|
|
4
4
|
const { getUtils, cloneCsn, forEachGeneric,
|
|
5
5
|
forEachMember,
|
|
6
6
|
forEachMemberRecursively, forEachRef,
|
|
7
|
-
forAllQueries, forAllElements,
|
|
7
|
+
forAllQueries, forAllElements, hasAnnotationValue, getArtifactDatabaseNameOf,
|
|
8
8
|
getElementDatabaseNameOf, isBuiltinType, applyTransformations,
|
|
9
|
-
isPersistedOnDatabase, getNormalizedQuery, isAspect,
|
|
10
|
-
getServiceNames
|
|
9
|
+
isPersistedOnDatabase, getNormalizedQuery, isAspect, walkCsnPath,
|
|
11
10
|
} = require('../model/csnUtils');
|
|
12
11
|
const { makeMessageFunction } = require('../base/messages');
|
|
13
12
|
const transformUtils = require('./transformUtilsNew');
|
|
@@ -17,13 +16,17 @@ const { checkCSNVersion } = require('../json/csnVersion');
|
|
|
17
16
|
const validate = require('../checks/validator');
|
|
18
17
|
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');
|
|
19
18
|
const timetrace = require('../utils/timetrace');
|
|
20
|
-
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./
|
|
19
|
+
const { createReferentialConstraints, assertConstraintIdentifierUniqueness } = require('./db/constraints');
|
|
21
20
|
const { createDict } = require('../utils/objectUtils');
|
|
22
|
-
const handleExists = require('./
|
|
23
|
-
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./
|
|
24
|
-
const replaceAssociationsInGroupByOrderBy = require('./
|
|
21
|
+
const handleExists = require('./db/transformExists');
|
|
22
|
+
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./db/helpers');
|
|
23
|
+
const replaceAssociationsInGroupByOrderBy = require('./db/groupByOrderBy');
|
|
25
24
|
const _forEachDefinition = require('../model/csnUtils').forEachDefinition;
|
|
26
|
-
const
|
|
25
|
+
const flattening = require('./db/flattening');
|
|
26
|
+
const expansion = require('./db/expansion');
|
|
27
|
+
const assertUnique = require('./db/assertUnique');
|
|
28
|
+
const generateDrafts = require('./db/draft');
|
|
29
|
+
const enrichUniversalCsn = require('./universalCsnEnricher');
|
|
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,15 +100,13 @@ 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 draftSuffix = isDeprecatedEnabled(options, 'generatedEntityNameWithUnderscore') ? '_drafts' : '.drafts';
|
|
109
103
|
const columnClearer = [];
|
|
110
104
|
// copy the model as we don't want to change the input model
|
|
111
105
|
timetrace.start('HANA transformation');
|
|
112
106
|
/** @type {CSN.Model} */
|
|
113
107
|
let csn = cloneCsn(inputModel, options);
|
|
114
108
|
|
|
115
|
-
|
|
116
|
-
const allServices = getServiceNames(inputModel);
|
|
109
|
+
|
|
117
110
|
|
|
118
111
|
checkCSNVersion(csn, options);
|
|
119
112
|
|
|
@@ -123,14 +116,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
123
116
|
/** @type {() => void} */
|
|
124
117
|
let throwWithError;
|
|
125
118
|
let artifactRef, inspectRef, queryOrMain, effectiveType, // csnRefs
|
|
126
|
-
addDefaultTypeFacets,
|
|
119
|
+
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
|
|
120
|
+
get$combined; // csnUtils
|
|
127
121
|
|
|
128
122
|
bindCsnReference();
|
|
129
123
|
|
|
130
124
|
throwWithError(); // reclassify and throw in case of non-configurable errors
|
|
125
|
+
|
|
126
|
+
if (options.csnFlavor === 'universal' && isBetaEnabled(options, 'enableUniversalCsn')) {
|
|
127
|
+
enrichUniversalCsn(csn, options);
|
|
128
|
+
bindCsnReference();
|
|
129
|
+
}
|
|
131
130
|
|
|
132
131
|
const dialect = options.forHana && options.forHana.dialect || options.toSql && options.toSql.dialect;
|
|
133
|
-
const doA2J = !options.
|
|
132
|
+
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
|
|
134
133
|
if (!doA2J)
|
|
135
134
|
forEachDefinition(csn, handleMixinOnConditions);
|
|
136
135
|
|
|
@@ -139,34 +138,59 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
139
138
|
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
|
|
140
139
|
});
|
|
141
140
|
|
|
142
|
-
// Check if structured elements and managed associations are compared in an
|
|
141
|
+
// Check if structured elements and managed associations are compared in an expression
|
|
143
142
|
// and expand these structured elements. This tuple expansion allows all other
|
|
144
|
-
// subsequent procession steps (especially a2j) to see plain paths in
|
|
143
|
+
// subsequent procession steps (especially a2j) to see plain paths in expressions.
|
|
145
144
|
// If errors are detected, throwWithError() will return from further processing
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
expandStructsInExpression(csn, { drillRef: true });
|
|
148
147
|
|
|
149
148
|
throwWithError();
|
|
150
149
|
|
|
151
150
|
// FIXME: This does something very similar to cloneWithTransformations -> refactor?
|
|
152
151
|
const transformCsn = transformUtils.transformModel;
|
|
153
152
|
|
|
154
|
-
handleExists(csn, error);
|
|
153
|
+
handleExists(csn, options, error);
|
|
155
154
|
|
|
156
155
|
// (001) Add a temporal where condition to views where applicable before assoc2join
|
|
157
156
|
// assoc2join eventually rewrites the table aliases
|
|
158
157
|
forEachDefinition(csn, addTemporalWhereConditionToView);
|
|
159
158
|
|
|
160
|
-
|
|
159
|
+
// check unique constraints - further processing is done in rewriteUniqueConstraints
|
|
161
160
|
assertUnique.prepare(csn, options, error, info);
|
|
162
161
|
|
|
162
|
+
if(doA2J) {
|
|
163
|
+
// Expand a structured thing in: keys, columns, order by, group by
|
|
164
|
+
expansion.expandStructureReferences(csn, options, pathDelimiter, {error, info, throwWithError});
|
|
165
|
+
bindCsnReference();
|
|
166
|
+
}
|
|
167
|
+
|
|
163
168
|
// Remove properties attached by validator - they do not "grow" as the model grows.
|
|
164
169
|
cleanup();
|
|
165
170
|
|
|
171
|
+
bindCsnReferenceOnly();
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
if(doA2J) {
|
|
175
|
+
const resolved = new WeakMap();
|
|
176
|
+
// No refs with struct-steps exist anymore
|
|
177
|
+
flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
|
|
178
|
+
// No type references exist anymore
|
|
179
|
+
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
|
|
180
|
+
flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
|
|
181
|
+
// No structured elements exists anymore
|
|
182
|
+
flattening.flattenElements(csn, options, pathDelimiter, error);
|
|
183
|
+
} else {
|
|
184
|
+
// For to.hdbcds with naming mode hdbcds we also need to resolve the types
|
|
185
|
+
flattening.resolveTypeReferences(csn, options, undefined, pathDelimiter);
|
|
186
|
+
}
|
|
187
|
+
|
|
166
188
|
// (010) If requested, translate associations to joins
|
|
167
189
|
if (doA2J)
|
|
168
190
|
handleAssocToJoins();
|
|
169
191
|
|
|
192
|
+
bindCsnReference();
|
|
193
|
+
|
|
170
194
|
const redoProjections = [];
|
|
171
195
|
// Use the "raw" forEachDefinition here to ensure that the _ignore takes effect
|
|
172
196
|
_forEachDefinition(csn, (artifact) => {
|
|
@@ -188,12 +212,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
188
212
|
}
|
|
189
213
|
});
|
|
190
214
|
|
|
215
|
+
// Must happen after A2J, as A2J needs $self to correctly resolve stuff
|
|
216
|
+
if(doA2J)
|
|
217
|
+
flattening.removeLeadingSelf(csn);
|
|
218
|
+
|
|
191
219
|
const {
|
|
192
220
|
flattenStructuredElement,
|
|
193
|
-
flattenStructStepsInRef,
|
|
221
|
+
flattenStructStepsInRef, getForeignKeyArtifact,
|
|
194
222
|
isAssociationOperand, isDollarSelfOrProjectionOperand,
|
|
195
|
-
createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,
|
|
196
|
-
addElement, copyAndAddElement, createAssociationPathComparison,
|
|
197
223
|
extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments,
|
|
198
224
|
recurseElements
|
|
199
225
|
} = transformUtils.getTransformers(csn, options, pathDelimiter);
|
|
@@ -201,7 +227,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
201
227
|
const {
|
|
202
228
|
getCsnDef,
|
|
203
229
|
isAssocOrComposition,
|
|
204
|
-
isComposition,
|
|
205
230
|
isManagedAssociationElement,
|
|
206
231
|
isStructured,
|
|
207
232
|
addStringAnnotationTo,
|
|
@@ -245,13 +270,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
245
270
|
},
|
|
246
271
|
}, true);
|
|
247
272
|
|
|
248
|
-
// (030) - For all elements, replace derived types by final base type
|
|
249
|
-
forEachDefinition(csn, (artifact) => {
|
|
250
|
-
forEachMemberRecursively(artifact, (member) => {
|
|
251
|
-
toFinalBaseType(member);
|
|
252
|
-
});
|
|
253
|
-
});
|
|
254
|
-
|
|
255
273
|
// (040) Ignore entities and views that are abstract or implemented
|
|
256
274
|
// or carry the annotation cds.persistence.skip/exists
|
|
257
275
|
// These entities are not removed from the csn, but flagged as "to be ignored"
|
|
@@ -263,56 +281,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
263
281
|
// Temporal only in beta-mode
|
|
264
282
|
forEachDefinition(csn, handleTemporalAnnotations);
|
|
265
283
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// the following step can rely on the rest of the model having
|
|
269
|
-
// a certain structure, i.e. structs unfolded
|
|
270
|
-
if(!options.forHana.keepStructsAssocs) {
|
|
271
|
-
const flatteningOfStructured = [];
|
|
272
|
-
const adaptRefs = [];
|
|
273
|
-
// A2J resolves paths in queries to their flattened version,
|
|
274
|
-
// that is the foreign key of a managed assoc that will be generated in a later step
|
|
275
|
-
// we need to adapt the refs after the foreign keys are generated
|
|
276
|
-
const adaptQueryRefLater = [];
|
|
277
|
-
|
|
278
|
-
const fkRefs = new WeakMap();
|
|
279
|
-
|
|
280
|
-
applyTransformations(csn, {
|
|
281
|
-
keys: (parent, prop, keys) => {
|
|
282
|
-
keys.forEach(key => fkRefs.set(key.ref, true));
|
|
283
|
-
},
|
|
284
|
-
ref: (parent, prop, ref, path) => {
|
|
285
|
-
// Do not process fk refs - no need to adapt
|
|
286
|
-
if(fkRefs.has(ref))
|
|
287
|
-
return;
|
|
288
|
-
|
|
289
|
-
setProp(parent, '$path', [...path]);
|
|
290
|
-
const lastRef = ref[ref.length-1];
|
|
291
|
-
const fn = () => {
|
|
292
|
-
const scopedPath = [...parent.$path];
|
|
293
|
-
parent.ref = flattenStructStepsInRef(ref, scopedPath);
|
|
294
|
-
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
295
|
-
if (parent.ref[ref.length - 1] != lastRef && insideColumns(scopedPath) && !parent.as)
|
|
296
|
-
parent.as = lastRef;
|
|
297
|
-
};
|
|
298
|
-
// adapt queries later
|
|
299
|
-
const enclosingArtifact = csn.definitions[path[1]];
|
|
300
|
-
if(enclosingArtifact.query)
|
|
301
|
-
adaptQueryRefLater.push(fn);
|
|
302
|
-
else
|
|
303
|
-
adaptRefs.push(fn);
|
|
304
|
-
}
|
|
305
|
-
}, [(definitions, artifactName, artifact) => flatteningOfStructured.push(() => flattenStructuredElements(artifact, artifactName))]);
|
|
306
|
-
|
|
307
|
-
adaptRefs.forEach(fn => fn());
|
|
308
|
-
flatteningOfStructured.forEach(fn => fn());
|
|
309
|
-
handleManagedAssociationsAndCreateForeignKeys();
|
|
310
|
-
// now the foreign key references in queries are resolvable
|
|
311
|
-
adaptQueryRefLater.forEach(fn => fn());
|
|
312
|
-
}else {
|
|
313
|
-
handleManagedAssociationsAndCreateForeignKeys();
|
|
314
|
-
}
|
|
315
|
-
|
|
284
|
+
handleManagedAssociationsAndCreateForeignKeys();
|
|
285
|
+
|
|
316
286
|
function handleManagedAssociationsAndCreateForeignKeys() {
|
|
317
287
|
forEachDefinition(csn, (art, artName) => handleManagedAssociationFKs(art, artName));
|
|
318
288
|
forEachDefinition(csn, (art, artName) => createForeignKeyElements(art, artName));
|
|
@@ -368,18 +338,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
368
338
|
* hence we do not generate the referential constraints for them.
|
|
369
339
|
*/
|
|
370
340
|
const validOptionsForConstraint = () => {
|
|
371
|
-
return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') &&
|
|
372
|
-
!(options.toHana && options.toHana.names === 'hdbcds')
|
|
341
|
+
return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
|
|
373
342
|
}
|
|
374
343
|
if(validOptionsForConstraint())
|
|
375
344
|
createReferentialConstraints(csn, options);
|
|
376
345
|
}
|
|
377
346
|
|
|
378
|
-
|
|
379
|
-
// Note that this needs to happen after implicit redirection has been performed, because it checks
|
|
380
|
-
// for all draft nodes (additional artifacts reachable via compositions) to be part of a service.
|
|
381
|
-
// This is typically achieved only by means of implicit redirection.
|
|
382
|
-
forEachDefinition(csn, generateDraft);
|
|
347
|
+
generateDrafts(csn, options, pathDelimiter, { info, warning, error });
|
|
383
348
|
|
|
384
349
|
// Set the final constraint paths and produce hana tc indexes if required
|
|
385
350
|
// See function comment for extensive information.
|
|
@@ -396,12 +361,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
396
361
|
// Recursively apply transformCommon and attach @cds.persistence.name
|
|
397
362
|
forEachDefinition(csn, recursivelyApplyCommon);
|
|
398
363
|
|
|
399
|
-
// (20 a) If we keep associations as they are (hdbcds naming convention), we cannot have structured
|
|
400
|
-
// view elements (we could enumerate the elements but we can't give them the names one would expect)
|
|
401
|
-
// Check foreign keys of redirected associations
|
|
402
|
-
// (200) Strip 'key' property from type elements
|
|
403
|
-
forEachDefinition(csn, recursiveChecks);
|
|
404
|
-
|
|
405
364
|
const checkConstraintIdentifiers = (artifact, artifactName, prop, path) => {
|
|
406
365
|
assertConstraintIdentifierUniqueness(artifact, artifactName, path, error);
|
|
407
366
|
};
|
|
@@ -430,7 +389,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
430
389
|
/* Check Type Parameters (precision, scale, length ...) */
|
|
431
390
|
checkTypeParameters,
|
|
432
391
|
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
|
|
433
|
-
ignoreNonPersistedArtifactsWithAnonymousAspectComposition
|
|
392
|
+
ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
|
|
393
|
+
// (200) Strip 'key' property from type elements
|
|
394
|
+
removeKeyPropInType,
|
|
434
395
|
]);
|
|
435
396
|
|
|
436
397
|
throwWithError();
|
|
@@ -445,7 +406,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
445
406
|
'_ignore': function (parent, a, b, path){
|
|
446
407
|
if(path.length > 2) {
|
|
447
408
|
const tail = path[path.length-1];
|
|
448
|
-
const
|
|
409
|
+
const parentPath = path.slice(0, -1)
|
|
410
|
+
const parentParent = walkCsnPath(csn, parentPath);
|
|
449
411
|
delete parentParent[tail];
|
|
450
412
|
} else {
|
|
451
413
|
delete parent._ignore;
|
|
@@ -484,7 +446,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
484
446
|
* @param {string} artName
|
|
485
447
|
*/
|
|
486
448
|
function createForeignKeyElements(art, artName) {
|
|
487
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
449
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
488
450
|
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
489
451
|
const elementsArray = [];
|
|
490
452
|
forEachGeneric(parent, 'elements', (element, elemName) => {
|
|
@@ -535,8 +497,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
535
497
|
function bindCsnReference(){
|
|
536
498
|
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
|
|
537
499
|
({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
|
|
538
|
-
({ getFinalBaseType } = getUtils(csn));
|
|
539
|
-
({ addDefaultTypeFacets,
|
|
500
|
+
({ getFinalBaseType, get$combined } = getUtils(csn));
|
|
501
|
+
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function bindCsnReferenceOnly(){
|
|
505
|
+
// invalidate caches for CSN ref API
|
|
506
|
+
({ artifactRef, inspectRef, queryOrMain, effectiveType } = csnRefs(csn));
|
|
540
507
|
}
|
|
541
508
|
|
|
542
509
|
function handleMixinOnConditions(artifact, artifactName) {
|
|
@@ -561,25 +528,36 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
561
528
|
}
|
|
562
529
|
, [ 'definitions', artifactName, 'query' ]);
|
|
563
530
|
function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){
|
|
564
|
-
|
|
531
|
+
const { inspectRef } = csnRefs(csn);
|
|
532
|
+
const referencedThroughStar = query.SELECT.columns.some((column) => column === '*');
|
|
565
533
|
return mixinAssociation.on
|
|
566
534
|
.map((onConditionPart, i) => {
|
|
567
535
|
let columnToReplace;
|
|
568
536
|
if(onConditionPart.ref && (onConditionPart.ref[0] === '$projection' || onConditionPart.ref[0] === '$self')){
|
|
569
537
|
const { links } = inspectRef(path.concat(['on', i]));
|
|
570
538
|
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
539
|
columnToReplace = onConditionPart.ref[links.length - 1];
|
|
578
540
|
}
|
|
579
541
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
542
|
+
if (!columnToReplace)
|
|
543
|
+
return onConditionPart;
|
|
544
|
+
|
|
545
|
+
const replaceWith = query.SELECT.columns.find((column) =>
|
|
546
|
+
column.as && column.as === columnToReplace ||
|
|
547
|
+
column.ref && column.ref[0] === columnToReplace
|
|
548
|
+
);
|
|
549
|
+
if (!replaceWith && referencedThroughStar) {
|
|
550
|
+
// not explicitly in column list, check query sources
|
|
551
|
+
// get$combined also includes elements which are part of "excluding {}"
|
|
552
|
+
// this shouldn't be an issue here, as such references get rejected
|
|
553
|
+
const elementsOfQuerySources = get$combined(query);
|
|
554
|
+
Object.entries(elementsOfQuerySources).forEach(([id, element]) => {
|
|
555
|
+
// if the ref points to an element which is not explicitly exposed in the column list,
|
|
556
|
+
// but through the '*' operator -> replace the $projection / $self with the correct source entity
|
|
557
|
+
if(id === columnToReplace)
|
|
558
|
+
onConditionPart.ref[0] = element[0].parent;
|
|
559
|
+
});
|
|
560
|
+
}
|
|
583
561
|
|
|
584
562
|
// No implicit CAST in on-condition
|
|
585
563
|
if(replaceWith && replaceWith.cast) {
|
|
@@ -632,16 +610,9 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
632
610
|
* @param {CSN.Artifact} artifact
|
|
633
611
|
* @param {string} artifactName
|
|
634
612
|
*/
|
|
635
|
-
function
|
|
613
|
+
function removeKeyPropInType(artifact, artifactName) {
|
|
636
614
|
if (!artifact._ignore) {
|
|
637
|
-
forEachMemberRecursively(artifact, (member
|
|
638
|
-
if (options.forHana.keepStructsAssocs &&
|
|
639
|
-
artifact.query &&
|
|
640
|
-
isStructured(member)) {
|
|
641
|
-
error(null, path, `With "hdbcds" naming, structured elements can't be used in a view`);
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
|
|
615
|
+
forEachMemberRecursively(artifact, (member) => {
|
|
645
616
|
if (artifact.kind === 'type' && member.key)
|
|
646
617
|
delete member.key;
|
|
647
618
|
}, [ 'definitions', artifactName ]);
|
|
@@ -663,36 +634,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
663
634
|
}, [ 'definitions', artifactName ]);
|
|
664
635
|
}
|
|
665
636
|
|
|
666
|
-
/**
|
|
667
|
-
* @param {CSN.Artifact} artifact
|
|
668
|
-
* @param {string} artifactName
|
|
669
|
-
*/
|
|
670
|
-
function generateDraft(artifact, artifactName) {
|
|
671
|
-
if ((artifact.kind === 'entity' || artifact.kind === 'view') && hasBoolAnnotation(artifact, '@odata.draft.enabled')) {
|
|
672
|
-
// Ignore if not part of a service
|
|
673
|
-
if (!isPartOfService(artifactName)) {
|
|
674
|
-
warning(null, [ 'definitions', artifactName ], 'Ignoring annotation “@odata.draft.enabled” as the artifact is not part of a service');
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// Determine the set of target draft nodes belonging to this draft root (the draft root
|
|
679
|
-
// itself plus all its transitively composition-reachable targets)
|
|
680
|
-
const draftNodes = Object.create(null);
|
|
681
|
-
collectDraftNodesInto(artifact, artifactName, artifact, draftNodes);
|
|
682
|
-
// Draft-enable all of them
|
|
683
|
-
for (const name in draftNodes)
|
|
684
|
-
generateDraftForHana(draftNodes[name], name, artifactName);
|
|
685
|
-
|
|
686
|
-
// Redirect associations/compositions between draft shadow nodes
|
|
687
|
-
for (const name in draftNodes) {
|
|
688
|
-
const shadowNode = csn.definitions[`${ name }${draftSuffix}`];
|
|
689
|
-
// Might not exist because of previous errors
|
|
690
|
-
if (shadowNode)
|
|
691
|
-
redirectDraftTargets(csn.definitions[`${ name }${draftSuffix}`], draftNodes);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
637
|
/**
|
|
697
638
|
* @param {CSN.Artifact} artifact
|
|
698
639
|
* @param {string} artifactName
|
|
@@ -734,7 +675,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
734
675
|
if (artifact.params) {
|
|
735
676
|
// HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:
|
|
736
677
|
// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
|
|
737
|
-
error(null, path,
|
|
678
|
+
error(null, path, 'Unexpected association in parameterized view');
|
|
738
679
|
}
|
|
739
680
|
else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
740
681
|
// UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
|
|
@@ -743,7 +684,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
743
684
|
if (csn.definitions[member.target].params) {
|
|
744
685
|
// HANA does not allow association targets with parameters or to UDFs/CVs w/o parameters:
|
|
745
686
|
// SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
|
|
746
|
-
error(null, path,
|
|
687
|
+
error(null, path, 'Unexpected parameterized association target');
|
|
747
688
|
}
|
|
748
689
|
else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
|
|
749
690
|
// HANA won't check the assoc target but when querying an association with target UDF, this is the error:
|
|
@@ -774,7 +715,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
774
715
|
const elem = elements[elemName];
|
|
775
716
|
// (140) Generate foreign key elements and ON-condition for managed associations
|
|
776
717
|
// (unless explicitly asked to keep assocs unchanged)
|
|
777
|
-
if (
|
|
718
|
+
if (doA2J) {
|
|
778
719
|
if (isManagedAssociationElement(elem))
|
|
779
720
|
transformManagedAssociation(parent, artifactName, elem, elemName);
|
|
780
721
|
}
|
|
@@ -785,7 +726,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
785
726
|
|
|
786
727
|
function fixBorkedElementsOfLocalized(elements, pathToElements){
|
|
787
728
|
const pathToNonLocalized = ['definitions', pathToElements[1].replace('localized.',''), ...pathToElements.slice(2)];
|
|
788
|
-
const nonLocalizedElements = walkCsnPath(pathToNonLocalized);
|
|
729
|
+
const nonLocalizedElements = walkCsnPath(csn, pathToNonLocalized);
|
|
789
730
|
|
|
790
731
|
|
|
791
732
|
for(const elementName in elements){
|
|
@@ -832,7 +773,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
832
773
|
else {
|
|
833
774
|
for (const pname in artifact.params) {
|
|
834
775
|
if (pname.match(/\W/g) || pname.match(/^\d/) || pname.match(/^_/)) { // parameter name must be regular SQL identifier
|
|
835
|
-
warning(null, [ 'definitions', artifactName, 'params', pname ], `
|
|
776
|
+
warning(null, [ 'definitions', artifactName, 'params', pname ], `Expecting regular SQL-Identifier`);
|
|
836
777
|
}
|
|
837
778
|
else if (options.forHana.names !== 'plain' && pname.toUpperCase() !== pname) { // not plain mode: param name must be all upper
|
|
838
779
|
warning(null, [ 'definitions', artifactName, 'params', pname ], { name: options.forHana.names },
|
|
@@ -848,7 +789,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
848
789
|
* @param {string} artifactName
|
|
849
790
|
*/
|
|
850
791
|
function handleQueryish(artifact, artifactName) {
|
|
851
|
-
const stripQueryish = artifact.query &&
|
|
792
|
+
const stripQueryish = artifact.query && hasAnnotationValue(artifact, '@cds.persistence.table');
|
|
852
793
|
|
|
853
794
|
if (stripQueryish) {
|
|
854
795
|
artifact.kind = 'entity';
|
|
@@ -869,13 +810,13 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
869
810
|
function handleCdsPersistence(artifact, artifactName) {
|
|
870
811
|
if (artifact.kind === 'entity' || artifact.kind === 'view') {
|
|
871
812
|
if (artifact.abstract
|
|
872
|
-
||
|
|
873
|
-
||
|
|
813
|
+
|| hasAnnotationValue(artifact, '@cds.persistence.skip')
|
|
814
|
+
|| hasAnnotationValue(artifact, '@cds.persistence.exists'))
|
|
874
815
|
artifact._ignore = true;
|
|
875
816
|
|
|
876
817
|
// issue #3450 HANA CDS can not handle external artifacts which are part of a HANA CDS context
|
|
877
818
|
if (options.forHana.names === 'quoted' &&
|
|
878
|
-
|
|
819
|
+
hasAnnotationValue(artifact, '@cds.persistence.exists')) {
|
|
879
820
|
const firstPath = artifactName.split('.')[0];
|
|
880
821
|
const topParent = csn.definitions[firstPath];
|
|
881
822
|
// namespaces, contexts and services become contexts in HANA CDS
|
|
@@ -886,9 +827,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
886
827
|
}
|
|
887
828
|
|
|
888
829
|
function handleAssocToJoins() {
|
|
830
|
+
// With flattening errors, it makes little sense to continue.
|
|
831
|
+
throwWithError();
|
|
889
832
|
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we
|
|
890
833
|
// simply make it invisible and copy it over to the result csn
|
|
891
834
|
forEachDefinition(csn, art => art.technicalConfig && setProp(art, 'technicalConfig', art.technicalConfig));
|
|
835
|
+
|
|
892
836
|
const newCsn = translateAssocsToJoinsCSN(csn, options);
|
|
893
837
|
|
|
894
838
|
// restore all (non-enumerable) properties that wouldn't survive reaugmentation/compactification into the new compact model
|
|
@@ -910,7 +854,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
910
854
|
const mixinElement = q.SELECT.mixin[mixinName];
|
|
911
855
|
if (mixinElement._ignore && options.toSql) {
|
|
912
856
|
columnClearer.push(() => {
|
|
913
|
-
const query = walkCsnPath(p);
|
|
857
|
+
const query = walkCsnPath(csn, p);
|
|
914
858
|
for(let i = query.columns.length-1; i > -1; i--){
|
|
915
859
|
const col = query.columns[i];
|
|
916
860
|
if(col && col.ref && col.ref[0] === mixinName){
|
|
@@ -925,7 +869,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
925
869
|
}
|
|
926
870
|
});
|
|
927
871
|
csn = newCsn;
|
|
928
|
-
bindCsnReference();
|
|
929
872
|
}
|
|
930
873
|
|
|
931
874
|
/**
|
|
@@ -984,6 +927,25 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
984
927
|
}
|
|
985
928
|
}
|
|
986
929
|
|
|
930
|
+
function hasFalsyTemporalAnnotations(SELECT, elements, from, to) {
|
|
931
|
+
let fromElement = elements[from.name];
|
|
932
|
+
let toElement = elements[to.name];
|
|
933
|
+
|
|
934
|
+
if(SELECT.columns) {
|
|
935
|
+
for(const col of SELECT.columns) {
|
|
936
|
+
if(col.ref) {
|
|
937
|
+
const implicitAlias = implicitAs(col.ref);
|
|
938
|
+
if(implicitAlias === from.name)
|
|
939
|
+
fromElement = elements[col.as || implicitAlias];
|
|
940
|
+
else if(implicitAlias === to.name)
|
|
941
|
+
toElement = elements[col.as || implicitAlias];
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
const val = fromElement && toElement && hasAnnotationValue(fromElement, '@cds.valid.from', false) && hasAnnotationValue(toElement, '@cds.valid.to', false);
|
|
946
|
+
return val;
|
|
947
|
+
}
|
|
948
|
+
|
|
987
949
|
/**
|
|
988
950
|
* Add a where condition to views that
|
|
989
951
|
* - are annotated with @cds.valid.from and @cds.valid.to,
|
|
@@ -1009,31 +971,33 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1009
971
|
if (from.length === 1 && to.length === 1) {
|
|
1010
972
|
// and both are from the same origin
|
|
1011
973
|
if (from[0].source === to[0].source && from[0].parent === to[0].parent) {
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
974
|
+
if(!hasFalsyTemporalAnnotations(normalizedQuery.query.SELECT, artifact.elements, from[0], to[0])) {
|
|
975
|
+
const fromPath = {
|
|
976
|
+
ref: [
|
|
977
|
+
from[0].parent,
|
|
978
|
+
from[0].name,
|
|
979
|
+
],
|
|
980
|
+
};
|
|
1018
981
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
982
|
+
const toPath = {
|
|
983
|
+
ref: [
|
|
984
|
+
to[0].parent,
|
|
985
|
+
to[0].name,
|
|
986
|
+
],
|
|
987
|
+
};
|
|
1025
988
|
|
|
1026
989
|
|
|
1027
|
-
|
|
1028
|
-
|
|
990
|
+
const atFrom = { ref: [ '$at', 'from' ] };
|
|
991
|
+
const atTo = { ref: [ '$at', 'to' ] };
|
|
1029
992
|
|
|
1030
|
-
|
|
993
|
+
const cond = [ '(', fromPath, '<', atTo, 'and', toPath, '>', atFrom, ')' ];
|
|
1031
994
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
995
|
+
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
996
|
+
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
997
|
+
}
|
|
998
|
+
else {
|
|
999
|
+
normalizedQuery.query.SELECT.where = cond;
|
|
1000
|
+
}
|
|
1037
1001
|
}
|
|
1038
1002
|
}
|
|
1039
1003
|
else {
|
|
@@ -1050,127 +1014,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1050
1014
|
}
|
|
1051
1015
|
}
|
|
1052
1016
|
|
|
1053
|
-
|
|
1054
|
-
/**
|
|
1055
|
-
* Compute and return $combined for the given query.
|
|
1056
|
-
*
|
|
1057
|
-
* @param {CSN.Query} query
|
|
1058
|
-
* @returns {object}
|
|
1059
|
-
*/
|
|
1060
|
-
function get$combined(query) {
|
|
1061
|
-
const sources = getSources(query);
|
|
1062
|
-
return sources;
|
|
1063
|
-
|
|
1064
|
-
/**
|
|
1065
|
-
* Get the union of all elements from the from clause
|
|
1066
|
-
* - descend into unions, following the lead query
|
|
1067
|
-
* - merge all queries in case of joins
|
|
1068
|
-
* - follow subqueries
|
|
1069
|
-
*
|
|
1070
|
-
* @param {CSN.Query} query Query to check
|
|
1071
|
-
* @returns {object} Map of sources
|
|
1072
|
-
*/
|
|
1073
|
-
function getSources(query) {
|
|
1074
|
-
// Remark CW: better just a while along query.SET.args[0]
|
|
1075
|
-
if (query.SET) {
|
|
1076
|
-
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
|
|
1077
|
-
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
|
|
1078
|
-
|
|
1079
|
-
return getSources(query.SET.args[0]);
|
|
1080
|
-
}
|
|
1081
|
-
else if (query.SELECT) {
|
|
1082
|
-
if (query.SELECT.from.args) {
|
|
1083
|
-
return walkArgs(query.SELECT.from.args);
|
|
1084
|
-
}
|
|
1085
|
-
else if (query.SELECT.from.ref) {
|
|
1086
|
-
const art = artifactRef(query.SELECT.from);
|
|
1087
|
-
return mergeElementsIntoMap(Object.create(null), art.elements, art.$location,
|
|
1088
|
-
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
|
|
1089
|
-
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
|
|
1090
|
-
}
|
|
1091
|
-
else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
|
|
1092
|
-
return getSources(query.SELECT.from);
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
function walkArgs(args) {
|
|
1097
|
-
let elements = Object.create(null);
|
|
1098
|
-
for (const arg of args) {
|
|
1099
|
-
if (arg.args) {
|
|
1100
|
-
elements = mergeElementMaps(elements, walkArgs(arg.args));
|
|
1101
|
-
}
|
|
1102
|
-
else if (arg.ref) {
|
|
1103
|
-
const art = artifactRef(arg);
|
|
1104
|
-
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
|
|
1105
|
-
}
|
|
1106
|
-
else if (arg.SELECT || arg.SET) {
|
|
1107
|
-
elements = mergeElementMaps(elements, getSources(arg));
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
return elements;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
return {};
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* Merge two maps of elements together
|
|
1118
|
-
*
|
|
1119
|
-
* @param {object} mapA Map a - will be returned
|
|
1120
|
-
* @param {object} mapB Map b - will not be returned
|
|
1121
|
-
* @returns {object} mapA
|
|
1122
|
-
*/
|
|
1123
|
-
function mergeElementMaps(mapA, mapB) {
|
|
1124
|
-
for (const elementName in mapB) {
|
|
1125
|
-
if (!mapA[elementName])
|
|
1126
|
-
mapA[elementName] = [];
|
|
1127
|
-
|
|
1128
|
-
mapB[elementName].forEach(e => mapA[elementName].push(e));
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
return mapA;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
/**
|
|
1135
|
-
* Merge elements into an existing map
|
|
1136
|
-
*
|
|
1137
|
-
* @param {any} existingMap map to merge into - will be returned
|
|
1138
|
-
* @param {object} elements elements to merge into the map
|
|
1139
|
-
* @param {CSN.Location} $location $location of the elements - where they come from
|
|
1140
|
-
* @param {any} [parent] Name of the parent of the elements, alias before ref
|
|
1141
|
-
* @param {any} [error_parent] Parent name to use for error messages, ref before alias
|
|
1142
|
-
* @returns {object} existingMap
|
|
1143
|
-
*/
|
|
1144
|
-
function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
|
|
1145
|
-
for (const elementName in elements) {
|
|
1146
|
-
const element = elements[elementName];
|
|
1147
|
-
if (!existingMap[elementName])
|
|
1148
|
-
existingMap[elementName] = [];
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
existingMap[elementName].push({
|
|
1152
|
-
element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
|
|
1153
|
-
});
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
return existingMap;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
/**
|
|
1161
|
-
* Return the name part of the artifact name - no namespace etc.
|
|
1162
|
-
* @param {string|object} name Absolute name of the artifact
|
|
1163
|
-
*/
|
|
1164
|
-
function getBaseName(name) {
|
|
1165
|
-
if (!name)
|
|
1166
|
-
return name;
|
|
1167
|
-
|
|
1168
|
-
if (name.id)
|
|
1169
|
-
return name.id.substring( name.id.lastIndexOf('.')+1 );
|
|
1170
|
-
|
|
1171
|
-
return name.substring( name.lastIndexOf('.')+1 )
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
1017
|
/**
|
|
1175
1018
|
* Get all elements tagged with @cds.valid.from/to from the union of all entities of the from-clause.
|
|
1176
1019
|
*
|
|
@@ -1185,10 +1028,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1185
1028
|
if (!Array.isArray(elt))
|
|
1186
1029
|
elt = [ elt ];
|
|
1187
1030
|
elt.forEach((e) => {
|
|
1188
|
-
if (
|
|
1031
|
+
if (hasAnnotationValue(e.element, '@cds.valid.from'))
|
|
1189
1032
|
from.push(e);
|
|
1190
1033
|
|
|
1191
|
-
if (
|
|
1034
|
+
if (hasAnnotationValue(e.element, '@cds.valid.to'))
|
|
1192
1035
|
to.push(e);
|
|
1193
1036
|
});
|
|
1194
1037
|
}
|
|
@@ -1219,7 +1062,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1219
1062
|
}
|
|
1220
1063
|
function ignore(member, memberName, prop, path) {
|
|
1221
1064
|
if (dialect === 'hana' && !member._ignore && member.target && isAssocOrComposition(member.type) && isUnreachableAssociationTarget(csn.definitions[member.target])) {
|
|
1222
|
-
const targetAnnotation =
|
|
1065
|
+
const targetAnnotation = hasAnnotationValue(csn.definitions[member.target], '@cds.persistence.exists') ? '@cds.persistence.exists' : '@cds.persistence.skip';
|
|
1223
1066
|
info(null, path,
|
|
1224
1067
|
{ target: member.target, anno: targetAnnotation },
|
|
1225
1068
|
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)'
|
|
@@ -1234,7 +1077,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1234
1077
|
* @returns {boolean}
|
|
1235
1078
|
*/
|
|
1236
1079
|
function isUnreachableAssociationTarget(art) {
|
|
1237
|
-
return !isPersistedOnDatabase(art) ||
|
|
1080
|
+
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, '@cds.persistence.exists');
|
|
1238
1081
|
}
|
|
1239
1082
|
|
|
1240
1083
|
/**
|
|
@@ -1255,16 +1098,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1255
1098
|
replaceEnumSymbolsByValues(obj, path);
|
|
1256
1099
|
}
|
|
1257
1100
|
|
|
1258
|
-
function walkCsnPath(path) {
|
|
1259
|
-
/** @type {object} */
|
|
1260
|
-
let obj = csn;
|
|
1261
|
-
for(let i = 0; i < path.length; i++){
|
|
1262
|
-
obj = obj[path[i]];
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
return obj;
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
1101
|
// Change the names of those builtin types that have different names in HANA.
|
|
1269
1102
|
// (do that directly in the csn where the builtin types are defined, so that
|
|
1270
1103
|
// all users of the types benefit from it). Also add the type parameter 'length'
|
|
@@ -1351,7 +1184,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1351
1184
|
});
|
|
1352
1185
|
}
|
|
1353
1186
|
}
|
|
1354
|
-
if (query && options.
|
|
1187
|
+
if (query && options.transformation === 'hdbcds') {
|
|
1355
1188
|
// check all queries/subqueries for mixin publishing inside of unions -> forbidden in hdbcds
|
|
1356
1189
|
if (query.SELECT && query.SELECT.mixin && path.indexOf('SET') !== -1) {
|
|
1357
1190
|
for (const elementName in elements) {
|
|
@@ -1419,17 +1252,14 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1419
1252
|
|
|
1420
1253
|
// For associations - make sure that the foreign keys have the same "style"
|
|
1421
1254
|
// If A.assoc => A.assoc_id, else if assoc => assoc_id or assoc as Assoc => Assoc_id
|
|
1422
|
-
if (elem.keys &&
|
|
1255
|
+
if (elem.keys && doA2J) {
|
|
1423
1256
|
const assoc_col = columnMap[elemName];
|
|
1424
1257
|
if (assoc_col && assoc_col.ref) {
|
|
1425
1258
|
elem.keys.forEach((key) => {
|
|
1426
1259
|
const ref = cloneCsn(assoc_col.ref, options);
|
|
1427
1260
|
ref[ref.length - 1] = [ ref[ref.length - 1] ].concat(key.as || key.ref).join(pathDelimiter);
|
|
1428
|
-
// Keep the $env of the parent column
|
|
1429
|
-
// so that we can still resolve the ref later on
|
|
1430
1261
|
const result = {
|
|
1431
1262
|
ref,
|
|
1432
|
-
$env: assoc_col.$env,
|
|
1433
1263
|
};
|
|
1434
1264
|
if (assoc_col.as)
|
|
1435
1265
|
result.as = key.$generatedFieldName;
|
|
@@ -1446,7 +1276,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1446
1276
|
}
|
|
1447
1277
|
// Add flattened structured things preserving aliases and refs with/without table alias
|
|
1448
1278
|
// If we add them when we get to them in "elements", we cannot know what table alias was used...
|
|
1449
|
-
if (isStructured(elem) &&
|
|
1279
|
+
if (isStructured(elem) && doA2J) {
|
|
1450
1280
|
const col = columnMap[elemName];
|
|
1451
1281
|
const originalName = col.ref[col.ref.length - 1];
|
|
1452
1282
|
const flatElements = flattenStructuredElement(elem, originalName, [], path);
|
|
@@ -1471,21 +1301,10 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1471
1301
|
if (!elem.on && !elem._ignore)
|
|
1472
1302
|
hasNonAssocElements = true;
|
|
1473
1303
|
|
|
1474
|
-
|
|
1475
|
-
// (230 b) If we keep associations as they are (hdbcds naming convention), we cannot have managed associations
|
|
1476
|
-
// as view elements (their foreign keys cannot be addressed in the view)
|
|
1477
|
-
if (options.forHana.keepStructsAssocs &&
|
|
1478
|
-
query &&
|
|
1479
|
-
isAssocOrComposition(elem.type) &&
|
|
1480
|
-
!elem.on) {
|
|
1481
|
-
error(null, [ 'definitions', artName, 'elements', elemName ], `With "hdbcds" naming, managed association elements can't be used in a view`);
|
|
1482
|
-
continue;
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
1304
|
// (180 b) Create MIXINs for association elements in projections or views (those that are not mixins by themselves)
|
|
1486
1305
|
// CDXCORE-585: Allow mixin associations to be used and published in parallel
|
|
1487
1306
|
if (query !== undefined && elem.target) {
|
|
1488
|
-
if(isUnion(path) && options.
|
|
1307
|
+
if(isUnion(path) && options.transformation === 'hdbcds'){
|
|
1489
1308
|
if(isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J){
|
|
1490
1309
|
if(elem.keys) {
|
|
1491
1310
|
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`)
|
|
@@ -1497,7 +1316,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1497
1316
|
else {
|
|
1498
1317
|
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`)
|
|
1499
1318
|
}
|
|
1500
|
-
} else if(path.length > 4 && options.
|
|
1319
|
+
} else if(path.length > 4 && options.transformation === 'hdbcds'){ // path.length > 4 -> is a subquery
|
|
1501
1320
|
error(null, path, { name: elemName },
|
|
1502
1321
|
'Association $(NAME) can\'t be published in a subquery')
|
|
1503
1322
|
} else {
|
|
@@ -1579,8 +1398,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1579
1398
|
if (query && !hasNonAssocElements) {
|
|
1580
1399
|
// Complain if there are no elements other than unmanaged associations
|
|
1581
1400
|
// Allow with plain
|
|
1582
|
-
error(null, [ 'definitions', artName ],
|
|
1583
|
-
'
|
|
1401
|
+
error(null, [ 'definitions', artName ], { $reviewed: true } ,
|
|
1402
|
+
'Expecting view or projection to have at least one element that is not an unmanaged association');
|
|
1584
1403
|
}
|
|
1585
1404
|
|
|
1586
1405
|
if (isSelect) {
|
|
@@ -1627,13 +1446,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1627
1446
|
if (!Enum) {
|
|
1628
1447
|
// Not an enum at all
|
|
1629
1448
|
// Looks like it is always run?! But message says HANA CDS?!
|
|
1630
|
-
error(null, path,
|
|
1449
|
+
error(null, path, {
|
|
1450
|
+
$reviewed: true,
|
|
1451
|
+
name: `#${elem.default['#']}`
|
|
1452
|
+
},
|
|
1453
|
+
'Expecting enum literal $(NAME) to be used with an enum type');
|
|
1631
1454
|
}
|
|
1632
1455
|
else {
|
|
1633
1456
|
// Try to get the corresponding enum symbol from the element's type
|
|
1634
1457
|
const enumSymbol = Enum[elem.default['#']];
|
|
1635
1458
|
if (!enumSymbol) {
|
|
1636
|
-
error(null, path,
|
|
1459
|
+
error(null, path, {
|
|
1460
|
+
$reviewed: true,
|
|
1461
|
+
name: `#${elem.default['#']}`
|
|
1462
|
+
}, 'Enum literal $(NAME) is undefined in enumeration type');
|
|
1637
1463
|
}
|
|
1638
1464
|
else if (enumSymbol.val !== undefined) { // `val` may be `null`
|
|
1639
1465
|
// Replace default with enum value
|
|
@@ -1703,23 +1529,41 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1703
1529
|
const assoc = inspectRef(path.concat([ i + 2 ])).art;
|
|
1704
1530
|
if (multipleExprs)
|
|
1705
1531
|
result.push('(');
|
|
1532
|
+
const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
|
|
1706
1533
|
result.push(...transformDollarSelfComparison(xprArgs[i + 2],
|
|
1707
1534
|
assoc,
|
|
1708
|
-
|
|
1535
|
+
backlinkName,
|
|
1709
1536
|
elem, elemName, art, artName, path.concat([ i ])
|
|
1710
1537
|
));
|
|
1711
1538
|
if (multipleExprs)
|
|
1712
1539
|
result.push(')');
|
|
1713
1540
|
i += 3;
|
|
1541
|
+
// remember name of backlink, important for foreign key constraints
|
|
1542
|
+
if(elem.$selfOnCondition)
|
|
1543
|
+
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1544
|
+
else {
|
|
1545
|
+
setProp(elem, '$selfOnCondition', {
|
|
1546
|
+
backlinkName
|
|
1547
|
+
}) // important for the foreign key constraints
|
|
1548
|
+
}
|
|
1714
1549
|
}
|
|
1715
1550
|
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
|
|
1716
1551
|
const assoc = inspectRef(path.concat([ i ])).art;
|
|
1717
1552
|
if (multipleExprs)
|
|
1718
1553
|
result.push('(');
|
|
1719
|
-
|
|
1554
|
+
const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
|
|
1555
|
+
result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
|
|
1720
1556
|
if (multipleExprs)
|
|
1721
1557
|
result.push(')');
|
|
1722
1558
|
i += 3;
|
|
1559
|
+
// remember name of backlink, important for foreign key constraints
|
|
1560
|
+
if(elem.$selfOnCondition)
|
|
1561
|
+
elem.$selfOnCondition.backlinkName += `_${ backlinkName }`;
|
|
1562
|
+
else {
|
|
1563
|
+
setProp(elem, '$selfOnCondition', {
|
|
1564
|
+
backlinkName
|
|
1565
|
+
}) // important for the foreign key constraints
|
|
1566
|
+
}
|
|
1723
1567
|
}
|
|
1724
1568
|
// Otherwise take one (!) token unchanged
|
|
1725
1569
|
else {
|
|
@@ -1796,8 +1640,8 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1796
1640
|
assoc.keys.forEach((k) => {
|
|
1797
1641
|
// Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
|
|
1798
1642
|
// With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
|
|
1799
|
-
const keyName = k.as &&
|
|
1800
|
-
const fKeyPath =
|
|
1643
|
+
const keyName = k.as && doA2J ? [k.as] : k.ref;
|
|
1644
|
+
const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
|
|
1801
1645
|
// FIXME: _artifact to the args ???
|
|
1802
1646
|
const a = [
|
|
1803
1647
|
{
|
|
@@ -1852,243 +1696,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
1852
1696
|
}
|
|
1853
1697
|
}
|
|
1854
1698
|
|
|
1855
|
-
// Collect all artifacts that are transitively reachable via compositions from 'artifact' into 'draftNodes'.
|
|
1856
|
-
// 'rootArtifact' is the root artifact where composition traversal started.
|
|
1857
|
-
// Check that no artifact other than the root node has '@odata.draft.enabled'
|
|
1858
|
-
function collectDraftNodesInto(artifact, artifactName, rootArtifact, draftNodes) {
|
|
1859
|
-
// Collect the artifact itself
|
|
1860
|
-
draftNodes[artifactName] = artifact;
|
|
1861
|
-
// Follow all composition targets in elements of 'artifact'
|
|
1862
|
-
for (const elemName in artifact.elements) {
|
|
1863
|
-
const elem = artifact.elements[elemName];
|
|
1864
|
-
if (elem.target && isComposition(elem.type)) {
|
|
1865
|
-
const draftNode = getCsnDef(elem.target);
|
|
1866
|
-
const draftNodeName = elem.target;
|
|
1867
|
-
// Sanity check
|
|
1868
|
-
if (!draftNode)
|
|
1869
|
-
throw new Error(`Expecting target to be resolved: ${ JSON.stringify(elem, null, 2) }`);
|
|
1870
|
-
|
|
1871
|
-
// Ignore composition if not part of a service
|
|
1872
|
-
if (!isPartOfService(draftNodeName)) {
|
|
1873
|
-
|
|
1874
|
-
warning(null, [ 'definitions', artifactName, 'elements', elemName ], { target: draftNodeName },
|
|
1875
|
-
'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
|
|
1876
|
-
continue;
|
|
1877
|
-
}
|
|
1878
|
-
// Barf if a draft node other than the root has @odata.draft.enabled itself
|
|
1879
|
-
if (draftNode != rootArtifact && hasBoolAnnotation(draftNode, '@odata.draft.enabled')) {
|
|
1880
|
-
error(null, [ 'definitions', artifactName, 'elements', elemName ], `Composition in draft-enabled entity can't lead to another entity with “@odata.draft.enabled”`);
|
|
1881
|
-
delete draftNodes[draftNodeName];
|
|
1882
|
-
continue;
|
|
1883
|
-
}
|
|
1884
|
-
// Recurse unless already known
|
|
1885
|
-
if (!hasBoolAnnotation(draftNode, '@odata.draft.enabled', false) && !draftNodes[draftNodeName])
|
|
1886
|
-
collectDraftNodesInto(draftNode, draftNodeName, rootArtifact, draftNodes);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
/**
|
|
1892
|
-
* Generate all that is required in HANA CDS for draft enablement of 'artifact'.
|
|
1893
|
-
*
|
|
1894
|
-
* @param {CSN.Artifact} artifact
|
|
1895
|
-
* @param {string} artifactName
|
|
1896
|
-
* @param {string} draftRootName
|
|
1897
|
-
*/
|
|
1898
|
-
function generateDraftForHana(artifact, artifactName, draftRootName) {
|
|
1899
|
-
// Sanity check
|
|
1900
|
-
if (!isPartOfService(artifactName))
|
|
1901
|
-
throw new Error(`Expecting artifact to be part of a service: ${ JSON.stringify(artifact) }`);
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
// The name of the draft shadow entity we should generate
|
|
1905
|
-
const draftsArtifactName = `${ artifactName }${draftSuffix}`;
|
|
1906
|
-
|
|
1907
|
-
// extract keys for UUID inspection
|
|
1908
|
-
const keys = [];
|
|
1909
|
-
forEachGeneric( artifact, 'elements', (elt) => {
|
|
1910
|
-
if (elt.key && elt.key === true && !elt.virtual)
|
|
1911
|
-
keys.push(elt);
|
|
1912
|
-
});
|
|
1913
|
-
|
|
1914
|
-
// In contrast to EDM, the DB entity may have more than one technical keys but should have idealy exactly one key of type cds.UUID
|
|
1915
|
-
if (keys.length !== 1)
|
|
1916
|
-
warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have exactly one key element');
|
|
1917
|
-
|
|
1918
|
-
const uuidCount = keys.reduce((uuidCount, k) => ((k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? uuidCount+1 : uuidCount), 0);
|
|
1919
|
-
if (uuidCount === 0)
|
|
1920
|
-
warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
const matchingService = getMatchingService(artifactName);
|
|
1924
|
-
// Generate the DraftAdministrativeData projection into the service, unless there is already one
|
|
1925
|
-
const draftAdminDataProjectionName = `${ matchingService }.` + 'DraftAdministrativeData';
|
|
1926
|
-
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
|
|
1927
|
-
if (!draftAdminDataProjection) {
|
|
1928
|
-
draftAdminDataProjection = createAndAddDraftAdminDataProjection(matchingService, true);
|
|
1929
|
-
|
|
1930
|
-
if (!draftAdminDataProjection.projection.columns && draftAdminDataProjection.elements.DraftUUID) {
|
|
1931
|
-
draftAdminDataProjection.projection.columns = Object.keys(draftAdminDataProjection.elements).map(e => e === 'DraftUUID' ? { key: true, ref: ['DraftAdministrativeData', e]} : {ref: ['DraftAdministrativeData', e]});
|
|
1932
|
-
}
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
// Barf if it is not an entity or not what we expect
|
|
1936
|
-
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
|
|
1937
|
-
// See draftAdminDataProjection which is defined in `csn.definitions`.
|
|
1938
|
-
const path = [ 'definitions', draftAdminDataProjectionName ];
|
|
1939
|
-
error(null, path, { name: draftAdminDataProjectionName },
|
|
1940
|
-
'Generated entity $(NAME) conflicts with existing artifact');
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
const persistenceName = getResultingName(csn, options.forHana.names, draftsArtifactName);
|
|
1944
|
-
// Duplicate the artifact as a draft shadow entity
|
|
1945
|
-
if (csn.definitions[persistenceName]) {
|
|
1946
|
-
const definingDraftRoot = csn.definitions[persistenceName].$draftRoot;
|
|
1947
|
-
if (!definingDraftRoot)
|
|
1948
|
-
error(null, [ 'definitions', artifactName ], { name: persistenceName },
|
|
1949
|
-
'Generated entity name $(NAME) conflicts with existing entity');
|
|
1950
|
-
|
|
1951
|
-
else
|
|
1952
|
-
error(null, [ 'definitions', draftRootName ], { name: persistenceName },
|
|
1953
|
-
`Entity $(NAME) already generated by draft root "${ definingDraftRoot }"`);
|
|
1954
|
-
|
|
1955
|
-
return;
|
|
1956
|
-
}
|
|
1957
|
-
const draftsArtifact = {
|
|
1958
|
-
kind: 'entity',
|
|
1959
|
-
elements: Object.create(null),
|
|
1960
|
-
};
|
|
1961
|
-
|
|
1962
|
-
// Add draft shadow entity to the csn
|
|
1963
|
-
csn.definitions[draftsArtifactName] = draftsArtifact;
|
|
1964
|
-
|
|
1965
|
-
setProp(draftsArtifact, '$draftRoot', draftRootName);
|
|
1966
|
-
if(artifact.$location)
|
|
1967
|
-
setProp(draftsArtifact, '$location', artifact.$location);
|
|
1968
|
-
|
|
1969
|
-
// Copy all elements
|
|
1970
|
-
for (const elemName in artifact.elements) {
|
|
1971
|
-
const origElem = artifact.elements[elemName];
|
|
1972
|
-
let elem = undefined;
|
|
1973
|
-
if(isDeprecatedEnabled(options, 'renderVirtualElements') && origElem.virtual)
|
|
1974
|
-
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
|
|
1975
|
-
else if(!origElem.virtual)
|
|
1976
|
-
elem = copyAndAddElement(origElem, draftsArtifact, draftsArtifactName, elemName)[elemName];
|
|
1977
|
-
if(elem) {
|
|
1978
|
-
// Remove "virtual" - cap/issues 4956
|
|
1979
|
-
if(elem.virtual) {
|
|
1980
|
-
delete elem.virtual;
|
|
1981
|
-
}
|
|
1982
|
-
// explicitly set nullable if not key and not unmanaged association
|
|
1983
|
-
if (!elem.key && !elem.on)
|
|
1984
|
-
elem.notNull = false;
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
// Generate the additional elements into the draft-enabled artifact
|
|
1989
|
-
|
|
1990
|
-
// key IsActiveEntity : Boolean default true
|
|
1991
|
-
const isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', false);
|
|
1992
|
-
// Use artifactName and not draftsArtifactName because otherwise we may point to the generated
|
|
1993
|
-
// entity in CSN and won't get a proper location (draftsArtifact has inherited all
|
|
1994
|
-
// elements from the original artifact).
|
|
1995
|
-
addElement(isActiveEntity, draftsArtifact, artifactName);
|
|
1996
|
-
|
|
1997
|
-
// HasActiveEntity : Boolean default false
|
|
1998
|
-
const hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false);
|
|
1999
|
-
addElement(hasActiveEntity, draftsArtifact, artifactName);
|
|
2000
|
-
|
|
2001
|
-
// HasDraftEntity : Boolean default false;
|
|
2002
|
-
const hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false);
|
|
2003
|
-
addElement(hasDraftEntity, draftsArtifact, artifactName);
|
|
2004
|
-
|
|
2005
|
-
// DraftAdministrativeData : Association to one DraftAdministrativeData not null;
|
|
2006
|
-
const draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
|
|
2007
|
-
draftAdministrativeData.DraftAdministrativeData.cardinality = {
|
|
2008
|
-
max: 1,
|
|
2009
|
-
};
|
|
2010
|
-
draftAdministrativeData.DraftAdministrativeData.notNull = true;
|
|
2011
|
-
addElement(draftAdministrativeData, draftsArtifact, artifactName);
|
|
2012
|
-
// Note that we may need to do the HANA transformation steps for managed associations
|
|
2013
|
-
// (foreign key field generation, generatedFieldName, creating ON-condition) by hand,
|
|
2014
|
-
// because the corresponding transformation steps have already been done on all artifacts
|
|
2015
|
-
// when we come here). Only for 'keepStructsAssocs' this is not required.
|
|
2016
|
-
/**
|
|
2017
|
-
* The given association has a key named DraftUUID
|
|
2018
|
-
*
|
|
2019
|
-
* @param {CSN.Association} association Assoc to check
|
|
2020
|
-
* @returns {object}
|
|
2021
|
-
*/
|
|
2022
|
-
function getDraftUUIDKey(association) {
|
|
2023
|
-
if (association.keys) {
|
|
2024
|
-
const filtered = association.keys.filter(o => (o.ref && !o.as && o.ref.length === 1 && o.ref[0] === 'DraftUUID') || (o.as && o.as === 'DraftUUID'));
|
|
2025
|
-
if (filtered.length === 1)
|
|
2026
|
-
return filtered[0];
|
|
2027
|
-
|
|
2028
|
-
else if (filtered.length > 1)
|
|
2029
|
-
return filtered.filter(o => o.as && o.as === 'DraftUUID');
|
|
2030
|
-
}
|
|
2031
|
-
|
|
2032
|
-
return undefined;
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
|
-
function getNameForRef(obj) {
|
|
2036
|
-
if (obj.as)
|
|
2037
|
-
return obj.as;
|
|
2038
|
-
|
|
2039
|
-
return obj.ref[obj.ref.length - 1];
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
const draftUUIDKey = getDraftUUIDKey(draftAdministrativeData.DraftAdministrativeData);
|
|
2043
|
-
if (!options.forHana.keepStructsAssocs && draftUUIDKey) {
|
|
2044
|
-
const path = [ 'definitions', draftsArtifactName, 'elements', 'DraftAdministrativeData', 'keys', 0 ];
|
|
2045
|
-
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', draftUUIDKey, draftsArtifact, draftsArtifactName, path);
|
|
2046
|
-
draftAdministrativeData.DraftAdministrativeData.on = createAssociationPathComparison('DraftAdministrativeData',
|
|
2047
|
-
getNameForRef(draftUUIDKey),
|
|
2048
|
-
'=',
|
|
2049
|
-
`DraftAdministrativeData${ pathDelimiter }DraftUUID`);
|
|
2050
|
-
// The notNull has been transferred to the foreign key field and must be removed on the association
|
|
2051
|
-
delete draftAdministrativeData.DraftAdministrativeData.notNull;
|
|
2052
|
-
|
|
2053
|
-
// The association is now unmanaged, i.e. actually it should no longer have foreign keys
|
|
2054
|
-
// at all. But the processing of backlink associations below expects to have them, so
|
|
2055
|
-
// we don't delete them (but mark them as implicit so that toCdl does not render them)
|
|
2056
|
-
// draftAdministrativeData.DraftAdministrativeData.implicitForeignKeys = true;
|
|
2057
|
-
}
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
// Redirect all association/composition targets in 'artifact' that point to targets in
|
|
2061
|
-
// the dictionary 'draftNodes' to their corresponding draft shadow artifacts.
|
|
2062
|
-
function redirectDraftTargets(artifact, draftNodes) {
|
|
2063
|
-
for (const elemName in artifact.elements) {
|
|
2064
|
-
const elem = artifact.elements[elemName];
|
|
2065
|
-
if (elem.target) {
|
|
2066
|
-
const targetArt = getCsnDef(elem.target);
|
|
2067
|
-
// Nothing to do if target is not a draft node
|
|
2068
|
-
if (!draftNodes[elem.target])
|
|
2069
|
-
continue;
|
|
2070
|
-
|
|
2071
|
-
// Redirect the composition/association in this draft shadow entity to the target draft shadow entity
|
|
2072
|
-
// console.error(`Redirecting target of ${elemName} in ${artifact.name.absolute} to ${target.name.absolute + '_drafts'}`);
|
|
2073
|
-
const { shadowTarget, shadowTargetName } = getDraftShadowEntityFor(targetArt, elem.target);
|
|
2074
|
-
// Might not exist because of previous errors
|
|
2075
|
-
if (shadowTarget)
|
|
2076
|
-
elem.target = shadowTargetName;
|
|
2077
|
-
// FIXME: Strictly speaking, we would also need to replace the foreign keys' _artifact links,
|
|
2078
|
-
// but since their content is identical anyway, we simply omit that for now.
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
// Returns the corresponding draft shadow artifact for draft node 'draftNode'.
|
|
2083
|
-
function getDraftShadowEntityFor(draftNode, draftNodeName) {
|
|
2084
|
-
// Sanity check
|
|
2085
|
-
if (!draftNodes[draftNodeName])
|
|
2086
|
-
throw new Error(`Not a draft node: ${ draftNodeName }`);
|
|
2087
|
-
|
|
2088
|
-
return { shadowTarget: csn.definitions[`${ draftNodeName }${draftSuffix}`], shadowTargetName: `${ draftNodeName }${draftSuffix}` };
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
1699
|
/**
|
|
2093
1700
|
* @todo: XSN - Implementation most likely too naive, can we rely on query.SELECT.mixin?
|
|
2094
1701
|
*
|
|
@@ -2109,10 +1716,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2109
1716
|
return true;
|
|
2110
1717
|
}
|
|
2111
1718
|
|
|
2112
|
-
function insideColumns(path) {
|
|
2113
|
-
return path.length >= 3 && path[path.length - 3] === 'SELECT' && path[path.length - 2] === 'columns';
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
1719
|
/**
|
|
2117
1720
|
* @param {CSN.Artifact} artifact
|
|
2118
1721
|
* @param {string} artifactName
|
|
@@ -2137,20 +1740,20 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2137
1740
|
for (const name in parameters) {
|
|
2138
1741
|
const param = parameters[name];
|
|
2139
1742
|
if (!node[param] && absolute !== 'cds.hana.ST_POINT' && absolute !== 'cds.hana.ST_GEOMETRY')
|
|
2140
|
-
error(
|
|
1743
|
+
error('missing-type-parameter', path, { name: param, id: absolute, $reviewed: false });
|
|
2141
1744
|
}
|
|
2142
1745
|
switch (absolute) {
|
|
2143
1746
|
case 'cds.String':
|
|
2144
1747
|
case 'cds.Binary':
|
|
2145
1748
|
case 'cds.hana.VARCHAR': {
|
|
2146
|
-
checkTypeParamValue(node, 'length',
|
|
1749
|
+
checkTypeParamValue(node, 'length', { min: 1, max: 5000 }, path);
|
|
2147
1750
|
break;
|
|
2148
1751
|
}
|
|
2149
1752
|
case 'cds.Decimal': {
|
|
2150
1753
|
// Don't check with "plain"?
|
|
2151
1754
|
if (node.precision || node.scale) {
|
|
2152
|
-
checkTypeParamValue(node, 'precision',
|
|
2153
|
-
checkTypeParamValue(node, 'scale',
|
|
1755
|
+
checkTypeParamValue(node, 'precision', { max: 38 }, path);
|
|
1756
|
+
checkTypeParamValue(node, 'scale', { max: node.precision }, path);
|
|
2154
1757
|
}
|
|
2155
1758
|
break;
|
|
2156
1759
|
}
|
|
@@ -2158,12 +1761,12 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2158
1761
|
case 'cds.hana.BINARY':
|
|
2159
1762
|
case 'cds.hana.NCHAR':
|
|
2160
1763
|
case 'cds.hana.CHAR': {
|
|
2161
|
-
checkTypeParamValue(node, 'length',
|
|
1764
|
+
checkTypeParamValue(node, 'length', { min: 1, max: 2000 }, path);
|
|
2162
1765
|
break;
|
|
2163
1766
|
}
|
|
2164
1767
|
case 'cds.hana.ST_POINT':
|
|
2165
1768
|
case 'cds.hana.ST_GEOMETRY': {
|
|
2166
|
-
checkTypeParamValue(node, 'srid',
|
|
1769
|
+
checkTypeParamValue(node, 'srid', { max: Number.MAX_SAFE_INTEGER }, path);
|
|
2167
1770
|
break;
|
|
2168
1771
|
}
|
|
2169
1772
|
}
|
|
@@ -2171,32 +1774,26 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2171
1774
|
|
|
2172
1775
|
// Check that the value of the type property `paramName` (e.g. length, precision, scale ...) is of `expectedType`
|
|
2173
1776
|
// (which can currently only be 'positiveInteger') and (optional) the value is in a given range
|
|
2174
|
-
function checkTypeParamValue(node, paramName,
|
|
1777
|
+
function checkTypeParamValue(node, paramName, range = null, path = null) {
|
|
2175
1778
|
const paramValue = node[paramName];
|
|
2176
1779
|
if (paramValue == undefined) {
|
|
2177
|
-
if(options.toSql || artifact.query || ![
|
|
1780
|
+
if(options.toSql || artifact.query || !['cds.Binary','cds.hana.BINARY', 'cds.hana.NCHAR','cds.hana.CHAR'].includes(node.type)) {
|
|
2178
1781
|
return true;
|
|
2179
1782
|
} else {
|
|
2180
|
-
return error(
|
|
1783
|
+
return error('missing-type-parameter', path, { name: paramName, id: node.type, $reviewed: false });
|
|
2181
1784
|
}
|
|
2182
1785
|
}
|
|
2183
|
-
switch (expectedType) {
|
|
2184
|
-
case 'positiveInteger':
|
|
2185
|
-
if (!(Number.isInteger(paramValue) && paramValue >= 0)) {
|
|
2186
|
-
error(null, path, `Actual parameter '${ paramName }' for '${ node.type }' must be positive integer`);
|
|
2187
|
-
return false;
|
|
2188
|
-
}
|
|
2189
|
-
break;
|
|
2190
|
-
default:
|
|
2191
|
-
throw 'Unknown "expectedType"';
|
|
2192
|
-
}
|
|
2193
1786
|
if (range) {
|
|
2194
1787
|
if (isMaxParameterLengthRestricted(node.type) && range.max && paramValue > range.max) {
|
|
2195
|
-
error(null, path,
|
|
1788
|
+
error(null, path,
|
|
1789
|
+
{ prop: paramName, type: node.type, number: range.max, $reviewed: false },
|
|
1790
|
+
'Expecting parameter $(PROP) for type $(TYPE) to not exceed $(NUMBER)');
|
|
2196
1791
|
return false;
|
|
2197
1792
|
}
|
|
2198
1793
|
if (range.min && paramValue < range.min) {
|
|
2199
|
-
error(null, path,
|
|
1794
|
+
error(null, path,
|
|
1795
|
+
{ prop: paramName, type: node.type, number: range.min, $reviewed: false },
|
|
1796
|
+
'Expecting parameter $(PROP) for type $(TYPE) to be greater than or equal to $(NUMBER)');
|
|
2200
1797
|
return false;
|
|
2201
1798
|
}
|
|
2202
1799
|
}
|
|
@@ -2215,152 +1812,6 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2215
1812
|
return !(options.toSql && type === 'cds.String' && (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain'));
|
|
2216
1813
|
}
|
|
2217
1814
|
|
|
2218
|
-
/**
|
|
2219
|
-
* Check if the given artifact is part of a service.
|
|
2220
|
-
*
|
|
2221
|
-
* @param {string} artifactName Absolute name of the artifact
|
|
2222
|
-
* @returns {boolean}
|
|
2223
|
-
*/
|
|
2224
|
-
function isPartOfService(artifactName) {
|
|
2225
|
-
for (const serviceName of allServices) {
|
|
2226
|
-
if (artifactName.startsWith(`${ serviceName }.`))
|
|
2227
|
-
return true;
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
return false;
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
/**
|
|
2234
|
-
* Get the service name containing the artifact.
|
|
2235
|
-
*
|
|
2236
|
-
* @param {string} artifactName Absolute name of the artifact
|
|
2237
|
-
* @returns {boolean|string} Name of the service or false if no match is found.
|
|
2238
|
-
*/
|
|
2239
|
-
function getMatchingService(artifactName) {
|
|
2240
|
-
const matches = [];
|
|
2241
|
-
for (const serviceName of allServices) {
|
|
2242
|
-
if (artifactName.startsWith(`${ serviceName }.`))
|
|
2243
|
-
matches.push(serviceName);
|
|
2244
|
-
}
|
|
2245
|
-
if (matches.length === 0)
|
|
2246
|
-
return false;
|
|
2247
|
-
else
|
|
2248
|
-
return matches.sort((a, b) => a.length - b.length)[0];
|
|
2249
|
-
}
|
|
2250
|
-
|
|
2251
|
-
/**
|
|
2252
|
-
* Get not just the leafs, but all the branches of a structured element
|
|
2253
|
-
*
|
|
2254
|
-
* @param {object} element Structured element
|
|
2255
|
-
* @param {string} elementName Name of the structured element
|
|
2256
|
-
* @returns {object} Returns a dictionary, where the key is the flat name of the branch and the value is an array of element-steps.
|
|
2257
|
-
*/
|
|
2258
|
-
function getBranches(element, elementName){
|
|
2259
|
-
const branches = {};
|
|
2260
|
-
const subbranchNames = [];
|
|
2261
|
-
const subbranchElements = [];
|
|
2262
|
-
walkElements(element, elementName);
|
|
2263
|
-
function walkElements(e, name){
|
|
2264
|
-
if(isBuiltinType(e)){
|
|
2265
|
-
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
2266
|
-
} else {
|
|
2267
|
-
const eType = effectiveType(e)
|
|
2268
|
-
const subelements = e.elements || eType.elements;
|
|
2269
|
-
if(subelements){
|
|
2270
|
-
subbranchElements.push(e);
|
|
2271
|
-
subbranchNames.push(name);
|
|
2272
|
-
for(let [subelementName, subelement] of Object.entries(subelements)){
|
|
2273
|
-
walkElements(subelement, subelementName);
|
|
2274
|
-
}
|
|
2275
|
-
subbranchNames.pop();
|
|
2276
|
-
subbranchElements.pop();
|
|
2277
|
-
} else {
|
|
2278
|
-
branches[subbranchNames.concat(name).join(pathDelimiter)] = subbranchElements.concat(e);
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
return branches;
|
|
2284
|
-
}
|
|
2285
|
-
|
|
2286
|
-
/**
|
|
2287
|
-
* Flatten structures
|
|
2288
|
-
*
|
|
2289
|
-
* @param {CSN.Artifact} art Artifact
|
|
2290
|
-
* @param {string} artName Artifact Name
|
|
2291
|
-
*/
|
|
2292
|
-
function flattenStructuredElements(art, artName) {
|
|
2293
|
-
if ((art.kind === 'entity' || art.kind === 'view') && !options.forHana.keepStructsAssocs) {
|
|
2294
|
-
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
2295
|
-
const elementsArray = [];
|
|
2296
|
-
for (const elemName in elements) {
|
|
2297
|
-
const pathToElement = pathToElements.concat([elemName])
|
|
2298
|
-
const elem = parent.elements[elemName];
|
|
2299
|
-
elementsArray.push([elemName, elem]);
|
|
2300
|
-
if (isStructured(elem)) {
|
|
2301
|
-
// Ignore the structured element, replace it by its flattened form
|
|
2302
|
-
// TODO: use $ignore - _ is for links
|
|
2303
|
-
elem._ignore = true;
|
|
2304
|
-
|
|
2305
|
-
const branches = getBranches(elem, elemName);
|
|
2306
|
-
const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
|
|
2307
|
-
|
|
2308
|
-
for (const flatElemName in flatElems) {
|
|
2309
|
-
if (parent.elements[flatElemName])
|
|
2310
|
-
error(null, pathToElement, `"${ artName }.${ elemName }": Flattened struct element name conflicts with existing element: "${ flatElemName }"`);
|
|
2311
|
-
|
|
2312
|
-
const flatElement = flatElems[flatElemName];
|
|
2313
|
-
|
|
2314
|
-
// Check if we have a valid notNull chain
|
|
2315
|
-
const branch = branches[flatElemName];
|
|
2316
|
-
if(flatElement.notNull !== false && !branch.some(s => !s.notNull)){
|
|
2317
|
-
flatElement.notNull = true;
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
if(flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on){
|
|
2321
|
-
// Make refs resolvable by fixing the first ref step
|
|
2322
|
-
for (let i = 0; i < flatElement.on.length; i++) {
|
|
2323
|
-
const onPart = flatElement.on[i];
|
|
2324
|
-
if (onPart.ref) {
|
|
2325
|
-
const firstRef = flatElement.on[i].ref[0];
|
|
2326
|
-
|
|
2327
|
-
/*
|
|
2328
|
-
when element is defined in the current name resolution scope, like
|
|
2329
|
-
entity E {
|
|
2330
|
-
key x: Integer;
|
|
2331
|
-
s : {
|
|
2332
|
-
y : Integer;
|
|
2333
|
-
a3 : association to E on a3.x = y;
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
2337
|
-
*/
|
|
2338
|
-
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,-1).join(pathDelimiter);
|
|
2339
|
-
const possibleFlatName = prefix + pathDelimiter + firstRef;
|
|
2340
|
-
|
|
2341
|
-
if (flatElems[possibleFlatName])
|
|
2342
|
-
flatElement.on[i].ref[0] = possibleFlatName;
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
elementsArray.push([flatElemName, flatElement]);
|
|
2347
|
-
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
2348
|
-
parent.elements[flatElemName] = flatElement;
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
// Don't fake consistency of the model by adding empty elements {}
|
|
2353
|
-
if(elementsArray.length === 0)
|
|
2354
|
-
return;
|
|
2355
|
-
|
|
2356
|
-
parent.elements = elementsArray.reduce((previous, [name, element]) => {
|
|
2357
|
-
previous[name] = element;
|
|
2358
|
-
return previous;
|
|
2359
|
-
}, Object.create(null));
|
|
2360
|
-
});
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
1815
|
/**
|
|
2365
1816
|
* Flatten and create the foreign key elements of managed associaitons
|
|
2366
1817
|
*
|
|
@@ -2368,7 +1819,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2368
1819
|
* @param {string} artName
|
|
2369
1820
|
*/
|
|
2370
1821
|
function handleManagedAssociationFKs(art, artName) {
|
|
2371
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
1822
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
2372
1823
|
forAllElements(art, artName, (parent, elements, pathToElements) => {
|
|
2373
1824
|
if(artName.startsWith('localized.') && pathToElements.length > 3) {
|
|
2374
1825
|
// In subqueries, the elements of localized views are missing all the important bits and pieces...
|
|
@@ -2512,7 +1963,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2512
1963
|
function flattenIndexes(art, artName) {
|
|
2513
1964
|
// Flatten structs in indexes (unless explicitly asked to keep structs)
|
|
2514
1965
|
const tc = art.technicalConfig;
|
|
2515
|
-
if ((art.kind === 'entity' || art.kind === 'view') &&
|
|
1966
|
+
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
|
|
2516
1967
|
if (tc && tc[dialect]) {
|
|
2517
1968
|
// Secondary and fulltext indexes
|
|
2518
1969
|
for (const name in tc[dialect].indexes) {
|
|
@@ -2579,7 +2030,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2579
2030
|
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
|
|
2580
2031
|
for (const elemName in artifact.elements) {
|
|
2581
2032
|
const elem = artifact.elements[elemName];
|
|
2582
|
-
if (
|
|
2033
|
+
if (doA2J) {
|
|
2583
2034
|
// The association is an unmanaged on
|
|
2584
2035
|
if (!elem.keys && elem.target && elem.on) {
|
|
2585
2036
|
forEachRef(elem.on, (ref, refOwner, path) => {
|
|
@@ -2592,7 +2043,7 @@ function transformForHanaWithCsn(inputModel, options, moduleName) {
|
|
|
2592
2043
|
const link = links[i];
|
|
2593
2044
|
// We found the latest managed assoc path step
|
|
2594
2045
|
if (link.art && link.art.target && link.art.keys) {
|
|
2595
|
-
// Doesn't work when ref-
|
|
2046
|
+
// Doesn't work when ref-target (filter condition) or similar is used
|
|
2596
2047
|
if (!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
2597
2048
|
// We join the managed assoc with everything following it
|
|
2598
2049
|
const sourceElementName = ref.slice(i).join(pathDelimiter);
|