Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
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 2.11.2 to 2.11.4

lib/transform/db/applyTransformations.js

3

bin/.eslintrc.json

@@ -14,5 +14,4 @@ {

"radix": "off",
// should probably be on
"no-shadow": "off"
"no-shadow": "warn"
}
}

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

const parseLanguage = require('../lib/language/antlrParser');
const { createMessageFunctions } = require('../lib/base/messages');

@@ -55,6 +56,7 @@ const fs = require('fs');

const options = { messages: [], attachTokens: true };
const messageFunctions = createMessageFunctions( options, 'parse', null );
// parseLanguage does not throw on CompilationError, so
// we do not need a try...catch block.
const ast = parseLanguage(source, filename, options);
const ast = parseLanguage(source, filename, options, messageFunctions);

@@ -61,0 +63,0 @@ // To avoid spam, only report errors.

@@ -164,5 +164,12 @@ #!/usr/bin/env node

)
cmdLine.options.assertIntegrity = Boolean(cmdLine.options.assertIntegrity);
cmdLine.options.assertIntegrity = cmdLine.options.assertIntegrity === 'true';
// remap string values for `constraintsAsAlter` option to boolean
if (cmdLine.options.constraintsAsAlter &&
cmdLine.options.constraintsAsAlter === 'true' ||
cmdLine.options.constraintsAsAlter === 'false'
)
cmdLine.options.constraintsAsAlter = cmdLine.options.constraintsAsAlter === 'true';
// Enable all beta-flags if betaMode is set to true

@@ -169,0 +176,0 @@ if (cmdLine.options.betaMode)

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

