@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -9,12 +9,13 @@ const { setProp } = require('../base/model');
|
|
|
9
9
|
const { csnRefs } = require('../model/csnRefs');
|
|
10
10
|
|
|
11
11
|
const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
|
|
12
|
-
const { cloneCsn, getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
12
|
+
const { cloneCsn, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
|
|
13
|
+
const { ModelError } = require("../base/error");
|
|
13
14
|
|
|
14
15
|
// Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).
|
|
15
16
|
// Use 'pathDelimiter' for flattened names (e.g. of struct elements or foreign key elements).
|
|
16
17
|
// 'model' is compacted new style CSN
|
|
17
|
-
// TODO: Error and warnings handling with compacted CSN? - currently just throw new
|
|
18
|
+
// TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
|
|
18
19
|
// TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
|
|
19
20
|
function getTransformers(model, options, pathDelimiter = '_') {
|
|
20
21
|
const { error, warning, info } = makeMessageFunction(model, options);
|
|
@@ -108,7 +109,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
108
109
|
// Transfer selected type properties from target key element
|
|
109
110
|
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.
|
|
110
111
|
for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) {
|
|
111
|
-
if (fkArtifact[prop]
|
|
112
|
+
if (fkArtifact[prop] !== undefined) {
|
|
112
113
|
foreignKeyElement[prop] = fkArtifact[prop];
|
|
113
114
|
}
|
|
114
115
|
}
|
|
@@ -118,7 +119,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
118
119
|
|
|
119
120
|
// If the association is non-fkArtifact resp. key, so should be the foreign key field
|
|
120
121
|
for (const prop of ['notNull', 'key']) {
|
|
121
|
-
if (assoc[prop]
|
|
122
|
+
if (assoc[prop] !== undefined) {
|
|
122
123
|
foreignKeyElement[prop] = assoc[prop];
|
|
123
124
|
}
|
|
124
125
|
}
|
|
@@ -246,6 +247,8 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
246
247
|
const struct = elemType ? elemType.elements : elem.elements;
|
|
247
248
|
|
|
248
249
|
// Collect all child elements (recursively) into 'result'
|
|
250
|
+
// TODO: Do not report collisions in the generated elements here, but instead
|
|
251
|
+
// leave that work to the receiver of this result
|
|
249
252
|
let result = Object.create(null);
|
|
250
253
|
const addGeneratedFlattenedElement = (e, eName) => {
|
|
251
254
|
if(result[eName]){
|
|
@@ -286,7 +289,11 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
286
289
|
// Copy annotations from struct (not overwriting, because deep annotations should have precedence)
|
|
287
290
|
copyAnnotations(elem, flatElem, false);
|
|
288
291
|
// Copy selected type properties
|
|
289
|
-
|
|
292
|
+
const props = ['key', 'virtual', 'masked', 'viaAll'];
|
|
293
|
+
// 'localized' is needed for OData
|
|
294
|
+
if(options.toOdata)
|
|
295
|
+
props.push('localized');
|
|
296
|
+
for (let p of props) {
|
|
290
297
|
if (elem[p]) {
|
|
291
298
|
flatElem[p] = elem[p];
|
|
292
299
|
}
|
|
@@ -302,7 +309,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
302
309
|
* [ (Entity), (struct1), (struct2), (assoc), (elem) ] should result in
|
|
303
310
|
* [ (Entity), (struct1_struct2_assoc), (elem) ]
|
|
304
311
|
*
|
|
305
|
-
* @param {string[]} ref
|
|
312
|
+
* @param {string[]} ref
|
|
306
313
|
* @param {CSN.Path} path CSN path to the ref
|
|
307
314
|
* @param {object[]} [links] Pre-resolved links for the given ref - if not provided, will be calculated JIT
|
|
308
315
|
* @param {string} [scope] Pre-resolved scope for the given ref - if not provided, will be calculated JIT
|
|
@@ -313,12 +320,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
313
320
|
// Refs of length 1 cannot contain steps - no need to check
|
|
314
321
|
if (ref.length < 2) {
|
|
315
322
|
return ref;
|
|
323
|
+
} else if(scope === '$self' && ref.length === 2) {
|
|
324
|
+
return ref;
|
|
316
325
|
}
|
|
317
326
|
|
|
318
327
|
return flatten(ref, path);
|
|
319
328
|
|
|
320
329
|
function flatten(ref, path) {
|
|
321
|
-
let result = [];
|
|
330
|
+
let result = scope === '$self' ? [ref[0]] : [];
|
|
322
331
|
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
|
|
323
332
|
if(!links && !scope) { // calculate JIT if not supplied
|
|
324
333
|
const res = inspectRef(path);
|
|
@@ -327,23 +336,25 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
327
336
|
}
|
|
328
337
|
if (scope === '$magic')
|
|
329
338
|
return ref;
|
|
339
|
+
// Don't process a leading $self - it will a .art with .elements!
|
|
340
|
+
const startIndex = scope === '$self' ? 1 : 0;
|
|
330
341
|
let flattenStep = false;
|
|
331
|
-
links.
|
|
342
|
+
for(let i = startIndex; i < links.length; i++) {
|
|
343
|
+
const value = links[i];
|
|
332
344
|
if (flattenStep) {
|
|
333
|
-
result[result.length - 1] += pathDelimiter + (ref[
|
|
345
|
+
result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
|
|
334
346
|
// if we had a filter or args, we had an assoc so this step is done
|
|
335
347
|
// we then keep along the filter/args by updating the id of the current ref
|
|
336
|
-
if(ref[
|
|
337
|
-
ref[
|
|
338
|
-
result[result.length-1] = ref[
|
|
348
|
+
if(ref[i].id) {
|
|
349
|
+
ref[i].id = result[result.length-1];
|
|
350
|
+
result[result.length-1] = ref[i];
|
|
339
351
|
}
|
|
340
352
|
}
|
|
341
353
|
else {
|
|
342
|
-
result.push(ref[
|
|
354
|
+
result.push(ref[i]);
|
|
343
355
|
}
|
|
344
|
-
|
|
345
356
|
flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
|
|
346
|
-
}
|
|
357
|
+
}
|
|
347
358
|
|
|
348
359
|
return result;
|
|
349
360
|
}
|
|
@@ -351,7 +362,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
351
362
|
|
|
352
363
|
/**
|
|
353
364
|
* Copy properties of the referenced type, but don't resolve to the final base type.
|
|
354
|
-
*
|
|
365
|
+
*
|
|
355
366
|
* Do not copy the length if it was just set via the default-value.
|
|
356
367
|
*
|
|
357
368
|
* @param {any} node Node to copy to
|
|
@@ -387,9 +398,9 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
387
398
|
* also unravel derived enum types, i.e. take the final base type of the enum's base type.
|
|
388
399
|
* Similar with associations and compositions (we probably need a _baseType link)
|
|
389
400
|
*
|
|
390
|
-
* @param {CSN.Artifact} node
|
|
401
|
+
* @param {CSN.Artifact} node
|
|
391
402
|
* @param {WeakMap} [resolved] WeakMap containing already resolved refs
|
|
392
|
-
* @param {boolean} [keepLocalized=false]
|
|
403
|
+
* @param {boolean} [keepLocalized=false] Whether to clone .localized from a type def
|
|
393
404
|
* @returns {void}
|
|
394
405
|
*/
|
|
395
406
|
function toFinalBaseType(node, resolved, keepLocalized=false) {
|
|
@@ -406,7 +417,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
406
417
|
delete node.type; // delete the type reference as edm processing does not expect it
|
|
407
418
|
} else if(finalBaseType.items) {
|
|
408
419
|
// This changes the order - to be discussed!
|
|
409
|
-
node.items = cloneCsn(finalBaseType, options)
|
|
420
|
+
node.items = cloneCsn(finalBaseType.items, options); // copy items
|
|
410
421
|
delete node.type;
|
|
411
422
|
} else {
|
|
412
423
|
node.type=finalBaseType;
|
|
@@ -420,20 +431,23 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
420
431
|
let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
|
|
421
432
|
// Nothing to do if type is an array or a struct type
|
|
422
433
|
if (typeDef.items || typeDef.elements) {
|
|
434
|
+
// Expand structured types on entity elements for flat OData
|
|
423
435
|
if(!(options.transformation === 'hdbcds' || options.toSql))
|
|
424
436
|
return;
|
|
425
437
|
|
|
426
438
|
// cloneCsn only works correctly if we start "from the top"
|
|
427
|
-
const
|
|
428
|
-
// With hdbcds-hdbcds, don't resolve structured types - but
|
|
439
|
+
const cloneTypeDef = cloneCsn(typeDef, options);
|
|
440
|
+
// With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.
|
|
429
441
|
if(typeDef.items) {
|
|
430
442
|
delete node.type;
|
|
431
|
-
|
|
443
|
+
if(!node.items)
|
|
444
|
+
Object.assign(node, {items: cloneTypeDef.items});
|
|
432
445
|
}
|
|
433
446
|
if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {
|
|
434
447
|
if(!typeDef.items)
|
|
435
448
|
delete node.type;
|
|
436
|
-
|
|
449
|
+
if(!node.elements)
|
|
450
|
+
Object.assign(node, {elements: cloneTypeDef.elements});
|
|
437
451
|
}
|
|
438
452
|
|
|
439
453
|
|
|
@@ -441,7 +455,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
441
455
|
}
|
|
442
456
|
// if the declared element is an enum, these values are with priority
|
|
443
457
|
if (!node.enum && typeDef.enum) {
|
|
444
|
-
const clone =
|
|
458
|
+
const clone = cloneCsnDictionary(typeDef.enum, options);
|
|
445
459
|
Object.assign(node, { enum: clone });
|
|
446
460
|
}
|
|
447
461
|
if (node.length === undefined && typeDef.length !== undefined)
|
|
@@ -593,7 +607,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
593
607
|
// elemName typeName isKey defaultVal notNull
|
|
594
608
|
function createScalarElement(elemName, typeName, isKey = false, defaultVal = undefined, notNull=false) {
|
|
595
609
|
if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
|
|
596
|
-
throw new
|
|
610
|
+
throw new ModelError('Expecting valid type name: ' + typeName);
|
|
597
611
|
}
|
|
598
612
|
let result = {
|
|
599
613
|
[elemName]: {
|
|
@@ -684,7 +698,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
684
698
|
function addForeignKey(foreignKey, elem) {
|
|
685
699
|
// Sanity checks
|
|
686
700
|
if (!elem.target || !elem.keys) {
|
|
687
|
-
throw new
|
|
701
|
+
throw new ModelError('Expecting managed association element with foreign keys');
|
|
688
702
|
}
|
|
689
703
|
|
|
690
704
|
// Add the foreign key
|
|
@@ -703,7 +717,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
703
717
|
function addElement(elem, artifact, artifactName) {
|
|
704
718
|
// Sanity check
|
|
705
719
|
if (!artifact.elements) {
|
|
706
|
-
throw new
|
|
720
|
+
throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
|
|
707
721
|
}
|
|
708
722
|
let elemName = Object.keys(elem)[0];
|
|
709
723
|
// Element must not exist
|
|
@@ -734,7 +748,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
734
748
|
*/
|
|
735
749
|
function copyAndAddElement(elem, artifact, artifactName, elementName) {
|
|
736
750
|
if (!artifact.elements) {
|
|
737
|
-
throw new
|
|
751
|
+
throw new ModelError('Expected structured artifact');
|
|
738
752
|
}
|
|
739
753
|
// Must not already have such an element
|
|
740
754
|
if (artifact.elements[elementName]) {
|
|
@@ -765,14 +779,14 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
765
779
|
|
|
766
780
|
if (returnTypeName) {
|
|
767
781
|
if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
|
|
768
|
-
throw new
|
|
782
|
+
throw new ModelError('Expecting valid return type name: ' + returnTypeName);
|
|
769
783
|
action.returns = { type: returnTypeName };
|
|
770
784
|
}
|
|
771
785
|
|
|
772
786
|
// Add parameter if provided
|
|
773
787
|
if (paramName && paramTypeName) {
|
|
774
788
|
if (!isBuiltinType(paramTypeName) && !model.definitions[paramTypeName])
|
|
775
|
-
throw new
|
|
789
|
+
throw new ModelError('Expecting valid parameter type name: ' + paramTypeName);
|
|
776
790
|
|
|
777
791
|
action.params = Object.create(null);
|
|
778
792
|
action.params[paramName] = {
|
|
@@ -865,7 +879,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
865
879
|
function checkMultipleAssignments(array, annoName, artifact, artifactName, err = true) {
|
|
866
880
|
if (array.length > 1) {
|
|
867
881
|
const loc = ['definitions', artifactName];
|
|
868
|
-
if (err
|
|
882
|
+
if (err === true) {
|
|
869
883
|
error(null, loc, { anno: annoName }, `Annotation $(ANNO) must be assigned only once`);
|
|
870
884
|
} else {
|
|
871
885
|
warning(null, loc, { anno: annoName },`Annotation $(ANNO) must be assigned only once`);
|
|
@@ -1084,7 +1098,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1084
1098
|
* Expand structured expression arguments to flat reference paths.
|
|
1085
1099
|
* Structured elements are real sub element lists and managed associations.
|
|
1086
1100
|
* All unmanaged association definitions are rewritten if applicable (elements/mixins).
|
|
1087
|
-
* Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
|
|
1101
|
+
* Also, HAVING and WHERE clauses are rewritten. We also check for infix filters and
|
|
1088
1102
|
* .xpr in columns.
|
|
1089
1103
|
*
|
|
1090
1104
|
* @todo Check if can be skipped for abstract entity and or cds.persistence.skip ?
|
|
@@ -1105,7 +1119,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
1105
1119
|
'xpr': (parent, name, xpr, path) => {
|
|
1106
1120
|
parent.xpr = expand(parent.xpr, path.concat(name));
|
|
1107
1121
|
}
|
|
1108
|
-
},
|
|
1122
|
+
}, [], options);
|
|
1109
1123
|
|
|
1110
1124
|
/*
|
|
1111
1125
|
flatten structured leaf types and return array of paths
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { setProp, forEachGeneric, forEachDefinition, isBetaEnabled } = require('../base/model');
|
|
4
|
-
|
|
4
|
+
const { makeMessageFunction } = require('../base/messages');
|
|
5
5
|
const { recompileX } = require('../compiler/index');
|
|
6
|
-
|
|
6
|
+
const { linkToOrigin, pathName } = require('../compiler/utils');
|
|
7
7
|
const {compactModel, compactExpr} = require('../json/to-csn');
|
|
8
8
|
const { deduplicateMessages } = require('../base/messages');
|
|
9
9
|
const { timetrace } = require('../utils/timetrace');
|
|
@@ -120,7 +120,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
120
120
|
/*
|
|
121
121
|
Setup QAs for mixins
|
|
122
122
|
Mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
|
|
123
|
-
This QA is required to detect mixin assoc usages to decide
|
|
123
|
+
This QA is required to detect mixin assoc usages to decide whether a minimum or full
|
|
124
124
|
join needs to be done
|
|
125
125
|
*/
|
|
126
126
|
forEachQuery(createQAForMixinAssoc, env);
|
|
@@ -309,13 +309,13 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
309
309
|
let [QA, ps] = rightMostQA(tail, head._navigation._parent.$QA || head._navigation.$QA);
|
|
310
310
|
if(!QA) {
|
|
311
311
|
error(null, pathNode.$location,
|
|
312
|
-
{ name: pathNode.path
|
|
312
|
+
{ name: pathName(pathNode.path) },
|
|
313
313
|
'Please debug me: No QA found for generic path rewriting in $(NAME)')
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
// if the found QA is the mixin QA and if the path length is one,
|
|
317
317
|
// this indicates the publishing of a mixin assoc, don't rewrite the path
|
|
318
|
-
if(QA.mixin && tail.length
|
|
318
|
+
if(QA.mixin && tail.length === 1)
|
|
319
319
|
return;
|
|
320
320
|
let pos = tail.indexOf(ps);
|
|
321
321
|
// cut off ps if it's a join relevant association with postfix
|
|
@@ -670,7 +670,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
670
670
|
if(fwdAssoc)
|
|
671
671
|
{
|
|
672
672
|
//env.assocStack.includes(fwdAssoc) => recursion
|
|
673
|
-
if(env.assocStack.length
|
|
673
|
+
if(env.assocStack.length === 2) {
|
|
674
674
|
// reuse (ugly) error message from forHana
|
|
675
675
|
error(null, env.assocStack[0].location,
|
|
676
676
|
{ name: '$self', id: '$self' },
|
|
@@ -702,7 +702,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
702
702
|
// ON cond of the forward assoc with swapped src/tgt aliases
|
|
703
703
|
let fwdAssoc = getForwardAssociationExpr(expr);
|
|
704
704
|
if(fwdAssoc) {
|
|
705
|
-
if(env.assocStack.length
|
|
705
|
+
if(env.assocStack.length === 2) {
|
|
706
706
|
// reuse (ugly) error message from forHana
|
|
707
707
|
error(null, expr.location, 'An association that uses “$self” in its ON-condition can\'t be compared to “$self”');
|
|
708
708
|
// don't check these paths again
|
|
@@ -736,7 +736,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
736
736
|
let newSrcAlias = tgtAlias;
|
|
737
737
|
let newTgtAlias = {};
|
|
738
738
|
// first try to identify table alias for complex views or redirected associations
|
|
739
|
-
if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
|
|
739
|
+
if(fwdAssoc._redirected && fwdAssoc._redirected.length &&
|
|
740
740
|
// redirected target must have a $QA
|
|
741
741
|
fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA &&
|
|
742
742
|
// $QA's artifact must either be same srcAlias artifact
|
|
@@ -793,25 +793,8 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
793
793
|
If the value is an expression in the select block, return the unmodified
|
|
794
794
|
expression. rewriteGenericPaths will check and rewrite these paths later
|
|
795
795
|
to the correct ON condition expression.
|
|
796
|
-
|
|
797
|
-
Hack alert:
|
|
798
|
-
But if this mixin ON condition path starts with $self no foreign key can
|
|
799
|
-
be generated, raise the error here and set $check to false
|
|
800
|
-
as it is too hard for checkPathDictionary() to find out that this is a
|
|
801
|
-
mixin speciality. Use same mesage as in checkPathDictionary().
|
|
802
796
|
*/
|
|
803
797
|
|
|
804
|
-
path.forEach(ps => { // eslint-disable-line consistent-return
|
|
805
|
-
if(ps._artifact.target) {
|
|
806
|
-
error(null, pathNode.location,
|
|
807
|
-
{ name: env.lead.name.absolute, id: ps.id, alias: pathAsStr(pathNode.path) },
|
|
808
|
-
'$(NAME): $(ID) in path $(ALIAS) must not be an association'
|
|
809
|
-
);
|
|
810
|
-
//pathNode.$check = false;
|
|
811
|
-
return pathNode;
|
|
812
|
-
}
|
|
813
|
-
});
|
|
814
|
-
|
|
815
798
|
if(!value.path)
|
|
816
799
|
return value;
|
|
817
800
|
else {
|
|
@@ -912,7 +895,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
912
895
|
combined list of elements made available by the from clause.
|
|
913
896
|
*/
|
|
914
897
|
let _navigation = undefined; // don't modify original path
|
|
915
|
-
if(env.assocStack.length
|
|
898
|
+
if(env.assocStack.length === 2) {
|
|
916
899
|
// a mixin assoc cannot have a structure prefix, it's sufficient to check head
|
|
917
900
|
if(head.id === env.assocStack.id()) {
|
|
918
901
|
// source side from view point of view (target side from forward point of view)
|
|
@@ -974,7 +957,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
974
957
|
|
|
975
958
|
// Return the original association if expr is a backlink term, undefined otherwise
|
|
976
959
|
function getForwardAssociationExpr(expr) {
|
|
977
|
-
if(expr.op && expr.op.val === '=' && expr.args.length
|
|
960
|
+
if(expr.op && expr.op.val === '=' && expr.args.length === 2) {
|
|
978
961
|
return getForwardAssociation(expr.args[0].path, expr.args[1].path);
|
|
979
962
|
}
|
|
980
963
|
return undefined;
|
|
@@ -983,10 +966,10 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
983
966
|
function getForwardAssociation(lhs, rhs) {
|
|
984
967
|
// [alpha.]BACKLINK.[beta.]FORWARD
|
|
985
968
|
if(lhs && rhs) {
|
|
986
|
-
if(rhs.length
|
|
969
|
+
if(rhs.length === 1 && rhs[0].id === '$self' &&
|
|
987
970
|
lhs.length > 1 && hasPrefix(lhs))
|
|
988
971
|
return lhs[lhs.length-1]._artifact;
|
|
989
|
-
if(lhs.length
|
|
972
|
+
if(lhs.length === 1 && lhs[0].id === '$self' &&
|
|
990
973
|
rhs.length > 1 && hasPrefix(rhs))
|
|
991
974
|
return rhs[rhs.length-1]._artifact;
|
|
992
975
|
}
|
|
@@ -1090,7 +1073,7 @@ function translateAssocsToJoins(model, inputOptions = {})
|
|
|
1090
1073
|
|
|
1091
1074
|
/*
|
|
1092
1075
|
Replace the table alias node in $tableAliases inplace with the newly created JOIN node
|
|
1093
|
-
See
|
|
1076
|
+
See define.js initTableExpression for details where _joinParent and $joinArgsIndex is set.
|
|
1094
1077
|
*/
|
|
1095
1078
|
function replaceTableAliasInPlace( tableAlias, replacementNode ) {
|
|
1096
1079
|
if (tableAlias._joinParent)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"plugins": ["sonarjs", "jsdoc"],
|
|
4
|
+
"extends": ["../../../.eslintrc-ydkjsi.json", "plugin:sonarjs/recommended", "plugin:jsdoc/recommended"],
|
|
5
|
+
"rules": {
|
|
6
|
+
"prefer-const": "error",
|
|
7
|
+
"quotes": ["error", "single", "avoid-escape"],
|
|
8
|
+
"prefer-template": "error",
|
|
9
|
+
"no-trailing-spaces": "error",
|
|
10
|
+
"template-curly-spacing":["error", "never"],
|
|
11
|
+
"complexity": ["warn", 30],
|
|
12
|
+
"max-len": "off",
|
|
13
|
+
// Don't enforce stupid descriptions
|
|
14
|
+
"jsdoc/require-param-description": "off",
|
|
15
|
+
"jsdoc/require-returns-description": "off",
|
|
16
|
+
// Very whiny and nitpicky
|
|
17
|
+
"sonarjs/cognitive-complexity": "off",
|
|
18
|
+
// Does not recognize TS types
|
|
19
|
+
"jsdoc/no-undefined-types": "off",
|
|
20
|
+
// Just annoying as hell
|
|
21
|
+
"sonarjs/no-duplicate-string": "off"
|
|
22
|
+
},
|
|
23
|
+
"parserOptions": {
|
|
24
|
+
"ecmaVersion": 2018,
|
|
25
|
+
"sourceType": "script"
|
|
26
|
+
},
|
|
27
|
+
"env": {
|
|
28
|
+
"es6": true,
|
|
29
|
+
"node": true
|
|
30
|
+
},
|
|
31
|
+
"settings": {
|
|
32
|
+
"jsdoc": {
|
|
33
|
+
"mode": "typescript"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getUtils, forEachDefinition, forAllQueries, getNormalizedQuery,
|
|
5
|
+
} = require('../../model/csnUtils');
|
|
6
|
+
const { setAnnotationIfNotDefined } = require('./utils');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Set @Core.Computed on the elements of views (and projections).
|
|
10
|
+
*
|
|
11
|
+
* @param {CSN.Model} csn
|
|
12
|
+
*/
|
|
13
|
+
function setCoreComputedOnViews(csn) {
|
|
14
|
+
const {
|
|
15
|
+
artifactRef, getColumn, getElement,
|
|
16
|
+
} = getUtils(csn, 'init-all');
|
|
17
|
+
|
|
18
|
+
forEachDefinition(csn, (artifact) => {
|
|
19
|
+
if (artifact.query || artifact.projection) {
|
|
20
|
+
forAllQueries(getNormalizedQuery(artifact).query, (query) => {
|
|
21
|
+
if (query.SELECT)
|
|
22
|
+
traverseQueryAndAttachCoreComputed(query, query.SELECT.elements || artifact.elements);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Attach @Core.Computed to elements resulting from calculated fields
|
|
28
|
+
*
|
|
29
|
+
* To do that, for a given element, we search for its matching column/subquery-element (its ancestor).
|
|
30
|
+
* At the ancestor, we can see if it needs a @CoreComputed - check out {@link needsCoreComputed} for details.
|
|
31
|
+
*
|
|
32
|
+
*
|
|
33
|
+
* @param {CSN.Query} query
|
|
34
|
+
* @param {CSN.Elements} elements
|
|
35
|
+
*/
|
|
36
|
+
function traverseQueryAndAttachCoreComputed(query, elements) {
|
|
37
|
+
for (const [ name, element ] of Object.entries(elements)) {
|
|
38
|
+
const ancestor = getAncestor(element, name, query.SELECT);
|
|
39
|
+
|
|
40
|
+
if (needsCoreComputed(ancestor)) // calculated field, function or virtual
|
|
41
|
+
setAnnotationIfNotDefined(element, '@Core.Computed', true);
|
|
42
|
+
if (ancestor && (ancestor.expand || ancestor.inline))
|
|
43
|
+
traverseExpandInline(ancestor, attachCoreComputed);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the ancestor of a given element - either a direct column that "caused" it, or an element
|
|
48
|
+
* from some other artifact (table or view/subquery). The later happens via SELECT * and can be found by drilling down into the
|
|
49
|
+
* FROM-clause.
|
|
50
|
+
*
|
|
51
|
+
* @param {CSN.Element} element
|
|
52
|
+
* @param {string} name
|
|
53
|
+
* @param {CSN.QuerySelect} base
|
|
54
|
+
* @returns {CSN.Column|CSN.Element}
|
|
55
|
+
*/
|
|
56
|
+
function getAncestor(element, name, base) {
|
|
57
|
+
const column = getColumn(element);
|
|
58
|
+
if (column)
|
|
59
|
+
return column;
|
|
60
|
+
return getElementFromFrom(name, base.from);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the element <name> from the given query-base (from of a select).
|
|
65
|
+
*
|
|
66
|
+
* For a simple ref to a table, resolve the ref and check the elements
|
|
67
|
+
* For a UNION, drill down into the leading query
|
|
68
|
+
* For a JOIN, check each join-argument
|
|
69
|
+
* For a query with subelements, check the subelements
|
|
70
|
+
*
|
|
71
|
+
* @param {string} name
|
|
72
|
+
* @param {object} base
|
|
73
|
+
* @returns {CSN.Element}
|
|
74
|
+
* @todo cleanup throw(s) - but leave in during dev
|
|
75
|
+
*/
|
|
76
|
+
function getElementFromFrom(name, base) {
|
|
77
|
+
if (base.SELECT && base.SELECT.elements) {
|
|
78
|
+
return getAncestor(base.SELECT.elements[name], name, base.SELECT);
|
|
79
|
+
}
|
|
80
|
+
else if (base.ref) {
|
|
81
|
+
let artifact = artifactRef(base);
|
|
82
|
+
if (artifact.target)
|
|
83
|
+
artifact = artifactRef(artifact.target);
|
|
84
|
+
return artifact.elements[name];
|
|
85
|
+
}
|
|
86
|
+
else if (base.SET) {
|
|
87
|
+
return getElementFromFrom(name, base.SET.args[0]);
|
|
88
|
+
}
|
|
89
|
+
else if (base.args && base.join) {
|
|
90
|
+
const result = checkJoinSources(base.args, name);
|
|
91
|
+
if (!result)
|
|
92
|
+
throw new Error(`Could not find ${name} in ${JSON.stringify(base.args)}`);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw new Error(JSON.stringify(base));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* For the given JOIN-args, check if one of the join sources provides an element <name>
|
|
101
|
+
*
|
|
102
|
+
* @param {Array} args
|
|
103
|
+
* @param {string} name
|
|
104
|
+
* @returns {CSN.Element|null} Null if no element was found
|
|
105
|
+
*/
|
|
106
|
+
function checkJoinSources(args, name) {
|
|
107
|
+
for (const arg of args) {
|
|
108
|
+
if (arg.args) { // Join after join - A join B on <..> join C on <..>
|
|
109
|
+
const result = checkJoinSources(arg.args, name);
|
|
110
|
+
if (result)
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
else { // All other cases - normal ref, a subselect, etc. pp
|
|
114
|
+
const result = getElementFromFrom(name, arg);
|
|
115
|
+
if (result)
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* On a given column, attach @Core.Computed if needed
|
|
125
|
+
*
|
|
126
|
+
* @param {CSN.Column} column
|
|
127
|
+
*/
|
|
128
|
+
function attachCoreComputed(column) {
|
|
129
|
+
if (needsCoreComputed(column))
|
|
130
|
+
setAnnotationIfNotDefined(getElement(column), '@Core.Computed', true);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Return whether the given columns element needs to be marked with @Core.Computed.
|
|
135
|
+
*
|
|
136
|
+
* @param {CSN.Column} column
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
function needsCoreComputed(column) {
|
|
140
|
+
return column && ( column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET || column.ref && column.ref[0] in {
|
|
141
|
+
$at: 1, $now: 1, $user: 1, $session: 1,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Call the given callback for all sub-things of a .expand/.inline and drill further down into other .expand/.inline
|
|
147
|
+
*
|
|
148
|
+
* @param {CSN.Column} column
|
|
149
|
+
* @param {Function} callback
|
|
150
|
+
*/
|
|
151
|
+
function traverseExpandInline(column, callback) {
|
|
152
|
+
if (column.expand) {
|
|
153
|
+
column.expand.forEach((col) => {
|
|
154
|
+
callback(col);
|
|
155
|
+
traverseExpandInline(col, callback);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
else if (column.inline) {
|
|
159
|
+
column.inline.forEach((col) => {
|
|
160
|
+
callback(col);
|
|
161
|
+
traverseExpandInline(col, callback);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
setCoreComputedOnViews,
|
|
170
|
+
};
|