@sap/cds-compiler 2.12.0 → 2.13.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/CHANGELOG.md +110 -15
  2. package/bin/cdsc.js +13 -13
  3. package/bin/cdsse.js +2 -2
  4. package/doc/CHANGELOG_BETA.md +13 -6
  5. package/doc/CHANGELOG_DEPRECATED.md +22 -6
  6. package/doc/NameResolution.md +21 -16
  7. package/lib/api/main.js +28 -63
  8. package/lib/api/options.js +3 -3
  9. package/lib/api/validate.js +0 -5
  10. package/lib/backends.js +15 -23
  11. package/lib/base/dictionaries.js +0 -8
  12. package/lib/base/error.js +26 -0
  13. package/lib/base/keywords.js +7 -17
  14. package/lib/base/location.js +9 -4
  15. package/lib/base/message-registry.js +25 -4
  16. package/lib/base/messages.js +16 -26
  17. package/lib/base/model.js +2 -63
  18. package/lib/base/optionProcessorHelper.js +158 -123
  19. package/lib/checks/annotationsOData.js +1 -1
  20. package/lib/checks/cdsPersistence.js +2 -1
  21. package/lib/checks/enricher.js +17 -1
  22. package/lib/checks/invalidTarget.js +3 -1
  23. package/lib/checks/managedWithoutKeys.js +3 -1
  24. package/lib/checks/selectItems.js +4 -4
  25. package/lib/checks/sql-snippets.js +27 -26
  26. package/lib/checks/types.js +1 -1
  27. package/lib/checks/validator.js +4 -7
  28. package/lib/compiler/assert-consistency.js +5 -3
  29. package/lib/compiler/builtins.js +8 -6
  30. package/lib/compiler/checks.js +14 -3
  31. package/lib/compiler/cycle-detector.js +1 -1
  32. package/lib/compiler/define.js +1103 -0
  33. package/lib/compiler/extend.js +983 -0
  34. package/lib/compiler/finalize-parse-cdl.js +231 -0
  35. package/lib/compiler/index.js +32 -13
  36. package/lib/compiler/kick-start.js +190 -0
  37. package/lib/compiler/moduleLayers.js +4 -4
  38. package/lib/compiler/populate.js +1226 -0
  39. package/lib/compiler/propagator.js +111 -46
  40. package/lib/compiler/resolve.js +1433 -0
  41. package/lib/compiler/shared.js +64 -37
  42. package/lib/compiler/tweak-assocs.js +529 -0
  43. package/lib/compiler/utils.js +197 -33
  44. package/lib/edm/.eslintrc.json +5 -0
  45. package/lib/edm/annotations/genericTranslation.js +5 -9
  46. package/lib/edm/annotations/preprocessAnnotations.js +2 -2
  47. package/lib/edm/csn2edm.js +9 -8
  48. package/lib/edm/edm.js +11 -12
  49. package/lib/edm/edmPreprocessor.js +137 -73
  50. package/lib/edm/edmUtils.js +116 -22
  51. package/lib/gen/Dictionary.json +10 -3
  52. package/lib/gen/language.checksum +1 -1
  53. package/lib/gen/language.interp +9 -1
  54. package/lib/gen/language.tokens +86 -83
  55. package/lib/gen/languageLexer.interp +10 -1
  56. package/lib/gen/languageLexer.js +860 -833
  57. package/lib/gen/languageLexer.tokens +78 -75
  58. package/lib/gen/languageParser.js +5282 -4265
  59. package/lib/json/from-csn.js +12 -1
  60. package/lib/json/to-csn.js +126 -66
  61. package/lib/language/docCommentParser.js +2 -2
  62. package/lib/language/genericAntlrParser.js +76 -3
  63. package/lib/language/language.g4 +297 -130
  64. package/lib/language/multiLineStringParser.js +5 -5
  65. package/lib/main.d.ts +468 -59
  66. package/lib/main.js +35 -9
  67. package/lib/model/api.js +3 -1
  68. package/lib/model/csnRefs.js +225 -156
  69. package/lib/model/csnUtils.js +192 -223
  70. package/lib/model/enrichCsn.js +70 -29
  71. package/lib/model/revealInternalProperties.js +27 -6
  72. package/lib/model/sortViews.js +2 -1
  73. package/lib/modelCompare/compare.js +17 -12
  74. package/lib/optionProcessor.js +5 -4
  75. package/lib/render/manageConstraints.js +35 -32
  76. package/lib/render/toCdl.js +73 -288
  77. package/lib/render/toHdbcds.js +25 -23
  78. package/lib/render/toSql.js +98 -41
  79. package/lib/render/utils/common.js +5 -10
  80. package/lib/render/utils/sql.js +4 -3
  81. package/lib/render/utils/stringEscapes.js +111 -0
  82. package/lib/sql-identifier.js +1 -1
  83. package/lib/transform/.eslintrc.json +5 -0
  84. package/lib/transform/db/.eslintrc.json +2 -0
  85. package/lib/transform/db/applyTransformations.js +35 -12
  86. package/lib/transform/db/assertUnique.js +1 -1
  87. package/lib/transform/db/associations.js +103 -305
  88. package/lib/transform/db/cdsPersistence.js +2 -2
  89. package/lib/transform/db/constraints.js +55 -52
  90. package/lib/transform/db/expansion.js +46 -24
  91. package/lib/transform/db/flattening.js +553 -102
  92. package/lib/transform/db/groupByOrderBy.js +3 -1
  93. package/lib/transform/db/transformExists.js +59 -6
  94. package/lib/transform/db/views.js +5 -4
  95. package/lib/transform/draft/.eslintrc.json +38 -0
  96. package/lib/transform/{db/draft.js → draft/db.js} +6 -5
  97. package/lib/transform/draft/odata.js +227 -0
  98. package/lib/transform/forHanaNew.js +67 -183
  99. package/lib/transform/forOdataNew.js +17 -171
  100. package/lib/transform/localized.js +34 -19
  101. package/lib/transform/odata/generateForeignKeyElements.js +1 -1
  102. package/lib/transform/odata/referenceFlattener.js +95 -89
  103. package/lib/transform/odata/structureFlattener.js +1 -1
  104. package/lib/transform/odata/toFinalBaseType.js +86 -12
  105. package/lib/transform/odata/typesExposure.js +5 -5
  106. package/lib/transform/odata/utils.js +2 -2
  107. package/lib/transform/transformUtilsNew.js +36 -22
  108. package/lib/transform/translateAssocsToJoins.js +2 -19
  109. package/lib/transform/universalCsn/.eslintrc.json +36 -0
  110. package/lib/transform/universalCsn/coreComputed.js +170 -0
  111. package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
  112. package/lib/transform/universalCsn/utils.js +63 -0
  113. package/lib/utils/objectUtils.js +30 -0
  114. package/package.json +1 -1
  115. package/share/messages/README.md +26 -0
  116. package/lib/compiler/definer.js +0 -2361
  117. package/lib/compiler/resolver.js +0 -3079
  118. package/lib/transform/universalCsnEnricher.js +0 -237
