@sap/cds-compiler 2.12.0 → 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 +110 -15
- package/bin/cdsc.js +13 -13
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +28 -63
- package/lib/api/options.js +3 -3
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +25 -4
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +158 -123
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +4 -7
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +14 -3
- 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 +32 -13
- 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 +111 -46
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +64 -37
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +5 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +9 -8
- package/lib/edm/edm.js +11 -12
- package/lib/edm/edmPreprocessor.js +137 -73
- package/lib/edm/edmUtils.js +116 -22
- package/lib/gen/Dictionary.json +10 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5282 -4265
- package/lib/json/from-csn.js +12 -1
- package/lib/json/to-csn.js +126 -66
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +76 -3
- package/lib/language/language.g4 +297 -130
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +468 -59
- package/lib/main.js +35 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +225 -156
- package/lib/model/csnUtils.js +192 -223
- package/lib/model/enrichCsn.js +70 -29
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +5 -4
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +73 -288
- package/lib/render/toHdbcds.js +25 -23
- package/lib/render/toSql.js +98 -41
- package/lib/render/utils/common.js +5 -10
- package/lib/render/utils/sql.js +4 -3
- 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/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +103 -305
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +55 -52
- package/lib/transform/db/expansion.js +46 -24
- package/lib/transform/db/flattening.js +553 -102
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +59 -6
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +67 -183
- package/lib/transform/forOdataNew.js +17 -171
- package/lib/transform/localized.js +34 -19
- package/lib/transform/odata/generateForeignKeyElements.js +1 -1
- 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 +36 -22
- package/lib/transform/translateAssocsToJoins.js +2 -19
- 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/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/universalCsnEnricher.js +0 -237
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
forEachDefinition, forEachMemberRecursively,
|
|
5
|
-
isBuiltinType, cloneCsnDictionary,
|
|
5
|
+
isBuiltinType, cloneCsnDictionary, cloneCsn,
|
|
6
6
|
} = require('../../model/csnUtils');
|
|
7
7
|
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
|
|
8
8
|
|
|
@@ -86,21 +86,95 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
86
86
|
// EDMX at the moment and the reference in the OData CSN is fulfilled.
|
|
87
87
|
if (node.kind === 'event') return;
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
89
|
+
// elements have precedence over type
|
|
90
|
+
if (node.type && (!isBuiltinType(node.type) &&isExpandable(node, defName) || node.kind === 'type')) {
|
|
91
|
+
// 1. Get the final type of the node (resolve derived type chain)
|
|
92
|
+
const finalType = csnUtils.getFinalBaseType(node.type);
|
|
93
|
+
if (finalType) {
|
|
94
|
+
// The type replacement depends on whether 'node' is a definition or a member[element].
|
|
95
|
+
if (node.kind) {
|
|
96
|
+
// It is a definition and we expand to builtin type and to elements
|
|
97
|
+
// type T: S; --> Integer;
|
|
98
|
+
// type S: X; --> Integer;
|
|
99
|
+
// type X: Integer;
|
|
100
|
+
//
|
|
101
|
+
// type A: B; -> {...}
|
|
102
|
+
// type B: C; -> { ... }
|
|
103
|
+
// type C { .... };
|
|
104
|
+
if (isBuiltinType(finalType)) {
|
|
105
|
+
// use transformUrilsNew::toFinalBaseType for the moment,
|
|
106
|
+
// as it is collects along the chain of types
|
|
107
|
+
// attributes that need to be propagated
|
|
108
|
+
// enum, length, scale, etc.
|
|
109
|
+
transformers.toFinalBaseType(node);
|
|
110
|
+
// node.type = finalType;
|
|
111
|
+
}
|
|
112
|
+
else if (csnUtils.isStructured(finalType)) {
|
|
113
|
+
cloneElements(finalType);
|
|
114
|
+
}
|
|
115
|
+
else if (node.type && node.items)
|
|
99
116
|
delete node.type;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// this is a member and we expand to final base only if builtin
|
|
120
|
+
// type T: S; --> Integer;
|
|
121
|
+
// type S: X; --> Integer;
|
|
122
|
+
// type X: Integer;
|
|
123
|
+
//
|
|
124
|
+
// type {
|
|
125
|
+
// struct_elt: many A; ---> stays the same
|
|
126
|
+
// scalar_elt: T; ---> Integer;
|
|
127
|
+
// type_ref_elt: type of struct_elt;
|
|
128
|
+
// };
|
|
129
|
+
// type A: B; -> {...}
|
|
130
|
+
// type B: C; -> { ... }
|
|
131
|
+
// type C { .... };
|
|
132
|
+
if (isBuiltinType(finalType)) {
|
|
133
|
+
// use transformUrilsNew::toFinalBaseType for the moment,
|
|
134
|
+
// as it is collects along the chain of types
|
|
135
|
+
// attributes that need to be propagated
|
|
136
|
+
// enum, length, scale, etc.
|
|
137
|
+
transformers.toFinalBaseType(node);
|
|
138
|
+
// node.type = finalType;
|
|
139
|
+
}
|
|
140
|
+
else if (node.type && node.type.ref) {
|
|
141
|
+
cloneElements(finalType);
|
|
100
142
|
}
|
|
101
143
|
}
|
|
102
144
|
}
|
|
103
145
|
}
|
|
146
|
+
if (node.type && !isBuiltinType(node.type)) {
|
|
147
|
+
// handle array of defined via a named type
|
|
148
|
+
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
|
|
149
|
+
const currService = csnUtils.getServiceName(defName);
|
|
150
|
+
const finalType = csnUtils.getFinalTypeDef(node.type);
|
|
151
|
+
if (finalType.items && isBuiltinType(finalType.items.type)) {
|
|
152
|
+
if (!isArtifactInService(node.type, currService) || !isV4) {
|
|
153
|
+
node.items = finalType.items;
|
|
154
|
+
delete node.type;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function cloneElements(finalType) {
|
|
160
|
+
// cloneCsn only works correctly if we start "from the top"
|
|
161
|
+
let clone;
|
|
162
|
+
// do the clone only if really needed
|
|
163
|
+
if((finalType.items && !node.items) ||
|
|
164
|
+
(finalType.elements && !node.elements))
|
|
165
|
+
clone = cloneCsn({ definitions: { 'TypeDef': finalType } }, options);
|
|
166
|
+
if (finalType.items) {
|
|
167
|
+
delete node.type;
|
|
168
|
+
if(!node.items)
|
|
169
|
+
Object.assign(node, { items: clone.definitions.TypeDef.items });
|
|
170
|
+
}
|
|
171
|
+
if (finalType.elements) {
|
|
172
|
+
if(!finalType.items)
|
|
173
|
+
delete node.type;
|
|
174
|
+
if(!node.elements)
|
|
175
|
+
Object.assign(node, { elements: clone.definitions.TypeDef.elements });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
104
178
|
}
|
|
105
179
|
|
|
106
180
|
function isExpandable(node, defName) {
|
|
@@ -119,4 +193,4 @@ function expandToFinalBaseType(csn, transformers, csnUtils, services, options, i
|
|
|
119
193
|
}
|
|
120
194
|
}
|
|
121
195
|
|
|
122
|
-
module.exports = expandToFinalBaseType;
|
|
196
|
+
module.exports = expandToFinalBaseType;
|
|
@@ -18,7 +18,7 @@ const { copyAnnotations } = require('../../model/csnUtils');
|
|
|
18
18
|
* @param {*} csnUtils
|
|
19
19
|
* @param {object} message message object with { error } function
|
|
20
20
|
*/
|
|
21
|
-
function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
21
|
+
function typesExposure(csn, whatsMyServiceName, autoexposeSchemaName, options, csnUtils, message) {
|
|
22
22
|
const { error } = message;
|
|
23
23
|
// are we working with OData proxies or cross-service refs
|
|
24
24
|
const isMultiSchema = options.toOdata.version === 'v4' && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs);
|
|
@@ -30,9 +30,9 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
30
30
|
// we do expose types only for definition from inside services
|
|
31
31
|
const serviceName = whatsMyServiceName(defName, false);
|
|
32
32
|
if (serviceName) {
|
|
33
|
-
if (
|
|
33
|
+
if (def.kind === 'type' || def.kind === 'entity') {
|
|
34
34
|
forEachMember(def, (element, elementName, propertyName, path) => {
|
|
35
|
-
if (
|
|
35
|
+
if (propertyName === 'elements' || propertyName === 'params') {
|
|
36
36
|
const artificialtName = `${isMultiSchema ?
|
|
37
37
|
defNameWithoutServiceOrContextName(defName, serviceName)
|
|
38
38
|
: defNameWithoutServiceOrContextName(defName, serviceName).replace(/\./g, '_')}_${elementName}`;
|
|
@@ -190,7 +190,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
190
190
|
} else {
|
|
191
191
|
const typeContext = csnUtils.getContextOfArtifact(typeName);
|
|
192
192
|
const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
|
|
193
|
-
const newSchemaName = `${service}.${typeContext || typeNamespace ||
|
|
193
|
+
const newSchemaName = `${service}.${typeContext || typeNamespace || autoexposeSchemaName}`;
|
|
194
194
|
// new type name without any prefixes
|
|
195
195
|
const typePlainName = typeContext ? defNameWithoutServiceOrContextName(typeName, typeContext)
|
|
196
196
|
: typeName.replace(`${typeNamespace}.`, '');
|
|
@@ -209,7 +209,7 @@ function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
|
|
|
209
209
|
*/
|
|
210
210
|
function getAnonymousTypeNameInMultiSchema(typeName, parentName) {
|
|
211
211
|
let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
|
|
212
|
-
const newSchemaName = currPrefix ||
|
|
212
|
+
const newSchemaName = currPrefix || autoexposeSchemaName;
|
|
213
213
|
// new type name without any prefixes
|
|
214
214
|
const typePlainName = defNameWithoutServiceOrContextName(typeName, newSchemaName);
|
|
215
215
|
|
|
@@ -18,7 +18,7 @@ function isAssociationOrComposition(artifact) {
|
|
|
18
18
|
return isAssociation(artifact) || isComposition(artifact);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function
|
|
21
|
+
function isManagedAssociation(artifact) {
|
|
22
22
|
return artifact.target !== undefined && artifact.on === undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -94,5 +94,5 @@ module.exports = {
|
|
|
94
94
|
isArtifactInSomeService,
|
|
95
95
|
isAssociationOrComposition,
|
|
96
96
|
isLocalizedArtifactInService,
|
|
97
|
-
|
|
97
|
+
isManagedAssociation,
|
|
98
98
|
}
|
|
@@ -10,11 +10,12 @@ const { csnRefs } = require('../model/csnRefs');
|
|
|
10
10
|
|
|
11
11
|
const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
|
|
12
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);
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|
|
@@ -389,7 +400,7 @@ function getTransformers(model, options, pathDelimiter = '_') {
|
|
|
389
400
|
*
|
|
390
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) {
|
|
@@ -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
439
|
const cloneTypeDef = cloneCsn(typeDef, options);
|
|
428
|
-
// With hdbcds-hdbcds, don't resolve structured types - but
|
|
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
|
|
|
@@ -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] = {
|
|
@@ -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
|
|
@@ -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);
|
|
@@ -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 {
|
|
@@ -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
|
+
};
|