Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
3
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 1.19.2 to 1.20.3

benchmark/benchmarks.js

74

bin/cdsc.js

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

const { translatePathLocations } = require('../lib/base/messages')
const { translateAssocsToJoins } = require('../lib/transform/translateAssocsToJoins');
const term = require('../lib/utils/term');

@@ -78,3 +80,6 @@ // Note: Instead of throwing ProcessExitError, we would rather just call process.exit(exitCode),

}
// Default color omde is 'auto'
term.useColor(cmdLine.options.color || 'auto');
// Do the work for the selected command (default 'toCsn')

@@ -135,26 +140,8 @@ executeCommandLine(cmdLine.command || 'toCsn', cmdLine.options, cmdLine.args, cmdLine.unknownOptions);

// special case when we want to call a transformer directly
// without compiling before that (for now only available for toOdata, toHana, toSql)
// works only with combination of --beta-mode in the transformers
if (options.skipCompile && ['toOdata', 'toHana', 'toSql'].includes(command)) {
if (args.length !== 1)
throw new Error(`--skip-compile option works with only one file`);
let csn = '';
try {
csn = JSON.parse(fs.readFileSync(args[0]));
} catch (e) {
throw new Error(`${args[0]} must be a valid json file`);
}
options.betaMode = true;
csn.options = options;
commands[command](csn);
} else {
const fileCache = Object.create(null)
compiler.compile( args, undefined, options, fileCache )
.then( commands[command] )
.then( displayMessages, displayErrors )
.catch( catchErrors );
compiler.compile( args, undefined, options )
.then( commands[command] )
.then( displayMessages, displayErrors )
.catch( catchErrors );
}
if (unknownOptions.length) {

@@ -187,8 +174,8 @@ let out = process.stdout;

let hanaResult;
if(options.newTransformers) {
if(options.oldTransformers) {
hanaResult = compiler.toHana(model);
} else {
let csn = compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that
hanaResult = toHanaWithCsn(csn, options)
} else {
hanaResult = compiler.toHana(model);
}

@@ -206,8 +193,8 @@ for (let name in hanaResult.hdbcds) {

let odataResult;
if(options.newTransformers) {
if(options.oldTransformers) {
odataResult = compiler.toOdata(model)
} else {
let csn = compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that
odataResult = toOdataWithCsn(csn, options)
} else {
odataResult = compiler.toOdata(model)
odataResult = toOdataWithCsn(csn, options);
}

@@ -254,3 +241,3 @@ translatePathLocations(model.messages, model);

function toSql( model ) {
let sqlResult = options.newTransformers ? toSqlWithCsn(compactModel(model, options), options) : compiler.toSql(model);
let sqlResult = options.oldTransformers ? compiler.toSql(model) : toSqlWithCsn(compactModel(model, options), options);

@@ -285,3 +272,3 @@ ['hdbtabletype', 'hdbtable', 'hdbindex', 'hdbfulltextindex', 'hdbview', 'sql'].forEach(pluginName => {

if (options.rawOutput)
console.error( util.inspect( reveal( err.model ), false, null ));
console.error( util.inspect( reveal( err.model, options.rawOutput ), false, null ));
else

@@ -309,6 +296,15 @@ displayMessages( err.model, err.errors );

for (let msg of messages) {
if (options.internalMsg)
if (options.internalMsg) {
console.error( util.inspect( msg, { depth: null, maxArrayLength: null} ) );
else if (messageLevels[ msg.severity ] <= options.warning)
console.error( compiler.messageString( msg, normalizeFilename, !options.showMessageId ) );
} else if (messageLevels[ msg.severity ] <= options.warning) {
if (!options.showMessageContext) {
console.error(compiler.messageString(msg, normalizeFilename, !options.showMessageId));
} else {
console.error(compiler.messageStringMultiline(msg, normalizeFilename, !options.showMessageId));
const fullfilepath = (msg.location && msg.location.filename )? path.resolve('', msg.location.filename) : undefined;
const sourceLines = fileCache[fullfilepath] ? fileCache[fullfilepath].split('\n') : fileCache;
console.error(compiler.messageContext(sourceLines, msg));
}
}
}

@@ -333,3 +329,9 @@ }

if (options.rawOutput) {
writeToFileOrDisplay(options.out, name + '_raw.txt', util.inspect(reveal(xsn), false, null), true);
if(options.toCsn && options.toCsn.associations === "joins"){
options.forHana = {};
options.forHana.associations = options.toCsn.associations;
writeToFileOrDisplay(options.out, name + '_raw.txt', util.inspect(reveal(translateAssocsToJoins(xsn, options), options.rawOutput), false, null), true);
} else {
writeToFileOrDisplay(options.out, name + '_raw.txt', util.inspect(reveal(xsn, options.rawOutput), false, null), true);
}
}

@@ -336,0 +338,0 @@ else if (options.internalMsg) {

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

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

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

const compiler = require('../lib/main');
const { locationString } = require('../lib/base/messages');

@@ -31,2 +32,3 @@ let argv = process.argv;

let frel = path.relative( '', file||'' );
// TODO: proper realname

@@ -41,3 +43,7 @@ if (argv.length > 5 && cmd && line > 0 && column > 0)

console.error( 'ERROR:', err.toString() );
console.error( 'Usage: cdsse complete <line> <col> <file> [-]' );
console.error( 'Usage: cdsse <cmd> <line> <col> <file> [-]' );
console.error( '----------- supported commands <cmd>:' );
console.error( ' complete: syntactic and semantic code completion' );
console.error( ' find: location of definition' );
console.error( ' lint: linter (<line> and <col> are ignored, should be numbers)' );
process.exitCode = 2;

@@ -86,2 +92,65 @@ return false;

function find( err, buf ) {
if (err)
return usage( err );
let fname = path.resolve( '', file );
compiler.compile( [file], '', { lintMode: true, betaMode: true } , { [fname]: buf } )
.then( select, () => true );
return true;
function select( xsn ) {
const [src] = Object.values( xsn.sources );
pathitem( src.usings );
pathitem( src.extensions );
pathitem( src.artifacts );
return true;
}
}
const inspect = {
'_': () => false,
'$': () => false,
id: () => false,
location: () => false,
queries: () => false, // XSN TODO: remove
origin: () => false, // XSN TODO: remove?
elements: (n, p) => (!p || !p.query && !p.columns) && pathitem( n ),
}
function pathitem( node ) {
if (!node || typeof node !== 'object')
return false;
else if (Array.isArray( node ))
return node.some( pathitem );
else if (!Object.getPrototypeOf( node ))
return Object.values( node ).some( pathitem );
const { location } = node;
if (!location || node.$inferred || location.$weak)
return false;
if (location.filename !== frel)
return false;
if (location.start.line > line || location.start.line === line && location.start.column > column)
return 'stop'; // 'stop' means: stop search if in array or dictionary
if (location.end.line < line || location.end.line === line && location.end.column < column) {
if (!node.id)
return false;
}
else if (node.id) {
const art = node._artifact;
// TODO: we could skip over some internal artifacts here
if (art && art.location)
console.log( locationString( art.name.location || art.location ) + ': Definition' );
return true; // true means: stop in array or dictionary or other object
}
let found = false;
for (const prop in node) {
found = (inspect[prop] || inspect[prop.charAt()] || pathitem)( node[prop], node ) || found;
if (found === true)
return found;
}
return found;
}
function lint( err, buf ) {

@@ -88,0 +157,0 @@ if (err)

@@ -143,1 +143,34 @@ # Error Messages Explained

would provide little benefit, but would add complexity and confusion.
### Redirection issues
The target `OrigTarget` of an existing association can only be redirected to another target `NewTarget`
if the `NewTarget` is a direct or indirect projection of `OrigTarget`
(complex views are questionable and lead to a Warning),
or an entity definition which directly or indirectly includes `OrigTarget`.
```
entity Base {
key i: Integer;
}
entity Proj as projection on Base;
entity NewTarget as projection on Intermediate;
entity Intermediate as projection on Base;
entity Assocs {
base: association to Base;
proj: association to Proj;
}
entity Redirect as projection on Assocs {
base: redirected to NewTarget, // works
proj: redirected to NewTarget // ERROR: does not originate from Proj
}
```
For the above CDS code, you get the following error message:
```
redirect-to-unrelated.cds:16:25-34: Error: The redirected target does not originate from "Proj"
(in entity:"Redirect"/element:"proj")
```

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

const { compactModel, sortCsn } = require('./json/to-csn')
const { toCdsSource } = require('./render/toCdl');
const { toCdsSource, toCdsSourceCsn } = require('./render/toCdl');
const { toSqlDdl } = require('./render/toSql');

@@ -23,2 +23,3 @@ const { toRenameDdl } = require('./render/toRename');

const { optionProcessor } = require('./optionProcessor')
const { translateAssocsToJoinsCSN } = require('./transform/translateAssocsToJoins');

@@ -152,6 +153,12 @@ // Transform an augmented CSN 'model' into HANA-compatible CDS source.

if (options.toHana.src) {
result.hdbcds = toCdsSource(forHanaCsn, options);
if(options.testMode){
const sorted = sortCsn(forHanaCsn, true);
setProp(sorted, "options", forHanaCsn.options);
//const sorted = forHanaCsn;
result.hdbcds = toCdsSourceCsn(sorted, options);
} else {
result.hdbcds = toCdsSourceCsn(forHanaCsn, options);
}
}
if (options.toHana.csn) {
result._augmentedCsn = options.testMode ? sortCsn(forHanaCsn) : forHanaCsn;
result.csn = options.testMode ? sortCsn(forHanaCsn) : forHanaCsn;

@@ -187,2 +194,3 @@ }

// toOdata.version
// toOdata.odataFormat
// toOdata.xml

@@ -867,4 +875,12 @@ // toOdata.json

}
return options.newCsn === false ? (options.testMode ? compactSorted(model) : compact(model))
let csn = options.newCsn === false ? (options.testMode ? compactSorted(model) : compact(model))
: compactModel(model);
if(options.toCsn.associations === "joins"){
options.forHana = {};
options.forHana.associations = options.toCsn.associations;
csn = translateAssocsToJoinsCSN(csn, options);
}
return csn;
}

@@ -884,2 +900,3 @@

version : 'v4',
odataFormat: 'flat',
},

@@ -886,0 +903,0 @@ toRename: {

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

const term = require('../utils/term');
// For messageIds, where no severity has been provided via code (central def)

@@ -53,4 +55,4 @@ const standardSeverities = {

'expected-type': 'A type or an element of a type is expected here',
'expected-entity': 'An entity, projection or view is expected here',
'expected-source': 'A query source must be an entity or an association to an entity',
'expected-entity': 'A non-abstract entity, projection or view is expected here',
'expected-source': 'A query source must be a non-abstract entity or an association to an entity',
}

@@ -103,3 +105,3 @@

* Class for individual compile errors.
*
*
* @class CompileMessage

@@ -115,5 +117,5 @@ * @extends {Error}

* @param {any} id The ID of the message - visible as property messageId
* @param {any} home
* @param {any} home
* @param {any} context Some further context information, if needed
*
*
* @memberOf CompileMessage

@@ -306,3 +308,6 @@ */

// Return message string with location if present
// Return message string with location if present in compact form (i.e. one line)
//
// Example:
// <source>.cds:3:11: Error: cannot find value `nu` in this scope
function messageString( err, normalizeFilename, noMessageId, noHome ) {

@@ -314,4 +319,56 @@ return (err.location ? locationString( err.location, normalizeFilename ) + ': ' : '') +

(!err.home || noHome && err.location && !err.location.$weak ? '' : ' (in ' + err.home + ')');
}
// Return message string with location if present.
//
// Example:
// Error: cannot find value `nu` in this scope
// <source>.cds:3:11
//
function messageStringMultiline( err, normalizeFilename, noMessageId, noHome ) {
const msgId = (err.messageId && !noMessageId ? '[' + err.messageId + ']' : '');
const home = (!err.home || noHome && err.location && !err.location.$weak ? '' : ' (in ' + err.home + ')');
const severity = err.severity || 'Error';
const location = (err.location ? '\n=> ' + locationString( err.location, normalizeFilename ) : '');
return term.asSeverity(severity, severity + msgId) + ': ' + err.message + home + location;
}
// Returns a context (code) string that is human readable (similar to rust's compiler)
//
// Example:
// |
// 3 | num * nu
// | ^^
//
function messageContext(sourceLines, err) {
if (!err.location || !(err.location instanceof Object)) {
return "";
}
const loc = normalizeLocation(err.location);
if (!loc.start) {
return "";
}
const startLine = loc.start.line;
if (!sourceLines[startLine - 1]) {
return "";
}
const digits = loc.end ? String(loc.end.line).length : String(loc.start.line).length;
const severity = err.severity || 'Error';
const indent = ' '.repeat(2 + digits);
const endColumn = loc.end ? loc.end.column : loc.start.column + 1;
let msg = "";
msg += indent + '| ' + '\n';
msg += ' ' + startLine + ' | ' + sourceLines[startLine - 1].replace('\t', ' ') + '\n';
msg += indent + '| ' + term.asSeverity(severity, ' '.repeat(loc.start.column - 1).padEnd(endColumn - 1, '^')) + '\n';
return msg;
}
// Compare two messages `a` and `b`. Return 0 if they are equal, 1 if `a` is

@@ -344,3 +401,3 @@ // larger than `b`, and -1 if `a` is smaller than `b`. Messages without a location

let r = (name.absolute) ? [ quoted( name.absolute ) ] : [];
if (name.query || name.query != null && art.kind !== 'element' || name.$mixin ) // Yes, omit $query.0 for element - TODO: rename to block
if (name.query || name.query != null && art.kind !== 'element' || name.$mixin ) // Yes, omit $query.0 for element - TODO: rename to block
r.push( (art.kind === 'block' ? 'block:' : 'query:') + (name.query + 1) );

@@ -354,2 +411,3 @@ if (name.action && omit !== 'action')

if (name.element && omit !== 'element')
// r.push( `${ art.kind }: ${ quoted( name.element )}` ); or even better element:"assoc"/key:"i" same with enum
r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );

@@ -414,2 +472,4 @@ return r.join('/');

messageString,
messageStringMultiline,
messageContext,
searchName,

@@ -416,0 +476,0 @@ getMessageFunction,

@@ -88,4 +88,7 @@ //

// 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.
function setProp (obj, prop, value) {
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
let descriptor = { value, configurable: true, writable: true, enumerable: false };
Object.defineProperty( obj, prop, descriptor );
}

@@ -92,0 +95,0 @@

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

},
$weak: { test: isBoolean },
$weak: { test: isBoolean, parser: true },
$notFound: { test: isBoolean },

@@ -174,2 +174,3 @@ },

foreignKeys: { kind: true, inherits: 'definitions' },
$keysNavigation: { kind: true, test: TODO },
foreignKeys_: { kind: true, parser: true, test: TODO }, // TODO: remove

@@ -390,3 +391,3 @@ _foreignKeysIndexNo: { kind: true, parser: true, test: TODO }, // TODO: remove

projection: { kind: true, test: TODO }, // TODO: remove in JSON/CDL parser
technicalConfig: { kind: [ 'entity' ], test: TODO }, // TODO: some spec
technicalConfig: { enumerable: () => true, kind: [ 'entity' ], test: TODO }, // TODO: some spec
sequenceOptions: { kind: true, test: TODO }, // hanaFlavor

@@ -393,0 +394,0 @@ targetElement: { kind: true, inherits: 'type' }, // for foreign keys

@@ -69,6 +69,6 @@ // Compiler functions and utilities shared across all phases

// TODO: delete envFn again and specify some reject for extend/annotate ?
function fns( model, environment = artifactsEnv ) {
const options = model.options || {};
const message = getMessageFunction( model );
// TODO: combine envFn and assoc ?
const specExpected = {

@@ -81,3 +81,4 @@ annotation: { useDefinitions: true, noMessage: true },

type: { reject: rejectNonType }, // TODO: more detailed later (e.g. for enum base type?)
typeOf: { next: '_$next', assoc: false }, // warn
// if we want to disallow assoc nav for TYPE, do not do it her
typeOf: { next: '_$next' },
include: { reject: rejectNonStruct, envFn: artifactsEnv },

@@ -88,3 +89,4 @@ context: { reject: rejectNonContext, envFn: artifactsEnv },

// TODO: dep for (explicit+implicit!) foreign keys
element: { next: '__none_' }, // foreign key element
element: { next: '__none_' }, // TODO: something for technical config - re-think
targetElement: { next: '__none_', assoc: false },
filter: { next: '_$next', lexical: 'main' },

@@ -116,2 +118,5 @@ from: { reject: rejectNonSource, assoc: 'from' },

// TODO: for reject, distinguish between "completely wrong", i.e. assoc
// target is no struct, and "rejected"; the former have _artifact: null, the
// latter the referred one.
function rejectNonConst( art ) {

@@ -143,3 +148,3 @@ return [ 'builtin', 'param', 'const' ].includes( art.kind ) ? undefined : 'expected-const';

function rejectNonEntity( art ) {
return ([ 'view', 'entity' ].includes( art.kind )) // TODO: also not abstract
return ([ 'view', 'entity' ].includes( art.kind ) && !(art.abstract && art.abstract.val))
? undefined

@@ -150,3 +155,6 @@ : 'expected-entity';

function rejectNonTarget( art ) {
return (options.betaMode && art.kind === 'type') ? rejectNonStruct( art ) : rejectNonEntity( art );
return (options.betaMode && // TODO: delete 'abstract', proper kind: 'aspect'
(art.kind === 'type' || art.kind === 'entity' && art.abstract && art.abstract.val))
? rejectNonStruct( art )
: rejectNonEntity( art );
}

@@ -156,5 +164,6 @@

if ([ 'view', 'entity' ].includes( art.kind ))
return undefined;
const main = [ ...path ].reverse().find( item => !item._artifact._main );
if (![ 'view', 'entity' ].includes( main._artifact.kind ))
return (art.abstract && art.abstract.val) ? 'expected-source' : undefined;
const main = [ ...path ].reverse().find( item => !item._artifact._main )._artifact;
// TODO: better error location if error for main
if (![ 'view', 'entity' ].includes( main.kind ) || main.abstract && main.abstract.val)
return 'expected-source'; // orig: 'A source must start at an entity, projection or view';

@@ -471,3 +480,4 @@ environment( art ); // sets _finalType on art

let art;
const assoc = spec.assoc && (spec.assoc == null || user);
let nav = spec.assoc !== '$keys' && null; // false for '$keys'
const last = path[path.length - 1];
for (const item of path) {

@@ -482,3 +492,3 @@ if (!item || !item.id) // incomplete AST due to parse error

else {
const env = (spec.envFn || environment)( art, item.location, assoc );
const env = (spec.envFn || environment)( art, item.location, user, spec.assoc );
const sub = setLink( item, env && env[item.id] );

@@ -489,2 +499,26 @@ if (!sub)

return false;
if (nav) { // we have already "pseudo-followed" a managed association
// We currently rely on the check that targetElement references do
// not (pseudo-) follow associations, otherwise potential redirection
// there had to be considered, too. Also, fk refs to sub elements in
// combinations with redirections of the target which directly access
// the potentially renamed sub elements would be really complex.
// With our restriction, no renaming must be considered for item.id.
nav = setTargetReferenceKey( item.id, item );
}
// Now set an _navigation link for managed assocs in ON condition etc
else if (art && art.target && nav != null) {
// Find the original ref for sub and the original foreign key
// definition. This way, we do not need the foreign keys with
// rewritten target element path, which might not be available at
// this point (rewriteKeys in Resolver Phase 5). If we want to
// follow associations in foreign key definitions, rewriteKeys must
// be moved to the on-demand Resolver Phase 2.
let orig; // for the original target element
for (let o = sub; o; o = o.value && o.value._artifact)
orig = o;
nav = (orig._finalType || orig).$keysNavigation;
nav = setTargetReferenceKey( orig.name.id, item );
}
art = sub;

@@ -496,2 +530,22 @@ setXref( art, user, item );

function setTargetReferenceKey( id, item ) {
const node = nav && nav[id];
nav = null;
if (node) {
if (node._artifact) {
// set the original(!) foreign key for the assoc - the "right" ones
// after rewriteKeys() is the one with the same name.id
setLink( item, node._artifact, '_navigation' );
if (item === last)
return;
}
else if (item !== last) {
nav = node.$keysNavigation;
return;
}
}
message( null, item.location, user, {}, 'Error',
'You cannot follow associations other than to elements referred to in a managed association\'s key' );
}
function error( item, env ) {

@@ -498,0 +552,0 @@ if (!spec.next) { // artifact ref

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

const edmlib = require('../edm.js')(options);
const Edm = require('../edm.js')(csn, options);

@@ -158,5 +158,5 @@ // annotation preprocessing

// generate the edmx "frame" around the annotations
let schema = new edmlib.Schema(v, serviceName, serviceName, g_annosArray, false);
let service = new edmlib.DataServices(v, schema);
let edm = new edmlib.Edm(v, service);
let schema = new Edm.Schema(v, serviceName, serviceName, g_annosArray, false);
let service = new Edm.DataServices(v, schema);
let edm = new Edm.Edm(v, service);

@@ -166,4 +166,4 @@ // add references for the used vocabularies

if(vocabularyDefinitions[n].used) {
let r = new edmlib.Reference(v, vocabularyDefinitions[n].ref);
r.append(new edmlib.Include(v, vocabularyDefinitions[n].inc))
let r = new Edm.Reference(v, vocabularyDefinitions[n].ref);
r.append(new Edm.Include(v, vocabularyDefinitions[n].inc))
edm._defaultRefs.push(r);

@@ -370,3 +370,3 @@ }

if(carrier['@cds.api.ignore']) {
if(carrier['@cds.api.ignore'] || (carrier['@cds.odata.'+options.version+'.ignore'] && options.betaMode)) {
return;

@@ -405,4 +405,4 @@ }

// newAnnosStd: the corresponding <Annotations ...> tag
// for some carriers (service, entity), there can be an alternative target
// altName: name of alternative target
// for some carriers (service, entity), there can be an alternative target (usually the EntitySet)
// alternativeEdmTargetName: name of alternative target
// newAnnosAlt: the corresponding <Annotations ...> tag

@@ -415,3 +415,4 @@ // which one to choose depends on the "AppliesTo" info of the single annotations, so we have

let stdName = edmTargetName;
let altName = null;
let alternativeEdmTargetName = null;
let hasAlternativeCarrier = true; // is the alternative annotation target available in the EDM?
if (carrier.kind === 'entity' || carrier.kind === 'view') {

@@ -421,4 +422,10 @@ // if annotated object is an entity, annotation goes to the EntityType,

appliesTest = (x => x.match(/EntitySet/) && !x.match(/EntityType/));
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName
// (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts
// are split into *Parameter and *Type entities and their respective EntitySets are eventually
// renamed.
// (which is the definition key in the CSN and usually the name of the EntityType)
// find last . in name and insert "EntityContainer/"
altName = edmTargetName.replace(/\.(?=[^.]*$)/, '.EntityContainer/');
alternativeEdmTargetName = (carrier.entitySetName || edmTargetName).replace(/\.(?=[^.]*$)/, '.EntityContainer/');
hasAlternativeCarrier = carrier.hasEntitySet;
}

@@ -430,9 +437,10 @@ else if (carrier.kind === 'service') {

stdName = edmTargetName + '.EntityContainer';
altName = edmTargetName;
alternativeEdmTargetName = edmTargetName;
hasAlternativeCarrier = true; // EntityContainer is always available
}
// result objects that holds all the annotation objects to be created
let newAnnosStd = new edmlib.Annotations(v, stdName); // used in closure
let newAnnosStd = new Edm.Annotations(v, stdName); // used in closure
g_annosArray.push(newAnnosStd);
let newAnnosAlt = null; // create only on demand
let newAnnosAlternative = null; // create only on demand

@@ -448,8 +456,9 @@ return function(annotation, appliesTo) {

}
else {
if (!newAnnosAlt) { // only create upon insertion of first anno
newAnnosAlt = new edmlib.Annotations(v, altName);
g_annosArray.push(newAnnosAlt);
// Add <Annotations> to existing carrier only
else if(hasAlternativeCarrier) {
if (!newAnnosAlternative) { // only create upon insertion of first anno
newAnnosAlternative = new Edm.Annotations(v, alternativeEdmTargetName);
g_annosArray.push(newAnnosAlternative);
}
newAnnosAlt.append(annotation);
newAnnosAlternative.append(annotation);
}

@@ -540,3 +549,3 @@ }

// create the <Annotation ...> tag
let newAnno = new edmlib.Annotation(v, termName);
let newAnno = new Edm.Annotation(v, termName);

@@ -648,3 +657,3 @@ // termName may contain a qualifier: @UI.FieldGroup#shippingStatus

if(oTermName == "Core.OperationAvailable" && dTypeName === "Edm.Boolean" && cAnnoValue === null) {
oTarget.append(new edmlib.ValueThing(v, 'Null'));
oTarget.append(new Edm.ValueThing(v, 'Null'));
oTarget._ignoreChildren = true;

@@ -834,3 +843,5 @@ }

}
else {
else if(val === null && dTypeName == null && typeName == 'String') {
dTypeName = 'Edm.String';
} else {
warningMessage(context, "expected simple value, but found value '" + val + "' with type '" + typeof val + "'");

@@ -855,3 +866,3 @@ }

function generateRecord(obj, termName, dTypeName, context) {
let newRecord = new edmlib.Record(v);
let newRecord = new Edm.Record(v);

@@ -935,3 +946,3 @@ // first determine what is the actual type to be used for the record

let newPropertyValue = new edmlib.PropertyValue(v, i);
let newPropertyValue = new Edm.PropertyValue(v, i);
// property value can be anything, so delegate handling to handleValue

@@ -952,3 +963,3 @@ handleValue(obj[i], newPropertyValue, termName, dictPropertyTypeName, context);

function generateCollection(annoValue, termName, dTypeName, context) {
let newCollection = new edmlib.Collection(v);
let newCollection = new Edm.Collection(v);

@@ -979,3 +990,3 @@ let innerTypeName = null;

let res = handleExpression(value["="], innerTypeName, context);
let newPropertyPath = new edmlib.ValueThing(v, res.name, res.value );
let newPropertyPath = new Edm.ValueThing(v, res.name, res.value );
newPropertyPath.setJSON( { [res.name] : res.value } );

@@ -994,3 +1005,3 @@ newCollection.append(newPropertyPath);

let res = handleSimpleValue(value, innerTypeName, context);
let newThing = new edmlib.ValueThing(v, res.name, value );
let newThing = (value === null) ?new Edm.ValueThing(v, 'Null') : new Edm.ValueThing(v, res.name, value );
newThing.setJSON( { [res.jsonName] : res.value });

@@ -1025,3 +1036,3 @@ newCollection.append(newThing);

let k = Object.keys(obj)[0];
return new edmlib.ValueThing(v, k.slice(1), obj[k] );
return new Edm.ValueThing(v, k.slice(1), obj[k] );
}

@@ -1033,3 +1044,3 @@ warningMessage(context, "edmJson code contains no special property out of: " + specialProperties);

// name of special property determines element kind
let newElem = new edmlib.Thing(v, subset[0].slice(1));
let newElem = new Edm.Thing(v, subset[0].slice(1));
let mainAttribute = null;

@@ -1065,3 +1076,3 @@

}
newElem.append(new edmlib.ValueThing(v, getTypeName(a), a));
newElem.append(new Edm.ValueThing(v, getTypeName(a), a));
}

@@ -1148,3 +1159,3 @@ }

let type = getDictType(dTypeName);
return type && type["$kind"] == "ComplexType";
return dTypeName == 'Edm.ComplexType' || type && type["$kind"] == "ComplexType";
}

@@ -1151,0 +1162,0 @@

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

const { getUtils, cloneCsn } = require('../model/csnUtils');
const { isNewCSN } = require('../json/csnVersion');
const { CompilationError } = require('../base/messages');
/*

@@ -25,17 +28,22 @@ OData V2 spec 06/01/2017 PDF version is available from here:

function csn2edm(csn, serviceName, _options) {
return csn2edmAll(csn, _options, serviceName)[serviceName];
function csn2edm(_csn, serviceName, _options) {
return csn2edmAll(_csn, _options, serviceName)[serviceName];
}
function csn2edmAll(csn, _options, serviceName=undefined) {
function csn2edmAll(_csn, _options, serviceName=undefined) {
// get us a fresh model copy that we can work with
let csn = cloneCsn(_csn);
const { error, warning, info, signal } = alerts(csn);
if (!isNewCSN(csn, _options)) {
let version = csn.version ? csn.version.csn : csn.$version ? csn.$version : 'not available';
signal(error`CSN Version not supported, version tag: "${version}", options.newCsn: ${_options.newCsn}`);
throw new CompilationError(csn.messages, csn);
}
const { isBuiltinType } = getUtils(csn);
// get us a fresh model copy that we can work with
let model = cloneCsn(csn);
let rc = Object.create(null);
const { isBuiltinType, hasBoolAnnotation } = getUtils(csn);
let [services, options] = initializeModel(model, _options, signal, error, warning, info);
const Edm = require('./edm.js')(options, signal, error, warning, info);
let [services, options] = initializeModel(csn, _options, signal, error, warning, info);
const Edm = require('./edm.js')(csn, options);

@@ -47,3 +55,2 @@

let rc = Object.create(null);
if(serviceName) {

@@ -95,16 +102,16 @@ let serviceCsn = services.filter(s => s.name == serviceName)[0];

*/
edmUtils.foreach(model.definitions,
edmUtils.foreach(csn.definitions,
a => edmUtils.isEntityOrView(a) && !a.abstract && a.name.startsWith(serviceName + '.'),
a => createEntityTypeAndSet(a, !(options.isV4() && edmUtils.isContainee(a)) && !a.$proxy)
createEntityTypeAndSet
);
// create unbound actions/functions
edmUtils.foreach(model.definitions, a => edmUtils.isActionOrFunction(a) && a.name.startsWith(serviceName + '.'),
edmUtils.foreach(csn.definitions, a => edmUtils.isActionOrFunction(a) && a.name.startsWith(serviceName + '.'),
(options.isV4()) ? createActionV4 : createActionV2);
// create the complex types
edmUtils.foreach(model.definitions, a => edmUtils.isStructuredType(a) && a.name.startsWith(serviceName + '.'), createComplexType);
edmUtils.foreach(csn.definitions, a => edmUtils.isStructuredType(a) && a.name.startsWith(serviceName + '.'), createComplexType);
if(options.isV4())
{
edmUtils.foreach(model.definitions,
edmUtils.foreach(csn.definitions,
artifact => edmUtils.isDerivedType(artifact) &&

@@ -149,3 +156,3 @@ !edmUtils.isAssociationOrComposition(artifact) &&

function createEntityTypeAndSet(entityCsn, createEntitySet=true)
function createEntityTypeAndSet(entityCsn)
{

@@ -156,3 +163,3 @@ // EntityType attributes are: Name, BaseType, Abstract, OpenType, HasStream

let EntityTypeName = entityCsn.name.replace(namespace, '');
let EntitySetName = (entityCsn.setName || entityCsn.name).replace(namespace, '');
let EntitySetName = (entityCsn.entitySetName || entityCsn.name).replace(namespace, '');

@@ -177,3 +184,3 @@ let [ properties, hasStream ] = createProperties(entityCsn);

if (createEntitySet)
if (entityCsn.hasEntitySet)
{

@@ -237,3 +244,3 @@ let entitySet = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);

{
let definition = model.definitions[rt];
let definition = csn.definitions[rt];
if(definition && definition.kind == 'entity' && !definition.abstract)

@@ -282,3 +289,3 @@ {

{
let defintion = model.definitions[rt];
let defintion = csn.definitions[rt];
if(defintion && edmUtils.isEntityOrView(defintion))

@@ -380,3 +387,4 @@ {

}
else if(!elementCsn['@cds.api.ignore'] || elementCsn['@cds.api.ignore'] !== true)
else if(!hasBoolAnnotation(elementCsn, '@cds.api.ignore', true) &&
!(hasBoolAnnotation(elementCsn, '@cds.odata.'+options.version+'.ignore', true) && options.betaMode))
{

@@ -403,3 +411,3 @@ // CDXCORE-CDXCORE-173

if ( options.isV2() && elementCsn['@Core.MediaType']
|| options.isV4() && isContainerAssoc && !elementCsn.key ) {
|| options.isV4() && isContainerAssoc /*&& !elementCsn.key*/ ) {
hasStream = true;

@@ -474,4 +482,4 @@ // CDXCORE-CDXCORE-177: remove elementCsn from elements dictionary

// differing set names (<T>Parameters => <T>, <T>Type => <T>Set)
let fromEntitySet = ( navigationProperty._csn._parent.setName || fromEntityType).replace(namespace, '');
let toEntitySet = (navigationProperty._targetCsn.setName || toEntityType).replace(namespace, '');
let fromEntitySet = ( navigationProperty._csn._parent.entitySetName || fromEntityType).replace(namespace, '');
let toEntitySet = (navigationProperty._targetCsn.entitySetName || toEntityType).replace(namespace, '');

@@ -598,3 +606,3 @@ // from and to roles must be distinguishable (in case of self association entity E { toE: association to E; ... })

{
let annoEdm = translate.csn2annotationEdm(model, serviceName, options);
let annoEdm = translate.csn2annotationEdm(csn, serviceName, options);
for(let i = 0; i < annoEdm.getSchemaCount(); i++)

@@ -601,0 +609,0 @@ {

'use strict'
const edmUtils = require('./edmUtils.js');
const alerts = require('../base/alerts');
const transformUtils = require('../transform/transformUtilsNew.js');
const { getUtils } = require('../model/csnUtils');
module.exports = function (options, signal, error, warning, info) {
module.exports = function (csn, options) {
const { error, info, signal } = alerts(csn);
const { flattenStructuredElement } = transformUtils.getTransformers(csn, '/');
const {
getFinalTypeDef,
isStructured,
} = getUtils(csn);
class Node

@@ -675,5 +686,18 @@ {

this.append(...properties);
let keys = properties.filter(c => c.isKey).map(c => c.Name);
if(keys.length > 0)
this.set( { _keys: new Key(v, keys) } );
let keys = properties.filter(c => c.isKey);// .map(c => c.Name);
let keyPaths = [];
// if a key has a complex type, transform the key into an array of paths
keys.forEach(k => {
// TODO: assert !v4?
// why is flattenStructuredElements not working on simple types?
if (isStructured(k._csn.type, csn) || (k._csn.type && getFinalTypeDef(k._csn.type, csn).elements)) {
let flatElems = flattenStructuredElement(k._csn, k.Name);
keyPaths.push(...Object.keys(flatElems));
} else {
keyPaths.push(k.Name);
}
});
if(keyPaths.length > 0)
this.set( { _keys: new Key(v, keyPaths) } );
}

@@ -896,9 +920,2 @@

}
if(csn.type === 'cds.Composition')
{
// TODO: to be specified via @sap.on.delete
this.append(new OnDelete(v, { Action: 'Cascade' } ) );
}
let partner = (csn._partnerCsn.length > 0) ? csn._partnerCsn[0] : csn._constraints._originAssocCsn;

@@ -909,2 +926,3 @@ if(partner && partner['@odata.navigable'] !== false) {

/*

@@ -919,4 +937,11 @@ 1) If this navigation property belongs to an EntityType for a parameterized entity

*/
if(csn['@odata.contained'] == true || csn.containsTarget)
if(csn['@odata.contained'] == true || csn.containsTarget) {
this.ContainsTarget = true;
}
if(this.ContainsTarget === undefined && csn.type === 'cds.Composition') {
// Delete is redundant in containment
// TODO: to be specified via @sap.on.delete
this.append(new OnDelete(v, { Action: 'Cascade' } ) );
}
}

@@ -923,0 +948,0 @@

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

const { getUtils, cloneCsn } = require('../model/csnUtils');
const alerts = require('../base/alerts');
const edmUtils = require('./edmUtils.js')

@@ -28,3 +30,3 @@ const {

function initializeModel(model, _options, signal, error, warning, info)
function initializeModel(csn, _options)
{

@@ -34,14 +36,18 @@ if(_options == undefined)

const { error, warning, info, signal } = alerts(csn);
const {
isStructured,
isAssocOrComposition,
} = getUtils(csn);
// make sure options are complete
let options = validateOptions(_options);
// are we working with structured OData or not
//const structuredOData = options.betaModeOdata && options.toOdata.version === 'v4';
// First attach names to all definitions in the model
forAll(model.definitions, (a, n) => {
// First attach names to all definitions in the model
forAll(csn.definitions, (a, n) => {
a.name = n;
});
foreach(model.definitions, isActionOrFunction, a => {
foreach(csn.definitions, isActionOrFunction, a => {
forAll(a.params, (p,n) => {

@@ -53,4 +59,4 @@ p.name = n;

// Fetch service objects
let services = Object.keys(model.definitions).reduce((services, artName) => {
let art = model.definitions[artName];
let services = Object.keys(csn.definitions).reduce((services, artName) => {
let art = csn.definitions[artName];
if(art.kind === 'service' && !art.abstract) {

@@ -64,19 +70,97 @@ services.push(art);

// Link association targets and spray @odata.contained over untagged compositions
foreach(model.definitions, isStructuredArtifact, linkAssociationTarget);
foreach(csn.definitions, isStructuredArtifact, linkAssociationTarget);
// Create data structures for containments
foreach(model.definitions, isStructuredArtifact, initializeContainments);
foreach(csn.definitions, isStructuredArtifact, initializeContainments);
// Initialize entities with parameters (add Parameter entity)
foreach(model.definitions, isParameterizedEntityOrView, initializeParameterizedEntityOrView);
foreach(csn.definitions, isParameterizedEntityOrView, initializeParameterizedEntityOrView);
// Initialize structures
foreach(model.definitions, isStructuredArtifact, initializeStructure);
foreach(csn.definitions, isStructuredArtifact, initializeStructure);
// Initialize associations
foreach(model.definitions, isStructuredArtifact, initializeAssociation);
foreach(csn.definitions, isStructuredArtifact, initializeAssociation);
// get constraints for associations
foreach(model.definitions, isStructuredArtifact, initializeConstraints);
foreach(csn.definitions, isStructuredArtifact, initializeConstraints);
// create association target proxies
foreach(model.definitions, isStructuredArtifact, redirectDanglingAssociationsToProxyTargets);
foreach(csn.definitions, isStructuredArtifact, redirectDanglingAssociationsToProxyTargets);
foreach(csn.definitions, isStructuredArtifact, determineEntitySet);
return [services, options];
// link association target to association and add @odata.contained to compositions in V4
function linkAssociationTarget(struct) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore)
return;
if(!element._target) {
let target = csn.definitions[element.target];
if(target) {
setProp(element, '_target', target);
}
else {
signal(error`Target ${element.target} cannot be found in the model`, [ 'definitions', struct.name, 'elements', element.name ]);
}
}
// TODO: Remove betaMode if finalized
// in V4 tag all compositions to be containments
if(options.betaMode &&
options.isV4() &&
isComposition(element) &&
element['@odata.contained'] === undefined) {
element['@odata.contained'] = true;
}
});
}
// Perform checks and add attributes for "contained" sub-entities:
// - A container is recognized by having an association/composition annotated with '@odata.contained'.
// - All targets of such associations ("containees") are marked with a property
// '_containerEntity: []', having as value an array of container names (i.e. of entities
// that have a '@odata.contained' association pointing to the containee). Note that this
// may be multiple entities, possibly including the container itself.
// - All associations in the containee pointing back to the container are marked with
// a boolean property '_isToContainerEntity : true', except if the association itself
// has the annotation '@odata.contained' (indicating the top-down link in a hierarchy).
// - Rewrite annotations that would be assigned to the containees entity set for the
// non-containment rendering. If containment rendering is active, the containee has no
// entity set. Instead try to rewrite the annotation in such a way that it is effective
// on the containment navigation property.
function initializeContainments(container) {
foreach(container.elements, isAssociationOrComposition, (element, elementName) => {
if (element._ignore)
return;
if(element['@odata.contained']) {
// Let the containee know its container
// (array because the contanee may contained more then once)
let containee = element._target;
if (!element._target._containerEntity) {
setProp(element._target, '_containerEntity', []);
}
// add container only once per containee
if (!containee._containerEntity.includes(container.name)) {
containee._containerEntity.push(container.name);
// Mark associations in the containee pointing to the container (i.e. to this entity)
for (let containeeElementName in containee.elements) {
let containeeElement = containee.elements[containeeElementName];
if (containeeElement._target && containeeElement._target.name) {
// If this is an association that points to a container (but is not by itself contained,
// which would indicate the top role in a hierarchy) mark it with '_isToContainer'
if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) {
setProp(containeeElement, '_isToContainer', true);
}
}
}
}
rewriteContainmentAnnotations(container, containee, elementName);
}
});
}
// Split an entity with parameters into two entity types with their entity sets,
// one named <name>Parameter and one named <name>Type. Parameter contains Type.
// Containment processing must take place before because it might be that this
// artifact with parameters is already contained. In such a case the existing
// containment chain must be propagated and reused. This requires that the
// containment data structures must be manually added here and rewriteContainmentAnnotations()
// must be called.
function initializeParameterizedEntityOrView(entityCsn, entityName) {

@@ -107,3 +191,3 @@

name: parameterEntityName,
setName: parameterEntitySetName,
entitySetName: parameterEntitySetName,
kind: 'entity',

@@ -142,6 +226,8 @@ isParamEntity:true,

model.definitions[parameterCsn.name] = parameterCsn;
csn.definitions[parameterCsn.name] = parameterCsn;
// modify the original parameter entity with backlink and new name
csn.definitions[originalEntityName] = entityCsn;
delete csn.definitions[entityCsn.name];
entityCsn.name = originalEntityName;
entityCsn.setName = originalEntitySetName;
setProp(entityCsn, 'entitySetName', originalEntitySetName);

@@ -154,9 +240,11 @@ // add backlink association

type: 'cds.Association',
_partnerCsn: [],
on: [ { ref: [ 'Parameters', 'Set' ] }, '=', { ref: [ '$self' ] } ]
};
setProp(entityCsn.elements[backlinkAssocName], '_partnerCsn', []);
setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
setProp(entityCsn.elements[backlinkAssocName], '_parameterCsn', []);
}
rewriteContainmentAnnotations(parameterCsn, entityCsn, parameterToOriginalAssocName);
}
// Initialize structured artifact (type or entity) 'struct' by doing the

@@ -226,3 +314,2 @@ // following:

initializeActions(struct.actions);
}

@@ -241,27 +328,2 @@

// link association target to association and add @odata.contained to compositions in V4
function linkAssociationTarget(struct) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore)
return;
if(!element._target) {
let target = model.definitions[element.target];
if(target) {
setProp(element, '_target', target);
}
else {
signal(error`Target ${element.target} cannot be found in the model`, [ 'definitions', struct.name, 'elements', element.name ]);
}
}
// TODO: Remove betaMode if finalized
// in V4 tag all compositions to be containments
if(options.betaMode &&
options.isV4() &&
isComposition(element) &&
element['@odata.contained'] === undefined) {
element['@odata.contained'] = true;
}
});
}
// Resolve the association type of 'element' in 'struct' by doing the following:

@@ -292,39 +354,2 @@ // - collect the foreign key elements for the target into attribute 'elements'

// Perform checks and add attributes for "contained" sub-entities:
// - A container is recognized by having an association/composition annotated with '@odata.contained'.
// - All targets of such associations ("containees") are marked with a property
// '_containerEntity: []', having as value an array of container names (i.e. of entities
// that have a '@odata.contained' association pointing to the containee). Note that this
// may be multiple entities, possibly including the container itself.
// - All associations in the containee pointing back to the container are marked with
// a boolean property '_isToContainerEntity : true', except if the association itself
// has the annotation '@odata.contained' (indicating the top-down link in a hierarchy).
function initializeContainments(container) {
foreach(container.elements, isAssociationOrComposition, element => {
if (element._ignore)
return;
if(element['@odata.contained']) {
// Let the containee know its container
// (array because the contanee may contained more then once)
if (!element._target._containerEntity) {
setProp(element._target, '_containerEntity', []);
}
// add container only once per containee
if (!element._target._containerEntity.includes(container.name)) {
element._target._containerEntity.push(container.name);
// Mark associations in the containee pointing to the container (i.e. to this entity)
for (let containeeElementName in element._target.elements) {
let containeeElement = element._target.elements[containeeElementName];
if (containeeElement._target && containeeElement._target.name) {
// If this is an association that points to a container (but is not by itself contained,
// which would indicate the top role in a hierarchy) mark it with '_isToContainer'
if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) {
setProp(containeeElement, '_isToContainer', true);
}
}
}
}
}
});
}

@@ -360,6 +385,2 @@ function initializeConstraints(struct) {

function redirectDanglingAssociationsToProxyTargets(struct) {
const {
isStructured,
isAssocOrComposition,
} = getUtils(model);

@@ -395,7 +416,7 @@ function whatsMyServiceName(n) {

if(myServiceName !== whatsMyServiceName(element._target.name)) {
if(options.betaModeProxy) {
if(options.isStructured() && options.isV4() && options.odataProxies) {
// search for eventually existing proxy
let proxy = element._target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0];
if(!proxy) {
let name = myServiceName + '.' + element._target.name.split('.').join('_') + '_Proxy_0';
let name = myServiceName + '.' + element._target.name.split('.').join('_') + '_proxy';
proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) };

@@ -429,6 +450,11 @@ let hasKeys = false;

}
signal(info`Created proxy entity type "${name}" for unexposed association target "${element._target.name}"`,
// wire up proxy
if(csn.definitions[name] !== undefined)
signal(error`Duplicate proxy entity type "${name}" for unexposed association target "${element._target.name}"`,
['definitions', struct.name, 'elements', element.name]);
// wire up proxy
model.definitions[name] = proxy;
else {
signal(info`Created proxy entity type "${name}" for unexposed association target "${element._target.name}"`,
['definitions', struct.name, 'elements', element.name]);
csn.definitions[name] = proxy;
}
element._target.$proxies.push(proxy);

@@ -442,3 +468,3 @@ }

element._ignore = true;
signal(info`Association is auto-excluded, as target "${element._target.name}" is outside any service`,
signal(warning`No OData navigation property generated for association "${element.name}", as target "${element._target.name}" is outside any service`,
['definitions', struct.name, 'elements', element.name]);

@@ -451,2 +477,51 @@ return;

}
function determineEntitySet(struct) {
// if this is an entity or a view, determine if an entity set is required or not
// 1) must not be a proxy and not a containee in V4
// No annos are rendered for non-existing EntitySet targets.
if(struct.hasEntitySet === undefined) {
let hasEntitySet = ['entity', 'view'].includes(struct.kind) && !(options.isV4() && edmUtils.isContainee(struct)) && !struct.$proxy;
setProp(struct, 'hasEntitySet', hasEntitySet);
}
}
// If containment in V4 is active, annotations that would be assigned to the containees
// entity set are not renderable anymore. In such a case try to reassign the annotations to
// the containment navigation property.
// Today only Capabilities.*Restrictions are known to be remapped as there exists a CDS
// short cut annotation @readonly that gets expanded and can be safely remapped.
function rewriteContainmentAnnotations(container, containee, assocName) {
// rectify Restrictions to NavigationRestrictions
if(options.isV4() && container['@Capabilities.NavigationRestrictions'] === undefined) {
let navRestr = {
RestrictedProperties: [
{
NavigationProperty: assocName
}
]
};
let hasRestrictions = false;
if(containee['@Capabilities.DeleteRestrictions.Deletable'] !== undefined) {
navRestr.RestrictedProperties[0].DeleteRestrictions =
{ 'Deletable': containee['@Capabilities.DeleteRestrictions.Deletable'] };
delete containee['@Capabilities.DeleteRestrictions.Deletable'];
hasRestrictions = true;
}
if(containee['@Capabilities.InsertRestrictions.Insertable'] !== undefined) {
navRestr.RestrictedProperties[0].InsertRestrictions =
{ 'Insertable': containee['@Capabilities.InsertRestrictions.Insertable'] };
delete containee['@Capabilities.InsertRestrictions.Insertable'];
hasRestrictions = true;
}
if(containee['@Capabilities.UpdateRestrictions.Updatable'] !== undefined) {
navRestr.RestrictedProperties[0].UpdateRestrictions =
{ 'Updatable': containee['@Capabilities.UpdateRestrictions.Updatable'] };
delete containee['@Capabilities.UpdateRestrictions.Updatable'];
hasRestrictions = true;
}
if(hasRestrictions)
container['@Capabilities.NavigationRestrictions'] = navRestr;
}
}
}

@@ -453,0 +528,0 @@

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

options.isV4 = function() { return this.v[1] == true; }
options.isStructured = function() { return options.toOdata.odataFormat === 'structured' }
options.isFlat = function() { return options.toOdata.odataFormat === 'flat' }
return options;

@@ -27,0 +31,0 @@ }

@@ -82,2 +82,4 @@ let W = require("./walker");

processLocations(model)
//returns the last element of an array

@@ -88,2 +90,34 @@ function getLastElement(a) {

//converts $location to xsn-location
function toXSNLocation(l) {
let r = {
filename: l.file,
start: {line:l.line, column: l.col},
end: {line:l.line, column: l.col},
$weak:true
}
return r;
}
// walks object in depth and replaces location with $location recursively by passing down the $location
function processLocations(NODE,location,path=[]) {
if(typeof NODE != "object") return;
W.forEach(NODE, (name,node) => {
if(!node) return;
if(name=="$location") return;
if(name=="location") return;
let newLocation = location;
if(node.$location) {
newLocation = toXSNLocation(node.$location)
}
processLocations(node,newLocation,path.concat(name))
if(newLocation && node.location)
node.location=newLocation;
if(node.$location)
delete node.$location;
})
}
return model;
} // function augment

@@ -93,2 +127,2 @@

augment
};
};

@@ -529,2 +529,5 @@ // contains query relevant augmentor functions

}
if(X.args) {
R.namedArgs = augmentViewArgs(X.args,iRefPath.concat("args"))
}
U.setAugmented(R)

@@ -531,0 +534,0 @@ return R;

@@ -14,3 +14,5 @@ function newUtils(model) {

throw Error("Double augmentation: " + path.join("/"));
node.location = this.newLocation(path, which)
let location = this.newLocation(path, which);
if(location !== undefined)
node.location = location;
},

@@ -24,2 +26,10 @@

_newFakeLocation() {
return {
filename: "<stdin>.csn",
start: {line:1, column:1},
$weak: true
}
},
_newLocation: function (path, which=this.WILO_FULL) {

@@ -33,3 +43,3 @@

if(locations===undefined)
throw Error("no locations");
return this._newFakeLocation();
if(path===undefined)

@@ -74,2 +84,4 @@ throw Error("no path");

setAugmented: function (node) {
if(node===undefined)
return;
// hidden property "augmented" which prevents recursive augmentation

@@ -76,0 +88,0 @@ if(node===null)

@@ -169,5 +169,5 @@ // Transform augmented CSN into compact "official" CSN

// Only intended to be used for tests, as no non-enumerable properties are kept.
function sortCsn( csn ) { // only returns enumerable properties
function sortCsn( csn, keepLocation = false ) { // only returns enumerable properties, except for $location, if requested
if (csn instanceof Array)
return csn.map( v => (!v || typeof v !== 'object' ? v : sortCsn(v) ) );
return csn.map( v => (!v || typeof v !== 'object' ? v : sortCsn(v, keepLocation) ) );
let r = {};

@@ -180,12 +180,14 @@ for (let n of Object.keys(csn).sort( compareProperties ) ) {

else if (csnDictionaries.includes(n)) {
r[n] = csnDictionary( val, n === 'definitions' );
r[n] = csnDictionary( val, n === 'definitions', keepLocation );
}
else {
r[n] = sortCsn(val);
r[n] = sortCsn(val, keepLocation);
}
}
if (keepLocation && typeof csn === 'object' && csn.$location)
setHidden(r, '$location', csn.$location);
return r;
}
function csnDictionary( csn, sort ) {
function csnDictionary( csn, sort, keepLocation = false ) {
if (!csn || csn instanceof Array) // null or strange CSN

@@ -195,3 +197,3 @@ return csn;

for (let n of (sort) ? Object.keys(csn).sort() : Object.keys(csn)) {
r[n] = sortCsn( csn[n] );
r[n] = sortCsn( csn[n], keepLocation );
}

@@ -460,4 +462,6 @@ return r;

else if (typeof node.scope === 'number') {
index = node.scope || length; // artifact refs in CDL have scope:0 in XSN
id = (node.scope ? path.slice(0,index) : path).map( id => id.id ).join('.');
index = node.scope || // artifact refs in CDL have scope:0 in XSN
terse === false && path.findIndex( i => i.where || i.namedArgs || i.cardinality) + 1 ||
length;
id = path.slice( 0, index ).map( id => id.id ).join('.');
}

@@ -703,6 +707,6 @@ else { // JSON or i18n input (without compiler links)

else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
return extra( addExplicitAs( artifactRef( node, null ), node.name ), node );
return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
}
else // if FROM ref is from USING, we might need an AS
return extra( addExplicitAs( artifactRef( node, null ), node.name, function(id) {
return extra( addExplicitAs( artifactRef( node, false ), node.name, function(id) {
let name = node._artifact.name.absolute;

@@ -774,3 +778,3 @@ let dot = name.lastIndexOf('.');

function setHidden( obj, prop, value ) {
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
Object.defineProperty( obj, prop, { value, configurable: true, writable: true, enumerable: false } );
}

@@ -777,0 +781,0 @@

@@ -9,2 +9,3 @@ let W = require("../walker")

const $classes = "$classes";
const magicElements=["$location"]; // accepted on all levels

@@ -126,13 +127,15 @@ function reportError(ctx,path,node,msg) {

if(element===$classes) continue;
if(ctx.options && ctx.options.fuzzyCsnError)
reportError(ctx,epath,node,"{"+className+"}: Unknown element '"+element+"'");
else
reportInfo(ctx,epath,node,"{"+className+"}: Unknown element '"+element+"'");
if(!(ctx.options && ctx.options.skipFuzzyDelete)) {
// prepare, copy and delete
if(!Object.getOwnPropertyNames(node).includes($extra)) {
Object.defineProperty(node,$extra,{configurable:true,enumerable:true,value:{}})
if(!magicElements.includes(element)) {
if(ctx.options && ctx.options.fuzzyCsnError)
reportError(ctx,epath,node,"{"+className+"}: Unknown element '"+element+"'");
else
reportInfo(ctx,epath,node,"{"+className+"}: Unknown element '"+element+"'");
if(!(ctx.options && ctx.options.skipFuzzyDelete)) {
// prepare, copy and delete
if(!Object.getOwnPropertyNames(node).includes($extra)) {
Object.defineProperty(node,$extra,{configurable:true,enumerable:true,value:{}})
}
node[$extra][element]=node[element];
delete node[element];
}
node[$extra][element]=node[element];
delete node[element];
}

@@ -139,0 +142,0 @@ }

@@ -44,3 +44,3 @@ // Main entry point for the Research Vanilla CDS Compiler

var { CompilationError, messageString, handleMessages, hasErrors, getMessageFunction }
var { CompilationError, messageString, messageStringMultiline, messageContext, handleMessages, hasErrors, getMessageFunction }
= require('./base/messages');

@@ -78,2 +78,9 @@ var { promiseAllDoNotRejectImmediately } = require('./base/node-helpers');

else if (['.json', '.csn'].includes(ext)) {
if(options.stdJsonParser) {
let csn = JSON.parse(source);
let augmentor3 = require("./json/augmentor3");
let xsn = augmentor3.augment(csn); // in-place
xsn.$frontend = 'json';
return xsn;
}
let parseCSNfromJson = require("./json/from-json");

@@ -99,4 +106,10 @@ let model = parseCSNfromJson( source, filename, options );

function collectSources( filenames, dir ) {
return compile( filenames, dir, { collectSources: true } );
// Collect all sources for the given files in the given base directory.
// This means that all dependency sources (e.g. `using` statements) are
// loaded and stored.
// This function returns a Promise (i.e. calls compile() which returns
// a promise). The fulfullment value is an object with all sources and
// their dependencies.
function collectSources( filenames, basedir ) {
return compile( filenames, basedir, { collectSources: true } );
}

@@ -193,9 +206,13 @@

else {
a.fileContentDict[filename] = source;
let ast = parse( source, rel, parseOptions );
a.sources[filename] = ast;
ast.filename = rel;
ast.dirname = path.dirname( filename );
assertConsistency( ast, parseOptions );
fulfill( ast );
try {
a.fileContentDict[filename] = source;
let ast = parse( source, rel, parseOptions );
a.sources[filename] = ast;
ast.filename = rel;
ast.dirname = path.dirname( filename );
assertConsistency( ast, parseOptions );
fulfill( ast );
} catch(e){
reject(e);
}
}

@@ -440,4 +457,8 @@ });

}
// else
// sources[filename] = source;
else { // source is a CSN object
let augmentor3 = require("./json/augmentor3.js");
let xsn = augmentor3.augment(source); // in-place
xsn.$frontend = 'json';
sources[filename] = xsn;
}
}

@@ -569,2 +590,4 @@

messageString,
messageStringMultiline,
messageContext,
InvocationError,

@@ -571,0 +594,0 @@ hasErrors,

// CSN functionality for resolving references
// The functions in this module expect a CSN with valid references. If that is
// not the case, it simply throws an error without any claim for the error
// message to be user-friendly. CSN processors can provide user-friendly error
// messages by calling the Core Compiler in that case. For details, see
// The functions in this module expect a well-formed CSN with valid references.
// If that is not the case, it simply throws an error (which might even be a
// plain TypeError) without any claim for the error message to be
// user-friendly. CSN processors can provide user-friendly error messages by
// calling the Core Compiler in that case. For details, see
// internalDoc/CoreCompiler.md#use-of-the-core-compiler-for-csn-processors.

@@ -35,2 +36,4 @@

const views = Object.create(null); // cache for views - OK to add it to CSN?
let refCsnPath = [];
let refLinks = null;
return { effectiveType, artifactRef, inspectRef, queryOrMain };

@@ -97,7 +100,19 @@

function inspectRef( csnPath, whereEntity ) {
const { obj, parent, query, scope } = analyseCsnPath( csnPath, csn );
function whereEntity( csnPath, refCsnPathIndex ) {
if (refCsnPath.length !== refCsnPathIndex ||
refCsnPath.some( ( prop, idx ) => prop !== csnPath[idx] )) {
// safety: ref-where in ref-where -> first store in locals
const path = csnPath.slice( 0, refCsnPathIndex );
const links = inspectRef( path ).links;
refCsnPath = path;
refLinks = links;
}
return refLinks[ csnPath[ refCsnPathIndex + 1 ] ].env;
}
function inspectRef( csnPath ) {
const { obj, parent, query, scope, refCsnPathIndex } = analyseCsnPath( csnPath, csn );
const name = csnPath[1];
const main = csn.definitions[ name ];
const queries = views[ name ] || main.query && allQueries( name, main.query );
const queries = views[ name ] || main.query && allQueries( name, main );

@@ -109,3 +124,3 @@ const path = (typeof obj === 'string') ? [ obj ] : obj.ref;

// 1,2:
// 1,2: with 'param' or 'global' property, in `keys`
if (obj.param)

@@ -117,3 +132,3 @@ return expandRefPath( path, main.params[ head ], 'param' );

return expandRefPath( path, csn.definitions[ parent.target ].elements[ head ], 'keys' );
// 3:
// 3: $magic
if (head.charAt() === '$') {

@@ -128,12 +143,9 @@ if (head === '$self' || head === '$projection') {

}
// 4 (where inside ref - art for environment must be provided as computing
// 4: where inside ref - art for environment must be provided as computing
// it here would be too expensive)
if (scope === 'ref-where'){
if(!whereEntity){
// Caller SHOULD catch this and then try again with a correct whereEntity
throw new Error("Scope '" + scope + "' but no entity was provided.");
}
return expandRefPath( path, whereEntity.elements[ head ], scope );
if (scope === 'ref-where') {
const { elements } = whereEntity( csnPath, refCsnPathIndex );
return expandRefPath( path, elements[ head ], scope );
}
// 5,6,7:
// 5,6,7: outside queries, in queries where inferred elements are referred to
if (!query)

@@ -153,7 +165,7 @@ return expandRefPath( path, (parent || main).elements[ head ] );

else if (typeof obj.$env === 'string') {
const source = queryOrMain( select._sources[ obj.$env ], main );
const source = select._sources[ obj.$env ];
return expandRefPath( path, source.elements[ head ], 'source' );
}
// 8:
// 8: try to search in MIXIN section (not in ON of JOINs)
if (scope !== 'from-on' && select.mixin) {

@@ -164,3 +176,3 @@ const art = select.mixin[ head ];

}
// 9:
// 9: try to search for table aliases (partially in ON of JOINs)
if (path.length > 1 && (select.$alias || scope !== 'from-on')) {

@@ -171,3 +183,3 @@ const art = select._sources[ head ];

}
// 10:
// 10: search in elements of source entity
// TODO: do not do this if current query has a parent query !!!

@@ -178,3 +190,3 @@ if (scope === 'orderBy') {

if (typeof select.$alias === 'string') { // with unique source
const source = queryOrMain( select._sources[ select.$alias ], main );
const source = select._sources[ select.$alias ];
return expandRefPath( path, source.elements[ head ], 'source' );

@@ -201,5 +213,5 @@ }

function allQueries( name, topQuery ) {
function allQueries( name, main ) {
const all = [];
traverseQuery( topQuery, null, function memorize( query, select ) {
traverseQuery( main.query, null, function memorize( query, select ) {
if (query.ref) { // ref in from

@@ -212,3 +224,3 @@ const as = query.as || implicitAs( query.ref );

const as = query.as;
select._sources[ as ] = query;
select._sources[ as ] = queryOrMain( query, main );
setLink( select, '$alias',

@@ -292,6 +304,6 @@ (select.$alias != null) ? typeof select.$alias === 'string' : as );

let isName = false;
let refCsnPathIndex = 0;
for (const prop of csnPath) {
// if (isName || Array.isArray( obj )) { // array item, name/index of artifact/member, (named) argument
if (isName || typeof prop !== 'string') { // array item, name/index of artifact/member, (named) argument
csnPath.forEach( function loop( prop, index ) {
if (isName || Array.isArray( obj )) { // array item, name/index of artifact/member, (named) argument
if (typeof isName === 'string') {

@@ -318,2 +330,3 @@ parent = art;

scope = 'ref-where';
refCsnPathIndex = index - 2;
}

@@ -330,5 +343,5 @@ else if (prop === 'on' && scope === 'from') {

obj = obj[ prop ];
}
} );
// console.log( 'CPATH:', csnPath, scope, obj );
return { obj, parent, query, scope };
return { obj, parent, query, scope, refCsnPathIndex };
}

@@ -335,0 +348,0 @@

@@ -383,2 +383,4 @@ 'use strict'

return (getFinalBaseType(type.type, path, cycleCheck));
else if (type.items)
return getFinalBaseType(type.items, path, cycleCheck);
else

@@ -431,2 +433,4 @@ // TODO: this happens if we don't have a type, e.g. an expression in a select list

}
if (node.technicalConfig && !resultNode.technicalConfig)
setProp(resultNode, 'technicalConfig', node.technicalConfig);

@@ -457,2 +461,6 @@ if(node.$env && !resultNode.$env){

if(construct._ignore || obj._ignore){
return;
}
if(construct.returns){

@@ -459,0 +467,0 @@ obj_path.push("returns");

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

function standard( parent, prop, node, whereEntity ) {
function standard( parent, prop, node ) {
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))

@@ -62,3 +62,3 @@ return;

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

@@ -68,3 +68,3 @@ else {

const trans = transformers[name] || standard;
trans( node, name, node[name], whereEntity );
trans( node, name, node[name] );
}

@@ -104,6 +104,6 @@ }

function pathRef( node, prop, path, whereEntity ) {
function pathRef( node, prop, path ) {
const { links, art, scope } = (() => {
try {
return inspectRef( csnPath, whereEntity );
return inspectRef( csnPath );
}

@@ -127,3 +127,3 @@ catch (e) {

if (s.where)
standard( s, 'where', s.where, links[i].env );
standard( s, 'where', s.where );
csnPath.pop();

@@ -130,0 +130,0 @@ }

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

function revealInternalProperties( model ) {
function revealInternalProperties( model, name ) {
var unique_id = 0;

@@ -42,2 +42,3 @@

$navigation: dictionary,
$keysNavigation: dictionary,
$combined: artifactDictionary,

@@ -81,3 +82,3 @@ $dictOrderBy: artifactDictionary,

}
return reveal( model );
return reveal( name && name !== '+' ? { definitions: model.definitions[name] } : model );

@@ -84,0 +85,0 @@ function artifactIdentifier( node, parent ) {

@@ -14,2 +14,4 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper');

.option(' --show-message-id')
.option(' --show-message-context')
.option(' --color <mode>', ['auto', 'always', 'never'])
.option('-o, --out <dir>')

@@ -22,8 +24,9 @@ .option(' --lint-mode')

.option('-E, --enrich-csn')
.option('-R, --raw-output')
.option('-R, --raw-output <name>')
.option(' --internal-msg')
.option(' --beta-mode')
.option(' --new-transformers')
.option(' --old-transformers')
.option(' --new-csn')
.option(' --old-csn')
.option(' --std-json-parser')
.option(' --long-autoexposed')

@@ -55,2 +58,8 @@ .option(' --hana-flavor')

--show-message-id Show message ID in error, warning and info messages
--show-message-context Print messages as human readable text similar to Rust's compiler
(with code excerpt)
--color <mode> Use colors for warnings. Modes are:
auto: (default) Detect color support of the tty.
always:
never:
-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>

@@ -73,8 +82,10 @@ --lint-mode Generate nothing, just produce messages if any (for use by editors)

-E, --enrich-csn Show non-enumerable CSN properties and locations of references
-R, --raw-output Write raw augmented CSN and error output to <stdout>, long!
-R, --raw-output <name> Write XSN for definition "name" and error output to <stdout>,
with name = "+", write complete XSN, long!
--internal-msg Write raw messages with call stack to <stdout>/<stderr>
--beta-mode Enable unsupported, incomplete (beta) features
--new-transformers Use the new transformers that work on CSN instead of XSN
--old-transformers Use the old transformers that work on XSN instead of CSN
--new-csn Produce CSN version 1.0 format (default)
--old-csn Produce CSN version 0.1 format (deprecated, overrides --new-csn)
--std-json-parser Use standard JSON parser for CSN parsing
--hana-flavor Compile with backward compatibility for HANA CDS (incomplete)

@@ -138,2 +149,3 @@ --parse-only Stop compilation after parsing and write result to <stdout>

.option('-c, --csn')
.option('-f, --odata-format <format>', ['flat', 'structured'])
.option('-n, --names <style>', ['plain', 'quoted', 'hdbcds'])

@@ -155,2 +167,6 @@ .help(`

-c, --csn Generate "odata_csn.json" with ODATA-preprocessed model
-f, --odata-format <format> Set the format of the identifier rendering
(ignored by '--old-transformers')
flat : (default) Flat type and property names
structured : (V4 only) Render structured metadata
-n, --names <style> Annotate artifacts and elements with "@cds.persistence.name", which is

@@ -262,2 +278,3 @@ the corresponding database name (see "--names" for "toHana or "toSql")

.option('-f, --flavor <flavor>', ['client', 'gensrc'])
.option('-a, --associations <proc>', ['assocs', 'joins'])
.help(`

@@ -276,2 +293,5 @@ Usage: cdsc toCsn [options] <file...>

backends
-a, --associations <proc> Processing of associations:
assocs : (default) Keep associations in HANA CDS as far as possible
joins : Transform associations to joins
`);

@@ -278,0 +298,0 @@

@@ -10,3 +10,2 @@ /**

const walker = require("../json/walker")
const { transformLocation } = require('./renderUtil');

@@ -51,5 +50,5 @@ // database name - uppercase if not quoted

*/
addArtifact( name, location ){
addArtifact( name, location, modelName ){
const dbname = asDBName(name);
this.currentArtifact = { name, location, elements: {} };
this.currentArtifact = { name, location, elements: {}, modelName };
if(!this.seenArtifacts[dbname])

@@ -70,3 +69,3 @@ this.seenArtifacts[dbname] = [this.currentArtifact];

*/
addElement(name,location){
addElement(name,location, modelName){
if(!this.currentArtifact.elements)

@@ -76,3 +75,3 @@ return;

let currentElements = this.currentArtifact.elements;
const element = {name,location};
const element = {name,location, modelName};
if(!currentElements[dbname])

@@ -93,3 +92,3 @@ currentElements[dbname] = [element];

artifacts.forEach(artifact => { // report all colliding artifacts
signal(error`Duplicated artifact '${artifactName}', origin: '${artifact.name}'`, transformLocation(artifact.location));
signal(error`Duplicated artifact '${artifactName}', origin: '${artifact.name}'`, ['definitions', artifact.modelName] );
});

@@ -101,3 +100,3 @@ }

elements.forEach(element => { // report all colliding elements
signal(error`Duplicated element '${element.name}' in artifact '${artifact.name}'`, transformLocation(element.location));
signal(error`Duplicated element '${element.name}' in artifact '${artifact.name}'`, ['definitions', artifact.modelName,'elements', element.modelName]);
})

@@ -104,0 +103,0 @@ }

@@ -33,24 +33,5 @@ // Common render functions for toCdl.js and toSql.js

/**
* Get the $location from an object and make it look like the XSN location - delete - not necessary
*
* @param {any} location
*/
function transformLocation(location){
return {
start: {
line: location.line,
column: location.col
},
end: {
line: location.line,
column: location.col
},
filename: location.file
}
}
module.exports = {
renderFunc,
transformLocation
renderFunc
}

@@ -6,9 +6,22 @@ "use strict";

getLastPartOfRef, getParentNamesOf } = require('../model/modelUtils');
const { compactModel } = require('../json/to-csn');
const { compactModel, sortCsn } = require('../json/to-csn');
const keywords = require('../base/keywords');
const version = require('../../package.json').version;
const alerts = require('../base/alerts');
const { transformLocation, renderFunc } = require('./renderUtil'); // TODO: transformLocation should not be necessary
const { renderFunc } = require('./renderUtil');
const DuplicateChecker = require('./DuplicateChecker');
const { cloneCsn } = require('../model/csnUtils');
const { setProp } = require('../base/model');
function toCdsSource(model, options){
options = mergeOptions(model.options, options);
let csn = compactModel(model);
if (options.testMode)
csn = sortCsn(csn, true);
if(model.messages)
setProp(csn, "messages", model.messages);
return toCdsSourceCsn(csn, options);
}
// Render the CSN model 'model' to CDS source text. One source is created per

@@ -23,4 +36,3 @@ // top-level artifact. Return a dictionary of top-level artifacts

// FIXME: This comment no longer tells the whole truth
function toCdsSource(model, options) {
function toCdsSourceCsn(model, options) {
// Merge options (arguments first, then model options)

@@ -30,5 +42,5 @@ options = mergeOptions(model.options, options);

let hdbcdsNames = options.forHana && options.forHana.names == 'hdbcds';
// Skip compactModel if already using CSN
const csn = (options.newTransformers) ? model : compactModel(model);
const csn = cloneCsn(model);

@@ -273,3 +285,3 @@ const { signal, warning, error } = alerts(csn);

let normalizedArtifactName = renderArtifactName(artifactName, env);
globalDuplicateChecker && globalDuplicateChecker.addArtifact(normalizedArtifactName, art && art.$location);
globalDuplicateChecker && globalDuplicateChecker.addArtifact(normalizedArtifactName, art && art.$location, artifactName);
result += env.indent + (art.abstract ? 'abstract ' : '') + 'entity ' + normalizedArtifactName;

@@ -284,3 +296,3 @@ let parameters = Object.keys(art.params || []).map(name => renderParameter(name, art.params[name], childEnv)).join(',\n');

let duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
duplicateChecker.addArtifact(artifactName, art && art.$location)
duplicateChecker.addArtifact(artifactName, art && art.$location, artifactName)
for (let name in art.elements) {

@@ -384,3 +396,3 @@ result += renderElement(name, art.elements[name], childEnv, duplicateChecker);

let result = renderAnnotationAssignments(elm, env);
duplicateChecker && elm && duplicateChecker.addElement(quoteOrUppercaseId(elementName), elm && elm.$location);
duplicateChecker && elm && duplicateChecker.addElement(quoteOrUppercaseId(elementName), elm && elm.$location, elementName);
result += env.indent + (elm.virtual ? 'virtual ' : '')

@@ -563,3 +575,3 @@ + (elm.key ? 'key ' : '')

// Return the resulting source string (no trailing LF).
function renderViewColumn(col, env) {
function renderViewColumn(col, env, path) {
// Ignore if toHana says so

@@ -580,3 +592,3 @@ // FIXME: This probably needs to change ...

if(col.key && env.skipKeys){
signal(error`KEY must only be added in the first query of a UNION`, transformLocation(col.$location));
signal(error`KEY must only be added in the first query of a UNION`, path);
}

@@ -633,3 +645,3 @@ const key = (!env.skipKeys && (col.key || (options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].key)) ? 'key ' : '');

env._artifact = art;
result += renderQuery(art.query, true, syntax, env);
result += renderQuery(art.query, true, syntax, env, ['definitions',artifactName,'query']);
result += ';\n';

@@ -645,3 +657,3 @@ result += renderQueryElementAnnotations(artifactName, art, env);

// or 'entity')
function renderQuery(query, isLeadingQuery, syntax, env) {
function renderQuery(query, isLeadingQuery, syntax, env, path=[]) {
let result = '';

@@ -651,3 +663,3 @@ // Set operator, like UNION, INTERSECT, ...

// First arg may be leading query
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, 'view', env)}`
result += `(${renderQuery(query.SET.args[0], isLeadingQuery, 'view', env, path.concat(['SET','args',0]))}`
// FIXME: Clarify if set operators can be n-ary (assuming binary here)

@@ -661,3 +673,3 @@ if (query.SET.op) {

for(let i = 1; i < query.SET.args.length; i++){
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, 'view', env)}`;
result += `\n${env.indent}${query.SET.op}${query.SET.all ? ' all' : ''} ${renderQuery(query.SET.args[i], false, 'view', env, path.concat(['SET','args',i]))}`;
}

@@ -707,3 +719,3 @@ // Reset afterwards

result += ' {\n' +
(select.columns||['*']).map(col => renderViewColumn(col, childEnv))
(select.columns||['*']).map((col, index) => renderViewColumn(col, childEnv, path.concat('SELECT','columns',index)))
.filter(s => s != '')

@@ -1492,2 +1504,2 @@ .join(',\n') + '\n';

module.exports = { toCdsSource };
module.exports = { toCdsSource, toCdsSourceCsn };

@@ -9,3 +9,3 @@

const version = require('../../package.json').version;
const { renderFunc } = require('./renderUtil'); // TODO: transformLocation should not be necessary
const { renderFunc } = require('./renderUtil');
const DuplicateChecker = require("./DuplicateChecker");

@@ -213,3 +213,3 @@

let tableName = quoteSqlId(absoluteCdsName(artifactName));
duplicateChecker.addArtifact(tableName, art && art.$location)
duplicateChecker.addArtifact(tableName, art && art.$location, artifactName)
result += 'TABLE ' + tableName;

@@ -290,3 +290,3 @@ result += ' (\n';

let quotedElementName = quoteSqlId(elementName);
duplicateChecker.addElement(quotedElementName, elm && elm.$location);
duplicateChecker.addElement(quotedElementName, elm && elm.$location, elementName);
let result = env.indent + quotedElementName + ' '

@@ -553,3 +553,3 @@ + renderTypeReference(artifactName, elementName, elm)

let viewName = quoteSqlId(absoluteCdsName(artifactName));
duplicateChecker.addArtifact(viewName, art && art.$location)
duplicateChecker.addArtifact(viewName, art && art.$location, artifactName)
let result = 'VIEW ' + viewName;

@@ -686,3 +686,3 @@ result += renderParameterDefinitions(artifactName, art.params);

let typeName = quoteSqlId(absoluteCdsName(artifactName));
duplicateChecker.addArtifact(typeName, art && art.$location)
duplicateChecker.addArtifact(typeName, art && art.$location, artifactName)
let result = 'TYPE ' + quoteSqlId(absoluteCdsName(artifactName)) + ' AS TABLE (\n';

@@ -893,3 +893,3 @@ let childEnv = increaseIndent(env);

if(options.forHana.dialect === 'sqlite') {
return "current_time";
return "current_timestamp";
}

@@ -896,0 +896,0 @@ else if(options.forHana.dialect === 'hana') {

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

function transform4odata(inputModel, options) {
const { error, warning, signal } = alerts(inputModel);
const { error, warning, info, signal } = alerts(inputModel);
let model;

@@ -165,2 +165,5 @@

// key fields in types we have already processed.
// Check that each service has max one draft root
// This must be done before draft processing, since all composition targets
// are annotated with @odata.draft.enabled in that step
forEachDefinition(model, (artifact) => {

@@ -196,2 +199,12 @@ forEachMemberRecursively(artifact, (member) => {

});
if (artifact._service && (artifact.kind == 'entity' || artifact.kind == 'view') && hasBoolAnnotation(artifact, '@odata.draft.enabled')) {
let location = artifact['@odata.draft.enabled'].name && artifact['@odata.draft.enabled'].name.location;
if(artifact._service.$draftCount)
artifact._service.$draftCount++;
else
setProp(artifact._service, '$draftCount', 1);
if(artifact._service.$draftCount > 1)
signal(info`Service "${artifact._service.name.absolute}" should not have more then one draft root artifact`, location);
}
});

@@ -209,4 +222,4 @@

// Ignore if not part of a service
let location = artifact['@odata.draft.enabled'].name && artifact['@odata.draft.enabled'].name.location;
if (!artifact._service) {
let location = artifact['@odata.draft.enabled'].name && artifact['@odata.draft.enabled'].name.location;
signal(warning`Ignoring annotation "@odata.draft.enabled" - artifact "${artifact.name.absolute}" is not part of a service`, location);

@@ -401,2 +414,7 @@ }

// 'rootArtifact' is the root artifact where composition traversal started.
// Constraints
// Draft Root: Exactly one PK of type UUID
// Draft Node: One PK of type UUID + 0..1 PK of another type
// Draft Node: Must not be reachable from multiple draft roots
function generateDraftForOdata(artifact, rootArtifact, visitedArtifacts) {

@@ -414,19 +432,35 @@ // Sanity check

// FIXME: Current restriction: Must only have exactly one key, which is of type UUID
let keyNames = Object.keys(artifact.elements).filter(elemName => {
return artifact.elements[elemName].key && artifact.elements[elemName].key.val;
});
if (keyNames.length != 1) {
signal(warning`"${artifact.name.absolute}": Ignoring annotation "@odata.draft.enabled" - currently only supported for artifacts with exactly one key of type "UUID"`, artifact.location);
return;
if(artifact === rootArtifact) {
// draft root
if(keyNames.length != 1) {
signal(info`"${artifact.name.absolute}": "@odata.draft.enabled" - Draft root should have exactly one key element`, artifact.location);
} else {
let keyElem = artifact.elements[keyNames[0]];
if (keyElem._finalType.name.absolute != 'cds.UUID' && keyElem._finalType.name.$renamed != 'cds.UUID') {
signal(info`"${artifact.name.absolute}": "@odata.draft.enabled" - Draft root key element should be of type "cds.UUID"`, keyElem.location);
}
}
} else {
// draft node
if(keyNames.length < 1) {
signal(info`"${artifact.name.absolute}": "@odata.draft.enabled" - Draft node should have at least one key element`, artifact.location);
} else if(keyNames.length > 2) {
signal(info`"${artifact.name.absolute}": "@odata.draft.enabled" - Draft node should have not more then two key elements`, artifact.location);
} else {
let uuidCount = 0;
keyNames.forEach(kn => {
let keyElem = artifact.elements[kn];
if (keyElem._finalType.name.absolute == 'cds.UUID' || keyElem._finalType.name.$renamed == 'cds.UUID') {
uuidCount++;
}
});
if(uuidCount != 1) {
signal(info`"${artifact.name.absolute}": "@odata.draft.enabled" - Draft node should have one key element of type "cds.UUID"`, artifact.location);
}
}
}
let keyElem = artifact.elements[keyNames[0]];
// Sanity check
if (!keyElem._finalType) {
throw new Error('Expecting artifact to have final type: ' + JSON.stringify(keyElem));
}
if (keyElem._finalType.name.absolute != 'cds.UUID' && keyElem._finalType.name.$renamed != 'cds.UUID') {
signal(warning`"${artifact.name.absolute}": Ignoring annotation "@odata.draft.enabled" - currently only supported for key of type "UUID"`, keyElem.location);
return;
}

@@ -457,2 +491,3 @@ // Generate the DraftAdministrativeData projection into the service, unless there is already one

let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true);
addBoolAnnotationTo('@UI.Hidden', true, isActiveEntity);
addElement(isActiveEntity, artifact);

@@ -462,2 +497,3 @@

let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false);
addBoolAnnotationTo('@UI.Hidden', true, hasActiveEntity);
addElement(hasActiveEntity, artifact);

@@ -467,2 +503,3 @@

let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false);
addBoolAnnotationTo('@UI.Hidden', true, hasDraftEntity);
addElement(hasDraftEntity, artifact);

@@ -480,2 +517,3 @@

addBoolAnnotationTo('@odata.contained', true, draftAdministrativeData);
addBoolAnnotationTo('@UI.Hidden', true, draftAdministrativeData);
addElement(draftAdministrativeData, artifact);

@@ -488,2 +526,3 @@ // Note that we need to do the ODATA transformation steps for managed associations

addBoolAnnotationTo('@odata.contained', true, foreignKeyElement);
addBoolAnnotationTo('@UI.Hidden', true, foreignKeyElement);
}

@@ -490,0 +529,0 @@ // SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)

'use strict';
const alerts = require('../base/alerts');
const { CompilationError, hasErrors } = require('../base/messages');
const { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const { setProp } = require('../base/model');

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

// (3.2) Flatten on-conditions in unmanaged associations
// (3.3) Check that each service has max one draft root
// Step (3.1) is in the third walk of the model

@@ -60,5 +61,5 @@ //

let csn = cloneCsn(inputModel);
const { error, warning, signal } = alerts(csn);
const { error, warning, info, signal } = alerts(csn);
// the new transformer works only with new CSN

@@ -90,2 +91,3 @@ if (!isNewCSN(inputModel, options)) {

getCsnDef,
getFinalBaseType,
getFinalType,

@@ -95,12 +97,11 @@ getFinalTypeDef,

getServiceName,
isBuiltinType,
hasBoolAnnotation,
isAssocOrComposition,
isAssociation,
isManagedAssociationElement,
isStructured,
hasBoolAnnotation,
isManagedAssociationElement,
} = getUtils(csn);
// are we working with structured OData or not
const structuredOData = options.betaModeOdata && options.toOdata.version === 'v4';
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4';

@@ -137,12 +138,73 @@ // exposed struct types (see function exposeStructType)

// First walk:
// (1.1) Flatten structs
// (1.2) Expose (named or anonymous) structured types used in structured types
// (1.3) Unravel derived type chains,
// (1.4) Mark fields with @odata.on.insert/update as @Core.Computed
// (1.5) Resolve annotation shorthands,
// (1.6) Check @cds... annotations
// (1.1) Unravel derived type chains to final one for elements, actions, action parameters (propagating annotations)
forEachDefinition(csn, (def, defName) => {
forEachMemberRecursively(def, (member) => {
if (!member.elements && !member.items) // structured does not have final base type and arrays are covered on the next line
toFinalBaseType(member);
toFinalBaseType(member.items);
toFinalBaseType(member.returns);
toFinalBaseType(member.returns && member.returns.items);
}, ['definitions', defName]);
});
// (1.1) Unravel derived type chains for types and annotations (propagating annotations)
forEachDefinition(csn, (def) => {
if (def.kind != 'entity') {
if (!isStructured(getFinalTypeDef(def)))
toFinalBaseType(def);
toFinalBaseType(def.items);
toFinalBaseType(def.returns);
toFinalBaseType(def.returns && def.returns.items);
}
// If the definition('def' variable) is a type definition and the assigned type of this very same definition('def' variable)
// is structured type, e.g.:
//
// type Struct1 {
// a : Integer;
// b : Integer;
// };
// type Struct2: Struct1;
// after compilation the csn looks like this:
// ...
// "S.Struct1": {
// "kind": "type",
// "elements": {
// "a": { "type": "cds.Integer" },
// "b": { "type": "cds.Integer" }
// } },
// "S.Struct2": {
// "kind": "type",
// "type": "S.Struct1",
// "elements": {
// "a": { "type": "cds.Integer" },
// "b": { "type": "cds.Integer" }
// } } ...
//
// "S.Struct2" should looks just like "S.Struct1" => the "type": "S.Struct1" property has to be removed
if (def.kind === 'type' && def.type && isStructured(getFinalBaseType(def)) && !def.type.ref) {
// elements are already there -> do not show the type
delete def.type;
}
});
// (1.2) Mark fields with @odata.on.insert/update as @Core.Computed
// (1.3) Resolve annotation shorthands,
// (1.4) Check @cds... annotations
// (1.5) Flatten structs
// (1.6) Expose (named or anonymous) structured types used in structured types
// TODO: only for V2 or via special option???
forEachDefinition(csn, (def, defName, propertyName, path) => {
// (1.1) Flatten structs - for entities and views only (might result in new elements)
forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
// (1.2) Mark fields with @odata.on.insert/update as @Core.Computed
annotateCoreComputed(member);
// (1.3) Resolve annotation shorthands for elements, actions, action parameters
renameShorthandAnnotations(member);
// (1.4) check annotations
checkAnnotations(member, path);
}, ['definitions', defName]);
// (1.5) Flatten structs - for entities and views only (might result in new elements)
if (def.kind === 'entity' || def.kind === 'view') {

@@ -172,4 +234,4 @@ for (let elemName in def.elements) {

}
// (1.2) Expose (named or anonymous) structured types used in structured types
else if (isArtifactInSomeService(defName, services) && def.kind == 'type') {
// (1.6) Expose (named or anonymous) structured types used in structured types
else if (isArtifactInSomeService(defName, services) && def.kind === 'type') {
for (let elemName in def.elements) {

@@ -182,40 +244,2 @@ let elem = def.elements[elemName];

forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
// (1.3) Unravel derived type chains for elements, actions, action parameters (propagating annotations)
toFinalBaseType(member);
toFinalBaseType(member.items);
toFinalBaseType(member.returns);
toFinalBaseType(member.returns && member.returns.items);
// (1.4) Mark fields with @odata.on.insert/update as @Core.Computed
annotateCoreComputed(member);
// (1.5) Resolve annotation shorthands for elements, actions, action parameters
renameShorthandAnnotations(member);
// (1.6) check annotations
checkAnnotations(member, path);
}, ['definitions', defName]);
// (1.3) Unravel derived type chains for types and annotations (propagating annotations)
if (def.kind != 'entity') {
toFinalBaseType(def);
toFinalBaseType(def.items);
toFinalBaseType(def.returns);
toFinalBaseType(def.returns && def.returns.items);
}
// If the artifact is a derived structured type, unravel that as well
// "S.Struct2": { "kind": "type", "type": "S.Struct1",
// "elements":
// { "a": { "type": "cds.Integer" },
// "b": { "type": "cds.Integer" } }
// } where S.Struct1 is also struct type def and the elements come from there
if (def.kind == 'type' && def.type &&
!isBuiltinType(def.type) && getCsnDef(def.type).elements) {
// elements are already there -> do not show the type
delete def.type;
}
// (1.5) Resolve annotation shorthands for entities, types, annotations, ...

@@ -248,5 +272,17 @@ renameShorthandAnnotations(def);

// (3.2) Flatten on-conditions in unmanaged associations
// (3.3) Check that each service has max one draft root
// This must be done before 4.1, since all composition targets are annotated with @odata.draft.enabled in this step
let flattenedKeyArts = Object.create(null);
forEachDefinition(csn, (def, defName) => {
flattenForeignKeysForArt(def, defName, flattenedKeyArts);
if (isArtifactInSomeService(defName, services) && ['entity', 'view'].includes(def.kind) && def['@odata.draft.enabled']) {
let serviceName = getServiceOfArtifact(defName, services);
let service = getCsnDef(serviceName);
if (service.$draftCount)
service.$draftCount++;
else
setProp(service, '$draftCount', 1);
if (service.$draftCount > 1)
signal(info`Service "${serviceName}" should not have more then one draft root artifact`, ['definitions', defName]);
}
});

@@ -424,8 +460,8 @@

/**
* Check if any warnings were raised in earlier steps that need to be reclassified - i.e. as errors
*
* @param {any} csn The csn
* @returns {Array} Reclassified messages-Array
*/
/**
* Check if any warnings were raised in earlier steps that need to be reclassified - i.e. as errors
*
* @param {any} csn The csn
* @returns {Array} Reclassified messages-Array
*/
function reclassifyWarnings(csn) {

@@ -445,3 +481,3 @@ return csn.messages.map(message => {

csn.messages = reclassifyWarnings(csn);
throw new CompilationError(csn.messages, csn);
throw new CompilationError(sortMessages(csn.messages), csn);
}

@@ -623,3 +659,3 @@ return csn;

if (member.type && isAssocOrComposition(member.type, csn) && member.on) {
flattenOnCond(member, memberName, def.elements);
flattenOnCond(member, memberName, def.elements, defName);
}

@@ -632,2 +668,7 @@ });

// 'rootArtifact' is the root artifact where composition traversal started.
// Constraints
// Draft Root: Exactly one PK of type UUID
// Draft Node: One PK of type UUID + 0..1 PK of another type
// Draft Node: Must not be reachable from multiple draft roots
function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {

@@ -638,3 +679,2 @@ // Sanity check

}
// Nothing to do if already draft-enabled (composition traversal may have circles)

@@ -646,15 +686,35 @@ if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction'])

// FIXME: Current restriction: Must only have exactly one key, which is of type UUID
let keyNames = Object.keys(artifact.elements).filter(elemName => {
return artifact.elements[elemName].key && artifact.elements[elemName].key === true;
});
if (keyNames.length != 1) {
signal(warning`"${artifactName}": Ignoring annotation "@odata.draft.enabled" - currently only supported for artifacts with exactly one key of type "UUID"`, ['definitions', artifactName]);
return;
if (artifact === rootArtifact) {
// draft root
if (keyNames.length != 1) {
signal(info`"${artifactName}": "@odata.draft.enabled" - Draft root should have exactly one key element`, ['definitions', artifactName]);
} else {
let keyElem = artifact.elements[keyNames[0]];
if (keyElem.type != 'cds.UUID' /* && keyElem._finalType.name.$renamed != 'cds.UUID' */) {
signal(info`"${artifactName}": "@odata.draft.enabled" - Draft root key element should be of type "cds.UUID"`, ['definitions', artifactName, 'elements', keyNames[0]]);
}
}
} else {
// draft node
if (keyNames.length < 1) {
signal(info`"${artifactName}": "@odata.draft.enabled" - Draft node should have at least one key element`, ['definitions', artifactName]);
} else if (keyNames.length > 2) {
signal(info`"${artifactName}": "@odata.draft.enabled" - Draft node mshouldust have not more then two key elements`, ['definitions', artifactName]);
} else {
let uuidCount = 0;
keyNames.forEach(kn => {
let keyElem = artifact.elements[kn];
if (keyElem.type == 'cds.UUID' /* && keyElem._finalType.name.$renamed != 'cds.UUID' */) {
uuidCount++;
}
});
if (uuidCount != 1) {
signal(info`"${artifactName}": "@odata.draft.enabled" - Draft node should have one key element of type "cds.UUID"`, ['definitions', artifactName]);
}
}
}
let keyElem = artifact.elements[keyNames[0]];
if (keyElem.type != 'cds.UUID' /* && keyElem._finalType.name.$renamed != 'cds.UUID' */) {
signal(warning`"${artifactName}": Ignoring annotation "@odata.draft.enabled" - currently only supported for key of type "UUID"`);
return;
}

@@ -685,2 +745,3 @@ // Generate the DraftAdministrativeData projection into the service, unless there is already one

let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true);
isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
addElement(isActiveEntity, artifact);

@@ -690,2 +751,3 @@

let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false);
hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
addElement(hasActiveEntity, artifact);

@@ -695,2 +757,3 @@

let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false);
hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
addElement(hasDraftEntity, artifact);

@@ -703,2 +766,3 @@

draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
addElement(draftAdministrativeData, artifact);

@@ -712,4 +776,3 @@

uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
let foreignKeyElement = createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName);
foreignKeyElement['@odata.contained'] = true;
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName);
}

@@ -744,2 +807,3 @@ // SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)

let draftNode = csn.definitions[elem.target];
// Ignore if that is our own draft root

@@ -796,3 +860,3 @@ if (draftNode != rootArtifact) {

if (isAssociation(member.type, csn)) {
let navigable = member['@odata.navigable']!==false; // navigable disabled only if explicitly set to false
let navigable = member['@odata.navigable'] !== false; // navigable disabled only if explicitly set to false
let targetDef = getCsnDef(member.target);

@@ -799,0 +863,0 @@ if (navigable && targetDef['@cds.odata.valuelist'] && !member[vlAnno]) {

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

// type properties 'key', 'notNull', 'virtual', 'masked' to the flattened elements.
function flattenStructuredElement(elem) {
function flattenStructuredElement(elem, ipath=[]) {
let path=ipath.concat(elem.name.id)
// Sanity check

@@ -299,3 +300,3 @@ if (!isStructuredElement(elem)) {

// Descend recursively into structured children
let grandChildElems = flattenStructuredElement(childElem);
let grandChildElems = flattenStructuredElement(childElem, path);
for (let grandChildName in grandChildElems) {

@@ -310,3 +311,2 @@ let flatElemName = elem.name.id + pathDelimiter + grandChildName;

// Preserve the generated element name as it would have been with 'hdbcds' names
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + grandChildName);
result[flatElemName] = flatElem;

@@ -329,3 +329,3 @@ }

// Preserve the generated element name as it would have been with 'hdbcds' names
setProp(flatElem, '_flatElementNameWithDots', elem.name.id + '.' + childName);
setProp(flatElem, '_flatElementNameWithDots', path.concat(childName).join("."));
result[flatElemName] = flatElem;

@@ -332,0 +332,0 @@ }

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

const { dfilter } = require('./udict');
const { getUtils, cloneCsn } = require('../model/csnUtils');
const { cloneCsn, forEachRef, getUtils } = require('../model/csnUtils');

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

getCsnDef,
getFinalBaseType,
hasBoolAnnotation,
inspectRef,
isStructured,
isBuiltinType,
hasBoolAnnotation,
getFinalBaseType,
inspectRef
isBuiltinType
} = getUtils(model);

@@ -43,2 +43,3 @@

toFinalBaseType,
copyTypeProperties,
isAssociationOperand,

@@ -68,3 +69,3 @@ isDollarSelfOperand,

function addDefaultTypeFacets(element) {
if(!element || !element.type)
if (!element || !element.type)
return;

@@ -74,7 +75,9 @@

element.length = model.options && model.options.length ? model.options.length : 5000;
if (!(model.options && model.options.length))
setProp(element, "$default", true);
}
if(element.type === 'cds.Decimal' && element.precision === undefined && model.options.precision) {
if (element.type === 'cds.Decimal' && element.precision === undefined && model.options.precision) {
element.precision = model.options.precision;
}
if(element.type === 'cds.Decimal' && element.scale === undefined && model.options.scale) {
if (element.type === 'cds.Decimal' && element.scale === undefined && model.options.scale) {
element.scale = model.options.scale;

@@ -109,3 +112,4 @@ }

// this is flattened elem -> keys still not flattened, have to check if starts with key ref
return assoc.keys.map(key => key.ref.join('.')).some(keyDotName => elemName.startsWith(keyDotName));
// FIXME: review why join('.')? what about join(fkSeparator)?
return assoc.keys.map(key => key.ref.join('.')).some(keyDotName => elemName.startsWith(`${keyDotName}${fkSeparator}`));
} else {

@@ -138,3 +142,3 @@ // exact match of the name

for (let keyName in targetKeys) {
if (targetKeys[keyName].viaTransform && keyName.startsWith(fKeyName))
if (targetKeys[keyName].viaTransform && keyName.startsWith(`${fKeyName}${fkSeparator}`))
flattenedKeys.push(keyName);

@@ -174,7 +178,7 @@ }

let fkArtifact;
if(!path){
if (!path) {
fkArtifact = assocTargetDef.elements[foreignKey.ref.join(pathDelimiter)]; // foreignKey.as ???
} else {
const {art} = inspectRef(path);
const { art } = inspectRef(path);
fkArtifact = art;

@@ -184,3 +188,3 @@ }

// In case of compiler errors the foreign key might be missing
if(!fkArtifact && hasErrors((model.options && model.options.messages) || model.messages)) {
if (!fkArtifact && hasErrors((model.options && model.options.messages) || model.messages)) {
return null;

@@ -314,8 +318,10 @@ }

// }
function flattenStructuredElement(elem, elemName) {
function flattenStructuredElement(elem, elemName, ipath=[]) {
let path=ipath.concat(elemName)
// in case the element is of user defined type => take the definition of the type
// for type of 'x' -> elem.type is an object, not a string -> use directly
let elemType = getFinalBaseType(elem.type);
let elemType;
if(!elem.elements) // structures does not have final base type
elemType = getFinalBaseType(elem.type);
// if no elements => check if the element is of user defined structured type

@@ -337,3 +343,3 @@ if (!elem.elements && elem.type && !isBuiltinType(elem.type)) {

// Descend recursively into structured children
let grandChildElems = flattenStructuredElement(childElem, childName);
let grandChildElems = flattenStructuredElement(childElem, childName, path);
for (let grandChildName in grandChildElems) {

@@ -349,3 +355,2 @@ let flatElemName = elemName + pathDelimiter + grandChildName;

// Preserve the generated element name as it would have been with 'hdbcds' names
setProp(flatElem, '_flatElementNameWithDots', elemName + '.' + grandChildName);
result[flatElemName] = flatElem;

@@ -358,3 +363,3 @@ }

flatElem.viaTransform = true; // FIXME: This name is not ideal but used elsewhere, too)
setProp(flatElem, '_flatElementNameWithDots', elemName + '.' + childName);
setProp(flatElem, '_flatElementNameWithDots', path.concat(childName).join("."));
result[flatElemName] = flatElem;

@@ -386,16 +391,16 @@ }

// Refs of length 1 cannot contain steps - no need to check
if(ref.length < 2){
return ref;
if (ref.length < 2) {
return ref;
}
try{
try {
return flatten(ref, path);
} catch(e){
if(e.message && e.message == "Scope 'ref-where' but no entity was provided."){
} catch (e) {
if (e.message && e.message == "Scope 'ref-where' but no entity was provided.") {
const main = path.slice(0, path.lastIndexOf("ref"));
const { links } = inspectRef(main);
const whereEntity = links[path[path.lastIndexOf("ref")+1]].art;
return flatten(ref, path, whereEntity.target ? getCsnDef(whereEntity.target) : whereEntity );
const whereEntity = links[path[path.lastIndexOf("ref") + 1]].art;
return flatten(ref, path, whereEntity.target ? getCsnDef(whereEntity.target) : whereEntity);
} else {

@@ -406,12 +411,12 @@ throw e;

function flatten(ref, path, whereEntity){
function flatten(ref, path, whereEntity) {
let result = [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
const { links, scope } = inspectRef( path, whereEntity );
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
const { links, scope } = inspectRef(path, whereEntity);
if (scope === "$magic")
return ref;
let flattenStep = false;
links.forEach( (value, idx) => {
links.forEach((value, idx) => {
if (flattenStep)
result[result.length-1] += pathDelimiter + ref[idx];
result[result.length - 1] += pathDelimiter + ref[idx];
else {

@@ -422,4 +427,4 @@ result.push(ref[idx]);

});
// If the path starts with '$self', this is now redundant (because of flattening) and can be omitted,
// making life easier for consumers
// If the path starts with '$self', this is now redundant (because of flattening) and can be omitted,
// making life easier for consumers
if (result[0] == '$self' && result.length > 1) {

@@ -431,95 +436,53 @@ result = result.slice(1);

}
// After flattening of elements we need to flatten the on-conditions of
// unmanaged associations using those newly created elements
//
// Examples:
// Input as: assoc.on: [{ ref: ['a1', 'x'] }, '=', { ref: ['$self', 'x'] }]
// ^^^ ^^^ ^^^ ^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a1: association to ... }})
// (2) is non-structured element in the target
// (3) is '$self' ... clearly
// (4) is non-structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a1', 'x'] }, '=', { ref: ['x'] }]
//
// Input as: assoc.on: [{ ref: ['a2', 'x'] }, '=', { ref: ['$self', 's', 'y'] }]
// ^^^ ^^^ ^^^ ^^^^^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a2: association to ... }})
// (2) is non-structured element in the target
// (3) is '$self' ... clearly
// (4) is structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a2', 'x'] }, '=', { ref: ['s_y'] }]
//
// Input as: assoc.on: [{ ref: [{ ref: ['a3', 'x'] }, '=', { ref: ['y'] }]
// ^^^ ^^^ ^^^
// (1) (2) (3)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a3: association to ... }})
// (2) is non-structured element in the target
// (3) element from the current nameresolution scope ( .. elements: { s : { y: ... ; a3: association to ... })
// The flatten on-condition is: [{ ref: ['s_a3', 'x'] }, '=', { ref: ['s_y'] }]
//
// Input as: assoc.on: [{ ref: ['a4', 'struct', 'k1'] }, '=', { ref: ['$self', 's', 'struct', 'k2'] }]
// ^^^ ^^^^^^^^^^^^^ ^^^^ ^^^^^^^^^^^^^^^^^^
// (1) (2) (3) (4)
// (1) is the current association which is a sub element of struct elem 's' ( ... elements: { s: { a4: association to ... }})
// (2) is structured element in the target
// (3) is '$self' ... clearly
// (4) is structured element in the current scope
// The flatten on-condition is: [{ ref: ['s_a4', 'struct_k1'] }, '=', { ref: ['s_struct_k2'] }]
function flattenOnCond(assoc, assocName, defElements) {
// 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) {
if (!assoc.on) return; // nothing to do
let allRefs = assoc.on.filter(elem => typeof elem === 'object' && elem.ref);
let targetDef = getCsnDef(assoc.target);
for (let refObj of allRefs) {
// if reference is just '$self'
if (refObj.ref.length === 1 && refObj.ref[0] === '$self')
continue;
// remove '$self' when first elem in ref and flatten the rest if needed
if (refObj.ref[0] === '$self') {
refObj.ref.shift();
// the rest of the reference should point to an element from the current definition
let potentialFlattenElem = refObj.ref.join(pathDelimiter);
if (defElements && defElements[potentialFlattenElem] && defElements[potentialFlattenElem].viaTransform) {
refObj.ref = [potentialFlattenElem];
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]}`);
}
continue;
}
// assoc element itself was flatten
// -> every first step where current assoc is specified needs to be replaced
if (assoc.viaTransform && assocName.endsWith(`${pathDelimiter}${refObj.ref[0]}`)) {
refObj.ref.splice(0, 1, assocName);
// the rest of the reference should point to an element from the target
let potentialFlattenElem = refObj.ref.slice(1).join(pathDelimiter);
if (targetDef.elements && targetDef.elements[potentialFlattenElem] && targetDef.elements[potentialFlattenElem].viaTransform) {
refObj.ref.splice(1, refObj.ref.length - 1, potentialFlattenElem);
}
}
let flatRef = [];
let needToFlat = false;
// $user.locale must not be flatten and they are not understand by inspectRef
if (ref.join('.') === '$user.locale')
return;
// reference to flattened element in the target
let flattenRefName = refObj.ref.join(pathDelimiter);
if (targetDef.elements) {
// exact match of flatten element name
if (targetDef.elements[flattenRefName] && targetDef.elements[flattenRefName].viaTransform) {
refObj.ref = [flattenRefName];
continue;
}
// when element is defined in the current name resolution scope, like
// entity E {
// key x: Integer;
// s : {
// y : Integer;
// a3 : association to E on a3.x = y;
// }
// }
let potentialFlattenElem = Object.keys(targetDef.elements)
.find(elemName => elemName.endsWith(`${pathDelimiter}${flattenRefName}`));
if (potentialFlattenElem && targetDef.elements[potentialFlattenElem].viaTransform) {
refObj.ref = [potentialFlattenElem];
continue;
}
}
}
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);
}, ['definitions', defName, 'elements', assocName]);
}

@@ -537,2 +500,33 @@

/**
* Copy properties of the referenced type, but don't resolve to the final base type.
*
* @param {any} node Node to copy to
* @returns {undefined}
*/
function copyTypeProperties(node) {
// Nothing to do if no type (or if array/struct type)
if (!node || !node.type) return;
// .. or if it is a ref
if (node.type && node.type.ref) return;
// .. or builtin already
if (node.type && typeof node.type === 'string' && node.type.startsWith('cds.') && !node.type.startsWith('cds.foundation.')) return;
// The type might already be a full fledged type def (array of)
const typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
// Nothing to do if type is an array or a struct type
if (typeDef.items || typeDef.elements) return;
// if the declared element is an enum, these values are with priority
if (!node.enum && typeDef.enum)
Object.assign(node, { enum: typeDef.enum });
if (!node.length && typeDef.length && !typeDef.$default)
Object.assign(node, { length: typeDef.length });
if (!node.precision && typeDef.precision)
Object.assign(node, { precision: typeDef.precision });
if (!node.scale && typeDef.scale)
Object.assign(node, { scale: typeDef.scale });
if (!node.srid && typeDef.srid)
Object.assign(node, { srid: typeDef.srid });
}
// Replace the type of 'node' with its final base type (in contrast to the compiler,

@@ -545,3 +539,3 @@ // also unravel derived enum types, i.e. take the final base type of the enum's base type.

// In case of a ref -> Follow the ref
if(node.type && node.type.ref) {
if (node.type && node.type.ref) {
node.type = getFinalBaseType(node.type);

@@ -562,4 +556,2 @@ return;

Object.assign(node, { length: typeDef.length });
if (!node.length && typeDef.length)
Object.assign(node, { length: typeDef.length });
if (!node.precision && typeDef.precision)

@@ -726,6 +718,6 @@ Object.assign(node, { precision: typeDef.precision });

if (!arg.ref) {
// Not a path, hence not an association (literal, expression, function, whatever ...)
// Not a path, hence not an association (literal, expression, function, whatever ...)
return false;
}
const {art} = inspectRef(path);
const { art } = inspectRef(path);
// If it has a target, it is an association or composition

@@ -911,10 +903,10 @@ return art.target !== undefined;

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

@@ -941,5 +933,5 @@ return [validFroms, validTos, validKeys];

function checkAssignment(annoName, element, path, artifact) {
if(artifact.kind !== 'type' && !artifact.query) {
if (artifact.kind !== 'type' && !artifact.query) {
// path.length > 4 to check for structured elements
if(element.elements || element.target || path.length > 4) {
if (element.elements || element.target || path.length > 4) {
signal(error`Element cannot be annotated with "${annoName}"`, path);

@@ -961,5 +953,5 @@ return false;

*/
function checkMultipleAssignments(array, annoName, artifact, artifactName, err=true) {
if(array.length > 1) {
if(err == true) {
function checkMultipleAssignments(array, annoName, artifact, artifactName, err = true) {
if (array.length > 1) {
if (err == true) {
signal(error`"${annoName}" must be assigned only once`, ['definitions', artifactName]);

@@ -966,0 +958,0 @@ } else {

'use strict'
const { setProp, forEachGeneric, forEachDefinition } = require('../base/model');
var { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const modelUtils = require('../model/modelUtils.js');

@@ -9,3 +10,4 @@ const compactor = require('../json/compactor');

const {compactModel} = require('../json/to-csn');
const csn2xsn = require('../json/csn2xsn');
const { cloneCsn } = require('../model/csnUtils');
// Paths that start with an artifact of protected kind are special

@@ -16,4 +18,3 @@ // either ignore them in QAT building or in path rewriting

function translateAssocsToJoinsCSN(csn, options){
let main = require('../main');
const model = main.compileSources({ '<stdin>.json' : JSON.stringify(csn, null, 2) }, options);
const model = csn2xsn(cloneCsn(csn), options);
forEachDefinition(model, art => {

@@ -38,3 +39,11 @@ if (art.$queries) {

});
return compactModel(translateAssocsToJoins(model));
translateAssocsToJoins(model);
// If A2J reports error - end! Continuing with a broken CSN makes no sense
if (hasErrors(model.messages)) {
throw new CompilationError(sortMessages(model.messages), csn)
}
const compact = compactModel(model);
setProp(compact, "options", csn.options);
setProp(compact, "messages", csn.messages);
return compact;
}

@@ -41,0 +50,0 @@

{
"name": "@sap/cds-compiler",
"version": "1.19.2",
"version": "1.20.3",
"lockfileVersion": 1,

@@ -5,0 +5,0 @@ "requires": true,

@@ -1,1 +0,1 @@

{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.19.2","license":"SEE LICENSE IN developer-license-3.1.txt"}
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.20.3","license":"SEE LICENSE IN developer-license-3.1.txt"}

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

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

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

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

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

Sorry, the diff of this file is not supported yet

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

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