Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
1
Maintainers
1
Versions
101
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.4.4 to 2.5.0

bin/.eslintrc.json

15

bin/cds_update_identifiers.js

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

//
'use strict';

@@ -34,3 +35,3 @@

if (cliArgs.length != 1)
if (cliArgs.length !== 1)
exitError(`Expected exactly one argument, ${cliArgs.length} given`);

@@ -63,3 +64,3 @@

const errors = options.messages
.filter(msg => (msg.severity === 'Error' && msg.messageId !== 'syntax-deprecated-ident'));
.filter(msg => (msg.severity === 'Error' && msg.messageId !== 'syntax-deprecated-ident'));
if (errors.length > 0) {

@@ -75,3 +76,3 @@ errors.forEach((msg) => {

const tokens = ast.tokenStream.tokens;
const { tokens } = ast.tokenStream;
for (const token of tokens) {

@@ -90,6 +91,6 @@ if (token.type === ast.tokenStream.Identifier && token.text.startsWith('"'))

if (!identToken.stop)
throw new Error(`INTERNAL ERROR: Identifier at ${ identToken.start } has no end!`);
throw new Error(`INTERNAL ERROR: Identifier at ${identToken.start} has no end!`);
const start = identToken.start + currentOffset;
const end = identToken.stop + currentOffset + 1; // end points at the position *before* the character
const end = identToken.stop + currentOffset + 1; // end points at the position *before* the character

@@ -108,3 +109,3 @@ source = replaceSliceInSource(source, start, end, newIdentText);

return `![${ ident }]`;
return `![${ident}]`;
}

@@ -138,4 +139,4 @@ }

function usage() {
console.error('')
console.error('');
console.error(`usage: ${path.basename(process.argv[1])} <filename>`);
}

@@ -20,11 +20,12 @@ #!/usr/bin/env node

const main = require('../lib/main');
const { for_sql, for_hdi, for_hdbcds } = require('../lib/api/main');
const { compactModel } = require('../lib/json/to-csn');
const { toOdataWithCsn, toHanaWithCsn, toSqlWithCsn, toCdlWithCsn, toRenameWithCsn, alterConstraintsWithCsn } = require('../lib/backends');
var util = require('util');
var fs = require('fs');
var path = require('path');
var { reveal } = require('../lib/model/revealInternalProperties');
const { toRenameWithCsn, alterConstraintsWithCsn } = require('../lib/backends');
const util = require('util');
const fs = require('fs');
const path = require('path');
const { reveal } = require('../lib/model/revealInternalProperties');
const enrichCsn = require('../lib/model/enrichCsn');
const { optionProcessor } = require('../lib/optionProcessor');
const { explainMessage, hasMessageExplanation, sortMessages } = require('../lib/base/messages')
const { explainMessage, hasMessageExplanation, sortMessages } = require('../lib/base/messages');
const term = require('../lib/utils/term');

@@ -39,3 +40,3 @@ const { splitLines } = require('../lib/utils/file');

class ProcessExitError extends Error {
constructor(exitCode,...args) {
constructor(exitCode, ...args) {
super(...args);

@@ -46,8 +47,40 @@ this.exitCode = exitCode;

function remapCmdOptions(options, cmdOptions) {
if (!cmdOptions)
return options;
for (const [ key, value ] of Object.entries(cmdOptions)) {
switch (key) {
case 'names':
options.sqlMapping = value;
break;
case 'user':
if (!options.magicVars)
options.magicVars = {};
options.magicVars.user = value;
break;
case 'dialect':
options.sqlDialect = value;
break;
case 'version':
options.odataVersion = value;
break;
case 'locale':
if (!options.magicVars)
options.magicVars = {};
options.magicVars.locale = value;
break;
default:
options[key] = value;
}
}
return options;
}
// Parse the command line and translate it into options
try {
let cmdLine = optionProcessor.processCmdLine(process.argv);
const cmdLine = optionProcessor.processCmdLine(process.argv);
// Deal with '--version' explicitly
if (cmdLine.options.version) {
process.stdout.write(main.version() + '\n');
process.stdout.write(`${main.version()}\n`);
throw new ProcessExitError(0);

@@ -58,6 +91,6 @@ }

// Command specific help
if (cmdLine.options.help || cmdLine.options[cmdLine.command] && cmdLine.options[cmdLine.command].help) {
if (cmdLine.options.help || cmdLine.options[cmdLine.command] && cmdLine.options[cmdLine.command].help)
displayUsage(null, optionProcessor.commands[cmdLine.command].helpText, 0);
}
} else if (cmdLine.options.help) {
}
else if (cmdLine.options.help) {
// General help

@@ -77,3 +110,4 @@ displayUsage(null, optionProcessor.helpText, 0);

displayUsage(cmdLine.cmdErrors, optionProcessor.commands[cmdLine.command].helpText, 2);
} else if (cmdLine.errors.length > 0) {
}
else if (cmdLine.errors.length > 0) {
// General errors

@@ -85,9 +119,9 @@ displayUsage(cmdLine.errors, optionProcessor.helpText, 2);

// FIXME: Is that not set anywhere in the API?
if (!cmdLine.options.warning) {
if (!cmdLine.options.warning)
cmdLine.options.warning = 2;
}
// Default output goes to stdout
if (!cmdLine.options.out) {
if (!cmdLine.options.out)
cmdLine.options.out = '-';
}
// --cds-home <dir>: modules starting with '@sap/cds/' are searched in <dir>

@@ -114,35 +148,39 @@ if (cmdLine.options.cdsHome) {

const err = `'parseCdl' expects exactly one file! ${cmdLine.args.files.length} provided.`;
displayUsage(err, optionProcessor.commands['parseCdl'].helpText, 2);
displayUsage(err, optionProcessor.commands.parseCdl.helpText, 2);
}
}
if (cmdLine.options.directBackend) {
if (cmdLine.options.directBackend)
validateDirectBackendOption(cmdLine.command, cmdLine.options, cmdLine.args);
}
if (cmdLine.options.beta) {
const features = cmdLine.options.beta.split(',');
cmdLine.options.beta = {};
features.forEach((val) => cmdLine.options.beta[val] = true);
features.forEach((val) => {
cmdLine.options.beta[val] = true;
});
}
// Enable all beta-flags if betaMode is set to true
if(cmdLine.options.betaMode) {
if (cmdLine.options.betaMode)
cmdLine.options.beta = availableBetaFlags;
}
if (cmdLine.options.deprecated) {
const features = cmdLine.options.deprecated.split(',');
cmdLine.options.deprecated = {};
features.forEach((val) => cmdLine.options.deprecated[val] = true);
features.forEach((val) => {
cmdLine.options.deprecated[val] = true;
});
}
// Do the work for the selected command
executeCommandLine(cmdLine.command, cmdLine.options, cmdLine.args);
} catch (err) {
}
catch (err) {
// This whole try/catch is only here because process.exit does not work in combination with
// stdout/err - see comment at ProcessExitError
if (err instanceof ProcessExitError) {
if (err instanceof ProcessExitError)
process.exitCode = err.exitCode;
} else {
else
throw err;
}
}

@@ -160,14 +198,14 @@

function validateDirectBackendOption(command, options, args) {
if (!['toCdl', 'toOdata', 'toHana', 'toCsn', 'toSql'].includes(command)) {
if (![ 'toCdl', 'toOdata', 'toHana', 'toCsn', 'toSql' ].includes(command)) {
displayUsage(`Option '--direct-backend' can't be used with command '${command}'`,
optionProcessor.helpText, 2);
optionProcessor.helpText, 2);
}
if (!args.files || args.files.length !== 1) {
displayUsage(`Option '--direct-backend' expects exactly one JSON file, but ${args.files.length} given`,
optionProcessor.helpText, 2);
optionProcessor.helpText, 2);
}
const filename = args.files[0];
if (!filename.endsWith('.csn') && !filename.endsWith('.json')) {
displayUsage(`Option '--direct-backend' expects a filename with a *.csn or *.json suffix`,
optionProcessor.helpText, 2);
displayUsage('Option \'--direct-backend\' expects a filename with a *.csn or *.json suffix',
optionProcessor.helpText, 2);
}

@@ -179,11 +217,10 @@ }

// Display non-error output (like help) to stdout
let out = (code === 0 && !error) ? process.stdout : process.stderr;
const out = (code === 0 && !error) ? process.stdout : process.stderr;
// Display help text first, error at the end (more readable, no scrolling)
out.write(`${helpText}\n`);
if (error) {
if (error instanceof Array) {
out.write(error.map(error => `cdsc: ERROR: ${error}`).join('\n') + '\n');
} else {
if (error instanceof Array)
out.write(`${error.map(error => `cdsc: ERROR: ${error}`).join('\n')}\n`);
else
out.write(`cdsc: ERROR: ${error}\n`);
}
}

@@ -196,3 +233,5 @@ throw new ProcessExitError(code);

const normalizeFilename = options.testMode && process.platform === 'win32';
const messageLevels = { Error: 0, Warning: 1, Info: 2, None: 3 };
const messageLevels = {
Error: 0, Warning: 1, Info: 2, None: 3,
};
// All messages are put into the message array, even those which should not

@@ -202,6 +241,6 @@ // been displayed (severity 'None')

// Create output directory if necessary
if (options.out && options.out !== '-' && !fs.existsSync(options.out)) {
if (options.out && options.out !== '-' && !fs.existsSync(options.out))
fs.mkdirSync(options.out);
}
// Add implementation functions corresponding to commands here

@@ -221,6 +260,6 @@ const commands = {

if (!commands[command] && !commandsWithoutCompilation[command]) {
if (!commands[command] && !commandsWithoutCompilation[command])
throw new Error(`Missing implementation for command ${command}`);
}
if (commandsWithoutCompilation[command]) {

@@ -233,10 +272,10 @@ commandsWithoutCompilation[command]();

const fileCache = Object.create(null)
const compiled = options.directBackend ?
util.promisify(fs.readFile)( args.files[0], 'utf-8' ).then((str) => JSON.parse( str )) :
compiler.compileX( args.files, undefined, options, fileCache );
const fileCache = Object.create(null);
const compiled = options.directBackend
? util.promisify(fs.readFile)( args.files[0], 'utf-8' ).then(str => JSON.parse( str ))
: compiler.compileX( args.files, undefined, options, fileCache );
compiled.then( commands[command] )
.then( displayMessages, displayErrors )
.catch( catchErrors );
.then( displayMessages, displayErrors )
.catch( catchErrors );

@@ -249,6 +288,6 @@ return; // below are only command implementations.

const csn = options.directBackend ? model : compactModel(model, options);
const cdlResult = toCdlWithCsn(csn, options).result;
for (const name in cdlResult) {
writeToFileOrDisplay(options.out, name + '.cds', cdlResult[name]);
}
const cdlResult = main.to.cdl(csn, remapCmdOptions(options));
for (const name in cdlResult)
writeToFileOrDisplay(options.out, `${name}.cds`, cdlResult[name]);
return model;

@@ -262,3 +301,4 @@ }

displayNamedCsn(model, 'csn', options);
} else {
}
else {
// Result already provided by caller

@@ -274,9 +314,12 @@ displayNamedXsn(model, 'csn', options);

const csn = options.directBackend ? model : compactModel(model, options);
const hanaResult = toHanaWithCsn(csn, options);
for( const pluginName of ['hdbcds', 'hdbconstraint']){
for(const name in hanaResult[pluginName]){
writeToFileOrDisplay(options.out, `${name}.${pluginName}`, hanaResult[pluginName][name]);
}
if (options.toHana && options.toHana.csn) {
displayNamedCsn(for_hdbcds(csn, remapCmdOptions(options, options.toHana)), 'hana_csn', options);
}
displayNamedCsn(hanaResult.csn, 'hana_csn', options);
else {
const hanaResult = main.to.hdbcds(csn, remapCmdOptions(options, options.toHana));
for (const name in hanaResult)
writeToFileOrDisplay(options.out, name, hanaResult[name]);
}
return model;

@@ -288,3 +331,3 @@ }

function toOdata( model ) {
if(options.toOdata &&
if (options.toOdata &&
options.toOdata.version === 'v4x') {

@@ -295,23 +338,17 @@ options.toOdata.version = 'v4';

}
let csn = options.directBackend ? model : compactModel(model, options);
const odataResult = toOdataWithCsn(csn, options);
for (let serviceName in odataResult.services) {
// <service>_metadata.xml (metadata)
if (odataResult.services[serviceName].metadata) {
writeToFileOrDisplay(options.out, serviceName + '_metadata.xml', odataResult.services[serviceName].metadata);
}
// <service>_annotations.xml (annotations)
if (odataResult.services[serviceName].annotations) {
writeToFileOrDisplay(options.out, serviceName + '_annotations.xml', odataResult.services[serviceName].annotations);
}
// <service>.xml (combined)
if (odataResult.services[serviceName].combined) {
writeToFileOrDisplay(options.out, serviceName + '.xml', odataResult.services[serviceName].combined);
}
// <service>.json (metadata_json)
if (odataResult.services[serviceName].metadata_json) {
writeToFileOrDisplay(options.out, serviceName + '.json', odataResult.services[serviceName].metadata_json);
}
const csn = options.directBackend ? model : compactModel(model, options);
const odataCsn = main.for.odata(csn, remapCmdOptions(options, options.toOdata));
if (options.toOdata && options.toOdata.csn) {
displayNamedCsn(odataCsn, 'odata_csn', options);
}
displayNamedCsn(odataResult.csn, 'odata_csn', options);
else if (options.toOdata && options.toOdata.json) {
const result = main.to.edm.all(odataCsn, options);
for (const serviceName in result)
writeToFileOrDisplay(options.out, `${serviceName}.json`, result[serviceName]);
}
else {
const result = main.to.edmx.all(odataCsn, options);
for (const serviceName in result)
writeToFileOrDisplay(options.out, `${serviceName}.xml`, result[serviceName]);
}
return model;

@@ -323,18 +360,18 @@ }

//
/// THIS MUST SURVIVE IF WE REMOVE THE OLD API
/// DO NOT DELETE THIS TORENAME FUNCTIONALITY!!
// / THIS MUST SURVIVE IF WE REMOVE THE OLD API
// / DO NOT DELETE THIS TORENAME FUNCTIONALITY!!
function toRename( model ) {
let csn = options.directBackend ? model : compactModel(model, options);
let renameResult = toRenameWithCsn(csn, options);
let storedProcedure = 'PROCEDURE RENAME_' + renameResult.options.toRename.names.toUpperCase() + '_TO_PLAIN LANGUAGE SQLSCRIPT AS BEGIN\n';
for (let name in renameResult.rename) {
storedProcedure += ' --\n -- ' + name + '\n --\n';
const csn = options.directBackend ? model : compactModel(model, options);
const renameResult = toRenameWithCsn(csn, options);
let storedProcedure = `PROCEDURE RENAME_${renameResult.options.toRename.names.toUpperCase()}_TO_PLAIN LANGUAGE SQLSCRIPT AS BEGIN\n`;
for (const name in renameResult.rename) {
storedProcedure += ` --\n -- ${name}\n --\n`;
storedProcedure += renameResult.rename[name];
}
storedProcedure += "END;\n";
writeToFileOrDisplay(options.out, 'storedProcedure_' + renameResult.options.toRename.names + '_to_plain.sql', storedProcedure, true);
storedProcedure += 'END;\n';
writeToFileOrDisplay(options.out, `storedProcedure_${renameResult.options.toRename.names}_to_plain.sql`, storedProcedure, true);
return model;
}
// Execute the command line option 'manageConstraints' and display the results.
// Execute the command line option 'manageConstraints' and display the results.
function manageConstraints( model ) {

@@ -346,7 +383,7 @@ const csn = options.directBackend ? model : compactModel(model, options);

const renderedConstraintStatement = alterConstraintsResult[id];
if(src === 'hdi')
if (src === 'hdi')
writeToFileOrDisplay(options.out, `${id}.hdbconstraint`, renderedConstraintStatement);
else
writeToFileOrDisplay(options.out, `${id}.sql`, renderedConstraintStatement);
})
});
}

@@ -358,10 +395,19 @@

const csn = options.directBackend ? model : compactModel(model, options);
const sqlResult = toSqlWithCsn(csn, options);
['hdbtabletype', 'hdbtable', 'hdbindex', 'hdbfulltextindex', 'hdbview', 'hdbconstraint', 'sql'].forEach(pluginName => {
for(let name in sqlResult[pluginName]) {
writeToFileOrDisplay(options.out, name + '.' + pluginName, sqlResult[pluginName][name] + '\n', true);
if (options.toSql && options.toSql.src === 'hdi') {
if (options.toSql.csn) {
displayNamedCsn(for_hdi(csn, remapCmdOptions(options, options.toSql)), 'hdi_csn', options);
}
});
displayNamedCsn(sqlResult.csn, 'sql_csn', options);
else {
const hdiResult = main.to.hdi(csn, remapCmdOptions(options, options.toSql));
for (const name in hdiResult)
writeToFileOrDisplay(options.out, name, hdiResult[name]);
}
}
else if (options.toSql && options.toSql.csn) {
displayNamedCsn(for_sql(csn, remapCmdOptions(options, options.toSql)), 'sql_csn', options);
}
else {
const sqlResult = main.to.sql(csn, remapCmdOptions(options, options.toSql));
writeToFileOrDisplay(options.out, 'model.sql', sqlResult.join('\n'), true);
}
return model;

@@ -372,3 +418,3 @@ }

if (args.length !== 1)
displayUsage(`Command 'explain' expects exactly one message-id.`, optionProcessor.commands['explain'].helpText, 2);
displayUsage('Command \'explain\' expects exactly one message-id.', optionProcessor.commands.explain.helpText, 2);

@@ -385,3 +431,3 @@ const id = args.files[0];

// as possible = is problematic, since console.error() might be asynchronous
function displayErrors (err) {
function displayErrors(err) {
if (err instanceof main.CompilationError) {

@@ -396,3 +442,3 @@ if (options.rawOutput)

console.error( '' );
for (let sub of err.errors)
for (const sub of err.errors)
console.error( sub.message );

@@ -402,4 +448,5 @@ console.error( '' );

}
else
else {
throw err;
}

@@ -425,7 +472,7 @@ err.hasBeenReported = true;

if (options.internalMsg) {
messages.map(msg => util.inspect( msg, { depth: null, maxArrayLength: null} ) )
messages.map(msg => util.inspect( msg, { depth: null, maxArrayLength: null } ) )
.forEach(msg => log(msg));
}
else if (options.noMessageContext) {
messages.filter(msg => (messageLevels[ msg.severity ] <= options.warning))
messages.filter(msg => (messageLevels[msg.severity] <= options.warning))
.forEach(msg => log(main.messageString(msg, normalizeFilename, !options.showMessageId)));

@@ -442,15 +489,16 @@ }

let hasAtLeastOneExplanation = false;
messages.filter(msg => messageLevels[ msg.severity ] <= options.warning).forEach(msg => {
hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId)
messages.filter(msg => messageLevels[msg.severity] <= options.warning).forEach((msg) => {
hasAtLeastOneExplanation = hasAtLeastOneExplanation || main.hasMessageExplanation(msg.messageId);
const name = msg.location && msg.location.file;
const fullFilePath = name ? path.resolve('', name) : undefined;
const context = fullFilePath && sourceLines(fullFilePath);
log(main.messageStringMultiline(msg, { normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: true, hintExplanation: true }));
log(main.messageStringMultiline(msg, {
normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: true, hintExplanation: true,
}));
if (context)
log(main.messageContext(context, msg));
log() // newline
log(); // newline
});
if (options.showMessageId && hasAtLeastOneExplanation) {
log(`${term.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`)
}
if (options.showMessageId && hasAtLeastOneExplanation)
log(`${term.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
}

@@ -466,9 +514,9 @@ return model;

if (options.rawOutput) {
writeToFileOrDisplay(options.out, name + '_raw.txt', util.inspect(reveal(xsn, options.rawOutput), false, null), true);
writeToFileOrDisplay(options.out, `${name}_raw.txt`, util.inspect(reveal(xsn, options.rawOutput), false, null), true);
}
else if (options.internalMsg) {
writeToFileOrDisplay(options.out, name + '_raw.txt', util.inspect(reveal(xsn).messages, { depth: null, maxArrayLength: null}), true);
writeToFileOrDisplay(options.out, `${name}_raw.txt`, util.inspect(reveal(xsn).messages, { depth: null, maxArrayLength: null }), true);
}
else if (!options.lintMode) {
let csn = compactModel(xsn, options);
const csn = compactModel(xsn, options);
if (command === 'toCsn' && options.toCsn && options.toCsn.withLocalized)

@@ -478,3 +526,3 @@ addLocalizationViews(csn, options);

enrichCsn( csn, options );
writeToFileOrDisplay(options.out, name + '.json', csn, true);
writeToFileOrDisplay(options.out, `${name}.json`, csn, true);
}

@@ -492,3 +540,3 @@ }

if (options.internalMsg) {
writeToFileOrDisplay(options.out, name + '_raw.txt', options.messages, true);
writeToFileOrDisplay(options.out, `${name}_raw.txt`, options.messages, true);
}

@@ -498,3 +546,3 @@ else if (!options.lintMode && !options.internalMsg) {

addLocalizationViews(csn, options);
writeToFileOrDisplay(options.out, name + '.json', csn, true);
writeToFileOrDisplay(options.out, `${name}.json`, csn, true);
}

@@ -514,17 +562,17 @@ }

// replace all dots with underscore to get deployable .hdbcds sources (except the one before the file extension)
if(options.toHana)
fileName = fileName.replace(/\.(?=.*?\.)/g, '_')
if (options.toHana)
fileName = fileName.replace(/\.(?=.*?\.)/g, '_');
if (!(content instanceof String || typeof content === 'string')) {
if (!(content instanceof String || typeof content === 'string'))
content = JSON.stringify(content, null, 2);
}
if (dir === '-') {
if (!omitHeadline) {
if (!omitHeadline)
process.stdout.write(`// ------------------- ${fileName} -------------------\n`);
}
process.stdout.write(`${content}\n`);
if (!omitHeadline) {
process.stdout.write(`\n`);
}
} else {
if (!omitHeadline)
process.stdout.write('\n');
}
else {
// TODO: We might consider using async file-system API ...

@@ -535,4 +583,4 @@ fs.writeFileSync(path.join(dir, fileName), content);

function catchErrors (err) {
if (err instanceof Error && err['hasBeenReported'])
function catchErrors(err) {
if (err instanceof Error && err.hasBeenReported)
return;

@@ -539,0 +587,0 @@ console.error( '' );

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

const categoryChars = {
artref: 'm', paramname: 'b',
Entity: 'D', Enum: 'H', Index: 'J', AnnoDef: 'V', Extend: 'Z', Annotate: 'Z', Event: 'Y'
}
artref: 'm',
paramname: 'b',
Entity: 'D',
Enum: 'H',
Index: 'J',
AnnoDef: 'V',
Extend: 'Z',
Annotate: 'Z',
Event: 'Y',
};

@@ -29,17 +36,17 @@ function highlight( err, buf ) {

}
let ts = compiler.parseX( buf, 'hi.cds', { attachTokens: true, messages: [] } ).tokenStream;
const ts = compiler.parseX( buf, 'hi.cds', { attachTokens: true, messages: [] } ).tokenStream;
if (!buf.length || !ts.tokens || !ts.tokens.length)
return;
let chars = [...buf];
for (let tok of ts.tokens) {
let cat = tok.isIdentifier;
const chars = [ ...buf ];
for (const tok of ts.tokens) {
const cat = tok.isIdentifier;
if (cat && tok.start >= 0) {
if (cat !== 'ref' || chars[ tok.start ] !== '$')
chars[ tok.start ] = categoryChars[ cat ] || cat.charAt(0);
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 ] = '_';
chars[tok.start + 1] = '_';
}
}
for (let c of chars)
for (const c of chars)
process.stdout.write( c );
}

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

const commands = {
complete, find, lint
}
complete, find, lint,
};

@@ -29,8 +29,8 @@ const fs = require('fs');

let argv = process.argv;
let cmd = commands[ argv[2] ];
var line = Number.parseInt( argv[3] );
let column = Number.parseInt( argv[4] );
let file = argv[5];
let frel = path.relative( '', file||'' );
const { argv } = process;
const cmd = commands[argv[2]];
const line = Number.parseInt( argv[3] );
const column = Number.parseInt( argv[4] );
const file = argv[5];
const frel = path.relative( '', file || '' );
// TODO: proper realname

@@ -67,13 +67,15 @@

else {
let charBefore = buf[ off.prefix-1 ];
if ([':', '<', '.', '>', '!', '|', '='].includes( charBefore ))
const charBefore = buf[off.prefix - 1];
if ([ ':', '<', '.', '>', '!', '|', '=' ].includes( charBefore ))
// If first of multi-char symbols from 'literalNames' in
// gen/languageParser, calculate "symbol continuation"
tokensAt( buf, off.prefix-1, off.col-1, charBefore );
tokensAt( buf, off.prefix - 1, off.col - 1, charBefore );
hasId = tokensAt( buf, off.prefix, off.col, true );
}
if (hasId) {
let src = buf.substring( 0, off.prefix ) + '__NO_SUCH_ID__' + buf.substring( off.cursor );
let fname = path.resolve( '', file );
compiler.compileX( [file], '', { attachValidNames: true, lintMode: true, beta, messages: [] } , { [fname]: src } )
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: [],
}, { [fname]: src } )
.then( ident, ident );

@@ -86,7 +88,7 @@ }

return usage( xsnOrErr );
let vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
// TODO: if there is no such message, use console.log( 'arbitrary identifier' )
// if we want to avoid that the editor switches to fuzzy completion match
// against the prefix (not yet done anyway)
for (let n in vn)
for (const n in vn)
console.log( n, vn[n].kind );

@@ -111,15 +113,17 @@ if (!Object.keys( vn ).length)

return true;
const src = buf.substring( 0, off.prefix ) + '__NO_SUCH_ID__' + buf.substring( off.cursor );
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: [] } , { [fname]: src } )
compiler.compileX( [ file ], '', {
attachValidNames: true, lintMode: true, beta, messages: [],
}, { [fname]: src } )
.then( show, show );
return true;
function show( xsnOrErr ) {
function show( xsnOrErr ) {
if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
return usage( xsnOrErr );
let vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
const vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
const art = vn[buf.substring( off.prefix, off.cursor )];
if (art)
console.log( locationString( art.name.location || art.location ) + ': Definition' );
console.log( `${locationString( art.name.location || art.location )}: Definition` );
return true;

@@ -132,4 +136,4 @@ }

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

@@ -139,6 +143,6 @@ return true;

function display( xsnOrErr ) {
let messages = xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages;
const messages = xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages;
if (!messages)
return usage( xsnOrErr );
for (let msg of messages)
for (const msg of messages)
console.log( main.messageString( msg ) );

@@ -150,5 +154,5 @@ return true;

function tokensAt( buf, offset, col, symbol ) {
let src = buf.substring( 0, offset ) + '≠' + buf.substring( offset );
let et = messageAt( compiler.parseX( src, frel, { messages: [] } ), 'expectedTokens', col ) || [];
for (let n of et) {
const src = `${buf.substring( 0, offset )}≠${buf.substring( offset )}`;
const et = messageAt( compiler.parseX( src, frel, { messages: [] } ), 'expectedTokens', col ) || [];
for (const n of et) {
if (typeof symbol === 'string') {

@@ -165,4 +169,5 @@ if (n.length > 3 && n.charAt(0) === "'" && n.charAt(1) === symbol)

}
else if (n !== 'Identifier')
else if (n !== 'Identifier') {
console.log( n, 'unknown' );
}
}

@@ -173,4 +178,5 @@ return et.includes( 'Identifier' );

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

@@ -186,3 +192,3 @@ }

let pos = 0;
for (let l = line-1; l; --l) {
for (let l = line - 1; l; --l) {
pos = buf.indexOf( '\n', pos ) + 1;

@@ -189,0 +195,0 @@ if (!pos)

#!/usr/bin/env node
'use strict';
// Very simple command-line interface to support model migration from compiler

@@ -7,10 +9,9 @@ // v1 to v2. (If our client command processor would have been not as difficult

const commands = {
ria
}
ria,
};
const compiler = require('../lib/compiler');
const argv = process.argv;
const cmd = commands[ argv[2] ];
const { argv } = process;
const cmd = commands[argv[2]];
const files = argv.slice(3);

@@ -40,4 +41,4 @@ const options = { messages: [] };

const matches = msgObj.message.match( /["“][^"”]+["”]/g );
matches.slice(2).forEach( name => {
annotates[ name.slice( 1, -1 ) ] = true;
matches.slice(2).forEach( (name) => {
annotates[name.slice( 1, -1 )] = true;
} );

@@ -44,0 +45,0 @@ }

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

## Version 2.5.0 - 2021-07-28
### Added
- Allow to extend existing array annotation values via the ellipsis operator `...`.
An ellipsis may appear exactly once at an arbitrary position in the top level array
of an `annotate` directive. Only array values can be merged into arrays and unapplied
ellipses are removed from the final array value. Annotation layering rules remain unaffected.
- to.sql/hdi/hdbcds:
+ Doc comments are translated into HANA comments (or into `@Comment` annotation for `to.hdbcds`).
Such comments are possible on entities, views, elements of entities and `to.hdbcds` also supports comments on view columns.
Generation can be disabled via option `disableHanaComments`. Entites/views (and their elements/columns)
annotated with `@cds.persistence.journal` for `to.hdi`/`to.sql` will not have comments rendered.
+ Generation of temporal `WHERE` clause can be suppressed by annotating the `validFrom`/`validTo` elements of the projection with `false` or `null`.
- to.sql/hdi/hdbcds/edm(x)/for.odata: Structure/managed association comparisons (tuple comparisons) are now
also expanded in `WHERE` and `HAVING` - this was previously only supported in on-conditions.
- `cdsc` now internally uses SNAPI.
- to.hdi.migration:
+ Validate that the two supplied CSNs are compatible.
+ Improve delta-mechanism to not render superflous [ALTER|DROP|ADD] statements for unchanged SQL.
### Changed
- If the first source provided to the compile command has a `$sources` property
(whether enumerable or not) which is an array of strings,
use that instead of calculating one.
- Updated OData vocabularies 'Aggregation', 'Analytics', 'Authorization', 'Capabilities',
'CodeList', 'Common', 'Communication', 'Core', 'Graph', 'HTML5', 'Measures', 'ODM', 'PersonalData',
'Repeatability', 'Session', 'UI', 'Validation'
### Removed
- Removed internal property `$viaTransform` from CSN produced by OData/HANA transformation
### Fixed
- Remove warnings 'Ignoring annotation “@odata.draft.enabled” as the artifact is not part of a service'
and 'Ignoring draft node for composition target ... because it is not part of a service'
- Doc comments are no longer ignored after enum values and on view columns in parseCdl mode.
- to.cdl:
- Doc comments for enum values are correctly rendered.
- Enum value and doc comments are now correctly rendered if the enum is called `doc`.
- Doc comments at type references are correctly rendered.
- Empty doc comments are correctly rendered and not left out.
- Doc comments on view columns are correctly rendered.
- to.edm(x):
- OData V2: Ignore `@odata.singleton`.
- OData V4: Do not render an `edm:NavigationPropertyBinding` to a singleton if the association has
cardinality 'to-many'.
- forOData:
- Fix automatic renaming of shortcut annotation (eg. `@label`) with value `null`.
- CSN parser:
- Empty doc comments are correctly parsed and not complained about.
## Version 2.4.4 - 2021-07-02

@@ -16,3 +70,2 @@

- Do not remove parentheses around single literals and references on the right-hand side of an `in` and `not in` operator.
of an `in` and `not in` operator.

@@ -118,2 +171,7 @@ ## Version 2.4.2 - 2021-07-01

### 2.5.0 Addendum to Changed
- Replace outdated option `length` with `defaultStringLength` which is usable in `for.*` and `to.*` APIs.
## Version 2.2.4 - 2021-05-06

@@ -425,2 +483,8 @@

## Version 1.50.8 - 2021-07-01
### Fixed
- to.hdi.migration: Don't generate `ALTER` for type change from association to composition or vice versa (if the rest stays the same), as the resulting SQL is identical.
## Version 1.50.6 - 2021-05-05

@@ -427,0 +491,0 @@

@@ -10,2 +10,13 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 2.4.4
### Added `nestedProjections`
- Support `expand`: columns can look like `assoc_or_struct_or_tabalias { col_expression1, … }`,
`longer.ref as name { *, … } excluding { … }`, `{ col_expression1 as sub1, … } as name`, etc.
- Support `inline`: columns can look like `assoc_or_struct_or_tabalias.{ col_expression1, … }`,
`longer.ref[filter = condition].{ *, … } excluding { … }`, `assoc_or_struct_or_tabalias.*`, etc.
- _Some checks are missing and will be added! Minor changes might occur._
- __The SQL backends might not work properly yet if nested projections are used!__
## Version 2.4.2

@@ -12,0 +23,0 @@

@@ -156,2 +156,42 @@ /** @module API */

/**
* Transform a CSN like to.sql
*
* @param {CSN.Model} csn Plain input CSN
* @param {sqlOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.sql
* @private
*/
function forSql(csn, options = {}) {
const internalOptions = prepareOptions.to.sql(options);
internalOptions.toSql.csn = true;
return backends.toSqlWithCsn(csn, internalOptions).csn;
}
/**
* Transform a CSN like to.hdi
*
* @param {CSN.Model} csn Plain input CSN
* @param {hdiOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.hdi
* @private
*/
function forHdi(csn, options = {}) {
const internalOptions = prepareOptions.to.hdi(options);
internalOptions.toSql.csn = true;
return backends.toSqlWithCsn(csn, internalOptions).csn;
}
/**
* Transform a CSN like to.hdbcds
*
* @param {CSN.Model} csn Plain input CSN
* @param {hdbcdsOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.hdbcds
* @private
*/
function forHdbcds(csn, options = {}) {
const internalOptions = prepareOptions.to.hdbcds(options);
internalOptions.toHana.csn = true;
return backends.toHanaWithCsn(csn, internalOptions).csn;
}
/**
* Process the given CSN into SQL.

@@ -342,3 +382,3 @@ *

// Compare both images.
const diff = compareModels(beforeImage || afterImage, afterImage);
const diff = compareModels(beforeImage || afterImage, afterImage, internalOptions);

@@ -603,4 +643,9 @@ // Convert the diff to SQL.

edmx: publishCsnProcessor(edmx, 'to.edmx'),
/** Internal only */
for_sql: publishCsnProcessor(forSql, 'for.sql'),
for_hdi: publishCsnProcessor(forHdi, 'for.hdi'),
for_hdbcds: publishCsnProcessor(forHdbcds, 'for.hdbcds'),
renderSQL,
undeploy,
/** */
};

@@ -714,2 +759,8 @@

/**
* Available SQL change modes
*
* @typedef {'alter' | 'drop' } SqlChangeMode
*/
/**
* Available oData versions

@@ -756,2 +807,4 @@ *

* @property {NamingMode} [sqlMapping='plain'] Naming mode to use
* @property {SqlChangeMode} [sqlChangeMode='alter'] SQL change mode to use (for changed columns)
* @property {boolean} [allowCsnDowngrade=false] Allow downgrades of CSN major version (for modelCompare)
* @property {object} [beta] Enable experimental features - not for productive use!

@@ -758,0 +811,0 @@ * @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities

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

'sqlChangeMode',
'allowCsnDowngrade',
'joinfk',

@@ -53,2 +54,3 @@ 'magicVars',

'internalMsg',
'disableHanaComments', // in case of issues with hana comment rendering
'dependentAutoexposed', // deprecated, no effect - TODO: safe to remove?

@@ -55,0 +57,0 @@ 'longAutoexposed', // deprecated, no effect - TODO: safe to remove?

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

function combinedLocation( start, end ) {
if (!start)
if (!start || !start.location)
return end.location;
else if (!end)
else if (!end || !end.location)
return start.location;

@@ -22,0 +22,0 @@ const loc = {

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

'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
'odata-spec-violation-array-of-v2': { severity: 'Warning' }, // more than 30 chars
'odata-spec-violation-constraints': { severity: 'Warning' }, // more than 30 chars

@@ -111,2 +112,5 @@ 'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars

const centralMessageTexts = {
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
'anno-unexpected-ellipsis': 'Unexpected $(CODE) in annotation assignment',
'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',
'syntax-csn-expected-object': 'Expected object for property $(PROP)',

@@ -205,2 +209,6 @@ 'syntax-csn-expected-column': 'Expected object or string \'*\' for property $(PROP)',

'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
'odata-spec-violation-array-of-v2': 'EDM $(LITERAL) must not have an attribute $(PROP) with the value $(NAME)',
'odata-spec-violation-param-v2' : 'Type of EDM FunctionImport Parameter must be an EDM SimpleType or ComplexType',
'odata-spec-violation-returns-v2': 'EDM FunctionImport must return EDM SimpleType, ComplexType, EntityType or a collection of one of these',
'odata-spec-violation-assoc-v2': 'EDM ComplexType $(NAME) must not contain an EDM NavigationProperty',
'odata-spec-violation-attribute': 'EDM Property $(NAME) has no attribute $(PROP)',

@@ -207,0 +215,0 @@ 'odata-spec-violation-property-name': 'EDM Property $(NAME) must not have the same name as the declaring $(TYPE)',

@@ -615,2 +615,4 @@ // Functions and classes for syntax messages

names: transformManyWith( quoted ),
number: n => n,
literal: l => l,
art: transformArg,

@@ -625,4 +627,9 @@ service: transformArg,

// msg: m => m,
$reviewed: ignoreTextTransform,
};
function ignoreTextTransform() {
return null;
}
function transformManyWith( t, sorted ) {

@@ -771,3 +778,3 @@ return function transformMany( many, r, args, texts ) {

* Example:
* <source>.cds:3:11: Error: cannot find value `nu` in this scope *
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
*

@@ -809,8 +816,11 @@ * @param {CSN.Message} err

/**
* Return message string with location if present.
* Returns a message string with file- and semantic location if present
* in multiline form.
*
* Example:
* Error: cannot find value `nu` in this scope
* <source>.cds:3:11, at entity:“E”
*
* ```txt
* Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
* |
* <source>.cds:3:11, at entity:“E”
* ```
* @param {CSN.Message} err

@@ -1206,2 +1216,7 @@ * @param {object} [config = {}]

}
else if (inElement) {
result += select();
elements.push(step);
inQuery = false;
}
}

@@ -1318,3 +1333,3 @@ else if ( inMixin ) {

let isFound = false;
traverseQuery(rootQuery, null, (q, querySelect) => {
traverseQuery(rootQuery, null, null, (q, querySelect) => {
if ( querySelect )

@@ -1321,0 +1336,0 @@ totalQueryDepth += 1;

@@ -184,60 +184,3 @@ 'use strict';

// FIXME: this function is only used by old tests -- REMOVE!
//
// Clone 'node', transforming nodes therein recursively. Object 'transformers' is expected
// to contain a mapping of property 'key' names to transformer functions. The node's properties
// are walked recursively, calling each transformer function on its corresponding property
// 'key' of 'node', replacing 'value' in 'resultNode' with the function's return value
// (returning 'undefined' will delete the property).
// If no transformation function is found for 'key', the first letter of 'key' is tried
// instead (this seems to be intended for handling annotations that start with '@' ?)
// FIXME: Do we really need this ?
// If `withArtifactLink` is set, the `_artifact` links of `node` are copied too (not cloned,
// of course).
// Regardless of their names, transformers are never applied to dictionary elements.
//
// The transformer functions are called with the following signature:
// transformer(value, node, resultNode, key)
function cloneWithTransformations(node, transformers, withArtifactLink) {
return transformNode(node);
// This general transformation function will be applied to each node recursively
function transformNode(node) {
// Return primitive values and null unchanged, but let objects and dictionaries through
// (Note that 'node instanceof Object' would be false for dictionaries).
if (node === null || typeof node !== 'object') {
return node
}
// Simply return if node is to be ignored
if (node._ignore)
return undefined;
// Transform arrays element-wise
if (Array.isArray(node)) {
return node.map(transformNode);
}
// Things not having 'proto' are dictionaries
const proto = Object.getPrototypeOf(node);
// Iterate own properties of 'node' and transform them into 'resultNode'
const resultNode = Object.create(proto);
for (let key of Object.keys(node)) {
// Dictionary always use transformNode(), other objects their transformer according to key
const transformer = !proto ? transformNode : transformers[key] || transformers[key.charAt(0)];
// Apply transformer, or use transformNode() if there is none
const resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
if (resultValue !== undefined) {
resultNode[key] = resultValue;
}
}
// For non-dictionaries, take over `_artifact` if requested
if (withArtifactLink && proto) {
let pd = Object.getOwnPropertyDescriptor(node, '_artifact')
if (pd)
Object.defineProperty(resultNode, '_artifact', pd);
}
return resultNode;
}
}
module.exports = {

@@ -256,3 +199,2 @@ isBetaEnabled,

setProp,
cloneWithTransformations,
};

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

* @param {string} artName Name of the artifact
*
*/

@@ -54,0 +53,0 @@ function checkChainedArray(art, artName) {

@@ -34,3 +34,3 @@ // This is very similar to lib/model/enrichCsn - but the goal and the execution differ a bit:

// Annotations are ignored.
'@': () => {},
'@': () => { /* ignore annotations */ },
};

@@ -107,3 +107,5 @@ let cleanupCallbacks = [];

function pathRef( node, prop, path ) {
const { links, art, scope } = inspectRef( csnPath );
const {
links, art, scope, $env,
} = inspectRef( csnPath );
if (links) {

@@ -117,2 +119,6 @@ setProp(node, '_links', links);

}
if ($env) {
setProp(node, '$env', $env );
cleanupCallbacks.push(() => delete node.$env);
}
setProp(node, '$scope', scope);

@@ -119,0 +125,0 @@ cleanupCallbacks.push(() => delete node.$scope);

'use strict';
const { hasBoolAnnotation, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
const { hasAnnotationValue, isPersistedOnDatabase, isBuiltinType } = require('../model/csnUtils');
/**

@@ -72,3 +72,3 @@ * Make sure that all source artifacts and association targets reach the database

if (!isPersistedOnDatabase(endArtifact)) {
const cdsPersistenceSkipped = hasBoolAnnotation(endArtifact, '@cds.persistence.skip');
const cdsPersistenceSkipped = hasAnnotationValue(endArtifact, '@cds.persistence.skip');
this.error( null, obj.$path, {

@@ -105,3 +105,3 @@ id: pathStep, elemref: obj, name, '#': cdsPersistenceSkipped ? 'std' : 'abstract',

if (isPersistedOnDatabase(this.artifact) && !hasBoolAnnotation(this.artifact, '@cds.persistence.table')) {
if (isPersistedOnDatabase(this.artifact) && !hasAnnotationValue(this.artifact, '@cds.persistence.table')) {
const generalQueryProperties = [ 'from', 'columns', 'where', 'groupBy', 'orderBy', 'having', 'limit' ];

@@ -108,0 +108,0 @@ for (const prop of generalQueryProperties) {

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

forEachDefinition, forEachMemberRecursively, forAllQueries,
forEachMember, getNormalizedQuery, hasBoolAnnotation,
forEachMember, getNormalizedQuery, hasAnnotationValue,
} = require('../model/csnUtils');

@@ -113,4 +113,3 @@ const enrich = require('./enricher');

that.artifact = artifact;
if (memberValidators.length &&
(!iterateOptions.filterArtifact || iterateOptions.filterArtifact(artifact))) {
if (memberValidators.length) {
forEachMemberRecursively( artifact,

@@ -133,3 +132,2 @@ memberValidators.map(v => v.bind(that)),

* @param {object} that Will be provided to the validators via "this"
*
* @returns {Function} the validator function with the respective checks for the HANA backend

@@ -152,3 +150,3 @@ */

{
filterArtifact: artifact => !artifact.abstract && !hasBoolAnnotation(artifact, '@cds.persistence.skip'),
skipArtifact: artifact => artifact.abstract || hasAnnotationValue(artifact, '@cds.persistence.skip'),
skip: [

@@ -165,3 +163,2 @@ 'action',

* @param {object} that Will be provided to the validators via "this"
*
* @returns {Function} the validator function with the respective checks for the OData backend

@@ -184,5 +181,8 @@ */

),
forOdataQueryValidators.concat(commonQueryValidators));
forOdataQueryValidators.concat(commonQueryValidators),
{
skipArtifact: this.isExternalServiceMember,
});
}
module.exports = { forHana, forOdata };

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

'$withLocalized',
'$sources',
],

@@ -125,4 +126,4 @@ },

col: { test: isNumber },
endLine: { test: isNumber },
endCol: { test: isNumber },
endLine: { test: isNumber, also: [ undefined ] },
endCol: { test: isNumber, also: [ undefined ] },
$notFound: { test: isBoolean },

@@ -291,4 +292,4 @@ },

kind: [ 'extend' ],
inherits: 'definitions',
test: isArray( column ),
optional: thoseWithKind,
enum: [ '*' ],

@@ -301,2 +302,3 @@ requires: [ 'location' ],

excludingDict: {
kind: 'element',
test: isDictionary( definition ), // definition since redef

@@ -546,3 +548,3 @@ requires: [ 'location', 'name' ],

_origin: { kind: [ 'entity' ], test: TODO },
_pathHead: { kind: [ 'element' ], test: TODO },
_pathHead: { kind: [ 'element', undefined ], test: TODO }, // column or * (wildcard)
_from: { kind: true, test: TODO }, // all table refs necessary to compute elements

@@ -583,2 +585,3 @@ // array of $tableAlias (or includes) for explicit and implicit redirection:

$withLocalized: { test: isBoolean },
$sources: { parser: true, test: isArray( isString ) },
$expected: { parser: true, test: isString },

@@ -815,3 +818,3 @@ };

const requires = [ 'val', 'location' ];
const optional = [ 'literal', '$inferred' ];
const optional = [ 'literal', '$inferred', '_pathHead' ];
standard( node, parent, prop, { schema: valSchema, requires, optional }, name );

@@ -818,0 +821,0 @@ };

@@ -56,21 +56,19 @@ // Main XSN-based compiler functions

const ext = path.extname( filename ).toLowerCase();
if ([ '.json', '.csn' ].includes(ext) || (options.fallbackParser === 'csn')) {
if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
return parseCsn.parse( source, filename, options );
}
else if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext) || options.fallbackParser) {
// Note: Historically, all truthy values for options.fallbackParser were interpreted as 'cdl'.
// To not break existing programs, we do the same if it's not set to `csn`.
if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
return parseLanguage( source, filename, options );
}
// eslint-disable-next-line no-else-return
else {
const model = { location: { file: filename } };
const { error } = makeMessageFunction( model, options, 'compile' );
error( 'file-unknown-ext', emptyWeakLocation(filename),
{ file: ext && ext.slice(1), '#': !ext && 'none' }, {
std: 'Unknown file extension $(FILE)',
none: 'No file extension',
} );
return model;
}
if (options.fallbackParser === 'csn')
return parseCsn.parse( source, filename, options );
if (options.fallbackParser) // any other value: like 'cdl' (historic reasons)
return parseLanguage( source, filename, options );
const model = { location: { file: filename } };
const { error } = makeMessageFunction( model, options, 'compile' );
error( 'file-unknown-ext', emptyWeakLocation(filename),
{ file: ext && ext.slice(1), '#': !ext && 'none' }, {
std: 'Unknown file extension $(FILE)',
none: 'No file extension',
} );
return model;
}

