@sap/cds-compiler 4.9.6 → 5.1.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 +92 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +25 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +0 -1
- package/lib/utils/file.js +87 -8
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
|
@@ -193,7 +193,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
193
193
|
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
|
|
194
194
|
if (assoc.keys) {
|
|
195
195
|
if (assoc.keys.length)
|
|
196
|
-
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
|
|
196
|
+
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
197
197
|
|
|
198
198
|
if (options.transformation !== 'effective')
|
|
199
199
|
elem.$ignore = true;
|
|
@@ -202,7 +202,7 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
202
202
|
|
|
203
203
|
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
|
|
204
204
|
else if (assoc.on) {
|
|
205
|
-
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
|
|
205
|
+
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName, art, path);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${JSON.stringify(elem.on)}`);
|
|
@@ -220,9 +220,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
220
220
|
* @param {CSN.Element} assoc
|
|
221
221
|
* @param {string} originalAssocName
|
|
222
222
|
* @param {string} elemName
|
|
223
|
+
* @param {CSN.Artifact} art
|
|
224
|
+
* @param {CSN.Path} path
|
|
223
225
|
* @returns {Array} New on-condition
|
|
224
226
|
*/
|
|
225
|
-
function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
|
|
227
|
+
function transformDollarSelfComparisonWithManagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path) {
|
|
226
228
|
const conditions = [];
|
|
227
229
|
// if the element was structured then it was flattened => change of the delimiter from '.' to '_'
|
|
228
230
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
@@ -245,7 +247,9 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
245
247
|
if (prepend$self)
|
|
246
248
|
a[1].ref = [ '$self', ...a[1].ref ];
|
|
247
249
|
|
|
248
|
-
|
|
250
|
+
// Not without a2j so we can rely on a certain model state
|
|
251
|
+
if (doA2J && prepend$self && art.elements[k.ref[1]] || !prepend$self && !art.elements[k.ref[0]])
|
|
252
|
+
messageFunctions.message('ref-missing-self-counterpart', path, { prop: k.ref[0], name: assocName });
|
|
249
253
|
conditions.push([ a[0], '=', a[1] ]);
|
|
250
254
|
});
|
|
251
255
|
|
|
@@ -268,9 +272,11 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
268
272
|
* @param {CSN.Element} assoc
|
|
269
273
|
* @param {string} originalAssocName
|
|
270
274
|
* @param {string} elemName
|
|
275
|
+
* @param {CSN.Artifact} art
|
|
276
|
+
* @param {CSN.Path} path
|
|
271
277
|
* @returns {Array} New on-condition
|
|
272
278
|
*/
|
|
273
|
-
function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName ) {
|
|
279
|
+
function transformDollarSelfComparisonWithUnmanagedAssoc( assocOp, assoc, originalAssocName, elemName, art, path ) {
|
|
274
280
|
// if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
|
|
275
281
|
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
|
|
276
282
|
elemName = elemName.replace(/\./g, pathDelimiter);
|
|
@@ -279,11 +285,14 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
279
285
|
const newOnCond = cloneCsnNonDict(assoc.on, options);
|
|
280
286
|
applyTransformationsOnNonDictionary({ on: newOnCond }, 'on', {
|
|
281
287
|
ref: (parent, prop, ref) => {
|
|
288
|
+
let sourceSide = false;
|
|
282
289
|
// we are in the "path" from the forwarding assoc => need to remove the first part of the path
|
|
283
290
|
if (ref[0] === assocName) {
|
|
284
291
|
ref.shift();
|
|
285
292
|
if (prepend$self)
|
|
286
293
|
ref.unshift('$self');
|
|
294
|
+
|
|
295
|
+
sourceSide = true;
|
|
287
296
|
}
|
|
288
297
|
else if (ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
|
|
289
298
|
// We could also have a $self in front of the assoc name - so we would need to shift twice
|
|
@@ -291,6 +300,8 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
291
300
|
ref.shift();
|
|
292
301
|
if (prepend$self)
|
|
293
302
|
ref.unshift('$self');
|
|
303
|
+
|
|
304
|
+
sourceSide = true;
|
|
294
305
|
}
|
|
295
306
|
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
|
|
296
307
|
ref.unshift(elemName);
|
|
@@ -299,6 +310,10 @@ function getBacklinkTransformer( csnUtils, messageFunctions, options, pathDelimi
|
|
|
299
310
|
if (ref[1] === '$self')
|
|
300
311
|
ref.splice(1, 1);
|
|
301
312
|
}
|
|
313
|
+
|
|
314
|
+
// Not without a2j so we can rely on a certain model state
|
|
315
|
+
if (doA2J && sourceSide && (prepend$self && !art.elements[ref[1]] || !prepend$self && !art.elements[ref[0]]))
|
|
316
|
+
messageFunctions.message('ref-missing-self-counterpart', path, { '#': 'unmanaged', prop: ref[0], name: assocName });
|
|
302
317
|
},
|
|
303
318
|
});
|
|
304
319
|
return newOnCond;
|
|
@@ -47,7 +47,7 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
47
47
|
const isComplexQuery = parent.from.join !== undefined;
|
|
48
48
|
if (!options.toOdata)
|
|
49
49
|
parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
|
|
50
|
-
// FIXME(
|
|
50
|
+
// FIXME(v6): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
|
|
51
51
|
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
|
|
52
52
|
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
|
|
53
53
|
}
|
|
@@ -622,11 +622,6 @@ function expandStructureReferences( csn, options, pathDelimiter, messageFunction
|
|
|
622
622
|
return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {
|
|
623
623
|
const obj = { ...root, ref: currentRef };
|
|
624
624
|
if (withAlias) {
|
|
625
|
-
// TODO: Remove this line in case foreign key annotations should
|
|
626
|
-
// be addressed via full path into target instead of using alias
|
|
627
|
-
// names. See flattening.js::flattenAllStructStepsInRefs()
|
|
628
|
-
// apply transformations on `ref` counterpart comment.
|
|
629
|
-
setProp(obj, '$structRef', currentAlias);
|
|
630
625
|
obj.as = currentAlias.join(pathDelimiter);
|
|
631
626
|
// alias was implicit - to later distinguish expanded s -> s.a from explicitly written s.a
|
|
632
627
|
if (root.as === undefined)
|
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');
|
|
13
13
|
const transformUtils = require('../transformUtils');
|
|
14
14
|
const { csnRefs } = require('../../model/csnRefs');
|
|
15
|
-
const { setProp
|
|
15
|
+
const { setProp } = require('../../base/model');
|
|
16
16
|
const { forEach } = require('../../utils/objectUtils');
|
|
17
17
|
const { transformExpression } = require('./applyTransformations');
|
|
18
18
|
const { cloneCsnNonDict } = require('../../model/cloneCsn');
|
|
@@ -207,9 +207,24 @@ function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDe
|
|
|
207
207
|
* @param {object} iterateOptions
|
|
208
208
|
*/
|
|
209
209
|
function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
|
|
210
|
+
const adaptRefs = [];
|
|
211
|
+
|
|
212
|
+
applyTransformations(csn, getStructStepsFlattener(csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs), [], iterateOptions);
|
|
213
|
+
|
|
214
|
+
adaptRefs.forEach(fn => fn());
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* @param {CSN.Model} csn
|
|
218
|
+
* @param {CSN.Options} options
|
|
219
|
+
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
|
|
220
|
+
* @param {WeakMap} resolved Cache for resolved refs
|
|
221
|
+
* @param {string} pathDelimiter
|
|
222
|
+
* @param {Function[]} adaptRefs
|
|
223
|
+
* @returns {object} applyTransformations transformer
|
|
224
|
+
*/
|
|
225
|
+
function getStructStepsFlattener( csn, options, messageFunctions, resolved, pathDelimiter, adaptRefs ) {
|
|
210
226
|
const { inspectRef, effectiveType } = csnRefs(csn);
|
|
211
227
|
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
|
|
212
|
-
const adaptRefs = [];
|
|
213
228
|
|
|
214
229
|
/**
|
|
215
230
|
* For each step of the links, check if there is a type reference.
|
|
@@ -230,41 +245,57 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
|
|
|
230
245
|
return resolvedLinkTypes;
|
|
231
246
|
}
|
|
232
247
|
|
|
233
|
-
|
|
248
|
+
const transformer = {
|
|
234
249
|
// @ts-ignore
|
|
235
|
-
ref: (parent, prop, ref, path) => {
|
|
250
|
+
ref: (parent, prop, ref, path, _parent, _prop, context) => {
|
|
236
251
|
const { links, art, scope } = inspectRef(path);
|
|
237
252
|
const resolvedLinkTypes = resolveLinkTypes(links);
|
|
238
253
|
setProp(parent, '$path', [ ...path ]);
|
|
239
254
|
const lastRef = ref[ref.length - 1];
|
|
240
|
-
const fn = (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
parent.
|
|
255
|
+
const fn = (suspend = false, suspendPos = 0,
|
|
256
|
+
refFilter = _parent => true) => {
|
|
257
|
+
let refChanged = false;
|
|
258
|
+
if (refFilter(parent)) {
|
|
259
|
+
const scopedPath = [ ...parent.$path ];
|
|
260
|
+
// TODO: If foreign key annotations should be assigned via
|
|
261
|
+
// full path into target, uncomment this line and
|
|
262
|
+
// comment/remove setProp in expansion.js
|
|
263
|
+
// setProp(parent, '$structRef', parent.ref);
|
|
264
|
+
[ parent.ref, refChanged ] = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes, suspend, suspendPos, parent.$bparam);
|
|
265
|
+
resolved.set(parent, { links, art, scope });
|
|
266
|
+
// Explicitly set implicit alias for things that are now flattened - but only in columns
|
|
267
|
+
// TODO: Can this be done elegantly during expand phase already?
|
|
268
|
+
if (parent.$implicitAlias) { // an expanded s -> s.a is marked with this - do not add implicit alias "a" there, we want s_a
|
|
269
|
+
if (parent.ref[parent.ref.length - 1] === parent.as) // for a simple s that was expanded - for s.substructure this would not apply
|
|
270
|
+
delete parent.as;
|
|
271
|
+
delete parent.$implicitAlias;
|
|
272
|
+
}
|
|
273
|
+
// To handle explicitly written s.a - add implicit alias a, since after flattening it would otherwise be s_a
|
|
274
|
+
else if (parent.ref[parent.ref.length - 1] !== lastRef &&
|
|
275
|
+
(insideColumns(scopedPath) || insideKeys(scopedPath)) &&
|
|
276
|
+
!parent.as) {
|
|
277
|
+
parent.as = lastRef;
|
|
278
|
+
}
|
|
260
279
|
}
|
|
280
|
+
|
|
281
|
+
return refChanged;
|
|
261
282
|
};
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
|
|
284
|
+
if (context?.$annotation) {
|
|
285
|
+
const annotation = context.$annotation.value;
|
|
286
|
+
adaptRefs.push((...args) => {
|
|
287
|
+
const refChanged = fn(...args);
|
|
288
|
+
if (refChanged && annotation['='])
|
|
289
|
+
annotation['='] = true;
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// adapt queries later
|
|
294
|
+
adaptRefs.push(fn);
|
|
295
|
+
}
|
|
264
296
|
},
|
|
265
|
-
}
|
|
297
|
+
};
|
|
266
298
|
|
|
267
|
-
adaptRefs.forEach(fn => fn());
|
|
268
299
|
|
|
269
300
|
/**
|
|
270
301
|
* Return true if the path points inside columns
|
|
@@ -284,6 +315,8 @@ function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved,
|
|
|
284
315
|
function insideKeys( path ) {
|
|
285
316
|
return path.length >= 3 && path[path.length - 2] === 'keys' && typeof path[path.length - 1] === 'number';
|
|
286
317
|
}
|
|
318
|
+
|
|
319
|
+
return transformer;
|
|
287
320
|
}
|
|
288
321
|
|
|
289
322
|
/**
|
|
@@ -344,25 +377,28 @@ function flattenElements( csn, options, messageFunctions, pathDelimiter, iterate
|
|
|
344
377
|
if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
|
|
345
378
|
// unmanaged relations can't be primary key
|
|
346
379
|
delete flatElement.key;
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0,
|
|
380
|
+
if (options.transformation !== 'effective') {
|
|
381
|
+
const process = endIndex => function processRef(_parent, _prop, xpr) {
|
|
382
|
+
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, endIndex).join(pathDelimiter);
|
|
350
383
|
const possibleFlatName = prefix + pathDelimiter + xpr[0];
|
|
351
384
|
/*
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
385
|
+
when element is defined in the current name resolution scope, like
|
|
386
|
+
entity E {
|
|
387
|
+
key x: Integer;
|
|
388
|
+
s : {
|
|
389
|
+
y : Integer;
|
|
390
|
+
a3 : association to E on a3.x = y;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
|
|
394
|
+
*/
|
|
362
395
|
if (flatElems[possibleFlatName])
|
|
363
396
|
xpr[0] = possibleFlatName;
|
|
364
|
-
}
|
|
365
|
-
|
|
397
|
+
};
|
|
398
|
+
transformExpression(flatElement, 'on', {
|
|
399
|
+
ref: process(-1),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
366
402
|
}
|
|
367
403
|
parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
|
|
368
404
|
// Still add them - otherwise we might not detect collisions between generated elements.
|
|
@@ -428,36 +464,6 @@ function getBranches( element, elementName, effectiveType, pathDelimiter ) {
|
|
|
428
464
|
return branches;
|
|
429
465
|
}
|
|
430
466
|
|
|
431
|
-
/**
|
|
432
|
-
* Link annotate extensions to managed associations as a preparational step
|
|
433
|
-
* for later annotation assignment on the final foreignkeys
|
|
434
|
-
* This function must be applied on an unmodified, structured CSN in order to
|
|
435
|
-
* traverse both the extensions and dictionary trees in corresponding order.
|
|
436
|
-
*
|
|
437
|
-
* @param {CSN.Model} csn
|
|
438
|
-
* @param {object} options
|
|
439
|
-
*/
|
|
440
|
-
function linkForeignKeyAnnotationExtensionsToAssociation( csn, options ) {
|
|
441
|
-
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
442
|
-
csn.extensions?.forEach(( ext ) => {
|
|
443
|
-
const defName = ext.annotate;
|
|
444
|
-
|
|
445
|
-
const traverseExtensions = (env, enode) => {
|
|
446
|
-
if (env?.target && env?.keys) {
|
|
447
|
-
setProp(env, '$fkExtensions', enode);
|
|
448
|
-
}
|
|
449
|
-
else {
|
|
450
|
-
const elements = env?.items?.elements || env?.elements;
|
|
451
|
-
if (enode?.elements && elements)
|
|
452
|
-
Object.keys(enode.elements).forEach(en => traverseExtensions(elements[en], enode.elements[en]));
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
if (ext.annotate)
|
|
456
|
-
traverseExtensions(csn.definitions[defName], ext);
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
467
|
/**
|
|
462
468
|
* @param {CSN.Model} csn
|
|
463
469
|
* @param {CSN.Options} options
|
|
@@ -588,7 +594,7 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
588
594
|
* @returns {object} The clone of base
|
|
589
595
|
*/
|
|
590
596
|
function cloneAndExtendRef( key, base, ref ) {
|
|
591
|
-
const clone = cloneCsnNonDict(base,
|
|
597
|
+
const clone = cloneCsnNonDict(base, options );
|
|
592
598
|
if (key.ref) {
|
|
593
599
|
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
|
|
594
600
|
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
|
|
@@ -604,8 +610,6 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
604
610
|
}
|
|
605
611
|
setProp(clone, '$ref', $ref);
|
|
606
612
|
clone.ref = clone.ref.concat(key.ref);
|
|
607
|
-
if (clone.$structRef && key.$structRef)
|
|
608
|
-
clone.$structRef = clone.$structRef.concat(key.$structRef);
|
|
609
613
|
}
|
|
610
614
|
|
|
611
615
|
if (!clone.as && clone.ref && clone.ref.length > 0) {
|
|
@@ -705,34 +709,9 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
705
709
|
'Expected target cardinality $(VALUE) and $(CODE) to match');
|
|
706
710
|
}
|
|
707
711
|
}
|
|
708
|
-
}
|
|
709
|
-
// assign annotations from fkExtension tree to foreign keys
|
|
710
|
-
if (isBetaEnabled(options, 'annotateForeignKeys')) {
|
|
711
|
-
const extCollector = {};
|
|
712
|
-
fks.forEach(([ _fkn, fk ]) => {
|
|
713
|
-
let ext = element.$fkExtensions;
|
|
714
|
-
let extKey = elementName;
|
|
715
|
-
for (const step of fk.$extensionPath) {
|
|
716
|
-
extKey += `.${step}`;
|
|
717
|
-
ext = ext?.elements?.[step];
|
|
718
|
-
if (!ext)
|
|
719
|
-
break;
|
|
720
|
-
// collect annotations, lowest wins
|
|
721
|
-
// eslint-disable-next-line no-loop-func
|
|
722
|
-
Object.entries(ext).forEach(([ k, v ]) => {
|
|
723
|
-
if (k[0] === '@') {
|
|
724
|
-
fk[k] = v;
|
|
725
|
-
extCollector[extKey] = ext;
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
});
|
|
730
712
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if (k[0] === '@')
|
|
734
|
-
delete ext[k];
|
|
735
|
-
}));
|
|
713
|
+
if (options.transformation === 'effective')
|
|
714
|
+
delete element.default;
|
|
736
715
|
}
|
|
737
716
|
orderedElements.push(...fks);
|
|
738
717
|
});
|
|
@@ -757,11 +736,10 @@ function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFun
|
|
|
757
736
|
* @param {CSN.Model} csn
|
|
758
737
|
* @param {object} options
|
|
759
738
|
* @param {string} pathDelimiter
|
|
760
|
-
* @param {object} extensionPath
|
|
761
739
|
* @param {number} lvl
|
|
762
740
|
* @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
|
|
763
741
|
*/
|
|
764
|
-
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter,
|
|
742
|
+
function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathDelimiter, lvl = 0 ) {
|
|
765
743
|
const special$self = !csn?.definitions?.$self && '$self';
|
|
766
744
|
const isInspectRefResult = !Array.isArray(path);
|
|
767
745
|
|
|
@@ -807,7 +785,7 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
807
785
|
const continuePath = getContinuePath([ 'keys', keyIndex ]);
|
|
808
786
|
const alias = key.as || implicitAs(key.ref);
|
|
809
787
|
const result = csnUtils.inspectRef(continuePath);
|
|
810
|
-
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter,
|
|
788
|
+
fks = fks.concat(createForeignKeys(csnUtils, result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
|
|
811
789
|
});
|
|
812
790
|
if (!hasKeys)
|
|
813
791
|
delete finalElement.keys;
|
|
@@ -821,14 +799,13 @@ function createForeignKeys( csnUtils, path, element, prefix, csn, options, pathD
|
|
|
821
799
|
// Skip already produced foreign keys
|
|
822
800
|
if (!elem['@odata.foreignKey4']) {
|
|
823
801
|
const continuePath = getContinuePath([ 'elements', elemName ]);
|
|
824
|
-
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter,
|
|
802
|
+
fks = fks.concat(createForeignKeys(csnUtils, continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
|
|
825
803
|
}
|
|
826
804
|
});
|
|
827
805
|
}
|
|
828
806
|
// we have reached a leaf element, create a foreign key
|
|
829
807
|
else if (finalElement.type == null || isBuiltinType(finalElement.type)) {
|
|
830
808
|
const newFk = Object.create(null);
|
|
831
|
-
setProp(newFk, '$extensionPath', extensionPath);
|
|
832
809
|
[ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type', ...EdmTypeFacetNames.map(f => `@odata.${f}`) ].forEach((prop) => {
|
|
833
810
|
// copy props from original element to preserve derived types!
|
|
834
811
|
if (element[prop] !== undefined)
|
|
@@ -887,7 +864,7 @@ module.exports = {
|
|
|
887
864
|
flattenAllStructStepsInRefs,
|
|
888
865
|
flattenElements,
|
|
889
866
|
removeLeadingSelf,
|
|
890
|
-
linkForeignKeyAnnotationExtensionsToAssociation,
|
|
891
867
|
handleManagedAssociationsAndCreateForeignKeys,
|
|
892
868
|
getBranches,
|
|
869
|
+
getStructStepsFlattener,
|
|
893
870
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { sqlServiceAnnotation } = require('./processSqlServices');
|
|
4
|
+
|
|
3
5
|
const requiredAnnos = {
|
|
4
6
|
'@cds.persistence.skip': true,
|
|
5
7
|
'@cds.persistence.exists': true,
|
|
@@ -22,6 +24,7 @@ const requiredAnnos = {
|
|
|
22
24
|
'@cds.autoexposed': true,
|
|
23
25
|
'@cds.redirection.target': true,
|
|
24
26
|
'@Core.Computed': true,
|
|
27
|
+
[sqlServiceAnnotation]: true,
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setProp } = require('../../base/model');
|
|
4
|
+
|
|
5
|
+
const sqlServiceAnnotation = '@protocol';
|
|
6
|
+
// Problem: How can we clone a Symbol when sorting?
|
|
7
|
+
// const sqlServiceEntities = Symbol.for('SQL Service enabled entities');
|
|
8
|
+
/**
|
|
9
|
+
* Find all entities in SQL services and mark them with an annotation and
|
|
10
|
+
* remember them in a symbol property for easier processing in toSql-rendering.
|
|
11
|
+
*
|
|
12
|
+
* @param {CSN.Model} csn
|
|
13
|
+
* @returns {Function}
|
|
14
|
+
*/
|
|
15
|
+
function processSqlServices(csn) {
|
|
16
|
+
setProp(csn, '$sqlServiceEntities', Object.create(null));
|
|
17
|
+
return function findAndMarkSqlServiceArtifacts(artifact, artifactName) {
|
|
18
|
+
const sqlServiceName = isEntityInSqlService(artifact, artifactName, csn);
|
|
19
|
+
if (sqlServiceName?.length > 0)
|
|
20
|
+
setProp(artifact, '$sqlService', sqlServiceName);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* @param {CSN.Artifact} artifact
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isSqlService(artifact) {
|
|
30
|
+
return artifact.kind === 'service' && artifact[sqlServiceAnnotation] === 'sql';
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {CSN.Artifact} artifact
|
|
35
|
+
* @param {string} artifactName
|
|
36
|
+
* @param {CSN.Model} csn
|
|
37
|
+
* @returns {string|null}
|
|
38
|
+
*/
|
|
39
|
+
function isEntityInSqlService(artifact, artifactName, csn) {
|
|
40
|
+
if (artifact.kind !== 'entity' || !artifactName.includes('.'))
|
|
41
|
+
return null;
|
|
42
|
+
|
|
43
|
+
const nameParts = artifactName.split('.');
|
|
44
|
+
for (let i = nameParts.length; i >= 0; i--) {
|
|
45
|
+
const possibleServiceName = nameParts.slice(0, i).join('.');
|
|
46
|
+
if (!csn.definitions[possibleServiceName])
|
|
47
|
+
continue;
|
|
48
|
+
|
|
49
|
+
const definition = csn.definitions[possibleServiceName];
|
|
50
|
+
if (isSqlService(definition))
|
|
51
|
+
return possibleServiceName;
|
|
52
|
+
|
|
53
|
+
// We don't allow nested services/contexts - if we find one, we don't need to keep searching
|
|
54
|
+
if (definition.kind === 'service' || definition.kind === 'context')
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
processSqlServices, isSqlService, sqlServiceAnnotation,
|
|
63
|
+
};
|
|
@@ -70,11 +70,10 @@ function getViewDecorator( csn, messageFunctions, csnUtils, options ) {
|
|
|
70
70
|
],
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
const atTo = { ref: [ '$at', 'to' ] };
|
|
73
|
+
const validFrom = { ref: [ '$valid', 'from' ] };
|
|
74
|
+
const validTo = { ref: [ '$valid', 'to' ] };
|
|
76
75
|
|
|
77
|
-
const cond = [ '(', fromPath, '<',
|
|
76
|
+
const cond = [ '(', fromPath, '<', validTo, 'and', toPath, '>', validFrom, ')' ];
|
|
78
77
|
|
|
79
78
|
if (normalizedQuery.query.SELECT.where) { // if there is an existing where-clause, extend it by adding 'and (temporal clause)'
|
|
80
79
|
normalizedQuery.query.SELECT.where = [ '(', ...normalizedQuery.query.SELECT.where, ')', 'and', ...cond ];
|
|
@@ -324,7 +324,6 @@ function getViewTransformer( csn, options, messageFunctions ) {
|
|
|
324
324
|
* @param {string} artName
|
|
325
325
|
* @param {CSN.Path} path
|
|
326
326
|
*/
|
|
327
|
-
// eslint-disable-next-line complexity
|
|
328
327
|
function transformViewOrEntity( query, artifact, artName, path ) {
|
|
329
328
|
const ignoreAssociations = options.sqlDialect === 'hana' && options.withHanaAssociations === false;
|
|
330
329
|
csnUtils.initDefinition(artifact);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { forEachDefinition,
|
|
3
|
+
const { forEachDefinition, forEachMemberRecursively,
|
|
4
|
+
getServiceNames, applyAnnotationsFromExtensions,
|
|
5
|
+
transformExpression } = require('../../model/csnUtils');
|
|
4
6
|
const { forEach } = require('../../utils/objectUtils');
|
|
5
7
|
const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');
|
|
6
8
|
const { getTransformers } = require('../transformUtils');
|
|
@@ -58,12 +60,13 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
58
60
|
// Generate artificial draft fields for entities/views if requested, ignore if not part of a service
|
|
59
61
|
if (def.kind === 'entity' && def['@odata.draft.enabled'] && isArtifactInSomeService(defName, services))
|
|
60
62
|
generateDraftForOdata(def, defName, def);
|
|
61
|
-
|
|
62
|
-
visitedArtifacts[defName] = true;
|
|
63
63
|
}, { skipArtifact: isExternalServiceMember });
|
|
64
64
|
|
|
65
65
|
applyAnnotationsFromExtensions(csn, { override: true, filter: name => filterDict[name] });
|
|
66
|
+
rewriteDollarDraft();
|
|
67
|
+
|
|
66
68
|
return csn;
|
|
69
|
+
|
|
67
70
|
/**
|
|
68
71
|
* Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
|
|
69
72
|
* into its transitively reachable composition targets, and into the model.
|
|
@@ -84,6 +87,9 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
84
87
|
artifact.actions && artifact.actions.draftPrepare)
|
|
85
88
|
return;
|
|
86
89
|
|
|
90
|
+
if(!visitedArtifacts[artifactName])
|
|
91
|
+
visitedArtifacts[artifactName] = artifact;
|
|
92
|
+
|
|
87
93
|
const draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
|
|
88
94
|
assignAction(draftPrepare, artifact);
|
|
89
95
|
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
|
|
@@ -211,6 +217,53 @@ function generateDrafts( csn, options, services, messageFunctions ) {
|
|
|
211
217
|
}
|
|
212
218
|
}
|
|
213
219
|
}
|
|
220
|
+
|
|
221
|
+
/*
|
|
222
|
+
* After draft decoration, all visited artifacts are supposed to have the draft state elements
|
|
223
|
+
* Is/HasActiveEntity, HasDraftEntity. Now, $draft.<postfix> (with postfix defined as magic variable
|
|
224
|
+
* in the core compiler builtins) needs to be translated into $self.<postfix>.
|
|
225
|
+
*
|
|
226
|
+
* It has to be processed after the late 'applyAnnotationsFromExtensions' which could also merge in
|
|
227
|
+
* some $draft path expressions.
|
|
228
|
+
*/
|
|
229
|
+
function rewriteDollarDraft() {
|
|
230
|
+
|
|
231
|
+
function $draft2$self(member) {
|
|
232
|
+
Object.keys(member).forEach(pn => {
|
|
233
|
+
if(pn[0] === '@') {
|
|
234
|
+
let refChanged = false;
|
|
235
|
+
transformExpression(member, pn,{
|
|
236
|
+
ref: (_parent, _prop, xpr, _path) => {
|
|
237
|
+
if(xpr[0] === '$draft') {
|
|
238
|
+
xpr[0] = '$self';
|
|
239
|
+
refChanged = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
if (refChanged)
|
|
244
|
+
member[pn]['='] = true;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// entity parameters are not substituted as the EDM param entity is not draft enabled
|
|
250
|
+
Object.entries(visitedArtifacts).forEach(([artName, art]) => {
|
|
251
|
+
$draft2$self(art);
|
|
252
|
+
forEachMemberRecursively(art, $draft2$self,
|
|
253
|
+
[ 'definitions', artName ],
|
|
254
|
+
true, { elementsOnly: true }
|
|
255
|
+
);
|
|
256
|
+
if(art.actions) {
|
|
257
|
+
Object.entries(art.actions).forEach(([actionName, action]) => {
|
|
258
|
+
$draft2$self(action);
|
|
259
|
+
forEachMemberRecursively(action, $draft2$self,
|
|
260
|
+
[ 'definitions', artName, 'actions', actionName ]);
|
|
261
|
+
if(action.returns)
|
|
262
|
+
$draft2$self(action.returns);
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
}
|
|
214
267
|
}
|
|
215
268
|
|
|
216
269
|
module.exports = generateDrafts;
|
|
@@ -179,9 +179,10 @@ function remapODataAnnotations( csn ) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
return {
|
|
182
|
-
elements: (parent, prop, elements, path) => {
|
|
182
|
+
elements: (parent, prop, elements, path, _parentParent, _dummy, context) => {
|
|
183
183
|
const artifact = csn.definitions[path[1]];
|
|
184
|
-
|
|
184
|
+
// Don't process bound actions, as they are still structured
|
|
185
|
+
if (artifact?.kind === 'entity' && !context.$in_actions) {
|
|
185
186
|
for (const elementName in elements)
|
|
186
187
|
remapAnnotationsOnElement(artifact, elements[elementName]);
|
|
187
188
|
}
|