@@ -0,0 +1,529 @@
1
+ // Tweak associations: rewrite keys and on conditions
2
+
3
+ 'use strict';
4
+
5
+ const {
6
+ isDeprecatedEnabled,
7
+ forEachDefinition,
8
+ forEachGeneric,
9
+ forEachInOrder,
10
+ } = require('../base/model');
11
+ const { dictLocation } = require('../base/location');
12
+ const { weakLocation } = require('../base/messages');
13
+
14
+ const {
15
+ setLink,
16
+ setArtifactLink,
17
+ linkToOrigin,
18
+ copyExpr,
19
+ traverseQueryPost,
20
+ traverseQueryExtra,
21
+ setExpandStatus,
22
+ } = require('./utils');
23
+
24
+ const $location = Symbol.for('cds.$location');
25
+
26
+
27
+ // Export function of this file.
28
+ function tweakAssocs( model ) {
29
+ const { options } = model;
30
+ // Get shared functionality and the message function:
31
+ const {
32
+ info, warning, error,
33
+ } = model.$messageFunctions;
34
+ const {
35
+ effectiveType,
36
+ directType,
37
+ resolveExpr,
38
+ } = model.$functions;
39
+ const { environment } = model.$volatileFunctions;
40
+
41
+ // behavior depending on option `deprecated`:
42
+ const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
43
+ // TODO: we should get rid of noElementsExpansion soon; both
44
+ // beta.nestedProjections and beta.universalCsn do not work with it.
45
+
46
+ // Phase 5: rewrite associations
47
+ forEachDefinition( model, rewriteSimple );
48
+ // TODO: sequence not good enough with derived type of structure with
49
+ // includes: first "direct" structures, then _entities, then the rest.
50
+ // v2: We might run a silent cycle detection earlier, then we could use the
51
+ // SCC number (_scc.lowlink) to sort.
52
+ model._entities.forEach( rewriteView );
53
+ model._entities.forEach( rewriteViewCheck );
54
+ // Think hard whether an on condition rewrite can lead to a new cylcic
55
+ // dependency. If so, we need other messages anyway. TODO: probably do
56
+ // another cyclic check with testMode.js
57
+ return;
58
+
59
+
60
+ //--------------------------------------------------------------------------
61
+ // Phase 5: rewrite associations
62
+ //--------------------------------------------------------------------------
63
+ // Only top-level queries and sub queries in FROM
64
+
65
+ function rewriteSimple( art ) {
66
+ // return;
67
+ if (!art.includes && !art.query) {
68
+ // console.log(message( null, art.location, art, {target:art._target},
69
+ // 'Info','RAS').toString())
70
+ rewriteAssociation( art );
71
+ forEachGeneric( art, 'elements', rewriteAssociation );
72
+ }
73
+ if (art._service)
74
+ forEachGeneric( art, 'elements', excludeAssociation );
75
+ }
76
+
77
+ function rewriteView( view ) {
78
+ traverseQueryExtra( view, ( query ) => {
79
+ forEachGeneric( query, 'elements', rewriteAssociation );
80
+ } );
81
+ if (view.includes) // entities with structure includes:
82
+ forEachGeneric( view, 'elements', rewriteAssociation );
83
+ }
84
+
85
+ // Check explicit ON / keys with REDIRECTED TO
86
+ // TODO: run on all queries, but this is potentially incompatible
87
+ function rewriteViewCheck( view ) {
88
+ traverseQueryPost( view.query, false, ( query ) => {
89
+ forEachGeneric( query, 'elements', rewriteAssociationCheck );
90
+ } );
91
+ }
92
+
93
+ function excludeAssociation( elem ) {
94
+ const target = elem.target && elem.target._artifact;
95
+ if (!target || target._service) // assoc to other service is OK
96
+ return;
97
+ if (!elem.$inferred) { // && !elem.target.$inferred
98
+ // TODO: spec meeting 2021-01-22: no warning
99
+ warning( 'assoc-target-not-in-service', [ elem.target.location, elem ],
100
+ { target, '#': (elem._main.query ? 'select' : 'define') }, {
101
+ std: 'Target $(TARGET) of association is outside any service', // not used
102
+ // eslint-disable-next-line max-len
103
+ define: 'Target $(TARGET) of explicitly defined association is outside any service',
104
+ // eslint-disable-next-line max-len
105
+ select: 'Target $(TARGET) of explicitly selected association is outside any service',
106
+ } );
107
+ }
108
+ else {
109
+ info( 'assoc-outside-service', [ elem.target.location, elem ],
110
+ { target },
111
+ 'Association target $(TARGET) is outside any service' );
112
+ }
113
+ }
114
+
115
+ function rewriteAssociationCheck( element ) {
116
+ const elem = element.items || element; // TODO v2: nested items
117
+ if (elem.elements && enableExpandElements)
118
+ forEachGeneric( elem, 'elements', rewriteAssociationCheck );
119
+ if (!elem.target)
120
+ return;
121
+ if (elem.on && !elem.on.$inferred) {
122
+ const assoc = directType( elem );
123
+ if (assoc && assoc.foreignKeys) {
124
+ error( 'rewrite-key-for-unmanaged', [ elem.on.location, elem ],
125
+ { keyword: 'on', art: assocWithExplicitSpec( assoc ) },
126
+ // eslint-disable-next-line max-len
127
+ 'Do not specify an $(KEYWORD) condition when redirecting the managed association $(ART)' );
128
+ }
129
+ }
130
+ else if (elem.foreignKeys && !inferredForeignKeys( elem.foreignKeys )) {
131
+ const assoc = directType( elem );
132
+ if (assoc && assoc.on) {
133
+ error( 'rewrite-on-for-managed',
134
+ [ elem.foreignKeys[$location] || dictLocation( elem.foreignKeys ), elem ],
135
+ { art: assocWithExplicitSpec( assoc ) },
136
+ 'Do not specify foreign keys when redirecting the unmanaged association $(ART)' );
137
+ }
138
+ else if (assoc && assoc.foreignKeys) {
139
+ // same sequence is not checked
140
+ rewriteKeysMatch( elem, assoc );
141
+ rewriteKeysCovered( assoc, elem );
142
+ }
143
+ }
144
+ }
145
+
146
+ function rewriteKeysMatch( thisAssoc, otherAssoc ) {
147
+ const { foreignKeys } = thisAssoc;
148
+ for (const name in foreignKeys) {
149
+ if (otherAssoc.foreignKeys[name])
150
+ continue; // we would do a basic type check later
151
+ const key = foreignKeys[name];
152
+ const baseAssoc = assocWithExplicitSpec( otherAssoc );
153
+ if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
154
+ error( 'rewrite-key-not-matched-implicit', [ key.name.location, key ],
155
+ { name, target: baseAssoc.target },
156
+ 'No key $(NAME) is defined in original target $(TARGET)' );
157
+ }
158
+ else {
159
+ error( 'rewrite-key-not-matched-explicit', [ key.name.location, key ],
160
+ { name, art: baseAssoc },
161
+ 'No foreign key $(NAME) is specified in association $(ART)' );
162
+ }
163
+ }
164
+ }
165
+
166
+ function rewriteKeysCovered( thisAssoc, otherAssoc ) {
167
+ const names = [];
168
+ const { foreignKeys } = thisAssoc;
169
+ for (const name in foreignKeys) {
170
+ if (!otherAssoc.foreignKeys[name])
171
+ names.push( name );
172
+ }
173
+ if (names.length) {
174
+ const loc = otherAssoc.foreignKeys[$location] || dictLocation( otherAssoc.foreignKeys );
175
+ const location = loc && (!loc.endCol ? loc : {
176
+ file: loc.file,
177
+ line: loc.endLine,
178
+ col: loc.endCol - 1,
179
+ endLine: loc.endLine,
180
+ endCol: loc.endCol,
181
+ } );
182
+ const baseAssoc = assocWithExplicitSpec( thisAssoc );
183
+ if (inferredForeignKeys( baseAssoc.foreignKeys )) { // still inferred = via target keys
184
+ error( 'rewrite-key-not-covered-implicit', [ location, otherAssoc ],
185
+ { names, target: baseAssoc.target },
186
+ {
187
+ std: 'Specify keys $(NAMES) of original target $(TARGET) as foreign keys',
188
+ one: 'Specify key $(NAMES) of original target $(TARGET) as foreign key',
189
+ } );
190
+ }
191
+ else {
192
+ error( 'rewrite-key-not-covered-explicit', [ location, otherAssoc ],
193
+ { names, art: otherAssoc },
194
+ {
195
+ std: 'Specify foreign keys $(NAMES) of association $(ART)',
196
+ one: 'Specify foreign key $(NAMES) of association $(ART)',
197
+ } );
198
+ }
199
+ }
200
+ }
201
+
202
+ function assocWithExplicitSpec( assoc ) {
203
+ while (assoc.foreignKeys && inferredForeignKeys( assoc.foreignKeys, 'keys') ||
204
+ assoc.on && assoc.on.$inferred)
205
+ assoc = directType( assoc );
206
+ return assoc;
207
+ }
208
+
209
+ function rewriteAssociation( element ) {
210
+ let elem = element.items || element; // TODO v2: nested items
211
+ if (elem.elements && enableExpandElements)
212
+ forEachGeneric( elem, 'elements', rewriteAssociation );
213
+ if (!originTarget( elem ))
214
+ return;
215
+ // console.log(message( null, elem.location, elem,
216
+ // {art:assoc,target,ftype:JSON.stringify(ftype)}, 'Info','RA').toString())
217
+
218
+ // With cyclic dependencies on select items, testing for the _effectiveType to
219
+ // be 0 (test above) is not enough if we we have an explicit redirection
220
+ // target -> avoid infloop ourselves with _status.
221
+ const chain = [];
222
+ while (!elem.on && !elem.foreignKeys) {
223
+ chain.push( elem );
224
+ if (elem._status === 'rewrite') { // circular dependency (already reported)
225
+ for (const e of chain)
226
+ setLink( e, '_status', null ); // XSN TODO: nonenum _status -> enum $status
227
+ return;
228
+ }
229
+ setLink( elem, '_status', 'rewrite' );
230
+ elem = directType( elem );
231
+ if (!elem || elem.builtin) // safety
232
+ return;
233
+ }
234
+ chain.reverse();
235
+ for (const art of chain) {
236
+ setLink( elem, '_status', null );
237
+ if (elem.on)
238
+ rewriteCondition( art, elem );
239
+ else if (elem.foreignKeys)
240
+ rewriteKeys( art, elem );
241
+ elem = art;
242
+ }
243
+ }
244
+
245
+ function originTarget( elem ) {
246
+ const assoc = !elem.expand && directType( elem );
247
+ const ftype = assoc && effectiveType( assoc );
248
+ return ftype && ftype.target && ftype.target._artifact;
249
+ }
250
+
251
+ function inferredForeignKeys( foreignKeys, ignore ) {
252
+ // TODO: better use a symbol $inferred for dictionaries later
253
+ for (const name in foreignKeys)
254
+ return foreignKeys[name].$inferred && foreignKeys[name].$inferred !== ignore;
255
+ return false;
256
+ }
257
+
258
+ function rewriteKeys( elem, assoc ) {
259
+ // TODO: split this function: create foreign keys without `targetElement`
260
+ // already in Phase 2: redirectImplicitly()
261
+ // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
262
+ // 'Info','FK').toString())
263
+ forEachInOrder( assoc, 'foreignKeys', ( orig, name ) => {
264
+ const fk = linkToOrigin( orig, name, elem, 'foreignKeys', elem.location );
265
+ fk.$inferred = 'rewrite'; // TODO: other $inferred value?
266
+ // TODO: re-check for case that foreign key is managed association
267
+ if ('_effectiveType' in orig)
268
+ setLink( fk, '_effectiveType', orig._effectiveType);
269
+ const te = copyExpr( orig.targetElement, elem.location );
270
+ if (elem._redirected) {
271
+ const i = te.path[0]; // TODO: or also follow path like for ON?
272
+ const state = rewriteItem( elem, i, i.id, elem, true );
273
+ if (state && state !== true && te.path.length === 1)
274
+ setArtifactLink( te, state );
275
+ }
276
+ fk.targetElement = te;
277
+ });
278
+ }
279
+
280
+ // TODO: there is no need to rewrite the on condition of non-leading queries,
281
+ // i.e. we could just have on = {…}
282
+ // TODO: re-check $self rewrite (with managed composition of aspects),
283
+ // and actually also $self inside anonymous aspect definitions
284
+ // (not entirely urgent as we do not analyse it further, at least sole "$self")
285
+ function rewriteCondition( elem, assoc ) {
286
+ // the ON condition might need to be rewritten even if the target stays the
287
+ // same (TODO later: set status whether rewrite changes anything),
288
+ // especially problematic are refs starting with $self:
289
+ setExpandStatus( elem, 'target' );
290
+ if (enableExpandElements && elem._parent && elem._parent.kind === 'element') {
291
+ // managed association as sub element not supported yet
292
+ error( null, [ elem.location, elem ], {},
293
+ // eslint-disable-next-line max-len
294
+ 'Rewriting the ON condition of unmanaged association in sub element is not supported' );
295
+ return;
296
+ }
297
+ const nav = (elem._main && elem._main.query) ? pathNavigation( elem.value )
298
+ : { navigation: assoc };
299
+ const cond = copyExpr( assoc.on,
300
+ // replace location in ON except if from mixin element
301
+ nav.tableAlias && elem.name.location );
302
+ cond.$inferred = 'copy';
303
+ elem.on = cond;
304
+ // console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
305
+ // 'Info','ON').toString(), nav)
306
+ const { navigation } = nav;
307
+ if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
308
+ return; // should not happen: $projection, $magic, or ref to const
309
+ // console.log(message( null, elem.location, elem, {art:assoc}, 'Info','D').toString())
310
+ // Currently, having an unmanaged association inside a struct is not
311
+ // supported by this function:
312
+ if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
313
+ // For "assoc1.assoc2" and "structelem1.assoc2"
314
+ if (elem._redirected !== null) { // null = already reported
315
+ error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
316
+ 'The ON condition is not rewritten here - provide an explicit ON condition' );
317
+ }
318
+ return;
319
+ }
320
+ if (!nav.tableAlias || nav.tableAlias.path) {
321
+ resolveExpr( cond, rewriteExpr, elem, nav.tableAlias );
322
+ }
323
+ else {
324
+ // TODO: support that, now that the ON condition is rewritten in the right order
325
+ error( null, [ elem.value.location, elem ],
326
+ 'Selecting unmanaged associations from a sub query is not supported' );
327
+ }
328
+ cond.$inferred = 'rewrite';
329
+ }
330
+
331
+ function rewriteExpr( expr, assoc, tableAlias ) {
332
+ // Rewrite ON condition (resulting in outside perspective) for association
333
+ // 'assoc' in query or including entity from ON cond of mixin element /
334
+ // element in included structure / element in source ref/d by table alias.
335
+
336
+ // TODO: re-check args in references, forbid parameter use for the moment
337
+ // TODO: complain about $self (unclear semantics)
338
+ // console.log( info(null, [assoc.name.location, assoc],
339
+ // { art: expr._artifact, names: expr.path.map(i=>i.id) }, 'A').toString(), expr.path)
340
+
341
+ if (!expr.path || !expr._artifact)
342
+ return;
343
+ if (!assoc._main)
344
+ return;
345
+ if (tableAlias) { // from ON cond of element in source ref/d by table alias
346
+ const source = tableAlias._origin;
347
+ const root = expr.path[0]._navigation || expr.path[0]._artifact;
348
+ // console.log( info(null, [assoc.name.location, assoc],
349
+ // { names: expr.path.map(i=>i.id), art: root }, 'TA').toString())
350
+ if (!root || root._main !== source)
351
+ return; // not $self or source element
352
+ const item = expr.path[root.kind === '$self' ? 1 : 0];
353
+ // console.log('YE', assoc.name, item, root.name, expr.path)
354
+ rewritePath( expr, item, assoc,
355
+ navProjection( item && tableAlias.elements[item.id], assoc ),
356
+ assoc.value.location );
357
+ }
358
+ else if (assoc._main.query) { // from ON cond of mixin element in query
359
+ const nav = pathNavigation( expr );
360
+ if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
361
+ rewritePath( expr, nav.item, assoc,
362
+ navProjection( nav.navigation, assoc ),
363
+ nav.item ? nav.item.location : expr.path[0].location );
364
+ }
365
+ }
366
+ else { // from ON cond of element in included structure
367
+ const root = expr.path[0]._navigation || expr.path[0]._artifact;
368
+ if (root.builtin || root.kind !== '$self' && root.kind !== 'element')
369
+ return;
370
+ const item = expr.path[root.kind === '$self' ? 1 : 0];
371
+ if (!item)
372
+ return; // just $self
373
+ const elem = assoc._main.elements[item.id]; // corresponding elem in including structure
374
+ if (!(Array.isArray(elem) || // no msg for redefs
375
+ elem === item._artifact || // redirection for explicit def
376
+ elem._origin === item._artifact)) {
377
+ const art = assoc._origin;
378
+ warning( 'rewrite-shadowed', [ elem.name.location, elem ],
379
+ { art: art && effectiveType( art ) },
380
+ {
381
+ // eslint-disable-next-line max-len
382
+ std: 'This element is not originally referred to in the ON condition of association $(ART)',
383
+ // eslint-disable-next-line max-len
384
+ element: 'This element is not originally referred to in the ON condition of association $(MEMBER) of $(ART)',
385
+ } );
386
+ }
387
+ rewritePath( expr, item, assoc, (Array.isArray(elem) ? false : elem), null );
388
+ }
389
+ }
390
+
391
+ function rewritePath( ref, item, assoc, elem, location ) {
392
+ const { path } = ref;
393
+ let root = path[0];
394
+ if (!elem) {
395
+ if (location) {
396
+ error( 'rewrite-not-projected', [ location, assoc ],
397
+ { name: assoc.name.id, art: item._artifact }, {
398
+ // eslint-disable-next-line max-len
399
+ std: 'Projected association $(NAME) uses non-projected element $(ART)',
400
+ // eslint-disable-next-line max-len
401
+ element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
402
+ } );
403
+ }
404
+ delete root._navigation;
405
+ setArtifactLink( root, elem );
406
+ setArtifactLink( ref, elem );
407
+ return;
408
+ }
409
+ if (item !== root) {
410
+ root.id = '$self';
411
+ setLink( root, '_navigation', assoc._parent.$tableAliases.$self );
412
+ setArtifactLink( root, assoc._parent );
413
+ }
414
+ else if (elem.name.id.charAt(0) === '$') {
415
+ root = { id: '$self', location: item.location };
416
+ path.unshift( root );
417
+ setLink( root, '_navigation', assoc._parent.$tableAliases.$self );
418
+ setArtifactLink( root, assoc._parent );
419
+ }
420
+ else {
421
+ setLink( root, '_navigation', elem );
422
+ }
423
+ if (!elem.name) // nothing to do for own $projection, $projection.elem
424
+ return; // (except having it renamed to $self)
425
+ item.id = elem.name.id;
426
+ let state = null;
427
+ for (const i of path) {
428
+ if (!state) {
429
+ if (i === item)
430
+ state = setArtifactLink( i, elem );
431
+ }
432
+ else if (i) {
433
+ state = rewriteItem( state, i, i.id, assoc );
434
+ if (!state || state === true)
435
+ break;
436
+ }
437
+ else {
438
+ return;
439
+ }
440
+ }
441
+ if (state !== true)
442
+ setArtifactLink( ref, state );
443
+ }
444
+
445
+ function rewriteItem( elem, item, name, assoc, forKeys ) {
446
+ // TODO: for rewriting ON conditions of explicitly provided model targets,
447
+ // we need to only rewrite the current element, not all sibling elements
448
+ if (!elem._redirected)
449
+ return true;
450
+ for (const alias of elem._redirected) {
451
+ // TODO: a message for the same situation as msg 'rewrite-shadowed'?
452
+ if (alias.kind === '$tableAlias') { // _redirected also contains structures for includes
453
+ // TODO: if there is a "multi-step" redirection, we should probably
454
+ // consider intermediate "preferred" elements - not just `assoc`,
455
+ // but its origins, too.
456
+ const proj = navProjection( alias.elements[name], assoc );
457
+ name = proj && proj.name && proj.name.id;
458
+ if (!name) {
459
+ if (!forKeys)
460
+ break;
461
+ setArtifactLink( item, null );
462
+ error( 'rewrite-undefined-key', [ weakLocation( (elem.target || elem).location ), assoc ],
463
+ { id: item.id, art: alias._main },
464
+ 'Foreign key $(ID) has not been found in target $(ART)' );
465
+ return null;
466
+ }
467
+ item.id = name;
468
+ }
469
+ }
470
+ const env = name && environment(elem);
471
+ elem = setArtifactLink( item, env && env[name] );
472
+ if (elem && !Array.isArray(elem))
473
+ return elem;
474
+ // TODO: better (extra message), TODO: do it
475
+ error( 'query-undefined-element', [ item.location, assoc ], { id: name || item.id },
476
+ // eslint-disable-next-line max-len
477
+ 'Element $(ID) has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON condition' );
478
+ return (elem) ? false : null;
479
+ }
480
+ }
481
+
482
+ function navProjection( navigation, preferred ) {
483
+ // TODO: Info if more than one possibility?
484
+ // console.log(navigation,navigation._projections)
485
+ if (!navigation)
486
+ return {};
487
+ else if (!navigation._projections)
488
+ return null;
489
+ return (preferred && navigation._projections.includes( preferred ))
490
+ ? preferred
491
+ : navigation._projections[0] || null;
492
+ }
493
+
494
+ // Return condensed info about reference in select item
495
+ // - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
496
+ // - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
497
+ // - mixinElem -> { navigation: mixinElement, item: path[0] }
498
+ // - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
499
+ // - $self -> { item: undefined, tableAlias: $self }
500
+ // - $parameters.P, :P -> {}
501
+ // - $now, current_date -> {}
502
+ // - undef, redef -> {}
503
+ // With 'navigation': store that navigation._artifact is projected
504
+ // With 'navigation': rewrite its ON condition
505
+ // With navigation: Do KEY propagation
506
+ //
507
+ // TODO: re-think this function, copied in populate.js and tweak-assocs.js
508
+ function pathNavigation( ref ) {
509
+ // currently, indirectly projectable elements are not included - we might
510
+ // keep it this way! If we want them to be included - be aware: cycles
511
+ if (!ref._artifact)
512
+ return {};
513
+ let item = ref.path && ref.path[0];
514
+ const root = item && item._navigation;
515
+ if (!root)
516
+ return {};
517
+ if (root.kind === '$navElement')
518
+ return { navigation: root, item, tableAlias: root._parent };
519
+ if (root.kind === 'mixin')
520
+ return { navigation: root, item };
521
+ item = ref.path[1];
522
+ if (root.kind === '$self')
523
+ return { item, tableAlias: root };
524
+ if (root.kind !== '$tableAlias' || ref.path.length < 2)
525
+ return {}; // should not happen
526
+ return { navigation: root.elements[item.id], item, tableAlias: root };
527
+ }
528
+
529
+ module.exports = tweakAssocs;