@@ -87,3 +85,4 @@

// This function returns a Promise. See ../bin/cdsv.js for an example usage.
// This function returns a Promise and can be used with `await`. For an example
// see `examples/api-usage/`.
// See function `compileSyncX` or `compileSourcesX` for alternative compile

@@ -336,2 +335,4 @@ // functions.

*
* TODO: re-check `using from` dependencies.
*
* @param {string|object} sourcesDict Files to compile.

@@ -342,12 +343,9 @@ * @param {object} [options={}] Compilation options.

function compileSourcesX( sourcesDict, options = {} ) {
// eslint-disable-next-line no-nested-ternary
const content = sourcesDict.sources
? sourcesDict.sources
: (typeof sourcesDict === 'string') ? { '<stdin>.cds': sourcesDict } : sourcesDict;
if (typeof sourcesDict === 'string')
sourcesDict = { '<stdin>.cds': sourcesDict };
const sources = Object.create(null);
const model = { sources, options };
for (const filename in content) {
const source = content[filename];
for (const filename in sourcesDict) {
const source = sourcesDict[filename];
if (typeof source === 'string') {

@@ -363,17 +361,2 @@ const ast = parseX( source, filename, options );

}
// add dependencies to AST
for (const filename in sourcesDict.dependencies) {
const dependency = sourcesDict.dependencies[filename];
for (const val in dependency) {
const dep = {
literal: 'string', val, realname: dependency[val], // location ?
};
const arr = sources[filename].dependencies;
if (arr)
arr.push( dep );
else
sources[filename].dependencies = [ dep ];
}
}
moduleLayers.setLayers( sources );

@@ -380,0 +363,0 @@

@@ -287,2 +287,12 @@ // Compiler functions and utilities shared across all phases

function userQuery( user ) {
// TODO: we need _query links set by the definer
while (user._main) {
if (user.kind === 'select' || user.kind === '$join')
return user;
user = user._parent;
}
return null;
}
// Return artifact or element referred by the path in `ref`. The first

@@ -329,8 +339,4 @@ // environment we search in is `env`. If no such artifact or element exist,

// TODO: SIMPLIFY this function
// eslint-disable-next-line no-nested-ternary
const query = (spec.lexical === 'main')
? user._main // in path filter, just $magic (and $parameters)
: (user.kind === 'select' || user.kind === '$join')
? user
: user._parent && user._parent.kind === 'select' && user._parent;
const query = (spec.lexical === 'main') ? user._main : userQuery( user );
// in path filter, just $magic (and $parameters)
env = (spec.lexical === 'from') ? query._parent : query || user._main || user;

@@ -355,3 +361,3 @@ // queries: first tabaliases, then $magic - value refs: first $self, then $magic

}
else if (user._pathHead) {
else if (!spec.envFn && user._pathHead) {
// eslint-disable-next-line no-empty

@@ -403,3 +409,3 @@ }

// console.log(expected, ref.path.map(a=>a.id),artItemsCount)
art = getPathItem( path, spec, user, artItemsCount, user._pathHead && art);
art = getPathItem( path, spec, user, artItemsCount, !spec.envFn && user._pathHead && art);
if (!art)

@@ -512,3 +518,4 @@ return setLink( ref, art );

function getPathRoot( path, spec, user, env, extDict, msgArt ) {
if (user._pathHead) { // TODO: not necessarily for explicit ON condition in expand
if (!spec.envFn && user._pathHead) {
// TODO: not necessarily for explicit ON condition in expand
environment( user._pathHead ); // make sure _origin is set

@@ -813,3 +820,3 @@ return user._pathHead._origin;

// annotation definition is missing
function defineAnnotations( construct, art, block, priority ) {
function defineAnnotations( construct, art, block, priority = 'define' ) {
if (!options.parseCdl && construct.kind === 'annotate') {

@@ -835,7 +842,6 @@ // Namespaces cannot be annotated in CSN but because they exist as XSN artifacts

art.doc = construct.$annotations.doc;
if (!construct.$annotations || !construct.$annotations.length) {
if (construct === art)
return;
// $annotations is set if parsed from CDL but may not be set if
// the input is CSN so we extract them.
if (!construct.$annotations) {
if (!block || block.$frontend !== 'json')
return; // namespace, or in CDL source without @annos:
// CSN input: set _block and $priority, shallow-copy from extension
for (const annoProp in construct) {

@@ -848,3 +854,5 @@ if (annoProp.charAt(0) === '@') {

setProp( a, '_block', block );
setAnnotation(art, annoProp, a, priority);
a.$priority = priority;
if (construct !== art)
dictAddArray( art, annoProp, a );
}

@@ -899,10 +907,6 @@ }

// set _artifact?
setAnnotation( art, annoProp, anno, priority );
anno.$priority = priority;
dictAddArray( art, annoProp, anno );
}
}
function setAnnotation( art, annoProp, anno, priority = 'define') {
anno.$priority = priority;
dictAddArray( art, annoProp, anno );
}
}

@@ -909,0 +913,0 @@

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

if(!options)
throw 'Please debug me: csn2annotationsEdm must be invoked with options';
throw new Error('Please debug me: csn2annotationsEdm must be invoked with options');

@@ -140,0 +140,0 @@ const messageFunctions = makeMessageFunction(csn, options);

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

const { setProp } = require('../base/model');
const { cloneCsn, isEdmPropertyRendered } = require('../model/csnUtils');
const { cloneCsn, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
const { checkCSNVersion } = require('../json/csnVersion');

@@ -396,8 +396,12 @@ const { makeMessageFunction } = require('../base/messages');

properties.forEach(p => {
const pLoc = [...loc, 'elements', p.Name];
if(!p[p._typeName]) {
warning('odata-spec-violation-attribute', loc, { name: p.Name, prop: p._typeName });
warning('odata-spec-violation-attribute', pLoc, { name: p.Name, prop: p._typeName });
}
if(p.Name === EntityTypeName) {
warning('odata-spec-violation-property-name', loc, { name: p.Name, type: 'EntityType' });
warning('odata-spec-violation-property-name', pLoc, { name: p.Name, type: 'EntityType' });
}
if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-array-of-v2', pLoc, { literal: 'Property', prop: 'Type', name: 'Collection' });
}
});

@@ -418,7 +422,4 @@

let containerEntry;
let singleton = entityCsn['@odata.singleton'];
let hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined &&
entityCsn['@odata.singleton.nullable'] !== null;
if(singleton || ((singleton === undefined || singleton === null) && hasNullable)) {
if(isSingleton(entityCsn)) {
containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);

@@ -433,3 +434,2 @@ if(entityCsn['@odata.singleton.nullable'])

// V4: Create NavigationPropertyBinding in EntitySet
// if NavigationProperty is not a Containment and if the target is not a containee
if(options.isV4())

@@ -439,3 +439,7 @@ properties.filter(np =>

// @ts-ignore TypeScript does not recognize these properties on type NavigationProperty
!np.isContainment() && !edmUtils.isContainee(np._targetCsn) && !np._targetCsn.$proxy && !np._targetCsn.$externalRef
!np.isContainment() &&
!edmUtils.isContainee(np._targetCsn) &&
!np._targetCsn.$proxy &&
!np._targetCsn.$externalRef &&
!(np._isCollection && isSingleton(np._targetCsn))
). forEach(np =>

@@ -452,2 +456,8 @@ containerEntry.append(np.createNavigationPropertyBinding(schemaNamePrefix)));

});
function isSingleton(entityCsn) {
const singleton = entityCsn['@odata.singleton'];
const hasNullable = entityCsn['@odata.singleton.nullable'] !== undefined && entityCsn['@odata.singleton.nullable'] !== null;
return options.isV4() && singleton || ((singleton === undefined || singleton === null) && hasNullable);
}
}

@@ -537,2 +547,3 @@

const actLoc = ['definitions', ...(entityCsn ? [entityCsn.name, 'actions', actionCsn.name] : [actionCsn.name])];
let rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);

@@ -582,3 +593,11 @@ if(rt) // add EntitySet attribute only if return type is an entity

edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
functionImport.append(new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' ));
const paramLoc = [...actLoc, 'params', parameterName];
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
warning('odata-spec-violation-param-v2', paramLoc);
}
if(param._isCollection) {
warning('odata-spec-violation-array-of-v2', paramLoc, { literal: 'FunctionImport Parameter', prop: 'Type', name: 'Collection' });
}
functionImport.append(param);
});

@@ -594,4 +613,9 @@

let type = returns.type;
if(type)
if(type){
if(!isBuiltinType(type) && !['entity', 'view', 'type'].includes(csn.definitions[type].kind)){
const returnsLoc = [ ...actLoc, 'returns'];
warning('odata-spec-violation-returns-v2', returnsLoc);
}
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, options.isV2());
}

@@ -676,2 +700,3 @@ if(action.returns._isCollection)

let properties = createProperties(elementsCsn)[0];
const loc = ['definitions', structuredTypeCsn.name];

@@ -683,8 +708,17 @@ if(properties.length === 0) {

properties.forEach(p => {
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
if(!p[p._typeName]) {
warning('odata-spec-violation-attribute', ['definitions', structuredTypeCsn.name], { name: p.Name, prop: p._typeName });
warning('odata-spec-violation-attribute', pLoc, { name: p.Name, prop: p._typeName });
}
if(p.Name === complexType.Name) {
warning('odata-spec-violation-property-name', ['definitions', structuredTypeCsn.name], { name: p.Name, type: complexType.kind });
warning('odata-spec-violation-property-name', pLoc, { name: p.Name, type: complexType.kind });
}
if(options.isV2()) {
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-array-of-v2', pLoc, { literal: 'Property' ,prop: 'Type', name: 'Collection' });
}
if(edmUtils.isAssociationOrComposition(p._csn)) {
warning('odata-spec-violation-assoc-v2', pLoc, { name: structuredTypeCsn.name });
}
}
});

@@ -691,0 +725,0 @@

@@ -148,2 +148,17 @@ // CSN frontend - transform CSN into XSN

},
column: {
arrayOf: selectItem,
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET', 'expand' ],
schema: {
xpr: {
class: 'condition',
type: xprInValue,
inKind: [ '$column' ],
inValue: true,
},
},
},
};

@@ -227,31 +242,11 @@

columns: {
arrayOf: selectItem,
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref', 'xpr', 'val', '#', 'func', 'list', 'SELECT', 'SET' ], // requires one of...
inKind: [ 'extend' ], // only valid in extend and SELECT
schema: {
xpr: {
class: 'condition',
type: xprInValue,
inKind: [ '$column' ],
inValue: true,
},
},
class: 'column',
inKind: [ 'extend' ], // only valid in extend and SELECT/projection
},
expand: {
arrayOf: selectItem, // TODO: more specific
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref' ], // requires one of...
class: 'column',
inKind: [ '$column' ], // only valid in $column
},
inline: {
arrayOf: selectItem, // TODO: more specific
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref' ], // requires one of...
class: 'column',
inKind: [ '$column' ], // only valid in $column

@@ -518,2 +513,3 @@ },

excluding: {
inKind: [ '$column' ],
arrayOf: string,

@@ -562,3 +558,3 @@ type: excluding,

doc: {
type: stringVal,
type: stringValOrNull,
inKind: () => true, // allowed in all definitions (including columns and extensions)

@@ -705,2 +701,3 @@ },

let dollarLocations = [];
let arrayLvlCnt = 0;

@@ -1106,2 +1103,9 @@ /**

function stringValOrNull( val, spec ) {
if (val === null)
return { val, location: location() };
return stringVal(val, spec);
}
function natnum( val, spec ) {

@@ -1137,3 +1141,13 @@ if (typeof val === 'number' && val >= 0)

if (Array.isArray( val )) {
return {
const ec = val.reduce((c, v) => ((v && v['...'] && Object.keys(v).length === 1) ? ++c : c), 0);
if (arrayLvlCnt === 0 && ec > 1) {
error( 'syntax-csn-duplicate-ellipsis', location(true), { code: '...' },
'Expected no more than one $(CODE)' );
}
if (arrayLvlCnt > 0 && ec > 0) {
error( 'syntax-csn-unexpected-ellipsis', location(true), { code: '...' },
'Unexpected $(CODE) in nested array' );
}
arrayLvlCnt++;
const retval = {
location: location(),

@@ -1143,2 +1157,4 @@ val: arrayOf( annoValue )( val, spec ),

};
arrayLvlCnt--;
return retval;
}

@@ -1161,2 +1177,9 @@ if (typeof val['#'] === 'string') {

}
else if (val['...'] && Object.keys(val).length === 1) {
return {
val: '...',
literal: 'token',
location: location(),
};
}
const struct = Object.create(null);

@@ -1656,2 +1679,6 @@ ++virtualLine;

attachVocabInDefinitions( r );
if (csn.$sources && Array.isArray( csn.$sources ) &&
csn.$sources.every( fname => typeof fname === 'string' ))
// non-enumerable or enumerable, ignore with wrong value
r.$sources = csn.$sources;
return Object.assign( xsn, r );

@@ -1658,0 +1685,0 @@ }

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

inline: ignore, // do not list for select items as elements
excludingDict: renameTo( 'excluding', Object.keys ),
excludingDict,
groupBy: arrayOf( expression ),

@@ -287,2 +287,3 @@ where: condition, // also pathItem after 'cardinality' before 'args'

// 'namespace' for complete model is 'namespace' of first source
// (not a really useful property at all, avoids XSN inspection by Umbrella)
for (const first in srcDict) {

@@ -377,3 +378,3 @@ const { namespace } = srcDict[first];

(art.query || art.includes || art.$inferred)) {
const annos = art.$inferred && annotations( art, true );
const annos = art.$inferred && annotationsAndDocComment( art, true );
const elems = inferred( art.elements, art.$inferred );

@@ -399,3 +400,3 @@ /** @type {object} */

// happen through extensions.
const annos = annotations( art, true );
const annos = annotationsAndDocComment( art, true );
const annotate = Object.assign( { annotate: name }, annos );

@@ -440,4 +441,9 @@ if (Object.keys( annotate ).length > 1) {

function sources( srcDict, csn ) {
const names = Object.keys( srcDict );
const $sources = names.length && srcDict[names[0]].$sources;
if ($sources) {
setHidden( csn, '$sources', $sources );
return undefined;
}
// TODO: sort according to some layering order, see #6368
const names = Object.keys( srcDict);
setHidden( csn, '$sources', (!strictMode) ? names : names.map( relativeName ) );

@@ -458,3 +464,3 @@ return undefined;

continue;
const csn = annotations( elem, true );
const csn = annotationsAndDocComment( elem, true );
if (Object.keys(csn).length)

@@ -549,3 +555,3 @@ ext[name] = csn;

// or annotations (annotated==true)
function annotations( node, annotated ) {
function annotationsAndDocComment( node, annotated ) {
const csn = {};

@@ -567,2 +573,4 @@ const transformer = transformers['@'];

}
if (node.doc)
csn.doc = transformers.doc(node.doc);
return csn;

@@ -803,2 +811,4 @@ }

return node.val.map( value );
if (node.literal === 'token' && node.val === '...')
return extra( { '...': true } );
if (node.literal !== 'struct')

@@ -833,2 +843,3 @@ // no val (undefined) as true only for annotation values (and struct elem values)

const expr = expression( node );
// we do not set a hidden $parens on array - we could still do it if requested
return !expr.cast && expr.xpr || [ expr ];

@@ -893,3 +904,3 @@ }

if (node.query)
return query( node.query );
return query( node.query, null, null, 1 );
if (!node.op) // parse error

@@ -899,3 +910,3 @@ return { xpr: [] };

// do not use xpr() for xpr, as it would flatten inner xpr's
return extra({ xpr: node.args.map( expression ) }, dollarExtraNode );
return extra({ xpr: node.args.map( expression ) }, dollarExtraNode, 1 );
else if (node.op.val === 'cast')

@@ -905,6 +916,7 @@ return cast( expression( node.args[0] ), dollarExtraNode );

else if (node.op.val !== ',')
return extra( { xpr: xpr( node ) }, dollarExtraNode );
return extra( { xpr: xpr( node ) }, dollarExtraNode, 1 );
return (parensAsStrings)
? { xpr: [ '(', ...xpr( node ), ')' ] }
: extra( { list: node.args.map( expression ) } );
// the inner parens belong to the tuple construct, i.e. won't count as parens
: extra( { list: node.args.map( expression ) }, dollarExtraNode, 0 );
}

@@ -956,5 +968,3 @@

function query( node, csn, xsn ) {
while (Array.isArray(node)) // in parentheses -> remove
node = node[0];
function query( node, csn, xsn, expectedParens = 0 ) {
if (node.op.val === 'SELECT') {

@@ -966,3 +976,4 @@ if (xsn && xsn.query === node && xsn.$syntax === 'projection' &&

}
const select = { SELECT: standard( node ) };
const select = { SELECT: extra( standard( node ), node, expectedParens ) };
// one paren pair is not put into XSN - TODO: change that?
const elems = node.elements;

@@ -1013,7 +1024,9 @@ if (elems && node._main && node !== node._main._leadingQuery && gensrcFlavor !== true) {

function excludingDict( xsnDict, csn, xsn ) {
if (xsn.kind !== 'element')
csn.excluding = Object.keys( xsnDict );
}
function from( node ) {
while (Array.isArray(node)) // TODO: old-style parentheses - keep tmp for A2J
node = node[0];
// TODO: CSN: FROM ((SELECT...)) as -> also add 'subquery' op? - Together
// with []-elimination in FROM... -> normal standard()
// TODO: can we use the normal standard(), at least with JOIN?
if (node.join) {

@@ -1027,3 +1040,3 @@ const join = { join: node.join.val };

else if (node.query) {
return addExplicitAs( query( node.query ), node.name ); // $extra inside SELECT/SET
return addExplicitAs( query( node.query, null, null, 1 ), node.name );
}

@@ -1044,3 +1057,3 @@ else if (!node._artifact || node._artifact._main) { // CQL or follow assoc

// only list annotations here which are provided directly with definition
const col = (gensrcFlavor) ? annotations( elem, false ) : {};
const col = (gensrcFlavor) ? annotationsAndDocComment( elem, false ) : {};
// with `client` flavor, assignments are available at the element

@@ -1055,2 +1068,3 @@ const gensrcSaved = gensrcFlavor;

Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) );
gensrcFlavor = gensrcSaved; // for not having annotations in inline etc
if (elem.expand)

@@ -1060,2 +1074,5 @@ col.expand = columns( elem.expand );

col.inline = columns( elem.inline );
gensrcFlavor = gensrcFlavor || 'column';
if (elem.excludingDict)
col.excluding = Object.keys( elem.excludingDict );
// yes, the AS comes after the EXPAND

@@ -1084,2 +1101,4 @@ addExplicitAs( col, elem.name, neqPath( elem.value ) );

const parens = elem.value.$parens;
if (parens)
setHidden( col, '$parens', parens.length );
addLocation( (parens ? parens[parens.length - 1] : elem.value.location), col );

@@ -1099,8 +1118,9 @@ }

function extra( csn, node ) {
function extra( csn, node, expectedParens = 0 ) {
if (node) {
if (node.$extra)
Object.assign( csn, node.$extra );
if (node.$parens)
setHidden( csn, '$parens', node.$parens.length );
const parens = (node.$parens ? node.$parens.length : 0);
if (parens !== expectedParens)
setHidden( csn, '$parens', parens );
}

@@ -1179,3 +1199,3 @@ return csn;

module.exports = {
cloneCsnDictionary: csnDictionary,
cloneCsnDictionary: (csn, options) => csnDictionary(csn, false, options),
compactModel,

@@ -1182,0 +1202,0 @@ compactQuery,

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

function surroundByParens( expr, open, close ) {
function surroundByParens( expr, open, close, asQuery = false ) {
if (!expr)

@@ -317,3 +317,3 @@ return expr;

expr.$parens = [ location ];
return expr;
return (asQuery) ? { query: expr, location } : expr;
}

@@ -367,2 +367,3 @@

'Please add the keyword $(KEYWORD) in front of the alias name' );
return ast;
}

@@ -369,0 +370,0 @@

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

// FIXME: The implementation of those functions that delegate to 'backends' should probably move here
// ATTENTION: Keep in sync with main.d.ts!
module.exports = {

@@ -57,0 +58,0 @@ // Compiler

@@ -33,6 +33,26 @@ // CSN functionality for resolving references

// "csnPath" is the name or index of that property; 'args' (its value can be a
// dictionary) is handled extra here.
// dictionary) is handled extra here, also 'expand' and 'inline'
const artifactProperties = [ 'elements', 'columns', 'keys', 'mixin', 'enum',
'params', 'actions', 'definitions', 'extensions' ];
'params', 'actions', 'definitions', 'extensions' ]; // + 'args', see above
const scopeSpecs = {
type: { global: true },
includes: { global: true },
target: { global: true },
from: { global: true },
keys: { lexical: false, dynamic: 'target' },
excluding: { lexical: false },
expand: { lexical: justDollar }, // dynamic: 'baseEnv-or-source'
inline: { lexical: justDollar }, // dynamic: 'baseEnv'
'ref-target': { lexical: justDollar }, // dynamic: 'baseEnv'
on: { lexical: justDollar, dynamic: 'query' }, // assoc defs, redirected to
orderBy: { dynamic: 'query' },
'orderBy-set': { lexical: query => query.$next, dynamic: 'query' }, // to outer SELECT (from UNION)
// default: { lexical: query => query, dynamic: 'source' }
}
function justDollar() {
return null;
}
/**

@@ -56,2 +76,3 @@ * @param {CSN.Model} csn

effectiveType, artifactRef, inspectRef, queryOrMain,
__getCache_forEnrichCsnDebugging: obj => cache.get( obj ),
};

@@ -63,3 +84,3 @@

* confusion with the "base type", we do not use the term "final type".
* (This function could be omitted if we would use JS prototypes for type refs.)
* (This function could be simplified if we would use JS prototypes for type refs.)
*

@@ -114,3 +135,3 @@ * @param {CSN.ArtifactWithRefs} art

? csn.definitions[ref]
: cached( ref, 'ref', artifactPathRef );
: cached( ref, '_ref', artifactPathRef );
if (art)

@@ -143,2 +164,19 @@ return art;

* @param {CSN.Path} csnPath
*
* - return value `scope`
* global: first item is name of definition
* param: first item is parameter of definition (with param: true)
* parent: first item is elem of parent (definition or outer elem)
* target: first item is elem in target (for keys of assocs)
* $magic: magic variable (path starts with $magic, see also $self)
* // now values only in queries:
* mixin: first item is mixin
* alias: first item is table alias
* $self: first item is $self or $projection
* source: first item is element in a query source
* query: first item is element of current query
* ref-target: first item is element of target of outer ref item
* (used for filter condition)
* expand: ref is "path continuation" of an EXPAND
* inline: ref is "path continuation" of an INLINE
*/

@@ -150,88 +188,66 @@ function inspectRef( csnPath ) {

function resolveRef( obj, parent, query, scope, baseEnv, main ) {
const queries = cached( main, '$queries', allQueries );
const path = (typeof obj === 'string') ? [ obj ] : obj.ref;
if (!Array.isArray( path ))
throw new Error( 'Value references must look like {ref:[...]}' );
let head = pathId( path[0] );
// 1,2: with 'param' or 'global' property, in `keys`
if (obj.param) {
const head = pathId( path[0] );
if (obj.param)
return expandRefPath( path, main.params[head], 'param' );
const spec = scopeSpecs[scope] || {};
if (spec.global || obj.global)
return expandRefPath( path, csn.definitions[head], 'global', scope === 'from' );
cached( main, '$queries', allQueries );
let qenv = query && cache.get( query.projection || query );
// BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
// CSN and again inspect some refs without calling csnRefs() before!
// WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
if (query && !qenv) {
setCache( main, '$queries', allQueries( main ) );
qenv = cache.get( query.projection || query );
}
else if (obj.global || [ 'type', 'includes', 'target', 'from' ].includes( scope )) {
return expandRefPath( path, csn.definitions[head], scope );
// first the lexical scopes (due to query hierarchy) and $magic: ---------
if (spec.lexical !== false) {
const tryAlias = path.length > 1 || obj.expand || obj.inline;
let env = (qenv && spec.lexical) ? spec.lexical( qenv ) : qenv;
while (env) {
const alias = tryAlias && env.aliases[head];
if (alias)
return expandRefPath( path, alias._select || alias, 'alias', env.$queryNumber );
const mixin = env._select.mixin && env._select.mixin[head];
if (mixin && {}.hasOwnProperty.call( env._select.mixin, head ))
return expandRefPath( path, mixin, 'mixin', env.$queryNumber );
env = env.$next;
}
if (head.charAt(0) === '$') {
if (head !== '$self' && head !== '$projection')
return { scope: '$magic' };
const self = qenv && qenv.$queryNumber > 1 ? qenv._select : main;
return expandRefPath( path, self, '$self' );
}
}
else if (scope === 'keys') {
// now the dynamic environment: ------------------------------------------
if (spec.dynamic === 'target') { // ref in keys
// not selecting the corresponding element for a select column works,
// because explicit keys can only be provided with explicit redirection
// target
const target = csn.definitions[parent.target || parent.cast.target];
return expandRefPath( path, target.elements[head], 'keys' );
return expandRefPath( path, target.elements[head], 'target' );
}
// 3: $magic
if (head.charAt(0) === '$') {
if (head === '$self' || head === '$projection') {
const self = query ? queryOrMain( query, main ) : main;
return expandRefPath( path, self, '$self' );
}
return { scope: '$magic' };
}
// 4: where inside ref, expand, inline
if (baseEnv) {
if (baseEnv) // ref-target (filter condition), expand, inline
return expandRefPath( path, baseEnv.elements[head], scope );
}
// 5,6,7: outside queries, in queries where inferred elements are referred to
if (!query)
return expandRefPath( path, (parent || main).elements[head], scope );
const select = query.SELECT || query.projection;
const obj$env = obj.$env;
if (!select || obj$env === true)
// TODO: do not do this if current query has a parent query (except with obj.$env)
// TODO: also consider expand/inline
return expandRefPath( path, queryOrMain( query, main ).elements[head] );
if (!query) // outside queries - TODO: items?
return expandRefPath( path, parent.elements[head], 'parent' );
// With explicitly provided $env:
if (typeof obj$env === 'number') { // head is mixin or table alias name
const s = (obj$env) ? queries[obj$env - 1] : select;
const m = s.mixin && s.mixin[head];
return expandRefPath( path, m || getCache( s, '_sources' )[head], (m ? 'mixin' : 'alias') );
if (spec.dynamic === 'query')
// TODO: for ON condition in expand, would need to use cached _element
return expandRefPath( path, qenv.elements[head], 'query' );
for (const name in qenv.aliases) {
const found = qenv.aliases[name].elements[head];
if (found)
return expandRefPath( path, found, 'source', name )
}
else if (typeof obj$env === 'string') {
const source = getCache( select, '_sources' )[obj$env];
// Had a case where a obj.$env was the name of a mixin - TODO: should not be - example?
if (source)
return expandRefPath( path, source.elements[head], 'source' );
else if (select.mixin && select.mixin[obj$env])
return expandRefPath( path, select.mixin[head], 'source' );
throw new Error('No source found!');
}
// ON ref is to be searched only in the query elements
if (scope === 'on') // TODO: ok with expand/inline? Probably not with the latter
return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
// 8: try to search in MIXIN section (not in ON of JOINs)
if (scope !== 'from-on' && scope !== 'orderBy' && select.mixin) {
const art = select.mixin[head];
if (art)
return expandRefPath( path, art, 'mixin' );
}
// 9: try to search for table aliases (partially in ON of JOINs)
const alias = getCache( select, '$alias' );
if (path.length > 1 && (alias || scope !== 'from-on')) {
const art = getCache( select, '_sources' )[head];
if (art)
return expandRefPath( path, art, 'alias' );
}
// ORDER BY ref might have been a table alias (CSN not always has an $env),
// otherwise query elements
if (scope === 'orderBy')
return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
// 10: search in elements of source entity
// TODO: do not do this if current query has a parent query !!!
if (typeof alias === 'string') { // with unique source
const source = getCache( select, '_sources' )[alias];
return expandRefPath( path, source.elements[head], 'source' );
}
throw new Error( `Missing helper property $env: ${ scope }` );
// console.log(query.SELECT,qenv,qenv.$next,main)
throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, scope: ${ scope }` );
}

@@ -244,5 +260,8 @@

*/
function expandRefPath( path, art, scope = null ) {
function expandRefPath( path, art, scope, extraInfo ) {
/** @type {{idx, art?, env?}[]} */
const links = path.map( (_v, idx) => ({ idx }) );
// TODO: backends should be changed to enable uncommenting:
// if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
// throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
links[0].art = art;

@@ -262,7 +281,8 @@ for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above

}
const last = path[path.length - 1]
if (scope === 'from' || typeof last !== 'string') {
const last = path[path.length - 1];
const fromRef = scope === 'global' && extraInfo;
if (fromRef || typeof last !== 'string') {
const env = navigationEnv( art );
links[links.length - 1].env = env;
if (scope === 'from')
if (fromRef)
art = env;

@@ -272,3 +292,5 @@ if (typeof last !== 'string')

}
return { links, art, scope };
return (extraInfo && !fromRef)
? { links, art, scope, $env: extraInfo }
: { links, art, scope };
}

@@ -287,25 +309,72 @@

if (!projection)
return all;
traverseQuery( projection, null, function memorize( query, select ) {
return null;
traverseQuery( projection, null, null, function memorize( query, fromSelect, parentQuery ) {
if (query.ref) { // ref in from
// console.log('SQ:',query,cache.get(query))
const as = query.as || implicitAs( query.ref );
getCache( select, '_sources' )[as] = fromRef( query );
const alias = getCache( select, '$alias' ) // alias of unique source
setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as);
getCache( fromSelect, 'aliases' )[as] = fromRef( query );
}
else if (select && query.as) { // sub query in FROM
const { as } = query;
getCache( select, '_sources' )[as] = queryOrMain( query, main );
const alias = getCache( select, '$alias' ) // alias of unique source
setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as); }
const proj = query.SELECT || query.projection;
if (proj) { // every SELECT query -- TODO: remember number?
setCache( proj, '_sources', Object.create(null) );
setCache( proj, '$alias', null );
all.push( proj );
else {
const qenv = initQueryEnv( query !== main && query, parentQuery );
if (fromSelect)
getCache( fromSelect, 'aliases' )[query.as] = qenv;
const select = query.SELECT || query.projection;
if (select) {
cache.set( select, qenv ); // query and query.SELECT have the same cache qenv
qenv._select = select;
all.push( qenv );
}
}
} );
all.forEach( function initElements( qenv, index ) {
qenv.$queryNumber = index + 1;
qenv.elements = (index ? qenv._select : main).elements;
const columns = qenv._select.columns;
if (qenv.elements && columns)
columns.map( c => initColumnElement( c, qenv ) );
} );
return all;
}
function initQueryEnv( query, parentQuery ) {
let qenv; // = query && cache.get( query )
// if (qenv)
// return qenv;
const penv = parentQuery && parentQuery.SET && cache.get( parentQuery );
if (penv && !penv._select) { // SELECT not yet reached
qenv = penv; // for leading query
}
else {
qenv = { aliases: Object.create(null) };
if (parentQuery)
qenv.$next = cache.get( parentQuery );
}
if (query)
cache.set( query, qenv );
return qenv;
}
function initColumnElement( col, parentElementOrQueryEnv ) {
if (col === '*')
return;
if (col.inline) {
col.inline.map( c => initColumnElement( c, parentElementOrQueryEnv ) );
return;
}
setCache( col, '_parent', // not set for query (has property _select)
!parentElementOrQueryEnv._select && parentElementOrQueryEnv );
const as = col.as || col.func || implicitAs( col.ref );
let type = parentElementOrQueryEnv;
while (type.items)
type = type.items;
const elem = setCache( col, '_element', type.elements[as] );
// if requested, we could set a _column link in element
if (col.expand)
col.expand.map( c => initColumnElement( c, elem ) );
}
// property name convention in cache:
// - $name: to other cache object (with proto), dictionary (w/o proto), or scalar
// - _name, name: to CSN object value (_name) or dictionary (name)
function setCache( obj, prop, val ) {

@@ -340,7 +409,6 @@ let hidden = cache.get( obj );

// Return value of a query SELECT for the query, or the main artifact,
// Return value of a query SELECT for the query node, or the main artifact,
// i.e. a value with an `elements` property.
// TODO: avoid the term Query, use QuerySelect or QueryNode
/**
* @param {CSN.Query} query
* @param {CSN.Query} query node (object with SET or SELECT property)
* @param {CSN.Definition} main

@@ -365,22 +433,28 @@ */

/**
* Traverse query in pre-order
*
* @param {CSN.Query} query
* @param {CSN.QuerySelect} select
* @param {CSN.QuerySelect} fromSelect
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback
*/
function traverseQuery( query, select, callback ) {
function traverseQuery( query, fromSelect, parentQuery, callback ) {
if (query.SELECT || query.projection) {
callback( query, select );
query = query.SELECT || query.projection;
traverseFrom( query.from, query, callback );
callback( query, fromSelect, parentQuery );
const select = query.SELECT || query.projection;
traverseFrom( select.from, select, parentQuery, callback );
for (const prop of [ 'columns', 'where', 'having' ]) {
// all properties which could have sub queries
const expr = select[prop];
if (expr)
expr.forEach( q => traverseExpr( q, query, callback ) );
}
}
else if (query.SET) {
callback( query, select );
query = query.SET;
}
for (const prop of [ 'args', 'xpr', 'columns', 'where', 'having' ]) {
// all properties which could have sub queries (directly or indirectly)
const expr = query[prop];
if (expr && typeof expr === 'object') {
const args = Array.isArray( expr ) ? expr : Object.values( expr );
args.forEach( q => traverseQuery( q, null, callback ) );
callback( query, fromSelect, parentQuery );
const { args } = query.SET;
for (const q of args || []) {
if (q === args[0]) // leading query
traverseQuery( q, fromSelect, query, callback );
else
traverseQuery( q, null, query, callback );
}

@@ -395,16 +469,29 @@ }

*/
function traverseFrom( from, select, callback ) {
function traverseFrom( from, fromSelect, parentQuery, callback ) {
if (from.ref) {
callback( from, select );
callback( from, fromSelect, parentQuery );
}
else if (from.args) { // join
from.args.forEach( arg => traverseFrom( arg, select, callback ) );
if (from.on) // join
from.on.forEach( arg => traverseQuery( arg, select, callback ) );
from.args.forEach( arg => traverseFrom( arg, fromSelect, parentQuery, callback ) );
if (from.on) // join-on, potentially having a sub query
from.on.forEach( arg => traverseQuery( arg, null, fromSelect, callback ) );
}
else { // sub query in FROM
traverseQuery( from, select, callback );
traverseQuery( from, fromSelect, parentQuery, callback );
}
}
function traverseExpr( expr, parentQuery, callback ) {
if (expr.SELECT || expr.SET)
traverseQuery( expr, null, parentQuery, callback )
for (const prop of [ 'args', 'xpr' ]) {
// all properties which could have sub queries (directly or indirectly),
const val = expr[prop];
if (val && typeof val === 'object') {
const args = Array.isArray( val ) ? val : Object.values( val );
args.forEach( e => traverseExpr( e, parentQuery, callback ) );
}
}
}
function pathId( item ) {

@@ -457,7 +544,6 @@ return (typeof item === 'string') ? item : item.id;

isName = true; // for named arguments
if (scope === 'orderBy')
scope = 'orderBy-xpr'; // no need to extra 'orderBy-args'
}
else if (prop === 'SELECT' || prop === 'SET' || prop === 'projection') {
query = obj;
parent = null;
scope = prop;

@@ -469,18 +555,21 @@ }

csn.definitions[csnPath[1]] );
scope = 'ref-where';
scope = 'ref-target';
}
else if ((prop === 'expand' || prop === 'inline') && obj.ref) {
if (obj.ref && resolve) {
baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
csn.definitions[csnPath[1]] );
else if (prop === 'expand' || prop === 'inline') {
if (obj.ref) {
if (resolve)
baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
csn.definitions[csnPath[1]] );
scope = prop;
}
scope = prop;
if (prop === 'expand')
isName = prop;
}
else if (prop === 'on') {
if (scope === 'from')
scope = 'from-on';
scope = 'join-on';
else if (scope === 'mixin')
scope = 'mixin-on';
else
scope = 'on';
scope = 'on'; // will use query elements with REDIRECTED TO
}

@@ -491,8 +580,8 @@ else if (prop === 'ref') {

}
else if (prop === 'orderBy') {
scope = (query.SET ? 'orderBy-set' : 'orderBy');
}
else if (prop !== 'xpr') {
scope = prop;
}
else if (scope === 'orderBy') {
scope = 'orderBy-xpr';
}

@@ -499,0 +588,0 @@ obj = obj[prop];

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

const { csnRefs } = require('../model/csnRefs');
const { sortCsn } = require('../json/to-csn');
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
const version = require('../../package.json').version;

@@ -56,3 +56,3 @@

getServiceName,
hasBoolAnnotation,
hasAnnotationValue,
cloneWithTransformations,

@@ -431,5 +431,6 @@ getFinalBaseType,

* Deeply clone the given CSN model and return it.
* In testMode, definitions are sorted.
* In testMode (or with testSortCsn), definitions are sorted.
* Note that annotations are only copied shallowly.
*
* @param {object} csn
* @param {object} csn Top-level CSN. You can pass non-dictionary values.
* @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`

@@ -442,2 +443,16 @@ */

/**
* Deeply clone the given CSN dictionary and return it.
* Note that annotations are only copied shallowly.
* This function does _not_ sort the given dictionary.
* See cloneCsn() if you want sorted definitions.
*
* @param {object} csn
* @param {CSN.Options} options Only cloneOptions.dictionaryPrototype is
* used and cloneOptions are passed to sort().
*/
function cloneCsnDictionary(csn, options) {
return _cloneCsnDictionary(csn, options);
}
/**
* Apply function `callback` to all artifacts in dictionary

@@ -533,3 +548,5 @@ * `model.definitions`. See function `forEachGeneric` for details.

const dictObj = dict[name];
if(iterateOptions.skip && iterateOptions.skip.includes(dictObj.kind))
if((iterateOptions.skip && iterateOptions.skip.includes(dictObj.kind))
|| (iterateOptions.skipArtifact && typeof iterateOptions.skipArtifact === 'function'
&& iterateOptions.skipArtifact(dictObj, name)))
continue;

@@ -690,11 +707,26 @@ cb( dictObj, name );

/**
* Returns true if the element has a specific annotation set to the given value.
* Compare a given annotation value with an expectation value and return
*
* | Expected
* | true | false | null | arb val
* Anno Val |-------|-------|-------|--------
* true | true | false | false | false
* false | false | true | false | false
* null | false | true | true | false
* arb val | false | false | false | true/false
*
* If the annotation value is 'null', 'true' is returned for the expectation values
* 'null' and 'false'. Expecting 'null' for an annotation value 'false' returns
* 'false'.
*
* @param {CSN.Artifact} artifact
* @param {string} annotationName Name of the annotation (including the at-sign)
* @param {any} value
* @param {any} expected
* @returns {boolean}
*/
function hasBoolAnnotation(artifact, annotationName, value = true) {
return artifact[annotationName] === value;
function hasAnnotationValue(artifact, annotationName, expected = true) {
if(expected === false)
return artifact[annotationName] === expected || artifact[annotationName] === null;
else
return artifact[annotationName] === expected;
}

@@ -1056,3 +1088,3 @@

function isPersistedOnDatabase(art) {
return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract || hasBoolAnnotation(art, '@cds.persistence.skip')));
return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
}

@@ -1170,2 +1202,3 @@

// TODO: to be checked by author: still intended behaviour with 'cds' prefix?
// TODO: Can this be replaced by getRootArtifactName? Or maybe not rely on namespace-hacking...
// FIXME: This only works with namespace-hacking, i.e. adding them as artifacts...

@@ -1278,3 +1311,3 @@ function getTopLevelArtifactNameOf(name, model) {

if (prop.startsWith('@')) {
if (toNode[prop] == undefined || overwrite) {
if (toNode[prop] === undefined || overwrite) {
toNode[prop] = fromNode[prop];

@@ -1286,11 +1319,2 @@ }

// Return true if 'type' is a composition type
function isComposition(type) {
if (!type)
return type;
if (type._artifact)
type = type._artifact.name;
return type.absolute === 'cds.Composition';
}
function isAspect(node) {

@@ -1318,56 +1342,4 @@ return node && node.kind === 'aspect';

// Add an annotation with absolute name 'absoluteName' (including '@') and string value 'theValue' to 'node'
function addBoolAnnotationTo(absoluteName, theValue, node) {
// Sanity check
if (!absoluteName.startsWith('@')) {
throw Error('Annotation name should start with "@": ' + absoluteName);
}
// Assemble the annotation
if(isAnnotationAssignable(node, absoluteName)) {
node[absoluteName] = {
name: {
absolute: absoluteName.substring(1),
location: node.location, // inherit location from main element
},
val: theValue,
literal: 'boolean',
location: node.location, // inherit location from main element
};
}
}
// Add an annotation with absolute name 'absoluteName' (including '@') and array 'theValue' to 'node'
function addArrayAnnotationTo(absoluteName, theValue, node) {
// Sanity check
if (!absoluteName.startsWith('@')) {
throw Error(`Annotation name should start with "@": ${ absoluteName}`);
}
// Assemble the annotation
if(isAnnotationAssignable(node, absoluteName)) {
node[absoluteName] = {
name: {
absolute: absoluteName.substring(1),
location: node.location, // inherit location from main element
},
val: theValue,
literal: 'array',
location: node.location, // inherit location from main element
};
}
}
/**
* Check wether an annotation can be assigned or not
* (must be either undefined or null value)
* @param {object} node Assignee
* @param {string} name Annotation name
* @returns {boolean}
*/
function isAnnotationAssignable(node, name) {
if (!name.startsWith('@')) {
throw Error(`Annotation name should start with "@": ${ name}`);
}
return (node[name] === undefined || node[name] && node[name].val === null);
}
/**
* Return true if the artifact has a valid, truthy persistence.exists/skip annotation

@@ -1380,3 +1352,3 @@ *

return (artifact.kind === 'entity' || artifact.kind === 'view') &&
(hasBoolAnnotation(artifact, '@cds.persistence.exists', true) || hasBoolAnnotation(artifact, '@cds.persistence.skip', true))
(hasAnnotationValue(artifact, '@cds.persistence.exists', true) || hasAnnotationValue(artifact, '@cds.persistence.skip', true))

@@ -1435,3 +1407,3 @@ }

*
* @param {CSN.Model} csn
* @param {CSN.Model} csn
* @returns {CSN.Service[]}

@@ -1453,2 +1425,3 @@ */

cloneCsn,
cloneCsnDictionary,
isBuiltinType,

@@ -1463,3 +1436,3 @@ assignAll,

forAllElements,
hasBoolAnnotation,
hasAnnotationValue,
isEdmPropertyRendered,

@@ -1483,7 +1456,4 @@ getArtifactDatabaseNameOf,

copyAnnotations,
isComposition,
isAspect,
forEachPath,
addBoolAnnotationTo,
addArrayAnnotationTo,
hasValidSkipOrExists,

@@ -1490,0 +1460,0 @@ getNamespace,

@@ -35,3 +35,3 @@ // For testing: reveal non-enumerable properties in CSN, display result of csnRefs

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

@@ -48,6 +48,8 @@ definitions: dictionary,

includes: simpleRef,
'@': () => {},
// TODO: excluding
'@': () => { /* ignore annotations */ },
}
setLocations( csn, false, null );
const { inspectRef, artifactRef } = csnRefs( csn );
const { inspectRef, artifactRef, __getCache_forEnrichCsnDebugging } = csnRefs( csn );
let $$cacheObjectNumber = 0; // for debugging
const csnPath = [];

@@ -58,2 +60,4 @@ if (csn.definitions)

reveal( csn, '$location', locationString( csn.$location ) );
if (csn.$sources)
reveal( csn, '$sources', csn.$sources );
return csn;

@@ -74,2 +78,5 @@

}
if (obj.$parens)
reveal( obj, '$parens', obj.$parens );
_cache_debug( obj );
}

@@ -120,3 +127,3 @@ csnPath.pop();

function pathRef( parent, prop, path ) {
const { links, art, scope } = (() => {
const { links, art, scope, $env } = (() => {
if (options.testMode)

@@ -138,2 +145,4 @@ return inspectRef( csnPath );

parent._scope = scope;
if ($env)
parent._env = $env;

@@ -153,2 +162,47 @@ csnPath.push( prop );

}
function _cache_debug( obj ) {
if (options.enrichCsn !== 'DEBUG')
return;
const cache = __getCache_forEnrichCsnDebugging( obj );
if (!cache)
return;
if (cache.$$objectNumber > 0) {
obj.$$cacheObjectNumber = cache.$$objectNumber;
}
else {
cache.$$objectNumber = (cache.$$objectNumber)
? -cache.$$objectNumber
: ++$$cacheObjectNumber;
obj.$$cacheObject = {};
for (const name of Object.keys( cache )) {
const val = cache[name];
if (val === null || typeof val !== 'object') {
obj.$$cacheObject[name] = val;
}
else if (name[0] === '_') {
// _‹name›: link to CSN node, usually with kind & location
obj.$$cacheObject[name]
= (val.$location) ? locationString( val.$location ) : 'CSN node';
}
else if (name[0] !== '$' || !Object.getPrototypeOf( val )) {
// ‹name›: dictionary of CSN nodes,
// ‹$name›: dictionary of cache values if no prototype,
obj.$$cacheObject[name] = Object.keys( val ); // TODO: or dict?
}
else if (Array.isArray( val )) {
obj.$$cacheObject[name] = val.map( item => {
if (!item.$$objectNumber)
item.$$objectNumber = -(++$$cacheObjectNumber);
return item.$$objectNumber;
} );
}
else {
if (!val.$$objectNumber)
val.$$objectNumber = -(++$$cacheObjectNumber);
obj.$$cacheObject[name] = val.$$objectNumber || -(++$$cacheObjectNumber);
}
}
}
}
}

@@ -155,0 +209,0 @@

'use strict';
const { makeMessageFunction } = require('../base/messages');
const {
forEachDefinition,
forEachMember,
hasBoolAnnotation
hasAnnotationValue
} = require('../model/csnUtils');

@@ -14,6 +15,10 @@

* @param afterModel the after-model
* @param {hdiOptions|false} options
* @returns {object} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model
* to the after-model, together with all the definitions of the after-model
*/
function compareModels(beforeModel, afterModel) {
function compareModels(beforeModel, afterModel, options) {
if(!(options && options.testMode)) // no $version with testMode
validateCsnVersions(beforeModel, afterModel, options);
const deletedEntities = Object.create(null);

@@ -35,2 +40,25 @@ const elementAdditions = [];

function validateCsnVersions(beforeModel, afterModel, options) {
const beforeVersion = beforeModel.$version;
const afterVersion = afterModel.$version;
let beforeVersionParts = beforeVersion && beforeVersion.split('.');
let afterVersionParts = afterVersion && afterVersion.split('.');
if (!beforeVersionParts || beforeVersionParts.length < 2) {
const { error, throwWithError } = makeMessageFunction(beforeModel, options, 'modelCompare');
error(null, null, `Invalid CSN version: ${beforeVersion}`);
throwWithError();
}
if (!afterVersionParts || afterVersionParts.length < 2) {
const { error, throwWithError } = makeMessageFunction(afterModel, options, 'modelCompare');
error(null, null, `Invalid CSN version: ${afterVersion}`);
throwWithError();
}
if (beforeVersionParts[0] > afterVersionParts[0] && !(options && options.allowCsnDowngrade)) {
const { error, throwWithError } = makeMessageFunction(afterModel, options, 'modelCompare');
error(null, null, `Incompatible CSN versions: ${afterVersion} is a major downgrade from ${beforeVersion}. Is @sap/cds-compiler version ${require('../../package.json').version} outdated?`);
throwWithError();
}
}
function getArtifactComparator(otherModel, addedEntities, deletedEntities, elementAdditions, elementChanges) { // (, alerts)

@@ -111,5 +139,5 @@ return function compareArtifacts(artifact, name) { // (, topKey, path) topKey == 'definitions'

&& !artifact.abstract
&& (!artifact.query && !artifact.projection || hasBoolAnnotation(artifact, '@cds.persistence.table'))
&& !hasBoolAnnotation(artifact, '@cds.persistence.skip')
&& !hasBoolAnnotation(artifact, '@cds.persistence.exists');
&& (!artifact.query && !artifact.projection || hasAnnotationValue(artifact, '@cds.persistence.table'))
&& !hasAnnotationValue(artifact, '@cds.persistence.skip')
&& !hasAnnotationValue(artifact, '@cds.persistence.exists');
}

@@ -116,0 +144,0 @@

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

.option(' --parse-only')
.option(' --fallback-parser <type>', ['cdl', 'csn'])
.option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])
.option(' --test-mode')

@@ -115,4 +115,5 @@ .option(' --test-sort-csn')

parser as a fallback. Valid values are:
cdl : Use CDL parser
csn : Use CSN parser
cdl : Use CDL parser
csn : Use CSN parser
csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
--direct-backend Do not compile the given CSN but directly pass it to the backend.

@@ -184,4 +185,2 @@ Can only be used with certain new CSN based backends. Combination with

.option('-j, --json')
.option(' --separate')
.option(' --combined')
.option(' --odata-containment')

@@ -208,4 +207,2 @@ .option(' --odata-proxies')

-j, --json Generate JSON output as "<svc>.json" (not available for v2)
--separate Generate "<svc>_metadata.xml" and "<svc>_annotations.xml"
--combined (default) Generate "<svc>.xml"
-c, --csn Generate "odata_csn.json" with ODATA-preprocessed model

@@ -212,0 +209,0 @@ -f, --odata-format <format> Set the format of the identifier rendering

@@ -10,3 +10,3 @@

const {
renderFunc, processExprArray, cdsToSqlTypes,
renderFunc, processExprArray, cdsToSqlTypes, getHanaComment, hasHanaComment,
} = require('./utils/common');

@@ -399,2 +399,12 @@ const {

changeElementsDuplicateChecker.addElement(sqlId, undefined, eltName);
const eltStrOld = def.old.target
? renderAssociationElement(eltName, def.old, env)
: renderElement(artifactName, eltName, def.old, null, null, env);
const eltStrNew = def.new.target
? renderAssociationElement(eltName, def.new, env)
: renderElement(artifactName, eltName, def.new, null, null, env);
if (eltStrNew === eltStrOld)
return; // Prevent spurious migrations, where the column DDL does not change.
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {

@@ -413,4 +423,3 @@ // Lossy change because either an association is removed and/or added, or the type size is reduced.

// Lossless change: no associations directly affected, no size reduction.
const eltStr = renderElement(artifactName, eltName, def.new, null, null, env);
addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStr));
addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStrNew));
}

@@ -431,2 +440,3 @@ }

function renderEntityInto(artifactName, art, resultObj, env) {
env._artifact = art;
const childEnv = increaseIndent(env);

@@ -519,2 +529,5 @@ const hanaTc = art.technicalConfig && art.technicalConfig.hana;

if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
result += ` COMMENT '${getHanaComment(art)}'`;
resultObj.hdbtable[artifactName] = result;

@@ -618,2 +631,5 @@ }

if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options, env._artifact))
result += ` COMMENT '${getHanaComment(elm)}'`;
return result;

@@ -1000,4 +1016,9 @@ }

let result = `VIEW ${viewName}`;
if (options.toSql.dialect === 'hana' && hasHanaComment(art, options, art))
result += ` COMMENT '${getHanaComment(art)}'`;
result += renderParameterDefinitions(artifactName, art.params);
result += ` AS ${renderQuery(artifactName, getNormalizedQuery(art).query, env)}`;
const childEnv = increaseIndent(env);

@@ -1012,2 +1033,3 @@ const associations = Object.keys(art.elements).filter(name => !!art.elements[name].target)

}
return result;

@@ -1014,0 +1036,0 @@ }

@@ -21,2 +21,5 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

const { implicitAs } = require('../../model/csnRefs');
/**

@@ -288,2 +291,22 @@ * Render the given function

/**
* Get the element matching the column
*
* @param {CSN.Elements} elements Elements of a query
* @param {CSN.Column} column Column from the same query
* @returns {CSN.Element}
*/
function findElement(elements, column) {
if (!elements)
return undefined;
if (column.as)
return elements[column.as];
else if (column.ref)
return elements[implicitAs(column.ref)];
else if (column.func)
return elements[column.func];
return undefined;
}
/**
* If there is a context A and a context A.B.C without a definition A.B, create an

@@ -319,2 +342,27 @@ * intermediate context A.B to keep the context hierarchy intact.

/**
* Check wether the given artifact or element has a comment that needs to be rendered.
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
*
* @param {CSN.Artifact} obj
* @param {CSN.Options} options To check for `disableHanaComments`
* @param {CSN.Artifact} artifact Artifact containing obj (in case of elem or columns)
* @returns {boolean}
*/
function hasHanaComment(obj, options, artifact = {}) {
return !artifact['@cds.persistence.journal'] && !options.disableHanaComments && typeof obj.doc === 'string';
}
/**
* Return the comment of the given artifact or element.
* Uses the first block (everything up to the first empty line (double \n)).
* Remove leading/trailing whitespace.
*
* @param {CSN.Artifact|CSN.Element} obj
* @returns {string}
* @todo Warning/info to user?
*/
function getHanaComment(obj) {
return obj.doc.split('\n\n')[0].trim().replace(/'/g, "''");
}
/**
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.

@@ -343,2 +391,5 @@ *

cdsToSqlTypes,
hasHanaComment,
getHanaComment,
findElement,
};
'use strict';
const { handleMessages, makeMessageFunction } = require('../base/messages');
const { makeMessageFunction } = require('../base/messages');
const { isDeprecatedEnabled } = require('../base/model');

@@ -80,11 +80,2 @@ const transformUtils = require('./transformUtilsNew');

addLocalizationViews(csn, options);
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
forEachDefinition(csn, (artifact, artifactName) => {
if (hasLocalizedConvenienceView(csn, artifactName))
artifact.$localized = true;
else if (!keepLocalizedViews && isInLocalizedNamespace(artifactName))
delete csn.definitions[artifactName];
});
// the new transformer works only with new CSN

@@ -103,3 +94,3 @@ checkCSNVersion(csn, options);

recurseElements, setAnnotation, renameAnnotation,
expandStructsInOnConditions
expandStructsInExpression
} = transformers;

@@ -112,3 +103,3 @@

getServiceName,
hasBoolAnnotation,
hasAnnotationValue,
isAssocOrComposition,

@@ -128,6 +119,21 @@ isAssociation,

// use the array when there is a need to identify if an artifact is in a service or not
let services = getServiceNames(csn);
const services = getServiceNames(csn);
// @ts-ignore
const externalServices = services.filter(serviceName => csn.definitions[serviceName]['@cds.external']);
// @ts-ignore
const isExternalServiceMember = (_art, name) => externalServices.includes(getServiceName(name));
addLocalizationViews(csn, options);
const keepLocalizedViews = isDeprecatedEnabled(options, 'createLocalizedViews');
forEachDefinition(csn, (artifact, artifactName) => {
if (hasLocalizedConvenienceView(csn, artifactName))
artifact.$localized = true;
else if (!keepLocalizedViews && isInLocalizedNamespace(artifactName))
delete csn.definitions[artifactName];
}, { skipArtifact: isExternalServiceMember });
validate.forOdata(csn, {
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
});

@@ -137,3 +143,3 @@

// Throw exception in case of errors
handleMessages(csn, options);
throwWithError();

@@ -151,12 +157,13 @@ // Semantic checks before flattening regarding temporal data

}
}]
}],
{ skipArtifact: isExternalServiceMember }
);
expandToFinalBaseType(csn, transformers, csnUtils, services, options);
expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember);
// Check if structured elements and managed associations are compared in an ON condition
// Check if structured elements and managed associations are compared in an expression
// and expand these structured elements. This tuple expansion allows all other
// subsequent procession steps (especially a2j) to see plain paths in ON conditions.
// If errors are detected, handleMessages will return from further processing
forEachDefinition(csn, expandStructsInOnConditions);
// subsequent procession steps (especially a2j) to see plain paths in expressions.
// If errors are detected, throwWithError() will return from further processing
forEachDefinition(csn, expandStructsInExpression, { skipArtifact: isExternalServiceMember });

@@ -172,3 +179,4 @@ // handles reference flattening

// flatten structures
flattenCSN(csn, csnUtils, options, referenceFlattener, error);
// @ts-ignore
flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember);
// flatten references

@@ -179,3 +187,3 @@ referenceFlattener.flattenAllReferences(csn);

// structure flattener reports errors, further processing is not safe -> throw exception in case of errors
handleMessages(inputModel, options);
throwWithError();

@@ -187,5 +195,5 @@ // Process associations

if (!structuredOData)
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils);
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils, isExternalServiceMember);
// 2. generate foreign keys for managed associations
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error);
generateForeignKeys(csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember);

@@ -196,3 +204,3 @@ // Apply default type facets as set by options

// composition targets are annotated with @odata.draft.enabled in this step
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ]);
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ], { skipArtifact: isExternalServiceMember });

@@ -214,7 +222,3 @@ // Now all artificially generated things are in place

// Ignore if not part of a service
if (!isArtifactInSomeService(defName, services)) {
warning(null, ['definitions', defName], { art: defName },
'Ignoring annotation “@odata.draft.enabled” because artifact $(ART) is not part of a service');
}
else {
if (isArtifactInSomeService(defName, services)) {
generateDraftForOdata(def, defName, def, visitedArtifacts);

@@ -225,3 +229,3 @@ }

visitedArtifacts[defName] = true;
});
}, { skipArtifact: isExternalServiceMember });

@@ -259,2 +263,3 @@ // Deal with all kind of annotations manipulations here

// - Check for @Analytics.Measure and @Aggregation.default
// @ts-ignore
if (isArtifactInSomeService(defName, services) || isLocalizedArtifactInService(defName, services)) {

@@ -274,6 +279,6 @@ // If the member is an association and the target is annotated with @cds.odata.valuelist,

}
})
}, { skipArtifact: isExternalServiceMember })
// Throw exception in case of errors
handleMessages(csn, options);
throwWithError();

@@ -285,2 +290,3 @@ if (options.testMode) csn = cloneCsn(csn, options); // sort, keep hidden properties

// TODO: Move this to checks?
// @ts-ignore
function checkTemporalAnnotationsAssignment(artifact, artifactName, propertyName, path) {

@@ -422,2 +428,3 @@ // Gather all element names with @cds.valid.from/to/key

forEachMemberRecursively(def, (member) => {
// @ts-ignore
if (member.type && isAssocOrComposition(member.type) && member.on) {

@@ -449,2 +456,3 @@ removeLeadingDollarSelfInOnCondition(member);

// Sanity check
// @ts-ignore
if (!isArtifactInSomeService(artifactName, services)) {

@@ -460,5 +468,7 @@ throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));

// Generate the DraftAdministrativeData projection into the service, unless there is already one
// @ts-ignore
let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
if (!draftAdminDataProjection) {
// @ts-ignore
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));

@@ -546,14 +556,9 @@ }

// Barf if the draft node has @odata.draft.enabled itself
if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', true)) {
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
}
// Ignore composition if not part of a service
else if (!getServiceName(elem.target)) {
warning(null, ['definitions', artifactName, 'elements', elemName], { target: elem.target },
'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
// Ignore composition if not part of a service or explicitly draft disabled
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
return;
}
else if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', false)) {
return;
}
else {

@@ -560,0 +565,0 @@ // Generate draft stuff into the target

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

const { hasErrors } = require('../base/messages');
const { cloneCsnDictionary } = require('../json/to-csn');
const { cloneCsnDictionary } = require('../model/csnUtils');
const { cleanSymbols } = require('../base/cleanSymbols.js');

@@ -161,3 +161,3 @@ const {

},
elements: cloneCsnDictionary(entity.elements, false, options),
elements: cloneCsnDictionary(entity.elements, options),
[_isViewForEntity]: true,

@@ -192,3 +192,3 @@ };

if (!shouldUseJoin) {
return createColumnRef( [ entityName ], 'L', false );
return createColumnRef( [ entityName ], 'L');
}

@@ -199,3 +199,3 @@

args: [
createColumnRef( [ entityName ], 'L_0', false ),
createColumnRef( [ entityName ], 'L_0'),
createColumnRef( [ textsEntityName(entityName) ], 'localized_1' ),

@@ -243,3 +243,3 @@ ],

convenienceView.elements = cloneCsnDictionary(view.elements, false, options);
convenienceView.elements = cloneCsnDictionary(view.elements, options);
convenienceView[_isViewForView] = true;

@@ -253,3 +253,3 @@ copyLocation(convenienceView, view);

if (view.params)
convenienceView.params = cloneCsnDictionary(view.params, false, options);
convenienceView.params = cloneCsnDictionary(view.params, options);

@@ -345,3 +345,3 @@ return convenienceView;

info( null, artPath, { name: artName },
`Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements` );
'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );
return null;

@@ -355,3 +355,3 @@ }

info( null, artPath, { name: artName },
`Skipped creation of convenience view for $(NAME) because its texts entity could not be found` );
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );
return null;

@@ -362,3 +362,3 @@ }

info( null, [ 'definitions', textsName ], { name: artName },
`Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid` );
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );
return null;

@@ -619,9 +619,6 @@ }

* @param {string} [as] Alias for path.
* @param {boolean} [withEnv=true] If true, add `$env:1`
* @return {CSN.Column}
*/
function createColumnRef(ref, as = null, withEnv = true) {
function createColumnRef(ref, as = null) {
const column = { ref };
if (withEnv)
setProp(column, '$env', 1);
if (as)

@@ -647,5 +644,3 @@ column.as = as;

.map(elementName => {
const column = { ref: [ entityName, elementName ] }
setProp(column, '$env', 1);
return column;
return { ref: [ entityName, elementName ] };
});

@@ -652,0 +647,0 @@ }

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

*/
module.exports = function (csn, referenceFlattener, csnUtils) {
module.exports = function (csn, referenceFlattener, csnUtils, isExternalServiceMember) {

@@ -27,3 +27,3 @@ forEachManagedAssociation(csn, (element) => {

}
});
}, isExternalServiceMember);

@@ -30,0 +30,0 @@ // update paths and resolve references

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

*/
module.exports = function (csn, options, referenceFlattener, csnUtils, error) {
module.exports = function (csn, options, referenceFlattener, csnUtils, error, isExternalServiceMember) {

@@ -28,3 +28,3 @@ const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';

// sort all associations by their dependencies
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener);
const sortedAssociations = sortByAssociationDependency(csn, referenceFlattener, isExternalServiceMember);

@@ -31,0 +31,0 @@ // generate foreign keys

@@ -212,3 +212,6 @@ const { forEachRef } = require('../../model/csnUtils');

// is there a better way to detect if we are dealing with on-cond?
if (!['on', '$self', null].includes(node.$scope)) return;
// TODO: please do not use inspectRef() results for "where am I?" info
if (!['$self', 'parent'].includes(node.$scope)) return;
// the above condition means: only handle references starting with
// $self or element references outside queries

@@ -215,0 +218,0 @@ let { links } = inspectRef(path);

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

function buildDependenciesFor(csn, referenceFlattener) {
function buildDependenciesFor(csn, referenceFlattener, isExternalServiceMember) {

@@ -51,3 +51,3 @@ let dependencies = {};

}) // forEachMemberRecursively
}) // forEachDefinition
}, { skipArtifact: isExternalServiceMember }) // forEachDefinition

@@ -54,0 +54,0 @@ let result = []; // final list of sorted association items

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

* Each generated element gets hidden attributes:
* - $viaTransform - states that the element was generated during transformation
* - _flatElementNameWithDots - names in the element path concatenated with dot

@@ -63,5 +62,5 @@ * @param {CSN.Model} csn CSN-object to flatten

*/
function flattenCSN(csn, csnUtils, options, referenceFlattener, error) {
function flattenCSN(csn, csnUtils, options, referenceFlattener, error, isExternalServiceMember) {
forEachDefinition(csn, (def, defName, propertyName, path) =>
flattenDefinition(def, path, csnUtils, options, referenceFlattener, error));
flattenDefinition(def, path, csnUtils, options, referenceFlattener, error), { skipArtifact: isExternalServiceMember });
}

@@ -166,3 +165,2 @@

if (!isTopLevelElement) {
setProp(newElement, '$viaTransform', true);
setProp(newElement, '_flatElementNameWithDots', elementNameWithDots);

@@ -169,0 +167,0 @@ }

'use strict';
const {
cloneCsn, forEachDefinition,
forEachMemberRecursively, isBuiltinType,
forEachDefinition, forEachMemberRecursively,
isBuiltinType, cloneCsnDictionary,
} = require('../../model/csnUtils');
const { isArtifactInSomeService, isArtifactInService } = require('./utils');
function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
const isV4 = options.toOdata.version === 'v4';

@@ -63,3 +63,3 @@ forEachDefinition(csn, (def, defName) => {

}
});
}, { skipArtifact: isExternalServiceMember });

@@ -74,3 +74,3 @@ // In case we have in the model something like:

if (csnUtils.isStructured(finalType)) {
def.items.elements = cloneCsn(finalType.elements, options);
def.items.elements = cloneCsnDictionary(finalType.elements, options);
delete def.items.type;

@@ -77,0 +77,0 @@ }

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

const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember } = require('../../model/csnUtils');
const { cloneCsn, isBuiltinType, forEachDefinition, forEachMember, cloneCsnDictionary } = require('../../model/csnUtils');
const { copyAnnotations } = require('../../model/csnUtils');

@@ -171,3 +171,3 @@

* in case that the element has a named type.
*
*
* @param {string} typeName type of the element

@@ -201,3 +201,3 @@ * @param {string} service current service name

* in case that the element has an anonymous type.
*
*
* @param {string} typeName type of the element

@@ -218,3 +218,3 @@ * @param {string} parentName name of the parent def holding the element

* Tf does not exists, create a context with the given name in the CSN
* @param {string} name
* @param {string} name
*/

@@ -330,3 +330,3 @@ function createSchema(name) {

if (csnUtils.isStructured(finalType)) {
if (!def.items.elements) def.items.elements = cloneCsn(finalType.elements, options);
if (!def.items.elements) def.items.elements = cloneCsnDictionary(finalType.elements, options);
delete def.items.type;

@@ -333,0 +333,0 @@ }

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

function forEachManagedAssociation(csn, callback) {
function forEachManagedAssociation(csn, callback, isExternalServiceMember) {

@@ -34,3 +34,3 @@ forEachDefinition(csn, (def) => {

})
})
}, { skipArtifact: isExternalServiceMember });

@@ -37,0 +37,0 @@ }

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

getFinalBaseType,
hasBoolAnnotation,
hasAnnotationValue,
inspectRef,

@@ -66,3 +66,3 @@ isStructured,

setAnnotation,
expandStructsInOnConditions,
expandStructsInExpression,
};

@@ -273,3 +273,2 @@

delete flatElem.notNull;
setProp(flatElem, '$viaTransform', true); // FIXME: This name is not ideal but used elsewhere, too)
setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.'));

@@ -309,3 +308,4 @@ addGeneratedFlattenedElement(flatElem, flatElemName);

} catch (e) {
if (e.message && e.message === "Scope 'ref-where' but no entity was provided.") {
if (e.message && e.message === "Scope 'ref-target' but no entity was provided.") {
// TODO: should not happen anymore
return flatten(ref, path);

@@ -385,2 +385,4 @@ } else {

if (finalBaseType.elements) {
// ATTENTION: This should be cloneCsnDictionary() but that would
// change the foreign key order in many tests!
node.elements = cloneCsn(finalBaseType.elements, options); // copy elements

@@ -773,9 +775,9 @@ delete node.type; // delete the type reference as edm processing does not expect it

let validFroms = [], validTos = [], validKeys = [];
if (hasBoolAnnotation(element, '@cds.valid.from')) {
if (hasAnnotationValue(element, '@cds.valid.from')) {
validFroms.push({ element, path: [...path] });
}
if (hasBoolAnnotation(element, '@cds.valid.to')) {
if (hasAnnotationValue(element, '@cds.valid.to')) {
validTos.push({ element, path: [...path] });
}
if (hasBoolAnnotation(element, '@cds.valid.key')) {
if (hasAnnotationValue(element, '@cds.valid.key')) {
validKeys.push({ element, path: [...path] });

@@ -865,3 +867,3 @@ }

}
if (annotation == undefined) {
if (annotation === undefined) {
throw Error('Annotation ' + fromName + ' not found in ' + JSON.stringify(node));

@@ -1015,9 +1017,10 @@ }

/*
* Expand structured ON condition arguments to flat reference paths.
* Expand structured expression arguments to flat reference paths.
* Structured elements are real sub element lists and managed associations.
* All unmanaged association definitions are rewritten if applicable (elements/mixins).
* Also, HAVING and WHERE clauses are rewritten.
*
* TODO: Check if can be skipped for abstract entity and or cds.persistence.skip ?
*/
function expandStructsInOnConditions(artifact, artifactName, prop, path) {
function expandStructsInExpression(artifact, artifactName, prop, path) {
forEachMemberRecursively(artifact,

@@ -1033,10 +1036,15 @@ (elem, elemName, prop, path) => {

if(artifact.query) {
forAllQueries(artifact.query, (query) => {
if(query.SELECT && query.SELECT.mixin) {
forEachGeneric(query.SELECT, 'mixin', (mixin, mixinName, prop, path) => {
if(mixin.target && mixin.on) {
mixin.on = expand(mixin.on, path);
}
},
forAllQueries(artifact.query, (query, queryPath) => {
if(query.SELECT) {
if(query.SELECT.mixin)
forEachGeneric(query.SELECT, 'mixin', (mixin, mixinName, prop, path) => {
if(mixin.target && mixin.on) {
mixin.on = expand(mixin.on, path);
}
},
path);
if(query.SELECT.having)
query.SELECT.having = expand(query.SELECT.having, queryPath.concat(['SELECT', 'having']))
if(query.SELECT.where)
query.SELECT.where = expand(query.SELECT.where, queryPath.concat(['SELECT', 'where']))
}

@@ -1043,0 +1051,0 @@ }, path.concat([ 'query' ]));

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

const ignoreTimeTrace = {
start: () => { /* ignore */ },
stop: () => { /* ignore */ },
};
const doTimeTrace = process && process.env && process.env.CDSC_TIMETRACING !== undefined;
module.exports = doTimeTrace ? new TimeTracer() : { start: () => {}, stop: () => {} };
module.exports = doTimeTrace ? new TimeTracer() : ignoreTimeTrace;
{
"name": "@sap/cds-compiler",
"version": "2.4.4",
"version": "2.5.0",
"description": "CDS (Core Data Services) compiler and backends",

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

"main": "lib/main.js",
"types": "lib/main.d.ts",
"scripts": {

@@ -16,0 +17,0 @@ "postinstall": "node lib/fix_antlr4-8_warning.js"

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 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 not supported yet

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

Sorry, the diff of this file is not supported yet

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc