Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 2.1.6 to 2.2.4

12

bin/cdsc.js

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

const term = require('../lib/utils/term');
const nodeHelper = require('../lib/base/node-helpers');
const { splitLines } = require('../lib/utils/file');

@@ -219,3 +218,3 @@ const { addLocalizationViews } = require('../lib/transform/localized');

const compiled = options.directBackend ?
nodeHelper.readFile( args.files[0] ).then((str) => JSON.parse( str )) :
util.promisify(fs.readFile)( args.files[0] ).then((str) => JSON.parse( str )) :
compiler.compileX( args.files, undefined, options, fileCache );

@@ -257,5 +256,6 @@

const hanaResult = toHanaWithCsn(csn, options);
for (const name in hanaResult.hdbcds) {
const fileName = `${name}.hdbcds`;
writeToFileOrDisplay(options.out, fileName, hanaResult.hdbcds[name]);
for( const pluginName of ['hdbcds', 'hdbconstraint']){
for(const name in hanaResult[pluginName]){
writeToFileOrDisplay(options.out, `${name}.${pluginName}`, hanaResult[pluginName][name]);
}
}

@@ -418,3 +418,3 @@ displayNamedCsn(hanaResult.csn, 'hana_csn', options);

const context = fullFilePath && sourceLines(fullFilePath);
log(main.messageStringMultiline(msg, { normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: !!context, hintExplanation: true }));
log(main.messageStringMultiline(msg, { normalizeFilename, noMessageId: !options.showMessageId, withLineSpacer: true, hintExplanation: true }));
if (context)

@@ -421,0 +421,0 @@ log(main.messageContext(context, msg));

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

## Version 2.2.4 - 2021-05-06
No changes compared to Version 2.2.2; fixes latest NPM tag
## Version 2.2.2 - 2021-05-04
### Fixed
- Usually reserved names like `in` in references used as annotation values can now really
be provided without delimited identifiers (if the name is not `true`, `false` or `null`).
- Fixed the implicit redirection of associations to scoped targets (like texts entities).
- Fix regression: Allow virtual structured elements.
- to.edm(x):
+ OData V2:
+ Remove warning about scalar return types.
+ Render constraints only if all principal keys are used in association.
+ OData V4: Don't remove `@Capabilities` annotations from containee.
+ Allow `@Core.MediaType` on all types and raise a warning for those (scalar) types that can't be mapped to `Edm.String` or `Edm.Binary`.
- to.cdl: Also handle subelement-annotations by rendering a `annotate X with Y`.
- to.hdi/sql/hdbcds: Fixed the DB name (with naming mode `quoted`/`hdbcds`) and the `to.hdi` file name of scoped definitions (like `texts` entities) in services.
- Empty enums no longer result in a syntax error.
## Version 2.2.0 - 2021-04-28
### Added
- The compiler now takes the “definition scope” of associations and compositions into account
when implicitly redirecting the target and auto-exposing entities.
- odata: The warning `enum-value-ref` is no longer reclassified to an error.
However, references to other enum values are still not supported.
### Changed
- Remove special handling for implicit redirection to auto-exposed entity; consistently
do not overwrite user-specified target in a service anymore, also in this special case.
- Structured/Arrayed types for enums are now an error and not just a warning.
- to.cdl: Keywords in annotation paths are no longer escaped
### Removed
- Consistently reject references to auto-exposed entities except for `annotate`
(it might have worked before, depending on the sequence of definitions);
expose an entity manually if you want to refer to it.
### Fixed
- Do not omit indirectly annotated or redirected sub elements
during propagation of expanded sub elements.
- Also auto-expose composition targets of projected compositions,
not just those target which were used at the original definition of the composition.
- Improve checks for keys which are `array of` or of SAP HANA spatial type (`ST_POINT` & `ST_GEOMETRY`)
with checking also inside of used user-defined structured type.
- to.edm(x):
+ V2: `OnDelete=Cascade` was set on dependent instead on principal role.
+ V4: ReferentialConstraints Property and ReferencedProperty for managed composition to one were swapped.
## Version 2.1.6 - 2021-04-14

@@ -12,0 +68,0 @@

@@ -14,2 +14,17 @@ # ChangeLog of deprecated Features for cdx compiler and backends

## Version 2.2.0
## Added `noScopedRedirections`
When this option is set, the definition scope is not taken into account when
trying to find an implicit redirection target. Setting the following
deprecated options also switches off scoped redirections (additionally to their
other effect): `noElementsExpansion`, `generatedEntityNameWithUnderscore`,
`shortAutoexposed`, `longAutoexposed`, `noInheritedAutoexposeViaComposition`.
## Added `noInheritedAutoexposeViaComposition`
When this option is set, only entities directly specified after `Composition of` are
auto-exposed, not entities used as target via explicit or implicit `redirected to`.
## Version 2.0.16

@@ -16,0 +31,0 @@

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

'testMode',
'constraintsNotEnforced',
'constraintsNotValidated',
'noRecompile',

@@ -46,0 +48,0 @@ 'internalMsg',

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

const { toRenameDdl } = require('./render/toRename');
const { manageConstraints } = require('./render/manageConstraints');
const { manageConstraints, listReferentialIntegrityViolations } = require('./render/manageConstraints');
const { transform4odataWithCsn } = require('./transform/forOdataNew');

@@ -69,3 +69,3 @@ const { csn2edm, csn2edmAll } = require('./edm/csn2edm');

// Verify options
optionProcessor.verifyOptions(options, 'toHana', true).map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toHana', true).forEach(complaint => warning(null, null, `${complaint}`));

@@ -89,5 +89,5 @@ // Special case: For naming variant 'hdbcds' in combination with 'toHana' (and only there!), 'forHana'

const sorted = sortCsn(forHanaCsn, options);
result.hdbcds = toHdbcdsSource(sorted, options);
result = toHdbcdsSource(sorted, options);
} else {
result.hdbcds = toHdbcdsSource(forHanaCsn, options);
result = toHdbcdsSource(forHanaCsn, options);
}

@@ -132,3 +132,3 @@ }

// Verify options
optionProcessor.verifyOptions(options, 'toOdata', true).map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toOdata', true).forEach(complaint => warning(null, null, `${complaint}`));

@@ -243,3 +243,3 @@ // Prepare model for ODATA processing

// Verify options
optionProcessor.verifyOptions(options, 'toCdl', silent).map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toCdl', silent).forEach(complaint => warning(null, null, `${complaint}`));

@@ -354,3 +354,3 @@ return options;

// Verify options
optionProcessor.verifyOptions(options, 'toSql', true).map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toSql', true).forEach(complaint => warning(null, null, `${complaint}`));

@@ -461,3 +461,3 @@ // FIXME: Currently, '--to-sql' implies transformation for HANA (transferring the options to forHana)

// Verify options
optionProcessor.verifyOptions(options, 'toRename').map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));

@@ -500,3 +500,3 @@ // Requires beta mode

const {
drop, alter, names, src
drop, alter, names, src, violations
} = options.manageConstraints || {};

@@ -514,3 +514,12 @@

const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions);
const intermediateResult = manageConstraints(forSqlCsn, mergedOptions);
if (violations && src && src !== 'sql')
error(null, null, `Option “--violations“ can't be combined with source style “${src}“`);
let intermediateResult;
if (violations)
intermediateResult = listReferentialIntegrityViolations(forSqlCsn, mergedOptions);
else
intermediateResult = manageConstraints(forSqlCsn, mergedOptions);
if(options.testMode !== true)

@@ -558,3 +567,3 @@ return intermediateResult;

// Verify options
optionProcessor.verifyOptions(options, 'toCsn').map(complaint => warning(null, null, `${complaint}`));
optionProcessor.verifyOptions(options, 'toCsn').forEach(complaint => warning(null, null, `${complaint}`));

@@ -561,0 +570,0 @@ return compactModel(model, options);

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

found.$duplicates.push( entry );
if (entry.$duplicates instanceof Array)
if (Array.isArray(entry.$duplicates))
found.$duplicates.push( ...entry.$duplicates )

@@ -34,3 +34,3 @@ else if (duplicateCallback && name) // do not complain with empty name ''

