@sap/cds-compiler 4.8.0 → 4.9.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 +38 -4
- package/bin/cds_remove_invalid_whitespace.js +135 -0
- package/bin/cds_update_annotations.js +180 -0
- package/bin/cds_update_identifiers.js +3 -4
- package/bin/cdsc.js +30 -17
- package/doc/CHANGELOG_BETA.md +19 -0
- package/lib/api/main.js +59 -24
- package/lib/api/options.js +12 -1
- package/lib/api/validate.js +1 -5
- package/lib/base/builtins.js +27 -0
- package/lib/base/message-registry.js +38 -21
- package/lib/base/messages.js +51 -20
- package/lib/base/model.js +4 -5
- package/lib/checks/actionsFunctions.js +2 -2
- package/lib/checks/annotationsOData.js +3 -0
- package/lib/checks/defaultValues.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +3 -2
- package/lib/checks/validator.js +2 -34
- package/lib/compiler/assert-consistency.js +10 -3
- package/lib/compiler/checks.js +44 -18
- package/lib/compiler/define.js +38 -30
- package/lib/compiler/extend.js +33 -10
- package/lib/compiler/index.js +0 -1
- package/lib/compiler/lsp-api.js +5 -0
- package/lib/compiler/populate.js +0 -2
- package/lib/compiler/propagator.js +23 -19
- package/lib/compiler/resolve.js +48 -29
- package/lib/compiler/shared.js +60 -20
- package/lib/compiler/tweak-assocs.js +72 -116
- package/lib/compiler/xpr-rewrite.js +762 -0
- package/lib/edm/annotations/edmJson.js +24 -7
- package/lib/edm/annotations/genericTranslation.js +81 -61
- package/lib/edm/edm.js +4 -4
- package/lib/edm/edmInboundChecks.js +33 -0
- package/lib/edm/edmPreprocessor.js +9 -6
- package/lib/gen/Dictionary.json +129 -14
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1523 -1518
- package/lib/json/from-csn.js +13 -4
- package/lib/json/to-csn.js +12 -12
- package/lib/language/genericAntlrParser.js +14 -6
- package/lib/main.d.ts +67 -14
- package/lib/main.js +1 -0
- package/lib/model/cloneCsn.js +6 -3
- package/lib/model/csnRefs.js +23 -11
- package/lib/model/csnUtils.js +13 -7
- package/lib/model/enrichCsn.js +3 -1
- package/lib/model/revealInternalProperties.js +2 -1
- package/lib/model/sortViews.js +14 -6
- package/lib/modelCompare/compare.js +33 -34
- package/lib/optionProcessor.js +27 -2
- package/lib/render/DuplicateChecker.js +6 -6
- package/lib/render/manageConstraints.js +1 -0
- package/lib/render/toCdl.js +3 -1
- package/lib/transform/db/applyTransformations.js +33 -0
- package/lib/transform/db/constraints.js +75 -28
- package/lib/transform/db/expansion.js +8 -3
- package/lib/transform/db/flattening.js +2 -2
- package/lib/transform/db/groupByOrderBy.js +2 -2
- package/lib/transform/db/temporal.js +6 -3
- package/lib/transform/db/transformExists.js +2 -2
- package/lib/transform/effective/annotations.js +194 -0
- package/lib/transform/effective/main.js +6 -8
- package/lib/transform/effective/misc.js +31 -10
- package/lib/transform/forOdata.js +23 -7
- package/lib/transform/forRelationalDB.js +3 -3
- package/lib/transform/localized.js +7 -6
- package/lib/transform/odata/flattening.js +221 -124
- package/lib/transform/odata/toFinalBaseType.js +1 -1
- package/lib/transform/odata/typesExposure.js +15 -12
- package/lib/transform/parseExpr.js +4 -4
- package/lib/transform/transformUtils.js +47 -42
- package/lib/transform/translateAssocsToJoins.js +47 -47
- package/lib/transform/universalCsn/universalCsnEnricher.js +16 -19
- package/package.json +1 -1
- package/share/messages/anno-missing-rewrite.md +45 -0
- package/share/messages/message-explanations.json +1 -0
- package/bin/.eslintrc.json +0 -17
- package/lib/api/.eslintrc.json +0 -37
- package/lib/checks/.eslintrc.json +0 -31
- package/lib/compiler/.eslintrc.json +0 -8
- package/lib/edm/.eslintrc.json +0 -46
- package/lib/inspect/.eslintrc.json +0 -4
- package/lib/json/.eslintrc.json +0 -4
- package/lib/language/.eslintrc.json +0 -4
- package/lib/model/.eslintrc.json +0 -13
- package/lib/modelCompare/utils/.eslintrc.json +0 -22
- package/lib/render/.eslintrc.json +0 -22
- package/lib/transform/.eslintrc.json +0 -13
- package/lib/transform/db/.eslintrc.json +0 -41
- package/lib/transform/draft/.eslintrc.json +0 -4
- package/lib/transform/effective/.eslintrc.json +0 -4
- package/lib/transform/universalCsn/.eslintrc.json +0 -37
- package/lib/utils/.eslintrc.json +0 -7
|
@@ -58,7 +58,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
|
58
58
|
return { 'cast': [ xpr.cast, { [castKeys[0]]: xpr[castKeys[0]] } ] };
|
|
59
59
|
}
|
|
60
60
|
else {
|
|
61
|
-
for(
|
|
61
|
+
for(const n in xpr) {
|
|
62
62
|
// xpr could be an array with polluted prototype
|
|
63
63
|
if (Object.hasOwnProperty.call(xpr, n))
|
|
64
64
|
xpr[n] = Cast(xpr[n], state)
|
|
@@ -214,7 +214,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
|
214
214
|
while(i < e && xpr[i] !== 'and') i++;
|
|
215
215
|
const a = i < e ? i : -1;
|
|
216
216
|
if(b >= 0) {
|
|
217
|
-
|
|
217
|
+
const token = [ 'between' ];
|
|
218
218
|
not = (xpr[b-1] === 'not');
|
|
219
219
|
if(not)
|
|
220
220
|
token.splice(0,0, 'not');
|
|
@@ -312,7 +312,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
|
312
312
|
if (typeof xpr === 'object') {
|
|
313
313
|
// if(xpr?.func && funkyfuncs.includes(xpr?.func))
|
|
314
314
|
// return xpr;
|
|
315
|
-
for(
|
|
315
|
+
for(const n in xpr) {
|
|
316
316
|
// xpr could be an array with polluted prototype
|
|
317
317
|
if (!Object.hasOwnProperty.call(xpr, n))
|
|
318
318
|
continue;
|
|
@@ -349,7 +349,7 @@ function parseExpr(xpr, state = { array: true, nary: false }) {
|
|
|
349
349
|
s = p+tl;
|
|
350
350
|
[tl, p] = findToken(s, e);
|
|
351
351
|
while(p >= 0) {
|
|
352
|
-
|
|
352
|
+
const rhs = next(xpr, s, p, state);
|
|
353
353
|
naryExpr.push(...op, rhs);
|
|
354
354
|
if(state.array)
|
|
355
355
|
lhs = [ lhs, ...op, rhs ];
|
|
@@ -162,14 +162,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
162
162
|
// Note that this must happen after struct flattening(flattenStructuredElement) - both fot elements and foreign keys.
|
|
163
163
|
// Return the newly generated foreign key element.
|
|
164
164
|
function createForeignKeyElement(assoc, assocName, foreignKey, artifact, artifactName, path) {
|
|
165
|
-
|
|
165
|
+
const result = {};
|
|
166
166
|
|
|
167
167
|
// FIXME: Duplicate code (postfix is added herein, can this be optimized?)
|
|
168
168
|
// Assemble foreign key element name from assoc name, '_' and foreign key name/alias
|
|
169
|
-
|
|
169
|
+
const foreignKeyElementName = `${assocName.replace(/\./g, pathDelimiter)}${pathDelimiter}${foreignKey.as || foreignKey.ref.join(pathDelimiter)}`;
|
|
170
170
|
|
|
171
171
|
|
|
172
|
-
|
|
172
|
+
const fkArtifact = inspectRef(path).art;
|
|
173
173
|
newForeignKey(fkArtifact, foreignKeyElementName);
|
|
174
174
|
|
|
175
175
|
function processAssociationOrComposition(fkArtifact,foreignKeyElementName) {
|
|
@@ -188,7 +188,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
const foreignKeyElement = createRealFK(fkArtifact, assoc, assocName, foreignKey, path, foreignKeyElementName);
|
|
192
192
|
|
|
193
193
|
// FIXME: must this code go into createRealFK?
|
|
194
194
|
// Not present in getForeignKeyArtifact
|
|
@@ -229,7 +229,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
229
229
|
// length: 42 },
|
|
230
230
|
// }
|
|
231
231
|
function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[]) {
|
|
232
|
-
|
|
232
|
+
const elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
|
|
233
233
|
// in case the element is of user defined type => take the definition of the type
|
|
234
234
|
// for type of 'x' -> elem.type is an object, not a string -> use directly
|
|
235
235
|
let elemType;
|
|
@@ -241,7 +241,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
241
241
|
// Collect all child elements (recursively) into 'result'
|
|
242
242
|
// TODO: Do not report collisions in the generated elements here, but instead
|
|
243
243
|
// leave that work to the receiver of this result
|
|
244
|
-
|
|
244
|
+
const result = Object.create(null);
|
|
245
245
|
const addGeneratedFlattenedElement = (e, eName) => {
|
|
246
246
|
if (result[eName])
|
|
247
247
|
error('name-duplicate-element', pathInCsn, { '#': 'flatten-element-gen', name: eName })
|
|
@@ -251,10 +251,10 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
251
251
|
forEach(struct, (childName, childElem) => {
|
|
252
252
|
if (isStructured(childElem)) {
|
|
253
253
|
// Descend recursively into structured children
|
|
254
|
-
|
|
255
|
-
for (
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
const grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
|
|
255
|
+
for (const grandChildName in grandChildElems) {
|
|
256
|
+
const flatElemName = elemName + pathDelimiter + grandChildName;
|
|
257
|
+
const flatElem = grandChildElems[grandChildName];
|
|
258
258
|
addGeneratedFlattenedElement(flatElem, flatElemName);
|
|
259
259
|
// TODO: check with values. In CSN such elements have only "@Core.Computed": true
|
|
260
260
|
// If the original element had a value, construct one for the flattened element
|
|
@@ -265,8 +265,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
265
265
|
}
|
|
266
266
|
} else {
|
|
267
267
|
// Primitive child - clone it and restore its cross references
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
const flatElemName = elemName + pathDelimiter + childName;
|
|
269
|
+
const flatElem = cloneCsnNonDict(childElem, { ...options, hiddenPropertiesToClone: [ '$structRef', '$fkExtensions' ] } );
|
|
270
270
|
// Don't take over notNull from leaf elements
|
|
271
271
|
delete flatElem.notNull;
|
|
272
272
|
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));
|
|
@@ -302,7 +302,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
302
302
|
// 'localized' is needed for OData
|
|
303
303
|
if(options.toOdata)
|
|
304
304
|
props.push('localized');
|
|
305
|
-
for (
|
|
305
|
+
for (const p of props) {
|
|
306
306
|
if (elem[p]) {
|
|
307
307
|
flatElem[p] = elem[p];
|
|
308
308
|
}
|
|
@@ -324,12 +324,14 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
324
324
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
325
325
|
* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
|
|
326
326
|
* @param {bool} [suspend] suspend flattening by caller until association path step
|
|
327
|
-
* @
|
|
327
|
+
* @param {int} [suspendPos] suspend if starting pos is lower or equal to suspendPos and suspend is true
|
|
328
|
+
* @param {bool} [revokeAtSuspendPos] revoke suspension after suspendPos (binding parameter path use case)
|
|
329
|
+
* @returns [string[], bool]
|
|
328
330
|
*/
|
|
329
|
-
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0) {
|
|
331
|
+
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), suspend=false, suspendPos=0, revokeAtSuspendPos=false) {
|
|
330
332
|
// Refs of length 1 cannot contain steps - no need to check
|
|
331
333
|
if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {
|
|
332
|
-
return ref;
|
|
334
|
+
return [ ref, false ];
|
|
333
335
|
}
|
|
334
336
|
|
|
335
337
|
const result = scope === '$self' ? [ref[0]] : [];
|
|
@@ -340,7 +342,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
340
342
|
scope = res.scope;
|
|
341
343
|
}
|
|
342
344
|
if (scope === '$magic')
|
|
343
|
-
return ref;
|
|
345
|
+
return [ ref, false ];
|
|
344
346
|
|
|
345
347
|
// Don't process a leading $self - it will a .art with .elements!
|
|
346
348
|
let i = scope === '$self' ? 1 : 0;
|
|
@@ -351,7 +353,8 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
351
353
|
effectiveType(links[i].art)[propName] ||
|
|
352
354
|
(resolvedLinkTypes.get(links[i])||{})[propName]);
|
|
353
355
|
|
|
354
|
-
|
|
356
|
+
let refChanged = false
|
|
357
|
+
let flattenStep = false;
|
|
355
358
|
suspend = !!art('items') || (suspend && i <= suspendPos);
|
|
356
359
|
for(; i < links.length; i++) {
|
|
357
360
|
|
|
@@ -363,14 +366,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
363
366
|
ref[i].id = result[result.length-1];
|
|
364
367
|
result[result.length-1] = ref[i];
|
|
365
368
|
}
|
|
369
|
+
refChanged = true;
|
|
366
370
|
// suspend flattening if the next path step has some 'items'
|
|
367
371
|
suspend = !!art('items');
|
|
368
372
|
}
|
|
369
373
|
else {
|
|
370
374
|
result.push(ref[i]);
|
|
375
|
+
suspend ||= !!art('items');
|
|
371
376
|
}
|
|
372
377
|
// revoke items suspension for next assoc step
|
|
373
|
-
if(suspend && art('target'))
|
|
378
|
+
if(suspend && art('target') || (revokeAtSuspendPos && i === suspendPos))
|
|
374
379
|
suspend = false;
|
|
375
380
|
|
|
376
381
|
flattenStep = !links[i].art?.kind &&
|
|
@@ -378,7 +383,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
378
383
|
!links[i].art?.from &&
|
|
379
384
|
art('elements');
|
|
380
385
|
}
|
|
381
|
-
return result;
|
|
386
|
+
return [ result, refChanged ];
|
|
382
387
|
}
|
|
383
388
|
|
|
384
389
|
/**
|
|
@@ -471,11 +476,11 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
471
476
|
// Add the created projection to the model and complain if artifact already exists.
|
|
472
477
|
// Used by Draft generation
|
|
473
478
|
function createExposingProjection(art, artName, projectionId, service) {
|
|
474
|
-
|
|
479
|
+
const projectionAbsoluteName = `${service}.${projectionId}`;
|
|
475
480
|
// Create elements matching the artifact's elements
|
|
476
|
-
|
|
481
|
+
const elements = Object.create(null);
|
|
477
482
|
art.elements && Object.entries(art.elements).forEach(([elemName, artElem]) => {
|
|
478
|
-
|
|
483
|
+
const elem = Object.assign({}, artElem);
|
|
479
484
|
// Transfer xrefs, that are redirected to the projection
|
|
480
485
|
// TODO: shall we remove the transferred elements from the original?
|
|
481
486
|
// if (artElem._xref) {
|
|
@@ -485,7 +490,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
485
490
|
elements[elemName] = elem;
|
|
486
491
|
});
|
|
487
492
|
|
|
488
|
-
|
|
493
|
+
const query = {
|
|
489
494
|
'SELECT': {
|
|
490
495
|
'from': {
|
|
491
496
|
'ref': [
|
|
@@ -495,13 +500,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
495
500
|
}
|
|
496
501
|
};
|
|
497
502
|
// Assemble the projection itself and add it into the model
|
|
498
|
-
|
|
503
|
+
const projection = {
|
|
499
504
|
'kind': 'entity',
|
|
500
505
|
projection: query.SELECT, // it is important that projection and query refer to the same object!
|
|
501
506
|
elements
|
|
502
507
|
};
|
|
503
508
|
// copy annotations from art to projection
|
|
504
|
-
for (
|
|
509
|
+
for (const a of Object.keys(art).filter(x => x.startsWith('@'))) {
|
|
505
510
|
projection[a] = art[a];
|
|
506
511
|
}
|
|
507
512
|
model.definitions[projectionAbsoluteName] = projection;
|
|
@@ -606,7 +611,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
606
611
|
if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
|
|
607
612
|
throw new ModelError('Expecting valid type name: ' + typeName);
|
|
608
613
|
}
|
|
609
|
-
|
|
614
|
+
const result = {
|
|
610
615
|
[elemName]: {
|
|
611
616
|
type: typeName
|
|
612
617
|
}
|
|
@@ -634,16 +639,16 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
634
639
|
// keys: [{ ref: ['id'] }]
|
|
635
640
|
// } }
|
|
636
641
|
function createAssociationElement(elemName, target, isManaged = false) {
|
|
637
|
-
|
|
638
|
-
|
|
642
|
+
const elem = createScalarElement(elemName, 'cds.Association', false, undefined);
|
|
643
|
+
const assoc = elem[elemName];
|
|
639
644
|
assoc.target = target;
|
|
640
645
|
|
|
641
646
|
if (isManaged) {
|
|
642
647
|
assoc.keys = [];
|
|
643
|
-
|
|
648
|
+
const targetArt = getCsnDef(target);
|
|
644
649
|
targetArt.elements && Object.entries(targetArt.elements).forEach(([keyElemName, keyElem]) => {
|
|
645
650
|
if (keyElem.key) {
|
|
646
|
-
|
|
651
|
+
const foreignKey = createForeignKey(keyElemName, keyElem);
|
|
647
652
|
addForeignKey(foreignKey, assoc);
|
|
648
653
|
}
|
|
649
654
|
});
|
|
@@ -700,7 +705,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
700
705
|
if (!artifact.elements) {
|
|
701
706
|
throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
|
|
702
707
|
}
|
|
703
|
-
|
|
708
|
+
const elemName = Object.keys(elem)[0];
|
|
704
709
|
// Element must not exist
|
|
705
710
|
if (artifact.elements[elemName]) {
|
|
706
711
|
let path = null;
|
|
@@ -737,7 +742,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
737
742
|
error(null, path, { name: elementName }, 'Generated element $(NAME) conflicts with existing element');
|
|
738
743
|
}
|
|
739
744
|
|
|
740
|
-
|
|
745
|
+
const result = Object.create(null);
|
|
741
746
|
result[elementName] = {};
|
|
742
747
|
elem && Object.entries(elem).forEach(([prop, value]) => {
|
|
743
748
|
result[elementName][prop] = value;
|
|
@@ -750,13 +755,13 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
750
755
|
// of type name 'paramTypeName'
|
|
751
756
|
function createAction(actionName, returnTypeName = undefined, paramName = undefined, paramTypeName = undefined) {
|
|
752
757
|
// Assemble the action
|
|
753
|
-
|
|
758
|
+
const result = {
|
|
754
759
|
[actionName]: {
|
|
755
760
|
kind: 'action'
|
|
756
761
|
}
|
|
757
762
|
};
|
|
758
763
|
|
|
759
|
-
|
|
764
|
+
const action = result[actionName];
|
|
760
765
|
|
|
761
766
|
if (returnTypeName) {
|
|
762
767
|
if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
|
|
@@ -791,7 +796,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
791
796
|
artifact.actions = Object.create(null);
|
|
792
797
|
}
|
|
793
798
|
|
|
794
|
-
|
|
799
|
+
const actionName = Object.keys(action)[0];
|
|
795
800
|
// Element must not exist
|
|
796
801
|
if (!artifact.actions[actionName]) {
|
|
797
802
|
// Add the action
|
|
@@ -808,7 +813,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
808
813
|
* second field if it has @cds.valid.to. Default value is [] for each field.
|
|
809
814
|
*/
|
|
810
815
|
function extractValidFromToKeyElement(element, path) {
|
|
811
|
-
|
|
816
|
+
const validFroms = [], validTos = [], validKeys = [];
|
|
812
817
|
if (hasAnnotationValue(element, '@cds.valid.from')) {
|
|
813
818
|
validFroms.push({ element, path: [...path] });
|
|
814
819
|
}
|
|
@@ -878,7 +883,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
878
883
|
*/
|
|
879
884
|
function recurseElements(artifact, path, callback) {
|
|
880
885
|
callback(artifact, path);
|
|
881
|
-
|
|
886
|
+
const elements = artifact.elements;
|
|
882
887
|
if (elements) {
|
|
883
888
|
path.push('elements', null);
|
|
884
889
|
forEach(elements, (name, obj) => {
|
|
@@ -892,7 +897,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
892
897
|
|
|
893
898
|
// Rename annotation 'fromName' in 'node' to 'toName' (both names including '@')
|
|
894
899
|
function renameAnnotation(node, fromName, toName) {
|
|
895
|
-
|
|
900
|
+
const annotation = node && node[fromName];
|
|
896
901
|
// Sanity checks
|
|
897
902
|
if (!fromName.startsWith('@')) {
|
|
898
903
|
throw new CompilerAssertion('Annotation name should start with "@": ' + fromName);
|
|
@@ -1039,7 +1044,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1039
1044
|
if(art) {
|
|
1040
1045
|
if(art && !((art.items && art.items.elements) || art.elements)) {
|
|
1041
1046
|
if(followMgdAssoc && art.target && art.keys) {
|
|
1042
|
-
|
|
1047
|
+
const rc = [];
|
|
1043
1048
|
for(const k of art.keys) {
|
|
1044
1049
|
const nps = { ref: k.ref.map(p => fullRef ? { id: p } : p ) };
|
|
1045
1050
|
setProp(nps, '_art', k._art);
|
|
@@ -1057,7 +1062,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1057
1062
|
}
|
|
1058
1063
|
const elements = art.items && art.items.elements || art.elements;
|
|
1059
1064
|
if(elements) {
|
|
1060
|
-
|
|
1065
|
+
const rc = []
|
|
1061
1066
|
Object.entries(elements).forEach(([en, elt]) => {
|
|
1062
1067
|
const nps = { ref: [ (fullRef ? { id: en, _art: elt } : en )] };
|
|
1063
1068
|
setProp(nps, '_art', elt);
|
|
@@ -1106,7 +1111,7 @@ function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
|
|
|
1106
1111
|
Flattening stops on all non-structured types.
|
|
1107
1112
|
*/
|
|
1108
1113
|
function expand(expr, location) {
|
|
1109
|
-
|
|
1114
|
+
const rc = [];
|
|
1110
1115
|
for(let i = 0; i < expr.length; i++)
|
|
1111
1116
|
{
|
|
1112
1117
|
if(Array.isArray(expr[i]))
|
|
@@ -29,10 +29,10 @@ function translateAssocsToJoinsCSN(csn, options){
|
|
|
29
29
|
// Use the effective elements list as columns
|
|
30
30
|
forEachDefinition(model, art => {
|
|
31
31
|
if (art.$queries) {
|
|
32
|
-
for (
|
|
32
|
+
for (const query of art.$queries) {
|
|
33
33
|
query.columns = Object.values(query.elements);
|
|
34
34
|
// TODO: Remove viaAll
|
|
35
|
-
for (
|
|
35
|
+
for (const elemName in query.elements) {
|
|
36
36
|
const elem = query.elements[elemName];
|
|
37
37
|
if (elem.$inferred === '*')
|
|
38
38
|
delete elem.$inferred;
|
|
@@ -172,8 +172,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
172
172
|
// Transform each FROM table path into a join tree and attach the tree to the path object
|
|
173
173
|
function createInnerJoins(fromPathNode, env)
|
|
174
174
|
{
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
const fqat = env.lead.$tableAliases[fromPathNode.name.id].$fqat;
|
|
176
|
+
const joinTree = createJoinTree(env, undefined, fqat, 'inner', '$fqat', undefined);
|
|
177
177
|
|
|
178
178
|
replaceTableAliasInPlace( fromPathNode, joinTree);
|
|
179
179
|
}
|
|
@@ -185,11 +185,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
185
185
|
{
|
|
186
186
|
env.lead = query;
|
|
187
187
|
let joinTree = query.from;
|
|
188
|
-
for(
|
|
188
|
+
for(const tan in query.$tableAliases)
|
|
189
189
|
{
|
|
190
190
|
if(query.$tableAliases[tan].kind !== '$self') // don't drive into $projection/$self tableAlias (yet)
|
|
191
191
|
{
|
|
192
|
-
|
|
192
|
+
const ta = query.$tableAliases[tan];
|
|
193
193
|
joinTree = createJoinTree(env, joinTree, ta.$qat, 'left', '$qat', ta.$QA);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
@@ -207,9 +207,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
207
207
|
*/
|
|
208
208
|
function createQAForFromClauseSubQuery(query, env)
|
|
209
209
|
{
|
|
210
|
-
for (
|
|
210
|
+
for (const taName in query.$tableAliases) {
|
|
211
211
|
if (query.$tableAliases[taName].kind !== '$self') {
|
|
212
|
-
|
|
212
|
+
const ta = query.$tableAliases[taName];
|
|
213
213
|
if(!ta.$QA) {
|
|
214
214
|
let alias = taName;
|
|
215
215
|
if (ta.name.$inferred === '$internal') {
|
|
@@ -306,8 +306,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
306
306
|
if(env.location === 'onCondFrom')
|
|
307
307
|
{
|
|
308
308
|
if(checkPathDictionary(pathNode, env)) {
|
|
309
|
-
|
|
310
|
-
|
|
309
|
+
const [ tableAlias, tail ] = constructTableAliasAndTailPath(pathNode.path);
|
|
310
|
+
const pathStr = translateONCondPath(tail).map(ps => ps.id).join(pathDelimiter);
|
|
311
311
|
replaceNodeContent(pathNode,
|
|
312
312
|
constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]));
|
|
313
313
|
}
|
|
@@ -329,11 +329,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
329
329
|
tail = pathNode.path;
|
|
330
330
|
// if tail.length > 1, search bottom up for QA
|
|
331
331
|
// default to rootQA, _parent.$QA has precedence
|
|
332
|
-
|
|
332
|
+
const [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
|
|
333
333
|
if(!QA) {
|
|
334
334
|
error(null, pathNode.$location,
|
|
335
335
|
{ name: pathName(pathNode.path) },
|
|
336
|
-
'
|
|
336
|
+
'Debug me: No QA found for generic path rewriting in $(NAME)')
|
|
337
337
|
return;
|
|
338
338
|
}
|
|
339
339
|
// if the found QA is the mixin QA and if the path length is one,
|
|
@@ -360,7 +360,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
360
360
|
// const revealInternalProperties = require('../model/revealInternalProperties.js');
|
|
361
361
|
// console.log('++++++++ Path tail: ', revealInternalProperties(tail[tail.length-1]._artifact));
|
|
362
362
|
// console.log('******** Flat FKs\n', tail[i]._artifact.$flatSrcFKs.map(f => revealInternalProperties(f._artifact)));
|
|
363
|
-
throw new CompilerAssertion('
|
|
363
|
+
throw new CompilerAssertion('Debug me: No flat FK found for FK rewriting');
|
|
364
364
|
}
|
|
365
365
|
// replace tail path with flattened foreign key including prefix
|
|
366
366
|
tail.splice(i, tail.length, fk);
|
|
@@ -431,7 +431,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
431
431
|
*/
|
|
432
432
|
function createJoinTree(env, joinTree, parentQat, joinType, qatAttribName, lastAssocQA)
|
|
433
433
|
{
|
|
434
|
-
for(
|
|
434
|
+
for(const childQatId in parentQat)
|
|
435
435
|
{
|
|
436
436
|
const childQat = parentQat[childQatId];
|
|
437
437
|
|
|
@@ -454,10 +454,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
454
454
|
if(childQat._filter)
|
|
455
455
|
{
|
|
456
456
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
457
|
-
|
|
457
|
+
const filter = childQat._filter;
|
|
458
458
|
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
459
459
|
return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
|
|
460
|
-
/* filterPath=> */ pathNode.path ];
|
|
460
|
+
/* filterPath=> */ pathNode.path ];
|
|
461
461
|
}, env);
|
|
462
462
|
|
|
463
463
|
if(!env.lead.$startFilters)
|
|
@@ -486,28 +486,28 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
486
486
|
|
|
487
487
|
function createJoinQA(joinType, lhs, rhs, assocQAT, assocSourceQA, env)
|
|
488
488
|
{
|
|
489
|
-
|
|
489
|
+
const node = { op: { val: 'join' }, join: { val: joinType }, args: [ lhs, rhs ] };
|
|
490
490
|
const assoc = assocQAT._origin;
|
|
491
491
|
if(isBetaEnabled(options, 'mapAssocToJoinCardinality')) {
|
|
492
492
|
node.cardinality = mapAssocToJoinCardinality(assoc);
|
|
493
493
|
}
|
|
494
494
|
// 'path steps' for the src/tgt table alias
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
const srcTableAlias = constructTableAliasPathStep(assocSourceQA);
|
|
496
|
+
const tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);
|
|
497
497
|
|
|
498
498
|
node.on = createOnCondition(assoc, srcTableAlias, tgtTableAlias, options.tenantDiscriminator);
|
|
499
499
|
|
|
500
500
|
if(assocQAT._filter)
|
|
501
501
|
{
|
|
502
502
|
// Filter conditions are unique for each JOIN, they don't need to be copied
|
|
503
|
-
|
|
503
|
+
const filter = assocQAT._filter;
|
|
504
504
|
rewritePathsInFilterExpression(filter, function(pathNode) {
|
|
505
505
|
return [ tgtTableAlias, pathNode.path ];
|
|
506
506
|
}, env);
|
|
507
507
|
|
|
508
508
|
// If toplevel ON cond op is AND add filter condition to the args array,
|
|
509
509
|
// create a new toplevel AND op otherwise
|
|
510
|
-
|
|
510
|
+
const onCond = (Array.isArray(node.on) ? node.on[0] : node.on);
|
|
511
511
|
|
|
512
512
|
if(onCond.op.val === 'and')
|
|
513
513
|
onCond.args.push(parenthesise(filter));
|
|
@@ -567,7 +567,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
567
567
|
// produce the ON condition for a given association
|
|
568
568
|
function createOnCondition(assoc, srcAlias, tgtAlias, compareTenants)
|
|
569
569
|
{
|
|
570
|
-
|
|
570
|
+
const prefixes = [ assoc.name.id ];
|
|
571
571
|
/* This is no art and can be removed once ON cond for published
|
|
572
572
|
and renamed backlink assocs are publicly available. Example:
|
|
573
573
|
|
|
@@ -600,12 +600,12 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
600
600
|
Put all src/tgt path siblings into the EQ term and create the proper path objects
|
|
601
601
|
with the src/tgt table alias path steps in front.
|
|
602
602
|
*/
|
|
603
|
-
|
|
603
|
+
const args = compareTenants && addTenantComparison(assoc) || [];
|
|
604
604
|
for(let i = 0; i < assoc.$flatSrcFKs.length; i++)
|
|
605
605
|
{
|
|
606
606
|
args.push({op: {val: '=' },
|
|
607
607
|
args: [ constructPathNode( [ srcAlias, prefixFK(assoc.$elementPrefix, assoc.$flatSrcFKs[i]) ] ),
|
|
608
|
-
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
|
|
608
|
+
constructPathNode( [ tgtAlias, assoc.$flatTgtFKs[i] ] ) ] });
|
|
609
609
|
}
|
|
610
610
|
return parenthesise((args.length > 1 ? { op: { val: 'and' }, args: [ ...args.map(parenthesise) ] } : args[0] ));
|
|
611
611
|
}
|
|
@@ -761,7 +761,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
761
761
|
// If this is an ordinary expression, clone it and mangle its arguments
|
|
762
762
|
// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
|
|
763
763
|
if(expr.op) {
|
|
764
|
-
|
|
764
|
+
const x = clone(expr);
|
|
765
765
|
if(expr.args)
|
|
766
766
|
x.args = expr.args.map(cloneOnCondition);
|
|
767
767
|
return x;
|
|
@@ -845,7 +845,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
845
845
|
$projection must be removed from $projection.yid (get's aliased with the mixinAssocQAT.$QA)
|
|
846
846
|
*/
|
|
847
847
|
if(env.assocStack.length < 2) {
|
|
848
|
-
|
|
848
|
+
const value = env.lead.elements[path[0].id].value;
|
|
849
849
|
/*
|
|
850
850
|
If the value is an expression in the select block, return the unmodified
|
|
851
851
|
expression. rewriteGenericPaths will check and rewrite these paths later
|
|
@@ -957,7 +957,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
957
957
|
if(head.id === env.assocStack.id()) {
|
|
958
958
|
// source side from view point of view (target side from forward point of view)
|
|
959
959
|
path = tail; // pop assoc step
|
|
960
|
-
|
|
960
|
+
const elt = env.lead._combined[path[0].id];
|
|
961
961
|
|
|
962
962
|
if(elt) {
|
|
963
963
|
if(Array.isArray(elt)) {
|
|
@@ -1008,7 +1008,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1008
1008
|
path = translateONCondPath(path, !hasDollarSelfPrefix ? assoc.$elementPrefix : undefined);
|
|
1009
1009
|
}
|
|
1010
1010
|
}
|
|
1011
|
-
|
|
1011
|
+
const pathStr = path.map(ps => ps.id).join(pathDelimiter);
|
|
1012
1012
|
return constructPathNode([ tableAlias, { id: pathStr, _artifact: pathNode._artifact } ]);
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
@@ -1209,11 +1209,11 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1209
1209
|
*/
|
|
1210
1210
|
function constructTableAliasAndTailPath(path, navigation=undefined)
|
|
1211
1211
|
{
|
|
1212
|
-
|
|
1212
|
+
const [head, ...tail] = path;
|
|
1213
1213
|
if(navigation === undefined)
|
|
1214
1214
|
navigation = head._navigation;
|
|
1215
1215
|
|
|
1216
|
-
|
|
1216
|
+
const QA = navigation.$QA || navigation._parent.$QA;
|
|
1217
1217
|
|
|
1218
1218
|
// First path step is table alias, use and pop it off
|
|
1219
1219
|
if(navigation.$QA && tail.length > 0)
|
|
@@ -1251,7 +1251,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1251
1251
|
{
|
|
1252
1252
|
if(!pathStr)
|
|
1253
1253
|
pathStr = '';
|
|
1254
|
-
|
|
1254
|
+
const assocStep = path.find(ps => {
|
|
1255
1255
|
if(pathStr.length > 0)
|
|
1256
1256
|
pathStr += pathDelimiter;
|
|
1257
1257
|
pathStr += ps.id;
|
|
@@ -1303,7 +1303,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1303
1303
|
pathStr += fk.name.id;
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
1306
|
-
|
|
1306
|
+
const tail = path.slice(path.indexOf(fkPs)+1);
|
|
1307
1307
|
// If foreign key is an association itself, apply substituteFKAliasForPath on tail
|
|
1308
1308
|
if(fk && fk.targetElement._artifact.target && tail.length)
|
|
1309
1309
|
return substituteFKAliasForPath(fk.targetElement, tail, pathStr);
|
|
@@ -1377,7 +1377,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1377
1377
|
if(ps._artifact && ps._artifact.target)
|
|
1378
1378
|
{
|
|
1379
1379
|
// if this is not the last path step, complain
|
|
1380
|
-
|
|
1380
|
+
const la1 = pathDict.path[pathDict.path.indexOf(ps)+1];
|
|
1381
1381
|
if(la1) {
|
|
1382
1382
|
if(ps._artifact.on)
|
|
1383
1383
|
{
|
|
@@ -1438,7 +1438,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1438
1438
|
*/
|
|
1439
1439
|
function mergePathIntoQAT(pathDict, env)
|
|
1440
1440
|
{
|
|
1441
|
-
|
|
1441
|
+
const path = pathDict.path;
|
|
1442
1442
|
|
|
1443
1443
|
if(path.length === 0)
|
|
1444
1444
|
return;
|
|
@@ -1505,7 +1505,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1505
1505
|
if(qatParent == undefined)
|
|
1506
1506
|
throw new CompilerAssertion('table alias/qathost not found for path: ' + pathAsStr(path));
|
|
1507
1507
|
|
|
1508
|
-
|
|
1508
|
+
const rootQat = qatParent;
|
|
1509
1509
|
|
|
1510
1510
|
// Create the very first QAT if it doesn't exist
|
|
1511
1511
|
// (filter condition for table alias prefix not allowed)
|
|
@@ -1526,7 +1526,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1526
1526
|
}
|
|
1527
1527
|
if(pathStep.args) {
|
|
1528
1528
|
// sort named arguments
|
|
1529
|
-
|
|
1529
|
+
const sortedNamedArgs = Object.create(null);
|
|
1530
1530
|
Object.keys(pathStep.args).sort().forEach(p => {
|
|
1531
1531
|
sortedNamedArgs[p] = compactExpr(pathStep.args[p]);
|
|
1532
1532
|
})
|
|
@@ -1687,10 +1687,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1687
1687
|
// eslint-disable-next-line no-unused-vars
|
|
1688
1688
|
function printPath(pathDict, env)
|
|
1689
1689
|
{
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1690
|
+
const alias = (pathDict.name && pathDict.name.id) || '<undefined>'
|
|
1691
|
+
const path = pathDict.path;
|
|
1692
|
+
const s = pathAsStr(path, '"');
|
|
1693
|
+
const me = env.lead && (env.lead.name.id || env.lead.op);
|
|
1694
1694
|
// eslint-disable-next-line no-console
|
|
1695
1695
|
console.log(me + ': ' + env.location + ': ' + s + ' alias: ' + alias);
|
|
1696
1696
|
}
|
|
@@ -1708,9 +1708,9 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1708
1708
|
else
|
|
1709
1709
|
newObj = {};
|
|
1710
1710
|
|
|
1711
|
-
|
|
1712
|
-
for (
|
|
1713
|
-
|
|
1711
|
+
const props = Object.getOwnPropertyNames(obj); // clone own properties only, not inherited ones
|
|
1712
|
+
for (const p of props) {
|
|
1713
|
+
const pd = Object.getOwnPropertyDescriptor(obj, p);
|
|
1714
1714
|
if (pd && pd.enumerable === false)
|
|
1715
1715
|
{
|
|
1716
1716
|
pd.value = obj[p]; // don't copy references
|
|
@@ -1740,10 +1740,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1740
1740
|
*/
|
|
1741
1741
|
function constructPathNode(pathSteps, alias, rewritten=true)
|
|
1742
1742
|
{
|
|
1743
|
-
|
|
1743
|
+
const node = {
|
|
1744
1744
|
$rewritten: rewritten,
|
|
1745
1745
|
path : pathSteps.map(p => {
|
|
1746
|
-
|
|
1746
|
+
const o = {};
|
|
1747
1747
|
Object.keys(p).forEach(k => {
|
|
1748
1748
|
if(!(rewritten && ['_'].includes(k[0])))
|
|
1749
1749
|
o[k] = p[k];
|
|
@@ -1784,7 +1784,7 @@ function walkQuery(query, env)
|
|
|
1784
1784
|
env.location = 'select';
|
|
1785
1785
|
if(env.walkover[env.location])
|
|
1786
1786
|
{
|
|
1787
|
-
for(
|
|
1787
|
+
for(const alias in query.elements)
|
|
1788
1788
|
walk(query.elements[alias].value, env);
|
|
1789
1789
|
|
|
1790
1790
|
env.location = 'Where';
|
|
@@ -1809,7 +1809,7 @@ function walkQuery(query, env)
|
|
|
1809
1809
|
|
|
1810
1810
|
function walkFrom(fromBlock)
|
|
1811
1811
|
{
|
|
1812
|
-
|
|
1812
|
+
const aliases = [];
|
|
1813
1813
|
env.position = fromBlock;
|
|
1814
1814
|
if(fromBlock)
|
|
1815
1815
|
{
|