@sap/cds-compiler 2.11.2 → 2.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CHANGELOG.md +175 -2
  2. package/bin/.eslintrc.json +1 -2
  3. package/bin/cds_update_identifiers.js +10 -8
  4. package/bin/cdsc.js +23 -17
  5. package/bin/cdsse.js +2 -2
  6. package/bin/cdsv2m.js +3 -2
  7. package/doc/CHANGELOG_ARCHIVE.md +1 -1
  8. package/doc/CHANGELOG_BETA.md +25 -6
  9. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  10. package/doc/NameResolution.md +21 -16
  11. package/lib/api/main.js +32 -79
  12. package/lib/api/options.js +3 -2
  13. package/lib/api/validate.js +2 -1
  14. package/lib/backends.js +16 -26
  15. package/lib/base/dictionaries.js +0 -8
  16. package/lib/base/error.js +26 -0
  17. package/lib/base/keywords.js +10 -19
  18. package/lib/base/location.js +9 -4
  19. package/lib/base/message-registry.js +75 -9
  20. package/lib/base/messages.js +31 -35
  21. package/lib/base/model.js +2 -62
  22. package/lib/base/optionProcessorHelper.js +246 -183
  23. package/lib/checks/.eslintrc.json +2 -0
  24. package/lib/checks/actionsFunctions.js +2 -1
  25. package/lib/checks/annotationsOData.js +1 -1
  26. package/lib/checks/cdsPersistence.js +2 -1
  27. package/lib/checks/emptyOrOnlyVirtual.js +2 -2
  28. package/lib/checks/enricher.js +17 -1
  29. package/lib/checks/foreignKeys.js +4 -4
  30. package/lib/checks/invalidTarget.js +3 -1
  31. package/lib/checks/managedInType.js +4 -4
  32. package/lib/checks/managedWithoutKeys.js +3 -1
  33. package/lib/checks/queryNoDbArtifacts.js +1 -3
  34. package/lib/checks/selectItems.js +4 -4
  35. package/lib/checks/sql-snippets.js +94 -0
  36. package/lib/checks/types.js +1 -1
  37. package/lib/checks/unknownMagic.js +1 -1
  38. package/lib/checks/validator.js +12 -7
  39. package/lib/compiler/assert-consistency.js +12 -8
  40. package/lib/compiler/base.js +0 -1
  41. package/lib/compiler/builtins.js +42 -21
  42. package/lib/compiler/checks.js +46 -12
  43. package/lib/compiler/cycle-detector.js +1 -1
  44. package/lib/compiler/define.js +1103 -0
  45. package/lib/compiler/extend.js +983 -0
  46. package/lib/compiler/finalize-parse-cdl.js +231 -0
  47. package/lib/compiler/index.js +46 -39
  48. package/lib/compiler/kick-start.js +190 -0
  49. package/lib/compiler/moduleLayers.js +4 -4
  50. package/lib/compiler/populate.js +1226 -0
  51. package/lib/compiler/propagator.js +113 -47
  52. package/lib/compiler/resolve.js +1433 -0
  53. package/lib/compiler/shared.js +100 -65
  54. package/lib/compiler/tweak-assocs.js +529 -0
  55. package/lib/compiler/utils.js +215 -33
  56. package/lib/edm/.eslintrc.json +5 -0
  57. package/lib/edm/annotations/genericTranslation.js +38 -25
  58. package/lib/edm/annotations/preprocessAnnotations.js +3 -3
  59. package/lib/edm/csn2edm.js +10 -9
  60. package/lib/edm/edm.js +19 -20
  61. package/lib/edm/edmPreprocessor.js +166 -95
  62. package/lib/edm/edmUtils.js +127 -34
  63. package/lib/gen/Dictionary.json +92 -43
  64. package/lib/gen/language.checksum +1 -1
  65. package/lib/gen/language.interp +11 -1
  66. package/lib/gen/language.tokens +86 -82
  67. package/lib/gen/languageLexer.interp +18 -1
  68. package/lib/gen/languageLexer.js +925 -847
  69. package/lib/gen/languageLexer.tokens +78 -74
  70. package/lib/gen/languageParser.js +5434 -4298
  71. package/lib/json/from-csn.js +59 -17
  72. package/lib/json/to-csn.js +189 -71
  73. package/lib/language/antlrParser.js +3 -3
  74. package/lib/language/docCommentParser.js +3 -3
  75. package/lib/language/errorStrategy.js +26 -8
  76. package/lib/language/genericAntlrParser.js +144 -53
  77. package/lib/language/language.g4 +424 -200
  78. package/lib/language/multiLineStringParser.js +536 -0
  79. package/lib/main.d.ts +550 -61
  80. package/lib/main.js +38 -11
  81. package/lib/model/api.js +3 -1
  82. package/lib/model/csnRefs.js +322 -198
  83. package/lib/model/csnUtils.js +226 -370
  84. package/lib/model/enrichCsn.js +124 -69
  85. package/lib/model/revealInternalProperties.js +29 -7
  86. package/lib/model/sortViews.js +10 -2
  87. package/lib/modelCompare/compare.js +17 -12
  88. package/lib/optionProcessor.js +8 -3
  89. package/lib/render/.eslintrc.json +1 -2
  90. package/lib/render/DuplicateChecker.js +1 -1
  91. package/lib/render/manageConstraints.js +36 -33
  92. package/lib/render/toCdl.js +174 -275
  93. package/lib/render/toHdbcds.js +203 -122
  94. package/lib/render/toRename.js +7 -10
  95. package/lib/render/toSql.js +161 -82
  96. package/lib/render/utils/common.js +22 -8
  97. package/lib/render/utils/sql.js +10 -7
  98. package/lib/render/utils/stringEscapes.js +111 -0
  99. package/lib/sql-identifier.js +1 -1
  100. package/lib/transform/.eslintrc.json +5 -0
  101. package/lib/transform/braceExpression.js +4 -2
  102. package/lib/transform/db/.eslintrc.json +2 -0
  103. package/lib/transform/db/applyTransformations.js +212 -0
  104. package/lib/transform/db/assertUnique.js +1 -1
  105. package/lib/transform/db/associations.js +187 -0
  106. package/lib/transform/db/cdsPersistence.js +150 -0
  107. package/lib/transform/db/constraints.js +61 -56
  108. package/lib/transform/db/expansion.js +50 -29
  109. package/lib/transform/db/flattening.js +556 -106
  110. package/lib/transform/db/groupByOrderBy.js +3 -1
  111. package/lib/transform/db/temporal.js +236 -0
  112. package/lib/transform/db/transformExists.js +103 -28
  113. package/lib/transform/db/views.js +92 -44
  114. package/lib/transform/draft/.eslintrc.json +38 -0
  115. package/lib/transform/{db/draft.js → draft/db.js} +9 -7
  116. package/lib/transform/draft/odata.js +227 -0
  117. package/lib/transform/forHanaNew.js +98 -783
  118. package/lib/transform/forOdataNew.js +22 -175
  119. package/lib/transform/localized.js +36 -32
  120. package/lib/transform/odata/generateForeignKeyElements.js +3 -3
  121. package/lib/transform/odata/referenceFlattener.js +95 -89
  122. package/lib/transform/odata/structureFlattener.js +1 -1
  123. package/lib/transform/odata/toFinalBaseType.js +86 -12
  124. package/lib/transform/odata/typesExposure.js +5 -5
  125. package/lib/transform/odata/utils.js +2 -2
  126. package/lib/transform/transformUtilsNew.js +47 -33
  127. package/lib/transform/translateAssocsToJoins.js +13 -30
  128. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  129. package/lib/transform/universalCsn/coreComputed.js +170 -0
  130. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  131. package/lib/transform/universalCsn/utils.js +63 -0
  132. package/lib/utils/file.js +8 -3
  133. package/lib/utils/objectUtils.js +30 -0
  134. package/lib/utils/timetrace.js +8 -2
  135. package/package.json +1 -1
  136. package/share/messages/README.md +26 -0
  137. package/lib/compiler/definer.js +0 -2349
  138. package/lib/compiler/resolver.js +0 -2922
  139. package/lib/transform/db/helpers.js +0 -58
  140. package/lib/transform/universalCsnEnricher.js +0 -67
