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 5.1.2 to 5.2.0

lib/gen/BaseParser.js

4

bin/cdsc.js

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

inspect,
toEffectiveCsn,
forEffective,
forSeal,

@@ -356,3 +356,3 @@ };

function toEffectiveCsn( model ) {
function forEffective( model ) {
const features = [ 'resolveSimpleTypes', 'resolveProjections', 'remapOdataAnnotations', 'keepLocalized' ];

@@ -359,0 +359,0 @@ for (const feature of features) {

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

ExtParam: 'P', // highlight like entity/action parameter definition
FromImplicit: 'W',
Event: 'Y',

@@ -43,2 +44,4 @@ KeyImplicit: 'r', // handle as normal ref

const options = { newParser: true, attachTokens: true, messages: [] };
function highlight( err, buf ) {

@@ -49,26 +52,30 @@ if (err) {

}
const ts = compiler.parseX( buf, 'hi.cds', { attachTokens: true, messages: [] } ).tokenStream;
if (!buf.length || !ts.tokens || !ts.tokens.length)
const parser = compiler.parseX( buf, 'hi.cds', options ).tokenStream;
// ts is parser with new parser
const { tokens, lexer } = parser;
if (!buf.length || !tokens || !tokens.length)
return;
const chars = [ ...buf ];
for (const tok of ts.tokens) {
if (tok.start < 0)
for (const tok of tokens) {
const { location } = tok;
const start = lexer.characterPos( location.line, location.col );
if (start < 0)
continue;
if (tok.$isSkipped) {
if (tok.stop > tok.start) {
chars[tok.start] = (tok.$isSkipped === true ? '\x0f' : '\x16'); // ^O / ^V
chars[tok.stop] = '\x17'; // ^W
const stop = lexer.characterPos( location.endLine, location.endCol ) - 1;
const cat = tok.parsed;
// console.log(tok.location.toString(),tok.text,tok.parsed,stop > start)
if (!cat) {
if (stop > start) {
chars[start] = (cat !== 0 ? '\x0f' : '\x16'); // ^O / ^V (ERROR)
chars[stop] = '\x17'; // ^W
}
else {
chars[tok.start] = (tok.$isSkipped === true ? '\x0e' : '\x15'); // ^N / ^U
chars[start] = (cat !== 0 ? '\x0e' : '\x15'); // ^N / ^U (ERROR)
}
}
else {
const cat = tok.isIdentifier;
if (cat) {
if (cat !== 'ref' || chars[tok.start] !== '$')
chars[tok.start] = categoryChars[cat] || cat.charAt(0);
if (tok.stop > tok.start) // stop in ANTLR at last char, not behind
chars[tok.start + 1] = '_';
}
else if (cat !== 'keyword' && cat !== 'token') {
if (cat !== 'ref' || chars[start] !== '$')
chars[start] = categoryChars[cat] || cat.charAt(0);
if (stop > start) // stop in ANTLR at last char, not behind
chars[start + 1] = '_';
}

@@ -75,0 +82,0 @@ }

@@ -10,4 +10,4 @@ #!/usr/bin/env node

// The output could be used directly by some editors, e.g. Emacs. The
// capabilities supported at the moments is: complete - work in progress.
// Planned are: gotoDefinition, highlight (for syntax highlighting).
// capabilities supported at the moments is: complete, find, lint.
// Syntax highlighting is supported by ./cdshi.js.

@@ -56,2 +56,3 @@ /* eslint no-console:off */

function complete( err, buf ) {
const messages = [];
if (err)

@@ -79,4 +80,3 @@ return usage( err );

compiler.compileX( [ file ], '', {
attachValidNames: true, lintMode: true, beta, messages: [],
}, { [fname]: src } )
newParser: true, attachValidNames: true, lintMode: true, beta, messages }, { [fname]: src } )
.then( ident, ident );

@@ -86,6 +86,4 @@ }

function ident( xsnOrErr ) {
if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
return usage( xsnOrErr );
const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
function ident() {
const vn = messageAt( messages, 'validNames', off.col ) || Object.create(null);
// TODO: if there is no such message, use console.log( 'arbitrary identifier' )

@@ -114,6 +112,7 @@ // if we want to avoid that the editor switches to fuzzy completion match

return true;
const messages = [];
const src = `${buf.substring( 0, off.prefix )}__NO_SUCH_ID__${buf.substring( off.cursor )}`;
const fname = path.resolve( '', file );
compiler.compileX( [ file ], '', {
attachValidNames: true, lintMode: true, beta, messages: [],
newParser: true, attachValidNames: true, lintMode: true, beta, messages,
}, { [fname]: src } )

@@ -123,6 +122,4 @@ .then( show, show );

function show( xsnOrErr ) {
if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
return usage( xsnOrErr );
const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
function show() {
const vn = messageAt( messages, 'validNames', off.col ) || Object.create(null);
const art = vn[buf.substring( off.prefix, off.cursor )];

@@ -138,4 +135,5 @@ if (art)

return usage( err );
const messages = [];
const fname = path.resolve( '', file );
compiler.compileX( [ file ], '', { lintMode: true, beta, messages: [] }, { [fname]: buf } )
compiler.compileX( [ file ], '', { newParser: true, lintMode: true, beta, messages }, { [fname]: buf } )
.then( display, display );

@@ -145,3 +143,2 @@ return true;

function display( xsnOrErr ) {
const messages = xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages;
if (!messages)

@@ -156,4 +153,6 @@ return usage( xsnOrErr );

function tokensAt( buf, _offset, col, symbol ) {
const messages = [];
const src = `${buf.substring( 0, _offset )}≠${buf.substring( _offset )}`;
const et = messageAt( compiler.parseX( src, frel, { messages: [] } ), 'expectedTokens', col ) || [];
compiler.parseX( src, frel, { newParser: true, messages } );
const et = messageAt( messages, 'expectedTokens', col ) || [];
for (const n of et) {

@@ -178,4 +177,4 @@ if (typeof symbol === 'string') {

function messageAt( model, prop, col ) {
const msg = (model.messages || model.options && model.options.messages).find(
function messageAt( messages, prop, col ) {
const msg = messages.find(
m => m[prop] && m.$location.line === line && m.$location.col === col && m.$location.file === frel

@@ -182,0 +181,0 @@ );

@@ -471,4 +471,15 @@ /** @module API */

const internalOptions = prepareOptions.to.sql(options);
messageFunctions.setOptions( internalOptions );
handleTenantDiscriminator(options, internalOptions, messageFunctions);
if (!options.dry && internalOptions.script) {
messageFunctions.error('api-invalid-combination', null, { '#': 'dry-and-script', value: options.dry || 'undefined' });
messageFunctions.throwWithError();
}
if (internalOptions.script && !internalOptions.severities?.['type-unsupported-key-change']) {
internalOptions.severities ??= {};
internalOptions.severities['type-unsupported-key-change'] = 'warning';
}
const { error, throwWithError } = messageFunctions;

@@ -483,3 +494,3 @@

messageFunctions.setModel(diff);
const diffFilterObj = diffFilter[internalOptions.sqlDialect];
const diffFilterObj = diffFilter.getFilter(internalOptions);

@@ -492,2 +503,5 @@ if (diffFilterObj) {

.filter(an => diffFilterObj.changedPrimaryKeys(an));
if (internalOptions.script && diffFilterObj.hasLossyChanges())
messageFunctions.warning('def-unsupported-changes', null, null, 'Found potentially lossy changes - check generated SQL statements');
}

@@ -500,3 +514,6 @@

final: Object.entries(diff.deletions).reduce((previous, [ name, artifact ]) => {
previous[name] = `DROP ${ (artifact.query || artifact.projection) ? 'VIEW' : 'TABLE' } ${ identifierUtils.renderArtifactName(name) };`;
if (artifact.query || artifact.projection)
previous[name] = `DROP VIEW ${ identifierUtils.renderArtifactName(name) };`;
else
previous[name] = `-- [WARNING] this statement is lossy\nDROP TABLE ${ identifierUtils.renderArtifactName(name) };`;
return previous;

@@ -503,0 +520,0 @@ }, {}),

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

'odataVocabularies',
'odataNoCreator',
'service',

@@ -57,2 +58,4 @@ 'serviceNames',

'keepLocalized',
// to.sql.migration
'script',
];

@@ -200,3 +203,3 @@

},
for: { // TODO: Rename version to oDataVersion
for: {
odata: (options) => {

@@ -203,0 +206,0 @@ const hardOptions = { toOdata: true };

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

'@cds.external': 'never',
'@cds.java.this.name': 'onlyViaParent',
'@cds.persistence.calcview': 'never',

@@ -22,0 +23,0 @@ '@cds.persistence.exists': 'never',

@@ -10,11 +10,2 @@ // module- and csn/XSN-independent definitions

const queryOps = {
query: 'select', // TODO: rename to SELECT
union: 'union',
intersect: 'union',
except: 'union',
minus: 'union',
subquery: 'union', // for (subquery) with ORDER BY or LIMIT/OFFSET
};
/**

@@ -228,3 +219,2 @@ * Object of all available beta flags that will be enabled/disabled by `--beta-mode`

checkRemovedDeprecatedFlags,
queryOps,
forEachDefinition,

@@ -231,0 +221,0 @@ forEachMember,

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

@@ -24,4 +23,2 @@ // Only to be used with validator.js - a correct this value needs to be provided!

const serviceName = this.csnUtils.getServiceName(artName);
if (!serviceName && art.kind !== 'aspect')
this.warning(null, path, {}, 'Functions and actions must be declared in a service');

@@ -75,11 +72,2 @@ if (art.kind === 'entity') {

// "default" is always propagated to params
if (param.default && !isBetaEnabled(this.options, 'optionalActionFunctionParameters')) {
this.message('param-default', currPath, { '#': actKind }, {
std: 'Artifact parameters can\'t have a default value', // Not used
action: 'Action parameters can\'t have a default value',
function: 'Function parameters can\'t have a default value',
});
}
if (param.type && this.csnUtils.isAssocOrComposition(param)) {

@@ -86,0 +74,0 @@ this.error(null, currPath, { '#': actKind }, {

'use strict';
const { isBuiltinType } = require('../base/builtins');
const { transformExpression, applyTransformationsOnNonDictionary } = require('../model/csnUtils');
const { transformAnnotationExpression } = require('../model/csnUtils');
/**

@@ -11,16 +11,12 @@ *

Object.keys(member).filter(pn => pn[0] === '@').forEach((anno) => {
applyTransformationsOnNonDictionary(member, anno, {
xpr: (parent, prop, _xpr, xprPath) => {
transformExpression(parent, prop, {
ref: (elemref, __prop, ref, refPath) => {
const { art, scope } = this.csnUtils.inspectRef(refPath);
if (scope !== '$magic' && art) {
const ft = this.csnUtils.getFinalTypeInfo(art.type);
if (!isBuiltinType(ft?.type))
this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
}
},
}, xprPath);
transformAnnotationExpression(member, anno, {
ref: (elemref, __prop, _ref, refPath) => {
const { art, scope } = this.csnUtils.inspectRef(refPath);
if (scope !== '$magic' && art) {
const ft = this.csnUtils.getFinalTypeInfo(art.type);
if (!isBuiltinType(ft?.type))
this.error('odata-anno-xpr-ref', refPath, { anno, elemref, '#': 'flatten_builtin_type' });
}
},
}, {}, path);
}, path);
});

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

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

'_origin', '_block', '$contains',
'_projections', '_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
'_projections', '_complexProjections',
'_parent', '_main', '_effectiveType', '$effectiveSeqNo', '$expand',
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',

@@ -317,3 +318,3 @@ ],

'$parens', '_status', // TODO: only in from
'scope', '_artifact', '$inferred', 'kind',
'scope', '_artifact', '_originalArtifact', '$inferred', 'kind',
'_effectiveType', '$effectiveSeqNo', // TODO:check this

@@ -375,3 +376,4 @@ '$duplicates', // In JOIN if both sides are the same.

'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
'scope', '_artifact', '$inferred', '$expand', '$inCycle', '$tableAliases', '_$next',
'scope', '_artifact', '$inferred', '$expand', '$inCycle',
'$tableAliases', '_$next',
'_origin', '_effectiveType', '$effectiveSeqNo', '_extensions', '$contains',

@@ -396,3 +398,4 @@ ],

'cardinality',
'_artifact', '_navigation', '_user',
'_artifact', '_originalArtifact',
'_navigation', '_user',
'$inferred',

@@ -450,3 +453,6 @@ ],

requires: [ 'location', 'path' ],
optional: [ 'scope', 'variant', '_artifact', '$inferred', '$parens', 'sort', 'nulls' ],
optional: [
'scope', 'variant', '_artifact', '_originalArtifact',
'$inferred', '$parens', 'sort', 'nulls',
],
},

@@ -496,3 +502,3 @@ none: { optional: () => true }, // parse error

'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
'scale', 'srid', 'length', 'precision', 'scope',
'scale', 'srid', 'length', 'precision', 'scope', '$parens',
],

@@ -533,3 +539,3 @@ // TODO: restrict path to #simplePath

'args', 'op', 'func', 'suffix',
'$invalidPaths',
'$invalidPaths', '$parens',
],

@@ -548,3 +554,3 @@ // TODO: name requires if not in parser?

select: { test: TODO }, // TODO: remove
}, // TODO: rename query prop in name
},
requires: [ 'location' ],

@@ -618,2 +624,3 @@ optional: [

_artifact: { test: TODO },
_originalArtifact: { test: TODO },
_navigation: { test: TODO },

@@ -653,3 +660,3 @@ _effectiveType: { kind: true, test: TODO },

_calcOrigin: { kind: true, test: TODO },
_pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
_columnParent: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
_from: { kind: true, test: TODO }, // TODO: not necessary anymore ?

@@ -671,3 +678,4 @@ // array of $tableAlias (or includes) for explicit and implicit redirection:

_status: { kind: true, test: TODO }, // TODO: $status
_projections: { kind: true, test: TODO }, // for mixin definitions
_projections: { kind: true, test: TODO },
_complexProjections: { kind: true, test: TODO }, // for projected paths with filters
$entity: { kind: true, test: TODO },

@@ -986,3 +994,3 @@ _entities: { test: TODO },

const requires = [ 'val', 'location' ];
const optional = [ 'literal', '$inferred', '$priority', '_pathHead' ];
const optional = [ 'literal', '$inferred', '$priority', '_columnParent' ];
standard( node, parent, prop, {

@@ -989,0 +997,0 @@ schema: valSchema, requires, optional, instanceOf: spec.instanceOf,

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

const specialFunctions = compileFunctions( {
// TODO: use lower-case
'': [ // the default

@@ -235,3 +236,2 @@ {

* the error location is only correct for a literal <prefix>'<value>'
* - `literal`: the value which is used instead of `prefix` in the AST
* TODO: we might do a range check (consider leap seconds, i.e. max value 60),

@@ -238,0 +238,0 @@ * but always allow Feb 29 (no leap year computation)

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

if (parent.value)
setLink( col, '_pathHead', parent ); // also set for '*' in expand/inline
else if (parent._pathHead)
setLink( col, '_pathHead', parent._pathHead );
setLink( col, '_columnParent', parent ); // also set for '*' in expand/inline
else if (parent._columnParent)
setLink( col, '_columnParent', parent._columnParent );
}

@@ -911,0 +911,0 @@ if (col.val === '*') {

@@ -444,4 +444,4 @@ // Extend

if (ext.kind === 'extend' && art.$inferred) {
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
'You can\'t use EXTEND on the generated $(ART)' );
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
'You can\'t use $(KEYWORD) on the generated $(ART)' );
}

@@ -801,3 +801,3 @@ else if (art.kind !== 'annotate' && !art._outer) { // not with elem extension in targetAspect

// TODO: check - for each name? - better locations
const location = ext._parent[prop]?.[$location] || ext.name.location;
const location = ext._parent?.[prop]?.[$location] || ext.name.location;
// Remark: no `elements` dict location with `annotate Main:elem`

@@ -1057,4 +1057,4 @@ switch (prop) {

if (noExtend && ext.kind === 'extend') {
error( 'extend-for-generated', [ ext.name.location, ext ], { art },
'You can\'t use EXTEND on the generated $(ART)' );
error( 'extend-for-generated', [ ext.name.location, ext ], { art, keyword: 'extend' },
'You can\'t use $(KEYWORD) on the generated $(ART)' );
continue;

@@ -1061,0 +1061,0 @@ }

@@ -792,4 +792,4 @@ // Populate views with elements, elements with association targets, ...

}
else if (col.expand || col.value && (col._pathHead || query._parent.kind !== 'select')) {
// _pathHead => inline/expand; _parent -> only allowed in sub-selects
else if (col.expand || col.value && (col._columnParent || query._parent.kind !== 'select')) {
// _columnParent => inline/expand; _parent -> only allowed in sub-selects
error( 'query-req-name', [ col.value?.location || col.location, query ], {},

@@ -871,3 +871,3 @@ 'Alias name is required for this select item' );

const envParent = wildcard._pathHead; // TODO: rename _pathHead to _columnParent
const envParent = wildcard._columnParent;
const env = wildcardColumnEnv( wildcard, query );

@@ -936,3 +936,3 @@ if (!env)

if (!colParent || colParent.value._artifact) {
// avoid "not found" messages if pathHead can't be found
// avoid "not found" messages if columnParent can't be found
const user = colParent || query;

@@ -945,5 +945,5 @@ for (const name in user.excludingDict)

function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._pathHead;
function wildcardColumnEnv( wildcard, query ) { // etc. wildcard._columnParent;
// if (envParent) console.log( 'CE:', envParent._origin, query );
const colParent = wildcard._pathHead;
const colParent = wildcard._columnParent;
if (!colParent)

@@ -992,4 +992,4 @@ return userQuery( query )._combined; // see combinedSourcesOrParentElements

function setWildcardExpandInline( queryElem, pathHead, origin, name, location ) {
setLink( queryElem, '_pathHead', pathHead );
function setWildcardExpandInline( queryElem, columnParent, origin, name, location ) {
setLink( queryElem, '_columnParent', columnParent );
const path = [ { id: name, location } ];

@@ -1000,3 +1000,3 @@ queryElem.value = { path, location }; // TODO: can we omit that? We have _origin

// set _projections when inline with table alias:
// const alias = pathHead?.value?.path?.[0]?._navigation;
// const alias = columnParent?.value?.path?.[0]?._navigation;
// if (alias?.kind === '$tableAlias')

@@ -1003,0 +1003,0 @@ // pushLink( alias.elements[name], '_projections', queryElem );

@@ -69,2 +69,3 @@ // Propagate properties in XSN

onlyViaArtifact,
onlyViaParent,
notWithPersistenceTable,

@@ -71,0 +72,0 @@ };

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

function rewriteArtifact( art ) {
// return;
if (!art.query) {

@@ -337,4 +336,2 @@ rewriteAssociation( art );

// already in Phase 2: redirectImplicitly()
// console.log(message( null, elem.location, elem, {art:assoc,target:assoc.target},
// 'Info','FK').toString())
elem.foreignKeys = Object.create(null); // set already here (also for zero foreign keys)

@@ -346,10 +343,5 @@ forEachInOrder( assoc, 'foreignKeys', ( orig, name ) => {

setLink( fk, '_effectiveType', fk );
const te = copyExpr( orig.targetElement, location );
if (elem._redirected) {
const i = te.path[0]; // TODO: or also follow path like for ON?
const state = rewriteItem( elem, i, i.id, elem, true );
if (state && state !== true && te.path.length === 1)
setArtifactLink( te, state );
}
fk.targetElement = te;
fk.targetElement = copyExpr( orig.targetElement, location );
if (elem._redirected)
rewriteKey( elem, fk.targetElement );
} );

@@ -360,2 +352,44 @@ if (elem.foreignKeys) // Possibly no fk was set

function rewriteKey( elem, targetElement ) {
let projectedKey = null;
// rewrite along redirection chain
for (const alias of elem._redirected) {
if (alias.kind !== '$tableAlias')
continue;
projectedKey = firstProjectionForPath( targetElement.path, 0, alias, null );
if (projectedKey.elem) {
const item = targetElement.path[projectedKey.index];
item.id = projectedKey.elem.name.id;
if (projectedKey.index > 0)
targetElement.path.splice(0, projectedKey.index);
}
else {
setArtifactLink( targetElement.path[0], null );
setArtifactLink( targetElement, null );
const culprit = !elem.target.$inferred && elem.target ||
elem.value?.path?.[elem.value.path.length - 1] ||
elem;
// TODO: probably better to collect the non-projected foreign keys
// and have one message for all
error('rewrite-undefined-key', [ weakLocation( culprit.location ), elem ], {
'#': 'std',
id: targetElement.path.map(p => p.id).join('.'),
target: alias._main,
name: elem.name.id,
});
return null;
}
}
if (projectedKey?.elem) {
const item = targetElement.path[0];
setArtifactLink( item, projectedKey.elem );
setArtifactLink( targetElement, projectedKey.elem );
return projectedKey.elem;
}
return null;
}
// TODO: there is no need to rewrite the on condition of non-leading queries,

@@ -387,18 +421,25 @@ // i.e. we could just have on = {…}

const { navigation } = nav;
if (!navigation) // TODO: what about $projection.assoc as myAssoc ?
if (!navigation) { // TODO: what about $projection.assoc as myAssoc ?
if (elem._columnParent)
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
return; // should not happen: $projection, $magic, or ref to const
}
const isAssocInStruct = (navigation !== assoc && navigation._origin !== assoc);
if (isAssocInStruct) {
// For "[sub.]assoc1.assoc2": not supported, yet (#3977)
const multipleAssoc = elem.value.path.slice(0, -1).some(segment => segment._artifact?.target);
if (multipleAssoc && elem._redirected !== null) { // null = already reported
error('rewrite-not-supported', [ elem.target.location, elem ], { '#': 'secondary' });
return;
}
}
// Currently, having an unmanaged association inside a struct is not
// supported by this function:
if (navigation !== assoc && navigation._origin !== assoc) { // TODO: re-check
// For "assoc1.assoc2" and "struct.elem1.assoc2"
if (elem._redirected !== null) // null = already reported
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
}
else if (!nav.tableAlias || nav.tableAlias.path) {
if (!nav.tableAlias || nav.tableAlias.path) {
const navEnv = followNavigationPath( elem.value?.path, nav ) || nav.tableAlias;
traverseExpr( elem.on, 'rewrite-on', elem,
expr => rewriteExpr( expr, elem, nav.tableAlias ) );
expr => rewriteExpr( expr, elem, nav.tableAlias, navEnv ) );
}
else if (elem._pathHead) {
error( 'rewrite-not-supported', [ elem.target.location, elem ] );
else if (elem._columnParent) {
error( 'rewrite-not-supported', [ elem.target.location, elem ], { '#': 'inline-expand' } );
return;
}

@@ -409,2 +450,3 @@ else {

'Selecting unmanaged associations from a sub query is not supported' );
return;
}

@@ -537,3 +579,3 @@

function foreignKeysToOnCondition( elem, assoc, nav ) {
if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')
if (model.options.testMode && !nav.tableAlias && !elem._columnParent && elem.$syntax !== 'calc')
throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');

@@ -581,4 +623,7 @@

if (elem.$syntax !== 'calc') { // different to lhs!
const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, elem );
if (elem.$syntax !== 'calc') {
// Not passing an element, as we don't want to use our own filtered association here!
// That's done for lhs.
const projectedFk = firstProjectionForPath( rhs.path, 0, nav.tableAlias, null );
// different to lhs!
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );

@@ -618,3 +663,9 @@ }

function rewriteExpr( expr, assoc, tableAlias ) {
/**
* @param expr
* @param assoc
* @param tableAlias
* @param navEnv Navigation element / table alias, used to traverse/rewrite the path.
*/
function rewriteExpr( expr, assoc, tableAlias, navEnv = tableAlias ) {
// Rewrite ON condition (resulting in outside perspective) for association

@@ -632,3 +683,3 @@ // 'assoc' in query or including entity from ON cond of mixin element /

return;
if (tableAlias) { // from ON cond of element in source ref/d by table alias
if (navEnv) { // from ON cond of element in source ref/d by table alias
const source = tableAlias._origin;

@@ -641,3 +692,4 @@ const root = expr.path[0]._navigation || expr.path[0]._artifact;

const startIndex = (root.kind === '$self' ? 1 : 0);
const result = firstProjectionForPath( expr.path, startIndex, tableAlias, assoc );
const exprNavigation = (root.kind === '$self' ? tableAlias : navEnv);
const result = firstProjectionForPath( expr.path, startIndex, exprNavigation, assoc );
// For `assoc[…]`, ensure that we don't rewrite to another projection on `assoc`.

@@ -661,4 +713,4 @@ if (result.item && assoc._origin === result.item._artifact)

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

@@ -706,4 +758,5 @@ rewritePath( expr, nav.item, assoc, elem,

const elemref = root._navigation?.kind === '$self' ? path.slice(1) : path;
// TODO: Fix message for sub-elements: `s: { a: Association on x=1, x: Integer};` for x
error( 'rewrite-not-projected', [ location, assoc ], {
name: assoc.name.id, art: item._artifact, elemref: { ref: elemref },
name: assoc.name.id, art: elemref[0]._artifact, elemref: { ref: elemref },
}, {

@@ -745,10 +798,7 @@ std: 'Projected association $(NAME) uses non-projected element $(ELEMREF)',

}
else if (i) {
state = rewriteItem( state, i, i.id, assoc, false );
else {
state = rewriteItem( state, i, assoc );
if (!state || state === true)
break;
}
else {
return;
}
}

@@ -766,5 +816,11 @@ if (state !== true)

function rewriteItem( elem, item, name, assoc, forKeys ) {
/**
* @param elem "Navigation environment" (element) for `item`.
* @param item Path segment to rewrite.
* @param assoc Published association of query.
*/
function rewriteItem( elem, item, assoc ) {
if (!elem._redirected)
return true;
let name = item.id;
for (const alias of elem._redirected) {

@@ -778,17 +834,4 @@ // TODO: a message for the same situation as msg 'rewrite-shadowed'?

name = proj?.name?.id;
if (!name) {
if (!forKeys)
break;
setArtifactLink( item, null );
const culprit = elem.target && !elem.target.$inferred && elem.target ||
elem.value?.path?.[elem.value.path.length - 1] ||
elem;
// TODO: probably better to collect the non-projected foreign keys
// and have one message for all
error( 'rewrite-undefined-key', [ weakLocation( culprit.location ), assoc ], {
'#': 'std', id: item.id, target: alias._main, name: assoc.name.id,
});
// ''
return null;
}
if (!name)
break;
item.id = name;

@@ -821,10 +864,16 @@ // TODO: Why not break here? Test test3/scenarios/AFC/db/view/consumption/C_ScopedRole.cds

// TODO: Info if more than one possibility?
// console.log(navigation,navigation._projections)
if (!navigation)
return {};
else if (!navigation._projections)
if (!navigation._projections && !navigation._complexProjections)
return null;
return (preferred && navigation._projections.includes( preferred ))
? preferred
: navigation._projections[0] || null;
// _complexProjections contains projections that are not "simple",
// i.e. contain a filter or arguments. Only used if it contains our
// preferred association.
if (preferred && ( navigation._complexProjections?.includes( preferred ) ||
navigation._projections?.includes( preferred )))
return preferred;
return navigation._projections?.[0] || null;
}

@@ -844,2 +893,5 @@

*
* If nothing was found, `ret.elem` is null, and `ret.item` is the last segment for which
* there was a $navElement.
*
* @param {any[]} path

@@ -865,4 +917,5 @@ * @param {number} startIndex

let navItem = nav;
for (let i = startIndex; i < path.length; ++i) {
const item = path[i];
let navIndex = startIndex;
for (; navIndex < path.length; ++navIndex) {
const item = path[navIndex];
navItem = item?.id && navItem.elements?.[item.id];

@@ -872,7 +925,7 @@ if (!navItem) {

}
else if (navItem._projections) {
else if (navItem._projections || navItem._complexProjections) {
const projElem = navProjection( navItem, elem );
if (projElem && projElem === elem) {
// in case the specified association is found, _always_ use it.
return { index: i, item, elem };
return { index: navIndex, item, elem };
}

@@ -883,3 +936,3 @@ else if (projElem) {

proj = {
index: i, item, elem: projElem, queryIndex,
index: navIndex, item, elem: projElem, queryIndex,
};

@@ -890,7 +943,45 @@ }

}
if (proj)
return proj;
return proj || { index: startIndex, item: path[startIndex], elem: null };
const index = (navIndex - 1) <= startIndex ? startIndex : (navIndex - 1);
return { index, item: path[index], elem: null };
}
/**
* Follow the navigation along the given path to its N-1 path step, so
* that the last step can be resolved against the returned navigation like
* `returnValue.elements[last.id]`.
*
* @param {XSN.Path} path
* @param {object} nav
* @returns {object|null}
*/
function followNavigationPath( path, nav ) {
if (!nav.item || !path || path.length === 1)
return nav.tableAlias;
const startIndex = path.indexOf(nav.item);
if (startIndex === -1)
return null;
// navigation is already at last path step
if (startIndex === path.length - 1) {
return nav.navigation?.kind === '$navElement'
? nav.navigation._parent
: nav.tableAlias;
}
let navItem = nav.navigation || nav.tableAlias;
for (let i = startIndex + 1; i < path.length - 1; ++i) {
const item = path[i];
navItem = item?.id && navItem.elements?.[item.id];
if (!navItem)
return null;
}
return navItem;
}
/**
* Return condensed info about reference in select item

@@ -897,0 +988,0 @@ * - tableAlias.elem -> { navigation: navElem, item: path[1], tableAlias }

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

function columnRefStartsWithSelf( col ) {
for (; col; col = col._pathHead) {
for (; col; col = col._columnParent) {
const ref = col.value;

@@ -613,0 +613,0 @@ const head = ref && !ref.scope && ref.path?.[0];

@@ -358,3 +358,3 @@ // Rewrite paths in annotation expressions.

// path step, yet. We need to implement expand/inline.
const isSimpleSelectItem = target.value?.path && target._main?.query && !target._pathHead;
const isSimpleSelectItem = target.value?.path && target._main?.query && !target._columnParent;
if (isSimpleSelectItem) {

@@ -669,4 +669,5 @@ const isSelfPath = (expr.path[0]?._navigation?.kind === '$self');

const item = expr.path[index];
// Not a query -> no $navElement -> use `elements`
if (!env.query && env.kind !== 'select') {
// If the artifact is already in the same definition, we must not check the query.
// Or if it is not a query -> no $navElement -> use `elements`
if (item._artifact._main === env || !env.query && env.kind !== 'select') {
if (env.elements?.[item.id])

@@ -673,0 +674,0 @@ return [ env.elements[item.id], index ];

@@ -269,4 +269,15 @@ 'use strict';

});
if (!options.odataNoCreator) {
// remove unqualified @Core.Links and #CAP
Object.keys(serviceCsn).forEach((key) => {
if (key === '@Core.Links' || key.startsWith('@Core.Links.') ||
key === '@Core.Links#CAP' || key.startsWith('@Core.Links#CAP.'))
delete serviceCsn[key];
});
}
// Create annotations and distribute into Schemas, merge vocabulary cross refs into xServiceRefs
addAnnotations2XServiceRefs();
addAnnotationsAndXServiceRefs();
if (!options.odataNoCreator)
LeadSchema.prepend(waterMark());

@@ -312,2 +323,14 @@ // Finally add cross service references into the EDM and extract the targetSchemaNames

function waterMark() {
const rel = new Edm.PropertyValue(v, 'rel');
rel._xmlOnlyAttributes.String = 'author';
rel._jsonOnlyAttributes['Edm.String'] = 'author';
const href = new Edm.PropertyValue(v, 'href');
href._xmlOnlyAttributes.String = 'https://cap.cloud.sap';
href._jsonOnlyAttributes['Edm.String'] = 'https://cap.cloud.sap';
const watermark = new Edm.Annotation(v, 'Core.Links', new Edm.Collection(v, new Edm.Record(v, rel, href)));
// watermark._edmAttributes['Qualifier'] = 'CAP';
return watermark;
}
// Sort definitions into their schema container

@@ -1104,4 +1127,5 @@ function populateSchemas( schemas ) {

// generate the Edm.Annotations tree and append it to the corresponding schema
function addAnnotations2XServiceRefs( ) {
function addAnnotationsAndXServiceRefs( ) {
options.getFinalTypeInfo = csnUtils.getFinalTypeInfo;
const { annos, usedVocabularies, xrefs } = translate.csn2annotationEdm(reqDefs, csnUtils, csn.vocabularies, serviceCsn.name, Edm, options, messageFunctions, mergedVocabularies);

@@ -1108,0 +1132,0 @@ // distribute edm:Annotations into the schemas

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

this.toJSONattributes(json);
this.toJSONchildren(json);
return json;
return this.toJSONchildren(json);
}

@@ -135,2 +134,3 @@

});
return json;
}

@@ -330,3 +330,8 @@

// 'edmx:DataServices' should not appear in JSON
super.toJSONchildren(json);
// Annotations first
this._children.filter(c => c._edmAttributes.Term).forEach((c) => {
json = { ...json, ...c.toJSON() };
});
json = super.toJSONchildren(json);
if (this._annotations.length > 0) {

@@ -451,4 +456,3 @@ this._annotations.filter(a => a._edmAttributes.Term).forEach((a) => {

this._service.toJSONattributes(json);
this._service.toJSONchildren(json);
return json;
return this._service.toJSONchildren(json);
}

@@ -1161,4 +1165,3 @@

this.toJSONattributes(json);
this.toJSONchildren(json);
return json;
return this.toJSONchildren(json);
}

@@ -1234,2 +1237,3 @@

});
return json;
}

@@ -1251,4 +1255,5 @@ }

class Annotation extends AnnotationBase {
constructor(version, termName) {
constructor(version, termName, ...children) {
super(version, { Term: termName } );
this.append(...children);
}

@@ -1273,2 +1278,7 @@

class Collection extends AnnotationBase {
constructor(version, ...children) {
super(version);
this.append(...children);
}
toJSON() {

@@ -1281,2 +1291,6 @@ // EDM JSON doesn't mention annotations on collections

class Record extends AnnotationBase {
constructor(version, ...children) {
super(version);
this.append(...children);
}
toJSONattributes(json) {

@@ -1312,2 +1326,3 @@ if (this._jsonOnlyAttributes.Type)

});
return json;
}

@@ -1314,0 +1329,0 @@ }

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

forEachDefinition, forEachMemberRecursively, getUtils,
transformExpression, findAnnotationExpression,
transformAnnotationExpression,
} = require('../model/csnUtils');

@@ -148,10 +148,8 @@ const { isBuiltinType } = require('../base/builtins');

};
let exprAnnos = Object.keys(action).filter(pn => findAnnotationExpression(action, pn));
exprAnnos.forEach((pn) => {
transformExpression(action, pn, markBindingParam, loc);
Object.keys(action).filter(pn => pn[0] === '@').forEach((pn) => {
transformAnnotationExpression(action, pn, markBindingParam, loc);
});
forEachMemberRecursively(action, (member, _memberName, _prop, path, _parent) => {
exprAnnos = Object.keys(member).filter(pn => findAnnotationExpression(member, pn));
exprAnnos.forEach((pn) => {
transformExpression(member, pn, markBindingParam, path);
Object.keys(member).filter(pn => pn[0] === '@').forEach((pn) => {
transformAnnotationExpression(member, pn, markBindingParam, path);
});

@@ -158,0 +156,0 @@ }, loc);

@@ -20,2 +20,6 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

const CdlLexer = require( '../parsers/Lexer' );
const CdlParser = require( '../gen/CdlParser' );
const { createMessageFunctions } = require( '../base/messages' );
// Error listener used for ANTLR4-generated parser

@@ -133,2 +137,5 @@ class ErrorListener extends antlr4.error.ErrorListener {

rule = 'cdl' ) {
if (options.newParser)
return parseWithNewParser( source, filename, options, messageFunctions, rule );
const lexer = new Lexer( new antlr4.InputStream(source) );

@@ -216,2 +223,57 @@ const tokenStream = new RewriteTypeTokenStream(lexer);

function parseWithNewParser( source, filename, options, messageFunctions, rule ) {
if (CdlParser.tracingParser) // tracing → direct console output of message
messageFunctions = createMessageFunctions( {}, 'parse', {} );
const lexer = new CdlLexer( filename, source );
const parser = new CdlParser( lexer, options, messageFunctions ).init();
parser.filename = filename; // LSP compatibility
parser.tokenStream = parser; // LSP compatibility: object with property `tokens`
// LSP feature: provide parse listener with ANTLR-like context:
const { parseListener } = options;
if (parseListener) {
// TODO LSP: we could also call different listener methods: then LSP could
// have dedicated methods for ANTLR-based and new parser
parser.rule_ = function rule_( ...args ) {
CdlParser.prototype.rule_.apply( this, args );
let state = this.s;
while (typeof this.table[--state] !== 'string')
;
const $ctx = { // TODO LSP: more to add?
parser: this, // set in generated ANTLR parser for each rule context
ruleName: this.table[state], // instead of ruleIndex
start: this.la(), // set in Parser#enterRule
stop: null,
};
parser.stack.at( -1 ).$ctx = $ctx;
parseListener.enterEveryRule( $ctx );
};
parser.exit_ = function exit_( ...args ) {
const { $ctx } = parser.stack.at( -1 );
// TODO: what should we do in case of errors?
$ctx.stop = this.lb();
parseListener.exitEveryRule( $ctx );
CdlParser.prototype.exit_.apply( this, args );
};
}
const result = {};
const rulespec = rules[rule];
if (rulespec) {
try {
parser[rulespec.func]( result );
}
catch (e) {
if (!(e instanceof RangeError && /Maximum.*exceeded$/i.test( e.message )))
throw e;
messageFunctions.error('syntax-invalid-source', { file: filename },
{ '#': 'cdl-stackoverflow' } );
result[rulespec.returns] = undefined;
}
}
const ast = result[rulespec?.returns] || (rule === 'cdl' ? new XsnSource( 'cdl' ) : {} );
if (options.attachTokens === true || options.attachTokens === filename)
ast.tokenStream = parser; // with property tokens
return ast;
}
module.exports = parse;

@@ -104,2 +104,4 @@ // Generic ANTLR parser class with AST-building functions

identAst,
reportPathNamedManyOrOne,
reportMissingSemicolon,
pushXprToken,

@@ -280,2 +282,3 @@ pushOpToken,

if (t.text === '@' && t.line <= this._input.LT(-1).line) {
// TODO: use 'syntax-missing-newline'
this.warning( 'syntax-missing-semicolon', t, { code: ';' },

@@ -806,2 +809,27 @@ // eslint-disable-next-line max-len

function reportPathNamedManyOrOne( { path } ) {
if (path.length === 1 && !path[0].$delimited &&
[ 'many', 'one' ].includes( path[0].id.toLowerCase() )) {
this.message( 'syntax-unexpected-many-one', path[0].location,
{ code: path[0].id, delimited: path[0].id } );
}
}
function reportMissingSemicolon() {
const next = this._input.LT(1);
if (next.text !== ';' && next.text !== '' && // ';' by insertSemicolon()
next.text !== '}' && next.type !== antlr4.Token.EOF &&
this._input.LT(-1).text !== '}') {
const offending = this.literalNames[next.type] || this.symbolicNames[next.type];
const loc = this.tokenLocation( this._input.LT(-1) );
// better location after the previous token:
const location = new Location( loc.file, loc.endLine, loc.endCol );
// it would be nicer to mention the doc comment if present, but not worth the
// effort; 'syntax-missing-semicolon' already used
this.warning( 'syntax-missing-proj-semicolon', location,
{ expecting: [ "';'" ], offending },
'Missing $(EXPECTING) before $(OFFENDING)');
}
}
function pushXprToken( args ) {

@@ -808,0 +836,0 @@ const token = this._input.LT(-1);

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

transformExpression,
transformAnnotationExpression,
applyTransformations,

@@ -1423,2 +1424,3 @@ applyTransformationsOnNonDictionary,

transformExpression,
transformAnnotationExpression,
applyTransformations,

@@ -1425,0 +1427,0 @@ applyTransformationsOnNonDictionary,

@@ -309,2 +309,4 @@ // Make internal properties of the XSN / augmented CSN visible

function array( node, fn ) {
if (!Array.isArray( node ))
return node;
const r = node.map( n => fn( n, node ) );

@@ -311,0 +313,0 @@ if (node[$location])

@@ -13,42 +13,53 @@ 'use strict';

module.exports = {
sqlite: getFilterObject(
'sqlite',
function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
if (isKey(elementOrConstraint)) { // Key must not be extended
error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
function getFilter(options) {
const filters = {
sqlite: getFilterObject(
options,
'sqlite',
function forEachExtension(extend, name, elementOrConstraint, { error, warning }) {
if (isKey(elementOrConstraint)) { // Key must not be extended
error('type-unsupported-key-sqlite', [ 'definitions', extend, 'elements', name ], { id: name, name: 'sqlite', '#': 'std' } );
return false;
}
else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
return false;
}
return true;
},
function forEachMigration(migrate, name, migration, change, error) {
const newIsKey = isKey(migration.new);
const oldIsKey = isKey(migration.old);
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
else
delete change[name];
},
function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
delete constraintRemovals[name];
},
function primaryKey() {
return false;
}
else if (elementOrConstraint.parentTable) { // constraints have a .parentTable
warning('def-unsupported-constraint-add', [ 'definitions', elementOrConstraint.parentTable, 'elements', elementOrConstraint.paths ? name : name.slice(elementOrConstraint.parentTable.length + 1) ], { id: elementOrConstraint.identifier || name, name: 'sqlite' },
'Ignoring add of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
return false;
}
),
postgres: getFilterObject(options, 'postgres'),
h2: getFilterObject(options, 'h2'),
hana: getFilterObject(options, 'hana'),
};
return true;
},
function forEachMigration(migrate, name, migration, change, error) {
const newIsKey = isKey(migration.new);
const oldIsKey = isKey(migration.old);
if ((newIsKey || oldIsKey) && oldIsKey !== newIsKey) // Turned into key or key was removed
error('type-unsupported-key-sqlite', [ 'definitions', migrate, 'elements', name ], { id: name, name: 'sqlite', '#': 'changed' } );
else
delete change[name];
},
function forEachConstraintRemoval(constraintRemovals, name, constraint, warning) {
warning('def-unsupported-constraint-drop', [ 'definitions', constraint.parentTable, 'elements', constraint.paths ? name : name.slice(constraint.parentTable.length + 1) ], { id: constraint.identifier || name, name: 'sqlite' },
'Ignoring drop of constraint $(ID), as this is not supported for dialect $(NAME); you need to manually resolve this via shadow tables and data copy');
delete constraintRemovals[name];
},
function primaryKey() {
return false;
}
),
postgres: getFilterObject('postgres'),
h2: getFilterObject('h2'),
hana: getFilterObject('hana'),
return filters[options.sqlDialect];
}
module.exports = {
csn: filterCsn,
getFilter,
};
function getFilterObject( dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
function getFilterObject( options, dialect, extensionCallback, migrationCallback, removeConstraintsCallback, primaryKeyCallback ) {
const context = { hasLossyChanges: false };
const raiseErrorOrMarkAsLossy = getSafeguardManager(context, options);
return {

@@ -74,20 +85,24 @@ // will be called with a simple Array.filter, as we need to filter constraint `ADD` for SQLite

migration: (migrations, { error, warning, message }) => {
forEach(migrations.remove, (name) => {
error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported');
forEach(migrations.remove, (name, migration) => {
raiseErrorOrMarkAsLossy(migration, () => error('def-unsupported-element-drop', [ 'definitions', migrations.migrate, 'elements', name ], {}, 'Dropping elements is not supported'));
});
forEach(migrations.change, (name, migration) => {
const loc = [ 'definitions', migrations.migrate, 'elements', name ];
if (migration.new.type === migration.old.type && migration.new.length < migration.old.length)
error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported');
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-length-change', loc, { id: name }, 'Changed element $(ID) is a length reduction and is not supported'));
else if (migration.new.type === migration.old.type && migration.new.scale !== migration.old.scale)
error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported');
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-scale-change', loc, { id: name }, 'Changed element $(ID) is a scale change and is not supported'));
else if (migration.new.type === migration.old.type && migration.new.precision !== migration.old.scale)
error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported');
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-precision-change', loc, { id: name }, 'Changed element $(ID) is a precision change and is not supported'));
else if (migration.new.type !== migration.old.type && typeChangeIsNotCompatible(dialect, migration.old.type, migration.new.type))
error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported');
raiseErrorOrMarkAsLossy(migration, () => error('type-unsupported-change', loc, { id: name, name: migration.old.type, type: migration.new.type }, 'Changed element $(ID) is a lossy type change from $(NAME) to $(TYPE) and is not supported'));
else if (dialect !== 'sqlite' && isKey(migration.new) && !isKey(migration.old)) // key added/changed - pg, hana and sqlite do not support it, h2 probably also - issues when data is in the table already
message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } );
raiseErrorOrMarkAsLossy(migration, () => message('type-unsupported-key-change', [ 'definitions', migrations.migrate, 'elements', name ], { id: name, '#': 'changed' } ));
else if (migrationCallback)
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
if (options.script && migration.lossy && migrationCallback)
migrationCallback(migrations.migrate, name, migration, migrations.change, error);
// TODO: precision/scale growth

@@ -107,3 +122,3 @@ });

if (isPersistedAsTable(artifact))
error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported');
raiseErrorOrMarkAsLossy(artifact, () => error('def-unsupported-table-drop', [ 'definitions', artifactName ], 'Dropping tables is not supported'));
},

@@ -116,2 +131,3 @@ changedPrimaryKeys: (changedPrimaryKeyArtifactName) => {

},
hasLossyChanges: () => context.hasLossyChanges,
};

@@ -180,1 +196,13 @@ }

}
function getSafeguardManager( context, options ) {
return function raiseErrorOrMarkAsLossy(migration, raiseError) {
if (!options.script) {
raiseError();
}
else {
migration.lossy = true;
context.hasLossyChanges = true;
}
};
}

@@ -41,2 +41,3 @@ // Compiler options

.option('-R, --raw-output <name>')
.option(' --new-parser')
.option(' --internal-msg')

@@ -58,2 +59,3 @@ .option(' --beta-mode')

.option(' --no-recompile')
.option(' --skip-name-check', { optionName: '$skipNameCheck' })
.positionalArgument('<files...>')

@@ -118,2 +120,3 @@ .help(`

--internal-msg Write raw messages with call stack to <stdout>/<stderr>
--new-parser Use the new CDL parser
--beta-mode Enable all unsupported, incomplete (beta) features

@@ -156,2 +159,3 @@ --beta <list> Comma separated list of unsupported, incomplete (beta) features to use.

--no-recompile Don't recompile in case of internal errors
--skip-name-check Skip certain name checks, e.g. that there must be no '.' in element names.

@@ -173,3 +177,3 @@ Commands

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

@@ -253,2 +257,3 @@

.option(' --odata-vocabularies <list>')
.option(' --odata-no-creator')
.option('-c, --csn')

@@ -287,2 +292,3 @@ .option('-f, --odata-format <format>', { valid: ['flat', 'structured'] })

{ prefix: { alias, ns, uri }, ... }
--odata-no-creator Omit creator identification in API
-n, --sql-mapping <style> Annotate artifacts and elements with "@cds.persistence.name", which is

@@ -574,3 +580,3 @@ the corresponding database name (see "--sql-mapping" for "toHana or "toSql")

optionProcessor.command('toEffectiveCsn')
optionProcessor.command('forEffective')
.option('-h, --help')

@@ -583,3 +589,3 @@ .option('--resolve-simple-types <val>', { valid: ['true', 'false'] } )

.help(`
Usage: cdsc toEffectiveCsn [options] <files...>
Usage: cdsc forEffective [options] <files...>

@@ -586,0 +592,0 @@ (internal): Get the effective CSN model compiled from the provided CDS files.

@@ -309,18 +309,57 @@ 'use strict';

* @param {object} parent Start node
* @param {string} propName Start at specific property of parent
* @param {string|number} parentName Start at specific property of parent
* @param {object} transformers Map of callback functions
* @param {CSN.Path} path Path to parent
* @param {object} ctx bucket to tunnel various info into the transformers
* @returns {object} transformed node
*/
function transformExpression( parent, propName, transformers, path = [] ) {
const callT = (t, cpn, child) => {
const ct = t[cpn];
function transformExpression( parent, parentName, transformers, path = [], ctx = undefined ) {
const callT = (t, childName, child) => {
const ct = t[childName];
if (ct) {
const ppn = propName;
if (Array.isArray(ct))
ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
ct.forEach(cti => cti(child, childName, child[childName], path, parent, parentName, ctx));
else
ct(child, cpn, child[cpn], path, parent, ppn);
ct(child, childName, child[childName], path, parent, parentName, ctx);
}
};
if (parentName != null) {
const child = parent[parentName];
if (!child || typeof child !== 'object' ||
!{}.propertyIsEnumerable.call( parent, parentName ))
return parent;
path = [ ...path, parentName ];
if (Array.isArray(child)) {
child.forEach( (n, i) => transformExpression( child, i, transformers, path, ctx ) );
}
else {
for (const childName of Object.getOwnPropertyNames( child )) {
if (Array.isArray(transformers))
transformers.forEach(t => callT(t, childName, child));
else
callT(transformers, childName, child);
transformExpression(child, childName, transformers, path, ctx);
}
}
}
else {
for (parentName of Object.getOwnPropertyNames( parent ))
transformExpression( parent, parentName, transformers, path, ctx );
}
return parent;
}
/**
* Drill into an annotation value and inspect each (sub-)object value if it is
* an annotation expression. If so, call the real transformExpression that will
* execute the callbacks (most likely reference rewriting), continue otherwise
*
* @param {object} parent Start node
* @param {string|number} propName Start at specific property of parent
* @param {object} transformers Map of callback functions
* @param {CSN.Path} path Path to parent
* @returns {object} transformed node
*/
function transformAnnotationExpression( parent, propName, transformers, path = [] ) {
if (propName != null) {

@@ -332,14 +371,12 @@ const child = parent[propName];

if (isAnnotationExpression(child))
return transformExpression(parent, propName, transformers, path, { annoExpr: child });
path = [ ...path, propName ];
if (Array.isArray(child)) {
child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
child.forEach( (n, i) => transformAnnotationExpression( child, i, transformers, path ) );
}
else {
for (const cpn of Object.getOwnPropertyNames( child )) {
if (Array.isArray(transformers))
transformers.forEach(t => callT(t, cpn, child));
else
callT(transformers, cpn, child);
transformExpression(child, cpn, transformers, path);
}
for (const cpn of Object.getOwnPropertyNames( child ))
transformAnnotationExpression(child, cpn, transformers, path);
}

@@ -349,3 +386,3 @@ }

for (propName of Object.getOwnPropertyNames( parent ))
transformExpression( parent, propName, transformers, path );
transformAnnotationExpression( parent, propName, transformers, path );
}

@@ -390,2 +427,3 @@ return parent;

transformExpression,
transformAnnotationExpression,
applyTransformations,

@@ -392,0 +430,0 @@ applyTransformationsOnNonDictionary,

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

getServiceNames, applyAnnotationsFromExtensions,
transformExpression } = require('../../model/csnUtils');
transformAnnotationExpression } = require('../../model/csnUtils');
const { forEach } = require('../../utils/objectUtils');

@@ -235,13 +235,12 @@ const { isArtifactInSomeService, getServiceOfArtifact } = require('../odata/utils');

if(pn[0] === '@') {
let refChanged = false;
transformExpression(member, pn,{
ref: (_parent, _prop, xpr, _path) => {
if(xpr[0] === '$draft') {
xpr[0] = '$self';
refChanged = true;
transformAnnotationExpression(member, pn, {
ref: (_parent, _prop, xpr, _path, _p, _ppn, ctx) => {
if(xpr[0] === '$draft') {
xpr[0] = '$self';
if(ctx?.annoExpr?.['='])
ctx.annoExpr['='] = true;
}
}
}
});
if (refChanged)
member[pn]['='] = true;
},
);
}

@@ -248,0 +247,0 @@ });

'use strict';
const {
forEachDefinition, forEachMemberRecursively, findAnnotationExpression, applyTransformationsOnNonDictionary, transformExpression,
forEachDefinition, forEachMemberRecursively, applyTransformationsOnNonDictionary, transformExpression, transformAnnotationExpression,
} = require('../../model/csnUtils');

@@ -18,3 +18,3 @@ const { getStructStepsFlattener } = require('../db/flattening');

*/
function flattenRefs( csn, options, csnUtils, messageFunctions ) {
function flattenRefs(csn, options, csnUtils, messageFunctions) {
const cleanup = [];

@@ -40,11 +40,7 @@ forEachDefinition(csn, (artifact) => {

Object.keys(element)
.filter(pn => findAnnotationExpression(element, pn))
.filter(pn => pn.startsWith('@') && element[pn])
.forEach((anno) => {
applyTransformationsOnNonDictionary(element, anno, {
xpr: (parent, prop) => {
transformExpression(parent, prop, {
ref: absolutifier,
}, []);
},
}, {}, []);
transformAnnotationExpression(element, anno, {
ref: absolutifier,
}, []);
if (element[anno].ref)

@@ -88,5 +84,5 @@ absolutifier(element[anno], 'ref', element[anno].ref);

Object.keys(a)
.filter(pn => findAnnotationExpression(a, pn))
.filter(pn => pn.startsWith('@') && a[pn])
.forEach((pn) => {
transformExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
transformAnnotationExpression(a, pn, [ markBindingParam, refFlattener ], [ 'definitions', defName, 'actions', an ]);
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));

@@ -98,4 +94,4 @@ adaptRefs.length = 0;

forEachMemberRecursively(a, (member, memberName, prop, path) => {
Object.keys(member).filter(pn => findAnnotationExpression(member, pn)).forEach((pn) => {
transformExpression(member, pn, [ markBindingParam, refFlattener ], path);
Object.keys(member).filter(pn => pn.startsWith('@') && member[pn]).forEach((pn) => {
transformAnnotationExpression(member, pn, [ markBindingParam, refFlattener ], path);
adaptRefs.forEach(fn => fn(true, 1, parent => parent.$bparam));

@@ -102,0 +98,0 @@ adaptRefs.length = 0;

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

copyAnnotations, forEachMemberRecursively,
transformExpression, findAnnotationExpression } = require('../../model/csnUtils');
transformExpression, transformAnnotationExpression } = require('../../model/csnUtils');
const { isBuiltinType, isMagicVariable } = require('../../base/builtins');

@@ -58,3 +58,3 @@ const transformUtils = require('../transformUtils');

forEachMemberRecursively(flatElt.items, (elt, _eltName, _prop, path) => {
const exprAnnos = Object.keys(elt).filter(pn => findAnnotationExpression(elt, pn));
const exprAnnos = Object.keys(elt).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(elt, exprAnnos, elt.$path, path, 0, true);

@@ -72,3 +72,3 @@ }, [ flatEltName ], true, { pathWithoutProp: true } );

const flatAnnos = Object.create(null);
const annoNames = copyAnnotations(def, flatAnnos).filter(an => findAnnotationExpression(def, an));
const annoNames = copyAnnotations(def, flatAnnos).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(flatAnnos, annoNames, [ 'definitions', defName ], [ defName ], 0);

@@ -106,6 +106,6 @@ setProp(def, '$flatAnnotations', flatAnnos);

const flatAnnos = Object.create(null);
const annoNames = copyAnnotations(action, flatAnnos).filter(an => findAnnotationExpression(action, an));
const annoNames = copyAnnotations(action, flatAnnos).filter(pn => pn[0] === '@');
annoNames.forEach((an) => {
refCheck.anno = an;
transformExpression(flatAnnos, an,
transformAnnotationExpression(flatAnnos, an,
[ markBindingParam, refCheck, refFlattener ],

@@ -119,6 +119,6 @@ [ 'definitions', defName, 'actions', actionName ]);

forEachMemberRecursively(action, (member, memberName, prop, path, _parent) => {
const exprAnnos = Object.keys(member).filter(pn => findAnnotationExpression(member, pn));
const exprAnnos = Object.keys(member).filter(pn => pn[0] === '@');
exprAnnos.forEach((pn) => {
refCheck.anno = pn;
transformExpression(member, pn, [ markBindingParam, refCheck, refFlattener ], path);
transformAnnotationExpression(member, pn, [ markBindingParam, refCheck, refFlattener ], path);
adaptRefs.forEach(fn => fn(true, 1, (parent) => parent.$bparam));

@@ -201,3 +201,3 @@ adaptRefs.length = 0;

// of typePathRoot (which could be some completely different path)
const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => findAnnotationExpression(flatElt, pn));
const exprAnnoNames = copyAnnotations(elt, flatElt, false, excludes).filter(pn => pn[0] === '@');
flattenAndPrefixExprPaths(flatElt, exprAnnoNames, elt.$path, rootPrefix, typeIdx);

@@ -226,3 +226,3 @@ // Copy selected type properties

acc[0].push(pn);
if (findAnnotationExpression(flatElt, pn) || pn === 'value')
else
acc[1].push(pn);

@@ -336,5 +336,4 @@ return acc;

let refChanged = false;
const absolutifier = {
ref : (parent, prop, xpr) => {
ref : (parent, prop, xpr, _path, _p, _ppn, ctx) => {
const head = xpr[0].id || xpr[0];

@@ -376,16 +375,23 @@ let isPrefixed = false;

}
if(isPrefixed)
refChanged = isPrefixed;
if(isPrefixed && ctx?.annoExpr?.['=']) {
ctx.annoExpr['='] = true;
}
}
}
refFlattener.$fnArgs = [ refParentIsItems ];
propNames.forEach(pn => {
refChanged = false;
refCheck.anno = pn;
transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn =>
{ if( fn(refParentIsItems)) refChanged = true });
adaptRefs.length = 0;
transformExpression(carrier, pn, absolutifier, csnPath)
if(refChanged && carrier[pn]['='])
carrier[pn]['='] = true;
if(pn[0] === '@') {
transformAnnotationExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(refParentIsItems));
adaptRefs.length = 0;
transformAnnotationExpression(carrier, pn, absolutifier, csnPath);
}
if(pn === 'value') {
transformExpression(carrier, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(refParentIsItems));
adaptRefs.length = 0;
transformExpression(carrier, pn, absolutifier, csnPath);
}
});

@@ -466,10 +472,6 @@ }

Object.keys(member).filter(pn => pn[0] === '@').forEach(pn => {
let refChanged = false;
refCheck.anno = pn;
transformExpression(member, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => {
if (fn(true, 1)) refChanged = true });
transformAnnotationExpression(member, pn, [ refCheck, refFlattener ], csnPath);
adaptRefs.forEach(fn => fn(true, 1));
adaptRefs.length = 0;
if(refChanged && member[pn]['='])
member[pn]['='] = true;
});

@@ -501,4 +503,3 @@ }, [ 'definitions', tn ]);

const transformer = {
ref: (parent, prop, ref, path) => {
let refChanged = false;
ref: (parent, _prop, ref, path, _p, _ppn, ctx) => {
const { links, art, scope } = inspectRef(path);

@@ -516,5 +517,6 @@ const resolvedLinkTypes = resolveLinkTypes(links);

// setProp(parent, '$structRef', parent.ref);
[ parent.ref, refChanged ] = flattenStructStepsInRef(ref,
const [ newRef, refChanged ] = flattenStructStepsInRef(ref,
scopedPath, links, scope, resolvedLinkTypes,
suspend, suspendPos, parent.$bparam);
parent.ref = newRef;
resolved.set(parent, { links, art, scope });

@@ -561,4 +563,13 @@ // Explicitly set implicit alias for things that are now flattened - but only in columns

};
// adapt queries later
adaptRefs.push(fn);
if(ctx?.annoExpr?.['=']) {
const annoExpr = ctx.annoExpr;
adaptRefs.push((...args) => {
if(fn(...args))
annoExpr['='] = true;
});
}
else
adaptRefs.push(fn);
},

@@ -565,0 +576,0 @@ }

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

const {
transformExpression,
transformAnnotationExpression,
forEachDefinition,
forEachGeneric,
forEachMemberRecursively,
findAnnotationExpression,
} = require('../../model/csnUtils');

@@ -228,5 +227,5 @@ const { isBuiltinType } = require('../../base/builtins');

Object.keys(elt).filter(pn => pn[0] === '@' && findAnnotationExpression(elt, pn)).forEach(xprAName => {
transformExpression(elt, xprAName, {
ref: (parent, prop, xpr, csnPath) => {
Object.keys(elt).filter(pn => pn[0] === '@').forEach(anno => {
transformAnnotationExpression(elt, anno, {
ref: (parent, prop, xpr, csnPath, _p, _ppn, ctx) => {
let prefixMatch = true;

@@ -243,6 +242,8 @@ const head = xpr[0].id || xpr[0];

parent[prop] = [ '$self', ...xpr.slice(typeRefRootPath.length)];
if(ctx?.annoExpr?.['='])
ctx.annoExpr['='] = true;
}
else {
error('odata-anno-xpr-ref', csnPath,
{ anno: xprAName, elemref: xpr.join('.'), name: usingPositionStr, code: typeRefStr });
{ anno, elemref: xpr.join('.'), name: usingPositionStr, code: typeRefStr });
}

@@ -249,0 +250,0 @@ }

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

never: skip,
onlyViaParent: skip, // TODO: not correct
onlyViaArtifact,

@@ -59,0 +60,0 @@ notWithPersistenceTable,

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

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

"download": "node scripts/downloadANTLR.js",
"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js",
"gen": "node ./scripts/build.js && node scripts/genGrammarChecksum.js && node ./redepage/bin/redepage --pretty --compile lib/gen/CdlParser.js --copy-base-parser lib/parsers/CdlGrammar.g4",
"xmakeAfterInstall": "npm run gen",

@@ -21,0 +21,0 @@ "xmakePrepareRelease": "echo \"$(node scripts/stripReadme.js README.md)\" > README.md && node scripts/assertSnapshotVersioning.js && node scripts/assertChangelog.js && node scripts/cleanup.js --remove-dev",

@@ -34,4 +34,5 @@ # redirected-to-ambiguous

Entity `Target` exists more than once in `View`. In the previous example, this
happens through the *direct* sources in the select clause.
Entity `Target` exists more than once in `View` under different table aliases.
In the previous example, this happens through the *direct* sources in the
select clause.
Because the original target exists twice in the redirected target, the compiler

@@ -41,4 +42,4 @@ isn’t able to correctly resolve the redirection due to ambiguities.

This can also happen through *indirect* sources. For example if entity `Main`
were to include `Target` then selecting from `Target` just once would be enough
to trigger this error.
were to include `Target`, then selecting from `Target` just once would be
enough to trigger this error.

@@ -45,0 +46,0 @@ ## How to Fix

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

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

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

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

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

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

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

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