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.10.4 to 2.11.0

lib/compiler/base.js

67

bin/cdsc.js

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

const { optionProcessor } = require('../lib/optionProcessor');
const { explainMessage, hasMessageExplanation, sortMessages } = require('../lib/base/messages');
const term = require('../lib/utils/term');
const {
explainMessage, hasMessageExplanation, sortMessages, messageIdsWithExplanation,
} = require('../lib/base/messages');
const { term } = require('../lib/utils/term');
const { splitLines } = require('../lib/utils/file');

@@ -56,5 +58,7 @@ const { addLocalizationViews } = require('../lib/transform/localized');

case 'user':
if (!options.magicVars)
options.magicVars = {};
options.magicVars.user = value;
if (!options.variableReplacements)
options.variableReplacements = {};
if (!options.variableReplacements.$user)
options.variableReplacements.$user = {};
options.variableReplacements.$user.id = value;
break;

@@ -68,5 +72,7 @@ case 'dialect':

case 'locale':
if (!options.magicVars)
options.magicVars = {};
options.magicVars.locale = value;
if (!options.variableReplacements)
options.variableReplacements = {};
if (!options.variableReplacements.$user)
options.variableReplacements.$user = {};
options.variableReplacements.$user.locale = value;
break;

@@ -130,4 +136,2 @@ default:

}
// Default color mode is 'auto'
term.useColor(cmdLine.options.color || 'auto');

@@ -144,6 +148,3 @@ // Set default command if required

cmdLine.options.parseCdl = true;
if (cmdLine.args.files.length > 1) {
const err = `'parseCdl' expects exactly one file! ${cmdLine.args.files.length} provided.`;
displayUsage(err, optionProcessor.commands.parseCdl.helpText, 2);
}
cmdLine.args.files = [ cmdLine.args.file ];
}

@@ -163,2 +164,10 @@

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

@@ -240,2 +249,4 @@ if (cmdLine.options.betaMode)

// Default color mode is 'auto'
const colorTerm = term(options.color || 'auto');

@@ -403,10 +414,11 @@ // Add implementation functions corresponding to commands here

function explain() {
if (args.length !== 1)
displayUsage('Command \'explain\' expects exactly one message-id.', optionProcessor.commands.explain.helpText, 2);
if (args.messageId === 'list') {
console.log(messageIdsWithExplanation().join('\n'));
throw new ProcessExitError(0);
}
const id = args.files[0];
if (!hasMessageExplanation(id))
console.error(`Message '${id}' does not have an explanation!`);
if (!hasMessageExplanation(args.messageId))
console.error(`Message '${args.messageId}' does not have an explanation!`);
else
console.log(explainMessage(id));
console.log(explainMessage(args.messageId));
}

@@ -475,12 +487,16 @@

