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