@sap/cds-compiler 2.11.4 → 2.13.8

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