Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
1
Maintainers
1
Versions
101
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 2.2.8 to 2.4.4

doc/Versioning.md

10

bin/cdsc.js

@@ -87,2 +87,8 @@ #!/usr/bin/env node

}
// --cds-home <dir>: modules starting with '@sap/cds/' are searched in <dir>
if (cmdLine.options.cdsHome) {
if (!global.cds)
global.cds = {};
global.cds.home = cmdLine.options.cdsHome;
}
// Default color mode is 'auto'

@@ -218,3 +224,3 @@ term.useColor(cmdLine.options.color || 'auto');

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

@@ -462,2 +468,4 @@

else if (!options.lintMode && !options.internalMsg) {
if (command === 'toCsn' && options.toCsn && options.toCsn.withLocalized)
addLocalizationViews(csn, options);
writeToFileOrDisplay(options.out, name + '.json', csn, true);

@@ -464,0 +472,0 @@ }

96

bin/cdsse.js

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

const { locationString } = require('../lib/base/messages');
const { availableBetaFlags: beta } = require('../lib/base/model');

@@ -75,3 +76,3 @@ let argv = process.argv;

let fname = path.resolve( '', file );
compiler.compileX( [file], '', { attachValidNames: true, lintMode: true, beta: true, messages: [] } , { [fname]: src } )
compiler.compileX( [file], '', { attachValidNames: true, lintMode: true, beta, messages: [] } , { [fname]: src } )
.then( ident, ident );

@@ -90,2 +91,4 @@ }

console.log( n, vn[n].kind );
if (!Object.keys( vn ).length)
console.log( 'unknown_identifier', 'identifier' );
return true;

@@ -95,18 +98,27 @@ }

// For finding the definition for reference under cursor, do the following
// * replace identifier under cursor by an undefined name
// * call compiler and retrieve valid names at cursor position
// * use originally provided name to find definition and its location.
function find( err, buf ) {
if (err)
return usage( err );
let fname = path.resolve( '', file );
compiler.compileX( [file], '', { lintMode: true, beta: true, messages: [] } , { [fname]: buf } )
.then( select, select );
const off = offset( buf, true );
if (!off) // outside buffer range
return usage();
if (off.prefix === off.cursor) // not at name
return true;
const src = buf.substring( 0, off.prefix ) + '__NO_SUCH_ID__' + buf.substring( off.cursor );
const fname = path.resolve( '', file );
compiler.compileX( [file], '', { attachValidNames: true, lintMode: true, beta, messages: [] } , { [fname]: src } )
.then( show, show );
return true;
function select( xsnOrErr ) {
const xsn = (xsnOrErr instanceof Error) ? xsnOrErr['model'] : xsnOrErr;
if (!xsn)
return true;
const [src] = Object.values( xsn.sources );
pathitem( src.usings );
pathitem( src.extensions );
pathitem( src.artifacts );
function show( xsnOrErr ) {
if (!(xsnOrErr.messages || xsnOrErr.options && xsnOrErr.options.messages))
return usage( xsnOrErr );
let vn = messageAt( xsnOrErr, 'validNames', off.col ) || Object.create(null);
const art = vn[buf.substring( off.prefix, off.cursor )];
if (art)
console.log( locationString( art.name.location || art.location ) + ': Definition' );
return true;

@@ -116,49 +128,2 @@ }

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 );
/** @type {XSN.WithLocation} */
const { location } = node;
if (!location || node.$inferred || !location.endLine) // weak location
return false;
if (location.file !== frel)
return false;
if (location.line > line || location.line === line && location.col > column)
return 'stop'; // 'stop' means: stop search if in array or dictionary
if (location.endLine < line || location.endLine === line && location.endCol < 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(0)] || pathitem)( node[prop], node ) || found;
if (found === true)
return found;
}
return found;
}
function lint( err, buf ) {

@@ -168,3 +133,3 @@ if (err)

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

@@ -198,3 +163,3 @@ return true;

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

@@ -216,3 +181,3 @@ }

*/
function offset( buf ) { // for line and column
function offset( buf, alsoSuffix ) { // for line and column
let pos = 0;

@@ -224,5 +189,8 @@ for (let l = line-1; l; --l) {

}
const cursor = pos + column - 1;
let cursor = pos + column - 1;
const prefix = /[a-z_0-9$]*$/i.exec( buf.substring( pos, cursor ) ).index + pos;
return { cursor, prefix, col: column + prefix - cursor };
const col = column + prefix - cursor;
if (alsoSuffix)
cursor += buf.substring( cursor, buf.indexOf( '\n', cursor ) + 1 ).search( /[^a-z_0-9$]/i );
return { cursor, prefix, col };
}

@@ -8,4 +8,91 @@ # ChangeLog for cds compiler and backends

Note: `beta` fixes, changes and features are usually not listed in this ChangeLog but [here](doc/CHANGELOG_BETA.md).
The compiler behaviour concerning `beta` features can change at any time without notice.
The compiler behavior concerning `beta` features can change at any time without notice.
## Version 2.4.4 - 2021-07-02
### Fixed
- Do not remove parentheses around single literals and references on the right-hand side of an `in` and `not in` operator.
of an `in` and `not in` operator.
## Version 2.4.2 - 2021-07-01
- Only changes to beta features. Refer to the [beta ChangeLog](doc/CHANGELOG_BETA.md#version-242) for more.
## Version 2.4.0 - 2021-06-28
### Added
- to.edm(x):
+ Warn if an `edm:Property` has no `Type` attribute.
+ Warn about using the protected names 'Edm', 'odata', 'System', 'Transient' as `edm:Schema` `Namespace` values.
+ Allow `$edmJson` inline annotations in `edm:Collection` and nested annotations.
- to.hdi/sql/hdbcds: Transform a `exists <association>` into a `exists <subselect>`, where the subselect
selects from the target of `<association>` and establishes the same relation as `<association>` would via the WHERE clause.
Infix-filters of `<association>` are added to the WHERE clause.
### Changed
- Do not inherit `@cds.persistence.skip` when `@cds.persistence.table` is set on entity.
- to.cdl: Opening and closing braces of empty services and contexts are now on the same line.
### Fixed
- `cdsc`: Option `--direct-backend` can now be combined with `toCsn`'s option `--with-localized`
- The option `testSortCsn` was erroneously ignored in some compiler backends.
## Version 2.3.2 - 2021-06-14
### Fixed
- for.odata: Propagate the `virtual` attribute correctly while flattening structures.
- If internal relational types are used directly in CDL (e.g. `cds.Association`), an error is emitted.
In CSN, all artifacts of relational types need a `target` (/`targetAspect`) as well.
- In Association to Join translation don't produce a JOIN node for exposed (transitive) associations in
combination with their exposed foreign keys. Also resolve foreign keys correctly against the target
entity allowing to expose renamed foreign keys when aliased.
- The option `testSortCsn` (`--test-sort-csn` in `cdsc`) can be used to sort CSN definitions alphabetically.
This option is only intended for tests. This will restore the pre-v2.3.0 ordering in EDMX.
- to.sql:
+ for SQL-dialect `sqlite`, render the string-format-time function (`strftime()`)
+ `$at.from` with date-format: `'%Y-%m-%dT%H:%M:%S.000Z'`
+ `$at.to` with date-format: `'%Y-%m-%dT%H:%M:%S.001Z'` (**+1ms** compared to `$at.from`)
+ for SQL-dialect `hana` wrap `SESSION_CONTEXT('VALID-TO')` and `SESSION_CONTEXT('VALID-FROM')` in `TO_TIMESTAMP(..)` function
- to.hdbcds:
+ Wrap `SESSION_CONTEXT('VALID-TO')` and `SESSION_CONTEXT('VALID-FROM')` in `TO_TIMESTAMP(..)` function
## Version 2.3.0 - 2021-06-02
### Added
- `cdsc` got a new option `--fallback-parser <cdl|csn>` that is used
if an unknown or no file extension is used.
- to.hdi/sql: Allow association publishing in UNIONs - this was previously forbidden, but this limitation only applies to HANA CDS.
- to.edm(x): Support dynamic expressions as $edmJson inline code
### Changed
- Type `DecimalFloat` is no longer proposed for code-completion.
- Non-string enums without values for their enum elements are warned about.
- OData CSN is no longer sorted by definition names
- to.edm(x): Update OData vocabularies 'Aggregation', 'Analytics', 'CodeList', 'Common', 'Measures', 'Session', 'UI'
### Removed
- to.hdbcds: Association publishing in subqueries is not supported by HANA CDS - an error is raised during compile time, instead of waiting for a deployment error.
### Fixed
- Correct auto-exposure in model with unscoped projection on deep scoped entity
(from managed aspect compositions: component in component, like they are common in ODM).
- Internal types `cds.Association` and `cds.Composition` are no longer proposed for code-completion.
- Fix various issues with Association to Join translation:
+ Substitute `$self.alias` expressions and respect prefix paths in foreign key accesses.
- to.hdbcds: In naming mode "hdbcds", correctly resolve $self backlinks with aliased foreign keys.
- to.cdl:
+ Correctly traverse subelements when rendering annotations for them.
+ Quote element names (if required) in `annotate with` statements.
- for.odata: Fix regression with detecting collision when generating foreign keys.
- to.edmx: Correctly render final base types in EDMX V2 when called with transformed OData CSN for V4.
## Version 2.2.8 - 2021-05-20

@@ -336,2 +423,11 @@

## Version 1.50.6 - 2021-05-05
### Fixed
- to.edm(x):
+ OData V2: Render constraints only if all principal keys are used in association.
+ OData V4: Don't remove `@Capabilities` annotations from containee.
+ Allow `@Core.MediaType` on all types and raise a warning for those (scalar) types that can't be mapped to `Edm.String` or `Edm.Binary`.
## Version 1.50.4 - 2021-04-06

@@ -338,0 +434,0 @@

@@ -7,5 +7,31 @@ # ChangeLog of Beta Features for cdx compiler and backends

Note: `beta` fixes, changes and features are listed in this ChangeLog just for information.
The compiler behaviour concerning `beta` features can change at any time without notice.
The compiler behavior concerning `beta` features can change at any time without notice.
**Don't use `beta` fixes, changes and features in productive mode.**
## Version 2.4.2
### Added `keylessManagedAssoc`
- Support managed associations without foreign keys. Associations targeting a definition without primary keys or with an
explicit empty foreign key tuple or with empty structured elements as foreign keys and their corresponding `$self`
comparisons do not describe the relationship between the source and the target entity.
These associations can be used to establish API navigations but cannot be used to access elements in the target
entity as they cannot be transformed into a valid JOIN expression.
Consequently, these associations are not added to the `WITH ASSOCIATIONS` clause or forwarded to HANA CDS.
Managed Associations without foreign keys must be enabled with `--beta: keylessManagedAssoc`
## Version 2.4.0
### Changed `foreignKeyConstraints`
- `toSql`/`toHdbcds`: omit constraint generation if the option `skipDbConstraints` is set
- If the database constraints are switched off by the global option,
render constraints nevertheless if an association / composition
is annotated with `@cds.persistency.assert.integrity: true`
- omit constraint generation if an association / composition
is annotated with `@cds.persistency.assert.integrity: false`
-> for managed compositions, the `up_` link in the compositions target entity
will not result in a constraint if the composition is annotated as described
## Version 2.0.8

@@ -15,3 +41,4 @@

to.sql/to.hdi: If the beta option `foreignKeyConstraints` is supplied, referential constraints are generated for compliant associations and compositions.
to.sql/to.hdi: If the beta option `foreignKeyConstraints` is supplied,
referential constraints are generated for compliant associations and compositions.

@@ -22,4 +49,5 @@ ## Version 2.0.2

Virtual elements are no longer rendered in views as `null as <id>` or added to potentially generated
draft tables. This behavior can be turned off with deprecated option `renderVirtualElements` for backward compatibility.
Virtual elements are no longer rendered in views as `null as <id>` or
added to potentially generated draft tables. This behavior can be turned off
with deprecated option `renderVirtualElements` for backward compatibility.

@@ -38,3 +66,2 @@ ### Removed `originalKeysForTemporal`

## Version 1.44.0

@@ -120,6 +147,6 @@

* managed associations (and compositions to entities) are implicitly redirected
- managed associations (and compositions to entities) are implicitly redirected
when necessary,
* sub elements of referred structure types can be annotated individually,
* the resulting CSN is bigger (will be reduced in the future if possible)
- sub elements of referred structure types can be annotated individually,
- the resulting CSN is bigger (will be reduced in the future if possible)
as `type` references to structures will now have a sibling `elements`.

@@ -129,6 +156,6 @@

* rewriting the `on` conditions of associations in sub elements,
* aspect compositions as sub elements,
* `localized` sub elements,
* `key` property on sub elements.
- rewriting the `on` conditions of associations in sub elements,
- aspect compositions as sub elements,
- `localized` sub elements,
- `key` property on sub elements.

@@ -135,0 +162,0 @@ ## Version 1.23.0

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

const relevantGeneralOptions = [];
const relevantGeneralOptions = [ /* for future generic options */ ];
const relevantOdataOptions = [ 'sqlMapping', 'odataFormat' ];

@@ -251,3 +251,3 @@ const relevantSqlOptions = [ 'sqlMapping', 'sqlDialect' ];

* @param {CSN.Model} csn SQL transformed CSN
* @param {Function} filter
* @param {Function} filter Filter for keys not to remap
* @returns {object} New result structure

@@ -320,3 +320,3 @@ */

/**
* Check wether the given argument is a CSN
* Check whether the given argument is a CSN
*

@@ -475,3 +475,2 @@ * @param {object} arg Argument to verify

const lEdm = services[serviceName];
result[serviceName] = {};
// FIXME: Why only metadata_json - isn't this rather a 'combined_json' ? If so, rename it!

@@ -653,9 +652,9 @@ result[serviceName] = lEdm;

if (processor.all)
api.all = publishCsnProcessor(processor.all);
api.all = publishCsnProcessor(processor.all, `${ _name }.all`);
if (processor.migration)
api.migration = publishCsnProcessor(processor.migration);
api.migration = publishCsnProcessor(processor.migration, `${ _name }.migration`);
if (processor.mtx)
api.mtx = publishCsnProcessor(processor.mtx);
api.mtx = publishCsnProcessor(processor.mtx, `${ _name }.mtx`);

@@ -665,3 +664,3 @@ return api;

/**
* Function that calls the processor and recompiles in case of internal errors
* Function that calls the processor and re-compiles in case of internal errors
*

@@ -668,0 +667,0 @@ * @param {object} csn CSN

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

'withLocations',
'defaultStringLength',
// DB

@@ -46,4 +47,6 @@ 'sqlDialect',

'testMode',
'testSortCsn',
'constraintsNotEnforced',
'constraintsNotValidated',
'skipDbConstraints',
'noRecompile',

@@ -71,3 +74,3 @@ 'internalMsg',

function translateOptions(input = {}, defaults = {}, hardRequire = {},
customValidators, combinationValidators) {
customValidators = {}, combinationValidators = []) {
const options = Object.assign({}, defaults);

@@ -74,0 +77,0 @@ const inputOptionNames = Object.keys(input);

@@ -92,2 +92,7 @@ 'use strict';

},
defaultStringLength: {
validate: val => Number.isInteger(val),
expected: () => 'Integer literal',
found: val => `type ${ typeof val }`,
},
dictionaryPrototype: {

@@ -94,0 +99,0 @@ validate: () => true,

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

const { warning } = makeMessageFunction(csn, options, 'to.hana');
const { warning } = makeMessageFunction(csn, options, 'to.hdbcds');

@@ -80,3 +80,3 @@ // Verify options

// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)
let forHanaCsn = transformForHanaWithCsn(csn, options);
let forHanaCsn = transformForHanaWithCsn(csn, options, 'to.hdbcds');

@@ -304,3 +304,3 @@ // Assemble result

const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
const forSqlCsn = transformForHanaWithCsn(model, mergedOptions);
const forSqlCsn = transformForHanaWithCsn(model, mergedOptions, 'to.sql');

@@ -472,3 +472,3 @@ // Assemble result

// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forHana)
let forHanaCsn = transformForHanaWithCsn(csn, mergeOptions(options, { forHana : options.toRename } ));
let forHanaCsn = transformForHanaWithCsn(csn, mergeOptions(options, { forHana : options.toRename } ), 'to.rename');
// forHanaCsn looses empty contexts and services, add them again so that toRename can calculate the namespaces

@@ -509,3 +509,3 @@ forEachDefinition(csn, (artifact, artifactName) => {

const mergedOptions = mergeOptions(transformedOptions.options, { forHana : transformedOptions.forHanaOptions });
const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions);
const forSqlCsn = transformForHanaWithCsn(csn, mergedOptions, 'to.sql');

@@ -512,0 +512,0 @@ if (violations && src && src !== 'sql')

'use strict';
// This file contains functions related to XSN/CSN-location objects as well
// as for semantic locations
// This file contains functions related to XSN/CSN-location objects,
// but not semantic locations (which are message-specific),
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');
const { copyPropIfExist } = require('../utils/objectUtils');

@@ -27,4 +26,4 @@

};
copyPropIfExist(loc, 'endLine', end.location);
copyPropIfExist(loc, 'endCol', end.location);
copyPropIfExist(end.location, 'endLine', loc);
copyPropIfExist(end.location, 'endCol', loc);
return loc;

@@ -74,7 +73,32 @@ }

/**
* @returns {CSN.Location}
* Return gnu-style error string for location `loc`:
* - 'File:Line:Col' without `loc.end`
* - 'File:Line:StartCol-EndCol' if Line = start.line = end.line
* - 'File:StartLine.StartCol-EndLine.EndCol' otherwise
*
* @param {CSN.Location|CSN.Location} location
* @param {boolean} [normalizeFilename]
*/
function normalizeLocation( loc ) {
// TODO: remove function
return loc;
function locationString( location, normalizeFilename ) {
if (!location)
return '<???>';
const loc = location;
let filename = (loc.file && normalizeFilename)
? loc.file.replace( /\\/g, '/' )
: loc.file;
if (!(loc instanceof Object))
return loc;
if (!loc.line) {
return filename;
}
else if (!loc.endLine) {
return (loc.col)
? `${filename}:${loc.line}:${loc.col}`
: `${filename}:${loc.line}`;
}
else {
return (loc.line === loc.endLine)
? `${filename}:${loc.line}:${loc.col}-${loc.endCol}`
: `${filename}:${loc.line}.${loc.col}-${loc.endLine}.${loc.endCol}`;
}
}

@@ -130,226 +154,2 @@

function constructSemanticLocationFromCsnPath(csnPath, model) {
if (!model)
return null;
// Copy because this function shift()s from the path.
csnPath = [ ...csnPath ];
const csnDictionaries = [
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
];
const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
let { query } = analyseCsnPath(
csnPath,
model
);
// remove definitions
csnPath.shift();
const artName = csnPath.shift();
let currentThing = model.definitions[artName];
let result = `${ (currentThing && currentThing.kind) ? currentThing.kind : 'artifact' }:${ _quoted(artName) }`;
if (!currentThing)
return result;
if (query)
query = queryDepth(currentThing.query, query);
const elements = [];
let inCsnDict = false;
let inElement = false;
let inAction = false;
let inParam = false;
let inKeys = false;
let inRef = false;
let inEnum = false;
let inQuery = false;
let inColumn = false;
let inMixin = false;
let inItems = false;
// for top level actions
if (currentThing.kind === 'action')
inAction = true;
for (const [ index, step ] of csnPath.entries()) {
currentThing = currentThing[step];
if (csnDictionaries.includes(step) && !inCsnDict) {
inCsnDict = true;
switch (step) {
case 'elements':
if (!inElement){
inElement = true;
// do not print intermediate items
inItems = false;
}
break;
case 'actions':
inAction = true;
break;
case 'params':
inParam = true;
break;
case 'enum':
inElement = false;
inEnum = true;
break;
case 'mixin':
inMixin = true;
inQuery = false;
break;
default:
if (inElement) {
// close element
result += element();
inElement = false;
}
}
}
else if ( inQuery ) {
if (step === 'SELECT') {
if (!csnPath[index + 1]) {
result += select();
}
else if (queryProps.includes(csnPath[index + 1]) && !csnPath[index + 2]) {
const clause = csnPath[index + 1];
result += select();
result += `/${ clause }`;
}
}
else if (step === 'columns') {
result += select();
result += '/column';
inColumn = true;
inQuery = false;
}
}
else if ( inMixin ) {
if (step === 'on') {
result += '/on';
break;
}
else {
result += selectAndMixin(step);
}
}
else if (inEnum) {
result += elementAndEnum(step);
}
else if (!inElement && step === 'query') {
inQuery = true;
}
else if (inElement && step === 'keys') {
// close element
result += `${ element() }/key`;
inElement = false;
inKeys = true;
}
else if (inElement && step === 'on') {
// close element
result += `${ element() }/on`;
inElement = false;
break;
}
else if (inElement && step === 'items') {
// this is an element called items
if (csnPath[index - 1] === 'elements' && elements[elements.length - 1] !== 'elements') {
elements.push(step);
}
else {
inElement = false;
inItems = true;
}
}
else if (inElement && step === 'elements') {
// this is an element called elements
if (csnPath[index - 1] === 'elements')
elements.push(step);
}
else if (inItems && step === 'elements') {
inElement = true;
inItems = false;
}
else if ( inKeys || inColumn) {
if (typeof step === 'number') {
if (currentThing.as)
result += `:${ _quoted(currentThing.as) }`;
else
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.join('.')) }` : '';
break;
}
if ( step === 'ref')
inRef = true;
}
else if (inAction && step === 'returns') {
result += `/${ step }`;
break;
}
else if (inCsnDict) {
if (inElement)
elements.push(step);
else if (inParam)
result += param(step);
else if (inAction)
result += func(step);
inCsnDict = false;
}
}
if ( inItems )
result += `${ element() }/items`;
else if ( inElement )
result += element();
return result;
function select() {
let s = '/select';
s += query.isOnlySelect ? '' : `:${ query.depth }`;
return s;
}
function selectAndMixin(name) {
return `${ select() }/mixin:${ _quoted(name) }`;
}
function element() {
return `/element:${ _quoted(elements.join('.')) }`;
}
function param(name) {
return `/param:${ _quoted(name) }`;
}
function func(name) {
return `/function:${ _quoted(name) }`;
}
function elementAndEnum(name) {
return `${ element() }/enum:${ _quoted(name) }`;
}
/**
* Traverse rootQuery until targetQuery is found and count the depth,
* check if targetQuery is only select in entity.
*/
function queryDepth(rootQuery, targetQuery) {
let targetQueryDepth = 1;
let totalQueryDepth = 0;
let isFound = false;
traverseQuery(rootQuery, null, (q, querySelect) => {
if ( querySelect )
totalQueryDepth += 1;
if ( querySelect && !isFound)
targetQueryDepth += 1;
if (q === targetQuery)
isFound = true;
});
return { depth: targetQueryDepth, isOnlySelect: totalQueryDepth === 1 };
}
}
function _quoted( name ) {
return (name) ? `"${ name.replace( /"/g, '""' ) }"` : '<?>'; // sync ";
}
module.exports = {

@@ -360,5 +160,4 @@ combinedLocation,

builtinLocation,
normalizeLocation,
dictLocation,
constructSemanticLocationFromCsnPath,
locationString,
};

@@ -31,7 +31,7 @@ // Central registry for messages.

'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx' ] },
'check-proper-type-of': { severity: 'Info', errorFor: [ 'for.odata', 'to.edmx', 'to.hdbcds', 'to.sql', 'to.rename' ] },
'expr-no-filter': { severity: 'Error', configurableFor: 'deprecated' },
'empty-entity': { severity: 'Info', errorFor: [ 'forHana' ] },
'empty-entity': { severity: 'Info', errorFor: [ 'to.hdbcds', 'to.sql', 'to.rename' ] },
'empty-type': { severity: 'Info' }, // only still an error in old transformers

