@sap/cds-compiler 2.11.4 → 2.13.8
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 +159 -1
- package/bin/cds_update_identifiers.js +7 -7
- package/bin/cdsc.js +22 -23
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +30 -63
- package/lib/api/options.js +5 -5
- 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 +52 -2
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +10 -6
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +33 -14
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +76 -38
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +204 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +143 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/genericAntlrParser.js +144 -54
- package/lib/language/language.g4 +424 -203
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +472 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +321 -204
- package/lib/model/csnUtils.js +224 -263
- package/lib/model/enrichCsn.js +97 -40
- 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 +7 -6
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +201 -115
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +149 -75
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +552 -105
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +94 -28
- 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} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +94 -801
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +10 -27
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +2 -1
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2340
- package/lib/compiler/resolver.js +0 -2988
- package/lib/transform/universalCsnEnricher.js +0 -67
|
@@ -0,0 +1,1433 @@
|
|
|
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 './define.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
|
+
// Potential file names:
|
|
34
|
+
// lookup-refs / memorize: main refs loop (phase 2)
|
|
35
|
+
// monitor-refs: resolve-refs (not leading to new defs/elems)
|
|
36
|
+
// repair-props: rewrite, late extensions
|
|
37
|
+
// test-model: cycle detection, late tests (currently in checks)
|
|
38
|
+
|
|
39
|
+
'use strict';
|
|
40
|
+
|
|
41
|
+
const {
|
|
42
|
+
isDeprecatedEnabled,
|
|
43
|
+
forEachDefinition,
|
|
44
|
+
forEachMember,
|
|
45
|
+
forEachGeneric,
|
|
46
|
+
forEachInOrder,
|
|
47
|
+
} = require('../base/model');
|
|
48
|
+
const { dictAdd } = require('../base/dictionaries');
|
|
49
|
+
const { dictLocation } = require('../base/location');
|
|
50
|
+
const { searchName, weakLocation } = require('../base/messages');
|
|
51
|
+
const { combinedLocation } = require('../base/location');
|
|
52
|
+
const { forEachValue } = require('../utils/objectUtils');
|
|
53
|
+
|
|
54
|
+
const { kindProperties } = require('./base');
|
|
55
|
+
const {
|
|
56
|
+
setLink,
|
|
57
|
+
setArtifactLink,
|
|
58
|
+
pathName,
|
|
59
|
+
linkToOrigin,
|
|
60
|
+
setMemberParent,
|
|
61
|
+
withAssociation,
|
|
62
|
+
storeExtension,
|
|
63
|
+
dependsOn,
|
|
64
|
+
dependsOnSilent,
|
|
65
|
+
testExpr,
|
|
66
|
+
targetMaxNotOne,
|
|
67
|
+
traverseQueryPost,
|
|
68
|
+
} = require('./utils');
|
|
69
|
+
|
|
70
|
+
const detectCycles = require('./cycle-detector');
|
|
71
|
+
const layers = require('./moduleLayers');
|
|
72
|
+
|
|
73
|
+
const $location = Symbol.for('cds.$location');
|
|
74
|
+
|
|
75
|
+
const annotationPriorities = {
|
|
76
|
+
define: 1, extend: 2, annotate: 2, edmx: 3,
|
|
77
|
+
};
|
|
78
|
+
const $inferred = Symbol.for('cds.$inferred');
|
|
79
|
+
|
|
80
|
+
// Export function of this file. Resolve type references in augmented CSN
|
|
81
|
+
// `model`. If the model has a property argument `messages`, do not throw
|
|
82
|
+
// exception in case of an error, but push the corresponding error object to
|
|
83
|
+
// that property (should be a vector).
|
|
84
|
+
function resolve( model ) {
|
|
85
|
+
const { options } = model;
|
|
86
|
+
// Get shared functionality and the message function:
|
|
87
|
+
const {
|
|
88
|
+
info, warning, error, message,
|
|
89
|
+
} = model.$messageFunctions;
|
|
90
|
+
const {
|
|
91
|
+
resolvePath,
|
|
92
|
+
resolveTypeArguments,
|
|
93
|
+
defineAnnotations,
|
|
94
|
+
attachAndEmitValidNames,
|
|
95
|
+
lateExtensions,
|
|
96
|
+
effectiveType,
|
|
97
|
+
directType,
|
|
98
|
+
resolveType,
|
|
99
|
+
populateQuery,
|
|
100
|
+
} = model.$functions;
|
|
101
|
+
const { environment } = model.$volatileFunctions;
|
|
102
|
+
Object.assign( model.$functions, {
|
|
103
|
+
resolveExpr,
|
|
104
|
+
} );
|
|
105
|
+
|
|
106
|
+
/** @type {any} may also be a boolean */
|
|
107
|
+
|
|
108
|
+
// behavior depending on option `deprecated`:
|
|
109
|
+
const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
|
|
110
|
+
// TODO: we should get rid of noElementsExpansion soon; both
|
|
111
|
+
// beta.nestedProjections and beta.universalCsn do not work with it.
|
|
112
|
+
|
|
113
|
+
return doResolve();
|
|
114
|
+
|
|
115
|
+
function doResolve() {
|
|
116
|
+
// Phase 1: check paths in usings has been moved to kick-start.js Phase 2:
|
|
117
|
+
// calculate/init view elements & collect views in order:
|
|
118
|
+
// TODO: It might be that we need to call propagateKeyProps() and
|
|
119
|
+
// addImplicitForeignKeys() in populate.js, as we might need to know the
|
|
120
|
+
// foreign keys in populate.js (foreign key access w/o JOINs).
|
|
121
|
+
|
|
122
|
+
// Phase 3: calculate keys along simple queries in collected views:
|
|
123
|
+
model._entities.forEach( propagateKeyProps );
|
|
124
|
+
// While most dependencies leading have been added at this point, new
|
|
125
|
+
// cycles could be added later (e.g. via assocs in where conditions),
|
|
126
|
+
// i.e. keep cycle detection with messages at the end (or after phase 4).
|
|
127
|
+
|
|
128
|
+
// Phase 4: resolve all artifacts:
|
|
129
|
+
forEachDefinition( model, resolveRefs );
|
|
130
|
+
forEachGeneric( model, 'vocabularies', resolveRefs );
|
|
131
|
+
// for builtin types
|
|
132
|
+
forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
|
|
133
|
+
forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
|
|
134
|
+
// Phase 6: apply ANNOTATE on autoexposed entities and unknown artifacts:
|
|
135
|
+
lateExtensions( annotateMembers );
|
|
136
|
+
if (model.extensions)
|
|
137
|
+
model.extensions.map( annotateUnknown );
|
|
138
|
+
// Phase 7: report cyclic dependencies:
|
|
139
|
+
detectCycles( model.definitions, ( user, art, location ) => {
|
|
140
|
+
if (location) {
|
|
141
|
+
error( 'ref-cyclic', [ location, user ], { art }, {
|
|
142
|
+
std: 'Illegal circular reference to $(ART)',
|
|
143
|
+
element: 'Illegal circular reference to element $(MEMBER) of $(ART)',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return model;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
//--------------------------------------------------------------------------
|
|
151
|
+
// Phase 3: calculate propagated KEYs
|
|
152
|
+
//--------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
function propagateKeyProps( view ) {
|
|
155
|
+
// Second argument true ensure that `key` is only propagated along simple
|
|
156
|
+
// view, i.e. ref or subquery in FROM, not UNION or JOIN.
|
|
157
|
+
traverseQueryPost( view.query, true, ( query ) => {
|
|
158
|
+
if (!withExplicitKeys( query ) && inheritKeyProp( query ) &&
|
|
159
|
+
withKeyPropagation( query )) // now the part with messages
|
|
160
|
+
inheritKeyProp( query, true );
|
|
161
|
+
} );
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function withExplicitKeys( query ) {
|
|
165
|
+
for (const name in query.elements) {
|
|
166
|
+
const elem = query.elements[name];
|
|
167
|
+
if (elem.key && !elem.$duplicates) // also those from includes
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function inheritKeyProp( query, doIt ) {
|
|
174
|
+
for (const name in query.elements) {
|
|
175
|
+
const elem = query.elements[name];
|
|
176
|
+
// no key prop for duplicate elements or additional specified elements:
|
|
177
|
+
if (elem.$duplicates || !elem.value)
|
|
178
|
+
continue;
|
|
179
|
+
const nav = pathNavigation( elem.value );
|
|
180
|
+
if (!nav.navigation)
|
|
181
|
+
continue; // undefined, expr, $magic, :const, $self (!), $self.elem
|
|
182
|
+
const { item } = nav;
|
|
183
|
+
if (item !== elem.value.path[elem.value.path.length - 1])
|
|
184
|
+
continue; // having selected a sub elem / navigated along assoc
|
|
185
|
+
const { key } = item._artifact;
|
|
186
|
+
if (key) {
|
|
187
|
+
if (!doIt)
|
|
188
|
+
return true;
|
|
189
|
+
elem.key = { location: elem.value.location, val: key.val, $inferred: 'query' };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function primarySourceNavigation( aliases ) {
|
|
196
|
+
for (const name in aliases)
|
|
197
|
+
return aliases[name].elements;
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function withKeyPropagation( query ) {
|
|
202
|
+
const { from } = query;
|
|
203
|
+
if (!from) // parse error SELECT FROM <EOF>
|
|
204
|
+
return false;
|
|
205
|
+
|
|
206
|
+
let propagateKeys = true; // used instead early RETURN to get more messages
|
|
207
|
+
const toMany = withAssociation( from, targetMaxNotOne, true );
|
|
208
|
+
if (toMany) {
|
|
209
|
+
propagateKeys = false;
|
|
210
|
+
info( 'query-from-many', [ toMany.location, query ], { art: toMany },
|
|
211
|
+
{
|
|
212
|
+
// eslint-disable-next-line max-len
|
|
213
|
+
std: 'Selecting from to-many association $(ART) - key properties are not propagated',
|
|
214
|
+
// eslint-disable-next-line max-len
|
|
215
|
+
element: 'Selecting from to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
216
|
+
} );
|
|
217
|
+
}
|
|
218
|
+
// Check that all keys from the source are projected:
|
|
219
|
+
const notProjected = []; // we actually push to the array
|
|
220
|
+
const navElems = primarySourceNavigation( query.$tableAliases );
|
|
221
|
+
for (const name in navElems) {
|
|
222
|
+
const nav = navElems[name];
|
|
223
|
+
if (nav.$duplicates)
|
|
224
|
+
continue;
|
|
225
|
+
const { key } = nav._origin;
|
|
226
|
+
if (key && key.val && !(nav._projections && nav._projections.length))
|
|
227
|
+
notProjected.push( nav.name.id );
|
|
228
|
+
}
|
|
229
|
+
if (notProjected.length) {
|
|
230
|
+
propagateKeys = false;
|
|
231
|
+
info( 'query-missing-keys', [ from.location, query ], { names: notProjected },
|
|
232
|
+
{
|
|
233
|
+
std: 'Keys $(NAMES) have not been projected - key properties are not propagated',
|
|
234
|
+
one: 'Key $(NAMES) has not been projected - key properties are not propagated',
|
|
235
|
+
} );
|
|
236
|
+
}
|
|
237
|
+
// Check that there is no to-many assoc used in select item:
|
|
238
|
+
for (const name in query.elements) {
|
|
239
|
+
const elem = query.elements[name];
|
|
240
|
+
if (!elem.$inferred && elem.value &&
|
|
241
|
+
testExpr( elem.value, selectTest, () => false ))
|
|
242
|
+
propagateKeys = false;
|
|
243
|
+
}
|
|
244
|
+
return propagateKeys;
|
|
245
|
+
|
|
246
|
+
function selectTest( expr ) {
|
|
247
|
+
const art = withAssociation( expr, targetMaxNotOne );
|
|
248
|
+
if (art) {
|
|
249
|
+
info( 'query-navigate-many', [ art.location, query ], { art },
|
|
250
|
+
{
|
|
251
|
+
// eslint-disable-next-line max-len
|
|
252
|
+
std: 'Navigating along to-many association $(ART) - key properties are not propagated',
|
|
253
|
+
// eslint-disable-next-line max-len
|
|
254
|
+
element: 'Navigating along to-many association $(MEMBER) of $(ART) - key properties are not propagated',
|
|
255
|
+
// eslint-disable-next-line max-len
|
|
256
|
+
alias: 'Navigating along to-many mixin association $(MEMBER) - key properties are not propagated',
|
|
257
|
+
} );
|
|
258
|
+
}
|
|
259
|
+
return art;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
//--------------------------------------------------------------------------
|
|
264
|
+
// Phase 4:
|
|
265
|
+
//--------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
function adHocOrMainKind( elem ) {
|
|
268
|
+
const main = elem._main;
|
|
269
|
+
if (main) {
|
|
270
|
+
do {
|
|
271
|
+
elem = elem._parent;
|
|
272
|
+
if (elem.targetAspect)
|
|
273
|
+
return 'aspect'; // ad-hoc composition target aspect
|
|
274
|
+
} while (elem !== main);
|
|
275
|
+
}
|
|
276
|
+
return elem.kind;
|
|
277
|
+
}
|
|
278
|
+
// TODO: have $applied/$extension/$status on extension with the following values
|
|
279
|
+
// - 'unknown': artifact to extend/annotate is not defined or contains unknown member
|
|
280
|
+
// - 'referred': contains annotation for element of referred type (not yet supported)
|
|
281
|
+
// - 'inferred': only contains extension for known member, but some inferred ones
|
|
282
|
+
// (inferred = elements from structure includes, query elements)
|
|
283
|
+
// - 'original': only contains extensions on non-inferred members
|
|
284
|
+
|
|
285
|
+
// Resolve all references in artifact or element `art`. Do so recursively in
|
|
286
|
+
// all sub elements.
|
|
287
|
+
// TODO: make this function smaller
|
|
288
|
+
function resolveRefs( art ) {
|
|
289
|
+
// console.log(message( null, art.location, art, {}, 'Info','REFS').toString())
|
|
290
|
+
// console.log(message( null, art.location, art, {target:art.target}, 'Info','RR').toString())
|
|
291
|
+
const parent = art._parent;
|
|
292
|
+
const allowedInMain = [ 'entity', 'aspect' ].includes( adHocOrMainKind( art ) );
|
|
293
|
+
const isTopLevelElement = parent && (parent.kind !== 'element' || parent.targetAspect);
|
|
294
|
+
if (art.key && art.key.val && !art.key.$inferred && !(allowedInMain && isTopLevelElement)) {
|
|
295
|
+
warning( 'unexpected-key', [ art.key.location, art ],
|
|
296
|
+
{ '#': allowedInMain ? 'sub' : 'std' }, {
|
|
297
|
+
std: 'KEY is only supported for elements in an entity or an aspect',
|
|
298
|
+
sub: 'KEY is only supported for top-level elements',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
if (art.targetAspect && !(allowedInMain && isTopLevelElement)) {
|
|
302
|
+
message( 'type-managed-composition', [ art.targetAspect.location, art ],
|
|
303
|
+
{ '#': allowedInMain ? 'sub' : 'std' } );
|
|
304
|
+
}
|
|
305
|
+
if (art.includes && !allowedInMain) {
|
|
306
|
+
for (const include of art.includes) {
|
|
307
|
+
const struct = include._artifact;
|
|
308
|
+
if (struct && struct.kind !== 'type' && struct.elements &&
|
|
309
|
+
Object.values( struct.elements ).some( e => e.targetAspect)) {
|
|
310
|
+
message( 'type-managed-composition', [ include.location, art ],
|
|
311
|
+
{ '#': struct.kind, art: struct } );
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
let obj = art;
|
|
316
|
+
if (obj.type) // TODO: && !obj.type.$inferred ?
|
|
317
|
+
resolveTypeExpr( obj, art );
|
|
318
|
+
const type = effectiveType( obj ); // make sure implicitly redirected target exists
|
|
319
|
+
if (!obj.items && type && type.items && enableExpandElements) {
|
|
320
|
+
// TODO: shouldn't be this part of populate.js ?
|
|
321
|
+
const items = {
|
|
322
|
+
location: weakLocation( (obj.type || obj).location ),
|
|
323
|
+
$inferred: 'expand-items',
|
|
324
|
+
};
|
|
325
|
+
setLink( items, '_outer', obj );
|
|
326
|
+
setLink( items, '_origin', type.items );
|
|
327
|
+
obj.items = items;
|
|
328
|
+
obj.$expand = 'origin';
|
|
329
|
+
}
|
|
330
|
+
if (obj.items) { // TODO: make this a while in v2 (also items proxy)
|
|
331
|
+
obj = obj.items || obj; // the object which has type properties
|
|
332
|
+
if (enableExpandElements)
|
|
333
|
+
effectiveType(obj);
|
|
334
|
+
}
|
|
335
|
+
if (obj.type) { // TODO: && !obj.type.$inferred ?
|
|
336
|
+
if (obj !== (art.returns || art)) // not already checked
|
|
337
|
+
resolveTypeExpr( obj, art );
|
|
338
|
+
// typeOf unmanaged assoc? TODO: is this the right place to check this?
|
|
339
|
+
// (probably better in rewriteAssociations)
|
|
340
|
+
const elemtype = obj.type._artifact;
|
|
341
|
+
if (elemtype && effectiveType( elemtype )) {
|
|
342
|
+
const assocType = getAssocSpec( elemtype ) || {};
|
|
343
|
+
if (assocType.on && !obj.on)
|
|
344
|
+
obj.on = { $inferred: 'rewrite' };
|
|
345
|
+
if (assocType.targetAspect) {
|
|
346
|
+
error( 'composition-as-type-of', [ obj.type.location, art ], {},
|
|
347
|
+
'A managed aspect composition element can\'t be used as type' );
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
else if (assocType.on) {
|
|
351
|
+
error( 'assoc-as-type-of', [ obj.type.location, art ], {},
|
|
352
|
+
'An unmanaged association can\'t be used as type' );
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check if relational type is missing its target or if it's used directly.
|
|
357
|
+
if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
|
|
358
|
+
!obj.target && !obj.targetAspect) {
|
|
359
|
+
const isCsn = (obj._block && obj._block.$frontend === 'json');
|
|
360
|
+
error('type-missing-target', [ obj.type.location, obj ],
|
|
361
|
+
{ '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
|
|
362
|
+
// We don't say "use 'association to <target>" because the type could be used
|
|
363
|
+
// in action parameters, etc. as well.
|
|
364
|
+
std: 'The type $(TYPE) can\'t be used directly because it\'s compiler internal',
|
|
365
|
+
csn: 'Type $(TYPE) is missing a target',
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (obj.target) {
|
|
371
|
+
// console.log(obj.name,obj._origin.name)
|
|
372
|
+
if (obj._origin && obj._origin.$inferred === 'REDIRECTED')
|
|
373
|
+
resolveTarget( art, obj._origin );
|
|
374
|
+
// console.log(message( null, obj.location, obj, {target:obj.target}, 'Info','TARGET')
|
|
375
|
+
// .toString(), obj.target.$inferred)
|
|
376
|
+
if (!obj.target.$inferred || obj.target.$inferred === 'aspect-composition')
|
|
377
|
+
resolveTarget( art, obj );
|
|
378
|
+
else
|
|
379
|
+
// TODO: better write when inferred target must be redirected
|
|
380
|
+
resolveRedirected( obj, obj.target._artifact );
|
|
381
|
+
}
|
|
382
|
+
else if (obj.kind === 'mixin') {
|
|
383
|
+
error( 'non-assoc-in-mixin', [ (obj.type || obj.name).location, art ], {},
|
|
384
|
+
'Only unmanaged associations are allowed in mixin clauses' );
|
|
385
|
+
}
|
|
386
|
+
if (art.targetElement) { // in foreign keys
|
|
387
|
+
const target = parent && parent.target;
|
|
388
|
+
if (target && target._artifact) {
|
|
389
|
+
// we just look in target for the path
|
|
390
|
+
// TODO: also check that we do not follow associations? no args, no filter
|
|
391
|
+
resolvePath( art.targetElement, 'targetElement', art,
|
|
392
|
+
environment( target._artifact ), target._artifact );
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Resolve projections/views
|
|
396
|
+
// if (art.query)console.log( info( null, [art.query.location,art.query], 'VQ:' ).toString() );
|
|
397
|
+
|
|
398
|
+
if (art.$queries)
|
|
399
|
+
art.$queries.forEach( resolveQuery );
|
|
400
|
+
|
|
401
|
+
if (obj.type || obj._origin || obj.value && obj.value.path || obj.elements) // typed artifacts
|
|
402
|
+
effectiveType(obj); // set _effectiveType if appropriate, (future?): copy elems if extended
|
|
403
|
+
|
|
404
|
+
if (obj.elements) { // silent dependencies
|
|
405
|
+
forEachGeneric( obj, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
406
|
+
}
|
|
407
|
+
else if (obj.targetAspect && obj.targetAspect.elements) { // silent dependencies
|
|
408
|
+
forEachGeneric( obj.targetAspect, 'elements', elem => dependsOnSilent( art, elem ) );
|
|
409
|
+
}
|
|
410
|
+
if (obj.foreignKeys) { // silent dependencies
|
|
411
|
+
forEachGeneric( obj, 'foreignKeys', (elem) => {
|
|
412
|
+
dependsOnSilent( art, elem );
|
|
413
|
+
} );
|
|
414
|
+
addForeignKeyNavigations( art );
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
resolveExpr( art.default, 'default', art );
|
|
418
|
+
resolveExpr( art.value, 'expr', art, undefined, art.expand || art.inline );
|
|
419
|
+
if (art.value && !art.type && !art.target && !art.elements)
|
|
420
|
+
inferTypeFromCast( art );
|
|
421
|
+
|
|
422
|
+
if (art.kind === 'element' || art.kind === 'mixin')
|
|
423
|
+
effectiveType( art );
|
|
424
|
+
|
|
425
|
+
annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
|
|
426
|
+
chooseAnnotationsInArtifact( art );
|
|
427
|
+
|
|
428
|
+
forEachMember( art, resolveRefs, art.targetAspect );
|
|
429
|
+
|
|
430
|
+
// Set '@Core.Computed' in the Core Compiler to have it propagated...
|
|
431
|
+
if (art.kind !== 'element' || art['@Core.Computed'])
|
|
432
|
+
return;
|
|
433
|
+
if (art.virtual && art.virtual.val ||
|
|
434
|
+
art.value &&
|
|
435
|
+
(!art.value._artifact || !art.value.path || // in localization view: _artifact, but no path
|
|
436
|
+
art.value._artifact.kind === 'builtin' || art.value._artifact.kind === 'param' )) {
|
|
437
|
+
art['@Core.Computed'] = {
|
|
438
|
+
name: {
|
|
439
|
+
path: [ { id: 'Core.Computed', location: art.location } ],
|
|
440
|
+
location: art.location,
|
|
441
|
+
},
|
|
442
|
+
$inferred: '$generated',
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Return type containing the assoc spec (keys, on); note that no
|
|
448
|
+
// propagation/rewrite has been done yet, cyclic dependency must have been
|
|
449
|
+
// checked before!
|
|
450
|
+
function getAssocSpec( type ) {
|
|
451
|
+
// only to be called without cycles
|
|
452
|
+
while (type) {
|
|
453
|
+
if (type.on || type.foreignKeys || type.targetAspect)
|
|
454
|
+
return type;
|
|
455
|
+
type = directType( type );
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function inferTypeFromCast( elem ) {
|
|
461
|
+
// TODO: think about CAST checks in checks.js
|
|
462
|
+
const { op, type } = elem.value;
|
|
463
|
+
if (op && op.val === 'cast' && type && type._artifact) {
|
|
464
|
+
// op.val is also correctly set with CSN input
|
|
465
|
+
elem.type = { ...type, $inferred: 'cast' };
|
|
466
|
+
setArtifactLink( elem.type, type._artifact );
|
|
467
|
+
for (const prop of [ 'length', 'precision', 'scale', 'srid' ]) {
|
|
468
|
+
if (elem.value[prop])
|
|
469
|
+
elem[prop] = { ...elem.value[prop], $inferred: 'cast' };
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Phase 4 - annotations ---------------------------------------------------
|
|
475
|
+
|
|
476
|
+
function annotateUnknown( ext ) {
|
|
477
|
+
// extensions may have annotations for elements/actions/... which may
|
|
478
|
+
// themselves may be unknown
|
|
479
|
+
forEachMember(ext, annotateUnknown);
|
|
480
|
+
|
|
481
|
+
if (ext.$extension) // extension for known artifact -> already applied
|
|
482
|
+
return;
|
|
483
|
+
annotateMembers( ext );
|
|
484
|
+
for (const prop in ext) {
|
|
485
|
+
if (prop.charAt(0) === '@')
|
|
486
|
+
chooseAssignment( prop, ext );
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* @param {XSN.Artifact} art
|
|
492
|
+
* @param {XSN.Extension[]} [extensions]
|
|
493
|
+
* @param {string} [prop]
|
|
494
|
+
* @param {string} [name]
|
|
495
|
+
* @param {object} [parent]
|
|
496
|
+
* @param {string} [kind]
|
|
497
|
+
*/
|
|
498
|
+
function annotateMembers( art, extensions, prop, name, parent, kind ) {
|
|
499
|
+
const showMsg = !art && parent && parent.kind !== 'annotate';
|
|
500
|
+
if (!art && extensions && extensions.length) {
|
|
501
|
+
if (Array.isArray( parent ))
|
|
502
|
+
return;
|
|
503
|
+
const parentExt = extensionFor(parent);
|
|
504
|
+
art = parentExt[prop] && parentExt[prop][name];
|
|
505
|
+
if (!art) {
|
|
506
|
+
art = {
|
|
507
|
+
kind, // for setMemberParent()
|
|
508
|
+
name: { id: name, location: extensions[0].name.location },
|
|
509
|
+
location: extensions[0].location,
|
|
510
|
+
};
|
|
511
|
+
setMemberParent( art, name, parentExt, prop );
|
|
512
|
+
art.kind = 'annotate'; // after setMemberParent()!
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (const ext of extensions || []) {
|
|
517
|
+
if ('_artifact' in ext.name) // already applied
|
|
518
|
+
continue;
|
|
519
|
+
setArtifactLink( ext.name, art );
|
|
520
|
+
|
|
521
|
+
if (art) {
|
|
522
|
+
defineAnnotations( ext, art, ext._block, ext.kind );
|
|
523
|
+
// eslint-disable-next-line no-shadow
|
|
524
|
+
forEachMember( ext, ( elem, name, prop ) => {
|
|
525
|
+
storeExtension( elem, name, prop, art, ext._block );
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (showMsg) {
|
|
529
|
+
// somehow similar to checkDefinitions():
|
|
530
|
+
const feature = kindProperties[parent.kind][prop];
|
|
531
|
+
if (prop === 'elements' || prop === 'enum') {
|
|
532
|
+
if (!feature) {
|
|
533
|
+
warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
|
|
534
|
+
'Elements only exist in entities, types or typed constructs' );
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
notFound( 'anno-undefined-element', ext.name.location, art,
|
|
538
|
+
{ art: searchName( parent, name, parent.enum ? 'enum' : 'element' ) },
|
|
539
|
+
parent.elements || parent.enum );
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
else if (prop === 'actions') {
|
|
543
|
+
if (!feature) {
|
|
544
|
+
warning( 'anno-unexpected-actions', [ ext.name.location, art ], {},
|
|
545
|
+
'Actions and functions only exist top-level and for entities' );
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
notFound( 'anno-undefined-action', ext.name.location, art,
|
|
549
|
+
{ art: searchName( parent, name, 'action' ) },
|
|
550
|
+
parent.actions );
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
else if (!feature) {
|
|
554
|
+
warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
|
|
555
|
+
'Parameters only exist for actions or functions' );
|
|
556
|
+
} // TODO: entities betaMod
|
|
557
|
+
else {
|
|
558
|
+
notFound( 'anno-undefined-param', ext.name.location, art,
|
|
559
|
+
{ art: searchName( parent, name, 'param' ) },
|
|
560
|
+
parent.params );
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (art && art._annotate) {
|
|
565
|
+
if (art.kind === 'action' || art.kind === 'function') {
|
|
566
|
+
expandParameters( art );
|
|
567
|
+
if (art.returns)
|
|
568
|
+
effectiveType( art.returns );
|
|
569
|
+
}
|
|
570
|
+
const aor = art.returns || art;
|
|
571
|
+
const obj = aor.items || aor.targetAspect || aor;
|
|
572
|
+
// Currently(?), effectiveType() does not calculate the effective type of
|
|
573
|
+
// its line item:
|
|
574
|
+
effectiveType( obj );
|
|
575
|
+
if (art._annotate.elements)
|
|
576
|
+
setExpandStatusAnnotate( aor, 'annotate' );
|
|
577
|
+
annotate( obj, 'element', 'elements', 'enum', art );
|
|
578
|
+
annotate( art, 'action', 'actions' );
|
|
579
|
+
annotate( art, 'param', 'params' );
|
|
580
|
+
// const { returns } = art._annotate;
|
|
581
|
+
// if (returns) {
|
|
582
|
+
// const dict = returns.elements;
|
|
583
|
+
// const env = obj.returns && obj.returns.elements || null;
|
|
584
|
+
// for (const n in dict)
|
|
585
|
+
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
|
|
586
|
+
// }
|
|
587
|
+
}
|
|
588
|
+
return;
|
|
589
|
+
|
|
590
|
+
function notFound( msgId, location, address, args, validDict ) {
|
|
591
|
+
// TODO: probably move this to shared.js and use for EXTEND, too
|
|
592
|
+
const msg = message( msgId, [ location, address ], args );
|
|
593
|
+
attachAndEmitValidNames(msg, validDict);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// eslint-disable-next-line no-shadow
|
|
597
|
+
function annotate( obj, kind, prop, altProp, parent = obj ) {
|
|
598
|
+
const dict = art._annotate[prop];
|
|
599
|
+
const env = obj[prop] || altProp && obj[altProp] || null;
|
|
600
|
+
for (const n in dict)
|
|
601
|
+
annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function setExpandStatusAnnotate( elem, status ) {
|
|
606
|
+
for (;;) {
|
|
607
|
+
if (elem.$expand === status)
|
|
608
|
+
return; // already set
|
|
609
|
+
elem.$expand = status; // meaning: expanded, containing annos
|
|
610
|
+
for (let line = elem.items; line; line = line.items)
|
|
611
|
+
line.$expand = status; // to-csn just uses the innermost $expand
|
|
612
|
+
if (!elem._main)
|
|
613
|
+
return;
|
|
614
|
+
elem = elem._parent;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
function expandParameters( action ) {
|
|
620
|
+
// see also expandElements()
|
|
621
|
+
if (!enableExpandElements || !effectiveType( action ))
|
|
622
|
+
return;
|
|
623
|
+
const chain = [];
|
|
624
|
+
// Should we be able to consider params and returns separately?
|
|
625
|
+
// Probably not, let to-csn omit unchanged params/returns.
|
|
626
|
+
while (action._origin && !action.params) {
|
|
627
|
+
chain.push( action );
|
|
628
|
+
action = action._origin;
|
|
629
|
+
}
|
|
630
|
+
chain.reverse();
|
|
631
|
+
for (const art of chain) {
|
|
632
|
+
const origin = art._origin;
|
|
633
|
+
if (!art.params && origin.params) {
|
|
634
|
+
for (const name in origin.params) {
|
|
635
|
+
// TODO: we could check _annotate here to decide whether we really
|
|
636
|
+
// not to create proxies
|
|
637
|
+
const orig = origin.params[name];
|
|
638
|
+
linkToOrigin( orig, name, art, 'params', weakLocation( orig.location ), true )
|
|
639
|
+
.$inferred = 'expand-param';
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (!art.returns && origin.returns) {
|
|
643
|
+
// TODO: make linkToOrigin() work for returns, kind/name?
|
|
644
|
+
const location = weakLocation( origin.returns.location );
|
|
645
|
+
art.returns = {
|
|
646
|
+
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
|
|
647
|
+
kind: 'param',
|
|
648
|
+
location,
|
|
649
|
+
$inferred: 'expand-param',
|
|
650
|
+
};
|
|
651
|
+
setLink( art.returns, '_parent', art );
|
|
652
|
+
setLink( art.returns, '_main', art._main || art );
|
|
653
|
+
setLink( art.returns, '_origin', origin.returns );
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function extensionFor( art ) {
|
|
659
|
+
if (art.kind === 'annotate')
|
|
660
|
+
return art;
|
|
661
|
+
if (art._extension)
|
|
662
|
+
return art._extension;
|
|
663
|
+
|
|
664
|
+
// $extension means: already applied
|
|
665
|
+
const ext = {
|
|
666
|
+
kind: art.kind, // set kind for setMemberParent()
|
|
667
|
+
$extension: 'exists',
|
|
668
|
+
location: art.location, // location( extension to existing art ) = location(art)
|
|
669
|
+
};
|
|
670
|
+
const { location } = art.name;
|
|
671
|
+
if (!art._main) {
|
|
672
|
+
ext.name = {
|
|
673
|
+
path: [ { id: art.name.absolute, location } ],
|
|
674
|
+
location,
|
|
675
|
+
absolute: art.name.absolute,
|
|
676
|
+
};
|
|
677
|
+
if (model.extensions)
|
|
678
|
+
model.extensions.push(ext);
|
|
679
|
+
else
|
|
680
|
+
model.extensions = [ ext ];
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
ext.name = { id: art.name.id, location };
|
|
684
|
+
const parent = extensionFor( art._parent );
|
|
685
|
+
const kind = kindProperties[art.kind].normalized || art.kind;
|
|
686
|
+
// enums would be first in elements
|
|
687
|
+
if ( parent[kindProperties[kind].dict] &&
|
|
688
|
+
parent[kindProperties[kind].dict][art.name.id] )
|
|
689
|
+
throw new Error(art.name.id);
|
|
690
|
+
setMemberParent( ext, art.name.id, parent, kindProperties[kind].dict );
|
|
691
|
+
}
|
|
692
|
+
ext.kind = 'annotate'; // after setMemberParent()!
|
|
693
|
+
setLink( art, '_extension', ext );
|
|
694
|
+
setArtifactLink( ext.name, art );
|
|
695
|
+
if (art.returns)
|
|
696
|
+
ext.$syntax = 'returns';
|
|
697
|
+
return ext;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Goes through all (applied) annotations in the given artifact and chooses one
|
|
702
|
+
* if multiple exist according to the module layer.
|
|
703
|
+
*
|
|
704
|
+
* @param {XSN.Artifact} art
|
|
705
|
+
*/
|
|
706
|
+
function chooseAnnotationsInArtifact( art ) {
|
|
707
|
+
for (const prop in art) {
|
|
708
|
+
if (prop.charAt(0) === '@')
|
|
709
|
+
chooseAssignment( prop, art );
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function chooseAssignment( annoName, art ) {
|
|
714
|
+
// TODO: getPath an all names
|
|
715
|
+
const anno = art[annoName];
|
|
716
|
+
if (!Array.isArray(anno)) { // just one assignment -> use it
|
|
717
|
+
if (removeEllipsis( anno )) {
|
|
718
|
+
error( 'anno-unexpected-ellipsis',
|
|
719
|
+
[ anno.name.location, art ], { code: '...' } );
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
// sort assignment according to layer
|
|
724
|
+
const layerAnnos = Object.create(null);
|
|
725
|
+
for (const a of anno) {
|
|
726
|
+
const layer = layers.layer( a._block );
|
|
727
|
+
const name = (layer) ? layer.realname : '';
|
|
728
|
+
const done = layerAnnos[name];
|
|
729
|
+
if (done)
|
|
730
|
+
done.annos.push( a );
|
|
731
|
+
else
|
|
732
|
+
layerAnnos[name] = { layer, annos: [ a ] };
|
|
733
|
+
}
|
|
734
|
+
mergeArrayInSCCs();
|
|
735
|
+
art[annoName] = mergeLayeredArrays( findLayerCandidate( ) );
|
|
736
|
+
return;
|
|
737
|
+
|
|
738
|
+
// Merge annotations in each layer, i.e. multiple annotations in the same layer are
|
|
739
|
+
// stored in an array and need to be merged before different layers can be merged.
|
|
740
|
+
function mergeArrayInSCCs( ) {
|
|
741
|
+
let pos = 0;
|
|
742
|
+
forEachValue(layerAnnos, (layer) => {
|
|
743
|
+
const mergeSource = layer.annos.find(v => (v.$priority === undefined ||
|
|
744
|
+
annotationPriorities[v.$priority] === annotationPriorities.define));
|
|
745
|
+
if (mergeSource) {
|
|
746
|
+
// If the source annotation (at 'define' level) contains an ellipsis,
|
|
747
|
+
// there is no base to apply to.
|
|
748
|
+
if (removeEllipsis( mergeSource )) {
|
|
749
|
+
error( 'anno-unexpected-ellipsis',
|
|
750
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
751
|
+
}
|
|
752
|
+
// merge source into ellipsis array annotates
|
|
753
|
+
layer.annos.forEach( (mergeTarget) => {
|
|
754
|
+
if (mergeTarget.$priority &&
|
|
755
|
+
annotationPriorities[mergeTarget.$priority] > annotationPriorities.define) {
|
|
756
|
+
pos = findEllipsis( mergeTarget );
|
|
757
|
+
if (pos > -1) {
|
|
758
|
+
if (mergeSource.literal !== 'array') {
|
|
759
|
+
error( 'anno-mismatched-ellipsis',
|
|
760
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function mergeLayeredArrays( mergeTarget ) {
|
|
772
|
+
if (mergeTarget.literal === 'array') {
|
|
773
|
+
let layer = layers.layer( mergeTarget._block );
|
|
774
|
+
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
775
|
+
let pos = findEllipsis( mergeTarget );
|
|
776
|
+
let hasRun = false;
|
|
777
|
+
while (pos > -1 && Object.keys( layerAnnos ).length ) {
|
|
778
|
+
hasRun = true;
|
|
779
|
+
const mergeSource = findLayerCandidate();
|
|
780
|
+
if (mergeSource.literal !== 'array') {
|
|
781
|
+
error( 'anno-mismatched-ellipsis',
|
|
782
|
+
[ mergeSource.name.location, art ], { code: '...' } );
|
|
783
|
+
return mergeTarget;
|
|
784
|
+
}
|
|
785
|
+
mergeTarget.val = mergeArrayValues( mergeSource.val, mergeTarget.val );
|
|
786
|
+
layer = layers.layer( mergeSource._block );
|
|
787
|
+
delete layerAnnos[(layer) ? layer.realname : ''];
|
|
788
|
+
pos = findEllipsis( mergeTarget );
|
|
789
|
+
}
|
|
790
|
+
// All layers were processed. Remove excess ellipsis.
|
|
791
|
+
if (removeEllipsis( mergeTarget, pos ) && hasRun) {
|
|
792
|
+
// There shouldn't be any ellipsis or we don't have a base annotation.
|
|
793
|
+
// But only if the loop above has run. Otherwise the in-layer merge
|
|
794
|
+
// already warned about this case.
|
|
795
|
+
message( 'anno-unexpected-ellipsis-layers',
|
|
796
|
+
[ mergeTarget.name.location, art ], { code: '...' } );
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
return mergeTarget;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function mergeArrayValues( previousValue, arraySpec ) {
|
|
803
|
+
let prevPos = 0;
|
|
804
|
+
const result = [];
|
|
805
|
+
for (const item of arraySpec) {
|
|
806
|
+
const ell = item && item.literal === 'token' && item.val === '...';
|
|
807
|
+
if (!ell) {
|
|
808
|
+
result.push( item );
|
|
809
|
+
}
|
|
810
|
+
else {
|
|
811
|
+
let upToSpec = item.upTo && checkUpToSpec( item.upTo, true );
|
|
812
|
+
while (prevPos < previousValue.length) {
|
|
813
|
+
const prevItem = previousValue[prevPos++];
|
|
814
|
+
result.push( prevItem );
|
|
815
|
+
if (upToSpec && prevItem && equalUpTo( prevItem, item.upTo)) {
|
|
816
|
+
upToSpec = false;
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
if (upToSpec) { // non-matched UP TO
|
|
821
|
+
warning( null, [ item.upTo.location, art ], { anno: annoName, code: '... up to' },
|
|
822
|
+
'The $(CODE) value does not match any item in the base annotation $(ANNO)' );
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return result;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function checkUpToSpec( upToSpec, trueIfFullUpTo ) {
|
|
830
|
+
const { literal } = upToSpec;
|
|
831
|
+
if (trueIfFullUpTo !== true) { // inside struct of UP TO
|
|
832
|
+
if (literal !== 'struct' && literal !== 'array' )
|
|
833
|
+
return true;
|
|
834
|
+
}
|
|
835
|
+
else if (literal === 'struct') {
|
|
836
|
+
return Object.values( upToSpec.struct ).every( checkUpToSpec );
|
|
837
|
+
}
|
|
838
|
+
else if (literal !== 'array' && literal !== 'boolean' && literal !== 'null') {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
error( null, [ upToSpec.location, art ],
|
|
842
|
+
{ anno: annoName, code: '... up to', '#': literal },
|
|
843
|
+
{
|
|
844
|
+
std: 'Unexpected $(CODE) value type in the assignment of $(ANNO)',
|
|
845
|
+
array: 'Unexpected array as $(CODE) value in the assignment of $(ANNO)',
|
|
846
|
+
// eslint-disable-next-line max-len
|
|
847
|
+
struct: 'Unexpected structure as $(CODE) structure property value in the assignment of $(ANNO)',
|
|
848
|
+
boolean: 'Unexpected boolean as $(CODE) value in the assignment of $(ANNO)',
|
|
849
|
+
null: 'Unexpected null as $(CODE) value in the assignment of $(ANNO)',
|
|
850
|
+
} );
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function equalUpTo( previousItem, upToSpec ) {
|
|
855
|
+
if (!previousItem)
|
|
856
|
+
return false;
|
|
857
|
+
if ('val' in upToSpec) {
|
|
858
|
+
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
859
|
+
return true;
|
|
860
|
+
const typeUpTo = typeof upToSpec.val;
|
|
861
|
+
const typePrev = typeof previousItem.val;
|
|
862
|
+
if (typeUpTo === 'number')
|
|
863
|
+
return typePrev === 'string' && previousItem.val === upToSpec.val.toString();
|
|
864
|
+
if (typePrev === 'number')
|
|
865
|
+
return typeUpTo === 'string' && upToSpec.val === previousItem.val.toString();
|
|
866
|
+
}
|
|
867
|
+
else if (upToSpec.path) {
|
|
868
|
+
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
869
|
+
}
|
|
870
|
+
else if (upToSpec.sym) {
|
|
871
|
+
return previousItem.sym && previousItem.sym.id === upToSpec.sym.id;
|
|
872
|
+
}
|
|
873
|
+
else if (upToSpec.struct && previousItem.struct) {
|
|
874
|
+
return Object.entries( upToSpec.struct )
|
|
875
|
+
.every( ([ n, v ]) => equalUpTo( previousItem.struct[n], v ) );
|
|
876
|
+
}
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
function normalizeRef( node ) { // see to-csn.js
|
|
881
|
+
const ref = pathName( node.path );
|
|
882
|
+
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function removeEllipsis(a, pos = findEllipsis( a )) {
|
|
886
|
+
let count = 0;
|
|
887
|
+
while (a.literal === 'array' && pos > -1) {
|
|
888
|
+
count++;
|
|
889
|
+
a.val.splice(pos, 1);
|
|
890
|
+
pos = findEllipsis( a );
|
|
891
|
+
}
|
|
892
|
+
return count;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function findEllipsis(a) {
|
|
896
|
+
return (a.literal === 'array' && a.val)
|
|
897
|
+
? a.val.findIndex(v => v.literal === 'token' && v.val === '...') : -1;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function findLayerCandidate() {
|
|
901
|
+
// collect assignments of upper layers (are in no _layerExtends)
|
|
902
|
+
const exts = Object.keys( layerAnnos ).map( layerExtends );
|
|
903
|
+
const allExtends = Object.assign( Object.create(null), ...exts );
|
|
904
|
+
const collected = [];
|
|
905
|
+
for (const name in layerAnnos) {
|
|
906
|
+
if (!(name in allExtends))
|
|
907
|
+
collected.push( prioritizedAnnos( layerAnnos[name].annos ) );
|
|
908
|
+
}
|
|
909
|
+
// inspect collected assignments - choose the one or signal error
|
|
910
|
+
const justOnePerLayer = collected.every( annos => annos.length === 1);
|
|
911
|
+
if (!justOnePerLayer || collected.length > 1) {
|
|
912
|
+
for (const annos of collected) {
|
|
913
|
+
for (const a of annos ) {
|
|
914
|
+
// Only the message ID is different.
|
|
915
|
+
if (justOnePerLayer) {
|
|
916
|
+
message( 'anno-duplicate-unrelated-layer',
|
|
917
|
+
[ a.name.location, art ], { anno: annoName },
|
|
918
|
+
'Duplicate assignment with $(ANNO)' );
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
message( 'anno-duplicate', [ a.name.location, art ], { anno: annoName } );
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return collected[0][0]; // just choose any one with error
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function layerExtends( name ) {
|
|
930
|
+
const { layer } = layerAnnos[name];
|
|
931
|
+
return layer && layer._layerExtends;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function prioritizedAnnos( annos ) {
|
|
936
|
+
let prio = 0;
|
|
937
|
+
let r = [];
|
|
938
|
+
for (const a of annos) {
|
|
939
|
+
const p = annotationPriorities[a.$priority] || annotationPriorities.define;
|
|
940
|
+
if (p === prio) {
|
|
941
|
+
r.push(a);
|
|
942
|
+
}
|
|
943
|
+
else if (p > prio) {
|
|
944
|
+
r = [ a ];
|
|
945
|
+
prio = p;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
return r;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Phase 4 - queries and associations --------------------------------------
|
|
952
|
+
|
|
953
|
+
function resolveQuery( query ) {
|
|
954
|
+
if (!query._main) // parse error
|
|
955
|
+
return;
|
|
956
|
+
traverseQueryPost( query, null, populateQuery ); // TODO: still necessary?
|
|
957
|
+
forEachGeneric( query, '$tableAliases', ( alias ) => {
|
|
958
|
+
// console.log( info( null, [alias.location,alias], 'SQA:' ).toString() );
|
|
959
|
+
if (alias.kind === 'mixin')
|
|
960
|
+
resolveRefs( alias ); // mixin element
|
|
961
|
+
else if (alias.kind !== '$self')
|
|
962
|
+
// pure path has been resolved, resolve args and filter now:
|
|
963
|
+
resolveExpr( alias, 'from', query._parent );
|
|
964
|
+
} );
|
|
965
|
+
for (const col of query.$inlines)
|
|
966
|
+
resolveExpr( col.value, 'expr', col, undefined, true );
|
|
967
|
+
// for (const col of query.$inlines)
|
|
968
|
+
// if (!col.value.path) throw Error(col.name.element)
|
|
969
|
+
if (query !== query._main._leadingQuery) // will be done later
|
|
970
|
+
forEachGeneric( query, 'elements', resolveRefs );
|
|
971
|
+
if (query.from)
|
|
972
|
+
resolveJoinOn( query.from );
|
|
973
|
+
if (query.where)
|
|
974
|
+
resolveExpr( query.where, 'expr', query, query._combined );
|
|
975
|
+
if (query.groupBy)
|
|
976
|
+
resolveBy( query.groupBy, 'expr' );
|
|
977
|
+
resolveExpr( query.having, 'expr', query, query._combined );
|
|
978
|
+
if (query.$orderBy) // ORDER BY from UNION:
|
|
979
|
+
// TODO clarify: can I access the tab alias of outer queries? If not:
|
|
980
|
+
// 4th arg query._main instead query._parent.
|
|
981
|
+
resolveBy( query.$orderBy, 'order-by-union', query.elements, query._parent );
|
|
982
|
+
if (query.orderBy) { // ORDER BY
|
|
983
|
+
// search in `query.elements` after having checked table aliases of the current query
|
|
984
|
+
resolveBy( query.orderBy, 'expr', query.elements );
|
|
985
|
+
// TODO: disallow resulting element ref if in expression!
|
|
986
|
+
// Necessary to check it in the compiler as it might work with other semantics on DB!
|
|
987
|
+
// (we could downgrade it to a warning if name is equal to unique source element name)
|
|
988
|
+
// TODO: Some helping text mentioning an alias name would be useful
|
|
989
|
+
}
|
|
990
|
+
return;
|
|
991
|
+
|
|
992
|
+
function resolveJoinOn( join ) {
|
|
993
|
+
if (join && join.args) { // JOIN
|
|
994
|
+
for (const j of join.args)
|
|
995
|
+
resolveJoinOn( j );
|
|
996
|
+
if (join.on)
|
|
997
|
+
resolveExpr( join.on, 'expr', query, query._combined );
|
|
998
|
+
// TODO: check restrictions according to join "query"
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Note the strange name resolution (dynamic part) for ORDER BY: the same
|
|
1003
|
+
// as for select items if it is an expression, but first look at select
|
|
1004
|
+
// item alias (i.e. like `$projection.NAME` if it is a path. If it is an
|
|
1005
|
+
// ORDER BY of an UNION, do not allow any dynamic path in an expression,
|
|
1006
|
+
// and only allow the elements of the leading query if it is a path.
|
|
1007
|
+
//
|
|
1008
|
+
// This seem to be similar, but different in SQLite 3.22.0: ORDER BY seems
|
|
1009
|
+
// to bind stronger than UNION (see <SQLite>/src/parse.y), and the name
|
|
1010
|
+
// resolution seems to use select item aliases from all SELECTs of the
|
|
1011
|
+
// UNION (see <SQLite>/test/tkt2822.test).
|
|
1012
|
+
function resolveBy( array, mode, pathDict, q ) {
|
|
1013
|
+
for (const value of array ) {
|
|
1014
|
+
if (value)
|
|
1015
|
+
resolveExpr( value, mode, q || query, value.path && pathDict );
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
function resolveTarget( art, obj ) {
|
|
1021
|
+
if (art !== obj && obj.on && obj.$inferred !== 'REDIRECTED') {
|
|
1022
|
+
message( 'assoc-in-array', [ obj.on.location, art ], {},
|
|
1023
|
+
// TODO: also check parameter parent, two messages?
|
|
1024
|
+
'An association can\'t be used for arrays or parameters' );
|
|
1025
|
+
setArtifactLink( obj.target, undefined );
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const target = resolvePath( obj.target, 'target', art );
|
|
1029
|
+
if (obj.on) {
|
|
1030
|
+
if (!art._main || !art._parent.elements && !art._parent.items && !art._parent.targetAspect) {
|
|
1031
|
+
// TODO: test of .items a bit unclear - we should somehow restrict the
|
|
1032
|
+
// use of unmanaged assocs in MANY, at least with $self
|
|
1033
|
+
// TODO: $self usage in anonymous aspects to be corrected in Core Compiler
|
|
1034
|
+
const isComposition = obj.type && obj.type.path && obj.type.path[0] &&
|
|
1035
|
+
obj.type.path[0].id === 'cds.Composition';
|
|
1036
|
+
message( 'assoc-as-type', [ obj.on.location, art ],
|
|
1037
|
+
{ '#': isComposition ? 'comp' : 'std' }, {
|
|
1038
|
+
std: 'An unmanaged association can\'t be defined as type',
|
|
1039
|
+
comp: 'An unmanaged composition can\'t be defined as type',
|
|
1040
|
+
});
|
|
1041
|
+
// TODO: also warning if inside structure
|
|
1042
|
+
}
|
|
1043
|
+
else if (obj.$inferred !== 'REDIRECTED') {
|
|
1044
|
+
// TODO: extra with $inferred (to avoid messages)?
|
|
1045
|
+
// TODO: in the ON condition of an explicitly provided model entity
|
|
1046
|
+
// which is going to be implicitly redirected, we can never navigate
|
|
1047
|
+
// along associations, even not to the foreign keys (at least if they
|
|
1048
|
+
// are renamed) - introduce extra 'expected' which inspects REDIRECTED
|
|
1049
|
+
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );
|
|
1050
|
+
}
|
|
1051
|
+
else {
|
|
1052
|
+
const elements = Object.create( art._parent.elements );
|
|
1053
|
+
elements[art.name.id] = obj;
|
|
1054
|
+
resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art, elements );
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
else if (art.kind === 'mixin') {
|
|
1058
|
+
error( 'assoc-in-mixin', [ obj.target.location, art ], {},
|
|
1059
|
+
'Managed associations are not allowed for MIXIN elements' );
|
|
1060
|
+
}
|
|
1061
|
+
else if (target && !obj.foreignKeys && target.kind === 'entity') {
|
|
1062
|
+
if (obj.$inferred === 'REDIRECTED') {
|
|
1063
|
+
addImplicitForeignKeys( art, obj, target );
|
|
1064
|
+
}
|
|
1065
|
+
else if (!obj.type || obj.type.$inferred || obj.target.$inferred) { // REDIRECTED
|
|
1066
|
+
resolveRedirected( art, target );
|
|
1067
|
+
}
|
|
1068
|
+
else if (obj.type._artifact && obj.type._artifact.internal) { // cds.Association, ...
|
|
1069
|
+
addImplicitForeignKeys( art, obj, target );
|
|
1070
|
+
}
|
|
1071
|
+
// else console.log( message( null,obj.location,obj, {target}, 'Info','NOTARGET').toString())
|
|
1072
|
+
}
|
|
1073
|
+
// else console.log( message( null, obj.location, obj, {target}, 'Info','NORE').toString())
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function addImplicitForeignKeys( art, obj, target ) {
|
|
1077
|
+
obj.foreignKeys = Object.create(null);
|
|
1078
|
+
forEachInOrder( target, 'elements', ( elem, name ) => {
|
|
1079
|
+
if (elem.key && elem.key.val) {
|
|
1080
|
+
const { location } = art.target;
|
|
1081
|
+
const key = {
|
|
1082
|
+
name: { location, id: elem.name.id, $inferred: 'keys' }, // more by setMemberParent()
|
|
1083
|
+
kind: 'key',
|
|
1084
|
+
targetElement: { path: [ { id: elem.name.id, location } ], location },
|
|
1085
|
+
location,
|
|
1086
|
+
$inferred: 'keys',
|
|
1087
|
+
};
|
|
1088
|
+
setMemberParent( key, name, art );
|
|
1089
|
+
dictAdd( obj.foreignKeys, name, key );
|
|
1090
|
+
setArtifactLink( key.targetElement, elem );
|
|
1091
|
+
setArtifactLink( key.targetElement.path[0], elem );
|
|
1092
|
+
setLink( key, '_effectiveType', effectiveType(elem) );
|
|
1093
|
+
dependsOn(key, elem, location);
|
|
1094
|
+
dependsOnSilent(art, key);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
obj.foreignKeys[$inferred] = 'keys';
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function addForeignKeyNavigations( art ) {
|
|
1101
|
+
art.$keysNavigation = Object.create(null);
|
|
1102
|
+
forEachGeneric( art, 'foreignKeys', ( key ) => {
|
|
1103
|
+
if (!key.targetElement || !key.targetElement.path)
|
|
1104
|
+
return;
|
|
1105
|
+
let dict = art.$keysNavigation;
|
|
1106
|
+
const last = key.targetElement.path[key.targetElement.path.length - 1];
|
|
1107
|
+
for (const item of key.targetElement.path) {
|
|
1108
|
+
let nav = dict[item.id];
|
|
1109
|
+
if (!nav) {
|
|
1110
|
+
nav = {};
|
|
1111
|
+
dict[item.id] = nav;
|
|
1112
|
+
if (item === last)
|
|
1113
|
+
setArtifactLink( nav, key );
|
|
1114
|
+
else
|
|
1115
|
+
nav.$keysNavigation = Object.create(null);
|
|
1116
|
+
}
|
|
1117
|
+
else if (item === last || nav._artifact) {
|
|
1118
|
+
error( 'duplicate-key-ref', [ item.location, key ], {},
|
|
1119
|
+
'The same target reference has already been used in a key definition' );
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
dict = nav.$keysNavigation;
|
|
1123
|
+
}
|
|
1124
|
+
} );
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// TODO: add this somehow to tweak-assocs.js ?
|
|
1128
|
+
function resolveRedirected( elem, target ) {
|
|
1129
|
+
setLink( elem, '_redirected', null ); // null = do not touch path steps after assoc
|
|
1130
|
+
const assoc = directType( elem );
|
|
1131
|
+
const origType = assoc && effectiveType( assoc );
|
|
1132
|
+
if (!origType || !origType.target) {
|
|
1133
|
+
error( 'redirected-no-assoc', [ elem.target.location, elem ], {},
|
|
1134
|
+
'Only an association can be redirected' );
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
// console.log(message( null, elem.location, elem, {target,art:assoc}, 'Info','RE')
|
|
1138
|
+
// .toString(), elem.value)
|
|
1139
|
+
const nav = elem._main && elem._main.query && elem.value && pathNavigation( elem.value );
|
|
1140
|
+
if (nav && nav.item !== elem.value.path[elem.value.path.length - 1]) {
|
|
1141
|
+
if (origType.on) {
|
|
1142
|
+
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
|
|
1143
|
+
// TODO: Better text ?
|
|
1144
|
+
'The ON condition is not rewritten here - provide an explicit ON condition' );
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const origTarget = origType.target._artifact;
|
|
1149
|
+
if (!origTarget || !target)
|
|
1150
|
+
return;
|
|
1151
|
+
|
|
1152
|
+
const chain = [];
|
|
1153
|
+
if (target === origTarget) {
|
|
1154
|
+
if (!elem.target.$inferred) {
|
|
1155
|
+
info( 'redirected-to-same', [ elem.target.location, elem ], { art: target },
|
|
1156
|
+
'The redirected target is the original $(ART)' );
|
|
1157
|
+
}
|
|
1158
|
+
setLink( elem, '_redirected', chain ); // store the chain
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (elem.foreignKeys || elem.on)
|
|
1162
|
+
return; // TODO: or should we still bring an msg if nothing in common?
|
|
1163
|
+
// now check whether target and origTarget are "related"
|
|
1164
|
+
while (target.query) {
|
|
1165
|
+
const from = target.query.args ? {} : target.query.from;
|
|
1166
|
+
if (!from)
|
|
1167
|
+
return; // parse error - TODO: or UNION?
|
|
1168
|
+
if (!from.path) {
|
|
1169
|
+
warning( 'redirected-to-complex', [ elem.target.location, elem ],
|
|
1170
|
+
{ art: target, '#': target === elem.target._artifact ? 'target' : 'std' },
|
|
1171
|
+
{
|
|
1172
|
+
std: 'Redirection involves the complex view $(ART)',
|
|
1173
|
+
target: 'The redirected target $(ART) is a complex view',
|
|
1174
|
+
});
|
|
1175
|
+
break;
|
|
1176
|
+
}
|
|
1177
|
+
target = from._artifact;
|
|
1178
|
+
if (!target)
|
|
1179
|
+
return;
|
|
1180
|
+
chain.push( from );
|
|
1181
|
+
if (target === origTarget) {
|
|
1182
|
+
chain.reverse();
|
|
1183
|
+
setLink( elem, '_redirected', chain );
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
let redirected = null;
|
|
1188
|
+
let news = [ { chain: chain.reverse(), sources: [ target ] } ];
|
|
1189
|
+
const dict = Object.create(null);
|
|
1190
|
+
while (news.length) {
|
|
1191
|
+
const outer = news;
|
|
1192
|
+
news = [];
|
|
1193
|
+
for (const o of outer) {
|
|
1194
|
+
for (const s of o.sources) {
|
|
1195
|
+
const art = (s.kind === '$tableAlias') ? s._origin : s;
|
|
1196
|
+
if (art !== origTarget) {
|
|
1197
|
+
if (findOrig( o.chain, s, art ) && !redirected) // adds to news []
|
|
1198
|
+
redirected = false; // do not report further error
|
|
1199
|
+
}
|
|
1200
|
+
else if (!redirected) {
|
|
1201
|
+
redirected = (s.kind === '$tableAlias') ? [ s, ...o.chain ] : o.chain;
|
|
1202
|
+
}
|
|
1203
|
+
else {
|
|
1204
|
+
error( 'redirected-to-ambiguous', [ elem.target.location, elem ], { art: origTarget },
|
|
1205
|
+
'The redirected target originates more than once from $(ART)' );
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
if (redirected) {
|
|
1212
|
+
setLink( elem, '_redirected', redirected );
|
|
1213
|
+
}
|
|
1214
|
+
else if (redirected == null) {
|
|
1215
|
+
error( 'redirected-to-unrelated', [ elem.target.location, elem ], { art: origTarget },
|
|
1216
|
+
'The redirected target does not originate from $(ART)' );
|
|
1217
|
+
}
|
|
1218
|
+
return;
|
|
1219
|
+
|
|
1220
|
+
// B = proj on A, C = A x B, X = { a: assoc to A on a.Q1 = ...}, Y = X.{ a: redirected to C }
|
|
1221
|
+
// what does a: redirected to C means?
|
|
1222
|
+
// -> collect all elements Qi used in ON (corr: foreign keys)
|
|
1223
|
+
// -> only use an tableAlias which has propagation for all elements
|
|
1224
|
+
// no - error if the original target can be reached twice
|
|
1225
|
+
// even better: disallow complex view (try as error first)
|
|
1226
|
+
|
|
1227
|
+
// eslint-disable-next-line no-shadow
|
|
1228
|
+
function findOrig( chain, alias, art ) {
|
|
1229
|
+
if (!art || dict[art.name.absolute])
|
|
1230
|
+
// some include ref or query source cannot be found, or cyclic ref
|
|
1231
|
+
return true;
|
|
1232
|
+
dict[art.name.absolute] = true;
|
|
1233
|
+
|
|
1234
|
+
if (art.includes) {
|
|
1235
|
+
news.push( {
|
|
1236
|
+
chain: [ art, ...chain ],
|
|
1237
|
+
sources: art.includes
|
|
1238
|
+
.map( r => r._artifact )
|
|
1239
|
+
.filter( i => i ), // _artifact may be `null` if the include cannot be found
|
|
1240
|
+
} );
|
|
1241
|
+
}
|
|
1242
|
+
const query = art._leadingQuery;
|
|
1243
|
+
if (!query)
|
|
1244
|
+
return false; // non-query entity
|
|
1245
|
+
if (!query.$tableAliases) // previous error in query definition
|
|
1246
|
+
return true;
|
|
1247
|
+
const sources = [];
|
|
1248
|
+
for (const n in query.$tableAliases) {
|
|
1249
|
+
const a = query.$tableAliases[n];
|
|
1250
|
+
if (a.path && a.kind !== '$self' && a.kind !== 'mixin')
|
|
1251
|
+
sources.push( a );
|
|
1252
|
+
}
|
|
1253
|
+
if (alias.kind === '$tablealias')
|
|
1254
|
+
news.push( { chain: [ alias, ...chain ], sources } );
|
|
1255
|
+
else
|
|
1256
|
+
news.push( { chain, sources } );
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
//--------------------------------------------------------------------------
|
|
1262
|
+
// General resolver functions
|
|
1263
|
+
//--------------------------------------------------------------------------
|
|
1264
|
+
|
|
1265
|
+
// Resolve the type and its arguments if applicable.
|
|
1266
|
+
function resolveTypeExpr( art, user ) {
|
|
1267
|
+
const typeArt = resolveType( art.type, user );
|
|
1268
|
+
if (typeArt)
|
|
1269
|
+
resolveTypeArguments( art, typeArt, user );
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function resolveExpr( expr, expected, user, extDict, expandOrInline) {
|
|
1273
|
+
// TODO: when we have rewritten the resolvePath functions,
|
|
1274
|
+
// define a traverseExpr() in ./utils.js
|
|
1275
|
+
// TODO: extra "expected" 'expand'/'inline' instead o param `expandOrInline`
|
|
1276
|
+
if (!expr || typeof expr === 'string') // parse error or keywords in {xpr:...}
|
|
1277
|
+
return;
|
|
1278
|
+
if (Array.isArray(expr)) {
|
|
1279
|
+
expr.forEach( e => resolveExpr( e, expected, user, extDict ) );
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (expr.type) // e.g. cast( a as Integer )
|
|
1284
|
+
resolveTypeExpr( expr, user );
|
|
1285
|
+
|
|
1286
|
+
if (expr.path) {
|
|
1287
|
+
if (expr.$expected === 'exists') {
|
|
1288
|
+
error( 'expr-unexpected-exists', [ expr.location, user ], {},
|
|
1289
|
+
'An EXISTS predicate is not expected here' );
|
|
1290
|
+
// We complain about the EXISTS before, as EXISTS subquery is also not
|
|
1291
|
+
// supported (avoid that word if you do not want to get tickets when it
|
|
1292
|
+
// will be supported), TODO: location of EXISTS
|
|
1293
|
+
expr.$expected = 'approved-exists'; // only complain once
|
|
1294
|
+
}
|
|
1295
|
+
if (expected instanceof Function) {
|
|
1296
|
+
expected( expr, user, extDict );
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
resolvePath( expr, expected, user, extDict );
|
|
1300
|
+
|
|
1301
|
+
const last = !expandOrInline && expr.path[expr.path.length - 1];
|
|
1302
|
+
for (const step of expr.path) {
|
|
1303
|
+
if (step && (step.args || step.where || step.cardinality) &&
|
|
1304
|
+
step._artifact && !Array.isArray( step._artifact ) )
|
|
1305
|
+
resolveParamsAndWhere( step, expected, user, extDict, step === last );
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
else if (expr.query) {
|
|
1309
|
+
const { query } = expr;
|
|
1310
|
+
if (query.kind || query._leadingQuery) { // UNION has _leadingQuery
|
|
1311
|
+
// traverseQueryPost( query, false, resolveQuery );
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
error( 'expr-no-subquery', [ expr.location, user ], {},
|
|
1315
|
+
'Subqueries are not supported here' );
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
else if (expr.op && expr.args) {
|
|
1319
|
+
const args = Array.isArray(expr.args) ? expr.args : Object.values( expr.args );
|
|
1320
|
+
args.forEach( e => e && resolveExpr( e, e.$expected || expected, user, extDict ) );
|
|
1321
|
+
}
|
|
1322
|
+
if (expr.suffix && isDeprecatedEnabled( options )) {
|
|
1323
|
+
const { location } = expr.suffix[0] || expr;
|
|
1324
|
+
error( null, [ location, user ], { prop: 'deprecated' },
|
|
1325
|
+
'Window functions are not supported if $(PROP) options are set' );
|
|
1326
|
+
}
|
|
1327
|
+
if (expr.suffix)
|
|
1328
|
+
expr.suffix.forEach( s => s && resolveExpr( s, expected, user, extDict ) );
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
|
|
1332
|
+
const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
|
|
1333
|
+
const type = alias || effectiveType( step._artifact );
|
|
1334
|
+
const art = (type && type.target) ? type.target._artifact : type;
|
|
1335
|
+
if (!art)
|
|
1336
|
+
return;
|
|
1337
|
+
const entity = (art.kind === 'entity') &&
|
|
1338
|
+
(!isLast || [ 'from', 'exists', 'approved-exists' ].includes( expected )) && art;
|
|
1339
|
+
if (step.args)
|
|
1340
|
+
resolveParams( step.args, art, entity, expected, user, extDict, step.location );
|
|
1341
|
+
if (entity) {
|
|
1342
|
+
if (step.where)
|
|
1343
|
+
resolveExpr( step.where, 'filter', user, environment( type ) );
|
|
1344
|
+
}
|
|
1345
|
+
else if (step.where && step.where.location || step.cardinality ) {
|
|
1346
|
+
const location = combinedLocation( step.where, step.cardinality );
|
|
1347
|
+
// XSN TODO: filter$location including […]
|
|
1348
|
+
message( 'expr-no-filter', [ location, user ], { '#': expected },
|
|
1349
|
+
{
|
|
1350
|
+
std: 'A filter can only be provided when navigating along associations',
|
|
1351
|
+
from: 'A filter can only be provided for the source entity or associations',
|
|
1352
|
+
} );
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function resolveParams( dict, art, entity, expected, user, extDict, stepLocation ) {
|
|
1357
|
+
if (!entity || !entity.params) {
|
|
1358
|
+
let first = dict[Object.keys(dict)[0]];
|
|
1359
|
+
if (Array.isArray(first))
|
|
1360
|
+
first = first[0];
|
|
1361
|
+
message( 'args-no-params',
|
|
1362
|
+
[ dict[$location] ||
|
|
1363
|
+
dictLocation( dict, first && first.name && first.name.location || stepLocation),
|
|
1364
|
+
user ],
|
|
1365
|
+
{ art, '#': (entity ? 'entity' : expected ) },
|
|
1366
|
+
{
|
|
1367
|
+
std: 'Parameters can only be provided when navigating along associations',
|
|
1368
|
+
from: 'Parameters can only be provided for the source entity or associations',
|
|
1369
|
+
// or extra message id for entity?
|
|
1370
|
+
entity: 'Entity $(ART) has no parameters',
|
|
1371
|
+
} );
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const exp = (expected === 'from') ? 'expr' : expected;
|
|
1375
|
+
if (Array.isArray(dict)) {
|
|
1376
|
+
message( 'args-expected-named', [ dict[0] && dict[0].location || stepLocation, user ],
|
|
1377
|
+
'Named parameters must be provided for the entity' );
|
|
1378
|
+
for (const a of dict)
|
|
1379
|
+
resolveExpr( a, exp, user, extDict );
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
// TODO: allow to specify expected for arguments in in specExpected
|
|
1383
|
+
for (const name in dict) {
|
|
1384
|
+
const param = art.params[name];
|
|
1385
|
+
const arg = dict[name];
|
|
1386
|
+
for (const a of Array.isArray(arg) ? arg : [ arg ]) {
|
|
1387
|
+
setArtifactLink( a.name, param );
|
|
1388
|
+
if (!param) {
|
|
1389
|
+
message( 'args-undefined-param', [ a.name.location, user ], { art, id: name },
|
|
1390
|
+
'Entity $(ART) has no parameter $(ID)' );
|
|
1391
|
+
}
|
|
1392
|
+
resolveExpr( a, exp, user, extDict );
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// Return condensed info about reference in select item
|
|
1399
|
+
// - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }
|
|
1400
|
+
// - sourceElem (in query) -> { navigation: navElem, item: path[0], tableAlias }
|
|
1401
|
+
// - mixinElem -> { navigation: mixinElement, item: path[0] }
|
|
1402
|
+
// - $projection.elem -> also $self.item -> { item: path[1], tableAlias: $self }
|
|
1403
|
+
// - $self -> { item: undefined, tableAlias: $self }
|
|
1404
|
+
// - $parameters.P, :P -> {}
|
|
1405
|
+
// - $now, current_date -> {}
|
|
1406
|
+
// - undef, redef -> {}
|
|
1407
|
+
// With 'navigation': store that navigation._artifact is projected
|
|
1408
|
+
// With 'navigation': rewrite its ON condition
|
|
1409
|
+
// With navigation: Do KEY propagation
|
|
1410
|
+
//
|
|
1411
|
+
// TODO: re-think this function, copied in populate.js and tweak-assocs.js
|
|
1412
|
+
function pathNavigation( ref ) {
|
|
1413
|
+
// currently, indirectly projectable elements are not included - we might
|
|
1414
|
+
// keep it this way! If we want them to be included - be aware: cycles
|
|
1415
|
+
if (!ref._artifact)
|
|
1416
|
+
return {};
|
|
1417
|
+
let item = ref.path && ref.path[0];
|
|
1418
|
+
const root = item && item._navigation;
|
|
1419
|
+
if (!root)
|
|
1420
|
+
return {};
|
|
1421
|
+
if (root.kind === '$navElement')
|
|
1422
|
+
return { navigation: root, item, tableAlias: root._parent };
|
|
1423
|
+
if (root.kind === 'mixin')
|
|
1424
|
+
return { navigation: root, item };
|
|
1425
|
+
item = ref.path[1];
|
|
1426
|
+
if (root.kind === '$self')
|
|
1427
|
+
return { item, tableAlias: root };
|
|
1428
|
+
if (root.kind !== '$tableAlias' || ref.path.length < 2)
|
|
1429
|
+
return {}; // should not happen
|
|
1430
|
+
return { navigation: root.elements[item.id], item, tableAlias: root };
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
module.exports = resolve;
|