@@ -1,2922 +0,0 @@
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 './definer.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
- 'use strict';
34
-
35
- const {
36
- isDeprecatedEnabled,
37
- isBetaEnabled,
38
- setProp,
39
- forEachDefinition,
40
- forEachMember,
41
- forEachGeneric,
42
- forEachInOrder,
43
- } = require('../base/model');
44
- const {
45
- dictAdd, dictAddArray,
46
- } = require('../base/dictionaries');
47
- const { dictLocation } = require('../base/location');
48
- const { searchName, weakLocation } = require('../base/messages');
49
- const { combinedLocation } = require('../base/location');
50
-
51
- const { kindProperties } = require('./base');
52
- const {
53
- pushLink,
54
- setLink,
55
- augmentPath,
56
- splitIntoPath,
57
- linkToOrigin,
58
- setMemberParent,
59
- withAssociation,
60
- storeExtension,
61
- dependsOn,
62
- dependsOnSilent,
63
- } = require('./utils');
64
-
65
- const detectCycles = require('./cycle-detector');
66
- const layers = require('./moduleLayers');
67
-
68
- const annotationPriorities = {
69
- define: 1, extend: 2, annotate: 2, edmx: 3,
70
- };
71
- const $inferred = Symbol.for('cds.$inferred');
72
-
73
- // Export function of this file. Resolve type references in augmented CSN
74
- // `model`. If the model has a property argument `messages`, do not throw
75
- // exception in case of an error, but push the corresponding error object to
76
- // that property (should be a vector).
77
- function resolve( model ) {
78
- const { options } = model;
79
- // Get shared functionality and the message function:
80
- const {
81
- info, warning, error, message,
82
- } = model.$messageFunctions;
83
- const {
84
- resolvePath,
85
- resolveTypeArguments,
86
- defineAnnotations,
87
- attachAndEmitValidNames,
88
- initArtifact,
89
- lateExtensions,
90
- projectionAncestor,
91
- } = model.$functions;
92
- model.$volatileFunctions.environment = environment;
93
-
94
- /** @type {any} may also be a boolean */
95
- let newAutoExposed = [];
96
-
97
- // behavior depending on option `deprecated`:
98
- const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
99
- // TODO: we should get rid of noElementsExpansion soon; both
100
- // beta.nestedProjections and beta.universalCsn do not work with it.
101
- const scopedRedirections
102
- = enableExpandElements &&
103
- !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
104
- !isDeprecatedEnabled( options, 'shortAutoexposed' ) &&
105
- !isDeprecatedEnabled( options, 'longAutoexposed' ) &&
106
- !isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ) &&
107
- !isDeprecatedEnabled( options, 'noScopedRedirections' );
108
- const autoexposeViaComposition
109
- = (isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ))
110
- ? 'Composition'
111
- : true;
112
-
113
- return doResolve();
114
-
115
- function doResolve() {
116
- // Phase 1: check paths in usings:
117
- forEachGeneric( model, 'sources', resolveUsings );
118
- // Phase 2: calculate/init view elements & collect views in order:
119
- forEachDefinition( model, traverseElementEnvironments );
120
- while (newAutoExposed.length) {
121
- // console.log( newAutoExposed.map( a => a.name.absolute ) )
122
- const all = newAutoExposed;
123
- newAutoExposed = [];
124
- all.forEach( traverseElementEnvironments );
125
- }
126
- newAutoExposed = true; // internal error if auto-expose after here
127
- // It might be that we need to call propagateKeyProps() and
128
- // addImplicitForeignKeys() in Phase 2, as we might need to know the
129
- // foreign keys in Phase 2 (foreign key access w/o JOINs).
130
-
131
- // Phase 3: calculate keys along simple queries in collected views:
132
- model._entities.forEach( propagateKeyProps );
133
- // While most dependencies leading have been added at this point, new
134
- // cycles could be added later (e.g. via assocs in where conditions),
135
- // i.e. keep cycle detection with messages at the end (or after phase 4).
136
-
137
- // Phase 4: resolve all artifacts:
138
- forEachDefinition( model, resolveRefs );
139
- forEachGeneric( model, 'vocabularies', resolveRefs );
140
- // for builtin types
141
- forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
142
- forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
143
-
144
- // Phase 5: rewrite associations
145
- forEachDefinition( model, rewriteSimple );
146
- // TODO: sequence not good enough with derived type of structure with
147
- // includes: first "direct" structures, then _entities, then the rest.
148
- // v2: We might run a silent cycle detection earlier, then we could use the
149
- // SCC number (_scc.lowlink) to sort.
150
- model._entities.forEach( rewriteView );
151
- model._entities.forEach( rewriteViewCheck );
152
- // Phase 6: apply ANNOTATE on autoexposed entities and unknown artifacts:
153
- lateExtensions( annotateMembers );
154
- if (model.extensions)
155
- model.extensions.map( annotateUnknown );
156
- // Phase 7: report cyclic dependencies:
157
- detectCycles( model.definitions, ( user, art, location ) => {
158
- if (location) {
159
- error( 'ref-cyclic', [ location, user ], { art }, {
160
- std: 'Illegal circular reference to $(ART)',
161
- element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
162
- });
163
- }
164
- });
165
- return model;
166
- }
167
-
168
- // Resolve the using declarations in `using`. Issue
169
- // error message if the referenced artifact does not exist.
170
- function resolveUsings( src, topLevel ) {
171
- if (!src.usings)
172
- return;
173
- for (const def of src.usings) {
174
- if (def.usings) // using {...}
175
- resolveUsings( def );
176
- if (!def.name || !def.name.absolute || def.$inferred === 'LOCALIZED-IGNORED')
177
- continue; // using {...}, parse error, USING localized.XYZ
178
- const art = model.definitions[def.name.absolute];
179
- if (art && art.$duplicates)
180
- continue;
181
- const ref = def.extern;
182
- const from = (topLevel ? def : src).fileDep;
183
- if (art || !from || from.realname) // no error for non-existing ref with non-existing module
184
- resolvePath( ref, 'global', def ); // TODO: consider FROM for validNames
185
- }
186
- }
187
-
188
-
189
- //--------------------------------------------------------------------------
190
- // The central functions for path resolution - must work on-demand
191
- //--------------------------------------------------------------------------
192
- // Phase 2: call populateView(), which also works on-demand
193
-
194
- // Return effective search environment provided by artifact `art`, i.e. the
195
- // `artifacts` or `elements` dictionary. For the latter, follow the `type`
196
- // chain and resolve the association `target`. View elements are calculated
197
- // on demand.
198
- function environment( art, location, user, assocSpec ) {
199
- if (!art)
200
- return Object.create(null);
201
- const env = navigationEnv( art, location, user, assocSpec );
202
- return env && env.elements || Object.create(null);
203
- }
204
-
205
- function navigationEnv( art, location, user, assocSpec ) {
206
- let type = effectiveType(art) || art;
207
- // console.log( info(null, [ art.location, art ], { art, type }, 'ENV')
208
- // .toString(), art.elements && Object.keys(art.elements))
209
- if (type.target) {
210
- type = resolvePath( type.target, 'target', type );
211
- if (!type) {
212
- if (type === 0 && location)
213
- dependsOn( art, art, (art.target || art.type).location );
214
- return type;
215
- }
216
- // TODO: combine this with setTargetReferenceKey&Co in getPathItem?
217
- else if (assocSpec === false) { // TODO: else warning for assoc usage
218
- error( null, [ location, user ], {},
219
- 'Following an association is not allowed in an association key definition' );
220
- }
221
- else if (assocSpec && user) {
222
- dependsOn( user, type, location );
223
- }
224
- }
225
- populateView( type );
226
- return type;
227
- }
228
-
229
- // Follow the `type` chain, i.e. derived types and TYPE OF, stop just before
230
- // built-in types (otherwise, we would loose type parameters). Return that
231
- // type and set it as property `_effectiveType` on all artifacts on the chain.
232
- // TODO: clarify for (query) elements without type: self, not undefined - also for entities!
233
- // TODO: directly "propagate" (with implicit redirection the targets), also
234
- // "proxy-copy" elements
235
-
236
- // In v2, the name-resolution relevant properties (elements,items,target) are
237
- // proxy-copied, i.e. the effectiveType of an artifact is always the artifact
238
- // itself. Except for the situation with recursive element expansions; then
239
- // we have element: 0, and use original final type.
240
- function effectiveType( art ) {
241
- if ('_effectiveType' in art)
242
- return art._effectiveType;
243
-
244
- // console.log(message( null, art.location, art, {}, 'Info','FT').toString())
245
- const chain = [];
246
- while (art && !('_effectiveType' in art) &&
247
- (art.type || art._origin || art.value && art.value.path) &&
248
- // TODO: really stop at art.enum?
249
- !art.target && !art.enum && !art.elements && !art.items) {
250
- chain.push( art );
251
- setProp( art, '_effectiveType', 0 ); // initial setting in case of cycles
252
- art = directType( art );
253
- }
254
- if (art) {
255
- if ('_effectiveType' in art) { // is the case for builtins
256
- art = art._effectiveType;
257
- }
258
- else {
259
- setProp( art, '_effectiveType', art );
260
- if (art.expand && !art.value && !art.elements)
261
- initFromColumns( art, art.expand );
262
- // When not blocked (by future origin = false) and not REDIRECTED TO and not MIXIN
263
- // try to implicitly redirect explicitly provided target:
264
- else if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
265
- redirectImplicitly( art, art );
266
- }
267
- }
268
- chain.reverse();
269
- if (!art) {
270
- for (const a of chain)
271
- setProp( a, '_effectiveType', art );
272
- }
273
- else {
274
- let eType = art;
275
- if (eType._outer)
276
- eType = effectiveType( eType._outer );
277
- // collect the "latest" cardinality (calculate lazyly if necessary)
278
- let cardinality = art.cardinality ||
279
- art._effectiveType && (() => getCardinality( art._effectiveType ));
280
- let prev = art;
281
- for (const a of chain) {
282
- if (a.cardinality)
283
- cardinality = a.cardinality;
284
- if (a.expand && expandFromColumns( a, art, cardinality ) ||
285
- art.target && redirectImplicitly( a, art ) ||
286
- art.elements && expandElements( a, art, eType ) ||
287
- art.items && expandItems( a, art, eType ))
288
- art = a;
289
- else if (art.enum && expandEnum( a, prev ))
290
- prev = a; // do not set art - effective type is base
291
- setProp( a, '_effectiveType', art );
292
- }
293
- }
294
- return art;
295
- }
296
-
297
- function expandFromColumns( elem, assoc, cardinality ) {
298
- const path = elem.value && elem.value.path;
299
- if (!path || path.broken)
300
- return null;
301
- if (!assoc.target)
302
- return initFromColumns( elem, elem.expand );
303
- const { targetMax } = path[path.length - 1].cardinality ||
304
- (cardinality instanceof Function ? cardinality() : cardinality);
305
- if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
306
- elem.items = { location: dictLocation( elem.expand ) };
307
- return initFromColumns( elem, elem.expand );
308
- }
309
-
310
- function getCardinality( type ) {
311
- // only to be called without cycles
312
- while (type) {
313
- if (type.cardinality)
314
- return type.cardinality;
315
- type = directType( type );
316
- }
317
- return {};
318
- }
319
-
320
- function userQuery( user ) {
321
- // TODO: we need _query links set by the definer
322
- while (user._main) {
323
- if (user.kind === 'select' || user.kind === '$join')
324
- return user;
325
- user = user._parent;
326
- }
327
- return null;
328
- }
329
-
330
- // TODO: test it in combination with top-level CAST function
331
- function directType( art ) {
332
- // Be careful when using it with art.target or art.enum or art.elements
333
- if (art._origin || art.builtin)
334
- return art._origin;
335
- if (art.type)
336
- return resolveType( art.type, art );
337
- // console.log( 'EXPR-IN', art.kind, refString(art.name) )
338
- if (!art._main || !art.value || !art.value.path)
339
- return undefined;
340
- if (art._pathHead && art.value) {
341
- setProp( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
342
- return art._origin;
343
- }
344
- const query = userQuery( art ) || art._parent;
345
- if (query.kind !== 'select')
346
- return undefined;
347
- // Reached an element in a query which is a simple ref -> return referred artifact
348
- // TODO: remember that we still have to resolve path arguments and filters
349
- return setProp( art, '_origin',
350
- resolvePath( art.value, 'expr', art, query._combined ) );
351
- // console.log( 'EXPR-OUT', art.value._artifact.kind, refString(art.val ue._artifact.name) );
352
- }
353
-
354
- function resolveType( ref, user ) {
355
- if ('_artifact' in ref)
356
- return ref._artifact;
357
- if (ref.scope === 'typeOf') {
358
- let struct = user;
359
- while (struct.kind === 'element')
360
- struct = struct._parent;
361
- if (struct.kind === 'select') {
362
- message( 'ref-invalid-typeof', [ ref.location, user ],
363
- { keyword: 'type of', '#': struct.kind } );
364
- // we actually refer to an element in _combined; TODO: return null if
365
- // not configurable; would produce illegal CSN with sub queries in FROM
366
- }
367
- else if (struct !== user._main) {
368
- message( 'ref-invalid-typeof', [ ref.location, user ],
369
- { keyword: 'type of', '#': struct.kind } );
370
- return setProp( ref, '_artifact', null );
371
- }
372
- return resolvePath( ref, 'typeOf', user );
373
- }
374
- while (user._outer) // in items
375
- user = user._outer;
376
- if (user.kind === 'event')
377
- return resolvePath( ref, 'eventType', user );
378
- if (user.kind === 'param' && user._parent &&
379
- [ 'action', 'function' ].includes( user._parent.kind ))
380
- return resolvePath( ref, 'actionParamType', user );
381
- return resolvePath( ref, 'type', user );
382
- }
383
-
384
- // Make a view to have elements (remember: wildcard), and prepare that their
385
- // final type can be resolved, i.e. we know how to resolve select item refs.
386
- // We do so by first populate views in the FROM clause, then the view query.
387
- function populateView( art ) {
388
- if (!art._from || art._status === '_query')
389
- return;
390
- const resolveChain = [];
391
- const fromChain = [ art ];
392
- while (fromChain.length) {
393
- const view = fromChain.pop();
394
- if (view._status === '_query') // already fully resolved (status at def)
395
- continue;
396
- resolveChain.push( view );
397
- for (const from of view._from) {
398
- if (from._status) // status at the ref -> illegal recursion -> stop
399
- continue;
400
- setProp( from, '_status', '_query' );
401
- // setProp before resolvePath - Cycle: view V as select from V.toV
402
- let source = resolvePath( from, 'from', view ); // filter and args in resolveQuery
403
- // console.log('ST:',msgName(source),from._status)
404
- if (source && source._main) { // element -> should be assoc
405
- const type = effectiveType( source );
406
- source = type && type.target;
407
- }
408
- if (source && source._from && source._status !== '_query')
409
- fromChain.push( source );
410
- }
411
- }
412
- // console.log( resolveChain.map( v => msgName(v)+v._status ) );
413
- for (const view of resolveChain.reverse()) {
414
- if (view._status !== '_query' ) { // not already resolved
415
- setProp( view, '_status', '_query' );
416
- traverseQueryPost( view.query, null, populateQuery );
417
- if (view.elements$) // specified elements
418
- mergeSpecifiedElements( view );
419
- if (!view.$entity) {
420
- model._entities.push( view );
421
- view.$entity = ++model.$entity;
422
- }
423
- }
424
- }
425
- }
426
-
427
- function mergeSpecifiedElements( view ) {
428
- // Later we use specified elements as proxies to inferred of leading query
429
- for (const id in view.elements) {
430
- const ielem = view.elements[id]; // inferred element
431
- const selem = view.elements$[id]; // specified element
432
- if (!selem) {
433
- info( 'query-missing-element', [ ielem.name.location, view ], { id },
434
- 'Element $(ID) is missing in specified elements' );
435
- }
436
- else {
437
- for (const prop in selem) {
438
- // just annotation assignments and doc comments for the moment
439
- if (prop.charAt(0) === '@' || prop === 'doc')
440
- ielem[prop] = selem[prop];
441
- }
442
- selem.$replacement = true;
443
- }
444
- }
445
- for (const id in view.elements$) {
446
- const selem = view.elements$[id]; // specified element
447
- if (!selem.$replacement) {
448
- error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
449
- 'Element $(ID) does not result from the query' );
450
- }
451
- }
452
- }
453
-
454
- function traverseElementEnvironments( art ) {
455
- populateView( art );
456
- environment( art );
457
- forEachMember( art, traverseElementEnvironments );
458
- }
459
-
460
- function populateQuery( query ) {
461
- if (query._combined || !query.from || !query.$tableAliases)
462
- // already done or $join query or parse error
463
- return;
464
- setProp( query, '_combined', Object.create(null) );
465
- query.$inlines = [];
466
- forEachGeneric( query, '$tableAliases', resolveTabRef );
467
-
468
- initFromColumns( query, query.columns );
469
- // TODO: already in definer: complain about EXCLUDING with no wildcard
470
- // (would have been automatically with a good CDL syntax: `* without (...)`)
471
- if (query.excludingDict) {
472
- for (const name in query.excludingDict)
473
- resolveExcluding( name, query._combined, query.excludingDict, query );
474
- }
475
- return;
476
-
477
- function resolveTabRef( alias ) {
478
- if (alias.kind === 'mixin' || alias.kind === '$self')
479
- return;
480
- if (!alias.elements) { // could be false in hierarchical JOIN
481
- // if (main._block.$frontend!=='json') console.log('TABREF:',alias.name,main,main._block)
482
- // const tab = alias.path && resolvePath( alias, 'from', query );
483
- // // if (tab) setProp( alias, '_effectiveType', alias );
484
- // const elements = alias.query ? alias.query.elements : environment( tab );
485
- if (!('_origin' in alias) && alias.path) {
486
- const tab = resolvePath( alias, 'from', query );
487
- setProp( alias, '_origin', tab && navigationEnv( tab ) );
488
- }
489
- alias.elements = Object.create(null); // Set explicitly, as...
490
- // ...with circular dep to source, no elements can be found.
491
- const location = alias.path && alias.path.location;
492
- const qtab = alias._origin;
493
- if (!qtab || !qtab.elements)
494
- return;
495
- forEachGeneric( qtab, 'elements', ( origin, name ) => {
496
- const elem = linkToOrigin( origin, name, alias, 'elements',
497
- location || origin.name.location );
498
- elem.kind = '$navElement';
499
- // elem.name.select = query.name.select;
500
- if (origin.masked)
501
- elem.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
502
- });
503
- }
504
- forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
505
- if (elem.$duplicates !== true)
506
- dictAddArray( query._combined, name, elem, null ); // not dictAdd()
507
- });
508
- }
509
- }
510
-
511
- function initElem( elem, query ) {
512
- if (elem.type && !elem.type.$inferred)
513
- return; // explicit type -> enough or directType()
514
- if (elem.$inferred) {
515
- // redirectImplicitly( elem, elem._origin );
516
- return;
517
- }
518
- if (!elem.value || !elem.value.path) // TODO: test $inferred
519
- return; // no value ref or $inferred
520
- // TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
521
-
522
- const env = columnEnv( elem._pathHead, query );
523
- const origin = setProp( elem, '_origin',
524
- resolvePath( elem.value, 'expr', elem, env ) );
525
- // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
526
- // elem.value)
527
- // TODO: make this resolvePath() also part of directType() ?!
528
- if (!origin)
529
- return;
530
- if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
531
- forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
532
- }
533
-
534
- // now set things which are necessary for later sub phases:
535
- const nav = pathNavigation( elem.value );
536
- if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
537
- // redirectImplicitly( elem, origin );
538
- pushLink( nav.navigation, '_projections', elem );
539
- }
540
- }
541
-
542
- function initKey( key, name, elem ) {
543
- setProp( key, '_block', elem._block );
544
- setMemberParent( key, name, elem ); // TODO: set _block here if not present?
545
- }
546
-
547
- function expandItems( art, origin, eType ) {
548
- if (!enableExpandElements || art.items)
549
- return false;
550
- if (isInParents( art, eType )) {
551
- art.items = 0; // circular
552
- return true;
553
- }
554
- const ref = art.type || art.value || art.name;
555
- const location = ref && ref.location || art.location;
556
- art.items = { $inferred: 'expand-element', location };
557
- setProp( art.items, '_outer', art );
558
- setProp( art.items, '_origin', origin.items );
559
- if (!art.$expand)
560
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
561
- return true;
562
- }
563
-
564
- function expandElements( art, struct, eType ) {
565
- if (!enableExpandElements)
566
- return false;
567
- if (art.elements || art.kind === '$tableAlias' ||
568
- // no element expansions for "non-proper" types like
569
- // entities (as parameter types) etc:
570
- struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
571
- !struct._outer)
572
- return false;
573
- if (struct.elements === 0 || isInParents( art, eType )) {
574
- art.elements = 0; // circular
575
- return true;
576
- }
577
- const ref = art.type || art.value || art.name;
578
- const location = ref && ref.location || art.location;
579
- // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
580
- // .toString(), Object.keys(struct.elements))
581
- for (const name in struct.elements) {
582
- const orig = struct.elements[name];
583
- // const orig = elem.kind === '$navElement'
584
- if (Array.isArray( orig )) // redefinitions
585
- continue;
586
- linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
587
- // or should we use orig.location? - TODO: try to find test to see message
588
- .$inferred = 'expand-element';
589
- }
590
- // Set elements expansion status (the if condition is always true, as no
591
- // elements expansion will take place on artifact with existing other
592
- // member property):
593
- if (!art.$expand)
594
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
595
- // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
596
- return true;
597
- }
598
-
599
- function expandEnum( art, origin ) {
600
- if (!enableExpandElements || art.enum)
601
- return false;
602
- const ref = art.type || art.value || art.name;
603
- const location = weakLocation( ref && ref.location || art.location );
604
- art.enum = Object.create(null);
605
- for (const name in origin.enum) {
606
- const orig = origin.enum[name];
607
- linkToOrigin( orig, name, art, 'enum', location, true )
608
- // or should we use orig.location? - TODO: try to find test to see message
609
- .$inferred = 'expand-element';
610
- }
611
- // Set elements expansion status (the if condition is always true, as no
612
- // elements expansion will take place on artifact with existing other
613
- // member property):
614
- if (!art.$expand)
615
- art.$expand = 'origin'; // if value stays, elements won't appear in CSN
616
- art.enum[$inferred] = 'expand-element';
617
- return true;
618
- }
619
-
620
- /**
621
- * Return true iff `struct` is `art` or a direct or indirect parent of `art`,
622
- * we also check for the outer objects of `items`. (There is no need to
623
- * check the parents of main artifacts, as these are contexts, services or
624
- * namespaces, and do not serve as type.)
625
- */
626
- function isInParents( art, struct ) {
627
- if (art === struct)
628
- return true;
629
- while (art._outer) { // for items
630
- art = art._outer;
631
- if (art === struct)
632
- return true;
633
- }
634
- while (art._main) {
635
- art = art._parent;
636
- if (art === struct)
637
- return true;
638
- }
639
- return false;
640
- }
641
-
642
- // About Helper property $expand for faster the XSN-to-CSN transformation
643
- // - null/undefined: artifact, member, items does not contain expanded members
644
- // - 'origin': all expanded (sub) elements have no new target/on and no new annotations
645
- // that value is only on elements, types, and params -> no other members
646
- // when set, only on elem/art with expanded elements
647
- // - 'target': all expanded (sub) elements might only have new target/on, but
648
- // no indivual annotations on any (sub) member
649
- // when set, traverse all parents where the value has been 'origin' before
650
- // - 'annotate': at least one inferred (sub) member has an individual annotation,
651
- // not counting propagated ones; set up to the definition (main artifact)
652
- // (only set with anno on $inferred elem)
653
- // Usage according to CSN flavor:
654
- // - gensrc: do not render inferred elements (including expanded elements),
655
- // collect annotate statements with value 'annotate'
656
- // - client: do not render expanded sub elements if artifact/member is no type, has a type,
657
- // has $expand = 'origin', and all its _origin also have $expand = 'origin'
658
- // (might sometimes render the elements unnecessarily, which is not wrong)
659
- // - universal: do not render expanded sub elements if $expand = 'origin'
660
- function setExpandStatus( elem, status ) {
661
- // set on element
662
- while (elem._main) {
663
- elem = elem._parent;
664
- if (elem.$expand !== 'origin')
665
- return;
666
- elem.$expand = status; // meaning: expanded, containing assocs
667
- for (let line = elem.items; line; line = line.items)
668
- line.$expand = status; // to-csn just uses the innermost $expand
669
- }
670
- }
671
- function setExpandStatusAnnotate( elem, status ) {
672
- for (;;) {
673
- if (elem.$expand === status)
674
- return; // already set
675
- elem.$expand = status; // meaning: expanded, containing annos
676
- for (let line = elem.items; line; line = line.items)
677
- line.$expand = status; // to-csn just uses the innermost $expand
678
- if (!elem._main)
679
- return;
680
- elem = elem._parent;
681
- }
682
- }
683
-
684
- // Conditions for redirecting target of assoc in elem
685
- // - we (the elem) are in a service
686
- // - assoc is not defined in current service
687
- // - target provided in assoc is not defined in current service
688
- function redirectImplicitly( elem, assoc ) {
689
- // PRE: elem has no target, assoc has target prop
690
- if (elem.kind === '$tableAlias')
691
- return false;
692
- setExpandStatus( elem, 'target' );
693
- let target = resolvePath( assoc.target, 'target', assoc );
694
- // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
695
- // 'RED').toString())
696
- if (!target)
697
- return false; // error in target ref
698
- const { location } = elem.value || elem.type || elem.name;
699
- const service = (elem._main || elem)._service;
700
- if (service &&
701
- (service !== (assoc._main || assoc)._service || elem === assoc || assoc.kind === 'mixin') &&
702
- service !== target._service) {
703
- // console.log('ES:',elem.name.absolute,elem.name.element);
704
- const elemScope = scopedRedirections && // null if no scoped redirections
705
- preferredElemScope( target, service, elem, assoc._main || assoc );
706
- const exposed = minimalExposure( target, service, elemScope );
707
-
708
- if (!exposed.length && elemScope !== true) {
709
- const origTarget = target;
710
- if (isAutoExposed( target ))
711
- target = createAutoExposed( origTarget, service, elemScope );
712
- const desc = origTarget._descendants ||
713
- setLink( origTarget, Object.create(null), '_descendants' );
714
- if (!desc[service.name.absolute]) // could be the target itself (no repeated msgs)!
715
- desc[service.name.absolute] = [ target ];
716
- else
717
- desc[service.name.absolute].push( target );
718
- }
719
- else if (exposed.length === 1) {
720
- target = exposed[0];
721
- }
722
- else {
723
- message( (elem !== assoc ? 'redirected-implicitly-ambiguous' : 'type-ambiguous-target'),
724
- [ (elem.value || elem.name).location, elem ],
725
- {
726
- target,
727
- service,
728
- art: definitionScope( target ),
729
- sorted_arts: exposed,
730
- '#': ( elemScope !== true ? 'std' : 'scoped' ),
731
- }, {
732
- // eslint-disable-next-line max-len
733
- std: 'Target $(TARGET) is exposed in service $(SERVICE) by multiple projections $(SORTED_ARTS) - no implicit redirection',
734
- // eslint-disable-next-line max-len
735
- scoped: 'Target $(TARGET) is defined in scope $(ART) which exposed in service $(SERVICE) by multiple projections - no implicit redirection',
736
- });
737
- // continuation semantics: no implicit redirections
738
- }
739
- }
740
- if (elem.target) { // redirection for Association to / Composition of
741
- if (elem.target._artifact === target) // no change (due to no implicit redirection)
742
- return true;
743
- const origin = {
744
- kind: elem.kind,
745
- name: elem.name,
746
- target: elem.target,
747
- $inferred: 'REDIRECTED',
748
- location: elem.target.location,
749
- };
750
- setLink( elem, origin, '_origin' );
751
- setLink( origin, elem._parent, '_parent' );
752
- if (elem._main) // remark: the param `elem` can also be a type
753
- setLink( origin, elem._main, '_main' );
754
- setLink( origin, origin, '_effectiveType' );
755
- setLink( origin, elem._block, '_block' );
756
- if (elem.foreignKeys) {
757
- origin.foreignKeys = elem.foreignKeys;
758
- delete elem.foreignKeys;
759
- }
760
- if (elem.on) {
761
- origin.on = elem.on;
762
- delete elem.on;
763
- }
764
- }
765
- elem.target = {
766
- path: [ { id: target.name.absolute, location } ],
767
- scope: 'global',
768
- location,
769
- $inferred: (target !== assoc.target._artifact ? 'IMPLICIT' : 'rewrite' ),
770
- };
771
- setLink( elem.target, target );
772
- setLink( elem.target.path[0], target );
773
- return true;
774
- }
775
-
776
- function preferredElemScope( target, service, elem, assocMain ) {
777
- const assocScope = definitionScope( assocMain );
778
- const targetScope = definitionScope( target );
779
- if (targetScope === assocScope) { // intra-scope in model
780
- const elemScope = definitionScope( elem._main || elem );
781
- if (targetScope === target || // unscoped target in model
782
- assocScope === assocMain || // unscoped assoc source in model
783
- elemScope !== (elem._main || elem)) // scoped assoc source in service
784
- return elemScope; // own scope, then global
785
- }
786
- if (targetScope === target) // unscoped target in model / other service
787
- return false; // all (there could be no scoped autoexposed)
788
- // scoped target in model:
789
- const exposed = minimalExposure( targetScope, service, false );
790
- // console.log('PES:',elem.name.absolute,elem.name.element,exposed.map(e=>e.name.absolute))
791
- if (exposed.length === 1) // unique redirection for target scope: use that
792
- return exposed[0];
793
- // TODO: warning if exposed.length >= 2? Probably not
794
- // TODO: use excessive testing for the following
795
- // Now re-scope according to naming of auto-exposed entity:
796
- const autoScopeName = autoExposedName( targetScope, service, false );
797
- const autoScope = model.definitions[autoScopeName];
798
- // console.log('AEN:',autoScopeName,autoScope&&(autoScope.$inferred || autoScope.kind))
799
- if (autoScope)
800
- return autoScope;
801
- const { location } = service.name;
802
- const nullScope = {
803
- kind: 'namespace', name: { absolute: autoScopeName, location }, location,
804
- };
805
- model.definitions[autoScopeName] = nullScope;
806
- initArtifact( nullScope );
807
- return nullScope;
808
- }
809
-
810
- // Return projections of `target` in `service`. Shorted by
811
- // - first, only consider projections with @cds.redirection.target=true
812
- // - exclude all indirect projections, i.e. those which are projection on others in list
813
- //
814
- // To avoid repeated messages: if already tried to do autoexposure, return
815
- // autoexposed entity when successful, or `target` otherwise (no/failed autoexposure)
816
- function minimalExposure( target, service, elemScope ) {
817
- const descendants = scopedExposure( target._descendants &&
818
- target._descendants[service.name.absolute] ||
819
- [],
820
- elemScope, target );
821
- const preferred = descendants.filter( ( d ) => {
822
- const anno = d['@cds.redirection.target'];
823
- return anno && (anno.val === undefined || anno.val );
824
- } );
825
- const exposed = preferred.length ? preferred : descendants;
826
- if (exposed.length < 2)
827
- return exposed || [];
828
- let min = null;
829
- for (const e of exposed) {
830
- if (!min || min._ancestors && min._ancestors.includes(e)) {
831
- min = e;
832
- }
833
- else if (!e._ancestors || !e._ancestors.includes( min )) {
834
- if (elemScope === '')
835
- return [];
836
- return exposed;
837
- }
838
- }
839
- return [ min ];
840
- }
841
-
842
- function isDirectProjection( proj, base ) {
843
- return proj.kind === 'entity' && // not event
844
- projectionAncestor( base, proj.params ) && // same params
845
- // direct proj (TODO: or should we add them to another list?)
846
- proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
847
- proj._from && proj._from.length === 1 &&
848
- base === resolvePath( proj._from[0], 'from', proj );
849
- }
850
-
851
- function scopedExposure( descendants, elemScope, target ) {
852
- if (!elemScope) // no scoped redirections
853
- return descendants;
854
- if (elemScope === true || elemScope === 'auto') {
855
- // cross-scope navigation, scoped model target, but there is no unique
856
- // redirection target for target model scope -> unsure redirection scope
857
- const unscoped = descendants.filter( d => d === definitionScope( d ) );
858
- if (unscoped.length) // use unscoped new targets if present
859
- return unscoped;
860
- // Need to filter out auto-exposed, otherwise the behavior is
861
- // processing-order dependent (not storing the autoexposed in
862
- // _descendents would only be an alternative w/o recompilation)
863
- return descendants.filter( d => !d['@cds.autoexposed'] );
864
- }
865
- // try scope as target first, even if it has @cds.redirection.target: false
866
- if (isDirectProjection( elemScope, target ))
867
- return [ elemScope ];
868
- const scoped = descendants.filter( d => elemScope === definitionScope( d ) );
869
- if (scoped.length) // use scoped new targets if present
870
- return scoped;
871
- // otherwise return new targets outside any scope
872
- return descendants.filter( d => d === definitionScope( d ) );
873
- }
874
-
875
- function isAutoExposed( target ) {
876
- if (target.$autoexpose !== undefined)
877
- return target.$autoexpose;
878
- const origTarget = target;
879
- const chain = [];
880
- let source = target._from && resolvePath( target._from[0], 'from', target );
881
- // query source ref might not have been resolved yet, cycle avoided as
882
- // setAutoExposed() sets $autoexpose and a second call on same art would
883
- // return false
884
- while (target.$autoexpose === undefined && setAutoExposed( target ) && source) {
885
- // stop at first ancestor with annotation or at non-query entity
886
- chain.push( target );
887
- target = source;
888
- source = target._from && resolvePath( target._from[0], 'from', target );
889
- }
890
- const autoexpose = target.$autoexpose;
891
- if (typeof autoexpose === 'boolean') {
892
- for (const a of chain)
893
- a.$autoexpose = autoexpose;
894
- }
895
- return origTarget.$autoexpose;
896
- }
897
-
898
- function setAutoExposed( art ) {
899
- const anno = art['@cds.autoexpose'];
900
- if (anno && anno.val !== null) { // XSN TODO: set val, but no location for anno short form
901
- // @cds.autoexpose:true or @cds.autoexpose:false
902
- art.$autoexpose = anno.val === undefined || !!anno.val;
903
- return false;
904
- }
905
- // no @cds.autoexpose or @cds.autoexpose:null
906
- // TODO: introduce deprecated.noInheritedAutoexposeViaComposition
907
- art.$autoexpose = model.$compositionTargets[art.name.absolute]
908
- ? autoexposeViaComposition
909
- : null;
910
- return true; // still check for inherited @cds.autoexpose
911
- }
912
-
913
- // Return the scope of a definition. It is the last parent of the definition
914
- // which is not a context/service/namespace, or the definition itself.
915
- // If inside service, it is the direct child of the (most inner) service.
916
- function definitionScope( art ) {
917
- if (art._base) // with deprecated.generatedEntityNameWithUnderscore
918
- return art._base;
919
- let base = art;
920
- while (art._parent) {
921
- if (art._parent.kind === 'service')
922
- return art;
923
- art = art._parent;
924
- if (!kindProperties[art.kind].artifacts)
925
- base = art;
926
- }
927
- return base;
928
- }
929
-
930
- function autoExposedName( target, service, elemScope ) {
931
- const { absolute } = target.name;
932
- if (isDeprecatedEnabled( options, 'shortAutoexposed' )) {
933
- const parent = definitionScope( target )._parent;
934
- const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
935
- // no need for dedot here (as opposed to deprecated.longAutoexposed), as
936
- // the name for dependent entities have already been created using `_` then
937
- return `${ service.name.absolute }.${ name }`;
938
- }
939
- if (isDeprecatedEnabled( options, 'longAutoexposed' )) {
940
- const dedot = isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' );
941
- return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
942
- }
943
- const base = definitionScope( target );
944
- if (base === target)
945
- return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
946
- // for scoped (e.g. calculated) entities, use exposed name of base:
947
- const exposed = minimalExposure( base, service, elemScope );
948
- // console.log(exposed.map( a => a.name.absolute ));
949
- const sbasename = (exposed.length === 1 && exposed[0] !== base) // same with no/failed expose
950
- ? exposed[0].name.absolute
951
- : autoExposedName( base, service, elemScope );
952
- return sbasename + absolute.slice( base.name.absolute.length );
953
- }
954
-
955
- function createAutoExposed( target, service, elemScope ) {
956
- const absolute = autoExposedName( target, service, elemScope );
957
- const autoexposed = model.definitions[absolute];
958
- if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
959
- if (isDirectProjection( autoexposed, target )) {
960
- if (options.testMode)
961
- throw new Error( `Tried to auto-expose ${ target.name.absolute } twice`);
962
- return autoexposed;
963
- }
964
- error( 'duplicate-autoexposed', [ service.name.location, service ],
965
- { target, art: absolute },
966
- 'Name $(ART) of autoexposed entity for $(TARGET) collides with other definition' );
967
- info( null, [ target.name.location, target ],
968
- { art: service },
969
- 'Expose this (or the competing) entity explicitly in service $(ART)' );
970
- if (autoexposed.$inferred !== 'autoexposed')
971
- return target;
972
- const firstTarget = autoexposed.query.from._artifact;
973
- error( 'duplicate-autoexposed', [ service.name.location, service ],
974
- { target: firstTarget, art: absolute },
975
- 'Name $(ART) of autoexposed entity for $(TARGET) collides with other definition' );
976
- info( null, [ firstTarget.name.location, firstTarget ],
977
- { art: service },
978
- 'Expose this (or the competing) entity explicitly in service $(ART)' );
979
- autoexposed.$inferred = 'duplicate-autoexposed';
980
- return target;
981
- }
982
- // console.log(absolute)
983
- const { location } = target.name;
984
- const from = augmentPath( location, target.name.absolute );
985
- let art = {
986
- kind: 'entity',
987
- name: { location, path: splitIntoPath( location, absolute ), absolute },
988
- location: target.location,
989
- query: { location, op: { val: 'SELECT', location }, from },
990
- $syntax: 'projection',
991
- $inferred: 'autoexposed',
992
- '@cds.autoexposed': {
993
- name: { path: [ { id: 'cds.autoexposed', location } ], location },
994
- },
995
- };
996
- // TODO: do we need to tag the generated entity with elemScope = 'auto'?
997
- if (autoexposed) {
998
- Object.assign( autoexposed, art );
999
- art = autoexposed;
1000
- }
1001
- else {
1002
- model.definitions[absolute] = art;
1003
- }
1004
- setLink( art, service, '_service' );
1005
- setLink( art, model.$internal, '_block' );
1006
- initArtifact( art, !!autoexposed );
1007
- // populate view (phase 2 of resolver has to be repeated as the view was created afterwards)
1008
- populateView( art );
1009
- // TODO: try to set locations of elements locations of orig target elements
1010
- newAutoExposed.push( art );
1011
- return art;
1012
- }
1013
-
1014
- // TODO: probably do this already in definer.js
1015
- function ensureColumnName( col, query ) {
1016
- if (col.name)
1017
- return col.name.id;
1018
- if (col.inline || col.val === '*')
1019
- return '';
1020
- const path = col.value &&
1021
- (col.value.path || !col.value.args && col.value.func && col.value.func.path);
1022
- if (path) {
1023
- const last = !path.broken && path.length && path[path.length - 1];
1024
- if (last) {
1025
- col.name = { id: last.id, location: last.location, $inferred: 'as' };
1026
- return col.name.id;
1027
- }
1028
- }
1029
- else if (col.value || col.expand) {
1030
- error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
1031
- 'Alias name is required for this select item' );
1032
- }
1033
- // invent a name for code completion in expression
1034
- col.name = {
1035
- id: '',
1036
- location: col.value && col.value.location || col.location,
1037
- $inferred: 'none',
1038
- };
1039
- return '';
1040
- }
1041
-
1042
- // TODO: make this function shorter - make part of this (e.g. setting
1043
- // parent/name) also be part of definer.js
1044
- // TODO: query is actually the elemParent, where the new elements are added to
1045
- // top-level: just query, columns
1046
- // inline: + elements (TODO: remove), colParent
1047
- // expand: just query (which is a column/element), columns=array of expand
1048
- function initFromColumns( query, columns, inlineHead = undefined ) {
1049
- const elemsParent = query.items || query;
1050
- if (!inlineHead) {
1051
- elemsParent.elements = Object.create(null);
1052
- if (query._main._leadingQuery === query) // never the case for 'expand'
1053
- query._main.elements = elemsParent.elements;
1054
- }
1055
-
1056
- for (const col of columns || [ { val: '*' } ]) {
1057
- if (col.val === '*') {
1058
- const siblings = wildcardSiblings( columns, query );
1059
- expandWildcard( col, siblings, inlineHead, query );
1060
- }
1061
- if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
1062
- error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
1063
- 'Unsupported nested $(PROP)' );
1064
- }
1065
- if (!col.value && !col.expand)
1066
- continue; // error should have been reported by parser
1067
- if (col.inline) {
1068
- col.kind = '$inline';
1069
- col.name = {};
1070
- // a name for this internal symtab entry (e.g. '.2' to avoid clashes
1071
- // with real elements) is only relevant for for `cdsc -R`/debugging
1072
- const q = userQuery( query );
1073
- q.$inlines.push( col );
1074
- // or use userQuery( query ) in the following, too?
1075
- setMemberParent( col, `.${ q.$inlines.length }`, query );
1076
- initFromColumns( query, col.inline, col );
1077
- continue;
1078
- }
1079
- else if (!col.$replacement) {
1080
- const id = ensureColumnName( col, query );
1081
- col.kind = 'element';
1082
- dictAdd( elemsParent.elements, id, col, ( name, location ) => {
1083
- error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
1084
- });
1085
- setMemberParent( col, id, query );
1086
- }
1087
- }
1088
- forEachGeneric( query, 'elements', e => initElem( e, query ) );
1089
- return true;
1090
- }
1091
-
1092
- // col ($replacement set before *)
1093
- // false if two cols have same name
1094
- function wildcardSiblings( columns, query ) {
1095
- const siblings = Object.create(null);
1096
- if (!columns)
1097
- return siblings;
1098
-
1099
- let seenWildcard = null;
1100
- for (const col of columns) {
1101
- const id = ensureColumnName( col, query );
1102
- if (id) {
1103
- col.$replacement = !seenWildcard;
1104
- siblings[id] = !(id in siblings) && col;
1105
- }
1106
- else if (col.val === '*') {
1107
- seenWildcard = true;
1108
- }
1109
- }
1110
- return siblings;
1111
- }
1112
-
1113
- // TODO: make struct.* are to be added at place, not sub-wildcards first,
1114
- // see test3/Queries/ExpandInlineCreate/Excluding.cds
1115
- // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
1116
- function expandWildcard( wildcard, siblingElements, colParent, query ) {
1117
- const { elements } = query.items || query;
1118
- let location = wildcard.location || query.from && query.from.location || query.location;
1119
- const inferred = query._main.$inferred;
1120
- const excludingDict = (colParent || query).excludingDict || Object.create(null);
1121
-
1122
- const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
1123
- // console.log('S1:',location.line,location.col,
1124
- // envParent&&!!envParent._origin&&envParent._origin.name)
1125
- const env = columnEnv( envParent, query );
1126
- // console.log('S2:',location.line,location.col,
1127
- // envParent&&!!envParent._origin&&envParent._origin.name,
1128
- // Object.keys(env),Object.keys(elements))
1129
- for (const name in env) {
1130
- const navElem = env[name];
1131
- // TODO: if it is an array, filter out those with masked
1132
- if (excludingDict[name] || navElem.masked && navElem.masked.val)
1133
- continue;
1134
- const sibling = siblingElements[name];
1135
- if (sibling) { // is explicitly provided (without duplicate)
1136
- if (!inferred && !envParent) // not yet for expand/inline
1137
- reportReplacement( sibling, navElem, query );
1138
- if (!sibling.$replacement) {
1139
- sibling.$replacement = true;
1140
- sibling.kind = 'element';
1141
- dictAdd( elements, name, sibling, ( _name, loc ) => {
1142
- // there can be a definition from a previous inline with the same name:
1143
- error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1144
- });
1145
- setMemberParent( sibling, name, query );
1146
- }
1147
- // else {
1148
- // sibling.$inferred = 'query';
1149
- // }
1150
- }
1151
- else if (Array.isArray(navElem)) {
1152
- const names = navElem.filter( e => !e.$duplicates)
1153
- .map( e => `${ e.name.alias }.${ e.name.element }` );
1154
- if (names.length) {
1155
- error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
1156
- 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
1157
- }
1158
- }
1159
- else {
1160
- location = weakLocation( location );
1161
- const origin = envParent ? navElem : navElem._origin;
1162
- const elem = linkToOrigin( origin, name, query, null, location );
1163
- // TODO: check assocToMany { * }
1164
- dictAdd( elements, name, elem, ( _name, loc ) => {
1165
- // there can be a definition from a previous inline with the same name:
1166
- error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
1167
- });
1168
- elem.$inferred = '*';
1169
- elem.name.$inferred = '*';
1170
- if (envParent)
1171
- setWildcardExpandInline( elem, envParent, origin, name, location );
1172
- else
1173
- setElementOrigin( elem, navElem, name, location );
1174
- }
1175
- }
1176
- if (envParent || query.kind !== 'select') {
1177
- // already done in populateQuery (TODO: change that and check whether
1178
- // `*` is allowed at all in definer)
1179
- const user = colParent || query;
1180
- for (const name in user.excludingDict)
1181
- resolveExcluding( name, env, excludingDict, query );
1182
- }
1183
- }
1184
-
1185
- function reportReplacement( sibling, navElem, query ) {
1186
- // TODO: bring this much less often = only if shadowed elem does not appear
1187
- // in expr and if not projected as other name.
1188
- // Probably needs to be reported at a later phase
1189
- const path = sibling.value && sibling.value.path;
1190
- if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
1191
- path && path[path.length - 1].id !== sibling.name.id) { // or renamed
1192
- const { id } = sibling.name;
1193
- if (Array.isArray(navElem)) {
1194
- info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
1195
- 'This select item replaces $(ID) from two or more sources' );
1196
- }
1197
- else {
1198
- info( 'wildcard-excluding-one', [ sibling.name.location, query ],
1199
- { id, alias: navElem._parent.name.id },
1200
- 'This select item replaces $(ID) from table alias $(ALIAS)' );
1201
- }
1202
- }
1203
- }
1204
-
1205
- function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
1206
- return (envParent)
1207
- ? environment( directType( envParent ) || envParent )
1208
- : userQuery( query )._combined;
1209
- }
1210
-
1211
- function resolveExcluding( name, env, excludingDict, user ) {
1212
- if (env[name])
1213
- return;
1214
- /** @type {object} */
1215
- // console.log(name,Object.keys(env),Object.keys(excludingDict))
1216
- const compileMessageRef = info(
1217
- 'ref-undefined-excluding', [ excludingDict[name].location, user ], { name },
1218
- 'Element $(NAME) has not been found'
1219
- );
1220
- attachAndEmitValidNames( compileMessageRef, env );
1221
- }
1222
-
1223
- function setElementOrigin( queryElem, navElem, name, location ) {
1224
- const sourceElem = navElem._origin;
1225
- const alias = navElem._parent;
1226
- // always expand * to path with table alias (reason: columns current_date etc)
1227
- const path = [ { id: alias.name.id, location }, { id: name, location } ];
1228
- queryElem.value = { path, location };
1229
- setProp( path[0], '_navigation', alias );
1230
- setProp( path[0], '_artifact', alias._origin );
1231
- setProp( path[1], '_artifact', sourceElem );
1232
- // TODO: or should we set the _artifact/_effectiveType directly to the target?
1233
- setProp( queryElem.value, '_artifact', sourceElem );
1234
- pushLink( navElem, '_projections', queryElem );
1235
- // TODO: _effectiveType?
1236
- }
1237
-
1238
- function setWildcardExpandInline( queryElem, pathHead, origin, name, location ) {
1239
- setProp( queryElem, '_pathHead', pathHead );
1240
- const path = [ { id: name, location } ];
1241
- queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
1242
- setProp( path[0], '_artifact', origin );
1243
- setProp( queryElem, '_origin', origin );
1244
- // TODO: set _projections when top-level?
1245
- }
1246
-
1247
- //--------------------------------------------------------------------------
1248
- // Phase 3: calculate propagated KEYs
1249
- //--------------------------------------------------------------------------
1250
-
1251
- function propagateKeyProps( view ) {
1252
- // Second argument true ensure that `key` is only propagated along simple
1253
- // view, i.e. ref or subquery in FROM, not UNION or JOIN.
1254
- traverseQueryPost( view.query, true, ( query ) => {
1255
- if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
1256
- withKeyPropagation( query )) // now the part with messages
1257
- inheritKeyProp( query, true );
1258
- } );
1259
- }
1260
-
1261
- function withExplicitKeys( query ) {
1262
- for (const name in query.elements) {
1263
- const elem = query.elements[name];
1264
- if (elem.key && !elem.$duplicates) // also those from includes
1265
- return true;
1266
- }
1267
- return false;
1268
- }
1269
-
1270
- function inheritKeyProp( query, doIt ) {
1271
- for (const name in query.elements) {
1272
- const elem = query.elements[name];
1273
- // no key prop for duplicate elements or additional specified elements:
1274
- if (elem.$duplicates || !elem.value)
1275
- continue;
1276
- const nav = pathNavigation( elem.value );
1277
- if (!nav.navigation)
1278
- continue; // undefined, expr, $magic, :const, $self (!), $self.elem
1279
- const { item } = nav;
1280
- if (item !== elem.value.path[elem.value.path.length - 1])
1281
- continue; // having selected a sub elem / navigated along assoc
1282
- const { key } = item._artifact;
1283
- if (key) {
1284
- if (!doIt)
1285
- return true;
1286
- elem.key = { location: elem.value.location, val: key.val, $inferred: 'query' };
1287
- }
1288
- }
1289
- return false;
1290
- }
1291
-
1292
- function primarySourceNavigation( aliases ) {
1293
- for (const name in aliases)
1294
- return aliases[name].elements;
1295
- return undefined;
1296
- }
1297
-
1298
- function withKeyPropagation( query ) {
1299
- const { from } = query;
1300
- if (!from) // parse error SELECT FROM <EOF>
1301
- return false;
1302
-
1303
- let propagateKeys = true; // used instead early RETURN to get more messages
1304
- const toMany = withAssociation( from, targetMaxNotOne, true );
1305
- if (toMany) {
1306
- propagateKeys = false;
1307
- info( 'query-from-many', [ toMany.location, query ], { art: toMany },
1308
- {
1309
- // eslint-disable-next-line max-len
1310
- std: 'Selecting from to-many association $(ART) - key properties are not propagated',
1311
- // eslint-disable-next-line max-len
1312
- element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
1313
- } );
1314
- }
1315
- // Check that all keys from the source are projected:
1316
- const notProjected = []; // we actually push to the array
1317
- const navElems = primarySourceNavigation( query.$tableAliases );
1318
- for (const name in navElems) {
1319
- const nav = navElems[name];
1320
- if (nav.$duplicates)
1321
- continue;
1322
- const { key } = nav._origin;
1323
- if (key && key.val && !(nav._projections && nav._projections.length))
1324
- notProjected.push( nav.name.id );
1325
- }
1326
- if (notProjected.length) {
1327
- propagateKeys = false;
1328
- info( 'query-missing-keys', [ from.location, query ], { names: notProjected },
1329
- {
1330
- std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
1331
- one: 'Key $(NAMES) has not been projected - key properties are not propagated',
1332
- } );
1333
- }
1334
- // Check that there is no to-many assoc used in select item:
1335
- for (const name in query.elements) {
1336
- const elem = query.elements[name];
1337
- if (!elem.$inferred && elem.value &&
1338
- testExpr( elem.value, selectTest, () => false ))
1339
- propagateKeys = false;
1340
- }
1341
- return propagateKeys;
1342
-
1343
- function selectTest( expr ) {
1344
- const art = withAssociation( expr, targetMaxNotOne );
1345
- if (art) {
1346
- info( 'query-navigate-many', [ art.location, query ], { art },
1347
- {
1348
- // eslint-disable-next-line max-len
1349
- std: 'Navigating along to-many association $(ART) - key properties are not propagated',
1350
- // eslint-disable-next-line max-len
1351
- element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
1352
- // eslint-disable-next-line max-len
1353
- alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
1354
- } );
1355
- }
1356
- return art;
1357
- }
1358
- }
1359
-
1360
- //--------------------------------------------------------------------------
1361
- // Phase 4:
1362
- //--------------------------------------------------------------------------
1363
-
1364
- function adHocOrMainKind( elem ) {
1365
- const main = elem._main;
1366
- if (main) {
1367
- do {
1368
- elem = elem._parent;
1369
- if (elem.targetAspect)
1370
- return 'aspect'; // ad-hoc composition target aspect
1371
- } while (elem !== main);
1372
- }
1373
- return elem.kind;
1374
- }
1375
- // TODO: have $applied/$extension/$status on extension with the following values
1376
- // - 'unknown': artifact to extend/annotate is not defined or contains unknown member
1377
- // - 'referred': contains annotation for element of referred type (not yet supported)
1378
- // - 'inferred': only contains extension for known member, but some inferred ones
1379
- // (inferred = elements from structure includes, query elements)
1380
- // - 'original': only contains extensions on non-inferred members
1381
-
1382
- // Resolve all references in artifact or element `art`. Do so recursively in
1383
- // all sub elements.
1384
- // TODO: make this function smaller
1385
- function resolveRefs( art ) {
1386
- // console.log(message( null, art.location, art, {}, 'Info','REFS').toString())
1387
- // console.log(message( null, art.location, art, {target:art.target}, 'Info','RR').toString())
1388
- const parent = art._parent;
1389
- const allowedInMain = [ 'entity', 'aspect' ].includes( adHocOrMainKind( art ) );
1390
- const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
1391
- if (art.key && art.key.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
1392
- warning( 'unexpected-key', [ art.key.location, art ],
1393
- { '#': allowedInMain ? 'sub' : 'std' }, {
1394
- std: 'KEY is only supported for elements in an entity or an aspect',
1395
- sub: 'KEY is only supported for top-level elements',
1396
- });
1397
- }
1398
- if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
1399
- message( 'type-managed-composition', [ art.targetAspect.location, art ],
1400
- { '#': allowedInMain ? 'sub' : 'std' } );
1401
- }
1402
- if (art.includes && !allowedInMain) {
1403
- for (const include of art.includes) {
1404
- const struct = include._artifact;
1405
- if (struct && struct.kind !== 'type' && struct.elements &&
1406
- Object.values( struct.elements ).some( e => e.targetAspect)) {
1407
- message( 'type-managed-composition', [ include.location, art ],
1408
- { '#': struct.kind, art: struct } );
1409
- }
1410
- }
1411
- }
1412
- let obj = art;
1413
- if (obj.type) // TODO: && !obj.type.$inferred ?
1414
- resolveTypeExpr( obj, art );
1415
- const type = effectiveType( obj ); // make sure implicitly redirected target exists
1416
- if (!obj.items && type && type.items && enableExpandElements) {
1417
- const items = {
1418
- location: weakLocation( (obj.type || obj).location ),
1419
- $inferred: 'expand-items',
1420
- };
1421
- setProp( items, '_outer', obj );
1422
- setProp( items, '_origin', type.items );
1423
- obj.items = items;
1424
- obj.$expand = 'origin';
1425
- }
1426
- if (obj.items) { // TODO: make this a while in v2 (also items proxy)
1427
- obj = obj.items || obj; // the object which has type properties
1428
- if (enableExpandElements)
1429
- effectiveType(obj);
1430
- }
1431
- if (obj.type) { // TODO: && !obj.type.$inferred ?
1432
- if (obj !== (art.returns || art)) // not already checked
1433
- resolveTypeExpr( obj, art );
1434
- // typeOf unmanaged assoc?
1435
- const elemtype = obj.type._artifact;
1436
- if (elemtype) {
1437
- if (elemtype.on && !obj.on)
1438
- obj.on = { $inferred: 'rewrite' };
1439
- if (elemtype.targetAspect) {
1440
- error( 'composition-as-type-of', [ obj.type.location, art ], {},
1441
- 'A managed aspect composition element can\'t be used as type' );
1442
- return;
1443
- }
1444
- else if (elemtype.on) {
1445
- error( 'assoc-as-type-of', [ obj.type.location, art ], {},
1446
- 'An unmanaged association can\'t be used as type' );
1447
- return;
1448
- }
1449
-
1450
- // Check if relational type is missing its target or if it's used directly.
1451
- if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
1452
- !obj.target && !obj.targetAspect) {
1453
- const isCsn = (obj._block && obj._block.$frontend === 'json');
1454
- error('type-missing-target', [ obj.type.location, obj ],
1455
- { '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
1456
- // We don't say "use 'association to <target>" because the type could be used
1457
- // in action parameters, etc. as well.
1458
- std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
1459
- csn: 'Type $(TYPE) is missing a target',
1460
- });
1461
- }
1462
- }
1463
- }
1464
- if (obj.target) {
1465
- // console.log(obj.name,obj._origin.name)
1466
- if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
1467
- resolveTarget( art, obj._origin );
1468
- // console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
1469
- // .toString(), obj.target.$inferred)
1470
- if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
1471
- resolveTarget( art, obj );
1472
- else
1473
- // TODO: better write when inferred target must be redirected
1474
- resolveRedirected( obj, obj.target._artifact );
1475
- }
1476
- else if (obj.kind === 'mixin') {
1477
- error( 'non-assoc-in-mixin', [ (obj.type || obj.name).location, art ], {},
1478
- 'Only unmanaged associations are allowed in mixin clauses' );
1479
- }
1480
- if (art.targetElement) { // in foreign keys
1481
- const target = parent && parent.target;
1482
- if (target && target._artifact) {
1483
- // we just look in target for the path
1484
- // TODO: also check that we do not follow associations? no args, no filter
1485
- resolvePath( art.targetElement, 'targetElement', art,
1486
- environment( target._artifact ), target._artifact );
1487
- }
1488
- }
1489
- // Resolve projections/views
1490
- // if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
1491
-
1492
- if (art.$queries)
1493
- art.$queries.forEach( resolveQuery );
1494
-
1495
- if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
1496
- effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
1497
-
1498
- if (obj.elements) { // silent dependencies
1499
- forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
1500
- }
1501
- else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
1502
- forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
1503
- }
1504
- if (obj.foreignKeys) { // silent dependencies
1505
- forEachGeneric( obj, 'foreignKeys', (elem) => {
1506
- dependsOnSilent( art, elem );
1507
- } );
1508
- addForeignKeyNavigations( art );
1509
- }
1510
-
1511
- resolveExpr( art.default, 'default', art );
1512
- resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
1513
- if (art.value && !art.type && !art.target && !art.elements)
1514
- inferTypeFromCast( art );
1515
-
1516
- if (art.kind === 'element' || art.kind === 'mixin')
1517
- effectiveType( art );
1518
-
1519
- annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
1520
- chooseAnnotationsInArtifact( art );
1521
-
1522
- forEachMember( art, resolveRefs, art.targetAspect );
1523
-
1524
- // Set '@Core.Computed' in the Core Compiler to have it propagated...
1525
- if (art.kind !== 'element' || art['@Core.Computed'])
1526
- return;
1527
- if (art.virtual && art.virtual.val ||
1528
- art.value &&
1529
- (!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
1530
- [ 'builtin', 'param' ].includes( art.value._artifact.kind ))) {
1531
- art['@Core.Computed'] = {
1532
- name: {
1533
- path: [ { id: 'Core.Computed', location: art.location } ],
1534
- location: art.location,
1535
- },
1536
- $inferred: 'computed',
1537
- };
1538
- }
1539
- }
1540
-
1541
- function inferTypeFromCast( elem ) {
1542
- // TODO: think about CAST checks in checks.js
1543
- const { op, type } = elem.value;
1544
- if (op && op.val === 'cast' && type && type._artifact) {
1545
- // op.val is also correctly set with CSN input
1546
- elem.type = { ...type, $inferred: 'cast' };
1547
- setProp( elem.type, '_artifact', type._artifact );
1548
- for (const prop of [ 'length', 'precision', 'scale', 'srid' ]) {
1549
- if (elem.value[prop])
1550
- elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
1551
- }
1552
- }
1553
- }
1554
-
1555
- // Phase 4 - annotations ---------------------------------------------------
1556
-
1557
- function annotateUnknown( ext ) {
1558
- // extensions may have annotations for elements/actions/... which may
1559
- // themselves may be unknown
1560
- forEachMember(ext, annotateUnknown);
1561
-
1562
- if (ext.$extension) // extension for known artifact -> already applied
1563
- return;
1564
- annotateMembers( ext );
1565
- for (const prop in ext) {
1566
- if (prop.charAt(0) === '@')
1567
- chooseAssignment( prop, ext );
1568
- }
1569
- }
1570
-
1571
- function annotateMembers( art, extensions = [], prop, name, parent, kind ) {
1572
- const showMsg = !art && parent && parent.kind !== 'annotate';
1573
- if (!art && extensions.length) {
1574
- if (Array.isArray( parent ))
1575
- return;
1576
- const parentExt = extensionFor(parent);
1577
- art = parentExt[prop] && parentExt[prop][name];
1578
- if (!art) {
1579
- art = {
1580
- kind, // for setMemberParent()
1581
- name: { id: name, location: extensions[0].name.location },
1582
- location: extensions[0].location,
1583
- };
1584
- setMemberParent( art, name, parentExt, prop );
1585
- art.kind = 'annotate'; // after setMemberParent()!
1586
- }
1587
- }
1588
-
1589
- for (const ext of extensions) {
1590
- if ('_artifact' in ext.name) // already applied
1591
- continue;
1592
- setProp( ext.name, '_artifact', art );
1593
-
1594
- if (art) {
1595
- defineAnnotations( ext, art, ext._block, ext.kind );
1596
- // eslint-disable-next-line no-shadow
1597
- forEachMember( ext, ( elem, name, prop ) => {
1598
- storeExtension( elem, name, prop, art, ext._block );
1599
- });
1600
- }
1601
- if (showMsg) {
1602
- // somehow similar to checkDefinitions():
1603
- const feature = kindProperties[parent.kind][prop];
1604
- if (prop === 'elements' || prop === 'enum') {
1605
- if (!feature) {
1606
- warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
1607
- 'Elements only exist in entities, types or typed constructs' );
1608
- }
1609
- else {
1610
- notFound( 'anno-undefined-element', ext.name.location, art,
1611
- { art: searchName( parent, name, parent.enum ? 'enum' : 'element' ) },
1612
- parent.elements || parent.enum );
1613
- }
1614
- }
1615
- else if (prop === 'actions') {
1616
- if (!feature) {
1617
- warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
1618
- 'Actions and functions only exist top-level and for entities' );
1619
- }
1620
- else {
1621
- notFound( 'anno-undefined-action', ext.name.location, art,
1622
- { art: searchName( parent, name, 'action' ) },
1623
- parent.actions );
1624
- }
1625
- }
1626
- else if (!feature) {
1627
- warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
1628
- 'Parameters only exist for actions or functions' );
1629
- } // TODO: entities betaMod
1630
- else {
1631
- notFound( 'anno-undefined-param', ext.name.location, art,
1632
- { art: searchName( parent, name, 'param' ) },
1633
- parent.params );
1634
- }
1635
- }
1636
- }
1637
- if (art && art._annotate) {
1638
- if (art.kind === 'action' || art.kind === 'function') {
1639
- expandParameters( art );
1640
- if (art.returns)
1641
- effectiveType( art.returns );
1642
- }
1643
- const aor = art.returns || art;
1644
- const obj = aor.items || aor.targetAspect || aor;
1645
- // Currently(?), effectiveType() does not calculate the effective type of
1646
- // its line item:
1647
- effectiveType( obj );
1648
- if (art._annotate.elements)
1649
- setExpandStatusAnnotate( aor, 'annotate' );
1650
- annotate( obj, 'element', 'elements', 'enum', art );
1651
- annotate( art, 'action', 'actions' );
1652
- annotate( art, 'param', 'params' );
1653
- // const { returns } = art._annotate;
1654
- // if (returns) {
1655
- // const dict = returns.elements;
1656
- // const env = obj.returns && obj.returns.elements || null;
1657
- // for (const n in dict)
1658
- // annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
1659
- // }
1660
- }
1661
- return;
1662
-
1663
- function notFound( msgId, location, address, args, validDict ) {
1664
- // TODO: probably move this to shared.js and use for EXTEND, too
1665
- const msg = message( msgId, [ location, address ], args );
1666
- attachAndEmitValidNames(msg, validDict);
1667
- }
1668
-
1669
- // eslint-disable-next-line no-shadow
1670
- function annotate( obj, kind, prop, altProp, parent = obj ) {
1671
- const dict = art._annotate[prop];
1672
- const env = obj[prop] || altProp && obj[altProp] || null;
1673
- for (const n in dict)
1674
- annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
1675
- }
1676
- }
1677
- function expandParameters( action ) {
1678
- // see also expandElements()
1679
- if (!enableExpandElements || !effectiveType( action ))
1680
- return;
1681
- const chain = [];
1682
- // Should we be able to consider params and returns separately?
1683
- // Probably not, let to-csn omit unchanged params/returns.
1684
- while (action._origin && !action.params) {
1685
- chain.push( action );
1686
- action = action._origin;
1687
- }
1688
- chain.reverse();
1689
- for (const art of chain) {
1690
- const origin = art._origin;
1691
- if (!art.params && origin.params) {
1692
- for (const name in origin.params) {
1693
- // TODO: we could check _annotate here to decide whether we really
1694
- // not to create proxies
1695
- const orig = origin.params[name];
1696
- linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
1697
- .$inferred = 'expand-param';
1698
- }
1699
- }
1700
- if (!art.returns && origin.returns) {
1701
- // TODO: make linkToOrigin() work for returns, kind/name?
1702
- const location = weakLocation( origin.returns.location );
1703
- art.returns = {
1704
- name: Object.assign( {}, art.name, { id: '', param: '', location } ),
1705
- kind: 'param',
1706
- location,
1707
- $inferred: 'expand-param',
1708
- };
1709
- setProp( art.returns, '_parent', art );
1710
- setProp( art.returns, '_main', art._main || art );
1711
- setProp( art.returns, '_origin', origin.returns );
1712
- }
1713
- }
1714
- }
1715
-
1716
- function extensionFor( art ) {
1717
- if (art.kind === 'annotate')
1718
- return art;
1719
- if (art._extension)
1720
- return art._extension;
1721
-
1722
- // $extension means: already applied
1723
- const ext = {
1724
- kind: art.kind, // set kind for setMemberParent()
1725
- $extension: 'exists',
1726
- location: art.location, // location( extension to existing art ) = location(art)
1727
- };
1728
- const { location } = art.name;
1729
- if (!art._main) {
1730
- ext.name = {
1731
- path: [ { id: art.name.absolute, location } ],
1732
- location,
1733
- absolute: art.name.absolute,
1734
- };
1735
- if (model.extensions)
1736
- model.extensions.push(ext);
1737
- else
1738
- model.extensions = [ ext ];
1739
- }
1740
- else {
1741
- ext.name = { id: art.name.id, location };
1742
- const parent = extensionFor( art._parent );
1743
- const kind = kindProperties[art.kind].normalized || art.kind;
1744
- // enums would be first in elements
1745
- if ( parent[kindProperties[kind].dict] &&
1746
- parent[kindProperties[kind].dict][art.name.id] )
1747
- throw new Error(art.name.id);
1748
- setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
1749
- }
1750
- ext.kind = 'annotate'; // after setMemberParent()!
1751
- setProp( art, '_extension', ext );
1752
- setProp( ext.name, '_artifact', art );
1753
- if (art.returns)
1754
- ext.$syntax = 'returns';
1755
- return ext;
1756
- }
1757
-
1758
- /**
1759
- * Goes through all (applied) annotations in the given artifact and chooses one
1760
- * if multiple exist according to the module layer.
1761
- *
1762
- * @param {XSN.Artifact} art
1763
- */
1764
- function chooseAnnotationsInArtifact( art ) {
1765
- for (const prop in art) {
1766
- if (prop.charAt(0) === '@')
1767
- chooseAssignment( prop, art );
1768
- }
1769
- }
1770
-
1771
- function chooseAssignment( annoName, art ) {
1772
- // TODO: getPath an all names
1773
- const anno = art[annoName];
1774
- if (!Array.isArray(anno)) { // just one assignment -> use it
1775
- if (removeEllipsis( anno )) {
1776
- error( 'anno-unexpected-ellipsis',
1777
- [ anno.name.location, art ], { code: '...' } );
1778
- }
1779
- return;
1780
- }
1781
- // sort assignment according to layer
1782
- const layerAnnos = Object.create(null);
1783
- for (const a of anno) {
1784
- const layer = layers.layer( a._block );
1785
- const name = (layer) ? layer.realname : '';
1786
- const done = layerAnnos[name];
1787
- if (done)
1788
- done.annos.push( a );
1789
- else
1790
- layerAnnos[name] = { layer, annos: [ a ] };
1791
- }
1792
- mergeArrayInSCCs();
1793
- art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
1794
- return;
1795
-
1796
- function mergeArrayInSCCs( ) {
1797
- let pos = 0;
1798
- Object.values( layerAnnos ).forEach( (layer) => {
1799
- const mergeSource
1800
- = layer.annos.find(v => (v.$priority === undefined ||
1801
- annotationPriorities[v.$priority] === annotationPriorities.define));
1802
- if (mergeSource) {
1803
- if (removeEllipsis( mergeSource )) {
1804
- error( 'anno-unexpected-ellipsis',
1805
- [ mergeSource.name.location, art ], { code: '...' } );
1806
- }
1807
- // merge source into elipsis array annotates
1808
- layer.annos.forEach( (mergeTarget) => {
1809
- if (mergeTarget.$priority &&
1810
- annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
1811
- pos = findEllipsis( mergeTarget );
1812
- if (pos > -1) {
1813
- if (mergeSource.literal !== 'array') {
1814
- error( 'anno-mismatched-ellipsis',
1815
- [ mergeSource.name.location, art ], { code: '...' } );
1816
- return;
1817
- }
1818
- mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1819
- }
1820
- }
1821
- });
1822
- }
1823
- });
1824
- }
1825
-
1826
- function mergeLayeredArrays( mergeTarget ) {
1827
- if (mergeTarget.literal === 'array') {
1828
- let layer = layers.layer( mergeTarget._block );
1829
- delete layerAnnos[(layer) ? layer.realname : ''];
1830
- let pos = findEllipsis( mergeTarget );
1831
- while (pos > -1 && Object.keys( layerAnnos ).length ) {
1832
- const mergeSource = findLayerCandidate();
1833
- if (mergeSource.literal !== 'array') {
1834
- error( 'anno-mismatched-ellipsis',
1835
- [ mergeSource.name.location, art ], { code: '...' } );
1836
- return mergeTarget;
1837
- }
1838
- mergeTarget.val.splice(pos, 1, ...mergeSource.val);
1839
- layer = layers.layer( mergeSource._block );
1840
- delete layerAnnos[(layer) ? layer.realname : ''];
1841
- pos = findEllipsis( mergeTarget );
1842
- }
1843
- // remove excess ellipsis
1844
- removeEllipsis( mergeTarget, pos );
1845
- }
1846
- return mergeTarget;
1847
- }
1848
-
1849
- function removeEllipsis(a, pos = findEllipsis( a )) {
1850
- let count = 0;
1851
- while (a.literal === 'array' && pos > -1) {
1852
- count++;
1853
- a.val.splice(pos, 1);
1854
- pos = findEllipsis( a );
1855
- }
1856
- return count;
1857
- }
1858
-
1859
- function findEllipsis(a) {
1860
- return (a.literal === 'array' && a.val)
1861
- ? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
1862
- }
1863
-
1864
- function findLayerCandidate() {
1865
- // collect assignments of upper layers (are in no _layerExtends)
1866
- const exts = Object.keys( layerAnnos ).map( layerExtends );
1867
- const allExtends = Object.assign( Object.create(null), ...exts );
1868
- const collected = [];
1869
- for (const name in layerAnnos) {
1870
- if (!(name in allExtends))
1871
- collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
1872
- }
1873
- // inspect collected assignments - choose the one or signal error
1874
- const justOnePerLayer = collected.every( annos => annos.length === 1);
1875
- if (!justOnePerLayer || collected.length > 1) {
1876
- for (const annos of collected) {
1877
- for (const a of annos ) {
1878
- // Only the message ID is different.
1879
- if (justOnePerLayer) {
1880
- message( 'anno-duplicate-unrelated-layer',
1881
- [ a.name.location, art ], { anno: annoName },
1882
- 'Duplicate assignment with $(ANNO)' );
1883
- }
1884
- else {
1885
- message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName },
1886
- 'Duplicate assignment with $(ANNO)' );
1887
- }
1888
- }
1889
- }
1890
- }
1891
- return collected[0][0]; // just choose any one with error
1892
- }
1893
-
1894
- function layerExtends( name ) {
1895
- const { layer } = layerAnnos[name];
1896
- return layer && layer._layerExtends;
1897
- }
1898
- }
1899
-
1900
- function prioritizedAnnos( annos ) {
1901
- let prio = 0;
1902
- let r = [];
1903
- for (const a of annos) {
1904
- const p = annotationPriorities[a.$priority] || annotationPriorities.define;
1905
- if (p === prio) {
1906
- r.push(a);
1907
- }
1908
- else if (p > prio) {
1909
- r = [ a ];
1910
- prio = p;
1911
- }
1912
- }
1913
- return r;
1914
- }
1915
-
1916
- // Phase 4 - queries and associations --------------------------------------
1917
-
1918
- function resolveQuery( query ) {
1919
- if (!query._main) // parse error
1920
- return;
1921
- traverseQueryPost( query, null, populateQuery );
1922
- forEachGeneric( query, '$tableAliases', ( alias ) => {
1923
- // console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
1924
- if (alias.kind === 'mixin')
1925
- resolveRefs( alias ); // mixin element
1926
- else if (alias.kind !== '$self')
1927
- // pure path has been resolved, resolve args and filter now:
1928
- resolveExpr( alias, 'from', query._parent );
1929
- } );
1930
- for (const col of query.$inlines)
1931
- resolveExpr( col.value, 'expr', col, undefined, true );
1932
- // for (const col of query.$inlines)
1933
- // if (!col.value.path) throw Error(col.name.element)
1934
- if (query !== query._main._leadingQuery) // will be done later
1935
- forEachGeneric( query, 'elements', resolveRefs );
1936
- if (query.from)
1937
- resolveJoinOn( query.from );
1938
- if (query.where)
1939
- resolveExpr( query.where, 'expr', query, query._combined );
1940
- if (query.groupBy)
1941
- resolveBy( query.groupBy, 'expr' );
1942
- resolveExpr( query.having, 'expr', query, query._combined );
1943
- if (query.$orderBy) // ORDER BY from UNION:
1944
- // TODO clarify: can I access the tab alias of outer queries? If not:
1945
- // 4th arg query._main instead query._parent.
1946
- resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
1947
- if (query.orderBy) { // ORDER BY
1948
- // search in `query.elements` after having checked table aliases of the current query
1949
- resolveBy( query.orderBy, 'expr', query.elements );
1950
- // TODO: disallow resulting element ref if in expression!
1951
- // Necessary to check it in the compiler as it might work with other semantics on DB!
1952
- // (we could downgrade it to a warning if name is equal to unique source element name)
1953
- // TODO: Some helping text mentioning an alias name would be useful
1954
- }
1955
- return;
1956
-
1957
- function resolveJoinOn( join ) {
1958
- if (join && join.args) { // JOIN
1959
- for (const j of join.args)
1960
- resolveJoinOn( j );
1961
- if (join.on)
1962
- resolveExpr( join.on, 'expr', query, query._combined );
1963
- // TODO: check restrictions according to join "query"
1964
- }
1965
- }
1966
-
1967
- // Note the strange name resolution (dynamic part) for ORDER BY: the same
1968
- // as for select items if it is an expression, but first look at select
1969
- // item alias (i.e. like `$projection.NAME` if it is a path. If it is an
1970
- // ORDER BY of an UNION, do not allow any dynamic path in an expression,
1971
- // and only allow the elements of the leading query if it is a path.
1972
- //
1973
- // This seem to be similar, but different in SQLite 3.22.0: ORDER BY seems
1974
- // to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
1975
- // resolution seems to use select item aliases from all SELECTs of the
1976
- // UNION (see <SQLite>/test/tkt2822.test).
1977
- function resolveBy( array, mode, pathDict, q ) {
1978
- for (const value of array ) {
1979
- if (value)
1980
- resolveExpr( value, mode, q || query, value.path && pathDict );
1981
- }
1982
- }
1983
- }
1984
-
1985
- function resolveTarget( art, obj ) {
1986
- if (art !== obj && obj.on && obj.$inferred !== 'REDIRECTED') {
1987
- message( 'assoc-in-array', [ obj.on.location, art ], {},
1988
- // TODO: also check parameter parent, two messages?
1989
- 'An association can\'t be used for arrays or parameters' );
1990
- setProp( obj.target, '_artifact', undefined );
1991
- return;
1992
- }
1993
- const target = resolvePath( obj.target, 'target', art );
1994
- if (obj.on) {
1995
- if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
1996
- // TODO: test of .items a bit unclear - we should somehow restrict the
1997
- // use of unmanaged assocs in MANY, at least with $self
1998
- // TODO: $self usage in anonymous aspects to be corrected in Core Compiler
1999
- const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
2000
- obj.type.path[0].id === 'cds.Composition';
2001
- message( 'assoc-as-type', [ obj.on.location, art ],
2002
- { '#': isComposition ? 'comp' : 'std' }, {
2003
- std: 'An unmanaged association can\'t be defined as type',
2004
- comp: 'An unmanaged composition can\'t be defined as type',
2005
- });
2006
- // TODO: also warning if inside structure
2007
- }
2008
- else if (obj.$inferred !== 'REDIRECTED') {
2009
- // TODO: extra with $inferred (to avoid messages)?
2010
- // TODO: in the ON condition of an explicitly provided model entity
2011
- // which is going to be implicitly redirected, we can never navigate
2012
- // along associations, even not to the foreign keys (at least if they
2013
- // are renamed) - introduce extra 'expected' which inspects REDIRECTED
2014
- resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
2015
- }
2016
- else {
2017
- const elements = Object.create( art._parent.elements );
2018
- elements[art.name.id] = obj;
2019
- resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art, elements );
2020
- }
2021
- }
2022
- else if (art.kind === 'mixin') {
2023
- error( 'assoc-in-mixin', [ obj.target.location, art ], {},
2024
- 'Managed associations are not allowed for MIXIN elements' );
2025
- }
2026
- else if (target && !obj.foreignKeys && [ 'entity' ].includes( target.kind )) {
2027
- if (obj.$inferred === 'REDIRECTED') {
2028
- addImplicitForeignKeys( art, obj, target );
2029
- }
2030
- else if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
2031
- resolveRedirected( art, target );
2032
- }
2033
- else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
2034
- addImplicitForeignKeys( art, obj, target );
2035
- }
2036
- // else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
2037
- }
2038
- // else console.log( message( null, obj.location, obj, {target}, 'Info','NORE').toString())
2039
- }
2040
-
2041
- function addImplicitForeignKeys( art, obj, target ) {
2042
- obj.foreignKeys = Object.create(null);
2043
- forEachInOrder( target, 'elements', ( elem, name ) => {
2044
- if (elem.key && elem.key.val) {
2045
- const { location } = art.target;
2046
- const key = {
2047
- name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()
2048
- kind: 'key',
2049
- targetElement: { path: [ { id: elem.name.id, location } ], location },
2050
- location,
2051
- $inferred: 'keys',
2052
- };
2053
- setMemberParent( key, name, art );
2054
- dictAdd( obj.foreignKeys, name, key );
2055
- setProp( key.targetElement, '_artifact', elem );
2056
- setProp( key.targetElement.path[0], '_artifact', elem );
2057
- setProp( key, '_effectiveType', effectiveType(elem) );
2058
- dependsOn(key, elem, location);
2059
- dependsOnSilent(art, key);
2060
- }
2061
- });
2062
- }
2063
-
2064
- function addForeignKeyNavigations( art ) {
2065
- art.$keysNavigation = Object.create(null);
2066
- forEachGeneric( art, 'foreignKeys', ( key ) => {
2067
- if (!key.targetElement || !key.targetElement.path)
2068
- return;
2069
- let dict = art.$keysNavigation;
2070
- const last = key.targetElement.path[key.targetElement.path.length - 1];
2071
- for (const item of key.targetElement.path) {
2072
- let nav = dict[item.id];
2073
- if (!nav) {
2074
- nav = {};
2075
- dict[item.id] = nav;
2076
- if (item === last)
2077
- setLink( nav, key );
2078
- else
2079
- nav.$keysNavigation = Object.create(null);
2080
- }
2081
- else if (item === last || nav._artifact) {
2082
- error( 'duplicate-key-ref', [ item.location, key ], {},
2083
- 'The same target reference has already been used in a key definition' );
2084
- return;
2085
- }
2086
- dict = nav.$keysNavigation;
2087
- }
2088
- } );
2089
- }
2090
-
2091
- function resolveRedirected( elem, target ) {
2092
- setProp( elem, '_redirected', null ); // null = do not touch path steps after assoc
2093
- const assoc = directType( elem );
2094
- const origType = assoc && effectiveType( assoc );
2095
- if (!origType || !origType.target) {
2096
- error( 'redirected-no-assoc', [ elem.target.location, elem ], {},
2097
- 'Only an association can be redirected' );
2098
- return;
2099
- }
2100
- // console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
2101
- // .toString(), elem.value)
2102
- const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
2103
- if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
2104
- if (origType.on) {
2105
- error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
2106
- // TODO: Better text ?
2107
- 'The ON condition is not rewritten here - provide an explicit ON condition' );
2108
- return;
2109
- }
2110
- }
2111
- const origTarget = origType.target._artifact;
2112
- if (!origTarget || !target)
2113
- return;
2114
-
2115
- const chain = [];
2116
- if (target === origTarget) {
2117
- if (!elem.target.$inferred) {
2118
- info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
2119
- 'The redirected target is the original $(ART)' );
2120
- }
2121
- setProp( elem, '_redirected', chain ); // store the chain
2122
- return;
2123
- }
2124
- if (elem.foreignKeys || elem.on)
2125
- return; // TODO: or should we still bring an msg if nothing in common?
2126
- // now check whether target and origTarget are "related"
2127
- while (target.query) {
2128
- const from = target.query.args ? {} : target.query.from;
2129
- if (!from)
2130
- return; // parse error - TODO: or UNION?
2131
- if (!from.path) {
2132
- warning( 'redirected-to-complex', [ elem.target.location, elem ],
2133
- { art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
2134
- {
2135
- std: 'Redirection involves the complex view $(ART)',
2136
- target: 'The redirected target $(ART) is a complex view',
2137
- });
2138
- break;
2139
- }
2140
- target = from._artifact;
2141
- if (!target)
2142
- return;
2143
- chain.push( from );
2144
- if (target === origTarget) {
2145
- chain.reverse();
2146
- setProp( elem, '_redirected', chain );
2147
- return;
2148
- }
2149
- }
2150
- let redirected = null;
2151
- let news = [ { chain: chain.reverse(), sources: [ target ] } ];
2152
- const dict = Object.create(null);
2153
- while (news.length) {
2154
- const outer = news;
2155
- news = [];
2156
- for (const o of outer) {
2157
- for (const s of o.sources) {
2158
- const art = (s.kind === '$tableAlias') ? s._origin : s;
2159
- if (art !== origTarget) {
2160
- if (findOrig( o.chain, s, art ) && !redirected) // adds to news []
2161
- redirected = false; // do not report further error
2162
- }
2163
- else if (!redirected) {
2164
- redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
2165
- }
2166
- else {
2167
- error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
2168
- 'The redirected target originates more than once from $(ART)' );
2169
- return;
2170
- }
2171
- }
2172
- }
2173
- }
2174
- if (redirected) {
2175
- setProp( elem, '_redirected', redirected );
2176
- }
2177
- else if (redirected == null) {
2178
- error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
2179
- 'The redirected target does not originate from $(ART)' );
2180
- }
2181
- return;
2182
-
2183
- // B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
2184
- // what does a: redirected to C means?
2185
- // -> collect all elements Qi used in ON (corr: foreign keys)
2186
- // -> only use an tableAlias which has propagation for all elements
2187
- // no - error if the original target can be reached twice
2188
- // even better: disallow complex view (try as error first)
2189
-
2190
- // eslint-disable-next-line no-shadow
2191
- function findOrig( chain, alias, art ) {
2192
- if (!art || dict[art.name.absolute])
2193
- // some include ref or query source cannot be found, or cyclic ref
2194
- return true;
2195
- dict[art.name.absolute] = true;
2196
-
2197
- if (art.includes) {
2198
- news.push( {
2199
- chain: [ art, ...chain ],
2200
- sources: art.includes
2201
- .map( r => r._artifact )
2202
- .filter( i => i ), // _artifact may be `null` if the include cannot be found
2203
- } );
2204
- }
2205
- const query = art._leadingQuery;
2206
- if (!query)
2207
- return false; // non-query entity
2208
- if (!query.$tableAliases) // previous error in query definition
2209
- return true;
2210
- const sources = [];
2211
- for (const n in query.$tableAliases) {
2212
- const a = query.$tableAliases[n];
2213
- if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
2214
- sources.push( a );
2215
- }
2216
- if (alias.kind === '$tablealias')
2217
- news.push( { chain: [ alias, ...chain ], sources } );
2218
- else
2219
- news.push( { chain, sources } );
2220
- return false;
2221
- }
2222
- }
2223
-
2224
- //--------------------------------------------------------------------------
2225
- // Phase 5: rewrite associations
2226
- //--------------------------------------------------------------------------
2227
- // Only top-level queries and sub queries in FROM
2228
-
2229
- function rewriteSimple( art ) {
2230
- // If we have a proper seperation of view elements and elements of the
2231
- // primary query, we can delete this function.
2232
- // return;
2233
- if (!art.includes && !art.query) {
2234
- // console.log(message( null, art.location, art, {target:art._target},
2235
- // 'Info','RAS').toString())
2236
- rewriteAssociation( art );
2237
- forEachGeneric( art, 'elements', rewriteAssociation );
2238
- }
2239
- if (art._service)
2240
- forEachGeneric( art, 'elements', excludeAssociation );
2241
- }
2242
-
2243
- function rewriteView( view ) {
2244
- traverseQueryPost( view.query, false, ( query ) => {
2245
- forEachGeneric( query, 'elements', rewriteAssociation );
2246
- } );
2247
- if (view.includes) // entities with structure includes:
2248
- forEachGeneric( view, 'elements', rewriteAssociation );
2249
- }
2250
-
2251
- // Check explicit ON / keys with REDIRECTED TO
2252
- function rewriteViewCheck( view ) {
2253
- traverseQueryPost( view.query, false, ( query ) => {
2254
- forEachGeneric( query, 'elements', rewriteAssociationCheck );
2255
- } );
2256
- }
2257
-
2258
- function excludeAssociation( elem ) {
2259
- const target = elem.target && elem.target._artifact;
2260
- if (!target || target._service) // assoc to other service is OK
2261
- return;
2262
- if (!elem.$inferred) { // && !elem.target.$inferred
2263
- // TODO: spec meeting 2021-01-22: no warning
2264
- warning( 'assoc-target-not-in-service', [ elem.target.location, elem ],
2265
- { target, '#': (elem._main.query ? 'select' : 'define') }, {
2266
- std: 'Target $(TARGET) of association is outside any service', // not used
2267
- // eslint-disable-next-line max-len
2268
- define: 'Target $(TARGET) of explicitly defined association is outside any service',
2269
- // eslint-disable-next-line max-len
2270
- select: 'Target $(TARGET) of explicitly selected association is outside any service',
2271
- } );
2272
- }
2273
- else {
2274
- info( 'assoc-outside-service', [ elem.target.location, elem ],
2275
- { target },
2276
- 'Association target $(TARGET) is outside any service' );
2277
- }
2278
- }
2279
-
2280
- function rewriteAssociationCheck( element ) {
2281
- const elem = element.items || element; // TODO v2: nested items
2282
- if (elem.elements && enableExpandElements)
2283
- forEachGeneric( elem, 'elements', rewriteAssociationCheck );
2284
- if (!elem.target)
2285
- return;
2286
- if (elem.on && !elem.on.$inferred) {
2287
- const assoc = directType( elem );
2288
- if (assoc && assoc.foreignKeys) {
2289
- error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
2290
- { keyword: 'on', art: assocWithExplicitSpec( assoc ) },
2291
- // eslint-disable-next-line max-len
2292
- 'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
2293
- }
2294
- }
2295
- else if (elem.foreignKeys && !inferredForeignKeys( elem.foreignKeys )) {
2296
- const assoc = directType( elem );
2297
- if (assoc && assoc.on) {
2298
- error( 'rewrite-on-for-managed', [ dictLocation( elem.foreignKeys ), elem ],
2299
- { art: assocWithExplicitSpec( assoc ) },
2300
- 'Do not specify foreign keys when redirecting the unmanaged association $(ART)' );
2301
- }
2302
- else if (assoc && assoc.foreignKeys) {
2303
- // same sequence is not checked
2304
- rewriteKeysMatch( elem, assoc );
2305
- rewriteKeysCovered( assoc, elem );
2306
- }
2307
- }
2308
- }
2309
-
2310
- function rewriteKeysMatch( thisAssoc, otherAssoc ) {
2311
- const { foreignKeys } = thisAssoc;
2312
- for (const name in foreignKeys) {
2313
- if (otherAssoc.foreignKeys[name])
2314
- continue; // we would do a basic type check later
2315
- const key = foreignKeys[name];
2316
- const baseAssoc = assocWithExplicitSpec( otherAssoc );
2317
- if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
2318
- error( 'rewrite-key-not-matched-implicit', [ key.name.location, key ],
2319
- { name, target: baseAssoc.target },
2320
- 'No key $(NAME) is defined in original target $(TARGET)' );
2321
- }
2322
- else {
2323
- error( 'rewrite-key-not-matched-explicit', [ key.name.location, key ],
2324
- { name, art: baseAssoc },
2325
- 'No foreign key $(NAME) is specified in association $(ART)' );
2326
- }
2327
- }
2328
- }
2329
-
2330
- function rewriteKeysCovered( thisAssoc, otherAssoc ) {
2331
- const names = [];
2332
- const { foreignKeys } = thisAssoc;
2333
- for (const name in foreignKeys) {
2334
- if (!otherAssoc.foreignKeys[name])
2335
- names.push( name );
2336
- }
2337
- if (names.length) {
2338
- const location = dictLocation.end( otherAssoc.foreignKeys );
2339
- const baseAssoc = assocWithExplicitSpec( thisAssoc );
2340
- if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
2341
- error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
2342
- { names, target: baseAssoc.target },
2343
- {
2344
- std: 'Specify keys $(NAMES) of original target $(TARGET) as foreign keys',
2345
- one: 'Specify key $(NAMES) of original target $(TARGET) as foreign key',
2346
- } );
2347
- }
2348
- else {
2349
- error( 'rewrite-key-not-covered-explicit', [ location, otherAssoc ],
2350
- { names, art: otherAssoc },
2351
- {
2352
- std: 'Specify foreign keys $(NAMES) of association $(ART)',
2353
- one: 'Specify foreign key $(NAMES) of association $(ART)',
2354
- } );
2355
- }
2356
- }
2357
- }
2358
-
2359
- function assocWithExplicitSpec( assoc ) {
2360
- while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys') ||
2361
- assoc.on && assoc.on.$inferred)
2362
- assoc = directType( assoc );
2363
- return assoc;
2364
- }
2365
-
2366
- function rewriteAssociation( element ) {
2367
- let elem = element.items || element; // TODO v2: nested items
2368
- if (elem.elements && enableExpandElements)
2369
- forEachGeneric( elem, 'elements', rewriteAssociation );
2370
- if (!originTarget( elem ))
2371
- return;
2372
- // console.log(message( null, elem.location, elem,
2373
- // {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
2374
-
2375
- // With cyclic dependencies on select items, testing for the _effectiveType to
2376
- // be 0 (test above) is not enough if we we have an explicit redirection
2377
- // target -> avoid infloop ourselves with _status.
2378
- const chain = [];
2379
- while (!elem.on && !elem.foreignKeys) {
2380
- chain.push( elem );
2381
- if (elem._status === 'rewrite') { // circular dependency (already reported)
2382
- for (const e of chain)
2383
- setProp( e, '_status', null ); // XSN TODO: nonenum _status -> enum $status
2384
- return;
2385
- }
2386
- setProp( elem, '_status', 'rewrite' );
2387
- elem = directType( elem );
2388
- if (!elem || elem.builtin) // safety
2389
- return;
2390
- }
2391
- chain.reverse();
2392
- for (const art of chain) {
2393
- setProp( elem, '_status', null );
2394
- if (elem.on)
2395
- rewriteCondition( art, elem );
2396
- else if (elem.foreignKeys)
2397
- rewriteKeys( art, elem );
2398
- elem = art;
2399
- }
2400
- }
2401
-
2402
- function originTarget( elem ) {
2403
- const assoc = !elem.expand && directType( elem );
2404
- const ftype = assoc && effectiveType( assoc );
2405
- return ftype && ftype.target && ftype.target._artifact;
2406
- }
2407
-
2408
- function inferredForeignKeys( foreignKeys, ignore ) {
2409
- // TODO: better use a symbol $inferred for dictionaries later
2410
- for (const name in foreignKeys)
2411
- return foreignKeys[name].$inferred && foreignKeys[name].$inferred !== ignore;
2412
- return false;
2413
- }
2414
-
2415
- function rewriteKeys( elem, assoc ) {
2416
- // TODO: split this function: create foreign keys without `targetElement`
2417
- // already in Phase 2: redirectImplicitly()
2418
- // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
2419
- // 'Info','FK').toString())
2420
- forEachInOrder( assoc, 'foreignKeys', ( orig, name ) => {
2421
- const fk = linkToOrigin( orig, name, elem, 'foreignKeys', elem.location );
2422
- fk.$inferred = 'rewrite'; // TODO: other $inferred value?
2423
- // TODO: re-check for case that foreign key is managed association
2424
- if ('_effectiveType' in orig)
2425
- setProp( fk, '_effectiveType', orig._effectiveType);
2426
- const te = copyExpr( orig.targetElement, elem.location );
2427
- if (elem._redirected) {
2428
- const i = te.path[0]; // TODO: or also follow path like for ON?
2429
- const state = rewriteItem( elem, i, i.id, elem, true );
2430
- if (state && state !== true && te.path.length === 1)
2431
- setLink( te, state );
2432
- }
2433
- fk.targetElement = te;
2434
- });
2435
- }
2436
-
2437
- // TODO: there is no need to rewrite the on condition of non-leading queries,
2438
- // i.e. we could just have on = {…}
2439
- // TODO: re-check $self rewrite (with managed composition of aspects),
2440
- // and actually also $self inside anonymous aspect definitions
2441
- // (not entirely urgent as we do not analyse it further, at least sole "$self")
2442
- function rewriteCondition( elem, assoc ) {
2443
- if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
2444
- // managed association as sub element not supported yet
2445
- error( null, [ elem.location, elem ], {},
2446
- // eslint-disable-next-line max-len
2447
- 'Rewriting the ON condition of unmanaged association in sub element is not supported' );
2448
- return;
2449
- }
2450
- const nav = (elem._main && elem._main.query) ? pathNavigation( elem.value )
2451
- : { navigation: assoc };
2452
- const cond = copyExpr( assoc.on,
2453
- // replace location in ON except if from mixin element
2454
- nav.tableAlias && elem.name.location );
2455
- cond.$inferred = 'copy';
2456
- elem.on = cond;
2457
- // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
2458
- // 'Info','ON').toString(), nav)
2459
- const { navigation } = nav;
2460
- if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
2461
- return; // should not happen: $projection, $magic, or ref to const
2462
- // console.log(message( null, elem.location, elem, {art:assoc}, 'Info','D').toString())
2463
- // Currently, having an unmanaged association inside a struct is not
2464
- // supported by this function:
2465
- if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
2466
- // For "assoc1.assoc2" and "structelem1.assoc2"
2467
- if (elem._redirected !== null) { // null = already reported
2468
- error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
2469
- 'The ON condition is not rewritten here - provide an explicit ON condition' );
2470
- }
2471
- return;
2472
- }
2473
- if (!nav.tableAlias || nav.tableAlias.path) {
2474
- resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
2475
- }
2476
- else {
2477
- // TODO: support that
2478
- error( null, [ elem.value.location, elem ],
2479
- 'Selecting unmanaged associations from a sub query is not supported' );
2480
- }
2481
- cond.$inferred = 'rewrite';
2482
- }
2483
-
2484
- function rewriteExpr( expr, assoc, tableAlias ) {
2485
- // Rewrite ON condition (resulting in outside perspective) for association
2486
- // 'assoc' in query or including entity from ON cond of mixin element /
2487
- // element in included structure / element in source ref/d by table alias.
2488
-
2489
- // TODO: re-check args in references, forbid parameter use for the moment
2490
- // TODO: complain about $self (unclear semantics)
2491
- // console.log( info(null, [assoc.name.location, assoc],
2492
- // { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
2493
-
2494
- if (!expr.path || !expr._artifact)
2495
- return;
2496
- if (!assoc._main)
2497
- return;
2498
- if (tableAlias) { // from ON cond of element in source ref/d by table alias
2499
- const source = tableAlias._origin;
2500
- const root = expr.path[0]._navigation || expr.path[0]._artifact;
2501
- // console.log( info(null, [assoc.name.location, assoc],
2502
- // { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
2503
- if (!root || root._main !== source)
2504
- return; // not $self or source element
2505
- const item = expr.path[root.kind === '$self' ? 1 : 0];
2506
- // console.log('YE', assoc.name, item, root.name, expr.path)
2507
- rewritePath( expr, item, assoc,
2508
- navProjection( item && tableAlias.elements[item.id], assoc ),
2509
- assoc.value.location );
2510
- }
2511
- else if (assoc._main.query) { // from ON cond of mixin element in query
2512
- const nav = pathNavigation( expr );
2513
- if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
2514
- rewritePath( expr, nav.item, assoc,
2515
- navProjection( nav.navigation, assoc ),
2516
- nav.item ? nav.item.location : expr.path[0].location );
2517
- }
2518
- }
2519
- else { // from ON cond of element in included structure
2520
- const root = expr.path[0]._navigation || expr.path[0]._artifact;
2521
- if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
2522
- return;
2523
- const item = expr.path[root.kind === '$self' ? 1 : 0];
2524
- if (!item)
2525
- return; // just $self
2526
- const elem = assoc._main.elements[item.id]; // corresponding elem in including structure
2527
- if (!(Array.isArray(elem) || // no msg for redefs
2528
- elem === item._artifact || // redirection for explicit def
2529
- elem._origin === item._artifact)) {
2530
- const art = assoc._origin;
2531
- warning( 'rewrite-shadowed', [ elem.name.location, elem ],
2532
- { art: art && effectiveType( art ) },
2533
- {
2534
- // eslint-disable-next-line max-len
2535
- std: 'This element is not originally referred to in the ON condition of association $(ART)',
2536
- // eslint-disable-next-line max-len
2537
- element: 'This element is not originally referred to in the ON condition of association $(MEMBER) of $(ART)',
2538
- } );
2539
- }
2540
- rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
2541
- }
2542
- }
2543
-
2544
- function rewritePath( ref, item, assoc, elem, location ) {
2545
- const { path } = ref;
2546
- let root = path[0];
2547
- if (!elem) {
2548
- if (location) {
2549
- error( 'rewrite-not-projected', [ location, assoc ],
2550
- { name: assoc.name.id, art: item._artifact }, {
2551
- // eslint-disable-next-line max-len
2552
- std: 'Projected association $(NAME) uses non-projected element $(ART)',
2553
- // eslint-disable-next-line max-len
2554
- element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
2555
- } );
2556
- }
2557
- delete root._navigation;
2558
- setProp( root, '_artifact', elem );
2559
- setProp( ref, '_artifact', elem );
2560
- return;
2561
- }
2562
- if (item !== root) {
2563
- root.id = '$self';
2564
- setLink( root, assoc._parent.$tableAliases.$self, '_navigation' );
2565
- setLink( root, assoc._parent );
2566
- }
2567
- else if (elem.name.id.charAt(0) === '$') {
2568
- root = { id: '$self', location: item.location };
2569
- path.unshift( root );
2570
- setLink( root, assoc._parent.$tableAliases.$self, '_navigation' );
2571
- setLink( root, assoc._parent );
2572
- }
2573
- else {
2574
- setLink( root, elem, '_navigation' );
2575
- }
2576
- if (!elem.name) // nothing to do for own $projection, $projection.elem
2577
- return; // (except having it renamed to $self)
2578
- item.id = elem.name.id;
2579
- let state = null;
2580
- for (const i of path) {
2581
- if (!state) {
2582
- if (i === item)
2583
- state = setLink( i, elem );
2584
- }
2585
- else if (i) {
2586
- state = rewriteItem( state, i, i.id, assoc );
2587
- if (!state || state === true)
2588
- break;
2589
- }
2590
- else {
2591
- return;
2592
- }
2593
- }
2594
- if (state !== true)
2595
- setLink( ref, state );
2596
- }
2597
-
2598
- function rewriteItem( elem, item, name, assoc, forKeys ) {
2599
- // TODO: for rewriting ON conditions of explicitly provided model targets,
2600
- // we need to only rewrite the current element, not all sibling elements
2601
- if (!elem._redirected)
2602
- return true;
2603
- for (const alias of elem._redirected) {
2604
- // TODO: a message for the same situation as msg 'rewrite-shadowed'?
2605
- if (alias.kind === '$tableAlias') { // _redirected also contains structures for includes
2606
- // TODO: if there is a "multi-step" redirection, we should probably
2607
- // consider intermediate "preferred" elements - not just `assoc`,
2608
- // but its origins, too.
2609
- const proj = navProjection( alias.elements[name], assoc );
2610
- name = proj && proj.name && proj.name.id;
2611
- if (!name) {
2612
- if (!forKeys)
2613
- break;
2614
- setLink( item, null );
2615
- error( 'rewrite-undefined-key', [ weakLocation( (elem.target || elem).location ), assoc ],
2616
- { id: item.id, art: alias._main },
2617
- 'Foreign key $(ID) has not been found in target $(ART)' );
2618
- return null;
2619
- }
2620
- item.id = name;
2621
- }
2622
- }
2623
- const env = name && environment(elem);
2624
- elem = setLink( item, env && env[name] );
2625
- if (elem && !Array.isArray(elem))
2626
- return elem;
2627
- // TODO: better (extra message), TODO: do it
2628
- error( 'query-undefined-element', [ item.location, assoc ], { id: name || item.id },
2629
- // eslint-disable-next-line max-len
2630
- 'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON condition' );
2631
- return (elem) ? false : null;
2632
- }
2633
-
2634
- //--------------------------------------------------------------------------
2635
- // General resolver functions
2636
- //--------------------------------------------------------------------------
2637
-
2638
- // Resolve the type and its arguments if applicable.
2639
- function resolveTypeExpr( art, user ) {
2640
- const typeArt = resolveType( art.type, user );
2641
- if (typeArt)
2642
- resolveTypeArguments( art, typeArt, user );
2643
- }
2644
-
2645
- function resolveExpr( expr, expected, user, extDict, expandOrInline) {
2646
- // TODO: when we have rewritten the resolvePath functions,
2647
- // define a traverseExpr() in ./utils.js
2648
- // TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
2649
- if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
2650
- return;
2651
- if (Array.isArray(expr)) {
2652
- expr.forEach( e => resolveExpr( e, expected, user, extDict ) );
2653
- return;
2654
- }
2655
-
2656
- if (expr.type) // e.g. cast( a as Integer )
2657
- resolveTypeExpr( expr, user );
2658
-
2659
- if (expr.path) {
2660
- if (expr.$expected === 'exists') {
2661
- error( 'expr-unexpected-exists', [ expr.location, user ], {},
2662
- 'An EXISTS predicate is not expected here' );
2663
- // We complain about the EXISTS before, as EXISTS subquery is also not
2664
- // supported (avoid that word if you do not want to get tickets when it
2665
- // will be supported), TODO: location of EXISTS
2666
- expr.$expected = 'approved-exists'; // only complain once
2667
- }
2668
- if (expected instanceof Function) {
2669
- expected( expr, user, extDict );
2670
- return;
2671
- }
2672
- resolvePath( expr, expected, user, extDict );
2673
-
2674
- const last = !expandOrInline && expr.path[expr.path.length - 1];
2675
- for (const step of expr.path) {
2676
- if (step && (step.args || step.where || step.cardinality) &&
2677
- step._artifact && !Array.isArray( step._artifact ) )
2678
- resolveParamsAndWhere( step, expected, user, extDict, step === last );
2679
- }
2680
- }
2681
- else if (expr.query) {
2682
- const { query } = expr;
2683
- if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
2684
- // traverseQueryPost( query, false, resolveQuery );
2685
- }
2686
- else {
2687
- error( 'expr-no-subquery', [ expr.location, user ], {},
2688
- 'Subqueries are not supported here' );
2689
- }
2690
- }
2691
- else if (expr.op && expr.args) {
2692
- const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
2693
- args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
2694
- }
2695
- if (expr.suffix && isDeprecatedEnabled( options )) {
2696
- const { location } = expr.suffix[0] || expr;
2697
- error( null, [ location, user ], { prop: 'deprecated' },
2698
- 'Window functions are not supported if $(PROP) options are set' );
2699
- }
2700
- if (expr.suffix)
2701
- expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
2702
- }
2703
-
2704
- function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
2705
- const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
2706
- const type = alias || effectiveType( step._artifact );
2707
- const art = (type && type.target) ? type.target._artifact : type;
2708
- if (!art)
2709
- return;
2710
- const entity = (art.kind === 'entity') &&
2711
- (!isLast || [ 'from', 'exists', 'approved-exists' ].includes( expected )) && art;
2712
- if (step.args)
2713
- resolveParams( step.args, art, entity, expected, user, extDict, step.location );
2714
- if (entity) {
2715
- if (step.where)
2716
- resolveExpr( step.where, 'filter', user, environment( type ) );
2717
- }
2718
- else if (step.where && step.where.location || step.cardinality ) {
2719
- const location = combinedLocation( step.where, step.cardinality );
2720
- // XSN TODO: filter$location including […]
2721
- message( 'expr-no-filter', [ location, user ], { '#': expected },
2722
- {
2723
- std: 'A filter can only be provided when navigating along associations',
2724
- from: 'A filter can only be provided for the source entity or associations',
2725
- } );
2726
- }
2727
- }
2728
-
2729
- function resolveParams( dict, art, entity, expected, user, extDict, stepLocation ) {
2730
- if (!entity || !entity.params) {
2731
- let first = dict[Object.keys(dict)[0]];
2732
- if (Array.isArray(first))
2733
- first = first[0];
2734
- message( 'args-no-params',
2735
- [ dictLocation( dict, first && first.name && first.name.location || stepLocation),
2736
- user ],
2737
- { art, '#': (entity ? 'entity' : expected ) },
2738
- {
2739
- std: 'Parameters can only be provided when navigating along associations',
2740
- from: 'Parameters can only be provided for the source entity or associations',
2741
- // or extra message id for entity?
2742
- entity: 'Entity $(ART) has no parameters',
2743
- } );
2744
- return;
2745
- }
2746
- const exp = (expected === 'from') ? 'expr' : expected;
2747
- if (Array.isArray(dict)) {
2748
- message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
2749
- 'Named parameters must be provided for the entity' );
2750
- for (const a of dict)
2751
- resolveExpr( a, exp, user, extDict );
2752
- return;
2753
- }
2754
- // TODO: allow to specify expected for arguments in in specExpected
2755
- for (const name in dict) {
2756
- const param = art.params[name];
2757
- const arg = dict[name];
2758
- for (const a of Array.isArray(arg) ? arg : [ arg ]) {
2759
- setProp( a.name, '_artifact', param );
2760
- if (!param) {
2761
- message( 'args-undefined-param', [ a.name.location, user ], { art, id: name },
2762
- 'Entity $(ART) has no parameter $(ID)' );
2763
- }
2764
- resolveExpr( a, exp, user, extDict );
2765
- }
2766
- }
2767
- }
2768
- }
2769
-
2770
- function copyExpr( expr, location, skipUnderscored, rewritePath ) {
2771
- if (!expr || typeof expr !== 'object')
2772
- return expr;
2773
- else if (Array.isArray(expr))
2774
- return expr.map( e => copyExpr( e, location, skipUnderscored, rewritePath ) );
2775
-
2776
- const proto = Object.getPrototypeOf( expr );
2777
- if (proto && proto !== Object.prototype) // do not copy object from special classes
2778
- return expr;
2779
- const r = Object.create( proto );
2780
- for (const prop of Object.getOwnPropertyNames( expr )) {
2781
- const pd = Object.getOwnPropertyDescriptor( expr, prop );
2782
- if (!pd.enumerable) { // should include all properties starting with _
2783
- if (!skipUnderscored ||
2784
- prop === '_artifact' || prop === '_navigation' || prop === '_effectiveType')
2785
- Object.defineProperty( r, prop, pd );
2786
- }
2787
- else if (!proto) {
2788
- r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
2789
- }
2790
- else if (prop === 'location') {
2791
- r[prop] = location || pd.value;
2792
- }
2793
- else if (prop.charAt(0) !== '$' || prop === '$inferred') {
2794
- r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
2795
- }
2796
- else if (!skipUnderscored) { // skip $ properties
2797
- Object.defineProperty( r, prop, pd );
2798
- }
2799
- }
2800
- return r;
2801
- }
2802
-
2803
- function testExpr( expr, pathTest, queryTest ) {
2804
- // TODO: also check path arguments/filters
2805
- if (!expr || typeof expr === 'string') { // parse error or keywords in {xpr:...}
2806
- return false;
2807
- }
2808
- else if (Array.isArray(expr)) {
2809
- return expr.some( e => testExpr( e, pathTest, queryTest ) );
2810
- }
2811
- else if (expr.path) {
2812
- return pathTest( expr );
2813
- }
2814
- else if (expr.query) {
2815
- return queryTest( expr.query );
2816
- }
2817
- else if (expr.op && expr.args) {
2818
- // unnamed args => array
2819
- if (Array.isArray(expr.args))
2820
- return expr.args.some( e => testExpr( e, pathTest, queryTest ) );
2821
- // named args => dictionary
2822
- for (const namedArg of Object.keys(expr.args)) {
2823
- if (testExpr(expr.args[namedArg], pathTest, queryTest))
2824
- return true;
2825
- }
2826
- }
2827
- return false;
2828
- }
2829
-
2830
- // Return true if the path `item` with a final type `assoc` has a max target
2831
- // cardinality greater than one - either specified on the path item or assoc type.
2832
- function targetMaxNotOne( assoc, item ) {
2833
- // Semantics of associations without provided cardinality: [*,0..1]
2834
- const cardinality = item.cardinality || assoc.cardinality;
2835
- return cardinality && cardinality.targetMax && cardinality.targetMax.val !== 1;
2836
- }
2837
-
2838
- // Return condensed info about reference in select item
2839
- // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
2840
- // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
2841
- // - mixinElem -> { navigation: mixinElement, item: path[0] }
2842
- // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
2843
- // - $self -> { item: undefined, tableAlias: $self }
2844
- // - $parameters.P, :P -> {}
2845
- // - $now, current_date -> {}
2846
- // - undef, redef -> {}
2847
- // With 'navigation': store that navigation._artifact is projected
2848
- // With 'navigation': rewrite its ON condition
2849
- // With navigation: Do KEY propagation
2850
- function pathNavigation( ref ) {
2851
- // currently, indirectly projectable elements are not included - we might
2852
- // keep it this way! If we want them to be included - be aware: cycles
2853
- if (!ref._artifact)
2854
- return {};
2855
- let item = ref.path && ref.path[0];
2856
- const root = item && item._navigation;
2857
- if (!root)
2858
- return {};
2859
- if (root.kind === '$navElement')
2860
- return { navigation: root, item, tableAlias: root._parent };
2861
- if (root.kind === 'mixin')
2862
- return { navigation: root, item };
2863
- item = ref.path[1];
2864
- if (root.kind === '$self')
2865
- return { item, tableAlias: root };
2866
- if (root.kind !== '$tableAlias' || ref.path.length < 2)
2867
- return {}; // should not happen
2868
- return { navigation: root.elements[item.id], item, tableAlias: root };
2869
- }
2870
-
2871
- function navProjection( navigation, preferred ) {
2872
- // TODO: Info if more than one possibility?
2873
- // console.log(navigation,navigation._projections)
2874
- if (!navigation)
2875
- return {};
2876
- else if (!navigation._projections)
2877
- return null;
2878
- return (preferred && navigation._projections.includes( preferred ))
2879
- ? preferred
2880
- : navigation._projections[0] || null;
2881
- }
2882
-
2883
- // Query tree post-order traversal - called for everything which makes a query
2884
- // except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
2885
- function traverseQueryPost( query, simpleOnly, callback ) {
2886
- if (!query) // parser error
2887
- return;
2888
- if (!query.op) { // in FROM (not JOIN)
2889
- if (query.query) // subquery
2890
- traverseQueryPost( query.query, simpleOnly, callback );
2891
- return;
2892
- }
2893
- if (simpleOnly) {
2894
- const { from } = query;
2895
- if (!from || from.join) // parse error or join
2896
- return; // ok are: path or simple sub query (!)
2897
- }
2898
- if (query.from) { // SELECT
2899
- traverseQueryPost( query.from, simpleOnly, callback );
2900
- // console.log('FC:')
2901
- callback( query );
2902
- // console.log('FE:')
2903
- }
2904
- else if (query.args) { // JOIN, UNION, INTERSECT
2905
- if (!query.join && simpleOnly == null) {
2906
- // enough for elements: traverse only first args for UNION/INTERSECT
2907
- // TODO: we might use this also when we do not rewrite associations
2908
- // in non-referred sub queries
2909
- traverseQueryPost( query.args[0], simpleOnly, callback );
2910
- }
2911
- else {
2912
- for (const q of query.args)
2913
- traverseQueryPost( q, simpleOnly, callback );
2914
- // The ON condition has to be traversed extra, because it must be evaluated
2915
- // after the complete FROM has been traversed. It is also not necessary to
2916
- // evaluate it in populateQuery().
2917
- }
2918
- }
2919
- // else: with parse error (`select from <EOF>`, `select distinct from;`)
2920
- }
2921
-
2922
- module.exports = resolve;