@@ -72,3 +72,2 @@

'ref-undefined-var': { severity: 'Error' },
'ref-undefined-typeof': { severity: 'Error' },
'ref-undefined-element': { severity: 'Error' },

@@ -93,2 +92,5 @@ 'ref-obsolete-parameters': { severity: 'Error', configurableFor: true }, // does not hurt us

'syntax-anno-after-struct': { severity: 'Error', configurableFor: true }, // does not hurt
'syntax-csn-expected-cardinality': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-expected-translation': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-required-subproperty': { severity: 'Error' }, // TODO: more than 30 chars
'syntax-csn-unexpected-property': { severity: 'Error', configurableFor: true }, // is the removed

@@ -101,4 +103,8 @@ 'syntax-deprecated-ident': { severity: 'Error', configurableFor: true },

'unexpected-keys-for-composition': { severity: 'Error' }, // TODO: more than 30 chars
'unmanaged-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing
'composition-as-key': { severity: 'Error', configurableFor: 'deprecated' }, // is confusing and not supported
'odata-spec-violation-constraints': { severity: 'Warning' }, // more than 30 chars
'odata-spec-violation-property-name': { severity: 'Warning' }, // more than 30 chars
'odata-spec-violation-namespace-name': { severity: 'Warning' }, // more than 30 chars
};

@@ -117,2 +123,8 @@

'syntax-anno-after-params': 'Avoid annotation assignments after parameters',
'syntax-dollar-ident': {
std: 'An artifact starting with $(NAME) might shadow a special variable - replace by another name',
$tableAlias: 'A table alias name starting with $(NAME) might shadow a special variable - replace by another name',
$tableImplicit: 'The resulting table alias starts with $(NAME) and might shadow a special variable - specify another name with $(KEYWORD)',
mixin: 'A mixin name starting with $(NAME) might shadow a special variable - replace by another name' ,
},
'ref-undefined-def': {

@@ -130,5 +142,14 @@ std: 'Artifact $(ART) has not been found',

'ref-rejected-on': {
std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used
mixin: 'Do not refer to a mixin like $(ID) in the explicit ON of a redirection',
alias: 'Do not refer to a source element (via table alias $(ID)) in the explicit ON of a redirection',
},
'ref-invalid-typeof': {
std: 'Do not use $(KEYWORD) for the type reference here',
type: 'Do not use $(KEYWORD) for the type of a type',
event: 'Do not use $(KEYWORD) for the type of an event',
param: 'Do not use $(KEYWORD) for the type of a parameter',
select: 'Do not use $(KEYWORD) for type references in queries',
},
'anno-builtin': 'Builtin types should not be annotated. Use custom type instead',
'anno-undefined-def': 'Artifact $(ART) has not been found',

@@ -149,19 +170,44 @@ 'anno-undefined-art': 'No artifact has been found with name $(NAME)',

},
'duplicate-definition': {
std: 'Duplicate definition of $(NAME)',
absolute: 'Duplicate definition of artifact $(NAME)',
namespace: 'Other definition blocks $(NAME) for namespace name',
element: 'Duplicate definition of element $(NAME)',
enum: 'Duplicate definition of enum $(NAME)',
key: 'Duplicate definition of key $(NAME)',
action: 'Duplicate definition of action or function $(NAME)',
param: 'Duplicate definition of parameter $(NAME)',
$tableAlias: 'Duplicate definition of table alias or mixin $(NAME)',
},
// TODO: rename to ref-expected-XYZ
'expected-actionparam-type': 'A type, an element, or a service entity is expected here',
'expected-const': 'A constant value is expected here',
'expected-struct': 'A type, entity, aspect or event with direct elements is expected here',
'expected-context': 'A context or service is expected here',
'expected-type': 'A type or an element is expected here',
'ref-sloppy-type': 'A type or an element is expected here',
'expected-actionparam-type': 'A type, an element, or a service entity is expected here',
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
'expected-event-type': 'A type, an element, an event, or a service entity is expected here',
'ref-sloppy-event-type': 'A type, an element, an event, or a service entity is expected here',
'expected-entity': 'An entity, projection or view is expected here',
'expected-struct': 'A type, entity, aspect or event with direct elements is expected here',
'expected-type': 'A type or an element is expected here',
// TOODO: text variant if the association does not start an an entity
'expected-source': 'A query source must be an entity or an association',
'expected-target': 'An entity or an aspect is expected here',
'extend-columns': 'Artifact $(ART) can\'t be extended with columns, only projections can',
'extend-repeated-intralayer': 'Unstable element order due to repeated extensions in same layer',
'ref-sloppy-type': 'A type or an element is expected here',
'ref-sloppy-actionparam-type': 'A type, an element, or a service entity is expected here',
'ref-sloppy-target': 'An entity or an aspect (not type) is expected here',
'ref-sloppy-event-type': 'A type, an element, an event, or a service entity is expected here',
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers'
'type-managed-composition': {
std: 'Managed compositions can\'t be used in types', // yet
sub: 'Managed compositions can\'t be used in sub elements',
aspect: 'Aspect $(ART) with managed compositions can\'t be used in types', // yet
entity: 'Entity $(ART) with managed compositions can\'t be used in types', // yet
},
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers',
'odata-spec-violation-attribute': 'EDM Property $(NAME) has no attribute $(PROP)',
'odata-spec-violation-property-name': 'EDM Property $(NAME) must not have the same name as the declaring $(TYPE)',
}

@@ -168,0 +214,0 @@

@@ -6,8 +6,8 @@ // Functions and classes for syntax messages

const term = require('../utils/term');
const { normalizeLocation } = require('./location');
const { locationString } = require('./location');
const { isDeprecatedEnabled } = require('./model');
const { centralMessages, centralMessageTexts } = require('./message-registry');
const { constructSemanticLocationFromCsnPath } = require('./location');
const { copyPropIfExist } = require('../utils/objectUtils');
const _messageIdsWithExplanation = require('../../share/messages/message-explanations.json').messages;
const { analyseCsnPath, traverseQuery } = require('../model/csnRefs');

@@ -23,22 +23,2 @@ const fs = require('fs');

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

@@ -80,4 +60,9 @@ * Returns true if at least one of the given messages is of severity "Error"

const msg = centralMessages[messageId];
return (!msg.errorFor || !msg.errorFor.includes(moduleName)) &&
(msg.severity !== 'Error' ||
// errorFor has the highest priority. If the message is an error for
// the module, it is NEVER downgradable.
if (msg.errorFor && msg.errorFor.includes(moduleName))
return false;
return (msg.severity !== 'Error' ||
msg.configurableFor === true || // useful with error for syntax variants

@@ -88,35 +73,2 @@ msg.configurableFor && msg.configurableFor.includes( moduleName ));

/**
* Return gnu-style error string for location `loc`:
* - 'File:Line:Col' without `loc.end`
* - 'File:Line:StartCol-EndCol' if Line = start.line = end.line
* - 'File:StartLine.StartCol-EndLine.EndCol' otherwise
*
* @param {CSN.Location|CSN.Location} location
* @param {boolean} [normalizeFilename]
*/
function locationString( location, normalizeFilename ) {
if (!location)
return '<???>';
const loc = normalizeLocation( location );
let filename = (loc.file && normalizeFilename)
? loc.file.replace( /\\/g, '/' )
: loc.file;
if (!(loc instanceof Object))
return loc;
if (!loc.line) {
return filename;
}
else if (!loc.endLine) {
return (loc.col)
? `${filename}:${loc.line}:${loc.col}`
: `${filename}:${loc.line}`;
}
else {
return (loc.line === loc.endLine)
? `${filename}:${loc.line}:${loc.col}-${loc.endCol}`
: `${filename}:${loc.line}.${loc.col}-${loc.endLine}.${loc.endCol}`;
}
}
/**
* Class for combined compiler errors. Additional members:

@@ -145,4 +97,2 @@ * `errors`: vector of errors (CompileMessage and errors from peg.js)

/** @type {object} model */
this.model; // TODO: what is this for?
/** @type {boolean} model */

@@ -181,3 +131,3 @@ this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics

this.message = msg;
this.location = normalizeLocation( location );
this.location = location;
this.$location = dollarLocation( this.location );

@@ -217,4 +167,4 @@ this.validNames = null;

};
copyPropIfExist(loc, 'endLine', location);
copyPropIfExist(loc, 'endCol', location);
copyPropIfExist(location, 'endLine', loc);
copyPropIfExist(location, 'endCol', loc);
// TODO:

@@ -272,7 +222,2 @@ // return {

function reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable ) {
// The following is a bug workaround: the input severity (at least 'Error')
// must be as provided centrally for that module - TODO: test with testMode,
// probably also that error() is only used if message is always `Error` in that module
if (severity === 'Error')
return 'Error';
const spec = centralMessages[id] || { severity };

@@ -381,8 +326,4 @@ if (spec.severity === 'Error') {

// ensure message consistency during runtime with --test-mode
if (options.testMode && !options.severities && !test$severities) {
test$severities = Object.create(null);
test$texts = Object.create(null);
Object.entries( centralMessages ).forEach( test$initSeverities );
Object.entries( centralMessageTexts ).forEach( test$initTexts );
}
if (options.testMode)
_check$Init( options );

@@ -415,3 +356,4 @@ const hasMessageArray = !!options.messages;

if (id) {
_checkId(id, severity);
if (options.testMode && !options.$recompile)
_check$Consistency( id, moduleName, severity, texts, options )
severity = reclassifiedSeverity( id, severity, severities, moduleName, deprecatedDowngradable );

@@ -433,10 +375,2 @@ }

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

@@ -492,4 +426,4 @@ }

if (typeof location === 'object' && !Array.isArray(location))
// CSN/CSN.Location (with line/endLine, col/endCol)
return [ normalizeLocation(location), location.home || null, null ]
// CSN.Location (with line/endLine, col/endCol)
return [ location, location.home || null, null ]

@@ -520,27 +454,2 @@ const isCsnPath = (typeof location[0] === 'string');

/**
* Check that the central message severity matches the given one.
*
* @param {string} id
* @param {CSN.MessageSeverity} severity
*/
function _checkId(id, severity) {
if (!options.testMode || !severity)
return;
if (!centralMessages[id]) {
centralMessages[id] = { severity, throughMessageCall: true };
}
else if (centralMessages[id].severity !== severity) {
// TODO: Enable if getMessageFunction() is removed because that function
// does message reclassification before calling _message();
//
// if (centralMessages[id].throughMessageCall) {
// throw new Error(`Mismatch for message '${ id }' between provided severity '${ severity }' and previous call with '${ centralMessages[id].severity }'`)
// } else {
// throw new Error(`Mismatch for message '${ id }' between provided severity '${ severity }' and central one '${ centralMessages[id].severity }'`)
// }
}
}
/**
* Create a compiler message for model developers.

@@ -633,2 +542,62 @@ *

/**
* Perform message consistency check during runtime with --test-mode
*/
function _check$Init( options ) {
if (!test$severities && !options.severities)
test$severities = Object.create(null);
if (!test$texts) {
test$texts = Object.create(null);
for (const [id, texts] of Object.entries( centralMessageTexts ))
test$texts[id] = (typeof texts === 'string') ? { std: texts } : { ...texts };
}
}
function _check$Consistency( id, moduleName, severity, texts, options ) {
if (id.length > 30 && !centralMessages[id])
throw new Error( `The message ID "${id}" has more than 30 chars and must be listed centrally` );
if (!options.severities)
_check$Severities( id, moduleName || '?', severity );
for (const [variant, text] of
Object.entries( (typeof texts === 'string') ? { std: texts } : texts || {} ))
_check$Texts( id, variant, text );
}
function _check$Severities( id, moduleName, severity ) {
if (!severity) // if just used message(), we are automatically consistent
return;
const spec = centralMessages[id];
if (!spec) {
const expected = test$severities[id];
if (!expected)
test$severities[id] = severity;
else if (expected !== severity)
throw new Error( `Expecting severity "${expected}" from previous call, not "${severity}" for message ID "${id}"` );
return;
}
// now try whether the message could be something less than an Error in the module due to user wishes
const user = reclassifiedSeverity( id, null, { [id]: 'Info' }, moduleName, false );
if (user === 'Error') { // always an error in module
if (severity !== 'Error')
throw new Error( `Expecting severity "Error", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}
else if (spec.severity === 'Error') {
throw new Error( `Expecting the use of function message() when message ID "${id}" is a configurable error in module "${moduleName}"` );
}
else if (spec.severity !== severity) {
throw new Error( `Expecting severity "${spec.severity}", not "${severity}" for message ID "${id}" in module "${moduleName}"` );
}
}
function _check$Texts( id, prop, value ) {
if (!test$texts[id])
test$texts[id] = Object.create(null);
const expected = test$texts[id][prop];
if (!expected)
test$texts[id][prop] = value;
else if (expected !== value)
throw new Error( `Expecting text "${expected}", not "${value}" for message ID "${id}" and text variant "${prop}"`);
}
const quote = { // could be an option in the future

@@ -664,2 +633,3 @@ name: n => `“${ n }”`,

target: transformArg,
elemref: transformElementRef,
type: transformArg,

@@ -700,2 +670,21 @@ offending: tokenSymbol,

/**
* Transform an element reference (/path), e.g. on-condition path.
*/
function transformElementRef(arg) {
if (arg.ref) {
// Can be used by CSN backends to create a simple path such as E:elem
return quoted(arg.ref.map(ref => {
if (ref.id) {
// Indicate that the path has a filter.
if (ref.where)
return `${ ref.id }[…]`;
return ref.id;
}
return ref;
}).join('.'));
}
return quoted(arg);
}
function transformArg( arg, r, args, texts ) {

@@ -710,2 +699,8 @@ if (!arg || typeof arg !== 'object')

return shortArtName( arg );
if (arg.ref) {
// Can be used by CSN backends to create a simple path such as E:elem
if (arg.ref.length > 1)
return quoted(arg.ref[0] + ':' + arg.ref.slice(1).join('.'));
return quoted(arg.ref);
}
let name = arg.name;

@@ -883,3 +878,3 @@ if (!name)

const loc = normalizeLocation(err.$location);
const loc = err.$location;
if (!loc || !loc.line) {

@@ -903,3 +898,4 @@ return '';

// Columns are limited in width to avoid too long output.
let startColumn = Math.min(MAX_COL_LENGTH, loc.col);
// "col" is 1-based but could still be set to 0, e.g. by CSN frontend.
const startColumn = Math.min(MAX_COL_LENGTH, loc.col || 1);
// end column points to the place *after* the last character index,

@@ -926,7 +922,8 @@ // e.g. for single character locations it is "start + 1"

if (startLine === endLine) {
// highlight only for one-line location; at least one character is highlighted
if (startLine === endLine && loc.col > 0) {
// highlight only for one-line locations with valid columns
// at least one character is highlighted
let highlighter = ' '.repeat(startColumn - 1).padEnd(endColumn, '^');
// Indicate that the error is further to the right.
if (endColumn == MAX_COL_LENGTH)
if (endColumn === MAX_COL_LENGTH)
highlighter = highlighter.replace(' ^', '..^');

@@ -1131,2 +1128,224 @@ msg += indent + '| ' + term.asSeverity(severity, highlighter);

function constructSemanticLocationFromCsnPath(csnPath, model) {
if (!model)
return null;
// Copy because this function shift()s from the path.
csnPath = [ ...csnPath ];
const csnDictionaries = [
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'definitions',
];
const queryProps = [ 'from', 'where', 'groupBy', 'having', 'orderBy', 'limit', 'offset' ];
let { query } = analyseCsnPath(
csnPath,
model
);
// remove definitions
csnPath.shift();
const artName = csnPath.shift();
let currentThing = model.definitions[artName];
let result = `${ (currentThing && currentThing.kind) ? currentThing.kind : 'artifact' }:${ _quoted(artName) }`;
if (!currentThing)
return result;
if (query)
query = queryDepth(currentThing.query || { SELECT: currentThing.projection }, query);
const elements = [];
let inCsnDict = false;
let inElement = false;
let inAction = false;
let inParam = false;
let inKeys = false;
let inRef = false;
let inEnum = false;
let inQuery = false;
let inColumn = false;
let inMixin = false;
let inItems = false;
// for top level actions
if (currentThing.kind === 'action')
inAction = true;
for (const [ index, step ] of csnPath.entries()) {
currentThing = currentThing[step];
if (csnDictionaries.includes(step) && !inCsnDict) {
inCsnDict = true;
switch (step) {
case 'elements':
if (!inElement){
inElement = true;
// do not print intermediate items
inItems = false;
}
break;
case 'actions':
inAction = true;
break;
case 'params':
inParam = true;
break;
case 'enum':
inElement = false;
inEnum = true;
break;
case 'mixin':
inMixin = true;
inQuery = false;
break;
default:
if (inElement) {
// close element
result += element();
inElement = false;
}
}
}
else if ( inQuery ) {
if (step === 'SELECT') {
if (!csnPath[index + 1]) {
result += select();
}
else if (queryProps.includes(csnPath[index + 1]) && !csnPath[index + 2]) {
const clause = csnPath[index + 1];
result += select();
result += `/${ clause }`;
}
}
else if (step === 'columns') {
result += select();
result += '/column';
inColumn = true;
inQuery = false;
}
}
else if ( inMixin ) {
if (step === 'on') {
result += '/on';
break;
}
else {
result += selectAndMixin(step);
}
}
else if (inEnum) {
result += elementAndEnum(step);
}
else if (!inElement && step === 'query') {
inQuery = true;
}
else if (inElement && step === 'keys') {
// close element
result += `${ element() }/key`;
inElement = false;
inKeys = true;
}
else if (inElement && step === 'on') {
// close element
result += `${ element() }/on`;
inElement = false;
break;
}
else if (inElement && step === 'items') {
// this is an element called items
if (csnPath[index - 1] === 'elements' && elements[elements.length - 1] !== 'elements') {
elements.push(step);
}
else {
inElement = false;
inItems = true;
}
}
else if (inElement && step === 'elements') {
// this is an element called elements
if (csnPath[index - 1] === 'elements')
elements.push(step);
}
else if (inItems && step === 'elements') {
inElement = true;
inItems = false;
}
else if ( inKeys || inColumn) {
if (typeof step === 'number') {
if (currentThing.as)
result += `:${ _quoted(currentThing.as) }`;
else
result += inRef ? `:${ _quoted(currentThing) }` : currentThing.ref ? `:${ _quoted(currentThing.ref.join('.')) }` : '';
break;
}
if ( step === 'ref')
inRef = true;
}
else if (inAction && step === 'returns') {
result += `/${ step }`;
break;
}
else if (inCsnDict) {
if (inElement)
elements.push(step);
else if (inParam)
result += param(step);
else if (inAction)
result += func(step);
inCsnDict = false;
}
}
if ( inItems )
result += `${ element() }/items`;
else if ( inElement )
result += element();
return result;
function select() {
let s = '/select';
s += query.isOnlySelect ? '' : `:${ query.depth }`;
return s;
}
function selectAndMixin(name) {
return `${ select() }/mixin:${ _quoted(name) }`;
}
function element() {
return `/element:${ _quoted(elements.join('.')) }`;
}
function param(name) {
return `/param:${ _quoted(name) }`;
}
function func(name) {
return `/function:${ _quoted(name) }`;
}
function elementAndEnum(name) {
return `${ element() }/enum:${ _quoted(name) }`;
}
/**
* Traverse rootQuery until targetQuery is found and count the depth,
* check if targetQuery is only select in entity.
*/
function queryDepth(rootQuery, targetQuery) {
let targetQueryDepth = 1;
let totalQueryDepth = 0;
let isFound = false;
traverseQuery(rootQuery, null, (q, querySelect) => {
if ( querySelect )
totalQueryDepth += 1;
if ( querySelect && !isFound)
targetQueryDepth += 1;
if (q === targetQuery)
isFound = true;
});
return { depth: targetQueryDepth, isOnlySelect: totalQueryDepth === 1 };
}
}
function _quoted( name ) {
return (name) ? `"${ name.replace( /"/g, '""' ) }"` : '<?>'; // sync ";
}
/**

@@ -1181,2 +1400,3 @@ * Get the explanation string for the given message-id.

messageIdsWithExplanation,
constructSemanticLocationFromCsnPath,
};

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

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

@@ -30,2 +31,3 @@ toRename: true,

ignoreAssocPublishingInUnion: true,
nestedProjections: true,
// disabled by --beta-mode

@@ -32,0 +34,0 @@ pretransformedCSN: false,

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

// $ node cdsc.js -x 1 --foo toXyz -y --bar-wiz bla arg1 arg2
//
// The following definitions should be made
//
// const optionProcessor = createOptionProcessor();

@@ -17,2 +19,3 @@ // optionProcessor

// .option(' --bar-wiz <w>', ['bla', 'foo'])
//
// Options *must* have a long form, can have at most one <param>, and optionally

@@ -65,2 +68,3 @@ // an array of valid param values as strings. Commands and param values must not

function command(cmdString) {
/** @type {object} */
const command = {

@@ -70,5 +74,9 @@ options: {},

help,
..._parseCommandString(cmdString)
};
if (optionProcessor.commands[command.longName]) {
throw new Error(`Duplicate assignment for long command ${command.longName}`);
}
Object.assign(command, _parseCommandString(cmdString));
optionProcessor.commands[command.longName] = command;
if (command.shortName) {

@@ -315,3 +323,8 @@ if (optionProcessor.commands[command.shortName]) {

for (let i = 2; i < argv.length; i++) {
const arg = argv[i];
let arg = argv[i];
// To be compatible with NPM arguments, we need to support `--arg=val` as well.
if (arg.includes('=')) {
argv = [ ...argv.slice(0, i), ...arg.split('='), ...argv.slice(i + 1)];
arg = argv[i];
}
if (!seenDashDash && arg.startsWith('-') && arg !== '--') {

@@ -393,2 +406,4 @@ if (result.command) {

function processPositionalArgument(argumentValue) {
if ( result.args.length === 0 && optionProcessor.positionalArguments.length === 0 )
return;
const inBounds = result.args.length < optionProcessor.positionalArguments.length;

@@ -480,3 +495,3 @@ const lastIndex = inBounds ? result.args.length : optionProcessor.positionalArguments.length - 1;

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

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

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

{
std: 'Artifact parameters can\'t have a default value', // Not used
action: 'Action parameters can\'t have a default value',

@@ -67,2 +68,3 @@ function: 'Function parameters can\'t have a default value',

{
std: 'An association is not allowed as this artifact\'s parameter type', // Not used
action: 'An association is not allowed as action\'s parameter type',

@@ -93,2 +95,3 @@ function: 'An association is not allowed as function\'s parameter type',

{
std: 'An association is not allowed as this artifact\'s return type', // Not used
action: 'An association is not allowed as action\'s return type',

@@ -95,0 +98,0 @@ function: 'An association is not allowed as function\'s return type',

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

const { isGeoTypeName } = require('../compiler/builtins');
const { isBetaEnabled } = require('../base/model');

@@ -41,6 +42,7 @@ // Only to be used with validator.js - a correct `this` value needs to be provided!

const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType))
if (typeof finalBaseType === 'string' && isGeoTypeName(finalBaseType)) {
this.error(null, parentPath || member.$path,
{ type: finalBaseType, name: elemFqName },
'Type $(TYPE) can\'t be used as primary key in element $(NAME)');
}
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {

@@ -68,5 +70,6 @@ forEachMemberRecursively(finalBaseType,

const finalBaseType = this.csnUtils.getFinalBaseType(member.type);
if (member.items || (finalBaseType && finalBaseType.items))
if (member.items || (finalBaseType && finalBaseType.items)) {
this.error(null, parentPath || member.$path, { name: elemFqName },
'Array-like type in element $(NAME) can\'t be used as primary key');
}
else if (finalBaseType && this.csnUtils.isStructured(finalBaseType)) {

@@ -99,4 +102,5 @@ forEachMemberRecursively(finalBaseType,

/**
* Checks whether managed associations have keys and whether managed associations
* with cardinality 'to many' have on-condition.
* Checks whether managed associations
* with cardinality 'to many' have an on-condition
* and if managed associations have foreign keys.
*

@@ -111,5 +115,5 @@ * @param {CSN.Artifact} art The artifact

return;
if (!member.keys) {
this.error(null, member.$path, { target: member.target, name: memberName },
`The target $(TARGET) of the managed association $(NAME) does not have keys`);
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && (!member.keys || member.keys.length === 0)) {
this.error(null, member.$path, { name: memberName },
`The managed association $(NAME) has no foreign keys`);
}

@@ -137,2 +141,4 @@ const max = member.cardinality && member.cardinality.max;

return true;
if (!member.target)
return false;
const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);

@@ -139,0 +145,0 @@ return target.kind !== 'entity';

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

includes: simpleRef,
// Annotations are ignored.
'@': () => {},
};

@@ -50,3 +52,3 @@ let cleanupCallbacks = [];

function standard( parent, prop, node ) {
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')))
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))
return;

@@ -63,3 +65,3 @@

for (const name of Object.getOwnPropertyNames( node )) {
const trans = transformers[name] || standard;
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
trans( node, name, node[name] );

@@ -66,0 +68,0 @@ }

'use strict';
const { isBetaEnabled } = require('../base/model');
// Only to be used with validator.js - a correct this value needs to be provided!

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

const handleAssociation = (mem) => {
let elementCount = 0;
for (let i = 0; i < mem.keys.length; i++) {

@@ -28,3 +31,6 @@ if (mem.keys[i].ref) {

checkForItems(mem.keys[i]._art);
elementCount++;
}
if (!isBetaEnabled(this.options, 'keylessManagedAssoc') && elementCount === 0)
this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
}

@@ -35,3 +41,2 @@ };

const handleStructured = (mem) => {
let elementCount = 0;
for (const elementName of Object.keys(mem.elements)) {

@@ -41,7 +46,3 @@ const element = mem.elements[elementName];

checkForItems(element);
elementCount++;
}
if (elementCount === 0)
this.error(null, member.$path, 'Empty structured types/elements must not be used as foreign keys');
};

@@ -48,0 +49,0 @@

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

const id = logReady(ref[j]);
const target = ref.map(ps => logReady(ps)).join('.');
const elemref = { ref };

@@ -123,3 +123,3 @@ if (_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))) {

// It's an unmanaged association - traversal is always forbidden
this.error(null, csnPath, { id, target }, 'ON-Conditions can\'t follow unmanaged associations, step $(ID) of path $(TARGET)');
this.error(null, csnPath, { id, elemref }, 'ON-Conditions can\'t follow unmanaged associations, step $(ID) of path $(ELEMREF)');
}

@@ -130,3 +130,3 @@ else {

if (!_links[j].art.keys.some(r => r.ref[0] === nextRef))
this.error(null, csnPath, { id, target }, 'ON-Conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(TARGET)');
this.error(null, csnPath, { id, elemref }, 'ON-Conditions can only follow managed associations to the foreign keys of the managed association, step $(ID) of path $(ELEMREF)');
}

@@ -136,9 +136,9 @@ }

if (_links[j].art.virtual)
this.error(null, csnPath, { id, target }, 'Virtual elements can\'t be used in ON-Conditions, step $(ID) of path $(TARGET)');
this.error(null, csnPath, { id, elemref }, 'Virtual elements can\'t be used in ON-Conditions, step $(ID) of path $(ELEMREF)');
if (ref[j].where)
this.error(null, csnPath, { id, target }, 'ON-Conditions must not contain filters, step $(ID) of path $(TARGET)');
this.error(null, csnPath, { id, elemref }, 'ON-Conditions must not contain filters, step $(ID) of path $(ELEMREF)');
if (ref[j].args)
this.error(null, csnPath, { id, target }, 'ON-Conditions must not contain parameters, step $(ID) of path $(TARGET)');
this.error(null, csnPath, { id, elemref }, 'ON-Conditions must not contain parameters, step $(ID) of path $(ELEMREF)');
}

@@ -148,3 +148,2 @@ if (_art && $scope !== '$self') {

// For error messages
const target = ref.map(ps => logReady(ps)).join('.');
const onPath = path.concat([ 'on', i, 'ref', ref.length - 1 ]);

@@ -161,12 +160,12 @@ // Paths of an ON condition may end on a structured element or an association only if:

!_art.virtual) {
this.error(null, onPath, { target },
'The last path of an on-condition must be a scalar value, path $(TARGET)');
this.error(null, onPath, { elemref: { ref } },
'The last path of an on-condition must be a scalar value, path $(ELEMREF)');
}
else if (_art.items && !_art.virtual) {
this.error(null, onPath, { target },
'ON-Conditions can\'t use array-like elements, path $(TARGET)');
this.error(null, onPath, { elemref: { ref } },
'ON-Conditions can\'t use array-like elements, path $(ELEMREF)');
}
else if (_art.virtual) {
this.error(null, onPath, { target },
'Virtual elements can\'t be used in ON-Conditions, path $(TARGET)');
this.error(null, onPath, { elemref: { ref } },
'Virtual elements can\'t be used in ON-Conditions, path $(ELEMREF)');
}

@@ -189,2 +188,3 @@ }

}
module.exports = { validateOnCondition, validateMixinOnCondition };

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

*
* @param {CSN.Artifact | CSN.Element} artOrElement can either be an element or a type definition
* @param {object} artOrElement can either be an element or a type definition
* @param {string} name the name of the element or of the artifact

@@ -104,0 +104,0 @@ * @param {CSN.Model} model the csn model in which the element/artifact resides

'use strict';
const {
forEachDefinition, forEachMemberRecursively, forAllQueries, forEachMember,
forEachDefinition, forEachMemberRecursively, forAllQueries,
forEachMember, getNormalizedQuery, hasBoolAnnotation,
} = require('../model/csnUtils');

@@ -29,2 +30,4 @@ const enrich = require('./enricher');

const { validateAssociationsInItems } = require('./arrayOfs');
const checkQueryForNoDBArtifacts = require('./queryNoDbArtifacts');
const checkExplicitlyNullableKeys = require('./nullableKeys');

@@ -36,2 +39,3 @@ const forHanaMemberValidators

checkTypeIsScalar,
checkExplicitlyNullableKeys,
];

@@ -51,2 +55,3 @@

validateSelectItems,
checkQueryForNoDBArtifacts,
];

@@ -67,2 +72,6 @@

// NOTE: moved to the renderer for a while
// TODO: Re-enable this code and remove the duplicated code from the renderer.
// Not possible at the moment, because running this at the beginning of
// the renderer does not work because the enricher can't handle certain
// OData specifics.
// checkChainedArray,

@@ -107,3 +116,4 @@ checkForMultipleCoreMediaTypes,

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

@@ -116,4 +126,4 @@ memberValidators.map(v => v.bind(that)),

if (queryValidators.length && artifact.query)
forAllQueries(artifact.query, queryValidators.map(v => v.bind(that)), path.concat([ 'query' ]));
if (queryValidators.length && getNormalizedQuery(artifact).query)
forAllQueries(getNormalizedQuery(artifact).query, queryValidators.map(v => v.bind(that)), path.concat([ 'query' ]));
}, iterateOptions);

@@ -145,2 +155,3 @@

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

@@ -147,0 +158,0 @@ 'action',

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

'_effectiveType', 'elements', '_origin', '_joinParent', '$joinArgsIndex',
'$duplicates', // duplicate query in FROM clause
],

@@ -295,2 +296,4 @@ },

},
expand: { kind: [ 'element' ], inherits: 'columns' },
inline: { kind: [ 'element' ], inherits: 'columns' },
excludingDict: {

@@ -424,3 +427,3 @@ test: isDictionary( definition ), // definition since redef

inherits: 'value',
optional: [ 'name', '$duplicate' ],
optional: [ 'name', '$duplicate', '$expected' ],
test: args,

@@ -542,2 +545,3 @@ },

_origin: { kind: [ 'entity' ], test: TODO },
_pathHead: { kind: [ 'element' ], test: TODO },
_from: { kind: true, test: TODO }, // all table refs necessary to compute elements

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

$withLocalized: { test: isBoolean },
$expected: { parser: true, test: isString },
};

@@ -882,5 +887,5 @@ let _noSyntaxErrors = null;

function TODO() {}
function TODO() { /* no-op */ }
}
module.exports = assertConsistency;

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

Decimal: { parameters: [ 'precision', 'scale' ], category: 'decimal' },
DecimalFloat: { category: 'decimal' },
DecimalFloat: { category: 'decimal', deprecated: true },
Integer64: { category: 'integer' },

@@ -91,3 +91,5 @@ Integer: { category: 'integer' },

$at: {
elements: { from: {}, to: {} },
elements: {
from: {}, to: {},
},
},

@@ -94,0 +96,0 @@ $now: {}, // Dito

@@ -72,22 +72,8 @@ // Checks on XSN performed during compile()

if (construct.name.id && construct.name.id.indexOf('.') !== -1) {
// No, we should not forbid this
// TODO: No, we should not forbid this
error(null, [ construct.name.location, construct ],
'The character \'.\' is not allowed in identifiers');
}
// TODO: Move this check to definer
if (construct.kind === 'namespace' && construct.name.absolute === 'localized')
forEachGeneric( construct, '_subArtifacts', checkLocalizedObjects );
}
// TODO: remove (see checkName above)
function checkLocalizedObjects( artifact ) {
if (artifact.kind === 'namespace') {
forEachGeneric( artifact, '_subArtifacts', checkLocalizedObjects );
}
else if (!artifact.query) {
error(null, [ artifact.name.location, artifact ],
'The namespace "localized" is reserved for localization views');
}
}
// TODO: move into definer.js

@@ -214,9 +200,5 @@ function checkLocalizedElement(elem) {

checkEnumValueType(enumNode);
if (builtins.isNumericTypeName(name))
checkNumericEnumHasValues(enumNode);
checkEnumValue(enumNode);
}
// TODO: share value-type check with that of annotation assignments
/**

@@ -229,3 +211,3 @@ * Check the given enum's elements and their values. For example

*/
function checkEnumValueType(enumNode) {
function checkEnumValue(enumNode) {
const type = enumNode.type && enumNode.type._artifact &&

@@ -239,3 +221,18 @@ enumNode.type._artifact._effectiveType;

// Only string and numeric enums are allowed. Other checks handle other types.
if (!isString) {
// Non-string enums MUST have a value as the value is only deducted for string types.
const emptyValue = Object.keys(enumNode.enum).find(name => !enumNode.enum[name].value);
if (emptyValue) {
const failedEnum = enumNode.enum[emptyValue];
warning('enum-missing-value', [ failedEnum.location, failedEnum ],
{ '#': isNumeric ? 'numeric' : 'std', name: emptyValue },
{
std: 'Missing value for non-string enum element $(NAME)',
numeric: 'Missing value for numeric enum element $(NAME)',
});
}
}
// We only check string and numeric value types.
// TODO: share value-type check with that of annotation assignments
if (!isString && !isNumeric)

@@ -260,2 +257,3 @@ return;

{ '#': expectedType, name: key, prop: actualType }, {
std: 'Incorrect value type $(PROP) for enum element $(NAME)', // Not used
number: 'Expected numeric value for enum element $(NAME) but was $(PROP)',

@@ -267,27 +265,2 @@ string: 'Expected string value for enum element $(NAME) but was $(PROP)',

// TODO: correct: do it the other way round - value is required except with string-alike
/**
* Run checks on the whole enum type, e.g. that elements have a value.
*
* @param {XSN.Artifact} enumNode
*/
function checkNumericEnumHasValues(enumNode) {
const type = enumNode.type && enumNode.type._artifact &&
enumNode.type._artifact._effectiveType;
if (!enumNode.enum || !type || !type.builtin)
return;
// Currently only run check for numeric enums.
if (!builtins.isNumericTypeName(type.name.absolute))
return;
const failedAt = Object.keys(enumNode.enum).find(name => !enumNode.enum[name].value);
if (!failedAt)
return;
const failedEnum = enumNode.enum[failedAt];
warning('enum-missing-value', [ failedEnum.location, failedEnum ],
{ name: failedAt }, 'Missing value for numeric enum element $(NAME)');
}
// TODO: check inside compiler as it is a compiler restriction - improve

@@ -317,11 +290,12 @@ /**

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

@@ -377,3 +351,5 @@

// Min cardinality must be a non-negative number (already checked by parser)
// Min cardinality must be a non-negative number
// Note: Already checked by parser (syntax error if -1 is used) and
// from-csn.json (expected non-negative number)
for (const prop of [ 'sourceMin', 'targetMin' ]) {

@@ -503,3 +479,2 @@ if (elem.cardinality[prop]) {

const missingArgs = [];
const unknownArgs = [];
for (const fAName in formalArgs) {

@@ -509,6 +484,2 @@ if (!actualArgs[fAName] && !formalArgs[fAName].default)

}
for (const aAName in actualArgs) {
if (!formalArgs[aAName])
unknownArgs.push(aAName);
}

@@ -520,8 +491,5 @@ if (missingArgs.length) {

}
// already checked in resolver
if (unknownArgs.length) {
error(null, [ pathStep.location, pathStep ],
{ names: unknownArgs, expected: expectedNames.length, given: aArgsCount },
'Expected $(EXPECTED) arguments but $(GIVEN) given; unknown: $(NAMES)');
}
// Note:
// Unknown arguments are already handled by messages
// args-expected-named and args-undefined-param
}

@@ -645,12 +613,11 @@ }

* @param {any} xpr The expression to check
* @param {Boolean} inOnCond
* @param {Boolean} allowAssocTail
* @returns {void}
*/
function checkExpression(xpr, inOnCond = false) {
function checkExpression(xpr, allowAssocTail = false) {
// Since the checks for tree-like and token-stream expressions differ,
// check here what kind of expression we are looking at
if (xpr.op && xpr.op.val === 'xpr')
return checkTokenStreamExpression(xpr, inOnCond);
return checkTreeLikeExpression(xpr, inOnCond);
return checkTokenStreamExpression(xpr, allowAssocTail);
return checkTreeLikeExpression(xpr, allowAssocTail);
}

@@ -677,3 +644,3 @@ /**

*/
function checkTokenStreamExpression(xpr, inOnCond) {
function checkTokenStreamExpression(xpr, allowAssocTail) {
// Check for illegal argument usage within the expression

@@ -686,3 +653,3 @@ for (const arg of xpr.args || []) {

// Recursively traverse the argument expression
checkTokenStreamExpression(arg, inOnCond);
checkTokenStreamExpression(arg, allowAssocTail);
}

@@ -697,3 +664,3 @@ }

*/
function checkTreeLikeExpression(xpr, inOnCond) {
function checkTreeLikeExpression(xpr, allowAssocTail) {
// No further checks regarding associations and $self required if this is a

@@ -710,4 +677,10 @@ // backlink-like expression (a comparison of $self with an assoc)

// Arg must not be an association and not $self
if (!inOnCond && isAssociationOperand(arg))
// Only if path is not approved exists path (that is non-query position)
if (arg.path && arg.$expected !== undefined) { // not 'approved-exists'
if (arg.$expected === 'exists')
error(null, arg.location, 'An association can\'t be used as a value in an expression');
}
else if (!allowAssocTail && isAssociationOperand(arg)) {
error(null, arg.location, 'An association can\'t be used as a value in an expression');
}

@@ -717,5 +690,4 @@ if (isDollarSelfOrProjectionOperand(arg))

// Recursively traverse the argument expression
checkTreeLikeExpression(arg, inOnCond);
checkTreeLikeExpression(arg, allowAssocTail);
}

@@ -722,0 +694,0 @@ }

@@ -5,3 +5,3 @@ // Main XSN-based compiler functions

const { resolveModule, resolveModuleSync } = require('../utils/resolve');
const { resolveModule, resolveModuleSync } = require('../utils/moduleResolve');
const parseLanguage = require('../language/antlrParser');

@@ -57,6 +57,8 @@ const parseCsn = require('../json/from-csn');

const ext = path.extname( filename ).toLowerCase();
if ([ '.json', '.csn' ].includes(ext)) {
if ([ '.json', '.csn' ].includes(ext) || (options.fallbackParser === 'csn')) {
return parseCsn.parse( source, filename, options );
}
else if (options.fallbackParser || [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext)) {
else if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext) || options.fallbackParser) {
// Note: Historically, all truthy values for options.fallbackParser were interpreted as 'cdl'.
// To not break existing programs, we do the same if it's not set to `csn`.
return parseLanguage( source, filename, options );

@@ -205,4 +207,3 @@ }

/**
* Synchronous version of function `compile` with limited functionality:
* - option `--follow-deps` is not supported,
* Synchronous version of function `compile`
* - an invocation error ends the compilation immediately.

@@ -209,0 +210,0 @@ *

@@ -21,2 +21,3 @@ //

'@cds.persistence.udf': never,
'@cds.persistence.skip': notWithPersistenceTable,
'@Analytics.hidden': never,

@@ -148,3 +149,3 @@ '@Analytics.visible': never,

function never() {}
function never() { /* no-op: don't propagate */ }

@@ -219,2 +220,8 @@ function always( prop, target, source ) {

function notWithPersistenceTable( prop, target, source ) {
const tableAnno = target['@cds.persistence.table'];
if (!tableAnno || tableAnno.val === null)
withKind( prop, target, source );
}
function withKind( prop, target, source ) {

@@ -257,3 +264,5 @@ if (target.kind && (!target._parent || target._parent.returns !== target))

if (art._origin)
return art._origin;
return !art.expand && art._origin;
// Remark: a column with an 'inline' is never an element -> no need to check
// art.inline
if (art._from && art._from.length) { // query

@@ -260,0 +269,0 @@ const tabref = art._from[0]._artifact;

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

$navElement: { normalized: 'element' },
$inline: { normalized: 'element' }, // column with inline property
event: { elements: true },

@@ -82,2 +83,3 @@ type: { elements: propExists, enum: propExists },

function fns( model, environment = artifactsEnv ) {
/** @type {CSN.Options} */
const options = model.options || {};

@@ -174,2 +176,8 @@ const {

},
exists: { // same as expr
next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
},
'approved-exists': { // same as expr
next: '_$next', dollar: true, escape: 'param', assoc: 'nav',
},
on: { // TODO: there will also be a 'from-on' (see 'expr')

@@ -207,2 +215,3 @@ noAliasOrMixin: true, // TODO: some headReject or similar

defineAnnotations,
attachAndEmitValidNames,
};

@@ -348,2 +357,5 @@

}
else if (user._pathHead) {
// eslint-disable-next-line no-empty
}
else if (art.kind === 'using') {

@@ -393,3 +405,3 @@ art = model.definitions[art.name.absolute];

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

@@ -502,2 +514,6 @@ return setLink( ref, art );

function getPathRoot( path, spec, user, env, extDict, msgArt ) {
if (user._pathHead) { // TODO: not necessarily for explicit ON condition in expand
environment( user._pathHead ); // make sure _origin is set
return user._pathHead._origin;
}
const head = path[0];

@@ -547,3 +563,3 @@ if (!head || !head.id || !env)

}
else if (r.kind !== '$tableAlias' || path.length > 1) {
else if (r.kind !== '$tableAlias' || path.length > 1 || user.expand || user.inline) {
// except "real" table aliases (not $self) with path len 1

@@ -631,4 +647,4 @@ // TODO: $projection only if not delimited _and_ length > 1

// element item in the path)
function getPathItem( path, spec, user, artItemsCount ) {
let art;
function getPathItem( path, spec, user, artItemsCount, headArt ) {
let art = headArt;
let nav = spec.assoc !== '$keys' && null; // false for '$keys'

@@ -640,54 +656,53 @@ const last = path[path.length - 1];

return undefined;
if (item._artifact) { // should be there on first path element
if (item._artifact) { // should be there on first path element (except with expand)
art = item._artifact;
if (Array.isArray(art))
return false;
continue;
}
else if (art && art.$uncheckedElements) {
if (art && art.$uncheckedElements) {
// do not check any elements of the path, e.g. $session
return art;
}
else {
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
const env = fn( art, item.location, user, spec.assoc );
const sub = setLink( item, env && env[item.id] );
if (!sub)
return (sub === 0) ? 0 : errorNotFound( item, env );
else if (Array.isArray(sub)) // redefinitions
return false;
const fn = (spec.envFn && artItemsCount >= 0) ? spec.envFn : environment;
const env = fn( art, item.location, user, spec.assoc );
const sub = setLink( item, env && env[item.id] );
if (!sub)
return (sub === 0) ? 0 : errorNotFound( item, env );
else if (Array.isArray(sub)) // redefinitions
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) // TODO: or use _origin?
orig = o;
nav = (orig._effectiveType || orig).$keysNavigation;
nav = setTargetReferenceKey( orig.name.id, item );
}
art = sub;
if (spec.envFn && (!artItemsCount || item === last) &&
art && art.$inferred === 'autoexposed' && !user.$inferred) {
// Depending on the processing sequence, the following could be a
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
// could "change" to this message at the end of compile():
message( 'ref-autoexposed', [ item.location, user ], { art },
// eslint-disable-next-line max-len
'An autoexposed entity can\'t be referred to - expose entity $(ART) explicitly' );
}
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.
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) // TODO: or use _origin?
orig = o;
nav = (orig._effectiveType || orig).$keysNavigation;
setTargetReferenceKey( orig.name.id, item );
}
art = sub;
if (spec.envFn && (!artItemsCount || item === last) &&
art && art.$inferred === 'autoexposed' && !user.$inferred) {
// Depending on the processing sequence, the following could be a
// simple 'ref-undefined-art'/'ref-undefined-def' - TODO: which we
// could "change" to this message at the end of compile():
message( 'ref-autoexposed', [ item.location, user ], { art },
// eslint-disable-next-line max-len
'An autoexposed entity can\'t be referred to - expose entity $(ART) explicitly' );
}
}

@@ -752,3 +767,3 @@ return art;

* @param {any} location
* @param {any} valid
* @param {object[]} valid
* @param {object} [textParams]

@@ -758,3 +773,2 @@ * @param {any} [texts]

function signalNotFound(msgId, location, valid, textParams, texts ) {
// if (!location) console.log(msgId, valid, textParams, texts)
if (location.$notFound)

@@ -765,9 +779,31 @@ return;

const err = message( msgId, location, textParams, texts );
// console.log( Object.keys( Object.assign( Object.create(null), ...valid.reverse() ) ) )
if (valid && (options.attachValidNames || options.testMode))
err.validNames = Object.assign( Object.create(null), ...valid.reverse() );
// TODO: remove internal, i.e. cds.Association
if (options.testMode && valid) {
const names = Object.keys( err.validNames );
info( null, location[0] || location, // no semantic location
if (valid)
attachAndEmitValidNames(err, ...valid.reverse());
}
/**
* Attaches a dictionary of valid names to the given compiler message.
* In test mode, an info message is emitted with a list of valid names.
*
* @param {CSN.Message} msg CDS Compiler message
* @param {...object} validDicts One ore more artifact dictionaries such as in `_block`.
*/
function attachAndEmitValidNames(msg, ...validDicts) {
if (!options.testMode && !options.attachValidNames)
return;
const valid = Object.assign( Object.create( null ), ...validDicts );
msg.validNames = Object.create( null );
for (const name of Object.keys( valid )) {
// ignore internal types such as cds.Association
if (valid[name].internal || valid[name].deprecated)
continue;
msg.validNames[name] = valid[name];
}
if (options.testMode) {
// no semantic location => either first of [loc, semantic loc] pair or just location.
const loc = msg.location[0] || msg.location;
const names = Object.keys(msg.validNames);
info( null, loc,
{ '#': !names.length ? 'zero' : 'std' },

@@ -987,3 +1023,3 @@ { std: `Valid: ${ names.sort().join(', ') }`, zero: 'No valid names' });

// without truthy optional argument `alsoTestLast`.
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast ) {
function withAssociation( ref, test = testFunctionPlaceholder, alsoTestLast = false ) {
for (const item of ref.path || []) {

@@ -990,0 +1026,0 @@ const art = item && item._artifact; // item can be null with parse error

@@ -142,4 +142,7 @@ 'use strict';

const Edm = require('../edm.js')(options);
const Edm = require('../edm.js')(options, error);
// Static dynamic expression dictionary, loaded with Edm creators
const [ dynamicExpressions, dynamicExpressionNames ] = initEdmJson();
// annotation preprocessing

@@ -236,3 +239,3 @@ preprocessAnnotations.preprocessAnnotations(csn, serviceName, options);

const carrierName = annos.Target;
let targetSchema = fqSchemaNames.reduce((rc, sn) => !rc && carrierName && carrierName.startsWith(sn + '.') ? rc = sn : rc, undefined);
let targetSchema = fqSchemaNames.reduce((rc, sn) => !rc && carrierName && carrierName.startsWith(sn + '.') ? sn : rc, undefined);
// if no target schema has been found, it's a service annotation that applies to the service schema

@@ -393,3 +396,5 @@ if(targetSchema === undefined)

if (isV2()) { // Replace up to last dot with <serviceName>.EntityContainer
actionName = actionName.replace(/.*\.(?=[^.]*$)/, serviceName+'.EntityContainer/')
const lastDotIndex = actionName.lastIndexOf('.');
if (lastDotIndex > -1)
actionName = serviceName + '.EntityContainer/' + actionName.substr(lastDotIndex + 1);
}

@@ -448,5 +453,8 @@ else { // add parameter type list

}
let annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@' );
let nullWhitelist = [ '@Core.OperationAvailable' ];
let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
// Filter unknown toplevel annotations
// Final filtering of all annotations is done in handleTerm
const annoNames = Object.keys(carrier).filter( x => x.substr(0,1) === '@' );
const nullWhitelist = [ '@Core.OperationAvailable' ];
const knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
if (knownAnnos.length === 0) return;

@@ -461,14 +469,30 @@

// see example at definition of function mergePathStepsIntoPrefixTree
let prefixTree = {};
const prefixTree = {};
for (let a of knownAnnos) {
// remove leading @ and split at "."
// stop splitting at ".@" (used for nested annotations)
let sa = a.split('.@');
let steps = sa[0].slice(1).split('.');
if (sa[1]) {
steps.push('@' + sa[1]);
// Inline JSON EDM allows to add annotations to record members
// by prefixing the annotation with the record member 'foo@Common.Label'
// The splitter should leave such annotations alone, handleEdmJson
// takes care of assigning these annotations to the record members
const [ prefix, innerAnnotation ] = a.split('.@');
const steps = prefix.slice(1).split('.');
let i = steps.lastIndexOf('$edmJson');
if(i > -1) {
i = steps.findIndex(s => s.includes('@'), i+1);
if(i > -1) {
steps.splice(i, steps.length-i, steps.slice(i).join('.'));
}
}
if (innerAnnotation) {
// A voc annotation has two steps (Namespace+Name),
// any furter steps need to be rendered separately
const innerAnnoSteps = innerAnnotation.split('.');
const tailSteps = innerAnnoSteps.splice(2, innerAnnoSteps.length-2);
// prepend annotation prefix (path) to tail steps
tailSteps.splice(0, 0, '@' + innerAnnoSteps.join('.'));
steps.push(...tailSteps);
}
mergePathStepsIntoPrefixTree(prefixTree, steps, 0, carrier);
}
// construct a function that is used to add an <Annotation ...> to the

@@ -489,4 +513,4 @@ // respective <Annotations ...> element

let testToAlternativeEdmTarget = null; // if true, assign to alternative Edm Target
/** @type {(val?: any) => boolean} */
let testToStandardEdmTarget = ()=>true; // if true, assign to standard Edm Target
// eslint-disable-next-line no-unused-vars
let testToStandardEdmTarget = (val) => true; // if true, assign to standard Edm Target
let stdName = edmTargetName;

@@ -506,3 +530,6 @@ let alternativeEdmTargetName = null;

// Replace up to last dot with <serviceName>.EntityContainer/
alternativeEdmTargetName = (carrier.$entitySetName || edmTargetName).replace(/.*\.(?=[^.]*$)/, serviceName+'.EntityContainer/');
alternativeEdmTargetName = carrier.$entitySetName || edmTargetName
const lastDotIndex = alternativeEdmTargetName.lastIndexOf('.');
if (lastDotIndex > -1)
alternativeEdmTargetName = serviceName + '.EntityContainer/' + alternativeEdmTargetName.substr(lastDotIndex + 1);
hasAlternativeCarrier = carrier.$hasEntitySet;

@@ -591,2 +618,7 @@ }

}
// Another crazy hack due to this crazy function:
// If carrier is a managed association (has keys) and rc is false (annotation was not applicable)
// return true to NOT trigger 'unapplicable' info message
if(rc === false && carrier.target && carrier.keys && appliesTo.includes('Property'))
rc = true;
return rc;

@@ -656,9 +688,10 @@ }

let anno = handleTerm(fullTermName, prefixTree[voc][term], context);
// addAnnotationFunc needs AppliesTo info from dictionary to decide where to put the anno
fullTermName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
let dictTerm = getDictTerm(fullTermName, context); // message for unknown term was already issued in handleTerm
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
if(dictTerm && dictTerm.AppliesTo) {
message(info, context, `Term "${ fullTermName }" is not applied (AppliesTo="${ dictTerm.AppliesTo.join(' ') }")`);
if(anno !== undefined) {
// addAnnotationFunc needs AppliesTo info from dictionary to decide where to put the anno
fullTermName = fullTermName.replace(/#(\w+)$/g, ''); // remove qualifier
let dictTerm = getDictTerm(fullTermName, context); // message for unknown term was already issued in handleTerm
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) {
if(dictTerm && dictTerm.AppliesTo) {
message(info, context, `Term "${ fullTermName }" is not applied (AppliesTo="${ dictTerm.AppliesTo.join(' ') }")`);
}
}

@@ -681,4 +714,40 @@ }

* */
let newAnno = new Edm.Annotation(v, termName);
let newAnno = undefined;
const nullWhitelist = [ 'Core.OperationAvailable' ];
const voc = termName.slice(0, termName.indexOf('.'));
if(vocabularyDefinitions[voc] && annoValue !== null || nullWhitelist.includes(termName)) {
newAnno = new Edm.Annotation(v, termName);
// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
// -> remove qualifier from termName and set Qualifier attribute in newAnno
let p = termName.split('#');
let termNameWithoutQualifiers = p[0];
if (p.length > 1) {
checkOdataTerm(p[0]);
if (!edmUtils.isODataSimpleIdentifier(p[1])) {
message(error, context,
`OData annotation qualifier "${ p[1] }" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`);
}
newAnno.Term = termNameWithoutQualifiers;
newAnno.Qualifier = p[1];
}
if (p.length>2) {
message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
}
// get the type of the term from the dictionary
let termTypeName = null;
let dictTerm = getDictTerm(termNameWithoutQualifiers, context);
if (dictTerm) {
termTypeName = dictTerm.Type;
}
else {
message(info, context, `Unknown term “${ termNameWithoutQualifiers }”`);
}
// handle the annotation value and put the result into the <Annotation ...> tag just created above
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, context);
}
return newAnno;
function checkOdataTerm(ns) {

@@ -694,32 +763,2 @@ const simpleIdentifiers = ns.split('.');

// termName may contain a qualifier: @UI.FieldGroup#shippingStatus
// -> remove qualifier from termName and set Qualifier attribute in newAnno
let p = termName.split('#');
let termNameWithoutQualifiers = p[0];
if (p.length > 1) {
checkOdataTerm(p[0]);
if (!edmUtils.isODataSimpleIdentifier(p[1])) {
message(error, context,
`OData annotation qualifier "${ p[1] }" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`);
}
newAnno.Term = termNameWithoutQualifiers;
newAnno.Qualifier = p[1];
}
if (p.length>2) {
message(warning, context, `multiple qualifiers (${ p[1] },${ p[2] }${ p.length > 3 ? ',...' : '' })`);
}
// get the type of the term from the dictionary
let termTypeName = null;
let dictTerm = getDictTerm(termNameWithoutQualifiers, context);
if (dictTerm) {
termTypeName = dictTerm.Type;
}
else {
message(info, context, `Unknown term “${ termNameWithoutQualifiers }”`);
}
// handle the annotation value and put the result into the <Annotation ...> tag just created above
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, context);
return newAnno;
}

@@ -767,10 +806,8 @@

checkEnumValue(cAnnoValue['#'], dTypeName, context);
oTarget.setJSON({ 'EnumMember': cAnnoValue['#'], 'EnumMember@odata.type' : '#'+dTypeName, });
oTarget.setXml( { 'EnumMember': dTypeName + '/' + cAnnoValue['#'] });
}
else {
// do something seemingly reasonable even if there is no dictionary info
oTarget.setJSON({ 'EnumMember': cAnnoValue['#'], 'EnumMember@odata.type' : '#'+oTermName + 'Type/' });
oTarget.setXml( { 'EnumMember': oTermName + 'Type/' + cAnnoValue['#'] });
}
oTarget.setJSON({ 'Edm.String': cAnnoValue['#'] });
}

@@ -1037,2 +1074,4 @@ else if (cAnnoValue['$value'] !== undefined) {

message(warning, context, `explicitly specified type '${ actualTypeName }' not found in vocabulary`);
// explicitly mentioned type, render in XML and JSON
newRecord.Type = actualTypeName;
}

@@ -1044,2 +1083,4 @@ else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {

actualTypeName = dTypeName;
// explicitly mentioned type, render in XML and JSON
newRecord.Type = actualTypeName;
}

@@ -1049,8 +1090,12 @@ else if (isAbstractType(actualTypeName)) {

message(warning, context, `explicitly specified type '${ actualTypeName }' is abstract, specify a concrete type`);
actualTypeName = dTypeName;
if(dTypeName)
actualTypeName = dTypeName;
// set to definition name and render in XML and JSON
newRecord.Type = actualTypeName;
}
else {
// ok
// Dictionary Type, render in XML only for backward compatibility
newRecord.setXml( { Type: actualTypeName });
}
newRecord.Type = actualTypeName;
}

@@ -1066,9 +1111,7 @@ else if (dTypeName) { // there is an expected type name according to dictionary

}
if (isAbstractType(actualTypeName))
message(warning, context, `type '${ dTypeName }' is abstract, use '$Type' to specify a concrete type`);
newRecord.Type = actualTypeName;
// Dictionary Type, render in XML only for backward compatibility
newRecord.setXml( { Type: actualTypeName });
}

@@ -1153,5 +1196,7 @@ else {

}
else if(value['$edmJson']) {
newCollection.append(handleEdmJson(value['$edmJson'], context));
}
else {
let rec = generateRecord(value, termName, innerTypeName, context);
newCollection.append(rec);
newCollection.append(generateRecord(value, termName, innerTypeName, context));
}

@@ -1178,84 +1223,118 @@ }

// See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
function handleEdmJson(obj, context)
{
let specialProperties = [ '$Apply', '$LabeledElement' ];
let subset = edmUtils.intersect(specialProperties, Object.keys(obj));
// and test3/ODataBackends/DynExpr
if(subset.length > 1) { // doesn't work for three or more...
message(warning, context, `EDM JSON code contains more than one special property: ${ subset }`);
return null;
function handleEdmJson(obj, context, exprDef=undefined) {
let edmNode = undefined;
if(obj === undefined)
return edmNode;
const dynExprs = edmUtils.intersect(dynamicExpressionNames, Object.keys(obj));
if(dynExprs.length > 1) {
message(warning, context, `EDM JSON code contains more than one dynamic expression: ${ dynExprs }`);
return edmNode;
}
if(subset.length === 0) {
// only one property (that is not a special property)
if (Object.keys(obj) != undefined && Object.keys(obj).length==1) {
let k = Object.keys(obj)[0];
return new Edm.ValueThing(v, k.slice(1), obj[k] );
if(dynExprs.length === 0) {
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj) && Object.keys(obj).length==1) {
const k = Object.keys(obj)[0];
const val = obj[k];
edmNode = new Edm.ValueThing(v, k[0] === '$' ? k.slice(1) : k, val );
edmNode.setJSON( { [edmNode.kind]: val } );
}
message(warning, context, `EDM JSON code contains no special property out of: ${ specialProperties }`);
return null;
}
// name of special property determines element kind
let newElem = new Edm.Thing(v, subset[0].slice(1));
let mainAttribute = null;
for (let p of Object.keys(obj)) {
// copy all '$' attributes that are not $Apply or $LabeledElement to Thing
if (specialProperties.every(v => p != v)) {
if (p.charAt(0) === '$') {
// simple attribute
newElem[p.slice(1)] = obj[p];
else {
// This thing is either a record or a collection or a literal
if(Array.isArray(obj)) {
// EDM JSON doesn't mention annotations on collections
edmNode = new Edm.Collection(v);
obj.forEach(o => edmNode.append(handleEdmJson(o, context)));
}
else {
message(warning, context, `unexpected element without $: ${ p }`);
}
}
else { // we are either $Apply or $LabeledElement
// handle value of special property
let val = obj[p];
if (Array.isArray(val)) {
for (let a of val) {
if (a && typeof a === 'object' && !Array.isArray(a)) {
newElem.append(handleEdmJson(a, context));
else if(typeof obj === 'object') {
edmNode = new Edm.Record(v);
const annos = Object.create(null);
const props = Object.create(null);
Object.entries(obj).forEach(([k, val]) => {
if(k === '@type') {
edmNode.Type = val;
}
else if (Array.isArray(a)) {
message(warning, context, 'verbatim code contains nested array');
}
else {
if (typeof a === 'string') {
a = a.replace(/&/g, '&amp;')
let child = undefined;
const [ head, tail ] = k.split('@');
if(tail) {
child = handleTerm(tail, val, context);
}
newElem.append(new Edm.ValueThing(v, getTypeName(a), a));
else {
child = new Edm.PropertyValue(v, head);
child.append(handleEdmJson(val, context));
}
if(child) {
if(tail && head.length) {
if(!annos[head])
annos[head] = [ child ];
else
annos[head].push(child);
}
else {
if(head.length)
props[head] = child;
edmNode.append(child);
}
}
}
});
// add collected annotations to record members
Object.entries(annos).forEach(([n, val]) => {
props[n] && props[n].prepend(...val);
});
}
else { // literal
let escaped = obj;
if (typeof escaped === 'string') {
escaped = escaped.replace(/&/g, '&amp;')
}
edmNode = new Edm.ValueThing(v,
exprDef && exprDef.valueThingName || getXmlTypeName(escaped), escaped);
// typename for static expression rendering
edmNode.setJSON( { [getJsonTypeName(escaped)]: escaped } );
}
else if (val && typeof val === 'object') {
if (Object.keys(val) != undefined && Object.keys(val).length==1) {
let k = Object.keys(val)[0];
mainAttribute = { name: k.slice(1), val: val[k] };
}
}
else {
// name of special property determines element kind
exprDef = dynamicExpressions[dynExprs[0]];
edmNode = exprDef.create(obj);
// iterate over each obj.property and translate expression into EDM
Object.entries(obj).forEach(([name, val]) => {
if(exprDef) {
if(exprDef.anno && name[0] === '@') {
edmNode.append(handleTerm(name.slice(1), val, context));
}
else {
let el = handleEdmJson(val, context);
if (el) {
newElem.append(el);
else if (exprDef.attr && exprDef.attr.includes(name)) {
if (name[0] === '$') {
edmNode[name.slice(1)] = val;
}
}
else if (exprDef.jsonAttr && exprDef.jsonAttr.includes(name)) {
if (name[0] === '$') {
edmNode.setJSON( { [name.slice(1)]: val }) ;
}
}
else if(exprDef.children) {
if (Array.isArray(val)) {
val.forEach(a => {
edmNode.append(handleEdmJson(a, context, exprDef));
});
}
else {
edmNode.append(handleEdmJson(val, context, exprDef));
}
}
}
else {
mainAttribute = { name: getTypeName(val), val: val };
}
}
});
}
return edmNode;
// special property has a simple value:
// value is added as attribute to the element; we add it
// only after the other attributes in order to reproduce order
// (which is semantically insignificant, but it's nicer this way)
if (mainAttribute) {
newElem[mainAttribute.name] = mainAttribute.val;
}
return newElem;
function getTypeName(val) {
function getXmlTypeName(val) {
let typeName = 'String';

@@ -1270,4 +1349,71 @@ if (typeof val === 'boolean') {

}
function getJsonTypeName(val) {
let typeName = getXmlTypeName(val);
if(typeName === 'Int')
return 'Edm.Int32'
else
return 'Edm.'+typeName;
}
}
function initEdmJson() {
// Static dynamic expression dictionary, loaded with Edm creators
const dynamicExpressions = {
'$And': { create: () => { return new Edm.Expr(v, 'And') }, anno: true },
'$Or': { create: () => { return new Edm.Expr(v, 'Or') }, anno: true },
'$Not': { create: () => { return new Edm.Expr(v, 'Not') }, anno: true },
'$Eq': { create: () => { return new Edm.Expr(v, 'Eq') }, anno: true },
'$Ne': { create: () => { return new Edm.Expr(v, 'Ne') }, anno: true },
'$Gt': { create: () => { return new Edm.Expr(v, 'Gt') }, anno: true },
'$Ge': { create: () => { return new Edm.Expr(v, 'Ge') }, anno: true },
'$Lt': { create: () => { return new Edm.Expr(v, 'Lt') }, anno: true },
'$Le': { create: () => { return new Edm.Expr(v, 'Le') }, anno: true },
//valueThingName: 'EnumMember' Implicit Cast Rule String => Primitive Type is OK
'$Has': { create: () => { return new Edm.Expr(v, 'Has') }, anno: true },
'$In': { create: () => { return new Edm.Expr(v, 'In') }, anno: true },
'$Add': { create: () => { return new Edm.Expr(v, 'Add') }, anno: true },
'$Sub': { create: () => { return new Edm.Expr(v, 'Sub') }, anno: true },
'$Neg': { create: () => { return new Edm.Expr(v, 'Neg') }, anno: true },
'$Mul': { create: () => { return new Edm.Expr(v, 'Mul') }, anno: true },
'$Div': { create: () => { return new Edm.Expr(v, 'Div') }, anno: true },
'$DivBy': { create: () => { return new Edm.Expr(v, 'DivBy') }, anno: true },
'$Mod': { create: () => { return new Edm.Expr(v, 'Mod') }, anno: true },
'$Apply': {
create: () => { return new Edm.Apply(v) },
attr: [ '$Function' ],
anno: true
},
'$Cast': {
create: () => { return new Edm.Cast(v) },
attr: [ '$Type' ],
jsonAttr: [ '$Collection' ],
anno: true
},
'$IsOf': {
create: () => { return new Edm.IsOf(v) },
attr: [ '$Type' ],
anno: true
},
'$If': { create: () => { return new Edm.If(v) }, anno: true },
'$LabeledElement': {
create: () => { return new Edm.LabeledElement(v) },
attr: [ '$Name' ],
anno: true
},
'$LabeledElementReference': {
create: (obj) => { return new Edm.LabeledElementReference(v, obj['$LabeledElementReference']); },
},
'$UrlRef': { create: () => { return new Edm.UrlRef(v); }, anno: true },
'$Null': { create: () => { return new Edm.Null(v); }, anno: true, children: false },
};
Object.entries(dynamicExpressions).forEach(([k, v]) => {
if(!v.name)
v.name = k.slice(1);
if(v.children === undefined)
v.children = true;
});
return [ dynamicExpressions, Object.keys(dynamicExpressions) ];
}
//-------------------------------------------------------------------------------------------------

@@ -1274,0 +1420,0 @@ //-------------------------------------------------------------------------------------------------

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

if (keyNames.length === 0) {
keyNames.push['MISSING'];
keyNames.push('MISSING');
warning(null, null, `in annotation preprocessing: target ${targetName} has no key`);

@@ -129,3 +129,4 @@ }

aNameWithoutQualifier === '@Common.ValueList.viaAssociation') {
try {
const _fixedValueListShortCut = () => {
// note: we loop over all annotations that were originally present, even if they are

@@ -137,3 +138,3 @@ // removed from the carrier via this handler

if (carrier['@Common.ValueList.CollectionPath']) {
throw 'leave';
return false;
}

@@ -143,3 +144,3 @@

warning(null, null, `annotation preprocessing/${aNameWithoutQualifier}: annotation must not be used for an entity, ${ctx}`);
throw 'leave';
return false;
}

@@ -158,3 +159,3 @@

warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: value of 'viaAssociation' must be a path, ${ctx}`);
throw 'leave';
return false;
}

@@ -164,3 +165,3 @@ let assoc = csn.definitions[art].elements[assocName];

warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: there is no association "${assocName}", ${ctx}`);
throw 'leave';
return false;
}

@@ -175,3 +176,3 @@

warning(null, null, `in annotation preprocessing/@Common.ValueList: 'entity' is ignored, as 'viaAssociation' is present, ${ctx}`);
throw 'leave';
return false;
}

@@ -193,3 +194,3 @@

warning(null, null, `in annotation preprocessing/${aNameWithoutQualifier}: entity "${enameFull}" does not exist, ${ctx}`);
throw 'leave';
return false;
}

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

warning(null, null, `in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);
throw 'leave';
return false;
}

@@ -285,5 +286,8 @@ else if (keys.length > 1)

Object.assign(carrier, newObj);
return true;
}
catch (e) {
// avoid subsequent warnings
const success = _fixedValueListShortCut();
if (!success) {
// In case of failure, avoid subsequent warnings
delete carrier[aNameWithoutQualifier];

@@ -290,0 +294,0 @@ delete carrier['@Common.ValueList.type'];

@@ -37,4 +37,12 @@ 'use strict';

// Currently, the cloneCsn keeps only the creator from the csn.meta.
// There is the need to assign the odata options because we would like to determine
// whether to execute toFinalBaseType in the edmPreprocessor or not
if (_csn.meta && _csn.meta.transformation === 'odata' && _csn.meta.options) {
if (!csn.meta) setProp(csn, 'meta', Object.create(null));
setProp(csn.meta, 'options', _csn.meta.options);
}
let [ allServices, allSchemas, whatsMyServiceRootName, options ] = initializeModel(csn, _options);
const Edm = require('./edm.js')(options);
const Edm = require('./edm.js')(options, error);

@@ -121,3 +129,3 @@ let v = options.v;

const whatsMySchemaName = function(n) {
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? rc = sn : rc, undefined);
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? sn : rc, undefined);
}

@@ -144,3 +152,3 @@

// Add additional schema containers as sub contexts to the service
Object.entries(allSchemas).reduce((subSchemaDictionary, [fqName, art]) => {
Object.entries(allSchemas).forEach(([fqName, art]) => {
if(serviceCsn.name === whatsMyServiceRootName(fqName) &&

@@ -159,3 +167,2 @@ fqName.startsWith(serviceCsn.name + '.') && art.kind === 'schema') {

}
return subSchemaDictionary;
}, subSchemaDictionary);

@@ -227,3 +234,3 @@

function populateSchemas(schemas) {
Object.entries(csn.definitions).reduce((schemas, [fqName, art]) => {
Object.entries(csn.definitions).forEach(([fqName, art]) => {
// Identify service members by their definition name only, this allows

@@ -254,3 +261,2 @@ // to let the internal object.name have the sub-schema name.

}
return schemas;
}, schemas);

@@ -297,2 +303,9 @@ }

/** @type {object} */
// Same check for alias (if supported by us)
const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
if(reservedNames.includes(schema.name)) {
warning('odata-spec-violation-namespace-name', [ 'definitions', schema.name ], { name: schema.name, names: reservedNames },
'The EDM namespace $(NAME) must not be one of the reserved values $(NAMES)');
}
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);

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

// create the complex types
edmUtils.foreach(schemaCsn.definitions,
edmUtils.foreach(schemaCsn.definitions,
a => edmUtils.isStructuredType(a) && a.name.startsWith(schemaNamePrefix),

@@ -350,4 +363,5 @@ createComplexType);

}
else
addAssociation(np);
else {
addAssociation(np);
}
});

@@ -389,2 +403,10 @@

}
properties.forEach(p => {
if(!p[p._typeName]) {
warning('odata-spec-violation-attribute', loc, { name: p.Name, prop: p._typeName });
}
if(p.Name === EntityTypeName) {
warning('odata-spec-violation-property-name', loc, { name: p.Name, type: 'EntityType' });
}
});

@@ -637,4 +659,5 @@ // construct EntityType attributes

} else
} else {
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
}
}

@@ -660,2 +683,12 @@ }

}
properties.forEach(p => {
if(!p[p._typeName]) {
warning('odata-spec-violation-attribute', ['definitions', structuredTypeCsn.name], { name: p.Name, prop: p._typeName });
}
if(p.Name === complexType.Name) {
warning('odata-spec-violation-property-name', ['definitions', structuredTypeCsn.name], { name: p.Name, type: complexType.kind });
}
});
complexType.append(...(properties));

@@ -670,14 +703,4 @@

// derived types are already resolved to base types
let typeDef;
let props = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
/* Do not render enum types any more, code remains here for future reference
if((typeCsn.items && typeCsn.items.enum) || typeCsn.enum) {
if (!builtins.isIntegerTypeName(typeCsn.type)) {
warning(null, ['definitions', typeCsn.name], `Only integer enums are allowed in OData`);
}
typeDef = new Edm.EnumType(v, props, typeCsn);
} else {
*/
typeDef = new Edm.TypeDefinition(v, props, typeCsn );
// }
const typeDef = new Edm.TypeDefinition(v, props, typeCsn );
Schema.append(typeDef);

@@ -684,0 +707,0 @@ }

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

module.exports = function (options) {
module.exports = function (options, error) {
class Node

@@ -15,7 +15,7 @@ {

if(!attributes || typeof attributes !== 'object')
throw Error('Please debug me: attributes must be a dictionary');
error(null, 'Please debug me: attributes must be a dictionary');
if(!Array.isArray(v))
throw Error('Please debug me: v is either undefined or not an array: ' + v);
error(null, 'Please debug me: v is either undefined or not an array: ' + v);
if(v.filter(v=>v).length != 1)
throw Error('Please debug me: exactly one version must be set');
error(null, 'Please debug me: exactly one version must be set');
Object.assign(this, attributes);

@@ -39,3 +39,3 @@ this.set({ _children: [], _xmlOnlyAttributes: Object.create(null), _jsonOnlyAttributes: Object.create(null), _v: v, _ignoreChildren: false });

if(!attributes || typeof attributes !== 'object')
throw Error('Please debug me: attributes must be a dictionary');
error(null, 'Please debug me: attributes must be a dictionary');
let newAttributes = Object.create(null);

@@ -57,3 +57,3 @@ edmUtils.forAll(attributes, (value, p) => {

if(!attributes || typeof attributes !== 'object')
throw Error('Please debug me: attributes must be a dictionary');
error(null, 'Please debug me: attributes must be a dictionary');
return Object.assign(this._xmlOnlyAttributes, attributes);

@@ -67,6 +67,10 @@ }

if(!attributes || typeof attributes !== 'object')
throw Error('Please debug me: attributes must be a dictionary');
error(null, 'Please debug me: attributes must be a dictionary');
return Object.assign(this._jsonOnlyAttributes, attributes);
}
prepend(...children)
{
this._children.splice(0, 0, ...children.filter(c => c));
}
append(...children)

@@ -209,3 +213,3 @@ {

{
constructor(v, ns, alias=undefined, serviceCsn, annotations=[], withEntityContainer=true)
constructor(v, ns, alias=undefined, serviceCsn=null, annotations=[], withEntityContainer=true)
{

@@ -286,3 +290,7 @@ let props = Object.create(null);

if(this._annotations.length > 0) {
this._annotations.filter(a => a.Term).forEach(a => json['@'+a.Term] = a.toJSON());
this._annotations.filter(a => a.Term).forEach(a => {
Object.entries(a.toJSON()).forEach(([n, v]) => {
json[n] = v;
});
});
let json_Annotations = Object.create(null);

@@ -588,3 +596,3 @@ this._annotations.filter(a => a.Target).forEach(a => json_Annotations[a.Target] = a.toJSON());

if(!(csn instanceof Object || (typeof csn === 'object' && csn !== null)))
throw Error('Please debug me: csn must be an object');
error(null, 'Please debug me: csn must be an object');

@@ -1077,3 +1085,3 @@ // ??? Is CSN still required? NavProp?

default:
throw Error('Unhandled NavProp child: ' + c.kind);
error(null, 'Please debug me: Unhandled NavProp child: ' + c.kind);

@@ -1143,52 +1151,55 @@ }

// short form: key: value
let inlineConstExpr =
const inlineConstExpr =
[ 'Edm.Binary', 'Edm.Boolean', 'Edm.Byte', 'Edm.Date', 'Edm.DateTimeOffset', 'Edm.Decimal', 'Edm.Double', 'Edm.Duration', 'Edm.Guid',
'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.SByte','Edm.Single', /*'Edm.Stream',*/ 'Edm.String', 'Edm.TimeOfDay',
/* UI.xml: defines Annotations with generic type 'Edm.PrimitiveType' */
'Edm.PrimitiveType', 'Bool' ];
'Edm.PrimitiveType', 'Bool'
// Official JSON V4.01 Spec defines these paths as constant inline expression (OKRA requires them as explicit exprs):
// 'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath',
];
let constExpr = [ ...inlineConstExpr,
'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath', 'Path',
'EnumMember', 'EnumMember@odata.type' ];
let expr = edmUtils.intersect(constExpr, Object.keys(this._jsonOnlyAttributes))
if(expr.length === 0)
throw Error('Please debug me: neither child nor constant expression found on annotation: ' + JSON.stringify(this._jsonOnlyAttributes, null, 2));
return addExpressions(expr, this._jsonOnlyAttributes);
function addExpressions(expr, dict)
const dict = this._jsonOnlyAttributes;
const inline = edmUtils.intersect(Object.keys(dict), inlineConstExpr);
if(inline.length === 1)
{
let inline = edmUtils.intersect(expr, inlineConstExpr);
if(inline.length > 1)
throw Error('Please debug me: more than one inline constant expression found on annotation ' + inline.join(', '));
if(inline.length==1)
let v = dict[inline[0]];
switch(inline[0])
{
let v = dict[inline[0]];
switch(inline[0])
{
/* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project:
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
*/
case 'Edm.Boolean':
v = (v=='true'?true:(v=='false'?false:v));
// eslint-no-fallthrough
case 'Edm.String':
// eslint-no-fallthrough
case 'Edm.Float':
return v;
default:
return { '$Cast': v, '$Type': inline[0] };
}
/* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project:
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md
*/
case 'Edm.Boolean':
v = (v=='true'?true:(v=='false'?false:v));
// eslint-no-fallthrough
case 'Edm.String':
// eslint-no-fallthrough
case 'Edm.Float':
// eslint-no-fallthrough
return v;
default:
// OKRA requires this for JSON->XML mapping
// because they didn't want to lookup the type in the vocabulary
return { '$Cast': v, '$Type': inline[0] };
}
else
{
let json = Object.create(null);
for(let i = 0; i < expr.length; i++)
json['$' + expr[i]] = dict[expr[i]]
return json;
}
}
else
{
// if this is not a constant expression shortcut, render key/value pair verbatim
// without filtering non-spec-compliant constExpr
let json = Object.create(null);
Object.entries(dict).forEach(([k,v]) => {
json['$'+k] = v;
});
return json;
}
}
mergeJSONAnnotations(prefix='') {
return this._children.filter(c => c.kind === 'Annotation').reduce((o, a) => {
Object.entries(a.toJSON()).forEach(([n, v]) => {
o[prefix+n] = v;
});
return o; },
Object.create(null));
}
}

@@ -1217,4 +1228,5 @@

this._children.forEach(a => {
const name = '@' + a.Term + (a.Qualifier ? '#' + a.Qualifier : '');
json[name] = a.toJSON()
Object.entries(a.toJSON()).forEach(([n, v]) => {
json[n] = v;
});
})

@@ -1245,8 +1257,15 @@ }

{
if(this._children.length === 0 || this._ignoreChildren) // must be a constant expression
return this.getConstantExpressionValue();
const json = super.mergeJSONAnnotations(this.getJsonFQTermName());
const e = this._children.filter(c => c.kind !== 'Annotation');
if(e.length === 0 || this._ignoreChildren) // must be a constant expression
json[this.getJsonFQTermName()] = this.getConstantExpressionValue();
else
// annotation must have exactly one child (=record or collection)
return this._children[0].toJSON();
json[this.getJsonFQTermName()] = e[0].toJSON();
return json;
}
getJsonFQTermName() {
return '@' + this.Term + (this.Qualifier ? '#' + this.Qualifier : '');
}
}

@@ -1258,2 +1277,3 @@

{
// EDM JSON doesn't mention annotations on collections
return this._children.map(a => a.toJSON());

@@ -1267,2 +1287,4 @@ }

{
if(this.Type)
json['@type'] = this.Type;
let keys = Object.keys(this).filter(k => k !== 'Type');

@@ -1275,16 +1297,23 @@ for(let i = 0; i < keys.length; i++)

{
this._children.forEach(a => {
let name;
switch(a.kind)
this._children.forEach(c => {
switch(c.kind)
{
case 'Annotation':
name = '@' + a.Term;
case 'Annotation': {
Object.entries(c.toJSON()).forEach(([n, v]) => {
json[n] = v;
});
break;
case 'PropertyValue':
name = a.Property;
}
case 'PropertyValue': {
// plus property annotations as [a.Property]@anno: val
Object.entries(c.mergeJSONannotations()).forEach(([n, a]) => {
json[n] = a;
});
// render property as const expr (or subnode)
json[c.Property] = c.toJSON();
break;
}
default:
throw Error('Please debug me: default not reachable');
error(null, 'Pease debug me: Unhandled Record child: ' + c.kind);
}
json[name] = a.toJSON()
});

@@ -1304,9 +1333,13 @@ }

{
if(this._children.length === 0 || this._ignoreChildren)
const c = this._children.filter(c => c.kind !== 'Annotation')
if(c.length === 0 || this._ignoreChildren)
return this.getConstantExpressionValue();
else
{
return this._children[0].toJSON();
return c[0].toJSON();
}
}
mergeJSONannotations() {
return super.mergeJSONAnnotations(this.Property);
}
}

@@ -1355,3 +1388,107 @@

// Binary/Unary dynamic expression
class Expr extends Thing {
constructor(v, kind, details) {
super(v, kind, details);
}
toJSON()
{
// toJSON: depending on number of children unary or n-ary expr
const json = this.mergeJSONAnnotations();
const e = this._children.filter(c=>c.kind !== 'Annotation');
if(e.length === 1) {
json['$'+this.kind] = e[0].toJSON();
}
else {
json['$'+this.kind] = e.map(c => c.toJSON());
}
return json;
}
}
class Null extends AnnotationBase {
toXMLattributes() {
return '';
}
toJSON() {
const json = this.mergeJSONAnnotations();
json['$'+this.kind] = null;
return json;
}
}
class Apply extends AnnotationBase {
toJSON() {
const json = this.mergeJSONAnnotations();
json['$'+this.kind] = this._children.filter(c=>c.kind !== 'Annotation').map(c => c.toJSON());
this.toJSONattributes(json);
return json;
}
}
class Cast extends AnnotationBase {
toXMLattributes() {
if(this._jsonOnlyAttributes['Collection'])
return ` Type="Collection(${this.Type})"`
else
return ` Type="${this.Type}"`
}
toJSON() {
const json = this.mergeJSONAnnotations();
// first expression only, if any
const c = this._children.filter(c=>c.kind !== 'Annotation');
json['$'+this.kind] = c.length ? c[0].toJSON() : {};
this.toJSONattributes(json);
return json;
}
toJSONattributes(json) {
super.toJSONattributes(json);
edmUtils.forAll(this._jsonOnlyAttributes, (v,p) => {
json[p[0] === '@' ? p : '$' + p] = v;
});
return json;
}
}
class IsOf extends Cast {}
class If extends AnnotationBase {
toJSON() {
const json = this.mergeJSONAnnotations();
json['$'+this.kind] = this._children.filter(c=>c.kind !== 'Annotation').map(c => c.toJSON());
return json;
}
}
class LabeledElement extends AnnotationBase {
toJSON() {
const json = this.mergeJSONAnnotations();
// first expression only, if any
const c = this._children.filter(c=>c.kind !== 'Annotation');
json['$'+this.kind] = c.length ? c[0].toJSON() : '';
this.toJSONattributes(json);
return json;
}
toJSONattributes(json) // including Name
{
edmUtils.forAll(this, (v,p) => {
json[p[0] === '@' ? p : '$' + p] = v;
});
return json;
}
}
// LabeledElementReference is a
class LabeledElementReference extends ValueThing {
constructor(v, val) {
super(v, 'LabeledElementReference', val);
}
}
class UrlRef extends AnnotationBase {
toJSON() {
const json = this.mergeJSONAnnotations();
// first expression only, if any
const c = this._children.filter(c=>c.kind !== 'Annotation');
json['$'+this.kind] = c.length ? c[0].toJSON() : {};
return json;
}
}
// V2 specials

@@ -1459,2 +1596,12 @@ class End extends Node {}

PropertyValue,
// Expressions
Expr,
Null,
Apply,
Cast,
If,
IsOf,
LabeledElement,
LabeledElementReference,
UrlRef,
// V2 specials

@@ -1461,0 +1608,0 @@ End,

@@ -90,3 +90,2 @@ // CSN frontend - transform CSN into XSN

const { normalizeLocation } = require('../base/location');
const { makeMessageFunction } = require('../base/messages');

@@ -243,2 +242,18 @@ const { dictAdd } = require('../base/dictionaries');

},
expand: {
arrayOf: selectItem, // TODO: more specific
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref' ], // requires one of...
inKind: [ '$column' ], // only valid in $column
},
inline: {
arrayOf: selectItem, // TODO: more specific
msgId: 'syntax-csn-expected-column',
defaultKind: '$column',
validKinds: [], // pseudo kind '$column'
requires: [ 'ref' ], // requires one of...
inKind: [ '$column' ], // only valid in $column
},
keys: {

@@ -1164,6 +1179,7 @@ arrayOf: definition,

function value( val, spec, xsn ) { // for CSN property 'val'
if (!xsn.literal) // might be overwritten
xsn.literal = (val === null) ? 'null' : typeof val;
if ((val == null) ? val === null : typeof val !== 'object')
if ((val == null) ? val === null : typeof val !== 'object') {
if (!xsn.literal) // might be overwritten; only set if literal type is valid
xsn.literal = (val === null) ? 'null' : typeof val;
return val;
}
error( 'syntax-csn-expected-scalar', location(true), { prop: spec.msgProp },

@@ -1195,3 +1211,3 @@ 'Only scalar values are supported for property $(PROP)' );

xsn.op = { val: 'xpr', location: location() };
xsn.args = arrayOf( exprOrString )( exprs, spec, xsn );
xsn.args = exists( exprs, spec, xsn );
}

@@ -1260,9 +1276,22 @@

// mark path argument of 'exits' predicate with $expected:'exists'
function exists( cond, spec, xsn, csn ) {
const rxsn = arrayOf( exprOrString )(cond, spec, xsn, csn);
if (Array.isArray(rxsn) && rxsn.some(x => x === 'exists')) {
for (let i = 0; i < rxsn.length - 1; i++) {
if (rxsn[i] === 'exists' && rxsn[i + 1].path)
rxsn[++i].$expected = 'exists';
}
}
return rxsn;
}
function condition( cond, spec ) {
const loc = location();
return {
const x = {
op: { val: 'xpr', location: loc },
args: arrayOf( exprOrString )( cond, spec ),
args: exists( cond, spec ),
location: loc,
};
return x;
}

@@ -1562,3 +1591,3 @@

if (loc && typeof loc === 'object' && !Array.isArray( loc )) {
dollarLocations.push( loc.line ? normalizeLocation( loc ) : null );
dollarLocations.push( loc.line ? loc : null );
return;

@@ -1572,3 +1601,3 @@ }

// hidden feature: string $location
const m = /:([0-9]+)(:([0-9]+)(-[0-9---]+)?)?$/.exec( loc ); // extra - at end for .refloc
const m = /:(\d+)(?::(\d+)(?:-[0-9-]+)?)?$/.exec( loc ); // extra - at end for .refloc
if (!m) {

@@ -1579,3 +1608,3 @@ dollarLocations.push( null );

const line = Number( m[1] );
const column = m[3] && Number( m[3] ) || 0;
const column = m[2] && Number( m[2] ) || 0;
dollarLocations.push( {

@@ -1582,0 +1611,0 @@ file: loc.substring( 0, m.index ),

@@ -68,2 +68,4 @@ // Transform XSN (augmented CSN) into CSN

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

@@ -177,3 +179,4 @@ groupBy: arrayOf( expression ),

isNotNull: postfix( [ 'is', 'not', 'null' ] ),
notIn: [ 'not', 'in' ],
in: binaryRightParen( [ 'in' ] ),
notIn: binaryRightParen( [ 'not', 'in' ] ),
between: ternary( [ 'between' ], [ 'and' ] ),

@@ -195,6 +198,8 @@ notBetween: ternary( [ 'not', 'between' ], [ 'and' ] ),

* Sort property names of CSN according to sequence which is also used by the compactModel function
* Only intended to be used for tests, as no non-enumerable properties are kept.
* Only returns enumerable properties, except for certain hidden properties if requested:
* $location, $env, elements.
* Only returns enumerable properties, except for certain hidden properties
* if requested (cloneOptions != false): $location, $env, elements.
*
* If cloneOptions is false or if either cloneOptions.testMode or cloneOptions.testSortCsn
* are set, definitions are also sorted.
*
* @param {object} csn

@@ -204,3 +209,3 @@ * @param {CSN.Options|false} cloneOptions

function sortCsn( csn, cloneOptions = false ) {
if (typeof cloneOptions === 'object')
if (cloneOptions && typeof cloneOptions === 'object')
initModuleVars( cloneOptions );

@@ -212,2 +217,4 @@

for (const n of Object.keys(csn).sort( compareProperties ) ) {
const sortDict = n === 'definitions' &&
(!cloneOptions || cloneOptions.testMode || cloneOptions.testSortCsn);
const val = csn[n];

@@ -219,3 +226,3 @@ if (!val || typeof val !== 'object' || n.charAt(0) === '@' || csnDirectValues.includes(n))

// Array check for property `args` which may either be a dictionary or an array.
r[n] = csnDictionary( val, n === 'definitions', cloneOptions );
r[n] = csnDictionary( val, sortDict, cloneOptions );

@@ -244,2 +251,10 @@ else

/**
* @param {object} csn
* @param {boolean} sort
* @param {CSN.Options | false} cloneOptions If != false,
* cloneOptions.dictionaryPrototype is used and cloneOptions are
* passed to sort().
* @returns {object}
*/
function csnDictionary( csn, sort, cloneOptions = false ) {

@@ -571,3 +586,3 @@ if (!csn || Array.isArray(csn)) // null or strange CSN

function ignore() {}
function ignore() { /* no-op: ignore property */ }

@@ -639,5 +654,6 @@ function location( loc, csn, xsn ) {

keys.push( d );
else
return;
}
if (keys.length)
csn.keys = keys;
csn.keys = keys;
}

@@ -825,3 +841,3 @@

const nav = path[0]._navigation;
if (nav && nav.kind !== '$self' && nav.name.select != null) {
if (nav && nav.kind !== '$self' && nav.kind !== 'element' && nav.name.select != null) {
setHidden( ref, '$env', (nav.kind === '$navElement')

@@ -881,12 +897,12 @@ ? nav.name.alias

else if (node.op.val === 'xpr')
// do not use xpr() for xpr, as it would flatten inner xpr's (semantically ok)
return extra({ xpr: node.args.map( expression ) }, node );
// do not use xpr() for xpr, as it would flatten inner xpr's
return extra({ xpr: node.args.map( expression ) }, dollarExtraNode );
else if (node.op.val === 'cast')
return cast( expression( node.args[0] ), node);
// from here on: CDL input (no $extra possible)
return cast( expression( node.args[0] ), dollarExtraNode );
// from here on: CDL input (no $extra possible - but $parens)
else if (node.op.val !== ',')
return { xpr: xpr( node ) };
return extra( { xpr: xpr( node ) }, dollarExtraNode );
return (parensAsStrings)
? { xpr: [ '(', ...xpr( node ), ')' ] }
: { list: node.args.map( expression ) };
: extra( { list: node.args.map( expression ) } );
}

@@ -929,2 +945,11 @@

function binaryRightParen( op ) {
return ( exprs ) => {
const right = exprs[1].length === 1 ? exprs[1][0] : {};
return (right.xpr || right.list || !right.$parens)
? [ ...exprs[0], ...op, ...exprs[1] ]
: [ ...exprs[0], ...op, { xpr: exprs[1] } ];
};
}
function query( node, csn, xsn ) {

@@ -948,4 +973,3 @@ while (Array.isArray(node)) // in parentheses -> remove

gensrcFlavor = false;
// TODO: or set inside SELECT?
setHidden( select, 'elements', insertOrderDict( elems ) );
setHidden( select.SELECT, 'elements', insertOrderDict( elems ) );
}

@@ -1025,5 +1049,9 @@ finally {

const expr = expression( elem.value, true );
// console.log(Object.keys(expr))
addExplicitAs( Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) ),
elem.name, neqPath( elem.value ) );
Object.assign( col, (expr.cast ? { xpr: [ expr ] } : expr) );
if (elem.expand)
col.expand = columns( elem.expand );
if (elem.inline)
col.inline = columns( elem.inline );
// yes, the AS comes after the EXPAND
addExplicitAs( col, elem.name, neqPath( elem.value ) );
// $env and elements (sub queries) in expr are hidden (not set via Object.assign):

@@ -1065,4 +1093,8 @@ if (!expr.cast) {

function extra( csn, node ) {
if (node && node.$extra)
Object.assign( csn, node.$extra );
if (node) {
if (node.$extra)
Object.assign( csn, node.$extra );
if (node.$parens)
setHidden( csn, '$parens', node.$parens.length );
}
return csn;

@@ -1069,0 +1101,0 @@ }

@@ -7,11 +7,2 @@ /**

/**
* Checks if the provided parameter is an object
* @param obj the variable to check
* @returns true if the parameter is an object otherwise it returns false
*/
function isObject(obj) {
return obj && typeof obj === "object";
}
/**
* Callback of the forEach function called for each node it walks

@@ -34,67 +25,4 @@ * @callback forEachCallback

/**
* Callback of the walkWithPath function
* @callback walkWithPathCallback
* @param {boolean} isNode false if leaf
* @param {array} path specifies the path from the root to the node
* @param {object} node
*/
/**
* Callback check function
* @callback walkWithPathCheck
* @param {array} path specifies the path from the root to the node
* @param {object} node current node
* @param {string} key current key
* @param {boolean} visible Whether "key" is enumerable, i.e. visible.
* @returns {boolean} true process the current node
*/
/**
* Walks all nodes (also non-enumerable) calling the provided callback function for each node.
* Use optional 'check' callback function to ignore specific nodes.
* @param {object} node to start from
* @param {walkWithPathCallback} callback function called for each node: callback(isNode,path,node)
* @param {walkWithPathCheck} check optional callback function which should return true if the node should be further processed
*/
function walkWithPath(node, callback, check) {
function filterNodes (path, objs, result) {
let visibleKeys = Object.keys(objs);
let allKeys = Object.getOwnPropertyNames(objs); // consider non-enumerable properties also
let ignoreLength = Array.isArray(objs);
for(var key of allKeys) {
let visible = visibleKeys.includes(key);
if(ignoreLength && key==='length') continue; // ignore length for arrays
var obj = objs[key];
var newPath = path.concat([key]);
if(isObject(obj)) {
if(check) { // callback provided - ask if the scan should proceed
if(check(newPath,obj,key,visible))
result.push([newPath,obj]);
} else {
result.push([newPath,obj]);
}
} else {
callback(false,path.concat([key]),obj);
}
}
}
var next = [[[],node]];
while(next.length>0) {
var more = []
next.forEach(X => {
var P,O;
[P,O] = X;
filterNodes(P, O, more);
callback(true,P,O);
});
next = more;
} // while array not empty
}
module.exports = {
forEach,
walkWithPath
}

@@ -46,4 +46,4 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

n.type = this.Identifier;
else if (n.type === this.BRACE)
t.type = this.DOTbeforeBRACE;
else if (n.type === this.BRACE || n.type === this.ASTERISK)
t.type = this.DOTbeforeBRACE || t.type;
}

@@ -85,3 +85,4 @@ else if (t.type === this.AT) {

ts.BRACE = tokenTypeOf( recognizer, "'{'" );
ts.DOT = ts.DOTbeforeBRACE && ts.BRACE && tokenTypeOf( recognizer, "'.'" );
ts.DOT = tokenTypeOf( recognizer, "'.'" );
ts.ASTERISK = tokenTypeOf( recognizer, "'*'" );
ts.AT = tokenTypeOf( recognizer, "'@'" );

@@ -88,0 +89,0 @@ ts.NEW = Parser.NEW;

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

// remove "/***/" and trim white space
const content = lines[0].replace(/^\/[*]{2,}\s*/, '').replace(/\s*\*\/$/, '');
const content = lines[0].replace(/^\/[*]{2,}/, '').replace(/\*\/$/, '').trim();
return isWhiteSpaceOnly(content) ? null : content;

@@ -52,3 +52,3 @@ }

// Tabs are regarded as one space.
const spacesAtBeginning = firstNonEmptyLine.match(/(\s*)/)[0].length;
const spacesAtBeginning = firstNonEmptyLine.match(/^\s*/)[0].length;
if (spacesAtBeginning > 0)

@@ -74,3 +74,3 @@ lines = lines.map(line => removeWhitespace(line, spacesAtBeginning));

function isWhiteSpaceOnly(content) {
return content.match(/^[\n\s]*$/);
return content.trim().length === 0;
}

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

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

handleComposition,
reportExpandInline,
notSupportedYet,

@@ -232,3 +233,3 @@ csnParseOnly,

const func = pathItem.id && specialFunctions[pathItem.id.toUpperCase()];
const spec = func && func[pathItem.args.length];
const spec = func && func[pathItem.args ? pathItem.args.length : 0];
if (!spec)

@@ -310,2 +311,4 @@ return;

function surroundByParens( expr, open, close ) {
if (!expr)
return expr;
const location = this.tokenLocation( open, close );

@@ -351,3 +354,3 @@ if (expr.$parens)

function classifyImplicitName( category, ref ) {
if (!ref || ref.path) {
if (!ref || ref.path && this.getCurrentToken().text !== '.') {
const implicit = this._input.LT(-1);

@@ -423,3 +426,3 @@ if (implicit.isIdentifier)

return ref;
this.error( 'syntax-not-suported', item.location,
this.error( 'syntax-not-supported', item.location,
'Methods in expressions are not supported yet' );

@@ -637,3 +640,5 @@ path.broken = true;

art.kind = kind;
if (!parent[env])
if (!env)
parent.push( art );
else if (!parent[env])
parent[env] = [ art ];

@@ -656,3 +661,3 @@ else

*/
function assignProps( target, annos = [], props, location ) {
function assignProps( target, annos = [], props = null, location = null) {
if (annos === true)

@@ -748,2 +753,12 @@ return Object.assign( target, props );

function reportExpandInline( clauseName ) {
let token = this.getCurrentToken();
// improve error location when using "inline" `.{…}` after ref (arguments and
// filters not covered, not worth the effort); after an expression where
// the last token is an identifier, not the `.` is wrong, but the `{`:
if (token.text === '.' && this._input.LT(-1).type >= this.constructor.Identifier)
token = this._input.LT(2);
this.error( 'syntax-unexpected-refclause', token, { prop: clauseName },
'Unexpected nested $(PROP), can only be used after a reference' );
}

@@ -750,0 +765,0 @@ module.exports = {

@@ -28,5 +28,4 @@ // CSN functionality for resolving references

const { setProp: setLink } = require('../base/model');
const BUILTIN_TYPE = {};
const { locationString } = require('../base/location');

@@ -43,5 +42,14 @@ // Properties in which artifact or members are defined - next property in the

function csnRefs( csn ) {
const views = Object.create(null); // cache for views - OK to add it to CSN?
let refCsnPath = [];
let refLinks = null;
const cache = new WeakMap();
// TODO: code cleanup after getting rid of $env
resolveRef.expand = function resolveRefExpand( obj, ...args ) {
return cached( obj, '_env', () => navigationEnv( resolveRef( obj, ...args ).art ) );
}
resolveRef.refWhere = function resolveRefWhere( refObj, obj, ...args ) {
return cached( obj, '_env', () => {
resolveRef( refObj, ...args ); // sets _env cache for non-string ref items
return getCache( obj, '_env' );
} );
}
return {

@@ -60,18 +68,19 @@ effectiveType, artifactRef, inspectRef, queryOrMain,

function effectiveType( art ) {
if (art._effectiveType)
return art._effectiveType;
const cachedType = getCache( art, '_effectiveType' );
if (cachedType !== undefined)
return cachedType;
else if (!art.type || art.elements || art.target || art.targetAspect || art.enum)
return art;
return setCache( art, '_effectiveType', art );
const chain = [];
while (art._effectiveType == null && art.type &&
while (getCache( art, '_effectiveType' ) === undefined && art.type &&
!art.elements && !art.target && !art.targetAspect && !art.enum) {
chain.push( art );
setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles
setCache( art, '_effectiveType', 0 ); // initial setting in case of cycles
art = artifactRef( art.type, BUILTIN_TYPE );
}
if (art._effectiveType === 0)
if (getCache( art, '_effectiveType' ) === 0)
throw new Error( 'Circular type reference');
const type = art._effectiveType || art;
chain.forEach( a => setLink( a, '_effectiveType', type ) );
const type = getCache( art, '_effectiveType' ) || art;
chain.forEach( a => setCache( a, '_effectiveType', type ) );
return type;

@@ -103,21 +112,20 @@ }

function artifactRef( ref, notFound ) {
if (typeof ref === 'string') {
const art = csn.definitions[ref] || notFound;
if (art !== undefined)
return art;
}
else {
const [ head, ...tail ] = ref.ref;
let art = csn.definitions[pathId( head )];
for (const elem of tail)
art = navigationEnv( art ).elements[pathId( elem )];
if (art)
return art;
else if (notFound !== undefined)
return notFound;
}
const art = (typeof ref === 'string')
? csn.definitions[ref]
: cached( ref, 'ref', artifactPathRef );
if (art)
return art;
if (notFound !== undefined)
return notFound;
throw new Error( 'Undefined reference' );
}
function artifactPathRef( ref ) {
const [ head, ...tail ] = ref.ref;
let art = csn.definitions[pathId( head )];
for (const elem of tail)
art = navigationEnv( art ).elements[pathId( elem )];
return art;
}
/**

@@ -130,7 +138,3 @@ * Return the entity we select from

function fromRef( ref ) {
const path = ref.ref;
const name = (path.length === 1)
? pathId( path[0] )
: effectiveType( artifactRef( ref ) ).target;
return csn.definitions[name];
return navigationEnv( artifactRef( ref ));
}

@@ -140,26 +144,9 @@

* @param {CSN.Path} csnPath
* @param {number} refCsnPathIndex
*/
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 );
refCsnPath = path;
refLinks = links;
}
return refLinks[csnPath[refCsnPathIndex + 1]].env;
function inspectRef( csnPath ) {
return analyseCsnPath( csnPath, csn, resolveRef );
}
/**
* @param {CSN.Path} csnPath
*/
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] || allQueries( name, main );
function resolveRef( obj, parent, query, scope, baseEnv, main ) {
const queries = cached( main, '$queries', allQueries );

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

throw new Error( 'Value references must look like {ref:[...]}' );
const head = pathId( path[0] );
let head = pathId( path[0] );

@@ -192,6 +179,5 @@ // 1,2: with 'param' or 'global' property, in `keys`

}
// 4: where inside ref
if (scope === 'ref-where') {
const { elements } = whereEntity( csnPath, refCsnPathIndex );
return expandRefPath( path, elements[head], scope );
// 4: where inside ref, expand, inline
if (baseEnv) {
return expandRefPath( path, baseEnv.elements[head], scope );
}

@@ -202,18 +188,20 @@ // 5,6,7: outside queries, in queries where inferred elements are referred to

const select = query.SELECT || query.projection;
if (!select || obj.$env === true)
const obj$env = obj.$env;
if (!select || obj$env === true)
// TODO: do not do this if current query has a parent query (except with obj.$env)
// TODO: also consider expand/inline
return expandRefPath( path, queryOrMain( query, main ).elements[head] );
// With explicitly provided $env:
if (typeof obj.$env === 'number') { // head is mixin or table alias name
const s = (obj.$env) ? queries[obj.$env - 1] : select;
if (typeof obj$env === 'number') { // head is mixin or table alias name
const s = (obj$env) ? queries[obj$env - 1] : select;
const m = s.mixin && s.mixin[head];
return expandRefPath( path, m || s._sources[head], (m ? 'mixin' : 'alias') );
return expandRefPath( path, m || getCache( s, '_sources' )[head], (m ? 'mixin' : 'alias') );
}
else if (typeof obj.$env === 'string') {
const source = select._sources[obj.$env];
// Had a case where a obj.$env was the name of a mixin
else if (typeof obj$env === 'string') {
const source = getCache( select, '_sources' )[obj$env];
// Had a case where a obj.$env was the name of a mixin - TODO: should not be - example?
if (source)
return expandRefPath( path, source.elements[head], 'source' );
else if (select.mixin && select.mixin[obj.$env])
else if (select.mixin && select.mixin[obj$env])
return expandRefPath( path, select.mixin[head], 'source' );

@@ -223,4 +211,8 @@ throw new Error('No source found!');

// ON ref is to be searched only in the query elements
if (scope === 'on') // TODO: ok with expand/inline? Probably not with the latter
return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
// 8: try to search in MIXIN section (not in ON of JOINs)
if (scope !== 'from-on' && select.mixin) {
if (scope !== 'from-on' && scope !== 'orderBy' && select.mixin) {
const art = select.mixin[head];

@@ -231,14 +223,17 @@ if (art)

// 9: try to search for table aliases (partially in ON of JOINs)
if (path.length > 1 && (select.$alias || scope !== 'from-on')) {
const art = select._sources[head];
const alias = getCache( select, '$alias' );
if (path.length > 1 && (alias || scope !== 'from-on')) {
const art = getCache( select, '_sources' )[head];
if (art)
return expandRefPath( path, art, 'alias' );
}
// ORDER BY ref might have been a table alias (CSN not always has an $env),
// otherwise query elements
if (scope === 'orderBy')
return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
// 10: search in elements of source entity
// TODO: do not do this if current query has a parent query !!!
if (scope === 'on' || scope === 'orderBy')
return expandRefPath( path, queryOrMain( query, main ).elements[head], scope );
if (typeof select.$alias === 'string') { // with unique source
const source = select._sources[select.$alias];
if (typeof alias === 'string') { // with unique source
const source = getCache( select, '_sources' )[alias];
return expandRefPath( path, source.elements[head], 'source' );

@@ -257,3 +252,2 @@ }

const links = path.map( (_v, idx) => ({ idx }) );
links[0].art = art;

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

links[i - 1].env = art;
if (typeof path[i - 1] !== 'string')
setCache( path[i - 1], '_env', art );
art = art.elements[pathId( path[i] )];
if (!art) {
const env = links[i - 1].env;
const loc = env.name && env.name.$location || env.$location;
throw new Error ( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
}
links[i].art = art;
}
if (scope === 'from') {
art = navigationEnv( art );
links[links.length - 1].env = art;
const last = path[path.length - 1]
if (scope === 'from' || typeof last !== 'string') {
const env = navigationEnv( art );
links[links.length - 1].env = env;
if (scope === 'from')
art = env;
if (typeof last !== 'string')
setCache( last, '_env', env )
}

@@ -275,9 +281,9 @@ return { links, art, scope };

/**
* Get the array of all (sub-)queries inside the given `main` artifact (of `main.query`).
* Get the array of all (sub-)queries (value of the `SELECT`/`projection`
* property) inside the given `main` artifact (of `main.query`).
*
* @param {CSN.PathSegment} name Name of the view (as a string)
* @param {CSN.Definition} main
* @returns {CSN.Query[]}
*/
function allQueries( name, main ) {
function allQueries( main ) {
const all = [];

@@ -290,23 +296,53 @@ const projection = main.query || main.projection && main;

const as = query.as || implicitAs( query.ref );
select._sources[as] = fromRef( query );
select.$alias = (select.$alias !== null) ? typeof select.$alias === 'string' : as;
getCache( select, '_sources' )[as] = fromRef( query );
const alias = getCache( select, '$alias' ) // alias of unique source
setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as);
}
else if (select && query.as) { // sub query in FROM
const { as } = query;
select._sources[as] = queryOrMain( query, main );
setLink( select, '$alias',
(select.$alias !== null) ? typeof select.$alias === 'string' : as );
}
getCache( select, '_sources' )[as] = queryOrMain( query, main );
const alias = getCache( select, '$alias' ) // alias of unique source
setCache( select, '$alias', (alias != null) ? typeof alias === 'string' : as); }
const proj = query.SELECT || query.projection;
if (proj) { // every SELECT query -- TODO: remember number?
setLink( proj, '_sources', Object.create(null) );
setLink( proj, '$alias', null );
setCache( proj, '_sources', Object.create(null) );
setCache( proj, '$alias', null );
all.push( proj );
}
} );
views[name] = all;
return all;
}
function setCache( obj, prop, val ) {
let hidden = cache.get( obj );
if (!hidden) {
hidden = {};
cache.set( obj, hidden );
}
hidden[prop] = val;
return val;
}
function getCache( obj, prop ) {
const hidden = cache.get( obj );
return hidden && hidden[prop];
}
function cached( obj, prop, calc ) {
let hidden = cache.get( obj );
if (!hidden) {
hidden = {};
cache.set( obj, hidden );
}
else if (hidden[prop] !== undefined)
return hidden[prop];
const val = calc( obj );
hidden[prop] = val;
return val;
}
}
// Return value of a query SELECT for the query, or the main artifact,
// i.e. a value with an `elements` property.
// TODO: avoid the term Query, use QuerySelect or QueryNode
/**

@@ -319,9 +355,11 @@ * @param {CSN.Query} query

query = query.SET.args[0];
if (query.elements)
return query;
let leading = main.query;
if (query.SELECT && query.SELECT.elements)
return query.SELECT;
let leading = main.query || main;
while (leading.SET)
leading = leading.SET.args[0];
const elements = leading === query && main.elements;
if (elements)
// If an entity has both a projection and query property, the param `query`
// can be the entity itself (when inspect is called with a csnPath containing
// 'projection'), but `leading` can be its `query` property:
if ((leading === query || leading === query.query) && main.elements)
return main;

@@ -370,5 +408,5 @@ throw new Error( `Query elements not available: ${ Object.keys( query ).join('+') }`);

}
else {
else { // sub query in FROM
traverseQuery( from, select, callback );
} // sub query in FROM
}
}

@@ -389,3 +427,3 @@

*/
function analyseCsnPath( csnPath, csn ) {
function analyseCsnPath( csnPath, csn, resolve ) {
if (csnPath[0] !== 'definitions')

@@ -402,3 +440,4 @@ throw new Error( 'References outside definitions not supported yet');

let isName = false;
let refCsnPathIndex = 0;
let refObj = null;
let baseEnv = null;

@@ -432,5 +471,14 @@ for (let index = 0; index < csnPath.length; index++) {

else if (prop === 'where' && scope === 'ref') {
if (resolve)
baseEnv = resolve.refWhere( refObj, obj, parent, query, scope, baseEnv,
csn.definitions[csnPath[1]] );
scope = 'ref-where';
refCsnPathIndex = index - 2;
}
else if ((prop === 'expand' || prop === 'inline') && obj.ref) {
if (obj.ref && resolve) {
baseEnv = resolve.expand( obj, parent, query, scope, baseEnv,
csn.definitions[csnPath[1]] );
}
scope = prop;
}
else if (prop === 'on') {

@@ -444,2 +492,6 @@ if (scope === 'from')

}
else if (prop === 'ref') {
refObj = obj;
scope = prop;
}
else if (prop !== 'xpr') {

@@ -452,12 +504,11 @@ scope = prop;

if (!obj[prop])
// Path does not match the CSN. Break out of loop and use current object as best guess.
obj = obj[prop];
if (!obj && !resolve)
// For the semantic location, use current object as best guess
break;
obj = obj[prop];
}
// console.log( 'CPATH:', csnPath, scope, obj, parent.$location );
return {
obj, parent, query, scope, refCsnPathIndex,
};
if (!resolve)
return { query }; // for constructSemanticLocationFromCsnPath
return resolve( obj, parent, query, scope, baseEnv, csn.definitions[csnPath[1]] );
}

@@ -464,0 +515,0 @@

@@ -429,8 +429,9 @@ 'use strict';

* Deeply clone the given CSN model and return it.
* In testMode, definitions are sorted.
*
* @param {object} csn
* @param {CSN.Options} options CSN Options, only used for options.dictionaryPrototype
* @param {CSN.Options} options CSN Options, only used for `dictionaryPrototype`, `testMode`, and `testSortCsn`
*/
function cloneCsn(csn, options) {
return sortCsn(csn, options || true); // TODO: Remove `true` when all cloneCsn calls pass options.
return sortCsn(csn, options);
}

@@ -672,7 +673,8 @@

forAllQueries(artifact.query, (q, p) => {
if(q.SELECT) {
if(q.elements) {
cb(q, q.elements, [...p.slice(0,-1), 'elements']);
} else if(q.$elements) {
cb(q, q.$elements, [...p.slice(0,-1), '$elements']);
const s = q.SELECT;
if(s) {
if(s.elements) {
cb(s, s.elements, [...p, 'elements']);
} else if(s.$elements) { // huh?, is just refloc output
cb(s, s.$elements, [...p, '$elements']);
}

@@ -1093,4 +1095,5 @@ }

// Use original "messages"
const msgOptions = optionsObjects.reverse().find(opt => opt && Array.isArray(opt.messages));
// Reverse the array to ensure that the rightmost option has priority
const reversedOptions = [...optionsObjects].reverse(); // de-structure and create a new array, so reverse doesn't impact optionsObject
const msgOptions = reversedOptions.find(opt => opt && Array.isArray(opt.messages));
if (msgOptions) {

@@ -1420,3 +1423,19 @@ result.messages = msgOptions.messages;

/**
* Return an array of non-abstract service names contained in CSN
*
* @param {CSN.Model} csn
* @returns {CSN.Service[]}
*/
function getServiceNames(csn) {
let result = [];
forEachDefinition(csn, (artifact, artifactName) => {
if (artifact.kind === 'service' && !artifact.abstract) {
result.push(artifactName);
}
});
return result;
}
module.exports = {

@@ -1461,2 +1480,3 @@ getUtils,

sortCsnDefinitionsForTests,
getServiceNames,
};

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

const { csnRefs, artifactProperties } = require('./csnRefs');
const { locationString } = require('../base/messages');
const { locationString } = require('../base/location');

@@ -48,2 +48,3 @@ function enrichCsn( csn, options = {} ) {

includes: simpleRef,
'@': () => {},
}

@@ -59,14 +60,14 @@ setLocations( csn, false, null );

function standard( parent, prop, node ) {
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || (typeof prop === 'string' && prop.startsWith('@')))
function standard( parent, prop, obj ) {
if (!obj || typeof obj !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ))
return;
csnPath.push( prop );
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );
if (Array.isArray(obj)) {
obj.forEach( (n, i) => standard( obj, i, n ) );
}
else {
for (let name of Object.getOwnPropertyNames( node )) {
const trans = transformers[name] || standard;
trans( node, name, node[name] );
for (let name of Object.getOwnPropertyNames( obj )) {
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
trans( obj, name, obj[name] );
}

@@ -77,3 +78,3 @@ }

function dictionary( node, prop, dict ) {
function dictionary( parent, prop, dict ) {
csnPath.push( prop );

@@ -83,4 +84,4 @@ for (let name of Object.getOwnPropertyNames( dict )) {

}
if (!Object.prototype.propertyIsEnumerable.call( node, prop ))
node['$'+prop] = dict;
if (!Object.prototype.propertyIsEnumerable.call( parent, prop ))
parent['$'+prop] = dict;
csnPath.pop();

@@ -97,14 +98,14 @@ }

function simpleRef( node, prop, ref ) {
function simpleRef( parent, prop, ref ) {
// try {
const notFound = (options.testMode) ? undefined : null;
if (Array.isArray( ref )) {
node['_' + prop] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
parent['_' + prop] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
}
else if (typeof ref === 'string') {
if (!ref.startsWith( 'cds.'))
node['_' + prop] = refLocation( artifactRef( ref, notFound ) );
parent['_' + prop] = refLocation( artifactRef( ref, notFound ) );
}
else if (!ref.elements) {
node['_' + prop] = refLocation( artifactRef( ref, notFound ) );
parent['_' + prop] = refLocation( artifactRef( ref, notFound ) );
}

@@ -117,6 +118,6 @@ else { // targetAspect, target

// } catch (e) {
// node['_' + prop] = e.toString(); }
// parent['_' + prop] = e.toString(); }
}
function pathRef( node, prop, path ) {
function pathRef( parent, prop, path ) {
const { links, art, scope } = (() => {

@@ -135,6 +136,6 @@ if (options.testMode)

if (links)
node._links = links.map( l => refLocation( l.art ) );
parent._links = links.map( l => refLocation( l.art ) );
if (links && links[links.length-1].art !== art)
node._art = refLocation( art );
node._scope = scope;
parent._art = refLocation( art );
parent._scope = scope;

@@ -141,0 +142,0 @@ csnPath.push( prop );

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

columns,
expand: columns,
inline: columns,
actions: dictionary,

@@ -161,3 +163,3 @@ params: dictionary,

// If we will have specified elements, we need another test to see columns in --parse-cdl
return nodes && nodes.map( c => (c._parent && c._parent.elements)
return nodes && nodes.map( c => (c._parent && c._parent.elements && c.kind !== '$inline')
? artifactIdentifier( c, query )

@@ -171,3 +173,3 @@ : reveal( c, nodes ) );

? '{ ... }'
: dictionary( dict, parent );
: dictionary( dict );
}

@@ -210,3 +212,3 @@

function origin( node, parent ) {
if (node.$inferred === 'REDIRECTED')
if (!node || node.$inferred === 'REDIRECTED')
return reveal( node, parent );

@@ -213,0 +215,0 @@ else

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

.option('-o, --out <dir>')
.option(' --cds-home <dir>')
.option(' --lint-mode')

@@ -36,6 +37,8 @@ .option(' --fuzzy-csn-error')

.option(' --parse-only')
.option(' --fallback-parser <type>', ['cdl', 'csn'])
.option(' --test-mode')
.option(' --test-sort-csn')
.option(' --doc-comment')
.option(' --localized-without-coalesce')
.option(' --length <length>')
.option(' --defaultStringLength <length>')
.positionalArgument('<files...>')

@@ -69,2 +72,3 @@ .help(`

-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
--lint-mode Generate nothing, just produce messages if any (for use by editors)

@@ -75,3 +79,3 @@ --fuzzy-csn-error Report free-style CSN properties as errors

Type options
--length <length> Default 'length' for 'cds.String'
--defaultStringLength <length> Default 'length' for 'cds.String'

@@ -91,2 +95,3 @@ Diagnostic options

Valid values are:
keylessManagedAssoc
foreignKeyConstraints

@@ -112,2 +117,6 @@ addTextsLanguageAssoc

--parse-only Stop compilation after parsing and write result to <stdout>
--fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
parser as a fallback. Valid values are:
cdl : Use CDL parser
csn : Use CSN parser
--direct-backend Do not compile the given CSN but directly pass it to the backend.

@@ -119,2 +128,5 @@ Can only be used with certain new CSN based backends. Combination with

in errors, sort properties in CSN, omit version in CSN)
--test-sort-csn Sort the generated CSN by definitions. This impacts the order of EDMX,
OData CSN, CDL order and more. When --test-mode is enabled, this
option is implicitly enabled as well.
--doc-comment Preserve /** */ comments at annotation positions as doc property in CSN

@@ -142,2 +154,3 @@ --localized-without-coalesce Omit coalesce in localized convenience views

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

@@ -166,2 +179,3 @@ .option('-s, --src')

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

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

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

@@ -263,2 +278,3 @@ .option('-u, --user <user>')

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

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

hdbcds : Assume existing SQL tables and column names were produced
as HANA CDS would have generated them from the same CDS source
as HANA CDS would have generated them from the same CDS source
(like "quoted", but using element names with dots).

@@ -331,4 +347,4 @@ -s, --src <style> Generate SQL source files as <artifact>.<suffix>

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

@@ -335,0 +351,0 @@

@@ -32,4 +32,4 @@ /**

class DuplicateChecker {
constructor() {
this.init();
constructor(names) {
this.init(names);
}

@@ -40,5 +40,6 @@

*/
init() {
init(names) {
this.seenArtifacts = {};
this.currentArtifact = {};
this.names = names;
}

@@ -49,3 +50,3 @@

*
* @param {string} name Rendered artifact name
* @param {string} name Persistence name of the artifact
* @param {CSN.Location} location CSN location of the artifact

@@ -55,3 +56,3 @@ * @param {string} modelName CSN artifact name

addArtifact( name, location, modelName ) {
const dbName = asDBName(name);
const dbName = this.names === 'plain' ? asDBName(name) : name; // uppercase for plain names
this.currentArtifact = {

@@ -95,3 +96,3 @@ name, location, elements: {}, modelName,

if (artifacts.length > 1) {
artifacts.forEach((artifact) => { // report all colliding artifacts
artifacts.slice(1).forEach((artifact) => { // report all colliding artifacts, except the first one
const collidesWith = this.seenArtifacts[artifactName].find( art => art !== artifact );

@@ -105,4 +106,7 @@ let namingMode;

error(null, [ 'definitions', artifact.modelName ],
{ name: collidesWith.modelName, prop: namingMode },
'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)');
{ name: collidesWith.modelName, prop: namingMode, '#': artifact.modelName.includes('.') ? 'dots' : 'std' },
{
std: 'Artifact name can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
dots: 'Artifact name containing dots can\'t be mapped to a SQL compliant identifier in naming mode $(PROP) because it conflicts with existing definition $(NAME)',
});
});

@@ -114,4 +118,6 @@ }

elements.forEach((element) => { // report all colliding elements
error(null, [ 'definitions', artifact.modelName, 'elements', element.modelName ],
`Duplicated element '${element.name}' in artifact '${artifact.name}'`);
error(null,
[ 'definitions', artifact.modelName, 'elements', element.modelName ],
{ name: element.name, id: artifact.modelName },
'Duplicated element $(NAME) in artifact $(ID)');
});

@@ -118,0 +124,0 @@ }

@@ -80,3 +80,5 @@

// SELECT <primary_key>,
selectViolations += selectPrimaryKeyColumns(constraint);
const primaryKeyList = selectPrimaryKeyColumns(constraint);
if (primaryKeyList)
selectViolations += `${primaryKeyList},\n`;
// ... <foreign_key>

@@ -113,3 +115,3 @@ selectViolations += selectForeignKeyColumns(constraint);

return '';
return `${primaryKeyOfDependentTable.reduce(pkReducer, `${quoteSqlId(primaryKeyOfDependentTable[0])} AS "K:${primaryKeyOfDependentTable[0]}"`)},\n`;
return primaryKeyOfDependentTable.reduce(pkReducer, `${quoteSqlId(primaryKeyOfDependentTable[0])} AS "K:${primaryKeyOfDependentTable[0]}"`);
}

@@ -116,0 +118,0 @@

@@ -115,2 +115,8 @@

/*
Render key addition as HANA SQL.
*/
addKey(tableName, elementsObj) {
return [ `ALTER TABLE ${tableName} ADD ${render.primaryKey(elementsObj)}` ];
},
/*
Render column removals as HANA SQL.

@@ -128,2 +134,8 @@ */

/*
Render primary-key removals as HANA SQL.
*/
dropKey(tableName) {
return [ `ALTER TABLE ${tableName} DROP PRIMARY KEY;` ];
},
/*
Render column modifications as HANA SQL.

@@ -134,2 +146,19 @@ */

},
/*
Render primary keys as HANA SQL.
*/
primaryKey(elementsObj) {
const primaryKeys = Object.keys(elementsObj)
.filter(name => elementsObj[name].key)
.filter(name => !elementsObj[name].virtual)
.map(name => quoteSqlId(name))
.join(', ');
return primaryKeys && `PRIMARY KEY(${primaryKeys})`;
},
/*
Concatenate multiple statements which are to be treated as one by the API caller.
*/
concat(...statements) {
return [ statements.join('\n') ];
},
};

@@ -159,3 +188,3 @@

// Registries for artifact and element names per CSN section
const definitionsDuplicateChecker = new DuplicateChecker();
const definitionsDuplicateChecker = new DuplicateChecker(options.toSql.names);
const deletionsDuplicateChecker = new DuplicateChecker();

@@ -190,3 +219,3 @@ const extensionsDuplicateChecker = new DuplicateChecker();

const env = { indent: '' };
renderArtifactExtensionInto(artifactName, extension, resultObj, env);
renderArtifactExtensionInto(artifactName, csn.definitions[artifactName], extension, resultObj, env);
}

@@ -278,9 +307,3 @@ }

break;
case 'type': {
const result = renderType(artifactName, art, env);
if (result)
resultObj.hdbview[artifactName] = result;
break;
}
case 'type':
case 'context':

@@ -306,2 +329,3 @@ case 'service':

* @param {string} artifactName Name of the artifact to render
* @param {CSN.Artifact} artifact The complete artifact
* @param {CSN.Artifact} ext Extension to render

@@ -311,7 +335,7 @@ * @param {object} resultObj Result collector

*/
function renderArtifactExtensionInto(artifactName, ext, resultObj, env) {
function renderArtifactExtensionInto(artifactName, artifact, ext, resultObj, env) {
// Property kind is always omitted for elements and can be omitted for
// top-level type definitions, it does not exist for extensions.
if (artifactName && !ext.query)
renderExtendInto(artifactName, ext.elements, resultObj, env, extensionsDuplicateChecker);
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);

@@ -354,5 +378,2 @@ if (!artifactName)

}
function concat(...statements) {
return [ statements.join('\n') ];
}

@@ -395,3 +416,3 @@ const tableName = renderArtifactName(artifactName);

: render.addColumns.fromElementsObj(artifactName, tableName, { [eltName]: def.new }, env);
addMigration(resultObj, artifactName, true, concat(...drop, ...add));
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
}

@@ -432,3 +453,3 @@ else {

const tableName = renderArtifactName(artifactName);
definitionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art.$location, artifactName);
result += `TABLE ${tableName}`;

@@ -444,6 +465,2 @@ result += ' (\n';

}
const primaryKeys = Object.keys(art.elements).filter(name => art.elements[name].key)
.filter(name => !art.elements[name].virtual)
.map(name => quoteSqlId(name))
.join(', ');
const uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name].virtual)

@@ -455,4 +472,5 @@ .map(name => quoteSqlId(name))

const primaryKeys = render.primaryKey(art.elements);
if (primaryKeys !== '')
result += `,\n${childEnv.indent}PRIMARY KEY(${primaryKeys})`;
result += `,\n${childEnv.indent}${primaryKeys}`;

@@ -519,3 +537,4 @@ if (art.$tableConstraints && art.$tableConstraints.referential) {

* @param {string} artifactName Name of the artifact to render
* @param {object} elementsObj ??
* @param {object} artifactElements Elements comprising the artifact
* @param {object} extElements Elements comprising the extension
* @param {object} resultObj Result collector

@@ -525,10 +544,16 @@ * @param {object} env Render environment

*/
function renderExtendInto(artifactName, elementsObj, resultObj, env, duplicateChecker) {
function renderExtendInto(artifactName, artifactElements, extElements, resultObj, env, duplicateChecker) {
const tableName = renderArtifactName(artifactName);
if (duplicateChecker)
duplicateChecker.addArtifact(tableName, undefined, artifactName);
const elements = render.addColumns.fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker);
const associations = render.addAssociations(artifactName, tableName, elementsObj, env);
const elements = render.addColumns.fromElementsObj(artifactName, tableName, extElements, env, duplicateChecker);
const associations = render.addAssociations(artifactName, tableName, extElements, env);
if (elements.length + associations.length > 0)
addMigration(resultObj, artifactName, false, [ ...elements, ...associations ]);
if (Object.values(extElements).some(elt => elt.key)) {
const drop = render.dropKey(tableName);
const add = render.addKey(tableName, artifactElements);
addMigration(resultObj, artifactName, true, render.concat(...drop, ...add));
}
}

@@ -982,3 +1007,3 @@

const viewName = renderArtifactName(artifactName);
definitionsDuplicateChecker.addArtifact(viewName, art && art.$location, artifactName);
definitionsDuplicateChecker.addArtifact(art['@cds.persistence.name'], art && art.$location, artifactName);
let result = `VIEW ${viewName}`;

@@ -1143,51 +1168,2 @@ result += renderParameterDefinitions(artifactName, art.params);

/**
* Render a type.
* Return the resulting source string.
*
* @todo prop 'dbType' does not exist (anymore)
* @param {string} artifactName Name of the type
* @param {CSN.Type} art CSN type
* @param {object} env Render environment
*
* @returns {string} Rendered type
*/
function renderType(artifactName, art, env) {
// Only HANA table types are SQL-relevant
if (!art.dbType)
return '';
// In Sqlite dialect do not generate table type and throw an info
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
// TODO: Signal is not covered by tests + better location
info(null, [ 'definitions', artifactName ], `"${artifactName}": SAP HANA table types are not supported in SQLite`);
return '';
}
const typeName = renderArtifactName(artifactName);
definitionsDuplicateChecker.addArtifact(typeName, art && art.$location, artifactName);
let result = `TYPE ${typeName} AS TABLE (\n`;
const childEnv = increaseIndent(env);
if (art.elements) {
// Structured type
const elements = `${Object.keys(art.elements).map(name => renderElement(artifactName, name, art.elements[name], definitionsDuplicateChecker, null, childEnv))
.filter(s => s !== '')
.join(',\n')}\n`;
if (elements !== '') {
result += elements;
result += `${env.indent})`;
}
else {
// TODO: Signal is not covered by tests + better location
error(null, [ 'definitions', artifactName ], 'SAP HANA table types must have at least one element that is non-virtual');
}
}
else {
// TODO: Signal is not covered by tests + better location
// Non-structured HANA table type
error(null, [ 'definitions', artifactName ], 'SAP HANA table types must have structured types for conversion to SQL');
return '';
}
return result;
}
/**
* Render a reference to the type used by 'elm' (with name 'elementName' in 'artifactName', both used only for error messages).

@@ -1352,3 +1328,3 @@ *

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

@@ -1458,6 +1434,7 @@ // Nested expression

else if (x.ref[1] === 'locale') {
return (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain')
? options.toSql.user && options.toSql.user.locale
? `'${options.toSql.user && options.toSql.user.locale}'` : '\'en\''
: 'SESSION_CONTEXT(\'LOCALE\')';
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
return (options.toSql.user && options.toSql.user.locale)
? `'${options.toSql.user && options.toSql.user.locale}'` : '\'en\'';
}
return 'SESSION_CONTEXT(\'LOCALE\')';
}

@@ -1468,2 +1445,11 @@ // Basically: Second path step was invalid, do nothing

/**
* For a given reference starting with $at, render a 'current_timestamp' literal for plain.
* For the sql-dialect hana, we render the TO_TIMESTAMP(SESSION_CONTEXT(..)) function.
*
*
* For sqlite, we render the string-format-time (strftime) function.
* Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
* the format for TimeStamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
* --> Therefore the comparison in the temporal where clause doesn't work properly.
*
* @param {object} x

@@ -1473,14 +1459,32 @@ * @returns {string|null} Null in case of an invalid second path step

function render$at(x) {
// return current_time for all $at
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
return 'current_timestamp';
if (x.ref[1] === 'from') {
switch (options.toSql.dialect) {
case 'sqlite': {
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
return `strftime('${dateFromFormat}', 'now')`;
}
case 'hana':
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
case 'plain':
return 'current_timestamp';
default:
break;
}
}
else if (options.toSql.dialect === 'hana') {
if (x.ref[1] === 'from')
return 'SESSION_CONTEXT(\'VALID-FROM\')';
else if (x.ref[1] === 'to')
return 'SESSION_CONTEXT(\'VALID-TO\')';
if (x.ref[1] === 'to') {
switch (options.toSql.dialect) {
case 'sqlite': {
// + 1ms compared to $at.from
const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
return `strftime('${dateToFormat}', 'now')`;
}
case 'hana':
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
case 'plain':
return 'current_timestamp';
default:
break;
}
}
return null;

@@ -1537,3 +1541,3 @@ }

if (s.func)
return `${s.func}(${renderArgs(s.args, '=>', env)})`;
return `${s.func}(${renderArgs(s.args, '=>', env, null)})`;

@@ -1544,3 +1548,3 @@ // Path step, possibly with view parameters and/or filters

// View parameters
result += `(${renderArgs(s.args, '=>', env)})`;
result += `(${renderArgs(s.args, '=>', env, null)})`;
}

@@ -1547,0 +1551,0 @@ if (s.where) {

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

const { names } = options.forHana;
const forSqlite = options.toSql && options.toSql.dialect === 'sqlite';
let result = '';

@@ -54,4 +55,11 @@ result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;

result += `${indent}REFERENCES ${quoteId(getResultingName(csn, names, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
result += `${indent}ON UPDATE RESTRICT\n`;
result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
if (forSqlite) {
if (constraint.onDelete === 'CASCADE' )
result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
}
else {
result += `${indent}ON UPDATE RESTRICT\n`;
result += `${indent}ON DELETE ${constraint.onDelete}${constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : ''}\n`;
}
}

@@ -58,0 +66,0 @@ // constraint enforcement / validation must be switched off using sqlite pragma statement

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

isAspect,
isBuiltinType,
getServiceNames,
} = require('../model/csnUtils');

@@ -25,2 +27,3 @@ const { checkCSNVersion } = require('../json/csnVersion');

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

@@ -69,3 +72,3 @@ const { addLocalizationViews, hasLocalizedConvenienceView, isInLocalizedNamespace } = require('./localized');

// (Linter check candidate)
module.exports = { transform4odataWithCsn, getServiceNames };
module.exports = { transform4odataWithCsn };

@@ -94,2 +97,3 @@ function transform4odataWithCsn(inputModel, options) {

const {
addDefaultTypeFacets,
createForeignKeyElement,

@@ -99,3 +103,3 @@ createAndAddDraftAdminDataProjection, createScalarElement,

addElement, createAction, assignAction,
checkForeignKeys, extractValidFromToKeyElement,
extractValidFromToKeyElement,
checkAssignment, checkMultipleAssignments,

@@ -161,3 +165,3 @@ recurseElements, setAnnotation, renameAnnotation,

referenceFlattener.resolveAllReferences(csn, inspectRef, isStructured);
referenceFlattener.attachPaths(csn);
attachPath(csn);

@@ -185,6 +189,7 @@ referenceFlattener.applyAliasesInOnCond(csn, inspectRef);

// Apply default type facets as set by options
// Flatten on-conditions in unmanaged associations
// This must be done before all the draft logic as all
// composition targets are annotated with @odata.draft.enabled in this step
forEachDefinition(csn, processOnCond);
forEachDefinition(csn, [ setDefaultTypeFacets, processOnCond ]);

@@ -214,9 +219,2 @@ // Now all artificially generated things are in place

}
def.elements && Object.entries(def.elements).forEach( ([elemName, elem]) => {
// Check for valid foreign keys
if (isAssocOrComposition(elem.type)) {
checkForeignKeys(elem, elemName, defName, options);
}
});
}

@@ -377,20 +375,25 @@ visitedArtifacts[defName] = true;

// for enums @assert.range changes into a boolean annotation
else if (node.enum && node[name] === true) {
let enumValue = Object.keys(node.enum).map(enumSymbol => {
const enumSymbolDef = node.enum[enumSymbol];
let result = { '@Core.SymbolicName': enumSymbol };
if (enumSymbolDef.val !== undefined)
result.Value = enumSymbolDef.val;
else if (node.type && node.type === 'cds.String')
else if (node[name] === true) {
let typeDef = node;
if(!node.enum && node.type && !isBuiltinType(node.type))
typeDef = csn.definitions[node.type];
if(typeDef.enum) {
let enumValue = Object.keys(typeDef.enum).map(enumSymbol => {
const enumSymbolDef = typeDef.enum[enumSymbol];
let result = { '@Core.SymbolicName': enumSymbol };
if (enumSymbolDef.val !== undefined)
result.Value = enumSymbolDef.val;
else if (node.type && node.type === 'cds.String')
// the symbol is used as value only for type 'cds.String'
result.Value = enumSymbol;
result.Value = enumSymbol;
// Can't rely that @description has already been renamed to @Core.Description
// Eval description according to precedence (doc comment must be considered already in Odata transformer
// as in contrast to the other doc commments as it is used to annotate the @Validation.AllowedValues)
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
if (desc)
result['@Core.Description'] = desc;
return result;
});
setAnnotation(node, '@Validation.AllowedValues', enumValue);
const desc = enumSymbolDef['@Core.Description'] || enumSymbolDef['@description'] || enumSymbolDef.doc;
if (desc)
result['@Core.Description'] = desc;
return result;
});
setAnnotation(node, '@Validation.AllowedValues', enumValue);
}
}

@@ -401,2 +404,11 @@ }

// Apply default type facets to each type definition and every member
// But do not apply default string length 5000 (as in DB)
function setDefaultTypeFacets(def) {
addDefaultTypeFacets(def.items || def, false)
forEachMemberRecursively(def, m=>addDefaultTypeFacets(m.items || m, false));
if(def.returns)
addDefaultTypeFacets(def.returns.items || def.returns, false);
}
// Handles on-conditions in unmanaged associations

@@ -531,4 +543,4 @@ function processOnCond(def) {

else if (!getServiceName(elem.target)) {
warning(null, ['definitions', artifactName, 'elements', elemName], { target: elem.target, name: `${artifactName}:${elemName}` },
`Target $(TARGET) of composition $(NAME) can't be a draft node because it is not part of a service`);
warning(null, ['definitions', artifactName, 'elements', elemName], { target: elem.target },
'Ignoring draft node for composition target $(TARGET) because it is not part of a service');
return;

@@ -585,12 +597,1 @@ }

} // transform4odataWithCsn
// Return an array of non-abstract service names contained in compacted 'model'
function getServiceNames(model) {
let result = [];
forEachDefinition(model, (artifact, artifactName) => {
if (artifact.kind === 'service' && !artifact.abstract) {
result.push(artifactName);
}
});
return result;
}

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

(query.columns || []).forEach((column) => {
if (column && column.cast)
if (column && typeof column === 'object' && column.cast)
rewriteDirectRefPropsToLocalized(column.cast);

@@ -510,0 +510,0 @@ });

'use strict';
const { forEachManagedAssociation } = require('./utils');
const { attachPath, attachPathOnPartialCSN } = require('./attachPath');

@@ -28,3 +29,3 @@ /**

// update paths and resolve references
referenceFlattener.attachPaths(csn);
attachPath(csn);
referenceFlattener.resolveAllReferences(csn, csnUtils.inspectRef, csnUtils.isStructured);

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

if (newKeys.length) {
referenceFlattener.attachPaths(newKeys, assoc.keys.$path)
attachPathOnPartialCSN(newKeys, assoc.$path.concat('keys'));
assoc.keys = newKeys;

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

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

* @param {CSN.Model} csn
* @param {*} options
* @param {*} referenceFlattener

@@ -128,3 +129,3 @@ * @param {*} csnUtils

// After that, add the new elements to the definition.
// At the same time:
// At the same time:
// -> Check for coliding element's name

@@ -143,5 +144,6 @@ // &

if (parent.elements[foreignKeyName]) {
const path = parent.elements.$path ? parent.elements.$path.concat([foreignKeyName]) : ['definitions', definitionName, 'elements', foreignKeyName];
if (parent.elements[foreignKeyName]['@odata.foreignKey4'] && !isDeepEqual(parent.elements[foreignKeyName], foreignKey))
error(null, path, `Generated foreign key element "${foreignKeyName}" for association "${assocName}" conflicts with existing element`);
if (!(parent.elements[foreignKeyName]['@odata.foreignKey4'] || isDeepEqual(parent.elements[foreignKeyName], foreignKey))) {
const path = parent.elements[foreignKeyName].$path;
error(null, path, { name: foreignKeyName, art: assocName }, 'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
}
}

@@ -163,3 +165,3 @@ }

// FIXME: Very similar code to
// FIXME: Very similar code to
// transformUtilsNew::getForeignKeyArtifact & createForeignKeyElement

@@ -171,9 +173,10 @@ // Can this be streamlined?

const fkArtifact = csnUtils.inspectRef(pathInKeysArr).art;
if (csnUtils.isStructured(fkArtifact)) {
processStucturedKey(fkArtifact, assocName, foreignKeyRef);
} else {
if(fkArtifact) {
if (csnUtils.isStructured(fkArtifact)) {
processStucturedKey(fkArtifact, assocName, foreignKeyRef);
} else {
// built-in
const foreignKeyElementName = `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as || foreignKeyRef.ref.join('_')}`;
newForeignKey(fkArtifact, foreignKeyElementName);
const foreignKeyElementName = `${assocName.replace(/\./g, '_')}_${foreignKeyRef.as || foreignKeyRef.ref.join('_')}`;
newForeignKey(fkArtifact, foreignKeyElementName);
}
}

@@ -226,4 +229,5 @@

function processAssociationOrComposition(fkArtifact, foreignKeyElementName) {
fkArtifact.keys.forEach(keyRef => {
let fksForAssoc = generateForeignKeysForRef(assoc, foreignKeyElementName, keyRef, keyRef.$path, foreignKey4);
fkArtifact.keys.forEach((keyRef,keyId) => {
const path = fkArtifact.$path.concat('keys').concat(keyId);
const fksForAssoc = generateForeignKeysForRef(assoc, foreignKeyElementName, keyRef, path, foreignKey4);
Object.assign(generatedFks, fksForAssoc);

@@ -236,5 +240,5 @@ })

/**
*
* @param {object} obj
* @param {*} other
*
* @param {object} obj
* @param {*} other
* @returns {boolean} Whether 'obj' and 'other' are deeply equal. We need the

@@ -258,6 +262,7 @@ * deep comparison because of annotations that have structured values and they

return false;
} else if (obj[key] !== other[key])
} else if (obj[key] !== other[key]) {
return false;
}
}
return true;
}
const { forEachRef } = require('../../model/csnUtils');
const { setProp } = require('../../base/model');
const { implicitAs } = require('../../model/csnRefs');
const walker = require('../../json/walker');
/** This class is used for generic reference flattening.

@@ -46,39 +44,2 @@ * It provides the following functionality:

/**
* Walks a given CSN or a CSN-subnode in depth
* and attaches a non-enumerable property $path,
* which is the complete path from the root of csnNode to the single property.
* The path is an array of property names in the chain to the single node.
*
* @param {*} csnNode - The CSN root node or a CSN-subnode.
* @param {Array.<string>} pathPrefix - Optional path prefix used in case of CSN-subnodes passsed as a first argument.
*/
attachPaths(csnNode, pathPrefix = undefined) {
// A callback function used in the walkWithPath to limit the treversed nodes. Ignores all non-enumerable properties but 'elements'.
function isResolvable(path, _obj, name, visible) {
if (name) {
if (path[0] === 'messages')
return false;
// ignore all hidden properties but not 'elements'
if (!visible && name !== 'elements') {
return false;
}
}
return true;
}
walker.walkWithPath(csnNode, (isNode, path, node) => {
if (isNode) {
const descr = Object.getOwnPropertyDescriptor(node, '$path')
if (descr && descr.enumerable) // check if it is an element -> do not overwrite it
return;
if (!pathPrefix)
setProp(node, '$path', path);
else
setProp(node, '$path', pathPrefix.concat(...path));
}
}, isResolvable)
}
/**
* Resolves all references in the specified CSN and attaches the paths of resolved reference items

@@ -235,3 +196,3 @@ * as non-enumerable array of paths property called $paths.

}
setProp(newRef, '$path', node.$path.concat('ref'));
setProp(newRef, '$path', path.concat('ref'));
if (!node.as) {

@@ -269,5 +230,6 @@ // FIXME: rather use scope from inspectRef to determin if path leads to a key

}
} else
} else {
keysOfPreviousStepWhenManagedAssoc =
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
}
}

@@ -295,7 +257,8 @@ node.ref = aliasedRef;

function isColumnInSelect(path) {
if (!path) return false;
let len = path.length;
if (!path)
return false;
const len = path.length;
if (path[len - 4] === 'SELECT' && path[len - 3] === 'columns') {
// check if -2 element is an integer / array index
let arrayIndex = parseInt(path[len - 2], 10);
const arrayIndex = parseInt(String(path[len - 2]), 10);
if (!isNaN(arrayIndex))

@@ -302,0 +265,0 @@ return true;

@@ -6,15 +6,21 @@ 'use strict';

const { cloneCsn, forEachDefinition } = require('../../model/csnUtils');
const { attachPathOnPartialCSN } = require('./attachPath');
// these functions are used for propagation of the annotations, collected along the path during flattening
const { addAnnotationsForPropagationFromElement, propagateAnnotationsToElement, resetAnnotationsForPropagation } = function () {
let toBePropagatedAnnotations = Object.create(null);
// These functions are used for propagation of the annotations, virtual, key,
// notNull attributes collected along the path during flattening.
const { addPropsForPropagationFromElement, propagatePropsToElement, resetPropsForPropagation } = function () {
let toBePropagated = Object.create(null);
return {
addAnnotationsForPropagationFromElement: function (element) {
copyAnnotations(element, toBePropagatedAnnotations);
addPropsForPropagationFromElement: function (element) {
copyAnnotations(element, toBePropagated);
if (element.virtual) toBePropagated.virtual = element.virtual;
if (element.key) toBePropagated.key = element.key;
},
propagateAnnotationsToElement: function (element) {
copyAnnotations(toBePropagatedAnnotations, element);
propagatePropsToElement: function (element) {
copyAnnotations(toBePropagated, element);
if (toBePropagated.virtual) element.virtual = toBePropagated.virtual;
if (toBePropagated.key) element.key = toBePropagated.key;
},
resetAnnotationsForPropagation: function () {
toBePropagatedAnnotations = Object.create(null);
resetPropsForPropagation: function () {
toBePropagated = Object.create(null);
}

@@ -53,2 +59,3 @@ }

* @param {*} csnUtils instances of utility functions
* @param {*} options
* @param {*} referenceFlattener

@@ -67,2 +74,3 @@ * @param {Function} error

* @param {*} csnUtils utility functions
* @param {*} options
* @param {*} referenceFlattener

@@ -77,3 +85,3 @@ * @param {Function} error

referenceFlattener.attachPaths(newFlatElements, definitionPath.concat('elements'));
attachPathOnPartialCSN(newFlatElements, definitionPath.concat('elements'));

@@ -94,17 +102,15 @@ definition.elements = newFlatElements;

* @param {boolean} [isTopLevelElement] states if this is a top level element
* @param {boolean} [isKey] true if this or the parent element is a key - will be propagated to all child elements
* @param {boolean} [propagateAnnotations]
*/
function flattenStructure(struct, path, csnUtils, options, error, referenceFlattener = undefined, elementPathInStructure = [],
newFlatElements = Object.create(null), isTopLevelElement = true, isKey = false, propagateAnnotations = false, isParentNotNull = false) {
newFlatElements = Object.create(null), isTopLevelElement = true, isParentNotNull = false) {
isTopLevelElement ? resetAnnotationsForPropagation() : addAnnotationsForPropagationFromElement(struct);
if (!isTopLevelElement) addPropsForPropagationFromElement(struct);
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure
struct.elements && Object.entries(struct.elements).forEach( ([elementName, element]) => {
struct.elements && Object.entries(struct.elements).forEach(([elementName, element]) => {
let currPath = path.concat('elements', elementName);
if (isTopLevelElement) {
isKey = element.key;
resetPropsForPropagation();
setNotNull(element.notNull)

@@ -120,7 +126,7 @@ } else {

addAnnotationsForPropagationFromElement(element);
addPropsForPropagationFromElement(element);
// if the child element is structured itself -> needs to be flattened
const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type);
let result = flattenStructure(subStruct, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isKey || element.key, true, isNotNull());
let result = flattenStructure(subStruct, currPath, csnUtils, options, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isNotNull());
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements

@@ -161,5 +167,4 @@

let newElement = cloneCsn(element, options);
if (propagateAnnotations) propagateAnnotationsToElement(newElement);
if (!isTopLevelElement) propagatePropsToElement(newElement);
if (isNotNull() === undefined) delete newElement.notNull;
if (isKey) newElement.key = true;
if (!isTopLevelElement) {

@@ -166,0 +171,0 @@ setProp(newElement, '$viaTransform', true);

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

if (def.kind === 'type' && def.items && isArtifactInSomeService(defName, services)) {
expandFirstLevelOfArrayed(def, defName);
expandFirstLevelOfArrayed(def);
}

@@ -82,2 +82,10 @@ });

if (!node) return;
// TODO: Clarify how should events be handled?
// They are not treated by the transformUtilsNew::toFinalBaseType function
// in the same manner as named types, because the elements of structured events are not
// propagated as it is with types.
// It is ok to skip the expansion to the final base type for now as events are not rendered in
// EDMX at the moment and the reference in the OData CSN is fulfilled.
if (node.kind === 'event') return;
if (isExpandable(node, defName) || node.kind === 'type') {

@@ -105,3 +113,3 @@ transformers.toFinalBaseType(node);

function isUserDefinedBuiltinFromTheCurrService(node, defName) {
// in V4 we should use TypeDefinitions whever possible, thus in case the final type of a field is
// in V4 we should use TypeDefinitions whenever possible, thus in case the final type of a field is
// a builtin from the service - do not expand to the final base type

@@ -108,0 +116,0 @@ let finalBaseType = csnUtils.getFinalBaseType(node.type);

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

getFinalBaseType,
getFinalTypeDef,
hasBoolAnnotation,

@@ -43,3 +42,2 @@ inspectRef,

getForeignKeyArtifact,
checkForeignKeys,
flattenStructuredElement,

@@ -75,3 +73,3 @@ flattenStructStepsInRef,

// if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
function addDefaultTypeFacets(element) {
function addDefaultTypeFacets(element, defStrLen5k=true) {
if (!element || !element.type)

@@ -81,6 +79,10 @@ return;

if (element.type === 'cds.String' && element.length === undefined) {
element.length = options.length ? options.length : 5000;
if (!options.length)
if(options.defaultStringLength) {
element.length = options.defaultStringLength;
setProp(element, '$default', true);
}
else if(defStrLen5k)
element.length = 5000;
}
/*
if (element.type === 'cds.Decimal' && element.precision === undefined && options.precision) {

@@ -92,2 +94,3 @@ element.precision = options.precision;

}
*/
}

@@ -198,8 +201,4 @@

artifact = artifact.items;
// Insert artificial element into artifact, with all cross-links (must not exist already)
if (artifact.elements[foreignKeyElementName]) {
const loc = artifact.elements.$path ? artifact.elements.$path.concat([foreignKeyElementName]) : ['definitions', artifactName, 'elements', foreignKeyElementName];
error(null, loc, { name: foreignKeyElementName, art: assocName },
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
}
// Insert artificial element into artifact, with all cross-links
artifact.elements[foreignKeyElementName] = foreignKeyElement;

@@ -212,37 +211,2 @@

// For an association 'assoc', check that all foreign keys (if any) actually exist in the
// target. Must only be applied after flattening structured foreign keys.
// Note that this may also be called for unmanaged associations with artificially created
// ON-conditions, because these still retain their foreign key info.
// FIXME: For all cases except implicit redirection, this should actually be done by the compiler
function checkForeignKeys(assoc, assocName, artifactName, options) {
// FIXME: Because this assumes flattening, it does not work with 'hdbcds' naming mode. Because
// it will become obsolete soon anyway (compiler checking and rewriting ON-conditions), we
// don't bother to adapt it.
if (options && options.forHana && options.forHana.keepStructsAssocs) {
return;
}
assoc.keys && Object.values(assoc.keys).forEach(foreignKey => {
let target = model.definitions[assoc.target];
// Sanity checks
if(options && options.toOdata && options.toOdata.odataFormat !== 'structured')
if (foreignKey.ref.length > 1) {
throw Error('Expecting foreign key ' + foreignKey.$generatedFieldName + ' to be flattened');
}
if (!target) {
throw Error('Expecting target of association ' + assocName + ' to be resolved');
}
if (!target.elements || Object.keys(target.elements).length === 0) {
throw Error('Expecting target of association ' + assocName + ' to have elements');
}
// Try to "resolve" the corresponding element
let targetElementName = foreignKey.ref[0];
if (target.elements[targetElementName] == undefined) {
error(null, ['definitions', artifactName, 'elements', assocName],
`Foreign key "${targetElementName}" not found in target "${assoc.target}" of association "${artifactName}.${assocName}"`);
}
});
}
// For a structured element 'elem', return a dictionary of flattened elements to

@@ -369,3 +333,3 @@ // replace it, flattening names with pathDelimiter's value and propagating all annotations and the

}
flattenStep = value.art && !(value.art.kind === 'entity') && !value.art.SELECT && (value.art._effectiveType && value.art._effectiveType.elements || value.art.elements);
flattenStep = value.art && !(value.art.kind === 'entity') && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements);
});

@@ -439,3 +403,3 @@ const magicVars = ['$now']

// The type might already be a full fledged type def (array of)
let typeDef = typeof node.type === 'string' ? getFinalTypeDef(node.type) : node.type;
let typeDef = typeof node.type === 'string' ? getCsnDef(node.type) : node.type;
// Nothing to do if type is an array or a struct type

@@ -463,9 +427,2 @@ if (typeDef.items || typeDef.elements) return;

let projectionAbsoluteName = `${service}.${projectionId}`;
// If there already is an artifact with this name, this is either the second attempt or a conflict
let existingProjection = model.definitions[projectionAbsoluteName];
if (existingProjection) {
error(null, ['definitions', projectionAbsoluteName],
`Cannot generate projection "${projectionAbsoluteName}" because of name conflict with existing artifact "${service.name.absolute}.$projectionId}"`);
return null;
}
// Create elements matching the artifact's elements

@@ -497,3 +454,2 @@ let elements = Object.create(null);

projection: query.SELECT, // it is important that projetion and query refer to the same object!
query,
elements

@@ -505,6 +461,2 @@ };

}
// Sanity check: Can't already be there (checked above)
if (model.definitions[projectionAbsoluteName]) {
throw new Error('Duplicate projection: ' + projectionAbsoluteName);
}
model.definitions[projectionAbsoluteName] = projection;

@@ -514,5 +466,13 @@ return projection;

// Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData'
// in service 'service' and add it to the model.
function createAndAddDraftAdminDataProjection(service) {
/**
* Create a 'DraftAdministrativeData' projection on entity 'DRAFT.DraftAdministrativeData'
* in service 'service' and add it to the model.
*
* For forHanaNew, use String(36) instead of UUID and UTCTimestamp instead of Timestamp
*
* @param {string} service
* @param {boolean} [hanaMode=false] Turn UUID into String(36)
* @returns {CSN.Artifact}
*/
function createAndAddDraftAdminDataProjection(service, hanaMode=false) {
// Make sure we have a DRAFT.DraftAdministrativeData entity

@@ -524,7 +484,2 @@ let draftAdminDataEntity = model.definitions['DRAFT.DraftAdministrativeData'];

}
// Barf if it is not an entity or not what we expect
if (draftAdminDataEntity.kind !== 'entity' || !draftAdminDataEntity.elements['DraftUUID']) {
error(null, ['definitions', 'DRAFT.DraftAdministrativeData'],
`Generated entity "DRAFT.DraftAdministrativeData" conflicts with existing artifact`);
}

@@ -547,3 +502,6 @@ // Create a projection within this service

// key DraftUUID : UUID
let draftUuid = createScalarElement('DraftUUID', 'cds.UUID', true);
const draftUuid = createScalarElement('DraftUUID', hanaMode ? 'cds.String' : 'cds.UUID', true);
if(hanaMode)
draftUuid.DraftUUID.length = 36;
draftUuid.DraftUUID['@UI.Hidden'] = true;

@@ -554,3 +512,3 @@ draftUuid.DraftUUID['@Common.Label'] = '{i18n>Draft_DraftUUID}';

// CreationDateTime : Timestamp;
let creationDateTime = createScalarElement('CreationDateTime', 'cds.Timestamp');
const creationDateTime = createScalarElement('CreationDateTime', hanaMode ? 'cds.UTCTimestamp' : 'cds.Timestamp');
creationDateTime.CreationDateTime['@Common.Label'] = '{i18n>Draft_CreationDateTime}';

@@ -560,3 +518,3 @@ addElement(creationDateTime, artifact, artifactName);

// CreatedByUser : String(256);
let createdByUser = createScalarElement('CreatedByUser', 'cds.String');
const createdByUser = createScalarElement('CreatedByUser', 'cds.String');
createdByUser['CreatedByUser'].length = 256;

@@ -567,3 +525,3 @@ createdByUser.CreatedByUser['@Common.Label'] = '{i18n>Draft_CreatedByUser}';

// DraftIsCreatedByMe : Boolean;
let draftIsCreatedByMe = createScalarElement('DraftIsCreatedByMe', 'cds.Boolean');
const draftIsCreatedByMe = createScalarElement('DraftIsCreatedByMe', 'cds.Boolean');
draftIsCreatedByMe.DraftIsCreatedByMe['@UI.Hidden'] = true;

@@ -574,3 +532,3 @@ draftIsCreatedByMe.DraftIsCreatedByMe['@Common.Label'] = '{i18n>Draft_DraftIsCreatedByMe}';

// LastChangeDateTime : Timestamp;
let lastChangeDateTime = createScalarElement('LastChangeDateTime', 'cds.Timestamp');
const lastChangeDateTime = createScalarElement('LastChangeDateTime', hanaMode ? 'cds.UTCTimestamp' : 'cds.Timestamp');
lastChangeDateTime.LastChangeDateTime['@Common.Label'] = '{i18n>Draft_LastChangeDateTime}';

@@ -580,3 +538,3 @@ addElement(lastChangeDateTime, artifact, artifactName);

// LastChangedByUser : String(256);
let lastChangedByUser = createScalarElement('LastChangedByUser', 'cds.String');
const lastChangedByUser = createScalarElement('LastChangedByUser', 'cds.String');
lastChangedByUser['LastChangedByUser'].length = 256;

@@ -587,3 +545,3 @@ lastChangedByUser.LastChangedByUser['@Common.Label'] = '{i18n>Draft_LastChangedByUser}';

// InProcessByUser : String(256);
let inProcessByUser = createScalarElement('InProcessByUser', 'cds.String');
const inProcessByUser = createScalarElement('InProcessByUser', 'cds.String');
inProcessByUser['InProcessByUser'].length = 256;

@@ -594,3 +552,3 @@ inProcessByUser.InProcessByUser['@Common.Label'] = '{i18n>Draft_InProcessByUser}';

// DraftIsProcessedByMe : Boolean;
let draftIsProcessedByMe = createScalarElement('DraftIsProcessedByMe', 'cds.Boolean');
const draftIsProcessedByMe = createScalarElement('DraftIsProcessedByMe', 'cds.Boolean');
draftIsProcessedByMe.DraftIsProcessedByMe['@UI.Hidden'] = true;

@@ -635,3 +593,3 @@ draftIsProcessedByMe.DraftIsProcessedByMe['@Common.Label'] = '{i18n>Draft_DraftIsProcessedByMe}';

function isDollarSelfOrProjectionOperand(arg) {
return arg.ref && arg.ref.length == 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
return arg.ref && arg.ref.length === 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
}

@@ -707,8 +665,2 @@

// Foreign key must not exist
if (elem.keys.some(key => JSON.stringify(foreignKey) === JSON.stringify(key))) {
error(null, null, `Key already exists: ${JSON.stringify(foreignKey)}`);
return;
}
// Add the foreign key

@@ -1242,43 +1194,86 @@ elem.keys.push(foreignKey);

// A generic function that applies a number of (simple) transformations to a csn model
// by visiting each node. The transformer object has properties that are looked for in
// the node(s) and the corresponding functions are applied. The functions are called
// with the oldValue, the node and the property name as parameters
// E.g. { type: fctToMapType() } as a transformer would change each type node in the model
// by replacing the oldValue of type by fctToMapType(oldValue, node, 'type')
// The function is aware of dictionaries and would not call the transformer on an
// entity called 'type'
function transformModel(model, transformers, transformNonEnumerableElements=false) {
/**
* Modify the given CSN/artifact in-place, applying the given customTransformations.
* Dictionaries are correctly handled - a "type" transformer will not be called on an entity called "type".
*
* A custom transformation function has the following signature:
* (any, object, string, CSN.Path) => undefined
*
* Given that we have a custom transformation for "type" and stumble upon a thing like below:
*
* {
* type: "cds.String",
* anotherProp: 1
* }
*
* The input for the function would be:
*
* ("cds.String", { type: <>, anotherProp: <>}, "type", [xy, "type"])
*
* @param {CSN.Model} csn
* @param {object} customTransformations Dictionary of functions to apply - if the property matches a key in this dict, it will be called
* @param {boolean} [transformNonEnumerableElements=false] Transform non-enumerable elements to work with cds linked...
* @returns {CSN.Model|CSN.Artifact} Return the CSN/artifact
*/
function transformModel(csn, customTransformations, transformNonEnumerableElements=false){
const transformers = {
elements: dictionary,
definitions: dictionary,
actions: dictionary,
params: dictionary,
enum: dictionary,
mixin: dictionary,
args: dictionary
};
return transformNode(model, undefined, undefined, []);
const csnPath = [];
if (csn.definitions)
dictionary( csn, 'definitions', csn.definitions );
else {
// fake it till you make it
const obj = { definitions: Object.create(null)};
obj.definitions.thing = csn;
dictionary(obj, 'definitions', obj.definitions);
}
// This general transformation function will be applied to each node recursively
function transformNode(node, bla, blub, path) {
// Return primitive values and null unchanged, but let objects and dictionaries through
// (Note that 'node instanceof Object' would be false for dictionaries).
if (node === null || typeof node !== 'object') {
return csn;
function standard( parent, prop, node ) {
// checking for .kind and .type is safe because annotations with such properties, are already flattened out
const isAnnotation = () => (typeof prop === 'string' && prop.startsWith('@') && !node.kind && !node.type);
if (!node || node._ignore || typeof node !== 'object' || !{}.propertyIsEnumerable.call( parent, prop ) || isAnnotation())
return;
}
// Simply return if node is to be ignored
if (node == undefined || node._ignore)
return;
// Transform arrays element-wise
csnPath.push( prop );
if (Array.isArray(node)) {
node.forEach((subnode, index) => transformNode(subnode, null, null, path.concat([index])));
return;
node.forEach( (n, i) => standard( node, i, n ) );
}
// Things not having 'proto' are dictionaries
let proto = Object.getPrototypeOf(node);
// Iterate own properties of 'node' and transform them into 'resultNode'
const toIterateOver = Object.keys(node);
// cds-linked resolves types and add's them to elements as non-enum - need to be processed
if(transformNonEnumerableElements && node.elements && !Object.prototype.propertyIsEnumerable.call(node, 'elements')){
toIterateOver.push('elements');
else {
const iterateOver = Object.getOwnPropertyNames( node );
// cds-linked resolves types and add's them to elements as non-enum - need to be processed
if(transformNonEnumerableElements && node.elements && !Object.prototype.propertyIsEnumerable.call(node, 'elements')){
iterateOver.push('elements');
}
for (const name of iterateOver) {
if(customTransformations[name])
customTransformations[name](node[name], node, name, csnPath.concat(name))
const trans = transformers[name] || standard;
trans( node, name, node[name] );
}
}
for (let key of toIterateOver) {
// Dictionary always use transformNode(), other objects their transformer according to key
let transformer = (proto == undefined) ? transformNode : transformers[key] || transformers[key.charAt(0)];
// Apply transformer, or use transformNode() if there is none
(transformer || transformNode)(node[key], node, key, path.concat([key]));
csnPath.pop();
}
function dictionary( node, prop, dict ) {
csnPath.push( prop );
if (Array.isArray(dict)) {
dict.forEach( (n, i) => standard(dict, i, n))
} else {
for (const name of Object.getOwnPropertyNames( dict ))
standard( dict, name, dict[name] );
}
csnPath.pop();
}

@@ -1285,0 +1280,0 @@ }

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

*
* @param {object} sourceObj
* @param {string} property
* @param {object} targetObj
* @param {string} property
* @param {object} sourceObj
*/
function copyPropIfExist(targetObj, property, sourceObj) {
function copyPropIfExist(sourceObj, property, targetObj) {
if (sourceObj && property in sourceObj)

@@ -14,0 +14,0 @@ targetObj[property] = sourceObj[property];

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

@@ -32,3 +32,6 @@ "homepage": "https://cap.cloud.sap/",

"LICENSE"
]
],
"engines": {
"node": ">=12"
}
}

@@ -7,2 +7,3 @@ {

"check-proper-type-of",
"duplicate-autoexposed",
"extend-repeated-intralayer",

@@ -9,0 +10,0 @@ "extend-unrelated-layer",

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

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

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc