@sap/cds-compiler 4.4.4 → 4.6.0
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 +88 -0
- package/bin/cdsc.js +18 -11
- package/bin/cdsv2m.js +7 -5
- package/doc/CHANGELOG_BETA.md +22 -0
- package/lib/api/main.js +306 -144
- package/lib/api/options.js +18 -6
- package/lib/api/validate.js +1 -1
- package/lib/base/message-registry.js +45 -10
- package/lib/base/messages.js +33 -16
- package/lib/base/model.js +4 -0
- package/lib/base/optionProcessorHelper.js +45 -176
- package/lib/checks/annotationsOData.js +49 -0
- package/lib/checks/elements.js +32 -34
- package/lib/checks/enricher.js +39 -3
- package/lib/checks/validator.js +8 -7
- package/lib/compiler/assert-consistency.js +40 -17
- package/lib/compiler/builtins.js +30 -53
- package/lib/compiler/checks.js +46 -14
- package/lib/compiler/cycle-detector.js +1 -4
- package/lib/compiler/define.js +35 -10
- package/lib/compiler/extend.js +21 -7
- package/lib/compiler/generate.js +3 -0
- package/lib/compiler/populate.js +5 -1
- package/lib/compiler/propagator.js +46 -9
- package/lib/compiler/resolve.js +94 -35
- package/lib/compiler/shared.js +60 -33
- package/lib/compiler/tweak-assocs.js +188 -92
- package/lib/compiler/utils.js +11 -1
- package/lib/edm/annotations/edmJson.js +41 -66
- package/lib/edm/annotations/genericTranslation.js +27 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -3
- package/lib/edm/csn2edm.js +28 -11
- package/lib/edm/edmInboundChecks.js +58 -15
- package/lib/edm/edmPreprocessor.js +12 -16
- package/lib/edm/edmUtils.js +5 -2
- package/lib/gen/Dictionary.json +10 -0
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +15 -2
- package/lib/gen/language.tokens +1 -0
- package/lib/gen/languageParser.js +6557 -5618
- package/lib/json/from-csn.js +4 -5
- package/lib/json/to-csn.js +29 -4
- package/lib/language/antlrParser.js +19 -1
- package/lib/language/errorStrategy.js +28 -7
- package/lib/language/genericAntlrParser.js +118 -24
- package/lib/language/textUtils.js +16 -0
- 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 +20 -14
- package/lib/model/revealInternalProperties.js +5 -2
- package/lib/optionProcessor.js +23 -22
- package/lib/render/manageConstraints.js +13 -29
- package/lib/render/toCdl.js +47 -26
- package/lib/render/toHdbcds.js +63 -42
- package/lib/render/toRename.js +6 -10
- package/lib/render/toSql.js +71 -117
- package/lib/render/utils/common.js +41 -6
- package/lib/transform/.eslintrc.json +9 -1
- package/lib/transform/addTenantFields.js +228 -0
- package/lib/transform/db/applyTransformations.js +57 -4
- package/lib/transform/db/assertUnique.js +4 -4
- package/lib/transform/db/backlinks.js +13 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/expansion.js +24 -3
- package/lib/transform/db/flattening.js +70 -71
- package/lib/transform/db/killAnnotations.js +37 -0
- package/lib/transform/db/rewriteCalculatedElements.js +46 -6
- 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 +26 -55
- package/lib/transform/forRelationalDB.js +38 -18
- 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 +45 -11
- package/lib/transform/universalCsn/coreComputed.js +1 -1
- package/lib/transform/universalCsn/universalCsnEnricher.js +4 -4
- package/package.json +7 -6
|
@@ -126,6 +126,7 @@ function assertConsistency( model, stage ) {
|
|
|
126
126
|
'@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version'
|
|
127
127
|
'$withLocalized',
|
|
128
128
|
'$sources',
|
|
129
|
+
'tokenStream',
|
|
129
130
|
],
|
|
130
131
|
instanceOf: XsnSource,
|
|
131
132
|
},
|
|
@@ -196,13 +197,14 @@ function assertConsistency( model, stage ) {
|
|
|
196
197
|
schema: {
|
|
197
198
|
kind: { test: isString, enum: [ '$magicVariables' ] },
|
|
198
199
|
elements: {
|
|
199
|
-
// Do not use "normal" definitions spec because
|
|
200
|
+
// Do not use "normal" definitions spec because these artifacts
|
|
200
201
|
// are missing the location property
|
|
201
202
|
test: isDictionary( definition ),
|
|
202
203
|
requires: [ 'kind', 'name' ],
|
|
203
204
|
optional: [
|
|
204
205
|
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
|
|
205
|
-
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
206
|
+
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
|
|
207
|
+
'$calcDepElement', '$filtered', '_parent',
|
|
206
208
|
],
|
|
207
209
|
schema: {
|
|
208
210
|
kind: { test: isString, enum: [ 'builtin' ] },
|
|
@@ -249,10 +251,13 @@ function assertConsistency( model, stage ) {
|
|
|
249
251
|
elements$: { kind: true, enumerable: false, test: TODO },
|
|
250
252
|
enum$: { kind: true, enumerable: false, test: TODO },
|
|
251
253
|
typeProps$: { kind: true, enumerable: false, test: TODO },
|
|
254
|
+
// helper property for faster processing:
|
|
255
|
+
$contains: { kind: true, test: TODO },
|
|
252
256
|
actions: { kind: true, inherits: 'definitions' },
|
|
253
257
|
enum: { kind: true, inherits: 'definitions' },
|
|
254
258
|
foreignKeys: { kind: true, inherits: 'definitions', instanceOf: 'ignore' },
|
|
255
259
|
$keysNavigation: { kind: true, test: TODO },
|
|
260
|
+
$filtered: { kind: true, inherits: 'value' }, // for assoc+filter
|
|
256
261
|
params: { kind: true, inherits: 'definitions' },
|
|
257
262
|
_extendType: { kind: true, test: TODO },
|
|
258
263
|
mixin: { inherits: 'definitions' },
|
|
@@ -264,16 +269,18 @@ function assertConsistency( model, stage ) {
|
|
|
264
269
|
requires: [ 'op', 'location', 'args' ],
|
|
265
270
|
optional: [
|
|
266
271
|
'quantifier', 'orderBy', 'limit', 'name', '$parens', 'kind',
|
|
267
|
-
'_origin',
|
|
272
|
+
'_origin', '$contains', // TODO tmp, see TODO in getOriginRaw()
|
|
268
273
|
'_parent', '_main', '_leadingQuery', '_effectiveType', '$effectiveSeqNo', // in FROM
|
|
274
|
+
'_$next', // parsing error: tableTerm with UNION on rhs.
|
|
269
275
|
],
|
|
270
276
|
},
|
|
271
277
|
select: { // sub query
|
|
272
278
|
requires: [ 'op', 'location', 'from' ],
|
|
273
279
|
optional: [
|
|
274
280
|
'name', '$parens', 'quantifier', 'mixin', 'excludingDict', 'columns', 'elements', '_deps',
|
|
281
|
+
'$calcDepElement',
|
|
275
282
|
'where', 'groupBy', 'having', 'orderBy', '$orderBy', 'limit', '$limit',
|
|
276
|
-
'_origin', '_block',
|
|
283
|
+
'_origin', '_block', '$contains',
|
|
277
284
|
'_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
|
|
278
285
|
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
|
|
279
286
|
],
|
|
@@ -396,7 +403,7 @@ function assertConsistency( model, stage ) {
|
|
|
396
403
|
'annotate', 'extend', '$column',
|
|
397
404
|
'select', '$join', 'mixin',
|
|
398
405
|
'source', 'namespace', 'using',
|
|
399
|
-
'$tableAlias', '$navElement',
|
|
406
|
+
'$tableAlias', '$navElement', '$calculation', '$annotation',
|
|
400
407
|
'builtin', // magic variables
|
|
401
408
|
],
|
|
402
409
|
},
|
|
@@ -473,7 +480,8 @@ function assertConsistency( model, stage ) {
|
|
|
473
480
|
optional: [
|
|
474
481
|
'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
|
|
475
482
|
// expressions as annotation values
|
|
476
|
-
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type',
|
|
483
|
+
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
|
|
484
|
+
'scale', 'srid', 'length', 'precision',
|
|
477
485
|
],
|
|
478
486
|
// TODO: restrict path to #simplePath
|
|
479
487
|
},
|
|
@@ -505,7 +513,8 @@ function assertConsistency( model, stage ) {
|
|
|
505
513
|
optional: [
|
|
506
514
|
'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
|
|
507
515
|
// annotation values
|
|
508
|
-
'$tokenTexts',
|
|
516
|
+
'$tokenTexts', 'kind', '_outer',
|
|
517
|
+
'_effectiveType', '$effectiveSeqNo', '_origin', '_deps',
|
|
509
518
|
// CSN parser may let these properties slip through to XSN, even if input is invalid.
|
|
510
519
|
'args', 'op', 'func', 'suffix',
|
|
511
520
|
],
|
|
@@ -554,6 +563,7 @@ function assertConsistency( model, stage ) {
|
|
|
554
563
|
'elements', 'cardinality', 'target', 'on', 'foreignKeys', 'items',
|
|
555
564
|
'_outer', '_effectiveType', '$effectiveSeqNo', 'notNull', '_parent',
|
|
556
565
|
'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
|
|
566
|
+
'$calcDepElement',
|
|
557
567
|
'$syntax', '_extensions',
|
|
558
568
|
'_status', '_redirected',
|
|
559
569
|
...typeProperties,
|
|
@@ -563,20 +573,20 @@ function assertConsistency( model, stage ) {
|
|
|
563
573
|
artifacts: { kind: true, inherits: 'definitions', test: isDictionary( inDefinitions ) },
|
|
564
574
|
_subArtifacts: { kind: true, inherits: 'definitions', test: isDictionary( inDefinitions ) },
|
|
565
575
|
blocks: { kind: true, test: TODO }, // TODO: make it $blocks ?
|
|
566
|
-
length: { kind: true,
|
|
567
|
-
precision: { kind: true,
|
|
568
|
-
scale: { kind: true,
|
|
569
|
-
srid: { kind: true,
|
|
576
|
+
length: { kind: true, test: isNumberVal }, // for number is to be checked in resolver
|
|
577
|
+
precision: { kind: true, test: isNumberVal },
|
|
578
|
+
scale: { kind: true, test: isNumberVal, also: [ 'floating', 'variable' ] },
|
|
579
|
+
srid: { kind: true, test: isNumberVal },
|
|
570
580
|
localized: { kind: true, test: locationVal() },
|
|
571
581
|
cardinality: {
|
|
572
582
|
kind: true,
|
|
573
583
|
requires: [ 'location' ],
|
|
574
584
|
optional: [ 'sourceMin', 'sourceMax', 'targetMin', 'targetMax' ],
|
|
575
585
|
},
|
|
576
|
-
sourceMin: { test:
|
|
577
|
-
sourceMax: { test:
|
|
578
|
-
targetMin: { test:
|
|
579
|
-
targetMax: { test:
|
|
586
|
+
sourceMin: { test: isNumberVal },
|
|
587
|
+
sourceMax: { test: isNumberVal, also: [ '*' ] },
|
|
588
|
+
targetMin: { test: isNumberVal },
|
|
589
|
+
targetMax: { test: isNumberVal, also: [ '*' ] },
|
|
580
590
|
default: { kind: true, inherits: 'value' },
|
|
581
591
|
$typeArgs: { parser: true, kind: true, test: TODO },
|
|
582
592
|
$tableAliases: { kind: true, test: TODO }, // containing $self outside queries
|
|
@@ -585,13 +595,18 @@ function assertConsistency( model, stage ) {
|
|
|
585
595
|
_service: { kind: true, test: TODO },
|
|
586
596
|
_main: { kind: true, test: TODO },
|
|
587
597
|
_user: { kind: true, test: TODO },
|
|
598
|
+
// - on a path item with a filter condition to the user of the ref (not nested)
|
|
599
|
+
// - on a JOIN node to the query (TODO: _outer?)
|
|
588
600
|
_artifact: { test: TODO },
|
|
589
601
|
_navigation: { test: TODO },
|
|
590
602
|
_effectiveType: { kind: true, test: TODO },
|
|
591
603
|
$effectiveSeqNo: { kind: true, test: isNumber },
|
|
592
604
|
_joinParent: { test: TODO },
|
|
593
605
|
$joinArgsIndex: { test: isNumber },
|
|
594
|
-
_outer: { test: TODO }, // for
|
|
606
|
+
_outer: { test: TODO }, // for items
|
|
607
|
+
// - on an array item to the array elem/type/item (nested)
|
|
608
|
+
// - on an anonymous aspect to the composition element
|
|
609
|
+
// - on an annotation assignment to the annotatee
|
|
595
610
|
$queries: {
|
|
596
611
|
kind: [ 'entity', 'event' ],
|
|
597
612
|
test: isArray(),
|
|
@@ -605,7 +620,7 @@ function assertConsistency( model, stage ) {
|
|
|
605
620
|
],
|
|
606
621
|
optional: [
|
|
607
622
|
'_effectiveType', '$effectiveSeqNo', '$parens',
|
|
608
|
-
'_deps', '$expand',
|
|
623
|
+
'_deps', '$calcDepElement', '$expand', '$contains',
|
|
609
624
|
// query specific
|
|
610
625
|
'where', 'columns', 'mixin', 'quantifier', 'offset',
|
|
611
626
|
'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
|
|
@@ -628,6 +643,9 @@ function assertConsistency( model, stage ) {
|
|
|
628
643
|
_annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
|
|
629
644
|
_extension: { kind: true, test: TODO }, // on artifact to its "super extend/annotate" statement
|
|
630
645
|
_deps: { kind: true, test: TODO }, // for cyclic calculation
|
|
646
|
+
// a fake element for cyclic dependency detection: e.g. dependencies to target entities.
|
|
647
|
+
// dependants don't only depend on the calc element, but on this element as well.
|
|
648
|
+
$calcDepElement: { kind: true, test: TODO },
|
|
631
649
|
_scc: { kind: true, test: TODO }, // for cyclic calculation
|
|
632
650
|
_sccCaller: { kind: true, test: TODO }, // for cyclic calculation
|
|
633
651
|
_status: { kind: true, test: TODO }, // TODO: $status
|
|
@@ -703,6 +721,7 @@ function assertConsistency( model, stage ) {
|
|
|
703
721
|
$extra: { parser: true, test: TODO }, // for unexpected properties in CSN
|
|
704
722
|
$withLocalized: { test: isBoolean },
|
|
705
723
|
$sources: { parser: true, test: isArray( isString ) },
|
|
724
|
+
tokenStream: { parser: true, test: TODO },
|
|
706
725
|
$expected: { parser: true, test: isOneOf( [ 'approved-exists', 'exists' ] ) },
|
|
707
726
|
$messageFunctions: { test: TODO },
|
|
708
727
|
$functions: { test: TODO },
|
|
@@ -958,6 +977,10 @@ function assertConsistency( model, stage ) {
|
|
|
958
977
|
throw new InternalConsistencyError( `Expected boolean or null${ at( [ node, parent ], prop ) }` );
|
|
959
978
|
}
|
|
960
979
|
|
|
980
|
+
function isNumberVal() {
|
|
981
|
+
return locationVal( isNumber );
|
|
982
|
+
}
|
|
983
|
+
|
|
961
984
|
function isNumber( node, parent, prop, spec ) {
|
|
962
985
|
if (spec.also && spec.also.includes( node ))
|
|
963
986
|
return;
|
package/lib/compiler/builtins.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
const { builtinLocation } = require('../base/location');
|
|
11
11
|
const { setLink: setProp } = require('./utils');
|
|
12
|
+
const { isBetaEnabled } = require('../base/model');
|
|
12
13
|
|
|
13
14
|
// TODO: make type parameters a dict
|
|
14
15
|
const core = {
|
|
@@ -31,6 +32,7 @@ const core = {
|
|
|
31
32
|
Timestamp: { category: 'dateTime' },
|
|
32
33
|
Boolean: { category: 'boolean' },
|
|
33
34
|
UUID: { category: 'string' },
|
|
35
|
+
Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
|
|
34
36
|
Association: { internal: true, category: 'relation' },
|
|
35
37
|
Composition: { internal: true, category: 'relation' },
|
|
36
38
|
};
|
|
@@ -190,14 +192,14 @@ function compileArg( src ) {
|
|
|
190
192
|
*/
|
|
191
193
|
const magicVariables = {
|
|
192
194
|
$user: {
|
|
193
|
-
//
|
|
194
|
-
elements: { id: {}, locale: {}
|
|
195
|
+
// always available
|
|
196
|
+
elements: { id: {}, locale: {} },
|
|
195
197
|
// Allow $user.<any>
|
|
196
198
|
$uncheckedElements: true,
|
|
197
199
|
// Allow shortcut in CDL: `$user` becomes `$user.id` in CSN.
|
|
198
200
|
$autoElement: 'id',
|
|
199
201
|
},
|
|
200
|
-
$at: {
|
|
202
|
+
$at: {
|
|
201
203
|
elements: {
|
|
202
204
|
from: {}, to: {},
|
|
203
205
|
},
|
|
@@ -211,7 +213,8 @@ const magicVariables = {
|
|
|
211
213
|
// Require that elements are accessed, i.e. no $valid, only $valid.<element>.
|
|
212
214
|
$requireElementAccess: true,
|
|
213
215
|
},
|
|
214
|
-
$now: {},
|
|
216
|
+
$now: {},
|
|
217
|
+
$tenant: { $requiresBetaFlag: 'tenantVariable' },
|
|
215
218
|
$session: {
|
|
216
219
|
// In ABAP CDS session variables are accessed in a generic way via
|
|
217
220
|
// the pseudo variable $session.
|
|
@@ -229,7 +232,7 @@ const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})
|
|
|
229
232
|
// eslint-disable-next-line max-len
|
|
230
233
|
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
|
|
231
234
|
// YYYY - MM - dd T HH : mm : ss . fraction TZD
|
|
232
|
-
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]
|
|
235
|
+
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]?\d+)?[ \t]*$/i;
|
|
233
236
|
|
|
234
237
|
/**
|
|
235
238
|
* Patterns for literal token tests and creation. The value is a map from the
|
|
@@ -318,9 +321,7 @@ function checkDate( year, month, day ) {
|
|
|
318
321
|
* Return whether JSON object `val` is a representation for an annotation expression
|
|
319
322
|
*/
|
|
320
323
|
function isAnnotationExpression( val ) {
|
|
321
|
-
|
|
322
|
-
// decided → just check truthy at the moment
|
|
323
|
-
return val['='] && xprInAnnoProperties.some( prop => val[prop] !== undefined );
|
|
324
|
+
return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
|
|
324
325
|
}
|
|
325
326
|
|
|
326
327
|
/**
|
|
@@ -352,6 +353,7 @@ const typeCategories = {
|
|
|
352
353
|
boolean: [],
|
|
353
354
|
relation: [],
|
|
354
355
|
geo: [],
|
|
356
|
+
vector: [],
|
|
355
357
|
};
|
|
356
358
|
// Fill type categories with `cds.*` types
|
|
357
359
|
Object.keys( core ).forEach( (type) => {
|
|
@@ -364,46 +366,10 @@ Object.keys( coreHana ).forEach( (type) => {
|
|
|
364
366
|
typeCategories[coreHana[type].category].push( `cds.hana.${ type }` );
|
|
365
367
|
} );
|
|
366
368
|
|
|
367
|
-
/** @param {string} typeName */
|
|
368
|
-
function isIntegerTypeName( typeName ) {
|
|
369
|
-
return typeCategories.integer.includes( typeName );
|
|
370
|
-
}
|
|
371
|
-
/** @param {string} typeName */
|
|
372
|
-
function isDecimalTypeName( typeName ) {
|
|
373
|
-
return typeCategories.decimal.includes( typeName );
|
|
374
|
-
}
|
|
375
|
-
/** @param {string} typeName */
|
|
376
|
-
function isNumericTypeName( typeName ) {
|
|
377
|
-
return isIntegerTypeName( typeName ) || isDecimalTypeName( typeName );
|
|
378
|
-
}
|
|
379
|
-
/** @param {string} typeName */
|
|
380
|
-
function isStringTypeName( typeName ) {
|
|
381
|
-
return typeCategories.string.includes( typeName );
|
|
382
|
-
}
|
|
383
|
-
/** @param {string} typeName */
|
|
384
|
-
function isDateOrTimeTypeName( typeName ) {
|
|
385
|
-
return typeCategories.dateTime.includes( typeName );
|
|
386
|
-
}
|
|
387
|
-
/** @param {string} typeName */
|
|
388
|
-
function isBooleanTypeName( typeName ) {
|
|
389
|
-
return typeCategories.boolean.includes( typeName );
|
|
390
|
-
}
|
|
391
|
-
/** @param {string} typeName */
|
|
392
|
-
function isBinaryTypeName( typeName ) {
|
|
393
|
-
return typeCategories.binary.includes( typeName );
|
|
394
|
-
}
|
|
395
369
|
/** @param {string} typeName */
|
|
396
370
|
function isGeoTypeName( typeName ) {
|
|
397
371
|
return typeCategories.geo.includes( typeName );
|
|
398
372
|
}
|
|
399
|
-
/**
|
|
400
|
-
* Whether the given type name is a relation, i.e. an association or composition.
|
|
401
|
-
*
|
|
402
|
-
* @param {string} typeName
|
|
403
|
-
*/
|
|
404
|
-
function isRelationTypeName( typeName ) {
|
|
405
|
-
return typeCategories.relation.includes( typeName );
|
|
406
|
-
}
|
|
407
373
|
|
|
408
374
|
/**
|
|
409
375
|
* Checks whether the given absolute path is inside a reserved namespace.
|
|
@@ -433,6 +399,16 @@ function isBuiltinType( type ) {
|
|
|
433
399
|
return typeof type === 'string' && isInReservedNamespace( type );
|
|
434
400
|
}
|
|
435
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
|
+
|
|
436
412
|
/**
|
|
437
413
|
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to
|
|
438
414
|
* `definitions` of the XSN model as well as to `$builtins`.
|
|
@@ -445,9 +421,14 @@ function initBuiltins( model ) {
|
|
|
445
421
|
// namespace:"cds" stores the builtins ---
|
|
446
422
|
const cds = createNamespace( 'cds', 'reserved' );
|
|
447
423
|
model.definitions.cds = cds;
|
|
424
|
+
|
|
448
425
|
// Also add the core artifacts to model.definitions`
|
|
449
|
-
|
|
426
|
+
const c = { ...core };
|
|
427
|
+
if (!isBetaEnabled( model.options, 'vectorType' ))
|
|
428
|
+
delete c.Vector;
|
|
429
|
+
model.$builtins = env( c, 'cds.', cds );
|
|
450
430
|
model.$builtins.cds = cds;
|
|
431
|
+
|
|
451
432
|
// namespace:"cds.hana" stores HANA-specific builtins ---
|
|
452
433
|
const hana = createNamespace( 'cds.hana', 'reserved' );
|
|
453
434
|
model.definitions['cds.hana'] = hana;
|
|
@@ -507,6 +488,9 @@ function initBuiltins( model ) {
|
|
|
507
488
|
model.$magicVariables = { kind: '$magicVariables', elements };
|
|
508
489
|
for (const id in builtins) {
|
|
509
490
|
const magic = builtins[id];
|
|
491
|
+
if (magic.$requiresBetaFlag && !isBetaEnabled( options, magic.$requiresBetaFlag ))
|
|
492
|
+
continue;
|
|
493
|
+
|
|
510
494
|
// TODO: rename to $builtinFunction
|
|
511
495
|
const art = {
|
|
512
496
|
kind: 'builtin', // TODO: $var
|
|
@@ -565,13 +549,6 @@ module.exports = {
|
|
|
565
549
|
isAnnotationExpression,
|
|
566
550
|
isInReservedNamespace,
|
|
567
551
|
isBuiltinType,
|
|
568
|
-
|
|
569
|
-
isDecimalTypeName,
|
|
570
|
-
isNumericTypeName,
|
|
571
|
-
isStringTypeName,
|
|
572
|
-
isDateOrTimeTypeName,
|
|
573
|
-
isBooleanTypeName,
|
|
574
|
-
isBinaryTypeName,
|
|
552
|
+
isMagicVariable,
|
|
575
553
|
isGeoTypeName,
|
|
576
|
-
isRelationTypeName,
|
|
577
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
|
|
|
@@ -182,12 +188,14 @@ function check( model ) {
|
|
|
182
188
|
} );
|
|
183
189
|
break; // Avoid spam: Only emit the first error.
|
|
184
190
|
}
|
|
185
|
-
else if (!typeParameters.expectedLiteralsFor[param].includes( art[param].
|
|
191
|
+
else if (!typeParameters.expectedLiteralsFor[param].includes( typeof art[param].val )) {
|
|
192
|
+
// TODO: this could be probably better done via syntax check (already for CSN input)
|
|
186
193
|
error( 'type-unexpected-argument', [ art[param].location, user ], {
|
|
187
194
|
'#': 'incorrect-type',
|
|
188
195
|
prop: param,
|
|
189
|
-
code: art[param].
|
|
196
|
+
code: typeof art[param].val,
|
|
190
197
|
names: typeParameters.expectedLiteralsFor[param],
|
|
198
|
+
// TODO: no double quote via $(NAMES), but see TODO above
|
|
191
199
|
} );
|
|
192
200
|
break; // Avoid spam: Only emit the first error.
|
|
193
201
|
}
|
|
@@ -422,8 +430,8 @@ function check( model ) {
|
|
|
422
430
|
// Max cardinalities must be a positive number or '*'
|
|
423
431
|
for (const prop of [ 'sourceMax', 'targetMax' ]) {
|
|
424
432
|
if (art.cardinality[prop]) {
|
|
425
|
-
const {
|
|
426
|
-
if (
|
|
433
|
+
const { val, location } = art.cardinality[prop];
|
|
434
|
+
if (val !== '*' && val <= 0) {
|
|
427
435
|
error( 'type-invalid-cardinality', [ location, art ],
|
|
428
436
|
{ '#': prop, prop: val, otherprop: '*' } );
|
|
429
437
|
}
|
|
@@ -630,24 +638,30 @@ function check( model ) {
|
|
|
630
638
|
}
|
|
631
639
|
|
|
632
640
|
function checkCalculatedElementValue( elem ) {
|
|
641
|
+
const isStored = elem.value.stored?.val;
|
|
633
642
|
visitExpression( elem.value, elem, (xpr, user) => {
|
|
634
643
|
// We only need to check artifact references. To avoid false positives and conflicts
|
|
635
644
|
// with $self comparison-checks, ignore bare $self.
|
|
636
645
|
const isArtRef = xpr._artifact && !(xpr.path?.length === 1 &&
|
|
637
646
|
xpr.path[0]._navigation?.kind === '$self');
|
|
638
647
|
if (isArtRef) {
|
|
639
|
-
const
|
|
648
|
+
const lastStep = xpr.path?.[xpr.path.length - 1];
|
|
649
|
+
const sourceLoc = lastStep.location || xpr.location;
|
|
640
650
|
checkExpressionNotVirtual( xpr, user );
|
|
641
651
|
// For inferred (e.g. included) calc elements, this error is already emitted at the origin.
|
|
642
652
|
// And users can't change structured to non-structured elements.
|
|
643
653
|
if (!elem.$inferred && xpr._artifact._effectiveType?.elements) {
|
|
644
654
|
error( 'ref-unexpected-structured', [ sourceLoc, elem ], { '#': 'expr' } );
|
|
645
655
|
}
|
|
646
|
-
else if (xpr._artifact.target !== undefined) {
|
|
647
|
-
|
|
656
|
+
else if (xpr._artifact.target !== undefined && (!lastStep.where || isStored)) {
|
|
657
|
+
// Allow using an association _with filter_, but only for on-read calculated elements.
|
|
658
|
+
// TODO: Also allow bare unmanaged association references and remove beta.
|
|
659
|
+
const variant = (isStored && lastStep.where && 'assoc-stored') ||
|
|
660
|
+
(isComposition( model, xpr._artifact ) && 'expr-comp') ||
|
|
661
|
+
'expr';
|
|
648
662
|
error( 'ref-unexpected-assoc', [ sourceLoc, elem ], { '#': variant } );
|
|
649
663
|
}
|
|
650
|
-
else if (xpr._artifact.localized?.val &&
|
|
664
|
+
else if (xpr._artifact.localized?.val && isStored) {
|
|
651
665
|
error( 'ref-unexpected-localized', [ sourceLoc, elem ], { '#': 'calc' } );
|
|
652
666
|
}
|
|
653
667
|
}
|
|
@@ -807,6 +821,9 @@ function check( model ) {
|
|
|
807
821
|
// Has been slightly adapted for model.vocabularies but comments need to be
|
|
808
822
|
// adapted, etc.
|
|
809
823
|
function checkAnnotationAssignment1( art, anno ) {
|
|
824
|
+
if (art.$contains?.$annotation)
|
|
825
|
+
checkAnnotationExpressions( anno, art );
|
|
826
|
+
|
|
810
827
|
// Sanity checks (ignore broken assignments)
|
|
811
828
|
if (!anno.name?.id)
|
|
812
829
|
return;
|
|
@@ -874,7 +891,6 @@ function check( model ) {
|
|
|
874
891
|
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art, anno ],
|
|
875
892
|
{ '#': 'std', anno: anno.name.id } );
|
|
876
893
|
}
|
|
877
|
-
|
|
878
894
|
return;
|
|
879
895
|
}
|
|
880
896
|
|
|
@@ -882,6 +898,22 @@ function check( model ) {
|
|
|
882
898
|
checkValueAssignableTo( anno, anno, elementDecl, art );
|
|
883
899
|
}
|
|
884
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
|
+
|
|
885
917
|
// Check that annotation assignment 'value' (having 'path or 'literal' and
|
|
886
918
|
// 'val') is potentially assignable to element 'element'. Complain on 'loc'
|
|
887
919
|
// if not
|
|
@@ -30,10 +30,7 @@ function detectCycles( definitions, reportCycle, cbScc ) {
|
|
|
30
30
|
|
|
31
31
|
for (const name in definitions) {
|
|
32
32
|
const a = definitions[name];
|
|
33
|
-
|
|
34
|
-
a.forEach( strongConnectRec );
|
|
35
|
-
else
|
|
36
|
-
strongConnectRec( a );
|
|
33
|
+
strongConnectRec( a );
|
|
37
34
|
}
|
|
38
35
|
// now the cleanup
|
|
39
36
|
let nodes = Object.getOwnPropertyNames( definitions ).map( n => definitions[n] );
|
package/lib/compiler/define.js
CHANGED
|
@@ -133,6 +133,7 @@ const { kindProperties, dictKinds } = require('./base');
|
|
|
133
133
|
const {
|
|
134
134
|
setLink,
|
|
135
135
|
setMemberParent,
|
|
136
|
+
createAndLinkCalcDepElement,
|
|
136
137
|
storeExtension,
|
|
137
138
|
dependsOnSilent,
|
|
138
139
|
pathName,
|
|
@@ -939,6 +940,8 @@ function define( model ) {
|
|
|
939
940
|
initExprForQuery( col.value, parent );
|
|
940
941
|
initSelectItems( col, null, user ); // TODO: use col as user (i.e. remove param)
|
|
941
942
|
}
|
|
943
|
+
|
|
944
|
+
initItemsLinks( col, parent._block );
|
|
942
945
|
}
|
|
943
946
|
|
|
944
947
|
if (hasItems && !wildcard && parent.excludingDict && !options.$recompile) {
|
|
@@ -974,7 +977,7 @@ function define( model ) {
|
|
|
974
977
|
// Drill down
|
|
975
978
|
if (exprOrPathElement.args)
|
|
976
979
|
exprOrPathElement.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
977
|
-
else if (exprOrPathElement.where
|
|
980
|
+
else if (exprOrPathElement.where?.args)
|
|
978
981
|
exprOrPathElement.where.args.forEach( elem => approveExistsInChildren( elem ) );
|
|
979
982
|
else if (exprOrPathElement.path)
|
|
980
983
|
exprOrPathElement.path.forEach( elem => approveExistsInChildren( elem ) );
|
|
@@ -998,15 +1001,7 @@ function define( model ) {
|
|
|
998
1001
|
// TODO: split extend from init
|
|
999
1002
|
const main = parent._main || parent;
|
|
1000
1003
|
const isQueryExtension = construct.kind === 'extend' && main.query;
|
|
1001
|
-
let obj = construct;
|
|
1002
|
-
let { items } = obj;
|
|
1003
|
-
while (items) {
|
|
1004
|
-
setLink( items, '_outer', obj );
|
|
1005
|
-
setLink( items, '_parent', obj._parent );
|
|
1006
|
-
setLink( items, '_block', block );
|
|
1007
|
-
obj = items;
|
|
1008
|
-
items = obj.items;
|
|
1009
|
-
}
|
|
1004
|
+
let obj = initItemsLinks( construct, block );
|
|
1010
1005
|
if (obj.target && targetIsTargetAspect( obj )) {
|
|
1011
1006
|
obj.targetAspect = obj.target;
|
|
1012
1007
|
delete obj.target;
|
|
@@ -1169,6 +1164,16 @@ function define( model ) {
|
|
|
1169
1164
|
elem.type = { ...elem.value.type, $inferred: 'cast' };
|
|
1170
1165
|
}
|
|
1171
1166
|
elem.$syntax = 'calc';
|
|
1167
|
+
// TODO: it is not just "syntax" - maybe better test for `$calcDepElement`?
|
|
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
|
+
}
|
|
1172
1177
|
}
|
|
1173
1178
|
}
|
|
1174
1179
|
|
|
@@ -1196,6 +1201,26 @@ function define( model ) {
|
|
|
1196
1201
|
}
|
|
1197
1202
|
}
|
|
1198
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
|
+
|
|
1199
1224
|
// To be reworked -------------------------------------------------------------
|
|
1200
1225
|
|
|
1201
1226
|
// TODO: is only necessary for extensions - make special for extend/annotate
|
package/lib/compiler/extend.js
CHANGED
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
copyExpr,
|
|
19
19
|
setExpandStatusAnnotate,
|
|
20
20
|
linkToOrigin,
|
|
21
|
+
createAndLinkCalcDepElement,
|
|
21
22
|
dependsOnSilent,
|
|
22
23
|
pathName,
|
|
23
24
|
annotationHasEllipsis,
|
|
@@ -390,7 +391,8 @@ function extend( model ) {
|
|
|
390
391
|
col.$extended = true;
|
|
391
392
|
|
|
392
393
|
if (!query?.from?.path) {
|
|
393
|
-
|
|
394
|
+
const variant = (query?.from || query)?.op?.val || 'std';
|
|
395
|
+
error( 'extend-columns', [ ext.columns[$location], ext ], { '#': variant, art } );
|
|
394
396
|
return;
|
|
395
397
|
}
|
|
396
398
|
if (!query.columns)
|
|
@@ -485,12 +487,13 @@ function extend( model ) {
|
|
|
485
487
|
if ('val' in upToSpec) {
|
|
486
488
|
if (previousItem.val === upToSpec.val) // enum, struct and ref have no val
|
|
487
489
|
return true;
|
|
488
|
-
|
|
489
|
-
const
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
490
|
+
// TODO v5: delete the speciao UP TO comparison?
|
|
491
|
+
const upToVal = upToSpec.val;
|
|
492
|
+
const prevVal = previousItem.val;
|
|
493
|
+
// eslint-disable-next-line eqeqeq
|
|
494
|
+
return prevVal == upToVal &&
|
|
495
|
+
( typeof upToVal === 'number' && stringCouldHaveBeenCdlNumber( prevVal ) ||
|
|
496
|
+
typeof prevVal === 'number' && stringCouldHaveBeenCdlNumber( upToVal ) );
|
|
494
497
|
}
|
|
495
498
|
else if (upToSpec.path) {
|
|
496
499
|
return previousItem.path && normalizeRef( previousItem ) === normalizeRef( upToSpec );
|
|
@@ -505,6 +508,16 @@ function extend( model ) {
|
|
|
505
508
|
return false;
|
|
506
509
|
}
|
|
507
510
|
|
|
511
|
+
// We only compare a string by number if the string is not empty, and could have
|
|
512
|
+
// been produced for a CDL number by (a previous version of) the compiler,
|
|
513
|
+
// i.e. having used a decimal dot, or using the scientific notation:
|
|
514
|
+
function stringCouldHaveBeenCdlNumber( val ) { // also consider previous compiler versions
|
|
515
|
+
return val && typeof val === 'string' && /[.eE]/.test( val );
|
|
516
|
+
// We do not use `!Number.isSafeInteger( Number.parseFloat( text||'0' )`
|
|
517
|
+
// because it is unlikely that people have written a non-integer like this,
|
|
518
|
+
// more likely is meant a digit-sequence as string
|
|
519
|
+
}
|
|
520
|
+
|
|
508
521
|
function normalizeRef( node ) { // see to-csn.js
|
|
509
522
|
const ref = pathName( node.path );
|
|
510
523
|
// TODO: get rid of name.variant (induces a wrong structure anyway)
|
|
@@ -1206,6 +1219,7 @@ function extend( model ) {
|
|
|
1206
1219
|
// TODO: Unify with coding in extend.js
|
|
1207
1220
|
elem.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
1208
1221
|
elem.$syntax = 'calc';
|
|
1222
|
+
createAndLinkCalcDepElement( elem );
|
|
1209
1223
|
setLink( elem, '_calcOrigin', origin._calcOrigin || origin );
|
|
1210
1224
|
}
|
|
1211
1225
|
// TODO: also complain if elem is just defined in art
|
package/lib/compiler/generate.js
CHANGED
|
@@ -13,6 +13,7 @@ const {
|
|
|
13
13
|
setAnnotation,
|
|
14
14
|
linkToOrigin,
|
|
15
15
|
setMemberParent,
|
|
16
|
+
createAndLinkCalcDepElement,
|
|
16
17
|
augmentPath,
|
|
17
18
|
isDirectComposition,
|
|
18
19
|
copyExpr,
|
|
@@ -770,6 +771,8 @@ function generate( model ) {
|
|
|
770
771
|
// TODO: Unify with coding in extend.js
|
|
771
772
|
proxy.value = Object.assign( { $inferred: 'include' }, copyExpr( origin.value ));
|
|
772
773
|
proxy.$syntax = 'calc';
|
|
774
|
+
createAndLinkCalcDepElement( proxy );
|
|
775
|
+
// TODO: re-check _calcOrigin
|
|
773
776
|
setLink( proxy, '_calcOrigin', origin._calcOrigin || origin );
|
|
774
777
|
}
|
|
775
778
|
if (anno)
|