@sap/cds-compiler 4.5.0 → 4.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -7
- package/bin/cdsc.js +13 -11
- package/doc/CHANGELOG_BETA.md +6 -0
- package/lib/api/main.js +256 -115
- package/lib/api/options.js +8 -0
- package/lib/base/message-registry.js +17 -4
- package/lib/base/messages.js +15 -3
- package/lib/base/model.js +1 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +2 -3
- package/lib/compiler/assert-consistency.js +2 -1
- package/lib/compiler/builtins.js +20 -4
- package/lib/compiler/checks.js +30 -6
- package/lib/compiler/define.js +31 -9
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/resolve.js +26 -21
- package/lib/compiler/shared.js +19 -9
- package/lib/compiler/tweak-assocs.js +82 -107
- package/lib/compiler/utils.js +2 -1
- package/lib/edm/annotations/edmJson.js +23 -22
- package/lib/edm/annotations/genericTranslation.js +14 -4
- package/lib/edm/csn2edm.js +24 -10
- package/lib/edm/edmInboundChecks.js +1 -2
- package/lib/edm/edmPreprocessor.js +11 -9
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +3 -1
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +4 -1
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +5253 -5214
- package/lib/json/to-csn.js +7 -1
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +21 -4
- package/lib/language/genericAntlrParser.js +9 -11
- package/lib/main.d.ts +28 -3
- package/lib/main.js +3 -0
- package/lib/model/csnRefs.js +4 -1
- package/lib/model/csnUtils.js +12 -7
- package/lib/optionProcessor.js +21 -19
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +18 -15
- package/lib/render/toHdbcds.js +59 -28
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +57 -82
- package/lib/render/utils/common.js +17 -0
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +27 -31
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/flattening.js +68 -69
- package/lib/transform/db/temporal.js +1 -1
- package/lib/transform/draft/db.js +2 -16
- package/lib/transform/draft/odata.js +3 -3
- package/lib/transform/effective/associations.js +3 -5
- package/lib/transform/effective/main.js +6 -9
- package/lib/transform/forOdata.js +13 -9
- package/lib/transform/forRelationalDB.js +36 -17
- package/lib/transform/odata/toFinalBaseType.js +3 -3
- package/lib/transform/odata/typesExposure.js +14 -5
- package/lib/transform/transformUtils.js +47 -34
- package/lib/transform/translateAssocsToJoins.js +33 -8
- package/package.json +2 -2
package/lib/checks/enricher.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
const { csnRefs } = require('../model/csnRefs');
|
|
8
8
|
const { setProp } = require('../base/model');
|
|
9
|
+
const { xprInAnnoProperties } = require('../compiler/builtins');
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* The following properties are attached as non-enumerable where appropriate:
|
|
11
13
|
*
|
|
@@ -16,9 +18,10 @@ const { setProp } = require('../base/model');
|
|
|
16
18
|
*- `$path` has the csnPath to reach that property.
|
|
17
19
|
*
|
|
18
20
|
* @param {CSN.Model} csn CSN to enrich in-place
|
|
21
|
+
* @param {enrichCsnOptions} [options={}]
|
|
19
22
|
* @returns {{ csn: CSN.Model, cleanup: () => void }} CSN with all ref's pre-resolved
|
|
20
23
|
*/
|
|
21
|
-
function enrichCsn( csn ) {
|
|
24
|
+
function enrichCsn( csn, options ) {
|
|
22
25
|
const transformers = {
|
|
23
26
|
elements: dictionary,
|
|
24
27
|
definitions: dictionary,
|
|
@@ -31,8 +34,7 @@ function enrichCsn( csn ) {
|
|
|
31
34
|
target,
|
|
32
35
|
includes: simpleRef,
|
|
33
36
|
columns,
|
|
34
|
-
|
|
35
|
-
'@': () => { /* ignore annotations */ },
|
|
37
|
+
'@': annotation,
|
|
36
38
|
};
|
|
37
39
|
let cleanupCallbacks = [];
|
|
38
40
|
|
|
@@ -83,6 +85,35 @@ function enrichCsn( csn ) {
|
|
|
83
85
|
csnPath.pop();
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Transformer for things that are annotations. When we have a "=" plus an expression of some sorts,
|
|
90
|
+
* we treat it like a "standard" thing.
|
|
91
|
+
*
|
|
92
|
+
* @param {object | Array} _parent the thing that has _prop
|
|
93
|
+
* @param {string|number} _prop the name of the current property or index
|
|
94
|
+
* @param {object} node The value of node[_prop]
|
|
95
|
+
*/
|
|
96
|
+
function annotation( _parent, _prop, node ) {
|
|
97
|
+
if (options.processAnnotations) {
|
|
98
|
+
if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
|
|
99
|
+
standard(_parent, _prop, node);
|
|
100
|
+
}
|
|
101
|
+
else if (node && typeof node === 'object') {
|
|
102
|
+
csnPath.push(_prop);
|
|
103
|
+
|
|
104
|
+
if (Array.isArray(node)) {
|
|
105
|
+
node.forEach( (n, i) => annotation( node, i, n ) );
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
for (const name of Object.getOwnPropertyNames( node ))
|
|
109
|
+
annotation( node, name, node[name] );
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
csnPath.pop();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
86
117
|
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
87
118
|
function columns( parent, prop, node ) {
|
|
88
119
|
// Establish the link relationships
|
|
@@ -178,3 +209,8 @@ function enrichCsn( csn ) {
|
|
|
178
209
|
}
|
|
179
210
|
|
|
180
211
|
module.exports = enrichCsn;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @typedef {object} enrichCsnOptions
|
|
215
|
+
* @property {boolean} [processAnnotations=false] Wether to process annotations and call custom transformers on them
|
|
216
|
+
*/
|
package/lib/checks/validator.js
CHANGED
|
@@ -121,12 +121,11 @@ const forOdataQueryValidators = [];
|
|
|
121
121
|
const commonMemberValidators
|
|
122
122
|
= [ validateOnCondition, validateForeignKeys,
|
|
123
123
|
validateAssociationsInItems, checkForInvalidTarget,
|
|
124
|
-
checkVirtualElement ];
|
|
124
|
+
checkVirtualElement, checkManagedAssoc ];
|
|
125
125
|
|
|
126
126
|
// TODO: checkManagedAssoc is a forEachMemberRecursively!
|
|
127
127
|
const commonArtifactValidators = [
|
|
128
128
|
checkTypeDefinitionHasType,
|
|
129
|
-
checkManagedAssoc,
|
|
130
129
|
checkRecursiveTypeUsage,
|
|
131
130
|
];
|
|
132
131
|
// TODO: Does it make sense to run the on-condition check as part of a CSN validator?
|
|
@@ -150,7 +149,7 @@ function _validate( csn, that,
|
|
|
150
149
|
artifactValidators = [],
|
|
151
150
|
queryValidators = [],
|
|
152
151
|
iterateOptions = {} ) {
|
|
153
|
-
const { cleanup } = enrich(csn);
|
|
152
|
+
const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });
|
|
154
153
|
|
|
155
154
|
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });
|
|
156
155
|
|
|
@@ -480,7 +480,8 @@ function assertConsistency( model, stage ) {
|
|
|
480
480
|
optional: [
|
|
481
481
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
482
482
|
// expressions as annotation values
|
|
483
|
-
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type',
|
|
483
|
+
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
|
|
484
|
+
'scale', 'srid', 'length', 'precision',
|
|
484
485
|
],
|
|
485
486
|
// TODO: restrict path to #simplePath
|
|
486
487
|
},
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -32,6 +32,7 @@ const core = {
|
|
|
32
32
|
Timestamp: { category: 'dateTime' },
|
|
33
33
|
Boolean: { category: 'boolean' },
|
|
34
34
|
UUID: { category: 'string' },
|
|
35
|
+
Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
|
|
35
36
|
Association: { internal: true, category: 'relation' },
|
|
36
37
|
Composition: { internal: true, category: 'relation' },
|
|
37
38
|
};
|
|
@@ -320,9 +321,7 @@ function checkDate( year, month, day ) {
|
|
|
320
321
|
* Return whether JSON object `val` is a representation for an annotation expression
|
|
321
322
|
*/
|
|
322
323
|
function isAnnotationExpression( val ) {
|
|
323
|
-
|
|
324
|
-
// decided → just check truthy at the moment
|
|
325
|
-
return val['='] && xprInAnnoProperties.some( prop => val[prop] !== undefined );
|
|
324
|
+
return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
|
|
326
325
|
}
|
|
327
326
|
|
|
328
327
|
/**
|
|
@@ -354,6 +353,7 @@ const typeCategories = {
|
|
|
354
353
|
boolean: [],
|
|
355
354
|
relation: [],
|
|
356
355
|
geo: [],
|
|
356
|
+
vector: [],
|
|
357
357
|
};
|
|
358
358
|
// Fill type categories with `cds.*` types
|
|
359
359
|
Object.keys( core ).forEach( (type) => {
|
|
@@ -399,6 +399,16 @@ function isBuiltinType( type ) {
|
|
|
399
399
|
return typeof type === 'string' && isInReservedNamespace( type );
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
+
/**
|
|
403
|
+
* Tell if a name is a magic variable
|
|
404
|
+
*
|
|
405
|
+
* @param {string} name
|
|
406
|
+
* @returns {boolean}
|
|
407
|
+
*/
|
|
408
|
+
function isMagicVariable( name ) {
|
|
409
|
+
return typeof name === 'string' && Object.prototype.hasOwnProperty.call(magicVariables, name);
|
|
410
|
+
}
|
|
411
|
+
|
|
402
412
|
/**
|
|
403
413
|
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
|
|
404
414
|
* `definitions` of the XSN model as well as to `$builtins`.
|
|
@@ -411,9 +421,14 @@ function initBuiltins( model ) {
|
|
|
411
421
|
// namespace:"cds" stores the builtins ---
|
|
412
422
|
const cds = createNamespace( 'cds', 'reserved' );
|
|
413
423
|
model.definitions.cds = cds;
|
|
424
|
+
|
|
414
425
|
// Also add the core artifacts to model.definitions`
|
|
415
|
-
|
|
426
|
+
const c = { ...core };
|
|
427
|
+
if (!isBetaEnabled( model.options, 'vectorType' ))
|
|
428
|
+
delete c.Vector;
|
|
429
|
+
model.$builtins = env( c, 'cds.', cds );
|
|
416
430
|
model.$builtins.cds = cds;
|
|
431
|
+
|
|
417
432
|
// namespace:"cds.hana" stores HANA-specific builtins ---
|
|
418
433
|
const hana = createNamespace( 'cds.hana', 'reserved' );
|
|
419
434
|
model.definitions['cds.hana'] = hana;
|
|
@@ -534,5 +549,6 @@ module.exports = {
|
|
|
534
549
|
isAnnotationExpression,
|
|
535
550
|
isInReservedNamespace,
|
|
536
551
|
isBuiltinType,
|
|
552
|
+
isMagicVariable,
|
|
537
553
|
isGeoTypeName,
|
|
538
554
|
};
|
package/lib/compiler/checks.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
forEachDefinition,
|
|
16
16
|
forEachMember,
|
|
17
17
|
forEachMemberRecursively,
|
|
18
|
+
isDeprecatedEnabled,
|
|
18
19
|
} = require('../base/model');
|
|
19
20
|
const { CompilerAssertion } = require('../base/error');
|
|
20
21
|
const { typeParameters } = require('./builtins');
|
|
@@ -153,13 +154,18 @@ function check( model ) {
|
|
|
153
154
|
while (effectiveType?.enum)
|
|
154
155
|
effectiveType = (effectiveType._origin || effectiveType.type?._artifact)?._effectiveType;
|
|
155
156
|
|
|
156
|
-
if (!effectiveType) {
|
|
157
|
-
return; // e.g. illegal definition references, cycles,
|
|
157
|
+
if (!effectiveType || (effectiveType.type && !effectiveType.type._artifact)) {
|
|
158
|
+
return; // e.g. illegal definition references, cycles, unknown artifacts, …
|
|
158
159
|
}
|
|
159
160
|
else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
// Special case for deprecated flag "ignore specified elements": The `type` property
|
|
162
|
+
// is lost in columns, but `length`,… are kept -> mismatch. This behavior is the
|
|
163
|
+
// same as in cds-compiler v3. See #12169 for details.
|
|
164
|
+
if (!isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' )) {
|
|
165
|
+
error( 'type-missing-type', [ art.location, user ],
|
|
166
|
+
{ otherprop: 'type', prop: actualParams[0] },
|
|
167
|
+
'Missing $(OTHERPROP) property next to $(PROP)' );
|
|
168
|
+
}
|
|
163
169
|
return;
|
|
164
170
|
}
|
|
165
171
|
|
|
@@ -815,6 +821,9 @@ function check( model ) {
|
|
|
815
821
|
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
816
822
|
// adapted, etc.
|
|
817
823
|
function checkAnnotationAssignment1( art, anno ) {
|
|
824
|
+
if (art.$contains?.$annotation)
|
|
825
|
+
checkAnnotationExpressions( anno, art );
|
|
826
|
+
|
|
818
827
|
// Sanity checks (ignore broken assignments)
|
|
819
828
|
if (!anno.name?.id)
|
|
820
829
|
return;
|
|
@@ -882,7 +891,6 @@ function check( model ) {
|
|
|
882
891
|
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
|
|
883
892
|
{ '#': 'std', anno: anno.name.id } );
|
|
884
893
|
}
|
|
885
|
-
|
|
886
894
|
return;
|
|
887
895
|
}
|
|
888
896
|
|
|
@@ -890,6 +898,22 @@ function check( model ) {
|
|
|
890
898
|
checkValueAssignableTo( anno, anno, elementDecl, art );
|
|
891
899
|
}
|
|
892
900
|
|
|
901
|
+
/**
|
|
902
|
+
* Check the expressions inside annotations.
|
|
903
|
+
*/
|
|
904
|
+
function checkAnnotationExpressions( anno, art ) {
|
|
905
|
+
if (anno.$tokenTexts) {
|
|
906
|
+
checkGenericExpression( anno, art );
|
|
907
|
+
}
|
|
908
|
+
else if (anno.literal === 'array') {
|
|
909
|
+
anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
|
|
910
|
+
}
|
|
911
|
+
else if (anno.literal === 'struct') {
|
|
912
|
+
const struct = Object.values(anno.struct);
|
|
913
|
+
struct.forEach(val => checkAnnotationExpressions( val, art ));
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
893
917
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
894
918
|
// 'val') is potentially assignable to element 'element'. Complain on 'loc'
|
|
895
919
|
// if not
|
package/lib/compiler/define.js
CHANGED
|
@@ -940,6 +940,8 @@ function define( model ) {
|
|
|
940
940
|
initExprForQuery( col.value, parent );
|
|
941
941
|
initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
|
|
942
942
|
}
|
|
943
|
+
|
|
944
|
+
initItemsLinks( col, parent._block );
|
|
943
945
|
}
|
|
944
946
|
|
|
945
947
|
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
@@ -999,15 +1001,7 @@ function define( model ) {
|
|
|
999
1001
|
// TODO: split extend from init
|
|
1000
1002
|
const main = parent._main || parent;
|
|
1001
1003
|
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
1002
|
-
let obj = construct;
|
|
1003
|
-
let { items } = obj;
|
|
1004
|
-
while (items) {
|
|
1005
|
-
setLink( items, '_outer', obj );
|
|
1006
|
-
setLink( items, '_parent', obj._parent );
|
|
1007
|
-
setLink( items, '_block', block );
|
|
1008
|
-
obj = items;
|
|
1009
|
-
items = obj.items;
|
|
1010
|
-
}
|
|
1004
|
+
let obj = initItemsLinks( construct, block );
|
|
1011
1005
|
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1012
1006
|
obj.targetAspect = obj.target;
|
|
1013
1007
|
delete obj.target;
|
|
@@ -1172,6 +1166,14 @@ function define( model ) {
|
|
|
1172
1166
|
elem.$syntax = 'calc';
|
|
1173
1167
|
// TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
|
|
1174
1168
|
createAndLinkCalcDepElement( elem );
|
|
1169
|
+
|
|
1170
|
+
// Special case (hack) for calculated elements that use associations+filter:
|
|
1171
|
+
// See "Notes on `$filtered`" in `ExposingAssocWithFilter.md` for details.
|
|
1172
|
+
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
|
|
1173
|
+
delete elem.type;
|
|
1174
|
+
delete elem.on;
|
|
1175
|
+
delete elem.target;
|
|
1176
|
+
}
|
|
1175
1177
|
}
|
|
1176
1178
|
}
|
|
1177
1179
|
|
|
@@ -1199,6 +1201,26 @@ function define( model ) {
|
|
|
1199
1201
|
}
|
|
1200
1202
|
}
|
|
1201
1203
|
|
|
1204
|
+
/**
|
|
1205
|
+
* Initialize artifact links inside `obj.items` (for nested ones as well).
|
|
1206
|
+
* Does nothing, it `obj.items` does not exist.
|
|
1207
|
+
*
|
|
1208
|
+
* @param {XSN.Artifact} obj
|
|
1209
|
+
* @param {object} block
|
|
1210
|
+
* @return {XSN.Artifact}
|
|
1211
|
+
*/
|
|
1212
|
+
function initItemsLinks( obj, block ) {
|
|
1213
|
+
let { items } = obj;
|
|
1214
|
+
while (items) {
|
|
1215
|
+
setLink( items, '_outer', obj );
|
|
1216
|
+
setLink( items, '_parent', obj._parent );
|
|
1217
|
+
setLink( items, '_block', block );
|
|
1218
|
+
obj = items;
|
|
1219
|
+
items = obj.items;
|
|
1220
|
+
}
|
|
1221
|
+
return obj;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1202
1224
|
// To be reworked -------------------------------------------------------------
|
|
1203
1225
|
|
|
1204
1226
|
// TODO: is only necessary for extensions - make special for extend/annotate
|
package/lib/compiler/populate.js
CHANGED
|
@@ -100,6 +100,8 @@ function populate( model ) {
|
|
|
100
100
|
? 'Composition'
|
|
101
101
|
: true;
|
|
102
102
|
const redirectInSubQueries = isDeprecatedEnabled( options, '_redirectInSubQueries' );
|
|
103
|
+
const ignoreSpecifiedElements
|
|
104
|
+
= isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' );
|
|
103
105
|
|
|
104
106
|
forEachDefinition( model, traverseElementEnvironments );
|
|
105
107
|
while (newAutoExposed.length) {
|
|
@@ -542,7 +544,9 @@ function populate( model ) {
|
|
|
542
544
|
ielem[prop].$priority = 'annotate';
|
|
543
545
|
wasAnnotated = true;
|
|
544
546
|
}
|
|
545
|
-
else if (typePropertiesFromSpecifiedElements[prop]) {
|
|
547
|
+
else if (typePropertiesFromSpecifiedElements[prop] && !ignoreSpecifiedElements) {
|
|
548
|
+
// If ignoreSpecifiedElements is set, we ignore type properties of specified elements,
|
|
549
|
+
// similar to how it was done in cds-compiler v3. Only annotations are copied.
|
|
546
550
|
if (!ielem.typeProps$)
|
|
547
551
|
setLink( ielem, 'typeProps$', Object.create( null ) );
|
|
548
552
|
// Note: At this point in time, effectiveType() was likely not called on the
|
package/lib/compiler/resolve.js
CHANGED
|
@@ -519,8 +519,8 @@ function resolve( model ) {
|
|
|
519
519
|
if (art.value) {
|
|
520
520
|
if (art.$syntax === 'calc')
|
|
521
521
|
checkCalculatedElement( art );
|
|
522
|
-
else if (art.type && !art.$inferred
|
|
523
|
-
|
|
522
|
+
else if (art.type && !art.$inferred)
|
|
523
|
+
checkColumnTypeCast( art );
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
resolveExprInAnnotations( art );
|
|
@@ -557,10 +557,12 @@ function resolve( model ) {
|
|
|
557
557
|
|
|
558
558
|
// Check explicit types: If either side has one, so must the other.
|
|
559
559
|
const sType = specifiedElement.type?._artifact;
|
|
560
|
-
const
|
|
560
|
+
const iTypeArt = getInferredPropFromOrigin( 'type' )?._artifact;
|
|
561
|
+
const iType = iTypeArt || inferredElement;
|
|
561
562
|
|
|
562
563
|
// xor: could be missing a type;
|
|
563
564
|
// FIXME: The coding above returns incorrect iType for expand on associations
|
|
565
|
+
|
|
564
566
|
if (!specifiedElement.type && inferredElement.type) {
|
|
565
567
|
error( 'query-mismatched-element', [ specifiedElement.location, user ], {
|
|
566
568
|
'#': !specifiedElement.type ? 'missing' : 'extra', name: user.name.id, prop: 'type',
|
|
@@ -568,8 +570,12 @@ function resolve( model ) {
|
|
|
568
570
|
return;
|
|
569
571
|
}
|
|
570
572
|
// If specified type is `null`, type could not be resolved.
|
|
571
|
-
else if (sType && sType !== iType
|
|
572
|
-
|
|
573
|
+
else if (sType && sType !== iType &&
|
|
574
|
+
// Special case for $recompilation: allow one level of type indirection. See #12113.
|
|
575
|
+
(!model.options.$recompile || sType !== iType.type?._artifact)) {
|
|
576
|
+
const typeName = !iTypeArt && 'typeExtra' || // no inferred type prop
|
|
577
|
+
iType?.name && sType?.name && 'typeName' || // both types are named
|
|
578
|
+
'type'; // unknown type names
|
|
573
579
|
const othertype = typeName !== 'type' && iType || '';
|
|
574
580
|
error( 'query-mismatched-element', [
|
|
575
581
|
specifiedElement.type.location || specifiedElement.location, user,
|
|
@@ -815,24 +821,20 @@ function resolve( model ) {
|
|
|
815
821
|
else if (!allowedInKind.includes( parent.kind )) {
|
|
816
822
|
error( 'def-invalid-calc-elem', loc, { '#': parent.kind } );
|
|
817
823
|
}
|
|
818
|
-
else if (effectiveType( art )?.elements) {
|
|
824
|
+
else if (effectiveType( art )?.elements && !art.$inferred) {
|
|
819
825
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
825
|
-
}
|
|
826
|
+
if (art.type)
|
|
827
|
+
error( 'type-unexpected-structure', [ art.type.location, art ], { '#': 'calc' } );
|
|
828
|
+
else
|
|
829
|
+
error( 'ref-unexpected-structured', [ art.value.location, art ], { '#': 'expr' } );
|
|
826
830
|
}
|
|
827
|
-
else if (effectiveType( art )?.items) {
|
|
831
|
+
else if (effectiveType( art )?.items && !art.$inferred) {
|
|
828
832
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
} );
|
|
835
|
-
}
|
|
833
|
+
const isCast = art.type?.$inferred === 'cast';
|
|
834
|
+
error( 'type-unexpected-many', [ (art.type || art.value).location, art ], {
|
|
835
|
+
'#': (!art.type && 'calc-implicit') || (isCast && 'calc-cast') || 'calc',
|
|
836
|
+
elemref: art.type ? undefined : { ref: art.value.path },
|
|
837
|
+
} );
|
|
836
838
|
}
|
|
837
839
|
else {
|
|
838
840
|
const noTruthyAllowed = [ 'localized', 'key', 'virtual' ];
|
|
@@ -847,7 +849,7 @@ function resolve( model ) {
|
|
|
847
849
|
}
|
|
848
850
|
}
|
|
849
851
|
|
|
850
|
-
function
|
|
852
|
+
function checkColumnTypeCast( art ) {
|
|
851
853
|
const elem = (art.value.op?.val === 'cast')
|
|
852
854
|
? art.value.args[0]?._artifact
|
|
853
855
|
: art.value._artifact;
|
|
@@ -856,6 +858,9 @@ function resolve( model ) {
|
|
|
856
858
|
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'to-structure' } );
|
|
857
859
|
else if (elem.elements) // TODO: calc elements
|
|
858
860
|
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-structure' } );
|
|
861
|
+
else if (elem.target && !art.type._artifact?.target)
|
|
862
|
+
// allow cast to association -> that is already checked and denied elsewhere
|
|
863
|
+
error( 'type-invalid-cast', [ art.type.location, art ], { '#': 'from-assoc' } );
|
|
859
864
|
}
|
|
860
865
|
}
|
|
861
866
|
|
package/lib/compiler/shared.js
CHANGED
|
@@ -105,7 +105,7 @@ function fns( model ) {
|
|
|
105
105
|
dynamic: modelBuiltinsOrDefinitions,
|
|
106
106
|
navigation: environment,
|
|
107
107
|
notFound: undefinedDefinition,
|
|
108
|
-
accept:
|
|
108
|
+
accept: acceptQuerySource,
|
|
109
109
|
noDep: '', // dependency special for from
|
|
110
110
|
},
|
|
111
111
|
type: {
|
|
@@ -335,9 +335,7 @@ function fns( model ) {
|
|
|
335
335
|
if (expr.args && !first) {
|
|
336
336
|
const args = Array.isArray( expr.args ) ? expr.args : Object.values( expr.args );
|
|
337
337
|
// TODO: re-think $expected
|
|
338
|
-
first =
|
|
339
|
-
? callback.traverse( args, exprCtx, user, callback )
|
|
340
|
-
: args.find( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
338
|
+
first = args.find( e => traverseExpr( e, exprCtx, user, callback ) );
|
|
341
339
|
}
|
|
342
340
|
|
|
343
341
|
first ??= expr.suffix && // fn( … ) OVER …
|
|
@@ -1281,15 +1279,17 @@ function fns( model ) {
|
|
|
1281
1279
|
return false;
|
|
1282
1280
|
}
|
|
1283
1281
|
|
|
1284
|
-
function
|
|
1282
|
+
function acceptQuerySource( art, user, ref ) { // for FROM
|
|
1285
1283
|
const { path, scope } = ref;
|
|
1286
1284
|
// see getPathItem(): how many path items are for the main artifact ref?
|
|
1287
1285
|
const artItemsCount = (typeof scope === 'number' && scope) || (scope ? 1 : path.length);
|
|
1288
|
-
// at least the last main definition should be an entity
|
|
1286
|
+
// at least the last main definition should be an entity or an
|
|
1287
|
+
// event (if the user is also an event)
|
|
1289
1288
|
// an additional check for target would need effectiveType()
|
|
1290
1289
|
const source = path[artItemsCount - 1]._artifact;
|
|
1291
|
-
|
|
1292
|
-
|
|
1290
|
+
const variant = (user._main.kind === 'event') ? 'event' : 'std';
|
|
1291
|
+
if (source.kind !== 'entity' && !acceptEventProjectionSource( source, user )) {
|
|
1292
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
|
|
1293
1293
|
return false;
|
|
1294
1294
|
}
|
|
1295
1295
|
if (source === art)
|
|
@@ -1297,10 +1297,20 @@ function fns( model ) {
|
|
|
1297
1297
|
const assoc = Functions.effectiveType( art );
|
|
1298
1298
|
if (assoc.target)
|
|
1299
1299
|
return art; // TODO: use target here
|
|
1300
|
-
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null );
|
|
1300
|
+
signalNotFound( 'ref-invalid-source', [ ref.location, user ], null, { '#': variant } );
|
|
1301
1301
|
return false;
|
|
1302
1302
|
}
|
|
1303
1303
|
|
|
1304
|
+
function acceptEventProjectionSource( source, user ) {
|
|
1305
|
+
if (user._main.kind !== 'event' || (source.kind !== 'event' && source.kind !== 'type'))
|
|
1306
|
+
return false;
|
|
1307
|
+
const effectiveType = Functions.effectiveType( source );
|
|
1308
|
+
if (!effectiveType)
|
|
1309
|
+
return false;
|
|
1310
|
+
const { kind } = effectiveType;
|
|
1311
|
+
return (kind === 'entity' || kind === 'event' || (kind === 'type' && effectiveType.elements));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1304
1314
|
function acceptTypeOrElement( art, user, ref ) { // for type
|
|
1305
1315
|
// was ['action', 'function'].includes( user._parent?.kind ))
|
|
1306
1316
|
while (user._outer)
|