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