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