const entry = dict[name];
if (entry instanceof Array) {
if (Array.isArray(entry)) {
entry.forEach( callback );

@@ -40,3 +40,3 @@ }

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

@@ -58,4 +58,4 @@ }

}
if (entry instanceof Array) {
if (found instanceof Array) {
if (Array.isArray(entry)) {
if (Array.isArray(found)) {
dict[name] = [ ...found, ...entry ];

@@ -71,3 +71,3 @@ }

else {
if (found instanceof Array) {
if (Array.isArray(found)) {
dict[name] = [ ...found, entry ];

@@ -88,13 +88,2 @@ }

function clearDict( parent, env, inPlace ) {
if (!inPlace || !parent[env])
parent[env] = Object.create(null);
else {
let dict = parent[env];
let keys = Object.keys( dict );
for (let k of keys)
delete dict[k];
}
}
// Push `entry` to the array value with key `name` in the dictionary `dict`.

@@ -118,3 +107,2 @@ function pushToDict( dict, name, entry ) {

dictAddArray,
clearDict,
pushToDict,

@@ -121,0 +109,0 @@ forEachInDict,

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

if (!(dict instanceof Array))
if (!Array.isArray(dict))
dict = Object.getOwnPropertyNames( dict ).map( name => dict[name] );

@@ -126,3 +126,3 @@

function _objLocations( obj ) {
return (obj instanceof Array) ? obj.map( o => o.location ) : [ obj.location ];
return Array.isArray(obj) ? obj.map( o => o.location ) : [ obj.location ];
}

@@ -129,0 +129,0 @@

@@ -38,3 +38,4 @@ // Central registry for messages.

'enum-value-ref': { severity: 'Warning', errorFor: [ 'for.odata', 'to.edmx' ] },
// Structured types were warned about but made CSN un-recompilable.
'enum-invalid-type': { severity: 'Error', configurableFor: 'deprecated' },

@@ -66,2 +67,3 @@ // TODO: rename to ref-expected-XYZ

'ref-autoexposed': { severity: 'Error', configurableFor: 'deprecated' },
'ref-undefined-art': { severity: 'Error' }, // TODO: Remove if shared.js uses makeMessageFunction()

@@ -166,6 +168,7 @@ 'ref-undefined-def': { severity: 'Error' },

* @property {CSN.MessageSeverity} severity Default severity for the message.
* @property {string[]|true} [configurableFor]
* @property {string[]|'deprecated'|true} [configurableFor]
* Whether the error can be reclassified to a warning or lower.
* If not `true` then an array is expected with specified modules in which the error is downgradable.
* Only has an effect if default severity is 'Error'.
* 'deprecated': severity can only be changed with deprecated.downgradableErrors.
* TODO: Value `true` is temporary. Use an array instead.

@@ -172,0 +175,0 @@ * @property {string[]} [errorFor] Array of module names where the message shall be reclassified to an error.

@@ -16,2 +16,28 @@ // Functions and classes for syntax messages

// Functions ensuring message consistency during runtime with --test-mode
let test$severities = null;
let test$texts = null;
function test$initSeverities( [id, spec] ) {
test$severities[id] = { compiler: spec.severity };
for (const module of spec.errorFor || [])
test$severities[id][module] = 'Error';
}
function test$initTexts( [id, texts] ) {
test$texts[id] = (typeof texts === 'string') ? { std: texts } : { ...texts };
}
function test$compare( dict, id, prop, value, where ) {
if (!dict[id])
dict[id] = Object.create(null);
const expected = dict[id][prop];
if (!expected)
dict[id][prop] = value;
else if (expected !== value)
throw new Error( `Message-${where}.${id}.${prop}: expected "${expected}", not "${value}` );
}
/**

@@ -347,2 +373,10 @@ * Returns true if at least one of the given messages is of severity "Error"

function makeMessageFunction( model, options = model.options || {}, moduleName = null ) {
// ensure message consistency during runtime with --test-mode
if (options.testMode && !options.severities && !test$severities) {
test$severities = Object.create(null);
test$texts = Object.create(null);
Object.entries( centralMessages ).forEach( test$initSeverities );
Object.entries( centralMessageTexts ).forEach( test$initTexts );
}
const hasMessageArray = !!options.messages;

@@ -392,2 +426,9 @@ const severities = options.severities || {};

// perform message consistency check during runtime with --test-mode
if (options.testMode && !options.severities && !options.$recompile && id) {
test$compare( test$severities, id, moduleName || '?', severity, 'severities' );
for (const [variant, text] of
Object.entries( (typeof texts === 'string') ? { std: texts } : texts || {} ))
test$compare( test$texts, id, variant, text, 'texts' );
}
return msg;

@@ -609,2 +650,4 @@ }

art: transformArg,
service: transformArg,
sorted_arts: transformManyWith( transformArg, true ),
target: transformArg,

@@ -617,7 +660,9 @@ type: transformArg,

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

@@ -648,4 +693,2 @@ return many.length && t( many[0] );

return quoted( arg );
if (args['#'] || args.member )
return shortArtName( arg );
if (arg._artifact)

@@ -655,2 +698,4 @@ arg = arg._artifact;

arg = arg._outer;
if (args['#'] || args.member )
return shortArtName( arg );
let name = arg.name;

@@ -745,3 +790,5 @@ if (!name)

function messageString( err, normalizeFilename, noMessageId, noHome ) {
return (err.location ? locationString( err.location, normalizeFilename ) + ': ' : '') +
return (err.$location && err.$location.file
? locationString( err.$location, normalizeFilename ) + ': '
: '') +
(err.severity||'Error') +

@@ -752,3 +799,3 @@ // TODO: use [message-id]

// even with noHome, print err.home if the location is weak
(!err.home || noHome && err.location && err.location.endLine ? '' : ' (in ' + err.home + ')');
(!err.home || noHome && err.$location && err.$location.endLine ? '' : ' (in ' + err.home + ')');
}

@@ -768,3 +815,3 @@

const copy = {...msg};
copy.location = undefined;
copy.$location = undefined;
return messageString(copy);

@@ -795,15 +842,18 @@ }

let location = '';
if (err.location) {
location += locationString( err.location, config.normalizeFilename )
if (err.$location && err.$location.file) {
location += locationString( err.$location, config.normalizeFilename )
if (home)
location += ', '
}
else if (!home)
return term.asSeverity(severity, severity + msgId) + ' ' + err.message;
let lineSpacer = '';
if (config.withLineSpacer) {
let additionalIndent = err.location ? `${ err.location.endLine || err.location.line || 1 }`.length : 1;
let additionalIndent = err.$location ? `${ err.$location.endLine || err.$location.line || 1 }`.length : 1;
lineSpacer = `\n ${ ' '.repeat( additionalIndent ) }|`;
}
return term.asSeverity(severity, severity + msgId) + ' ' + err.message + lineSpacer + '\n ' + location + home;
// TODO: use ':' before text
return term.asSeverity(severity, severity + msgId) + ': ' + err.message + lineSpacer + '\n ' + location + home;
}

@@ -825,3 +875,5 @@

function messageContext(sourceLines, err) {
const loc = normalizeLocation(err.location);
const MAX_COL_LENGTH = 100;
const loc = normalizeLocation(err.$location);
if (!loc || !loc.line) {

@@ -843,5 +895,10 @@ return '';

const indent = ' '.repeat(2 + digits);
// Columns are limited in width to avoid too long output.
let startColumn = Math.min(MAX_COL_LENGTH, loc.col);
// end column points to the place *after* the last character index,
// e.g. for single character locations it is "start + 1"
let endColumn = loc.endCol ? loc.endCol - 1 : loc.col;
let endColumn = (loc.endCol && loc.endCol > loc.col) ? loc.endCol - 1 : loc.col;
endColumn = Math.min(MAX_COL_LENGTH, endColumn);
/** Only print N lines even if the error spans more lines. */

@@ -856,2 +913,4 @@ const maxLine = Math.min((startLine + 2), endLine);

let sourceCode = sourceLines[line].replace(/\t/g, ' ');
if (sourceCode.length >= MAX_COL_LENGTH)
sourceCode = sourceCode.slice(0, MAX_COL_LENGTH);
// Only prepend space if the line contains any sources.

@@ -864,8 +923,12 @@ sourceCode = sourceCode.length ? ' ' + sourceCode : '';

// highlight only for one-line location; at least one character is highlighted
const highlighter = ' '.repeat(Math.max(0, loc.col - 1))
.padEnd(Math.max(loc.col, endColumn), '^');
let highlighter = ' '.repeat(startColumn - 1).padEnd(endColumn, '^');
// Indicate that the error is further to the right.
if (endColumn == MAX_COL_LENGTH)
highlighter = highlighter.replace(' ^', '..^');
msg += indent + '| ' + term.asSeverity(severity, highlighter);
} else if (maxLine !== endLine) {
// error spans more lines which we don't print
msg += indent + '| ...';
} else {

@@ -887,8 +950,10 @@ msg += indent + '|';

function compareMessage( a, b ) {
if (a.location && b.location) {
const aEnd = a.location.endLine && a.location.endCol && a.location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
const bEnd = b.location.endLine && b.location.endCol && b.location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
return ( c( a.location.file, b.location.file ) ||
c( a.location.line, b.location.line ) ||
c( a.location.col, b.location.col ) ||
const aFile = a.$location && a.$location.file;
const bFile = b.$location && b.$location.file;
if (aFile && bFile) {
const aEnd = a.$location.endLine && a.$location.endCol && a.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
const bEnd = b.$location.endLine && b.$location.endCol && b.$location || { endLine: Number.MAX_SAFE_INTEGER, endCol: Number.MAX_SAFE_INTEGER };
return ( c( aFile, bFile ) ||
c( a.$location.line, b.$location.line ) ||
c( a.$location.col, b.$location.col ) ||
c( aEnd.endLine, bEnd.endLine ) ||

@@ -899,7 +964,9 @@ c( aEnd.endCol, bEnd.endCol ) ||

}
else if (!a.location === !b.location)
else if (!aFile && !bFile)
return ( c( homeSortName( a ), homeSortName( b ) ) ||
c( a.message, b.message ) );
else if (!aFile)
return (a.messageId && a.messageId.startsWith( 'api-' )) ? -1 : 1;
else
return (!a.location) ? 1 : -1;
return (b.messageId && b.messageId.startsWith( 'api-' )) ? 1 : -1;

@@ -933,3 +1000,3 @@ function c( x, y ) {

return (!home)
? (messageId && messageId.startsWith( 'syntax-' ) ? '' : '~')
? (messageId && /^(syntax|api)-/.test( messageId ) ? ' ' + messageId : '~')
: home.substring( home.indexOf(':') ); // i.e. starting with the ':', is always there

@@ -959,3 +1026,3 @@ }

} else if (msg.location) {
} else if (msg.$location) {
const existing = seen.get(hash);

@@ -965,4 +1032,4 @@ // If this messages has an end but the existing does not, then the new message is more precise.

// Assume that a message is more precise if it comes later (i.e. may be included in the other).
if (msg.location.endLine && !existing.location.endLine ||
(!msg.location.endLine === !existing.location.endLine && compareMessage(msg, existing) > 0)) {
if (msg.$location.endLine && !existing.$location.endLine ||
(!msg.$location.endLine === !existing.$location.endLine && compareMessage(msg, existing) > 0)) {
seen.set(hash, msg);

@@ -969,0 +1036,0 @@ }

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

callback( obj, name, prop );
if (obj.$duplicates instanceof Array) // redefinitions
if (Array.isArray(obj.$duplicates)) // redefinitions
obj.$duplicates.forEach( o => callback( o, name, prop ) )

@@ -214,3 +214,3 @@ }

// Transform arrays element-wise
if (node instanceof Array) {
if (Array.isArray(node)) {
return node.map(transformNode);

@@ -217,0 +217,0 @@ }

@@ -5,17 +5,2 @@ // Wrappers around core JavaScript / node functions

var fs = require('fs');
// Promise-variant of fs.readFile:
function readFile( filename ) {
return new Promise( function (fulfill, reject) {
fs.readFile( filename, 'utf8', function (err, res) {
if (err)
reject(err);
else
fulfill(res);
});
});
}
// Version of Promise.all() which does not reject immediately, i.e. after one

@@ -50,4 +35,3 @@ // promise rejects, the others are still resolved.

module.exports = {
readFile,
promiseAllDoNotRejectImmediately
}

@@ -96,8 +96,2 @@ 'use strict';

if (isBuiltinType(finalReturnType) && this.options.toOdata && this.options.toOdata.version === 'v2') {
// in ODATA v2 scalar types cannot be returned
this.warning(null, currPath, 'Scalar return types are not allowed in OData V2');
return;
}
if (finalReturnType.items) // check array return type

@@ -104,0 +98,0 @@ checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind);

'use strict';
// ------------------------------------------------------------------------------

@@ -15,6 +16,15 @@ // Only to be used with validator.js - a correct this value needs to be provided!

function checkCoreMediaTypeAllowence(member) {
const allowedCoreMediaTypes = [ 'cds.String', 'cds.Binary', 'cds.LargeBinary' ];
const allowedCoreMediaTypes = [
'cds.String',
'cds.LargeString',
'cds.hana.VARCHAR',
'cds.hana.CHAR',
'cds.Binary',
'cds.LargeBinary',
'cds.hana.CLOB',
'cds.hana.BINARY',
];
if (member['@Core.MediaType'] && member.type && !allowedCoreMediaTypes.includes(member.type)) {
this.error(null, member.$path, { names: allowedCoreMediaTypes },
'Element annotated with “@Core.MediaType” must be of either type $(NAMES)');
this.warning(null, member.$path, { names: [ 'Edm.String', 'Edm.Binary' ] },
'Element annotated with “@Core.MediaType” should be of a type mapped to $(NAMES)');
}

@@ -21,0 +31,0 @@ }

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

forEachMember(art, (member, memberName, prop, path) => {
checkIfPrimaryKeyIsOfGeoType.bind(this)(member);
checkIfPrimaryKeyIsArray.bind(this)(member);
checkIfPrimaryKeyIsOfGeoType.bind(this)(member, memberName);
checkIfPrimaryKeyIsArray.bind(this)(member, memberName);
if (member.elements) {
forEachMemberRecursively(member, (subMember) => {
checkIfPrimaryKeyIsOfGeoType.bind(this)(subMember, member.key);
checkIfPrimaryKeyIsArray.bind(this)(subMember, member.key);
forEachMemberRecursively(member, (subMember, subMemberName) => {
checkIfPrimaryKeyIsOfGeoType.bind(this)(subMember, subMemberName, member.key);
checkIfPrimaryKeyIsArray.bind(this)(subMember, subMemberName, member.key);
},

@@ -33,9 +33,22 @@ path);

* @param {CSN.Element} member The member
* @param {string} elemFqName Full name of the element following the structure,
* concatenated with '/', used for error reporting
* @param {boolean} parentIsKey Whether parent is a key
* @param {CSN.Path} parentPath The path of the parent element (optional)
*/
function checkIfPrimaryKeyIsOfGeoType(member, parentIsKey) {
function checkIfPrimaryKeyIsOfGeoType(member, elemFqName, parentIsKey, parentPath) {
if (member.key || parentIsKey) {
const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType))
this.error(null, member.$path, { type: finalBaseType }, 'Type $(TYPE) can\'t be used as primary key');
this.error(null, parentPath || member.$path,
{ type: finalBaseType, name: elemFqName },
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
forEachMemberRecursively(finalBaseType,
(subMember, subMemberName) => checkIfPrimaryKeyIsOfGeoType
.bind(this)(subMember,
`${ elemFqName }/${ subMemberName }`,
member.key || parentIsKey,
member.$path));
}
}

@@ -47,9 +60,21 @@ }

* @param {CSN.Element} member The member
* @param {string} elemFqName Full name of the element following the structure,
* concatenated with '/', used for error reporting
* @param {boolean} parentIsKey Whether parent is a key
* @param {CSN.Path} parentPath The path of the parent element (optional)
*/
function checkIfPrimaryKeyIsArray(member, parentIsKey) {
function checkIfPrimaryKeyIsArray(member, elemFqName, parentIsKey, parentPath) {
if (member.key || parentIsKey) {
const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
if (member.items || (finalBaseType && finalBaseType.items))
this.error(null, member.$path, 'Array-like types can\'t be used as primary key');
this.error(null, parentPath || member.$path, { name: elemFqName },
'Array-like type in element $(NAME) can\'t be used as primary key');
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {
forEachMemberRecursively(finalBaseType,
(subMember, subMemberName) => checkIfPrimaryKeyIsArray
.bind(this)(subMember,
`${ elemFqName }/${ subMemberName }`,
member.key || parentIsKey,
member.$path));
}
}

@@ -67,4 +92,2 @@ }

if (member.virtual) {
if (this.csnUtils.isStructured(member))
this.error(null, member.$path, `Element can't be virtual and structured`);
if (this.csnUtils.isAssociation(member.type)) { // or Composition ???

@@ -71,0 +94,0 @@ this.error(null, member.$path, `Element can't be virtual and an association`);

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

if (node instanceof Array) {
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );

@@ -59,0 +59,0 @@ }

@@ -115,6 +115,10 @@ 'use strict';

// For error messages
const id = logReady(ref[j]);
const target = ref.map(ps => logReady(ps)).join('.');
if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {
if (_links[j].art.on) {
// It's an unmanaged association - traversal is always forbidden
this.error(null, csnPath, `ON-Conditions can't follow unmanaged associations, step "${ logReady(ref[j]) }" of path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
this.error(null, csnPath, { id, target }, 'ON-Conditions can\'t follow unmanaged associations, step $(ID) of path $(TARGET)');
}

@@ -125,15 +129,20 @@ else {

if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
this.error(null, csnPath, `ON-Conditions can only follow managed associations to the foreign keys of the managed association, step "${ logReady(ref[j]) }" of path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
this.error(null, csnPath, { id, target }, 'ON-Conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(TARGET)');
}
}
if (_links[j].art.virtual)
this.error(null, csnPath, { id, target }, 'Virtual elements can\'t be used in ON-Conditions, step $(ID) of path $(TARGET)');
if (ref[j].where)
this.error(null, csnPath, `ON-Conditions must not contain filters, step "${ logReady(ref[j]) }" of path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
this.error(null, csnPath, { id, target }, 'ON-Conditions must not contain filters, step $(ID) of path $(TARGET)');
if (ref[j].args)
this.error(null, csnPath, `ON-Conditions must not contain parameters, step "${ logReady(ref[j]) }" of path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
this.error(null, csnPath, { id, target }, 'ON-Conditions must not contain parameters, step $(ID) of path $(TARGET)');
}
if (_art && $scope !== '$self') {
_art = resolveArtifactType.call(this, _art);
// For error messages
const target = ref.map(ps => logReady(ps)).join('.');
const onPath = path.concat([ 'on', i, 'ref', ref.length - 1 ]);
// Paths of an ON condition may end on a structured element or an association only if:

@@ -147,10 +156,15 @@ // 1) Both operands in the expression end on a structured element or on

!( /* 1) */ (_art.elements || _art.target && _art.keys) && validStructuredElement ||
/* 2) */ (_art.target && validDollarSelf))) {
this.error(null, path.concat([ 'on', i, 'ref', ref.length - 1 ]),
`The last path of an on-condition must be a scalar value, path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
/* 2) */ (_art.target && validDollarSelf)) &&
!_art.virtual) {
this.error(null, onPath, { target },
'The last path of an on-condition must be a scalar value, path $(TARGET)');
}
else if (_art.items) {
this.error(null, path.concat([ 'on', i, 'ref', ref.length - 1 ]),
`ON-Conditions can't use array-like elements, path ${ ref.map(ps => `"${ logReady(ps) }"`).join('.') }`);
else if (_art.items && !_art.virtual) {
this.error(null, onPath, { target },
'ON-Conditions can\'t use array-like elements, path $(TARGET)');
}
else if (_art.virtual) {
this.error(null, onPath, { target },
'Virtual elements can\'t be used in ON-Conditions, path $(TARGET)');
}
}

@@ -157,0 +171,0 @@ }

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

'_effectiveType', // TODO:check this
'$duplicates', // In JOIN if both sides are the same.
],

@@ -607,3 +608,3 @@ },

function definition( node, parent, prop, spec, name ) {
if (!(node instanceof Array))
if (!Array.isArray( node ))
node = [ node ];

@@ -660,3 +661,3 @@ // TODO: else check that there is a redefinition error

for (const n of names) {
const opt = (optional instanceof Array)
const opt = Array.isArray(optional)
? optional.includes( n ) || optional.includes( n.charAt(0) )

@@ -723,3 +724,3 @@ : optional( n, spec );

return;
while (node instanceof Array) {
while (Array.isArray(node)) {
// TODO: also check getOwnPropertyNames(node)

@@ -759,3 +760,3 @@ if (node.length !== 1) {

function args( node, parent, prop, spec ) {
if (node instanceof Array) {
if (Array.isArray(node)) {
if (parent.op && parent.op.val === 'xpr') // remove keywords for `xpr` expressions

@@ -798,3 +799,3 @@ node = node.filter( a => typeof a !== 'string');

return function vector( node, parent, prop, spec ) {
if (!(node instanceof Array))
if (!Array.isArray(node))
throw new Error( `Expected array${ at( [ null, parent ], prop ) }` );

@@ -842,3 +843,3 @@ node.forEach( (item, n) => func( item, parent, prop, spec, n ) );

function isVal( node, parent, prop, spec ) {
if (node instanceof Array)
if (Array.isArray(node))
node.forEach( (item, n) => standard( item, parent, prop, spec, n ) );

@@ -855,3 +856,3 @@ else if (node !== null && ![ 'string', 'number', 'boolean' ].includes( typeof node ))

function inDefinitions( art, parent, prop, spec, name ) {
if (art instanceof Array) // do not check with redefinitions
if (Array.isArray(art)) // do not check with redefinitions
return;

@@ -858,0 +859,0 @@ isObject( art, parent, prop, spec, name );

@@ -148,4 +148,2 @@ // Checks on XSN performed during compile()

/**
* Enum specific checks. For example this function checks that the value type
* of enum values are allowed.
* The enumNode is a single enum element and not the whole type.

@@ -179,3 +177,9 @@ *

enumNode.type._artifact._effectiveType;
if (!type)
// We can't distinguish (in CSN) between these two cases:
// type Base : String enum { b;a = 'abc'; };
// type ThroughRef : Base; (1)
// type NotAllowed : Base enum { a } (2)
// (2) should not be allowed but (1) should be. That's why we allow (2).
if (!type || type.enum)
return;

@@ -190,10 +194,19 @@

if (!type.builtin || type.internal || isBinary) {
let typeclass = isBinary ? 'binary' : 'std';
if (builtins.isRelationTypeName(name))
let typeclass = 'std';
if (isBinary)
typeclass = 'binary';
else if (builtins.isRelationTypeName(name))
typeclass = 'relation';
else if (type.elements)
typeclass = 'struct';
else if (type.items)
typeclass = 'items';
warning('enum-invalid-type', [ enumNode.type.location, enumNode ], { '#': typeclass }, {
error('enum-invalid-type', [ enumNode.type.location, enumNode ], { '#': typeclass }, {
std: 'Only builtin types are allowed as enums',
binary: 'Binary types are not allowed as enums',
relation: 'Relational types are not allowed as enums',
struct: 'Structured types are not allowed as enums',
items: 'Arrayed types are not allowed as enums',
});

@@ -303,7 +316,8 @@ return;

warning('localized-sub-element', [ element.localized.location, element ], {},
'Keyword "localized" is ignored for sub elements');
{ std: 'Keyword "localized" is ignored for sub elements' } );
}
else if (element.type && isTypeLocalized(element.type._artifact)) {
warning('localized-sub-element', [ element.type.location, element ], { type: element.type },
'Keyword "localized" in type $(TYPE) is ignored for sub elements');
warning('localized-sub-element', [ element.type.location, element ],
{ type: element.type, '#': 'type' },
{ type: 'Keyword "localized" in type $(TYPE) is ignored for sub elements' } );
}

@@ -391,3 +405,4 @@ return;

if (elem._effectiveType && elem._effectiveType.elements) {
error('type-cast-structured', [ loc, elem ], {}, 'Can\'t cast to structured element');
error('type-cast-to-structured', [ loc, elem ], {},
'Can\'t cast to structured element');
}

@@ -680,3 +695,3 @@ else if (elem.value && elem.value._artifact && elem.value._artifact._effectiveType &&

// Check for illegal argument usage within the expression
for (const arg of xpr.args instanceof Array && xpr.args || []) { // TODO named args?
for (const arg of Array.isArray(xpr.args) && xpr.args || []) { // TODO named args?
if (isVirtualElement(arg))

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

@@ -33,3 +33,3 @@ // Detect cycles in the dependencies between nodes (artifacts and elements)

const a = definitions[name];
if (a instanceof Array)
if (Array.isArray(a))
a.forEach( strongConnectRec );

@@ -36,0 +36,0 @@ else

@@ -171,2 +171,4 @@ //

return false;
// We do not consider the $expand status, as elements are already expanded
// by the resolve(), and if not due to deprecated.noElementsExpansion
run( type );

@@ -178,4 +180,2 @@ return type[prop];

// accessed at their type being a main artifact
// This should be adapted if elements of referred types can be annotated
// (i.e. check whether there are annotations on it)
function expensive( prop, target, source ) {

@@ -189,4 +189,2 @@ // console.log(prop,source.name,'->',target.kind,target.name);

return;
if (prop === 'elements' && source.$expand === 'origin' && source.type)
return;
const location = target.type && !target.type.$inferred && target.type.location ||

@@ -193,0 +191,0 @@ target.location ||

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

undefinedArt: 'anno-undefined-art',
allowAutoexposed: true,
},

@@ -386,3 +387,3 @@ type: { // TODO: more detailed later (e.g. for enum base type?)

? ref.scope || Number.MAX_SAFE_INTEGER
: spec.artItemsCount || 1; // 0 or 1 does not matter (first item resolved by getPathRoot)
: spec.artItemsCount || 1;
// console.log(expected, ref.path.map(a=>a.id),artItemsCount)

@@ -503,3 +504,3 @@ art = getPathItem( path, spec, user, artItemsCount );

if ('_artifact' in head)
return (head._artifact instanceof Array) ? false : head._artifact;
return Array.isArray(head._artifact) ? false : head._artifact;
// console.log(pathName(path), !spec.next && !extDict &&

@@ -522,3 +523,3 @@ // (spec.useDefinitions || env.$frontend === 'json' || env))

if (r) {
if (r instanceof Array) { // redefinitions
if (Array.isArray(r)) { // redefinitions
setLink( head, r );

@@ -552,3 +553,3 @@ return false;

const r = extDict[head.id];
if (r instanceof Array) {
if (Array.isArray(r)) {
if (r[0].kind === '$navElement') {

@@ -584,3 +585,3 @@ const names = r.filter( e => !e.$duplicates)

const def = extDict[name];
if (!(def instanceof Array && def[0].kind === '$navElement'))
if (!(Array.isArray(def) && def[0].kind === '$navElement'))
e[name] = def;

@@ -626,3 +627,3 @@ }

// search environment (for the first path item) is `arg`. For messages about
// missing artifacts (as opposed to elements), provide the `head`(first
// missing artifacts (as opposed to elements), provide the `head` (first
// element item in the path)

@@ -639,3 +640,3 @@ function getPathItem( path, spec, user, artItemsCount ) {

art = item._artifact;
if (art instanceof Array)
if (Array.isArray(art))
return false;

@@ -653,3 +654,3 @@ }

return (sub === 0) ? 0 : errorNotFound( item, env );
else if (sub instanceof Array) // redefinitions
else if (Array.isArray(sub)) // redefinitions
return false;

@@ -681,2 +682,11 @@

art = sub;
if (spec.envFn && (!artItemsCount || item === last) &&
art && art.$inferred === 'autoexposed' && !user.$inferred) {
// Depending on the processing sequence, the following could be a
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
// could "change" to this message at the end of compile():
message( 'ref-autoexposed', [ item.location, user ], { art },
// eslint-disable-next-line max-len
'An autoexposed entity can\'t be referred to - expose entity $(ART) explicitly' );
}
}

@@ -801,3 +811,3 @@ }

let annos = construct[annoProp];
if (!(annos instanceof Array))
if (!(Array.isArray(annos)))
annos = [ annos ];

@@ -824,3 +834,3 @@ for (const a of annos) {

// Be robust if struct value has duplicate element names
if (value instanceof Array) // TODO: do that differently in CDL parser
if (Array.isArray(value)) // TODO: do that differently in CDL parser
return; // discard duplicates in flattened form

@@ -827,0 +837,0 @@

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

// TODO: to be specified via @sap.on.delete
forwardAssocCsn._NavigationProperty.set( { _OnDeleteSrcEnd: new Edm.OnDelete(v, { Action: 'Cascade' }) } );
forwardAssocCsn._NavigationProperty.set( { _OnDelete: new Edm.OnDelete(v, { Action: 'Cascade' }) } );
}

@@ -782,0 +782,0 @@ return;

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

throw Error('Please debug me: attributes must be a dictionary');
if(!(v instanceof Array))
if(!Array.isArray(v))
throw Error('Please debug me: v is either undefined or not an array: ' + v);

@@ -242,3 +242,3 @@ if(v.filter(v=>v).length != 1)

{
if(annotations instanceof Array && annotations.length > 0)
if(Array.isArray(annotations) && annotations.length > 0)
this._annotations.push(...annotations);

@@ -916,3 +916,3 @@ }

Additionally: The attribute is named 'Default' in V2 and 'DefaultValue' in V4
*/
*/
if(this.v4)

@@ -1092,4 +1092,12 @@ this[`Default${this.v4 ? 'Value' : ''}`] = ['cds.Boolean', 'cds.Binary', 'cds.LargeBinary', 'cds.Integer64', 'cds.Integer'].includes(csn.type)

{
edmUtils.forAll(this._csn._constraints.constraints,
c => this.append(new ReferentialConstraint(this._v, { Property: c[0].join(options.pathDelimiter), ReferencedProperty: c[1].join(options.pathDelimiter) } ) ) );
// flip the constrains if this is a $self partner
let _constraints = this._csn._constraints;
let [i,j] = [0,1];
if(this._csn._constraints._partnerCsn) {
_constraints = this._csn._constraints._partnerCsn._constraints;
[i,j] = [1,0];
}
edmUtils.forAll(_constraints.constraints,
c => this.append(new ReferentialConstraint(this._v,
{ Property: c[i].join(options.pathDelimiter), ReferencedProperty: c[j].join(options.pathDelimiter) } ) ) );
}

@@ -1349,10 +1357,11 @@ }

// if eventually a backlink is a composition and this (Forward-)Association has not yet been
// produced, add the OnDelete property now to the source end.
// produced, add the OnDelete property now to the target end.
// undefined values are not appended
this._end[0].append(navProp._OnDeleteSrcEnd);
if(navProp._OnDelete)
this._end[1].append(navProp._OnDelete);
// if the NavProp is a composition, add the OnDelete to the target end
// if the NavProp is a composition, add the OnDelete to the source end
if(navProp._csn.type === 'cds.Composition') {
// TODO: to be specified via @sap.on.delete
this._end[1].append(new OnDelete(v, { Action: 'Cascade' } ) );
this._end[0].append(new OnDelete(v, { Action: 'Cascade' } ) );
}

@@ -1359,0 +1368,0 @@ }

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

}
// Remove all arget elements that are not key in the principal entity
// Remove all target elements that are not key in the principal entity
// and all elements that annotated with '@cds.api.ignore'
const remainingPrincipalRefs = [];
foreach(assocCsn._constraints.constraints,

@@ -335,8 +336,20 @@ c => {

// in structured mode only resolve top level element (path rewriting is done elsewhere)
let fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
let pk = principalEntity.$keys && principalEntity.$keys[ ( options.isFlatFormat ? c[1].join('_') : c[1][0] )];
const fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
const principalEltRef = ( options.isFlatFormat ? c[1].join('_') : c[1][0] );
const pk = principalEntity.$keys && principalEntity.$keys[ principalEltRef ];
if(isConstraintCandidate(fk) && isConstraintCandidate(pk))
remainingPrincipalRefs.push(principalEltRef);
return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));
},
(c, cn) => { delete assocCsn._constraints.constraints[cn]; } );
(c, cn) => { delete assocCsn._constraints.constraints[cn]; }
);
// V2 check that ALL primary keys are constraints
if(principalEntity.$keys) {
const renderedKeys = Object.values(principalEntity.$keys).filter(isConstraintCandidate).map(v=>v.name);
if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length)
assocCsn._constraints.constraints = {};
}
}
}

@@ -353,3 +366,4 @@ // Handle managed association, a managed composition is treated as association

// there are no constraints for them.
if(!assocCsn._target.$isParamEntity && assocCsn.keys && assocCsn._parent) {
if(!assocCsn._target.$isParamEntity && assocCsn.keys) {
const remainingPrincipalRefs = [];
for(let fk of assocCsn.keys) {

@@ -360,2 +374,3 @@ let realFk = assocCsn._parent.items ? assocCsn._parent.items.elements[fk.$generatedFieldName] : assocCsn._parent.elements[fk.$generatedFieldName];

{
remainingPrincipalRefs.push(fk.ref[0]);
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];

@@ -366,2 +381,7 @@ const key = c.join(',');

}
// V2 check that ALL primary keys are constraints
const renderedKeys = Object.values(assocCsn._target.$keys).filter(isConstraintCandidate).map(v=>v.name);
if(options.isV2() && intersect(renderedKeys, remainingPrincipalRefs).length !== renderedKeys.length)
assocCsn._constraints.constraints = {};
}

@@ -544,3 +564,3 @@ }

node.Precision = 7;
else if([ 'cds.Decimal', 'cds.DecimalFloat' ].includes(csn.type) && !csn.precision && !csn.scale) {
else if([ 'cds.Decimal', 'cds.DecimalFloat', 'cds.hana.SMALLDECIMAL' ].includes(csn.type) && !csn.precision && !csn.scale) {
if(isV2) {

@@ -547,0 +567,0 @@ node.setXml( { 'sap:variable-scale': true } );

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

// Because we can't update to ANTLR 4.9, yet, since it requires NodeJS 14 and
// we need to support NodeJS 10, this script is run as a postinstall step to
// we need to support NodeJS 12, this script is run as a postinstall step to
// fix the ANTLR circular dependency bug.

@@ -10,0 +10,0 @@ //

@@ -411,3 +411,6 @@ // CSN frontend - transform CSN into XSN

type: symbol,
inKind: [ '$column' ],
// Note: We emit a warning if '#' is used in enums. Because the compiler
// can generate CSN like this, we need to be able to parse it.
// Also, in "extensions", an entity's element of type enum has kind "element".
inKind: [ '$column', 'enum', 'element' ],
},

@@ -667,9 +670,13 @@ path: { // in CSN v0.1.0 'foreignKeys'

/** @type {(id, location, textOrArguments, texts?) => void} */
let message = () => undefined;
// eslint-disable-next-line no-unused-vars
let message = (id, loc, textOrArguments, texts) => undefined;
/** @type {(id, location, textOrArguments, texts?) => void} */
let error = () => undefined;
// eslint-disable-next-line no-unused-vars
let error = (id, loc, textOrArguments, texts) => undefined;
/** @type {(id, location, textOrArguments, texts?) => void} */
let warning = () => undefined;
// eslint-disable-next-line no-unused-vars
let warning = (id, loc, textOrArguments, texts) => undefined;
/** @type {(id, location, textOrArguments, texts?) => void} */
let info = () => undefined;
// eslint-disable-next-line no-unused-vars
let info = (id, loc, textOrArguments, texts) => undefined;

@@ -676,0 +683,0 @@ let csnVersionZero = false;

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

if (csn instanceof Array)
if (Array.isArray(csn))
return csn.map( v => (!v || typeof v !== 'object' ? v : sortCsn(v, cloneOptions) ) );

@@ -240,3 +240,3 @@ const r = {};

function csnDictionary( csn, sort, cloneOptions = false ) {
if (!csn || csn instanceof Array) // null or strange CSN
if (!csn || Array.isArray(csn)) // null or strange CSN
return csn;

@@ -360,3 +360,3 @@ const proto = cloneOptions && (typeof cloneOptions === 'object') &&

// From definitions (without redefinitions) with potential inferred elements:
if (!(art instanceof Array) && art.elements &&
if (!Array.isArray(art) && art.elements &&
(art.query || art.includes || art.$inferred)) {

@@ -439,3 +439,3 @@ const annos = art.$inferred && annotations( art, true );

const elem = elems[name];
if (elem instanceof Array || !inferredParent && !elem.$inferred)
if (Array.isArray(elem) || !inferredParent && !elem.$inferred)
continue;

@@ -452,3 +452,3 @@ const csn = annotations( elem, true );

return undefined;
if (node instanceof Array)
if (Array.isArray(node))
return node.map( standard );

@@ -504,3 +504,3 @@ const csn = {};

function items( obj, csn, node ) {
if (node.$expand === 'origin' && node.type && node.kind !== 'type')
if (!keepElements( node ) && node.type && node.kind !== 'type')
// no 'elements' with SELECT or inferred elements with gensrc;

@@ -515,3 +515,3 @@ // hidden 'elements' will be set in query()

gensrcFlavor && (node.query || node.type) ||
node.$expand === 'origin' && node.type && node.kind !== 'type')
node.type && node.kind !== 'type' && !keepElements( node ))
// no 'elements' with SELECT or inferred elements with gensrc;

@@ -523,2 +523,13 @@ // hidden 'elements' will be set in query()

// We do not optimize away elements which are potentially adapted during their
// way from the original structure type definition to the current usage
function keepElements( node ) {
while (node) {
if (node.$expand !== 'origin')
return true;
node = node._origin;
}
return false;
}
// for gensrcFlavor and namespace/builtin annotation extraction:

@@ -675,3 +686,3 @@ // return annotations from definition (annotated==false)

const link = path[0]._artifact; // XSN TODO: store double definitions differently
const root = (link instanceof Array) ? link[0] : link;
const root = Array.isArray(link) ? link[0] : link;
if (!root) { // XSN directly coming from the parser

@@ -752,3 +763,3 @@ if (strictMode && node.scope === 'typeOf')

function args( node ) {
if (node instanceof Array)
if (Array.isArray(node))
return node.map( expression );

@@ -904,3 +915,3 @@ const dict = Object.create( dictionaryPrototype );

function query( node, csn, xsn ) {
while (node instanceof Array) // in parentheses -> remove
while (Array.isArray(node)) // in parentheses -> remove
node = node[0];

@@ -961,3 +972,3 @@ if (node.op.val === 'SELECT') {

function from( node ) {
while (node instanceof Array) // TODO: old-style parentheses - keep tmp for A2J
while (Array.isArray(node)) // TODO: old-style parentheses - keep tmp for A2J
node = node[0];

@@ -1075,2 +1086,6 @@ // TODO: CSN: FROM ((SELECT...)) as -> also add 'subquery' op? - Together

const annoOrder = propertyOrder['@'];
// Usually sort according to the "natural" property order; sort annotations
// alphabetically with --test-mode and "as set" (fragile, node >=12) without.
function compareProperties( a, b ) {

@@ -1081,3 +1096,3 @@ if (a === b)

const ob = propertyOrder[b] || propertyOrder[b.charAt(0)] || 9999;
return oa - ob || (a < b ? -1 : 1);
return oa - ob || (strictMode || oa !== annoOrder || 0) && (a < b ? -1 : 1);
}

@@ -1084,0 +1099,0 @@

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

if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) {
const excludes = (excludesForNextToken && recognizer.$adaptExpectedExcludes[0] instanceof Array)
const excludes = (excludesForNextToken && Array.isArray(recognizer.$adaptExpectedExcludes[0]))
? recognizer.$adaptExpectedExcludes[0]

@@ -342,0 +342,0 @@ : recognizer.$adaptExpectedExcludes;

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

this.$adaptExpectedToken = t;
this.$adaptExpectedExcludes = (excludes instanceof Array) ? excludes : [ excludes ];
this.$adaptExpectedExcludes = Array.isArray(excludes) ? excludes : [ excludes ];
this.$nextTokensToken = t;

@@ -219,6 +219,7 @@ this.$nextTokensContext = null;

function meltKeywordToIdentifier() {
function meltKeywordToIdentifier( exceptTrueFalseNull = false ) {
const { Identifier } = this.constructor;
const token = this.getCurrentToken() || { type: Identifier };
if (token.type < Identifier && /^[a-z]+$/i.test( token.text ))
if (token.type < Identifier && /^[a-z]+$/i.test( token.text ) &&
!(exceptTrueFalseNull && /^(true|false|null)$/i.test( token.text )))
token.type = Identifier;

@@ -442,2 +443,3 @@ }

(signToken.text === '-' &&
expr && // expr may be null if `-` rule can't be parsed
expr.literal === 'number' &&

@@ -450,3 +452,3 @@ sign.location.endLine === expr.location.line &&

if (nval === false)
return { op: sign, args: [ expr ] };
return { op: sign, args: expr ? [ expr ] : [] };
expr.val = nval;

@@ -570,3 +572,3 @@ --expr.location.col;

function addDef( parent, env, kind, name, annos, props, location ) {
if (name instanceof Array) {
if (Array.isArray(name)) {
// XSN TODO: clearly say: definitions have name.path, members have name.id

@@ -666,3 +668,3 @@ const last = name.length && name[name.length - 1];

(typeof val !== 'object' ||
(val instanceof Array ? val.length : Object.getOwnPropertyNames(val).length) ) )
(Array.isArray(val) ? val.length : Object.getOwnPropertyNames(val).length) ) )
target[key] = val;

@@ -669,0 +671,0 @@ }

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

while (model.definitions[name]) {
if (model.definitions[name].kind === 'context')
if (model.definitions[name].kind === 'context' || model.definitions[name].kind === 'service')
return name;

@@ -313,3 +313,3 @@ lastDotIdx = name.lastIndexOf('.');

// Transform arrays element-wise
if (node instanceof Array) {
if (Array.isArray(node)) {
return node.map(transformNode);

@@ -454,7 +454,7 @@ }

/**
* Apply function `callback` to all members of object `obj` (main artifact or
* Apply function `callback` to all members of object `construct` (main artifact or
* parent member). Members are considered those in dictionaries `elements`,
* `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
* searched inside property `items` (array of). See function `forEachGeneric`
* for details.
* `enum`, `actions` and `params` of `construct`, `elements` and `enums` are also
* searched inside property `items` (array of) and `returns` (actions).
* See function `forEachGeneric` for details.
*

@@ -468,26 +468,24 @@ * @param {CSN.Artifact} construct

function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) {
let obj = construct.returns || construct; // why the extra `returns` for actions?
const obj_path = Array.from(path); // Clone the path
// Allow processing _ignored elements if requested
if(ignoreIgnore && ( construct._ignore || obj._ignore )){
if (ignoreIgnore && construct._ignore) {
return;
}
if(construct.returns){
obj_path.push("returns");
// `items` itself is a structure that can contain "elements", and more.
if (construct.items) {
// TODO: Should we go to the deepest items.items?
forEachMember( construct.items, callback, [...path, 'items'], ignoreIgnore, iterateOptions );
}
if(obj.items){
obj_path.push("items");
// Unlike XSN, we don't make "returns" a "params" in the callback.
// Backends rely on the fact that `forEachElement` also goes through all
// `elements` of the return type (if structured).
// TODO: `returns` should be handled like a parameter just like XSN (maybe with different prop name)
if (construct.returns) {
forEachMember( construct.returns, callback, [...path, 'returns'], ignoreIgnore, iterateOptions );
}
obj = obj.items || obj;
const props = ['elements', 'enum', 'foreignKeys', 'actions', 'params'];
props.forEach((prop) => {
if(prop === 'actions' || prop === 'params')
forEachGeneric( construct, prop, callback, path, iterateOptions );
else
forEachGeneric( obj, prop, callback, obj_path, iterateOptions );
});
path = [...path]; // Copy
const propsWithMembers = ['elements', 'enum', 'foreignKeys', 'actions', 'params'];
propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));
}

@@ -539,3 +537,3 @@

function cb(o, name ) {
if (callback instanceof Array)
if (Array.isArray(callback))
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));

@@ -575,3 +573,3 @@ else

if (name === 'ref' && Object.getPrototypeOf(node)) {
if(callback instanceof Array)
if(Array.isArray(callback))
callback.forEach(cb => cb( node.ref, node, path ));

@@ -647,3 +645,3 @@ else

function cb(q, p) {
if(callback instanceof Array)
if(Array.isArray(callback))
callback.forEach(cb => cb( q, p ));

@@ -741,3 +739,3 @@ else

* - For the 'quoted' naming convention, this means correctly replacing some '.' with '_'.
*
*
* If the old function signature is used - with a namespace as the third argument - the result might be wrong,

@@ -748,3 +746,3 @@ * since the '.' -> '_' conversion for quoted/hdbcds is missing.

* @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
* @param {CSN.Model|string|undefined} csn
* @param {CSN.Model|string|undefined} csn
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming convention.

@@ -822,6 +820,6 @@ */

* the first shadowing definition that is not a namespace, context or service.
*
*
* Anything following is joined by '_'.
*
*
*
* @param {number} startIndex Index to start looking at the parts - used to skip the namespace

@@ -846,2 +844,13 @@ * @param {string[]} parts Parts of the name, split at .

return result;
} else if(art && art.kind === 'service') {
// inside services, we immediatly turn . into _
const prefix = parts.slice(0, i).join('.');
const suffix = parts.slice(i).join('_');
const result = [];
if (prefix)
result.push(prefix);
if (suffix)
result.push(suffix);
return result;
}

@@ -922,3 +931,3 @@ }

if (node instanceof Array) {
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );

@@ -1210,3 +1219,3 @@ }

// Our artifact seems to be contained in this context
if (art && art.kind === 'context')
if (art && (art.kind === 'context' || art.kind === 'service'))
return seen;

@@ -1405,3 +1414,19 @@ }

/**
* Sorts the definition dictionary in tests mode.
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
*/
function sortCsnDefinitionsForTests(csn, options) {
if (!options.testMode)
return;
const sorted = Object.create(null);
Object.keys(csn.definitions).sort().forEach((name) => {
sorted[name] = csn.definitions[name];
});
csn.definitions = sorted;
}
module.exports = {

@@ -1444,3 +1469,4 @@ getUtils,

hasValidSkipOrExists,
getNamespace
getNamespace,
sortCsnDefinitionsForTests,
};

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

csnPath.push( prop );
if (node instanceof Array) {
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );

@@ -65,0 +65,0 @@ }

@@ -19,3 +19,3 @@ // Make internal properties of the XSN / augmented CSN visible

function locationString( loc ) {
if (loc instanceof Array)
if (Array.isArray(loc))
return loc.map( locationString );

@@ -67,3 +67,3 @@ if (loc == null)

$tableAliases: dictionary,
_combined: artifactDictionary,
$keysNavigation: dictionary,
$layerNumber: n => n,

@@ -143,3 +143,3 @@ $extra: e => e,

function dependencyInfo( deps ) {
if (!(deps instanceof Array))
if (!Array.isArray(deps))
return primOrString( deps );

@@ -196,3 +196,3 @@ return deps

return primOrString( node );
if (node instanceof Array) // with args
if (Array.isArray(node)) // with args
return node.map( n => reveal( n, node ) );

@@ -210,4 +210,7 @@ // Make unexpected prototype visible with node-10+:

function revealNonEnum( node, parent ) {
if (!(node && node instanceof Object))
if (node == null || typeof node !== 'object' )
return primOrString( node );
if (Array.isArray(node))
return node.map( n => revealNonEnum( n, node ) );
if (Object.getPrototypeOf( node ))

@@ -220,5 +223,4 @@ return artifactIdentifier( node, parent );

if (node == null || typeof node !== 'object' )
// node instanceof Object would be false for dict
return node
if (node instanceof Array)
if (Array.isArray(node))
return node.map( n => reveal( n, node ) );

@@ -245,3 +247,3 @@

function artifactIdentifier( node, parent ) {
if (node instanceof Array)
if (Array.isArray(node))
return node.map( a => artifactIdentifier( a, node ) );

@@ -289,8 +291,7 @@ if (unique_id && node.__unique_id__ == null)

function primOrString( node ) {
if (node === null || typeof node !== 'object')
// node instanceof Object would be false for dict
if (node == null || typeof node !== 'object')
return node
if (node instanceof Array)
if (Array.isArray(node))
return node.map( primOrString );
if (node instanceof Object)
if (Object.getPrototypeOf( node ))
return '' + node;

@@ -297,0 +298,0 @@ else

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

.option(' --beta <list>')
.option(' --constraints-not-validated')
.option(' --constraints-not-enforced')
.option(' --deprecated <list>')

@@ -91,2 +93,6 @@ .option(' --hana-flavor')

ignoreAssocPublishingInUnion
--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"
--deprecated <list> Comma separated list of deprecated options.

@@ -193,5 +199,4 @@ Valid values are:

--odata-containment Generate Containment Navigation Properties for compositions (V4 only)
--odata-proxies (highly experimental) Generate Proxies for out-of-service navigation targets.
--odata-x-service-refs (highly experimental) Generate schema references and external proxies,
is overruled by --odata-proxies.
--odata-proxies Generate Proxies for out-of-service navigation targets (V4 only).
--odata-x-service-refs Generate schema references (V4 only).
--odata-foreign-keys Render foreign keys in structured format (V4 only)

@@ -286,7 +291,6 @@ -n, --names <style> Annotate artifacts and elements with "@cds.persistence.name", which is

.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])
.option('-v, --validated <bool>', ['true', 'false'])
.option('-e, --enforced <bool>', ['true', 'false'])
.option('-s, --src <style>', ['sql', 'hdi'])
.option(' --drop')
.option(' --alter')
.option(' --violations')
.help(`

@@ -297,2 +301,4 @@ Usage: cdsc manageConstraints [options] <files...>

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

@@ -310,8 +316,2 @@ Options

(like "quoted", but using element names with dots).
-v, --validated <bool> Whether existing records are checked for referential integrity upon constraint activation:
true : (default) VALIDATED
false : NOT VALIDATED
-e, --enforced <bool> Whether future operations on constrained fields are checked for referential integrity:
true : (default) ENFORCED
false : NOT ENFORCED
-s, --src <style> Generate SQL source files as <artifact>.<suffix>

@@ -322,3 +322,4 @@ sql : (default) <suffix> is "sql"

--alter Generate "ALTER TABLE <table> ALTER CONSTRAINT <constraint>" statements
--violations Generates SELECT statements which can be used to list
referential integrity violations on the existing data
`);

@@ -325,0 +326,0 @@

@@ -26,12 +26,2 @@

} = options.manageConstraints || {};
const referentialConstraints = {};
forEachDefinition(csn, (artifact) => {
if (artifact.$tableConstraints && artifact.$tableConstraints.referential) {
Object.entries(artifact.$tableConstraints.referential)
.forEach(([ identifier, referentialConstraint ]) => {
referentialConstraints[identifier] = referentialConstraint;
});
}
});
const indent = '';

@@ -44,7 +34,7 @@ // either ALTER TABLE statements or .hdbconstraint artifacts

Object.entries(artifact.$tableConstraints.referential)
.forEach(([ id, constraint ]) => {
.forEach(([ fileName, constraint ]) => {
const renderAlterConstraintStatement = alter && src !== 'hdi';
const renderedConstraint = renderReferentialConstraint(constraint, id, indent, false, csn, options, renderAlterConstraintStatement);
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
if (src === 'hdi') {
resultArtifacts[id] = renderedConstraint;
resultArtifacts[fileName] = renderedConstraint;
return;

@@ -57,7 +47,7 @@ }

else if (drop)
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(getResultingName(csn, options.toSql.names, id))};`;
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
else
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
resultArtifacts[id] = alterTableStatement;
resultArtifacts[fileName] = alterTableStatement;
});

@@ -69,4 +59,138 @@ }

/**
* For a given csn model with foreign keys constraints, generate SELECT statements
* which can be used to SELECT all rows of the dependent table which violate the referential integrity.
*
* @param {CSN.Model} csn
* @returns a map holding the constraint identifier as key and the corresponding rendered SQL-SELECT statement as value.
*/
function listReferentialIntegrityViolations(csn, options) {
const { quoteSqlId } = getIdentifierUtils(options);
const referentialConstraints = getListOfAllConstraints(csn);
const resultArtifacts = {};
const indent = ' ';
const increaseIndent = indent => `${indent} `;
// helper function to reduce parent key / foreign key array to a comma separated string which can be used in a select clause
const keyStringReducer = prefix => (prev, curr, index) => (index > 0 ? `${prev},\n${curr} AS "${prefix}:${curr}"` : prev);
// helper function to reduce the parent key / foreign key arrays of a referential constraint to a join list which can be used in a where clause
const joinPkWithFkReducer = (constraint, subQueryAlias, mainQueryAlias) => (prev, curr, index) => (index > 0
? `${prev} AND
${increaseIndent(indent)}${mainQueryAlias}.${quoteSqlId(constraint.foreignKey[index])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[index])}`
: increaseIndent(increaseIndent(indent)) + prev);
Object.entries(referentialConstraints).forEach(([ identifier, constraint ]) => {
let selectViolations = 'SELECT\n';
// SELECT <primary_key>,
selectViolations += selectPrimaryKeyColumns(constraint);
// ... <foreign_key>
selectViolations += selectForeignKeyColumns(constraint);
// ... FROM <dependent table> AS "MAIN"
selectViolations += `\nFROM ${quoteAndGetResultingName(constraint.dependentTable)} AS "MAIN"\n`;
// ... WHERE NOT (<(part of) foreign key is null>)
selectViolations += whereNotForeignKeyIsNull(constraint);
/*
... AND NOT EXISTS (
SELECT * FROM <parent_table> WHERE <dependent_table>.<foreign_key> = <parent_table>.<parent_key>
)
*/
selectViolations += andNoMatchingPrimaryKeyExists(constraint);
resultArtifacts[identifier] = selectViolations;
});
/**
* Generate a SELECT list holding all primary key columns of the dependent table found in the referential constraint.
*
* @param {CSN.ReferentialConstraint} constraint
* @returns comma separated list of primary key columns
*/
function selectPrimaryKeyColumns(constraint) {
const pkReducer = keyStringReducer('K');
const primaryKeyOfDependentTable = Object.keys(csn.definitions[constraint.dependentTable].elements)
.filter((key) => {
const element = csn.definitions[constraint.dependentTable].elements[key];
return element.key && element.type !== 'cds.Association' && element.type !== 'cds.Composition';
});
// if no primary key is set in the table
if (primaryKeyOfDependentTable.length === 0)
return '';
return `${primaryKeyOfDependentTable.reduce(pkReducer, `${quoteSqlId(primaryKeyOfDependentTable[0])} AS "K:${primaryKeyOfDependentTable[0]}"`)},\n`;
}
/**
* Generate a SELECT list holding all foreign key columns found in the referential constraint.
*
* @param {CSN.ReferentialConstraint} constraint
* @returns comma separated list of foreign key columns
*/
function selectForeignKeyColumns(constraint) {
const fkReducer = keyStringReducer('FK');
return constraint.foreignKey.reduce(fkReducer, `${quoteSqlId(constraint.foreignKey[0])} AS "FK:${constraint.foreignKey[0]}"`);
}
/**
* Generate SQL WHERE condition asserting to true if none of the foreign key parts has a NULL value in the DB.
*
* @param {CSN.ReferentialConstraint} constraint
* @returns WHERE NOT ( <foreign_key IS NULL ... ) statement
*/
function whereNotForeignKeyIsNull(constraint) {
let whereNot = `${indent}WHERE NOT (\n`;
whereNot += constraint.foreignKey
.reduce((prev, curr, index) => {
if (index > 0)
return `${prev} OR \n${increaseIndent(indent)}${quoteSqlId(curr)} IS NULL`;
return increaseIndent(indent) + prev;
}, `${quoteSqlId(constraint.foreignKey[0])} IS NULL`);
whereNot += `\n${indent})`;
return whereNot;
}
/**
* Generate SQL sub-SELECT, listing all rows of the parent table where no matching primary key column for the respective foreign key is found.
*
* @param {CSN.ReferentialConstraint} constraint
* @returns AND NOT EXISTS ( SELECT * FROM <parent_table> WHERE <dependent_table>.<foreign_key> = <parent_table>.<parent_key> ) statement
*/
function andNoMatchingPrimaryKeyExists(constraint) {
let andNotExists = `\n${indent}AND NOT EXISTS (\n`;
andNotExists += `${increaseIndent(indent)}SELECT * FROM ${quoteAndGetResultingName(constraint.parentTable)}`;
// add an alias to both queries so that they can be distinguished at all times
const subQueryAlias = '"SUB"';
const mainQueryAlias = '"MAIN"';
andNotExists += ` AS ${subQueryAlias}`;
andNotExists += '\n';
const joinListReducer = joinPkWithFkReducer(constraint, subQueryAlias, mainQueryAlias);
andNotExists += `${increaseIndent(indent)}WHERE (\n`;
andNotExists += constraint.foreignKey
.reduce(joinListReducer,
`${mainQueryAlias}.${quoteSqlId(constraint.foreignKey[0])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[0])}`);
andNotExists += `\n${increaseIndent(indent)})`;
andNotExists += `\n${indent});`;
return andNotExists;
}
function quoteAndGetResultingName(id) {
return quoteSqlId(getResultingName(csn, options.toSql.names, id));
}
return resultArtifacts;
}
function getListOfAllConstraints(csn) {
const referentialConstraints = {};
forEachDefinition(csn, (artifact) => {
if (artifact.$tableConstraints && artifact.$tableConstraints.referential) {
Object.entries(artifact.$tableConstraints.referential)
.forEach(([ identifier, referentialConstraint ]) => {
referentialConstraints[identifier] = referentialConstraint;
});
}
});
return referentialConstraints;
}
module.exports = {
manageConstraints,
listReferentialIntegrityViolations,
};

@@ -450,8 +450,8 @@

Object.entries(art.$tableConstraints.referential)
.forEach(([ identifier, referentialConstraint ]) => {
referentialConstraints[identifier] = renderReferentialConstraint(referentialConstraint, identifier, childEnv.indent, false, csn, options);
.forEach(([ fileName, referentialConstraint ]) => {
referentialConstraints[fileName] = renderReferentialConstraint(referentialConstraint, childEnv.indent, false, csn, options);
});
if (renderReferentialConstraintsAsHdbconstraint) {
Object.entries(referentialConstraints).forEach( ([ identifier, constraint ]) => {
resultObj.hdbconstraint[identifier] = constraint;
Object.entries(referentialConstraints).forEach( ([ fileName, constraint ]) => {
resultObj.hdbconstraint[fileName] = constraint;
});

@@ -545,3 +545,3 @@ }

if (hanaTc.fzindexes[elemName][0] instanceof Array) {
if (Array.isArray(hanaTc.fzindexes[elemName][0])) {
// FIXME: Should we allow multiple fuzzy search indices on the same column at all?

@@ -691,3 +691,3 @@ // And if not, why do we wrap this into an array?

let result = '';
if (indexes[idxName][0] instanceof Array) {
if (Array.isArray(indexes[idxName][0])) {
// FIXME: Should we allow multiple indices with the same name at all? (last one wins)

@@ -906,3 +906,3 @@ for (const index of indexes[idxName])

// Positional arguments
if (args instanceof Array)
if (Array.isArray(args))
return args.map(arg => renderExpr(arg, env)).join(', ');

@@ -1298,3 +1298,3 @@

// Compound expression
if (x instanceof Array) {
if (Array.isArray(x)) {
return processExprArray(x, renderExpr, env, inline, nestedExpr);

@@ -1301,0 +1301,0 @@ }

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

if (art && art.kind !== 'context')
if (art && art.kind !== 'context' && art.kind !== 'service')
return parts.slice(i).join('_');
else if (art && art.kind === 'context')
else if (art && (art.kind === 'context' || art.kind === 'service'))
indexOfLastParent = i;

@@ -131,3 +131,3 @@ }

if (art && art.kind === 'context')
if (art && (art.kind === 'context' || art.kind === 'service'))
return name;

@@ -297,3 +297,3 @@ }

const artifact = csn.definitions[artifactName];
if (artifact.kind === 'context' && !artifact._ignore) {
if ((artifact.kind === 'context') && !artifact._ignore) {
// If context A.B.C and entity A exist, we still need generate context A_B.

@@ -300,0 +300,0 @@ // But if no entity A exists, A.B is just a namespace.

@@ -12,3 +12,2 @@ // Render functions for toSql.js

* @param {CSN.ReferentialConstraint} constraint Content of the constraint
* @param {string} identifier Name of the constraint
* @param {string} indent Indent to render the SQL with

@@ -18,9 +17,21 @@ * @param {boolean} toUpperCase Wether to uppercase the identifier

* @param {CSN.Options} options is needed for the naming mode and the sql dialect
* @param {boolean} alterConstraint whether the constraint should be rendered as part of an ALTER TABLE statement
*
* @returns {string} SQL statement which can be used to create the referential constraint on the db.
*/
function renderReferentialConstraint(constraint, identifier, indent, toUpperCase, csn, options, alterConstraint) {
const { quoteSqlId } = getIdentifierUtils(options);
function renderReferentialConstraint(constraint, indent, toUpperCase, csn, options, alterConstraint) {
let quoteId;
// for to.hana we can't utilize the sql identifier utils
if (options.toHana) {
quoteId = (id) => {
if (options.toHana.names === 'plain')
return id.replace(/\./g, '_');
return `"${id}"`;
};
}
else {
quoteId = getIdentifierUtils(options).quoteSqlId;
}
if (toUpperCase) {
identifier = identifier.toUpperCase();
constraint.identifier = constraint.identifier.toUpperCase();
constraint.foreignKey = constraint.foreignKey.map(fk => fk.toUpperCase());

@@ -32,14 +43,15 @@ constraint.parentKey = constraint.parentKey.map(fk => fk.toUpperCase());

const renderAsHdbconstraint = (options.toSql && options.toSql.src === 'hdi') ||
const renderAsHdbconstraint = options.toHana ||
(options.toSql && options.toSql.src === 'hdi') ||
(options.manageConstraints && options.manageConstraints.src === 'hdi');
const { names } = options.forHana;
let result = '';
result += `${indent}CONSTRAINT ${quoteSqlId(getResultingName(csn, options.toSql.names, identifier))}\n`;
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
if (renderAsHdbconstraint)
result += `${indent}ON ${quoteSqlId(getResultingName(csn, options.toSql.names, constraint.dependentTable))}\n`;
result += `${indent}ON ${quoteId(getResultingName(csn, names, constraint.dependentTable))}\n`;
if (!alterConstraint) {
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(fk => quoteSqlId(fk)).join(', ')})\n`;
result += `${indent}REFERENCES ${quoteSqlId(getResultingName(csn, options.toSql.names, constraint.parentTable))}(${constraint.parentKey.map(pk => quoteSqlId(pk)).join(', ')})\n`;
result += `${indent}ON UPDATE CASCADE\n`;
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
result += `${indent}ON UPDATE RESTRICT\n`;
result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;

@@ -49,4 +61,4 @@ }

if (options.toSql && options.toSql.dialect !== 'sqlite') {
result += `${indent}${constraint.validated === 'false' ? 'NOT ' : ''}VALIDATED\n`;
result += `${indent}${constraint.enforced === 'false' ? 'NOT ' : ''}ENFORCED\n`;
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
}

@@ -53,0 +65,0 @@ // for sqlite, the DEFERRABLE keyword is required

'use strict';
const { forEachDefinition } = require('../base/model');
const { forAllElements, hasBoolAnnotation } = require('../model/csnUtils');
const { forAllElements, hasBoolAnnotation, getResultingName } = require('../model/csnUtils');
const { csnRefs } = require('../model/csnRefs');

@@ -16,5 +16,10 @@

function createReferentialConstraints(csn, options){
const {
validated, enforced
} = options.manageConstraints || {};
let validated = true;
let enforced = true;
if(options.constraintsNotValidated)
validated = false;
if(options.constraintsNotEnforced)
enforced = false;
const { inspectRef } = csnRefs(csn);

@@ -78,3 +83,5 @@ // prepare the functions with the compositions and associations across all entities first

const parentKeys = Array.from(elementsOfSourceSide(onCondition, elements));
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3 ], path, 'CASCADE');
// sanity check; do not generate constraints for on-conditons like "dependent.idOne = id AND dependent.idTwo = id"
if(dependentKeys.length === parentKeys.length)
attachConstraintsToDependentKeys(dependentKeys, parentKeys, path[path.length - 3 ], path, 'CASCADE');
}

@@ -105,3 +112,5 @@ } else if(!onCondition && composition.keys){

const parentKeys = Array.from(elementsOfTargetSide(onCondition, associationTarget.elements));
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
// sanity check; do not generate constraints for on-conditons like "dependent.idOne = id AND dependent.idTwo = id"
if(dependentKeys.length === parentKeys.length)
attachConstraintsToDependentKeys(dependentKeys, parentKeys, association.target, path);
} else if (!onCondition && association.keys) {

@@ -139,4 +148,4 @@ throw new Error('Please debug me, an on-condition was expected here, but only found keys');

onDelete,
validated: validated || 'true' ,
enforced: enforced || 'true'
validated,
enforced
}

@@ -294,4 +303,5 @@ dependentKey.$foreignKeyConstraint = constraint;

const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : onDeleteRules.has('SET NULL') ? 'SET NULL' : 'CASCADE';
let onDeleteRemark;
if(onDelete === 'CASCADE')
let onDeleteRemark = null;
// comments in sqlite files are causing the JDBC driver to throw an error on deployment
if(options.testMode && onDelete === 'CASCADE')
onDeleteRemark = `Composition "${$foreignKeyConstraint.sourceAssociation}" implies existential dependency`;

@@ -302,4 +312,4 @@ // if(onDelete === 'RESTRICT' && dependentKey.some(dk => artifact.elements[dk].notNull === true || artifact.elements[dk].key === true))

// onDeleteRemark = `Target cardinality of association "${$foreignKeyConstraint.sourceAssociation}" implies existence of parent`;
referentialConstraints[`${artifactName}_${$foreignKeyConstraint.nameSuffix}`] = {
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.nameSuffix}`] = {
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.nameSuffix}`,
foreignKey: dependentKey,

@@ -306,0 +316,0 @@ parentKey,

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

const {
createForeignKeyElement, flattenOnCond,
createForeignKeyElement,
createAndAddDraftAdminDataProjection, createScalarElement,

@@ -393,18 +393,12 @@ createAssociationElement, createAssociationPathComparison,

// Flatten on-conditions in unmanaged associations
function processOnCond(def, defName) {
const rootPath = ['definitions', defName];
forEachMemberRecursively(def, (member, memberName, prop, subpath) => {
// (3.2) Flatten/normalize on-conditions in unmanaged associations
// Handles on-conditions in unmanaged associations
function processOnCond(def) {
forEachMemberRecursively(def, (member) => {
if (member.type && isAssocOrComposition(member.type) && member.on) {
if (!structuredOData) // flat-mode
flattenOnCond(member, memberName, def.elements, defName, rootPath.concat(subpath));
else // structured-mode
normalizeOnCondForStructuredMode(member);
removeLeadingDollarSelfInOnCondition(member);
}
});
// The function performs normalization for on-conditions in 'structured'-mode (for odata) as follows:
// 1. removes leading $self in references
function normalizeOnCondForStructuredMode(assoc) {
// removes leading $self in on-conditions's references
function removeLeadingDollarSelfInOnCondition(assoc) {
if (!assoc.on) return; // nothing to do

@@ -411,0 +405,0 @@ forEachRef(assoc, (ref, node) => {

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

forAllQueries,
sortCsnDefinitionsForTests,
} = require('../model/csnUtils');

@@ -733,18 +734,2 @@

/**
* Sorts the definition dictionary in tests mode.
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
*/
function sortCsnDefinitionsForTests(csn, options) {
if (!options.testMode)
return;
const sorted = Object.create(null);
Object.keys(csn.definitions).sort().forEach((name) => {
sorted[name] = csn.definitions[name];
});
csn.definitions = sorted;
}
module.exports = {

@@ -751,0 +736,0 @@ addLocalizationViews,

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

* @param {CSN.Model} csn
* @param {*} flatKeys
* @param {*} referenceFlattener
* @param {*} csnUtils
* @param {Function} error
* @param {object} error;
*/

@@ -140,8 +139,9 @@ module.exports = function (csn, options, referenceFlattener, csnUtils, error) {

for (const [foreignKeyName, foreignKey] of Object.entries(foreignKeyElements)) {
// Insert artificial element into artifact, with all cross-links (must not exist already)
copyAnnotations(assoc, foreignKey, true);
// Insert artificial element into artifact, with all cross-links
if (parent.elements[foreignKeyName]) {
const path = parent.elements.$path ? parent.elements.$path.concat([foreignKeyName]) : ['definitions', definitionName, 'elements', foreignKeyName];
error(null, path, `Generated foreign key element "${foreignKeyName}" for association "${assocName}" conflicts with existing element`);
if (parent.elements[foreignKeyName]['@odata.foreignKey4'] && !isDeepEqual(parent.elements[foreignKeyName], foreignKey))
error(null, path, `Generated foreign key element "${foreignKeyName}" for association "${assocName}" conflicts with existing element`);
}
copyAnnotations(assoc, foreignKey, true);
}

@@ -230,1 +230,29 @@

}
/**
*
* @param {object} obj
* @param {*} other
* @returns {boolean} Whether 'obj' and 'other' are deeply equal. We need the
* deep comparison because of annotations that have structured values and they
* are propagated to the generated foreign keys.
*/
function isDeepEqual(obj, other) {
const objectKeys = Object.keys(obj);
const otherKeys = Object.keys(other);
if (objectKeys.length !== otherKeys.length)
return false;
for (let key of objectKeys) {
const areValuesObjects = (obj[key] != null && typeof obj[key] === 'object')
&& (other[key] !== null && typeof other[key] === 'object');
if (areValuesObjects) {
if (!isDeepEqual(obj[key], other[key]))
return false;
} else if (obj[key] !== other[key])
return false;
}
return true;
}

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

forEachRef(csn, (ref, node, path) => {
if (ref.length == 1) return; // too short, no flattening
if (node.$paths) {

@@ -215,2 +214,3 @@ let newRef = []; // flattened reference

let spath = path.join('/')
let movedTo = this.elementTransitions[spath]; // detect element transition
let flattened = this.flattenedElementPaths[spath];

@@ -220,2 +220,6 @@ if (flattenWithPrevious) {

anythingFlattened = true;
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
let movedToElementName = movedTo[movedTo.length-1];
newRef.push(movedToElementName);
anythingFlattened=true;
} else {

@@ -222,0 +226,0 @@ newRef.push(ref[i]);

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

} = require('./utils');
const { forEach } = require('../udict');
const { forEach } = require('../../utils/objectUtils.js');

@@ -22,0 +22,0 @@ function buildDependenciesFor(csn, referenceFlattener) {

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

referenceFlattener.registerElementTransition(path, newPath);
let movedTo = referenceFlattener.getElementTransition(path)
if (movedTo) {
setProp(newElement, '$paths', [movedTo]); // moved always on top-level -> new $paths has only one path element
}
}

@@ -154,0 +150,0 @@ }

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

const { cloneCsn, forEachMemberRecursively, forEachGeneric, forAllQueries,
forEachRef, getUtils, isBuiltinType } = require('../model/csnUtils');
getUtils, isBuiltinType } = require('../model/csnUtils');

@@ -45,3 +45,2 @@ // Return the public functions of this module, with 'model' captured in a closure (for definitions, options etc).

flattenStructuredElement,
flattenOnCond,
flattenStructStepsInRef,

@@ -93,3 +92,3 @@ toFinalBaseType,

// createRealFK 'really' creates the new foreign key element and is used by the two
// createRealFK 'really' creates the new foreign key element and is used by the two
// main FK generators getForeignKeyArtifact & createForeignKeyElement

@@ -378,54 +377,2 @@ // and (not yet) generateForeignKeyElements.js::generateForeignKeysForRef()

// Flatten on conditions of unmanaged associations. This method assumes that
// other flattening of the elements was already performed and there are
// no left over structure elements marked with some ignore flag. Also, uses
// the non-enumerable property '_flatElementNameWithDots'.
function flattenOnCond(assoc, assocName, defElements, defName, path) {
if (!assoc.on) return; // nothing to do
forEachRef(assoc, (ref, node, path) => {
// assoc itself is inside a struct -> all the first refs need to be flattened;
if (assoc.$viaTransform) {
// when the first ref id is the same association
if (assoc._flatElementNameWithDots.endsWith(`.${ref[0]}`))
ref.splice(0, 1, assocName);
// elem from the curr name resolution scope
// but not a $self, will deal with this one later
else if (ref[0] !== '$self') {
// .splice(-1, 1, ref[0]) does not work here, why???
let currStructFlatName = `${assoc._flatElementNameWithDots.split('.').slice(0, -1).join('_')}`
node.ref.splice(0, 1, `${currStructFlatName}_${ref[0]}`);
}
}
let flatRef = [];
let needToFlat = false;
// $user.locale & $user.id must not be flatten and they are not understand by inspectRef
if (/^(\$user\.locale|\$user\.id)$/.test(ref.join('.')))
return;
ref.slice().forEach(refId => {
if (needToFlat) {
let flatLastElem = `${flatRef[flatRef.length - 1]}${pathDelimiter}${refId}`;
flatRef.splice(-1, 1, flatLastElem);
needToFlat = false;
} else
flatRef.push(refId);
node.ref = flatRef;
let { art } = inspectRef(path);
// if not resolved to an art, then this element was flattened
if (!art)
needToFlat = true;
});
// remove leading $self when at the begining of a ref
if (ref.length > 1 && ref[0] === '$self')
node.ref.splice(0, 1);
}, path);
}
/**

@@ -1298,3 +1245,3 @@ * Copy properties of the referenced type, but don't resolve to the final base type.

// Transform arrays element-wise
if (node instanceof Array) {
if (Array.isArray(node)) {
node.forEach((subnode, index) => transformNode(subnode, null, null, path.concat([index])));

@@ -1301,0 +1248,0 @@ return;

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

function copyPropIfExist(targetObj, property, sourceObj) {
if (property in sourceObj)
if (sourceObj && property in sourceObj)
targetObj[property] = sourceObj[property];

@@ -33,5 +33,19 @@ }

/**
* Loops over all elements in an object and calls the specified callback(key,obj)
*
* @param {object} obj
* @param {(string, object) => void} callback
*/
function forEach(obj, callback) {
for (const key in obj) {
if (Object.hasOwnProperty.call(obj, key))
callback(key, obj[key]);
}
}
module.exports = {
copyPropIfExist,
createDict,
forEach,
};
{
"name": "@sap/cds-compiler",
"version": "2.1.6",
"version": "2.2.4",
"description": "CDS (Core Data Services) compiler and backends",

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

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

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc