Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
105
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 3.7.2 to 3.8.0

3

bin/cdsc.js

@@ -356,2 +356,5 @@ #!/usr/bin/env node

}
if (options.odataVocRefs && typeof options.odataVocRefs === 'string')
options.odataVocRefs = JSON.parse(options.odataVocRefs);
const csn = options.directBackend ? model : compactModel(model, options);

@@ -358,0 +361,0 @@ if (options.csn) {

@@ -10,3 +10,64 @@ # ChangeLog for cds compiler and backends

## Version 3.8.0 - 2023-03-27
### Added
- compiler:
+ Table aliases for sub-queries are no longer required.
+ A time zone designator can now be used in time literals, e.g. `time'16:41:01+01:30'`or `time'16:41:01Z'`.
- Calculated elements ("on-read") are now enabled per default.
When used in views, they are replaced by their value, for example:
```cds
entity E { one: Integer; two = one + 1; };
entity P as projection on E { two };
// P is the same as:
entity P as projection on E { one + 1 as two };
```
This allows to define calculations centrally at the entity, which can be used by
other views.
- In CDL, a ternary operator was added as a shortcut for `CASE` expressions:
`a ? b : c` is a shortcut for `CASE WHEN a THEN b ELSE c END`. There is no CSN
representation. The ternary operator is rendered as a `CASE` expression in CSN.
- In CDL and CSN, `not null` can now also be used in type definitions.
- In CDL (and CSN as before), elements can be defined without specifying a type.
### Changed
- API: We now report an error for most backends, if the input CSN has
`meta.flavor == 'xtended'`, because only client/inferred CSN is supported.
- Update OData vocabularies 'PersonalData', 'UI'
- for.odata: Shortcut annotations `@label`, `@title`, `@description`, `@readonly` are no longer
removed from the OData processed CSN.
- to.cdl:
+ Annotation arrays are split into multiple lines, if a single line would be too long.
+ Nested `SELECT`s are put into separate lines to make them more readable.
+ (Annotation) paths are quoted less often.
- to.sql: The list of reserved SAP HANA identifiers was updated (for smart quoting).
### Fixed
- The CSN parser now accepts bare `list`s in `columns[]`, similar to the CDL parser.
- to.cdl:
+ Delimited identifiers in filters are now surrounded by spaces if necessary, to avoid `]]`
being interpreted as an escaped bracket.
- to.edm(x):
+ Remove empty `Edm.EntityContainer` again. Removal of an empty entity container has been
revoked with [3.5.0](#fixed-7) which was wrong. An empty container must not be rendered
as it is not spec compliant.
+ Correctly resolve chained enum symbols.
+ Fix a program abort during structured rendering in combination with `--odata-foreign-keys`
and foreign keys in structured types.
+ Correctly render paths to nested foreign keys as primary key in structured mode with
`--odata-foreign-keys`.
- to.hdi/to.sql/to.edm(x):
+ Reject unmanaged associations as ON-condition path end points.
+ Fix bug in message rendering for tuple expansion.
+ Correctly detect invalid @sql.append/prepend in projections.
- to.hdi/to.sql: The list of SAP HANA keywords was updated to the latest version.
### Removed
- for.odata: Undocumented shortcut annotation `@important` has been removed.
## Version 3.7.2 - 2023-02-24

@@ -19,2 +80,3 @@

## Version 3.7.0 - 2023-02-22

@@ -49,6 +111,3 @@

### Removed
- tbd
## Version 3.6.2 - 2023-02-06

@@ -461,3 +520,3 @@

Only managed or `$self` backlink association targets are proxy/service cross reference candidates.
+ Explicit foreign keys of a managed association that are not a primary key in the target are exposed in the the proxy.
+ Explicit foreign keys of a managed association that are not a primary key in the target are exposed in the proxy.
+ If an association is primary key, the resulting navigation property is set to `Nullable:false` in structured mode.

@@ -464,0 +523,0 @@

@@ -11,3 +11,18 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 3.8.0 - 2023-03-27
### Added `v4preview`
This beta flags tries to imitate cds-compiler v4 behavior.
This includes new compiler messages as well as potentially breaking changes.
Enable this beta flag to ease upgrading to cds-compiler v4.
This flag does not guarantee full compatibility with v4, but only
helps to identify potential issues as soon as possible.
### Removed `calculatedElements`
Now enabled per default.
## Version 3.7.0 - 2023-02-22

@@ -14,0 +29,0 @@

2

doc/DeprecatedOptions_v2.md

@@ -54,3 +54,3 @@ # Deprecated Options and How to Avoid Them

For the following sub sections (and in general), is is important to understand that
For the following subsections (and in general), it is important to understand that
you can define all auto-exposed entities yourself (well, they are not

@@ -57,0 +57,0 @@ _auto_-exposed anymore)

@@ -603,3 +603,3 @@ # Name Resolution in CDS

* The first lexical search environment is the the environment containing all parameter names of the current view.
* The first lexical search environment is the environment containing all parameter names of the current view.
* The following search environments are the usual ones from the "main artifact name resolution";

@@ -606,0 +606,0 @@ constant values can be accessed this way (_TODO_: probably not now).

@@ -338,3 +338,3 @@ /** @module API */

* @param {CSN.Model} beforeImage A db-transformed CSN representing the "before-image", or null in case no such image
* is known, i.e. for the very first migration step
* is known, i.e. for the very first migration step.
* @returns {object} An object with three properties:

@@ -816,4 +816,4 @@ * - afterImage: A db-transformed CSN representing the "after-image"

*
* @param {object} csn CSN
* @param {object} options Options
* @param {CSN.Model} csn CSN
* @param {CSN.Options} options Options
* @param {any} args Any additional arguments

@@ -824,8 +824,11 @@ * @returns {any} What ever the processor returns

try {
if (options.deprecated) {
const messageFunctions = messages.makeMessageFunction(csn, options, 'api');
const messageFunctions = messages.makeMessageFunction(csn, options, 'api');
if (options.deprecated)
checkRemovedDeprecatedFlags( options, messageFunctions );
}
checkOutdatedOptions( options );
checkOutdatedOptions( options, messageFunctions );
checkCsnFlavor( csn, options, messageFunctions, _name );
messageFunctions.throwWithError();
timetrace.timetrace.start(_name);

@@ -867,33 +870,69 @@ const result = processor( csn, options, ...args );

* @param {CSN.Options} options Backend options
* @param {object} messageFunctions Functions returned by makeMessageFunction()
*/
function checkOutdatedOptions( options ) {
const { error, throwWithError } = messages.makeMessageFunction(null, options, 'api');
function checkOutdatedOptions( options, messageFunctions ) {
// This error has been emitted once, we don't need to emit it again.
if (options.messages?.some(m => m.messageId === 'api-invalid-option')) {
throwWithError();
if (options.messages?.some(m => m.messageId === 'api-invalid-option' || m.messageId === 'api-invalid-variable-replacement'))
return;
}
for (const name of oldBackendOptionNames) {
if (typeof options[name] === 'object') // may be a boolean due to internal options
error('api-invalid-option', null, { '#': 'std', name });
messageFunctions.error('api-invalid-option', null, { '#': 'deprecated', name });
}
if (options.magicVars)
error('api-invalid-option', null, { '#': 'magicVars' });
messageFunctions.error('api-invalid-option', null, { '#': 'magicVars', prop: 'magicVars', otherprop: 'variableReplacements' });
// Don't check `options.magicVars`. It's likely that the user renamed `magicVars` but
// forgot about user -> $user and locale -> $user.locale
if (options.variableReplacements?.user)
error('api-invalid-option', null, { '#': 'user' });
if (options.variableReplacements?.locale)
error('api-invalid-option', null, { '#': 'locale' });
if (options.variableReplacements?.user) {
messageFunctions.error('api-invalid-variable-replacement', null, {
'#': 'user', option: 'variableReplacements', prop: '$user', otherprop: 'user',
});
}
if (options.variableReplacements?.locale) {
messageFunctions.error('api-invalid-variable-replacement', null, {
'#': 'locale', option: 'variableReplacements', prop: '$user.locale', otherprop: 'locale',
});
}
forEachKey(options.variableReplacements || {}, (name) => {
if (!name.startsWith('$') && name !== 'user' && name !== 'locale')
error('api-invalid-option', null, { '#': 'noDollar', name });
if (!name.startsWith('$') && name !== 'user' && name !== 'locale') {
messageFunctions.error('api-invalid-variable-replacement', null, {
'#': 'noDollar', option: 'variableReplacements', code: '$', name,
});
}
});
}
throwWithError();
/**
* Checks that the given CSN is usable by our backends, e.g. that
* the CSN is not a gensrc (a.k.a. xtended) for most backends.
*
* For reference, cds-compiler/cds-dk CSN flavor map:
* - client -> inferred
* - gensrc -> xtended
* - parseCdl -> parsed
*
* If this function becomes more complex (e.g. more module conditions),
* move it from then generic api wrapper to the individual module.
*
* TODO: The compiler does not set any marker in `meta`; we use the umbrella one
* for easier debugging.
*
* @param {CSN.Model} csn User CSN
* @param {CSN.Options} options User options
* @param {object} messageFunctions Functions returned by makeMessageFunction()
* @param {string} module Backend module, e.g. to.cdl or to.sql
*/
function checkCsnFlavor( csn, options, messageFunctions, module ) {
if (module === 'to.cdl' || !csn)
return; // to.cdl allows every CSN flavor
if (csn.meta?.flavor === 'xtended') {
// TODO: csn.meta?.flavor === 'parsed'; currently used by `@sap/cds` tests.
messageFunctions.error('api-unsupported-csn-flavor', null, { name: module, option: csn.meta?.flavor },
'Module $(NAME) expects a client/inferred CSN, not $(OPTION)');
}
}

@@ -900,0 +939,0 @@

@@ -41,2 +41,3 @@ 'use strict';

'odataV2PartialConstr',
'odataVocRefs',
'service',

@@ -43,0 +44,0 @@ 'serviceNames',

@@ -85,2 +85,7 @@ 'use strict';

odataFormat: generateStringValidator([ 'flat', 'structured' ]),
odataVocRefs: {
validate: val => (typeof val === 'object' && !Array.isArray(val)),
expected: () => 'type JSON object',
found: val => `type ${ Array.isArray(val) ? 'JSON array' : typeof val }`,
},
service: {

@@ -87,0 +92,0 @@ validate: val => typeof val === 'string',

@@ -94,7 +94,9 @@ // Functions for dictionaries (Objects without prototype)

// Push `entry` to the array value with key `name` in the dictionary `dict`.
function pushToDict( dict, name, entry ) {
function pushToDict( dict, name, ...entries ) {
if (dict[name])
dict[name].push( entry );
dict[name].push( ...entries );
else if (name.charAt(0) !== '_')
dict[name] = entries;
else
dict[name] = [ entry ];
Object.defineProperty( dict, name, { value: entries, configurable: true, writable: true } );
}

@@ -101,0 +103,0 @@

@@ -582,2 +582,3 @@ 'use strict';

'ST_ALPHASHAPEEDGEAGGR',
'ST_ASGEOJSON',
'ST_ASMVT',

@@ -610,2 +611,3 @@ 'ST_ASSVGAGGR',

'ST_MAKELINE',
'ST_MAKELINEAGGR',
'ST_MAKEPOLYGON',

@@ -612,0 +614,0 @@ 'ST_MEMORY_LOB',

@@ -63,3 +63,3 @@ // Central registry for messages.

'anno-undefined-param': { severity: 'Warning' },
'anno-unexpected-ellipsis-layers': { severity: 'Error', configurableFor: true }, // TODO(v3): Merge with anno-unexpected-ellipsis and make non-configurable
'anno-unexpected-ellipsis': { severity: 'Error', configurableFor: 'v3' },

@@ -189,2 +189,3 @@ 'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy

'odata-anno-dict': { severity: 'Warning', configurableFor: true },
'odata-anno-vocref': { severity: 'Warning', configurableFor: true },
'odata-anno-dict-enum': { severity: 'Error' },

@@ -225,11 +226,14 @@ 'odata-anno-value': { severity: 'Warning', configurableFor: true },

'api-invalid-option': {
// TODO: too many different error situations for one message id,
// TODO: use message parameter - do not use quotes in message texts
std: 'Option $(NAME) is no longer supported! Use SNAPI options instead',
magicVars: 'Option “magicVars” is no longer supported! Use “variableReplacements” instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
user: 'Option “variableReplacements” expects “$user” instead of “user”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
locale: 'Option “variableReplacements” expects “$user.locale” instead of “locale”. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
'noDollar': 'Option “variableReplacements” does not know $(NAME). Did you forget a leading “$”?'
std: 'Invalid option $(NAME)!',
deprecated: 'Option $(NAME) is no longer supported! Use latest API options instead',
magicVars: 'Option $(PROP) is no longer supported! Use $(OTHERPROP) instead. See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
},
'api-invalid-variable-replacement': {
std: 'Option $(OPTION) does not support $(NAME)',
user: 'Option $(OPTION) expects $(PROP) instead of $(OTHERPROP). See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
locale: 'Option $(OPTION) expects $(PROP) instead of $(OTHERPROP). See <https://cap.cloud.sap/docs/guides/databases#configuring-variables> for details',
'noDollar': 'Option $(OPTION) does not know $(NAME). Did you forget a leading $(CODE)?'
},
'anno-duplicate': {

@@ -250,3 +254,2 @@ std: 'Duplicate assignment with $(ANNO)',

'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',

@@ -275,2 +278,8 @@

'name-missing-alias': {
std: 'Missing table alias for this subquery',
duplicate: 'Missing table alias for this subquery; add $(CODE) to fix name clash of internal and explicit table alias',
hdbcds: 'Missing table alias for a subquery; SAP HANA CDS requires table aliases'
},
// Syntax messages, both CDL and CSN parser: ----------------------------------

@@ -308,5 +317,5 @@ 'syntax-deprecated-abstract': {

'invalid-hex': 'A binary literal must only contain characters ‹0-9›, ‹a-f› and ‹A-F›',
'time': 'A time literal must look like ‹hh:mm:ss› or ‹hh:mm› where each letter represents a digit',
'time': 'A time literal must look like ‹hh:mm:ss› or ‹hh:mm› where each letter represents a digit. A timezone is optional',
'date': 'A date literal must look like ‹YYYY-MM-DD› where each letter represents a digit',
'timestamp': 'A timestamp literal must look like ‹YYYY-MM-DD hh:mm:ss.u…u› or ‹YYYY-MM-DD hh:mm› where each letter represents a digit, ‹u…u› represents 1 to 7 digits',
'timestamp': 'A timestamp literal must look like ‹YYYY-MM-DD hh:mm:ss.u…u› or ‹YYYY-MM-DD hh:mm› where each letter represents a digit, ‹u…u› represents 1 to 7 digits. A timezone is optional',
'number': 'The string value in property $(PROP) does not represent a number',

@@ -393,2 +402,6 @@ 'expecting': 'Expecting literal type $(OP) for the value in property $(OTHERPROP)',

},
'syntax-deprecated-type-ref': {
std: 'Expecting a string as value for property $(PROP) for a reference to a definition',
'ref-item': 'Expecting a string as value for property $(PROP) for a type reference to an element',
},

@@ -448,2 +461,8 @@ 'syntax-expecting-object': {

},
'ref-invalid-calc-elem': {
std: 'Can\'t include artifact with calculated element',
event: 'An event can\'t include an entity with calculated elements',
type: 'A type can\'t include an entity with calculated elements',
annotation: 'An annotation can\'t include an entity with calculated elements',
},
'def-unsupported-calc-elem': {

@@ -537,2 +556,3 @@ std: 'Calculated elements are not supported',

unmanaged: 'Can\'t follow unmanaged association $(ID) of path $(ELEMREF) in an ON-condition; only foreign keys can be referred to',
unmanagedleaf: 'Unexpected unmanged association as final path step of $(ELEMREF) in an ON-condition',
},

@@ -569,13 +589,19 @@

returns: 'Return value of $(ART) has no element $(NAME)',
'entity-element': 'Elements of entity types can\'t be annotated',
'enum-returns': 'Return value of $(ART) has no enum $(NAME)',
},
'anno-undefined-action': {
std: 'Action $(ART) has not been found',
action: 'Artifact $(ART) has no action $(MEMBER)'
action: 'Artifact $(ART) has no action $(NAME)'
},
'anno-undefined-param': {
std: 'Parameter $(ART) has not been found',
param: 'Artifact $(ART) has no parameter $(MEMBER)'
param: 'Artifact $(ART) has no parameter $(NAME)'
},
// annotation checks against their definition
'anno-expecting-value': {
'std': 'Expecting a value for the annotation; see annotation definition for $(ANNO)',
'type': 'Expecting a value of type $(TYPE) for the annotation'
},
'def-unexpected-paramview-assoc': {

@@ -635,3 +661,6 @@ std: 'SAP HANA doesn\'t support associations in/to parameterized entities',

alias: 'Duplicate definition of table alias or mixin $(NAME)',
'include-elements': 'Duplicate element $(NAME) through multiple includes $(SORTED_ARTS)',
'include-actions': 'Duplicate action or function $(NAME) through multiple includes $(SORTED_ARTS)',
},
// TODO: Remove in v4, use duplicate-definition
'ref-duplicate-include-member': {

@@ -659,14 +688,14 @@ std: 'Duplicate member $(NAME) through multiple includes $(SORTED_ARTS)',

'ext-duplicate-same-file': 'Duplicate extension with $(PROP) in same file',
'ext-duplicate-extend-type': 'Duplicate type extension for type $(TYPE)',
'ext-duplicate-extend-type-unrelated-layer': 'Duplicate type extension for type $(TYPE)',
'ext-invalid-type-property': {
std: 'Property $(PROP) can only be extended',
'new-prop': 'Property $(PROP) can only be extended, not added',
std: 'Type property $(PROP) can only be extended',
'indirect': 'Type property $(PROP) can only be extended if directly provided at the definition',
'new-prop': 'Type property $(PROP) can only be extended, not added',
string: 'Only numerical properties can be extended, but found string for $(PROP)',
// eslint-disable-next-line max-len
smaller: 'Property $(PROP) can only be extended, but $(VALUE) is smaller than $(NUMBER) of type definition',
number: 'Value of type property $(PROP) must be $(NUMBER) or higher, it can\'t be smaller than originally provided',
// eslint-disable-next-line max-len
'ext-smaller': 'Property $(PROP) can only be extended, but extended value $(VALUE) is smaller than $(NUMBER) from previous type extension',
prop: 'Type property $(PROP) can\'t be extended',
scale: 'If property $(PROP) is increased, then so must $(OTHERPROP)',
string: 'Only numerical properties can be extended, but found string for $(PROP)',
scale: 'With the extension for type property $(OTHERPROP), the value of $(PROP) must be $(NUMBER) or higher',
},

@@ -702,2 +731,8 @@ 'ref-expected-scalar-type': {

'ref-ambiguous': {
std: 'Replace ambiguous $(ID) by $(NAMES)',
few: 'Replace ambiguous $(ID) by $(NAMES) or a new table alias for sub-queries that don\'t have one',
none: 'Ambiguous $(ID) requires an explicit table alias, but there are none: add table aliases to all sub-queries to disambiguate $(ID)',
},
'type-managed-composition': {

@@ -729,3 +764,4 @@ std: 'Managed compositions can\'t be used in types', // yet

'v2firstchar': 'Unexpected first character $(PROP) of EDM Name $(ID) for OData $(VERSION)',
'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
'qualifier': 'Expected annotation qualifier $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits',
'vocrefalias': 'Expected value $(VALUE) of vocabulary reference attribute $(ID) to start with a letter or underscore, followed by at most 127 letters, underscores or digits'
},

@@ -799,2 +835,8 @@ // version independent messages

},
'odata-anno-vocref': {
'std': 'Vocabulary reference $(ID) doesn\'t match alias $(NAME), reference is ignored',
'redef': 'Vocabulary reference $(ID) is the alias of the official OASIS/SAP vocabulary $(TYPE) which can\'t be redefined, reference is ignored',
'service': 'Vocabulary reference collides with service $(NAME), reference is ignored',
'malformed': 'Vocabulary reference $(ID) has invalid or missing value for attribute $(NAME), reference is ignored'
},
'odata-anno-dict-enum': {

@@ -801,0 +843,0 @@ 'std' : 'Unexpected annotation definition $(NAME) with many enum type',

@@ -14,2 +14,3 @@ // Functions and classes for syntax messages

const { CompilerAssertion } = require('./error');
const { getArtifactName } = require('../compiler/base');

@@ -845,2 +846,3 @@ const fs = require('fs');

// TODO: very likely delete this function
function searchName( art, id, variant ) {

@@ -1191,3 +1193,3 @@ if (!variant) {

function shortArtName( art ) {
const { name } = art;
const name = getArtifactName( art );
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&

@@ -1200,3 +1202,3 @@ !name.absolute.includes(':'))

function artName( art, omit ) {
const { name } = art;
const name = getArtifactName( art );
const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];

@@ -1207,3 +1209,3 @@ if (name.select && name.select > 1 || name.select != null && art.kind !== 'element') // Yes, omit select:1 for element - TODO: re-check

r.push( `${ memberActionName(art) }:${ quoted( name.action ) }` );
if (name.alias && art.kind !== '$self')
if (name.alias && art.kind !== '$self' && name.$inferred !== '$internal')
r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) );

@@ -1239,3 +1241,3 @@ if (name.param != null && omit !== 'param')

return art;
if (art._outer) // in returns / items property
if (art._outer) // in items property
return homeName( art._outer, absoluteOnly );

@@ -1260,3 +1262,6 @@ else if (art.kind === 'source' || !art.name) // error reported in parser or on source level

// set for unknown extensions, and we could have nested extensions.
// TODO: delete this function, just set correct name/_parent for extensions
function homeNameForExtend( art ) {
if (!art.name.absolute && art._main) // new-style member name
return `${ art._main.kind }:${ artName( art ) }`;
const kind = art.kind || 'extend';

@@ -1600,7 +1605,7 @@ // TODO: fix the following - do like in collectArtifactExtensions() or

const rootQuery = view.query || { SELECT: view.projection };
let depth = 1;
let depth = 0;
let totalDepth = 0;
let isFound = false;
traverseQuery(rootQuery, null, null, (q, querySelect) => {
if (querySelect) {
traverseQuery(rootQuery, null, null, (q) => {
if (q.SELECT) {
totalDepth += 1;

@@ -1607,0 +1612,0 @@ if (!isFound)

@@ -24,3 +24,2 @@ 'use strict';

// enabled by --beta-mode
calculatedElements: true,
annotationExpressions: true,

@@ -40,2 +39,3 @@ toRename: true,

nestedServices: false,
v4preview: false,
};

@@ -116,3 +116,3 @@

* @param {CSN.Options} options
* @param error Error message function returned by makeMessageFunctions().
* @param error Error message function returned by makeMessageFunction().
*/

@@ -134,2 +134,3 @@ function checkRemovedDeprecatedFlags( options, { error } ) {

// `model.definitions`. See function `forEachGeneric` for details.
// TODO: should we skip "namespaces" already here?
function forEachDefinition( model, callback ) {

@@ -136,0 +137,0 @@ forEachGeneric( model, 'definitions', callback );

@@ -35,3 +35,3 @@ 'use strict';

};
if (this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items && member.$path[2] === 'elements') {
if (this.artifact && this.artifact.kind === 'entity' && member && member.items && member.$path[2] === 'elements') {
if (member.items.type) {

@@ -38,0 +38,0 @@ const type = member.items.type.ref

@@ -56,3 +56,3 @@ 'use strict';

const art = this.csn.definitions[path[1]];
if (!art.query && this.options.transformation === 'hdbcds' && member.target && member.default) {
if (!art.query && !art.projection && this.options.transformation === 'hdbcds' && member.target && member.default) {
this.warning(null, path, { '#': member._type.type === 'cds.Association' ? 'std' : 'comp' },

@@ -59,0 +59,0 @@ {

@@ -20,3 +20,3 @@ 'use strict';

// TODO: Maybe check if there are only calc elements and adapt the message?
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
this.error('def-missing-element', path, { '#': ( artifact.query || artifact.projection ) ? 'view' : 'std' });
}

@@ -23,0 +23,0 @@ }

@@ -58,3 +58,3 @@ 'use strict';

this.artifact &&
(this.artifact.kind === 'entity' || this.artifact.query) &&
this.artifact.kind === 'entity' &&
member.$path[2] === 'elements'

@@ -61,0 +61,0 @@ )

@@ -98,3 +98,2 @@ 'use strict';

}
if (_links[j].art.virtual)

@@ -120,7 +119,7 @@ this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-conditions, step $(ID) of path $(ELEMREF)');

// If this path ends structured or on an association, perform the check:
if ((type.target || type.elements) &&
!( /* 1) */ (type.target && type.keys || type.elements) && validStructuredElement ||
/* 2) */ (type.target && validDollarSelf)) &&
!type.virtual) {
// Do nothing - handled by lib/checks/nonexpandableStructured.js
if (
((type.target && type.keys || type.elements) && validStructuredElement ||
(type.target && validDollarSelf)) && !type.virtual
) {
// Do nothing - handled by lib/checks/nonexpandableStructured.js
}

@@ -135,2 +134,6 @@ else if (type.items && !type.virtual) {

}
else if (type.on) {
// Path leaf is an unmanaged association, can't use an unmanaged assoc as operand
this.error('ref-unexpected-navigation', onPath, { '#': 'unmanagedleaf', id: logReady(ref[ref.length - 1]), elemref: { ref } });
}
}

@@ -137,0 +140,0 @@ }

@@ -21,3 +21,3 @@ 'use strict';

if (member['@sql.append']) {
if (this.artifact.query)
if (this.artifact.query || this.artifact.projection)
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, 'Annotation $(ANNO) can\'t be used on elements in views' );

@@ -61,3 +61,3 @@ else if (this.csnUtils.isStructured(member))

else if (artifact['@sql.prepend']) {
if (artifact.query)
if (artifact.query || artifact.projection)
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, 'Annotation $(NAME) can\'t be used on views' );

@@ -64,0 +64,0 @@ else

'use strict';
const { hasAnnotationValue } = require('../model/csnUtils');
const { isBetaEnabled } = require('../base/model');

@@ -58,3 +57,3 @@ // Only to be used with validator.js - a correct this value needs to be provided!

// calculated elements may not have a .type (requires beta flag)
if ((!member.value || !isBetaEnabled(this.options, 'calculatedElements')) &&
if (!member.value &&
!parent.projection && !parent.query && !hasArtifactTypeInformation(member)) {

@@ -61,0 +60,0 @@ errorAboutMissingType(this.error, path, memberName, true);

@@ -194,3 +194,3 @@ // Consistency checker on model (XSN = augmented CSN)

optional: [
'elements', '$autoElement', '$uncheckedElements', '_origin',
'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',

@@ -299,2 +299,3 @@ ],

'$duplicates', // duplicate query in FROM clause
'$inferred', // table alias with $inferred: '$internal'
],

@@ -344,3 +345,3 @@ },

'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
'_origin', '_effectiveType', '$effectiveSeqNo',
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions',
],

@@ -394,2 +395,6 @@ },

},
$tokenTexts: {
parser: true,
test: isString,
},
value: {

@@ -401,2 +406,4 @@ optional: [

'elements', 'items', 'enum',
// CSN parser may let these properties slip through to XSN, even if input is invalid.
'args', 'op', 'func', 'suffix',
],

@@ -451,2 +458,4 @@

'literal', 'val', 'sym', 'struct', 'variant', 'path', 'name', '$duplicates', 'upTo',
// expressions as annotation values
'$tokenTexts', 'op', 'args', 'func',
],

@@ -479,3 +488,9 @@ // TODO: restrict path to #simplePath

inherits: 'value',
optional: [ 'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported' ],
optional: [
'name', '_block', '$priority', '$inferred', '$duplicates', '$errorReported',
// annotation values
'$tokenTexts',
// CSN parser may let these properties slip through to XSN, even if input is invalid.
'args', 'op', 'func', 'suffix',
],
// TODO: name requires if not in parser?

@@ -504,3 +519,3 @@ },

alias: { test: isString },
expectedKind: { kind: [ 'extend' ], inherits: 'kind' },
expectedKind: { kind: [ 'extend' ], test: locationVal( isString ) },
virtual: { kind: true, test: locationVal() },

@@ -525,3 +540,3 @@ key: { kind: true, test: locationVal(), also: [ null, undefined ] },

'_origin', '_block', '$inferred', '$expand', '$inCycle', '_deps',
'$syntax',
'$syntax', '_extensions',
'_status', '_redirected',

@@ -586,2 +601,3 @@ ...typeProperties,

_origin: { kind: true, test: TODO },
_calcOrigin: { kind: true, test: TODO },
_pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)

@@ -593,2 +609,3 @@ _from: { kind: true, test: TODO }, // TODO: not necessary anymore ?

_$next: { kind: true, test: TODO }, // next lexical search environment for values
_extensions: { kind: true, test: TODO }, // for collecting extend/annotate on artifact
_extend: { kind: true, test: TODO }, // for collecting extend/annotate on artifact

@@ -623,2 +640,3 @@ _annotate: { kind: true, test: TODO }, // for collecting extend/annotate on artifact

test: isOneOf([
'', // constructed “super annotate” statement
// Uppercase values are used in logic, lowercase value are "just for us", i.e.

@@ -635,2 +653,3 @@ // debugging or to add properties such as $generated in Universal CSN.

'$generated', // compiler generated annotations, e.g. @Core.Computed
'$internal', // compiler internal; must not reach CSN output
'*', // inferred from query wildcard

@@ -637,0 +656,0 @@ 'as', // query alias name

@@ -32,3 +32,3 @@ // Base Definitions for the Core Compiler

annotation: { elements: propExists, enum: propExists },
enum: { normalized: 'element' },
enum: { normalized: 'element', dict: 'enum' },
element: { elements: propExists, enum: propExists, dict: 'elements' },

@@ -40,3 +40,7 @@ mixin: { normalized: 'alias' },

function: {
params: () => false, elements: () => false, enum: () => false, normalized: 'action',
params: () => false,
elements: () => false,
enum: () => false,
normalized: 'action',
dict: 'actions',
}, // no extend params, only annotate

@@ -65,5 +69,48 @@ key: { normalized: 'element' },

// Return the "old style" name structure with `absolute`, `action`, `param`,
// `element`. Later we also need to add `select` and `alias`.
// (Currently not needed, as only used for extend and annotate statements.)
function getArtifactName( art ) {
if (!art.name || art.name.absolute)
return art.name;
// extend and annotate statement already have "sparse" names → calculate old one
const link = art.name._artifact;
const namePath = [];
while (art._main && !art.name.absolute) { // until we hit an old-style name or the main artifact
namePath.push( art );
art = art._parent;
}
namePath.reverse();
const name = { ...art.name };
for (const np of namePath) {
const prop = getMemberNameProp( np, np.kind );
name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
name.id = np.name.id;
name.location = np.name.location;
}
if (link !== undefined)
Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
return name;
}
// TODO: probably store this prop in name
function getMemberNameProp( elem, kind ) {
if (kind !== 'annotate' && kind !== 'extend')
return kindProperties[kind]?.normalized || kind;
let obj = elem._parent;
if (obj.params || obj.returns)
return 'param';
if (obj.actions)
return 'action';
while (obj.items)
obj = obj.items;
if (obj.elements || obj.enum)
return 'element';
return 'id';
}
module.exports = {
dictKinds,
kindProperties,
getArtifactName,
};

@@ -13,2 +13,3 @@ // The builtin artifacts of CDS

// TODO: make type parameters a dict
const core = {

@@ -203,4 +204,13 @@ String: { parameters: [ 'length' ], category: 'string' },

// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP CDS via function
const dateRegEx = /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/;
// YYYY - MM - dd
const timeRegEx = /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
// T HH : mm : ss TZD
// eslint-disable-next-line max-len
const timestampRegEx = /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?(?:Z|[+-]\d{2}(?::\d{2})?)?$/;
// YYYY - MM - dd T HH : mm : ss . fraction TZD
const numberRegEx = /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i;
/**

@@ -232,3 +242,3 @@ * Patterns for literal token tests and creation. The value is a map from the

// Leading `T` allowed in ISO 8601.
const match = x.match( /^T?(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/ );
const match = x.match( timeRegEx );
return match !== null && checkTime( match[1], match[2], match[3] );

@@ -241,3 +251,3 @@ },

test_fn: (x) => {
const match = x.match( /^(-?\d{4})-(\d{1,2})-(\d{1,2})$/ );
const match = x.match( dateRegEx );
return match !== null && checkDate( match[1], match[2], match[3] );

@@ -250,4 +260,3 @@ },

test_fn: (x) => {
// eslint-disable-next-line max-len
const match = x.match( /^(-?\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2})(\.\d{1,7})?)?$/ );
const match = x.match( timestampRegEx );
return match !== null && checkDate( match[1], match[2], match[3] ) &&

@@ -267,3 +276,3 @@ checkTime( match[4], match[5], match[6] );

test_variant: 'number',
test_fn: (x => /^[ \t]*[-+]?(\d+(\.\d*)?|\.\d+)(e[-+]\d+)?[ \t]*$/i.test( x )),
test_fn: (x => numberRegEx.test( x )),
json_type: 'number',

@@ -270,0 +279,0 @@ secondary_json_type: 'string',

@@ -780,8 +780,8 @@ // Checks on XSN performed during compile()

if (elementDecl.type && elementDecl.type._artifact.name.absolute) {
warning(null, anno.location || anno.name.location, { type: elementDecl.type._artifact },
'Expecting a value of type $(TYPE) for the annotation');
warning('anno-expecting-value', anno.location || anno.name.location,
{ '#': 'type', type: elementDecl.type._artifact });
}
else {
warning(null, anno.location || anno.name.location, {},
'Expecting a value for the annotation');
warning('anno-expecting-value', anno.location || anno.name.location,
{ '#': 'std', anno: anno.name.absolute });
}

@@ -788,0 +788,0 @@

@@ -122,3 +122,7 @@ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN

const { forEachGeneric, forEachInOrder, isBetaEnabled } = require('../base/model');
const {
forEachGeneric,
forEachInOrder,
forEachMember,
} = require('../base/model');
const shuffleGen = require('../base/shuffle');

@@ -136,3 +140,2 @@ const {

splitIntoPath,
annotationHasEllipsis,
isDirectComposition,

@@ -161,3 +164,3 @@ } = require('./utils');

const {
error, warning, info, message, messages,
error, warning, info, messages,
} = model.$messageFunctions;

@@ -169,3 +172,2 @@ const {

const extensionsDict = Object.create(null);
Object.assign( model.$functions, {

@@ -176,5 +178,3 @@ shuffleDict,

initMembers,
extensionsDict, // a dictionary - TODO: put directly into model?
checkDefinitions,
initAnnotations,
checkDefinitions, // TODO: remove
} );

@@ -209,3 +209,3 @@ // During the definer, we can only resolve artifact references, i.e,

model.$compositionTargets = Object.create(null);
model.$lateExtensions = Object.create(null); // for generated artifacts
model.$lateExtensions = Object.create(null); // TODO: rename to $collectedExtensions

@@ -220,3 +220,3 @@ initBuiltins( model );

dictForEach( model.vocabularies, initVocabulary );
dictForEach( extensionsDict, initExtension );
dictForEach( model.$lateExtensions, e => e._extensions.forEach( initExtension ) );

@@ -407,5 +407,14 @@ addI18nBlocks();

ext.name.absolute = absolute; // definition might not be there yet, no _artifact link
pushToDict( extensionsDict, absolute, ext );
const location = { file: '' }; // stupid required location
const late = model.$lateExtensions[absolute] ||
(model.$lateExtensions[absolute] = {
kind: 'annotate',
name: { absolute, location },
$inferred: '',
location,
});
pushToDict( late, '_extensions', ext );
if (!ext.artifacts)
return;
// Directly add the artifacts of context and service extension:

@@ -417,2 +426,4 @@ if (!model.$blocks)

ext.name.select = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
// add "namespace" for the case that ext.artifacts is empty (TODO: later)
// now add all definitions in ext.artifacts:
const prefix = `${ absolute }.`;

@@ -422,2 +433,24 @@ dictForEach( ext.artifacts, a => addArtifact( a, ext, prefix ) );

function initExtension( parent ) {
forEachMember( parent, function init( sub ) {
if (sub.kind !== 'extend' && sub.kind !== 'annotate')
return; // for defs inside, set somewhere else - TODO: rethink
setLink( sub, '_block', parent._block );
setLink( sub, '_parent', parent );
setLink( sub, '_main', parent._main || parent );
initExtension( sub );
} );
if (parent.kind !== 'extend')
return;
if (parent.columns) // TODO: sub queries? expand/inline?
parent.columns.forEach( c => setLink( c, '_block', parent._block ) );
if (parent.scale && !parent.precision) {
// TODO: where could we store the location of the name?
error( 'syntax-missing-type-property', [ parent.scale.location ],
{ prop: 'scale', otherprop: 'precision' },
'Type extension with property $(PROP) must also have property $(OTHERPROP)' );
parent.scale = undefined; // no consequential error
}
}
function addVocabulary( vocab, block, prefix ) {

@@ -546,3 +579,2 @@ setLink( vocab, '_block', block );

checkRedefinition( art );
initAnnotations( art, block );
initMembers( art, art, block );

@@ -552,4 +584,2 @@ initDollarSelf( art ); // $self

initDollarParameters( art );
if (art.includes && !(art.name.absolute in extensionsDict)) // TODO: in next phase?
extensionsDict[art.name.absolute] = []; // structure with includes must be "extended"

@@ -575,3 +605,2 @@ if (!art.query)

const block = art._block;
initAnnotations( art, block );
initMembers( art, art, block );

@@ -602,53 +631,2 @@ }

/** Initialize the extension `ext`.
*
* Currently:
*
* - initialize annotations (set _block, $priority, `...` check) on "main"
* extension and its columns do more later
* - for members in compile(): init annotations via extendMembers/annotateMembers
* - for members in parse.cdl(): init annotation via initMembers
*
* In the future (after name cleanup): -- TODO --
*
* - also initialize members and member extensions/annotations here
* - we might also do other things, like calculating whether an `extend` is
* `annotate`-like, i.e. only contains name-resolution irrelevant extensions.
*/
function initExtension( ext ) {
const block = ext._block;
initAnnotations( ext, block, ext.kind );
if (ext.columns) // the columns themselves are "definitions"
ext.columns.forEach( col => initAnnotations( col, block ) );
}
// Set _block links for annotations (necessary for layering) and do a late
// syntax check (`...` only with extensions, not definitions).
// extKind is either ext.kind (=art is extension) or false (=art is not an extension)
function initAnnotations( art, block, extKind = false ) {
// TODO: think of removing $priority, then
// no _block: define, _block: annotate/extend/edmx
// would fit with extending defs with props like length
for (const prop in art) {
if (prop.charAt(0) === '@' || prop === 'doc') {
const anno = art[prop];
// TODO: make anno never be an array, see addAnnotation() in genericAntlrParser
if (Array.isArray( anno ))
anno.forEach( init );
else
init( anno );
}
}
return;
function init( anno ) {
setLink( anno, '_block', block );
anno.$priority = extKind;
if (!extKind && annotationHasEllipsis( anno )) {
error( 'anno-unexpected-ellipsis',
[ anno.name.location, art ], { code: '...' } );
}
}
}
// Init special things: -------------------------------------------------------

@@ -789,6 +767,7 @@

else if (table.query) {
if (!table.name || !table.name.id) {
error( 'query-req-alias', [ table.location, query ], {}, // TODO: not subquery.location ?
'Table alias is required for this subquery' );
return;
if (!table.name?.id) {
// We don't worry about duplicate names here.
const id = `$_select_${ query._main.$queries.length + 1 }__`;
table.name = { id, location: table.location, $inferred: '$internal' };
table.$inferred = '$internal';
}

@@ -840,4 +819,11 @@ addAsAlias();

setLink( table, '_block', query._block );
dictAdd( query.$tableAliases, table.name.id, table, ( name, loc ) => {
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
dictAdd( query.$tableAliases, table.name.id, table, ( name, loc, tableAlias ) => {
if (tableAlias.$inferred === '$internal') {
const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
error( 'name-missing-alias', [ tableAlias.location, semanticLoc ],
{ '#': 'duplicate', code: 'as ‹alias›' });
}
else {
error( 'duplicate-definition', [ loc, table ], { name, '#': 'alias' } );
}
} );

@@ -850,3 +836,3 @@ // also add to JOIN nodes for name restrictions:

}
if (table.name.id[0] === '$') {
if (table.name?.id[0] === '$' && table.name.$inferred !== '$internal') {
warning( 'name-invalid-dollar-alias', [ table.name.location, table ], {

@@ -954,3 +940,2 @@ '#': (table.name.$inferred ? '$tableImplicit' : '$tableAlias'),

setLink( col, '_block', parent._block );
initAnnotations( col, parent._block );
if (col.inline) { // `@anno elem.{ * }` does not work

@@ -1030,3 +1015,3 @@ if (col.doc) {

*
* Param `initExtensions` is for parse.cdl
* Param `initExtensions` is for parse.cdl - TODO delete
*

@@ -1188,3 +1173,2 @@ * TODO: separate extension!

checkRedefinition( elem );
initAnnotations( elem, bl );
initMembers( elem, elem, bl, initExtensions );

@@ -1204,7 +1188,2 @@ if (boundSelfParamType && (elem.kind === 'action' || elem.kind === 'function'))

}
if (!isBetaEnabled( options, 'calculatedElements' )) {
const loc = [ elem.value.location, elem ];
// TODO: this could be considered a syntax check
message( 'def-unsupported-calc-elem', loc, { '#': 'std' } );
}
elem.$syntax = 'calc';

@@ -1211,0 +1190,0 @@ }

@@ -32,3 +32,2 @@ // Things which needs to done for parse.cdl after define()

initMembers,
extensionsDict,
} = model.$functions;

@@ -40,6 +39,8 @@

function resolveTypesAndExtensionsForParseCdl() {
const late = model.$lateExtensions;
const extensions = [];
for (const name in extensionsDict) {
for (const ext of extensionsDict[name]) {
// TODO: why not just use the extensions as they are from the first source?
for (const name in late) {
for (const ext of late[name]._extensions) {
ext.name.absolute = resolveUncheckedPath( ext.name, 'extend', ext );

@@ -46,0 +47,0 @@ // Initialize members and define annotations in sub-elements.

@@ -18,3 +18,3 @@ // Main XSN-based compiler functions

const { resolveModule, resolveModuleSync } = require('../utils/moduleResolve');
const parseLanguage = require('../language/antlrParser');
const parseLanguage = require('../language/antlrParser'); // TODO: should we do some lazyload here?
const parseCsn = require('../json/from-csn');

@@ -21,0 +21,0 @@

@@ -34,5 +34,5 @@ // Kick-start: prepare to resolve all references

const art = model.definitions[name];
if (!('_parent' in art))
if (art._parent === undefined)
return; // nothing to do for builtins and redefinitions
if (art.query && !('_ancestors' in art))
if (art.query && art._ancestors === undefined)
setProjectionAncestors( art );

@@ -39,0 +39,0 @@

@@ -29,3 +29,2 @@ // Populate views with elements, elements with association targets, ...

} = require('../base/dictionaries');
const { dictLocation } = require('../base/location');
const { weakLocation } = require('../base/messages');

@@ -52,2 +51,3 @@ const { CompilerAssertion } = require('../base/error');

const $inferred = Symbol.for('cds.$inferred');
const $location = Symbol.for('cds.$location');

@@ -66,2 +66,4 @@

initArtifact,
chooseAnnotationsInArtifact,
extendArtifactAfter,
} = model.$functions;

@@ -197,3 +199,3 @@ model.$volatileFunctions.environment = environment;

// if (--depth) throw Error(`ET: ${ Object.keys(art) }`)
if ('_effectiveType' in art)
if (art._effectiveType !== undefined)
return art._effectiveType;

@@ -204,3 +206,3 @@

// console.log( 'ET-START:', art.kind, art.name )
while (art && !('_effectiveType' in art)) {
while (art && art._effectiveType === undefined) {
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles

@@ -218,4 +220,4 @@ chain.push( art );

for (const a of chain) {
// console.log( 'ET-DO:', a.name, art?.kind )
// Without type, value.path or _origin at beginning, link to itself:
chooseAnnotationsInArtifact( a );
art = populateArtifact( a, art ) || a;

@@ -226,2 +228,4 @@ if (a.elements$ || a.enum$)

a.$effectiveSeqNo = ++effectiveSeqNo;
// console.log( 'ET-DO:', effectiveSeqNo, a?.kind, a?.name, a._extensions?.elements?.length )
extendArtifactAfter( a ); // after setting _effectiveType (for messages)
}

@@ -236,2 +240,4 @@ // console.log( 'ET-END:', art?.kind, art?.name )

// console.log('Q:',art.elements,art.enum,art.items,!!art.query)
if (art.includes) // first version of includes via effectiveTpe()
art.includes.forEach( i => effectiveType( i._artifact ) );
if (art.elements != null || art.enum != null || art.items != null)

@@ -305,3 +311,3 @@ return art;

// if (--depth) throw Error(`GOR: ${ Object.keys(art) }`)
if ('_origin' in art)
if (art._origin !== undefined)
return art._origin;

@@ -340,3 +346,3 @@ if (art.type) // not stored in _origin

function resolveType( ref, user ) {
if ('_artifact' in ref)
if (ref._artifact !== undefined)
return ref._artifact;

@@ -644,4 +650,6 @@ while (user._outer) // in items

const { targetMax } = path[path.length - 1].cardinality || getCardinality( assoc );
if (targetMax && (targetMax.val === '*' || targetMax.val > 1))
elem.items = { location: dictLocation( elem.expand ) }; // TODO: array location
if (targetMax && (targetMax.val === '*' || targetMax.val > 1)) {
elem.items = { location: elem.expand[$location] };
setLink( elem.items, '_outer', elem );
}
return initFromColumns( elem, elem.expand );

@@ -1300,3 +1308,3 @@ }

initArtifact( art, !!autoexposed );
effectiveType( art ); // TODO: necessary? see traverseElementEnvironments()
effectiveType( art );
// TODO: try to set locations of elements locations of orig target elements

@@ -1303,0 +1311,0 @@ newAutoExposed.push( art );

@@ -102,2 +102,9 @@ // Propagate properties in XSN

if (origin) {
// Calculated elements that are simple references: `calc = field;`.
// Respect sibling properties in inheritance.
if (target._calcOrigin?._origin && target.value?._artifact) {
chain.push({ target, source: target.value._artifact });
if (checkAndSetStatus( target.value._artifact ))
news.push(target.value._artifact);
}
chain.push( { target, source: origin } );

@@ -182,9 +189,9 @@ if (checkAndSetStatus( origin ))

target[prop] = Object.assign( {}, val, { $inferred: 'prop' } );
if ('_artifact' in val)
if (val._artifact !== undefined)
setLink( target[prop], '_artifact', val._artifact );
if ('_outer' in val)
if (val._outer !== undefined)
setLink( target[prop], '_outer', val._outer );
if ('_parent' in val)
if (val._parent !== undefined)
setLink( target[prop], '_parent', val._parent );
if ('_main' in val)
if (val._main !== undefined)
setLink( target[prop], '_main', val._main );

@@ -364,3 +371,3 @@ }

function setEffectiveType( target, source ) {
if ('_effectiveType' in source)
if (source._effectiveType !== undefined)
setLink( target, '_effectiveType', source._effectiveType);

@@ -367,0 +374,0 @@ }

@@ -49,7 +49,6 @@ // Compiler phase "resolve": resolve all references

const { dictLocation } = require('../base/location');
const { searchName, weakLocation } = require('../base/messages');
const { weakLocation } = require('../base/messages');
const { combinedLocation } = require('../base/location');
const { typeParameters } = require('./builtins');
const { kindProperties } = require('./base');
const {

@@ -61,6 +60,4 @@ pushLink,

withAssociation,
storeExtension,
dependsOn,
dependsOnSilent,
setExpandStatusAnnotate,
testExpr,

@@ -89,12 +86,5 @@ targetMaxNotOne,

resolvePath,
checkAnnotate,
initAnnotations,
copyAnnotationsForExtensions,
attachAndEmitValidNames,
lateExtensions,
applyTypeExtensions,
effectiveType,
getOrigin,
chooseAnnotationsInArtifact,
extensionFor,
resolveType,

@@ -132,10 +122,5 @@ resolveTypeArgumentsUnchecked,

forEachGeneric( model, 'vocabularies', resolveRefs );
// for builtin types
forEachGeneric( model.definitions.cds, '_subArtifacts', chooseAnnotationsInArtifact);
forEachGeneric( model.definitions['cds.hana'], '_subArtifacts', chooseAnnotationsInArtifact);
// Phase 6: apply ANNOTATE on auto-exposed entities and unknown artifacts:
lateExtensions( annotateMembers );
if (model.extensions)
model.extensions.map( annotateUnknown );
// Phase 7: report cyclic dependencies:
// create “super” ANNOTATE statements for annotations on unknown artifacts:
lateExtensions();
// report cyclic dependencies:
detectCycles( model.definitions, ( user, art, location ) => {

@@ -468,5 +453,2 @@ if (location) {

annotateMembers( art ); // TODO recheck - recursively, but also forEachMember below
chooseAnnotationsInArtifact( art );
forEachMember( art, resolveRefs, art.targetAspect );

@@ -505,3 +487,11 @@

if (!allowedInKind.includes(art._main.kind)) {
error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
if (art.$inferred === 'include') {
// even for include-chains, we find the correct ref due to element-expansion.
const include = art._main.includes.find(i => i._artifact === art._origin._main);
error('ref-invalid-calc-elem', [ include.location || art.value.location, art ],
{ '#': art._main.kind });
}
else {
error( 'def-invalid-calc-elem', loc, { '#': art._main.kind } );
}
}

@@ -569,145 +559,2 @@ else if (!allowedInKind.includes(parent.kind)) {

// Phase 4 - annotations ---------------------------------------------------
// Some functions remain here for the moment, as resolve functionality is called
// from annotate functions, which should not be the case (TODO!)
function annotateUnknown( ext ) {
// extensions may have annotations for elements/actions/... which may
// themselves may be unknown
forEachMember(ext, annotateUnknown);
if (ext.$extension) // extension for known artifact -> already applied
return;
annotateMembers( ext );
chooseAnnotationsInArtifact( ext );
}
/**
* @param {XSN.Artifact} art
* @param {XSN.Extension[]} [extensions]
* @param {string} [prop]
* @param {string} [name]
* @param {object} [parent]
* @param {string} [kind]
*/
function annotateMembers( art, extensions, prop, name, parent, kind ) {
const showMsg = !art && parent && parent.kind !== 'annotate';
if (!art && extensions && extensions.length) {
if (Array.isArray( parent ))
return;
const parentExt = extensionFor(parent);
art = parentExt[prop] && parentExt[prop][name];
if (!art) {
art = {
kind, // for setMemberParent()
name: { id: name, location: extensions[0].name.location },
location: extensions[0].location,
};
setMemberParent( art, name, parentExt, prop );
art.kind = 'annotate'; // after setMemberParent()!
}
}
for (const ext of extensions || []) {
if ('_artifact' in ext.name) // already applied
continue;
setArtifactLink( ext.name, art );
if (art) {
checkAnnotate( ext, art );
initAnnotations( ext, ext._block, ext.kind ); // TODO: do in define.js
copyAnnotationsForExtensions( ext, art );
// eslint-disable-next-line no-shadow
forEachMember( ext, ( elem, name, prop ) => {
storeExtension( elem, name, prop, art, ext._block );
});
}
if (showMsg) {
// somehow similar to checkDefinitions():
const feature = kindProperties[parent.kind][prop];
if (prop === 'elements' || prop === 'enum') {
if (!feature) {
warning( 'anno-unexpected-elements', [ ext.name.location, art ], {},
'Elements only exist in entities, types or typed constructs' );
}
else {
const isEntity = (parent.returns?.type || parent.type)?._artifact?.kind === 'entity';
let variant = parent.enum ? 'enum' : 'element';
if (isEntity)
variant = 'entity-element';
else if (parent.returns)
variant = 'returns';
notFound( 'anno-undefined-element', ext.name.location, art,
{ '#': variant, art: parent, name },
parent.elements || parent.enum );
}
}
else if (prop === 'actions') {
if (!feature) {
warning( 'anno-unexpected-actions', [ ext.name.location, art._parent || art ], {},
'Actions and functions only exist top-level and for entities' );
}
else {
notFound( 'anno-undefined-action', ext.name.location, art,
{ art: searchName( parent, name, 'action' ) },
parent.actions );
}
}
else if (!feature) {
warning( 'anno-unexpected-params', [ ext.name.location, art ], {},
'Parameters only exist for actions or functions' );
} // TODO: entities betaMod
else {
notFound( 'anno-undefined-param', ext.name.location, art,
{ art: searchName( parent, name, 'param' ) },
parent.params );
}
}
}
if (art?._annotate) {
const aor = art.returns || art;
const obj = aor.items || aor.targetAspect || aor;
// Currently(?), effectiveType() does not calculate the effective type of
// its line item:
if (art._annotate.elements) // explicit $expand on aor needed
setExpandStatusAnnotate( aor, 'annotate' );
annotate( obj, 'element', 'elements', 'enum', art );
annotate( art, 'action', 'actions' );
annotate( art, 'param', 'params' );
// const { returns } = art._annotate;
// if (returns) {
// const dict = returns.elements;
// const env = obj.returns && obj.returns.elements || null;
// for (const n in dict)
// annotateMembers( env && env[n], dict[n], 'elements', n, parent, 'element' );
// }
}
if (art?._extendType) {
// Only works because annotateMembers is called in resolveRefs() _after_ `.type` was resolved.
// TODO: If we allow extending included elements, we may need custom $expand,
// similar to annotate above.
art._extendType.forEach(resolveRefs);
applyTypeExtensions(art);
}
return;
function notFound( msgId, location, address, args, validDict ) {
// TODO: probably move this to shared.js and use for EXTEND, too
const msg = message( msgId, [ location, address ], args );
attachAndEmitValidNames(msg, validDict);
}
// eslint-disable-next-line no-shadow
function annotate( obj, kind, prop, altProp, parent = obj ) {
const dict = art._annotate[prop];
if (dict && art._annotate[prop])
setExpandStatusAnnotate( art, 'annotate' );
const env = obj[prop] || altProp && obj[altProp] || null;
for (const n in dict)
annotateMembers( env && env[n], dict[n], prop, n, parent, kind );
}
}
// Phase 4 - queries and associations --------------------------------------

@@ -1079,2 +926,3 @@

// TODO: this is wrong - we must check typeArt.enum, not its effectiveType
// TODO: this function is not complete(!): parallel `elements` and `length`, … - rework function
const cyclic = new Set();

@@ -1188,3 +1036,3 @@ let effectiveTypeArt = effectiveType( typeArt );

function resolveParamsAndWhere( step, expected, user, extDict, isLast ) {
const alias = step._navigation && step._navigation.kind === '$tableAlias' && step._navigation;
const alias = (step._navigation?.kind === '$tableAlias') ? step._navigation : null;
const type = alias || effectiveType( step._artifact );

@@ -1202,10 +1050,15 @@ const art = (type && type.target) ? type.target._artifact : type;

}
else if (step.where && step.where.location || step.cardinality ) {
else if (step.where?.location || step.cardinality ) {
const location = combinedLocation( step.where, step.cardinality );
let variant = alias ? 'tableAlias' : 'std';
if (expected === 'from')
variant = 'from';
// XSN TODO: filter$location including […]
message( 'expr-no-filter', [ location, user ], { '#': expected },
{
std: 'A filter can only be provided when navigating along associations',
from: 'A filter can only be provided for the source entity or associations',
} );
message( 'expr-no-filter', [ location, user ], { '#': variant }, {
std: 'A filter can only be provided when navigating along associations',
// to help users for `… from E:toF { toF[…].x }`
// eslint-disable-next-line max-len
tableAlias: 'A filter can only be provided when navigating along associations, but found table alias',
from: 'A filter can only be provided for the source entity or associations',
} );
}

@@ -1212,0 +1065,0 @@ }

@@ -185,3 +185,2 @@ // Compiler functions and utilities shared across all phases

resolvePath,
checkAnnotate,
attachAndEmitValidNames,

@@ -305,3 +304,3 @@ } );

return undefined;
if ('_artifact' in ref) // also true for _artifact: undefined
if (ref._artifact !== undefined)
return ref._artifact;

@@ -603,3 +602,3 @@ if (!ref.path || ref.path.broken || !ref.path.length) {

// if head._artifact is set or is null then it was already computed once
if ('_artifact' in head)
if (head._artifact !== undefined)
return Array.isArray(head._artifact) ? false : head._artifact;

@@ -622,3 +621,3 @@ // console.log(pathName(path), !spec.next && !extDict &&

const r = e[head.id];
if (r) {
if (r && r.$inferred !== '$internal') {
if (Array.isArray(r)) { // redefinitions

@@ -657,7 +656,10 @@ setArtifactLink( head, r );

// duplicate table aliases, only mention non-ambiguous source elems
const names = r.filter( e => !e.$duplicates )
.map( e => `${ e.name.alias }.${ e.name.element }` );
if (names.length) {
error( 'ref-ambiguous', [ head.location, user ], { id: head.id, names },
'Ambiguous $(ID), replace by $(NAMES)' );
const uniqueNames = r.filter( e => !e.$duplicates);
if (uniqueNames.length) {
const names = uniqueNames.filter( e => e._parent.$inferred !== '$internal' )
.map( e => `${ e.name.alias }.${ e.name.element }` );
let variant = names.length === uniqueNames.length ? 'std' : 'few';
if (names.length === 0)
variant = 'none';
error( 'ref-ambiguous', [ head.location, user ], { '#': variant, id: head.id, names });
}

@@ -943,3 +945,3 @@ }

// ignore internal types such as cds.Association
if (valid[name].internal || valid[name].deprecated)
if (valid[name].internal || valid[name].deprecated || valid[name].$inferred === '$internal')
continue;

@@ -958,45 +960,2 @@ msg.validNames[name] = valid[name];

}
// Issue messages for annotations on namespaces and builtins
// (TODO: really here?, probably split main artifacts vs returns)
// see also lateExtensions() where similar messages are reported
function checkAnnotate( construct, art ) {
// TODO: Handle extend statements properly: Different message for empty extend?
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
// they can still be applied. Namespace annotations are extracted in to-csn.js
// In parseCdl mode USINGs and other unknown references are generated as
// namespaces which would lead to false positives.
// TODO: should this really be different to annotate-unknown?
if (art.kind === 'namespace') {
info( 'anno-namespace', [ construct.name.location, construct ], {},
'Namespaces can\'t be annotated' );
}
// Builtin annotations would also get lost. Same as for namespaces:
// extracted in to-csn.js
else if (art.builtin === true) {
info( 'anno-builtin', [ construct.name.location, construct ], {},
'Builtin types should not be annotated. Use custom type instead' );
}
// --> without art._block, art not found
else if (construct.kind === 'annotate' && art._block?.$frontend === 'cdl') {
if (construct.$syntax === 'returns' && art.kind !== 'action' && art.kind !== 'function' ) {
// `annotate ABC with returns {}` is handled just like `elements`. Warn if it is used
// for non-actions. We can't only check for !art.returns, because `action A();` is valid.
// `art._block` ensures that `art` is a defined def.
warning('ext-unexpected-returns', [ construct.name.location, construct ],
{ keyword: 'returns', meta: art.kind }, 'Unexpected $(KEYWORD) for $(META)');
}
else if (construct.$syntax !== 'returns' &&
(art.kind === 'action' || art.kind === 'function') && construct.elements) {
warning('ext-expected-returns', [ construct.name.location, construct ], {
'#': art.kind, keyword: 'returns', code: 'annotate ‹name› with returns { … }',
}, {
std: 'Expected $(CODE)', // unused variant
action: 'Expected $(KEYWORD) when annotating action return structure, i.e. $(CODE)',
function: 'Expected $(KEYWORD) when annotating function return structure, i.e. $(CODE)',
});
}
}
}
}

@@ -1003,0 +962,0 @@

@@ -259,3 +259,3 @@ // Tweak associations: rewrite keys and on conditions

// TODO: re-check for case that foreign key is managed association
if ('_effectiveType' in orig)
if (orig._effectiveType !== undefined)
setLink( fk, '_effectiveType', orig._effectiveType);

@@ -262,0 +262,0 @@ const te = copyExpr( orig.targetElement, elem.location );

@@ -58,3 +58,3 @@ // Simple compiler utility functions

*/
function annotateWith( art, anno, location = art.location, val = true, literal = 'boolean' ) {
function setAnnotation( art, anno, location = art.location, val = true, literal = 'boolean' ) {
if (art[anno]) // do not overwrite user-defined including null

@@ -452,3 +452,3 @@ return;

annotationLocation,
annotateWith,
setAnnotation,
setLink,

@@ -455,0 +455,0 @@ setArtifactLink,

@@ -54,2 +54,4 @@ 'use strict';

options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
const mergedVocabularies = translate.mergeVocRefs(options, message);

@@ -82,2 +84,3 @@ const Edm = getEdm(options, messageFunctions);

}
throwWithError();

@@ -428,2 +431,20 @@ return rc;

/*
Remove EntityContainer if empty
V4 spec says:
Chapter 5 Element edm:Schema
It MAY contain elements [...], edm:EntityContainer, [...].
Chapter 13 Element edm:EntityContainer
The edm:EntityContainer MUST contain one or more edm:EntitySet, edm:Singleton, edm:ActionImport, or edm:FunctionImport elements.
The first sentence in chapter 13 is:
Each metadata document used to describe an OData service MUST define exactly one entity container.
This sentence expresses that an OData SERVICE must contain an entity container, but an EDMX is not required to have a container.
Therefore it is absolutely legal and necessary to remove an empty container from the IR!
*/
if(Schema._ec && Schema._ec._children.length === 0) {
Schema._children.splice(Schema._children.indexOf(Schema._ec), 1);
}
Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {

@@ -1032,3 +1053,3 @@ if(refs.length > 1) {

function addAnnotations(xServiceRefs) {
let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions);
let { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);
// distribute edm:Annotations into the schemas

@@ -1035,0 +1056,0 @@ // Distribute each anno into Schema

@@ -7,4 +7,10 @@ 'use strict';

const { term } = require('../utils/term');
const { CompilerAssertion } = require('../base/error');
const inferredNiceOutput = {
'*': 'wildcard',
'aspect-composition': 'composition',
prop: 'propagation',
$generated: 'generated',
};
/**

@@ -81,26 +87,13 @@ * @param {XSN.Model} xsn

const loc = locationString(annoXsn.name.location);
let origin;
switch (annoXsn.$priority) {
case false:
if (annoXsn.$inferred)
origin = 'propagation';
else
origin = 'direct';
break;
if (annoXsn.$inferred === '$generated')
origin = 'generated';
else if (annoXsn.$inferred)
origin = inferredNiceOutput[annoXsn.$inferred] || annoXsn.$inferred;
else if (isContainedInParentLocation(annoXsn.name, artifactXsn))
origin = 'direct';
else
origin = 'annotate'; // ...or `extend`
case 'extend':
case 'annotate':
origin = annoXsn.$priority;
break;
case undefined:
if (annoXsn.$inferred === '$generated') {
origin = 'generated';
break;
}
// fallthrough
default:
throw new CompilerAssertion(`inspect anno: Unhandled Case: ${ annoXsn.$priority }`);
}
maxAnnoLength = Math.max(maxAnnoLength, anno.length);

@@ -127,7 +120,2 @@

const inferredNiceOutput = {
'*': 'wildcard',
'aspect-composition': 'composition',
};
let maxElemLength = 12;

@@ -159,3 +147,3 @@ let maxOriginLength = 6;

else if (!isContainedInParentLocation(elementXsn, artifactXsn)) {
// just a heuristic
// just a heuristic - a good enough one
origin = 'extend';

@@ -199,9 +187,5 @@ }

return false;
// Warning is correct, but we have a "TODO" below.
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
if (artLoc.line < parentLoc.line || artLoc.line > parentLoc.endLine)
return false;
// Good enough for now
// TODO: Check columns
return true;
const startDiff = artLoc.line - parentLoc.line || artLoc.col - parentLoc.col;
const endDiff = artLoc.endLine - parentLoc.endLine || artLoc.endCol - parentLoc.endCol;
return startDiff >= 0 && endDiff <= 0;
}

@@ -208,0 +192,0 @@

@@ -403,105 +403,15 @@ // Transform XSN (augmented CSN) into CSN

const exts = node.map( definition );
// builtins are non-enumerable for smaller display
for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
const art = model.definitions[name];
// For namespaces and builtins: Extract annotations since they cannot be represented
// in CSN. For all other artifacts, check whether they may be auto-exposed,
// $inferred, etc. and extract their annotations.
// In parseCdl mode extensions were already put into "extensions".
if (!model.options.parseCdl && (art.kind === 'namespace' || art.builtin)) {
extractAnnotationsToExtension( art );
}
else if (gensrcFlavor) {
if (gensrcFlavor) {
for (const name of Object.getOwnPropertyNames( model.definitions || {} ).sort()) {
const art = model.definitions[name];
// From definitions (without redefinitions) with potential inferred elements:
const result = { annotate: Object.create(null) };
attachAnnotations(result, 'annotate', { [name]: art }, art.$inferred );
attachAnnotations( result, 'annotate', { [name]: art }, art.$inferred );
if (result.annotate[name])
exts.push({ annotate: name, ...result.annotate[name] } );
exts.push( { annotate: name, ...result.annotate[name] } );
}
}
return exts.sort(
return exts.sort( // TODO: really sort with parse.cdl?
(a, b) => (a.annotate || a.extend).localeCompare( b.annotate || b.extend )
);
/*
function attachElementAnnos( annotate, art ) {
while (art.items)
art = art.items;
if (art.elements) {
const elems = inferred( art.elements, art.$inferred );
if (Object.keys( elems ).length)
annotate.elements = elems;
}
}
function attachParamAnnos( annotate, art ) {
const inferredParent = art.$inferred;
if (art.params) {
const ext = Object.create( dictionaryPrototype );
for (const name in art.params) {
const par = art.params[name];
if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
continue;
const render = annotationsAndDocComment( par );
const subElems = par.$expand !== 'origin' && (par.items || par).elements;
if (subElems) {
const sub = inferred( subElems, par.$inferred );
if (Object.keys( sub ).length)
render.elements = sub;
}
if (Object.keys(render).length)
ext[name] = render;
}
if (obj.keys( ext ))
annotate.params = ext;
}
if (art.returns) {
const par = art.returns;
if (!inferredParent && !par.$inferred && par.$expand !== 'annotate')
return;
const render = annotationsAndDocComment( par );
const subElems = par.$expand !== 'origin' && (par.items || par).elements;
if (subElems) {
const sub = inferred( subElems, par.$inferred );
if (Object.keys( sub ).length)
render.elements = sub;
}
if (Object.keys(render).length)
const sub = inferred( subElems, par.$inferred );
if (Object.keys( sub ).length)
render.elements = sub;
}
}
return ext;
*/
// extract namespace/builtin annotations
function extractAnnotationsToExtension( art ) {
const name = art.name.absolute;
// 'true' because annotations on namespaces and builtins can only
// happen through extensions.
const annos = annotationsAndDocComment( art );
const annotate = Object.assign( { annotate: name }, annos );
if (Object.keys( annotate ).length > 1) {
const loc = locationForAnnotationExtension();
if (loc)
location( loc, annotate, art );
exts.push( annotate );
}
// Either the artifact's name's location or (for builtin types) the location
// of its first annotation.
function locationForAnnotationExtension() {
if (art.location)
return art.location;
for (const key in art) {
if (key.charAt(0) === '@' && art[key].name)
return art[key].name.location;
}
return null;
}
}
}

@@ -544,3 +454,6 @@

const annoDict = Object.create( dictionaryPrototype );
for (const name in dict) {
const names = Object.keys( dict );
if (strictMode)
names.sort();
for (const name of names) {
const entry = dict[name];

@@ -554,3 +467,3 @@ const inf = inferred || entry.$inferred; // is probably always inferred if parent was

attachAnnotations( sub, 'params', entry.params, inf );
const obj = entry.returns || entry;
const obj = entry.returns || entry; // TODO: create returns !
const many = obj.items || obj;

@@ -655,5 +568,7 @@ const elems = (many.targetAspect || many).elements;

return undefined;
if (dict !== 0)
return insertOrderDict( dict );
return undefined;
// TODO(!): inside `annotate`, use sorted with --test-mode
if (dict === 0)
return undefined;
// In "super annotate" statements, use sorted dictionary
return (node.$inferred === '') ? sortedDict( dict ) : insertOrderDict( dict );
}

@@ -773,3 +688,3 @@

function addLocation( loc, csn ) {
if (loc) {
if (loc?.file) {
// Remove endLine/endCol:

@@ -799,4 +714,6 @@ // Reasoning: $location is mostly attached to definitions/members but the name

function actions( dict ) {
function actions( dict, _csn, node ) {
const keys = Object.keys( dict );
if (strictMode && node.kind === 'annotate')
keys.sort(); // TODO: always sort with --test-mode ?
return (keys.length)

@@ -909,2 +826,21 @@ ? dictionary( dict, keys, 'actions' )

/**
* Calculated elements via `includes` can inherit annotations from sibling elements.
* These annotations need to be put into `$origin`, because `$origin` points to
* the calculated element, not the simple ref's artifact.
*/
function calculatedElementOrigin( csn, xsn, origin ) {
const $origin = originRef( origin );
const result = { $origin };
for (const prop in xsn) {
if ((prop.charAt(0) === '@' || prop === 'doc') && !origin[prop] && xsn[prop].$inferred) {
const annoVal = xsn[prop];
if (annoVal.val !== null)
// materialize non-null annos (whether direct or inherited)
result[prop] = value( Object.create( annoVal, { $inferred: { value: null } } ) );
}
}
return (Object.keys( result ).length === 1) ? undefined : result;
}
function addOrigin( csn, xsn, node ) {

@@ -955,3 +891,3 @@ if (!universalCsn)

// TODO: write a xsnNode._csnOrigin, which is useful to decide whether to write
// $origins for its members
// $origins for its members
const parent = getParent( xsn );

@@ -971,4 +907,12 @@ const parentOrigin = getOrigin( parent );

const { id } = origin.name || {};
if (id && xsn.name && id !== xsn.name.id)
if (id && xsn.name && id !== xsn.name.id) {
csn.$origin = id;
}
else if (xsn._calcOrigin) {
const calcOrigin = calculatedElementOrigin( csn, xsn, origin );
if (calcOrigin)
csn.$origin = calcOrigin;
}
return;

@@ -1088,3 +1032,3 @@ }

* @param art
* @param user
* @param [user]
* @return {boolean|string[]}

@@ -1268,5 +1212,4 @@ */

function anno( node ) {
if (node.value) // expressions in annotation values
// TODO: actual string representation and not placeholder "42"
return Object.assign({ '=': '42' }, expression( node.value ));
if (node.$tokenTexts) // expressions in annotation values
return Object.assign({ '=': node.$tokenTexts }, expression( node ));
return value(node);

@@ -1288,2 +1231,4 @@ }

return undefined;
if (node.$tokenTexts)
return Object.assign({ '=': node.$tokenTexts }, expression( node ));
if (node.path) {

@@ -1389,2 +1334,4 @@ const ref = pathName( node.path );

break;
case '?:':
return ternaryOperator( node );
case 'cast':

@@ -1426,2 +1373,16 @@ return cast( expression( node.args[0] ), node );

function ternaryOperator( node ) {
const rargs = [
'case',
'when', exprInternal(node.args[0]),
'then', exprInternal(node.args[2]),
'else', exprInternal(node.args[4]),
'end',
];
if (node.$parens?.length)
return { xpr: flattenInternalXpr( rargs, 'xpr' ) };
return flattenInternalXpr( rargs, 'xpr' );
}
function query( node, csn, xsn, _prop, expectedParens = 0 ) {

@@ -1596,3 +1557,3 @@ if (node.op.val === 'SELECT') {

function addExplicitAs( node, name, implicit ) {
if (name && name.id &&
if (name?.id && name.$inferred !== '$internal' &&
(!name.$inferred || !node.ref && !node.func || implicit && implicit(name.id) ))

@@ -1599,0 +1560,0 @@ node.as = name.id;

@@ -410,2 +410,3 @@ // Error strategy with special handling for (non-reserved) keywords

// Used for sorting in messages
const token1sort = {

@@ -412,0 +413,0 @@ // 0: Identifier, Number, ...

@@ -23,2 +23,3 @@ // Generic ANTLR parser class with AST-building functions

const { weakLocation } = require('../base/messages');
const { normalizeNewLine } = require('./textUtils');

@@ -82,2 +83,3 @@ const $location = Symbol.for('cds.$location');

addAnnotation,
expressionAsAnnotationValue,
checkExtensionDict,

@@ -93,2 +95,3 @@ handleDuplicateExtension,

surroundByParens,
tokensToStringRepresentation,
secureParens,

@@ -532,4 +535,6 @@ unaryOpForParens,

/**
* Return `val` with the location of `token`. If `endToken` is provided, use its end
* location as end location in the result.
* Return `val` with a location; if `val` and `endToken` are not provided, use the
* lower-cased token string of `startToken` as `val`. As location, use the
* location covered by `startToken` and `endToken`, or only `startToken` if no
* `endToken` is provided. The `startToken` defaults to the previous token.
*

@@ -540,7 +545,11 @@ * @param {object} startToken

*/
function valueWithTokenLocation( val, startToken, endToken = null ) {
if (!startToken)
return undefined;
function valueWithTokenLocation( val = undefined, startToken = this._input.LT(-1),
endToken = undefined ) {
// if (!startToken)
// startToken = this._input.LT(-1);
const loc = this.tokenLocation( startToken, endToken );
return { location: loc, val };
return {
location: loc,
val: (endToken || val !== undefined) ? val : startToken.text.toLowerCase(),
};
}

@@ -593,2 +602,19 @@

function tokensToStringRepresentation( matchedRule ) {
const tokens = this._input.getTokens(
matchedRule.start.tokenIndex,
matchedRule.stop.tokenIndex + 1, null
).filter(tok => tok.channel === antlr4.Token.DEFAULT_CHANNEL);
if (tokens.length === 0)
return '';
let result = tokens[0].text;
for (let i = 1; i < tokens.length; ++i) {
const str = normalizeNewLine(tokens[i].text);
result += (tokens[i].start > tokens[i - 1].stop + 1) ? ` ${ str }` : str;
}
return result;
}
function unaryOpForParens( query, val ) {

@@ -706,4 +732,7 @@ const parens = query?.$parens;

}
// eslint-disable-next-line no-nested-ternary
const val = nary === '?:' ? nary
: (nary && nary !== '=' ? 'nary' : 'ixpr');
const op = {
val: (nary && nary !== '=' ? 'nary' : 'ixpr'), // there is no n-ary in rule conditionTerm
val, // there is no n-ary in rule conditionTerm
location: this.startLocation(),

@@ -824,2 +853,12 @@ };

function expressionAsAnnotationValue( assignment, cond ) {
if (!cond.cond) // parse error
return;
Object.assign(assignment, cond.cond);
assignment.$tokenTexts = this.tokensToStringRepresentation(cond);
if (!this.isBetaEnabled(this.options, 'annotationExpressions')) {
this.error( 'syntax-unsupported-expression', [ cond.cond.location ], {},
'Expressions in annotation values are not supported' );
}
}

@@ -1074,3 +1113,3 @@ // If a '-' is directly before an unsigned number, consider it part of the number;

// must be in action directly after having parsed '{' or '(`
// must be in action directly after having parsed '{', '(`, or a keyword before
function createDict() {

@@ -1077,0 +1116,0 @@ const dict = Object.create(null);

@@ -40,2 +40,14 @@ 'use strict';

/**
* Normalized CDL newlines to LF (\n). CDL newlines also contain PS and other
* characters, see cdlNewLineRegEx.
*
* @param {string} str
* @return {string}
*/
function normalizeNewLine( str ) {
// Note: cdlNewLineRegEx does not have `g` flag and we can't use replaceAll in Node 14.
return str.replace(new RegExp(cdlNewLineRegEx, 'ug'), '\n');
}
module.exports = {

@@ -45,2 +57,3 @@ isWhitespaceOrNewLineOnly,

cdlNewLineRegEx,
normalizeNewLine,
};

@@ -821,2 +821,19 @@ // Official cds-compiler API.

function delimitedId(name: string, dialect: string) : string;
/**
* Return all non-lossy changes in artifacts between two given models.
* Note: Only supports changes in artifacts compiled/rendered as db-CSN/SQL.
*
* ATTENTION: This function may change without prior notice!
* It is still considered work-in-progress!
*
* @beta
*
* @param csn A client/inferred CSN model representing the desired "after-image"
* @param options SQL specific options
* @param beforeImage A db-transformed CSN representing the "before-image", or null in case no such image
* is known, i.e. for the very first migration step.
* @returns See {@link SqlMigrationResult} for details.
*/
function migration(csn: CSN, options: SqlOptions, beforeImage: CSN): SqlMigrationResult;
}

@@ -888,6 +905,6 @@

*
* @param csn A clean input CSN representing the desired "after-image"
* @param csn A client/inferred CSN model representing the desired "after-image"
* @param options Options
* @param beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image
* is known, i.e. for the very first migration step
* @param beforeImage A SAP HANA-transformed CSN representing the "before-image", or null in case
* no such image is known, i.e. for the very first migration step.
* @returns See {@link HdiMigrationResult} for details.

@@ -948,2 +965,25 @@ */

/**
* Result type of {@link to.sql.migration}.
*
* ATTENTION: This structure may change without prior notice!
* It is still considered work-in-progress!
*
* @beta
*/
export type SqlMigrationResult = {
/**
* The desired after-image in db-transformed CSN format
*/
afterImage: CSN
/**
* An array of SQL statements to drop views/tables.
*/
drops: string[],
/**
* An array of SQL statements to ALTER/CREATE tables/views.
*/
createsAndAlters: string[],
}
/**
* Return the resulting database name for (absolute) 'artifactName', depending on the current naming

@@ -950,0 +990,0 @@ * mode.

@@ -29,2 +29,3 @@ // Main entry point for the CDS Compiler

const builtins = lazyload('./compiler/builtins');
const base = lazyload('./compiler/base');
const finalizeParseCdl = lazyload('./compiler/finalize-parse-cdl');

@@ -161,5 +162,6 @@

// new releases (even having the same major version):
$lsp: { parse: (...args) => compiler.parseX(...args),
$lsp: {
parse: (...args) => compiler.parseX(...args),
compile: (...args) => compiler.compileX(...args),
getArtifactName: a => a.name
getArtifactName: (...args) => base.getArtifactName(...args),
},

@@ -166,0 +168,0 @@

@@ -640,2 +640,3 @@ // CSN functionality for resolving references

* @param {CSN.Artifact} art
* @param {CSN.Artifact} parent
* @param {string} [scope]

@@ -645,2 +646,9 @@ * @param [extraInfo]

function resolvePath( path, art, parent, scope, extraInfo ) {
if (!art && path.length > 1) {
// TODO: For path.length===1, it may be that `art` is undefined, e.g. for CSN paths such
// as `[…, 'on', 1]` where the path segment refers to `=`.
// TODO: Check the call-side.
const loc = locationString(parent?.$location);
throw new ModelError(`Path item 0='${ pathId(path[0]) }' refers to nothing; in ${ loc }; path=${ JSON.stringify(path) }`);
}
const staticAssoc = extraInfo === 'static' && scope === 'global';

@@ -711,4 +719,8 @@ /** @type {{idx, art?, env?}[]} */

if (fromSelect)
getCache( fromSelect, '$aliases' )[query.as] = qcache;
if (fromSelect) {
const $queryNumber = all.length + 1;
const alias = query.as || `$_select_${ $queryNumber }__`;
getCache(fromSelect, '$aliases')[alias] = qcache;
}
const select = query.SELECT || query.projection;

@@ -715,0 +727,0 @@ if (select) {

@@ -66,3 +66,2 @@ 'use strict';

hasAnnotationValue,
cloneWithTransformations,
getFinalBaseTypeWithProps,

@@ -85,3 +84,3 @@ get$combined,

/**
* Get the union of all elements from the from clause
* Get the union of all elements from the "from" clause
* - descend into unions, following the lead query

@@ -98,3 +97,3 @@ * - merge all queries in case of joins

if (query.SET) {
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
if (query.SET.args[0].SELECT?.elements)
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);

@@ -117,5 +116,8 @@

return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
const elements = isSubquery ? query.SELECT.elements : art.elements;
// sub-queries also have an alias that is reachable by outer queries, in contrast to `from.as`.
const parent = query.as || query.SELECT.from.as || implicitAs(query.SELECT.from.ref);
// for better error messages, we refer to the actual reference name first
const errorParent = implicitAs(query.SELECT.from.ref);
return mergeElementsIntoMap(Object.create(null), elements, art.$location, parent, errorParent);
}

@@ -138,3 +140,3 @@ else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {

const art = artifactRef(arg);
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || implicitAs(arg.ref), implicitAs(arg.ref) || arg.as);
}

@@ -184,3 +186,3 @@ else if (arg.SELECT || arg.SET) {

existingMap[elementName].push({
element, name: elementName, source: $location, parent: getBaseName(parent), errorParent,
element, name: elementName, source: $location, parent, errorParent,
});

@@ -193,16 +195,2 @@ }

/**
* Return the name part of the artifact name - no namespace etc.
* @param {string|object} name Absolute name of the artifact
*/
function getBaseName( name ) {
if (!name)
return name;
if (name.id)
return name.id.substring( name.id.lastIndexOf('.') + 1 );
return name.substring( name.lastIndexOf('.') + 1 );
}
/**
* Return the left-most, primary source of the given query.

@@ -352,53 +340,2 @@ * @param {*} query Definition's query object

/**
* Clone 'node', transforming nodes therein recursively. Object 'transformers' is expected
* to contain a mapping of property 'key' names to transformer functions. The node's properties
* are walked recursively, calling each transformer function on its corresponding property
* 'key' of 'node', replacing 'value' in 'resultNode' with the function's return value
* (returning 'undefined' will delete the property).
* If no transformation function is found for 'key', the first letter of 'key' is tried
* instead (this seems to be intended for handling annotations that start with '@' ?)
*
* Regardless of their names, transformers are never applied to dictionary elements.
*
* The transformer functions are called with the following signature:
* transformer(value, node, resultNode, key)
*
* @param {any} rootNode Node to transform
* @param {any} transformers Object defining transformer functions
* @returns {object}
*/
function cloneWithTransformations( rootNode, transformers ) {
return transformNode(rootNode);
// This general transformation function will be applied to each node recursively
function transformNode( node ) {
// Return primitive values and null unchanged, but let objects and dictionaries through
// (Note that 'node instanceof Object' would be false for dictionaries).
if (node === null || typeof node !== 'object')
return node;
// Simply return if node is to be ignored
if (node === undefined || node._ignore)
return undefined;
// Transform arrays element-wise
if (Array.isArray(node))
return node.map(transformNode);
// Things not having 'proto' are dictionaries
const proto = Object.getPrototypeOf(node);
// Iterate own properties of 'node' and transform them into 'resultNode'
const resultNode = Object.create(proto);
for (const key of Object.keys(node)) {
// Dictionary always use transformNode(), other objects their transformer according to key
const transformer = (proto === null || proto === undefined) ? transformNode : transformers[key] || transformers[key.charAt(0)];
// Apply transformer, or use transformNode() if there is none
const resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
if (resultValue !== undefined)
resultNode[key] = resultValue;
}
return resultNode;
}
}
/**
* Resolve to the final type of a type, that means follow type chains, references, etc.

@@ -942,3 +879,3 @@ * Input is a fully qualified type name, i.e. string, or type ref, i.e. `{ ref: [...] }`.

const art = csn.definitions[namePart];
if (art && !(art.kind === 'namespace' || art.kind === 'context' || art.kind === 'service')) {
if (art && !(art.kind === 'context' || art.kind === 'service')) {
const prefix = parts.slice(0, i - 1).join('.');

@@ -945,0 +882,0 @@ const suffix = parts.slice(i - 1).join('_');

@@ -64,2 +64,3 @@ // Make internal properties of the XSN / augmented CSN visible

function revealInternalProperties( model, nameOrPath ) {
// return model;
const transformers = {

@@ -75,2 +76,3 @@ messages: m => m,

vocabularies: dictionary,
$lateExtensions: dictionary,
elements,

@@ -100,2 +102,3 @@ columns,

_annotate: reveal,
_annotateS: artifactIdentifier,
_deps: dependencyInfo,

@@ -102,0 +105,0 @@ _status: primOrString, // is a string anyway

@@ -229,2 +229,3 @@ 'use strict';

.option(' --odata-v2-partial-constr')
.option(' --odata-voc-refs <list>')
.option('-c, --csn')

@@ -258,2 +259,4 @@ .option('-f, --odata-format <format>', ['flat', 'structured'])

(Not spec compliant and V2 only)
--odata-voc-refs <list> JSON array of adhoc vocabulary definitions
[ { alias, ns, uri }, ... ]
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is

@@ -260,0 +263,0 @@ the corresponding database name (see "--sql-mapping" for "toHana or "toSql")

@@ -6,6 +6,7 @@

const { checkCSNVersion } = require('../json/csnVersion');
const { getNamespace, forEachDefinition } = require('../model/csnUtils');
const { forEachDefinition } = require('../model/csnUtils');
const { optionProcessor } = require('../optionProcessor');
const { isBetaEnabled } = require('../base/model');
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');
const { getIdentifierUtils } = require('./utils/sql');

@@ -50,2 +51,5 @@

const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);
const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });
// forRelationalDB looses empty contexts and services, add them again so that toRename can calculate the namespaces

@@ -87,6 +91,8 @@ forEachDefinition(csn, (artifact, artifactName) => {

if (art.kind === 'entity' && !art.query) {
const beforeTableName = quoteSqlId(absoluteCdsName(artifactName));
const afterTableName = plainSqlId(artifactName);
const beforeTableName = hdbcdsOrQuotedIdentifiers.renderArtifactName(artifactName);
const afterTableName = plainIdentifiers.renderArtifactName(artifactName);
if (beforeTableName !== afterTableName)
if (beforeTableName.toUpperCase() === `"${afterTableName}"`)
resultStr += ` --EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;
else if (beforeTableName !== afterTableName)
resultStr += ` EXEC 'RENAME TABLE ${beforeTableName} TO ${afterTableName}';\n`;

@@ -99,4 +105,4 @@

const beforeColumnName = quoteSqlId(name);
const afterColumnName = plainSqlId(name);
const beforeColumnName = hdbcdsOrQuotedIdentifiers.quoteSqlId(name);
const afterColumnName = plainIdentifiers.quoteSqlId(name);

@@ -106,3 +112,4 @@ if (!e._ignore) {

str = ` EXEC 'ALTER TABLE ${afterTableName} DROP ASSOCIATION ${beforeColumnName}';\n`;
else if (beforeColumnName.toUpperCase() === `"${afterColumnName}"` ) // Basically a no-op - render commented out
str = ` --EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;
else if (beforeColumnName !== afterColumnName)

@@ -116,46 +123,2 @@ str = ` EXEC 'RENAME COLUMN ${afterTableName}.${beforeColumnName} TO ${afterColumnName}';\n`;

}
/**
* Return 'name' in the form of an absolute CDS name - for the 'hdbcds' naming convention,
* this means converting '.' to '::' on the border between namespace and top-level artifact.
* For all other naming conventions, this is a no-op.
*
* @param {string} name Name to absolutify
* @returns {string} Absolute name
*/
function absoluteCdsName( name ) {
if (options.sqlMapping !== 'hdbcds')
return name;
const namespaceName = getNamespace(inputCsn, name);
if (namespaceName)
return `${namespaceName}::${name.substring(namespaceName.length + 1)}`;
return name;
}
/**
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
* is 'quoted'
*
* @param {string} name Name to quote
* @returns {string} Quoted string
*/
function quoteSqlId( name ) {
if (options.sqlMapping === 'quoted')
name = name.replace(/::/g, '.');
return `"${name.replace(/"/g, '""')}"`;
}
/**
* Return 'name' with uppercasing and appropriate "-quotes, also replacing '::' and '.' by '_'
* (to be used by 'plain' naming convention).
*
* @param {string} name Name to turn into a plain identifier
* @returns {string} A plain SQL identifier
*/
function plainSqlId( name ) {
return `"${name.toUpperCase().replace(/(::|\.)/g, '_').replace(/"/g, '""')}"`;
}
}

@@ -162,0 +125,0 @@

@@ -0,1 +1,7 @@

// Currently unused, but may become useful again if HDBCDS -> HDBTABLE
// handover becomes more prominent. Historically used with the no longer
// existent option `--compatibility`.
// If necessary, more complex expressions could be parsed with parseExpr.js
// and then stringified with parentheses again.
'use strict';

@@ -2,0 +8,0 @@

'use strict';
const { isBetaEnabled, setProp } = require('../../base/model');
const { setProp } = require('../../base/model');
const { CompilerAssertion } = require('../../base/error');

@@ -10,2 +10,3 @@ const {

const { getColumnMap } = require('./views');
const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };

@@ -22,5 +23,2 @@ /**

function rewriteCalculatedElementsInViews( csn, options, pathDelimiter, error ) {
if (!isBetaEnabled(options, 'calculatedElements'))
return;
const { inspectRef, effectiveType } = getUtils(csn, 'init-all');

@@ -112,3 +110,3 @@

const name = SELECT.from.args ? undefined : SELECT.from.as || implicitAs(SELECT.from.ref);
const name = SELECT.from.args ? undefined : SELECT.from.as || (SELECT.from.ref && implicitAs(SELECT.from.ref));

@@ -125,3 +123,3 @@ if (!containsExpandInline) {

// TODO: What about other scopes? expand/inline?
const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : art.value;
const value = (scope !== 'ref-target') ? absolutifyPaths(env, art, ref, links, name).value : keepAssocStepsInRef(ref, links, art).value;

@@ -153,4 +151,3 @@ // Is a shallow copy enough?

for (let i = 0; i < SELECT.columns.length; i++) {
const column = SELECT.columns[i];
for (const column of SELECT.columns) {
if (column.expand || column.inline)

@@ -315,3 +312,5 @@ return true;

else if (from.SET) {
return from.SET.elements || getDirectlyAdressableElements(from.SET.args[0].SELECT);
// FIXME: Check if this is correct
// args[0] could be SELECT or UNION
return getDirectlyAdressableElements({ from: from.SET.args[0] });
}

@@ -325,2 +324,6 @@ else if (from.args) {

}
else if (arg.SET) {
// FIXME: Check if this is correct
return getDirectlyAdressableElements({ from: arg.SET.args[0] });
}
else if (arg.SELECT) { // TODO: UNION

@@ -330,2 +333,9 @@ for (const elementName in arg.SELECT.elements)

}
else if (arg.args) { // TODO: Is it safe to do recursion here?
for (const subarg of arg.args) {
const elements = getDirectlyAdressableElements({ from: subarg });
for (const elementName in elements)
mergedElements[elementName] = elements[elementName];
}
}
else {

@@ -452,3 +462,37 @@ throw new CompilerAssertion(`Unhandled arg type: ${JSON.stringify(arg, null, 2)}`);

/**
* We need to keep association steps in front of the paths - else they would lead into nothing
*
* @param {Array} artRef
* @param {Array} links
* @param {object} art
* @returns {object}
*/
function keepAssocStepsInRef( artRef, links, art ) {
let lastAssocIndex = -1;
for (let i = links.length - 1; i > -1; i--) {
if (links[i].art.target) {
lastAssocIndex = i;
break;
}
}
if (lastAssocIndex > -1) {
const clone = { value: cloneCsnNonDict(art.value, cloneCsnOptions) };
applyTransformationsOnNonDictionary(clone, 'value', {
ref: (parent, prop, ref) => {
parent.ref = [ ...artRef.slice(0, lastAssocIndex + 1), ...ref ];
if (parent._links)
parent._links = [ ...links.slice(0, lastAssocIndex + 1), ...parent._links ];
},
}, {
skipStandard: { where: true }, // Do not rewrite refs inside of an association-where
});
return clone;
}
return art;
}
/**

@@ -520,9 +564,6 @@ * In order to just replace them in views, our calculated elements need to reference absolute things, i.e. have a table alias in front!

/**
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
* @param {CSN.Options} _options
*/
function processCalculatedElementsInEntities( csn, options ) {
if (!isBetaEnabled(options, 'calculatedElements'))
return;
function processCalculatedElementsInEntities( csn, _options ) {
forEachDefinition(csn, (artifact, artifactName) => {

@@ -529,0 +570,0 @@ if (artifact.kind === 'entity' && !(artifact.query || artifact.projection))

@@ -63,3 +63,3 @@ 'use strict';

// - Rename shorthand annotations according to a builtin list (EdmPreproc Candidate)
// e.g. @label -> @Common.Label or @important: [true|false] -> @UI.Importance: [#High|#Low]
// e.g. @label -> @Common.Label
// - If the association target is annotated with @cds.odata.valuelist, annotate the

@@ -319,6 +319,8 @@ // association with @Common.ValueList.viaAssociation (EdmPreproc Candidate)

// FIXME: Verify this list - are they all still required? Do we need any more?
const mappings = {
const setMappings = {
'@label': '@Common.Label',
'@title': '@Common.Label',
'@description': '@Core.Description',
};
const renameMappings = {
'@ValueList.entity': '@Common.ValueList.entity',

@@ -330,18 +332,21 @@ '@ValueList.type': '@Common.ValueList.type',

'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
}
};
const shortCuts = Object.keys(mappings);
const setShortCuts = Object.keys(setMappings);
const renameShortCuts = Object.keys(renameMappings);
Object.keys(node).forEach( name => {
if (!name.startsWith('@'))
return;
// Rename according to map above
const prefix = shortCuts.find(p => name.startsWith(p + '.') || name === p);
if(prefix) {
renameAnnotation(node, name, name.replace(prefix, mappings[prefix]));
const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.'));
if(renamePrefix) {
renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix]));
} else {
// The two mappings have no overlap, so no need to check for second map if first matched.
// Rename according to map above
const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.'));
if(setPrefix) {
setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]);
}
}
// Special case: '@important: [true|false]' becomes '@UI.Importance: [#High|#Low]'
if (name === '@important') {
renameAnnotation(node, name, '@UI.Importance');
let annotation = node['@UI.Importance'];
if (annotation !== null)
node['@UI.Importance'] = { '#': annotation ? 'High' : 'Low' };
}

@@ -357,3 +362,3 @@ // Special case: '@readonly' becomes a triplet of capability restrictions for entities,

} else {
renameAnnotation(node, name, '@Core.Computed');
setAnnotation(node, '@Core.Computed', true);
}

@@ -360,0 +365,0 @@ }

@@ -12,3 +12,3 @@ 'use strict';

const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');
const { csnRefs, pathId } = require('../model/csnRefs');
const { csnRefs, pathId, traverseQuery } = require('../model/csnRefs');
const { checkCSNVersion } = require('../json/csnVersion');

@@ -131,4 +131,3 @@ const validate = require('../checks/validator');

isAssocOrComposition,
addStringAnnotationTo,
cloneWithTransformations;
addStringAnnotationTo;
// transformUtils

@@ -180,7 +179,5 @@ let addDefaultTypeFacets,

// FIXME: This does something very similar to cloneWithTransformations -> refactor?
const transformCsn = transformUtils.transformModel;
forEachDefinition(csn, [
forEachDefinition(csn, [
// (001) Add a temporal where condition to views where applicable before assoc2join

@@ -465,3 +462,2 @@ // assoc2join eventually rewrites the table aliases

addStringAnnotationTo,
cloneWithTransformations
} = csnUtils);

@@ -547,2 +543,16 @@ }

if ((artifact.kind === 'entity') && artifact.query) {
// First pass: Set alias name for SELECTs without table alias. Required for setting proper table aliases
// for HDBCDS in naming mode HDBCDS. We use the same schema as the core-compiler, so duplicates should
// have already been reported.
let selectDepth = 0;
traverseQuery(artifact.query, null, null, (query, fromSelect) => {
if (!query.ref && !query.as && fromSelect) {
// Use +1; for UNION, it's the next select, for SELECT, it's increased later.
query.as = `$_select_${selectDepth + 1}__`;
}
if (query.SELECT)
++selectDepth;
});
const process = (parent, prop, query, path) => {

@@ -995,7 +1005,4 @@ transformEntityOrViewPass2(parent, artifact, artifactName, path.concat(prop))

const assocName = originalAssocName.replace(/\./g, pathDelimiter);
// clone the onCond for later use in the path transformation,
// also assign the _artifact elements of the path elements to the copy
const newOnCond = cloneWithTransformations(assoc.on, {
ref: (value) => cloneWithTransformations(value, {}),
});
// clone the onCond for later use in the path transformation
const newOnCond = cloneCsnNonDict(assoc.on, options);
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {

@@ -1007,3 +1014,3 @@ ref: (parent, prop, ref) => {

} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
// We could also have a $self infront of the assoc name - so we would need to shift twice
// We could also have a $self in front of the assoc name - so we would need to shift twice
ref.shift();

@@ -1015,3 +1022,3 @@ ref.shift();

// if there was a $self identifier in the forwarding association onCond
// we do not need it any more, as we prepended in the previous step the back association's id
// we do not need it anymore, as we prepended in the previous step the back association's id
if (ref[1] === '$self')

@@ -1018,0 +1025,0 @@ ref.splice(1, 1);

@@ -349,2 +349,4 @@ // @ts-nocheck

// Expressions as annotation values always have a `=` and another property.
// TODO: There must be at least one known expression property, otherwise
// it could be `type: 'unchecked'`.
return !val?.['='] || Object.keys(val) < 2;

@@ -351,0 +353,0 @@ }

@@ -15,3 +15,2 @@ 'use strict';

const { forEach } = require('../utils/objectUtils');
const { pathName } = require('../compiler/utils');

@@ -1164,2 +1163,5 @@ const RestrictedOperators = ['<', '>', '>=', '<='];

const prefix = (lhs, op, rhs) => {
return `${lhsIsVal ? lhs.val : lhs.ref.join('.')} ${op} ${rhsIsVal ? rhs : rhs.ref.join('.')}`
}
if(op === 'like' && xrefvalues.reduce((a, v) => {

@@ -1169,4 +1171,7 @@ return (v.lhs && v.rhs) ? a + 1: a;

// error if intersection of paths is zero
error(null, location, { lhs: pathName(lhs.ref), op, rhs: pathName(rhs.ref) },
'Expected compatible types for $(LHS) $(OP) $(RHS)');
error(null, location,
{
prefix: prefix(lhs, op, rhs)
},
'Expected compatible types for $(PREFIX)');
cont = false;

@@ -1177,11 +1182,20 @@ }

const x = xref[xn];
const prefix = `${pathName(lhs.ref)} ${op} ${pathName(rhs.ref)}`;
// do the paths match?
if(op !== 'like' && !(x.lhs && x.rhs)) {
if(xn.length) {
error(null, location, { prefix, name: xn, alias: pathName((x.lhs ? rhs : lhs).ref) },
error(null, location,
{
prefix: prefix(lhs, op, rhs),
name: xn,
alias: (x.lhs ? rhs : lhs).ref.join('.')
},
'$(PREFIX): Sub path $(NAME) not found in $(ALIAS)');
}
else {
error(null, location, { prefix, name: pathName((x.lhs ? lhs : rhs).ref), alias: pathName((x.lhs ? rhs : lhs).ref) },
error(null, location,
{
prefix: prefix(lhs, op, rhs),
name: (x.lhs ? lhs : rhs).ref.join('.'),
alias: (x.lhs ? rhs : lhs).ref.join('.')
},
'$(PREFIX): Path $(NAME) does not match $(ALIAS)');

@@ -1196,3 +1210,7 @@ }

if(!lhsIsVal && x.lhs && !isScalarOrNoType(x.lhs._art)) {
error(null, location, { prefix, name: `${pathName(x.lhs.ref)}${(xn.length ? '.' + xn : '')}` },
error(null, location,
{
prefix: prefix(lhs, op, rhs),
name: `${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
},
'$(PREFIX): Path $(NAME) must end on a scalar type')

@@ -1203,3 +1221,7 @@ cont = false;

if(!rhsIsVal && x.rhs && !isScalarOrNoType(x.rhs._art)) {
error(null, location, { prefix, name: `${pathName(x.rhs.ref)}${(xn.length ? '.' + xn : '')}` },
error(null, location,
{
prefix: prefix(lhs, op, rhs),
name: `${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}`
},
'$(PREFIX): Path $(NAME) must end on a scalar type');

@@ -1214,3 +1236,8 @@ cont = false;

if(lhst !== rhst) {
info(null, location, { prefix, name: xn },'$(PREFIX): Types for sub path $(NAME) don\'t match');
info(null, location,
{
prefix: prefix(lhs, op, rhs),
name: xn
},
'$(PREFIX): Types for sub path $(NAME) don\'t match');
}

@@ -1217,0 +1244,0 @@ }

'use strict';
const {
forEachDefinition, forAllQueries, getNormalizedQuery,
forEachDefinition, forAllQueries, getNormalizedQuery, forEachMemberRecursively,
} = require('../../model/csnUtils');

@@ -10,3 +10,4 @@ const { setAnnotationIfNotDefined } = require('./utils');

/**
* Set @Core.Computed on the elements of views (and projections).
* Set @Core.Computed on the elements of views (and projections) as well
* as on calculated elements of entities and aspects.
*

@@ -16,3 +17,3 @@ * @param {CSN.Model} csn

*/
function setCoreComputedOnViews( csn, csnUtils ) {
function setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils ) {
const {

@@ -29,2 +30,8 @@ artifactRef, getColumn, getElement, getOrigin,

}
else if (artifact.kind === 'entity' || artifact.kind === 'aspect') {
forEachMemberRecursively(artifact, (element) => {
if (element.value && !element.value?.ref) // calculated elements, but simple references are ignored
setAnnotationIfNotDefined(element, '@Core.Computed', true);
}, path);
}
});

@@ -90,3 +97,3 @@ /**

function getElementFromFrom( name, base ) {
if (base.SELECT && base.SELECT.elements) {
if (base.SELECT?.elements?.[name]) {
return getAncestor(base.SELECT.elements[name], name, base.SELECT);

@@ -107,3 +114,3 @@ }

throw new CompilerAssertion(JSON.stringify(base));
throw new CompilerAssertion(`Element “${name}” not found in: ${JSON.stringify(base)}`);
}

@@ -154,3 +161,4 @@

(
column.xpr || column.func || column.val !== undefined || column.param || column.SELECT || column.SET ||
column.xpr || column.list || column.func || column.val !== undefined || column.param ||
column.SELECT || column.SET ||
column.ref && [ '$at', '$now', '$user', '$session' ].includes(column.ref[0])

@@ -184,3 +192,3 @@ );

module.exports = {
setCoreComputedOnViews,
setCoreComputedOnViewsAndCalculatedElements,
};

@@ -16,3 +16,3 @@ 'use strict';

} = require('../../utils/objectUtils');
const { setCoreComputedOnViews } = require('./coreComputed');
const { setCoreComputedOnViewsAndCalculatedElements } = require('./coreComputed');

@@ -138,3 +138,3 @@ /**

*/
setCoreComputedOnViews( csn, csnUtils );
setCoreComputedOnViewsAndCalculatedElements( csn, csnUtils );
/**

@@ -278,3 +278,3 @@ * Construct an extensions object which maps a built-in type to it's annotations

* Walk over properties on member level and propagate all relevant properties
* from it's prototype.
* from its prototype.
*/

@@ -326,3 +326,3 @@ function propagateOnMemberLevel() {

/**
* Propagate properties to the `member` from it's prototype.
* Propagate properties to the `member` from its prototype.
* For that to work we calculate the prototype chain of the member and

@@ -329,0 +329,0 @@ * propagate properties along this prototype chain until we reach the `member`

{
"name": "@sap/cds-compiler",
"version": "3.7.2",
"version": "3.8.0",
"description": "CDS (Core Data Services) compiler and backends",

@@ -22,2 +22,3 @@ "homepage": "https://cap.cloud.sap/",

"test": "node scripts/verifyGrammarChecksum.js && mocha --reporter min --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter min --reporter-option maxDiffSize=0 test/ test3/",
"testci": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 scripts/testLazyLoading.js && mocha --parallel --reporter-option maxDiffSize=0 test/ test3/",
"testverbose": "node scripts/verifyGrammarChecksum.js && mocha --parallel test/ test3/",

@@ -24,0 +25,0 @@ "test3": "node scripts/verifyGrammarChecksum.js && mocha --reporter-option maxDiffSize=0 test3/",

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc