@sap/cds-compiler 2.12.0 → 2.15.2

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 (128) hide show
  1. package/CHANGELOG.md +221 -15
  2. package/bin/cdsc.js +125 -50
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +47 -84
  8. package/lib/api/options.js +5 -6
  9. package/lib/api/validate.js +6 -11
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +114 -18
  16. package/lib/base/messages.js +101 -90
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +177 -123
  19. package/lib/checks/annotationsOData.js +12 -33
  20. package/lib/checks/arrayOfs.js +1 -34
  21. package/lib/checks/cdsPersistence.js +2 -1
  22. package/lib/checks/enricher.js +17 -1
  23. package/lib/checks/invalidTarget.js +3 -1
  24. package/lib/checks/managedWithoutKeys.js +3 -1
  25. package/lib/checks/selectItems.js +4 -4
  26. package/lib/checks/sql-snippets.js +27 -26
  27. package/lib/checks/types.js +1 -1
  28. package/lib/checks/validator.js +6 -11
  29. package/lib/compiler/assert-consistency.js +6 -3
  30. package/lib/compiler/base.js +1 -0
  31. package/lib/compiler/builtins.js +19 -6
  32. package/lib/compiler/checks.js +23 -60
  33. package/lib/compiler/cycle-detector.js +1 -1
  34. package/lib/compiler/define.js +1151 -0
  35. package/lib/compiler/extend.js +1000 -0
  36. package/lib/compiler/finalize-parse-cdl.js +237 -0
  37. package/lib/compiler/index.js +107 -39
  38. package/lib/compiler/kick-start.js +190 -0
  39. package/lib/compiler/moduleLayers.js +4 -4
  40. package/lib/compiler/populate.js +1227 -0
  41. package/lib/compiler/propagator.js +114 -46
  42. package/lib/compiler/resolve.js +1521 -0
  43. package/lib/compiler/shared.js +126 -65
  44. package/lib/compiler/tweak-assocs.js +535 -0
  45. package/lib/compiler/utils.js +197 -33
  46. package/lib/edm/.eslintrc.json +5 -0
  47. package/lib/edm/annotations/genericTranslation.js +38 -24
  48. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  49. package/lib/edm/csn2edm.js +219 -100
  50. package/lib/edm/edm.js +302 -230
  51. package/lib/edm/edmPreprocessor.js +554 -419
  52. package/lib/edm/edmUtils.js +138 -44
  53. package/lib/gen/Dictionary.json +100 -19
  54. package/lib/gen/language.checksum +1 -1
  55. package/lib/gen/language.interp +11 -1
  56. package/lib/gen/language.tokens +86 -83
  57. package/lib/gen/languageLexer.interp +10 -1
  58. package/lib/gen/languageLexer.js +860 -833
  59. package/lib/gen/languageLexer.tokens +78 -75
  60. package/lib/gen/languageParser.js +5765 -4480
  61. package/lib/json/csnVersion.js +10 -11
  62. package/lib/json/from-csn.js +15 -3
  63. package/lib/json/to-csn.js +126 -68
  64. package/lib/language/docCommentParser.js +4 -4
  65. package/lib/language/genericAntlrParser.js +123 -5
  66. package/lib/language/language.g4 +355 -156
  67. package/lib/language/multiLineStringParser.js +5 -5
  68. package/lib/main.d.ts +486 -59
  69. package/lib/main.js +41 -9
  70. package/lib/model/api.js +3 -1
  71. package/lib/model/csnRefs.js +252 -156
  72. package/lib/model/csnUtils.js +384 -297
  73. package/lib/model/enrichCsn.js +71 -29
  74. package/lib/model/revealInternalProperties.js +29 -8
  75. package/lib/model/sortViews.js +2 -1
  76. package/lib/modelCompare/compare.js +23 -18
  77. package/lib/optionProcessor.js +63 -26
  78. package/lib/render/manageConstraints.js +35 -32
  79. package/lib/render/toCdl.js +897 -947
  80. package/lib/render/toHdbcds.js +205 -257
  81. package/lib/render/toSql.js +264 -225
  82. package/lib/render/utils/common.js +136 -25
  83. package/lib/render/utils/sql.js +4 -3
  84. package/lib/render/utils/stringEscapes.js +111 -0
  85. package/lib/sql-identifier.js +1 -1
  86. package/lib/transform/.eslintrc.json +5 -0
  87. package/lib/transform/db/.eslintrc.json +3 -1
  88. package/lib/transform/db/applyTransformations.js +35 -12
  89. package/lib/transform/db/assertUnique.js +1 -1
  90. package/lib/transform/db/associations.js +104 -306
  91. package/lib/transform/db/cdsPersistence.js +2 -2
  92. package/lib/transform/db/constraints.js +58 -53
  93. package/lib/transform/db/expansion.js +60 -33
  94. package/lib/transform/db/flattening.js +582 -104
  95. package/lib/transform/db/groupByOrderBy.js +3 -1
  96. package/lib/transform/db/transformExists.js +66 -13
  97. package/lib/transform/db/views.js +11 -7
  98. package/lib/transform/draft/.eslintrc.json +38 -0
  99. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  100. package/lib/transform/draft/odata.js +227 -0
  101. package/lib/transform/forHanaNew.js +109 -208
  102. package/lib/transform/forOdataNew.js +59 -212
  103. package/lib/transform/localized.js +46 -26
  104. package/lib/transform/odata/toFinalBaseType.js +85 -11
  105. package/lib/transform/odata/typesExposure.js +147 -199
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +44 -33
  108. package/lib/transform/translateAssocsToJoins.js +3 -20
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +172 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +737 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/moduleResolve.js +13 -6
  114. package/lib/utils/objectUtils.js +30 -0
  115. package/package.json +1 -1
  116. package/share/messages/README.md +26 -0
  117. package/share/messages/message-explanations.json +2 -1
  118. package/share/messages/syntax-expected-integer.md +37 -0
  119. package/lib/compiler/definer.js +0 -2361
  120. package/lib/compiler/resolver.js +0 -3079
  121. package/lib/transform/odata/attachPath.js +0 -96
  122. package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
  123. package/lib/transform/odata/generateForeignKeyElements.js +0 -261
  124. package/lib/transform/odata/referenceFlattener.js +0 -290
  125. package/lib/transform/odata/sortByAssociationDependency.js +0 -105
  126. package/lib/transform/odata/structuralPath.js +0 -72
  127. package/lib/transform/odata/structureFlattener.js +0 -171
  128. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -0,0 +1,1227 @@
1
+ // Populate views with elements, elements with association targets, ...
2
+
3
+ // The functionality in this file is the heart of the Core Compiler and the
4
+ // most complex part. It essentially implements the function `environment`
5
+ // used when resolving element references: when starting a references at a
6
+ // certain definition or element, which names are allowed next?
7
+ //
8
+ // To calculate that info, the compiler might need the same info for other
9
+ // definitions. In other words: it calls itself recursively (using an iterative
10
+ // algorithm where appropriate). To be able to calculate that info on demand,
11
+ // the definitions need to have enough information, which must have been set in
12
+ // an earlier compiler phase. It is essential to do things in the right order.
13
+
14
+ // TODO: It might be that we need to call propagateKeyProps() and
15
+ // addImplicitForeignKeys() in populate.js, as we might need to know the
16
+ // foreign keys in populate.js (foreign key access w/o JOINs).
17
+
18
+ 'use strict';
19
+
20
+ const {
21
+ isDeprecatedEnabled,
22
+ isBetaEnabled,
23
+ forEachDefinition,
24
+ forEachMember,
25
+ forEachGeneric,
26
+ } = require('../base/model');
27
+ const {
28
+ dictAdd, dictAddArray,
29
+ } = require('../base/dictionaries');
30
+ const { dictLocation } = require('../base/location');
31
+ const { weakLocation } = require('../base/messages');
32
+
33
+ const { kindProperties } = require('./base');
34
+ const {
35
+ pushLink,
36
+ setLink,
37
+ setArtifactLink,
38
+ annotationVal,
39
+ augmentPath,
40
+ splitIntoPath,
41
+ linkToOrigin,
42
+ setMemberParent,
43
+ dependsOn,
44
+ traverseQueryPost,
45
+ setExpandStatus,
46
+ } = require('./utils');
47
+
48
+ const $inferred = Symbol.for('cds.$inferred');
49
+
50
+
51
+ // Export function of this file.
52
+ function populate( model ) {
53
+ const { options } = model;
54
+ // Get shared functionality and the message function:
55
+ const {
56
+ info, warning, error, message,
57
+ } = model.$messageFunctions;
58
+ const {
59
+ resolvePath,
60
+ attachAndEmitValidNames,
61
+ initArtifact,
62
+ projectionAncestor,
63
+ } = model.$functions;
64
+ model.$volatileFunctions.environment = environment;
65
+ Object.assign( model.$functions, {
66
+ effectiveType,
67
+ directType,
68
+ resolveType,
69
+ populateQuery,
70
+ } );
71
+
72
+
73
+ /** @type {any} may also be a boolean */
74
+ let newAutoExposed = [];
75
+
76
+ // behavior depending on option `deprecated`:
77
+ const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
78
+ // TODO: we should get rid of noElementsExpansion soon; both
79
+ // beta.nestedProjections and beta.universalCsn do not work with it.
80
+ const scopedRedirections
81
+ = enableExpandElements &&
82
+ !isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' ) &&
83
+ !isDeprecatedEnabled( options, 'shortAutoexposed' ) &&
84
+ !isDeprecatedEnabled( options, 'longAutoexposed' ) &&
85
+ !isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ) &&
86
+ !isDeprecatedEnabled( options, 'noScopedRedirections' );
87
+ const autoexposeViaComposition
88
+ = (isDeprecatedEnabled( options, 'noInheritedAutoexposeViaComposition' ))
89
+ ? 'Composition'
90
+ : true;
91
+ const redirectInSubQueries = isDeprecatedEnabled( options, 'redirectInSubQueries' );
92
+
93
+ forEachDefinition( model, traverseElementEnvironments );
94
+ while (newAutoExposed.length) {
95
+ // console.log( newAutoExposed.map( a => a.name.absolute ) )
96
+ const all = newAutoExposed;
97
+ newAutoExposed = [];
98
+ all.forEach( traverseElementEnvironments );
99
+ }
100
+ newAutoExposed = true; // internal error if auto-expose after here
101
+ return;
102
+
103
+
104
+ //--------------------------------------------------------------------------
105
+ // The central functions for path resolution - must work on-demand
106
+ //--------------------------------------------------------------------------
107
+ // Phase 2: call populateView(), which also works on-demand
108
+
109
+ function traverseElementEnvironments( art ) {
110
+ populateView( art );
111
+ environment( art );
112
+ forEachMember( art, traverseElementEnvironments );
113
+ }
114
+
115
+ // Return effective search environment provided by artifact `art`, i.e. the
116
+ // `artifacts` or `elements` dictionary. For the latter, follow the `type`
117
+ // chain and resolve the association `target`. View elements are calculated
118
+ // on demand.
119
+ function environment( art, location, user, assocSpec ) {
120
+ if (!art)
121
+ return Object.create(null);
122
+ const env = navigationEnv( art, location, user, assocSpec );
123
+ return env && env.elements || Object.create(null);
124
+ }
125
+
126
+ function navigationEnv( art, location, user, assocSpec ) {
127
+ let type = effectiveType(art) || art;
128
+ // console.log( info(null, [ art.location, art ], { art, type }, 'ENV')
129
+ // .toString(), art.elements && Object.keys(art.elements))
130
+ if (type.target) {
131
+ type = resolvePath( type.target, 'target', type );
132
+ if (!type) {
133
+ if (type === 0 && location)
134
+ dependsOn( art, art, (art.target || art.type).location );
135
+ return type;
136
+ }
137
+ // TODO: combine this with setTargetReferenceKey&Co in getPathItem?
138
+ else if (assocSpec === false) { // TODO: else warning for assoc usage
139
+ error( null, [ location, user ], {},
140
+ 'Following an association is not allowed in an association key definition' );
141
+ }
142
+ else if (assocSpec && user) {
143
+ dependsOn( user, type, location );
144
+ }
145
+ }
146
+ populateView( type );
147
+ return type;
148
+ }
149
+
150
+ // Follow the `type` chain, i.e. derived types and TYPE OF, stop just before
151
+ // built-in types (otherwise, we would loose type parameters). Return that
152
+ // type and set it as property `_effectiveType` on all artifacts on the chain.
153
+ // TODO: clarify for (query) elements without type: self, not undefined - also for entities!
154
+ // TODO: directly "propagate" (with implicit redirection the targets), also
155
+ // "proxy-copy" elements
156
+
157
+ // In v2, the name-resolution relevant properties (elements,items,target) are
158
+ // proxy-copied, i.e. the effectiveType of an artifact is always the artifact
159
+ // itself. Except for the situation with recursive element expansions; then
160
+ // we have element: 0, and use original final type.
161
+ function effectiveType( art ) {
162
+ if ('_effectiveType' in art)
163
+ return art._effectiveType;
164
+
165
+ // console.log(message( null, art.location, art, {}, 'Info','FT').toString())
166
+ const chain = [];
167
+ while (art && !('_effectiveType' in art) &&
168
+ (art.type || art._origin || art.value && art.value.path) &&
169
+ // TODO: really stop at art.enum? See #8942
170
+ !art.target && !art.enum && !art.elements && !art.items) {
171
+ chain.push( art );
172
+ setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
173
+ art = directType( art );
174
+ }
175
+ if (art) {
176
+ if ('_effectiveType' in art) { // is the case for builtins
177
+ art = art._effectiveType;
178
+ }
179
+ else {
180
+ setLink( art, '_effectiveType', art );
181
+ if (art.expand && !art.value && !art.elements)
182
+ initFromColumns( art, art.expand );
183
+ // When not blocked (by future origin = false) and not REDIRECTED TO and not MIXIN
184
+ // try to implicitly redirect explicitly provided target:
185
+ else if (art.target && art._origin == null && !art.value && art.kind !== 'mixin')
186
+ redirectImplicitly( art, art );
187
+ }
188
+ }
189
+ chain.reverse();
190
+ if (!art) {
191
+ for (const a of chain)
192
+ setLink( a, '_effectiveType', art );
193
+ }
194
+ else {
195
+ let eType = art;
196
+ if (eType._outer)
197
+ eType = effectiveType( eType._outer );
198
+ // collect the "latest" cardinality (calculate lazyly if necessary)
199
+ let cardinality = art.cardinality ||
200
+ art._effectiveType && (() => getCardinality( art._effectiveType ));
201
+ let prev = art;
202
+ for (const a of chain) {
203
+ if (a.cardinality)
204
+ cardinality = a.cardinality;
205
+ if (a.expand && expandFromColumns( a, art, cardinality ) ||
206
+ art.target && redirectImplicitly( a, art ) ||
207
+ art.elements && expandElements( a, art, eType ) ||
208
+ art.items && expandItems( a, art, eType ))
209
+ art = a;
210
+ else if (art.enum && expandEnum( a, prev ))
211
+ prev = a; // do not set art - effective type is base
212
+ setLink( a, '_effectiveType', art );
213
+ }
214
+ }
215
+ return art;
216
+ }
217
+
218
+ // TODO: test it in combination with top-level CAST function
219
+ function directType( art ) {
220
+ // Be careful when using it with art.target or art.enum or art.elements
221
+ if (art._origin || art.builtin)
222
+ return art._origin;
223
+ if (art.type)
224
+ return resolveType( art.type, art );
225
+ // console.log( 'EXPR-IN', art.kind, refString(art.name) )
226
+ if (!art._main || !art.value || !art.value.path)
227
+ return undefined;
228
+ if (art._pathHead && art.value) {
229
+ setLink( art, '_origin', resolvePath( art.value, 'expr', art, null ) );
230
+ return art._origin;
231
+ }
232
+ const query = userQuery( art ) || art._parent;
233
+ if (query.kind !== 'select')
234
+ return undefined;
235
+ // Reached an element in a query which is a simple ref -> return referred artifact
236
+ // TODO: remember that we still have to resolve path arguments and filters
237
+ return setLink( art, '_origin',
238
+ resolvePath( art.value, 'expr', art, query._combined ) );
239
+ // console.log( 'EXPR-OUT', art.value._artifact.kind, refString(art.val ue._artifact.name) );
240
+ }
241
+
242
+ function resolveType( ref, user ) {
243
+ if ('_artifact' in ref)
244
+ return ref._artifact;
245
+ if (ref.scope === 'typeOf') {
246
+ let struct = user;
247
+ while (struct.kind === 'element')
248
+ struct = struct._parent;
249
+ if (struct.kind === 'select') {
250
+ message( 'type-unexpected-typeof', [ ref.location, user ],
251
+ { keyword: 'type of', '#': struct.kind } );
252
+ // we actually refer to an element in _combined; TODO: return null if
253
+ // not configurable; would produce illegal CSN with sub queries in FROM
254
+ }
255
+ else if (struct !== user._main) {
256
+ message( 'type-unexpected-typeof', [ ref.location, user ],
257
+ { keyword: 'type of', '#': struct.kind } );
258
+ return setArtifactLink( ref, null );
259
+ }
260
+ return resolvePath( ref, 'typeOf', user );
261
+ }
262
+ while (user._outer) // in items
263
+ user = user._outer;
264
+ if (user.kind === 'event')
265
+ return resolvePath( ref, 'eventType', user );
266
+ if (user.kind === 'param' && user._parent &&
267
+ (user._parent.kind === 'action' || user._parent.kind === 'function'))
268
+ return resolvePath( ref, 'actionParamType', user );
269
+ return resolvePath( ref, 'type', user );
270
+ }
271
+
272
+ function getCardinality( type ) {
273
+ // only to be called without cycles
274
+ while (type) {
275
+ if (type.cardinality)
276
+ return type.cardinality;
277
+ type = directType( type );
278
+ }
279
+ return {};
280
+ }
281
+
282
+ function userQuery( user ) {
283
+ // TODO: we need _query links set by the definer
284
+ while (user._main) {
285
+ if (user.kind === 'select' || user.kind === '$join')
286
+ return user;
287
+ user = user._parent;
288
+ }
289
+ return null;
290
+ }
291
+
292
+ // Expansiosn --------------------------------------------------------------
293
+
294
+
295
+ function expandItems( art, origin, eType ) {
296
+ if (!enableExpandElements || art.items)
297
+ return false;
298
+ if (isInParents( art, eType )) {
299
+ art.items = 0; // circular
300
+ return true;
301
+ }
302
+ const ref = art.type || art.value || art.name;
303
+ const location = ref && ref.location || art.location;
304
+ art.items = { $inferred: 'expand-element', location };
305
+ setLink( art.items, '_outer', art );
306
+ setLink( art.items, '_origin', origin.items );
307
+ if (!art.$expand)
308
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
309
+ return true;
310
+ }
311
+
312
+ function expandElements( art, struct, eType ) {
313
+ if (!enableExpandElements)
314
+ return false;
315
+ if (art.elements || art.kind === '$tableAlias' ||
316
+ // no element expansions for "non-proper" types like
317
+ // entities (as parameter types) etc:
318
+ struct.kind !== 'type' && struct.kind !== 'element' && struct.kind !== 'param' &&
319
+ !struct._outer)
320
+ return false;
321
+ if (struct.elements === 0 || isInParents( art, eType )) {
322
+ art.elements = 0; // circular
323
+ return true;
324
+ }
325
+ const ref = art.type || art.value || art.name;
326
+ const location = ref && ref.location || art.location;
327
+ // console.log( message( null, location, art, {target:struct,art}, 'Info','EXPAND-ELEM')
328
+ // .toString(), Object.keys(struct.elements))
329
+ for (const name in struct.elements) {
330
+ const orig = struct.elements[name];
331
+ // const orig = elem.kind === '$navElement'
332
+ if (Array.isArray( orig )) // redefinitions
333
+ continue;
334
+ linkToOrigin( orig, name, art, 'elements', weakLocation( location ), true )
335
+ // or should we use orig.location? - TODO: try to find test to see message
336
+ .$inferred = 'expand-element';
337
+ }
338
+ // Set elements expansion status (the if condition is always true, as no
339
+ // elements expansion will take place on artifact with existing other
340
+ // member property):
341
+ if (!art.$expand)
342
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
343
+ // TODO: have some art.elements[SYM.$inferred] = 'expand-element';
344
+ return true;
345
+ }
346
+
347
+ function expandEnum( art, origin ) {
348
+ if (!enableExpandElements || art.enum)
349
+ return false;
350
+ const ref = art.type || art.value || art.name;
351
+ const location = weakLocation( ref && ref.location || art.location );
352
+ art.enum = Object.create(null);
353
+ for (const name in origin.enum) {
354
+ const orig = origin.enum[name];
355
+ linkToOrigin( orig, name, art, 'enum', location, true )
356
+ // or should we use orig.location? - TODO: try to find test to see message
357
+ .$inferred = 'expand-element';
358
+ }
359
+ // Set elements expansion status (the if condition is always true, as no
360
+ // elements expansion will take place on artifact with existing other
361
+ // member property):
362
+ if (!art.$expand)
363
+ art.$expand = 'origin'; // if value stays, elements won't appear in CSN
364
+ art.enum[$inferred] = 'expand-element';
365
+ return true;
366
+ }
367
+
368
+ /**
369
+ * Return true iff `struct` is `art` or a direct or indirect parent of `art`,
370
+ * we also check for the outer objects of `items`. (There is no need to
371
+ * check the parents of main artifacts, as these are contexts, services or
372
+ * namespaces, and do not serve as type.)
373
+ */
374
+ function isInParents( art, struct ) {
375
+ if (art === struct)
376
+ return true;
377
+ while (art._outer) { // for items
378
+ art = art._outer;
379
+ if (art === struct)
380
+ return true;
381
+ }
382
+ while (art._main) {
383
+ art = art._parent;
384
+ if (art === struct)
385
+ return true;
386
+ }
387
+ return false;
388
+ }
389
+
390
+ //--------------------------------------------------------------------------
391
+ // Views
392
+ //--------------------------------------------------------------------------
393
+
394
+ // Make a view to have elements (remember: wildcard), and prepare that their
395
+ // final type can be resolved, i.e. we know how to resolve select item refs.
396
+ // We do so by first populate views in the FROM clause, then the view query.
397
+ function populateView( art ) {
398
+ if (!art._from || art._status === '_query')
399
+ return;
400
+ const resolveChain = [];
401
+ const fromChain = [ art ];
402
+ while (fromChain.length) {
403
+ const view = fromChain.pop();
404
+ if (view._status === '_query') // already fully resolved (status at def)
405
+ continue;
406
+ resolveChain.push( view );
407
+ for (const from of view._from) {
408
+ if (from._status) // status at the ref -> illegal recursion -> stop
409
+ continue;
410
+ setLink( from, '_status', '_query' );
411
+ // setLink before resolvePath - Cycle: view V as select from V.toV
412
+ let source = resolvePath( from, 'from', view ); // filter and args in resolveQuery
413
+ // console.log('ST:',msgName(source),from._status)
414
+ if (source && source._main) { // element -> should be assoc
415
+ const type = effectiveType( source );
416
+ source = type && type.target;
417
+ }
418
+ if (source && source._from && source._status !== '_query')
419
+ fromChain.push( source );
420
+ }
421
+ }
422
+ // console.log( resolveChain.map( v => msgName(v)+v._status ) );
423
+ for (const view of resolveChain.reverse()) {
424
+ if (view._status !== '_query' ) { // not already resolved
425
+ setLink( view, '_status', '_query' );
426
+ // must be run in order “sub query in FROM first”:
427
+ traverseQueryPost( view.query, null, populateQuery );
428
+ if (view.elements$) // specified elements
429
+ mergeSpecifiedElements( view );
430
+ if (!view.$entity) {
431
+ model._entities.push( view );
432
+ view.$entity = ++model.$entity;
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ function mergeSpecifiedElements( view ) {
439
+ // Later we use specified elements as proxies to inferred of leading query
440
+ // (No, we probably do not.)
441
+ for (const id in view.elements) {
442
+ const ielem = view.elements[id]; // inferred element
443
+ const selem = view.elements$[id]; // specified element
444
+ if (!selem) {
445
+ info( 'query-missing-element', [ ielem.name.location, view ], { id },
446
+ 'Element $(ID) is missing in specified elements' );
447
+ }
448
+ else {
449
+ for (const prop in selem) {
450
+ // just annotation assignments and doc comments for the moment
451
+ if (prop.charAt(0) === '@' || prop === 'doc')
452
+ ielem[prop] = selem[prop];
453
+ }
454
+ selem.$replacement = true;
455
+ }
456
+ }
457
+ for (const id in view.elements$) {
458
+ const selem = view.elements$[id]; // specified element
459
+ if (!selem.$replacement) {
460
+ error( 'query-unspecified-element', [ selem.name.location, selem ], { id },
461
+ 'Element $(ID) does not result from the query' );
462
+ }
463
+ }
464
+ }
465
+
466
+ function populateQuery( query ) {
467
+ if (query._combined || !query.from || !query.$tableAliases)
468
+ // already done or $join query or parse error
469
+ return;
470
+ setLink( query, '_combined', Object.create(null) );
471
+ query.$inlines = [];
472
+ forEachGeneric( query, '$tableAliases', resolveTabRef );
473
+
474
+ initFromColumns( query, query.columns );
475
+ // TODO: already in definer: complain about EXCLUDING with no wildcard
476
+ // (would have been automatically with a good CDL syntax: `* without (...)`)
477
+ if (query.excludingDict) {
478
+ for (const name in query.excludingDict)
479
+ resolveExcluding( name, query._combined, query.excludingDict, query );
480
+ }
481
+ return;
482
+
483
+ function resolveTabRef( alias ) {
484
+ if (alias.kind === 'mixin' || alias.kind === '$self')
485
+ return;
486
+ if (!alias.elements) { // could be false in hierarchical JOIN
487
+ // if (main._block.$frontend!=='json') console.log('TABREF:',alias.name,main,main._block)
488
+ // const tab = alias.path && resolvePath( alias, 'from', query );
489
+ // // if (tab) setLink( alias, '_effectiveType', alias );
490
+ // const elements = alias.query ? alias.query.elements : environment( tab );
491
+ if (!('_origin' in alias) && alias.path) {
492
+ const tab = resolvePath( alias, 'from', query );
493
+ setLink( alias, '_origin', tab && navigationEnv( tab ) );
494
+ }
495
+ alias.elements = Object.create(null); // Set explicitly, as...
496
+ // ...with circular dep to source, no elements can be found.
497
+ const location = alias.path && alias.path.location;
498
+ const qtab = alias._origin;
499
+ if (!qtab || !qtab.elements)
500
+ return;
501
+ forEachGeneric( qtab, 'elements', ( origin, name ) => {
502
+ const elem = linkToOrigin( origin, name, alias, 'elements',
503
+ location || origin.name.location );
504
+ elem.kind = '$navElement';
505
+ // elem.name.select = query.name.select;
506
+ if (origin.masked)
507
+ elem.masked = Object.assign( { $inferred: 'nav' }, origin.masked );
508
+ });
509
+ }
510
+ forEachGeneric( { elements: alias.elements }, 'elements', ( elem, name ) => {
511
+ if (elem.$duplicates !== true)
512
+ dictAddArray( query._combined, name, elem, null ); // not dictAdd()
513
+ });
514
+ }
515
+ }
516
+
517
+ function resolveExcluding( name, env, excludingDict, user ) {
518
+ if (env[name])
519
+ return;
520
+ /** @type {object} */
521
+ // console.log(name,Object.keys(env),Object.keys(excludingDict))
522
+ const compileMessageRef = info(
523
+ 'ref-undefined-excluding', [ excludingDict[name].location, user ], { name },
524
+ 'Element $(NAME) has not been found'
525
+ );
526
+ attachAndEmitValidNames( compileMessageRef, env );
527
+ }
528
+
529
+ // query columns -----------------------------------------------------------
530
+
531
+ function expandFromColumns( elem, assoc, cardinality ) {
532
+ const path = elem.value && elem.value.path;
533
+ if (!path || path.broken)
534
+ return null;
535
+ if (!assoc.target)
536
+ return initFromColumns( elem, elem.expand );
537
+ const { targetMax } = path[path.length - 1].cardinality ||
538
+ (cardinality instanceof Function ? cardinality() : cardinality);
539
+ if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
540
+ elem.items = { location: dictLocation( elem.expand ) }; // TODO: array location
541
+ return initFromColumns( elem, elem.expand );
542
+ }
543
+
544
+ // TODO: make this function shorter - make part of this (e.g. setting
545
+ // parent/name) also be part of definer.js
546
+ // TODO: query is actually the elemParent, where the new elements are added to
547
+ // top-level: just query, columns
548
+ // inline: + elements (TODO: remove), colParent
549
+ // expand: just query (which is a column/element), columns=array of expand
550
+ function initFromColumns( query, columns, inlineHead = undefined ) {
551
+ const elemsParent = query.items || query;
552
+ if (!inlineHead) {
553
+ elemsParent.elements = Object.create(null);
554
+ if (query._main._leadingQuery === query) // never the case for 'expand'
555
+ query._main.elements = elemsParent.elements;
556
+ }
557
+
558
+ for (const col of columns || [ { val: '*' } ]) {
559
+ if (col.val === '*') {
560
+ const siblings = wildcardSiblings( columns, query );
561
+ expandWildcard( col, siblings, inlineHead, query );
562
+ }
563
+ if ((col.expand || col.inline) && !isBetaEnabled( options, 'nestedProjections' )) {
564
+ error( null, [ col.location, query ], { prop: (col.expand ? 'expand' : 'inline') },
565
+ 'Unsupported nested $(PROP)' );
566
+ }
567
+ // If neither expression (value), expand nor new association.
568
+ if (!col.value && !col.expand && !(col.target && col.type))
569
+ continue; // error should have been reported by parser
570
+ if (col.inline) {
571
+ col.kind = '$inline';
572
+ col.name = {};
573
+ // a name for this internal symtab entry (e.g. '.2' to avoid clashes
574
+ // with real elements) is only relevant for for `cdsc -R`/debugging
575
+ const q = userQuery( query );
576
+ q.$inlines.push( col );
577
+ // or use userQuery( query ) in the following, too?
578
+ setMemberParent( col, `.${ q.$inlines.length }`, query );
579
+ initFromColumns( query, col.inline, col );
580
+ }
581
+ else if (!col.$replacement) {
582
+ const id = ensureColumnName( col, query );
583
+ col.kind = 'element';
584
+ dictAdd( elemsParent.elements, id, col, ( name, location ) => {
585
+ error( 'duplicate-definition', [ location, query ], { name, '#': 'element' } );
586
+ });
587
+ setMemberParent( col, id, query );
588
+ }
589
+ }
590
+ forEachGeneric( query, 'elements', e => initElem( e, query ) );
591
+ return true;
592
+ }
593
+
594
+ // TODO: probably do this already in definer.js
595
+ function ensureColumnName( col, query ) {
596
+ if (col.name)
597
+ return col.name.id;
598
+ if (col.inline || col.val === '*')
599
+ return '';
600
+ const path = col.value &&
601
+ (col.value.path || !col.value.args && col.value.func && col.value.func.path);
602
+ if (path) {
603
+ const last = !path.broken && path.length && path[path.length - 1];
604
+ if (last) {
605
+ col.name = { id: last.id, location: last.location, $inferred: 'as' };
606
+ return col.name.id;
607
+ }
608
+ }
609
+ else if (col.value || col.expand) {
610
+ error( 'query-req-name', [ col.value && col.value.location || col.location, query ], {},
611
+ 'Alias name is required for this select item' );
612
+ }
613
+ // invent a name for code completion in expression
614
+ col.name = {
615
+ id: '',
616
+ location: col.value && col.value.location || col.location,
617
+ $inferred: 'none',
618
+ };
619
+ return '';
620
+ }
621
+
622
+ function initElem( elem, query ) {
623
+ if (elem.type && !elem.type.$inferred)
624
+ return; // explicit type -> enough or directType()
625
+ if (elem.$inferred) {
626
+ // redirectImplicitly( elem, elem._origin );
627
+ return;
628
+ }
629
+ if (!elem.value || !elem.value.path) // TODO: test $inferred
630
+ return; // no value ref or $inferred
631
+ // TODO: what about SELECT from E { $projection.a as a1, a } !!!!!!
632
+
633
+ const env = columnEnv( elem._pathHead, query );
634
+ const origin = setLink( elem, '_origin',
635
+ resolvePath( elem.value, 'expr', elem, env ) );
636
+ // console.log( message( null, elem.location, elem, {art:query}, 'Info','RED').toString(),
637
+ // elem.value)
638
+ // TODO: make this resolvePath() also part of directType() ?!
639
+ if (!origin)
640
+ return;
641
+ if (elem.foreignKeys) { // REDIRECTED with explicit foreign keys
642
+ forEachGeneric( elem, 'foreignKeys', (key, name) => initKey( key, name, elem ) );
643
+ }
644
+
645
+ // now set things which are necessary for later sub phases:
646
+ const nav = pathNavigation( elem.value );
647
+ if (nav.navigation && nav.item === elem.value.path[elem.value.path.length - 1]) {
648
+ // redirectImplicitly( elem, origin );
649
+ pushLink( nav.navigation, '_projections', elem );
650
+ }
651
+ }
652
+
653
+ function initKey( key, name, elem ) {
654
+ setLink( key, '_block', elem._block );
655
+ setMemberParent( key, name, elem ); // TODO: set _block here if not present?
656
+ }
657
+
658
+ // col ($replacement set before *)
659
+ // false if two cols have same name
660
+ function wildcardSiblings( columns, query ) {
661
+ const siblings = Object.create(null);
662
+ if (!columns)
663
+ return siblings;
664
+
665
+ let seenWildcard = null;
666
+ for (const col of columns) {
667
+ const id = ensureColumnName( col, query );
668
+ if (id) {
669
+ col.$replacement = !seenWildcard;
670
+ siblings[id] = !(id in siblings) && col;
671
+ }
672
+ else if (col.val === '*') {
673
+ seenWildcard = true;
674
+ }
675
+ }
676
+ return siblings;
677
+ }
678
+
679
+ // TODO: disallow $self.elem.* and $self.*, toSelf.* (circular dependency)
680
+ function expandWildcard( wildcard, siblingElements, colParent, query ) {
681
+ const { elements } = query.items || query;
682
+ let location = wildcard.location || query.from && query.from.location || query.location;
683
+ const inferred = query._main.$inferred;
684
+ const excludingDict = (colParent || query).excludingDict || Object.create(null);
685
+
686
+ const envParent = wildcard._pathHead; // TODO: rename _pathHead to _pathEnv
687
+ // console.log('S1:',location.line,location.col,
688
+ // envParent&&!!envParent._origin&&envParent._origin.name)
689
+ const env = columnEnv( envParent, query );
690
+ // console.log('S2:',location.line,location.col,
691
+ // envParent&&!!envParent._origin&&envParent._origin.name,
692
+ // Object.keys(env),Object.keys(elements))
693
+ for (const name in env) {
694
+ const navElem = env[name];
695
+ // TODO: if it is an array, filter out those with masked
696
+ if (excludingDict[name] || navElem.masked && navElem.masked.val)
697
+ continue;
698
+ const sibling = siblingElements[name];
699
+ if (sibling) { // is explicitly provided (without duplicate)
700
+ if (!inferred && !envParent) // not yet for expand/inline
701
+ reportReplacement( sibling, navElem, query );
702
+ if (!sibling.$replacement) {
703
+ sibling.$replacement = true;
704
+ sibling.kind = 'element';
705
+ dictAdd( elements, name, sibling, ( _name, loc ) => {
706
+ // there can be a definition from a previous inline with the same name:
707
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
708
+ });
709
+ setMemberParent( sibling, name, query );
710
+ }
711
+ // else {
712
+ // sibling.$inferred = 'query';
713
+ // }
714
+ }
715
+ else if (Array.isArray(navElem)) {
716
+ const names = navElem.filter( e => !e.$duplicates)
717
+ .map( e => `${ e.name.alias }.${ e.name.element }` );
718
+ if (names.length) {
719
+ error( 'wildcard-ambiguous', [ location, query ], { id: name, names },
720
+ 'Ambiguous wildcard, select $(ID) explicitly with $(NAMES)' );
721
+ }
722
+ }
723
+ else {
724
+ location = weakLocation( location );
725
+ const origin = envParent ? navElem : navElem._origin;
726
+ const elem = linkToOrigin( origin, name, query, null, location );
727
+ // TODO: check assocToMany { * }
728
+ dictAdd( elements, name, elem, ( _name, loc ) => {
729
+ // there can be a definition from a previous inline with the same name:
730
+ error( 'duplicate-definition', [ loc, query ], { name, '#': 'element' } );
731
+ });
732
+ elem.$inferred = '*';
733
+ elem.name.$inferred = '*';
734
+ if (envParent)
735
+ setWildcardExpandInline( elem, envParent, origin, name, location );
736
+ else
737
+ setElementOrigin( elem, navElem, name, location );
738
+ }
739
+ }
740
+ if (envParent || query.kind !== 'select') {
741
+ // already done in populateQuery (TODO: change that and check whether
742
+ // `*` is allowed at all in definer)
743
+ const user = colParent || query;
744
+ for (const name in user.excludingDict)
745
+ resolveExcluding( name, env, excludingDict, query );
746
+ }
747
+ }
748
+
749
+ function columnEnv( envParent, query ) { // etc. wildcard._pathHead;
750
+ return (envParent)
751
+ ? environment( directType( envParent ) || envParent )
752
+ : userQuery( query )._combined;
753
+ }
754
+
755
+ function reportReplacement( sibling, navElem, query ) {
756
+ // TODO: bring this much less often = only if shadowed elem does not appear
757
+ // in expr and if not projected as other name.
758
+ // Probably needs to be reported at a later phase
759
+ const path = sibling.value && sibling.value.path;
760
+ if (!sibling.target || sibling.target.$inferred || // not explicit REDIRECTED TO
761
+ path && path[path.length - 1].id !== sibling.name.id) { // or renamed
762
+ const { id } = sibling.name;
763
+ if (Array.isArray(navElem)) {
764
+ info( 'wildcard-excluding-many', [ sibling.name.location, query ], { id },
765
+ 'This select item replaces $(ID) from two or more sources' );
766
+ }
767
+ else {
768
+ info( 'wildcard-excluding-one', [ sibling.name.location, query ],
769
+ { id, alias: navElem._parent.name.id },
770
+ 'This select item replaces $(ID) from table alias $(ALIAS)' );
771
+ }
772
+ }
773
+ }
774
+
775
+ function setWildcardExpandInline( queryElem, pathHead, origin, name, location ) {
776
+ setLink( queryElem, '_pathHead', pathHead );
777
+ const path = [ { id: name, location } ];
778
+ queryElem.value = { path, location }; // TODO: can we omit that? We have _origin
779
+ setArtifactLink( path[0], origin );
780
+ setLink( queryElem, '_origin', origin );
781
+ // TODO: set _projections when top-level?
782
+ }
783
+
784
+ function setElementOrigin( queryElem, navElem, name, location ) {
785
+ const sourceElem = navElem._origin;
786
+ const alias = navElem._parent;
787
+ // always expand * to path with table alias (reason: columns current_date etc)
788
+ const path = [ { id: alias.name.id, location }, { id: name, location } ];
789
+ queryElem.value = { path, location };
790
+ setLink( path[0], '_navigation', alias );
791
+ setArtifactLink( path[0], alias._origin );
792
+ setArtifactLink( path[1], sourceElem );
793
+ // TODO: or should we set the _artifact/_effectiveType directly to the target?
794
+ setArtifactLink( queryElem.value, sourceElem );
795
+ pushLink( navElem, '_projections', queryElem );
796
+ // TODO: _effectiveType?
797
+ }
798
+
799
+ //--------------------------------------------------------------------------
800
+ // Auto-Redirections
801
+ //--------------------------------------------------------------------------
802
+
803
+ // Conditions for redirecting target of assoc in elem
804
+ // - we (the elem) are in a service
805
+ // - target provided in assoc is not defined in current service
806
+ // - elem is to be auto-redirected (included elem, elem from main query, ...)
807
+ // - assoc is not defined in current service (or was not to be auto-redirected)
808
+ function redirectImplicitly( elem, assoc ) {
809
+ // PRE: elem has no target, assoc has target prop
810
+ if (elem.kind === '$tableAlias')
811
+ return false;
812
+ const assocTarget = resolvePath( assoc.target, 'target', assoc );
813
+ let target = assocTarget;
814
+ // console.log( info( null, [ elem.location, elem ], {target,art:assoc,name:''+assoc.target},
815
+ // 'RED').toString())
816
+ if (!target)
817
+ return false; // error in target ref
818
+ const { location } = elem.value || elem.type || elem.name;
819
+ const service = (elem._main || elem)._service;
820
+ if (service && service !== target._service && assocIsToBeRedirected( elem )) {
821
+ if (service !== (assoc._main || assoc)._service ||
822
+ !assocIsToBeRedirected( assoc ) ||
823
+ elem === assoc)
824
+ target = redirectImplicitlyDo( elem, assoc, target, service );
825
+ }
826
+ if (elem === assoc) { // redirection of user-provided target
827
+ if (assocTarget === target) // no change (due to no implicit redirection)
828
+ return true;
829
+ const type = resolvePath( elem.type, 'type', elem ); // cds.Association or cds.Composition
830
+ const origin = {
831
+ kind: elem.kind, // necessary for rewrite, '$user-provided' would be best
832
+ name: elem.name,
833
+ type: { // TODO: necessary?
834
+ path: [ { id: type.name.absolute, location: elem.type.location } ],
835
+ scope: 'global',
836
+ location: elem.type.location,
837
+ $inferred: 'REDIRECTED',
838
+ },
839
+ target: elem.target,
840
+ $inferred: 'REDIRECTED',
841
+ location: elem.target.location,
842
+ };
843
+ setLink( elem, '_origin', origin );
844
+ setArtifactLink( elem.type, type );
845
+ setLink( origin, '_outer', elem );
846
+ setLink( origin, '_parent', elem._parent );
847
+ if (elem._main) // remark: the param `elem` can also be a type
848
+ setLink( origin, '_main', elem._main );
849
+ setLink( origin, '_effectiveType', origin );
850
+ setLink( origin, '_block', elem._block );
851
+ if (elem.foreignKeys) {
852
+ origin.foreignKeys = elem.foreignKeys;
853
+ delete elem.foreignKeys; // will be rewritten
854
+ }
855
+ if (elem.on) {
856
+ origin.on = elem.on;
857
+ delete elem.on; // will be rewritten
858
+ }
859
+ }
860
+ if (target !== assocTarget)
861
+ setExpandStatus( elem, 'target' ); // (might) also set in rewriteCondition
862
+ elem.target = {
863
+ path: [ { id: target.name.absolute, location } ],
864
+ scope: 'global',
865
+ location,
866
+ $inferred: (target !== assocTarget ? 'IMPLICIT' : 'rewrite' ),
867
+ };
868
+ setArtifactLink( elem.target, target );
869
+ setArtifactLink( elem.target.path[0], target );
870
+ return true;
871
+ }
872
+
873
+ function assocIsToBeRedirected( assoc ) {
874
+ if (assoc.kind === 'mixin')
875
+ return false;
876
+ const query = userQuery( assoc );
877
+ return redirectInSubQueries || !query || query._main._leadingQuery === query;
878
+ }
879
+
880
+ function redirectImplicitlyDo( elem, assoc, target, service ) {
881
+ // console.log('ES:',elem.name.absolute,elem.name.element);
882
+ const elemScope = scopedRedirections && // null if no scoped redirections
883
+ preferredElemScope( target, service, elem, assoc._main || assoc );
884
+ const exposed = minimalExposure( target, service, elemScope );
885
+
886
+ if (!exposed.length && elemScope !== true) {
887
+ const origTarget = target;
888
+ if (isAutoExposed( target ))
889
+ target = createAutoExposed( origTarget, service, elemScope );
890
+ const desc = origTarget._descendants ||
891
+ setLink( origTarget, '_descendants', Object.create(null) );
892
+ if (!desc[service.name.absolute]) // could be the target itself (no repeated msgs)!
893
+ desc[service.name.absolute] = [ target ];
894
+ else
895
+ desc[service.name.absolute].push( target );
896
+ }
897
+ else if (exposed.length === 1) {
898
+ return exposed[0];
899
+ }
900
+ else if (elem === assoc) {
901
+ // `assoc: Association to ModelEntity`: user-provided target is to be auto-redirected
902
+ warning( 'type-ambiguous-target',
903
+ [ elem.target.location, elem ],
904
+ {
905
+ target,
906
+ // art: definitionScope( target ), - TODO extra debug info in message
907
+ sorted_arts: exposed,
908
+ }, {
909
+ // eslint-disable-next-line max-len
910
+ std: 'Replace target $(TARGET) by one of $(SORTED_ARTS); can\'t auto-redirect this association if multiple projections exist in this service',
911
+ // eslint-disable-next-line max-len
912
+ two: 'Replace target $(TARGET) by $(SORTED_ARTS) or $(SECOND); can\'t auto-redirect this association if multiple projections exist in this service',
913
+ });
914
+ // continuation semantics: no auto-redirection
915
+ }
916
+ else {
917
+ // referred (and probably inferred) assoc (without a user-provided target at that place)
918
+ // HINT: consider bin/cdsv2m.js when changing the following message text
919
+ // No grouped and sub messages yet (TODO v3): mention at all target places with all assocs
920
+ const withAnno = annotationVal( exposed[0]['@cds.redirection.target'] );
921
+ for (const proj of exposed) {
922
+ // TODO: def-ambiguous-target (just v3, as the current is infamous and used in options),
923
+ message( 'redirected-implicitly-ambiguous',
924
+ [ weakLocation( proj.location ), proj ],
925
+ {
926
+ '#': withAnno && 'justOne',
927
+ target,
928
+ art: elem,
929
+ // art: definitionScope( target ), - TODO extra debug info in message
930
+ anno: 'cds.redirection.target',
931
+ sorted_arts: exposed,
932
+ }, {
933
+ // eslint-disable-next-line max-len
934
+ 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',
935
+ // eslint-disable-next-line max-len
936
+ 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',
937
+ // eslint-disable-next-line max-len
938
+ 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',
939
+ } );
940
+ }
941
+ // continuation semantics: no implicit redirections
942
+ }
943
+ return target;
944
+ }
945
+
946
+ // Return projections of `target` in `service`. Shorted by
947
+ // - first, only consider projections with @cds.redirection.target=true
948
+ // - exclude all indirect projections, i.e. those which are projection on others in list
949
+ //
950
+ // To avoid repeated messages: if already tried to do autoexposure, return
951
+ // autoexposed entity when successful, or `target` otherwise (no/failed autoexposure)
952
+ function minimalExposure( target, service, elemScope ) {
953
+ const descendants = scopedExposure( target._descendants &&
954
+ target._descendants[service.name.absolute] ||
955
+ [],
956
+ elemScope, target );
957
+ const preferred = descendants.filter( d => annotationVal( d['@cds.redirection.target'] ) );
958
+ const exposed = preferred.length ? preferred : descendants;
959
+ if (exposed.length < 2)
960
+ return exposed || [];
961
+ let min = null;
962
+ for (const e of exposed) {
963
+ if (!min || min._ancestors && min._ancestors.includes(e)) {
964
+ min = e;
965
+ }
966
+ else if (!e._ancestors || !e._ancestors.includes( min )) {
967
+ if (elemScope === '')
968
+ return [];
969
+ return exposed;
970
+ }
971
+ }
972
+ return [ min ];
973
+ }
974
+
975
+ // Scoped redirections -----------------------------------------------------
976
+
977
+
978
+ function preferredElemScope( target, service, elem, assocMain ) {
979
+ const assocScope = definitionScope( assocMain );
980
+ const targetScope = definitionScope( target );
981
+ if (targetScope === assocScope) { // intra-scope in model
982
+ const elemScope = definitionScope( elem._main || elem );
983
+ if (targetScope === target || // unscoped target in model
984
+ assocScope === assocMain || // unscoped assoc source in model
985
+ elemScope !== (elem._main || elem)) // scoped assoc source in service
986
+ return elemScope; // own scope, then global
987
+ }
988
+ if (targetScope === target) // unscoped target in model / other service
989
+ return false; // all (there could be no scoped autoexposed)
990
+ // scoped target in model:
991
+ const exposed = minimalExposure( targetScope, service, false );
992
+ // console.log('PES:',elem.name.absolute,elem.name.element,exposed.map(e=>e.name.absolute))
993
+ if (exposed.length === 1) // unique redirection for target scope: use that
994
+ return exposed[0];
995
+ // TODO: warning if exposed.length >= 2? Probably not
996
+ // TODO: use excessive testing for the following
997
+ // Now re-scope according to naming of auto-exposed entity:
998
+ const autoScopeName = autoExposedName( targetScope, service, false );
999
+ const autoScope = model.definitions[autoScopeName];
1000
+ // console.log('AEN:',autoScopeName,autoScope&&(autoScope.$inferred || autoScope.kind))
1001
+ if (autoScope)
1002
+ return autoScope;
1003
+ const { location } = service.name;
1004
+ const nullScope = {
1005
+ kind: 'namespace', name: { absolute: autoScopeName, location }, location,
1006
+ };
1007
+ model.definitions[autoScopeName] = nullScope;
1008
+ initArtifact( nullScope );
1009
+ return nullScope;
1010
+ }
1011
+
1012
+ function scopedExposure( descendants, elemScope, target ) {
1013
+ if (!elemScope) // no scoped redirections
1014
+ return descendants;
1015
+ if (elemScope === true || elemScope === 'auto') {
1016
+ // cross-scope navigation, scoped model target, but there is no unique
1017
+ // redirection target for target model scope -> unsure redirection scope
1018
+ const unscoped = descendants.filter( d => d === definitionScope( d ) );
1019
+ if (unscoped.length) // use unscoped new targets if present
1020
+ return unscoped;
1021
+ // Need to filter out auto-exposed, otherwise the behavior is
1022
+ // processing-order dependent (not storing the autoexposed in
1023
+ // _descendents would only be an alternative w/o recompilation)
1024
+ return descendants.filter( d => !d.$generated && !annotationVal( d['@cds.autoexposed'] ) );
1025
+ }
1026
+ // try scope as target first, even if it has @cds.redirection.target: false
1027
+ if (isDirectProjection( elemScope, target ))
1028
+ return [ elemScope ];
1029
+ const scoped = descendants.filter( d => elemScope === definitionScope( d ) );
1030
+ if (scoped.length) // use scoped new targets if present
1031
+ return scoped;
1032
+ // otherwise return new targets outside any scope
1033
+ return descendants.filter( d => d === definitionScope( d ) );
1034
+ }
1035
+
1036
+ // Return the scope of a definition. It is the last parent of the definition
1037
+ // which is not a context/service/namespace, or the definition itself.
1038
+ // If inside service, it is the direct child of the (most inner) service.
1039
+ function definitionScope( art ) {
1040
+ if (art._base) // with deprecated.generatedEntityNameWithUnderscore
1041
+ return art._base;
1042
+ let base = art;
1043
+ while (art._parent) {
1044
+ if (art._parent.kind === 'service')
1045
+ return art;
1046
+ art = art._parent;
1047
+ if (!kindProperties[art.kind].artifacts)
1048
+ base = art;
1049
+ }
1050
+ return base;
1051
+ }
1052
+
1053
+ function isDirectProjection( proj, base ) {
1054
+ return proj.kind === 'entity' && // not event
1055
+ projectionAncestor( base, proj.params ) && // same params
1056
+ // direct proj (TODO: or should we add them to another list?)
1057
+ proj.query && proj.query.op && proj.query.op.val === 'SELECT' &&
1058
+ proj._from && proj._from.length === 1 &&
1059
+ base === resolvePath( proj._from[0], 'from', proj );
1060
+ }
1061
+
1062
+ // Auto-exposure -----------------------------------------------------------
1063
+
1064
+ // TODO: do something in kick-start.js ?
1065
+ function isAutoExposed( target ) {
1066
+ if (target.$autoexpose !== undefined)
1067
+ return target.$autoexpose;
1068
+ const origTarget = target;
1069
+ const chain = [];
1070
+ let source = target._from && resolvePath( target._from[0], 'from', target );
1071
+ // query source ref might not have been resolved yet, cycle avoided as
1072
+ // setAutoExposed() sets $autoexpose and a second call on same art would
1073
+ // return false
1074
+ while (target.$autoexpose === undefined && setAutoExposed( target ) && source) {
1075
+ // stop at first ancestor with annotation or at non-query entity
1076
+ chain.push( target );
1077
+ target = source;
1078
+ source = target._from && resolvePath( target._from[0], 'from', target );
1079
+ }
1080
+ const autoexpose = target.$autoexpose;
1081
+ if (typeof autoexpose === 'boolean') {
1082
+ for (const a of chain)
1083
+ a.$autoexpose = autoexpose;
1084
+ }
1085
+ return origTarget.$autoexpose;
1086
+ }
1087
+
1088
+ // TODO: less auto-exposed for compositions (see lengthy discussions)
1089
+ function setAutoExposed( art ) {
1090
+ const anno = art['@cds.autoexpose'];
1091
+ if (anno && anno.val !== null) { // XSN TODO: set val, but no location for anno short form
1092
+ // @cds.autoexpose:true or @cds.autoexpose:false
1093
+ art.$autoexpose = anno.val === undefined || !!anno.val;
1094
+ return false;
1095
+ }
1096
+ // no @cds.autoexpose or @cds.autoexpose:null
1097
+ // TODO: introduce deprecated.noInheritedAutoexposeViaComposition
1098
+ art.$autoexpose = model.$compositionTargets[art.name.absolute]
1099
+ ? autoexposeViaComposition
1100
+ : null;
1101
+ return true; // still check for inherited @cds.autoexpose
1102
+ }
1103
+
1104
+ function autoExposedName( target, service, elemScope ) {
1105
+ const { absolute } = target.name;
1106
+ if (isDeprecatedEnabled( options, 'shortAutoexposed' )) {
1107
+ const parent = definitionScope( target )._parent;
1108
+ const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
1109
+ // no need for dedot here (as opposed to deprecated.longAutoexposed), as
1110
+ // the name for dependent entities have already been created using `_` then
1111
+ return `${ service.name.absolute }.${ name }`;
1112
+ }
1113
+ if (isDeprecatedEnabled( options, 'longAutoexposed' )) {
1114
+ const dedot = isDeprecatedEnabled( options, 'generatedEntityNameWithUnderscore' );
1115
+ return `${ service.name.absolute }.${ dedot ? absolute.replace( /\./g, '_' ) : absolute }`;
1116
+ }
1117
+ const base = definitionScope( target );
1118
+ if (base === target)
1119
+ return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf('.') + 1 ) }`;
1120
+ // for scoped (e.g. calculated) entities, use exposed name of base:
1121
+ const exposed = minimalExposure( base, service, elemScope );
1122
+ // console.log(exposed.map( a => a.name.absolute ));
1123
+ const sbasename = (exposed.length === 1 && exposed[0] !== base) // same with no/failed expose
1124
+ ? exposed[0].name.absolute
1125
+ : autoExposedName( base, service, elemScope );
1126
+ return sbasename + absolute.slice( base.name.absolute.length );
1127
+ }
1128
+
1129
+
1130
+ function createAutoExposed( target, service, elemScope ) {
1131
+ const absolute = autoExposedName( target, service, elemScope );
1132
+ const autoexposed = model.definitions[absolute];
1133
+ if (autoexposed && (autoexposed.kind !== 'namespace' || !scopedRedirections)) {
1134
+ if (isDirectProjection( autoexposed, target )) {
1135
+ if (options.testMode)
1136
+ throw new Error( `Tried to auto-expose ${ target.name.absolute } twice`);
1137
+ return autoexposed;
1138
+ }
1139
+ error( 'duplicate-autoexposed', [ service.name.location, service ],
1140
+ { target, art: absolute },
1141
+ 'Name $(ART) of autoexposed entity for $(TARGET) collides with other definition' );
1142
+ info( null, [ target.name.location, target ],
1143
+ { art: service },
1144
+ 'Expose this (or the competing) entity explicitly in service $(ART)' );
1145
+ if (autoexposed.$inferred !== 'autoexposed')
1146
+ return target;
1147
+ const firstTarget = autoexposed.query.from._artifact;
1148
+ error( 'duplicate-autoexposed', [ service.name.location, service ],
1149
+ { target: firstTarget, art: absolute },
1150
+ 'Name $(ART) of autoexposed entity for $(TARGET) collides with other definition' );
1151
+ info( null, [ firstTarget.name.location, firstTarget ],
1152
+ { art: service },
1153
+ 'Expose this (or the competing) entity explicitly in service $(ART)' );
1154
+ autoexposed.$inferred = 'duplicate-autoexposed';
1155
+ return target;
1156
+ }
1157
+ // console.log(absolute)
1158
+ const { location } = target.name;
1159
+ const from = augmentPath( location, target.name.absolute );
1160
+ let art = {
1161
+ kind: 'entity',
1162
+ name: { location, path: splitIntoPath( location, absolute ), absolute },
1163
+ location: target.location,
1164
+ query: { location, op: { val: 'SELECT', location }, from },
1165
+ $syntax: 'projection',
1166
+ $inferred: 'autoexposed',
1167
+ '@cds.autoexposed': {
1168
+ name: { path: [ { id: 'cds.autoexposed', location } ], location },
1169
+ $inferred: '$generated',
1170
+ },
1171
+ };
1172
+ // TODO: do we need to tag the generated entity with elemScope = 'auto'?
1173
+ if (autoexposed) {
1174
+ Object.assign( autoexposed, art );
1175
+ art = autoexposed;
1176
+ }
1177
+ else {
1178
+ model.definitions[absolute] = art;
1179
+ }
1180
+ setLink( art, '_service', service );
1181
+ setLink( art, '_block', model.$internal );
1182
+ initArtifact( art, !!autoexposed );
1183
+ // populate view (phase 2 of resolver has to be repeated as the view was created afterwards)
1184
+ populateView( art );
1185
+ // TODO: try to set locations of elements locations of orig target elements
1186
+ newAutoExposed.push( art );
1187
+ return art;
1188
+ }
1189
+ }
1190
+
1191
+ // Return condensed info about reference in select item
1192
+ // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
1193
+ // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
1194
+ // - mixinElem -> { navigation: mixinElement, item: path[0] }
1195
+ // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
1196
+ // - $self -> { item: undefined, tableAlias: $self }
1197
+ // - $parameters.P, :P -> {}
1198
+ // - $now, current_date -> {}
1199
+ // - undef, redef -> {}
1200
+ // With 'navigation': store that navigation._artifact is projected
1201
+ // With 'navigation': rewrite its ON condition
1202
+ // With navigation: Do KEY propagation
1203
+ //
1204
+ // TODO: copy of fn in resolve.js; used just once here - do it differently here
1205
+ // and then delete the function here
1206
+ function pathNavigation( ref ) {
1207
+ // currently, indirectly projectable elements are not included - we might
1208
+ // keep it this way! If we want them to be included - be aware: cycles
1209
+ if (!ref._artifact)
1210
+ return {};
1211
+ let item = ref.path && ref.path[0];
1212
+ const root = item && item._navigation;
1213
+ if (!root)
1214
+ return {};
1215
+ if (root.kind === '$navElement')
1216
+ return { navigation: root, item, tableAlias: root._parent };
1217
+ if (root.kind === 'mixin')
1218
+ return { navigation: root, item };
1219
+ item = ref.path[1];
1220
+ if (root.kind === '$self')
1221
+ return { item, tableAlias: root };
1222
+ if (root.kind !== '$tableAlias' || ref.path.length < 2)
1223
+ return {}; // should not happen
1224
+ return { navigation: root.elements[item.id], item, tableAlias: root };
1225
+ }
1226
+
1227
+ module.exports = populate;