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 4.2.4 to 4.3.0

lib/checks/checkPathsInStoredCalcElement.js

8

bin/cdsc.js

@@ -281,2 +281,3 @@ #!/usr/bin/env node

inspect,
toEffectiveCsn,
};

@@ -321,2 +322,9 @@ const commandsWithoutCompilation = {

function toEffectiveCsn( model ) {
const csn = options.directBackend ? model : compactModel(model, options);
displayNamedCsn(main.for.effective(csn, options), 'effective');
return model;
}
// Execute the command line option 'toCsn' and display the results.

@@ -323,0 +331,0 @@ // Return the original model (for chaining)

6

bin/cdshi.js

@@ -44,7 +44,7 @@ #!/usr/bin/env node

if (tok.stop > tok.start) {
chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16');
chars[tok.stop] = '\x17';
chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16'); // ^O / ^V
chars[tok.stop] = '\x17'; // ^W
}
else {
chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15');
chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15'); // ^N / ^U
}

@@ -51,0 +51,0 @@ }

@@ -10,2 +10,28 @@ # ChangeLog for cds compiler and backends

## Version 4.3.0 - 2023-09-29
### Added
- compiler: it is possible to publish associations with filters in views.
Managed associations become unmanaged ones. For example:
```cds
entity Proj as projection on Base {
assoc[id = 1],
};
```
### Changed
- Update OData vocabularies: 'Aggregation', 'Capabilities', 'Common', 'PDF', 'PersonalData', 'UI'.
### Fixed
- parser: Chained methods without arguments such as `b` in `a().b.c()` were lost.
- compiler:
+ Type arguments in `cast()` functions, whose column also has an explicit type set, were not
properly checked. Now the `cast()`s type and type arguments are checked.
+ SQL function `STDDEV(*)` was not parsable.
+ In views, published association's ON-conditions containing `$projection` are now rewritten
to `$self` in the CSN `elements` property. This ensures recompilability of the CSN.
## Version 4.2.4 - 2023-09-14

@@ -12,0 +38,0 @@

@@ -11,2 +11,9 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 4.3.0 - 2023-09-29
### Removed `associationDefault`
This flag is now the default. Managed associations with exactly one foreign key can now
have a default value.
## Version 4.1.0 - 2023-07-28

@@ -13,0 +20,0 @@

@@ -21,2 +21,3 @@ /** @module API */

const sqlUtils = lazyload('../render/utils/sql');
const effective = lazyload('../transform/effective/main');

@@ -230,2 +231,19 @@ /**

/**
* Effective CSN transformation
*
* @param {CSN.Model} csn Plain input CSN
* @param {EffectiveCsnOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed
* @private
*/
function forEffective( csn, options = {} ) {
const internalOptions = prepareOptions.to.sql(options);
internalOptions.transformation = 'effective';
const eCsn = effective.effectiveCsn(csn, internalOptions);
return internalOptions.testMode ? toCsn.sortCsn(eCsn, internalOptions) : eCsn;
}
/**
* Process the given CSN into SQL.

@@ -856,2 +874,3 @@ *

for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
for_effective: publishCsnProcessor(forEffective, 'for.effective'),
/** Deprecated, will be removed in cds-compiler@v4 */

@@ -858,0 +877,0 @@ preparedCsnToEdmx,

@@ -75,2 +75,17 @@ 'use strict';

/**
* @param {CsnLocation} loc
* @returns {CsnLocation}
*/
function weakLocation( loc ) {
return {
__proto__: CsnLocation.prototype,
file: loc.file,
line: loc.line,
col: loc.col,
endLine: undefined,
endCol: undefined,
};
}
/**
* Returns a dummy location for built-in definitions.

@@ -160,2 +175,3 @@ *

emptyWeakLocation,
weakLocation,
builtinLocation,

@@ -162,0 +178,0 @@ dictLocation,

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

'def-missing-type': { severity: 'Error', configurableFor: [ 'compile' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.hdi', 'to.rename', 'for.effective' ] },

@@ -96,2 +96,3 @@ 'def-duplicate-autoexposed': { severity: 'Error' },

'ref-expecting-const': { severity: 'Error' },
'ref-expecting-foreign-key': { severity: 'Error' },
'ref-invalid-source': { severity: 'Error' },

@@ -191,9 +192,9 @@ 'ref-invalid-target': { severity: 'Error' },

'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
'odata-anno-preproc': { severity: 'Warning', configurableFor: true },
'odata-anno-dict': { severity: 'Warning', configurableFor: true },
'odata-anno-vocref': { severity: 'Warning', configurableFor: true },
'odata-anno-preproc': { severity: 'Warning' },
'odata-anno-dict': { severity: 'Warning' },
'odata-anno-vocref': { severity: 'Warning' },
'odata-anno-dict-enum': { severity: 'Error' },
'odata-anno-value': { severity: 'Warning', configurableFor: true },
'odata-anno-type': { severity: 'Warning', configurableFor: true },
'odata-anno-def': { severity: 'Info', configurableFor: true },
'odata-anno-value': { severity: 'Warning' },
'odata-anno-type': { severity: 'Warning' },
'odata-anno-def': { severity: 'Info' },
'query-ignoring-assoc-in-union': { severity: 'Info' }

@@ -280,2 +281,8 @@ };

'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
'anno-unexpected-localized-skip': {
std: 'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped',
view: 'Compiler generated view $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped',
},
'chained-array-of': '"Array of"/"many" must not be chained with another "array of"/"many" inside a service',

@@ -470,2 +477,3 @@

sibling: 'Object with property $(SIBLINGPROP) must also have a property $(PROP)',
bothTargets: 'Object with properties $(SIBLINGPROP) and $(OTHERPROP) must also have a property $(PROP)',
columns: 'Object in $(PARENTPROP) must have an expression property like $(PROP)',

@@ -477,2 +485,4 @@ extensions: 'Object in $(PARENTPROP) must have the property $(PROP) or $(OTHERPROP)',

sibling: 'CSN property $(PROP) is not expected in an object with property $(SIBLINGPROP)',
target: 'CSN property $(PROP) with sub property $(SUBPROP) is not expected in an object with property $(SIBLINGPROP)',
targetAspect: 'CSN property $(PROP) is not expected in an object with property $(SIBLINGPROP) having sub property $(SUBPROP)',
prop: 'CSN property $(PROP) is not expected in $(PARENTPROP)',

@@ -599,2 +609,5 @@ top: 'CSN property $(PROP) is not expected top-level',

cast: 'Casting to an association is not supported',
'managed-filter': 'Unexpected managed association $(NAME) in filter expression of $(ID)',
'unmanaged-filter': 'Unexpected unmanaged association $(NAME) in filter expression of $(ID)'
},

@@ -629,2 +642,5 @@ 'ref-unexpected-calculated': {

// TODO: Better text ?
'rewrite-not-supported': 'The ON-condition is not rewritten here - provide an explicit ON-condition',
'type-unexpected-typeof': {

@@ -714,3 +730,3 @@ std: 'Unexpected $(KEYWORD) for the type reference here',

std: '$(ART) can\'t have additional keys',
virtual: 'Unexpected $(PROP) for virtual element $(ART)',
virtual: 'Unexpected $(PROP) for virtual element',
// TODO: Better message?

@@ -766,3 +782,3 @@ include: '$(ART) can\'t have additional keys (through include)',

},
// TODO: Remove in v4, use duplicate-definition
// TODO: Remove in v5, use duplicate-definition (or another ID?)
'ref-duplicate-include-member': {

@@ -786,3 +802,5 @@ std: 'Duplicate member $(NAME) through multiple includes $(SORTED_ARTS)',

'ref-expecting-assoc': 'Expecting path $(ELEMREF) following “EXISTS” predicate to end with association/composition, found $(TYPE)',
'ref-expecting-const': 'A constant expression or variable is expected here',
'ref-expecting-foreign-key': 'Expecting foreign key access after managed association $(NAME) in filter expression of $(ID), but found $(ALIAS)',
'ref-invalid-target': {

@@ -840,2 +858,7 @@ std: 'An entity, projection or view is expected here', // TODO: change text

},
'query-ignoring-filter': {
std: 'Ignoring filter on published association due to explicit redirection', // unused
onCond: 'Ignoring filter on published association due to explicit redirection with ON-condition',
fKey: 'Ignoring filter on published association due to explicit redirection with explicit foreign keys',
},
'query-expected-identifier': {

@@ -857,2 +880,3 @@ std: 'Expected identifier for select item',

foreignKeys: 'Expected foreign keys of specified element $(NAME) to be the same as the inferred element\'s foreign keys',
unmanagedToManaged: 'Unexpected foreign keys in specified element $(NAME); inferred element is a managed association',
prop: 'Value for $(PROP) of the specified element $(NAME) does not match the inferred element\'s value',

@@ -862,3 +886,2 @@ enumExtra: 'Specified element $(NAME) differs from inferred element: it has an additional enum element $(ID)',

},
'query-unexpected-property': {

@@ -868,2 +891,6 @@ std: 'Unexpected property $(PROP) in the specified element $(NAME)',

},
'query-ignoring-assoc-in-union': {
'managed': 'Ignoring managed association $(NAME) that is published in a UNION',
'std': 'Ignoring association $(NAME) that is published in a UNION'
},

@@ -918,4 +945,10 @@ 'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',

'expr-missing-foreign-key': 'Path step $(ID) of $(ELEMREF) has no valid foreign keys',
'expr-missing-foreign-key': {
std: 'Path step $(ID) of $(ELEMREF) has no valid foreign keys',
publishingFilter: 'Can\'t publish managed association $(ID) with filter, as it must have at least one foreign key',
},
// -----------------------------------------------------------------------------------
// OData Message section starts here
// -----------------------------------------------------------------------------------
// OData version dependent messages

@@ -1036,7 +1069,5 @@ 'odata-spec-violation-array': 'Unexpected array type for OData $(VERSION)',

},
'query-ignoring-assoc-in-union': {
'managed': 'Ignoring managed association $(NAME) that is published in a UNION',
'std': 'Ignoring association $(NAME) that is published in a UNION'
}
// -----------------------------------------------------------------------------------
// OData Message section ends here, no messages below this line
// -----------------------------------------------------------------------------------
}

@@ -1043,0 +1074,0 @@

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

parentprop: quote.single,
subprop: quote.single,
otherprop: quote.single,

@@ -824,6 +825,7 @@ code: quote.single,

}
const { name } = arg;
if (!name)
return quoted( name );
if (!arg.name)
return quoted( arg.name );
const name = getArtifactName( arg );
const prop = [ 'element', 'param', 'action', 'alias' ].find( p => name[p] );
//if (!prop) throw Error()
if (!prop || !texts[prop] )

@@ -836,8 +838,2 @@ return shortArtName( arg );

const nameProp = {
enum: 'element',
key: 'element',
function: 'action',
};
// TODO: very likely delete this function

@@ -850,3 +846,3 @@ function searchName( art, id, variant ) {

if (type.elements) { // only mentioned elements
art = type.target && type.target._artifact || type;
art = type.target?._artifact || type;
variant = 'element';

@@ -858,6 +854,19 @@ }

}
const prop = nameProp[variant] || variant;
const name = Object.assign( { $variant: variant }, (art._artifact || art).name );
name[prop] = name[prop] ? `${ name[prop] }.${ id }` : id || '?';
return { name, kind: art.kind };
if (variant === 'absolute') {
const absolute = `${ art.name.id }.${ id }`;
return {
kind: art.kind,
name: { id: absolute, $variant: variant },
};
}
const undef = {
kind: variant || art.kind,
name: { id, $variant: variant },
};
Object.defineProperty( undef, '_parent',
{ value: art, configurable: true, writable: true } );
Object.defineProperty( undef, '_main',
{ value: art._main || art, configurable: true, writable: true } );
// console.log('SN:',undef)
return undef;
}

@@ -901,13 +910,2 @@

/**
* @param {CsnLocation} loc
* @returns {CsnLocation}
*/
function weakLocation( loc ) {
if (!loc)
return new CsnLocation();
// no endLine/endCol
return new CsnLocation( loc.file, loc.line, loc.col, undefined, undefined );
}
/**
* Return message string with location if present in compact form (i.e. one line).

@@ -1258,3 +1256,3 @@ *

* Messages without semantic locations are considered smaller (for syntax errors)
* and (currently - should not happen in v2) larger for other messages.
* and (currently - should not happen in v5) larger for other messages.
*

@@ -1309,3 +1307,19 @@ * @param {CompileMessage} msg

function shortArtName( art ) {
if (!art.name)
return artName( art );
const name = getArtifactName( art );
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
!name.absolute.includes(':'))
return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
return artName( art );
}
function artName( art, omit ) {
let suffix = 0;
while (!art.name && art._outer) {
++suffix;
art = art._outer;
}
const name = getArtifactName( art );
if (!name) {

@@ -1318,10 +1332,3 @@ const loc = art.location ? ` at ${ locationString( art.location ) }` : '';

}
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
!name.absolute.includes(':'))
return quote.double( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );
return artName( art );
}
function artName( art, omit ) {
const name = getArtifactName( art );
const r = (name.absolute) ? [ quoted( name.absolute ) ] : [];

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

r.push( 'column:' + quoted( name.element ) );
else if (art.kind === 'builtin')
return `$var:${ quoted( name.element ) }`;
else

@@ -1348,2 +1357,5 @@ // r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum

r.push( `alias:${ quoted( name.alias ) }` ); // should be late due to $self in anonymous aspect
if (suffix)
r.push( art.targetAspect ? 'target' : art.items?.items ? `items:${ suffix }` : 'items');
return r.join('/');

@@ -1375,10 +1387,10 @@ }

return !absoluteOnly && homeNameForExtend( art );
else if (!art.kind) // annotation assignments are not really supported
return (absoluteOnly) ? art.name.id : quoted( `@${ art.name.id }` );
else if (art.name._artifact) // block, extend, annotate
return homeName( art.name._artifact, absoluteOnly ); // use corresponding definition
else if (absoluteOnly)
return art.name.absolute;
let main = art._main || art;
while (main._outer) // anonymous aspect
main = main._outer._main;
return `${ main.kind }:${ artName( art ) }`;
return (absoluteOnly) ? main.name.id : `${ main.kind }:${ artName( art ) }`;
}

@@ -1390,3 +1402,3 @@

function homeNameForExtend( art ) {
if (!art.name.absolute && art._main) // new-style member name
if (art._main) // new-style member name
return `${ art._main.kind }:${ artName( art ) }`;

@@ -1792,3 +1804,2 @@ const kind = art.kind || 'extend';

hasErrors,
weakLocation,
locationString,

@@ -1795,0 +1806,0 @@ messageString,

@@ -36,4 +36,4 @@ // module- and csn/XSN-independent definitions

optionalActionFunctionParameters: true, // not supported by runtime, yet.
associationDefault: true,
annotateForeignKeys: true,
effectiveCsn: true,
// disabled by --beta-mode

@@ -40,0 +40,0 @@ nestedServices: false,

@@ -175,5 +175,13 @@ 'use strict';

function requireForeignKeyAccess( parent, refIndex, noForeignKeyCallback ) {
const { ref, _links } = parent;
const { _links } = parent;
const ref = [ ...parent.ref ]; // copy so the original ref stays untouched
const assoc = _links[refIndex].art;
const nextLink = _links[refIndex + 1]?.art;
if (nextLink?.value) {
const resolved = resolveCalculatedElementRef(nextLink);
if (resolved)
ref.splice(refIndex + 1, 1, ...resolved);
}
const next = pathId(ref[refIndex + 1]);

@@ -223,2 +231,40 @@ let possibleKeys = next && assoc.keys.filter(r => r.ref[0] === next);

/**
* As calculated elements are only resolved in a later transformation step,
* we must provide a way to check whether a calc element references e.g.
* a foreign key somewhere down the line.
*
* In the following example, `G:indirect` is eventually a foreign key of `G:toG`,
* hence it is allowed to be used in e.g. an infix filter:
* @example
* ```
* entity G {
* key id : Integer;
* idx : Integer;
* toG: Association to G { idx };
* cidx = idx;
* indirect = cidx;
* }
*
* view V as select from G where exists toG[toG.indirect = 1];
* ^^^^^^^^
* ```
*
* @param {CSN.Element} calculatedElement
* @returns {CSN.ArtifactReference} the resolved element or the calculated element itself if it is complex
*/
function resolveCalculatedElementRef( calculatedElement ) {
if (calculatedElement.value.ref) {
const { _links } = calculatedElement.value;
const leaf = _links[_links.length - 1];
// TODO: once #11538 is available, checking the leaf for `.value`
// is not enough anymore.
if (leaf.art.value)
return resolveCalculatedElementRef(leaf.art);
return calculatedElement.value.ref;
}
return null;
}
module.exports = { validateOnCondition, validateMixinOnCondition, requireForeignKeyAccess };