const fullFilePath = name ? path.resolve('', name) : undefined;
const context = fullFilePath && sourceLines(fullFilePath);
const context = fullFilePath ? sourceLines(fullFilePath) : [];
log(main.messageStringMultiline(msg, {
normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: true, hintExplanation: true,
normalizeFilename,
noMessageId: !options.showMessageId,
withLineSpacer: true,
hintExplanation: true,
color: options.color,
}));
if (context)
log(main.messageContext(context, msg));
log(main.messageContext(context, msg, { color: options.color }));
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.`);
log(`${colorTerm.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`);
}

@@ -561,6 +577,7 @@ return model;

function catchErrors(err) {
// @ts-ignore
if (err instanceof Error && err.hasBeenReported)
return;
console.error( '' );
console.error( 'INTERNAL ERROR: %s', err );
console.error( 'INTERNAL ERROR:' );
console.error( util.inspect(err, false, null) );

@@ -567,0 +584,0 @@ console.error( '' );

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

/* eslint no-console:off */
// @ts-nocheck

@@ -16,0 +17,0 @@ 'use strict';

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

## Version 2.11.0
### Removed `foreignKeyConstraints`
## Version 2.10.4

@@ -13,0 +17,0 @@

@@ -17,2 +17,4 @@ {

"rules": {
// Does not recognize TS types
"jsdoc/no-undefined-types": "off",
// eslint-plugin-jsdoc warning

@@ -19,0 +21,0 @@ "jsdoc/require-property": 0,

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

const { getResultingName } = require('../model/csnUtils');
const timetrace = require('../utils/timetrace');
const { timetrace } = require('../utils/timetrace');
const { transformForHanaWithCsn } = require('../transform/forHanaNew');

@@ -139,3 +139,3 @@

* @param {CSN.Model} csn Clean input CSN
* @param {oDataOptions} [options={}] Options
* @param {ODataOptions} [options={}] Options
* @returns {oDataCSN} Return an oData-pre-processed CSN

@@ -459,3 +459,3 @@ */

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {oDataOptions} [options={}] Options
* @param {ODataOptions} [options={}] Options
* @returns {edm} The JSON representation of the service

@@ -490,3 +490,3 @@ */

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {oDataOptions} [options={}] Options
* @param {ODataOptions} [options={}] Options
* @returns {edms} { <service>:<JSON representation>, ...}

@@ -523,3 +523,3 @@ */

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {oDataOptions} [options={}] Options
* @param {ODataOptions} [options={}] Options
* @returns {edmx} The XML representation of the service

@@ -555,3 +555,3 @@ */

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {oDataOptions} [options={}] Options
* @param {ODataOptions} [options={}] Options
* @returns {edmxs} { <service>:<XML representation>, ...}

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

/**
* Options available for all oData-based functions
*
* @typedef {object} oDataOptions
* @property {object} [beta] Enable experimental features - not for productive use!
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
* @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
* @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
* @property {oDataVersion} [odataVersion='v4'] Odata version to use
* @property {oDataFormat} [odataFormat='flat'] Wether to generate oData as flat or as structured. Structured only with v4.
* @property {NamingMode} [sqlMapping='plain'] Naming mode to use
* @property {string} [service] If a single service is to be rendered
*/
/**
* Options available for to.hdi

@@ -763,5 +749,5 @@ *

* @property {SQLDialect} [sqlDialect='sqlite'] SQL dialect to use
* @property {object} [magicVars] Object containing values for magic variables like "$user"
* @property {string} [magicVars.locale] Value for the "$user.locale" in "sqlite" dialect
* @property {string} [magicVars.user] Value for the "$user" variable in "sqlite" dialect
* @property {object} [variableReplacements] Object containing values for magic variables like "$user"
* @property {string} [variableReplacements.$user.locale] Value for the "$user.locale" variable
* @property {string} [variableReplacements.$user.id] Value for the "$userid" variable
* @property {object} [beta] Enable experimental features - not for productive use!

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

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

'withLocations',
'defaultBinaryLength',
'defaultStringLength',

@@ -27,3 +28,4 @@ 'csnFlavor',

'joinfk',
'magicVars',
'magicVars', // deprecated
'variableReplacements',
// ODATA

@@ -52,5 +54,6 @@ 'odataVersion',

'testSortCsn',
'constraintsNotEnforced',
'constraintsNotValidated',
'skipDbConstraints',
'integrityNotEnforced',
'integrityNotValidated',
'assertIntegrity',
'assertIntegrityType',
'noRecompile',

@@ -115,2 +118,6 @@ 'internalMsg',

// Convenience for $user -> $user.id replacement
if (options.variableReplacements && options.variableReplacements.$user && typeof options.variableReplacements.$user === 'string')
options.variableReplacements.$user = { id: options.variableReplacements.$user };
/**

@@ -137,2 +144,3 @@ * Map a new-style option to it's old format

break;
// No need to remap variableReplacements here - we use the new mechanism with that directly
case 'magicVars':

@@ -139,0 +147,0 @@ if (optionValue.user)

@@ -73,2 +73,10 @@ 'use strict';

},
// TODO: Maybe do a deep validation of the whole object with leafs?
variableReplacements: {
validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
expected: () => 'type object',
found: (val) => {
return val === null ? val : `type ${ typeof val }`;
},
},
messages: {

@@ -93,6 +101,13 @@ validate: val => Array.isArray(val),

},
defaultBinaryLength: {
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
expected: () => 'Integer literal',
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
},
defaultStringLength: {
validate: val => Number.isInteger(val),
validate: val => !Number.isNaN(Number(val)) && Number.isInteger(Number.parseFloat(val)),
expected: () => 'Integer literal',
found: val => `type ${ typeof val }`,
found: val => `${ (!Number.isNaN(Number(val)) ? val : 'Not a Number') }`,
},

@@ -107,2 +122,8 @@ csnFlavor: {

},
assertIntegrity: {
validate: val => typeof val === 'string' && val === 'individual' || typeof val === 'boolean',
expected: () => 'a boolean or a string with value \'individual\'',
found: val => (typeof val === 'string' ? val : `type ${ typeof val }`),
},
assertIntegrityType: generateStringValidator([ 'DB', 'RT' ]),
};

@@ -109,0 +130,0 @@

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

const { optionProcessor } = require('./optionProcessor')
const timetrace = require('./utils/timetrace');
const { timetrace } = require('./utils/timetrace');
const { makeMessageFunction } = require('./base/messages');

@@ -96,5 +96,5 @@ const { forEachDefinition } = require('./model/csnUtils');

function preparedCsnToEdmx(csn, service, options) {
let edmx = csn2edm(csn, service, options).toXML('all');
const e = csn2edm(csn, service, options)
return {
edmx,
edmx: (e ? e.toXML('all') : undefined)
};

@@ -120,5 +120,5 @@ }

options = mergeOptions(options, { toOdata : { version : 'v4' }});
const edmj = csn2edm(csn, service, options).toJSON();
const e = csn2edm(csn, service, options);
return {
edmj,
edmj: (e ? e.toJSON() : undefined)
};

@@ -411,5 +411,2 @@ }

const { error } = makeMessageFunction(csn, options, 'manageConstraints');
// Requires beta mode
if (!isBetaEnabled(options, 'foreignKeyConstraints'))
error(null, null, 'ALTER TABLE statements for adding/modifying referential constraints are only available in beta mode');

@@ -427,2 +424,6 @@ const {

}
// Of course we want the database constraints
options.assertIntegrityType = 'DB';
const transformedOptions = transformSQLOptions(csn, options);

@@ -429,0 +430,0 @@ const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });

@@ -31,2 +31,3 @@ // Functions for dictionaries (Objects without prototype)

function dictForEach( dict, callback ) {
// TODO: probably define an extra dictForEachArray()
for (const name in dict) {

@@ -39,3 +40,3 @@ const entry = dict[name];

callback( entry );
if (Array.isArray(entry.$duplicates))
if (Array.isArray( entry.$duplicates ))
entry.$duplicates.forEach( callback );

@@ -42,0 +43,0 @@ }

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

'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-expected-length': { severity: 'Error' },
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars

@@ -137,2 +138,3 @@ 'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars

'odata-spec-violation-type': { severity: 'Error', configurableFor: [ 'to.edmx' ] },
'odata-spec-violation-no-key': { severity: 'Warning' },
'odata-spec-violation-key-array': { severity: 'Error' }, // more than 30 chars

@@ -165,2 +167,7 @@ 'odata-spec-violation-key-null': { severity: 'Error' }, // more than 30 chars

},
'syntax-csn-expected-length': {
std: 'Expected array in $(PROP) to have at least $(N) items',
one: 'Expected array in $(PROP) to have at least one item',
suffix: 'With sibling property $(OTHERPROP), expected array in $(PROP) to have at least one item',
},
'ref-undefined-def': {

@@ -209,3 +216,3 @@ std: 'Artifact $(ART) has not been found',

absolute: 'Duplicate definition of artifact $(NAME)',
namespace: 'Other definition blocks $(NAME) for namespace name',
annotation: 'Duplicate definition of annotation vocabulary $(NAME)',
element: 'Duplicate definition of element $(NAME)',

@@ -216,3 +223,3 @@ enum: 'Duplicate definition of enum $(NAME)',

param: 'Duplicate definition of parameter $(NAME)',
$tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
alias: 'Duplicate definition of table alias or mixin $(NAME)',
},

@@ -270,2 +277,3 @@

},
'odata-spec-violation-no-key': 'Expected entity to have a primary key',
'odata-spec-violation-type': 'Expected element to have a type',

@@ -272,0 +280,0 @@ 'odata-spec-violation-property-name': 'Expected element name to be different from declaring $(KIND)',

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

const term = require('../utils/term');
const { term } = require('../utils/term');
const { locationString } = require('./location');

@@ -19,2 +19,4 @@ const { isDeprecatedEnabled } = require('./model');

// term instance for messages
const colorTerm = term();

@@ -858,6 +860,7 @@ // Functions ensuring message consistency during runtime with --test-mode

* ```txt
* Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
* Error[message-id]: Can't find type `nu` in this scope
* |
* <source>.cds:3:11, at entity:“E”
* <source>.cds:3:11, at entity:“E”/element:“e”
* ```
*
* @param {CSN.Message} err

@@ -868,6 +871,12 @@ * @param {object} [config = {}]

* @param {boolean} [config.hintExplanation] If true, messages with explanations will get a "…" marker.
* @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
* @param {boolean} [config.withLineSpacer] If true, an additional line (with `|`) will be inserted between message and location.
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the severity. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
* @returns {string}
*/
function messageStringMultiline( err, config = {} ) {
colorTerm.changeColorMode(config ? config.color : 'auto');
const explainHelp = (config.hintExplanation && hasMessageExplanation(err.messageId)) ? '…' : '';

@@ -885,3 +894,3 @@ const msgId = (err.messageId && !config.noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';

else if (!home)
return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
return colorTerm.severity(severity, severity + msgId) + ' ' + err.message;

@@ -894,4 +903,3 @@ let lineSpacer = '';

// TODO: use ':' before text
return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
return colorTerm.severity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
}

@@ -910,5 +918,11 @@

* @param {CSN.Message} err Error object containing all details like line, message, etc.
* @param {object} [config = {}]
* @param {boolean | 'auto'} [config.color] If true, ANSI escape codes will be used for coloring the `^`. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
* @returns {string}
*/
function messageContext(sourceLines, err) {
function messageContext(sourceLines, err, config) {
colorTerm.changeColorMode(config ? config.color : 'auto');
const MAX_COL_LENGTH = 100;

@@ -965,3 +979,3 @@

highlighter = highlighter.replace(' ^', '..^');
msg += indent + '| ' + term.asSeverity(severity, highlighter);
msg += indent + '| ' + colorTerm.severity(severity, highlighter);

@@ -968,0 +982,0 @@ } else if (maxLine !== endLine) {

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

// enabled by --beta-mode
foreignKeyConstraints: true,
toRename: true,

@@ -32,3 +31,2 @@ addTextsLanguageAssoc: true,

enableUniversalCsn: true,
windowFunctions: true,
// disabled by --beta-mode

@@ -60,2 +58,3 @@ nestedServices: false,

* With that, the value of `deprecated` is a dictionary of feature=>Boolean.
* If no `feature` is provided, checks if any deprecated option is set.
*

@@ -65,7 +64,9 @@ * Please do not move this function to the "option processor" code.

* @param {object} options Options
* @param {string} feature Feature to check for
* @param {string} [feature] Feature to check for
* @returns {boolean}
*/
function isDeprecatedEnabled( options, feature ) {
function isDeprecatedEnabled( options, feature = null ) {
const { deprecated } = options;
if(!feature)
return !!deprecated;
return deprecated && typeof deprecated === 'object' && deprecated[feature];

@@ -72,0 +73,0 @@ }

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

command,
positionalArgument,
positionalArgument: (argumentDefinition) => {
// Default positional arguments; may be overwritten by commands.
_positionalArguments(argumentDefinition);
return optionProcessor;
},
help,

@@ -70,3 +74,8 @@ processCmdLine,

options: {},
positionalArguments: [],
option,
positionalArgument: (argumentDefinition) => {
_positionalArguments(argumentDefinition, command.positionalArguments);
return command;
},
help,

@@ -101,18 +110,24 @@ ..._parseCommandString(cmdString)

/**
* Adds positional arguments to the command line processor. Instructs the processor
* to either require N positional arguments or a dynamic number (but at least one)
* @param {string} positionalArgumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
* Set the positional arguments to the command line processor. Instructs the processor
* to either require N positional arguments or a dynamic number (but at least one).
* Note that you can only call this function once. Only the last invocation sets
* the positional arguments.
*
* @param {string} argumentDefinition Positional arguments, e.g. '<input> <output>' or '<files...>'
* @param {object[]} argList Array, to which the parsed arguments will be added. Default is global scope.
*/
function positionalArgument(positionalArgumentDefinition) {
if (optionProcessor.positionalArguments.find((arg) => arg.isDynamic)) {
function _positionalArguments(argumentDefinition, argList = optionProcessor.positionalArguments) {
if (argList.find((arg) => arg.isDynamic)) {
throw new Error(`Can't add positional arguments after a dynamic one`);
}
const registeredNames = optionProcessor.positionalArguments.map((arg) => arg.name);
const args = positionalArgumentDefinition.split(' ');
const registeredNames = argList.map((arg) => arg.name);
const args = argumentDefinition.split(' ');
for (const arg of args) {
const argName = arg.replace('<', '').replace('>', '').replace('...', '');
// Remove braces, dots and camelify.
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
if (registeredNames.includes(argName)) {
throw new Error(`Duplicate positional argument ${arg}`);
throw new Error(`Duplicate positional argument: ${arg}`);
}

@@ -123,3 +138,3 @@ if (!isParam(arg) && !isDynamicPositionalArgument(arg)) {

optionProcessor.positionalArguments.push({
argList.push({
name: argName,

@@ -132,3 +147,2 @@ isDynamic: isDynamicPositionalArgument(arg),

}
return optionProcessor;
}

@@ -398,5 +412,6 @@

// Complain about first missing positional arguments
const missingArg = optionProcessor.positionalArguments.find((arg) => arg.required && !result.args[arg.name]);
const missingArg = getCurrentPositionArguments().find((arg) => arg.required && !result.args[arg.name]);
if (missingArg) {
result.errors.push(`Missing positional argument: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
const forCommand = result.command ? ` for '${ result.command }'` : '';
result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
}

@@ -406,10 +421,26 @@

/**
* Specific commands may have custom positional arguments.
* If the current one does, use it instead of the defaults.
*
* @returns {object[]} Array of positional argument configurations.
*/
function getCurrentPositionArguments() {
const cmd = optionProcessor.commands[result.command];
const args = ( cmd && cmd.positionalArguments && cmd.positionalArguments.length ) ? cmd.positionalArguments : optionProcessor.positionalArguments;
return args;
}
function processPositionalArgument(argumentValue) {
if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
const argList = getCurrentPositionArguments();
if ( result.args.length === 0 && argList.length === 0 )
return;
const inBounds = result.args.length < optionProcessor.positionalArguments.length;
const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;
const nextUnsetArgument = optionProcessor.positionalArguments[lastIndex];
const inBounds = result.args.length < argList.length;
const lastIndex = inBounds ? result.args.length : argList.length - 1;
const nextUnsetArgument = argList[lastIndex];
if (!inBounds && !nextUnsetArgument.isDynamic) {
result.errors.push(`Too many arguments. Expected ${optionProcessor.positionalArguments.length}`);
if (result.command)
result.errors.push(`Too many arguments. '${result.command}' expects ${argList.length}`);
else
result.errors.push(`Too many arguments. Expected ${argList.length}`);
return;

@@ -496,3 +527,6 @@ }

if(options) {
['defaultStringLength', /*'length', 'precision', 'scale'*/].forEach(facet => {
[
'defaultBinaryLength', 'defaultStringLength',
/*'length', 'precision', 'scale'*/
].forEach(facet => {
if(options[facet] && isNaN(options[facet])) {

@@ -588,3 +622,3 @@ result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);

function isParam(opt) {
return /^<[a-zA-Z]+>$/.test(opt);
return /^<[a-zA-Z-]+>$/.test(opt);
}

@@ -594,3 +628,3 @@

function isDynamicPositionalArgument(arg) {
return /^<[a-zA-Z]+[.]{3}>$/.test(arg);
return /^<[a-zA-Z-]+[.]{3}>$/.test(arg);
}

@@ -597,0 +631,0 @@

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

}
else if (this.options.transformation === 'hdbcds' && selectItem.xpr && selectItem.func) {
this.error(null, selectItem.$path,
'Window functions are not supported by SAP HANA CDS');
}
});

@@ -28,0 +32,0 @@ // .call() with 'this' to ensure we have access to the options

'use strict';
const { getVariableReplacement } = require('../model/csnUtils');
// We only care about the "wild" ones - $at is validated by the compiler

@@ -20,3 +22,3 @@ const magicVariables = {

* - We know what to do -> $user.id on HANA
* - The user tells us what to do -> options.magicVars
* - The user tells us what to do -> options.variableReplacements
*

@@ -32,4 +34,5 @@ * @param {object} parent Object with the ref as a property

const magicVariable = magicVariables[head];
if (magicVariable && magicVariable.indexOf(tail) === -1)
this.error(null, parent.$location, { id: tail, elemref: parent }, 'Magic variable is not supported - path $(ELEMREF), step $(ID)');
if (magicVariable && magicVariable.indexOf(tail) === -1 &&
getVariableReplacement(ref, this.options) === null)
this.error(null, parent.$location, { id: tail, elemref: parent }, 'No configuration for magic variable was provided - path $(ELEMREF), step $(ID)');
}

@@ -36,0 +39,0 @@ }

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

'$messageFunctions',
'$functions',
'$volatileFunctions',
],

@@ -483,2 +485,3 @@ },

kind: true,
also: [ 0 ], // 0 for cyclic expansions
requires: [ 'location' ],

@@ -593,2 +596,4 @@ optional: [

$messageFunctions: { test: TODO },
$functions: { test: TODO },
$volatileFunctions: { test: TODO },
};

@@ -665,2 +670,4 @@ let _noSyntaxErrors = null;

function standard( node, parent, prop, spec, name ) {
if (spec.also && spec.also.includes( node ))
return;
isObject( node, parent, prop, spec, name );

@@ -667,0 +674,0 @@

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

const { builtinLocation } = require('../base/location');
const { setProp } = require('../base/model');
const { setProp } = require('./utils');

@@ -172,2 +172,27 @@ const core = {

/**
* Checks whether the given absolute path is inside a reserved namespace.
*
* @param {string} absolute
* @returns {boolean}
*/
function isInReservedNamespace(absolute) {
return absolute.startsWith( 'cds.') &&
!absolute.match(/^cds\.foundation(\.|$)/) &&
!absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
}
/**
* Tell if a type is (directly) a builtin type
* Note that in CSN builtins are not in the definition of the model, so we can only
* check against their absolute names. Builtin types are "cds.<something>", i.e. they
* are directly in 'cds', but not for example in 'cds.foundation'.
*
* @param {string} type
* @returns {boolean}
*/
function isBuiltinType(type) {
return typeof type === 'string' && isInReservedNamespace(type);
}
/**
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to

@@ -274,2 +299,4 @@ * `definitions` of the XSN model as well as to `$builtins`.

initBuiltins,
isInReservedNamespace,
isBuiltinType,
isIntegerTypeName,

@@ -276,0 +303,0 @@ isDecimalTypeName,

@@ -635,4 +635,5 @@ // Checks on XSN performed during compile()

function checkTokenStreamExpression(xpr, allowAssocTail) {
const args = Array.isArray(xpr.args) ? xpr.args : Object.values(xpr.args || {});
// Check for illegal argument usage within the expression
for (const arg of xpr.args || []) {
for (const arg of args) {
if (isVirtualElement(arg))

@@ -639,0 +640,0 @@ error(null, arg.location, 'Virtual elements can\'t be used in an expression');

// Main XSN-based compiler functions
// ...
// How functions are shared across the Core Compiler sub modules:
// - Shared XSN-related functions which do not use a context are in utils.js,
// they are `require`d as usual at the beginning of sub modules.
// - The XSN is the only context which context-dependent functions can depend on.
// - Sharing such a function is by adding it to `‹xsn›.$functions`,
// e.g. `resolvePath` and similar will be attached to the XSN.
// - Functions which might be overwritten in a next sub module
// are added to `‹xsn›.$volatileFunctions`, currently just `environment`.
'use strict';

@@ -11,2 +23,3 @@

const moduleLayers = require('./moduleLayers');
const { fns } = require('./shared');
const { define } = require('./definer');

@@ -114,3 +127,2 @@ const resolve = require('./resolver');

const a = processFilenames( filenames, dir );
a.fileContentDict = Object.create(null);

@@ -154,3 +166,2 @@ const model = { sources: a.sources, options };

try {
a.fileContentDict[filename] = source;
const ast = parseX( source, rel, options, model.$messageFunctions );

@@ -226,3 +237,2 @@ a.sources[filename] = ast;

const a = processFilenames( filenames, dir );
a.fileContentDict = Object.create(null);

@@ -288,3 +298,2 @@ const model = { sources: a.sources, options };

try {
a.fileContentDict[filename] = source;
const ast = parseX( source, rel, options, model.$messageFunctions );

@@ -424,2 +433,5 @@ a.sources[filename] = ast;

}
model.$functions = {};
model.$volatileFunctions = {};
fns( model ); // attach (mostly) paths functions
define( model );

@@ -426,0 +438,0 @@ // do not run the resolver in parse-cdl mode or we get duplicate annotations, etc.

@@ -11,3 +11,3 @@ //

} = require( '../base/model');
const { linkToOrigin, withAssociation } = require('./shared');
const { linkToOrigin, withAssociation } = require('./utils');
// const { refString } = require( '../base/messages')

@@ -183,2 +183,4 @@

// console.log(prop,source.name,'->',target.kind,target.name);
if (source.kind === 'builtin')
return;
if (prop !== 'foreignKeys' && availableAtType( prop, target, source ))

@@ -216,3 +218,4 @@ // foreignKeys must always be copied with target to avoid any confusion

function onlyViaParent( prop, target, source ) {
if (target.$inferred === 'proxy') // assocs and enums do not have 'include'
if (target.$inferred === 'proxy' || target.$inferred === 'expand-element')
// assocs and enums do not have 'include'
always( prop, target, source );

@@ -219,0 +222,0 @@ }

// Compiler functions and utilities shared across all phases
// TODO: rename to paths.js and move non resolve-paths functions to somewhere else
'use strict';
const { searchName } = require('../base/messages');
const { dictAdd, dictAddArray, pushToDict } = require('../base/dictionaries');
const { dictAddArray } = require('../base/dictionaries');
const { setProp } = require('../base/model');
const dictKinds = {
definitions: 'absolute',
elements: 'element',
enum: 'enum',
foreignKeys: 'key',
actions: 'action',
params: 'param',
};
const { setLink, dependsOn } = require('./utils');
const kindProperties = {
// TODO: also foreignKeys ?
namespace: { artifacts: true }, // on-the-fly context
context: { artifacts: true, normalized: 'namespace' },
service: { artifacts: true, normalized: 'namespace' },
entity: { elements: true, actions: true, params: () => false },
select: { normalized: 'select', elements: true },
$join: { normalized: 'select' },
$tableAlias: { normalized: 'alias' }, // table alias in select
$self: { normalized: 'alias' }, // table alias in select
$navElement: { normalized: 'element' },
$inline: { normalized: 'element' }, // column with inline property
event: { elements: true },
type: { elements: propExists, enum: propExists },
aspect: { elements: propExists },
annotation: { elements: propExists, enum: propExists },
enum: { normalized: 'element' },
element: { elements: propExists, enum: propExists, dict: 'elements' },
mixin: { normalized: 'alias' },
action: {
params: () => false, elements: () => false, enum: () => false, dict: 'actions',
}, // no extend params, only annotate
function: {
params: () => false, elements: () => false, enum: () => false, normalized: 'action',
}, // no extend params, only annotate
key: { normalized: 'element' },
param: { elements: () => false, enum: () => false, dict: 'params' },
source: { artifacts: true }, // TODO -> $source
using: {},
extend: {
isExtension: true,
noDep: 'special',
elements: true, /* only for parse-cdl */
actions: true, /* only for parse-cdl */
},
annotate: {
isExtension: true, noDep: 'special', elements: true, enum: true, actions: true, params: true,
},
builtin: {}, // = CURRENT_DATE, TODO: improve
$parameters: {}, // $parameters in query entities
};
function propExists( prop, parent ) {
const obj = parent.returns || parent;
return (obj.items || obj.targetAspect || obj)[prop];
}
function artifactsEnv( art ) {

@@ -71,15 +17,16 @@ return art._subArtifacts || Object.create(null);

/**
* Main export function of this file. Return "resolve" functions shared for phase
* "define" and "resolve". Argument `model` is the augmented CSN. Optional
* argument `environment` is a function which returns the search environment
* defined by its argument - it defaults to the dictionary of subartifacts of
* the argument.
* Main export function of this file. Attach "resolve" functions shared for phase
* "define" and "resolve" to `model.$functions`, where argument `model` is the XSN.
*
* Before calling these functions, make sure that the following function
* in model.$volatileFunctions is set:
* - `environment`: a function which returns the search environment defined by
* its argument, e.g. a function which returns the dictionary of subartifacts.
*
* @param {XSN.Model} model
* @param {(a, b?, c?) => any} environment
* @returns {object} Commonly used "resolve" functions.
*/
function fns( model, environment = artifactsEnv ) {
// TODO: yes, this function will be renamed
function fns( model ) {
/** @type {CSN.Options} */
const options = model.options || {};
const { options } = model;
const {

@@ -208,3 +155,4 @@ info, warning, error, message,

return {
const VolatileFns = model.$volatileFunctions;
Object.assign( model.$functions, {
resolveUncheckedPath,

@@ -215,3 +163,4 @@ resolvePath,

attachAndEmitValidNames,
};
} );
return;

@@ -263,3 +212,3 @@ function checkConstRef( art ) {

return true; // elem not starting at entity
environment( art ); // sets _effectiveType on art
VolatileFns.environment( art ); // sets _effectiveType on art
return !(art._effectiveType || art).target;

@@ -352,3 +301,3 @@ }

(query._combined || query._parent._combined) ||
environment( user._main ? user._parent : user );
VolatileFns.environment( user._main ? user._parent : user );
}

@@ -447,3 +396,3 @@ }

dependsOn( user, art._main, location );
environment( art, location, user );
VolatileFns.environment( art, location, user );
// Without on-demand resolve, we can simply signal 'undefined "x"'

@@ -522,3 +471,3 @@ // instead of 'illegal cycle' in the following case:

// TODO: not necessarily for explicit ON condition in expand
environment( user._pathHead ); // make sure _origin is set
VolatileFns.environment( user._pathHead ); // make sure _origin is set
return user._pathHead._origin;

@@ -580,4 +529,6 @@ }

if (Array.isArray(r)) {
if (r[0].kind === '$navElement') {
const names = r.filter( e => !e.$duplicates)
if (r[0].kind === '$navElement' && r.every( e => !e._parent.$duplicates )) {
// only complain about ambiguous source elements if we do not have
// duplicate table aliases, only mention non-ambiguous source elems
const names = r.filter( e => !e.$duplicates )
.map( e => `${ e.name.alias }.${ e.name.element }` );

@@ -669,3 +620,3 @@ if (names.length) {

const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : VolatileFns.environment;
const env = fn( art, item.location, user, spec.assoc );

@@ -859,3 +810,5 @@

if (construct.$annotations && construct.$annotations.doc )
art.doc = construct.$annotations.doc;
art.doc = construct.$annotations.doc; // e.g. through `annotate` statement in CDL
else if (construct.doc)
art.doc = construct.doc; // e.g. through `extensions` array in CSN
if (!construct.$annotations) {

@@ -936,130 +889,4 @@ if (!block || block.$frontend !== 'json')

// The link (_artifact,_effectiveType,...) usually has the artifact as value.
// Falsy values are:
// - undefined: not computed yet, parse error, no ref
// - null: no valid reference, param:true if that is not allowed
// - false (only complete ref): multiple definitions, rejected
// - 0 (for _effectiveType only): circular reference
function setLink( obj, value = null, prop = '_artifact' ) {
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
return value;
}
function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
const elem = {
name: { location: location || origin.name.location, id: name },
kind: origin.kind,
location: location || origin.location,
};
if (origin.name.$inferred)
elem.name.$inferred = origin.name.$inferred;
if (parent)
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
setProp( elem, '_origin', origin );
// TODO: should we use silent dependencies also for other things, like
// included elements? (Currently for $inferred: 'expand-element' only)
if (silentDep)
dependsOnSilent( elem, origin );
else
dependsOn( elem, origin, location );
return elem;
}
function setMemberParent( elem, name, parent, prop ) {
if (prop) { // extension or structure include
// TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
const p = parent.items || parent.targetAspect || parent;
if (!(prop in p))
p[prop] = Object.create(null);
dictAdd( p[prop], name, elem );
}
if (parent._outer)
parent = parent._outer;
setProp( elem, '_parent', parent );
setProp( elem, '_main', parent._main || parent );
elem.name.absolute = elem._main.name.absolute;
if (name == null)
return;
const normalized = kindProperties[elem.kind].normalized || elem.kind;
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
if (normalized === kind)
elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
else if (parent.name[kind] != null)
elem.name[kind] = parent.name[kind];
else
delete elem.name[kind];
});
// try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
}
/**
* Adds a dependency user -> art with the given location.
*
* @param {XSN.Artifact} user
* @param {XSN.Artifact} art
* @param {XSN.Location} location
*/
function dependsOn( user, art, location ) {
if (!user._deps)
setProp( user, '_deps', [] );
user._deps.push( { art, location } );
}
/**
* Same as "dependsOn" but the dependency from user -> art is silent,
* i.e. not reported to the user.
*
* @param {XSN.Artifact} user
* @param {XSN.Artifact} art
*/
function dependsOnSilent( user, art ) {
if (!user._deps)
setProp( user, '_deps', [] );
user._deps.push( { art } );
}
function storeExtension( elem, name, prop, parent, block ) {
if (prop === 'enum')
prop = 'elements';
setProp( elem, '_block', block );
const kind = `_${ elem.kind }`; // _extend or _annotate
if (!parent[kind])
setProp( parent, kind, {} );
// if (name === '' && prop === 'params') {
// pushToDict( parent[kind], 'returns', elem ); // not really a dict
// return;
// }
if (!parent[kind][prop])
parent[kind][prop] = Object.create(null);
pushToDict( parent[kind][prop], name, elem );
}
/** @type {(a: any, b: any) => boolean} */
const testFunctionPlaceholder = () => true;
// Return path step if the path navigates along an association whose final type
// satisfies function `test`; "navigates along" = last path item not considered
// without truthy optional argument `alsoTestLast`.
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
for (const item of ref.path || []) {
const art = item && item._artifact; // item can be null with parse error
if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
}
return false;
}
module.exports = {
dictKinds,
kindProperties,
fns,
setLink,
linkToOrigin,
dependsOn,
dependsOnSilent,
setMemberParent,
storeExtension,
withAssociation,
};

@@ -11,2 +11,5 @@ // Simple compiler utility functions

const { dictAdd, pushToDict } = require('../base/dictionaries');
const { kindProperties } = require('./base');
// for links, i.e., properties starting with an underscore '_':

@@ -42,3 +45,163 @@

// TODO: define setLink() like the current setProp(), we might have setArtifactLink()
// Do not share this function with CSN processors!
// The link (_artifact,_effectiveType,...) usually has the artifact as value.
// Falsy values are:
// - undefined: not computed yet, parse error, no ref
// - null: no valid reference, param:true if that is not allowed
// - false (only complete ref): multiple definitions, rejected
// - 0 (for _effectiveType only): circular reference
function setLink( obj, value = null, prop = '_artifact' ) {
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
return value;
}
/**
* Like `obj.prop = value`, but not contained in JSON / CSN
* It's important to set enumerable explicitly to false (although 'false' is the default),
* as else, if the property already exists, it keeps the old setting for enumerable.
*
* @param {object} obj
* @param {string} prop
* @param {any} value
*/
function setProp(obj, prop, value) {
const descriptor = {
value,
configurable: true,
writable: true,
enumerable: false,
};
Object.defineProperty( obj, prop, descriptor );
return value;
}
function linkToOrigin( origin, name, parent, prop, location, silentDep ) {
const elem = {
name: { location: location || origin.name.location, id: name },
kind: origin.kind,
location: location || origin.location,
};
if (origin.name.$inferred)
elem.name.$inferred = origin.name.$inferred;
if (parent)
setMemberParent( elem, name, parent, prop ); // TODO: redef in template
setProp( elem, '_origin', origin );
// TODO: should we use silent dependencies also for other things, like
// included elements? (Currently for $inferred: 'expand-element' only)
if (silentDep)
dependsOnSilent( elem, origin );
else
dependsOn( elem, origin, location );
return elem;
}
function setMemberParent( elem, name, parent, prop ) {
if (prop) { // extension or structure include
// TODO: consider nested ARRAY OF and RETURNS, COMPOSITION OF type
const p = parent.items || parent.targetAspect || parent;
if (!(prop in p))
p[prop] = Object.create(null);
dictAdd( p[prop], name, elem );
}
if (parent._outer)
parent = parent._outer;
setProp( elem, '_parent', parent );
setProp( elem, '_main', parent._main || parent );
elem.name.absolute = elem._main.name.absolute;
if (name == null)
return;
const normalized = kindProperties[elem.kind].normalized || elem.kind;
[ 'element', 'alias', 'select', 'param', 'action' ].forEach( ( kind ) => {
if (normalized === kind)
elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
else if (parent.name[kind] != null)
elem.name[kind] = parent.name[kind];
else
delete elem.name[kind];
});
// try { throw new Error('Foo') } catch (e) { elem.name.stack = e; };
}
/**
* Adds a dependency user -> art with the given location.
*
* @param {XSN.Artifact} user
* @param {XSN.Artifact} art
* @param {XSN.Location} location
*/
function dependsOn( user, art, location ) {
if (!user._deps)
setProp( user, '_deps', [] );
user._deps.push( { art, location } );
}
/**
* Same as "dependsOn" but the dependency from user -> art is silent,
* i.e. not reported to the user.
*
* @param {XSN.Artifact} user
* @param {XSN.Artifact} art
*/
function dependsOnSilent( user, art ) {
if (!user._deps)
setProp( user, '_deps', [] );
user._deps.push( { art } );
}
function storeExtension( elem, name, prop, parent, block ) {
if (prop === 'enum')
prop = 'elements';
setProp( elem, '_block', block );
const kind = `_${ elem.kind }`; // _extend or _annotate
if (!parent[kind])
setProp( parent, kind, {} );
// if (name === '' && prop === 'params') {
// pushToDict( parent[kind], 'returns', elem ); // not really a dict
// return;
// }
if (!parent[kind][prop])
parent[kind][prop] = Object.create(null);
pushToDict( parent[kind][prop], name, elem );
}
/** @type {(a: any, b: any) => boolean} */
const testFunctionPlaceholder = () => true;
// Return path step if the path navigates along an association whose final type
// satisfies function `test`; "navigates along" = last path item not considered
// without truthy optional argument `alsoTestLast`.
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
for (const item of ref.path || []) {
const art = item && item._artifact; // item can be null with parse error
if (art && art._effectiveType && art._effectiveType.target && test( art._effectiveType, item ))
return (alsoTestLast || item !== ref.path[ref.path.length - 1]) && item;
}
return false;
}
/**
* Generates an XSN path out of the given name. Path segments are delimited by a dot.
* Each segment will have the given location assigned.
*
* @param {CSN.Location} location
* @param {string} name
* @returns {XSN.Path}
*/
function splitIntoPath( location, name ) {
return name.split('.').map( id => ({ id, location }) );
}
/**
* @param {CSN.Location} location
* @param {...any} args
*/
function augmentPath( location, ...args ) {
return { path: args.map( id => ({ id, location }) ), location };
}
module.exports = {

@@ -49,2 +212,12 @@ pushLink,

annotateWith,
setLink,
setProp,
linkToOrigin,
dependsOn,
dependsOnSilent,
setMemberParent,
storeExtension,
withAssociation,
augmentPath,
splitIntoPath,
};

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

dictPropertyTypeName = dictProperties[i];
if (!dictPropertyTypeName){
if (!dictPropertyTypeName && !getDictType(actualTypeName).OpenType){
message(warning, context, `record type '${ actualTypeName }' doesn't have a property '${ i }'`);

@@ -1125,0 +1125,0 @@ }

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

};
if(options.isV4()) {

@@ -308,2 +308,3 @@ // tunnel schema xref and servicename in options to edm.Typebase to rectify

}
/** @type {any} */
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);

@@ -395,3 +396,3 @@ const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);

} else if(entityCsn.$edmKeyPaths.length === 0) {
warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no primary key');
message('odata-spec-violation-no-key', loc);
}

@@ -398,0 +399,0 @@ properties.forEach(p => {

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

const originParentName = originAssocCsn.$abspath[0];
if(originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {
isBacklink = false;

@@ -196,3 +196,3 @@ // Partnership is ambiguous

if(isBacklink) {
// use first backlink as partner
// establish partnership with origin assoc but only if this association is the first one
if(originAssocCsn._selfReferences.length === 0) {

@@ -207,3 +207,3 @@ assocCsn._constraints._partnerCsn = originAssocCsn;

// if the termCount != 1 or more than one $self compare this is not a backlink
if(assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
if(parent.$mySchemaName && assocCsn._constraints.selfs.length === 1 && assocCsn._constraints.termCount === 1) {
originAssocCsn._selfReferences.push(assocCsn);

@@ -210,0 +210,0 @@ }

@@ -776,8 +776,4 @@ // CSN frontend - transform CSN into XSN

if (minLength > val.length) {
error( 'syntax-csn-expected-length', location(true),
{ prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' },
{
std: 'Expected array in $(PROP) to have at least $(N) items',
one: 'Expected array in $(PROP) to have at least one item',
} );
message( 'syntax-csn-expected-length', location(true),
{ prop: spec.prop, n: minLength, '#': minLength === 1 ? 'one' : 'std' });
}

@@ -917,3 +913,3 @@ if (val.length)

}
if (!r.name && name) {
if (!r.name && name != null) {
r.name = { id: name, location: r.location };

@@ -1007,9 +1003,9 @@ if (prop === 'columns' || prop === 'keys' || prop === 'foreignKeys')

function returnsDefinition( def, spec, xsn, csn, name ) {
function returnsDefinition( def, spec, xsn, csn ) {
// TODO: be stricter in what is allowed inside returns
if (!inExtensions)
return definition( def, spec, xsn, csn, name );
return definition( def, spec, xsn, csn, '' );
// for the moment, flatten elements in returns in an annotate
// TODO: bigger Core Compiler changes would have to be done otherwise
xsn.elements = definition( def, spec, xsn, csn, name ).elements;
xsn.elements = definition( def, spec, xsn, csn, '' ).elements;
xsn.$syntax = 'returns';

@@ -1277,2 +1273,6 @@ return undefined;

if (csn.func) {
if (!exprs.length) {
message( 'syntax-csn-expected-length', location(true),
{ prop: 'xpr', otherprop: 'func', '#': 'suffix' });
}
xsn.suffix = exprArgs( exprs, spec );

@@ -1279,0 +1279,0 @@ }

@@ -36,2 +36,6 @@ // Transform XSN (augmented CSN) into CSN

// Properties for dictionaries, set in compileX() and TODO: parseX(), must be
// stored with symbols as keys, as we do not want to disallow any key name:
const $inferred = Symbol.for('cds.$inferred');
// IMPORTANT: the order of these properties determine the order of properties

@@ -42,3 +46,2 @@ // in the resulting CSN !!! Also check const `csnPropertyNames`.

kind,
_outer: ( _, csn, node ) => addOrigin( csn, node ),
id: n => n, // in path item

@@ -72,3 +75,3 @@ doc: value,

foreignKeys,
enum: insertOrderDict,
enum: enumDict,
items,

@@ -205,2 +208,11 @@ includes: arrayOf( artifactRef ), // also entities

],
rows: exprs => [
'rows', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),
],
preceding: postfix( [ 'preceding' ] ),
unboundedPreceding: [ 'unbounded', 'preceding' ],
currentRow: [ 'current', 'row' ],
unboundedFollowing: [ 'unbounded', 'following' ],
following: postfix( [ 'following' ] ),
frameBetween: exprs => [ 'between', ...exprs[0], 'and', ...exprs[1] ],
// xpr: (exprs) => [].concat( ...exprs ), see below - handled extra

@@ -629,3 +641,3 @@ };

function items( obj, csn, node ) {
if (!keepElements( node ))
if (!keepElements( node, obj ))
return undefined;

@@ -645,2 +657,14 @@ return standard( obj ); // no 'elements' with inferred elements with gensrc

function enumDict( dict, csn, node ) {
if (gensrcFlavor && dict[$inferred] ||
!keepElements( node ))
// no 'elements' with SELECT or inferred elements with gensrc;
// hidden or visible 'elements' will be set in query()
return undefined;
if (universalCsn && node.type && !node.type.$inferred && node.$expand === 'annotate')
// derived type of enum type with individual annotations: also set $origin
csn.$origin = originRef( node.type._artifact );
return insertOrderDict( dict );
}
function enumerableQueryElements( select ) {

@@ -656,3 +680,3 @@ if (!universalCsn || select === select._main._leadingQuery)

// Should we render the elements? (and items?)
function keepElements( node ) {
function keepElements( node, line ) {
if (universalCsn)

@@ -664,2 +688,13 @@ // $expand = null/undefined: not elements not via expansion

return true;
// keep many SimpleType/Entity
if (line) {
if (!node.type)
return true;
const array = node.type._artifact; // see function items() in propagator.js
const ltype = line.type && line.type._artifact;
if (!array || // reference errors
array._main && !line.elements && !line.enum && !line.items && !line.notNull &&
(!ltype || !ltype._main)) // many Foo:bar -> not SimpleType
return true;
}
// even if expanded elements have no new target or direct annotation,

@@ -812,23 +847,115 @@ // they might have got one via propagation – any new target/annos during their

}
if (kind && kind !== 'key')
addOrigin( c, art, art._origin );
return c;
}
function addOrigin( csn, xsn ) {
if (!universalCsn)
return csn;
// create $origin specification for `includes` of `art`
function includesOrigin( includes, art ) {
const $origin = originRef( includes[0]._artifact );
if (includes.length === 1)
return $origin;
const result = { $origin };
for (const incl of includes.slice(1)) {
const aspect = incl._artifact;
for (const prop in aspect) {
if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
const anno = aspect[prop];
if (anno.val !== null)
// matererialize non-null annos (whether direct or inherited)
result[prop] = value( Object.create( anno, { $inferred: { value: null } } ) );
}
}
}
return (Object.keys( result ).length === 1) ? $origin : result;
}
function addOrigin( csn, xsn, origin ) {
if (!universalCsn || hasExplicitProp( xsn.type ))
return;
if (xsn._from) {
csn.$origin = originRef( xsn._from[0]._origin );
const source = xsn._from[0]._origin;
csn.$origin = originRef( source );
if (source.params && !xsn.params)
csn.params = null; // discontinue `params` inheritance
if (source.actions && !xsn.actions)
csn.actions = null; // discontinue `actions` inheritance
return;
}
else if (xsn.includes && xsn.includes.length > 1) {
csn.$origin = { $origin: originRef( xsn.includes[0]._artifact ) };
else if (xsn.includes) {
csn.$origin = includesOrigin( xsn.includes, xsn );
return;
}
else if (xsn._origin && !hasExplicitProp( xsn.type ) && xsn._origin.kind !== 'builtin') {
let origin = xsn._origin;
while (origin._parent && origin._parent.$expand === 'origin')
origin = origin._origin || origin.type._artifact;
csn.$origin = originRef( origin );
else if (!xsn._main || xsn.kind === 'select') {
return;
}
return csn;
const parent = getParent( xsn );
const parentOrigin = getOrigin( parent );
if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
csn.$origin = null;
return;
}
// Skip all proxies which do not make it into the CSN, as there are no
// individual annotations or redirection targets on it:
while (origin._parent && origin._parent.$expand === 'origin')
origin = origin._origin || origin.type._artifact;
// The while loop is not only for the else case below: when setting implicit
// prototypes, it is important that we do not have to follow the prototypes of
// other object; we would need to ensure a right order to avoid issues otherwise.
if (parentOrigin === getParent( origin )) {
// implicit prototype or shortened reference
const { id } = origin.name || {};
if (id && xsn.name && id !== xsn.name.id)
csn.$origin = id;
return;
}
if (origin.kind === 'mixin') {
// currently, target and on are always set - nothing to do here, just set type
csn.type = 'cds.Association';
return;
}
const ref = originRef( origin, xsn );
if (ref) {
csn.$origin = ref;
return;
}
// An element of a query with a query in FROM:
const anon = definition( origin ); // use $origin: {...} if necessary
// as there are no implicit $origin prototypes on sub query elements (yet),
// we do not have to care about $origin not being set
const { $origin } = anon;
if ($origin && typeof $origin === 'object' && !Array.isArray( $origin )) {
// repeated anon: flatten
csn.$origin = Object.assign( $origin, anon );
}
else if (Object.keys( anon )
// (we can use the properties in `csn`, because addOrigin() is called last)
.every( p => p in csn || p === '$origin' || p === '$location')) {
// nothing new in $origin: {...}
addOrigin( csn, xsn, origin._origin );
}
else {
csn.$origin = anon;
}
}
function getParent( art ) {
const parent = art._parent;
const main = parent._main;
return (main && parent === main._leadingQuery) ? main : parent;
}
function getOrigin( art ) {
if (art._origin)
return art._origin;
if (hasExplicitProp( art.type ))
return art.type._artifact;
if (art.includes)
return art.includes[0]._artifact;
if (art._from)
return art._from[0]._origin;
return undefined;
}
function hasExplicitProp( ref ) {

@@ -838,15 +965,22 @@ return ref && !ref.$inferred;

function originRef( art ) {
function originRef( art, user ) {
const r = [];
// do not use name.element, as we allow `.`s in name
let main = art;
while (main._main && main.kind !== 'select') {
const nkind = normalizedKind[main.kind];
if (main.name.id || !r.length) // { param: "" } only for return, not elements inside
r.push( nkind ? { [nkind]: main.name.id } : main.name.id );
main = main._parent;
let parent = art;
while (parent._main && parent.kind !== 'select') {
const nkind = normalizedKind[parent.kind];
if (parent.name.id || !r.length)
// Return parameter is in XSN - kind: 'param', name.id: ''
// eslint-disable-next-line no-nested-ternary, max-len
r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
parent = parent._parent;
}
if (main._main) // well, an element of an query in FROM
return definition( art ); // use $origin: {}
if (user && parent._main && parent._main === user._main && parent !== user._main._leadingQuery)
// well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
return null; // probably use $origin: {...}
// for sub query in FROM in sub query in FROM, we could condense the info
// Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
if (r.length === 1 && normalizedKind[art.kind] === 'action')
return [ art.name.absolute, art.name.id ];
r.push( art.name.absolute );

@@ -866,9 +1000,7 @@ r.reverse();

}
else {
if (![
'element', 'key', 'param', 'enum', 'select', '$join',
'$tableAlias', 'annotation', 'mixin',
].includes(k))
csn.kind = k;
addOrigin( csn, node );
else if (![
'element', 'key', 'param', 'enum', 'select', '$join',
'$tableAlias', 'annotation', 'mixin',
].includes(k)) {
csn.kind = k;
}

@@ -1011,3 +1143,6 @@ }

// Enums can have values but if enums are extended, their kind is 'element',
// so we check whether the node is inside an extension.
// so we check whether the node is inside an extension. (TODO: still?)
if (universalCsn && v.$inferred)
return;
// (with gensrc, the symbol itself would not make it into the CSN)
if (node.kind === 'enum' || node._parent && node._parent.kind === 'extend')

@@ -1200,3 +1335,3 @@ Object.assign( csn, expression( v, true ) );

}
else { // null = use elements
else { // null = use elements - TODO: still used by A2J? -> remove
for (const name in xsn.elements)

@@ -1203,0 +1338,0 @@ addElementAsColumn( xsn.elements[name], csnColumns );

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

// Warn about unused doc-comments.
// Do not warn if docComments are explicitly disabled.
if (options.docComment !== false) {
for (const token of tokenStream.tokens) {
if (token.channel === antlr4.Token.HIDDEN_CHANNEL && token.type === parser.constructor.DocComment && !token.isUsed) {
messageFunctions.info('syntax-ignoring-doc-comment', parser.multiLineTokenLocation(token), {},
"Ignoring doc-comment as it does not belong to any artifact");
}
}
}
// TODO: clarify with LSP colleagues: still necessary?

@@ -167,0 +178,0 @@ if (parser.messages) {

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

tokenLocation,
multiLineTokenLocation,
previousTokenAtLocation,
combinedLocation,

@@ -249,5 +251,8 @@ surroundByParens,

this.$genericKeywords.argFull = Object.keys( spec );
// @ts-ignore
const token = this.getCurrentToken() || { text: '' };
if (spec[token.text.toUpperCase()] === 'argFull')
if (spec[token.text.toUpperCase()] === 'argFull') {
// @ts-ignore
token.type = this.constructor.GenericArgFull;
}
}

@@ -302,3 +307,3 @@

col: token.column + 1,
// we only have single-line tokens
// we only have single-line tokens, except for docComments
endLine: endToken.line,

@@ -312,2 +317,47 @@ endCol: endToken.stop - endToken.start + endToken.column + 2, // after the last char (special for EOF?)

/**
* Return location of `token`. In contrast to `tokenLocation()`, this function
* can handle multiline tokens.
*/
function multiLineTokenLocation(token, val) {
if (!token)
return undefined;
// Count the number of newlines in the token.
// TODO: I want to avoid a substring, that's why I don't use RegEx here
const source = token.source[1].data;
let newLineCount = 0;
let lastNewlineIndex = token.start;
for (let i = token.start; i < token.stop; i++) {
if (source[i] === 10) { // ASCII code for '\n'
newLineCount++;
lastNewlineIndex = i;
}
}
if (newLineCount === 0)
// endCol calculation below requires at least one newLine.
return this.tokenLocation(token, token, val);
/** @type {CSN.Location} */
const r = {
file: this.filename,
line: token.line,
col: token.column + 1,
endLine: token.line + newLineCount,
endCol: token.stop - lastNewlineIndex + 1, // after the last char (special for EOF?)
};
if (val !== undefined)
return { location: r, val };
return r;
}
function previousTokenAtLocation( location ) {
let k = -1;
let token = this._input.LT(k);
while (token.line > location.line ||
token.line === location.line && token.column >= location.col)
token = this._input.LT(--k);
return (token.line === location.line && token.column + 1 === location.col) && token;
}
// Create a location with location properties `filename` and `start` from

@@ -349,7 +399,11 @@ // argument `start`, and location property `end` from argument `end`.

function docComment( node ) {
if (!this.options.docComment)
return;
const token = this._input.getHiddenTokenToLeft( this.constructor.DocComment );
if (!token)
return;
// This token is actually used by / assigned to an artifact.
token.isUsed = true;
if (!this.options.docComment)
return;
if (node.doc) {

@@ -359,3 +413,3 @@ this.warning( 'syntax-duplicate-doc-comment', token, {},

}
node.doc = this.tokenLocation( token, token, parseDocComment( token.text ) );
node.doc = this.multiLineTokenLocation( token, parseDocComment( token.text ) );
}

@@ -444,10 +498,14 @@

const { args, id, location } = path[0];
if (args) {
if (path[0].$syntax !== ':')
return { op: { location, val: 'call' }, func: ref, location: ref.location, args };
}
else if (!path[0].$delimited && functionsWithoutParens.includes( id.toUpperCase() )) {
return { op: { location, val: 'call' }, func: ref, location: ref.location };
}
return ref;
if (args
? path[0].$syntax === ':'
: path[0].$delimited || !functionsWithoutParens.includes( id.toUpperCase() ))
return ref;
const implicit = this.previousTokenAtLocation( location );
if (implicit && implicit.isIdentifier)
implicit.isIdentifier = 'func';
const op = { location, val: 'call' };
return (args)
? { op, func: ref, location: ref.location, args }
: { op, func: ref, location: ref.location };
}

@@ -611,3 +669,3 @@

}
else if (env === 'artifacts') {
else if (env === 'artifacts' || env === 'vocabularies') {
dictAddArray( parent[env], art.name.id, art );

@@ -614,0 +672,0 @@ }

@@ -7,2 +7,6 @@ // Official cds-compiler API.

// Author's note: All "options" interfaces should actually be types. However, due to
// https://github.com/TypeStrong/typedoc/issues/1519 we can't use
// intersection types at the moment.
export = compiler;

@@ -15,3 +19,3 @@

*/
export type Options = {
export interface Options {
[option: string]: any,

@@ -63,2 +67,98 @@

/**
* Options used by OData backends. Includes options for the OData
* transformer as well as for rendering EDM and EDMX.
*/
export interface ODataOptions extends Options {
/**
* OData version for output files. Either 'v4' or 'v2'.
*
* @default 'v4'
*/
odataVersion?: string | 'v4' | 'v2'
/**
* Whether to generate OData as flat or as structured.
* Structured is only supported for OData v4.
*
* @default 'flat'
*/
odataFormat?: string | 'flat' | 'structured'
/**
* Naming mode used by the corresponding SQL.
*
* @default 'plain'
*/
sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
/**
* If `true`, `cds.Compositions` are rendered as `edm:NavigationProperty` with the additional
* attribute `ContainsTarget="true"` and all contained entities (composition targets) have no
* `edm.EntitySet`.
*
* @note Only available for OData v4 EDM(X) rendering.
* @default false
*/
odataContainment?: boolean
/**
* If `true`, render generated foreign keys for managed associations.
* By default foreign keys are never visible in structured OData APIs.
*
* @note Only available for structured OData v4 EDM(X) rendering.
* @default false
*/
odataForeignKeys?: boolean
/**
* If `true`, association targets outside of the current service are added as
* `edm.EntityType` that only exposes their primary keys and have no `edm.EntitySet`.
* If the original association target is a service member, a corresponding `edm.Schema`
* representing the namespace of that service is added to `edm.Services`. All association
* targets that are no service members are collected in an `edm.Schema` with namespace `root`.
*
* @note Only valid for structured OData v4 EDM(X) rendering.
* @default false
* @since v2.1.0
*/
odataProxies?: boolean
/**
* This option is an extension to `odataProxies`.
* If `true`, an `edm:Reference` instead of a proxy `edm.EntityType` is rendered for each
* association target that is a service member outside the current service instead of proxies.
*
* @note Only valid for structured OData v4 EDM(X) rendering.
* @default false
* @since v2.1.0
*/
odataXServiceRefs?: boolean
/**
* The OData specification requires that all primary keys of the principal must be used as
* referential constraints. If an association is modelled with only a partial key, no
* referential constraints are added. If `true`, partial constraints are rendered for
* backwards compatibility and mocking scenarios. A spec violation warning is raised for
* each incomplete constraint.
*
* @note Only valid for OData v2 CSN transformation.
* @default false
* @since v2.2.6
*/
odataV2PartialConstr?: boolean
/**
* Service name for which EDMX or EDM shall be rendered.
*
* @note Only available for `to.edmx()` and `to.edm()`. For `to.edmx.all()`
* and `to.edm.all()`, use `serviceNames` instead.
*
* @see serviceNames
*/
service?: string
/**
* Array of service names for which EDMX or EDM shall be rendered.
* If unspecified, all services are rendered.
*
* @note Only available for `to.edmx.all()` and `to.edm.all()`. For `to.edmx()`
* and `to.edm()`, use `service` instead.
*
* @see service
*/
serviceNames?: string[]
}
/**
* The compiler's package version.

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

* @param filenames Array of files that should be compiled.
* @param dir Working directory. Relative paths in `filenames` will be resolved relatively to this directory.
* @param options Compiler options. If you do not set `messages`, they will be printed to console.

@@ -145,3 +246,3 @@ * @param fileCache A dictionary of absolute file names to the file content with values:

* Example of sorted messages:
* ```txt
* ```
* A.cds:1:11: Info id-3: First message text (in entity:“E”/element:“c”)

@@ -196,6 +297,7 @@ * A.cds:8:11: Error id-5: Another message text (in entity:“C”/element:“g”)

* Example:
* ```txt
* ```
* <source>.cds:3:11: Error message-id: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
* ```
*
* @param msg Compiler message which shall be stringified.
* @param normalizeFilename If true, the file path will be normalized to use `/` as the path separator.

@@ -210,10 +312,9 @@ * @param noMessageId If true, the message ID will _not_ be part of the string.

* in multiline form.
* The error (+ message id) will be colored according to their severity if
* run on a TTY.
* The error (+ message id) can colored according to their severity.
*
* Example:
* ```txt
* Error[message-id]: Can't find type `nu` in this scope (in entity:“E”/element:“e”)
* ```
* Error[message-id]: Can't find type `nu` in this scope
* |
* <source>.cds:3:11, at entity:“E”
* <source>.cds:3:11, at entity:“E”/element:“e”
* ```

@@ -225,2 +326,6 @@ *

* @param config.withLineSpacer If true, an additional line (with `|`) will be inserted between message and location.
* @param config.color If true, ANSI escape codes will be used for coloring the severity. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
*/

@@ -232,2 +337,3 @@ export function messageStringMultiline(msg: CompileMessage, config?: {

withLineSpacer?: boolean
color?: boolean | 'auto'
}): string;

@@ -245,3 +351,3 @@

* Example Output:
* ```txt
* ```
* |

@@ -252,6 +358,14 @@ * 13 | num * nu

*
* @param sourceLines The source code split up into lines, e.g. by `str.split(/\r\n?|\n/);`.
* @param msg Message whose location is used to print the message context.
* @param sourceLines The source code split up into lines, e.g. by `str.split(/\r\n?|\n/);`.
* @param msg Message whose location is used to print the message context.
* @param config Configuration for the message context.
* @param config.color If true, ANSI escape codes will be used for coloring the severity. If false, no
* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* unset.
*/
export function messageContext(sourceLines: string[], msg: CompileMessage): string;
export function messageContext(sourceLines: string[], msg: CompileMessage, config?: {
color?: boolean | 'auto'
}): string;

@@ -330,3 +444,8 @@ /**

export namespace For {
function odata(): any;
/**
* Transform the given (generic) CSN into one that is used for OData.
* Changes include flattening, type resolution and more, according to
* the provided options.
*/
function odata(csn: CSN, options: ODataOptions): any;
}

@@ -338,10 +457,10 @@

function edm(csn: CSN, options: Options): any;
function edm(csn: CSN, options: ODataOptions): any;
namespace edm {
function all(csn: CSN, options: Options): any;
function all(csn: CSN, options: ODataOptions): any;
}
function edmx(csn: CSN, options: Options): any;
function edmx(csn: CSN, options: ODataOptions): any;
namespace edmx {
function all(csn: CSN, options: Options): any;
function all(csn: CSN, options: ODataOptions): any;
}

@@ -348,0 +467,0 @@

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

const { parseX, compileX, compileSyncX, compileSourcesX, InvocationError } = require('./compiler');
const { fns } = require('./compiler/shared');
const { define } = require('./compiler/definer');

@@ -47,3 +48,3 @@

const sources = Object.create(null);
const model = { sources, options };
const model = { sources, options, $functions: {}, $volatileFunctions: {} };
const messageFunctions = createMessageFunctions( options, 'parse', model );

@@ -55,2 +56,3 @@ model.$messageFunctions = messageFunctions;

sources[filename] = xsn;
fns( model );
define( model );

@@ -57,0 +59,0 @@ messageFunctions.throwWithError();

@@ -18,3 +18,3 @@ // Miscellaneous CSN functions we put into our compiler API

const defaultFunctions = {
'@': () => {}, // do not traverse annotation assignments
'@': () => { /* do not traverse annotation assignments */ },
args: dictionary,

@@ -27,3 +27,3 @@ elements: dictionary,

definitions: dictionary,
'$': () => {}, // do not traverse properties starting with '$'
'$': () => { /* do not traverse properties starting with '$' */},
}

@@ -30,0 +30,0 @@

@@ -228,13 +228,15 @@ // CSN functionality for resolving references

return cachedType;
else if (!art.type && !art.$origin ||
art.elements || art.target || art.targetAspect || art.enum)
return setCache( art, '_effectiveType', art );
const chain = [];
while (getCache( art, '_effectiveType' ) === undefined && (art.type || art.$origin) &&
let origin;
while (getCache( art, '_effectiveType' ) === undefined &&
(origin = cached( art, '_origin', getOriginRaw )) &&
!art.elements && !art.target && !art.targetAspect && !art.enum && !art.items) {
chain.push( art );
setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
art = (art.$origin) ? getOrigin( art ) : artifactRef( art.type, BUILTIN_TYPE );
art = origin;
}
if (!chain.length)
return setCache( art, '_effectiveType', art );
if (getCache( art, '_effectiveType' ) === 0)

@@ -255,6 +257,12 @@ throw new Error( 'Circular type reference');

// semantic check)
while (type.items)
while (type.items) {
cached( type, '$origin', _a => setImplicitOrigin( type, origin ) );
type = effectiveType( type.items );
}
// cannot navigate along targetAspect!
return (type.target) ? csn.definitions[type.target] : type;
const env = (type.target) ? csn.definitions[type.target] : type;
const origin = cached( env, '_origin', getOriginRaw );
if (origin && origin !== BUILTIN_TYPE)
cached( env, '$origin', _a => setImplicitOrigin( env, origin ) );
return env;
}

@@ -284,36 +292,100 @@

let art = csn.definitions[pathId( head )];
for (const elem of tail)
art = navigationEnv( art ).elements[pathId( elem )];
for (const elem of tail) {
const env = navigationEnv( art );
art = env.elements[pathId( elem )];
}
return art;
}
function getOrigin( def ) {
const art = cached( def, '_origin', originPathRef );
if (art)
return art;
throw new Error( 'Undefined origin reference' );
function getOrigin( art, alsoType ) {
const origin = cached( art, '_origin', getOriginRaw );
if (origin && origin !== BUILTIN_TYPE)
cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
return art.type && !alsoType ? undefined : origin;
}
function originPathRef( def ) {
const [ head, ...tail ] = def.$origin;
let art = csn.definitions[head];
for (const elem of tail)
art = originNavigation( art, elem );
return art;
function getOriginRaw( art ) {
if (art.type) // TODO: make robust against "linked" = only direct
return artifactRef( art.type, BUILTIN_TYPE );
if (!art.$origin) // implicit $origin should have been set
return null;
// art.$origin must not be a string here - shortened refs should already
// have been used to set the _origin cache
if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
return cached( art.$origin, '_origin', getOriginRaw );
const [ head, ...tail ] = art.$origin;
let origin = csn.definitions[head];
// allow shorter $origin ref for actions/functions, just using a string:
let isAction = art.kind === 'action' || art.kind === 'function';
for (const elem of tail) {
origin = originNavigation( origin, elem, isAction );
isAction = false;
}
return origin;
}
function originNavigation( art, elem ) {
function originNavigation( art, elem, isAction ) {
if (typeof elem !== 'string') {
if (elem.action)
return art.actions[elem.action]
return art.actions[elem.action];
if (elem.param)
return (elem.param ? art.params[elem.param] : art.returns);
return art.params[elem.param];
if (elem.returns)
return art.returns;
}
if (art.returns)
if (isAction)
return art.actions[elem];
// TODO: if we use effectiveType(), we might have more implicit prototypes
// we might then need a function like effectiveArtifact,
// which cares about actions/params
// let origin = cached( art, '_origin', getOriginRaw );
// while (art.items) {
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
// art = art.items;
// origin = cached( art, '_origin', getOriginRaw );
// }
// if (origin)
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
}
// From the current CSN object, set implicit origin for the next navigation step
// Currently (TODO: ?) `elements` only, i.e. what is needed for name resolution.
function setImplicitOrigin( art, origin ) {
setMembersImplicit( art.actions, origin.actions );
setMembersImplicit( art.params, origin.params );
if (art.returns) {
art = art.returns;
while (art.items)
if (art.type || typeof art.$origin === 'object') // null, […], {…}
return true; // not implicit or shortened
origin = effectiveType( origin.returns );
setCache( art, '_origin', origin );
return true;
}
while (art.items) {
art = art.items;
return (art.elements || art.enum || (art.targetAspect || art.target).elements)[elem];
if (art.type || typeof art.$origin === 'object') // null, […], {…}
return true; // not implicit or shortened
origin = effectiveType( origin.items );
setCache( art, '_origin', origin );
}
setMembersImplicit( art.elements, origin.elements );
// The enum base type is _not_ where we find the origins of the enum symbols.
// A derived type of an enum type with individual annotations on a symbol
// has both 'type' and '$origin'.
if (!art.type || art.$origin)
setMembersImplicit( art.enum, origin.enum );
return true;
}
function setMembersImplicit( members, originMembers ) {
if (!members)
return;
for (const name in members) {
const elem = members[name];
if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
setCache( elem, '_origin', originMembers[elem.$origin || name] || false );
}
}
/**

@@ -377,2 +449,5 @@ * Return the entity we select from

const origin = cached( main, '_origin', getOriginRaw )
if (origin)
cached( main, '$origin', _a => setImplicitOrigin( main, origin ) );
cached( main, '$queries', allQueries );

@@ -394,3 +469,3 @@ let qcache = query && cache.get( query.projection || query );

if (alias)
return resolvePath( path, alias._select || alias, 'alias', cache.$queryNumber );
return resolvePath( path, alias._select || alias._ref, 'alias', cache.$queryNumber );
const mixin = cache._select.mixin && cache._select.mixin[head];

@@ -489,3 +564,4 @@ if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))

const as = query.as || implicitAs( query.ref );
getCache( fromSelect, '$aliases' )[as] = fromRef( query );
const _ref = fromRef( query );
getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
}

@@ -616,3 +692,3 @@ else {

* @param {CSN.QuerySelect} fromSelect
* @param {CSN.Query} parentQuery
* @param {CSN.Query} parentQuery
* @param {(query: CSN.Query&CSN.QueryFrom, select: CSN.QuerySelectEnriched) => void} callback

@@ -642,4 +718,5 @@ */

* @param {CSN.QueryFrom} from
* @param {CSN.QuerySelect} select
* @param {(from: CSN.QueryFrom, select: CSN.QuerySelect) => void} callback
* @param {CSN.QuerySelect} fromSelect
* @param {CSN.Query} parentQuery
* @param {(from: CSN.QueryFrom, select: CSN.QuerySelect, parentQuery: CSN.Query) => void} callback
*/

@@ -646,0 +723,0 @@ function traverseFrom( from, fromSelect, parentQuery, callback ) {

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

const { csnRefs } = require('../model/csnRefs');
const { isBuiltinType } = require('../compiler/builtins.js')
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');

@@ -15,4 +16,4 @@ const version = require('../../package.json').version;

* @callback genericCallback
* @param {CSN.Artifact} art
* @param {CSN.FQN} name Artifact Name
* @param {any} art
* @param {string} name Artifact Name
* @param {string} prop Dictionary Property

@@ -462,4 +463,4 @@ * @param {CSN.Path} path Location

}
/**

@@ -503,8 +504,8 @@ * Resolve to the final type of a type, that means follow type chains, references to other types or

* no type (e.g. expr in a view without explicit type) returns 'null'
*
*
* @param {string|object} type Type - either string or ref
* @param {CSN.Path} path
* @param {CSN.Path} path
* @param {WeakMap} [resolved=new WeakMap()] WeakMap containing already resolved refs - if a ref is not cached, it will be resolved JIT
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
* @returns
* @param {object} [cycleCheck] Dictionary to remember already resolved types - to be cycle-safe
* @returns
*/

@@ -559,12 +560,2 @@ function getFinalBaseType(type, path = [], resolved = new WeakMap(), cycleCheck = undefined) {

// Tell if a type is (directly) a builtin type
// Note that in CSN builtins are not in the definition of the model, so we can only check against their absolute names.
// Builtin types are "cds.<something>", i.e. they are directly in 'cds', but not for example
// in 'cds.foundation'. Also note, that a type might be a ref object, that refers to something else,
// so if you consider type chains don't forget first to resolve to the final type before
function isBuiltinType(type) {
return typeof(type) === 'string' && type.startsWith('cds.') && !type.startsWith('cds.foundation.')
}
/**

@@ -825,3 +816,3 @@ * Deeply clone the given CSN model and return it.

function forAllElements(artifact, artifactName, cb){
function forAllElements(artifact, artifactName, cb, includeActions = false){
if(artifact.elements) {

@@ -831,2 +822,16 @@ cb(artifact, artifact.elements, ['definitions', artifactName, 'elements']);

if(includeActions && artifact.actions) {
Object.entries(artifact.actions).forEach( ([actionName, action]) => {
const path = ['definitions', artifactName, 'actions', actionName];
if(action.params) {
Object.entries(action.params).forEach( ([paramName, param]) => {
if(param.elements)
cb(param, param.elements, path.concat(['params', paramName, 'elements']));
});
}
if(action.returns && action.returns.elements)
cb(action.returns, action.returns.elements,path.concat(['returns', 'elements']));
});
}
if(artifact.query) {

@@ -881,3 +886,3 @@ forAllQueries(artifact.query, (q, p) => {

* @param {CSN.Element} elementCsn
* @param {CSN.Options & CSN.ODataOptions} options EDM specific options
* @param {ODataOptions} options EDM specific options
*/

@@ -932,6 +937,6 @@ function isEdmPropertyRendered(elementCsn, options) {

else {
const namespace = csn;
console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
if (namingConvention === 'hdbcds') {
if (namespace) {
if (csn) {
const namespace = String(csn);
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;

@@ -1544,3 +1549,3 @@ }

const sorted = Object.create(null);
Object.keys(csn.definitions).sort().forEach((name) => {
Object.keys(csn.definitions || {}).sort().forEach((name) => {
sorted[name] = csn.definitions[name];

@@ -1555,3 +1560,3 @@ });

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

@@ -1570,4 +1575,4 @@ function getServiceNames(csn) {

* Check wether the artifact is @cds.persistence.skip
*
* @param {CSN.Artifact} artifact
*
* @param {CSN.Artifact} artifact
* @returns {Boolean}

@@ -1581,5 +1586,5 @@ */

* Walk path in the CSN and return the result.
*
* @param {CSN.Model} csn
* @param {CSN.Path} path
*
* @param {CSN.Model} csn
* @param {CSN.Path} path
* @returns {object} Whatever is at the end of path

@@ -1597,2 +1602,30 @@ */

/**
* If provided, get the replacement string for the given magic variable ref.
* No validation is done that the ref is actually magic!
*
* @param {array} ref
* @param {CSN.Options} options
* @returns {string|null}
*/
function getVariableReplacement(ref, options) {
if(options && options.variableReplacements) {
let replacement = options.variableReplacements;
for(let i = 0; i < ref.length; i++) {
replacement = replacement[ref[i]];
if(replacement === undefined)
return null;
}
if(replacement === undefined)
return null; // no valid replacement found
else if(typeof replacement === 'string')
return replacement; // valid replacement
else
return null; // $user.foo, but we only have configured $user.foo.bar -> error
} else {
return null;
}
}
module.exports = {

@@ -1638,2 +1671,3 @@ getUtils,

walkCsnPath,
getVariableReplacement
};

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

mixin: dictionary,
returns: definition,
items: definition,
ref: pathRef,

@@ -85,6 +87,15 @@ type: simpleRef,

function definition( parent, prop, obj ) {
const origin = getOrigin( obj ); // before standard for implicit protos inside
standard( parent, prop, obj );
if (obj.$origin === undefined && origin != null)
obj._origin = refLocation( origin );
}
function dictionary( parent, prop, dict ) {
if (!dict) // value null for inheritance interruption
return;
csnPath.push( prop );
for (let name of Object.getOwnPropertyNames( dict )) {
standard( dict, name, dict[name] );
definition( dict, name, dict[name] );
}

@@ -128,9 +139,13 @@ if (!Object.prototype.propertyIsEnumerable.call( parent, prop ))

if (options.testMode) {
if (Array.isArray( ref )) // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent ) );
if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent, true ) );
else if ( ref )
standard( parent, prop, ref )
}
else {
try {
if (Array.isArray( ref )) // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent ) );
if (Array.isArray( ref ) || typeof ref === 'string') // $origin: […], not $origin: {…}
parent._origin = refLocation( getOrigin( parent, true ) );
else if ( ref )
standard( parent, prop, ref )
} catch (e) {

@@ -177,6 +192,6 @@ parent._origin = e.toString();

function _cache_debug( obj ) {
function _cache_debug( obj, subCache ) {
if (options.enrichCsn !== 'DEBUG')
return;
const cache = __getCache_forEnrichCsnDebugging( obj );
const cache = subCache || __getCache_forEnrichCsnDebugging( obj );
if (!cache)

@@ -204,4 +219,16 @@ return;

// ‹name›: dictionary of CSN nodes,
// ‹$name›: dictionary of cache values if no prototype,
obj.$$cacheObject[name] = Object.keys( val ); // TODO: or dict?
// ‹$name›: dictionary of cache values if no prototype
if (name !== '$aliases') {
obj.$$cacheObject[name] = Object.keys( val ); // TODO: or dict?
}
else {
const sub = Object.create(null);
for (const n in val) {
const alias = val[n];
const c = {};
_cache_debug( c, alias );
sub[n] = c.$$cacheObject;
}
obj.$$cacheObject[name] = sub;
}
}

@@ -208,0 +235,0 @@ else if (Array.isArray( val )) {

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

const $inferred = Symbol.for('cds.$inferred');
class NOT_A_DICTIONARY {} // used for consol.log display

@@ -36,7 +38,7 @@

// represent table alias in from / join-args property as link:
$tableAlias: (art, parent) => art._parent && parent !== art._parent.$tableAliases,
// represent table alias in JOIN node as link:
$tableAlias: tableAliasAsLink,
// represent "navigation elemens" in _combined as links:
$navElement: (art, parent) => art._parent && parent !== art._parent.elements,
// represent mixin in $tableAliases as link:
mixin: (art, parent) => art._parent && parent !== art._parent.mixin,
mixin: tableAliasAsLink,
// represent $projection as link, as it is just another search name for $self:

@@ -46,2 +48,9 @@ $self: (_a, _p, name) => name !== '$self',

function tableAliasAsLink( art, parent, name ) {
return art._parent && art._parent.$tableAliases && // initXYZ() is run
parent !== art._parent.$tableAliases && // not in $tableAliases
!(art.$duplicates === true && name && // and its $duplicates
parent === art._parent.$tableAliases[name].$duplicates);
}
function revealInternalProperties( model, name ) {

@@ -71,2 +80,3 @@ const transformers = {

$tableAliases: dictionary,
$duplicates: duplicates,
$keysNavigation: dictionary,

@@ -210,2 +220,4 @@ $layerNumber: n => n,

}
if (node[$inferred] && !node['[$inferred]'])
r['[$inferred]'] = node[$inferred];
return r;

@@ -236,3 +248,3 @@ }

if (Array.isArray(node))
return node.map( n => reveal( n, node ) );
return node.map( n => reveal( n, node, name ) );

@@ -255,2 +267,6 @@ const asLinkTest = kindsRepresentedAsLinks[ node.kind ];

}
function duplicates( node, parent ) {
return reveal( node, parent, parent.name && parent.name.id );
}
}

@@ -257,0 +273,0 @@

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

* @param afterModel the after-model
* @param {hdiOptions|false} options
* @param {import('../api/main.js').hdiOptions|false} options
* @returns {object} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model

@@ -21,2 +21,3 @@ * to the after-model, together with all the definitions of the after-model

function compareModels(beforeModel, afterModel, options) {
// @ts-ignore
if(!(options && options.testMode)) // no $version with testMode

@@ -23,0 +24,0 @@ validateCsnVersions(beforeModel, afterModel, options);

@@ -30,4 +30,6 @@ 'use strict';

.option(' --beta <list>')
.option(' --constraints-not-validated')
.option(' --constraints-not-enforced')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', [ 'true', 'false', 'individual' ])
.option(' --assert-integrity-type <type>', [ 'RT', 'DB' ])
.option(' --deprecated <list>')

@@ -43,3 +45,4 @@ .option(' --hana-flavor')

.option(' --localized-without-coalesce')
.option(' --defaultStringLength <length>')
.option(' --default-binary-length <length>')
.option(' --default-string-length <length>')
.option(' --no-recompile')

@@ -80,3 +83,4 @@ .positionalArgument('<files...>')

Type options
--defaultStringLength <length> Default 'length' for 'cds.String'
--default-binary-length <length> Default 'length' for 'cds.Binary'
--default-string-length <length> Default 'length' for 'cds.String'

@@ -96,3 +100,2 @@ Diagnostic options

Valid values are:
foreignKeyConstraints
addTextsLanguageAssoc

@@ -102,7 +105,15 @@ hanaAssocRealCardinality

ignoreAssocPublishingInUnion
windowFunctions
--constraints-not-enforced If this option is supplied, referential constraints are NOT ENFORCED
This option is also applied to result of "cdsc manageConstraints"
--constraints-not-validated If this option is supplied, referential constraints are NOT VALIDATED
This option is also applied to result of "cdsc manageConstraints"
--integrity-not-enforced If this option is supplied, referential constraints are NOT ENFORCED.
This option is also applied to result of "cdsc manageConstraints"
--integrity-not-validated If this option is supplied, referential constraints are NOT VALIDATED.
This option is also applied to result of "cdsc manageConstraints"
--assert-integrity <mode> Turn DB constraints on/off:
true : Constraints will be generated for all associations if
the assert-integrity-type is set to DB
false : No constraints will be generated
individual : Constraints will be generated for selected associations
--assert-integrity-type <type> Specifies how the referential integrity checks should be performed:
RT : (default) No database constraint for an association
if not explicitly demanded via annotation
DB : Create database constraints for associations
--deprecated <list> Comma separated list of deprecated options.

@@ -138,3 +149,3 @@ Valid values are:

--no-recompile Don't recompile in case of internal errors
Commands

@@ -149,3 +160,3 @@ H, toHana [options] <files...> Generate HANA CDS source files

toRename [options] <files...> (internal) Generate SQL DDL rename statements
manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
manageConstraints [options] <files...> (internal) Generate ALTER TABLE statements to
add / modify referential constraints.

@@ -160,3 +171,2 @@ `);

.option(' --joinfk')
.option(' --skip-db-constraints')
.option('-u, --user <user>')

@@ -185,3 +195,2 @@ .option('-s, --src')

--joinfk Create JOINs for foreign key accesses
--skip-db-constraints Do not render referential constraints for associations
-u, --user <user> Value for the "$user" variable

@@ -253,3 +262,2 @@ -s, --src (default) Generate HANA CDS source files "<artifact>.hdbcds"

.option(' --joinfk')
.option(' --skip-db-constraints')
.option('-d, --dialect <dialect>', ['hana', 'sqlite', 'plain'])

@@ -280,3 +288,2 @@ .option('-u, --user <user>')

--joinfk Create JOINs for foreign key accesses
--skip-db-constraints Do not render referential constraints for associations
-d, --dialect <dialect> SQL dialect to be generated:

@@ -329,3 +336,3 @@ plain : (default) Common SQL - no assumptions about DB restrictions

referential constraints on an existing model.
Combine with options "--constraints-not-enforced" and "--constraint-not-validated"
Combine with options "--integrity-not-enforced" and "--integrity-not-validated"
to switch off foreign key constraint enforcement / validation.

@@ -378,2 +385,3 @@

.option('-h, --help')
.positionalArgument('<file>')
.help(`

@@ -391,2 +399,3 @@ Usage: cdsc parseCdl [options] <file>

.option('-h, --help')
.positionalArgument('<message-id>')
.help(`

@@ -398,4 +407,6 @@ Usage: cdsc explain [options] <message-id>

Use \`explain list\` to list all available messages.
Options
-h, --help Show this help text
-h, --help Show this help text
`);

@@ -402,0 +413,0 @@

@@ -47,3 +47,3 @@ /**

* @param {string} name Persistence name of the artifact
* @param {CSN.Location} location CSN location of the artifact
* @param {CSN.Location|CSN.Path} location CSN location of the artifact
* @param {string} modelName CSN artifact name

@@ -50,0 +50,0 @@ */

@@ -7,3 +7,3 @@

hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
forEachDefinition, getResultingName,
forEachDefinition, getResultingName, getVariableReplacement,
} = require('../model/csnUtils');

@@ -19,3 +19,3 @@ const {

const { makeMessageFunction } = require('../base/messages');
const timetrace = require('../utils/timetrace');
const { timetrace } = require('../utils/timetrace');
const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');

@@ -572,3 +572,3 @@ const { smartFuncId } = require('../sql-identifier');

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

@@ -1060,3 +1060,3 @@

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

@@ -1390,2 +1390,4 @@

const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
if (x.xpr)
return renderWindowFunction(funcName, x, env);
return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env, null));

@@ -1413,2 +1415,9 @@ }

function renderWindowFunction(funcName, node, env) {
const suffix = node.xpr.shift(); // OVER
let r = `${funcName}(${renderArgs(node, '=>', env, null)})`;
r += ` ${suffix} (${renderExpr(node.xpr, env)})`;
return r;
}
function renderExpressionLiteral(x) {

@@ -1450,3 +1459,8 @@ // Literal value, possibly with explicit 'literal' property

if (!x.param && !x.global) {
const magicReplacement = getVariableReplacement(x.ref, options);
if (x.ref[0] === '$user') {
if (magicReplacement !== null)
return `'${magicReplacement}'`;
const result = render$user(x);

@@ -1463,2 +1477,5 @@ // Invalid second path step doesn't cause a return

}
else if (x.ref[0] === '$session' && magicReplacement !== null) {
return `'${magicReplacement}'`;
}
}

@@ -1486,2 +1503,3 @@ // FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we

if (x.ref[1] === 'id') {
// Keep the old-style for compatibilty with magicVars.id - instead of magicVars.user.id...
if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String)

@@ -1506,3 +1524,3 @@ return `'${options.toSql.user}'`;

}
// Basically: Second path step was invalid, do nothing
// Basically: Second path step was invalid, do nothing - should not happen, see 'unknownMagic.js'
return null;

@@ -1509,0 +1527,0 @@ }

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

let enforced = true;
if (options.constraintsNotValidated)
if (options.integrityNotValidated)
validated = false;
if (options.constraintsNotEnforced)
if (options.integrityNotEnforced)
enforced = false;

@@ -28,14 +28,17 @@

// prepare the functions with the compositions and associations across all entities first
// and execute it afterwards because compositions must be processed first
// and execute it afterwards.
// compositions must be processed first, as the <up_> links for them must result in `ON DELETE CASCADE`
const compositions = [];
const associations = [];
forEachDefinition(csn, (artifact, artifactName) => {
if (!artifact.query && artifact.kind === 'entity' ) {
if (!artifact.query && artifact.kind === 'entity') {
forAllElements(artifact, artifactName, (parent, elements, path) => {
// Step I: iterate compositions, enrich dependent keys (in target entity)
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
for (const elementName in elements) {
const element = elements[elementName];
if (element.type === COMPOSITION && !treatCompositionLikeAssociation(element)) {
compositions.push(() => {
foreignKeyConstraintForComposition(element, parent, path.concat([ elementName ]));
if (element.type === COMPOSITION && element.$selfOnCondition) {
compositions.push({
fn: () => {
foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
},
});

@@ -47,6 +50,7 @@ }

const element = elements[elementName];
if (element.type === ASSOCIATION ||
element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
associations.push(() => {
foreignKeyConstraintForAssociation(element, elements, path.concat([ elementName ]), elementName);
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
associations.push({
fn: () => {
foreignKeyConstraintForAssociation(element, path.concat([ elementName ]));
},
});

@@ -59,35 +63,30 @@ }

// create constraints on foreign keys
compositions.forEach(fn => fn());
associations.forEach(fn => fn());
// always process unmanaged first, up_ links must be flagged
// before they are processed
compositions.forEach(composition => composition.fn());
associations.forEach(association => association.fn());
// Step III: Create the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation
forEachDefinition(csn, collectAndAttachReferentialConstraints);
/**
* Calculate referential constraints for dependent keys in target entity of cds.Composition.
* The DELETE rule for a referential constraint stemming from a composition will be CASCADE.
* A managed composition with a target cardinality of one, will be treated like a regular Association.
* Retrieve the <up_> link of an `cds.Composition` used in an on-condition like `$self = <comp>.<up_>`
* and calculate a foreign key constraint for this association if it is constraint compliant.
* The constraint will have an `ON DELETE CASCADE`.
*
* @param {CSN.Element} composition for that a constraint should be generated
* @param {CSN.Element} composition which might has the `$self = <comp>.<up_>` on-condition
* @param {CSN.Artifact} parent artifact containing the composition
* @param {CSN.Path} path
*/
function foreignKeyConstraintForComposition(composition, parent, path) {
if (skipConstraintGeneration(parent, composition))
function foreignKeyConstraintForUpLinkOfComposition(composition, parent, path) {
const dependent = csn.definitions[path[1]];
if (skipConstraintGeneration(parent, dependent, composition))
return;
const { elements } = parent;
const onCondition = composition.on;
if (onCondition) {
if (hasConstraintCompliantOnCondition(composition, elements, path)) {
// 1. cds.Composition has constraint compliant on-condition
// mark each dependent key referenced in the on-condition (in target entity)
const dependentKeys = Array.from(elementsOfTargetSide(onCondition, csn.definitions[composition.target].elements));
const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
const { backlinkName } = composition.$selfOnCondition || {};
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
// also: no constraints for compositions of many w/o backlink
if (dependentKeys.length === parentKeys.length && backlinkName)
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3], path, backlinkName, 'CASCADE');
}
if (composition.$selfOnCondition && composition.$selfOnCondition.up_.length === 1) {
const upLinkName = composition.$selfOnCondition.up_[0];
const up_ = csn.definitions[composition.target].elements[upLinkName];
if (up_.keys && isToOne(up_)) // no constraint for unmanaged / to-many up_ links
foreignKeyConstraintForAssociation(up_, [ 'definitions', composition.target, 'elements', upLinkName ], path[path.length - 1]);
}

@@ -102,13 +101,14 @@ else if (!onCondition && composition.keys.length > 0) {

* The DELETE rule for a referential constraint stemming from a cds.Association will be 'RESTRICT'
* If the association is used as an <up_> link in a compositions on-condition, the ON DELETE rule will be `CASCADE`
*
* @param {CSN.Association} association for that a constraint should be generated
* @param {CSN.Elements} elements of parent entity.
* @param {CSN.Path} path
* @param {string} assocName passed through as proper constraint suffix
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
*/
function foreignKeyConstraintForAssociation(association, elements, path, assocName) {
const associationTarget = csn.definitions[association.target];
if (skipConstraintGeneration(associationTarget, association))
function foreignKeyConstraintForAssociation(association, path, upLinkFor = null) {
const parent = csn.definitions[association.target];
const dependent = csn.definitions[path[1]];
if (skipConstraintGeneration(parent, dependent, association))
return;
const { elements } = csn.definitions[path[1]];
const onCondition = association.on;

@@ -119,6 +119,6 @@ if (onCondition && hasConstraintCompliantOnCondition(association, elements, path)) {

const dependentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
const parentKeys = Array.from(elementsOfTargetSide(onCondition, parent.elements));
// sanity check; do not generate constraints for on-conditions like "dependent.idOne = id AND dependent.idTwo = id"
if (dependentKeys.length === parentKeys.length)
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path, assocName);
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path[path.length - 1], upLinkFor);
}

@@ -138,10 +138,12 @@ else if (!onCondition && association.keys.length > 0) {

* @param {CSN.PathSegment} parentTable the sql-table where the foreign key constraints will be pointing to
* @param {CSN.Path} path
* @param {string | null} constraintIdentifierSuffix name of the association / the backlink association
* @param {string} onDelete the on delete rule which should be applied. Default for associations is 'RESTRICT'
* @param {CSN.PathSegment} sourceAssociation the name of the association from which the constraint originates
* @param {CSN.PathSegment} upLinkFor the name of the composition which used this association in a `$self = <comp>.<up_>` comparison
* it is used for a comment in the constraint, which is only printed out in test-mode
*/
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, path, constraintIdentifierSuffix, onDelete = 'RESTRICT') {
function attachConstraintsToDependentKeys(dependentKeys, parentKeys, parentTable, sourceAssociation, upLinkFor = null) {
while (dependentKeys.length > 0) {
const dependentKeyValuePair = dependentKeys.pop();
const dependentKey = dependentKeyValuePair[1];
// if it already has a dependent key assigned, do not overwrite it.
// this is the case for <up_> associations in on-conditions of compositions
if (Object.prototype.hasOwnProperty.call(dependentKey, '$foreignKeyConstraint'))

@@ -156,5 +158,5 @@ return;

parentTable,
sourceAssociation: path[path.length - 1],
nameSuffix: constraintIdentifierSuffix || 'up_',
onDelete,
upLinkFor,
sourceAssociation,
onDelete: upLinkFor ? 'CASCADE' : 'RESTRICT',
validated,

@@ -179,3 +181,3 @@ enforced,

*
* @param {CSN.Association | CSN.Composition} element
* @param {CSN.Association} element
* @param {CSN.Elements} siblingElements

@@ -201,19 +203,22 @@ * @param {CSN.Path} path the path to the element

// managed composition with target cardinality of one is treated like an association
const isComposition = element.type === COMPOSITION && !treatCompositionLikeAssociation(element);
// for cds.Associations the parent keys are in the associations target entity
// for cds.Composition the parent keys are in the entity, where the composition is defined
const parentElements = isComposition ? siblingElements : csn.definitions[element.target].elements;
const parentKeys = isComposition ? elementsOfSourceSide(onCondition, parentElements) : elementsOfTargetSide(onCondition, parentElements);
const parentElements = csn.definitions[element.target].elements;
const parentKeys = elementsOfTargetSide(onCondition, parentElements);
const referencesNonPrimaryKeyField = Array.from(parentKeys.values()).some(parentKey => !parentKey.key);
if (referencesNonPrimaryKeyField)
return false;
// returns true if the parentKeys found in the on-condition are covering the full primary key tuple in the parent entity
return Array.from(parentKeys.entries())
// check if primary key found in on-condition is present in association target / composition source
.filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length === Object.keys(parentElements)
// compare that with the length of the primary key tuple found in association target / composition source
.filter(key => parentElements[key].key &&
parentElements[key].type !== ASSOCIATION &&
parentElements[key].type !== COMPOSITION)
.length;
// check if primary key found in on-condition is present in association target / composition source
.filter(([ keyName, pk ]) => pk.key && parentElements[keyName].key).length ===
Object.keys(parentElements)
// compare that with the length of the primary key tuple found in association target / composition source
.filter(key => parentElements[key].key &&
parentElements[key].type !== ASSOCIATION &&
parentElements[key].type !== COMPOSITION)
.length;
}
/**

@@ -223,43 +228,220 @@ * Skip referential constraint if the parent table (association target, or artifact where composition is defined)

* - a query
* - annotated with '@cds.persistence.skip:true'
* - annotated with '@cds.persistence.exists:true'
* - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
* - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
*
* Skip referential constraint as well if:
* - global option 'skipDbConstraints' is set and if the element is not annotated with
* '@cds.persistency.assert.integrity: true'.
* - the element is annotated either with '@cds.persistency.assert.integrity: false' or '@assert.integrity: false'
* The following decision table reflects the current implementation:
*
* @param {CSN.Element} parent of association / composition
* @param {CSN.Element} element the composition or association
* +-----------------+--------------------+-------------------+----------+
* | Global Switch: | Global Check Type: | @assert.integrity | Generate |
* |"assertIntegrity"| "assertIntegrityType"| | Constraint|
* +-----------------+--------------------+-------------------+----------+
* | on | RT | false | no |
* +-----------------+--------------------+-------------------+----------+
* | on | RT | true/not set | no |
* +-----------------+--------------------+-------------------+----------+
* | on | RT | RT | no |
* +-----------------+--------------------+-------------------+----------+
* | on | RT | DB | yes |
* +-----------------+--------------------+-------------------+----------+
* | | | | |
* +-----------------+--------------------+-------------------+----------+
* | on | DB | false | no |
* +-----------------+--------------------+-------------------+----------+
* | on | DB | true/not set | yes |
* +-----------------+--------------------+-------------------+----------+
* | on | DB | RT | no |
* +-----------------+--------------------+-------------------+----------+
* | on | DB | DB | yes |
* +-----------------+--------------------+-------------------+----------+
* | | | | |
* +-----------------+--------------------+-------------------+----------+
* | off | don't care | don't care | no |
* +-----------------+--------------------+-------------------+----------+
* | | | | |
* +-----------------+--------------------+-------------------+----------+
* | individual | RT | true | no |
* +-----------------+--------------------+-------------------+----------+
* | individual | DB | true | yes |
* +-----------------+--------------------+-------------------+----------+
* | individual | don't care | RT | no |
* +-----------------+--------------------+-------------------+----------+
* | individual | don't care | DB | yes |
* +-----------------+--------------------+-------------------+----------+
* | individual | don't care | false/not set | no |
* +-----------------+--------------------+-------------------+----------+
*
* @param {CSN.Definition} parent entity where the foreign key reference will point at
* @param {CSN.Definition} dependent entity where the constraint will be defined on
* @param {CSN.Association} element the composition or association
* @returns {boolean}
*/
function skipConstraintGeneration(parent, element) {
if (hasAnnotationValue(element, '@assert.integrity', false) ||
hasAnnotationValue(element, '@cds.persistency.assert.integrity', false)) {
// in case of managed composition, the 'up_' link should not result in a constraint
const target = csn.definitions[element.target];
const { up_ } = target.elements;
if (up_)
up_.$skipReferentialConstraintForUp_ = true;
function skipConstraintGeneration(parent, dependent, element) {
// if set to 'off' don't even bother, just skip all constraints
if (options.assertIntegrity === false || options.assertIntegrity === 'false')
return true;
}
if (element.$skipReferentialConstraintForUp_) {
delete element.$skipReferentialConstraintForUp_;
if (parent.query)
return true;
// no constraint if either dependent or parent is not persisted
if (
hasAnnotationValue(parent, '@cds.persistence.skip') ||
hasAnnotationValue(dependent, '@cds.persistence.skip')
)
return true;
// some commonly used string literals
const RT = 'RT';
const DB = 'DB';
const CREATE_FOR_UP = '$createReferentialConstraintForUp_';
const SKIP_FOR_UP = '$skipReferentialConstraintForUp_';
// if the element itself is explicitly excluded from being checked
// skip the constraint for it (and its backlink)
if (isAssertIntegrityAnnotationSetTo(false) ||
isAssertIntegrityAnnotationSetTo(RT) ||
element[SKIP_FOR_UP]
) {
// for "auto-generated" associations like for the up_ of a composition of aspects,
// the annotation on the composition influences the referential constraint for the
// up_ association
if (element.$selfOnCondition && element.targetAspect) {
assignPropOnBacklinkIfPossible(SKIP_FOR_UP, true);
return true;
}
if (element.type === 'cds.Composition')
return false;
return true;
}
if (hasAnnotationValue(parent, '@cds.persistence.skip', true) ||
hasAnnotationValue(parent, '@cds.persistence.exists', true) ||
parent.query)
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
(!options.assertIntegrityType || options.assertIntegrityType === RT))
return assertForIntegrityTypeRT();
if ((!options.assertIntegrity || options.assertIntegrity === true || options.assertIntegrity === 'true') &&
options.assertIntegrityType === DB)
return assertForIntegrityTypeDB();
if ((options.assertIntegrity === 'individual'))
return assertForIndividual();
// The default for the assertIntegrityType is 'RT', no constraints in that case
if ((!options.assertIntegrity || options.assertIntegrity === true) &&
(!options.assertIntegrityType || options.assertIntegrityType === RT))
return true;
// '@cds.persistency.assert.integrity: true' supersedes global switch
if (!hasAnnotationValue(element, '@cds.persistency.assert.integrity', true) && options.forHana.skipDbConstraints)
if (!element.keys || !isToOne(element))
return true;
return false;
/**
* if global checks are 'individual' we evaluate every association,
* we create db constraints if it is annotated with @assert.integrity: 'DB' (or true)
*
* @returns {boolean}
*/
function assertForIndividual() {
if (isAssertIntegrityAnnotationSetTo(DB) || element[CREATE_FOR_UP]) {
// if this is has a $self comparison, the up_ link should then result in a constraint
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
return false;
}
if (options.assertIntegrityType === DB && isAssertIntegrityAnnotationSetTo(true)) {
// if this is has a $self comparison, the up_ link should then result in a constraint
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
return false;
}
// individual and no ('DB') annotation on constraint --> skip
return true;
}
/**
* if global check type is 'RT' (or not provided) only generate DB constraint if element
* is explicitly annotated "@assert.integrity: 'DB'"
*
* @returns {boolean}
*/
function assertForIntegrityTypeRT() {
// for "auto-generated" associations like for the up_ of a composition of aspects,
// the annotation on the composition influences the referential constraint for the
// up_ association
if (isAssertIntegrityAnnotationSetTo(DB)) {
if (element.targetAspect)
assignPropOnBacklinkIfPossible(CREATE_FOR_UP, true);
return false;
}
if (element[CREATE_FOR_UP])
return false;
return true;
}
/**
* if global checks are on and global integrity check type is 'DB'
* we create db constraints in any case except if annotated
* with @assert.integrity: 'RT' (or false, but that is rejected earlier)
*
* @returns {boolean}
*/
function assertForIntegrityTypeDB() {
if (isAssertIntegrityAnnotationSetTo(RT))
return true;
return false;
}
/**
* convenience to check if value of element's @assert.integrity annotation
* is the same as a given value
*
* @param {string|boolean} value
* @returns {boolean}
*/
function isAssertIntegrityAnnotationSetTo(value) {
return hasAnnotationValue(element, '@assert.integrity', value);
}
/**
* Assigns a helper key-value pair on the up_ association for a $self comparison
* for the current 'element', if applicable
*
* @param {string} prop
* @param {object} val
*/
function assignPropOnBacklinkIfPossible(prop, val) {
if (!element.$selfOnCondition)
return;
const target = csn.definitions[element.target];
const backlink = target.elements[element.$selfOnCondition.up_[0]];
backlink[prop] = val;
}
}
/**
* If we have a managed composition with a target cardinality of one, we will treat it like
* a regular association when it comes to referential constraints.
* The constraint will thus be generated for the foreign key we create in the source entity.
*
* @param {CSN.Composition} composition the composition which might be treated like an association
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
*/
function treatCompositionLikeAssociation(composition) {
return Boolean(isToOne(composition) && composition.keys);
}
/**
* returns true if the association/composition has a max target cardinality of one
*
* @param {CSN.Association|CSN.Composition} assocOrComposition
* @returns {boolean}
*/
function isToOne(assocOrComposition) {
const { min, max } = assocOrComposition.cardinality || {};
return !min && !max || max === 1;
}
/**
* Finds and returns elementNames and elements of target side mentioned in on-condition.

@@ -274,4 +456,4 @@ *

on.filter(element => typeof element === 'object' &&
element.ref.length > 1 &&
targetElements[element.ref[element.ref.length - 1]])
element.ref.length > 1 &&
targetElements[element.ref[element.ref.length - 1]])
.forEach((element) => {

@@ -287,3 +469,3 @@ elements.set(element.ref[element.ref.length - 1], targetElements[element.ref[element.ref.length - 1]]);

*
* @param {CSN.Association.on} on the on-condition
* @param {CSN.OnCondition} on the on-condition
* @param {CSN.Elements} sourceElements elements of source entity where the association/composition is defined.

@@ -295,4 +477,4 @@ * @returns {Map} of source elements with their name as key

on.filter(element => typeof element === 'object' &&
element.ref.length === 1 &&
sourceElements[element.ref[0]])
element.ref.length === 1 &&
sourceElements[element.ref[0]])
.forEach((element) => {

@@ -332,4 +514,4 @@ elements.set(element.ref[0], sourceElements[element.ref[0]]);

.filter(([ , e ]) => e.$foreignKeyConstraint &&
e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
.forEach(([ foreignKeyName, foreignKey ]) => {

@@ -347,5 +529,5 @@ const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);

if (options.testMode && onDelete === 'CASCADE')
onDeleteRemark = `Composition "${$foreignKeyConstraint.sourceAssociation}" implies existential dependency`;
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.nameSuffix}`] = {
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.nameSuffix}`,
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
foreignKey: dependentKey,

@@ -369,25 +551,2 @@ parentKey,

}
/**
* If we have a managed composition with a target cardinality of one, we will treat it like
* a regular association when it comes to referential constraints.
* The constraints will thus be generated in the entity containing the composition and not in the target entity.
*
* @param {CSN.Composition} composition the composition which might be treated like an association
* @returns {boolean} true if the composition should be treated as an association in regards to foreign key constraints
*/
function treatCompositionLikeAssociation(composition) {
return Boolean((isToOne(composition) && !composition.$selfOnCondition) || composition.keys);
}
/**
* returns true if the association/composition has a max target cardinality of one
*
* @param {CSN.Element} assocOrComposition
* @returns {boolean}
*/
function isToOne(assocOrComposition) {
const { min, max } = assocOrComposition.cardinality || {};
return !min && !max || max === 1;
}
}

@@ -394,0 +553,0 @@

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

const allServices = getServiceNames(csn);
const draftRoots = new WeakMap();
const {

@@ -159,3 +160,3 @@ createForeignKeyElement, createAndAddDraftAdminDataProjection, createScalarElement, createAssociationElement,

if (csn.definitions[persistenceName]) {
const definingDraftRoot = csn.definitions[persistenceName].$draftRoot;
const definingDraftRoot = draftRoots.get(csn.definitions[persistenceName]);
if (!definingDraftRoot) {

@@ -181,3 +182,3 @@ error(null, [ 'definitions', artifactName ], { name: persistenceName },

setProp(draftsArtifact, '$draftRoot', draftRootName);
draftRoots.set(draftsArtifact, draftRootName);
if (artifact.$location)

@@ -184,0 +185,0 @@ setProp(draftsArtifact, '$location', artifact.$location);

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

}
else if (col.ref && col.$scope === '$magic' && col.ref[0] === '$user' && !col.as) {
else if (col.ref && col.$scope === '$magic' && ( col.ref[0] === '$user' || col.ref[0] === '$session' ) && !col.as) {
col.as = implicitAs(col.ref);

@@ -556,5 +556,7 @@ newThing.push(col);

// The thing is shadowed - ignore names present because of .inline, as those "disappear"
if (names[part] !== undefined && !subs[names[part]].inline) {
replaced[part] = true;
star.push(subs[names[part]]);
if (names[part] !== undefined && !subs[names[part]].inline) { // Only works for a single * - but a second is forbidden anyway
if (names[part] > stars[0]) { // explicit definitions BEFORE the star should stay "infront" of the star
replaced[part] = true;
star.push(subs[names[part]]);
}
}

@@ -561,0 +563,0 @@ else { // the thing is not shadowed - use the name from the base

@@ -82,2 +82,18 @@ 'use strict';

toFinalBaseType(parent, resolved);
// structured types might not have the child-types replaced.
// Drill down to ensure this.
if (parent.elements) {
const stack = [ parent.elements ];
while (stack.length > 0) {
const elements = stack.pop();
for (const e of Object.values(elements)) {
if (e.type && !isBuiltinType(e.type))
toFinalBaseType(e, resolved);
if (e.elements)
stack.push(e.elements);
}
}
}
if (!directLocalized)

@@ -275,3 +291,3 @@ removeLocalized(parent);

}, Object.create(null));
});
}, true);
}

@@ -278,0 +294,0 @@

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

const { inspectRef } = csnRefs(csn);
const generatedExists = new WeakMap();
forEachDefinition(csn, (artifact, artifactName) => {
if (artifact.query) {
forAllQueries(artifact.query, (query, path) => {
if (!query.$generatedExists) {
if (!generatedExists.has(query)) {
const toProcess = []; // Collect all expressions we need to process here

@@ -70,2 +71,3 @@ if (query.SELECT && query.SELECT.where && query.SELECT.where.length > 1)

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

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

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

@@ -587,3 +646,3 @@ * If such a pattern is found, nest association steps therein into filters.

// We could also make getParent more robust to calculate the links JIT if they are missing
setProp(subselect, '$generatedExists', true);
generatedExists.set(subselect, true);

@@ -590,0 +649,0 @@ const nonEnumElements = Object.create(null);

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

const expandToFinalBaseType = require('./odata/toFinalBaseType');
const timetrace = require('../utils/timetrace');
const { timetrace } = require('../utils/timetrace');
const { attachPath } = require('./odata/attachPath');

@@ -195,3 +195,7 @@ const enrichUniversalCsn = require('./universalCsnEnricher');

// Apply default type facets as set by options
// Flatten on-conditions in unmanaged associations
// Flatten on-conditions in unmanaged associations
/* FIXME (HJB): Is this comment still correct? processOnCond only strips $self
We should not remove $self prefixes in structured OData to not
interfer with path resolution
*/
// This must be done before all the draft logic as all

@@ -574,2 +578,5 @@ // composition targets are annotated with @odata.draft.enabled in this step

// with @Common.ValueList.viaAssociation.
/*
FIXME (HJB): Comment outdated: Anno propagation to FKs is done in EdmPreprocessor
*/
// This must be done before foreign keys are calculated and the annotations are propagated

@@ -576,0 +583,0 @@ // to them. This will make sure that association and all its foreign keys are annotated with

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

function hasExistingLocalizationViews(csn, options) {
if (!csn || !csn.definitions)
return false;
const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);

@@ -703,0 +705,0 @@ if (firstLocalizedView) {

@@ -83,2 +83,10 @@ 'use strict';

}
if (element.type === 'cds.Binary' && element.length === undefined) {
if(options.defaultBinaryLength) {
element.length = options.defaultBinaryLength;
setProp(element, '$default', true);
}
else if(defStrLen5k)
element.length = 5000;
}
/*

@@ -347,2 +355,4 @@ if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {

* Copy properties of the referenced type, but don't resolve to the final base type.
*
* Do not copy the length if it was just set via the default-value.
*

@@ -349,0 +359,0 @@ * @param {any} node Node to copy to

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

try {
cb(null, fs.readFileSync( filename, enc ));
cb(null, fs.readFileSync( filename, { encoding: enc } ));
}

@@ -76,3 +76,3 @@ catch (err) {

*
* @param {(filename: string, enc: string, cb: (err, data) => void) => void} reader
* @param {(filename: string, enc, cb: (err, data) => void) => void} reader
*/

@@ -98,3 +98,5 @@ function _wrapReadFileCached( reader ) {

}
if (body instanceof Error) {
if (body && body.stack && body.message) {
// NOTE: checks for instanceof Error are not reliable if error
// created in different execution env
traceFS( 'READFILE:cache-error:', filename, body.message );

@@ -101,0 +103,0 @@ cb( body ); // no need for process.nextTick( cb, body ) with moduleResolve

@@ -5,4 +5,4 @@ //

// per default if the process runs in a TTY, i.e. stdout as well as
// stderr are TTYs. stderr/stdout are no TTYs if they (for example)
// are piped to another process or written to file:
// stderr are TTYs. stderr/stdout are no TTYs if they are
// (for example) piped into another process or written to file:
//

@@ -21,20 +21,6 @@ // node myApp.js # stdout.isTTY: true, stderr.isTTY: true

let hasColor = stdoutHasColor && stderrHasColor;
// Note: We require both stderr and stdout to be TTYs, as we don't
// know (in our exported functions) where the text will end up.
const hasColorShell = stdoutHasColor && stderrHasColor;
module.exports.useColor = (mode) => {
switch (mode) {
case false:
case 'never':
hasColor = false;
break;
case true:
case 'always':
hasColor = true;
break;
default:
hasColor = stdoutHasColor && stderrHasColor;
break;
}
};
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

@@ -52,27 +38,64 @@ const t = {

const as = (codes, o) => (hasColor ? (codes + o + t.reset) : (`${ o }`));
function term(useColor = 'auto') {
let hasColor = hasColorShell;
changeColorMode(useColor);
const asError = o => as(t.red + t.bold, o);
const asWarning = o => as(t.yellow, o);
const asInfo = o => as(t.green, o);
const asHelp = o => as(t.cyan, o);
module.exports.underline = o => as(t.underline, o);
module.exports.bold = o => as(t.bold, o);
module.exports.asSeverity = (severity, msg) => {
switch ((`${ severity }`).toLowerCase()) {
case 'error': return asError(msg);
case 'warning': return asWarning(msg);
case 'info': return asInfo(msg);
case 'help': return asHelp(msg);
// or e.g. 'none'
default: return msg;
function changeColorMode(mode) {
switch (mode) {
case false:
case 'never':
hasColor = false;
break;
case true:
case 'always':
hasColor = true;
break;
default:
// Note: See also: https://no-color.org/
// > Command-line software which adds ANSI color to its output by default
// > should check for the presence of a `NO_COLOR` environment variable
// > that, when present (regardless of its value), prevents the addition
// > of ANSI color.
// Note: To be able to disable colors in tests, we check the environment
// variable here again.
hasColor = hasColorShell && (process.env.NO_COLOR === undefined);
break;
}
}
};
module.exports.codes = t;
module.exports.as = as;
module.exports.error = asError;
module.exports.warn = asWarning;
module.exports.info = asInfo;
module.exports.help = asHelp;
const as = (codes, o) => (hasColor ? (codes + o + t.reset) : (`${ o }`));
const asError = o => as(t.red + t.bold, o);
const asWarning = o => as(t.yellow, o);
const asInfo = o => as(t.green, o);
const asHelp = o => as(t.cyan, o);
const underline = o => as(t.underline, o);
const bold = o => as(t.bold, o);
const asSeverity = (severity, msg) => {
switch ((`${ severity }`).toLowerCase()) {
case 'error': return asError(msg);
case 'warning': return asWarning(msg);
case 'info': return asInfo(msg);
case 'help': return asHelp(msg);
// or e.g. 'none'
default: return msg;
}
};
return {
changeColorMode,
as,
underline,
bold,
severity: asSeverity,
error: asError,
warn: asWarning,
info: asInfo,
help: asHelp,
};
}
module.exports = { term };

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

*/
class TimeTrace {
class StopWatch {
/**

@@ -17,26 +17,43 @@ * Creates an instance of TimeTrace.

constructor(id) {
let startTime;
/**
* Start measuring.
*
* @param {number} indent
*/
this.start = function start(indent) {
// eslint-disable-next-line no-console
console.error(`${ ' '.repeat((indent) * 2) }${ id } started`);
startTime = process.hrtime();
};
this.id = id;
// eslint-disable-next-line no-multi-assign
this.startTime = this.lapTime = process.hrtime();
}
/**
* Stop measuring and log the result
*
* @param {number} indent
/**
* Start watch.
*/
this.stop = function stop(indent) {
const endTime = process.hrtime(startTime);
const base = `${ ' '.repeat(indent * 2) }${ id } took:`;
// eslint-disable-next-line no-console
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, endTime[0], endTime[1] / 1000000);
};
start() {
// eslint-disable-next-line no-multi-assign
this.startTime = this.lapTime = process.hrtime();
}
/**
* Stop and return delta T
* but do not set start time
*/
stop() {
return process.hrtime(this.startTime);
}
/**
* return lap time
*/
lap() {
const dt = process.hrtime(this.lapTime);
this.lapTime = process.hrtime();
return dt;
}
// stop as sec.ns float
stopInFloatSecs() {
const dt = this.stop();
return dt[0] + dt[1] / 1000000000;
}
// lap as sec.ns float
lapInFloatSecs() {
const dt = this.lap();
return dt[0] + dt[1] / 1000000000;
}
}

@@ -72,5 +89,7 @@

try {
const b = new TimeTrace(id);
const b = new StopWatch(id);
this.traceStack.push(b);
b.start(this.traceStack.length - 1);
b.start();
// eslint-disable-next-line no-console
console.error(`${ ' '.repeat((this.traceStack.length - 1) * 2) }${ id } started`);
}

@@ -92,3 +111,6 @@ catch (e) {

const current = this.traceStack.pop();
current.stop(this.traceStack.length);
const dT = current.stop();
const base = `${ ' '.repeat(this.traceStack.length * 2) }${ current.id } took:`;
// eslint-disable-next-line no-console
console.error( `${ base }${ ' '.repeat(60 - base.length) } %ds %dms`, dT[0], dT[1] / 1000000);
}

@@ -108,2 +130,2 @@ catch (e) {

const doTimeTrace = process && process.env && process.env.CDSC_TIMETRACING !== undefined;
module.exports = doTimeTrace ? new TimeTracer() : ignoreTimeTrace;
module.exports = { timetrace: (doTimeTrace ? new TimeTracer() : ignoreTimeTrace), StopWatch };
{
"name": "@sap/cds-compiler",
"version": "2.10.4",
"version": "2.11.0",
"description": "CDS (Core Data Services) compiler and backends",

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

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

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

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

Sorry, the diff of this file is 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