const msgs = options.messages.filter( m => m.messageId === 'redirected-implicitly-ambiguous' );
// regex match on message text not for productive code!
// 'Choose via $(ANNO) one of $(SORTED_ARTS) as redirection target for $(TARGET) in … $(ART) otherwise'
// NOTE: regex match on message text not for productive code!
for (const msgObj of msgs) {
const matches = msgObj.message.match( /["“][^"”]+["”]/g );
matches.slice(2).forEach( (name) => {
matches.slice( 1, -2 ).forEach( (name) => {
annotates[name.slice( 1, -1 )] = true;

@@ -43,0 +44,0 @@ } );

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

* @param {CSN.Model} csn Plain input CSN
* @param {sqlOptions} [options={}] Options
* @param {SqlOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.sql

@@ -206,3 +206,3 @@ * @private

* @param {CSN.Model} csn A clean input CSN
* @param {sqlOptions} [options={}] Options
* @param {SqlOptions} [options={}] Options
* @returns {SQL[]} Array of SQL statements, tables first, views second

@@ -738,16 +738,2 @@ */

/**
* Options available for to.sql
*
* @typedef {object} sqlOptions
* @property {NamingMode} [sqlMapping='plain'] Naming mode to use
* @property {SQLDialect} [sqlDialect='sqlite'] SQL dialect to use
* @property {object} [variableReplacements] Object containing values for magic variables like "$user"
* @property {string} [variableReplacements.$user.locale] Value for the "$user.locale" variable
* @property {string} [variableReplacements.$user.id] Value for the "$userid" variable
* @property {object} [beta] Enable experimental features - not for productive use!
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
* @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
* @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
*/

@@ -754,0 +740,0 @@ /**

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

'testSortCsn',
'constraintsAsAlter',
'integrityNotEnforced',

@@ -161,3 +162,3 @@ 'integrityNotValidated',

const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming', 'constraints-as-alter-sqlite' ], 'to.sql');

@@ -170,3 +171,3 @@ const result = Object.assign({}, processed);

hdi: (options) => {
const hardOptions = { src: 'hdi' };
const hardOptions = { src: 'hdi', constraintsAsAlter: false };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };

@@ -173,0 +174,0 @@ const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');

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

* input is a string and one of the available options.
* The validation of the option values is case-insensitive.
*

@@ -35,3 +36,3 @@ * @param {any} availableValues Available values

return {
validate: val => typeof val === 'string' && availableValues.indexOf(val) !== -1,
validate: val => typeof val === 'string' && availableValues.some( av => av.toLowerCase() === val.toLowerCase() ),
expected: (val) => {

@@ -146,2 +147,7 @@ return typeof val !== 'string' ? 'type string' : availableValues.join(', ');

},
'constraints-as-alter-sqlite': {
validate: options => options.constraintsAsAlter && options.sqlDialect && options.sqlDialect === 'sqlite',
severity: 'warning',
getMessage: options => `Option 'constraintsAsAlter' is ignored for sqlDialect '${ options.sqlDialect }'`,
},
};

@@ -148,0 +154,0 @@ /* eslint-disable jsdoc/no-undefined-types */

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

if (options.locale) {
options.user
options.user = options.user
? Object.assign(options.user, { locale: options.locale })
: options.user = { locale: options.locale };
: { locale: options.locale };
delete options.locale;

@@ -399,8 +399,6 @@ }

// Assemble result
let result = {
return {
rename : toRenameDdl(forHanaCsn, options),
options
};
return result;
}

@@ -407,0 +405,0 @@

@@ -14,3 +14,3 @@ module.exports = {

'EXTRACT',
'FALSE',
'FALSE', // boolean
'FROM',

@@ -26,4 +26,5 @@ 'IN',

'SOME',
'WHEN',
'TRIM',
'TRUE',
'TRUE', // boolean
'WHERE',

@@ -30,0 +31,0 @@ 'WITH',

@@ -50,2 +50,3 @@ // Central registry for messages.

'anno-undefined-param': { severity: 'Info' },
'anno-unstable-hdbcds': { severity: 'Warning' },

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

'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
'empty-type': { severity: 'Info' }, // only still an error in old transformers

@@ -74,3 +74,3 @@

'ref-sloppy-type': { severity: 'Error' },
'ref-invalid-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
'type-unexpected-typeof': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: make it non-config
'expected-actionparam-type': { severity: 'Error' },

@@ -95,2 +95,3 @@ 'ref-sloppy-actionparam-type': { severity: 'Error' },

'query-unexpected-structure-hdbcds': { severity: 'Error' },
'query-ignoring-param-nullability': { severity: 'Info' },

@@ -106,2 +107,3 @@ 'recalculated-localized': { severity: 'Info' }, // KEEP: Downgrade in lib/transform/translateAssocsToJoins.js

'ref-undefined-element': { severity: 'Error' },
'ref-unknown-var': { severity: 'Info' },
'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us

@@ -136,2 +138,4 @@ 'ref-undefined-param': { severity: 'Error' },

'def-missing-element': { severity: 'Error' },
'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars

@@ -187,2 +191,5 @@ 'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing

},
'ref-unknown-var': {
std: 'Replacement $(ID) not found'
},
'ref-rejected-on': {

@@ -193,8 +200,8 @@ std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used

},
'ref-invalid-typeof': {
std: 'Do not use $(KEYWORD) for the type reference here',
type: 'Do not use $(KEYWORD) for the type of a type',
event: 'Do not use $(KEYWORD) for the type of an event',
param: 'Do not use $(KEYWORD) for the type of a parameter',
select: 'Do not use $(KEYWORD) for type references in queries',
'type-unexpected-typeof': {
std: 'Unexpected $(KEYWORD) for the type reference here',
type: 'Unexpected $(KEYWORD) in type of a type definition',
event: 'Unexpected $(KEYWORD) for the type of an event',
param: 'Unexpected $(KEYWORD) for the type of a parameter definition',
select: 'Unexpected $(KEYWORD) for type references in queries',
},

@@ -218,2 +225,7 @@ 'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',

'def-missing-element': {
std: 'Expecting entity to have at least one non-virtual element',
view: 'Expecting view to have at least one non-virtual element'
},
'duplicate-definition': {

@@ -247,2 +259,6 @@ std: 'Duplicate definition of $(NAME)',

'query-unexpected-structure-hdbcds': 'Publishing a structured element in a view is not possible for “hdbcds” naming mode',
'query-ignoring-param-nullability': {
std: 'Ignoring nullability constraint on parameter when generating SAP HANA CDS view',
sql: 'Ignoring nullability constraint on parameter when generating SQL view'
},

@@ -249,0 +265,0 @@ 'ref-sloppy-type': 'A type or an element is expected here',

@@ -675,9 +675,12 @@ // Functions and classes for syntax messages

return function transformMany( many, r, args, texts ) {
const prop = ['none','one'][ many.length ];
if (!prop || !texts[prop] || args['#'] ) {
const names = many.map(t);
return (sorted ? names.sort() : names).join(', ');
}
const prop = ['none','one','two'][ many.length ];
const names = many.map(t);
if (sorted)
names.sort();
if (!prop || !texts[prop] || args['#'] )
return names.join(', ');
r['#'] = prop; // text variant
return many.length && t( many[0] );
if (many.length === 2)
r.second = names[1];
return many.length && names[0];
};

@@ -797,3 +800,3 @@ }

parts.push( text.substring( start ) );
let remain = ('#' in params) ? [] : Object.keys( params ).filter( n => params[n] );
let remain = (params['#']) ? [] : Object.keys( params ).filter( n => params[n] );
return (remain.length)

@@ -850,3 +853,6 @@ ? parts.join('') + '; ' +

const copy = {...msg};
copy.$location = undefined;
// Note: This is a hack. deduplicateMessages() would otherwise remove
// all but one message about duplicated artifacts.
if (!msg.messageId || !msg.messageId.includes('duplicate'))
copy.$location = undefined;
return messageString(copy);

@@ -1088,3 +1094,3 @@ }

const { name } = art;
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null ) &&
if ([ 'select', 'action', 'alias', 'param' ].every( n => name[n] == null || name[n] === 1 ) &&
!name.absolute.includes(':'))

@@ -1091,0 +1097,0 @@ return quote.name( name.element ? `${ name.absolute }:${ name.element }` : name.absolute );

@@ -487,3 +487,3 @@ 'use strict'

result.options[opt.camelName] = value;
if (opt.validValues && !opt.validValues.includes(value)) {
if (opt.validValues && !opt.validValues.some( validValue => validValue.toLowerCase() === value.toLowerCase() ) ) {
result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);

@@ -490,0 +490,0 @@ }

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

function validateEmptyOrOnlyVirtual(artifact, artifactName, prop, path) {
if (artifact.kind === 'entity' && !artifact.query && isPersistedOnDatabase(artifact)) {
if (artifact.kind === 'entity' && isPersistedOnDatabase(artifact)) {
if (!artifact.elements || !hasRealElements(artifact.elements))
this.error(null, path, "Artifacts containing only virtual or empty elements can't be deployed");
this.error('def-missing-element', path, { '#': artifact.query ? 'view' : 'std' });
}

@@ -20,0 +20,0 @@ }

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

getVariableReplacement(ref, this.options) === null)
this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
this.error('ref-missing-replacement', parent.$location, { elemref: parent }, 'Missing replacement for variable $(ELEMREF)');
}

@@ -38,0 +38,0 @@ }

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

'_projections', '_block', '_parent', '_main', '_effectiveType', '$expand',
'$tableAliases', 'kind', '_$next', '_combined', '$inlines',
'$tableAliases', 'kind', '_$next', '_combined', '$inlines', '_status',
],

@@ -546,3 +546,3 @@ },

'orderBy', '$orderBy', 'groupBy', 'excludingDict', 'having',
'limit',
'limit', '_status',
],

@@ -549,0 +549,0 @@ },

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

const { forEachInDict } = require('../base/dictionaries');
const { builtinLocation } = require('../base/location');

@@ -86,2 +85,3 @@ const { setProp } = require('./utils');

$user: {
// id and locale are always available
elements: { id: {}, locale: {} },

@@ -92,4 +92,4 @@ // Allow $user.<any>

$autoElement: 'id',
}, // CDS-specific, not part of SQL
$at: {
},
$at: { // CDS-specific, not part of SQL
elements: {

@@ -99,3 +99,3 @@ from: {}, to: {},

},
$now: {}, // Dito
$now: {}, // Dito
$session: {

@@ -206,2 +206,3 @@ // In ABAP CDS session variables are accessed in a generic way via

function initBuiltins( model ) {
const { options } = model;
setMagicVariables( magicVariables );

@@ -274,6 +275,5 @@ // namespace:"cds" stores the builtins ---

// TODO: rename to $builtinFunction
const art = { kind: 'builtin', name: { id: name, element: name } };
const art = { kind: 'builtin', name: { element: name, id: name } };
artifacts[name] = art;
if (magic.elements)
art.elements = forEachInDict( magic.elements, (e, n) => magicElement( e, n, art ));
if (magic.$autoElement)

@@ -283,2 +283,6 @@ art.$autoElement = magic.$autoElement;

art.$uncheckedElements = magic.$uncheckedElements;
createMagicElements( art, magic.elements );
if (options.variableReplacements)
createMagicElements( art, options.variableReplacements[name] );
// setProp( art, '_effectiveType', art );

@@ -289,10 +293,25 @@ }

function magicElement( spec, name, parent ) {
const magic = {
kind: 'builtin',
name: { id: name, element: `${ parent.name.element }.${ name }` },
};
setProp( magic, '_parent', parent );
// setProp( magic, '_effectiveType', magic );
return magic;
function createMagicElements( art, elements ) {
if (!elements)
return;
const names = Object.keys(elements);
if (names.length > 0 && !art.elements)
art.elements = Object.create(null);
for (const n of names) {
const magic = {
kind: 'builtin',
name: { id: n, element: `${ art.name.element }.${ n }` },
};
// Propagate this property so that it is available for sub-elements.
if (art.$uncheckedElements)
magic.$uncheckedElements = art.$uncheckedElements;
setProp( magic, '_parent', art );
// setProp( magic, '_effectiveType', magic );
if (elements[n] && typeof elements[n] === 'object')
createMagicElements(magic, elements[n]);
art.elements[n] = magic;
}
}

@@ -299,0 +318,0 @@ }

@@ -29,3 +29,2 @@ // Main XSN-based compiler functions

const { emptyWeakLocation } = require('../base/location');

@@ -148,32 +147,21 @@ const { createMessageFunctions, deduplicateMessages } = require('../base/messages');

// Read file `filename` and parse its content, return messages
function readAndParse( filename ) {
async function readAndParse( filename ) {
const { sources } = a;
if ( filename === false ) // module which has not been found
return [];
const rel = a.sources[filename] || path.relative( dir, filename );
const rel = sources[filename] || path.relative( dir, filename );
if (typeof rel === 'object') // already parsed
return []; // no further dependency processing
// no parallel readAndParse with same resolved filename should read the file,
// also ensure deterministic sequence in a.sources:
a.sources[filename] = { location: { file: rel } };
// also ensure deterministic sequence in sources:
sources[filename] = { location: { file: rel } };
return new Promise( (fulfill, reject) => {
cdsFs( fileCache, options.traceFs ).readFile( filename, 'utf8', (err, source) => {
if (err) {
reject(err);
}
else {
try {
const ast = parseX( source, rel, options, model.$messageFunctions );
a.sources[filename] = ast;
ast.location = { file: rel };
ast.dirname = path.dirname( filename );
assertConsistency( ast, options );
fulfill( ast );
}
catch (e) {
reject( e );
}
}
});
});
const source = await cdsFs( fileCache, options.traceFs ).readFileAsync( filename, 'utf8' );
const ast = parseX( source, rel, options, model.$messageFunctions );
sources[filename] = ast;
ast.location = { file: rel };
ast.dirname = path.dirname( filename );
assertConsistency( ast, options );
return ast;
}

@@ -180,0 +168,0 @@

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

const { setLink, dependsOn } = require('./utils');
const { setLink, dependsOn, pathName } = require('./utils');

@@ -615,25 +615,20 @@ function artifactsEnv( art ) {

const env = fn( art, item.location, user, spec.assoc );
const sub = setLink( item, env && env[item.id] );
// do not check any elements of the path, e.g. $session - but still don't return path-head
if (art && art.$uncheckedElements) {
if (env && env[item.id]) // something like $user.id/$user.locale
return env[item.id];
// $user.foo - build our own valid path step obj
// Important: Don't directly modify item!
const obj = {
location: item.location,
kind: 'builtin',
name: { id: item.id, element: path.map(p => p.id).join('.') },
};
setLink(obj, art, '_parent');
return obj;
if (!sub) {
// element was not found in environment
if (sub === 0)
return 0;
if (art.$uncheckedElements) { // magic variable / replacement variable
signalNotFound( 'ref-unknown-var', [ item.location, user ], [ env ],
{ id: path.map(n => n.id).join('.') } );
}
else {
errorNotFound( item, env );
}
return null;
}
const sub = setLink( item, env && env[item.id] );
if (!sub)
return (sub === 0) ? 0 : errorNotFound( item, env );
else if (Array.isArray(sub)) // redefinitions
else if (Array.isArray(sub)) { // redefinitions
return false;
}

@@ -720,3 +715,3 @@ if (nav) { // we have already "pseudo-followed" a managed association

else {
signalNotFound( 'ref-undefined-element', [ item.location, user ],
signalNotFound( spec.undefinedDef || 'ref-undefined-element', [ item.location, user ],
[ env ], { art: searchName( art, item.id, 'element' ) } );

@@ -820,3 +815,3 @@ }

if (construct !== art)
dictAddArray( art, annoProp, a );
addAnnotation( art, annoProp, a );
}

@@ -872,3 +867,3 @@ }

anno.$priority = priority;
dictAddArray( art, annoProp, anno );
addAnnotation( art, annoProp, anno );
}

@@ -878,6 +873,8 @@ }

// Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
// locations):
function pathName(path) {
return (path.broken) ? '' : path.map( id => id.id ).join('.');
// Add annotation to definition - overwriting $inferred annos
function addAnnotation( art, annoProp, anno ) {
const old = art[annoProp];
if (old && old.$inferred)
delete art[annoProp];
dictAddArray( art, annoProp, anno );
}

@@ -884,0 +881,0 @@

@@ -185,2 +185,12 @@ // Simple compiler utility functions

/**
* Return string 'A.B.C' for parsed source `A.B.C` (is vector of ids with
* locations).
*
* @param {XSN.Path} path
*/
function pathName(path) {
return (path.broken) ? '' : path.map( id => id.id ).join('.');
}
/**
* Generates an XSN path out of the given name. Path segments are delimited by a dot.

@@ -219,4 +229,5 @@ * Each segment will have the given location assigned.

withAssociation,
pathName,
augmentPath,
splitIntoPath,
};

@@ -40,2 +40,10 @@ // Transform XSN (augmented CSN) into CSN

// XSN $inferred values mapped to Universal CSN $generated values:
const inferredAsGenerated = {
autoexposed: 'exposed',
'localized-entity': 'localized',
localized: 'localized', // on elements (texts, localized)
'composition-entity': 'composed', // ('aspect-composition' on element not in CSN)
};
// IMPORTANT: the order of these properties determine the order of properties

@@ -70,3 +78,3 @@ // in the resulting CSN !!! Also check const `csnPropertyNames`.

srid: value,
cardinality: standard, // also for pathItem: after 'id', before 'where'
cardinality, // also in pathItem: after 'id', before 'where'
targetAspect,

@@ -614,6 +622,17 @@ target,

function targetAspect( val, csn, node ) {
if (universalCsn) {
if (val.$inferred)
return undefined;
if (node.target) {
csn.$origin = { type: 'cds.Composition' };
if (node.cardinality)
csn.$origin.cardinality = standard( node.cardinality );
csn.$origin.target = (val.elements) ? standard( val ) : artifactRef( val, true );
return undefined;
}
}
const ta = (val.elements)
? addLocation( val.location, standard( val ) )
: artifactRef( val, true );
if (!gensrcFlavor || node.target && !node.target.$inferred)
if (!gensrcFlavor && !universalCsn || node.target && !node.target.$inferred)
return ta;

@@ -668,8 +687,3 @@ // For compatibility, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'

function enumerableQueryElements( select ) {
if (!universalCsn || select === select._main._leadingQuery)
return false;
if (select.orderBy || select.$orderBy)
return true;
const alias = select._parent;
return alias.query && (alias.query._leadingQuery || alias.query) === select;
return (universalCsn && select !== select._main._leadingQuery);
}

@@ -753,3 +767,4 @@

if (xsn.kind && xsn.kind.charAt(0) !== '$' && xsn.kind !== 'select' &&
(!xsn.$inferred || !xsn._main)) { // TODO: also for 'select'
(!xsn.$inferred || !xsn._main) &&
xsn.$inferred !== 'REDIRECTED') { // TODO: also for 'select'
// Also include $location for elements in queries (if not via '*')

@@ -810,4 +825,6 @@ addLocation( xsn.name && xsn.name.location || loc, csn );

function foreignKeys( dict, csn, node ) {
if (universalCsn && !target( node.target, csn, node ))
return;
if (universalCsn) {
if (!target( node.target, csn, node ) || dict[$inferred] === 'keys')
return;
}
if (gensrcFlavor && node._origin && node._origin.$inferred === 'REDIRECTED')

@@ -845,4 +862,4 @@ dict = node._origin.foreignKeys;

}
if (kind && kind !== 'key')
addOrigin( c, art, art._origin );
// precondition already fulfilled: art.kind !== 'key'
addOrigin( c, art, art._origin );
return c;

@@ -989,2 +1006,4 @@ }

function kind( k, csn, node ) {
if (node.$inferred === 'REDIRECTED')
return;
if (k === 'annotate' || k === 'extend') {

@@ -998,2 +1017,5 @@ // We just use `name.absolute` because it is very likely a "constructed"

}
else if (k === 'action' && node._main && universalCsn && node.$inferred) {
// Universal CSN: do not mention kind: 'action' on expanded action
}
else if (![

@@ -1005,10 +1027,34 @@ 'element', 'key', 'param', 'enum', 'select', '$join',

}
const generated = universalCsn && inferredAsGenerated[node.$inferred];
if (typeof generated === 'string')
csn.$generated = generated;
}
function type( node, csn, xsn ) {
if (universalCsn && node.$inferred && xsn._origin)
if (!universalCsn)
return artifactRef( node, !node.$extra );
if (node.$inferred)
return undefined;
if (xsn._origin) {
if (xsn._origin.$inferred === 'REDIRECTED') { // auto-redirected user-provided target
csn.$origin = definition( xsn._origin );
return undefined;
}
}
else if ( xsn.targetAspect && xsn.target ) {
// type moved to $origin: { type: … }
return undefined;
}
return artifactRef( node, !node.$extra );
}
function cardinality( node, csn, xsn ) {
if (!universalCsn)
return standard( node );
// cardinality might be moved to $origin: { cardinality: … }
if (node.$inferred || xsn.targetAspect && !xsn.targetAspect.$inferred && xsn.target)
return undefined;
return standard( node );
}
function artifactRef( node, terse ) {

@@ -1015,0 +1061,0 @@ // When called as transformer function, a CSN node is provided as argument

@@ -117,5 +117,6 @@ // Error strategy with special handling for (non-reserved) keywords

}
// TODO: expected token is identifier, current is KEYWORD
if (nextTokens.contains(antlr4.Token.EPSILON)) {
// when exiting a (innermost) rule, remember the state to make
// getExpectedTokensForMessage() calculate the full "expected set"
if (recognizer.$nextTokensToken !== token) {

@@ -130,9 +131,25 @@ // console.log('SET:',token.type,recognizer.state,recognizer.$nextTokensToken && recognizer.$nextTokensToken.type)

// Expected token is identifier, current is (reserved) KEYWORD:
// TODO: do not use this if "close enough" (1 char diff) to a keyword in nextTokens
//
// NOTE: it is important to do this only if EPSILON is not in `nextTokens`,
// which means that we cannot bring the better special syntax-fragile-ident
// in all cases. Reason: high performance impact of the alternative,
// i.e. calling method Parser#isExpectedToken() = invoking the ATN
// interpreter to see behind EPSILON.
const identType = recognizer.constructor.Identifier;
if (keywordRegexp.test( token.text ) && nextTokens.contains( identType )) {
recognizer.message( 'syntax-fragile-ident', token, { id: token.text, delimited: token.text },
'$(ID) is a reserved name here - write $(DELIMITED) instead if you want to use it' );
token.type = identType; // make next ANTLR decision assume identifier
return;
}
if (recognizer._ctx._sync === 'nop')
return;
switch (s.stateType) {
case ATNState.BLOCK_START:
case ATNState.STAR_BLOCK_START:
case ATNState.PLUS_BLOCK_START:
case ATNState.STAR_LOOP_ENTRY:
case ATNState.BLOCK_START: // 3
case ATNState.STAR_BLOCK_START: // 5
case ATNState.PLUS_BLOCK_START: // 4
case ATNState.STAR_LOOP_ENTRY: // 10
// report error and recover if possible

@@ -150,4 +167,4 @@ if ( token.text !== '}' && // do not just delete a '}'

case ATNState.PLUS_LOOP_BACK:
case ATNState.STAR_LOOP_BACK: {
case ATNState.PLUS_LOOP_BACK: // 11
case ATNState.STAR_LOOP_BACK: { // 9
// TODO: do not delete a '}'

@@ -431,3 +448,4 @@ this.reportUnwantedToken(recognizer);

offendingToken === recognizer.$nextTokensToken) {
// We have a state (via sync()) with more "expecting" for the same token
// Before exiting a rule, we had a state (via sync()) with a bigger
// "expecting set" for the same token
ll1._LOOK( atn.states[recognizer.$nextTokensState], null,

@@ -434,0 +452,0 @@ predictionContext( atn, recognizer.$nextTokensContext ),

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

// Count the number of newlines in the token.
// TODO: I want to avoid a substring, that's why I don't use RegEx here
const source = token.source[1].data;

@@ -329,2 +328,4 @@ let newLineCount = 0;

for (let i = token.start; i < token.stop; i++) {
// Note: We do NOT check for CR, LS, and PS (/[\r\u2028\u2029]/)
// because ANTLR only uses LF for line break detection.
if (source[i] === 10) { // ASCII code for '\n'

@@ -331,0 +332,0 @@ newLineCount++;

@@ -161,2 +161,80 @@ // Official cds-compiler API.

/**
* Options used by SQL `to.sql()` backend.
*
* @see to.sql()
*/
export interface SqlOptions extends Options {
/**
* The SQL naming mode decides how names are represented.
* Among others, this includes whether identifiers are quoted or not (note
* that "smart quoting" is handled by `sqlDialect`).
*
* - `plain`:
* In this naming mode, dots are replaced by underscores.
* Names are neither upper-cased nor quoted, unless "smart-quoting" is used.
* This mode can be used with all SQL dialects.
* - `quoted`:
* In this mode, all identifiers are quoted. Dots are not replaced in table
* and view names but are still replaced by underscores in element names.
* This mode can only be used with SQL dialect `hana`.
* - `hdbcds`:
* This mode uses names that are compatible to SAP HANA CDS.
* In this mode, all identifiers are quoted. Dots are neither replaced in table
* nor element names. Namespace identifiers are separated from the remaining
* identifier by `::`, i.e. the dot is replaced. For example `Ns.Books`
* becomes `"Ns::Books"`.
* This mode can only be used with SQL dialect `hana`.
*
* @default 'plain'
*/
sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
/**
* Use this option to specify what dialect of SQL you want.
*
* Different databases may support different feature sets of SQL.
* For example, timestamps are handled differently. Furthermore, "smart-quoting"
* is enabled for `sqlite` and `hana`. This is useful if identifiers
* collide with reserved keywords.
*
* - `plain`:
* Use this option for best compatibility with standard SQL.
* Note that "smart-quoting" is not available for this mode.
* Requires `sqlMapping: 'plain'`.
* - `sqlite`:
* This SQL dialect ensures compatibility with SQLite, which may not support
* all SQL features used in your CDS files. For example, `$at.from`/`$at.to` are
* handled differently to ensure correctness for SQLite. "smart-quoting"
* quotes identifiers that are reserved keywords, but does not upper-case them.
* Requires `sqlMapping: 'plain'`.
* - `hana`:
* Use this SQL dialect for best compatibility with SAP HANA.
* "smart-quoting" upper-cases and quotes identifiers.
*
* @default 'plain'
*/
sqlDialect?: string | 'plain' | 'sqlite' | 'hana'
/**
* Object containing magic variables. These magic variables are
* used as placeholder values.
*
* @since 2.11.0
*/
variableReplacements?: {
[option: string]: string | object,
/**
* Commonly used placeholders for user's name and locale.
*/
$user?: {
[option: string]: string | object,
id?: string
locale?: string
},
/**
* Commonly used placeholders for session variables.
*/
$session?: Record<string, string | object>
}
}
/**
* The compiler's package version.

@@ -444,3 +522,3 @@ * For more details on versioning and SemVer, see `doc/Versioning.md`

function cdl(csn: CSN, options: Options): object;
function sql(csn: CSN, options: Options): any;
function sql(csn: CSN, options: SqlOptions): any;

@@ -447,0 +525,0 @@ function edm(csn: CSN, options: ODataOptions): any;

@@ -346,7 +346,14 @@ // CSN functionality for resolving references

// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
// console.log(art)
return (art.elements || art.enum || targetAspect( art ).elements)[elem];
}
function targetAspect( art ) {
const { $origin } = art;
return art.targetAspect ||
$origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
art.target;
}
// From the current CSN object, set implicit origin for the next navigation step
// Currently (TODO: ?) `elements` only, i.e. what is needed for name resolution.
function setImplicitOrigin( art, origin ) {

@@ -484,3 +491,3 @@ setMembersImplicit( art.actions, origin.actions );

// target
const target = csn.definitions[parent.target || parent.cast.target];
const target = csn.definitions[parent.target || parent.$origin && parent.$origin.target || parent.cast.target];
return resolvePath( path, target.elements[head], 'target' );

@@ -598,3 +605,3 @@ }

return { $aliases: Object.create(null) };
const pcache = cache.get( parentQuery );
const pcache = cache.get( parentQuery.projection || parentQuery );
if (!parentQuery.SET) // SELECT / projection: real sub query

@@ -601,0 +608,0 @@ return { $aliases: Object.create(null), $next: pcache };

'use strict';
const { setProp } = require('../base/model');
const { csnRefs } = require('../model/csnRefs');
const { applyTransformations, applyTransformationsOnNonDictionary } = require('../transform/db/applyTransformations');
const { isBuiltinType } = require('../compiler/builtins.js')

@@ -1056,108 +1056,2 @@ const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');

/**
* Loop through the model, applying the custom transformations on the node's matching.
*
* Each transformer gets:
* - the parent having the property
* - the name of the property
* - the value of the property
* - the path to the property
*
* @param {object} csn CSN to enrich in-place
* @param {object} customTransformers Map of prop to transform and function to apply
* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
* @param {Boolean} [skipIgnore=true] Wether to skip _ignore elements or not
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
* @returns {object} CSN with transformations applied
*/
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true, options = {} ) {
const transformers = {
elements: dictionary,
definitions: dictionary,
actions: dictionary,
params: dictionary,
enum: dictionary,
mixin: dictionary,
ref: pathRef,
//type: simpleRef,
//target: simpleRef,
//includes: simpleRef,
}
const csnPath = [];
if (csn.definitions)
definitions( csn, 'definitions', csn.definitions );
return csn;
function standard( parent, prop, node ) {
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')) || (skipIgnore && node._ignore))
return;
csnPath.push( prop );
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );
}
else {
for (let name of Object.getOwnPropertyNames( node )) {
const trans = transformers[name] || standard;
if(customTransformers[name])
customTransformers[name](node, name, node[name], csnPath, parent, prop);
trans( node, name, node[name], csnPath );
}
}
csnPath.pop();
}
function dictionary( node, prop, dict ) {
// Allow skipping dicts like actions in forHanaNew
if(options.skipDict && options.skipDict[prop])
return;
csnPath.push( prop );
for (let name of Object.getOwnPropertyNames( dict )) {
standard( dict, name, dict[name] );
}
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
setProp(node, '$' + prop, dict);
csnPath.pop();
}
function definitions( node, prop, dict ) {
csnPath.push( prop );
for (let name of Object.getOwnPropertyNames( dict )) {
const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
if(!skip) {
artifactTransformers.forEach(fn => fn(dict, name, dict[name]));
standard( dict, name, dict[name] );
}
}
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
setProp(node, '$' + prop, dict);
csnPath.pop();
}
//Keep looping through the pathRef
function pathRef( node, prop, path ) {
csnPath.push( prop );
path.forEach( function step( s, i ) {
if (s && typeof s === 'object') {
csnPath.push( i );
if(options.drillRef) {
standard(path, i, s);
} else {
if (s.args)
standard( s, 'args', s.args );
if (s.where)
standard( s, 'where', s.where );
}
csnPath.pop();
}
} );
csnPath.pop();
}
}
const _dependencies = Symbol('_dependencies');

@@ -1638,2 +1532,3 @@ const _dependents = Symbol('_dependents');

applyTransformations,
applyTransformationsOnNonDictionary,
setDependencies,

@@ -1640,0 +1535,0 @@ isPersistedOnDatabase,

@@ -17,4 +17,6 @@ // For testing: reveal non-enumerable properties in CSN, display result of csnRefs

// * `$env` for the non-enumerable `$env` property in the original CSN.
// * `$elements` for a non-enumerable `elements` property for sub queries.
// * `$parens`: the number of parentheses provided by the user around an expression
// or query if the number is different to the usual (mostly 0, sometimes 1).
// * `$elements` (in client-style CSN only) for a non-enumerable `elements` property
// for sub queries.

@@ -24,6 +26,10 @@ // The following properties in the JSON represent the result of the CSN API

// * `_type`, `_includes` and `_targets` have as values the `$locations` of the
// * `_type`, `_includes` and `_targets` have as values the `$location`s of the
// referred artifacts which are returned by function `artifactRef`.
// * `_links`, `_art` and `_scope` as sibling properties of `ref` have as values
// the `$locations` of the artifacts/members returned by function `inspectRef`.
// * `_links` and `_art` as sibling properties of `ref` have as values the
// `$locations` of the artifacts/members returned by function `inspectRef`.
// * `_scope` and `_env` as sibling properties of `ref` have (string) values,
// returned by function `inspectRef`, giving add/ info about the “ref base”.
// * `_origin` (in Universal CSN only) has as value the `$location` of the
// prototype returned by function getOrigin().

@@ -37,3 +43,2 @@ 'use strict';

const transformers = {
// $env: reveal,
elements: dictionary,

@@ -90,3 +95,4 @@ definitions: dictionary,

function definition( parent, prop, obj ) {
const origin = getOrigin( obj ); // before standard for implicit protos inside
// call getOrigin() before standard() to set implicit protos inside standard():
const origin = handleError( err => err ? err.toString() : getOrigin( obj ) );
standard( parent, prop, obj );

@@ -110,6 +116,6 @@ if (obj.$origin === undefined && origin != null)

function refLocation( art ) {
if (art)
if (art && typeof art === 'object')
return art.$location || '<no location>';
if (!options.testMode)
return '<illegal link>';
return art || '<illegal link>';
throw new Error( 'Undefined reference' );

@@ -141,33 +147,15 @@ }

function $origin( parent, prop, ref ) {
if (options.testMode) {
if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
handleError( err => {
if (err)
parent._origin = err.toString();
else if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent, true ) );
else if ( ref )
standard( parent, prop, ref )
}
else {
try {
if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent, true ) );
else if ( ref )
standard( parent, prop, ref )
} catch (e) {
parent._origin = e.toString();
}
}
standard( parent, prop, ref );
} );
}
function pathRef( parent, prop, path ) {
const { links, art, scope, $env } = (() => {
if (options.testMode)
return inspectRef( csnPath );
else {
try {
return inspectRef( csnPath );
}
catch (e) {
return { scope: e.toString() };
}
}
} )();
const { links, art, scope, $env }
= handleError( err => (err) ? { scope: err.toString() } : inspectRef( csnPath ) );
if (links)

@@ -195,2 +183,12 @@ parent._links = links.map( l => refLocation( l.art ) );

function handleError( callback ) {
if (options.testMode)
return callback();
try {
return callback();
} catch (err) {
return callback( err );
}
}
function _cache_debug( obj, subCache ) {

@@ -197,0 +195,0 @@ if (options.enrichCsn !== 'DEBUG')

@@ -274,8 +274,9 @@ // Make internal properties of the XSN / augmented CSN visible

if (node._outer) {
outer = (node._outer.items === node) ? '/items'
: (node._outer.returns === node) ? '/returns' : '/returns/items';
if (node.$inferred === 'REDIRECTED')
outer = '/redirected';
else
outer = (node._outer.items === node) ? '/items'
: (node._outer.returns === node) ? '/returns' : '/returns/items';
node = node._outer;
}
else if (node.$inferred === 'REDIRECTED')
outer = '/redirected';
if (node === parent)

@@ -282,0 +283,0 @@ return 'this';

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

* For ordering, only the FROM clause of views is checked - this requires A2J to
* be run beforehand to resovle association usages.
* be run beforehand to resolve association usages.
*

@@ -100,3 +100,10 @@ * @param {object} sql Map of <object name>: "CREATE STATEMENT"

layers.forEach(layer => layer.forEach(objName => result.push({name: objName, sql: sql[objName]})));
// attach sql artifacts which are not considered during the view sorting algorithm
// --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements,
// because their identifiers are not part of the csn.definitions
Object.entries(sql).forEach(([ name, sqlString ]) => {
if (!result.some( o => o.name === name )) // not in result but in incoming sql
result.push({ name, sql: sqlString })
});
return result;
}

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

.option(' --assert-integrity-type <type>', [ 'RT', 'DB' ])
.option(' --constraints-as-alter <boolean>')
.option(' --deprecated <list>')

@@ -114,3 +115,6 @@ .option(' --hana-flavor')

if not explicitly demanded via annotation
DB : Create database constraints for associations
DB : Create database constraints for associations
--constraints-as-alter <boolean> If set to 'true', the foreign key constraints will be rendered as
"ALTER TABLE ADD CONSTRAINT" statement rather than being part of the
"CREATE TABLE" statement
--deprecated <list> Comma separated list of deprecated options.

@@ -117,0 +121,0 @@ Valid values are:

@@ -16,4 +16,3 @@ {

"max-len": "off",
// We should enable this
"no-shadow": "off"
"no-shadow": "warn"
},

@@ -20,0 +19,0 @@ "env": {

@@ -22,2 +22,3 @@

const { sortCsn } = require('../json/to-csn');
const { manageConstraints } = require('./manageConstraints');

@@ -225,3 +226,2 @@

// Render each artifact extension

@@ -278,3 +278,2 @@ // Only HANA SQL is currently supported.

sourceString = sourceString.slice('COLUMN '.length);
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;

@@ -290,2 +289,11 @@ }

// add `ALTER TABLE ADD CONSTRAINT` statements if requested
if (options.sqlDialect !== 'sqlite' && options.constraintsAsAlter) {
const alterStmts = manageConstraints(csn, options);
for ( const constraintName of Object.keys(alterStmts))
sql[constraintName] = `${options.testMode ? '' : sqlVersionLine}${alterStmts[constraintName]}`;
resultObj.sql = sql;
}
if (options.toSql.src === 'sql')

@@ -507,9 +515,5 @@ resultObj.sql = sql;

const elements = Object.keys(art.elements).map(eltName => renderElement(artifactName, eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv)).filter(s => s !== '').join(',\n');
if (elements !== '') {
if (elements !== '')
result += elements;
}
else {
// TODO: Already be handled by 'empty-entity' reclassification; better location
error(null, [ 'definitions', artifactName ], 'Entities must have at least one element that is non-virtual');
}
const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)

@@ -525,3 +529,4 @@ .map(name => quoteSqlId(name))

if (art.$tableConstraints && art.$tableConstraints.referential) {
const constraintsAsAlter = options.constraintsAsAlter && options.sqlDialect !== 'sqlite';
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';

@@ -1096,3 +1101,3 @@ const referentialConstraints = {};

if (p.notNull === true || p.notNull === false)
info(null, [ 'definitions', artifactName, 'params', pn ], 'Not Null constraints on SQL view parameters are not allowed and are ignored');
info('query-ignoring-param-nullability', [ 'definitions', artifactName, 'params', pn ], { '#': 'sql' });
// do not quote parameter identifiers for naming mode "quoted" / "hdbcds"

@@ -1510,3 +1515,3 @@ // this would be an incompatible change, as non-uppercased, quoted identifiers

if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
warning(null, null, 'The "$user" variable is not supported. Use the "toSql.user" option to set a value for "$user.id"');
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
return '\'$user.id\'';

@@ -1513,0 +1518,0 @@ }

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

function removeLeadingSelf(csn) {
const magicVars = [ '$now' ];
const magicVars = [ '$now', '$self', '$projection', '$user', '$session', '$at' ];
forEachDefinition(csn, (artifact, artifactName) => {

@@ -20,0 +20,0 @@ if (artifact.kind === 'entity' || artifact.kind === 'view') {

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

forEachDefinition(csn, (artifact, artifactName) => {
if (artifact.projection) // do the same hack we do for the other stuff...
artifact.query = { SELECT: artifact.projection };
if (artifact.query) {

@@ -87,2 +90,8 @@ forAllQueries(artifact.query, (query, path) => {

}
if (artifact.projection) { // undo our hack
artifact.projection = artifact.query.SELECT;
delete artifact.query;
}
});

@@ -89,0 +98,0 @@

'use strict';
const { usesMixinAssociation, getMixinAssocOfQueryIfPublished } = require('./helpers');
const { getUtils, cloneCsn } = require('../../model/csnUtils');
const {
getUtils, cloneCsn, applyTransformationsOnNonDictionary,
} = require('../../model/csnUtils');
const { implicitAs, csnRefs } = require('../../model/csnRefs');
const { isBetaEnabled } = require('../../base/model');
/**
* If a mixin association is published, return the mixin association.
*
* @param {CSN.Query} query Query of the artifact to check
* @param {object} association Association (Element) published by the view
* @param {string} associationName
* @returns {object} The mixin association
*/
function getMixinAssocOfQueryIfPublished(query, association, associationName) {
if (query && query.SELECT && 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 elem of Object.keys(query.SELECT.mixin)) {
const mixinElement = query.SELECT.mixin[elem];
let originalName = associationName;
if (aliasedColumnsMap[associationName])
originalName = aliasedColumnsMap[associationName].ref[0];
if (elem === originalName)
return { mixinElement, mixinName: originalName };
}
}
return {};
}
/**
* Check wether the given artifact uses the given mixin association.
*
* We can rely on the fact that there can be no usage starting with $self/$projection,
* since lib/checks/selectItems.js forbids that.
*
* @param {CSN.Query} query Query of the artifact to check
* @param {object} association Mixin association (Element) to check for
* @param {string} associationName
* @returns {boolean} True if used
*/
function usesMixinAssociation(query, association, associationName) {
if (query && query.SELECT && query.SELECT.columns) {
for (const column of query.SELECT.columns) {
if (typeof column === 'object' && column.ref && column.ref.length > 1 && (column.ref[0] === associationName || column.ref[0].id === associationName))
return true;
}
}
return false;
}
/**
* @param {CSN.Model} csn

@@ -18,3 +70,3 @@ * @param {CSN.Options} options

const {
get$combined, cloneWithTransformations, isAssocOrComposition,
get$combined, isAssocOrComposition,
} = getUtils(csn);

@@ -185,3 +237,2 @@ const { inspectRef, queryOrMain } = csnRefs(csn);

* @todo Factor out the checks
* @todo Union, Join, Subqueries? Assoc usage in there? __clone?
* @param {CSN.Query} query

@@ -193,11 +244,12 @@ * @param {object} elements

* @param {string} elemName
* @param {CSN.Path} path
* @param {CSN.Path} elementsPath Path pointing to elements
* @param {CSN.Path} queryPath Path pointing to the query
*/
function handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, path) {
if (isUnion(path) && options.transformation === 'hdbcds') {
function handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath) {
if (isUnion(queryPath) && options.transformation === 'hdbcds') {
if (isBetaEnabled(options, 'ignoreAssocPublishingInUnion') && doA2J) {
if (elem.keys)
info(null, path, `Managed association "${elemName}", published in a UNION, will be ignored`);
info(null, queryPath, `Managed association "${elemName}", published in a UNION, will be ignored`);
else
info(null, path, `Association "${elemName}", published in a UNION, will be ignored`);
info(null, queryPath, `Association "${elemName}", published in a UNION, will be ignored`);

@@ -207,17 +259,14 @@ elem._ignore = true;

else {
error(null, path, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
error(null, queryPath, `Association "${elemName}" can't be published in a SAP HANA CDS UNION`);
}
}
else if (path.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
error(null, path, { name: elemName },
else if (queryPath.length > 4 && options.transformation === 'hdbcds') { // path.length > 4 -> is a subquery
error(null, queryPath, { name: elemName },
'Association $(NAME) can\'t be published in a subquery');
}
else {
/* Old implementation:
const isNotMixinByItself = !(elem.value && elem.value.path && elem.value.path.length == 1 && art.query && art.query.mixin && art.query.mixin[elem.value.path[0].id]);
*/
const isNotMixinByItself = checkIsNotMixinByItself(query, columnMap, elemName);
const { mixinElement, mixinName } = getMixinAssocOfQueryIfPublished(query, elem, elemName);
if (isNotMixinByItself || mixinElement !== undefined) {
// If the mixin is only published and not used, only display the __ clone. Kill the "original".
// If the mixin is only published and not used, only display the __ clone. Kill the "original".
if (mixinElement !== undefined && !usesMixinAssociation(query, elem, elemName))

@@ -233,4 +282,4 @@ delete query.SELECT.mixin[mixinName];

// Copy the association element to the MIXIN clause under its alias name
// (shallow copy is sufficient, just fix name and value)
const mixinElem = Object.assign({}, elem);
// Needs to be a deep copy, as we transform the on-condition
const mixinElem = cloneCsn(elem, options);
// Perform common transformations on the newly generated MIXIN element (won't be reached otherwise)

@@ -246,26 +295,20 @@ transformCommon(mixinElem, mixinElemName);

if (mixinElem.on) {
mixinElem.on = cloneWithTransformations(mixinElem.on, {
ref: (ref) => {
// Clone the path, without any transformations
const clonedPath = cloneWithTransformations(ref, {});
// Prepend '$projection' to the path, unless the first path step is the (mixin) element itself or starts with '$')
if (clonedPath[0] === elemName) {
clonedPath[0] = mixinElemName;
mixinElem.on = applyTransformationsOnNonDictionary(mixinElem, 'on', {
ref: (parent, prop, ref, refpath) => {
if (ref[0] === elemName) {
ref[0] = mixinElemName;
}
else if (!(clonedPath[0] && clonedPath[0].startsWith('$'))) {
const projectionId = '$projection';
clonedPath.unshift(projectionId);
else if (!(ref[0] && ref[0].startsWith('$'))) {
ref.unshift('$projection');
}
return clonedPath;
else if (ref[0] && ref[0].startsWith('$')) {
// TODO: I think this is non-sense. Stuff with $ is either magic or must start with $self, right?
const { scope } = inspectRef(refpath);
if (scope !== '$magic' && scope !== '$self')
ref.unshift('$projection');
}
parent.ref = ref;
return ref;
},
func: (func) => {
// Unfortunately, function names are disguised as paths, so we would prepend a '$projection'
// above (no way to distinguish that in the callback for 'path' above). We can only pluck it
// off again here ... sigh
if (func.ref && func.ref[0] && func.ref[0] === '$projection')
func.ref = func.ref.slice(1);
return func;
},
});
}, elementsPath.concat(elemName));
}

@@ -313,2 +356,6 @@

const { elements } = queryOrMain(query, artifact);
// We use the elements from the leading query/main artifact - adapt the path
const elementsPath = elements === artifact.elements ? path.slice(0, 2).concat('elements') : path.concat('elements');
const queryPath = path;
let hasNonAssocElements = false;

@@ -348,8 +395,8 @@ const isSelect = query && query.SELECT;

if (query !== undefined && elem.target)
handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, path);
handleAssociationElement(query, elements, columnMap, publishedMixins, elem, elemName, elementsPath, queryPath);
}
if (query && !hasNonAssocElements) {
// Complain if there are no elements other than unmanaged associations
// Allow with plain
// Complain if there are no elements other than unmanaged associations
// Allow with plain
error(null, [ 'definitions', artName ], { $reviewed: true },

@@ -356,0 +403,0 @@ 'Expecting view or projection to have at least one element that is not an unmanaged association');

@@ -6,2 +6,3 @@ // Util functions for operations usually used with files.

const fs = require('fs');
const util = require('util');

@@ -23,4 +24,4 @@ /**

*
* Note: The synchronous versions accept a callback as well, which is executed
* immediately! This is different from NodeJS's readFileSync()!
* Note: The synchronous versions accept a callback instead of being async (duh!), which
* is executed immediately! This is different from NodeJS's readFileSync()!
* This is done to allow using it in places where fs.readFile (async) is used.

@@ -52,6 +53,9 @@ *

return {
readFileAsync: util.promisify(readFile),
readFile,
readFileSync,
isFileAsync: util.promisify(isFile),
isFile,
isFileSync,
realpathAsync: util.promisify(realpath),
realpath,

@@ -58,0 +62,0 @@ realpathSync,

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

@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/",

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