@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -0,0 +1,1433 @@
1
+ // Compiler phase "resolve": resolve all references
2
+
3
+ // The resolve phase tries to find the artifacts (and elements) for all
4
+ // references in the augmented CSN. If there are unresolved references, this
5
+ // compiler phase fails with an error containing a vector of corresponding
6
+ // messages (alternatively, we could just store this vector in the CSN).
7
+
8
+ // References are resolved according to the scoping rules of CDS specification.
9
+ // That means, the first name of a reference path is not only searched in the
10
+ // current environments, but also in the parent environments, with the source
11
+ // as second-last, and the environment for builtins as the last search
12
+ // environment.
13
+
14
+ // For all type references, we set the property `type._artifact`, the latter is
15
+ // the actual type definition.
16
+
17
+ // If the referred type definition has a `parameters` property, we use it to
18
+ // transform the `$typeArgs` property (sibling to the `type` property`) to
19
+ // named properties. See function `resolveTypeExpr` below for details.
20
+
21
+ // Example 'file.cds' (see './define.js' for the CSN before "resolve"):
22
+ // type C { elem: String(4); }
23
+ //
24
+ // The corresponding definition of element "elem" looks as follows:
25
+ // {
26
+ // kind: 'element',
27
+ // name: { id: 'elem', component: 'elem', location: ... }
28
+ // type: { absolute: 'cds.String', _artifact: {...}, path: ...},
29
+ // length: { val: 4, location: <of the number literal> },
30
+ // location: ..., _parent: ...
31
+ // }
32
+
33
+ // Potential file names:
34
+ // lookup-refs / memorize: main refs loop (phase 2)
35
+ // monitor-refs: resolve-refs (not leading to new defs/elems)
36
+ // repair-props: rewrite, late extensions
37
+ // test-model: cycle detection, late tests (currently in checks)
38
+
39
+ 'use strict';
40
+
41
+ const {
42
+ isDeprecatedEnabled,
43
+ forEachDefinition,
44
+ forEachMember,
45
+ forEachGeneric,
46
+ forEachInOrder,
47
+ } = require('../base/model');
48
+ const { dictAdd } = require('../base/dictionaries');
49
+ const { dictLocation } = require('../base/location');
50
+ const { searchName, weakLocation } = require('../base/messages');
51
+ const { combinedLocation } = require('../base/location');
52
+ const { forEachValue } = require('../utils/objectUtils');
53
+
54
+ const { kindProperties } = require('./base');
55
+ const {
56
+ setLink,
57
+ setArtifactLink,
58
+ pathName,
59
+ linkToOrigin,
60
+ setMemberParent,
61
+ withAssociation,
62
+ storeExtension,
63
+ dependsOn,
64
+ dependsOnSilent,
65
+ testExpr,
66
+ targetMaxNotOne,
67
+ traverseQueryPost,
68
+ } = require('./utils');
69
+
70
+ const detectCycles = require('./cycle-detector');
71
+ const layers = require('./moduleLayers');
72
+
73
+ const $location = Symbol.for('cds.$location');
74
+
75
+ const annotationPriorities = {
76
+ define: 1, extend: 2, annotate: 2, edmx: 3,
77
+ };
78
+ const $inferred = Symbol.for('cds.$inferred');
79
+
80
+ // Export function of this file. Resolve type references in augmented CSN
81
+ // `model`. If the model has a property argument `messages`, do not throw
82
+ // exception in case of an error, but push the corresponding error object to
83
+ // that property (should be a vector).
84
+ function resolve( model ) {
85
+ const { options } = model;
86
+ // Get shared functionality and the message function:
87
+ const {
88
+ info, warning, error, message,
89
+ } = model.$messageFunctions;
90
+ const {
91
+ resolvePath,
92
+ resolveTypeArguments,
93
+ defineAnnotations,
94
+ attachAndEmitValidNames,
95
+ lateExtensions,
96
+ effectiveType,
97
+ directType,
98
+ resolveType,
99
+ populateQuery,
100
+ } = model.$functions;
101
+ const { environment } = model.$volatileFunctions;
102
+ Object.assign( model.$functions, {
103
+ resolveExpr,
104
+ } );
105
+
106
+ /** @type {any} may also be a boolean */
107
+
108
+ // behavior depending on option `deprecated`:
109
+ const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
110
+ // TODO: we should get rid of noElementsExpansion soon; both
111
+ // beta.nestedProjections and beta.universalCsn do not work with it.
112
+
113
+ return doResolve();
114
+
115
+ function doResolve() {
116
+ // Phase 1: check paths in usings has been moved to kick-start.js Phase 2:
117
+ // calculate/init view elements & collect views in order:
118
+ // TODO: It might be that we need to call propagateKeyProps() and
119
+ // addImplicitForeignKeys() in populate.js, as we might need to know the
120
+ // foreign keys in populate.js (foreign key access w/o JOINs).
121
+
122
+ // Phase 3: calculate keys along simple queries in collected views:
123
+ model._entities.forEach( propagateKeyProps );
124
+ // While most dependencies leading have been added at this point, new
125
+ // cycles could be added later (e.g. via assocs in where conditions),
126
+ // i.e. keep cycle detection with messages at the end (or after phase 4).
127
+
128
+ // Phase 4: resolve all artifacts:
129
+ forEachDefinition( model, resolveRefs );
130
+ forEachGeneric( model, 'vocabularies', resolveRefs );
131
+ // for builtin types
132
+ forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
133
+ forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
134
+ // Phase 6: apply ANNOTATE on autoexposed entities and unknown artifacts:
135
+ lateExtensions( annotateMembers );
136
+ if (model.extensions)
137
+ model.extensions.map( annotateUnknown );
138
+ // Phase 7: report cyclic dependencies:
139
+ detectCycles( model.definitions, ( user, art, location ) => {
140
+ if (location) {
141
+ error( 'ref-cyclic', [ location, user ], { art }, {
142
+ std: 'Illegal circular reference to $(ART)',
143
+ element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
144
+ });
145
+ }
146
+ });
147
+ return model;
148
+ }
149
+
150
+ //--------------------------------------------------------------------------
151
+ // Phase 3: calculate propagated KEYs
152
+ //--------------------------------------------------------------------------
153
+
154
+ function propagateKeyProps( view ) {
155
+ // Second argument true ensure that `key` is only propagated along simple
156
+ // view, i.e. ref or subquery in FROM, not UNION or JOIN.
157
+ traverseQueryPost( view.query, true, ( query ) => {
158
+ if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
159
+ withKeyPropagation( query )) // now the part with messages
160
+ inheritKeyProp( query, true );
161
+ } );
162
+ }
163
+
164
+ function withExplicitKeys( query ) {
165
+ for (const name in query.elements) {
166
+ const elem = query.elements[name];
167
+ if (elem.key && !elem.$duplicates) // also those from includes
168
+ return true;
169
+ }
170
+ return false;
171
+ }
172
+
173
+ function inheritKeyProp( query, doIt ) {
174
+ for (const name in query.elements) {
175
+ const elem = query.elements[name];
176
+ // no key prop for duplicate elements or additional specified elements:
177
+ if (elem.$duplicates || !elem.value)
178
+ continue;
179
+ const nav = pathNavigation( elem.value );
180
+ if (!nav.navigation)
181
+ continue; // undefined, expr, $magic, :const, $self (!), $self.elem
182
+ const { item } = nav;
183
+ if (item !== elem.value.path[elem.value.path.length - 1])
184
+ continue; // having selected a sub elem / navigated along assoc
185
+ const { key } = item._artifact;
186
+ if (key) {
187
+ if (!doIt)
188
+ return true;
189
+ elem.key = { location: elem.value.location, val: key.val, $inferred: 'query' };
190
+ }
191
+ }
192
+ return false;
193
+ }
194
+
195
+ function primarySourceNavigation( aliases ) {
196
+ for (const name in aliases)
197
+ return aliases[name].elements;
198
+ return undefined;
199
+ }
200
+
201
+ function withKeyPropagation( query ) {
202
+ const { from } = query;
203
+ if (!from) // parse error SELECT FROM <EOF>
204
+ return false;
205
+
206
+ let propagateKeys = true; // used instead early RETURN to get more messages
207
+ const toMany = withAssociation( from, targetMaxNotOne, true );
208
+ if (toMany) {
209
+ propagateKeys = false;
210
+ info( 'query-from-many', [ toMany.location, query ], { art: toMany },
211
+ {
212
+ // eslint-disable-next-line max-len
213
+ std: 'Selecting from to-many association $(ART) - key properties are not propagated',
214
+ // eslint-disable-next-line max-len
215
+ element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
216
+ } );
217
+ }
218
+ // Check that all keys from the source are projected:
219
+ const notProjected = []; // we actually push to the array
220
+ const navElems = primarySourceNavigation( query.$tableAliases );
221
+ for (const name in navElems) {
222
+ const nav = navElems[name];
223
+ if (nav.$duplicates)
224
+ continue;
225
+ const { key } = nav._origin;
226
+ if (key && key.val && !(nav._projections && nav._projections.length))
227
+ notProjected.push( nav.name.id );
228
+ }
229
+ if (notProjected.length) {
230
+ propagateKeys = false;
231
+ info( 'query-missing-keys', [ from.location, query ], { names: notProjected },
232
+ {
233
+ std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
234
+ one: 'Key $(NAMES) has not been projected - key properties are not propagated',
235
+ } );
236
+ }
237
+ // Check that there is no to-many assoc used in select item:
238
+ for (const name in query.elements) {
239
+ const elem = query.elements[name];
240
+ if (!elem.$inferred && elem.value &&
241
+ testExpr( elem.value, selectTest, () => false ))
242
+ propagateKeys = false;
243
+ }
244
+ return propagateKeys;
245
+
246
+ function selectTest( expr ) {
247
+ const art = withAssociation( expr, targetMaxNotOne );
248
+ if (art) {
249
+ info( 'query-navigate-many', [ art.location, query ], { art },
250
+ {
251
+ // eslint-disable-next-line max-len
252
+ std: 'Navigating along to-many association $(ART) - key properties are not propagated',
253
+ // eslint-disable-next-line max-len
254
+ element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
255
+ // eslint-disable-next-line max-len
256
+ alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
257
+ } );
258
+ }
259
+ return art;
260
+ }
261
+ }
262
+
263
+ //--------------------------------------------------------------------------
264
+ // Phase 4:
265
+ //--------------------------------------------------------------------------
266
+
267
+ function adHocOrMainKind( elem ) {
268
+ const main = elem._main;
269
+ if (main) {
270
+ do {
271
+ elem = elem._parent;
272
+ if (elem.targetAspect)
273
+ return 'aspect'; // ad-hoc composition target aspect
274
+ } while (elem !== main);
275
+ }
276
+ return elem.kind;
277
+ }
278
+ // TODO: have $applied/$extension/$status on extension with the following values
279
+ // - 'unknown': artifact to extend/annotate is not defined or contains unknown member
280
+ // - 'referred': contains annotation for element of referred type (not yet supported)
281
+ // - 'inferred': only contains extension for known member, but some inferred ones
282
+ // (inferred = elements from structure includes, query elements)
283
+ // - 'original': only contains extensions on non-inferred members
284
+
285
+ // Resolve all references in artifact or element `art`. Do so recursively in
286
+ // all sub elements.
287
+ // TODO: make this function smaller
288
+ function resolveRefs( art ) {
289
+ // console.log(message( null, art.location, art, {}, 'Info','REFS').toString())
290
+ // console.log(message( null, art.location, art, {target:art.target}, 'Info','RR').toString())
291
+ const parent = art._parent;
292
+ const allowedInMain = [ 'entity', 'aspect' ].includes( adHocOrMainKind( art ) );
293
+ const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
294
+ if (art.key && art.key.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
295
+ warning( 'unexpected-key', [ art.key.location, art ],
296
+ { '#': allowedInMain ? 'sub' : 'std' }, {
297
+ std: 'KEY is only supported for elements in an entity or an aspect',
298
+ sub: 'KEY is only supported for top-level elements',
299
+ });
300
+ }
301
+ if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
302
+ message( 'type-managed-composition', [ art.targetAspect.location, art ],
303
+ { '#': allowedInMain ? 'sub' : 'std' } );
304
+ }
305
+ if (art.includes && !allowedInMain) {
306
+ for (const include of art.includes) {
307
+ const struct = include._artifact;
308
+ if (struct && struct.kind !== 'type' && struct.elements &&
309
+ Object.values( struct.elements ).some( e => e.targetAspect)) {
310
+ message( 'type-managed-composition', [ include.location, art ],
311
+ { '#': struct.kind, art: struct } );
312
+ }
313
+ }
314
+ }
315
+ let obj = art;
316
+ if (obj.type) // TODO: && !obj.type.$inferred ?
317
+ resolveTypeExpr( obj, art );
318
+ const type = effectiveType( obj ); // make sure implicitly redirected target exists
319
+ if (!obj.items && type && type.items && enableExpandElements) {
320
+ // TODO: shouldn't be this part of populate.js ?
321
+ const items = {
322
+ location: weakLocation( (obj.type || obj).location ),
323
+ $inferred: 'expand-items',
324
+ };
325
+ setLink( items, '_outer', obj );
326
+ setLink( items, '_origin', type.items );
327
+ obj.items = items;
328
+ obj.$expand = 'origin';
329
+ }
330
+ if (obj.items) { // TODO: make this a while in v2 (also items proxy)
331
+ obj = obj.items || obj; // the object which has type properties
332
+ if (enableExpandElements)
333
+ effectiveType(obj);
334
+ }
335
+ if (obj.type) { // TODO: && !obj.type.$inferred ?
336
+ if (obj !== (art.returns || art)) // not already checked
337
+ resolveTypeExpr( obj, art );
338
+ // typeOf unmanaged assoc? TODO: is this the right place to check this?
339
+ // (probably better in rewriteAssociations)
340
+ const elemtype = obj.type._artifact;
341
+ if (elemtype && effectiveType( elemtype )) {
342
+ const assocType = getAssocSpec( elemtype ) || {};
343
+ if (assocType.on && !obj.on)
344
+ obj.on = { $inferred: 'rewrite' };
345
+ if (assocType.targetAspect) {
346
+ error( 'composition-as-type-of', [ obj.type.location, art ], {},
347
+ 'A managed aspect composition element can\'t be used as type' );
348
+ return;
349
+ }
350
+ else if (assocType.on) {
351
+ error( 'assoc-as-type-of', [ obj.type.location, art ], {},
352
+ 'An unmanaged association can\'t be used as type' );
353
+ return;
354
+ }
355
+
356
+ // Check if relational type is missing its target or if it's used directly.
357
+ if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
358
+ !obj.target && !obj.targetAspect) {
359
+ const isCsn = (obj._block && obj._block.$frontend === 'json');
360
+ error('type-missing-target', [ obj.type.location, obj ],
361
+ { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
362
+ // We don't say "use 'association to <target>" because the type could be used
363
+ // in action parameters, etc. as well.
364
+ std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
365
+ csn: 'Type $(TYPE) is missing a target',
366
+ });
367
+ }
368
+ }
369
+ }
370
+ if (obj.target) {
371
+ // console.log(obj.name,obj._origin.name)
372
+ if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
373
+ resolveTarget( art, obj._origin );
374
+ // console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
375
+ // .toString(), obj.target.$inferred)
376
+ if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
377
+ resolveTarget( art, obj );
378
+ else
379
+ // TODO: better write when inferred target must be redirected
380
+ resolveRedirected( obj, obj.target._artifact );
381
+ }
382
+ else if (obj.kind === 'mixin') {
383
+ error( 'non-assoc-in-mixin', [ (obj.type || obj.name).location, art ], {},
384
+ 'Only unmanaged associations are allowed in mixin clauses' );
385
+ }
386
+ if (art.targetElement) { // in foreign keys
387
+ const target = parent && parent.target;
388
+ if (target && target._artifact) {
389
+ // we just look in target for the path
390
+ // TODO: also check that we do not follow associations? no args, no filter
391
+ resolvePath( art.targetElement, 'targetElement', art,
392
+ environment( target._artifact ), target._artifact );
393
+ }
394
+ }
395
+ // Resolve projections/views
396
+ // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
397
+
398
+ if (art.$queries)
399
+ art.$queries.forEach( resolveQuery );
400
+
401
+ if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
402
+ effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
403
+
404
+ if (obj.elements) { // silent dependencies
405
+ forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
406
+ }
407
+ else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
408
+ forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
409
+ }
410
+ if (obj.foreignKeys) { // silent dependencies
411
+ forEachGeneric( obj, 'foreignKeys', (elem) => {
412
+ dependsOnSilent( art, elem );
413
+ } );
414
+ addForeignKeyNavigations( art );
415
+ }
416
+
417
+ resolveExpr( art.default, 'default', art );
418
+ resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
419
+ if (art.value && !art.type && !art.target && !art.elements)
420
+ inferTypeFromCast( art );
421
+
422
+ if (art.kind === 'element' || art.kind === 'mixin')
423
+ effectiveType( art );
424
+
425
+ annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
426
+ chooseAnnotationsInArtifact( art );
427
+
428
+ forEachMember( art, resolveRefs, art.targetAspect );
429
+
430
+ // Set '@Core.Computed' in the Core Compiler to have it propagated...
431
+ if (art.kind !== 'element' || art['@Core.Computed'])
432
+ return;
433
+ if (art.virtual && art.virtual.val ||
434
+ art.value &&
435
+ (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
436
+ art.value._artifact.kind === 'builtin' || art.value._artifact.kind === 'param' )) {
437
+ art['@Core.Computed'] = {
438
+ name: {
439
+ path: [ { id: 'Core.Computed', location: art.location } ],
440
+ location: art.location,
441
+ },
442
+ $inferred: '$generated',
443
+ };
444
+ }
445
+ }
446
+
447
+ // Return type containing the assoc spec (keys, on); note that no
448
+ // propagation/rewrite has been done yet, cyclic dependency must have been
449
+ // checked before!
450
+ function getAssocSpec( type ) {
451
+ // only to be called without cycles
452
+ while (type) {
453
+ if (type.on || type.foreignKeys || type.targetAspect)
454
+ return type;
455
+ type = directType( type );
456
+ }
457
+ return null;
458
+ }
459
+
460
+ function inferTypeFromCast( elem ) {
461
+ // TODO: think about CAST checks in checks.js
462
+ const { op, type } = elem.value;
463
+ if (op && op.val === 'cast' && type && type._artifact) {
464
+ // op.val is also correctly set with CSN input
465
+ elem.type = { ...type, $inferred: 'cast' };
466
+ setArtifactLink( elem.type, type._artifact );
467
+ for (const prop of [ 'length', 'precision', 'scale', 'srid' ]) {
468
+ if (elem.value[prop])
469
+ elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
470
+ }
471
+ }
472
+ }
473
+
474
+ // Phase 4 - annotations ---------------------------------------------------
475
+
476
+ function annotateUnknown( ext ) {
477
+ // extensions may have annotations for elements/actions/... which may
478
+ // themselves may be unknown
479
+ forEachMember(ext, annotateUnknown);
480
+
481
+ if (ext.$extension) // extension for known artifact -> already applied
482
+ return;
483
+ annotateMembers( ext );
484
+ for (const prop in ext) {
485
+ if (prop.charAt(0) === '@')
486
+ chooseAssignment( prop, ext );
487
+ }
488
+ }
489
+
490
+ /**
491
+ * @param {XSN.Artifact} art
492
+ * @param {XSN.Extension[]} [extensions]
493
+ * @param {string} [prop]
494
+ * @param {string} [name]
495
+ * @param {object} [parent]
496
+ * @param {string} [kind]
497
+ */
498
+ function annotateMembers( art, extensions, prop, name, parent, kind ) {
499
+ const showMsg = !art && parent && parent.kind !== 'annotate';
500
+ if (!art && extensions && extensions.length) {
501
+ if (Array.isArray( parent ))
502
+ return;
503
+ const parentExt = extensionFor(parent);
504
+ art = parentExt[prop] && parentExt[prop][name];
505
+ if (!art) {
506
+ art = {
507
+ kind, // for setMemberParent()
508
+ name: { id: name, location: extensions[0].name.location },
509
+ location: extensions[0].location,
510
+ };
511
+ setMemberParent( art, name, parentExt, prop );
512
+ art.kind = 'annotate'; // after setMemberParent()!
513
+ }
514
+ }
515
+
516
+ for (const ext of extensions || []) {
517
+ if ('_artifact' in ext.name) // already applied
518
+ continue;
519
+ setArtifactLink( ext.name, art );
520
+
521
+ if (art) {
522
+ defineAnnotations( ext, art, ext._block, ext.kind );
523
+ // eslint-disable-next-line no-shadow
524
+ forEachMember( ext, ( elem, name, prop ) => {
525
+ storeExtension( elem, name, prop, art, ext._block );
526
+ });
527
+ }
528
+ if (showMsg) {
529
+ // somehow similar to checkDefinitions():
530
+ const feature = kindProperties[parent.kind][prop];
531
+ if (prop === 'elements' || prop === 'enum') {
532
+ if (!feature) {
533
+ warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
534
+ 'Elements only exist in entities, types or typed constructs' );
535
+ }
536
+ else {
537
+ notFound( 'anno-undefined-element', ext.name.location, art,
538
+ { art: searchName( parent, name, parent.enum ? 'enum' : 'element' ) },
539
+ parent.elements || parent.enum );
540
+ }
541
+ }
542
+ else if (prop === 'actions') {
543
+ if (!feature) {
544
+ warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
545
+ 'Actions and functions only exist top-level and for entities' );
546
+ }
547
+ else {
548
+ notFound( 'anno-undefined-action', ext.name.location, art,
549
+ { art: searchName( parent, name, 'action' ) },
550
+ parent.actions );
551
+ }
552
+ }
553
+ else if (!feature) {
554
+ warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
555
+ 'Parameters only exist for actions or functions' );
556
+ } // TODO: entities betaMod
557
+ else {
558
+ notFound( 'anno-undefined-param', ext.name.location, art,
559
+ { art: searchName( parent, name, 'param' ) },
560
+ parent.params );
561
+ }
562
+ }
563
+ }
564
+ if (art && art._annotate) {
565
+ if (art.kind === 'action' || art.kind === 'function') {
566
+ expandParameters( art );
567
+ if (art.returns)
568
+ effectiveType( art.returns );
569
+ }
570
+ const aor = art.returns || art;
571
+ const obj = aor.items || aor.targetAspect || aor;
572
+ // Currently(?), effectiveType() does not calculate the effective type of
573
+ // its line item:
574
+ effectiveType( obj );
575
+ if (art._annotate.elements)
576
+ setExpandStatusAnnotate( aor, 'annotate' );
577
+ annotate( obj, 'element', 'elements', 'enum', art );
578
+ annotate( art, 'action', 'actions' );
579
+ annotate( art, 'param', 'params' );
580
+ // const { returns } = art._annotate;
581
+ // if (returns) {
582
+ // const dict = returns.elements;
583
+ // const env = obj.returns && obj.returns.elements || null;
584
+ // for (const n in dict)
585
+ // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
586
+ // }
587
+ }
588
+ return;
589
+
590
+ function notFound( msgId, location, address, args, validDict ) {
591
+ // TODO: probably move this to shared.js and use for EXTEND, too
592
+ const msg = message( msgId, [ location, address ], args );
593
+ attachAndEmitValidNames(msg, validDict);
594
+ }
595
+
596
+ // eslint-disable-next-line no-shadow
597
+ function annotate( obj, kind, prop, altProp, parent = obj ) {
598
+ const dict = art._annotate[prop];
599
+ const env = obj[prop] || altProp && obj[altProp] || null;
600
+ for (const n in dict)
601
+ annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
602
+ }
603
+ }
604
+
605
+ function setExpandStatusAnnotate( elem, status ) {
606
+ for (;;) {
607
+ if (elem.$expand === status)
608
+ return; // already set
609
+ elem.$expand = status; // meaning: expanded, containing annos
610
+ for (let line = elem.items; line; line = line.items)
611
+ line.$expand = status; // to-csn just uses the innermost $expand
612
+ if (!elem._main)
613
+ return;
614
+ elem = elem._parent;
615
+ }
616
+ }
617
+
618
+
619
+ function expandParameters( action ) {
620
+ // see also expandElements()
621
+ if (!enableExpandElements || !effectiveType( action ))
622
+ return;
623
+ const chain = [];
624
+ // Should we be able to consider params and returns separately?
625
+ // Probably not, let to-csn omit unchanged params/returns.
626
+ while (action._origin && !action.params) {
627
+ chain.push( action );
628
+ action = action._origin;
629
+ }
630
+ chain.reverse();
631
+ for (const art of chain) {
632
+ const origin = art._origin;
633
+ if (!art.params && origin.params) {
634
+ for (const name in origin.params) {
635
+ // TODO: we could check _annotate here to decide whether we really
636
+ // not to create proxies
637
+ const orig = origin.params[name];
638
+ linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
639
+ .$inferred = 'expand-param';
640
+ }
641
+ }
642
+ if (!art.returns && origin.returns) {
643
+ // TODO: make linkToOrigin() work for returns, kind/name?
644
+ const location = weakLocation( origin.returns.location );
645
+ art.returns = {
646
+ name: Object.assign( {}, art.name, { id: '', param: '', location } ),
647
+ kind: 'param',
648
+ location,
649
+ $inferred: 'expand-param',
650
+ };
651
+ setLink( art.returns, '_parent', art );
652
+ setLink( art.returns, '_main', art._main || art );
653
+ setLink( art.returns, '_origin', origin.returns );
654
+ }
655
+ }
656
+ }
657
+
658
+ function extensionFor( art ) {
659
+ if (art.kind === 'annotate')
660
+ return art;
661
+ if (art._extension)
662
+ return art._extension;
663
+
664
+ // $extension means: already applied
665
+ const ext = {
666
+ kind: art.kind, // set kind for setMemberParent()
667
+ $extension: 'exists',
668
+ location: art.location, // location( extension to existing art ) = location(art)
669
+ };
670
+ const { location } = art.name;
671
+ if (!art._main) {
672
+ ext.name = {
673
+ path: [ { id: art.name.absolute, location } ],
674
+ location,
675
+ absolute: art.name.absolute,
676
+ };
677
+ if (model.extensions)
678
+ model.extensions.push(ext);
679
+ else
680
+ model.extensions = [ ext ];
681
+ }
682
+ else {
683
+ ext.name = { id: art.name.id, location };
684
+ const parent = extensionFor( art._parent );
685
+ const kind = kindProperties[art.kind].normalized || art.kind;
686
+ // enums would be first in elements
687
+ if ( parent[kindProperties[kind].dict] &&
688
+ parent[kindProperties[kind].dict][art.name.id] )
689
+ throw new Error(art.name.id);
690
+ setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
691
+ }
692
+ ext.kind = 'annotate'; // after setMemberParent()!
693
+ setLink( art, '_extension', ext );
694
+ setArtifactLink( ext.name, art );
695
+ if (art.returns)
696
+ ext.$syntax = 'returns';
697
+ return ext;
698
+ }
699
+
700
+ /**
701
+ * Goes through all (applied) annotations in the given artifact and chooses one
702
+ * if multiple exist according to the module layer.
703
+ *
704
+ * @param {XSN.Artifact} art
705
+ */
706
+ function chooseAnnotationsInArtifact( art ) {
707
+ for (const prop in art) {
708
+ if (prop.charAt(0) === '@')
709
+ chooseAssignment( prop, art );
710
+ }
711
+ }
712
+
713
+ function chooseAssignment( annoName, art ) {
714
+ // TODO: getPath an all names
715
+ const anno = art[annoName];
716
+ if (!Array.isArray(anno)) { // just one assignment -> use it
717
+ if (removeEllipsis( anno )) {
718
+ error( 'anno-unexpected-ellipsis',
719
+ [ anno.name.location, art ], { code: '...' } );
720
+ }
721
+ return;
722
+ }
723
+ // sort assignment according to layer
724
+ const layerAnnos = Object.create(null);
725
+ for (const a of anno) {
726
+ const layer = layers.layer( a._block );
727
+ const name = (layer) ? layer.realname : '';
728
+ const done = layerAnnos[name];
729
+ if (done)
730
+ done.annos.push( a );
731
+ else
732
+ layerAnnos[name] = { layer, annos: [ a ] };
733
+ }
734
+ mergeArrayInSCCs();
735
+ art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
736
+ return;
737
+
738
+ // Merge annotations in each layer, i.e. multiple annotations in the same layer are
739
+ // stored in an array and need to be merged before different layers can be merged.
740
+ function mergeArrayInSCCs( ) {
741
+ let pos = 0;
742
+ forEachValue(layerAnnos, (layer) => {
743
+ const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
744
+ annotationPriorities[v.$priority] === annotationPriorities.define));
745
+ if (mergeSource) {
746
+ // If the source annotation (at 'define' level) contains an ellipsis,
747
+ // there is no base to apply to.
748
+ if (removeEllipsis( mergeSource )) {
749
+ error( 'anno-unexpected-ellipsis',
750
+ [ mergeSource.name.location, art ], { code: '...' } );
751
+ }
752
+ // merge source into ellipsis array annotates
753
+ layer.annos.forEach( (mergeTarget) => {
754
+ if (mergeTarget.$priority &&
755
+ annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
756
+ pos = findEllipsis( mergeTarget );
757
+ if (pos > -1) {
758
+ if (mergeSource.literal !== 'array') {
759
+ error( 'anno-mismatched-ellipsis',
760
+ [ mergeSource.name.location, art ], { code: '...' } );
761
+ return;
762
+ }
763
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
764
+ }
765
+ }
766
+ });
767
+ }
768
+ });
769
+ }
770
+
771
+ function mergeLayeredArrays( mergeTarget ) {
772
+ if (mergeTarget.literal === 'array') {
773
+ let layer = layers.layer( mergeTarget._block );
774
+ delete layerAnnos[(layer) ? layer.realname : ''];
775
+ let pos = findEllipsis( mergeTarget );
776
+ let hasRun = false;
777
+ while (pos > -1 && Object.keys( layerAnnos ).length ) {
778
+ hasRun = true;
779
+ const mergeSource = findLayerCandidate();
780
+ if (mergeSource.literal !== 'array') {
781
+ error( 'anno-mismatched-ellipsis',
782
+ [ mergeSource.name.location, art ], { code: '...' } );
783
+ return mergeTarget;
784
+ }
785
+ mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
786
+ layer = layers.layer( mergeSource._block );
787
+ delete layerAnnos[(layer) ? layer.realname : ''];
788
+ pos = findEllipsis( mergeTarget );
789
+ }
790
+ // All layers were processed. Remove excess ellipsis.
791
+ if (removeEllipsis( mergeTarget, pos ) && hasRun) {
792
+ // There shouldn't be any ellipsis or we don't have a base annotation.
793
+ // But only if the loop above has run. Otherwise the in-layer merge
794
+ // already warned about this case.
795
+ message( 'anno-unexpected-ellipsis-layers',
796
+ [ mergeTarget.name.location, art ], { code: '...' } );
797
+ }
798
+ }
799
+ return mergeTarget;
800
+ }
801
+
802
+ function mergeArrayValues( previousValue, arraySpec ) {
803
+ let prevPos = 0;
804
+ const result = [];
805
+ for (const item of arraySpec) {
806
+ const ell = item && item.literal === 'token' && item.val === '...';
807
+ if (!ell) {
808
+ result.push( item );
809
+ }
810
+ else {
811
+ let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
812
+ while (prevPos < previousValue.length) {
813
+ const prevItem = previousValue[prevPos++];
814
+ result.push( prevItem );
815
+ if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
816
+ upToSpec = false;
817
+ break;
818
+ }
819
+ }
820
+ if (upToSpec) { // non-matched UP TO
821
+ warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
822
+ 'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
823
+ }
824
+ }
825
+ }
826
+ return result;
827
+ }
828
+
829
+ function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
830
+ const { literal } = upToSpec;
831
+ if (trueIfFullUpTo !== true) { // inside struct of UP TO
832
+ if (literal !== 'struct' && literal !== 'array' )
833
+ return true;
834
+ }
835
+ else if (literal === 'struct') {
836
+ return Object.values( upToSpec.struct ).every( checkUpToSpec );
837
+ }
838
+ else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
839
+ return true;
840
+ }
841
+ error( null, [ upToSpec.location, art ],
842
+ { anno: annoName, code: '... up to', '#': literal },
843
+ {
844
+ std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
845
+ array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
846
+ // eslint-disable-next-line max-len
847
+ struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
848
+ boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
849
+ null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
850
+ } );
851
+ return false;
852
+ }
853
+
854
+ function equalUpTo( previousItem, upToSpec ) {
855
+ if (!previousItem)
856
+ return false;
857
+ if ('val' in upToSpec) {
858
+ if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
859
+ return true;
860
+ const typeUpTo = typeof upToSpec.val;
861
+ const typePrev = typeof previousItem.val;
862
+ if (typeUpTo === 'number')
863
+ return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
864
+ if (typePrev === 'number')
865
+ return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
866
+ }
867
+ else if (upToSpec.path) {
868
+ return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
869
+ }
870
+ else if (upToSpec.sym) {
871
+ return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
872
+ }
873
+ else if (upToSpec.struct && previousItem.struct) {
874
+ return Object.entries( upToSpec.struct )
875
+ .every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
876
+ }
877
+ return false;
878
+ }
879
+
880
+ function normalizeRef( node ) { // see to-csn.js
881
+ const ref = pathName( node.path );
882
+ return node.variant ? `${ ref }#${ node.variant.id }` : ref;
883
+ }
884
+
885
+ function removeEllipsis(a, pos = findEllipsis( a )) {
886
+ let count = 0;
887
+ while (a.literal === 'array' && pos > -1) {
888
+ count++;
889
+ a.val.splice(pos, 1);
890
+ pos = findEllipsis( a );
891
+ }
892
+ return count;
893
+ }
894
+
895
+ function findEllipsis(a) {
896
+ return (a.literal === 'array' && a.val)
897
+ ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
898
+ }
899
+
900
+ function findLayerCandidate() {
901
+ // collect assignments of upper layers (are in no _layerExtends)
902
+ const exts = Object.keys( layerAnnos ).map( layerExtends );
903
+ const allExtends = Object.assign( Object.create(null), ...exts );
904
+ const collected = [];
905
+ for (const name in layerAnnos) {
906
+ if (!(name in allExtends))
907
+ collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
908
+ }
909
+ // inspect collected assignments - choose the one or signal error
910
+ const justOnePerLayer = collected.every( annos => annos.length === 1);
911
+ if (!justOnePerLayer || collected.length > 1) {
912
+ for (const annos of collected) {
913
+ for (const a of annos ) {
914
+ // Only the message ID is different.
915
+ if (justOnePerLayer) {
916
+ message( 'anno-duplicate-unrelated-layer',
917
+ [ a.name.location, art ], { anno: annoName },
918
+ 'Duplicate assignment with $(ANNO)' );
919
+ }
920
+ else {
921
+ message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
922
+ }
923
+ }
924
+ }
925
+ }
926
+ return collected[0][0]; // just choose any one with error
927
+ }
928
+
929
+ function layerExtends( name ) {
930
+ const { layer } = layerAnnos[name];
931
+ return layer && layer._layerExtends;
932
+ }
933
+ }
934
+
935
+ function prioritizedAnnos( annos ) {
936
+ let prio = 0;
937
+ let r = [];
938
+ for (const a of annos) {
939
+ const p = annotationPriorities[a.$priority] || annotationPriorities.define;
940
+ if (p === prio) {
941
+ r.push(a);
942
+ }
943
+ else if (p > prio) {
944
+ r = [ a ];
945
+ prio = p;
946
+ }
947
+ }
948
+ return r;
949
+ }
950
+
951
+ // Phase 4 - queries and associations --------------------------------------
952
+
953
+ function resolveQuery( query ) {
954
+ if (!query._main) // parse error
955
+ return;
956
+ traverseQueryPost( query, null, populateQuery ); // TODO: still necessary?
957
+ forEachGeneric( query, '$tableAliases', ( alias ) => {
958
+ // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
959
+ if (alias.kind === 'mixin')
960
+ resolveRefs( alias ); // mixin element
961
+ else if (alias.kind !== '$self')
962
+ // pure path has been resolved, resolve args and filter now:
963
+ resolveExpr( alias, 'from', query._parent );
964
+ } );
965
+ for (const col of query.$inlines)
966
+ resolveExpr( col.value, 'expr', col, undefined, true );
967
+ // for (const col of query.$inlines)
968
+ // if (!col.value.path) throw Error(col.name.element)
969
+ if (query !== query._main._leadingQuery) // will be done later
970
+ forEachGeneric( query, 'elements', resolveRefs );
971
+ if (query.from)
972
+ resolveJoinOn( query.from );
973
+ if (query.where)
974
+ resolveExpr( query.where, 'expr', query, query._combined );
975
+ if (query.groupBy)
976
+ resolveBy( query.groupBy, 'expr' );
977
+ resolveExpr( query.having, 'expr', query, query._combined );
978
+ if (query.$orderBy) // ORDER BY from UNION:
979
+ // TODO clarify: can I access the tab alias of outer queries? If not:
980
+ // 4th arg query._main instead query._parent.
981
+ resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
982
+ if (query.orderBy) { // ORDER BY
983
+ // search in `query.elements` after having checked table aliases of the current query
984
+ resolveBy( query.orderBy, 'expr', query.elements );
985
+ // TODO: disallow resulting element ref if in expression!
986
+ // Necessary to check it in the compiler as it might work with other semantics on DB!
987
+ // (we could downgrade it to a warning if name is equal to unique source element name)
988
+ // TODO: Some helping text mentioning an alias name would be useful
989
+ }
990
+ return;
991
+
992
+ function resolveJoinOn( join ) {
993
+ if (join && join.args) { // JOIN
994
+ for (const j of join.args)
995
+ resolveJoinOn( j );
996
+ if (join.on)
997
+ resolveExpr( join.on, 'expr', query, query._combined );
998
+ // TODO: check restrictions according to join "query"
999
+ }
1000
+ }
1001
+
1002
+ // Note the strange name resolution (dynamic part) for ORDER BY: the same
1003
+ // as for select items if it is an expression, but first look at select
1004
+ // item alias (i.e. like `$projection.NAME` if it is a path. If it is an
1005
+ // ORDER BY of an UNION, do not allow any dynamic path in an expression,
1006
+ // and only allow the elements of the leading query if it is a path.
1007
+ //
1008
+ // This seem to be similar, but different in SQLite 3.22.0: ORDER BY seems
1009
+ // to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
1010
+ // resolution seems to use select item aliases from all SELECTs of the
1011
+ // UNION (see <SQLite>/test/tkt2822.test).
1012
+ function resolveBy( array, mode, pathDict, q ) {
1013
+ for (const value of array ) {
1014
+ if (value)
1015
+ resolveExpr( value, mode, q || query, value.path && pathDict );
1016
+ }
1017
+ }
1018
+ }
1019
+
1020
+ function resolveTarget( art, obj ) {
1021
+ if (art !== obj && obj.on && obj.$inferred !== 'REDIRECTED') {
1022
+ message( 'assoc-in-array', [ obj.on.location, art ], {},
1023
+ // TODO: also check parameter parent, two messages?
1024
+ 'An association can\'t be used for arrays or parameters' );
1025
+ setArtifactLink( obj.target, undefined );
1026
+ return;
1027
+ }
1028
+ const target = resolvePath( obj.target, 'target', art );
1029
+ if (obj.on) {
1030
+ if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1031
+ // TODO: test of .items a bit unclear - we should somehow restrict the
1032
+ // use of unmanaged assocs in MANY, at least with $self
1033
+ // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1034
+ const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
1035
+ obj.type.path[0].id === 'cds.Composition';
1036
+ message( 'assoc-as-type', [ obj.on.location, art ],
1037
+ { '#': isComposition ? 'comp' : 'std' }, {
1038
+ std: 'An unmanaged association can\'t be defined as type',
1039
+ comp: 'An unmanaged composition can\'t be defined as type',
1040
+ });
1041
+ // TODO: also warning if inside structure
1042
+ }
1043
+ else if (obj.$inferred !== 'REDIRECTED') {
1044
+ // TODO: extra with $inferred (to avoid messages)?
1045
+ // TODO: in the ON condition of an explicitly provided model entity
1046
+ // which is going to be implicitly redirected, we can never navigate
1047
+ // along associations, even not to the foreign keys (at least if they
1048
+ // are renamed) - introduce extra 'expected' which inspects REDIRECTED
1049
+ resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
1050
+ }
1051
+ else {
1052
+ const elements = Object.create( art._parent.elements );
1053
+ elements[art.name.id] = obj;
1054
+ resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art, elements );
1055
+ }
1056
+ }
1057
+ else if (art.kind === 'mixin') {
1058
+ error( 'assoc-in-mixin', [ obj.target.location, art ], {},
1059
+ 'Managed associations are not allowed for MIXIN elements' );
1060
+ }
1061
+ else if (target && !obj.foreignKeys && target.kind === 'entity') {
1062
+ if (obj.$inferred === 'REDIRECTED') {
1063
+ addImplicitForeignKeys( art, obj, target );
1064
+ }
1065
+ else if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
1066
+ resolveRedirected( art, target );
1067
+ }
1068
+ else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
1069
+ addImplicitForeignKeys( art, obj, target );
1070
+ }
1071
+ // else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
1072
+ }
1073
+ // else console.log( message( null, obj.location, obj, {target}, 'Info','NORE').toString())
1074
+ }
1075
+
1076
+ function addImplicitForeignKeys( art, obj, target ) {
1077
+ obj.foreignKeys = Object.create(null);
1078
+ forEachInOrder( target, 'elements', ( elem, name ) => {
1079
+ if (elem.key && elem.key.val) {
1080
+ const { location } = art.target;
1081
+ const key = {
1082
+ name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()
1083
+ kind: 'key',
1084
+ targetElement: { path: [ { id: elem.name.id, location } ], location },
1085
+ location,
1086
+ $inferred: 'keys',
1087
+ };
1088
+ setMemberParent( key, name, art );
1089
+ dictAdd( obj.foreignKeys, name, key );
1090
+ setArtifactLink( key.targetElement, elem );
1091
+ setArtifactLink( key.targetElement.path[0], elem );
1092
+ setLink( key, '_effectiveType', effectiveType(elem) );
1093
+ dependsOn(key, elem, location);
1094
+ dependsOnSilent(art, key);
1095
+ }
1096
+ });
1097
+ obj.foreignKeys[$inferred] = 'keys';
1098
+ }
1099
+
1100
+ function addForeignKeyNavigations( art ) {
1101
+ art.$keysNavigation = Object.create(null);
1102
+ forEachGeneric( art, 'foreignKeys', ( key ) => {
1103
+ if (!key.targetElement || !key.targetElement.path)
1104
+ return;
1105
+ let dict = art.$keysNavigation;
1106
+ const last = key.targetElement.path[key.targetElement.path.length - 1];
1107
+ for (const item of key.targetElement.path) {
1108
+ let nav = dict[item.id];
1109
+ if (!nav) {
1110
+ nav = {};
1111
+ dict[item.id] = nav;
1112
+ if (item === last)
1113
+ setArtifactLink( nav, key );
1114
+ else
1115
+ nav.$keysNavigation = Object.create(null);
1116
+ }
1117
+ else if (item === last || nav._artifact) {
1118
+ error( 'duplicate-key-ref', [ item.location, key ], {},
1119
+ 'The same target reference has already been used in a key definition' );
1120
+ return;
1121
+ }
1122
+ dict = nav.$keysNavigation;
1123
+ }
1124
+ } );
1125
+ }
1126
+
1127
+ // TODO: add this somehow to tweak-assocs.js ?
1128
+ function resolveRedirected( elem, target ) {
1129
+ setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
1130
+ const assoc = directType( elem );
1131
+ const origType = assoc && effectiveType( assoc );
1132
+ if (!origType || !origType.target) {
1133
+ error( 'redirected-no-assoc', [ elem.target.location, elem ], {},
1134
+ 'Only an association can be redirected' );
1135
+ return;
1136
+ }
1137
+ // console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
1138
+ // .toString(), elem.value)
1139
+ const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
1140
+ if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
1141
+ if (origType.on) {
1142
+ error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
1143
+ // TODO: Better text ?
1144
+ 'The ON condition is not rewritten here - provide an explicit ON condition' );
1145
+ return;
1146
+ }
1147
+ }
1148
+ const origTarget = origType.target._artifact;
1149
+ if (!origTarget || !target)
1150
+ return;
1151
+
1152
+ const chain = [];
1153
+ if (target === origTarget) {
1154
+ if (!elem.target.$inferred) {
1155
+ info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
1156
+ 'The redirected target is the original $(ART)' );
1157
+ }
1158
+ setLink( elem, '_redirected', chain ); // store the chain
1159
+ return;
1160
+ }
1161
+ if (elem.foreignKeys || elem.on)
1162
+ return; // TODO: or should we still bring an msg if nothing in common?
1163
+ // now check whether target and origTarget are "related"
1164
+ while (target.query) {
1165
+ const from = target.query.args ? {} : target.query.from;
1166
+ if (!from)
1167
+ return; // parse error - TODO: or UNION?
1168
+ if (!from.path) {
1169
+ warning( 'redirected-to-complex', [ elem.target.location, elem ],
1170
+ { art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
1171
+ {
1172
+ std: 'Redirection involves the complex view $(ART)',
1173
+ target: 'The redirected target $(ART) is a complex view',
1174
+ });
1175
+ break;
1176
+ }
1177
+ target = from._artifact;
1178
+ if (!target)
1179
+ return;
1180
+ chain.push( from );
1181
+ if (target === origTarget) {
1182
+ chain.reverse();
1183
+ setLink( elem, '_redirected', chain );
1184
+ return;
1185
+ }
1186
+ }
1187
+ let redirected = null;
1188
+ let news = [ { chain: chain.reverse(), sources: [ target ] } ];
1189
+ const dict = Object.create(null);
1190
+ while (news.length) {
1191
+ const outer = news;
1192
+ news = [];
1193
+ for (const o of outer) {
1194
+ for (const s of o.sources) {
1195
+ const art = (s.kind === '$tableAlias') ? s._origin : s;
1196
+ if (art !== origTarget) {
1197
+ if (findOrig( o.chain, s, art ) && !redirected) // adds to news []
1198
+ redirected = false; // do not report further error
1199
+ }
1200
+ else if (!redirected) {
1201
+ redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
1202
+ }
1203
+ else {
1204
+ error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
1205
+ 'The redirected target originates more than once from $(ART)' );
1206
+ return;
1207
+ }
1208
+ }
1209
+ }
1210
+ }
1211
+ if (redirected) {
1212
+ setLink( elem, '_redirected', redirected );
1213
+ }
1214
+ else if (redirected == null) {
1215
+ error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
1216
+ 'The redirected target does not originate from $(ART)' );
1217
+ }
1218
+ return;
1219
+
1220
+ // B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
1221
+ // what does a: redirected to C means?
1222
+ // -> collect all elements Qi used in ON (corr: foreign keys)
1223
+ // -> only use an tableAlias which has propagation for all elements
1224
+ // no - error if the original target can be reached twice
1225
+ // even better: disallow complex view (try as error first)
1226
+
1227
+ // eslint-disable-next-line no-shadow
1228
+ function findOrig( chain, alias, art ) {
1229
+ if (!art || dict[art.name.absolute])
1230
+ // some include ref or query source cannot be found, or cyclic ref
1231
+ return true;
1232
+ dict[art.name.absolute] = true;
1233
+
1234
+ if (art.includes) {
1235
+ news.push( {
1236
+ chain: [ art, ...chain ],
1237
+ sources: art.includes
1238
+ .map( r => r._artifact )
1239
+ .filter( i => i ), // _artifact may be `null` if the include cannot be found
1240
+ } );
1241
+ }
1242
+ const query = art._leadingQuery;
1243
+ if (!query)
1244
+ return false; // non-query entity
1245
+ if (!query.$tableAliases) // previous error in query definition
1246
+ return true;
1247
+ const sources = [];
1248
+ for (const n in query.$tableAliases) {
1249
+ const a = query.$tableAliases[n];
1250
+ if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
1251
+ sources.push( a );
1252
+ }
1253
+ if (alias.kind === '$tablealias')
1254
+ news.push( { chain: [ alias, ...chain ], sources } );
1255
+ else
1256
+ news.push( { chain, sources } );
1257
+ return false;
1258
+ }
1259
+ }
1260
+
1261
+ //--------------------------------------------------------------------------
1262
+ // General resolver functions
1263
+ //--------------------------------------------------------------------------
1264
+
1265
+ // Resolve the type and its arguments if applicable.
1266
+ function resolveTypeExpr( art, user ) {
1267
+ const typeArt = resolveType( art.type, user );
1268
+ if (typeArt)
1269
+ resolveTypeArguments( art, typeArt, user );
1270
+ }
1271
+
1272
+ function resolveExpr( expr, expected, user, extDict, expandOrInline) {
1273
+ // TODO: when we have rewritten the resolvePath functions,
1274
+ // define a traverseExpr() in ./utils.js
1275
+ // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
1276
+ if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
1277
+ return;
1278
+ if (Array.isArray(expr)) {
1279
+ expr.forEach( e => resolveExpr( e, expected, user, extDict ) );
1280
+ return;
1281
+ }
1282
+
1283
+ if (expr.type) // e.g. cast( a as Integer )
1284
+ resolveTypeExpr( expr, user );
1285
+
1286
+ if (expr.path) {
1287
+ if (expr.$expected === 'exists') {
1288
+ error( 'expr-unexpected-exists', [ expr.location, user ], {},
1289
+ 'An EXISTS predicate is not expected here' );
1290
+ // We complain about the EXISTS before, as EXISTS subquery is also not
1291
+ // supported (avoid that word if you do not want to get tickets when it
1292
+ // will be supported), TODO: location of EXISTS
1293
+ expr.$expected = 'approved-exists'; // only complain once
1294
+ }
1295
+ if (expected instanceof Function) {
1296
+ expected( expr, user, extDict );
1297
+ return;
1298
+ }
1299
+ resolvePath( expr, expected, user, extDict );
1300
+
1301
+ const last = !expandOrInline && expr.path[expr.path.length - 1];
1302
+ for (const step of expr.path) {
1303
+ if (step && (step.args || step.where || step.cardinality) &&
1304
+ step._artifact && !Array.isArray( step._artifact ) )
1305
+ resolveParamsAndWhere( step, expected, user, extDict, step === last );
1306
+ }
1307
+ }
1308
+ else if (expr.query) {
1309
+ const { query } = expr;
1310
+ if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
1311
+ // traverseQueryPost( query, false, resolveQuery );
1312
+ }
1313
+ else {
1314
+ error( 'expr-no-subquery', [ expr.location, user ], {},
1315
+ 'Subqueries are not supported here' );
1316
+ }
1317
+ }
1318
+ else if (expr.op && expr.args) {
1319
+ const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
1320
+ args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
1321
+ }
1322
+ if (expr.suffix && isDeprecatedEnabled( options )) {
1323
+ const { location } = expr.suffix[0] || expr;
1324
+ error( null, [ location, user ], { prop: 'deprecated' },
1325
+ 'Window functions are not supported if $(PROP) options are set' );
1326
+ }
1327
+ if (expr.suffix)
1328
+ expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
1329
+ }
1330
+
1331
+ function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
1332
+ const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
1333
+ const type = alias || effectiveType( step._artifact );
1334
+ const art = (type && type.target) ? type.target._artifact : type;
1335
+ if (!art)
1336
+ return;
1337
+ const entity = (art.kind === 'entity') &&
1338
+ (!isLast || [ 'from', 'exists', 'approved-exists' ].includes( expected )) && art;
1339
+ if (step.args)
1340
+ resolveParams( step.args, art, entity, expected, user, extDict, step.location );
1341
+ if (entity) {
1342
+ if (step.where)
1343
+ resolveExpr( step.where, 'filter', user, environment( type ) );
1344
+ }
1345
+ else if (step.where && step.where.location || step.cardinality ) {
1346
+ const location = combinedLocation( step.where, step.cardinality );
1347
+ // XSN TODO: filter$location including […]
1348
+ message( 'expr-no-filter', [ location, user ], { '#': expected },
1349
+ {
1350
+ std: 'A filter can only be provided when navigating along associations',
1351
+ from: 'A filter can only be provided for the source entity or associations',
1352
+ } );
1353
+ }
1354
+ }
1355
+
1356
+ function resolveParams( dict, art, entity, expected, user, extDict, stepLocation ) {
1357
+ if (!entity || !entity.params) {
1358
+ let first = dict[Object.keys(dict)[0]];
1359
+ if (Array.isArray(first))
1360
+ first = first[0];
1361
+ message( 'args-no-params',
1362
+ [ dict[$location] ||
1363
+ dictLocation( dict, first && first.name && first.name.location || stepLocation),
1364
+ user ],
1365
+ { art, '#': (entity ? 'entity' : expected ) },
1366
+ {
1367
+ std: 'Parameters can only be provided when navigating along associations',
1368
+ from: 'Parameters can only be provided for the source entity or associations',
1369
+ // or extra message id for entity?
1370
+ entity: 'Entity $(ART) has no parameters',
1371
+ } );
1372
+ return;
1373
+ }
1374
+ const exp = (expected === 'from') ? 'expr' : expected;
1375
+ if (Array.isArray(dict)) {
1376
+ message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
1377
+ 'Named parameters must be provided for the entity' );
1378
+ for (const a of dict)
1379
+ resolveExpr( a, exp, user, extDict );
1380
+ return;
1381
+ }
1382
+ // TODO: allow to specify expected for arguments in in specExpected
1383
+ for (const name in dict) {
1384
+ const param = art.params[name];
1385
+ const arg = dict[name];
1386
+ for (const a of Array.isArray(arg) ? arg : [ arg ]) {
1387
+ setArtifactLink( a.name, param );
1388
+ if (!param) {
1389
+ message( 'args-undefined-param', [ a.name.location, user ], { art, id: name },
1390
+ 'Entity $(ART) has no parameter $(ID)' );
1391
+ }
1392
+ resolveExpr( a, exp, user, extDict );
1393
+ }
1394
+ }
1395
+ }
1396
+ }
1397
+
1398
+ // Return condensed info about reference in select item
1399
+ // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1400
+ // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1401
+ // - mixinElem -> { navigation: mixinElement, item: path[0] }
1402
+ // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1403
+ // - $self -> { item: undefined, tableAlias: $self }
1404
+ // - $parameters.P, :P -> {}
1405
+ // - $now, current_date -> {}
1406
+ // - undef, redef -> {}
1407
+ // With 'navigation': store that navigation._artifact is projected
1408
+ // With 'navigation': rewrite its ON condition
1409
+ // With navigation: Do KEY propagation
1410
+ //
1411
+ // TODO: re-think this function, copied in populate.js and tweak-assocs.js
1412
+ function pathNavigation( ref ) {
1413
+ // currently, indirectly projectable elements are not included - we might
1414
+ // keep it this way! If we want them to be included - be aware: cycles
1415
+ if (!ref._artifact)
1416
+ return {};
1417
+ let item = ref.path && ref.path[0];
1418
+ const root = item && item._navigation;
1419
+ if (!root)
1420
+ return {};
1421
+ if (root.kind === '$navElement')
1422
+ return { navigation: root, item, tableAlias: root._parent };
1423
+ if (root.kind === 'mixin')
1424
+ return { navigation: root, item };
1425
+ item = ref.path[1];
1426
+ if (root.kind === '$self')
1427
+ return { item, tableAlias: root };
1428
+ if (root.kind !== '$tableAlias' || ref.path.length < 2)
1429
+ return {}; // should not happen
1430
+ return { navigation: root.elements[item.id], item, tableAlias: root };
1431
+ }
1432
+
1433
+ module.exports = resolve;