@@ -42,2 +42,5 @@ 'use strict';

const nonexpandableStructuredInExpression = require('./nonexpandableStructured');
const existsMustEndInAssoc = require('./existsMustEndInAssoc');
const forbidAssocInExists = require('./existsExpressionsOnlyForeignKeys');
const checkPathsInStoredCalcElement = require('./checkPathsInStoredCalcElement');
const managedWithoutKeys = require('./managedWithoutKeys');

@@ -76,3 +79,9 @@ const {

const forRelationalDBCsnValidators = [ nonexpandableStructuredInExpression, navigationIntoMany ];
const forRelationalDBCsnValidators = [
existsMustEndInAssoc,
forbidAssocInExists,
nonexpandableStructuredInExpression,
navigationIntoMany,
checkPathsInStoredCalcElement,
];
/**

@@ -79,0 +88,0 @@ * @type {Array<(query: CSN.Query, path: CSN.Path) => void>}

@@ -195,6 +195,6 @@ // Consistency checker on model (XSN = augmented CSN)

// "normal artifacts" and therefore have a custom schema
requires: [ 'kind', 'artifacts' ],
requires: [ 'kind', 'elements' ],
schema: {
kind: { test: isString, enum: [ '$magicVariables' ] },
artifacts: {
elements: {
// Do not use "normal" definitions spec because of these artifacts

@@ -206,3 +206,3 @@ // are missing the location property

'elements', '$autoElement', '$uncheckedElements', '_origin', '_extensions',
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps',
'$requireElementAccess', '_effectiveType', '$effectiveSeqNo', '_deps', '_parent',
],

@@ -244,3 +244,5 @@ schema: {

extern: {
requires: [ 'location', 'path' ],
kind: [ 'using' ],
requires: [ 'location' ],
optional: [ 'location', 'path', 'id' ],
schema: { path: { inherits: 'path', optional: [ '$delimited' ] } },

@@ -350,5 +352,5 @@ },

kind: true,
requires: [ 'location', 'path' ],
requires: [ 'location' ],
optional: [
'scope', '_artifact', '$inferred', '$parens',
'path', 'scope', '_artifact', '$inferred', '$parens',
],

@@ -523,3 +525,4 @@ },

schema: {
select: { test: TODO },
id: { test: isStringOrNumber },
select: { test: TODO }, // TODO: remove
}, // TODO: rename query prop in name

@@ -530,3 +533,2 @@ requires: [ 'location' ],

'_artifact', '$inferred',
'absolute', 'select', 'alias', 'element', 'action', 'param',
],

@@ -674,2 +676,3 @@ },

'as', // query alias name
'path-prefix', // using declaration for `entity path.prefix.E`
'aspect-composition',

@@ -686,3 +689,3 @@ 'autoexposed', // for auto-exposed entities (they can't be referred to)

'localized-entity', // `.texts` entity
'nav', // only used for MASKED, TODO(v4): Remove
'nav', // only used for MASKED, TODO(v5): Remove
'none', // only used in ensureColumnName(): Used in object representing empty alias

@@ -770,3 +773,3 @@ 'query', // inferred query properties, e.g. `key`

const parentName = parent.name && parent.name.absolute;
const parentName = parent.name?.id;
if (parentName !== 'cds' && parentName !== 'localized')

@@ -980,2 +983,7 @@ throw new InternalConsistencyError(`Property '${ prop }' must be inside namespace 'cds' or 'localized' but was '${ parentName }'${ at( [ node, parent ], prop, name ) }` );

function isStringOrNumber( node, parent, prop, spec ) {
if (typeof node !== 'number')
isString( node, parent, prop, spec );
}
function isString( node, parent, prop, spec ) {

@@ -1018,8 +1026,8 @@ if (typeof node !== 'string')

}
else if (!art.name.absolute ||
!model.definitions[art.name.absolute] &&
!(model.vocabularies && model.vocabularies[art.name.absolute])) {
else if (!art.name.id ||
!model.definitions[art.name.id] &&
!model.vocabularies?.[art.name.id]) {
// TODO: sign ignored artifacts with $inferred = 'IGNORED'
if (parent.kind === 'source' ||
art.name.absolute && art.name.absolute.startsWith( 'localized.' ))
if (parent.kind === 'source' || art.kind === 'using' ||
art.name.id?.startsWith?.( 'localized.' ))
standard( art, parent, prop, spec, name );

@@ -1026,0 +1034,0 @@ else

@@ -64,4 +64,4 @@ // Base Definitions for the Core Compiler

},
builtin: {}, // = CURRENT_DATE, TODO: improve
$parameters: {}, // $parameters in query entities
builtin: { normalized: 'element' }, // = $now, $user.id, …
$parameters: {}, // $parameters in query entities - TODO: normalized: 'alias'?
};

@@ -83,22 +83,39 @@

function getArtifactName( art ) {
if (!art.name || art.name.absolute) // no name or “old style”
return art.name;
// extend and annotate statements as members already have "sparse" names → calculate old one
const { name } = art;
if (!name) // no name
return name;
if (!art.kind) // annotation assignments
return { ...art.name, absolute: art.name.id };
if (art.kind === 'using')
return { ...art.name, absolute: art.extern.id };
const namePath = [];
let parent = art;
while (parent._main) { // until we hit the main artifact
namePath.push( parent );
let parent = art._outer || art;
while (parent._main || parent.kind === 'builtin') { // until we hit the main artifact
if (parent.name.$inferred !== '$internal' || parent.kind === '$inline')
namePath.push( parent );
if (parent.kind === 'select')
break;
parent = parent._parent;
parent = parent._outer || parent; // for anonymous aspect and items
}
namePath.reverse();
// start with id/location of art.name, and absolute of art._main
const name = { id: art.name.id, location: art.name.location, absolute: parent.name.absolute };
const dot = (art._main || typeof name.id !== 'string') ? -1 : name.id.lastIndexOf( '.' );
const rname = (!parent?.name) ? { id: name.id } : {
id: (dot < 0 ? name.id : name.id.substring( dot + 1)),
location: name.location,
absolute: (parent._main || parent).name.id,
};
if (name.path !== undefined)
rname.path = name.path;
for (const np of namePath) {
const prop = getMemberNameProp( np, np.kind );
name[prop] = (name[prop]) ? `${ name[prop] }.${ np.name.id }` : np.name.id;
rname[prop] = (rname[prop]) ? `${ rname[prop] }.${ np.name.id }` : np.name.id;
}
const link = art.name._artifact;
if (link !== undefined)
Object.defineProperty( name, '_artifact', { value: link, configurable: true, writable: true } );
return name;
if (name._artifact !== undefined) {
Object.defineProperty( rname, '_artifact',
{ value: name._artifact, configurable: true, writable: true } );
}
return rname;
}

@@ -105,0 +122,0 @@

@@ -122,3 +122,3 @@ // The builtin artifacts of CDS

AVG: 'COUNT',
STDDDEV: 'COUNT',
STDDEV: 'COUNT',
VAR: 'COUNT',

@@ -445,3 +445,3 @@ LOCATE_REGEXPR: [

// builtin namespaces don't have a cds file, so no location available
name: { absolute: name, location: builtinLocation() },
name: { id: name, location: builtinLocation() },
blocks: [],

@@ -467,6 +467,6 @@ builtin,

for (const name of Object.keys( builtins )) {
const absolute = prefix + name;
const id = prefix + name;
// TODO: reconsider whether to set a type to itself - looks wrong
const art = {
kind: 'type', builtin: true, name: { absolute },
kind: 'type', builtin: true, name: { id },
};

@@ -481,3 +481,3 @@ if (parent)

artifacts[name] = art;
model.definitions[absolute] = art;
model.definitions[id] = art;
}

@@ -488,11 +488,13 @@ return artifacts;

function setMagicVariables( builtins ) {
const artifacts = Object.create( null );
for (const name in builtins) {
const magic = builtins[name];
const elements = Object.create( null );
model.$magicVariables = { kind: '$magicVariables', elements };
for (const id in builtins) {
const magic = builtins[id];
// TODO: rename to $builtinFunction
const art = {
kind: 'builtin',
name: { id: name, absolute: '', element: name },
kind: 'builtin', // TODO: $var
name: { id },
};
artifacts[name] = art;
elements[id] = art;
setProp( art, '_parent', model.$magicVariables );

@@ -507,7 +509,6 @@ if (magic.$autoElement)

createMagicElements( art, magic.elements );
if (options.variableReplacements?.[name])
createMagicElements( art, options.variableReplacements[name] );
if (options.variableReplacements?.[id])
createMagicElements( art, options.variableReplacements[id] );
// setProp( art, '_effectiveType', art );
}
model.$magicVariables = { kind: '$magicVariables', artifacts };
}

@@ -523,6 +524,6 @@

for (const n of names) {
for (const id of names) {
const magic = {
kind: 'builtin', // TODO: '$variable'
name: { id: n, absolute: '', element: `${ art.name.element }.${ n }` },
kind: 'builtin', // TODO: '$var'
name: { id },
};

@@ -534,6 +535,6 @@ // Propagate this property so that it is available for sub-elements.

// setProp( magic, '_effectiveType', magic );
if (elements[n] && typeof elements[n] === 'object')
createMagicElements( magic, elements[n] );
if (elements[id] && typeof elements[id] === 'object')
createMagicElements( magic, elements[id] );
art.elements[n] = magic;
art.elements[id] = magic;
}

@@ -540,0 +541,0 @@ }

@@ -13,3 +13,2 @@ // Checks on XSN performed during compile() that are useful for the user

const builtins = require('../compiler/builtins');
const {

@@ -20,6 +19,4 @@ forEachGeneric,

forEachMemberRecursively,
isBetaEnabled,
} = require('../base/model');
const { CompilerAssertion } = require('../base/error');
const { pathName } = require('./utils');
const { typeParameters } = require('./builtins');

@@ -99,4 +96,4 @@

if (isKey && isVirtual) {
error( 'def-unexpected-key', [ isKey.location, elem ],
{ '#': 'virtual', art: elem.name.element, prop: 'key' } );
error( 'def-unexpected-key', [ (parentProps.key || elem.key).location, elem ],
{ '#': 'virtual', prop: 'key' } );
}

@@ -122,3 +119,3 @@ }

function checkName( construct ) { // TODO: move to define.js
if (model.options.$skipNameCheck)
if (model.options.$skipNameCheck || !construct._main)
return;

@@ -470,11 +467,3 @@ // TODO: Move a corrected version of this check to definer (but do not rely on it!):

if (elem.default) {
if (!isBetaEnabled( model.options, 'associationDefault' )) {
error( 'type-unsupported-default', [ elem.default.location, elem ], {
'#': isComposition( model, elem ) ? 'comp' : 'std',
}, {
std: 'Unsupported default value on an association',
comp: 'Unsupported default value on a composition',
} );
}
else if (elem.targetAspect || elem.on || fkCount !== 1) {
if (elem.targetAspect || elem.on || fkCount !== 1) {
const variant = (elem.targetAspect && 'targetAspect') || (elem.on && 'onCond') || 'multi';

@@ -636,6 +625,5 @@ error( 'type-unexpected-default', [ elem.default.location, elem ], {

checkExpressionAssociationUsage( elem.value, elem, false );
// If a direct SQL-style cast() has no type, but type props, the compiler does not copy the type
// props/does not use the cast(). To avoid duplicate messages, only run this check if there is
// no explicit type, as otherwise we will check the cast() twice (once here, once via element).
if (elem.value?.op?.val === 'cast' && !elem.type) {
// To avoid duplicate messages, only run this check if the type wasn't inferred from
// the cast, as otherwise we will check it twice (once here, once via element).
if (elem.value?.op?.val === 'cast' && elem.type?.$inferred !== 'cast') {
requireExplicitTypeInSqlCast( elem.value, elem );

@@ -822,2 +810,3 @@ checkTypeArguments( elem.value, elem );

// TODO: rework completely!
// TODO: if we have such a check, consider #variant, anno.@anno, anno@anno

@@ -828,9 +817,14 @@ // Has been slightly adapted for model.vocabularies but comments need to be

// Sanity checks (ignore broken assignments)
if (!anno.name?.path?.length)
if (!anno.name?.id)
return;
// Just a little workaround to adapt to changed `name`s, not nice coding:
const hashIndex = anno.name.id.indexOf( '#' );
const path = (hashIndex > 0 ? anno.name.id.substring( 0, hashIndex ) : anno.name.id)
.split( '.' ).map( id => ({ id }) );
// Annotation artifact for longest path step of annotation path
let fromArtifact = null;
let pathStepsFound = 0;
for (let i = anno.name.path.length; i > 0; i--) {
const absoluteName = anno.name.path.slice( 0, i ).map( path => path.id ).join( '.' );
for (let i = path.length; i > 0; i--) {
const absoluteName = path.slice( 0, i ).map( p => p.id ).join( '.' );
if (model.vocabularies[absoluteName]) {

@@ -848,3 +842,3 @@ fromArtifact = model.vocabularies[absoluteName];

const { artifact, endOfPath } = resolvePathFrom( anno.name.path.slice( pathStepsFound ),
const { artifact, endOfPath } = resolvePathFrom( path.slice( pathStepsFound ),
fromArtifact );

@@ -871,3 +865,3 @@

warning( null, [ anno.location || anno.name.location, art ],
{ name: pathName( anno.name.path ), anno: annoDecl.name.absolute },
{ name: anno.name.id, anno: annoDecl.name.id },
'Element $(NAME) not found for annotation $(ANNO)' );

@@ -883,4 +877,4 @@ return;

// Must have literal or path unless it is a boolean
if (!anno.literal && !anno.path && getFinalTypeNameOf( elementDecl ) !== 'cds.Boolean') {
if (elementDecl.type?._artifact.name.absolute) {
if (!anno.literal && !anno.path && elementDecl._effectiveType?.category !== 'boolean') {
if (elementDecl.type?._artifact) {
warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],

@@ -891,3 +885,3 @@ { '#': 'type', type: elementDecl.type._artifact } );

warning( 'anno-expecting-value', [ anno.location || anno.name.location, art ],
{ '#': 'std', anno: anno.name.absolute } );
{ '#': 'std', anno: anno.name.id } );
}

@@ -911,3 +905,3 @@

const anno = annoDef.name.absolute;
const anno = annoDef.name.id;
const loc = [ value.location || value.name.location, art ];

@@ -941,4 +935,6 @@

// TODO: Don't rely on name; use actual type
const type = getFinalTypeNameOf( elementDecl );
if (builtins.isStringTypeName( type )) {
const type = elementDecl._effectiveType;
if (!type)
return;
if (type.category === 'string') {
if (value.literal !== 'string' && value.literal !== 'enum' &&

@@ -950,3 +946,3 @@ !elementDecl._effectiveType.enum) {

}
else if (builtins.isBinaryTypeName( type )) {
else if (type.category === 'binary') {
if (value.literal !== 'string' && value.literal !== 'x') {

@@ -957,3 +953,3 @@ warning( null, loc, { type, anno },

}
else if (builtins.isNumericTypeName( type )) {
else if (type.category === 'decimal' || type.category === 'integer') {
if (value.literal !== 'number' && value.literal !== 'enum' &&

@@ -965,5 +961,6 @@ !elementDecl._effectiveType.enum) {

}
else if (builtins.isDateOrTimeTypeName( type )) {
else if (type.category === 'dateTime') {
if (value.literal !== 'date' && value.literal !== 'time' &&
value.literal !== 'timestamp' && value.literal !== 'string') {
// Hm, actually date and time cannot be mixed
warning( null, loc, { type, anno },

@@ -974,3 +971,3 @@ // eslint-disable-next-line max-len

}
else if (builtins.isBooleanTypeName( type )) {
else if (type.category === 'boolean') {
if (value.literal && value.literal !== 'boolean') {

@@ -981,8 +978,9 @@ warning( null, loc, { type, anno },

}
else if (builtins.isRelationTypeName( type ) || builtins.isGeoTypeName( type )) {
warning( null, loc, { type, anno },
else if (type.target || type.category === 'geo') {
warning( null, loc, { type: (type.target ? 'cds.Association' : type), anno },
'Type $(TYPE) can\'t be assigned a value for annotation $(ANNO)' );
// TODO: complain at definition instead
}
else if (!elementDecl._effectiveType.enum) {
throw new CompilerAssertion(`Unknown primitive type name: ${ type }`);
else if (!type.enum) {
throw new CompilerAssertion(`Unknown primitive type name: ${ type.name.id }`);
}

@@ -1049,13 +1047,2 @@

}
// TODO: remove
// Return the absolute name of the final type of 'node'. May return 'undefined'
// for anonymous types. DO NOT USE THIS function, it has several assumptions
// which are not necessarily true.
function getFinalTypeNameOf( node ) {
let type = node._effectiveType;
if (type.type)
type = type.type._artifact;
return type?.name?.absolute;
}
}

@@ -1062,0 +1049,0 @@

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

} = require('../base/model');
const { weakLocation } = require('../base/location');
const shuffleGen = require('../base/shuffle');

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

pathName,
splitIntoPath,
isDirectComposition,

@@ -231,4 +231,8 @@ } = require('./utils');

let namespace = src.namespace && src.namespace.path;
let prefix = namespace ? `${ pathName( namespace ) }.` : '';
let { namespace } = src;
let prefix = '';
if (namespace?.path && !namespace.path.broken) {
namespace.id = pathName( namespace.path );
prefix = `${ namespace.id }.`;
}
if (isInReservedNamespace( prefix )) {

@@ -246,3 +250,3 @@ error( 'reserved-namespace-cds', [ src.namespace.location, src.namespace ], { name: 'cds' },

}
else if (src.usings || src.namespace) {
else if (src.usings || namespace) {
src.artifacts = Object.create( null );

@@ -252,3 +256,3 @@ }

shuffleArray( src.usings ).forEach( u => addUsing( u, src ) );
if (namespace)
if (namespace?.id) // successfully set a full name for namespace
addNamespace( namespace, src );

@@ -261,3 +265,3 @@ if (src.artifacts) { // addArtifact needs usings for context extensions

else if (src.definitions) { // CSN input
prefix = '';
prefix = ''; // also for addVocabulary() below
dictForEach( shuffleDict( src.definitions ), def => addDefinition( def, src, prefix ) );

@@ -276,7 +280,4 @@ }

function addDefinition( art, block, prefix ) {
if (!art.name.absolute) {
// TODO: art.name.absolute = art.name.id || …
art.name.absolute = (!art.name.path) ? art.name.id : prefix + pathName( art.name.path );
}
const { absolute } = art.name;
art.name.id ??= prefix + pathName( art.name.path );
const absolute = art.name.id;
// TODO: check reserved, see checkName()/checkLocalizedObjects() of checks.js

@@ -309,20 +310,17 @@ if (absolute === 'cds' || isInReservedNamespace( absolute )) {

const a = Array.isArray( d ) ? d[0] : d;
if (!a.name.absolute)
a.name.absolute = prefix + name;
a.name.id ??= prefix + pathName( a.name.path );
const index = name.indexOf( '.' );
if (index < 0)
continue; // also for newly added (i.e. does not matter whether visited or not)
const id = name.substring( 0, index );
if (artifacts[id])
const using = name.substring( 0, index );
if (artifacts[using])
continue;
// TODO: enable optional locations
const location = a.name.path && a.name.path[0].location || a.location;
const absolute = prefix + id;
artifacts[id] = {
const absolute = prefix + using;
artifacts[using] = {
kind: 'using', // !, not namespace - we do not know artifact yet
name: {
id, absolute, location, $inferred: 'as',
},
name: { id: using, location, $inferred: 'as' },
// TODO: use global ref (in general - all uses of splitIntoPath)
extern: { path: splitIntoPath( location, absolute ), location },
extern: { location, id: absolute },
location,

@@ -353,10 +351,10 @@ $inferred: 'path-prefix',

return;
decl.extern.id = pathName( path );
if (!decl.name)
decl.name = { ...path[path.length - 1], $inferred: 'as' };
decl.name.absolute = pathName( path );
const name = decl.name.id;
// TODO: check name: no "."
const found = src.artifacts[name];
if (found && found.$inferred === 'path-prefix' &&
found.name.absolute === decl.name.absolute)
// a real `using` declaration is “nicer” than a compiler-generated one:
if (found && found.$inferred === 'path-prefix' && found.extern.id === decl.extern.id)
src.artifacts[name] = decl;

@@ -368,8 +366,6 @@ else

// must be called after addUsing().
function addNamespace( path, src ) {
const absolute = pathName( path );
if (path.broken) // parsing may have failed
return;
function addNamespace( namespace, src ) {
// create using for own namespace:
const last = path[path.length - 1];
// TODO: should we really do that in v5? See also initNamespaceAndUsing().
const last = namespace.path[namespace.path.length - 1];
const { id } = last;

@@ -381,7 +377,5 @@ if (src.artifacts[id] || last.id.includes( '.' ))

kind: 'using',
name: {
id, absolute, location: last.location, $inferred: 'as',
},
extern: src.namespace,
location: src.namespace.location,
name: { id, location: last.location, $inferred: 'as' },
extern: namespace,
location: namespace.location,
$inferred: 'namespace',

@@ -395,3 +389,3 @@ };

if (art.artifacts) {
const p = `${ art.name.absolute }.`;
const p = `${ art.name.id }.`;
// path prefixes (usings) must be added before extensions in artifacts:

@@ -412,3 +406,3 @@ addPathPrefixes( art.artifacts, p );

delete ext.name.path[0]._artifact; // might point to wrong JS object in phase 1
ext.name.absolute = absolute; // definition might not be there yet, no _artifact link
ext.name.id = absolute; // definition might not be there yet, no _artifact link
const location = { file: '' }; // stupid required location

@@ -418,3 +412,3 @@ const late = model.$collectedExtensions[absolute] ||

kind: 'annotate',
name: { absolute, location },
name: { id: absolute, location },
$inferred: '',

@@ -432,3 +426,3 @@ location,

// eslint-disable-next-line no-multi-assign
ext.name.select = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
ext.$effectiveSeqNo = model.$blocks[absolute] = (model.$blocks[absolute] || 0) + 1;
// add "namespace" for the case that ext.artifacts is empty (TODO: later)

@@ -467,9 +461,4 @@ // now add all definitions in ext.artifacts:

const { name } = vocab;
if (!name.absolute) {
// TODO: art.name.absolute = vocab.name.id || …
vocab.name.absolute = (!vocab.name.path)
? vocab.name.id
: prefix + pathName( vocab.name.path );
}
dictAdd( model.vocabularies, name.absolute, vocab );
name.id ??= prefix + pathName( name.path );
dictAdd( model.vocabularies, name.id, vocab );
}

@@ -528,3 +517,4 @@

function checkRedefinition( art ) {
if (!art.$duplicates || art.$errorReported === 'syntax-duplicate-extend' ||
if (!art.$duplicates || !art.name.id ||
art.$errorReported === 'syntax-duplicate-extend' ||
art.$errorReported === 'syntax-duplicate-annotate')

@@ -542,3 +532,3 @@ return;

error( 'duplicate-definition', [ art.name.location, art ], {
name: art.name.absolute,
name: art.name.id,
'#': (art.kind === 'annotation' ? 'annotation' : 'absolute' ),

@@ -554,20 +544,16 @@ } );

const decl = src.namespace;
const { path } = decl;
if (path.broken) // parsing may have failed
if (!decl.id) // parsing may have failed
return;
const { id } = path[path.length - 1];
const absolute = pathName( path );
if (!model.definitions[absolute]) {
// TODO: do we really need this namespace entry - try without (msg change)
const location = path.location || decl.location;
if (!model.definitions[decl.id]) {
// TODO: make it possible to have no location
const ns = { kind: 'namespace', name: { absolute, location }, location };
model.definitions[absolute] = ns;
const ns = { kind: 'namespace', name: decl, location: decl.location };
model.definitions[decl.id] = ns;
initArtifactParentLink( ns, model.definitions );
}
const builtin = model.$builtins[id];
const last = decl.path[decl.path.length - 1];
const builtin = model.$builtins[last.id];
if (builtin && !builtin.internal &&
src.artifacts[id] && src.artifacts[id].extern === decl) {
src.artifacts[last.id] && src.artifacts[last.id].extern === decl) {
warning( 'ref-shadowed-builtin', [ decl.location, null ], // no home artifact
{ id, art: absolute, code: `using ${ builtin.name.absolute };` },
{ id: last.id, art: decl.id, code: `using ${ builtin.name.id };` },
'$(ID) now refers to $(ART) - consider $(CODE)' );

@@ -624,16 +610,18 @@ }

function initArtifactParentLink( art, definitions ) {
function initArtifactParentLink( art, definitions, path, pathIndex ) {
setLink( art, '_parent', null );
const { absolute } = art.name;
const dot = absolute.lastIndexOf( '.' );
const { id } = art.name;
const dot = id.lastIndexOf( '.' );
if (dot < 0)
return;
art.name.id = absolute.substring( dot + 1 ); // XSN TODO: remove name.id for artifacts
const prefix = absolute.substring( 0, dot );
const prefix = id.substring( 0, dot );
let parent = definitions[prefix];
if (!parent) {
const { location } = art.name; // TODO: make it possible to have no location
parent = { kind: 'namespace', name: { absolute: prefix, location }, location };
path ??= art.name.path;
pathIndex ??= path?.length - 1;
const pathItemOrName = (path && pathIndex) ? path[--pathIndex] : art.name;
const location = weakLocation( pathItemOrName.location );
parent = { kind: 'namespace', name: { id: prefix, location }, location };
definitions[prefix] = parent;
initArtifactParentLink( parent, definitions );
initArtifactParentLink( parent, definitions, path, pathIndex );
}

@@ -644,3 +632,3 @@ setLink( art, '_parent', parent );

if (art.$duplicates !== true) // no redef or "first def"
parent._subArtifacts[absolute.substring( dot + 1 )] = art; // not dictAdd()
parent._subArtifacts[id.substring( dot + 1 )] = art; // not dictAdd()
}

@@ -651,12 +639,8 @@

function initDollarSelf( art ) {
const selfname = '$self';
// TODO: use setMemberParent() ?
const name = art.name || art._outer.name;
const self = {
name: { id: selfname, alias: selfname, absolute: name.absolute },
name: { id: '$self', location: art.location },
kind: '$self',
location: art.location,
};
if (name.element) // $self for anonymous aspect
self.name.element = name.element;
setLink( self, '_parent', art );

@@ -666,10 +650,10 @@ setLink( self, '_main', art ); // used on main artifact

art.$tableAliases = Object.create( null );
art.$tableAliases[selfname] = self;
art.$tableAliases.$self = self;
}
function initDollarParameters( art ) {
// TODO: remove $parameters in v4?
// TODO: remove $parameters in v5?
// TODO: use setMemberParent() ?
const parameters = {
name: { id: '$parameters', param: '$parameters', absolute: art.name.absolute },
name: { id: '$parameters' },
kind: '$parameters',

@@ -713,3 +697,3 @@ location: art.location,

const self = {
name: { alias: '$self', query: query.name.select, absolute: art.name.absolute },
name: { id: '$self', location: query.location },
kind: '$self',

@@ -754,6 +738,5 @@ location: query.location,

query.kind = 'select';
query.name = { location: query.location };
setMemberParent( query, main.$queries.length + 1, main );
query.name = { location: query.location, id: main.$queries.length + 1 };
setMemberParent( query, null, main );
// console.log(art.kind,art.name,query.name,query._$next.name)
// if (query.name.query === 1 && query.name.absolute === 'S') throw new CompilerAssertion();
main.$queries.push( query );

@@ -822,3 +805,3 @@ setLink( query, '_parent', art ); // _parent should point to alias/main/query

if (table.on) { // after processing args to get the $tableAliases
setMemberParent( table, query.name.select, query ); // sets _parent,_main
setMemberParent( table, query.name.id, query ); // sets _parent,_main
initSubQuery( table ); // init sub queries in ON

@@ -829,3 +812,3 @@ const aliases = Object.keys( table.$tableAliases || {} );

// user messages or references - TODO: correct if join on left?
table.name.param = aliases[1] || aliases[0] || '<unknown>';
table.name.id = aliases[1] || aliases[0] || '<unknown>';
setLink( table, '_user', query ); // TODO: do not set kind/name

@@ -845,2 +828,4 @@ setLink( table, '_$next', query._$next );

const semanticLoc = tableAlias.query?.name ? tableAlias.query : tableAlias;
// TODO: the semanticLoc query is not initialized yet, and thus cannot
// be used here
error( 'name-missing-alias', [ tableAlias.location, semanticLoc ],

@@ -909,3 +894,2 @@ { '#': 'duplicate', code: 'as ‹alias›' } );

setMemberParent( mixin, name, query );
mixin.name.alias = mixin.name.id;
setLink( mixin, '_block', art._block );

@@ -1153,8 +1137,3 @@ // TODO: do some initMembers() ? If people had annotation

const up = {
name: {
id: 'up_',
alias: 'up_',
element: obj.name.element,
absolute: obj.name.absolute,
},
name: { id: 'up_' },
kind: '$navElement',

@@ -1233,3 +1212,3 @@ location: obj.location,

if (def) {
// TODO v4: bring this always, probably even as error
// TODO v5: bring this always, probably even as error
warning( 'name-deprecated-for-artifacts', [ def.location, def ], { name: '$self' },

@@ -1240,3 +1219,4 @@ 'Do not use $(NAME) as name for an artifact definition' );

}
boundSelfParamType = { name: { absolute: '$self' } };
const location = { file: '' };
boundSelfParamType = { name: { id: '$self', location }, location };
}

@@ -1243,0 +1223,0 @@ const first = params[Object.keys( params )[0] || ''];

@@ -5,3 +5,4 @@ // Extend

const { searchName, weakLocation } = require('../base/messages');
const { weakLocation } = require('../base/location');
const { searchName } = require('../base/messages');
const {

@@ -70,3 +71,3 @@ forEachInOrder,

if (art.includes)
extensionsDict[art.name.absolute] = [];
extensionsDict[art.name.id] = [];
}

@@ -91,6 +92,6 @@

art.kind !== 'namespace') {
const { absolute } = art.name;
setLink( art, '_extensions', model.$collectedExtensions[absolute]?._extensions || null );
const { id } = art.name;
setLink( art, '_extensions', model.$collectedExtensions[id]?._extensions || null );
if (art._extensions && !art.builtin) { // keep extensions for builtin in $collectedExtensions
delete model.$collectedExtensions[absolute];
delete model.$collectedExtensions[id];
// TODO: if the extension mechanism has been completed, we could uncomment:

@@ -188,4 +189,4 @@ // art._extensions.forEach( ext => resolvePath( ext.name, ext.kind, ext )); // for LSP

const { name } = ext;
if (name?.absolute && name._artifact === undefined) {
const refCtx = (name.absolute.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
if (name?.id && name._artifact === undefined) {
const refCtx = (name.id.startsWith( 'localized.' )) ? '_extensions' : ext.kind;
resolvePath( name, refCtx, ext ); // for LSP

@@ -368,6 +369,6 @@ }

else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect
const { absolute } = art.name;
const dict = extensionsDict[absolute] || (extensionsDict[absolute] = []);
const { id } = art.name;
const dict = extensionsDict[id] || (extensionsDict[id] = []);
dict.push( ext ); // TODO: change
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[absolute])
// console.log( 'ASI:',prop,art.name,ext,extensionsDict[id])
}

@@ -499,3 +500,4 @@ // art[prop] = (art[prop]) ? art[prop].concat( ext[prop] ) : ext[prop];

const ref = pathName( node.path );
return node.variant ? `${ ref }#${ node.variant.id }` : ref;
// TODO: get rid of name.variant (induces a wrong structure anyway)
return node.variant ? `${ ref }#${ pathName( node.variant.path ) }` : ref;
}

@@ -516,3 +518,3 @@

//
// TODO v4: do not allow `extend … with (precision: …)` alone if original def also has `scale`
// TODO v5: do not allow `extend … with (precision: …)` alone if original def also has `scale`
function applyTypeExtensions( art, ext, prop, scaleDiff ) {

@@ -662,5 +664,5 @@ // console.log('ATE:',art?.[prop],ext?.[prop],scaleDiff)

const { absolute } = art.name;
return model.$collectedExtensions[absolute] ||
annotateCreate( model.$collectedExtensions, absolute );
const { id } = art.name;
return model.$collectedExtensions[id] ||
annotateCreate( model.$collectedExtensions, id );
}

@@ -679,5 +681,2 @@

}
else {
annotate.name.absolute = id; // TODO later (if all names are sparse): delete absolute
}
dict[prop || id] = annotate;

@@ -786,5 +785,5 @@ return annotate;

if (extensions && !annotate._main) {
const { absolute } = annotate.name;
const isLocalized = absolute.startsWith( 'localized.' ); // TODO: && anno
const art = model.definitions[absolute];
const { id } = annotate.name;
const isLocalized = id.startsWith( 'localized.' ); // TODO: && anno
const art = model.definitions[id];
for (const ext of extensions)

@@ -812,3 +811,3 @@ checkRemainingMainExtensions( art, ext, isLocalized );

function checkRemainingMainExtensions( art, ext, localized ) {
if (localized) // TODO v4: ignore only for annotate
if (localized) // TODO v5: ignore only for annotate
return;

@@ -1049,9 +1048,12 @@ if (!resolvePath( ext.name, ext.kind, ext )) // error for extend, info for annotate

function extendNothing( extensions, prop, name, art, validDict ) {
// TODO: probably too much magic in the creation of artName…
const extMain = { ...(art._main || art) };
const artName = searchName( art, name, dictKinds[prop] );
setLink( artName, '_main', extMain );
for (const ext of extensions) {
// TODO: use shared functionality with notFound in resolver.js
const { location } = ext.name;
const extName = { ...artName, kind: ext.kind };
extMain.kind = ext.kind;
const msg
= error( 'extend-undefined', [ location, extName ],
= error( 'extend-undefined', [ location, artName ],
{ art: artName },

@@ -1225,3 +1227,3 @@ {

*
* TODO(v4): Make this a hard error; see checkRedefinition(); maybe combine both;
* TODO(v5): Make this a hard error; see checkRedefinition(); maybe combine both;
*/

@@ -1228,0 +1230,0 @@ function checkRedefinitionThroughIncludes( parent, prop ) {

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

for (const ext of late[name]._extensions) {
ext.name.absolute = resolveUncheckedPath( ext.name, '_extensions', ext );
ext.name.id = resolveUncheckedPath( ext.name, '_extensions', ext );
// Initialize members and define annotations in sub-elements.

@@ -46,0 +46,0 @@ initMembers( ext, ext, ext._block, true );

@@ -17,7 +17,6 @@ // Generate: localized data and managed compositions

augmentPath,
splitIntoPath,
isDirectComposition,
copyExpr,
} = require('./utils');
const { weakLocation } = require('../base/messages');
const { weakLocation } = require('../base/location');

@@ -169,3 +168,3 @@ function generate( model ) {

const textsName = `${ art.name.absolute }.texts`;
const textsName = `${ art.name.id }.texts`;
const textsEntity = model.definitions[textsName];

@@ -283,9 +282,34 @@ const localized = localizedData( art, textsEntity, fioriEnabled );

function createTextsEntity( base, absolute, textElems, fioriEnabled ) {
const art = useTextsAspect
? createTextsEntityWithInclude( base, absolute, fioriEnabled )
: createTextsEntityWithDefaultElements( base, absolute, fioriEnabled );
// both functions are rather similar...
const { location } = base.name;
const art = {
kind: 'entity',
name: { id: absolute, location },
location: base.location,
elements: Object.create( null ),
$inferred: 'localized-entity',
};
setLink( art, '_block', model.$internal );
model.definitions[absolute] = art;
extendArtifactBefore( art ); // having extensions here would be wrong
if (!fioriEnabled) {
// To be compatible, we switch off draft without @fiori.draft.enabled
setAnnotation( art, '@odata.draft.enabled', art.location, false );
}
else {
const textId = {
name: { location, id: 'ID_texts' },
kind: 'element',
key: { val: true, location },
type: linkMainArtifact( location, 'cds.UUID' ),
location,
};
dictAdd( art.elements, 'ID_texts', textId );
}
const enrich = useTextsAspect
? enrichTextsEntityWithInclude
: enrichTextsEntityWithDefaultElements;
enrich( art, base, absolute, fioriEnabled );
if (addTextsLanguageAssoc) {

@@ -296,4 +320,4 @@ const language = {

location,
type: augmentPath( location, 'cds.Association' ),
target: augmentPath( location, 'sap.common.Languages' ),
type: linkMainArtifact( location, 'cds.Association' ),
target: linkMainArtifact( location, 'sap.common.Languages' ),
on: {

@@ -369,3 +393,3 @@ op: { val: '=', location },

/**
* Create the `.texts` entity for the given base artifact.
* Enrich the `.texts` entity for the given base artifact.
* In contrast to createTextsEntityWithDefaultElements(), this one creates

@@ -376,2 +400,3 @@ * an include for `sap.common.TextsAspect`.

*
* @param {XSN.Artifact} art
* @param {XSN.Artifact} base

@@ -381,32 +406,10 @@ * @param {string} absolute

*/
function createTextsEntityWithInclude( base, absolute, fioriEnabled ) {
function enrichTextsEntityWithInclude( art, base, absolute, fioriEnabled ) {
const textsAspectName = 'sap.common.TextsAspect';
const textsAspect = model.definitions['sap.common.TextsAspect'];
const elements = Object.create( null );
const { location } = base.name;
const art = {
kind: 'entity',
name: { path: splitIntoPath( location, absolute ), absolute, location },
includes: [ createInclude( textsAspectName, base.location ) ],
location: base.location,
elements,
$inferred: 'localized-entity',
};
const { location } = art.name;
if (!fioriEnabled) {
// To be compatible, we switch off draft without @fiori.draft.enabled
setAnnotation( art, '@odata.draft.enabled', art.location, false );
}
else {
// @fiori.draft.enabled artifacts need default elements ID_texts and locale.
// `locale` is copied from `sap.common.TextsAspect`, but without "key".
const textId = {
name: { location, id: 'ID_texts' },
kind: 'element',
key: { val: true, location },
type: augmentPath( location, 'cds.UUID' ),
location,
};
dictAdd( art.elements, 'ID_texts', textId );
art.includes = [ createInclude( textsAspectName, base.location ) ];
if (fioriEnabled) {
// "Early" include; only for element `locale`, which has its `key` property

@@ -420,10 +423,6 @@ // removed (or rather: it is not copied).

// TODO: what is this necessary? We do not create a text entity in this case
setLink( art, '_block', model.$internal );
model.definitions[absolute] = art;
extendArtifactBefore( art ); // having extensions here would be wrong
return art;
}
/**
* @param {XSN.Artifact} art
* @param {XSN.Artifact} base

@@ -433,19 +432,11 @@ * @param {string} absolute

*/
function createTextsEntityWithDefaultElements( base, absolute, fioriEnabled ) {
const elements = Object.create( null );
const { location } = base.name;
const art = {
kind: 'entity',
name: { path: splitIntoPath( location, absolute ), absolute, location },
location: base.location,
elements,
$inferred: 'localized-entity',
};
function enrichTextsEntityWithDefaultElements( art, base, absolute, fioriEnabled ) {
// If there is a type `sap.common.Locale`, then use it as the type for the element `locale`.
// If not, use the default `cds.String` with a length of 14.
const hasLocaleType = model.definitions['sap.common.Locale']?.kind === 'type';
const { location } = art.name;
const locale = {
name: { location, id: 'locale' },
kind: 'element',
type: augmentPath( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
type: linkMainArtifact( location, hasLocaleType ? 'sap.common.Locale' : 'cds.String' ),
location,

@@ -456,23 +447,5 @@ };

if (!fioriEnabled) {
if (!fioriEnabled)
locale.key = { val: true, location };
// To be compatible, we switch off draft without @fiori.draft.enabled
setAnnotation( art, '@odata.draft.enabled', art.location, false );
}
else {
const textId = {
name: { location, id: 'ID_texts' },
kind: 'element',
key: { val: true, location },
type: augmentPath( location, 'cds.UUID' ),
location,
};
dictAdd( art.elements, 'ID_texts', textId );
}
dictAdd( art.elements, 'locale', locale );
setLink( art, '_block', model.$internal );
model.definitions[absolute] = art;
extendArtifactBefore( art ); // having extensions here would be wrong
return art;
}

@@ -495,5 +468,5 @@

$inferred: 'localized',
type: augmentPath( location, 'cds.Composition' ),
type: linkMainArtifact( location, 'cds.Composition' ),
cardinality: { targetMax: { literal: 'string', val: '*', location }, location },
target: augmentPath( location, textsName ),
target: linkMainArtifact( location, textsName ),
on: augmentEqual( location, 'texts', keys ),

@@ -511,4 +484,4 @@ };

$inferred: 'localized',
type: augmentPath( location, 'cds.Association' ),
target: augmentPath( location, textsName ),
type: linkMainArtifact( location, 'cds.Association' ),
target: linkMainArtifact( location, textsName ),
on: augmentEqual( location, 'localized', keys ),

@@ -552,3 +525,3 @@ };

const processed = Object.create( null ); // avoid infloops with circular refs
let name = art.name.absolute; // is ok, since no recursive type possible
let name = (art._main || art).name.id; // is ok, since no recursive type possible
while (art && !processed[name]) {

@@ -562,3 +535,3 @@ if (art[prop])

return false;
name = art && art.name.absolute;
name = (art._main || art)?.name?.id;
}

@@ -616,3 +589,3 @@ else if (art.type && art._block && art.type.scope !== 'typeOf') {

return;
const entityName = `${ base.name.absolute }.${ elem.name.id }`;
const entityName = `${ base.name.id }.${ elem.name.id }`;
const entity = allowAspectComposition( target, elem, keys, entityName ) &&

@@ -633,3 +606,3 @@ createTargetEntity( target, elem, keys, entityName, base );

setLink( up_, '_origin', entity.elements.up_ );
model.$compositionTargets[entity.name.absolute] = true;
model.$compositionTargets[entity.name.id] = true;
processAspectComposition( entity );

@@ -693,3 +666,3 @@ processLocalizedData( entity );

// Only issue warning for direct usages, not for projections, includes, etc.
// TODO: Make it configurable error; v4: error
// TODO: Make it configurable error; v5: error
warning( 'def-expected-comp-aspect', [ elem.type.location, elem ],

@@ -718,4 +691,3 @@ { prop: 'Composition of', otherprop: 'Association to' },

name: {
path: splitIntoPath( location, entityName ),
absolute: entityName,
id: entityName,
// for code navigation (e.g. via `extend`s): point to the element's name

@@ -745,4 +717,4 @@ location: elem.name.location,

$inferred: 'aspect-composition',
type: augmentPath( upLocation, 'cds.Association' ),
target: augmentPath( upLocation, base.name.absolute ),
type: linkMainArtifact( upLocation, 'cds.Association' ),
target: linkMainArtifact( upLocation, base.name.id ),
cardinality: {

@@ -777,5 +749,7 @@ targetMin: { val: 1, literal: 'number', location: upLocation },

extendArtifactBefore( art ); // having extensions here would be wrong
// Apply annotations to generated artifact, prepare (not apply!) element
// annotations (remark: adding elements is not allowed for generated artifacts):
extendArtifactBefore( art );
// Copy persistence annotations from aspect.
copyPersistenceAnnotations( art, target ); // after chooseAnnotation()
copyPersistenceAnnotations( art, target ); // after extendArtifactBefore()
return art;

@@ -831,2 +805,8 @@ }

}
function linkMainArtifact( location, absolute ) {
const r = { location };
setArtifactLink( r, model.definitions[absolute] );
return r;
}
}

@@ -833,0 +813,0 @@

@@ -108,3 +108,3 @@ // Kick-start: prepare to resolve all references

return;
const sname = service.name.absolute;
const sname = service.name.id;
art._ancestors.forEach( expose );

@@ -126,4 +126,6 @@ return;

function tagCompositionTargets( elem ) {
const type = elem.type && elem.type.path;
if (elem.target && type && type[0] && type[0].id === 'cds.Composition') {
const { type } = elem;
if (elem.target && type &&
(type._artifact === model.definitions['cds.Composition'] ||
type.path?.[0].id === 'cds.Composition')) {
// A target aspect would have already moved to property `targetAspect` in

@@ -134,3 +136,3 @@ // define.js (hm... more something for kick-start.js...)

if (target)
model.$compositionTargets[target.name.absolute] = true;
model.$compositionTargets[target.name.id] = true;
}

@@ -149,3 +151,3 @@ forEachGeneric( elem, 'elements', tagCompositionTargets );

resolveUsings( def );
if (!def.name || !def.name.absolute)
if (!def.name || !def.name.id)
continue; // using {...}, parse error

@@ -152,0 +154,0 @@ const art = model.definitions[def.name.absolute];

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

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

@@ -41,3 +41,2 @@

augmentPath,
splitIntoPath,
linkToOrigin,

@@ -109,3 +108,3 @@ setMemberParent,

while (newAutoExposed.length) {
// console.log( newAutoExposed.map( a => a.name.absolute ) )
// console.log( newAutoExposed.map( a => a.name.id ) )
const all = newAutoExposed;

@@ -257,3 +256,3 @@ newAutoExposed = [];

throw new CompilerAssertion(
`Unexpected _effectiveType on leading query of ${ art.name.absolute }`
`Unexpected _effectiveType on leading query of ${ art.name.id }`
);

@@ -450,3 +449,3 @@ }

art.returns = {
name: Object.assign( {}, art.name, { id: '', param: '', location } ),
name: Object.assign( {}, art.name, { id: '', location } ),
kind: 'param',

@@ -693,11 +692,14 @@ location,

if (col.inline) {
const q = userQuery( query );
q.$inlines.push( col );
col.kind = '$inline';
col.name = {};
col.name = { id: `.${ q.$inlines.length }`, $inferred: '$internal' };
// TODO: really use $inferred: '$internal', not '$inline' ? Re-check.
// a name for this internal symtab entry (e.g. '.2' to avoid clashes
// with real elements) is only relevant for `cdsc -R`/debugging
const q = userQuery( query );
q.$inlines.push( col );
// TODO: use number = column position if "top-level", negative numbers otherwise
// (is also relevant for the semantic location - only use positive)
dependsOnSilent( q, col );
// or use userQuery( query ) in the following, too?
setMemberParent( col, `.${ q.$inlines.length }`, query );
setMemberParent( col, null, query );
initFromColumns( query, col.inline, col );

@@ -844,3 +846,3 @@ }

const names = navElem.filter( e => !e.$duplicates )
.map( e => `${ e.name.alias }.${ e.name.element }` );
.map( e => `${ e._parent.name.id }.${ e.name.id }` );
if (names.length) {

@@ -992,3 +994,3 @@ error( 'wildcard-ambiguous', [ location, query ], { id: name, names },

elem.target = {
path: [ { id: target.name.absolute, location } ],
path: [ { id: target.name.id, location } ],
scope: 'global',

@@ -1011,3 +1013,3 @@ location,

function redirectImplicitlyDo( elem, assoc, target, service ) {
// console.log('ES:',elem.name.absolute,elem.name.element);
// console.log('ES:',elem.name.id,elem.name.element);
if (assoc._main === target && elem._main?.kind === 'entity' &&

@@ -1029,6 +1031,6 @@ elem._main?._ancestors?.includes( target )) {

setLink( origTarget, '_descendants', Object.create( null ) );
if (!desc[service.name.absolute]) // could be the target itself (no repeated msgs)!
desc[service.name.absolute] = [ target ];
if (!desc[service.name.id]) // could be the target itself (no repeated msgs)!
desc[service.name.id] = [ target ];
else
desc[service.name.absolute].push( target );
desc[service.name.id].push( target );
}

@@ -1092,3 +1094,3 @@ else if (exposed.length === 1) {

const descendants = scopedExposure( target._descendants &&
target._descendants[service.name.absolute] ||
target._descendants[service.name.id] ||
[],

@@ -1135,3 +1137,3 @@ elemScope, target );

const exposed = minimalExposure( targetScope, service, false );
// console.log('PES:',elem.name.absolute,elem.name.element,exposed.map(e=>e.name.absolute))
// console.log('PES:',elem.name.id,elem.name.element,exposed.map(e=>e.name.id))
if (exposed.length === 1) // unique redirection for target scope: use that

@@ -1149,3 +1151,3 @@ return exposed[0];

const nullScope = {
kind: 'namespace', name: { absolute: autoScopeName, location }, location,
kind: 'namespace', name: { id: autoScopeName, location }, location,
};

@@ -1232,3 +1234,3 @@ model.definitions[autoScopeName] = nullScope;

// TODO: introduce deprecated._noInheritedAutoexposeViaComposition
art.$autoexpose = model.$compositionTargets[art.name.absolute]
art.$autoexpose = model.$compositionTargets[art.name.id]
? autoexposeViaComposition

@@ -1240,22 +1242,22 @@ : null;

function autoExposedName( target, service, elemScope ) {
const { absolute } = target.name;
const absolute = target.name.id;
if (isDeprecatedEnabled( options, '_shortAutoexposed' )) {
const parent = definitionScope( target )._parent;
const name = (parent) ? absolute.substring( parent.name.absolute.length + 1 ) : absolute;
const name = (parent) ? absolute.substring( parent.name.id.length + 1 ) : absolute;
// no need for dedot here (as opposed to deprecated._longAutoexposed), as
// the name for dependent entities have already been created using `_` then
return `${ service.name.absolute }.${ name }`;
return `${ service.name.id }.${ name }`;
}
if (isDeprecatedEnabled( options, '_longAutoexposed' ))
return `${ service.name.absolute }.${ absolute }`;
return `${ service.name.id }.${ absolute }`;
const base = definitionScope( target );
if (base === target)
return `${ service.name.absolute }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
return `${ service.name.id }.${ absolute.substring( absolute.lastIndexOf( '.' ) + 1 ) }`;
// for scoped (e.g. calculated) entities, use exposed name of base:
const exposed = minimalExposure( base, service, elemScope );
// console.log(exposed.map( a => a.name.absolute ));
// console.log(exposed.map( a => a.name.id ));
const sbasename = (exposed.length === 1 && exposed[0] !== base) // same with no/failed expose
? exposed[0].name.absolute
? exposed[0].name.id
: autoExposedName( base, service, elemScope );
return sbasename + absolute.slice( base.name.absolute.length );
return sbasename + absolute.slice( base.name.id.length );
}

@@ -1286,3 +1288,3 @@

if (options.testMode)
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.absolute } twice`);
throw new CompilerAssertion( `Tried to auto-expose ${ target.name.id } twice`);
}

@@ -1311,6 +1313,6 @@ return autoexposed;

const { location } = target.name;
const from = augmentPath( location, target.name.absolute );
const from = augmentPath( location, target.name.id );
let art = {
kind: 'entity',
name: { location, path: splitIntoPath( location, absolute ), absolute },
name: { location, id: absolute },
location: target.location,

@@ -1317,0 +1319,0 @@ query: { location, op: { val: 'SELECT', location }, from },

@@ -241,2 +241,4 @@ // Propagate properties in XSN

return;
if (prop === 'foreignKeys' && target.on)
return; // e.g. published associations with filters
const location = target.type && !target.type.$inferred && target.type.location ||

@@ -243,0 +245,0 @@ target.location ||

@@ -49,4 +49,3 @@ // Compiler phase "resolve": resolve all references

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

@@ -418,3 +417,3 @@ const { typeParameters } = require('./builtins');

}
if (obj.items) { // TODO: make this a while in v2 (also items proxy)
if (obj.items) { // TODO: make this a while in v5 (also items proxy)
obj = obj.items || obj; // the object which has type properties

@@ -445,7 +444,7 @@ effectiveType( obj );

// Check if relational type is missing its target or if it's used directly.
if (elemtype.category === 'relation' && obj.type.path.length > 0 &&
if (elemtype.category === 'relation' &&
!obj.target && !obj.targetAspect) {
const isCsn = (obj._block && obj._block.$frontend === 'json');
error( 'type-missing-target', [ obj.type.location, obj ],
{ '#': isCsn ? 'csn' : 'std', type: elemtype.name.absolute }, {
{ '#': isCsn ? 'csn' : 'std', type: elemtype }, {
// We don't say "use 'association to <target>" because the type could be used

@@ -595,18 +594,21 @@ // in action parameters, etc. as well.

/** @type {any} */
let iKeys = inferredElement;
let iAssoc = inferredElement;
if (inferredElement._effectiveType !== 0) {
while (iKeys._origin && !iKeys.foreignKeys)
iKeys = iKeys._origin;
while (iAssoc._origin && !iAssoc.foreignKeys && !iAssoc.on)
iAssoc = iAssoc._origin;
}
iKeys = Object.keys( iKeys.foreignKeys || {} );
if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
error( 'query-mismatched-element', [
specifiedElement.foreignKeys.location || specifiedElement.location, user,
], {
'#': 'foreignKeys',
name: user.name.id,
target: specifiedElement.target,
art: inferredElement.target,
const iKeys = Object.keys( iAssoc.foreignKeys || {} );
const loc = [
specifiedElement.foreignKeys[$location] || specifiedElement.location, user,
];
if (iAssoc.on) {
error( 'query-mismatched-element', loc, {
'#': 'unmanagedToManaged', name: user.name.id,
} );
}
else if (sKeys.length !== iKeys.length || sKeys.some( fkey => !iKeys.includes( fkey ) )) {
error( 'query-mismatched-element', loc, {
'#': 'foreignKeys', name: user.name.id,
} );
}
}

@@ -967,3 +969,3 @@

}
else {
else { // if (obj.target._artifact)
// TODO: extra with $inferred (to avoid messages)?

@@ -1195,5 +1197,3 @@ resolveExpr( obj.on, art.kind === 'mixin' ? 'mixin-on' : 'on', art );

if (!elem.on && origType.on) {
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
// TODO: Better text ?
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
return;

@@ -1294,6 +1294,6 @@ }

function findOrig( chain, alias, art ) {
if (!art || dict[art.name.absolute])
if (!art || dict[art.name.id])
// some include ref or query source cannot be found, or cyclic ref
return true;
dict[art.name.absolute] = true;
dict[art.name.id] = true;

@@ -1388,3 +1388,7 @@ if (art.includes) {

resolveParams( step.args, art, entity, expected, user, step.location );
if (entity) {
// Publishing an association with filters is ok in column
const publishAssoc = art.kind === 'entity' && expected === 'column';
if (entity || publishAssoc) {
if (step.where) {

@@ -1396,3 +1400,3 @@ setLink( step, '_user', user._user || user );

}
else if (step.where || step.cardinality ) {
else if (step.where || step.cardinality) {
const location = combinedLocation( step.where, step.cardinality );

@@ -1399,0 +1403,0 @@ let variant = alias ? 'tableAlias' : 'std';

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

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

@@ -25,2 +24,3 @@ const {

const { CsnLocation } = require('./classes');
const { CompilerAssertion } = require('../base/error');

@@ -136,6 +136,7 @@ const $location = Symbol.for( 'cds.$location' );

}
checkIgnoredFilter( elem );
}
else if (elem.foreignKeys && !inferredForeignKeys( elem.foreignKeys )) {
const assoc = getOrigin( elem );
if (assoc && assoc.on) {
if (assoc?.on) {
error( 'rewrite-on-for-managed',

@@ -146,3 +147,3 @@ [ elem.foreignKeys[$location] || dictLocation( elem.foreignKeys ), elem ],

}
else if (assoc && assoc.foreignKeys) {
else if (assoc?.foreignKeys) {
// same sequence is not checked

@@ -152,5 +153,21 @@ rewriteKeysMatch( elem, assoc );

}
checkIgnoredFilter( elem );
}
}
/**
* Publishing an association with filters is allowed, but the filter is ignored
* if the association is redirected. That indicates modeling mistakes, so we
* emit a warning.
*/
function checkIgnoredFilter( elem ) {
const lastStep = elem.value?.path?.[elem.value.path.length - 1];
if (lastStep?.where) {
const loc = lastStep.where.location;
const variant = elem.foreignKeys ? 'fKey' : 'onCond';
warning( 'query-ignoring-filter', [ loc, elem ], { '#': variant } );
}
}
function rewriteKeysMatch( thisAssoc, otherAssoc ) {

@@ -216,3 +233,3 @@ const { foreignKeys } = thisAssoc;

function rewriteAssociation( element ) {
let elem = element.items || element; // TODO v2: nested items
let elem = element.items || element; // TODO v5: nested items
if (elem.elements)

@@ -285,2 +302,4 @@ forEachGeneric( elem, 'elements', rewriteAssociation );

elem.foreignKeys[$inferred] = 'rewrite';
addConditionFromAssocPublishing( elem, assoc );
}

@@ -311,4 +330,7 @@

nav.tableAlias && elem.name.location );
cond.$inferred = 'copy';
elem.on = cond;
addConditionFromAssocPublishing( elem, assoc );
// `cond` still points to the original condition; does not include possible assoc filter
elem.on.$inferred = 'copy';
// console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},

@@ -323,7 +345,5 @@ // 'Info','ON').toString(), nav)

if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
// For "assoc1.assoc2" and "structelem1.assoc2"
if (elem._redirected !== null) { // null = already reported
error( 'rewrite-not-supported', [ elem.target.location, elem ], {},
'The ON-condition is not rewritten here - provide an explicit ON-condition' );
}
// For "assoc1.assoc2" and "struct.elem1.assoc2"
if (elem._redirected !== null) // null = already reported
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
return;

@@ -340,5 +360,165 @@ }

}
cond.$inferred = 'rewrite';
elem.on.$inferred = 'rewrite';
}
/**
* If an unmanaged association is being published, we add a potential
* filter to the ON-condition and use its cardinality.
* If a managed association is published, we transform it into an unmanaged
* and do the same.
*
* The added condition (filter) is already rewritten relative to `elem`.
*/
function addConditionFromAssocPublishing( elem, assoc ) {
const publishAssoc = (elem._main?.query && elem.value?.path?.length > 0);
if (!publishAssoc)
return;
const { location } = elem.name;
const lastStep = elem.value.path[elem.value.path.length - 1];
if (lastStep?.cardinality) {
if (!elem.cardinality)
elem.cardinality = assoc.cardinality || { location };
for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {
if (lastStep.cardinality[card])
elem.cardinality[card] = copyExpr( lastStep.cardinality[card], location );
}
}
if (lastStep?.where) {
// If there are foreign keys, transform them into an ON-condition first.
if (assoc.foreignKeys) {
const cond = foreignKeysToOnCondition( elem );
if (cond) {
elem.on = cond;
elem.foreignKeys = undefined;
}
}
if (elem.on) {
elem.on = {
op: { val: 'and', location },
args: [
// TODO: Get rid of $parens
{ ...elem.on, $parens: [ assoc.location ] },
filterToCondition( lastStep, elem ),
],
location,
$inferred: 'copy',
};
}
}
}
/**
* Transform a filter on `assoc` into an ON-condition.
* Paths inside the filter are rewritten relative to `elem`.
*/
function filterToCondition( assoc, elem ) {
const cond = copyExpr( assoc.where );
// TODO: Get rid of $parens
cond.$parens = [ assoc.location ];
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {
if (!expr.path || expr.path.length === 0)
return;
const root = expr.path[0]._navigation || expr.path[0]._artifact;
if (!root)
return; // only for compile error, e.g. missing definition
if (root.kind === '$self') {
// $projection -> $self for recompilability
expr.path[0].id = '$self';
}
else if (!root.builtin && root.kind !== 'builtin') {
expr.path.unshift({
id: elem.name.id,
location: elem.name.location,
});
setLink( expr.path[0], '_artifact', elem );
// _navigation link not necessary because this condition is not rewritten
// inside the same view (would otherwise be needed for mixins).
}
} );
return cond;
}
// Caller must ensure ON-condition correctness via rewriteExpr()!
function foreignKeysToOnCondition( elem ) {
const nav = pathNavigation( elem.value );
if (!nav.tableAlias && model.options.testMode && !elem._pathHead)
throw new CompilerAssertion('rewriting keys to ON-condition: no tableAlias but not inline');
if (!nav.tableAlias || elem._parent?.kind === 'element' ||
(nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
// - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.
// - _parent is element for expand
// - nav.item is different for multi-path steps e.g. `sub.assoc`, which is not supported, yet
// TODO: Support this
error( 'rewrite-not-supported', [ elem.value.location, elem ] );
return null;
}
let cond = [];
forEachInOrder( elem, 'foreignKeys', function keyToCond( fKey ) {
// Format: lhs = rhs
// assoc.id = assoc_id
// lhs and rhs look the same, but have different ids and _artifact links.
// rhs is rewritten further down to ensure that the foreign key is projected.
const lhs = {
path: [
{ id: elem.name.id, location: elem.name.location },
...copyExpr( fKey.targetElement.path ),
],
location: elem.name.location,
};
setLink( lhs.path[0], '_artifact', elem ); // different to rhs!
setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
const rhs = {
path: [
// use origin's name; elem could have alias
{ id: elem._origin.name.id, location: elem._origin.name.location },
...copyExpr( fKey.targetElement.path ),
],
location: elem.name.location,
};
setLink( rhs.path[0], '_artifact', elem._origin ); // different to lhs!
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
// Can't use rewriteExpr as that would use `assoc[…]` itself as well.
const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
const fkCond = {
op: { val: 'ixpr', location: elem.name.location },
args: [
lhs,
{ val: '=', literal: 'token', location: elem.name.location },
rhs,
],
location: elem.name.location,
};
cond.push(fkCond);
} );
if (cond.length === 0) {
const lastStep = elem.value.path[elem.value.path.length - 1];
error( 'expr-missing-foreign-key', [ lastStep.location, elem ], {
'#': 'publishingFilter',
id: lastStep.id,
} );
return null;
}
cond = (cond.length === 1) ? cond[0]
: {
op: { val: 'and', location: elem.name.location },
args: cond,
location: elem.name.location,
};
return cond;
}
function rewriteExpr( expr, assoc, tableAlias ) {

@@ -360,4 +540,2 @@ // Rewrite ON condition (resulting in outside perspective) for association

const root = expr.path[0]._navigation || expr.path[0]._artifact;
// console.log( info(null, [ assoc.name.location, assoc ],
// { names: expr.path.map(i => i.id), art: root }, 'TA').toString());
if (!root || root._main !== source)

@@ -367,4 +545,8 @@ return; // not $self or source element

return; // are not allowed anyway - there was an error before
const { item, elem } = firstProjectionForPath( expr.path, tableAlias, assoc );
rewritePath( expr, item, assoc, elem, assoc.value.location );
const result = firstProjectionForPath( expr.path, tableAlias, assoc );
// For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.
if (result.item && assoc._origin === result.item._artifact)
result.elem = assoc;
rewritePath( expr, result.item, assoc, result.elem, assoc.value.location );
}

@@ -385,4 +567,4 @@ else if (assoc._main.query) { // from ON cond of mixin element in query

if (nav.navigation || nav.tableAlias) { // rewrite src elem, mixin, $self[.elem]
rewritePath( expr, nav.item, assoc,
navProjection( nav.navigation, assoc ),
const elem = (assoc._origin === root) ? assoc : navProjection( nav.navigation, assoc );
rewritePath( expr, nav.item, assoc, elem,
nav.item ? nav.item.location : expr.path[0].location );

@@ -400,2 +582,4 @@ }

const elem = (assoc._main.items || assoc._main).elements[item.id];
if (!elem)
return; // See #11755
if (!(Array.isArray( elem ) || // no msg for redefs

@@ -427,3 +611,3 @@ elem === item._artifact || // redirection for explicit def

std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',
element: 'Projected association $(NAME) uses non-projected element $(MEMBER) of $(ART)',
element: 'Projected association $(NAME) uses non-projected element $(ELEMREF) of $(ART)',
} );

@@ -479,5 +663,2 @@ }

function rewriteItem( elem, item, name, assoc, forKeys ) {
// TODO: for rewriting ON conditions of explicitly provided model targets,
// we need to only rewrite the current element, not all sibling elements
// TODO: this will be different in v4
if (!elem._redirected)

@@ -484,0 +665,0 @@ return true;

@@ -14,3 +14,2 @@ // Simple compiler utility functions

const { dictAdd, pushToDict, dictFirst } = require('../base/dictionaries');
const { kindProperties } = require('./base');
const { XsnName, XsnArtifact, CsnLocation } = require('./classes');

@@ -153,19 +152,2 @@

setLink( elem, '_main', parent._main || parent );
const parentName = parent.name || parent._outer?.name;
if (parentName) // may not be available in e.g. cast() - TODO recheck (#9503)
elem.name.absolute = parentName.absolute;
if (!parentName || name == null)
return;
const normalized = kindProperties[elem.kind].normalized || elem.kind;
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
if (normalized === kind)
elem.name[kind] = (parentName[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parentName[kind] }.${ name }` : name;
else if (parentName[kind] != null)
elem.name[kind] = parentName[kind];
else
delete elem.name[kind];
} );
// try { throw new CompilerAssertion('Foo') } catch (e) { elem.name.stack = e; };
}

@@ -172,0 +154,0 @@

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

result.push(color.underline('analyzing propagation for artifact:'));
// TODO: back to artifactXsn.name.id (not ok now, and not before this change!)
result.push(` name: ${ artifactXsn.name.id }`);

@@ -135,3 +136,3 @@ result.push(` kind: ${ artifactXsn.kind }`);

let origin;
const originName = elementXsn._origin?.name?.absolute || '';
const originName = (elementXsn._origin?._main || elementXsn._origin)?.name?.id || '';

@@ -138,0 +139,0 @@ if (elementXsn.$inferred) {

@@ -1039,3 +1039,3 @@ // Transform XSN (augmented CSN) into CSN

const name = parent.name || parent._outer.name;
if (name.id || !r.length)
if (name.id && parent.kind !== '$inline' || !r.length)
// Return parameter is in XSN - kind: 'param', name.id: ''

@@ -1051,3 +1051,3 @@ // eslint-disable-next-line no-nested-ternary, max-len

r.push( parent.name.absolute );
r.push( (parent._main || parent).name.id );
r.reverse();

@@ -1059,6 +1059,6 @@ return r;

if (k === 'annotate' || k === 'extend') {
// We just use `name.absolute` because it is very likely a "constructed"
// We just use `name.id` because it is very likely a "constructed"
// extensions. The CSN parser must produce name.path like for other refs.
if (!node._main)
csn[k] = node.name.absolute || artifactRef( node.name, true );
csn[k] = node.name.id || artifactRef( node.name, true );
else if (k === 'extend')

@@ -1108,3 +1108,3 @@ csn.kind = k;

if (terse && node._artifact && !node._artifact._main && terse !== '.path')
return node._artifact.name.absolute;
return node._artifact.name.id;
let { path } = node;

@@ -1118,3 +1118,4 @@ if (!path)

const root = head._artifact;
const id = root?.name.absolute;
const main = root?._main || root;
const id = (main?.extern || main?.name)?.id;
const scope = node.scope || path.length;

@@ -1125,3 +1126,3 @@

const name = item._artifact?.name;
const absolute = name?.absolute ||
const absolute = name?.id ||
`${ id || head.id }.${ path.slice( 1, scope ).map( i => i.id ).join('.') }`;

@@ -1131,10 +1132,19 @@ path = [ Object.assign( {}, item, { id: absolute } ), ...path.slice( scope ) ];

else if (scope === 'typeOf') { // TYPE OF without ':' in path
if (root) {
const structs = root.name.element?.split('.').map( n => ({ id: n }) );
// TODO: change (follow parents) if we introduce sparse names
path = [ { id }, ...(structs || []), ...path.slice(1) ];
}
else if (strictMode) {
if (!root) {
throw new CompilerAssertion( `Unexpected TYPE OF in ${ locationString(node.location) }`);
}
else if (!root._main) {
path = [ { id }, ...path.slice(1) ];
}
else {
path = path.slice(1).reverse();
let parent = root;
while (parent._main) {
path.push( { id: parent.name.id } );
parent = parent._parent;
parent = parent._outer || parent; // for anonymous aspect
}
path.push( { id } );
path.reverse();
}
}

@@ -1141,0 +1151,0 @@ else if (root && id !== head.id) {

@@ -53,3 +53,3 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

const n = super.LT(k + 1);
// TODO v4: rewrite token in grammar via `this.setLocalToken`
// TODO v5: rewrite token in grammar via `this.setLocalToken`
if (n?.type === this.Identifier) {

@@ -56,0 +56,0 @@ const o = super.LT(k + 2);

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

case ATNState.STAR_LOOP_BACK: { // 9
// TODO: do not delete a '}'
// TODO: do not delete a '}', ')', ',', ';'
this.reportUnwantedToken(recognizer);

@@ -349,2 +349,6 @@ const expecting = new IntervalSet();

const token = recognizer.getCurrentToken();
// TODO: do not delete `)`, `}`,
// TODO: overwrite singleTokenDeletion do not delete parens etc for identifier
// or non-reserved keywords
if (!keywordRegexp.test( token.text ))

@@ -351,0 +355,0 @@ return this._super.recoverInline.call( this, recognizer );

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

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

@@ -374,4 +374,5 @@

else {
absolute = prefix.slice( 0, -1 );
absolute = prefix.slice( 0, -1 ); // remove final dot
}
if ($flatten) {

@@ -382,3 +383,3 @@ for (const a of $flatten)

else {
name.absolute = absolute;
name.id = absolute;
this.addAnnotation( art, `@${ absolute }`, anno );

@@ -712,2 +713,14 @@ }

function identAst( token, category, noTokenTypeCheck = false ) {
if (!token) { // for rule identAst
const { start, stop } = this._ctx; // token.tokenIndex
// - correct parsing: start = stop
// - singleTokenDeletion(), e.g. with `| Ident`: start < stop → stop
// - after recoverInline: start > stop (!) → stop = the previous token, if it is
// ident-like and the one before not in `.@#`, → start ('') otherwise
token = stop;
if (start.tokenIndex > stop.tokenIndex &&
(stop.type !== this.constructor.Identifier && !/^[a-zA-Z_]+$/.test( stop.text ) ||
[ '.', '@', '#' ].includes( this._input.LT(-2)?.text )))
token = start;
}
token.isIdentifier = category;

@@ -783,8 +796,3 @@ let id = token.text;

return ref;
if (path.length !== 1) {
const item = path.find( i => i.args && i.$syntax !== ':' );
if (!item) // also covers empty paths
return ref;
}
else if (path.length === 1) {
if (path.length === 1) {
const { args, id, location } = path[0];

@@ -807,36 +815,42 @@ if (args

// method call ---------------------------
// $syntax === ':' => path(P: 1)
// $syntax !== ':' => path(P => 1) or path(1) or path()
const firstFunc = path.findIndex( i => i.args && i.$syntax !== ':' );
if (firstFunc === -1) // also covers empty paths
return ref;
// Method Call ---------------------------
// Transform the path into `.`-operators.
// Everything after the first function is also a function, and not a reference.
for (let i = firstFunc; i < path.length; ++i) {
if (path[i].args && path[i].$syntax === ':') {
// Error for `a(P => 1).b.c(P: 1)`: no ref after function.
this.$messageFunctions.error('syntax-invalid-ref', path[i].args[$location], {
code: '=>',
}, 'References after function calls can\'t be resolved. Use $(CODE) in function arguments');
break;
}
}
const args = [];
const pathRest = [ ...path ];
let pathHead = pathRest.shift();
if (pathHead.args) {
if (firstFunc > 0) {
args.push({
op: { location: pathHead.location, val: 'call' },
func: { path: [ pathHead ] },
location: pathHead.location,
args: pathHead.args || [],
path: path.slice(0, firstFunc),
location: locUtils.combinedLocation(path[0].location, path[path.length - 1].location),
});
pathHead = pathRest.shift();
}
else {
const refPath = [];
while (pathHead && !pathHead.args) {
refPath.push(pathHead);
pathHead = pathRest.shift();
}
args.push({ path: refPath, location: refPath[0].location });
}
if (pathHead?.args)
pathRest.unshift(pathHead);
const pathRest = path.slice(firstFunc);
for (const method of pathRest) {
args.push({
// TODO: Update parser to have proper location for `.`?
location: weakLocation(method.location),
val: '.',
literal: 'token',
});
if (method !== pathRest[0] || firstFunc > 0) {
args.push({
// TODO: Update parser to have proper location for `.`?
location: weakLocation(method.location),
val: '.',
literal: 'token',
});
}
const func = {

@@ -1035,42 +1049,34 @@ op: { location: method.location, val: 'call' },

// If argument `name` is provided, set `art.name`:
// - if `name` is an array, the name consist of the ID of the last path item
// - if `name` is an array, `name.id` consist of the ID of the last array item
// (for elements via columns, foreign keys, table aliases)
// - if `name` is an object, the name consist of the IDs of all path items
// (for main artifact definitions)
// - if `name` is an object, `name.id` is either set, or the (local) name is calculated
// from the IDs of all items in `name.path` (for main artifact definitions).
function addDef( art, parent, env, kind, name ) {
if (Array.isArray(name)) {
// XSN TODO: clearly say: definitions have name.path, members have name.id
const last = name.length && name[name.length - 1];
if (last && last.id) { // // A.B.C -> 'C'
art.name = {
id: last.id, location: last.location, $inferred: 'as',
};
}
art.name = { // A.B.C -> 'C'
id: last?.id || '', location: last.location, $inferred: 'as',
};
}
else if (name) {
art.name = name;
if (name.id == null)
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
// TODO: get rid of setting `id`, only use for named values in structs
if (!name.id && kind === null) // namedValue, fortunately no `variant` there
art.name.id = pathName( art.name?.path );
}
else {
art.name = { id: '' };
}
if (kind)
art.kind = kind;
if (!art.name || art.name.id == null) {
// no id was parsed, but with error recovery: no further error
// TODO: add to parent[env]['']
// which could be tested in name search (then no undefined-ref error)
// console.log( kind+': !!', art, parent, env, name )
return art;
}
// console.log( kind+':', art, parent, env, name )
const id = art.name?.id || pathName( art.name?.path ); // returns '' for corrupted name
if (env === 'artifacts' || env === 'vocabularies') {
dictAddArray( parent[env], art.name.id, art );
dictAddArray( parent[env], id, art );
}
else if (kind || this.options.parseOnly) {
dictAdd( parent[env], art.name.id, art );
else if (kind || this.options.parseOnly) { // TODO: do not check parseOnly
dictAdd( parent[env], id, art );
}
else {
dictAdd( parent[env], art.name.id, art, ( duplicateName, loc ) => {
dictAdd( parent[env], id, art, ( duplicateName, loc ) => {
// do not use function(), otherwise `this` is wrong:

@@ -1077,0 +1083,0 @@ if (kind === 0) {

@@ -252,2 +252,14 @@ // Official cds-compiler API.

/**
* Options used by the `for.effective()` CSN transformation.
*
* WORK IN PROGRESS
*
* @internal
* @see _for.effective()
*/
export interface EffectiveCsnOptions extends SqlOptions {
// TODO
}
/**
* Options used by SQL `to.sql()` backend.

@@ -822,3 +834,3 @@ *

/**
* Transform the given (generic) CSN into one that is used for OData.
* Transform the given (inferred/client) CSN into one that is used for OData.
* Changes include flattening, type resolution and more, according to

@@ -828,2 +840,15 @@ * the provided options.

function odata(csn: CSN, options?: ODataOptions): CSN;
/**
* Transform the given CSN into one that has these properties:
* - types are resolved
* - elements are flattened
* - …
*
* THIS IS HIGHLY EXPERIMENTAL
*
* Beta flag `effectiveCsn` is required.
*
* @internal
*/
function effective(csn: CSN, options?: EffectiveCsnOptions): CSN;
}

@@ -830,0 +855,0 @@

@@ -118,3 +118,4 @@ // Main entry point for the CDS Compiler

for: {
odata: (...args) => snapi.odata(...args)
odata: (...args) => snapi.odata(...args),
effective: (...args) => snapi.for_effective(...args),
},

@@ -121,0 +122,0 @@ to: {

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

const main = csn.definitions[head];
// if (!main) throw Error(JSON.stringify({$origin,csn}))
initDefinition( main );

@@ -438,0 +439,0 @@ return tail.reduce( originNavigation, main );

@@ -1414,2 +1414,28 @@ 'use strict';

/**
* Return true if 'arg' is an expression argument denoting "$self" || "$projection"
* @param {object} arg
* @returns {boolean}
*/
function isDollarSelfOrProjectionOperand( arg ) {
return arg.ref && arg.ref.length === 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
}
/**
* Return true if 'arg' is an expression argument of type association or composition
* @param {object} arg
* @param {CSN.Path} path
* @param {function} inspectRef
* @returns {boolean}
*/
function isAssociationOperand( arg, path, inspectRef ) {
if (!arg.ref) {
// Not a path, hence not an association (literal, expression, function, whatever ...)
return false;
}
const { art } = inspectRef(path);
// If it has a target, it is an association or composition
return art && art.target !== undefined;
}
module.exports = {

@@ -1460,2 +1486,4 @@ getUtils,

cardinality2str,
isAssociationOperand,
isDollarSelfOrProjectionOperand,
};

@@ -162,10 +162,4 @@ // Make internal properties of the XSN / augmented CSN visible

const name = reveal( node, parent );
if (name && typeof name === 'object' && name.absolute && node.kind) {
if (name && typeof name === 'object' && parent.kind) {
const text = artifactIdentifier( parent );
delete name.absolute;
delete name.select;
delete name.action;
delete name.parameter;
delete name.alias;
delete name.element;
name['-->'] = text;

@@ -197,3 +191,3 @@ }

? { name: reveal( name ), $flatten: $flatten.map( $annotation ) }
: `@${ name?.absolute }`;
: `@${ name?.id }`;
return { value, location: locationString( anno.location || anno.name.location ) };

@@ -354,3 +348,3 @@ }

case 'builtin':
return `$magicVariables/${ msg.artName(node) }`;
return msg.artName(node);
case 'source':

@@ -357,0 +351,0 @@ case 'using':

@@ -108,4 +108,5 @@ 'use strict';

${
Object.keys(availableBetaFlags).sort()
Object.keys(availableBetaFlags)
.filter(flag => availableBetaFlags[flag])
.sort()
.join('\n' + ' '.repeat(30))

@@ -155,2 +156,3 @@ }

inspect [options] <files...> (internal) Inspect the given CDS files.
toEffectiveCsn [options] <files...> (internal) Get an effective CSN for SEAL; requires beta mode

@@ -528,4 +530,18 @@ Environment variables

optionProcessor.command('toEffectiveCsn')
.option('-h, --help')
.positionalArgument('<files...>')
.help(`
Usage: cdsc toEffectiveCsn [options] <files...>
(internal): Get the effective CSN model compiled from the provided CDS files.
This command may change any time, including its name.
Beta mode is required.
Options
-h, --help Show this help text
`);
module.exports = {
optionProcessor
};

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

applyTransformations,
implicitAs,
} = require('../../model/csnUtils');

@@ -19,5 +20,3 @@

function attachOnConditions( csn, csnUtils, pathDelimiter ) {
const {
isManagedAssociation,
} = csnUtils;
const { isManagedAssociation } = csnUtils;

@@ -64,3 +63,3 @@ const alreadyHandled = new WeakMap();

};
const fkName = `${elemName}${pathDelimiter}${foreignKey.as}`;
const fkName = `${elemName}${pathDelimiter}${foreignKey.as || implicitAs(foreignKey.ref)}`;
const fKeyArg = {

@@ -67,0 +66,0 @@ ref: [

@@ -45,3 +45,5 @@ 'use strict';

parent.columns = replaceStar(root, columns, parent.excluding, isComplexQuery);
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery);
// FIXME(v5): Remove argument "isComplexOrNestedQuery"; we use path.length > 4 to check
// if we're inside the outermost "columns". If so, always prepend a table alias. See #11662
parent.columns = expand(parent.columns, path.concat('columns'), true, isComplexQuery || path.length > 4);
}

@@ -517,6 +519,6 @@ },

* @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias as well.
* @param {boolean} [isComplexQuery]
* @param {boolean} [isComplexOrNestedQuery]
* @returns {Array} New array - with all structured things expanded
*/
function expand( thing, path, withAlias = false, isComplexQuery = false ) {
function expand( thing, path, withAlias = false, isComplexOrNestedQuery = false ) {
const newThing = [];

@@ -528,3 +530,3 @@ for (let i = 0; i < thing.length; i++) {

if (_art && csnUtils.isStructured(_art))
newThing.push(...expandRef(_art, col, withAlias, isComplexQuery));
newThing.push(...expandRef(_art, col, withAlias, isComplexOrNestedQuery));
else

@@ -604,6 +606,6 @@ newThing.push(col);

* @param {boolean} withAlias Whether to add an explicit flattened alias to the expanded columns/references.
* @param {boolean} [isComplexQuery]
* @param {boolean} [isComplexOrNestedQuery]
* @returns {Array}
*/
function expandRef( art, root, withAlias, isComplexQuery ) {
function expandRef( art, root, withAlias, isComplexOrNestedQuery ) {
return _expandStructCol(art, columnAlias(root), root.ref, ( currentRef, currentAlias) => {

@@ -629,3 +631,3 @@ const obj = { ...root, ref: currentRef };

// - the first path step has the same name as the table alias (only one, as otherwise the query would be complex)
if (typeof root.$env === 'string' && (isComplexQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
if (typeof root.$env === 'string' && (isComplexOrNestedQuery || options.transformation !== 'odata' || root.$env === pathId(obj.ref[0])))
obj.ref = [ root.$env, ...obj.ref ];

@@ -632,0 +634,0 @@

@@ -843,4 +843,5 @@ 'use strict';

if (lvl === 0) {
fk[1]['@odata.foreignKey4'] = prefix;
if (!options.forHana)
if (options.transformation !== 'effective')
fk[1]['@odata.foreignKey4'] = prefix;
if (options.transformation === 'odata' || options.transformation === 'effective')
copyAnnotations(element, fk[1], true);

@@ -847,0 +848,0 @@

@@ -11,7 +11,5 @@ 'use strict';

cloneCsnNonDict,
forEachMemberRecursively,
} = require('../../model/csnUtils');
const { getBranches } = require('./flattening');
const { getColumnMap } = require('./views');
const { requireForeignKeyAccess } = require('../../checks/onConditions');

@@ -100,69 +98,5 @@ const cloneCsnOptions = { hiddenPropertiesToClone: [ '_art', '_links', '$env', '$scope' ] };

/**
* Check that all paths in calculated elements-on write either access normal fields
* (structures are already rejected by the compiler) or access the foreign keys of
* associations. Non-fk fields must be rejected.
* Filters and parameters are not allowed.
*
* Note: This coding is similar to checks/onConditions.js, but does not check $self or other
* ON-condition related stuff.
*
* @param {object} parent
* @param {(string|object)[]} value
* @param {CSN.Path} csnPath
*/
function checkPathsInStoredCalcElement( parent, value, csnPath ) {
const { _links } = parent;
// If there is only one path step, it's been checked before.
for (let i = 0; i < value.length - 1; ++i) {
let hasPathError = false;
const step = value[i];
const stepArt = _links[i].art;
if (stepArt.target) {
const id = step.id || step;
if (stepArt.on) {
// It's an unmanaged association - traversal is always forbidden
error('ref-unexpected-navigation', csnPath, { '#': 'calc-unmanaged', id, elemref: parent });
hasPathError = true;
}
else {
// It's a managed association - access of the foreign keys is allowed
requireForeignKeyAccess(parent, i, (errorIndex) => {
error('ref-unexpected-navigation', csnPath, {
'#': 'calc-non-fk', id, elemref: parent, name: value[errorIndex].id || value[errorIndex],
});
hasPathError = true;
});
}
}
if (typeof step === 'object') {
if (step.where) {
error('ref-unexpected-filter', csnPath, { '#': 'calc', elemref: parent });
hasPathError = true;
}
if (step.args) {
error('ref-unexpected-args', csnPath, { '#': 'calc', elemref: parent });
hasPathError = true;
}
}
if (hasPathError)
break; // avoid too many consequent errors
}
}
// Last pass, turn .value in tables into a simple 'val' so we don't need to rewrite/flatten properly - will kill them later
entities.forEach(({ artifact, artifactName }) => {
dummifyInEntity(artifact, [ 'definitions', artifactName ]);
forEachMemberRecursively({ elements: artifact.elements }, (element, elementName, _prop, path) => {
if (element.value?.stored) {
applyTransformationsOnNonDictionary(element, 'value', {
ref(parent, prop, value, csnPath) {
checkPathsInStoredCalcElement(parent, value, csnPath);
},
}, {}, path);
}
}, [ 'definitions', artifactName ]);
});

@@ -334,3 +268,3 @@

},
});
}, { skipStandard: { where: true } });
}

@@ -337,0 +271,0 @@ else if (current.value.ref && current.value._art?.value && !current.value._art?.value.stored) {

@@ -69,2 +69,5 @@ 'use strict';

if (query.SELECT?.having?.length > 1)
toProcess.push([ path.slice(0, -1), path.concat('having') ]);
if (query.SELECT?.columns)

@@ -77,3 +80,2 @@ toProcess.push([ path.slice(0, -1), path.concat('columns') ]);

for (const [ , exprPath ] of toProcess) {
forbidAssocInExists(exprPath);
const expr = nestExists(exprPath);

@@ -260,59 +262,2 @@ walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = expr;

/**
* Check that associations in filters (in an exists expression) are only fk-accesses. Everything else is forbidden.
*
* @param {CSN.Path} exprPath
* @returns {void}
*/
function forbidAssocInExists( exprPath ) {
const expr = walkCsnPath(csn, exprPath);
for (let i = 0; i < expr.length; i++) {
if (i < expr.length - 1 && expr[i] === 'exists' && expr[i + 1].ref) {
i++;
const current = expr[i];
const { links } = inspectRef(exprPath.concat(i));
const assocs = links.filter(link => link.art?.target).map(link => current.ref[link.idx]);
checkForInvalidAssoc(assocs);
}
}
}
/**
* @param {object[]} assocs Array of refs of assocs - possibly with a .where to check
*/
function checkForInvalidAssoc( assocs ) {
for (const assoc of assocs) {
if (assoc.where) {
for (let i = 0; i < assoc.where.length; i++) {
const part = assoc.where[i];
if (part._links && !(assoc.where[i - 1] && assoc.where[i - 1] === 'exists')) {
for (const link of part._links) {
if (link.art && link.art.target) {
if (link.art.keys) { // managed - allow FK access
if (part._links[link.idx + 1] !== undefined) { // there is a next path step - check if it is a fk
if (!(part._links[link.idx + 1] && link.art.keys.some(fk => fk._art === part._links[link.idx + 1].art)))
error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected non foreign key access after managed association $(NAME) in filter expression of $(ID)');
}
else { // no traversal, ends on managed
error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected managed association $(NAME) in filter expression of $(ID)');
}
}
else { // unmanaged - always wrong
error(null, part.$path, { id: assoc.id, name: assoc.where[part.$path[part.$path.length - 1]].ref[link.idx] }, 'Unexpected unmanaged association $(NAME) in filter expression of $(ID)');
}
// Recursively drill down if the assoc-step has a filter
if (part.ref[link.idx].where)
checkForInvalidAssoc([ part.ref[link.idx] ]);
}
}
}
}
}
}
}
/**
* Walk to the expr using the given path and scan it for the "exists" + "ref" pattern.

@@ -319,0 +264,0 @@ * If such a pattern is found, nest association steps therein into filters.

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

} = require('../../model/csnUtils');
const { implicitAs, columnAlias } = require('../../model/csnRefs');
const { implicitAs, columnAlias, pathId } = require('../../model/csnRefs');
const { ModelError } = require('../../base/error');

@@ -20,9 +20,7 @@ const { setProp } = require('../../base/model');

function getMixinAssocOfQueryIfPublished( query, association, associationName ) {
if (query && query.SELECT && query.SELECT.mixin) {
if (query?.SELECT?.mixin) {
const aliasedColumnsMap = Object.create(null);
if (query.SELECT.columns) {
for (const column of query.SELECT.columns) {
if (column.as && column.ref && column.ref.length === 1)
aliasedColumnsMap[column.as] = column;
}
for (const column of query.SELECT.columns || []) {
if (column.as && column.ref?.length === 1)
aliasedColumnsMap[column.as] = column;
}

@@ -34,3 +32,3 @@

if (aliasedColumnsMap[associationName])
originalName = aliasedColumnsMap[associationName].ref[0];
originalName = pathId(aliasedColumnsMap[associationName].ref[0]);

@@ -216,7 +214,3 @@ if (elem === originalName)

if (doA2J) {
if (elem.keys)
info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'managed' });
else
info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': 'std' });
info('query-ignoring-assoc-in-union', queryPath, { name: elemName, '#': elem.keys ? 'managed' : 'std' });
elem.$ignore = true;

@@ -455,3 +449,3 @@ }

function checkIsNotMixinByItself( query, columnMap, elementName ) {
if (query && query.SELECT && query.SELECT.mixin) {
if (query?.SELECT?.mixin) {
const col = columnMap[elementName];

@@ -458,0 +452,0 @@

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

getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,
isAspect, walkCsnPath, isPersistedOnDatabase,
isAspect, walkCsnPath, isPersistedOnDatabase
} = require('../model/csnUtils');

@@ -33,4 +33,4 @@ const { makeMessageFunction } = require('../base/messages');

const temporal = require('./db/temporal');
const associations = require('./db/associations')
const { ModelError } = require('../base/error');
const associations = require('./db/associations');
const backlinks = require('./db/backlinks');
const { getDefaultTypeLengths } = require('../render/utils/common');

@@ -130,8 +130,6 @@

// transformUtils
let addDefaultTypeFacets,
let addDefaultTypeFacets,
expandStructsInExpression,
flattenStructuredElement,
flattenStructStepsInRef,
isAssociationOperand,
isDollarSelfOrProjectionOperand;
flattenStructStepsInRef;

@@ -164,2 +162,5 @@ bindCsnReference();

// exit if validators found errors
throwWithAnyError();
rewriteCalculatedElementsInViews(csn, options, csnUtils, pathDelimiter, messageFunctions);

@@ -181,4 +182,2 @@

throwWithAnyError();
forEachDefinition(csn, [

@@ -223,2 +222,5 @@ // (001) Add a temporal where condition to views where applicable before assoc2join

// With flattening errors, it makes little sense to continue.
throwWithAnyError();
// (010) If requested, translate associations to joins

@@ -344,3 +346,3 @@ if (doA2J)

// because otherwise we would produce wrong ON-conditions for the keys involved. Sigh ...
forEachDefinition(csn, transformSelfInBacklinks);
forEachDefinition(csn, backlinks.getBacklinkTransformer(csnUtils, messageFunctions, options, pathDelimiter, doA2J));

@@ -461,4 +463,2 @@ /**

flattenStructStepsInRef,
isAssociationOperand,
isDollarSelfOrProjectionOperand,
addDefaultTypeFacets,

@@ -610,3 +610,2 @@ expandStructsInExpression,

/**

@@ -616,23 +615,2 @@ * @param {CSN.Artifact} artifact

*/
function transformSelfInBacklinks(artifact, artifactName, dummy, path) {
// Fixme: For toHana mixins must be transformed, for toSql -d hana
// mixin elements must be transformed, why can't toSql also use mixins?
if(artifact.kind === 'entity' || artifact.query || (options.forHana && options.sqlMapping === 'hdbcds' && artifact.kind === 'type'))
doit(artifact.elements, path.concat([ 'elements' ]));
if (artifact.query && artifact.query.SELECT && artifact.query.SELECT.mixin)
doit(artifact.query.SELECT.mixin, path.concat([ 'query', 'SELECT', 'mixin' ]));
function doit(dict, subPath) {
for (const elemName in dict) {
const elem = dict[elemName];
if (elem.on && csnUtils.isAssocOrComposition(elem))
processBacklinkAssoc(elem, elemName, artifact, artifactName, subPath.concat([ elemName, 'on' ]));
}
}
}
/**
* @param {CSN.Artifact} artifact
* @param {string} artifactName
*/
function handleDBChecks(artifact, artifactName) {

@@ -713,4 +691,2 @@ // Strip inheritance

function handleAssocToJoins() {
// With flattening errors, it makes little sense to continue.
throwWithAnyError();
// the augmentor isn't able to deal with technical configurations and since assoc2join can ignore it we

@@ -852,203 +828,4 @@ // simply make it invisible and copy it over to the result csn

// If the association element 'elem' of 'art' is a backlink association, massage its ON-condition
// (in place) so that it
// - compares the generated foreign key fields of the corresponding forward
// association with their respective keys in 'art' (for managed forward associations)
// - contains the corresponding forward association's ON-condition in "reversed" form,
// i.e. as seen from 'elem' (for unmanaged associations)
// Otherwise, do nothing.
function processBacklinkAssoc(elem, elemName, art, artName, pathToOn) {
// Don't add braces if it is a single expression (ignoring superfluous braces)
const multipleExprs = elem.on.filter(x => x !== '(' && x !== ')' ).length > 3;
/**
* Process the args
*
* @param {Array} xprArgs
* @param {CSN.Path} path
* @returns {Array} Array of parsed expression
*/
function processExpressionArgs(xprArgs, path) {
const result = [];
let i = 0;
while (i < xprArgs.length) {
// Only token tripel `<path>, '=', <path>` are of interest here
if (i < xprArgs.length - 2 && xprArgs[i + 1] === '=') {
// Check if one side is $self and the other an association
// (if so, replace all three tokens with the condition generated from the other side, in parentheses)
if (isDollarSelfOrProjectionOperand(xprArgs[i]) && isAssociationOperand(xprArgs[i + 2], path.concat([ i + 2 ]))) {
const assoc = csnUtils.inspectRef(path.concat([ i + 2 ])).art;
if (multipleExprs)
result.push('(');
const backlinkName = xprArgs[i + 2].ref[xprArgs[i + 2].ref.length - 1];
result.push(...transformDollarSelfComparison(xprArgs[i + 2],
assoc,
backlinkName,
elem, elemName, art, artName, path.concat([ i ])
));
if (multipleExprs)
result.push(')');
i += 3;
attachBacklinkInformation(backlinkName);
}
else if (isDollarSelfOrProjectionOperand(xprArgs[i + 2]) && isAssociationOperand(xprArgs[i], path.concat([ i ]))) {
const assoc = csnUtils.inspectRef(path.concat([ i ])).art;
if (multipleExprs)
result.push('(');
const backlinkName = xprArgs[i].ref[xprArgs[i].ref.length - 1];
result.push(...transformDollarSelfComparison(xprArgs[i], assoc, backlinkName, elem, elemName, art, artName, path.concat([ i + 2 ])));
if (multipleExprs)
result.push(')');
i += 3;
attachBacklinkInformation(backlinkName);
}
// Otherwise take one (!) token unchanged
else {
result.push(xprArgs[i]);
i++;
}
}
// Process subexpressions - but keep them as subexpressions
else if(xprArgs[i].xpr){
result.push({xpr: processExpressionArgs(xprArgs[i].xpr, path.concat([i, 'xpr']))});
i++;
}
// Take all other tokens unchanged
else {
result.push(xprArgs[i]);
i++;
}
}
return result;
/**
* The knowledge whether an association was an `<up_>` association in a
* `$self = <comp>.<up_>` comparison, is important for the foreign key constraints.
* By the time we generate them, such on-conditions are already transformed
* --> no more `$self` in the on-conditions, that is why we need to remember it here.
*
* @param {string} backlinkName name of `<up_>` in a `$self = <comp>.<up_>` comparison
*/
function attachBacklinkInformation(backlinkName) {
if (elem.$selfOnCondition)
elem.$selfOnCondition.up_.push(backlinkName);
else {
setProp(elem, '$selfOnCondition', {
up_: [backlinkName]
});
}
}
}
elem.on = processExpressionArgs(elem.on, pathToOn);
// Return the condition to replace the comparison `<assocOp> = $self` in the ON-condition
// of element <elem> of artifact 'art'. If there is anything to complain, use location <loc>
function transformDollarSelfComparison(assocOp, assoc, assocName, elem, elemName, art, artifactName, path) {
// Check: The forward link <assocOp> must point back to this artifact
// FIXME: Unfortunately, we can currently only check this for non-views (because when a view selects
// a backlink association element from an entity, the forward link will point to the entity,
// not to the view).
// FIXME: This also means that corresponding key fields should be in the select list etc ...
if (!art.query && !art.projection && assoc.target && assoc.target !== artifactName)
error( null, path, { id: '$self', name: artifactName, target: assoc.target },
'Expected association using $(ID) to point back to $(NAME) but found $(TARGET)' );
// Check: The forward link <assocOp> must not contain '$self' in its own ON-condition
if (assoc.on) {
const containsDollarSelf = assoc.on.some(isDollarSelfOrProjectionOperand);
if (containsDollarSelf)
error(null, path, { name: '$self' },
'An association that uses $(NAME) in its ON-condition can\'t be compared to $(NAME)');
}
// Transform comparison of $self to managed association into AND-combined foreign key comparisons
if (assoc.keys) {
if(assoc.keys.length)
return transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, assocName, elemName);
else {
elem.$ignore = true;
return [];
}
}
// Transform comparison of $self to unmanaged association into "reversed" ON-condition
else if (assoc.on)
return transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, assocName, elemName);
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
}
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
// where <assoc> is a managed association, return a condition comparing the generated
// foreign key elements <elemName>.<assoc>_<fkey1..n> of <assoc> to the corresponding
// keys in this artifact.
// For example, `ON elem.ass = $self` becomes `ON elem.ass_key1 = key1 AND elem.ass_key2 = key2`
// (assuming that `ass` has the foreign keys `key1` and `key2`)
function transformDollarSelfComparisonWithManagedAssoc(assocOp, assoc, originalAssocName, elemName) {
const conditions = [];
// if the element was structured then it was flattened => change of the delimiter from '.' to '_'
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
elemName = elemName.replace(/\./g, pathDelimiter);
assoc.keys.forEach((k) => {
// Depending on naming conventions, the foreign key may two path steps (hdbcds) or be a single path step with a flattened name (plain, quoted)
// With to.hdbcds in conjunction with hdbcds naming, we need to NOT use the alias - else we get deployment errors
const keyName = k.as && doA2J ? [k.as] : k.ref;
const fKeyPath = !doA2J ? [ assocName, ...keyName ] : [ `${ assocName }${ pathDelimiter }${ keyName[0] }` ];
// FIXME: _artifact to the args ???
const a = [
{
ref: [ elemName, ...fKeyPath ],
},
{ ref: k.ref },
];
conditions.push([ a[0], '=', a[1] ]);
});
return conditions.reduce((prev, current) => {
if (prev.length === 0)
return [ ...current ];
return [ ...prev, 'and', ...current ];
}, []);
}
// For a condition `<elemName>.<assoc> = $self` in the ON-condition of element <elemName>,
// where <assoc> is an unmanaged association, return the ON-condition of <assoc> as it would
// be written from the perspective of the artifact containing association <elemName>.
// For example, `ON elem.ass = $self` becomes `ON a = elem.x AND b = elem.y`
// (assuming that `ass` has the ON-condition `ON ass.a = x AND ass.b = y`)
function transformDollarSelfComparisonWithUnmanagedAssoc(assocOp, assoc, originalAssocName, elemName) {
// if the element was structured then it may have been flattened => change of the delimiter from '.' to '_'
// this is done in the flattening, but as we do not alter the onCond itself there should be done here as well
elemName = elemName.replace(/\./g, pathDelimiter);
const assocName = originalAssocName.replace(/\./g, pathDelimiter);
// clone the onCond for later use in the path transformation
const newOnCond = cloneCsnNonDict(assoc.on, options);
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
ref: (parent, prop, ref) => {
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
{
ref.shift();
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
// We could also have a $self in front of the assoc name - so we would need to shift twice
ref.shift();
ref.shift();
}
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
ref.unshift(elemName);
// if there was a $self identifier in the forwarding association onCond
// we do not need it anymore, as we prepended in the previous step the back association's id
if (ref[1] === '$self')
ref.splice(1, 1);
}
}
});
return newOnCond;
}
}
/**

@@ -1055,0 +832,0 @@ * @param {CSN.Artifact} artifact

@@ -398,4 +398,3 @@ 'use strict';

messageFunctions.message( 'anno-unexpected-localized-skip', artPath,
{ name: textsName, art: artName, anno: annoPersistenceSkip },
'Compiler generated entity $(NAME) must not be annotated with $(ANNO) if $(ART) is not skipped' );
{ name: textsName, art: artName, anno: annoPersistenceSkip } );
return null;

@@ -658,4 +657,24 @@ }

});
forEachDefinition(csn, checkAnnotationsOnLocalized);
}
/**
* @param {CSN.Definition} def
* @param {string} defName
*/
function checkAnnotationsOnLocalized(def, defName) {
const localizedPrefix = 'localized.';
if (defName.startsWith(localizedPrefix)) {
const artName = defName.substring(localizedPrefix.length);
const art = csn.definitions[artName];
if (def[annoPersistenceSkip] && !art?.[annoPersistenceSkip]) {
messageFunctions.message( 'anno-unexpected-localized-skip', ['definitions', defName], {
'#': 'view',
name: defName,
art: artName,
anno: annoPersistenceSkip
});
}
}
}
}

@@ -745,3 +764,3 @@

// In v2, `.exists` was never copied.
if (anno === '@cds.persistence.skip' || (!doNotCopyExists && anno === '@cds.persistence.exists'))
if (anno === annoPersistenceSkip || (!doNotCopyExists && anno === '@cds.persistence.exists'))
target[anno] = source[anno];

@@ -748,0 +767,0 @@ });

@@ -170,3 +170,3 @@ // @ts-nocheck

if(xpr.length >= 3 && xpr[s+1] === 'is') {
const isnull = conditionOR(xpr[s], state);
const isnull = conditionOR(xpr[s], 0, 0, state);
if(xpr[s+2] === 'null')

@@ -257,8 +257,12 @@ return state.array ? [ isnull, 'is', 'null' ] : { 'isNull': isnull };

function exprMulDiv(xpr, s, e, state) {
return binaryExpr(xpr, ['*', '/'], unary, s, e, state);
return binaryExpr(xpr, ['*', '/'], (state.array ? unary : dot), s, e, state);
}
function dot(xpr, s, e, state) {
return binaryExpr(xpr, ['.'], unary, s, e, state);
}
function unary(xpr, s, e, state) {
if(Array.isArray(xpr)) {
if(xpr[s] === '+' || xpr[s] === '-') {
if(xpr[s] === '+' || xpr[s] === '-' || (!state.array && xpr[s] === 'new')) {
return [ xpr[s], unary(xpr, s+1, e, state) ];

@@ -265,0 +269,0 @@ }

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

const { copyAnnotations, applyTransformations } = require('../model/csnUtils');
const { copyAnnotations, applyTransformations, isDollarSelfOrProjectionOperand } = require('../model/csnUtils');
const { cloneCsnNonDict, cloneCsnDictionary, getUtils } = require('../model/csnUtils');

@@ -47,4 +47,2 @@ const { typeParameters, isBuiltinType } = require('../compiler/builtins');

copyTypeProperties,
isAssociationOperand,
isDollarSelfOrProjectionOperand,
createExposingProjection,

@@ -78,3 +76,3 @@ createAndAddDraftAdminDataProjection,

* @param {null|object} [internalDefaultLengths] Either null (no implicit default) or an object `{ 'cds.String': N, 'cds.Binary': N }`.
* */
**/
function addDefaultTypeFacets(element, internalDefaultLengths = null) {

@@ -127,3 +125,3 @@ if (!element || !element.type)

if (!options.forHana)
if (options.transformation === 'odata' || options.transformation === 'effective')
copyAnnotations(assoc, foreignKeyElement, true);

@@ -141,3 +139,4 @@

// - foreign key info has 'generatedFieldName'
foreignKeyElement['@odata.foreignKey4'] = assocName;
if(options.transformation !== 'effective')
foreignKeyElement['@odata.foreignKey4'] = assocName;
foreignKey.$generatedFieldName = foreignKeyElementName;

@@ -616,18 +615,2 @@ // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition

// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
function isDollarSelfOrProjectionOperand(arg) {
return arg.ref && arg.ref.length === 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
}
// Return true if 'arg' is an expression argument of type association or composition
function isAssociationOperand(arg, path) {
if (!arg.ref) {
// Not a path, hence not an association (literal, expression, function, whatever ...)
return false;
}
const { art } = inspectRef(path);
// If it has a target, it is an association or composition
return art && art.target !== undefined;
}
// Create an artificial element 'elemName' of type 'cds.Association',

@@ -634,0 +617,0 @@ // having association target 'target'. If 'isManaged' is true, take all keys

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

target: notWithItemsOrElements,
keys: notWithItemsOrElements,
keys: specialKeysRules,
cardinality: notWithItemsOrElements,

@@ -752,2 +752,18 @@ };

/**
* Don't propagate property `keys` if there is an ON-condition. This happens for
* published managed associations with filters in views, which are transformed into
* unmanaged associations, i.e. get an ON-condition.
*
* Besides that, rules from notWithItemsOrElements() apply.
*
* @param {string} prop
* @param {CSN.Element} target
* @param {CSN.Element} source
*/
function specialKeysRules( prop, target, source ) {
if (target.on === undefined)
notWithItemsOrElements( prop, target, source );
}
/**
* Don't propagate certain properties if the target already has a .items or .elements

@@ -754,0 +770,0 @@ *

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

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

"generateToRenameRefs": "cross-env MAKEREFS='true' mocha test/testToRename.js",
"generateChecksRefs": "cross-env MAKEREFS='true' mocha test/testChecks.js",
"generateDraftRefs": "cross-env MAKEREFS='true' mocha test/testDraft.js",

@@ -44,0 +43,0 @@ "generateAllRefs": "node scripts/verifyGrammarChecksum.js && cross-env MAKEREFS='true' mocha --reporter-option maxDiffSize=0 test/ 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 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 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