Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.26.2 to 1.35.0

lib/api/.eslintrc.json

191

bin/cdsc.js

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

const { compactModel } = require('../lib/json/to-csn');
const { toOdataWithCsn, toHanaWithCsn, toSqlWithCsn } = require('../lib/backends');
const { toOdataWithCsn, toHanaWithCsn, toSqlWithCsn, toCdlWithCsn } = require('../lib/backends');
var util = require('util');

@@ -28,5 +28,7 @@ var fs = require('fs');

const { optionProcessor } = require('../lib/optionProcessor');
const { translatePathLocations } = require('../lib/base/messages')
const { translatePathLocations, explainMessage, hasMessageExplanation } = require('../lib/base/messages')
const { translateAssocsToJoins } = require('../lib/transform/translateAssocsToJoins');
const term = require('../lib/utils/term');
const nodeHelper = require('../lib/base/node-helpers');
const { splitLines } = require('../lib/utils/file');

@@ -89,2 +91,5 @@ // Note: Instead of throwing ProcessExitError, we would rather just call process.exit(exitCode),

// Set default command if required
cmdLine.command = cmdLine.command || 'toCsn';
// Internally, parseCdl is an option so we map the command to it.

@@ -100,2 +105,6 @@ if (cmdLine.command === 'parseCdl') {

if (cmdLine.options.directBackend) {
validateDirectBackendOption(cmdLine.command, cmdLine.options, cmdLine.args);
}
if (cmdLine.options.beta) {

@@ -107,4 +116,19 @@ const features = cmdLine.options.beta.split(',');

// Do the work for the selected command (default 'toCsn')
executeCommandLine(cmdLine.command || 'toCsn', cmdLine.options, cmdLine.args);
// Enable all beta-flags if betaMode is set to true
if(cmdLine.options.betaMode) {
cmdLine.options.beta = {
'toSwagger': true,
'toRename': true,
'subElemRedirections': true,
'technicalConfig': true,
'keyRefError': true,
'odataProxies': true,
'assocsWithParams': true,
'uniqueconstraints': true,
'cleanCsn': false,
'hanaAssocRealCardinality': true,
}
}
// Do the work for the selected command
executeCommandLine(cmdLine.command, cmdLine.options, cmdLine.args);
} catch (err) {

@@ -120,6 +144,31 @@ // This whole try/catch is only here because process.exit does not work in combination with

/**
* `--direct-backend` can only be used with certain backends and with certain files.
* This function checks these pre-conditions and emits an error if a condition isn't
* fulfilled.
*
* @param {string} command
* @param {CSN.Options} options
* @param {object} args
*/
function validateDirectBackendOption(command, options, args) {
if (!['toCdl', 'toOdata', 'toHana', 'toCsn', 'toSql'].includes(command)) {
displayUsage(`Option '--direct-backend' cannot be used with command '${command}'`,
optionProcessor.helpText, 2);
}
if (!args.files || args.files.length !== 1) {
displayUsage(`Option '--direct-backend' expects exactly one JSON file, but ${args.files.length} given`,
optionProcessor.helpText, 2);
}
const filename = args.files[0];
if (!filename.endsWith('.csn') && !filename.endsWith('.json')) {
displayUsage(`Option '--direct-backend' expects a filename with a *.csn or *.json suffix`,
optionProcessor.helpText, 2);
}
}
// Display help text 'helpText' and 'error' (if any), then exit with exit code <code>
function displayUsage(error, helpText, code) {
// Display non-error output (like help) to stdout
let out = (code == 0 && !error) ? process.stdout : process.stderr;
let out = (code === 0 && !error) ? process.stdout : process.stderr;
// Display help text first, error at the end (more readable, no scrolling)

@@ -157,20 +206,40 @@ out.write(`${helpText}\n`);

toSql,
toSwagger,
}
toSwagger
};
const commandsWithoutCompilation = {
explain,
};
if (!commands[command]) {
if (!commands[command] && !commandsWithoutCompilation[command]) {
throw new Error(`Missing implementation for command ${command}`);
}
if (commandsWithoutCompilation[command]) {
commandsWithoutCompilation[command]();
return;
}
const fileCache = Object.create(null)
compiler.compile( args.files, undefined, options, fileCache )
.then( commands[command] )
.then( displayMessages, displayErrors )
.catch( catchErrors );
const compiled = options.directBackend ?
nodeHelper.readFile( args.files[0] ).then((str) => JSON.parse( str )) :
compiler.compile( args.files, undefined, options, fileCache );
compiled.then( commands[command] )
.then( displayMessages, displayErrors )
.catch( catchErrors );
return; // below are only command implementations.
// Execute the command line option '--to-cdl' and display the results.
// Return the original model (for chaining)
function toCdl( model ) {
let cdlResult = compiler.toCdl(model);
for (let name in cdlResult) {
let cdlResult;
if(options.oldTransformers){
cdlResult = compiler.toCdl(model);
} else {
const csn = options.directBackend ? model : compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that
cdlResult = toCdlWithCsn(csn, options).result
}
for (const name in cdlResult) {
writeToFileOrDisplay(options.out, name + '.cds', cdlResult[name]);

@@ -184,4 +253,8 @@ }

function toCsn( model ) {
// Result already provided by caller
displayNamedXsn(model, 'csn', options);
if (options.directBackend) {
displayNamedCsn(model, 'csn', options);
} else {
// Result already provided by caller
displayNamedXsn(model, 'csn', options);
}
return model;

@@ -197,3 +270,3 @@ }

} else {
let csn = compactModel(model, options);
let csn = options.directBackend ? model : compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that

@@ -213,6 +286,12 @@ hanaResult = toHanaWithCsn(csn, options)

let odataResult;
if(options.toOdata &&
options.toOdata.version === 'v4x') {
options.toOdata.version = 'v4';
options.toOdata.odataFormat = 'structured';
options.toOdata.odataContainment = true;
}
if(options.oldTransformers) {
odataResult = compiler.toOdata(model)
} else {
let csn = compactModel(model, options);
let csn = options.directBackend ? model : compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that

@@ -264,3 +343,9 @@ odataResult = toOdataWithCsn(csn, options);

function toSql( model ) {
let sqlResult = options.oldTransformers ? compiler.toSql(model) : toSqlWithCsn(compactModel(model, options), options);
let sqlResult;
if (options.oldTransformers) {
sqlResult = compiler.toSql(model) ;
} else {
const csn = options.directBackend ? model : compactModel(model, options);
sqlResult = toSqlWithCsn(csn, options);
}

@@ -290,2 +375,13 @@ ['hdbtabletype', 'hdbtable', 'hdbindex', 'hdbfulltextindex', 'hdbview', 'sql'].forEach(pluginName => {

function explain() {
if (args.length !== 1)
displayUsage(`Command 'explain' expects exactly one message-id.`, optionProcessor.commands['explain'].helpText, 2);
const id = args.files[0];
if (!hasMessageExplanation(id))
console.error(`Message '${id}' does not have an explanation!`);
else
console.log(explainMessage(id));
}
// Display error messages in `err` resulting from a compilation. Also set

@@ -317,18 +413,34 @@ // process.exitCode - process.exit() will force the process to exit as quickly

function displayMessages( model, messages = model.messages ) {
if (messages instanceof Array)
{
for (let msg of messages) {
if (options.internalMsg) {
console.error( util.inspect( msg, { depth: null, maxArrayLength: null} ) );
} else if (messageLevels[ msg.severity ] <= options.warning) {
if (options.noMessageContext) {
console.error(compiler.messageString(msg, normalizeFilename, !options.showMessageId));
if (!Array.isArray(messages))
return model;
} else {
console.error(compiler.messageStringMultiline(msg, normalizeFilename, !options.showMessageId));
const fullfilepath = (msg.location && msg.location.filename )? path.resolve('', msg.location.filename) : undefined;
const sourceLines = fileCache[fullfilepath] ? fileCache[fullfilepath].split('\n') : fileCache;
console.error(compiler.messageContext(sourceLines, msg));
}
}
const log = console.error;
if (options.internalMsg) {
messages.map(msg => util.inspect( msg, { depth: null, maxArrayLength: null} ) )
.forEach(msg => log(msg));
}
else if (options.noMessageContext) {
messages.filter(msg => (messageLevels[ msg.severity ] <= options.warning))
.forEach(msg => log(compiler.messageString(msg, normalizeFilename, !options.showMessageId)));
}
else {
// Contains file-contents that are split at '\n'. Try to avoid multiple `.split()` calls.
const splitCache = Object.create(null);
const sourceLines = (name) => {
if (!splitCache[name])
splitCache[name] = fileCache[name] ? splitLines(fileCache[name]) : fileCache;
return splitCache[name];
};
let hasAtLeastOneExplanation = false;
messages.filter(msg => messageLevels[ msg.severity ] <= options.warning).forEach(msg => {
hasAtLeastOneExplanation = hasAtLeastOneExplanation || compiler.hasMessageExplanation(msg.messageId)
const name = msg.location && msg.location.filename;
const fullFilePath = name ? path.resolve('', name) : undefined;
log(compiler.messageStringMultiline(msg, normalizeFilename, !options.showMessageId));
log(compiler.messageContext(sourceLines(fullFilePath), msg));
log() // newline
});
if (options.showMessageId && hasAtLeastOneExplanation) {
log(`${term.help('help')}: Messages marked with '…' have an explanation text. Use \`cdsc explain <message-id>\` for a more detailed error description.`)
}

@@ -339,2 +451,8 @@ }

/**
* @param {XSN.Model} xsn
* @param {CSN.Model} csn
* @param {string} name
* @param {CSN.Options} options
*/
function displayNamedXsnOrCsn(xsn, csn, name, options) {

@@ -373,2 +491,7 @@ if(xsn && options.rawOutput) {

/**
* @param {CSN.Model} csn
* @param {string} name
* @param {CSN.Options} options
*/
function displayNamedCsn(csn, name, options) {

@@ -375,0 +498,0 @@ if (options.internalMsg) {

7

bin/cdsse.js

@@ -93,6 +93,9 @@ #!/usr/bin/env node

compiler.compile( [file], '', { lintMode: true, beta: true } , { [fname]: buf } )
.then( select, () => true );
.then( select, select );
return true;
function select( xsn ) {
function select( xsnOrErr ) {
const xsn = (xsnOrErr instanceof Error) ? xsnOrErr.model : xsnOrErr;
if (!xsn)
return true;
const [src] = Object.values( xsn.sources );

@@ -99,0 +102,0 @@ pathitem( src.usings );

@@ -9,2 +9,231 @@ # ChangeLog for cdx compiler and backends

## Version 1.35.0 - 2020-07-31
### Added
- Introduce option `localizedLanguageFallback`; if set to value `"none"`, the localized
convenience views do not use function `coalesce` to select from a default text as fallback.
### Fixed
- Properly consider targets of compositions in `mixin`s to be autoexposed.
- Uniformly limit propagation of `@cds.autoexposed`, i.e.
there is not inheritance from a query source navigating along an association.
Previously, compiling a compiled model could lead to new autoexposed entities.
- OData:
+ V2: Distribute various `@sap` specific annotations to the entity container.
+ Always set attribute `Nullable` on properties of type `Collection()`.
## Version 1.34.0 - 2020-07-27
### Fixed
- Do not dump with illegal references in explicit `on` conditions of redirections;
properly report them via error messages.
## Version 1.33.0 - 2020-07-24
### Added
- Allow to declare `many/array of` elements, parameters and return types to be `(not) null`.
The nullability applies to the array items of the element, not the element itself.
- New boolean option `dependentAutoexposed` to avoid name clashes in dependent
autoexposed entities (text entities, components of managed compositions).
- cdsc: Add toOdata version 'v4x' to combine `{ version: 'v4', odataFormat: 'structured', odataContainment: true }`.
### Changed
- OData:
+ Update vocabularies 'Common', 'Core', 'ODM'.
+ The default nullability for collection value properties is `false`, indicating that the returned collection must
not contain null value entries.
- toCdl: Identifiers are now quoted with `![` and `]`. Inner `]` characters are escaped with `]]`.
- toCdl/toSql: Function names containing non standard HANA identifier characters are rendered case preserving and quoted
if an appropriate naming mode has been set in the options.
### Fixed
- forHana: Correctly flatten managed associations as foreign keys used in the definition.
of another managed association.
- OData: Don't render aspects as `edm.ComplexType`.
## Version 1.32.0 - 2020-07-10
### Added
- Provide semantic code completion for elements, enums, actions and parameters
in `annotate` and `extend`.
- forHana: Allow the relational comparison of structures or managed associations in an ON condition.
Both operands must be structurally compatible, that is both structures must be expandable
to an identical set of leaf paths. Each leaf path must terminate on a builtin CDS scalar type.
The original relational term of the form `s1 op s2` is replaced by the resulting expression
`s1.leafpath_0 op s2.leafpath_0 (AND s1.leafpath_i op s2.leafpath_i)*` with `i < n leaf paths`.
### Changed
- toCdl: String enums without a value are no longer rendered with their name's string representation as their value.
### Fixed
- parseCdl: Fix missing extensions in files that extend unknown services/contexts.
- OData: Do not render an EDM document in case of raised errors
- to.cdl: Aspects are now correctly rendered as aspects and not as types
## Version 1.31.2 - 2020-07-03
### Fixed
- HANA/SQLite: Correctly handle already resolved types when a cds.linked CSN is passed in
- HANA/SQLite: Ensure that all elements in a Draft are non-virtual
## Version 1.31.0 - 2020-06-26
### Added
- forHana/toSql: A (proxy) entity representing a HANA User Defined Function or a HANA Calculation View
can now be annotated with `@cds.persistence { udf, calcview }` so that queries to these artifacts are
rendered with the appropriate parameter lists. Parameters for HANA Calculation Views are decorated with
`PLACEHOLDER."$$<id>$$"`. HANA User Defined Functions without arguments require an empty argument
list `()` as part of the query source.
Entities that are assigned with `@cds.persistence { udf, calcview }` cannot contain associations or act as
association targets, even if they have no defined parameter list.
Multiple assignments of `@cds.persistence { table, udf, calcview }` to the same entity are rejected.
- OData V4: Elements with type `array of <scalar type>` are now supported in flat mode
### Changed
- Option `beta` now only works with selective feature flags. Instead of `beta: true`, a dictionary of `<feature>: true` is expected. Available feature flags are:
1. subElemRedirections
2. keyRefError
3. aspectCompositions
4. odataProxies
5. uniqueconstraints
- OData V4: Unmanaged associations/compositions with a target cardinality of exactly one (that is `[1..1]`)
are rendered as `edmx:NavigationProperty` with attribute `Nullable="false"`
- OData: On-condition checks are now performed when generating OData as well.
- SQLite: The property length for string parameters is not longer restricted to 5000 characters.
- HANA/SQLite: Improved the error message when an entity without elements is found to make it clearer what is expected.
### Fixed
- An error is emitted if parameters in functions/actions have a default value as it is not yet supported.
For example by using `type of E:element` where `element` has a default value.
- OData V2: Derived scalar types are not rendered as `<edmx:TypeDefinition>`, so no annotation assignments to
such carriers must be rendered.
- HANA/SQLite: Fixed a bug when flattening structured elements - instead of a human-readable error, an exception was thrown.
## Version 1.30.0 - 2020-06-12
### Added
- Projections can now have `where` and `group by`/`having` clauses.
### Changed
### Fixed
- `doc` comments in CDL now support Windows-style line breaks (CRLF). They are replaced with `\n` in CSN.
- `toCdl` no longer renders a `*` column if no columns are used in the original source.
- Types that have both `type` and `items`/`elements` properties in CSN are now checked to avoid
mismatches if a unstructured / non-arrayed type is referenced but `items`/`elements` exists.
- OData:
+ Correctly render CRLF and LF to __&#xa;__ in EDMX
## Version 1.29.0 - 2020-06-08
### Added
- Projections can now have `limit` and `order by` clauses.
### Changed
- OData: Update vocabularies 'CodeList', 'Common', 'Graph', 'UI'
### Fixed
- Memory usage improvement - compile messages with id `ref-undefined-excluding` uses much less memory.
- HANA/SQL: Validate ON conditions of mixin association definitions in all subqueries
- OData V2: Assign various `@sap` annotations to the `<edmx:EnitySet>` and `<edmx:AssociationSet>`
if such annotations are assigned to CDS entities or associations.
- OData V4 Structured: Omit foreign keys of managed associations that establish the containment relationship to
a container, if this association was declared to be primary key.
- OData: Warn about non-integer enums as they are not supported by OData, yet.
- Warn about string values in integer enums and vice versa.
## Version 1.28.0 - 2020-05-27
### Added
- API: add `getArtifactCdsPersistenceName()` and `getElementCdsPersistenceName()` which return
the value of annotation `@cds.persistence.name` for the corresponding artifact/element.
### Changed
- Issue error if old backends are used with beta mode.
- Raise severity of message `Unmanaged associations cannot be used as primary key` with id `unmanaged-as-key` to error.
### Fixed
- OData:
+ Render vocabulary `<edmx:Reference>` and `<edmx:Include>` if vocabulary namespace was used.
+ Reduce memory consumption in EDM Renderer.
+ Render annotations for navigation properties if association is annotated with `@cds.api.ignore: true`.
## Version 1.27.0 - 2020-05-15
### Added
### Changed
- Improve warning messages for integer enum missing a value and chained array of.
- HANA/SQL
+ Empty structures are not allowed as foreign keys.
- Report a warning for integer enum elements that do not have values.
- Report a warning for enums that are not integer- or string-like.
- OData
+ Update vocabularies 'Common', 'Core', 'Validation'
+ Pass through structures without elements
+ `cds.Decimal` and `cds.DecimalFloat` (deprecated) without precision/scale are rendered
as `Edm.Decimal` with `Scale=variable` (V4) and `sap:variable-scale="true"` (V2)
### Fixed
- Memory usage improvement - compile messages do not inherit from Error any more.
- HANA types in annotation assignments work again.
- HANA/SQL: Correctly handle temporal in conjunction with namespaces.
- Fix a bug in Association to Join translation that prevents exposing managed associations via subqueries.
### Removed
## Version 1.26.4 - 2020-05-08
### Added
- Add new OData vocabulary `com.sap.vocabularies.HTML5.v1`
### Changed
- Report a warning when a deprecated non-snapi backend (OData, HANA/SQL) is called.
- OData:
+ Update vocabulary 'UI'
+ Add annotation `@Common.Label: '{i18n>Draft_DraftAdministrativeData}'` to entity `DraftAdministrativeData`
+ Improve info message for target mismatch on associations that are compared with $self
### Fixed
- The CSN `val` property is now allowed in enum element extensions. Such CSN can be
generated using the `parseCdl` mode and it is now compilable.
- Again allow negative values as enum values, fixing a regression introduced with v1.24.6.
- OData: Correctly handle associations in arrayed elements (keyword `many`).
- Annotation assignment checks now recognize HANA types.
## Version 1.26.2 - 2020-04-24

@@ -11,0 +240,0 @@

@@ -369,3 +369,3 @@ # Name Resolution in CDS

* When using associations, (mutually) recursive usage is quote common, and forward references are cumbersome.
* When using associations, (mutually) recursive usage is quite common, and forward references are cumbersome.
(We can always access shadowed artifacts.)

@@ -412,3 +412,3 @@ * Real-world models will very likely reside in multiple files/resources – there is no natural order in which the definitions are to be processed.

The navigation environment might depend on on the argument position.
The navigation environment might depend on the argument position.

@@ -453,3 +453,3 @@ If an object is typed with an array,

A reference to a main artifact can be a reference to a
A reference to a main artifact can be a reference to a:

@@ -491,3 +491,3 @@ * **projection or view source** (table reference after `SELECT … FROM` in SQL),

```
nameprefix test;
namespace test;
using cds.Boolean as Integer;

@@ -554,3 +554,3 @@ type Time {

* We usually have just one lexical search environment
which is sometimes only inpected if the path consists of at least two identifiers.
which is – dependening on the call context – only inspected if the path consists of at least two identifiers.
This basically introduces an **escape mechanism**.

@@ -564,3 +564,3 @@ * The last, non-lexical environments is usually the environment either

The semantics is best explained separately for the _diffent groups_ of argument positions.
The semantics is best explained separately for the _different groups_ of argument positions.

@@ -590,3 +590,3 @@

the SQL way is to use `:param1` instead of `$parameters.param1`, see below.
* The last, non-lexical environment is the environent containing the elements from all source entities of the current SELECT;
* The last, non-lexical environment is the environment containing the elements from all source entities of the current SELECT;
if an element with the same name is contained in more than one source,

@@ -596,5 +596,2 @@ this search environment binds the name to an "ambiguity" entry (i.e. a reference to it leads to an error)

The lexical search environments are only inspected
if the path consists of at least two identifiers.
The above mentioned `:`-escape mechanism leads to the following name resolution:

@@ -612,3 +609,3 @@

* **calculated field**,
* **calculated field**
* references in the **`default` value** (HANA SQL does not allow this)

@@ -623,3 +620,3 @@ * references in the `ON` condition of an **unmanaged association***

to the current instance of that artifact, e.g. the current line of an entity.
This environment is also inspecteded if the path consists of just `$self` –
This environment is also inspected if the path consists of just `$self` –
useful for `on` conditions of unmanaged associations.

@@ -683,3 +680,3 @@ * The second and last, the non-lexical search environment is the environment supplied by

If there is a annotation definition which allows to use paths
If there is an annotation definition which allows to use paths
by specifying the type _`cds.ArtifactRef`_ (or a variant of it),

@@ -689,3 +686,3 @@ then the path resolution works as described in

If there is a annotation definition which allows to use paths
If there is an annotation definition which allows to use paths
by specifying the type _`cds.ElementRef`_

@@ -695,3 +692,3 @@ then the path resolution works as described in

If that annotation is assigned to a main artifact
then _same main artifact_ mean the main artifact itself.
then _same main artifact_ means the main artifact itself.

@@ -721,3 +718,3 @@

we do not want to have the "extended" lexical scoping semantics of HANA CDS concerning elements,
which heavily relies on the package hierchy.
which heavily relies on the package hierarchy.
To avoid silent semantic changes with extensions,

@@ -803,3 +800,3 @@ the HANA-CDS compiler enforces the following properties:

If the search is not successful so far,
we finally inpect an environment containing artifacts
we finally inspect an environment containing artifacts
which are normally not defined in our own source:

@@ -806,0 +803,0 @@

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

const backends = require('../backends');
const { setProp } = require('../base/model');
const { setProp, isBetaEnabled } = require('../base/model');
const alerts = require('../base/alerts');

@@ -16,2 +16,4 @@ const { emptyLocation } = require('../base/location');

const { transform4odataWithCsn } = require('../transform/forOdataNew.js');
const { toSqlDdl } = require('../render/toSql');
const { compareModels } = require('../modelCompare/compare');

@@ -143,2 +145,3 @@ const propertyToCheck = {

const internalOptions = prepareOptions.for.odata(options);
cloneCsnMessages(csn, options, internalOptions);
const result = odataInternal(csn, internalOptions);

@@ -160,2 +163,3 @@

const internalOptions = prepareOptions.to.cdl(externalOptions);
cloneCsnMessages(csn, externalOptions, internalOptions);
const { result, options } = backends.toCdlWithCsn(cloneCsn(csn), internalOptions);

@@ -176,2 +180,3 @@

const internalOptions = prepareOptions.to.sql(options);
cloneCsnMessages(csn, options, internalOptions);
const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);

@@ -185,2 +190,36 @@

/**
* BETA: Return all the bits and pieces needed for schema evolution with SQL
*
* @param {CSN.Model} csn A clean input CSN representing the desired "after"
* @param {CSN.Model} sourceState A HANA transformed CSN representing the "before"
* @param {hdiOptions} [options={}] Options
* @returns {object} - createStatements: A dictionary of SQL CREATE statements - but only the subset of added things to the sourceState!
* - stateChanges: A dictionary of Arrays of SQL ALTER statements, where the key is the filename of the corresponding
* `.hdbtable` artifact. Ideally, the SQL statements describe the steps needed to get from sourceState to targetState.
* - targetState: A CSN representing what would exist on the db, if the returned `createStatements` are executed on top of the sourceState (and deletions are handled somehow).
*/
function mtx(csn, sourceState, options = {}) {
if (!isBetaEnabled(options, 'to.sql.mtx'))
throw new Error('"to.sql.mtx" is only available with beta!');
const internalOptions = prepareOptions.to.sql(options);
cloneCsnMessages(csn, options, internalOptions);
// get CSN output
internalOptions.toSql.csn = true;
const targetState = backends.toSqlWithCsn(csn, internalOptions).csn;
const diff = compareModels(sourceState, targetState, true);
// Make it pass the SQL rendering
internalOptions.forHana = true;
const result = toSqlDdl(diff, internalOptions);
return {
createStatements: result.sql,
stateChanges: result.alterTable,
targetState,
};
}
sql.mtx = mtx;
/**
* Process the given CSN into HDI artifacts.

@@ -194,2 +233,3 @@ *

const internalOptions = prepareOptions.to.hdi(options);
cloneCsnMessages(csn, options, internalOptions);
const intermediateResult = backends.toSqlWithCsn(csn, internalOptions);

@@ -201,2 +241,35 @@

/**
* BETA: Return all the bits and pieces needed for .hdbmigrationtable
*
* @param {CSN.Model} csn A clean input CSN representing the desired "after"
* @param {CSN.Model} sourceState A HANA transformed CSN representing the "before"
* @param {hdiOptions} [options={}] Options
* @returns {object} - hdiArtifacts: The same result as it would be returned by `to.hdi`.
* - stateChanges: A dictionary of Arrays of SQL ALTER statements as needed by `.hdbtablemigration`, where the key is the filename of the corresponding
* `.hdbtable` artifact. Ideally, the SQL statements describe the steps needed to get from sourceState to targetState.
* - targetState: A CSN representing what would exist on the db, if the returned `hdiArtifacts` are deployed.
*/
function hdiMigration(csn, sourceState, options = {}) {
if (!isBetaEnabled(options, 'to.hdi.migration'))
throw new Error('"to.hdi.migration" is only available with beta!');
const internalOptions = prepareOptions.to.hdi(options);
cloneCsnMessages(csn, options, internalOptions);
// get CSN output
internalOptions.toSql.csn = true;
const targetState = backends.toSqlWithCsn(csn, internalOptions).csn;
const diff = compareModels(sourceState, targetState);
// Make it pass the SQL rendering
internalOptions.forHana = true;
const { alterTable, ...hdiArtifacts } = toSqlDdl(diff, internalOptions);
return {
hdiArtifacts: flattenResultStructure(hdiArtifacts),
stateChanges: alterTable,
targetState,
};
}
hdi.migration = hdiMigration;
/**
* Process the given CSN into HDBCDS artifacts.

@@ -210,2 +283,3 @@ *

const internalOptions = prepareOptions.to.hdbcds(options);
cloneCsnMessages(csn, options, internalOptions);
const intermediateResult = backends.toHanaWithCsn(csn, internalOptions);

@@ -226,19 +300,21 @@

const internalOptions = prepareOptions.to.edm(
// eslint-disable-next-line comma-dangle
options.service ? options : Object.assign({ service: undefined }, options)
);
cloneCsnMessages(csn, options, internalOptions);
const { service } = options;
let services;
let servicesAndMessages;
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
services = backends.preparedCsnToEdm(csn, service, internalOptions);
servicesAndMessages = backends.preparedCsnToEdm(csn, service, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = backends.preparedCsnToEdm(oDataCsn, service, internalOptions);
servicesAndMessages = backends.preparedCsnToEdm(oDataCsn, service, internalOptions);
}
internalOptions.messages = servicesAndMessages.messages;
processMessages(internalOptions, options);
return services;
return servicesAndMessages.edmj;
}

@@ -257,2 +333,3 @@

const internalOptions = prepareOptions.to.edm(options);
cloneCsnMessages(csn, options, internalOptions);
const { error, signal } = alerts(csn, internalOptions);

@@ -264,3 +341,3 @@

let services;
let servicesAndMessages;

@@ -271,9 +348,10 @@ const result = {};

checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
services = backends.preparedCsnToEdmAll(csn, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmAll(csn, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = backends.preparedCsnToEdmAll(oDataCsn, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmAll(oDataCsn, internalOptions);
}
const { messages } = servicesAndMessages;
const services = servicesAndMessages.edmj;
for (const serviceName in services) {

@@ -285,3 +363,3 @@ const lEdm = services[serviceName];

}
internalOptions.messages = messages;
processMessages(internalOptions, options);

@@ -300,19 +378,21 @@ return result;

const internalOptions = prepareOptions.to.edmx(
// eslint-disable-next-line comma-dangle
options.service ? options : Object.assign({ service: undefined }, options)
);
cloneCsnMessages(csn, options, internalOptions);
const { service } = options;
let services;
let servicesAndMessages;
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
services = backends.preparedCsnToEdmx(csn, service, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmx(csn, service, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = backends.preparedCsnToEdmx(oDataCsn, service, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmx(oDataCsn, service, internalOptions);
}
processMessages(internalOptions, options);
return services;
return servicesAndMessages.edmx;
}

@@ -331,3 +411,4 @@

const internalOptions = prepareOptions.to.edmx(options);
let services;
cloneCsnMessages(csn, options, internalOptions);
let servicesAndMessages;

@@ -338,16 +419,17 @@ const result = {};

checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata);
services = backends.preparedCsnToEdmxAll(csn, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmxAll(csn, internalOptions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = backends.preparedCsnToEdmxAll(oDataCsn, internalOptions);
servicesAndMessages = backends.preparedCsnToEdmxAll(oDataCsn, internalOptions);
}
const { messages } = servicesAndMessages;
const services = servicesAndMessages.edmx;
// Create annotations and metadata once per service
for (const serviceName in services) {
const lEdm = services[serviceName];
result[serviceName] = lEdm;
}
internalOptions.messages = messages;
processMessages(internalOptions, options);

@@ -376,2 +458,19 @@ return result;

/**
* In case of recompilation, `csn.messages` may contain messages whereas
* `internalOptions.messages` may not.
* This function copies the messages so that they can be reclassified in
* the backends. Messages are only copied if user's `options.messages`
* does not exist. If it does then all messages should be there to
* begin with.
*
* @param {CSN.Model} csn User's CSN model
* @param {CSN.Model} externalOptions User's options.
* @param {CSN.Options} internalOptions Internal options, already cloned from user's options.
*/
function cloneCsnMessages(csn, externalOptions, internalOptions) {
if (!externalOptions.messages && csn.messages)
internalOptions.messages = [ ...internalOptions.messages, ...csn.messages ];
}
/**
* Flatten the result structure to a flat map.

@@ -397,2 +496,42 @@ *

/**
* Compute the .hdbcds files that would have been generated by the csn for an undeploy.json.
* This is needed for the handover between hdbcds and hdbtable - the existing hdbcds artifacts need to be undeployed.
*
* @param {any} csn A clean input CSN
* @param {hdbcdsOptions} [options={}] Options
* @returns {string[]} Array of .hdbcds filenames
*/
function undeploy(csn, options) {
const hdbcdsResult = hdbcds(csn, options);
return Object.keys(hdbcdsResult);
}
/**
* This function simply renders the given CSN to SQL - no integrity checks, no transformations, no guarantees for correctness.
* Strictly for internal evaluation!
*
* @param {CSN.Model} csn A CSN - for things to work correctly, this is expected to be a DB transformed CSN. Plain CSN might work - or might not.
* @param {sqlOptions} [options={}] Options
* @returns {SQL[]} Array of SQL statements, tables first, views second - the resulting statements might not be a consistent, deployable state!
*/
function renderSQL(csn, options) {
const internalOptions = prepareOptions.to.sql(options);
if (!isBetaEnabled(internalOptions, 'renderSQL'))
throw new Error('renderSQL is only available with beta-flag "renderSQL".');
cloneCsnMessages(csn, options, internalOptions);
// Add flag so it thinks we ran through forHanaNew
internalOptions.forHana = true;
const sqlResult = toSqlDdl(csn, internalOptions);
processMessages(internalOptions, options);
const result = Object.values(flattenResultStructure(sqlResult));
return result;
}
module.exports = {

@@ -406,2 +545,4 @@ odata: publishCsnProcessor(odata, 'for.odata'),

edmx: publishCsnProcessor(edmx, 'to.edmx'),
renderSQL,
undeploy,
};

@@ -418,5 +559,11 @@

function recompile(csn, options) {
// TODO: is it really a good idea to set options in the input parameter?
// (as long as we have the messages meddling of the option processor...)
// OK, I try a copy
options = { ...options };
// Explicitly set parseCdl to false because backends cannot handle
// the option and is only intended for CDL sources.
options.parseCdl = false;
// Explicitly delete all toCsn options
delete options.toCsn;
/* eslint-disable global-require */

@@ -426,3 +573,4 @@ const { augment } = require('../json/from-csn');

/* eslint-enable global-require */
const xsn = augment(csn); // in-place
const file = csn.$location && csn.$location.file || '<stdin>.csn';
const xsn = augment(csn, file); // in-place
return compileSources( { '<stdin>.csn': xsn }, options );

@@ -442,2 +590,8 @@ }

if (processor.migration)
api.migration = publishCsnProcessor(processor.migration);
if (processor.mtx)
api.mtx = publishCsnProcessor(processor.mtx);
return api;

@@ -536,2 +690,3 @@

* @property {boolean} [beta=false] Enable experimental features - not for productive use!
* @property {boolean} [dependentAutoexposed=false] For dependent autoexposed entities (managed compositions, texts entity), follow name of base entity
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities

@@ -538,0 +693,0 @@ * @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message

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

'beta',
'dependentAutoexposed',
'longAutoexposed',
'localizedLanguageFallback', // why can't I define the option type here?
'severities',

@@ -34,3 +36,4 @@ 'messages',

'testMode',
'compatibility',
'internalMsg',
'localizedWithoutCoalesce', // experiment version of 'localizedLanguageFallback',
];

@@ -66,3 +69,8 @@

// only "new-style" options are here
validate(options, customValidators, combinationValidators);
validate(options,
// TODO: is there a better place to specify the type of option values?
Object.assign( {
localizedLanguageFallback: generateStringValidator([ 'none', 'coalesce' ]),
}, customValidators ),
combinationValidators);

@@ -69,0 +77,0 @@ // Overwrite with the hardRequire options - like src: sql in to.sql()

@@ -46,4 +46,4 @@ 'use strict';

beta: {
validate: val => val !== null && [ 'object', 'boolean' ].includes(typeof val) && !Array.isArray(val),
expected: () => 'type object or boolean',
validate: val => val !== null && typeof val === 'object' && !Array.isArray(val),
expected: () => 'type object',
found: (val) => {

@@ -50,0 +50,0 @@ return val === null ? val : `type ${ typeof val }`;

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

const alerts = require('./base/alerts');
const { setProp } = require('./base/model');
const { handleMessages } = require('./base/messages');
const { setProp, isBetaEnabled } = require('./base/model');
const { optionProcessor } = require('./optionProcessor')
const timetrace = require('./utils/timetrace');
function deprecated( model, options, signal, backend ) {
if (options.beta || options.betaMode) { // by intention, no test for individual beta features
signal(signal.error`Deprecated backend '${backend}' cannot be used with beta features`);
handleMessages( model, options );
}
else if (model.$newfeatures) {
signal(signal.error`Deprecated backend '${backend}' cannot be used with most new features like ${model.$newfeatures}`);
handleMessages( model, options );
}
else {
signal(signal.warning`Deprecated backend '${backend}' - do not disable feature 'snapi'`);
}
}
/**

@@ -51,4 +66,4 @@ * Transform an augmented CSN 'model' into HANA-compatible CDS source.

*
* @param {CSN.Model} model
* @param {object} [options]
* @param {XSN.Model} model
* @param {CSN.Options} [options]
*/

@@ -70,5 +85,6 @@ function toHana(model, options) {

const { warning, signal } = alerts(model);
deprecated( model, options, signal, 'toHana' ); // -> to.hdbcds
// Backward compatibility for old naming modes
// FIXME: Remove after a few releases
const { warning, signal } = alerts(model);
if (options.toHana.names === 'flat') {

@@ -116,4 +132,4 @@ signal(warning`Option "{ toHana.names: 'flat' }" is deprecated, use "{ toHana.names: 'plain' }" instead`);

* The twin of the toHana function but using CSN as a model *
* @param {CSN.Model} csn
* @param {object} [options]
* @param {CSN.Model} csn
* @param {CSN.Options} [options]
*/

@@ -158,2 +174,3 @@ function toHanaWithCsn(csn, options) {

setProp(sorted, "options", forHanaCsn.options);
setProp(sorted, "messages", forHanaCsn.messages);
result.hdbcds = toCdsSourceCsn(sorted, options);

@@ -239,2 +256,3 @@ } else {

options = mergeOptions({ toOdata : getDefaultBackendOptions().toOdata }, model.options, options);
deprecated( model, options, signal, 'toOdata' ); // -> for.odata / to.edm / to.edmx

@@ -314,4 +332,4 @@ // Provide something to generate if nothing else was given (conditional default)

*
* @param {CSN.Model} csn
* @param {object} [options]
* @param {CSN.Model} csn
* @param {CSN.Options} [options]
*/

@@ -389,3 +407,6 @@ function toOdataWithCsn(csn, options) {

let edmx = csn2edm(csn, service, options).toXML('all');
return edmx;
return {
edmx,
messages: options.messages || csn.messages,
};
}

@@ -402,3 +423,6 @@

}
return edmx;
return {
edmx,
messages: options.messages || csn.messages,
};
}

@@ -412,3 +436,6 @@

let edmj = csn2edm(csn, service, options).toJSON();
return edmj;
return {
edmj,
messages: options.messages || csn.messages,
};
}

@@ -425,3 +452,6 @@

}
return edmj;
return {
edmj,
messages: options.messages || csn.messages,
};
}

@@ -448,10 +478,17 @@

*
* @param {CSN.Model} model
* @param {object} [options]
* @param {XSN.Model} model
* @param {CSN.Options} [options]
*/
function toCdl(model, options) {
const { signal } = alerts(model);
options = handleToCdlOptions(model, options);
deprecated( model, options, signal, 'toCdl' ); // -> to.cdl/to.hdbcds
return toCdsSource(model, options);
}
/**
* @param {XSN.Model | CSN.Model} model
* @param {CSN.Options} options
* @param {boolean} [silent]
*/
function handleToCdlOptions(model, options, silent=false){

@@ -476,3 +513,7 @@ // In case of API usage the options are in the 'options' argument

function toCdlWithCsn(csn, options){
/**
* @param {CSN.Model} csn
* @param {CSN.Options} options
*/
function toCdlWithCsn(csn, options) {
options = handleToCdlOptions(csn, options, true);

@@ -527,3 +568,3 @@ const result = toCdsSourceCsn(csn, options);

// hide to swagger behind betaMode
if (options.betaMode) {
if (isBetaEnabled(options, 'toSwagger')) {
signal(warning`The to swagger backend is experimental`);

@@ -574,3 +615,2 @@ } else {

const { warning, error, signal } = alerts(model);
// when toSql is invoked via the CLI - toSql options are under model.options

@@ -597,2 +637,3 @@ // ensure the desired format of the user option

options = mergeOptions({ toSql : getDefaultBackendOptions().toSql }, model.options, options);
deprecated( model, options, signal, 'toSql' ); // -> to.sql, to.hdi

@@ -694,4 +735,4 @@ // Provide something to generate if nothing else was given (conditional default)

*
* @param {CSN.Model} model
* @param {object} [options]
* @param {CSN.Model} model
* @param {CSN.Options} [options]
*/

@@ -858,3 +899,3 @@ function toSqlWithCsn(model, options) {

// Requires beta mode
if (!options.betaMode) {
if (!isBetaEnabled(options, 'toRename')) {
signal(error`Generation of SQL rename statements is not supported yet (only in beta mode)`);

@@ -861,0 +902,0 @@ }

@@ -17,2 +17,4 @@ // Implementation of alerts

const messageBuilder = require('./message-builder');
/**

@@ -30,4 +32,4 @@ * Return an object with functions for alert handling on 'model'

*
* @param {CSN.Model} model
* @param {object} [options]
* @param {XSN.Model|CSN.Model} model
* @param {CSN.Options} [options]
*/

@@ -90,9 +92,8 @@ function alerts(model, options = model.options || {}) {

* @param {XSN.Location|CSN.Location|any[]} [location] Location information (possibly semantic location)
* @param {string} [severity] severity: Info, Warning, Error
* @param {string} [severity] severity: Info, Warning, Error
* @param {string} [message_id=''] Message ID
* @param {any} [context] Further context information
* @returns {Boolean} true, if no 'Error' alert was handled.
*/
function signal(msg, location, severity, message_id='', context) {
const err = messageBuilder(model, msg, location, message_id in config ? config[message_id] : severity, message_id, context);
function signal(msg, location, severity, message_id='') {
const err = messageBuilder(model, msg, location, message_id in config ? config[message_id] : severity, message_id, options.internalMsg);
if (err.severity)

@@ -112,4 +113,2 @@ // don't collect stuff that doesn't have a severity

const messageBuilder = require('./message-builder');
module.exports = alerts;

@@ -27,3 +27,4 @@ // Deep copy an object structure

desc.value = newObj;
Object.defineProperty(target, prop, desc);
if (!(prop in target)) // shudder, also used for arrays
Object.defineProperty(target, prop, desc);
} else {

@@ -30,0 +31,0 @@ target[prop] = newObj;

@@ -62,8 +62,10 @@ 'use strict';

function normalizeLocation( loc ) {
if (!loc || !loc.file)
// `file` may be undefined, though it should not.
// TODO: `loc` may also be a string from $location
if (!loc || typeof loc !== 'object' || !('file' in loc))
return loc;
const location = {
filename: loc.file,
start: { line: loc.line, column: loc.col || 0 },
end: { line: loc.endLine || loc.line, column: loc.endCol || loc.col || 0 },
start: { line: loc.line || 0, column: loc.col || 0 },
end: { line: loc.endLine || loc.line || 0, column: loc.endCol || loc.col || 0 },
};

@@ -70,0 +72,0 @@ if (!loc.endLine)

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

const { CompileMessage } = require('../base/messages');
const { CompileMessage, DebugCompileMessage } = require('../base/messages');
/**
* @param {CSN.Model} model
* @param {XSN.Model | CSN.Model} model
* @param {any} msg

@@ -9,5 +9,5 @@ * @param {XSN.Location|CSN.Location|CSN.Path} location

* @param {string} message_id
* @param {any} context
* @param {boolean} useDebugMsg
*/
function buildMessage(model, msg, location, severity, message_id, context){
function buildMessage(model, msg, location, severity, message_id, useDebugMsg){
let semanticLocation = undefined;

@@ -19,3 +19,6 @@ if(Array.isArray(location)){

}
return new CompileMessage(location, msg, severity ? severity : msg._severity, message_id, beautifySemanticLocation(semanticLocation), context);
severity = severity || msg._severity;
return (useDebugMsg)
? new DebugCompileMessage(location, msg, severity, message_id, beautifySemanticLocation(semanticLocation))
: new CompileMessage(location, msg, severity, message_id, beautifySemanticLocation(semanticLocation));

@@ -44,11 +47,11 @@ /** @param {CSN.Path} semanticLocation */

if(semanticLocation.length == 0){
if (semanticLocation.length === 0){
throw new Error('An empty semantic location was supplied, this should not happen!');
}
if(semanticLocation[0] !== 'definitions'){
if (semanticLocation[0] !== 'definitions'){
throw new Error('Semantic locations must start with "definitions", found: ' + semanticLocation[0]);
}
if(semanticLocation.length == 1){
if (semanticLocation.length === 1){
throw new Error('Semantic locations must at least point to an artifact!');

@@ -55,0 +58,0 @@ }

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

const { normalizeLocation } = require('./location');
const fs = require('fs');
const path = require('path');

@@ -16,2 +18,3 @@ // For messageIds, where no severity has been provided via code (central def)

'ref-undefined-art': 'Error',
'ref-rejected-on': ['Error'], // TODO: 'Error'
'anno-undefined-def': 'Info', // for annotate statement (for CSN or CDL path cont)

@@ -44,2 +47,6 @@ 'anno-undefined-art': 'Info', // for annotate statement (for CDL path root)

},
'ref-rejected-on': {
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',
},
'anno-undefined-def': 'Artifact $(ART) has not been found',

@@ -61,3 +68,3 @@ 'anno-undefined-art': 'No artifact has been found with name $(NAME)',

'expected-const': 'A constant value is expected here',
'expected-struct': 'A structured type or a non-query entity without parameters is expected here',
'expected-struct': 'An aspect or a non-query entity without parameters is expected here',
'expected-context': 'A context or service is expected here',

@@ -69,2 +76,9 @@ 'expected-type': 'A type or an element of a type is expected here',

const availableMessageExplanations = {
'check-proper-type': true,
'check-proper-type-of': true,
'redirected-to-ambiguous': true,
'redirected-to-unrelated': true,
};
/**

@@ -96,3 +110,6 @@ * Returns true if at least one of the given messages is of severity "Error"

return loc;
if (!loc.end || loc.$weak) {
if (!loc.start || !loc.start.line) {
return filename;
}
else if (!loc.end || loc.$weak) {
return (loc.start.column)

@@ -103,3 +120,3 @@ ? `${filename}:${loc.start.line}:${loc.start.column}`

else {
return (loc.start.line == loc.end.line)
return (loc.start.line === loc.end.line)
? `${filename}:${loc.start.line}:${loc.start.column}-${loc.end.column}`

@@ -121,3 +138,3 @@ : `${filename}:${loc.start.line}.${loc.start.column}-${loc.end.line}.${loc.end.column}`;

* Creates an instance of CompilationError.
* @param {array} errs vector of errors (CompileMessage and errors from peg.js)
* @param {array} messages vector of errors (CompileMessage and errors from peg.js)
* @param {object} [model] the CSN model

@@ -129,7 +146,8 @@ * @param {string} [text] Text of the error

*/
constructor(errs, model, text, ...args) {
super( text || 'CDS compilation failed\n' + errs.map( m => m.toString() ).join('\n'),
constructor(messages, model, text, ...args) {
super( text || 'CDS compilation failed\n' + messages.map( m => m.toString() ).join('\n'),
// @ts-ignore Error does not take more arguments according to TypeScript...
...args );
this.errors = errs; // TODO: rename to messages
this.errors = messages; // TODO: remove
// this.messages = messages; // use this instead

@@ -139,4 +157,4 @@ /** @type {object} model */

/** @type {boolean} model */
this.hasBeenReported = false;
this.hasBeenReported = false; // TODO: remove this bin/cdsc.js specifics
// TODO: remove property `model`
Object.defineProperty( this, 'model', { value: model, configurable: true } );

@@ -151,5 +169,4 @@ }

// TODO: make it really extend Error?, change param order
/**
* Class for individual compile errors.
* Class for individual compile message.
*

@@ -159,3 +176,3 @@ * @class CompileMessage

*/
class CompileMessage extends Error {
class CompileMessage {
/**

@@ -168,7 +185,37 @@ * Creates an instance of CompileMessage.

* @param {any} [home]
* @param {any} [context] Some further context information, if needed
*
* @memberOf CompileMessage
*/
constructor(location, msg, severity = 'Error', id, home, context) {
constructor(location, msg, severity = 'Error', id = null, home = null) {
this.message = msg;
this.location = normalizeLocation( location );
if (home) // semantic location, e.g. 'entity:"E"/element:"x"'
this.home = home;
this.severity = severity;
if (id)
Object.defineProperty( this, 'messageId', { value: id } );
// this.messageId = id; // ids not yet finalized
}
toString() { // should have no argument...
return messageString( this, undefined, true ); // no message-id before finalization!
}
}
/**
* Class for individual compile message.
*
* @class CompileMessage
* @extends {Error}
*/
class DebugCompileMessage extends Error {
/**
* Creates an instance of DebugCompileMessage, used with option `internalMsg`
* @param {any} location Location of the message
* @param {string} msg The message text
* @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {string} [id] The ID of the message - visible as property messageId
* @param {any} [home]
*
* @memberOf CompileMessage
*/
constructor(location, msg, severity = 'Error', id = null, home = null) {
super(msg);

@@ -182,4 +229,2 @@ this.location = normalizeLocation( location );

// this.messageId = id; // ids not yet finalized
if (context)
this.context = context;
}

@@ -241,4 +286,6 @@ toString() { // should have no argument...

: messageText( texts || standardTexts[id], params, transform );
let msg = new CompileMessage( location, text, s, id,
(typeof home === 'string' ? home : homeName(home)) );
home = (typeof home === 'string') ? home : homeName(home);
let msg = (options.internalMsg)
? new DebugCompileMessage( location, text, s, id, home )
: new CompileMessage( location, text, s, id, home );
messages.push( msg );

@@ -395,3 +442,4 @@ return msg;

function messageStringMultiline( err, normalizeFilename, noMessageId, noHome ) {
const msgId = (err.messageId && !noMessageId ? '[' + err.messageId + ']' : '');
const explainHelp = hasMessageExplanation(err.messageId) ? '…' : '';
const msgId = (err.messageId && !noMessageId) ? `[${ err.messageId }${ explainHelp }]` : '';
const home = (!err.home || (noHome && err.location && !err.location.$weak) ? '' : ' (in ' + err.home + ')');

@@ -412,4 +460,6 @@ const severity = err.severity || 'Error';

*
* @param {string[]} sourceLines The source code split up into lines, e.g. by `src.split('\n')`
* @param {string[]} sourceLines The source code split up into lines, e.g. by `splitLines(src)`
* from `lib/utils/file.js`
* @param {CSN.Message} err Error object containing all details like line, message, etc.
* @returns {string}
*/

@@ -455,8 +505,8 @@ function messageContext(sourceLines, err) {

.padEnd(Math.max(loc.start.column, endColumn), '^');
msg += indent + '| ' + term.asSeverity(severity, highlighter) + '\n';
} else if (maxLine != endLine) {
msg += indent + '| ' + term.asSeverity(severity, highlighter);
} else if (maxLine !== endLine) {
// error spans more lines which we don't print
msg += indent + '| ...' + '\n';
msg += indent + '| ...';
} else {
msg += indent + '|' + '\n';
msg += indent + '|';
}

@@ -466,2 +516,3 @@

}
/**

@@ -492,6 +543,29 @@ * Compare two messages `a` and `b`. Return 0 if they are equal, 1 if `a` is

function c( x, y ) {
return (x == y) ? 0 : (x > y) ? 1 : -1;
return (x === y) ? 0 : (x > y) ? 1 : -1;
}
}
/**
* Removes duplicate messages from the given messages array without destroying
* references to the array.
*
* @param {CSN.Message[]} messages
*/
function deduplicateMessages( messages ) {
const seen = new Set();
const uniqueMessages = messages.filter((msg) => {
if (!msg.location)
return true;
const hash = messageString(msg);
const keep = !seen.has(hash);
seen.add(hash);
return keep;
});
messages.length = 0;
for (const msg of uniqueMessages) {
messages.push(msg);
}
}
function artName( art, omit ) {

@@ -564,2 +638,32 @@ let name = art.name;

/**
* Get the explanation string for the given message-id.
*
* @param {string} messageId
* @returns {string}
* @throws May throw an ENOENT error if the file cannot be found.
* @see hasMessageExplanation()
*/
function explainMessage(messageId) {
const filename = path.join(__dirname, '..', '..', 'share', 'messages', `${messageId}.md`);
return fs.readFileSync(filename, 'utf8');
}
/**
* Returns true if the given message has an explanation file.
*
* @param {string} messageId
* @returns {boolean}
*/
function hasMessageExplanation(messageId) {
return !!availableMessageExplanations[messageId];
}
/**
* Returns an array of message IDs that have an explanation text.
*/
function messageIdsWithExplanation() {
return Object.keys(availableMessageExplanations);
}
module.exports = {

@@ -577,6 +681,11 @@ hasErrors,

sortMessages: (m => m.sort(compareMessage)),
deduplicateMessages,
CompileMessage,
DebugCompileMessage,
CompilationError,
translatePathLocations
translatePathLocations,
explainMessage,
hasMessageExplanation,
messageIdsWithExplanation,
}

@@ -14,8 +14,17 @@ //

// Test for early-adaptor feature, stored in option `beta`(new-style) / `betaMode`(old-style)
// With that, the value of `beta` is a Boolean or a dictionary of feature=>Boolean.
// Please do not move this function to the "option processor" code.
/**
* Test for early-adaptor feature, stored in option `beta`(new-style) / `betaMode`(old-style)
* With that, the value of `beta` is a dictionary of feature=>Boolean.
*
* A feature always needs to be provided - otherwise false will be returned.
*
* Please do not move this function to the "option processor" code.
*
* @param {object} options Options
* @param {string} feature Feature to check for
* @returns {boolean}
*/
function isBetaEnabled( options, feature ) {
const beta = options.beta || options.betaMode;
return beta && (typeof beta !== 'object' || beta[feature]);
return beta && typeof beta === 'object' && feature && beta[feature];
}

@@ -169,3 +178,3 @@

// (Note that 'node instanceof Object' would be false for dictionaries).
if (node == null || typeof node !== 'object') {
if (node === null || typeof node !== 'object') {
return node

@@ -181,10 +190,10 @@ }

// Things not having 'proto' are dictionaries
let proto = Object.getPrototypeOf(node);
const proto = Object.getPrototypeOf(node);
// Iterate own properties of 'node' and transform them into 'resultNode'
let resultNode = Object.create(proto);
const resultNode = Object.create(proto);
for (let key of Object.keys(node)) {
// Dictionary always use transformNode(), other objects their transformer according to key
let transformer = (proto == undefined) ? transformNode : transformers[key] || transformers[key.charAt(0)];
const transformer = !proto ? transformNode : transformers[key] || transformers[key.charAt(0)];
// Apply transformer, or use transformNode() if there is none
let resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
const resultValue = (transformer || transformNode)(node[key], node, resultNode, key);
if (resultValue !== undefined) {

@@ -191,0 +200,0 @@ resultNode[key] = resultValue;

@@ -375,3 +375,3 @@ 'use strict'

// Avoid 'toXyz: {}' for command without options
if (result.command && Object.keys(result.options[result.command]).length == 0) {
if (result.command && Object.keys(result.options[result.command]).length === 0) {
delete result.options[result.command];

@@ -469,4 +469,5 @@ }

if(options.betaMode && !options.testMode && !silent) {
result.push('Option --beta-mode was used. This option should not be used in productive scenarios!')
if((options.betaMode || options.beta) && !options.testMode && !silent) {
const mode = options.beta ? 'beta' : 'beta-mode';
result.push(`Option --${mode} was used. This option should not be used in productive scenarios!`)
}

@@ -473,0 +474,0 @@

'use strict';
const { isArtifact, isContainerArtifact } = require('../model/modelUtils');
const builtins = require('../compiler/builtins.js');
const alerts = require('../base/alerts');

@@ -114,74 +115,27 @@

// Handle each (primitive) expected element type separately
let typeName = getFinalTypeNameOf(elementDecl);
switch (typeName) {
// String-ish
case 'cds.String':
case 'cds.LargeString':
case 'cds.UUID':
case 'hana.ALPHANUM':
case 'hana.CHAR':
case 'hana.NCHAR':
case 'hana.VARCHAR':
case 'hana.CLOB':
if (value.literal !== 'string' && value.literal !== 'enum' && !elementDecl._finalType.enum) {
signal(warning`A string value is required for type "${typeName}"`, value.location || value.name.location);
}
break;
// Binary-ish
case 'cds.Binary':
case 'cds.LargeBinary':
case 'hana.BINARY':
if (value.literal !== 'string' && value.literal !== 'hex') {
signal(warning`A hexadecimal string value is required for type "${typeName}"`, value.location || value.name.location);
}
break;
// Number-ish
case 'cds.Decimal':
case 'cds.DecimalFloat':
case 'cds.Integer64':
case 'cds.Integer':
case 'cds.Double':
case 'cds.BinaryFloat':
case 'hana.SMALLINT':
case 'hana.TINYINT':
case 'hana.SMALLDECIMAL':
case 'hana.REAL':
if (value.literal !== 'number' && value.literal !== 'enum' && !elementDecl._finalType.enum) {
signal(warning`A numerical value is required for type "${typeName}"`, value.location || value.name.location);
}
break;
// Date/Time-ish
case 'cds.Date':
case 'cds.Time':
case 'cds.DateTime':
case 'cds.Timestamp':
case 'cds.LocalDate':
case 'cds.LocalTime':
case 'cds.UTCDateTime':
case 'cds.UTCTimestamp':
if (value.literal !== 'date' && value.literal !== 'time' && value.literal !== 'timestamp' && value.literal !== 'string') {
signal(warning`A date/time value or a string is required for type "${typeName}"`, value.location || value.name.location);
}
break;
// Bool
case 'cds.Boolean':
if (value.literal && value.literal !== 'boolean') {
signal(warning`A boolean value is required for type "${typeName}"`, value.location || value.name.location);
}
break;
// Not at all usable for annotations
case 'cds.Association':
case 'cds.Composition':
case 'hana.ST_POINT':
case 'hana.ST_GEOMETRY':
signal(warning`Type "${typeName}" cannot be assigned a value`, value.location || value.name.location);
break;
default:
throw new Error('Unknown primitive type name: ' + typeName);
let type = getFinalTypeNameOf(elementDecl);
if (builtins.isStringTypeName(type)) {
if (value.literal !== 'string' && value.literal !== 'enum' && !elementDecl._finalType.enum) {
signal(warning`A string value is required for type "${type}"`, value.location || value.name.location);
}
} else if (builtins.isBinaryTypeName(type)) {
if (value.literal !== 'string' && value.literal !== 'hex') {
signal(warning`A hexadecimal string value is required for type "${type}"`, value.location || value.name.location);
}
} else if (builtins.isNumericTypeName(type)) {
if (value.literal !== 'number' && value.literal !== 'enum' && !elementDecl._finalType.enum) {
signal(warning`A numerical value is required for type "${type}"`, value.location || value.name.location);
}
} else if (builtins.isDateOrTimeTypeName(type)) {
if (value.literal !== 'date' && value.literal !== 'time' && value.literal !== 'timestamp' && value.literal !== 'string') {
signal(warning`A date/time value or a string is required for type "${type}"`, value.location || value.name.location);
}
} else if (builtins.isBooleanTypeName(type)) {
if (value.literal && value.literal !== 'boolean') {
signal(warning`A boolean value is required for type "${type}"`, value.location || value.name.location);
}
} else if(builtins.isRelationTypeName(type) || builtins.isGeoTypeName(type)) {
signal(warning`Type "${type}" cannot be assigned a value`, value.location || value.name.location);
} else {
throw new Error('Unknown primitive type name: ' + type);
}

@@ -200,3 +154,3 @@

// Enum symbol provided but not expected
signal(warning`Cannot use enum symbol "#${value.symbol.id}" for non-enum type "${typeName}"`, value.location || value.name.location);
signal(warning`Cannot use enum symbol "#${value.symbol.id}" for non-enum type "${type}"`, value.location || value.name.location);
}

@@ -228,3 +182,3 @@ } else {

// Stop if found or failed
if (path.length == 0 || !from) {
if (path.length === 0 || !from) {
return result;

@@ -231,0 +185,0 @@ }

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

const { hasArtifactTypeInformation } = require('../model/modelUtils')
const builtins = require('../compiler/builtins');

@@ -13,11 +14,31 @@ // Semantic checks that are performed on artifacts

function checkNotEmptyOrOnlyVirtualElems(art, model) {
const { info, signal } = alerts(model);
if (!art.abstract && emptyOrOnlyVirtualElements(art)) {
const location = art.name && art.name.location || art.location;
if (art.kind === 'entity')
signal(info`Dubious entity without non-virtual elements`, location, undefined, 'empty-entity');
else if (art.kind === 'type')
signal(info`Dubious type without non-virtual elements`, location, undefined, 'empty-type');
if (!art.abstract) {
const isEmpty = isEmptyArtifact(art);
const emptyOrOnlyVirtual = isEmpty || emptyOrOnlyVirtualElements(art);
if (emptyOrOnlyVirtual) {
const location = art.name && art.name.location || art.location;
const message = getMessageFunction(model);
if (art.kind === 'entity') {
message('empty-entity', location, art, { '#': isEmpty ? 'empty' : 'virtual' }, 'Info', {
virtual: 'An entity without non-virtual elements cannot be deployed or used in a service',
empty: 'An entity without elements cannot be deployed or used in a service'
});
}
else if (art.kind === 'type') {
message('empty-type', location, art, { '#': isEmpty ? 'empty' : 'virtual' }, 'Info', {
virtual: 'Structured type without non-virtual elements',
empty: 'Structured type without elements'
});
}
}
}
// Returns true if the construct is structured and has 0 elements.
function isEmptyArtifact(construct) {
const elements = (construct._finalType || construct).elements;
return elements && Object.keys(elements).length === 0;
}
// Return true if artifact or element 'construct' is a virtual element or an empty structure

@@ -30,4 +51,5 @@ // or a structure that (recursively) is empty or only contains virtual elements

// No elements or all elements recursively empty, too ?
return construct.elements.length == 0
|| Object.keys(construct.elements).map(k => construct.elements[k]).every(elm => emptyOrOnlyVirtualElements(elm));
return Object.keys(construct.elements)
.map(k => construct.elements[k])
.every(elm => emptyOrOnlyVirtualElements(elm));
}

@@ -140,2 +162,105 @@ // Non-structured type - just check for virtual-ness or non-existing elements for entities or views

/**
* Run checks on the whole enum type, e.g. that elements have a value.
*
* @param {XSN.Artifact} enumNode
*/
function checkIntegerEnumHasValues(enumNode, model) {
const type = enumNode.type && enumNode.type._artifact && enumNode.type._artifact._finalType;
if (!enumNode.enum || !type || !type.builtin)
return;
// Currently only run check for integer enums.
if (!builtins.isIntegerTypeName(type.name.absolute))
return;
const failedAt = Object.keys(enumNode.enum).find(name => !enumNode.enum[name].value);
if (!failedAt)
return;
const message = getMessageFunction(model);
message('enum-missing-value', enumNode.enum[failedAt].location, enumNode.enum[failedAt],
{ name: failedAt }, 'Warning', 'Missing value for integer enum element $(NAME)');
}
/**
* Check the given enum's elements and their values. For example
* whether the value types are valid for the used enum type.
* `enumNode` can be also be `type.items` if the type is an arrayed enum.
*
* @param {XSN.Definition} enumNode
* @param {XSN.Model} model
*/
function checkEnumValueType(enumNode, model) {
const type = enumNode.type && enumNode.type._artifact && enumNode.type._artifact._finalType;
if (!enumNode.enum || !type || !type.builtin)
return;
const isInteger = builtins.isIntegerTypeName(type.name.absolute);
const isString = builtins.isStringTypeName(type.name.absolute);
// Only string and integer enums are allowed. Other checks handle other types.
if (!isString && !isInteger)
return;
const expectedType = isInteger ? 'number' : 'string';
// Do not check elements that don't have a value at all or are
// references to other enum elements. There are other checks for that.
const hasWrongType = element => element.value &&
(element.value.literal !== expectedType) &&
(element.value.literal !== 'enum');
const message = getMessageFunction(model);
for (const key of Object.keys(enumNode.enum)) {
const element = enumNode.enum[key];
if (!hasWrongType(element))
continue;
// Literal type is 'number' but we only support integer enums and not decimals.
let actualType = element.value.literal;
actualType = (actualType === 'number' ? 'integer' : actualType);
message('enum-value-type', element.value.location, element,
{ '#': expectedType, name: key, prop: actualType }, 'Warning', {
number: 'Expected integer value for enum element $(NAME) but was $(PROP)',
string: 'Expected string value for enum element $(NAME) but was $(PROP)'
});
}
}
function checkEnumType(enumNode, model) {
// Either the type is an enum or an arrayed enum. We are only interested in
// the enum and don't care whether the enum is arrayed.
enumNode = enumNode.enum ? enumNode : enumNode.items;
if (!enumNode || !enumNode.enum)
return;
const finalType = enumNode.type && enumNode.type._artifact && enumNode.type._artifact._finalType;
if (!finalType)
return;
// _finalType may point to a type one level above a builtin.
// This `.type` may be another enum so we need to check for that.
const type = finalType.type && !finalType.enum ? finalType.type._artifact : finalType;
const name = type.name.absolute;
const isInteger = builtins.isIntegerTypeName(name);
const isString = builtins.isStringTypeName(name);
if (!isInteger && !isString) {
const message = getMessageFunction(model);
message('enum-invalid-type', enumNode.type.location, enumNode,
{}, 'Warning',
'Only string- or integer-like types are allowed for enums');
return;
}
checkEnumValueType(enumNode, model);
if (isInteger) {
checkIntegerEnumHasValues(enumNode, model);
}
}
module.exports = {

@@ -147,2 +272,4 @@ checkNotEmptyOrOnlyVirtualElems,

checkArtifactHasProperType,
checkEnumType,
checkIntegerEnumHasValues,
};

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

const { isComposition } = require('../model/modelUtils.js')
const { getMessageFunction } = require('../base/messages');

@@ -13,3 +14,3 @@ /**

function checkPrimaryKey(elem, model) {
const { error, warning, signal } = alerts(model);
const { error, signal } = alerts(model);
let type = '';

@@ -26,4 +27,4 @@ // apparently this is the resolved type (over an derived type chain)

function checkForUnmanagedAssociations(element){
if(element.on){
signal(warning`Unmanaged associations cannot be used as primary key`, elem.key.location);
if(element.onCond) {
signal(error`Unmanaged associations cannot be used as primary key`, elem.key.location, ['Error'], 'unmanaged-as-key');
}

@@ -86,13 +87,13 @@

const { error, warning, signal } = alerts(model);
let target = elem.target;
const target = elem.target;
// Not a managed assoc at all, inferred elem or redirected => nothing to check
if (!target || elem.on || elem.onCond || elem.$inferred || !elem.type || elem.type.$inferred || !['entity', 'view'].includes(target._artifact.kind) || target._artifact.abstract && target._artifact.abstract.val) {
if (!target || !target._artifact || elem.on || elem.onCond || elem.$inferred || !elem.type || elem.type.$inferred || !['entity', 'view'].includes(target._artifact.kind) || target._artifact.abstract && target._artifact.abstract.val) {
return;
}
// No foreign keys at all => error
let foreignKeys = elem.foreignKeys;
const foreignKeys = elem.foreignKeys;
if (!foreignKeys) {
signal(error`The target "${target._artifact.name.absolute}" of the managed association "${elem.name.id}" does not have keys.`, elem.location);
signal(error`The target "${target._artifact.name.absolute}" of the managed association "${elem.name.id}" does not have keys`, elem.location);
}
let targetMax = (elem.cardinality && elem.cardinality.targetMax && elem.cardinality.targetMax.val);
const targetMax = (elem.cardinality && elem.cardinality.targetMax && elem.cardinality.targetMax.val);
if (targetMax === '*' || Number(targetMax) > 1) {

@@ -159,9 +160,9 @@ if(doForeignKeysCoverTargetKeys(foreignKeys,target._artifact.elements)) {

if (elem.key && elem.key.val) {
signal(error`Element cannot be virtual and key.`, elem.location);
signal(error`Element cannot be virtual and key`, elem.location);
}
if (isStruct(elem)) {
signal(error`Element cannot be virtual and structured.`, elem.location);
signal(error`Element cannot be virtual and structured`, elem.location);
}
if (isAssoc(elem)) {
signal(error`Element cannot be virtual and an association.`, elem.location);
signal(error`Element cannot be virtual and an association`, elem.location);
}

@@ -178,6 +179,6 @@ }

if (elem._finalType && elem._finalType.elements)
signal(error`Cannot cast to structured element.`, elem.location);
signal(error`Cannot cast to structured element`, elem.location);
else if (elem.value && elem.value._artifact && elem.value._artifact._finalType && elem.value._artifact._finalType.elements)
signal(error`Structured element cannot be casted to a different type.`, elem.location);
signal(error`Structured element cannot be casted to a different type`, elem.location);
}

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

if (key && key._artifact && key._artifact.virtual && key._artifact.virtual.val === true) {
signal(error`Virtual elements cannot be used as a foreign key for a managed association.`, key.location);
signal(error`Virtual elements cannot be used as a foreign key for a managed association`, key.location);
}

@@ -263,3 +264,3 @@ }

const { error, signal } = alerts(model);
signal(error`Backlink association condition requires an operator equation`, op.location);
signal(error`$self comparison is only allowed with '='`, op.location);
}

@@ -387,3 +388,3 @@ }

|| elem.cardinality[prop].literal === 'string' && elem.cardinality[prop].val === '*')) {
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality[prop].val}" for max cardinality (must a positive number or "*")`, elem.cardinality[prop].location);
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality[prop].val}" for max cardinality (must be a positive number or "*")`, elem.cardinality[prop].location);
}

@@ -396,3 +397,3 @@ }

if (!(elem.cardinality.targetMin.literal === 'number' && elem.cardinality.targetMin.val >= 0)) {
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality.targetMin.val}" for min cardinality (must a non-negative number)`, elem.cardinality.targetMin.location);
signal(error`Element "${elem.name.absolute}.${elem.name.id}": Illegal value "${elem.cardinality.targetMin.val}" for min cardinality (must be a non-negative number)`, elem.cardinality.targetMin.location);
}

@@ -449,8 +450,9 @@ }

function checkForItemsChain(obj, model){
if(obj.items) {
if (obj.items) {
const itemsType = obj.items.type ? obj.items.type._artifact : obj.items;
if(itemsType.items){
const { signal, warning } = alerts(model);
signal(warning`"Array of"/"many" must not be chained - ${obj.name.id}, ${itemsType.name.id}.`, obj.location, 'Warning', 'chained-array-of');
if (itemsType.items){
const message = getMessageFunction( model );
message('chained-array-of', obj.location, obj, { art: itemsType }, 'Warning',
'"Array of"/"many" must not be chained with $(ART)');
}

@@ -460,2 +462,39 @@ }

/**
* Check that the given type has no conflicts between its `type` property
* and its `elements` or `items` property. For example if `type` is not
* structured but the artifact has an `elements` property then the user
* made a mistake. This scenario can only happen through CSN and not CDL.
*
* @param {XSN.Artifact} artifact
* @param {XSN.Model} model
*/
function checkTypeStructure(artifact, model) {
if (!artifact.items && !artifact.elements)
return;
const finalType = artifact._finalType;
// Just a basic check. We do not check that the inner structure of `items`
// is the same as the type but only that all are arrayed or structured.
if (artifact.type && artifact.type._artifact) {
const message = getMessageFunction(model);
if (artifact.items && !finalType.items) {
message('type-items-mismatch', artifact.type.location, artifact,
{ type: artifact.type, prop: 'items' }, 'Warning',
'Used type $(TYPE) is not arrayed and conflicts with $(PROP) property'
);
} else if (artifact.elements && !finalType.elements) {
message('type-elements-mismatch', artifact.type.location, artifact,
{ type: artifact.type, prop: 'elements' }, 'Warning',
'Used type $(TYPE) is not structured and conflicts with $(PROP) property'
);
}
} else if (artifact.items) {
checkTypeStructure(artifact.items, model);
}
}
module.exports = {

@@ -470,3 +509,4 @@ checkPrimaryKey,

checkElementHasValidTypeOf,
checkForItemsChain
checkForItemsChain,
checkTypeStructure
};

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

*/
function checkTokenStreamExpression(xpr, model){
function checkTokenStreamExpression(xpr, model, inOnCond){
const { error, signal } = alerts(model);

@@ -33,7 +33,7 @@

if(isVirtualElement(arg)){
signal(error`Virtual elements cannot be used in an expression.`, arg.location);
signal(error`Virtual elements cannot be used in an expression`, arg.location);
}
// Recursively traverse the argument expression
checkTokenStreamExpression(arg, model);
checkTokenStreamExpression(arg, model, inOnCond);
}

@@ -49,3 +49,3 @@ }

*/
function checkTreeLikeExpression(xpr, model){
function checkTreeLikeExpression(xpr, model, inOnCond){
const { error, signal } = alerts(model);

@@ -62,6 +62,6 @@ const { isAssociationOperand, isDollarSelfOperand } = transformUtils.getTransformers(model);

if(isVirtualElement(arg)){
signal(error`Virtual elements cannot be used in an expression.`, arg.location);
signal(error`Virtual elements cannot be used in an expression`, arg.location);
}
// Arg must not be an association and not $self
if (isAssociationOperand(arg)) {
if (!inOnCond && isAssociationOperand(arg)) {
signal(error`An association cannot be used as a value in an expression`, arg.location);

@@ -74,3 +74,3 @@ }

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

@@ -109,8 +109,8 @@

*/
function checkExpression(xpr, model) {
function checkExpression(xpr, model, inOnCond=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, model);
return checkTokenStreamExpression(xpr, model, inOnCond);
} else {
return checkTreeLikeExpression(xpr, model);
return checkTreeLikeExpression(xpr, model, inOnCond);
}

@@ -117,0 +117,0 @@ }

'use strict';
const alerts = require('../base/alerts');
const { getMessageFunction } = require('../base/messages');

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

const { warning, signal } = alerts(model);
const message = getMessageFunction(model);
let options = model.options;

@@ -113,2 +115,7 @@

if (paramTypeArtifact.default) {
message('param-default', param.name.location, param, {}, ['Error'],
'Action and function parameters cannot have a default value')
}
function checkEntityParam(paramTypeArtifact) {

@@ -115,0 +122,0 @@ if (serviceName && getAbsNameWithoutId(paramTypeArtifact.name.absolute) !== serviceName)

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

* The following properties are attached as non-enumerable where appropriate:
*
*
*- `_type`, `_includes` and `_targets` have as values the

@@ -19,3 +19,3 @@ * referred artifacts which are returned by function `artifactRef`.

*
* @param {object} csn CSN to enrich in-place
* @param {object} csn CSN to enrich in-place
* @returns {object} CSN with all ref's pre-resolved

@@ -70,3 +70,3 @@ */

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

@@ -110,2 +110,2 @@ }

module.exports = enrichCsn;
module.exports = enrichCsn;

@@ -7,9 +7,19 @@ 'use strict';

* Validate the foreign keys of a managed association
*
*
* - no usage of array-like fields as foreign keys (also not transitively)
*
* - no usage of unmanaged association as foreign keys (also not transitively)
*
* @param {object} member Member
*/
function validateForeignKeys(member){
// Declared as arrow-function to keep scope the same (this value)
const isManagedAssoc = (mem) => {
// We have a managed association
return mem && mem.target && !mem.on;
};
const isUnmanagedAssoc = (mem) => {
// We have an unmanaged association
return mem && mem.target && mem.on && !mem.keys;
};
// Declared as arrow-function to keep scope the same (this value)
const handleAssociation = (mem) => {

@@ -23,14 +33,22 @@ for(let i = 0; i < mem.keys.length; i++){

}
// Declared as arrow-function to keep scope the same (this value)
// Declared as arrow-function to keep scope the same (this value)
const handleStructured = (mem) => {
let elementCount = 0;
for(let elementName of Object.keys(mem.elements)){
const element = mem.elements[elementName];
checkForItems(element);
elementCount++;
}
if(elementCount === 0){
this.signal(this.error`Empty structured types/elements must not be used as foreign keys.`, member.$path);
}
}
// Recursively perform the checks on an element
// Declared as arrow-function to keep scope the same (this value)
// Declared as arrow-function to keep scope the same (this value)
const checkForItems = (mem) => {
if(mem.items){
this.signal(this.error`Array-like properties must not be foreign keys`, member.$path);
} else if(isUnmanagedAssoc(mem)){
this.signal(this.error`Unmanaged association must not be a foreign key`, member.$path);
} else if(mem.keys){

@@ -56,4 +74,3 @@ handleAssociation(mem);

// We have a managed association
if(member && member.target && !member.on){
if(isManagedAssoc(member)){
checkForItems(member);

@@ -63,2 +80,2 @@ }

module.exports = validateForeignKeys;
module.exports = validateForeignKeys;
'use strict';
const { forEachGeneric, isBuiltinType } = require('../../model/csnUtils');

@@ -45,2 +46,46 @@ // Only to be used with validator.js - a correct this value needs to be provided!

/*
* Check that the opposite operand to a relational term is something
* structured that can be used for tuple expansion. This can either be a
* real 'elements' thing or a managed association/composition with foreign keys.
*
* @param {Object[]} on On-Condition
* @param {Number} startIndex Index of the current expression to "look around"
* @param {Object} this to obtain bound functions and model
* @returns {boolean}
*/
function otherSideIsExpandableStructure(on, startIndex, me) {
if(on[startIndex-1] && ['=', '<', '>', '>=', '<=', '!=', '<>'].includes(on[startIndex-1])) {
return isOk(resolveArtifactType(on[startIndex-2]._art, me));
}
else if(on[startIndex+1] && ['=', '<', '>', '>=', '<=', '!=', '<>'].includes(on[startIndex+1])) {
return isOk(resolveArtifactType(on[startIndex+2]._art, me));
}
return false;
function isOk(art) {
return !!(art && (art.elements || (art.target && art.keys)));
}
}
/*
* @param {Object} artifact
* @param {Object} this to obtain bound functions and model
* @returns {Object} final artifact type
*/
function resolveArtifactType(art, me) {
if(art && art.type) {
// 1) Dereference 'type of'
if(art.type.ref) { // type of
art = me.artifactRef(art.type);
}
// 2) Lookup named 'type T', if not builtin
if(art && art.type && !isBuiltinType(art.type)) {
art = me.csn.definitions[me.effectiveType(art.type)];
}
}
return art;
}
/**

@@ -60,3 +105,3 @@ * Validate an on-condition

*/
function validateOnConditions(member, memberName, property, path){
function validateOnCondition(member, memberName, property, path){
if(member && member.on){

@@ -66,7 +111,8 @@ for(let i = 0; i < member.on.length; i++){

const ref = member.on[i].ref;
const { _links, _art, $scope } = member.on[i];
let { _links, _art, $scope } = member.on[i];
if(!_links) continue;
const validDollarSelf = otherSideIsValidDollarSelf(member.on, i);
const validStructuredElement = otherSideIsExpandableStructure(member.on, i, this);
for(let j = 0; j < _links.length-1; j++){
if(_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || validDollarSelf)){
if(_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))){
if(_links[j].art.on){

@@ -79,3 +125,3 @@ // It's an unmanaged association - traversal is always forbidden

if(!_links[j].art.keys.some(ref => ref.ref[0] === nextRef)){
this.signal(this.error`ON-Conditions can only follow managed associations to the foreign keys of the managed association, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`, path.concat(['on', i, 'ref', j]));
this.signal(this.error`ON-Conditions can only follow managed associations to the foreign keys of the managed association, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i, 'ref', j]));
}

@@ -93,19 +139,18 @@ }

}
if(_art && $scope !== '$self'){
if(_art.elements || (_art.target && !validDollarSelf)){
this.signal(this.error`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')} .`, path.concat(['on', i,'ref',ref.length-1]))
} else if(_art.type && _art.type.ref){ // type of
const typeArt = this.artifactRef(_art.type);
const type = this.csn.definitions[this.effectiveType(typeArt.type)];
if(type && (type.elements || (type.target && !validDollarSelf))){
this.signal(this.error`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')} .`, path.concat(['on', i,'ref',ref.length-1]))
}
} else if(_art.type) { // type T
const type = this.csn.definitions[this.effectiveType(_art.type)];
if(type && (type.elements || (type.target && !validDollarSelf))){
this.signal(this.error`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')} .`, path.concat(['on', i,'ref',ref.length-1]))
}
} else if(_art.items){
this.signal(this.error`ON-Conditions can not use array-like elements, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`, path.concat(['on', i, 'ref', ref.length-1]));
if(_art && $scope !== '$self') {
_art = resolveArtifactType(_art, this);
// Paths of an ON condition may end on a structured element or an association only if:
// 1) Both operands in the expression end on a structured element or on
// a managed association (that are both expandable)
// 2) Path ends on an association (managed or unmanaged) and the other operand is a '$self'
// If this path ends structured or on an association, perform the check:
if((_art.elements || _art.target) &&
!( /* 1) */ (_art.elements || _art.target && _art.keys) && validStructuredElement ||
/* 2) */ (_art.target && validDollarSelf))) {
this.signal(this.error`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i,'ref',ref.length-1]))
}
else if(_art.items){
this.signal(this.error`ON-Conditions can not use array-like elements, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i, 'ref', ref.length-1]));
}
}

@@ -115,4 +160,11 @@ }

}
}
module.exports = validateOnConditions;
function validateMixinOnCondition(query, path) {
if(query.SELECT && query.SELECT.mixin) {
forEachGeneric( query.SELECT, 'mixin', validateOnCondition.bind(this), path );
}
}
module.exports = { validateOnCondition, validateMixinOnCondition };
'use strict';
const { forEachDefinition, forEachMemberRecursively } = require('../../model/csnUtils');
const { forEachDefinition, forEachMemberRecursively, forAllQueries } = require('../../model/csnUtils');
const enrich = require('./enricher');

@@ -12,4 +12,5 @@

* @param {Function[]} [artifactValidators=[]] Validations on artifact-level
* @param {Function[]} [queryValidators=[]] Validations on query-level
*/
function validate(csn, that, memberValidators=[], artifactValidators=[]){
function validate(csn, that, memberValidators=[], artifactValidators=[], queryValidators=[]){
enrich(csn);

@@ -19,5 +20,8 @@

artifactValidators.forEach(artifactValidator => artifactValidator.bind(that)(artifact, artifactName, prop, path));
forEachMemberRecursively(artifact,(member, memberName, prop, path) => {
memberValidators.forEach(memberValidator => memberValidator.bind(that)(member, memberName, prop, path));
} , ['definitions', artifactName])
if(memberValidators.length)
forEachMemberRecursively( artifact, memberValidators.map(v=>v.bind(that)), path );
if(queryValidators.length && artifact.query) {
forAllQueries(artifact.query, queryValidators.map(v=>v.bind(that)), path.concat(['query']));
}
});

@@ -24,0 +28,0 @@ }

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

const { checkNotEmptyOrOnlyVirtualElems, checkNoUnmanagedAssocsInGroupByOrderBy,
checkTypeDefinitionHasType } = require('./checkArtifacts');
checkTypeDefinitionHasType, checkEnumType } = require('./checkArtifacts');
const { checkPrimaryKey, checkVirtualElement, checkManagedAssoc, checkCardinality,
checkLocalizedElement, checkStructureCasting, checkForItemsChain, checkElementHasValidTypeOf } = require('./checkElements');
checkLocalizedElement, checkStructureCasting, checkForItemsChain, checkTypeStructure,
checkElementHasValidTypeOf } = require('./checkElements');
const { checkExpression } = require('./checkExpressions');

@@ -86,3 +87,3 @@ const { foreachPath } = require('../model/modelUtils');

package: nothingToCheckYet,
param: checkParam,
param: checkParameter,
query: nothingToCheckYet,

@@ -198,3 +199,3 @@ role: nothingToCheckYet,

if(query.mixin[mixinName].onCond){
checkExpression(query.mixin[mixinName].onCond, model)
checkExpression(query.mixin[mixinName].onCond, model, true)
}

@@ -215,2 +216,3 @@ }

* of enum values are allowed.
* The enumNode is a single enum element and not the whole type.
*

@@ -226,3 +228,4 @@ * @param {XSN.Artifact} enumNode

// Special handling to print a more detailed error message
// Special handling to print a more detailed error message.
// Other cases like `null` as enum value are handled in `checkEnumValueType()`
if (type === 'enum') {

@@ -233,8 +236,2 @@ message('enum-value-ref', loc, enumNode, { }, 'Warning',

}
const allowedValueTypes = ['number', 'string'];
if (!allowedValueTypes.includes(type)) {
message('enum-value-type', loc, enumNode, { }, 'Warning',
'Only strings or numbers are allowed as enum values');
}
}

@@ -324,2 +321,4 @@

checkForItemsChain(type, model);
checkTypeStructure(type, model);
checkEnumType(type, model);
}

@@ -334,4 +333,5 @@

checkForItemsChain(elem, model);
checkTypeStructure(elem, model);
if (elem.onCond && !elem.onCond.$inferred) {
checkExpression(elem.onCond, model);
checkExpression(elem.onCond, model, true);
}

@@ -342,5 +342,6 @@ if (elem.value) {

checkLocalizedElement(elem, model);
checkEnumType(elem, model);
}
function checkParam(param) {
function checkParameter(param) {
if (param._parent && (param._parent.kind === 'action' || param._parent.kind === 'function')) {

@@ -347,0 +348,0 @@ checkActionOrFunctionParameter(param, serviceNameFor(param._parent));

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

//
// The consisteny check is NOT A SYNTAX CHECK: it accepts invalid CDS models,
// The consistency check is NOT A SYNTAX CHECK: it accepts invalid CDS models,
// it is usually not run in productive use, and its error message contains

@@ -82,3 +82,15 @@ // property names the user is not aware of. It is considered an _internal

requires: [ 'options', 'definitions', 'sources' ],
optional: [ 'messages', 'extensions', 'version', '$version', 'meta', '$magicVariables', '$builtins', '$internal', '$compositionTargets', '$lateExtensions', '_entities', '$entity' ], // version without --test-mode
optional: [
'messages',
'extensions',
'version', '$version', // version without --test-mode
'meta',
'$magicVariables',
'$builtins',
'$internal',
'$compositionTargets',
'$lateExtensions',
'_entities', '$entity',
'$newfeatures',
],
},

@@ -89,3 +101,3 @@ ':parser': { // top-level from parser

'messages', 'options', 'definitions', 'extensions',
'artifacts', 'namespace', 'usings', // CDL parser
'artifacts', 'artifacts_', 'namespace', 'usings', // CDL parser
'filename', 'dirname', // TODO: move filename into a normal location? Only in model.sources

@@ -136,2 +148,3 @@ 'dependencies', // for USING..FROM

$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },
$newfeatures: { test: TODO }, // if new features have been used which break the old backends
messages: {

@@ -151,3 +164,4 @@ enumerable: () => true, // does not matter (non-enum std), enum in CSN/XML parser

test: isArray(),
schema: { name: { inherits: 'name', isRequired: noSyntaxErrors } }, // name is required in parser, too
schema: { name: { inherits: 'name', isRequired: noSyntaxErrors } },
// name is required in parser, too
},

@@ -199,3 +213,4 @@ _localized: { kind: true, test: TODO }, // true or artifact

requires: [ 'kind', 'location' ],
optional: [ 'name', 'extern', 'usings', 'annotationAssignments', 'fileDep' ], // TODO: get rid of annos: []
optional: [ 'name', 'extern', 'usings', 'annotationAssignments', 'fileDep' ],
// TODO: get rid of annotationAssignments: []
},

@@ -282,3 +297,7 @@ extern: {

},
orderBy: { test: isArray(), requires: [ 'value' ], optional: [ 'location', 'sort', 'nulls', '_$queryNode' ] },
orderBy: {
test: isArray(),
requires: [ 'value' ],
optional: [ 'location', 'sort', 'nulls', '_$queryNode' ],
},
sort: { test: locationVal( isString ), enum: [ 'asc', 'desc' ] },

@@ -307,2 +326,11 @@ nulls: { test: locationVal( isString ), enum: [ 'first', 'last' ] },

},
targetAspect: {
kind: true,
requires: [ 'location' ],
optional: [
'path', 'elements', '_elementsIndexNo', '_outer',
'scope', '_artifact', '$inferred', // TODO: remove the rest
'calculated', // TODO: remove calculated
],
},
target: {

@@ -321,3 +349,9 @@ kind: true,

// TODO: rename namedArgs to args
optional: [ 'quoted', 'args', 'namedArgs', 'where', 'cardinality', '_artifact', '_navigation', '$inferred' ],
optional: [
'quoted', // TODO remove?
'args', 'namedArgs',
'where', 'cardinality',
'_artifact', '_navigation',
'$inferred',
],
},

@@ -350,3 +384,5 @@ id: { test: isString },

ref: { inherits: 'type' },
none: { optional: [ 'location' ] }, // TODO: why optional / enough in name? - TODO: "yes" instread "none": val: true, optional literal/location
none: { optional: [ 'location' ] },
// TODO: why optional / enough in name?
// TODO: "yes" instead "none": val: true, optional literal/location
val: {

@@ -361,4 +397,9 @@ requires: [ 'literal', 'location' ],

requires: [ 'op', 'location' ],
optional: [ 'args', 'namedArgs', 'func', 'quantifier', '$inferred', 'augmented', '_artifact' ],
// _artifact with "localized data"s 'coalesce'
optional: [
'args', 'namedArgs',
'func',
'quantifier',
'$inferred', 'augmented', // TODO: remove augmented
'_artifact', // _artifact with "localized data"s 'coalesce'
],
},

@@ -369,3 +410,7 @@ query: { inherits: 'query' },

test: isString,
enum: [ 'string', 'number', 'boolean', 'hex', 'time', 'date', 'timestamp', 'struct', 'array', 'enum', 'null', 'token' ],
enum: [
'string', 'number', 'boolean', 'hex',
'time', 'date', 'timestamp',
'struct', 'array', 'enum', 'null', 'token',
],
},

@@ -383,3 +428,4 @@ symbol: { requires: [ 'location', 'id' ], optional: [ 'quoted', 'augmented' ] },

onCond: { kind: true, inherits: 'value' },
on: { kind: true, inherits: 'value', test: expressionOrString }, // TODO: rename 'onCond' to 'on', remove 'on'
on: { kind: true, inherits: 'value', test: expressionOrString },
// TODO: rename 'onCond' to 'on', remove 'on'
where: { inherits: 'value' },

@@ -396,3 +442,4 @@ having: { inherits: 'value' },

inherits: 'value',
optional: [ 'name', '_block', 'priority', '$duplicate', '$inferred' ], // TODO: name requires if not in parser?
optional: [ 'name', '_block', 'priority', '$duplicate', '$inferred' ],
// TODO: name requires if not in parser?
},

@@ -436,3 +483,3 @@ priority: { test: TODO }, // TODO: rename to $priority

'elements', 'cardinality', 'target', 'on', 'onCond', 'foreignKeys', 'items',
'_outer', '_finalType',
'_outer', '_finalType', 'notNull',
'elements_', '_elementsIndexNo', '$syntax', // TODO: remove

@@ -473,2 +520,3 @@ ],

_artifact: { test: TODO },
_base: { test: TODO, kind: true },
_navigation: { test: TODO },

@@ -505,2 +553,3 @@ _finalType: { kind: true, test: TODO },

origin: { kind: true, test: TODO }, // TODO: define some _origin
_origin: { kind: [ 'entity' ], test: TODO }, // origin composition aspect for entity
$from: { kind: true, test: TODO }, // all table refs necesary to compute elements

@@ -523,2 +572,3 @@ _redirected: { kind: true, test: TODO }, // for REDIRECTED TO:

$lateExtensions: { test: TODO },
_upperAspects: { kind: [ 'type', 'entity', 'view' ], test: isArray( TODO ) },
_ancestors: { kind: [ 'type', 'entity', 'view' ], test: isArray( TODO ) },

@@ -677,3 +727,4 @@ _descendants: { kind: [ 'entity', 'view' ], test: isDictionary( isArray( TODO ) ) },

function expression( node, parent, prop, spec, idx ) {
if (typeof node === 'string') // TODO CSN parser: { val: <token>, literal: 'token' } for keywords
// TODO CSN parser?: { val: <token>, literal: 'token' } for keywords
if (typeof node === 'string')
return;

@@ -817,3 +868,4 @@ while (node instanceof Array) {

// TODO: sign ignored artifacts with $inferred = 'IGNORED'
if (parent.kind === 'source' || art.name.absolute && art.name.absolute.startsWith('localized.'))
if (parent.kind === 'source' ||
art.name.absolute && art.name.absolute.startsWith('localized.'))
standard( art, parent, prop, spec, name );

@@ -820,0 +872,0 @@ else

@@ -9,19 +9,19 @@ // The builtin artifacts of CDS

const core = {
String: { parameters: [ 'length' ] },
LargeString: {},
Binary: { parameters: [ 'length' ] },
LargeBinary: {},
Decimal: { parameters: [ 'precision', 'scale' ] },
DecimalFloat: {},
Integer64: {},
Integer: {},
Double: {},
Date: {},
Time: {},
DateTime: {},
Timestamp: {},
Boolean: {},
UUID: {},
Association: { internal: true },
Composition: { internal: true },
String: { parameters: [ 'length' ], category: 'string' },
LargeString: { category: 'string' },
Binary: { parameters: [ 'length' ], category: 'binary' },
LargeBinary: { category: 'binary' },
Decimal: { parameters: [ 'precision', 'scale' ], category: 'decimal' },
DecimalFloat: { category: 'decimal' },
Integer64: { category: 'integer' },
Integer: { category: 'integer' },
Double: { category: 'decimal' },
Date: { category: 'dateTime' },
Time: { category: 'dateTime' },
DateTime: { category: 'dateTime' },
Timestamp: { category: 'dateTime' },
Boolean: { category: 'boolean' },
UUID: { category: 'string' },
Association: { internal: true, category: 'relation' },
Composition: { internal: true, category: 'relation' },
};

@@ -31,14 +31,14 @@

// ALPHANUM: { parameters: [ 'length' ] },
SMALLINT: {},
TINYINT: {},
SMALLDECIMAL: {},
REAL: {},
CHAR: { parameters: [ 'length' ] },
NCHAR: { parameters: [ 'length' ] },
VARCHAR: { parameters: [ 'length' ] },
CLOB: {},
BINARY: { parameters: [ 'length' ] },
SMALLINT: { category: 'integer' },
TINYINT: { category: 'integer' },
SMALLDECIMAL: { category: 'decimal' },
REAL: { category: 'decimal' },
CHAR: { parameters: [ 'length' ], category: 'string' },
NCHAR: { parameters: [ 'length' ], category: 'string' },
VARCHAR: { parameters: [ 'length' ], category: 'string' },
CLOB: { category: 'string' },
BINARY: { parameters: [ 'length' ], category: 'binary' },
// TODO: probably remove default for ST_POINT, ST_GEOMETRY (to be done in backend);
ST_POINT: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ] },
ST_GEOMETRY: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ] },
ST_POINT: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ], category: 'geo' },
ST_GEOMETRY: { parameters: [ { name: 'srid', literal: 'number', val: 0 } ], category: 'geo' },
};

@@ -77,3 +77,67 @@

/** All types belong to one category. */
const typeCategories = {
string: [],
integer: [],
dateTime: [],
time: [],
decimal: [],
binary: [],
boolean: [],
relation: [],
geo: [],
};
// Fill type categories with `cds.*` types
Object.keys(core).forEach((type) => {
if (core[type].category)
typeCategories[core[type].category].push(`cds.${ type }`);
});
// Fill type categories with `cds.hana.*` types
Object.keys(coreHana).forEach((type) => {
if (coreHana[type].category)
typeCategories[coreHana[type].category].push(`cds.hana.${ type }`);
});
/** @param {string} typeName */
function isIntegerTypeName(typeName) {
return typeCategories.integer.includes(typeName);
}
/** @param {string} typeName */
function isDecimalTypeName(typeName) {
return typeCategories.decimal.includes(typeName);
}
/** @param {string} typeName */
function isNumericTypeName(typeName) {
return isIntegerTypeName(typeName) || isDecimalTypeName(typeName);
}
/** @param {string} typeName */
function isStringTypeName(typeName) {
return typeCategories.string.includes(typeName);
}
/** @param {string} typeName */
function isDateOrTimeTypeName(typeName) {
return typeCategories.dateTime.includes(typeName);
}
/** @param {string} typeName */
function isBooleanTypeName(typeName) {
return typeCategories.boolean.includes(typeName);
}
/** @param {string} typeName */
function isBinaryTypeName(typeName) {
return typeCategories.binary.includes(typeName);
}
/** @param {string} typeName */
function isGeoTypeName(typeName) {
return typeCategories.geo.includes(typeName);
}
/**
* Whether the given type name is a relation, i.e. an association or composition.
*
* @param {string} typeName
*/
function isRelationTypeName(typeName) {
return typeCategories.relation.includes(typeName);
}
/**
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to

@@ -108,3 +172,3 @@ * `definitions` of the XSN model as well as to `$builtins`.

kind: 'namespace',
// buitlin namespaces don't have a cds file, so no location available
// builtin namespaces don't have a cds file, so no location available
name: { absolute: name, location: builtinLocation() },

@@ -180,2 +244,13 @@ blocks: [],

module.exports = initBuiltins;
module.exports = {
initBuiltins,
isIntegerTypeName,
isDecimalTypeName,
isNumericTypeName,
isStringTypeName,
isDateOrTimeTypeName,
isBooleanTypeName,
isBinaryTypeName,
isGeoTypeName,
isRelationTypeName,
};

@@ -16,5 +16,9 @@ //

'@cds.persistence.table': never,
'@cds.persistence.calcview': never,
'@cds.persistence.udf': never,
'@Analytics.hidden': never,
'@Analytics.visible': never,
'@cds.autoexpose': onlyViaArtifact,
'@cds.autoexposed': never, // in case people set it themselves
'@fiori.draft.enabled': onlyViaArtifact,
'@': withKind, // always except in 'returns' and 'items'

@@ -37,2 +41,3 @@ doc: withKind, // always except in 'returns' and 'items'

target: always,
targetAspect: always,
cardinality: always,

@@ -152,3 +157,6 @@ onCond: always, // is expensive, but often rewritten - TODO: on

const type = ref && ref._artifact;
return type && !type._main && type[prop];
if (!type || type._main)
return false;
run( type );
return type[prop];
}

@@ -185,2 +193,10 @@

function onlyViaArtifact( prop, target, source ) {
if (target.kind) { // not in 'returns' and 'items'
const from = target.$from && target.$from[0].path;
if (!(from ? from[from.length - 1]._artifact : source)._main)
always( prop, target, source );
}
}
function onlyViaParent( prop, target, source ) {

@@ -187,0 +203,0 @@ if (target.$inferred === 'proxy') // assocs and enums do not have 'include'

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

const { isBetaEnabled } = require('../base/model');
const { searchName, getMessageFunction } = require('../base/messages');

@@ -24,3 +23,3 @@ const { addToDict, addToDictWithIndexNo, pushToDict } = require('../base/dictionaries');

context: { artifacts: true, normalized: 'namespace' },
service: { artifacts: true, normalized: 'namespace' }, // actions: true with "service-bound" actions
service: { artifacts: true, normalized: 'namespace' },
entity: { elements: true, actions: true, params: () => false },

@@ -89,3 +88,6 @@ view: { elements: true, actions: true, params: () => false },

annotate: {
useDefinitions: true, envFn: artifactsEnv, undefinedDef: 'anno-undefined-def', undefinedArt: 'anno-undefined-art',
useDefinitions: true,
envFn: artifactsEnv,
undefinedDef: 'anno-undefined-def',
undefinedArt: 'anno-undefined-art',
},

@@ -98,3 +100,7 @@ type: { reject: rejectNonType }, // TODO: more detailed later (e.g. for enum base type?)

target: { reject: rejectNonEntity, noDep: true, envFn: artifactsEnv },
compositionTarget: { reject: rejectNonTarget, noDep: true, envFn: artifactsEnv },
compositionTarget: {
reject: rejectNonTarget,
noDep: 'only-entity',
envFn: artifactsEnv,
},
// TODO: dep for (explicit+implicit!) foreign keys

@@ -106,6 +112,7 @@ element: { next: '__none_' }, // TODO: something for technical config - re-think

const: { next: '_$next', reject: rejectNonConst },
expr: {
expr: { // in: from-on,
next: '_$next', escape: 'param', assoc: 'nav',
},
on: { // TODO: there will also be a 'from-on'
on: { // TODO: there will also be a 'from-on' (see 'expr')
noAliasOrMixin: true, // TODO: some headReject or similar
escape: 'param', // meaning of ':' in front of path? search in 'params'

@@ -175,5 +182,3 @@ next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment

function rejectNonTarget( art ) {
return (isBetaEnabled( options, 'aspectCompositions' ) &&
// TODO: delete 'abstract', proper kind: 'aspect'
(art.kind === 'type' || art.kind === 'entity' && art.abstract && art.abstract.val))
return (art.$syntax === 'aspect' || art.kind === 'entity' && art.abstract && art.abstract.val)
? rejectNonStruct( art )

@@ -294,2 +299,8 @@ : rejectNonEntity( art );

else if (art.name.$mixin) { // TODO: art.kind === 'mixin'
if (spec.noAliasOrMixin) {
signalNotFound( 'ref-rejected-on', head.location, user, extDict && [ extDict ],
{ '#': 'mixin', id: head.id } );
// also set link on head?
return setLink( ref, false );
}
// console.log(message( null, art.location, art, {}, 'Info','MIX').toString())

@@ -305,2 +316,8 @@ setLink( head, art, '_navigation' );

else if (art.kind === '$tableAlias') {
if (spec.noAliasOrMixin && !art.self) { // TODO: extra kind $self?
signalNotFound( 'ref-rejected-on', head.location, user, extDict && [ extDict ],
{ '#': 'alias', id: head.id } );
// also set link on head?
return setLink( ref, false );
}
setLink( head, art, '_navigation' );

@@ -340,3 +357,4 @@ // console.log( message( null, art.location, art,

}
if (user && !spec.noDep) {
if (user && (!spec.noDep ||
spec.noDep === 'only-entity' && art.kind !== 'entity' && art.kind !== 'view')) {
const { location } = ref; // || combinedLocation( head, path[tail.length] );

@@ -446,3 +464,3 @@ // TODO: location of last path item if not main artifact

}
else if (r.kind === 'block') {
if (r.kind === 'block') {
return setLink( head, r.name._artifact );

@@ -461,4 +479,4 @@ }

(r.self ? !head.quoted : path.length > 1)) {
// except $self if quoted, or "real" table aliases (not $self) with path len 1
// TODO: $projection only if not quoted _and_ length > 1
// except $self if quoted, or "real" table aliases (not $self) with path len 1
// TODO: $projection only if not quoted _and_ length > 1
return setLink( head, r );

@@ -608,2 +626,3 @@ }

message( null, item.location, user, {}, 'Error',
// eslint-disable-next-line max-len
'You cannot follow associations other than to elements referred to in a managed association\'s key' );

@@ -667,2 +686,6 @@ }

function defineAnnotations( construct, art, block, priority ) {
// namespaces cannot be annotated but we check for it because of
// builtin contexts that appear as 'namespace'
if (art.kind === 'namespace')
return;
// TODO: block should be construct._block

@@ -707,3 +730,4 @@ if (construct.annotationAssignments && construct.annotationAssignments.doc )

if (iHaveVariant) {
message( 'anno-duplicate-variant', item.name.variant.location, construct, {}, // TODO: params
message( 'anno-duplicate-variant', item.name.variant.location, construct,
{}, // TODO: params
'Error', 'Annotation variant has been already provided' );

@@ -768,3 +792,3 @@ }

const elem = {
name: { location: location || origin.name.location, id: origin.name.id },
name: { location: location || origin.name.location, id: name },
kind: origin.kind,

@@ -771,0 +795,0 @@ origin: { location },

'use strict';
const { isEdmPropertyRendered } = require('../../model/csnUtils');
const { isEdmPropertyRendered, isBuiltinType } = require('../../model/csnUtils');
const edmUtils = require('../edmUtils.js');
const preprocessAnnotations = require('./preprocessAnnotations.js');
const oDataDictionary = require('./Dictionary.json');
const oDataDictionary = require('../../gen/Dictionary.json');
const alerts = require('../../base/alerts');

@@ -29,2 +28,3 @@

Hierarchy (not published, still experimental)
HTML5 (published, experimental)
ODM (published, experimental)

@@ -82,2 +82,7 @@ PersonalData (published)

},
'HTML5': {
'ref': { Uri: 'https://sap.github.io/odata-vocabularies/vocabularies/HTML5.xml' },
'inc': { Alias: 'HTML5', Namespace: 'com.sap.vocabularies.HTML5.v1' },
'int': { filename: 'HTML5.xml' }
},
'Measures': {

@@ -135,4 +140,6 @@ 'ref': { Uri: 'https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml' },

const Edm = require('../edm.js')(csn, options);
const { signal } = alerts(csn);
const Edm = require('../edm.js')(options);
// annotation preprocessing

@@ -159,13 +166,16 @@ preprocessAnnotations.preprocessAnnotations(csn, serviceName, options);

getDictTerm: function(termName, context) {
let dictTerm = dict.terms[termName]
const dictTerm = dict.terms[termName]
// register vocabulary usage if possible
const vocName = termName.slice(0, termName.indexOf('.'));
if(vocabularyDefinitions[vocName])
vocabularyDefinitions[vocName].used = true;
if (dictTerm) {
// register usage of vocabulary
vocabularyDefinitions[termName.slice(0, termName.indexOf('.'))].used = true;
// issue warning for usage of experimental Terms, but only once per Term
if (dictTerm['$experimental'] && !experimental[termName] && !options.betaMode) {
warningMessage(context, 'Term "' + termName + '" is experimental and can be changed or removed at any time, do not use productively!');
if (dictTerm['$experimental'] && !experimental[termName]) {
message(signal.warning, context, 'Term "' + termName + '" is experimental and can be changed or removed at any time, do not use productively!');
experimental[termName] = true;
}
if (dictTerm['$deprecated'] && !deprecated[termName] && !options.betaMode) {
infoMessage(context, 'Term "' + termName + '" is deprecated. ' + dictTerm['$deprecationText']);
if (dictTerm['$deprecated'] && !deprecated[termName]) {
message(signal.info, context, 'Term "' + termName + '" is deprecated. ' + dictTerm['$deprecationText']);
deprecated[termName] = true;

@@ -191,4 +201,2 @@ }

const { signal } = alerts(csn);
// global variable where we store all the generated annotations

@@ -223,3 +231,3 @@ let g_annosArray = [];

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

@@ -250,6 +258,6 @@ /** @type {object} */

// this function is called in the translation code to issue a warning message
// messages are reported via the alerts attribute of csn
// this function is called in the translation code to issue an info/warning/error message
// messages are reported via the alerts attribute of csn
// context contains "semantic location"
function warningMessage(context, message) {
function message(severity, context, message) {
let fullMessage = 'in annotation translation: ' + message;

@@ -263,23 +271,5 @@ if (context) {

}
signal(signal.warning`${fullMessage}`);
signal(severity`${fullMessage}`);
}
// this function is called in the translation code to issue a warning message
// messages are reported via the alerts attribute of csn
// context contains "semantic location"
function infoMessage(context, message) {
let fullMessage = 'in annotation translation: ' + message;
if (context) {
let loc = 'target: ' + context.target + ', annotation: ' + context.term;
if (context.stack.length > 0) {
loc += context.stack.join('');
}
fullMessage += ', ' + loc;
}
signal(signal.info`${fullMessage}`);
}
/*

@@ -351,3 +341,3 @@ Mapping annotated thing in cds/csn => annotated thing in edmx:

if (Object.keys(element).filter( x => x.substr(0,1) === '@' ).filter(filterKnownVocabularies).length > 0) {
warningMessage(null, 'annotations at nested elements are not yet supported, object ' + objname + ', element ' + baseElemName + '.' + elemName);
message(signal.warning, null, 'annotations at nested elements are not yet supported, object ' + objname + ', element ' + baseElemName + '.' + elemName);
}

@@ -415,3 +405,3 @@

if (action.kind === 'function') {
let mapType = (p) => (p.type.startsWith('cds.') && !p.type.startsWith('cds.foundation.')) ?
let mapType = (p) => (isBuiltinType(p.type)) ?
edmUtils.mapCdsToEdmType(p, signal, false /*is only called for v4*/) : p.type;

@@ -439,3 +429,7 @@ for (let n in action.params) {

if(!isEdmPropertyRendered(carrier, options)) {
// if the carier is an element that is not rendered or
// if the carrier is a derived type of a primitive type which is not rendered in V2
// do nothing
if(!isEdmPropertyRendered(carrier, options) ||
(carrier.kind === 'type' && !edmUtils.isStructuredType(carrier) && options.isV2())) {
return;

@@ -446,3 +440,3 @@ }

let knownAnnos = annoNames.filter(filterKnownVocabularies).filter(x => carrier[x] !== null || nullWhitelist.includes(x));
if (knownAnnos.length == 0) return;
if (knownAnnos.length === 0) return;

@@ -673,3 +667,3 @@ // in csn, all annotations are flattened

if (p.length>2) {
warningMessage(context, 'multiple qualifiers (' + p[1] + ',' + p[2] + (p.length>3?',...':'') + ')')
message(signal.warning, context, 'multiple qualifiers (' + p[1] + ',' + p[2] + (p.length>3?',...':'') + ')')
}

@@ -684,3 +678,3 @@

else {
infoMessage(context, 'unknown term ' + termNameWithoutQualifiers);
message(signal.info, context, 'unknown term ' + termNameWithoutQualifiers);
}

@@ -720,4 +714,4 @@

else if (cAnnoValue && typeof cAnnoValue === 'object') {
if (Object.keys(cAnnoValue).length == 0) {
warningMessage(context, 'empty record');
if (Object.keys(cAnnoValue).length === 0) {
message(signal.warning, context, 'empty record');
}

@@ -748,4 +742,4 @@ else if ('=' in cAnnoValue) {

let k = Object.keys(cAnnoValue).filter( x => x.charAt(0) === '@');
if (!k || k.length == 0) {
warningMessage(context, 'pseudo-struct without nested annotation');
if (!k || k.length === 0) {
message(signal.warning, context, 'pseudo-struct without nested annotation');
}

@@ -763,3 +757,3 @@ for (let nestedAnnoName of k) {

// object consists only of properties starting with "@"
warningMessage(context, 'nested annotations without corresponding base annotation');
message(signal.warning, context, 'nested annotations without corresponding base annotation');
}

@@ -790,12 +784,12 @@ else {

if (!expectedType && !isPrimitiveType(dTypeName)) {
warningMessage(context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
message(signal.warning, context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
}
else if (isComplexType(dTypeName)) {
warningMessage(context, 'found enum value, but expected complex type ' + dTypeName);
message(signal.warning, context, 'found enum value, but expected complex type ' + dTypeName);
}
else if (isPrimitiveType(dTypeName) || expectedType['$kind'] !== 'EnumType') {
warningMessage(context, 'found enum value, but expected non-enum type ' + dTypeName);
message(signal.warning, context, 'found enum value, but expected non-enum type ' + dTypeName);
}
else if (!expectedType['Members'].includes(enumValue)) {
warningMessage(context, 'enumeration type ' + dTypeName + ' has no value ' + enumValue);
message(signal.warning, context, 'enumeration type ' + dTypeName + ' has no value ' + enumValue);
}

@@ -812,3 +806,3 @@ return;

if (!type || type['IsFlags'] !== 'true') {
warningMessage(context, "enum type '" + dTypeName + "' doesn't allow multiple values");
message(signal.warning, context, "enum type '" + dTypeName + "' doesn't allow multiple values");
}

@@ -824,3 +818,3 @@

// TODO improve message: but found ...
warningMessage(context, 'expected an enum value');
message(signal.warning, context, 'expected an enum value');
}

@@ -850,3 +844,3 @@ context.stack.pop();

if (!expr) {
warningMessage(context, 'empty expression value');
message(signal.warning, context, 'empty expression value');
}

@@ -882,3 +876,3 @@ else {

if (!['true','false'].includes(val)) {
warningMessage(context, 'found String, but expected type ' + dTypeName);
message(signal.warning, context, 'found String, but expected type ' + dTypeName);
}

@@ -889,3 +883,3 @@ }

if (isNaN(Number(val)) || isNaN(parseFloat(val))) {
warningMessage(context, 'found non-numeric string, but expected type ' + dTypeName);
message(signal.warning, context, 'found non-numeric string, but expected type ' + dTypeName);
}

@@ -896,10 +890,10 @@ }

if (isNaN(Number(val)) || isNaN(parseFloat(val))) {
warningMessage(context, 'found non-numeric string, but expected type ' + dTypeName);
message(signal.warning, context, 'found non-numeric string, but expected type ' + dTypeName);
}
}
else if (isComplexType(dTypeName)) {
warningMessage(context, 'found String, but expected complex type ' + dTypeName);
message(signal.warning, context, 'found String, but expected complex type ' + dTypeName);
}
else if (isEnumType(dTypeName)) {
warningMessage(context, 'found String, but expected enum type ' + dTypeName);
message(signal.warning, context, 'found String, but expected enum type ' + dTypeName);
typeName = 'EnumMember';

@@ -915,3 +909,3 @@ }

// TODO
//warningMessage(context, "type is not yet handled: found String, expected type: " + dTypeName);
//message(signal.warning, context, "type is not yet handled: found String, expected type: " + dTypeName);
}

@@ -931,3 +925,3 @@ }

else {
warningMessage(context, 'found Boolean, but expected type ' + dTypeName);
message(signal.warning, context, 'found Boolean, but expected type ' + dTypeName);
}

@@ -937,3 +931,3 @@ }

if (isComplexType(dTypeName)) {
warningMessage(context, 'found number, but expected complex type ' + dTypeName);
message(signal.warning, context, 'found number, but expected complex type ' + dTypeName);
}

@@ -944,6 +938,6 @@ else if (dTypeName === 'Edm.String') {

else if (dTypeName === 'Edm.PropertyPath') {
warningMessage(context, 'found number, but expected type ' + dTypeName);
message(signal.warning, context, 'found number, but expected type ' + dTypeName);
}
else if (dTypeName === 'Edm.Boolean') {
warningMessage(context, 'found number, but expected type ' + dTypeName);
message(signal.warning, context, 'found number, but expected type ' + dTypeName);
}

@@ -973,3 +967,3 @@ else if (dTypeName === 'Edm.Decimal') {

} else {
warningMessage(context, "expected simple value, but found value '" + val + "' with type '" + typeof val + "'");
message(signal.warning, context, "expected simple value, but found value '" + val + "' with type '" + typeof val + "'");
}

@@ -999,5 +993,5 @@

if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
warningMessage(context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
message(signal.warning, context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
else
warningMessage(context, "found complex type, but expected type '" + dTypeName + "'");
message(signal.warning, context, "found complex type, but expected type '" + dTypeName + "'");
return newRecord;

@@ -1011,7 +1005,7 @@ }

// this type doesn't exist
warningMessage(context, "explicitly specified type '" + actualTypeName + "' not found in vocabulary");
message(signal.warning, context, "explicitly specified type '" + actualTypeName + "' not found in vocabulary");
}
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
// this type doesn't fit the expected one
warningMessage(context, "explicitly specified type '" + actualTypeName
message(signal.warning, context, "explicitly specified type '" + actualTypeName
+ "' is not derived from expected type '" + dTypeName + "'");

@@ -1022,3 +1016,3 @@ actualTypeName = dTypeName;

// this type is abstract
warningMessage(context, "explicitly specified type '" + actualTypeName + "' is abstract, specify a concrete type");
message(signal.warning, context, "explicitly specified type '" + actualTypeName + "' is abstract, specify a concrete type");
actualTypeName = dTypeName;

@@ -1042,3 +1036,3 @@ }

if (isAbstractType(actualTypeName)) {
warningMessage(context, "type '" + dTypeName + "' is abstract, use '$Type' to specify a concrete type");
message(signal.warning, context, "type '" + dTypeName + "' is abstract, use '$Type' to specify a concrete type");
}

@@ -1073,3 +1067,3 @@

if (!dictPropertyTypeName){
warningMessage(context, "record type '" + actualTypeName + "' doesn't have a property '" + i + "'");
message(signal.warning, context, "record type '" + actualTypeName + "' doesn't have a property '" + i + "'");
}

@@ -1103,3 +1097,3 @@ }

else {
warningMessage(context, 'found collection value, but expected non-collection type ' + dTypeName);
message(signal.warning, context, 'found collection value, but expected non-collection type ' + dTypeName);
}

@@ -1116,3 +1110,3 @@ }

if (Array.isArray(value)) {
warningMessage(context, 'nested collections are not supported');
message(signal.warning, context, 'nested collections are not supported');
}

@@ -1127,3 +1121,3 @@ else if (value && typeof value === 'object') {

else if (value['#']) {
warningMessage(context, 'enum inside collection is not yet supported');
message(signal.warning, context, 'enum inside collection is not yet supported');
}

@@ -1160,6 +1154,6 @@ else {

if(subset.length > 1) { // doesn't work for three or more...
warningMessage(context, 'edmJson code contains more than one special property: ' + subset);
message(signal.warning, context, 'edmJson code contains more than one special property: ' + subset);
return null;
}
if(subset.length == 0) {
if(subset.length === 0) {
// only one property (that is not a special property)

@@ -1170,3 +1164,3 @@ if (Object.keys(obj) != undefined && Object.keys(obj).length==1) {

}
warningMessage(context, 'edmJson code contains no special property out of: ' + specialProperties);
message(signal.warning, context, 'edmJson code contains no special property out of: ' + specialProperties);
return null;

@@ -1189,3 +1183,3 @@ }

else {
warningMessage(context, 'unexpected element without $: ' + p);
message(signal.warning, context, 'unexpected element without $: ' + p);
}

@@ -1202,3 +1196,3 @@ }

else if (Array.isArray(a)) {
warningMessage(context, 'verbatim code contains nested array');
message(signal.warning, context, 'verbatim code contains nested array');
}

@@ -1205,0 +1199,0 @@ else {

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

let keyNames = Object.keys(target.elements).filter(x => target.elements[x].key);
if (keyNames.length == 0) {
if (keyNames.length === 0) {
keyNames.push['MISSING'];

@@ -216,3 +216,3 @@ signal(warning`in annotation preprocessing: target ${targetName} has no key`);

let keys = Object.keys(vlEntity.elements).filter( x => vlEntity.elements[x].key );
if (keys.length == 0) {
if (keys.length === 0) {
signal(warning`in annotation preprocessing/value help shortcut: entity "${enameFull}" has no key, ${ctx}`);

@@ -219,0 +219,0 @@ throw 'leave';

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

const { setProp } = require('../base/model');
const { getUtils, cloneCsn, isEdmPropertyRendered } = require('../model/csnUtils');
const { cloneCsn, isEdmPropertyRendered } = require('../model/csnUtils');
const { checkCSNVersion } = require('../json/csnVersion');
const builtins = require('../compiler/builtins');
const { handleMessages } = require('../base/messages');

@@ -39,11 +41,12 @@ /*

let rc = Object.create(null);
const { isBuiltinType } = getUtils(csn);
let [services, options] = initializeModel(csn, _options);
const Edm = require('./edm.js')(csn, options);
let [ services, options ] = initializeModel(csn, _options);
const Edm = require('./edm.js')(options);
let v = options.v;
if(services.length == 0)
signal(signal.error`No Services found in model`);
if(services.length === 0) {
signal(signal.info`No Services in model`);
return rc;
}

@@ -58,9 +61,10 @@ if(serviceName) {

}
return rc;
}
else {
return services.reduce((services, serviceCsn) => {
rc = services.reduce((services, serviceCsn) => {
services[serviceCsn.name] = createEdm(serviceCsn);
return services; }, rc);
}
handleMessages(csn, options);
return rc;

@@ -84,3 +88,3 @@ //--------------------------------------------------------------------------------

/** @type {object} */
let Schema = new Edm.Schema(v, serviceName, undefined /* unset alias */);
let Schema = new Edm.Schema(v, serviceName, undefined /* unset alias */, serviceCsn );

@@ -107,4 +111,4 @@ // now namespace and alias are used to create the fullQualified(name)

// create the complex types
edmUtils.foreach(csn.definitions, a => edmUtils.isStructuredType(a) && a.name.startsWith(serviceName + '.'), createComplexType);
// create the complex types (don't render aspects by using $syntax hack until kind='aspect' is available)
edmUtils.foreach(csn.definitions, a => !(a.kind === 'aspect' || a.$syntax === 'aspect') && edmUtils.isStructuredType(a) && a.name.startsWith(serviceName + '.'), createComplexType);

@@ -149,7 +153,7 @@ if(options.isV4())

// remove EntityContainer if empty
if(Schema._ec._children.length == 0) {
if(Schema._ec._children.length === 0) {
let pos = Schema._children.indexOf(Schema._ec);
Schema._children.splice(pos, 1);
}
if(Schema._children.length == 0) {
if(Schema._children.length === 0) {
signal(signal.warning`Schema is empty`, ['definitions',Schema.Namespace]);

@@ -172,4 +176,3 @@ }

signal(signal.error`EntityType "${serviceName}.${EntityTypeName}" has no properties`, ['definitions',entityCsn.name]);
}
if(entityCsn.$edmKeyPaths.length === 0) {
} else if(entityCsn.$edmKeyPaths.length === 0) {
signal(signal.error`EntityType "${serviceName}.${EntityTypeName}" has no primary key`, ['definitions',entityCsn.name]);

@@ -363,3 +366,3 @@ }

let type = returns.type;
if(type && isBuiltinType(type))
if(type)
type = edmUtils.mapCdsToEdmType(returns, signal, options.isV2());

@@ -419,6 +422,8 @@

if ( options.isV2() && elementCsn['@Core.MediaType']) {
// CDXCORE-CDXCORE-177:
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
// V4: render property type 'Edm.Stream'
hasStream = true;
// CDXCORE-CDXCORE-177: remove elementCsn from elements dictionary
// FIXME: Is this still relevant?
delete parentCsn.elements[elementName];
signal(signal.info`"${parentCsn.name}: Property "${elementName}" annotated with '@Core.MediaType' is removed from EDM in Odata V2`, ['definitions', parentCsn.name]);
} else

@@ -440,3 +445,9 @@ props.push(new Edm.Property(v, { Name: elementName }, elementCsn));

let elementsCsn = structuredTypeCsn.items || structuredTypeCsn;
complexType.append(...(createProperties(elementsCsn)[0]));
let properties = createProperties(elementsCsn)[0];
if(properties.length === 0) {
signal(signal.error`ComplexType "${structuredTypeCsn.name}" has no properties`, ['definitions', structuredTypeCsn.name]);
}
complexType.append(...(properties));
Schema.append(complexType);

@@ -451,6 +462,10 @@ }

let props = { Name: typeCsn.name.replace(namespace, '') };
if((typeCsn.items && typeCsn.items.enum) || typeCsn.enum)
if((typeCsn.items && typeCsn.items.enum) || typeCsn.enum) {
if (!builtins.isIntegerTypeName(typeCsn.type)) {
signal(signal.warning`Only integer enums are allowed in OData`, ['definitions', typeCsn.name]);
}
typeDef = new Edm.EnumType(v, props, typeCsn);
else
} else {
typeDef = new Edm.TypeDefinition(v, props, typeCsn );
}
Schema.append(typeDef);

@@ -600,2 +615,4 @@ }

fromRole, toRole, fromEntitySet, toEntitySet);
if(navigationProperty._csn._SetAttributes)
assocSet.setSapVocabularyAsAttributes(navigationProperty._csn._SetAttributes);
Schema._ec.append(assocSet);

@@ -602,0 +619,0 @@ }

@@ -6,8 +6,5 @@ // @ts-nocheck

const edmUtils = require('./edmUtils.js');
const alerts = require('../base/alerts');
const { isBuiltinType } = require('../model/csnUtils.js');
module.exports = function (csn, options) {
const definitions = csn.definitions;
const { signal } = alerts(csn, options);
module.exports = function (options) {
class Node

@@ -152,3 +149,9 @@ {

// Do not escape > as it is a marker for {bi18n>...} translated string values
return (typeof s === 'string') ? s.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&amp;')./*.replace(/>/g, '&gt;').*/replace(/</g, '&lt;').replace(/"/g, '&quot;').replace(/\n/g, '&#0xa;') : s;
return (typeof s === 'string') ?
s.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&amp;')
//.replace(/>/g, '&gt;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/\r\n|\n/g, '&#xa;') :
s;
}

@@ -162,5 +165,2 @@ }

if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(e =>

@@ -172,9 +172,10 @@ xml += e.toXML(indent, what) + '\n');

// virtual
setSapVocabularyAsAttributes(csn)
setSapVocabularyAsAttributes(csn, useSetAttributes=false)
{
if(csn)
{
for (let p in csn)
const attr = (useSetAttributes ? csn._SetAttributes : csn);
for (let p in attr)
if (p.match(/^@sap./))
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : csn[p] } );
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : attr[p] } );
}

@@ -200,5 +201,2 @@ }

if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(c => includes.push(c.toJSON()));

@@ -223,3 +221,3 @@ if(includes.length > 0)

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

@@ -234,6 +232,9 @@ let props = Object.create(null);

if(this.v2 && serviceCsn)
this.setSapVocabularyAsAttributes(serviceCsn);
if(withEntityContainer)
{
let ecprops = { Name: 'EntityContainer' };
let ec = new EntityContainer(v, ecprops );
let ec = new EntityContainer(v, ecprops, serviceCsn );
if(this.v2)

@@ -331,4 +332,2 @@ ec.setXml( { 'm:IsDefaultEntityContainer': true } );

// 'edmx:DataServices' should not appear in JSON
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(s => json[s.Namespace] = s.toJSON());

@@ -409,4 +408,2 @@ return json;

this._defaultRefs.forEach(r => reference_json[r.Uri] = r.toJSON());
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(r => reference_json[r.Uri] = r.toJSON());

@@ -434,4 +431,2 @@

this._defaultRefs.forEach(r => xml += r.toXML(indent) + '\n');
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(e => xml += e.toXML(indent) + '\n');

@@ -443,3 +438,10 @@ xml += this._service.toXML(indent, what) + '\n';

class EntityContainer extends Node {}
class EntityContainer extends Node
{
// use the _SetAttributes
setSapVocabularyAsAttributes(csn)
{
super.setSapVocabularyAsAttributes(csn, true);
}
}

@@ -468,4 +470,2 @@

let json_navPropBinding = Object.create(null);
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(npb => json_navPropBinding[npb.Path] = npb.Target);

@@ -481,13 +481,6 @@ if(Object.keys(json_navPropBinding).length > 0)

{
// virtual
// use the _SetAttributes
setSapVocabularyAsAttributes(csn)
{
if(csn)
{
for (let p in csn._EntitySetAttributes)
{
if (p.match(/^@sap\./))
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : csn._EntitySetAttributes[p] } );
}
}
super.setSapVocabularyAsAttributes(csn, true);
}

@@ -518,4 +511,2 @@

let json = [];
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(c => json.push(c.toJSON()));

@@ -553,4 +544,2 @@ return json;

let json_parameters = [];
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(p => json_parameters.push(p.toJSON()));

@@ -602,12 +591,10 @@ if(json_parameters.length > 0)

if(typecsn.items && typecsn.items.type &&
typecsn.items.type.startsWith('cds.') &&
!typecsn.items.type.startsWith('cds.foundation.')) {
isBuiltinType(typecsn.items.type)) {
scalarType = typecsn.items;
}
else if(typecsn.type.startsWith('cds.') &&
!typecsn.type.startsWith('cds.foundation.')) {
else if(isBuiltinType(typecsn.type)) {
scalarType = typecsn;
}
if(scalarType) {
this[typeName] = edmUtils.mapCdsToEdmType(scalarType, signal, this.v2, csn['@Core.MediaType']);
this[typeName] = csn._edmType;
// CDXCORE-CDXCORE-173 ignore type facets for Edm.Stream

@@ -633,4 +620,2 @@ if(this[typeName] !== 'Edm.Stream')

this[typeName] = odataType;
} else if(odataType) {
signal(signal.info`@odata.Type: '${odataType}' is ignored, only 'Edm.String' and 'Edm.Int[16,32,64]' are allowed`, csn.$location);
}

@@ -641,3 +626,3 @@ }

if(csn.kind === undefined) {
this.set({ _isCollection: !!(csn.items || definitions[csn.type] && definitions[csn.type].items) });
this.set({ _isCollection: csn._isCollection });
}

@@ -677,28 +662,2 @@

/* ReturnType is only used in v4, mapCdsToEdmType can be safely
called with V2=false */
class ReturnType extends TypeBase
{
constructor(v, csn)
{
super(v, {}, csn);
/* all return types are nullable by default,
CDS does not allow to specify not null
*/
this.set( { _nullable: true });
}
// we need Name but NO $kind, can't use standard to JSON()
toJSON()
{
let json = Object.create(null);
this.toJSONattributes(json);
// !this._nullable if Nullable=true become default
if(this._nullable)
json['$Nullable'] = this._nullable;
return json;
}
}
class ComplexType extends TypeBase { }

@@ -783,4 +742,2 @@ class EntityType extends ComplexType

{
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(c => c.toJSONattributes(json));

@@ -823,6 +780,10 @@ return json;

{
// From the Spec: In OData 4.01 responses a collection-valued property MUST specify a value for the Nullable attribute.
if(this._isCollection) {
this.Nullable = !this.isNotNullable();
}
// Nullable=true is default, mention Nullable=false only in XML
// Nullable=false is default for EDM JSON representation 4.01
// When a key explicitly (!) has 'notNull = false', it stays nullable
if(this.isNotNullable())
else if(this.isNotNullable())
{

@@ -838,3 +799,4 @@ this.Nullable = false;

// When a key explicitly (!) has 'notNull = false', it stays nullable
return ((nodeCsn.key && !(nodeCsn.notNull === false)) || nodeCsn.notNull == true);
return (nodeCsn._NotNullCollection !== undefined ? nodeCsn._NotNullCollection :
(nodeCsn.key && !(nodeCsn.notNull === false)) || nodeCsn.notNull === true);
}

@@ -845,4 +807,4 @@

super.toJSONattributes(json);
// mention all nullable elements explictly, remove if Nullable=true becomes default
if(this.Nullable == undefined || this.Nullable == true)
// mention all nullable elements explicitly, remove if Nullable=true becomes default
if(this.Nullable === undefined || this.Nullable === true)
{

@@ -855,2 +817,23 @@ json['$Nullable'] = true;

/* ReturnType is only used in v4, mapCdsToEdmType can be safely
called with V2=false */
class ReturnType extends PropertyBase
{
constructor(v, csn)
{
super(v, {}, csn);
}
// we need Name but NO $kind, can't use standard to JSON()
toJSON()
{
let json = Object.create(null);
this.toJSONattributes(json);
// !this._nullable if Nullable=true become default
if(this._nullable)
json['$Nullable'] = this._nullable;
return json;
}
}
class Property extends PropertyBase

@@ -951,2 +934,5 @@ {

// attribute Nullable is not allowed in combination with Collection (see Spec)
// Even if min cardinality is > 0, remove Nullable, because the implicit OData contract
// is that a navigation property must either return an empty collection or all collection
// values are !null (with other words: a collection must never return [1,2,null,3])
delete this.Nullable;

@@ -999,3 +985,5 @@ }

// Do not derive Nullable=false from key attribute.
return (nodeCsn.notNull == true);
// If an unmanaged association has a cardinality min === max === 1 => Nullable=false
// If unmanaged assoc has min > 0 and max > 1 => target is 'Collection()' => Nullable is not applicable
return (nodeCsn.notNull === true || (nodeCsn.on && nodeCsn.cardinality && nodeCsn.cardinality.min === 1 && nodeCsn.cardinality.max === 1));
}

@@ -1021,4 +1009,2 @@ isToMany() {

let json_constraints = Object.create(null);
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(c => {

@@ -1104,3 +1090,3 @@ switch(c.kind) {

if(expr.length == 0)
if(expr.length === 0)
throw Error('Please debug me: neither child nor constant expression found on annotation');

@@ -1163,4 +1149,2 @@ return addExpressions(expr, this._jsonOnlyAttributes);

{
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(a => json['@' + a.Term] = a.toJSON())

@@ -1191,3 +1175,3 @@ }

{
if(this._children.length == 0 || this._ignoreChildren) // must be a constant expression
if(this._children.length === 0 || this._ignoreChildren) // must be a constant expression
return this.getConstantExpressionValue();

@@ -1219,4 +1203,2 @@ else

{
if(options.testMode===true) // sort nodes only in test mode
this._children.sort(compareNodes)
this._children.forEach(a => {

@@ -1250,3 +1232,3 @@ let name;

{
if(this._children.length == 0 || this._ignoreChildren)
if(this._children.length === 0 || this._ignoreChildren)
return this.getConstantExpressionValue();

@@ -1299,3 +1281,3 @@ else

{
if(this._children.length == 0 || this._ignoreChildren) // must be a constant expression
if(this._children.length === 0 || this._ignoreChildren) // must be a constant expression
return this.getConstantExpressionValue();

@@ -1416,23 +1398,1 @@ else

} // instance function
function compareNodes(A,B) {
if(A.kind<B.kind) return -1;
if(A.kind>B.kind) return 1;
if(A.Name !== undefined && B.Name !== undefined) {
if(A.Name<B.Name) return -1;
if(A.Name>B.Name) return 1;
}
if(A.Role !== undefined && B.Role !== undefined) {
if(A.Role<B.Role) return -1;
if(A.Role>B.Role) return 1;
}
if(A.Path !== undefined && B.Path !== undefined) {
if(A.Path<B.Path) return -1;
if(A.Path>B.Path) return 1;
}
if(A.Property !== undefined && B.Property !== undefined) {
if(A.Property<B.Property) return -1;
if(A.Property>B.Property) return 1;
}
return 0;
}
'use strict';
/* eslint max-statements-per-line:off */
const { setProp } = require('../base/model');
const { forEachDefinition, isEdmPropertyRendered, forEachMemberRecursively, getUtils, cloneCsn } = require('../model/csnUtils');
const { setProp, isBetaEnabled } = require('../base/model');
const { forEachDefinition, isEdmPropertyRendered, forEachMemberRecursively, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
const alerts = require('../base/alerts');

@@ -37,3 +37,3 @@ const edmUtils = require('./edmUtils.js')

const { signal } = alerts(csn);
const { signal } = alerts(csn, _options);
const {

@@ -71,34 +71,54 @@ getCsnDef,

let serviceNames = services.map(s => s.name);
// Link association targets and spray @odata.contained over untagged compositions
foreach(csn.definitions, isStructuredArtifact, linkAssociationTarget);
// Create data structures for containments
foreach(csn.definitions, isStructuredArtifact, initializeContainments);
// Initialize entities with parameters (add Parameter entity)
foreach(csn.definitions, isParameterizedEntityOrView, initializeParameterizedEntityOrView);
// Initialize structures
foreach(csn.definitions, isStructuredArtifact, initializeStructure);
// Initialize associations
foreach(csn.definitions, isStructuredArtifact, initializeAssociation);
// get constraints for associations
foreach(csn.definitions, isStructuredArtifact, initializeConstraints);
// create association target proxies
foreach(csn.definitions, isStructuredArtifact, redirectDanglingAssociationsToProxyTargets);
// create edmKeyRefPaths
foreach(csn.definitions, isStructuredArtifact, initializeEdmKeyRefPaths);
if(serviceNames.length) {
services.forEach(initializeService);
// Link association targets and spray @odata.contained over untagged compositions
foreach(csn.definitions, isStructuredArtifact, linkAssociationTarget);
// Create data structures for containments
foreach(csn.definitions, isStructuredArtifact, initializeContainments);
// Initialize entities with parameters (add Parameter entity)
foreach(csn.definitions, isParameterizedEntityOrView, initializeParameterizedEntityOrView);
// Initialize structures
foreach(csn.definitions, isStructuredArtifact, initializeStructure);
// Initialize associations
foreach(csn.definitions, isStructuredArtifact, initializeAssociation);
// get constraints for associations
foreach(csn.definitions, isStructuredArtifact, initializeConstraints);
// create association target proxies
foreach(csn.definitions, isStructuredArtifact, redirectDanglingAssociationsToProxyTargets);
// create edmKeyRefPaths
foreach(csn.definitions, isStructuredArtifact, initializeEdmKeyRefPaths);
// decide if an entity set needs to be constructed or not
foreach(csn.definitions, isStructuredArtifact, determineEntitySet);
// decide if an entity set needs to be constructed or not
foreach(csn.definitions, isStructuredArtifact, determineEntitySet);
// let all doc props become @Core.Descriptions
forEachDefinition(csn, artifact => {
assignAnnotation(artifact, '@Core.Description', artifact.doc);
forEachMemberRecursively(artifact,
member => {
assignAnnotation(member, '@Core.Description', member.doc);
// 1. let all doc props become @Core.Descriptions
// 2. mark a member that will become a collection
// 3. assign the edm primitive type to elements, to be used in the rendering later
forEachDefinition(csn, artifact => {
assignAnnotation(artifact, '@Core.Description', artifact.doc);
markCollection(artifact);
mapCdsToEdmProp(artifact);
if (artifact.returns) {
markCollection(artifact.returns);
mapCdsToEdmProp(artifact.returns);
}
);
});
forEachMemberRecursively(artifact,
member => {
assignAnnotation(member, '@Core.Description', member.doc);
markCollection(member);
mapCdsToEdmProp(member);
if (member.returns) {
markCollection(member.returns);
mapCdsToEdmProp(member.returns);
}
}
);
});
}
return [services, options];
// initialize the service itself
function initializeService(service) {
setSAPSpecificV2AnnotationsToEntityContainer(options, service);
}
// link association target to association and add @odata.contained to compositions in V4

@@ -142,3 +162,3 @@ function linkAssociationTarget(struct) {

// - All associations in the containee pointing back to the container are marked with
// a boolean property '_isToContainerEntity : true', except if the association itself
// a boolean property '_isToContainer : true', except if the association itself
// has the annotation '@odata.contained' (indicating the top-down link in a hierarchy).

@@ -218,7 +238,12 @@ // - Rewrite annotations that would be assigned to the containees entity set for the

if(entityCsn.$location){
assignProp(parameterCsn, '$location', entityCsn.$location);
}
/*
<EntitySet Name="ZRHA_TEST_CDS" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSParameters" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
<EntitySet Name="ZRHA_TEST_CDS" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSParameters" sap:creatable="false" sap:updatable="false"
sap:deletable="false" sap:pageable="false" sap:content-version="1"/>
*/
assignProp(parameterCsn, '_EntitySetAttributes',
assignProp(parameterCsn, '_SetAttributes',
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.pageable': false });

@@ -276,5 +301,6 @@

/*
<EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false" sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
<EntitySet Name="ZRHA_TEST_CDSSet" EntityType="ZRHA_TEST_CDS_CDS.ZRHA_TEST_CDSType" sap:creatable="false" sap:updatable="false"
sap:deletable="false" sap:addressable="false" sap:content-version="1"/>
*/
assignProp(entityCsn, '_EntitySetAttributes',
assignProp(entityCsn, '_SetAttributes',
{'@sap.creatable': false, '@sap.updatable': false, '@sap.deletable': false, '@sap.addressable': false });

@@ -397,5 +423,3 @@

});
if(struct['@Core.AlternateKeys'] === undefined) {
struct['@Core.AlternateKeys'] = altKeys;
}
assignAnnotation(struct, '@Core.AlternateKeys', altKeys);
keys = Object.create(null);

@@ -414,7 +438,7 @@ validKey.forEach(e => {

assignProp(struct, '_EntitySetAttributes', Object.create(null));
assignProp(struct, '_SetAttributes', Object.create(null));
assignProp(struct, '$keys', keys);
applyAppSpecificLateCsnTransformationOnStructure(options, struct);
setSAPSpecificV2AnnotationsToEntitySet(options, struct);
// initialize bound actions and functions

@@ -440,2 +464,3 @@

}
// in case this is a forward assoc, store the backlink partneres here, _partnerCsn.length > 1 => error

@@ -454,2 +479,4 @@ setProp(element, '_partnerCsn', []);

}
// and afterwards eventually remove some :)
setSAPSpecificV2AnnotationsToAssociation(options, element, struct);
});

@@ -510,3 +537,4 @@ }

element._ignore = true;
signal(signal.info`${element.type.replace('cds.', '')} "${element.name}" excluded, target "${element._target.name}" is annotated '@cds.autoexpose: ${element._target['@cds.autoexpose']}'`,
signal(signal.info`${element.type.replace('cds.', '')} "${element.name}" excluded,
target "${element._target.name}" is annotated '@cds.autoexpose: ${element._target['@cds.autoexpose']}'`,
['definitions', struct.name, 'elements', element.name]);

@@ -518,3 +546,3 @@ return;

if(myServiceName !== whatsMyServiceName(element._target.name)) {
if(edmUtils.isBetaEnabled(options) && options.isStructFormat && options.isV4() && options.toOdata.odataProxies) {
if(isBetaEnabled(options, 'odataProxies') && options.isStructFormat && options.isV4() && options.toOdata.odataProxies) {
// search for eventually existing proxy

@@ -618,3 +646,3 @@ let proxy = element._target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0];

let elements = (csn.definitions[node.type] || node).elements;
let isNotInProtNS = node.type && (!node.type.startsWith('cds.') || node.type.startsWith('cds.foundation'));
let isNotInProtNS = node.type && !isBuiltinType(node.type);
// let isNotInService = node.type && myServiceName !== whatsMyServiceName(node.type);

@@ -685,2 +713,4 @@ // Always expose types referred to by a proxy, never reuse an eventually exisiting type

is an association/composition, flatten out the foreign keys as well.
* In Strucutred V4 do not render primary key 'parent' associations that
establish the containment (_isToContainer=tue).
*/

@@ -690,4 +720,5 @@ function initializeEdmKeyRefPaths(struct) {

// for all key elements that shouldn't be ignored produce the paths
foreach(struct.$keys, kn => !kn._ignore, (k, kn) => {
if(isEdmPropertyRendered(k, options)) {
foreach(struct.$keys, kn => !kn._ignore && !kn._isToContainer, (k, kn) => {
if(isEdmPropertyRendered(k, options) &&
!(options.isV2() && k['@Core.MediaType'])) {
if(options.isV4() && options.isStructFormat)

@@ -716,4 +747,5 @@ // This is structured OData ONLY

if(!isEdmPropertyRendered(eltCsn, options)) {
let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
signal(signal.warning`${struct.name}: OData V4 primary key path: "${prefix}" is unexposed by one of these annotations "${annos}"`, ['definitions', struct.name, 'elements', eltCsn.name ]);
// let annos = Object.keys(eltCsn).filter(a=>a[0]==='@').join(', ');
// signal(signal.warning`${struct.name}: OData V4 primary key path: "${prefix}" is unexposed by one of these annotations "${annos}"`,
// ['definitions', struct.name, 'elements', eltCsn.name ]);
return keyPaths;

@@ -723,4 +755,3 @@ }

// OData requires all elements along the path to be nullable: false (that is either key or notNull)
let elements = eltCsn.elements || getFinalTypeDef(eltCsn.type).elements;
let elements = eltCsn.elements || getFinalTypeDef(eltCsn.items && eltCsn.items.type || eltCsn.type).elements;
if(elements) {

@@ -746,3 +777,3 @@ for(let eltName in elements) {

art = art.elements[ps];
if(!(art.type.startsWith('cds.') || art.type.startsWith('cds.foundation.'))) {
if(!isBuiltinType(art.type)) {
art = getCsnDef(art.type);

@@ -816,2 +847,30 @@ }

}
// mark members that need to be rendered as collections
function markCollection(obj) {
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
if (items) {
assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
assignProp(obj, '_isCollection', true);
}
}
function mapCdsToEdmProp(obj) {
if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {
let edmType = edmUtils.mapCdsToEdmType(obj, signal, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
assignProp(obj, '_edmType', edmType);
} else if (obj._isCollection && (obj.items && isBuiltinType(getFinalTypeDef(obj.items.type)))) {
let edmType = edmUtils.mapCdsToEdmType(obj.items, signal, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
assignProp(obj, '_edmType', edmType);
}
// This is the special case when we have array of array, but will not be supported in the future
else if (obj._isCollection && obj.items && obj.items.type && obj.items.items && isBuiltinType(getFinalTypeDef(obj.items.items.type))) {
let edmType = edmUtils.mapCdsToEdmType(obj.items.items, signal, _options.toOdata.version === 'v2', obj['@Core.MediaType']);
assignProp(obj, '_edmType', edmType);
}
// check against the value of the @odata.Type annotation
if (obj['@odata.Type'] && !['Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.String'].includes(obj['@odata.Type']))
signal(signal.info`@odata.Type: '${obj['@odata.Type']}' is ignored, only 'Edm.String' and 'Edm.Int[16,32,64]' are allowed`, obj.$location);
}
}

@@ -1018,3 +1077,3 @@

if(requiresFilter)
assignAnnotation(struct._EntitySetAttributes, '@sap.requires-filter', requiresFilter);
assignAnnotation(struct._SetAttributes, '@sap.requires-filter', requiresFilter);
}

@@ -1037,2 +1096,115 @@

function setSAPSpecificV2AnnotationsToEntityContainer(options, carrier) {
if(!options.isV2())
return;
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntityContainer
const SetAttributes = {
// EntityContainer only
'@sap.supported.formats' : addToSetAttr,
'@sap.use.batch': addToSetAttr,
'@sap.message.scope.supported': addToSetAttr,
};
Object.keys(carrier).forEach(p => {
(SetAttributes[p] || function() {})(carrier, p, carrier[p]);
});
function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
assignProp(carrier, '_SetAttributes', Object.create(null));
assignAnnotation(carrier._SetAttributes, propName, propValue);
if(removeFromType) {
delete carrier[propName];
}
}
}
function setSAPSpecificV2AnnotationsToEntitySet(options, carrier) {
if(!options.isV2())
return;
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0#SAPAnnotationsforODataVersion2.0-Elementedm:EntitySet
const SetAttributes = {
// EntitySet, EntityType
'@sap.label' : (s,pn, pv) => { addToSetAttr(s, pn, pv, false); },
'@sap.semantics': checkSemantics,
// EntitySet only
'@sap.creatable' : addToSetAttr,
'@sap.updatable' : addToSetAttr,
'@sap.deletable': addToSetAttr,
'@sap.updatable.path': addToSetAttr,
'@sap.deletable.path': addToSetAttr,
'@sap.searchable' : addToSetAttr,
'@sap.pagable': addToSetAttr,
'@sap.topable': addToSetAttr,
'@sap.countable': addToSetAttr,
'@sap.addressable': addToSetAttr,
'@sap.requires.filter': addToSetAttr,
'@sap.change.tracking': addToSetAttr,
'@sap.maxpagesize': addToSetAttr,
'@sap.delta.link.validity': addToSetAttr,
};
Object.keys(carrier).forEach(p => {
(SetAttributes[p] || function() {})(carrier, p, carrier[p]);
});
function addToSetAttr(carrier, propName, propValue, removeFromType=true) {
assignProp(carrier, '_SetAttributes', Object.create(null));
assignAnnotation(carrier._SetAttributes, propName, propValue);
if(removeFromType) {
delete carrier[propName];
}
}
function checkSemantics(struct, propName, propValue) {
if(['timeseries', 'aggregate'].includes(propValue)) {
// aggregate is forwarded to Set and must remain on Type
addToSetAttr(struct, propName, propValue, propValue !== 'aggregate');
}
}
}
function setSAPSpecificV2AnnotationsToAssociation(options, carrier, struct) {
if(!options.isV2())
return;
// documented in https://wiki.scn.sap.com/wiki/display/EmTech/SAP+Annotations+for+OData+Version+2.0
const SetAttributes = {
// Applicable to NavProp and foreign keys, add to AssociationSet
'@sap.creatable' : (struct, c,pn, pv) => { addToSetAttr(struct, c, pn, pv, false); },
// Not applicable to NavProp, applicable to foreign keys, add to AssociationSet
'@sap.updatable' : addToSetAttr,
// Not applicable to NavProp, not applicable to foreign key, add to AssociatonSet
'@sap.deletable': (struct, c, pn, pv) => {
addToSetAttr(struct, c, pn, pv);
removeFromForeignKey(struct, c, pn);
},
// applicable to NavProp, not applicable to foreign keys, not applicable to AssociationSet
'@sap.creatable.path': removeFromForeignKey,
'@sap.filterable': removeFromForeignKey,
};
Object.keys(carrier).forEach(p => {
(SetAttributes[p] || function() {})(struct, carrier, p, carrier[p]);
});
function addToSetAttr(struct, carrier, propName, propValue, removeFromType=true) {
assignProp(carrier, '_SetAttributes', Object.create(null));
assignAnnotation(carrier._SetAttributes, propName, propValue);
if(removeFromType) {
delete carrier[propName];
}
}
function removeFromForeignKey(struct, carrier, propName) {
if(carrier.target && carrier.keys) {
for(const en in struct.elements) {
const e = struct.elements[en];
if(e['@odata.foreignKey4'] === carrier.name) {
delete e[propName];
}
}
}
}
}
// Assign but not overwrite annotation

@@ -1039,0 +1211,0 @@ function assignAnnotation(node, name, value) {

'use strict';
const { isBuiltinType } = require('../model/csnUtils');
/* eslint max-statements-per-line:off */
function validateOptions(_options)

@@ -42,16 +43,2 @@ {

/**
* Check if betaMode is enabled either by the presence of beta as bool
* or object, or by oldstyle betaMode=true, Optionally, check if beta mode
* has been enabled for a certain feature
*
* @param {any} options
* @param {string} feature string
* @returns {boolean} on/off
*/
function isBetaEnabled( options, feature=undefined ) {
const beta = options.beta || options.betaMode;
return beta && (typeof beta !== 'object' || (feature ? beta[feature] : true));
}
// returns intersection of two arrays

@@ -176,56 +163,50 @@ function intersect(a,b)

*/
result.selfs./*filter(p => p).*/forEach(partner => {
if(partner) {
let originAssocCsn = assocCsn._target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)
originAssocCsn = assocCsn.originalTarget.elements[partner];
let parentArtifactName = assocCsn._parent.name;
if(originAssocCsn) {
if(originAssocCsn._target != assocCsn._parent) {
isBacklink = false;
signal(signal.info`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" with target "${originAssocCsn._target.name}" in ON condition with $self`, ['definitions', parentArtifactName]);
}
if(isAssociationOrComposition(originAssocCsn)) {
// if the assoc is marked as primary key, add all its foreign keys as constraint
// as they are primary keys of the other entity as well
if(!assocCsn._target.$isParamEntity && originAssocCsn.key) {
if(originAssocCsn.keys && isFlatFormat) {
for(let fk of originAssocCsn.keys) {
const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
const key = c.join(',');
result.constraints[key] = c;
}
result.selfs.filter(p => p).forEach(partner => {
let originAssocCsn = assocCsn._target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)
originAssocCsn = assocCsn.originalTarget.elements[partner];
let parentArtifactName = assocCsn._parent.name;
if(originAssocCsn) {
if(originAssocCsn._target != assocCsn._parent) {
isBacklink = false;
signal(signal.info`"${originAssocCsn._parent.name}:${partner}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentArtifactName}"`, ['definitions', parentArtifactName, 'elements', assocCsn.name]);
}
if(isAssociationOrComposition(originAssocCsn)) {
// if the assoc is marked as primary key, add all its foreign keys as constraint
// as they are primary keys of the other entity as well
if(!assocCsn._target.$isParamEntity && originAssocCsn.key) {
if(originAssocCsn.keys && isFlatFormat) {
for(let fk of originAssocCsn.keys) {
const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
const key = c.join(',');
result.constraints[key] = c;
}
}
// Mark this association as backlink if $self appears exactly once
// to surpress edm:Association generation in V2 mode
if(isBacklink) {
// use first backlink as partner
if(originAssocCsn._partnerCsn.length == 0) {
result._originAssocCsn = originAssocCsn;
}
else {
isBacklink = false;
}
// collect all backlinks at forward association
originAssocCsn._partnerCsn.push(assocCsn);
}
// Mark this association as backlink if $self appears exactly once
// to surpress edm:Association generation in V2 mode
if(isBacklink) {
// use first backlink as partner
if(originAssocCsn._partnerCsn.length === 0) {
result._originAssocCsn = originAssocCsn;
}
else {
isBacklink = false;
}
// collect all backlinks at forward association
originAssocCsn._partnerCsn.push(assocCsn);
}
else {
/*
entity E {
key id : Integer;
toMe: associaton to E on toMe.id = $self; };
*/
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name);
}
}
else
{
signal(signal.warning`Cannot resolve backlink to ${assocCsn._target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]);
else {
/*
entity E {
key id : Integer;
toMe: associaton to E on toMe.id = $self; };
*/
throw Error('Backlink association element is not an association or composition: "' + originAssocCsn.name);
}
}
else {
signal(signal.warning`Could not resolve partner association from "$self" expression`, ['definitions', assocCsn._parent.name, 'elements', assocCsn.name]);
else
{
signal(signal.warning`Cannot resolve backlink to ${assocCsn._target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]);
}

@@ -249,5 +230,7 @@ });

c => {
let fk = dependentEntity.elements[c[0][0]];
let pk = principalEntity.$keys[c[1][0]];
return !(pk && fk && !(pk['@cds.api.ignore'] || fk['@cds.api.ignore']));
// concatenate all paths in flat mode to identify the correct element
// in structured mode only resolve top level element (path rewriting is done elsewhere)
let fk = dependentEntity.elements[ ( isFlatFormat ? c[0].join('_') : c[0][0] )];
let pk = principalEntity.$keys[ ( isFlatFormat ? c[1].join('_') : c[1][0] )];
return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));
},

@@ -265,13 +248,13 @@ (c, cn) => { delete result.constraints[cn]; } );

if(!assocCsn._target.$isParamEntity && assocCsn.keys) {
// In structured format, foreign keys of managed associations are never rendered, so
// there are no constraints for them.
if(isFlatFormat && !assocCsn._target.$isParamEntity && assocCsn.keys) {
for(let fk of assocCsn.keys) {
if(isFlatFormat) {
let realFk = assocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn._target.elements[fk.ref[0]];
if(pk && pk.key && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore']))
{
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
const key = c.join(',');
result.constraints[key] = c;
}
let realFk = assocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn._target.elements[fk.ref[0]];
if(pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk))
{
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
const key = c.join(',');
result.constraints[key] = c;
}

@@ -290,2 +273,20 @@ }

/*
* In Flat Mode an element is a constraint candidate if it
* is of scalar type
* In Structured mode, it eventually can be of a named type (which is
* by the construction standards for OData either a complex type or a
* type definition (alias to a scalar type).
* The element must never be an association or composition and
* must not be hidden with @cds.api.ignore
*/
function isConstraintCandidate(elt) {
let rc= (elt &&
elt.type &&
(!isFlatFormat || isFlatFormat && isBuiltinType(elt.type)) &&
!['cds.Association', 'cds.Composition'].includes(elt.type) &&
!elt['@cds.api.ignore']);
return rc;
}
// nested functions

@@ -381,3 +382,3 @@ function getExpressionArguments(expr)

1 not null => 1 (targetMin=1 is set by transform/toOdata.js)
1..1 => 1
1..1 => 1 // especially for unmanaged assocs :)
0..m => '*' // CDS default mapping for compositions

@@ -432,2 +433,5 @@ m => '*'

}
if(!isBuiltinType(cdsType))
return cdsType;
let edmType = {

@@ -507,3 +511,3 @@ // Edm.String, Edm.Binary

{
// const isV2 = node.v2;
const isV2 = node.v2;
if (csn.length != null)

@@ -522,6 +526,11 @@ node.MaxLength = csn.length;

node.Precision = 7;
// else if(csn.type === 'cds.DecimalFloat' && !isV2) {
// node.Scale = 'floating'; // only upcoming OData v4.01
// node.Precision = 34;
// }
else if([ 'cds.Decimal', 'cds.DecimalFloat' ].includes(csn.type) && !csn.precision && !csn.scale) {
if(isV2) {
node.setXml( { 'sap:variable-scale': true } );
}
else {
// if Decimal has no p, s set scale 'variable'
node.setXml( { Scale: 'variable' } ); // floating is V4.01
}
}
// Unicode unused today

@@ -536,3 +545,2 @@ if(csn.unicode)

validateOptions,
isBetaEnabled,
intersect,

@@ -539,0 +547,0 @@ foreach,

@@ -177,3 +177,3 @@ // Transform augmented CSN into compact "official" CSN

// Ignore empty actions section
if (!actions || Object.keys(actions).length == 0) {
if (!actions || Object.keys(actions).length === 0) {
return undefined;

@@ -314,3 +314,3 @@ }

function normalizeNode( node ) {
if (node == null || typeof node !== 'object') {
if (node === null || typeof node !== 'object') {
return node

@@ -317,0 +317,0 @@ }

@@ -94,3 +94,3 @@ // CSN frontend - transform CSN into XSN

'target', 'elements', 'enum', 'items',
'type', 'length', 'precision', 'scale', 'srid', 'localized',
'type', 'length', 'precision', 'scale', 'srid', 'localized', 'notNull',
'keys', 'on', // only with 'target'

@@ -190,3 +190,5 @@ ];

mixin: {
dictionaryOf: definition, defaultKind: 'element', validKinds: [], // XSN TODO: kind 'mixin' by parser
dictionaryOf: definition,
defaultKind: 'element',
validKinds: [], // XSN TODO: kind 'mixin' by parser
},

@@ -243,2 +245,7 @@ columns: {

},
targetAspect: {
type: artifactRef,
optional: [ 'elements' ], // 'elements' for ad-hoc aspect compositions
inKind: [ 'element', 'type' ],
},
target: {

@@ -253,3 +260,3 @@ type: artifactRef,

inKind: [ 'element', 'type', 'mixin' ],
onlyWith: [ 'target', 'id' ], // also in 'ref[]'
onlyWith: [ 'target', 'targetAspect', 'id' ], // also in 'ref[]'
},

@@ -350,3 +357,3 @@ items: {

type: value,
inKind: [ '$column', 'enum' ],
inKind: [ '$column', 'enum', 'element' ],
},

@@ -527,3 +534,3 @@ literal: {

type: object,
optional: typeProperties,
optional: [ ...typeProperties ],
inKind: [ 'action', 'function' ],

@@ -584,3 +591,6 @@ },

type: object,
optional: [ 'requires', 'definitions', 'extensions', 'namespace', 'version', 'messages', 'meta', 'options', '@' ],
optional: [
'requires', 'definitions', 'extensions',
'namespace', 'version', 'messages', 'meta', 'options', '@', '$location',
],
requires: false, // empty object OK

@@ -839,2 +849,7 @@ schema,

for (const name of allNames) {
if (!name) {
message( 'syntax-csn-empty-name', location(true), null,
{ prop: spec.prop }, 'Warning', // TODO: Error
'Property names in dictionary $(PROP) must not be empty' );
}
r[name] = definition( dict[name], spec, r, dict, name );

@@ -1007,3 +1022,7 @@ ++virtualLine;

virtualLine += 2;
return { symbol: { id: val['#'], location: location() }, literal: 'enum', location: location() };
return {
symbol: { id: val['#'], location: location() },
literal: 'enum',
location: location(),
};
}

@@ -1202,4 +1221,5 @@ }

if (Array.isArray( val ) && val.length > 1 && !csn.op) {
message( 'syntax-csn-expected-property', location(true), null, { prop: 'args', otherprop: 'op' },
'Warning', 'CSN property $(PROP) expects property $(OTHERPROP) to be specified' );
message( 'syntax-csn-expected-property', location(true), null,
{ prop: 'args', otherprop: 'op' }, 'Warning',
'CSN property $(PROP) expects property $(OTHERPROP) to be specified' );
xsn.op = { val: 'union', location: location() };

@@ -1323,2 +1343,3 @@ }

columns: 'Object in $(OTHERPROP) must have an expression property like $(PROP)',
// eslint-disable-next-line max-len
extensions: 'Object in $(OTHERPROP) must have the property \'annotate\' or \'extend\'',

@@ -1406,23 +1427,26 @@ } );

if (loc && typeof loc === 'object' && !Array.isArray( loc )) {
dollarLocations.push( normalizeLocation( loc ) );
dollarLocations.push( loc.line ? normalizeLocation( loc ) : null );
return;
}
else if (typeof loc === 'string') { // hidden feature: string $location
const m = /:([0-9]+)(:([0-9]+)(-[0-9---]+)?)?$/.exec( loc ); // extra - at end for .refloc
if (m) {
const line = Number( m[1] );
const column = m[3] && Number( m[3] ) || 0;
dollarLocations.push( {
filename: loc.substring( 0, m.index ),
start: { line, column },
end: { line, column },
$weak: true,
} );
return;
}
else if (!loc || typeof loc !== 'string') {
if (loc)
dollarLocations.push( null ); // must match with popLocation()
message( 'syntax-csn-expected-object', location(true), null, { prop: '$location' },
'Error', 'Expected object for property $(PROP)' );
}
if (loc)
dollarLocations.push( null ); // must match with popLocation()
message( 'syntax-csn-expected-object', location(true), null, { prop: '$location' },
'Error', 'Expected object for property $(PROP)' );
// hidden feature: string $location
const m = /:([0-9]+)(:([0-9]+)(-[0-9---]+)?)?$/.exec( loc ); // extra - at end for .refloc
if (!m) {
dollarLocations.push( null );
}
else {
const line = Number( m[1] );
const column = m[3] && Number( m[3] ) || 0;
dollarLocations.push( {
filename: loc.substring( 0, m.index ),
start: { line, column },
end: { line, column },
$weak: true,
} );
}
}

@@ -1441,4 +1465,4 @@

* @param {CSN.Model} csn
* @param {string} [filename]
* @param {object} [options]
* @param {string} filename
* @param {CSN.Options} [options]
* @returns {object} Augmented CSN (a.k.a XSN)

@@ -1454,3 +1478,2 @@ */

message = getMessageFunction( xsn, options );
// TODO: pass transformers to pass schema spec instead prop
if (csnVersionZero) {

@@ -1457,0 +1480,0 @@ message( 'syntax-csn-zero-version', location(true), null, {},

@@ -48,2 +48,3 @@ // Transform augmented CSN into compact "official" CSN

cardinality: standard, // also for pathItem: after 'id', before 'where'
targetAspect,
target,

@@ -67,3 +68,4 @@ foreignKeys: renameTo( 'keys', dictAsArray ), // XSN: rename?

on: cond => (typeof cond === 'string' ? undefined : condition( cond )), // also for join
onCond: renameTo( 'on', c => ((gensrcFlavor && c.$inferred) ? undefined : condition(c)) ), // XSN TODO: onCond -> on
onCond: renameTo( 'on', c => ((gensrcFlavor && c.$inferred) ? undefined : condition(c)) ),
// XSN TODO: onCond -> on
// definitions, extensions, members ----------------------------------------

@@ -126,3 +128,7 @@ returns: standard, // storing the return type of actions

op: [ 'join', 'func', 'xpr' ], // TODO: 'func','xpr' into 'quantifier'? TODO: 'global'(scope)?
quantifier: [ 'some', 'any', 'distinct', 'ref', '_links', '_art', '_scope', 'param', 'val', 'literal', 'SELECT', 'SET' ], // 'all' explicitly listed
quantifier: [
'some', 'any', 'distinct', // 'all' explicitly listed
'ref', '_links', '_art', '_scope',
'param', 'val', 'literal', 'SELECT', 'SET',
],
type: [ '_type' ],

@@ -176,3 +182,5 @@ target: [ '_target' ],

const csnDictionaries = [ 'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'payload', 'definitions' ];
const csnDictionaries = [
'args', 'params', 'enum', 'mixin', 'elements', 'actions', 'payload', 'definitions',
];
const csnDirectValues = [ 'val', 'messages' ]; // + all starting with '@'

@@ -245,2 +253,6 @@

set( 'messages', csn, model );
const [ src ] = Object.keys( model.sources );
const file = src && model.sources[src].filename;
if (file)
setHidden( csn, '$location', { file } ); // no line
if (!options.testMode) {

@@ -367,6 +379,14 @@ csn.meta = Object.assign( {}, model.meta, { creator } );

function targetAspect( val, csn, node ) {
if (!gensrcFlavor || node.target && !node.target.$inferred)
return (val.elements) ? standard( val ) : artifactRef( val, true );
// For compatibilty, put aspect in 'target' with parse.cdl and csn flavor 'gensrc'
csn.target = (val.elements) ? standard( val ) : artifactRef( val, true );
return undefined;
}
function target( val ) {
if (!gensrcFlavor && val._artifact)
// target._artifact is different to _artifact from path with explicit target
// to model entity with @cds.autoexpose, also different for aspect compositions
// to model entity with @cds.autoexpose (TODO: remove, unncessary complication)
return val._artifact.name.absolute;

@@ -578,3 +598,8 @@ else if (!val.elements)

function pathItem( item ) {
if (!item.args && !item.namedArgs && !item.where && !item.cardinality && !item.$extra)
if (!item.args &&
!item.namedArgs &&
!item.where &&
!item.cardinality &&
!item.$extra &&
!item.$syntax)
return item.id;

@@ -626,4 +651,7 @@ return standard( item );

const magicFunctions // TODO: calculate from compiler/builtins.js (more with HANA?):
= [ 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER' ];
// TODO: calculate from compiler/builtins.js (more with HANA?):
const magicFunctions = [
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
'CURRENT_USER', 'SESSION_USER', 'SYSTEM_USER',
];
// TODO: quoted magic names like $now should be complained about in the compiler

@@ -634,3 +662,3 @@

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

@@ -1111,4 +1139,6 @@ ? nav.name.alias

if (idx.textMining.overlay)
stream.push('text', 'mining', 'configuration', 'overlay', expression(idx.textMining.overlay));
if (idx.textMining.overlay) {
stream.push('text', 'mining', 'configuration', 'overlay',
expression(idx.textMining.overlay));
}
}

@@ -1226,3 +1256,8 @@ if (idx.changeTracking) {

module.exports = {
compactModel, compactQuery, compactExpr, sortCsn,
compactModel,
compactQuery,
compactExpr,
sortCsn,
csnDictionaries,
csnDirectValues,
};

@@ -9,3 +9,3 @@ // Wrapper around generated ANTLR parser

var { getMessageFunction, CompileMessage } = require('../base/messages');
var { getMessageFunction, CompileMessage, DebugCompileMessage } = require('../base/messages');
var errorStrategy = require('./errorStrategy');

@@ -24,3 +24,3 @@

syntaxError( recognizer, offendingSymbol, line, column, msg, e ) {
if (!(e instanceof CompileMessage)) // not already reported
if (!(e instanceof CompileMessage || e instanceof DebugCompileMessage)) // not already reported
recognizer.message( null, offendingSymbol, msg );

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

'use strict';
const { splitLines } = require('../utils/file');
/**

@@ -10,8 +12,6 @@ * Get the content of a JSDoc-like comment and remove all surrounding asterisks, etc.

* Must be a valid doc comment.
* @param {boolean} [normalizeLineBreak=false] Whether to normalize line breaks, i.e. replace
* `\r\n` with `\n`. Useful for test mode.
* @returns {string|null} Parsed contents or if the comment has an invalid format or
* does not have any content, null is returned.
*/
function parseDocComment(comment, normalizeLineBreak = false) {
function parseDocComment(comment) {
// Also return "null" for empty doc comments so that doc comment propagation

@@ -22,7 +22,4 @@ // can be stopped.

if (normalizeLineBreak)
comment = comment.replace(/\r\n/g, '\n');
let lines = splitLines(comment);
let lines = comment.split('\n');
if (lines.length === 1) {

@@ -29,0 +26,0 @@ // special case for one-liners

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

identAst,
functionAst,
numberLiteral,

@@ -52,2 +53,3 @@ quotedLiteral,

setMaxCardinality,
makeAnnotationIdentifier,
handleComposition,

@@ -263,4 +265,3 @@ hanaFlavorOnly,

}
const normalizeLineBreak = !!this.options.testMode;
node.doc = this.tokenLocation( token, token, parseDocComment( token.text, normalizeLineBreak ) );
node.doc = this.tokenLocation( token, token, parseDocComment( token.text ) );
}

@@ -285,3 +286,3 @@

if (token.text[0] === '!') {
id = id.slice( 2, -1 );
id = id.slice( 2, -1 ).replace( /]]/g, ']' );
if (!id) {

@@ -309,2 +310,16 @@ this.message( 'syntax-empty-ident', token, {},

function functionAst( token, xprToken ) {
// TODO: XSN func cleanup
const location = this.tokenLocation( token );
const args = xprToken
? [ { op: { location, val: 'xpr' }, args: [], location: this.tokenLocation( xprToken ) } ]
: [];
return {
op: { location, val: 'call' },
func: { path: [ { id: token.text, location, args } ], location },
args,
location,
};
}
// Return AST for number token `token` with optional token `sign`. Represent

@@ -379,2 +394,20 @@ // the number as number in property `val` if the number can safely be

function makeAnnotationIdentifier( at, identifier ) {
if (!identifier)
return identifier;
const atLoc = { location: this.startLocation( at ) };
if (identifier.id)
identifier.id = '@' + identifier.id;
// `.stop` does not point to *after* the character as opposed to XSN locations.
if (at.stop + 1 !== identifier.location.start.offset) {
this.message("syntax-anno-space", identifier.location, {}, 'Error',
'Expected identifier after \'@\' but found whitespace')
}
identifier.location = this.combinedLocation( atLoc, identifier );
return identifier;
}
// Add new definition to dictionary property `env` of node `parent` and return

@@ -408,3 +441,3 @@ // that definition. Also attach the following properties to the new definition:

// no id was parsed, but with error recovery: no further error
env = env + '_'; // could be tested in name search - TODO: fails with --test-mode
env = env + '_'; // could be tested in name search
if (!parent[env])

@@ -411,0 +444,0 @@ parent[env] = [art];

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

const { emptyWeakLocation } = require('./base/location');
const { getArtifactDatabaseNameOf, getElementDatabaseNameOf } = require('./model/csnUtils');

@@ -26,4 +27,13 @@ // The compiler version (taken from package.json)

const { CompilationError, messageString, messageStringMultiline, messageContext, handleMessages, hasErrors, getMessageFunction }
= require('./base/messages');
const {
CompilationError,
messageString,
messageStringMultiline,
messageContext,
handleMessages,
hasErrors,
getMessageFunction,
explainMessage,
hasMessageExplanation
} = require('./base/messages');
const { promiseAllDoNotRejectImmediately } = require('./base/node-helpers');

@@ -543,3 +553,3 @@

if (sources[name] == null) {
if (!sources[name]) {
sources[name] = path.relative( dir, name );

@@ -603,2 +613,4 @@ files.push(name);

messageContext,
explainMessage,
hasMessageExplanation,
InvocationError,

@@ -612,4 +624,4 @@ hasErrors,

// toOdataWithCsn: backends.toOdataWithCsn,
preparedCsnToEdmx : backends.preparedCsnToEdmx,
preparedCsnToEdm : backends.preparedCsnToEdm,
preparedCsnToEdmx : (csn, service, options) => { return backends.preparedCsnToEdmx(csn, service, options).edmx},
preparedCsnToEdm : (csn, service, options) => { return backends.preparedCsnToEdm(csn, service, options).edmj},
toCdl : backends.toCdl,

@@ -624,4 +636,8 @@ toSwagger : backends.toSwagger,

parseToExpr,
// SNAPI
for: { odata },
to: { cdl, sql, hdi, hdbcds, edm, edmx }
to: { cdl, sql, hdi, hdbcds, edm, edmx },
// Convenience for hdbtabledata calculation in @sap/cds
getArtifactCdsPersistenceName: getArtifactDatabaseNameOf,
getElementCdsPersistenceName: getElementDatabaseNameOf
};

@@ -29,4 +29,4 @@ // CSN functionality for resolving references

// Properties in which artifact or members are defined - next property in the
// "csnPath" is the name or index of that property, also includes 'args'
// because the value can be a dictionary
// "csnPath" is the name or index of that property; 'args' (its value can be a
// dictionary) is handled extra here.
const artifactProperties

@@ -48,7 +48,8 @@ = ['elements', 'columns', 'keys', 'mixin', 'enum', 'params', 'actions', 'payload', 'definitions', 'extensions'];

return art._effectiveType;
else if (!art.type || art.elements || art.target || art.enum)
else if (!art.type || art.elements || art.target || art.targetAspect || art.enum)
return art;
const chain = [];
while (art._effectiveType == null && art.type && !art.elements && !art.target && !art.enum) {
while (art._effectiveType == null && art.type &&
!art.elements && !art.target && !art.targetAspect && !art.enum) {
chain.push( art );

@@ -66,3 +67,9 @@ setLink( art, '_effectiveType', 0 ); // initial setting in case of cycles

function navigationEnv( art ) {
const type = effectiveType( art );
let type = effectiveType( art );
// here, we do not care whether it is semantically ok to navigate into sub
// elements of array items (that is the task of the core compiler /
// semantic check)
while (type.items)
type = type.items;
// cannot navigate along targetAspect!
return (type.target) ? csn.definitions[ type.target ] : type;

@@ -69,0 +76,0 @@ }

@@ -10,2 +10,26 @@ 'use strict'

/**
* Generic Callback
*
* @callback genericCallback
* @param {CSN.Member} art
* @param {CSN.FQN} name Artifact Name
* @param {string} prop Dictionary Property
* @param {CSN.Path} path Location
* @param {CSN.Artifact} [dictionary]
*/
/**
* @callback refCallback
* @param {any} ref
* @param {object} node
* @param {CSN.Path} path
*/
/**
* @callback queryCallback
* @param {CSN.Query} query
* @param {CSN.Path} path
*/
/**
* Get utility functions for a given CSN.

@@ -15,3 +39,3 @@ * @param {CSN.Model} model (Compact) CSN model

function getUtils(model) {
const { artifactRef, inspectRef } = csnRefs(model);
const { artifactRef, inspectRef, effectiveType } = csnRefs(model);

@@ -23,3 +47,2 @@ return {

getFinalTypeDef,
isBuiltinType,
isManagedAssociationElement,

@@ -37,3 +60,4 @@ isAssocOrComposition,

inspectRef,
artifactRef
artifactRef,
effectiveType,
};

@@ -123,11 +147,2 @@

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

@@ -194,27 +209,2 @@ // TODO: what about elements having a type, which (finally) is an assoc?

// Return the resulting database name for (absolute) 'artifactName', depending on the current naming
// convention.
// - For the 'hdbcds' naming convention, this means converting '.' to '::' on
// the border between namespace and top-level artifact.
// - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
// - For the 'quoted' naming convention, this is just 'artifactName'.
// No other naming conventions are accepted
function getArtifactDatabaseNameOf(artifactName, namingConvention, namespace = undefined) {
if (namingConvention === 'hdbcds') {
if (namespace) {
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
}
return artifactName;
}
else if (namingConvention === 'plain') {
return artifactName.replace(/\./g, '_').toUpperCase();
}
else if (namingConvention === 'quoted') {
return artifactName;
}
else {
throw new Error('Unknown naming convention: ' + namingConvention);
}
}
/**

@@ -299,3 +289,3 @@ * Return the namespace part of the artifact name.

// (Note that 'node instanceof Object' would be false for dictionaries).
if (node == null || typeof node !== 'object') {
if (node === null || typeof node !== 'object') {
return node

@@ -412,2 +402,12 @@ }

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

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

* @param {CSN.Model} csn
* @param {(art: CSN.Artifact, name: string, prop: string, path: string[]) => any} callback
* @param {(genericCallback|genericCallback[])} callback
*/

@@ -449,3 +449,3 @@ function forEachDefinition( csn, callback ) {

* @param {CSN.Artifact} construct
* @param {(art: CSN.Member, name: string, prop: string, path: string[]) => any} callback
* @param {genericCallback|genericCallback[]} callback
* @param {string[]} [path]

@@ -485,3 +485,3 @@ * @param {boolean} [ignoreIgnore]

* @param {CSN.Artifact} construct
* @param {(art: CSN.Member, name: string, prop: string, path: string[], origConstruct: CSN.Artifact) => any} callback
* @param {genericCallback|genericCallback[]} callback
* @param {CSN.Path} [path]

@@ -492,3 +492,6 @@ * @param {boolean} [ignoreIgnore]

forEachMember( construct, ( member, memberName, prop, subpath ) => {
callback( member, memberName, prop, subpath, construct );
if(Array.isArray(callback))
callback.forEach(cb => cb( member, memberName, prop, subpath, construct ));
else
callback( member, memberName, prop, subpath, construct );
// Descend into nested members, too

@@ -507,3 +510,3 @@ forEachMemberRecursively( member, callback, subpath, ignoreIgnore);

* @param {string} prop
* @param {(dict: object, name: string, prop: string, path: CSN.Path) => any} callback
* @param {genericCallback|genericCallback[]} callback
* @param {CSN.Path} path

@@ -516,6 +519,12 @@ */

if (dictObj instanceof Array) // redefinitions - not in CSN!
dictObj.forEach( (o) => callback( o, name, prop, path.concat([prop, name])) )
dictObj.forEach( o => cb( o, name ) );
else
callback( dictObj, name, prop, path.concat([prop, name]) );
cb( dictObj, name );
}
function cb(o, name ) {
if(callback instanceof Array)
callback.forEach(cb => cb( o, name, prop, path.concat([prop, name])));
else
callback( o, name, prop, path.concat([prop, name]))
}
}

@@ -527,7 +536,7 @@

* @param {object} node
* @param {(ref: any, node: object, path: CSN.Path) => any} callback
* @param {refCallback|refCallback[]} callback
* @param {CSN.Path} path
*/
function forEachRef(node, callback, path = []) {
if (node == null || typeof node !== 'object') {
if (node === null || typeof node !== 'object') {
// Primitive node

@@ -550,3 +559,6 @@ return;

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

@@ -580,3 +592,3 @@ // Descend recursively

* @param {CSN.Query} query
* @param {(query: CSN.Query, path: CSN.Path) => any} callback
* @param {queryCallback|queryCallback[]} callback
* @param {CSN.Path} path

@@ -586,12 +598,12 @@ */

return traverseQuery(query, callback, path);
function traverseQuery( q, cb, p ) {
function traverseQuery( q, callback, p ) {
if (q.SELECT) {
p.push('SELECT');
cb( q, p);
cb( q, p );
q = q.SELECT;
traverseFrom( q.from, cb, p.concat(['from']) );
traverseFrom( q.from, callback, p.concat(['from']) );
}
else if (q.SET) {
p.push('SET');
cb( q, p);
cb( q, p );
q = q.SET;

@@ -605,7 +617,7 @@ }

for(let i = 0; i < expr.length; i++){
traverseQuery(expr[i], cb, p.concat([prop, i]));
traverseQuery(expr[i], callback, p.concat([prop, i]));
}
} else {
for(const argName of Object.keys( expr )){
traverseQuery(expr[argName], cb, p.concat([prop, argName]))
traverseQuery(expr[argName], callback, p.concat([prop, argName]))
}

@@ -615,2 +627,8 @@ }

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

@@ -649,4 +667,5 @@

/**
* EDM specific check: Render ordinary property if element is NOT ...
* EDM specific check: Render (navigation) property if element is NOT ...
* 1) ... annotated @cds.api.ignore
* 2) ... annotated @odata.navigable: false
* 2) ... annotated @odata.foreignKey4 and odataFormat: structured

@@ -656,14 +675,80 @@ * function accepts EDM internal and external options

* @param {CSN.Element} elementCsn
* @param {CSN.Options} options
* @param {CSN.Options & { isStructFormat: boolean}} options EDM specific options
*/
function isEdmPropertyRendered(elementCsn, options) {
let isStructuredFormat = options.toOdata && options.toOdata.odataFormat === 'structured' || options.isStructFormat;
return(!elementCsn['@cds.api.ignore']) &&
!(elementCsn['@odata.foreignKey4'] && isStructuredFormat)
const isNotIgnored = !elementCsn.target ? !elementCsn['@cds.api.ignore'] : true;
const isNavigable = elementCsn.target ?
(elementCsn['@odata.navigable'] === undefined ||
elementCsn['@odata.navigable'] !== undefined && (elementCsn['@odata.navigable'] === null || elementCsn['@odata.navigable'] === true)) : true;
return(isNotIgnored && isNavigable &&
!(elementCsn['@odata.foreignKey4'] && isStructuredFormat))
}
/**
* Return the resulting database name for (absolute) 'artifactName', depending on the current naming
* convention.
*
* - For the 'hdbcds' naming convention, this means converting '.' to '::' on
* the border between namespace and top-level artifact.
* - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
* - For the 'quoted' naming convention, this is just 'artifactName'.
*
* @param {string} artifactName The name of the artifact
* @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
* @param {string} [namespace] The namespace of the artifact
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming convention.
*/
function getArtifactDatabaseNameOf(artifactName, namingConvention, namespace = undefined) {
if (namingConvention === 'hdbcds') {
if (namespace) {
return `${namespace}::${artifactName.substring(namespace.length + 1)}`;
}
return artifactName;
}
else if (namingConvention === 'plain') {
return artifactName.replace(/\./g, '_').toUpperCase();
}
else if (namingConvention === 'quoted') {
return artifactName;
}
else {
throw new Error('Unknown naming convention: ' + namingConvention);
}
}
/**
* Return the resulting database element name for 'elemName', depending on the current naming
* convention.
* - For the 'hdbcds' naming convention, this is just 'elemName'.
* - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
* - For the 'quoted' naming convention, it means converting all '.' to '_'.
* No other naming conventions are accepted
*
* @param {string} elemName Name of the elemnt
* @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
* @returns {string} The resulting database element name for 'elemName', depending on the current naming convention.
*/
function getElementDatabaseNameOf(elemName, namingConvention) {
if (namingConvention === 'hdbcds') {
return elemName;
}
else if (namingConvention === 'plain') {
return elemName.replace(/\./g, '_').toUpperCase();
}
else if (namingConvention === 'quoted') {
return elemName.replace(/\./g, '_');
}
else {
throw new Error('Unknown naming convention: ' + namingConvention);
}
}
module.exports = {
getUtils,
cloneCsn,
isBuiltinType,
assignAll,

@@ -677,3 +762,5 @@ forEachGeneric,

hasBoolAnnotation,
isEdmPropertyRendered
isEdmPropertyRendered,
getArtifactDatabaseNameOf,
getElementDatabaseNameOf,
};

@@ -41,5 +41,7 @@ // For testing: reveal non-enumerable properties in CSN, display result of csnRefs

enum: dictionary,
mixin: dictionary,
payload: dictionary,
ref: pathRef,
type: simpleRef,
targetAspect: simpleRef,
target: simpleRef,

@@ -53,2 +55,4 @@ includes: simpleRef,

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

@@ -87,14 +91,22 @@

function simpleRef( node, prop ) {
function simpleRef( node, prop, ref ) {
// try {
const notFound = (options.testMode) ? undefined : null;
const ref = node[prop];
if (Array.isArray( ref )) {
node['_' + prop] = ref.map( r => refLocation( artifactRef( r, notFound ) ) );
}
else if (typeof ref !== 'string' || !ref.startsWith( 'cds.')) {
else if (typeof ref === 'string') {
if (!ref.startsWith( 'cds.'))
node['_' + prop] = refLocation( artifactRef( ref, notFound ) );
}
else if (!ref.elements) {
node['_' + prop] = refLocation( artifactRef( ref, notFound ) );
}
// catch (e) {
// node['_' + prop] = e.toString();
// }
else { // targetAspect, target
csnPath.push( prop );
dictionary( ref, 'elements', ref.elements );
csnPath.pop();
}
// } catch (e) {
// node['_' + prop] = e.toString(); }
}

@@ -101,0 +113,0 @@

@@ -245,2 +245,22 @@ 'use strict'

// Add an annotation with absolute name 'absoluteName' (including '@') and array 'theValue' to 'node'
function addArrayAnnotationTo(absoluteName, theValue, node) {
// Sanity check
if (!absoluteName.startsWith('@')) {
throw Error('Annotation name should start with "@": ' + absoluteName);
}
// Assemble the annotation
if(isAnnotationAssignable(node, absoluteName)) {
node[absoluteName] = {
name: {
absolute: absoluteName.substring(1),
location: node.location, // inherit location from main element
},
val: theValue,
literal: 'array',
location: node.location, // inherit location from main element
};
}
}
// Rename annotation 'fromName' in 'node' to 'toName' (both names including '@')

@@ -295,3 +315,3 @@ function renameAnnotation(node, fromName, toName) {

function foreachPath(node, callback) {
if (node == null || typeof node !== 'object') {
if (node === null || typeof node !== 'object') {
// Primitive node

@@ -310,7 +330,11 @@ return;

// Merge multiple 'options' objects (from right to left, i.e. rightmost wins). Structured option values are
// merged deeply. Structured option value from the right may override corresponding bool options on the left,
// but no other combination of struct/scalar values is allowed. Array options are not merged, i.e. their
// content is treated like scalars.
// Returns a new options object.
/**
* Merge multiple 'options' objects (from right to left, i.e. rightmost wins). Structured option values are
* merged deeply. Structured option value from the right may override corresponding bool options on the left,
* but no other combination of struct/scalar values is allowed. Array options are not merged, i.e. their
* content is treated like scalars.
* Returns a new options object.
*
* @param {...CSN.Options} optionsObjects
*/
function mergeOptions(...optionsObjects) {

@@ -519,2 +543,3 @@ let result = {};

addRefAnnotationTo,
addArrayAnnotationTo,
renameAnnotation,

@@ -521,0 +546,0 @@ copyAnnotations,

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

$compositionTargets: d => d, // dictionary( boolean )
_upperAspects: artifactIdentifier, // array
_ancestors: artifactIdentifier, // array

@@ -215,3 +216,3 @@ _descendants: artifactDictionary, // dict of array

function primOrString( node ) {
if (node == null || typeof node !== 'object')
if (node === null || typeof node !== 'object')
// node instanceof Object would be false for dict

@@ -218,0 +219,0 @@ return node

@@ -28,7 +28,10 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper');

.option(' --old-transformers')
.option(' --dependent-autoexposed')
.option(' --long-autoexposed')
.option(' --hana-flavor')
.option(' --direct-backend')
.option(' --parse-only')
.option(' --test-mode')
.option(' --doc-comment')
.option(' --localized-without-coalesce')
.option('--length <length>')

@@ -63,2 +66,7 @@ .positionalArgument('<files...>')

-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
--dependent-autoexposed If the root entity of dependent autoexposed entities
(for managed compositions, texts entity), is explicitly exposed
with a certain name in a service, use that name as prefix
for the name of the dependent autoexposed entities
(recommended option, but incompatible to switch it on by default).
--lint-mode Generate nothing, just produce messages if any (for use by editors)

@@ -82,9 +90,20 @@ --fuzzy-csn-error Report free-style CSN properties as errors

--beta-mode Enable all unsupported, incomplete (beta) features
--beta <list> Comma separated list of unsupported, incomplete (beta) features to use
--beta <list> Comma separated list of unsupported, incomplete (beta) features to use.
Valid values are:
subElemRedirections
keyRefError
odataProxies
uniqueconstraints
hanaAssocRealCardinality
--old-transformers Use the old transformers that work on XSN instead of CSN
--hana-flavor Compile with backward compatibility for HANA CDS (incomplete)
--parse-only Stop compilation after parsing and write result to <stdout>
--direct-backend Do not compile the given CSN but directly pass it to the backend.
Can only be used with certain new CSN based backends. Combination with
other flags is limited, e.g. --test-mode will not run a consistency check.
No recompilation is triggered in case of errors. cdsc will dump.
--test-mode Produce extra-stable output for automated tests (normalize filenames
in errors, sort properties in CSN, omit version in CSN)
--doc-comment Preserve /** */ comments at annotation positions as doc property in CSN
--localized-without-coalesce Omit coalesce in localized convenience views

@@ -101,3 +120,4 @@ Backward compatibility options (deprecated, do not use)

toCsn [options] <files...> (default) Generate original model as CSN
parseCdl Generate a CSN that is close to the CDL source.
parseCdl [options] <file> Generate a CSN that is close to the CDL source.
explain <message-id> Explain a compiler message.
toRename [options] <files...> (internal) Generate SQL DDL rename statements

@@ -142,3 +162,3 @@ `);

.option('-h, --help')
.option('-v, --version <version>', ['v2', 'v4'])
.option('-v, --version <version>', ['v2', 'v4', 'v4x'])
.option('-x, --xml')

@@ -163,2 +183,3 @@ .option('-j, --json')

v4: (default) ODATA V4
v4x: { version: 'v4', odataContainment:true, format:'structured' }
-x, --xml (default) Generate XML output (separate or combined)

@@ -219,3 +240,2 @@ -j, --json Generate JSON output as "<svc>.json" (not available for v2)

.option('-c, --csn')
.option('--compatibility')
.help(`

@@ -257,3 +277,2 @@ Usage: cdsc toSql [options] <files...>

-c, --csn Generate "sql_csn.json" with SQL-preprocessed model
--compatibility Try and create ON-conditions just like HDBCDS
`);

@@ -312,4 +331,16 @@

optionProcessor.command('explain')
.option('-h, --help')
.help(`
Usage: cdsc explain [options] <message-id>
Explain the compiler message that has the given message-id.
The explanation contains a faulty example and a solution.
Options
-h, --help Show this help text
`);
module.exports = {
optionProcessor
};

@@ -106,2 +106,2 @@ /**

module.exports = DuplicateChecker;
module.exports = DuplicateChecker;

@@ -16,8 +16,8 @@ // Common render functions for toCdl.js and toSql.js

// Dialect = 'hana' (only relevance at the moment) | 'cap' | 'sqlite'
function renderFunc( node, dialect, renderArgs, parenNameToUpper = false ) {
function renderFunc( funcName, node, dialect, renderArgs, parenNameToUpper = false ) {
if (funcWithoutParen( node, dialect ))
return node.func;
return funcName;
else
// unclear why we transform the function name to uppercase in SQL, but only with parentheses
return `${parenNameToUpper ? node.func.toUpperCase() : node.func}(${renderArgs( node.args )})`;
return `${parenNameToUpper ? funcName.toUpperCase() : funcName}(${renderArgs( node.args )})`;
}

@@ -24,0 +24,0 @@

@@ -5,2 +5,3 @@

const { getTopLevelArtifactNameOf, getParentNameOf, getParentNamesOf, getLastPartOf, getLastPartOfRef } = require('../model/modelUtils');
const { hasBoolAnnotation, isBuiltinType } = require('../model/csnUtils');
const keywords = require('../base/keywords');

@@ -14,2 +15,3 @@ const alerts = require('../base/alerts');

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

@@ -85,3 +87,2 @@ // Type mapping from cds type names to DB type names:

const { error, signal, warning, info } = alerts(csn, options);
const compatMode = options.toSql && options.toSql.compatibility && options.toSql.dialect === 'hana' && options.toSql.src === 'hdi';

@@ -117,5 +118,8 @@ // FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect

hdbview: Object.create(null),
alterTable: Object.create(null)
}
let duplicateChecker = new DuplicateChecker(); // registry for all artifact names and element names
// Registries for artifact and element names per CSN section
let definitionsDuplicateChecker = new DuplicateChecker();
let extensionsDuplicateChecker = new DuplicateChecker();

@@ -133,4 +137,20 @@ // Render each artifact on its own

duplicateChecker.check(signal, error); // trigger artifact and element name checks
// Render each artifact extension
for (let artifactIndex in csn.extensions) {
if ('extend' in csn.extensions[artifactIndex]) {
const artifactName = csn.extensions[artifactIndex]['extend']
// This environment is passed down the call hierarchy, for dealing with
// indentation issues
let env = {
// Current indentation string
indent: '',
}
renderArtifactExtensionInto(artifactName, csn.extensions[artifactIndex], resultObj, env);
}
}
// trigger artifact and element name checks
definitionsDuplicateChecker.check(signal, error);
extensionsDuplicateChecker.check(signal, error);
// Throw exception in case of errors

@@ -144,3 +164,6 @@ handleMessages(csn, options);

let sqlVersionLine = `-- generated by cds-compiler version ${version}\n`;
for (let hdbKind of Object.keys(resultObj)) {
// Handle hdbKinds separately from alterTable case
const { alterTable, ...hdbKinds } = resultObj;
for (let hdbKind of Object.keys(hdbKinds)) {
for (let name in resultObj[hdbKind]) {

@@ -168,2 +191,5 @@ if (options.toSql.src === 'sql') {

}
for (let name in alterTable) {
alterTable[name][0] = `${options.testMode ? '' : sqlVersionLine}` + alterTable[name][0];
}

@@ -176,3 +202,3 @@ timetrace.stop();

// Ignore whole artifacts if forHana says so
if (art._ignore) {
if (art._ignore || hasBoolAnnotation(art, '@cds.persistence.exists', true)) {
return;

@@ -213,2 +239,12 @@ }

// Render an artifact extension into the appropriate dictionary of 'resultObj'.
function renderArtifactExtensionInto(artifactName, art, 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 && !art.query) {
renderExtendInto(artifactName, art, resultObj, env)
}
if (!artifactName) throw new Error('Undefined artifact name: ' + artifactName);
}
// Render a (non-projection, non-view) entity (and possibly its indices) into the appropriate

@@ -218,3 +254,2 @@ // dictionaries of 'resultObj'.

let childEnv = increaseIndent(env);
childEnv.isEntity = true;
let hanaTc = art.technicalConfig && art.technicalConfig.hana;

@@ -233,8 +268,8 @@ let result = '';

let tableName = quoteSqlId(absoluteCdsName(artifactName));
duplicateChecker.addArtifact(tableName, art && art.$location, artifactName)
definitionsDuplicateChecker.addArtifact(tableName, art && art.$location, artifactName)
result += 'TABLE ' + tableName;
result += ' (\n';
let elements = Object.keys(art.elements).map(name => renderElement(artifactName, art, name, art.elements[name], getFzIndex(name, hanaTc), childEnv))
.filter(s => s !== '')
.join(',\n');
let elements = Object.keys(art.elements).map(eltName => {
return renderElement(artifactName, eltName, art.elements[eltName], definitionsDuplicateChecker, getFzIndex(eltName, hanaTc), childEnv);
}).filter(s => s !== '').join(',\n');
if (elements !== '') {

@@ -252,11 +287,23 @@ result += elements;

.join(', ');
if (uniqueFields !== '' && !compatMode) {
if (uniqueFields !== '') {
result += ',\n' + childEnv.indent + 'UNIQUE(' + uniqueFields + ')'
}
if (primaryKeys !== '') {
result += ',\n' + childEnv.indent + 'PRIMARY KEY(' + primaryKeys + ')\n'
} else {
result += '\n'
result += ',\n' + childEnv.indent + 'PRIMARY KEY(' + primaryKeys + ')'
}
result += env.indent + ')';
// Append table constraints if any
// 'CONSTRAINT <name> UNIQUE (<column_list>)
// OR create a unique index for HDI
for(const cn in art.$tableConstraints) {
const c = art.$tableConstraints[cn];
if(options.toSql.src === 'hdi') {
resultObj.hdbindex[`${artifactName}.${cn}`] =
`UNIQUE INVERTED INDEX ${quoteSqlId( absoluteCdsName(artifactName) + '_' + cn )} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
}
else {
result += ',\n' + childEnv.indent + 'CONSTRAINT ' + cn + ' UNIQUE (' +
c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ') + ')';
}
}
result += env.indent + '\n)';

@@ -282,2 +329,43 @@ if (options.toSql.dialect === 'hana') {

// Render an extended entity into the appropriate dictionaries of 'resultObj'.
function renderExtendInto(artifactName, art, resultObj, env) {
let tableName = quoteSqlId(absoluteCdsName(artifactName));
extensionsDuplicateChecker.addArtifact(tableName, art && art.$location, artifactName)
// Only extend with 'ADD' for elements/associations
// TODO: May also include 'RENAME' at a later stage
let elements = Object.keys(art.elements).map((eltName) => {
const eltStr = renderElement(artifactName, eltName, art.elements[eltName], extensionsDuplicateChecker, null, env);
if (options.toSql.dialect === 'sqlite') {
return eltStr.length ? 'ALTER TABLE ' + tableName + ' ADD COLUMN ' + eltStr + ';': eltStr;
}
return eltStr.length ? eltStr : eltStr;
})
.filter(s => s !== '')
if (options.toSql.dialect === 'hana' && elements.length) {
elements = ['ALTER TABLE ' + tableName + ' ADD (' + elements.join(', ') + ');'];
}
let associations = Object.keys(art.elements).map(name => {
const eltStr = renderAssociationElement(name, art.elements[name], env);
return eltStr.length ? 'ALTER TABLE ' + tableName + ' ADD ASSOCIATION (' + eltStr + ');': eltStr;
})
.filter(s => s !== '');
// In order to render a statement, elements for sql dialect 'hana' must be at least one of type or association
if ((elements.length > 0 && options.toSql.dialect === 'sqlite') ||
((elements.length > 0 || associations.length > 0) && options.toSql.dialect === 'hana')) {
let resultsArray = [];
resultsArray.push(...elements);
if (options.toSql.dialect === 'hana') {
resultsArray.push(...associations);
}
resultObj.alterTable[artifactName] = resultsArray;
}
}
// Retrieve the 'fzindex' (fuzzy index) property (if any) for element 'elemName' from hanaTc (if defined)

@@ -304,3 +392,3 @@ function getFzIndex(elemName, hanaTc) {

// Return the resulting source string (no trailing LF).
function renderElement(artifactName, art, elementName, elm, fzindex, env) {
function renderElement(artifactName, elementName, elm, duplicateChecker, fzindex, env) {
// Ignore if forHana says so, or if it is an association

@@ -312,4 +400,5 @@ if (elm.virtual)

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

@@ -335,15 +424,20 @@ + renderTypeReference(artifactName, elementName, elm)

if (elm.target && !elm._ignore) {
result += env.indent + 'MANY TO ';
if (elm.cardinality && elm.cardinality.max && (elm.cardinality.max === '*' || Number(elm.cardinality.max) > 1)) {
result += 'MANY';
result += env.indent;
if(elm.cardinality) {
if(isBetaEnabled(options, 'hanaAssocRealCardinality') && elm.cardinality.src && elm.cardinality.src === 1) {
result += 'ONE TO ';
} else {
result += 'MANY TO ';
}
if (elm.cardinality.max && (elm.cardinality.max === '*' || Number(elm.cardinality.max) > 1)) {
result += 'MANY';
} else {
result += 'ONE';
}
} else {
result += 'ONE';
result += 'MANY TO ONE';
}
result += ' JOIN ';
result += quoteSqlId(absoluteCdsName(elm.target)) + ' AS ' + quoteSqlId(elementName) + ` ON ${compatMode ? '' : '('}`;
if(compatMode && env.isEntity){
result += renderHdbcdsConformingOnCondition(prepOnCondition(elm.on), env);
} else {
result += renderExpr(elm.on, env) + ')';
}
result += quoteSqlId(absoluteCdsName(elm.target)) + ' AS ' + quoteSqlId(elementName) + ` ON (`;
result += renderExpr(elm.on, env) + ')';
}

@@ -518,11 +612,16 @@ return result;

let firstArtifactName = path.ref[0].id || path.ref[0];
let firstArtifact = csn.definitions[firstArtifactName];
if (!firstArtifact) {
throw new Error('Expecting first path step in path to be resolvable: ' + JSON.stringify(path));
}
let result = quoteSqlId(absoluteCdsName(firstArtifactName));
// store argument syntax hint in environment
// $syntax is set only by A2J and only at the first path step after FROM clause rewriting
const syntax = path.ref[0].$syntax;
// Even the first step might have parameters and/or a filter
if (path.ref[0].args) {
result += `(${renderArgs(path.ref[0].args, '=>', env)})`;
result += `(${renderArgs(path.ref[0].args, '=>', env, syntax)})`;
}
else if (['udf'].includes(syntax)) {
// if syntax is user defined function, render empty argument list
// CV without parameters is called as simple view
result += '()';
}
if (path.ref[0].where) {

@@ -540,3 +639,3 @@ result += `[${path.ref[0].cardinality ? (path.ref[0].cardinality.max + ': ') : ''}${renderExpr(path.ref[0].where, env)}]`;

// using 'sep' as separator for positional parameters
function renderArgs(args, sep, env) {
function renderArgs(args, sep, env, syntax) {
// Positional arguments

@@ -548,3 +647,3 @@ if (args instanceof Array) {

else if (typeof args === 'object') {
return Object.keys(args).map(key => `${quoteSqlId(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
return Object.keys(args).map(key => `${decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
}

@@ -554,2 +653,11 @@ else {

}
function decorateParameter(arg, syntax) {
if(syntax === 'calcview') {
return `PLACEHOLDER."$$${arg}$$"`
}
else {
return quoteSqlId(arg);
}
}
}

@@ -587,3 +695,3 @@

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

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

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

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

// Structured type
let elements = Object.keys(art.elements).map(name => renderElement(artifactName, art, name, art.elements[name], null, childEnv))
let elements = Object.keys(art.elements).map(name => renderElement(artifactName, art, name, art.elements[name], definitionsDuplicateChecker, null, childEnv))
.filter(s => s !== '')

@@ -788,10 +896,2 @@ .join(',\n') + '\n';

// Return true if 'typeName' is the name of a builtin type
function isBuiltinType(typeName) {
// FIXME: Rather than checking a list of builtin types, we just rely on the fact
// that in a valid model, the only types that do not occur in 'definitions' are
// the builtin ones.
return !csn.definitions[typeName];
}
// Render the nullability of an element or parameter (can be unset, true, or false)

@@ -832,3 +932,3 @@ function renderNullability(obj, treatKeyAsNotNull = false) {

}
return params.length == 0 ? '' : '(' + params.join(', ') + ')';
return params.length === 0 ? '' : '(' + params.join(', ') + ')';
}

@@ -838,196 +938,2 @@

function prepOnCondition(onCondition){
return onCondition.map((x,i) => {
if(typeof x === 'string'){
if(i === 0){
return `${x} `;
} else if(i === onCondition.length-1){
return ` ${x}`;
} else {
return ` ${x} `;
}
} else {
return x;
}
})
}
function renderHdbcdsConformingOnCondition(x, env){
// Compound expression
if (x instanceof Array) {
// Simply concatenate array parts with spaces (with a tiny bit of beautification)
// FIXME: Take this for `toCdl`, too
let tokens = x.map(item => renderHdbcdsConformingOnCondition(item, env));
let result = '';
for (let i = 0; i < tokens.length; i++) {
result += tokens[i];
// No space after last token, after opening parentheses, before closing parentheses, before comma
}
return result;
}
// Various special cases represented as objects
else if (typeof x === 'object' && x !== null) {
// Literal value, possibly with explicit 'literal' property
if (x.val !== undefined) {
switch (x.literal || typeof x.val) {
case 'number':
case 'boolean':
case 'null':
// 17.42, NULL, TRUE
return String(x.val);
case 'x':
// x'f000'
return `${x.literal}'${x.val}'`;
case 'date':
case 'time':
case 'timestamp':
if (options.toSql.dialect === 'sqlite') {
// date('2017-11-02')
return `${x.literal}('${x.val}')`;
} else {
// date'2017-11-02'
return `${x.literal}'${x.val}'`;
}
case 'string':
// 'foo', with proper escaping
return `'${x.val.replace(/'/g, "''")}'`;
case 'object':
if (x.val === null) {
return 'NULL';
}
// otherwise fall through to
default:
throw new Error('Unknown literal or type: ' + JSON.stringify(x));
}
}
// Enum symbol
else if (x['#']) {
// #foo
// FIXME: We can't do enums yet because they are not resolved (and we don't bother finding their value by hand)
signal(error`Enum values are not yet supported for conversion to SQL`, x.location);
return '';
}
// Reference: Array of path steps, possibly preceded by ':'
else if (x.ref) {
if (options.forHana && !x.param && !x.global) {
if(x.ref[0] === '$user') {
// FIXME: this is all not enough: we might need an explicit select item alias
if (x.ref[1] === 'id') {
if (options.toSql.user && typeof options.toSql.user === 'string' || options.toSql.user instanceof String) {
return `'${options.toSql.user}'`;
}
else if ((options.toSql.user && options.toSql.user.id) && (typeof options.toSql.user.id === 'string' || options.toSql.user.id instanceof String)) {
return `'${options.toSql.user.id}'`;
} else {
return "SESSION_CONTEXT ( 'APPLICATIONUSER' ) ";
}
}
}
else if (x.ref[1] === 'locale') {
return "SESSION_CONTEXT ( 'LOCALE' ) ";
}
}
else if(x.ref[0] === '$at') {
if(x.ref[1] === 'from') {
return "SESSION_CONTEXT ( 'VALID-FROM' ) ";
}
else if(x.ref[1] === 'to') {
return "SESSION_CONTEXT ( 'VALID-TO' ) ";
}
}
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
// assume that it was not if the path has length 2 (
if (firstPathStepId(x.ref) === '$parameters' && x.ref.length == 2) {
// Parameters must be uppercased and unquoted in SQL
return `:${x.ref[1].toUpperCase()}`;
}
if (x.param) {
return `:${x.ref[0].toUpperCase()}`;
}
return x.ref.map(renderPathStep)
.filter(s => s != '')
.map(ps => `"${ps.toUpperCase()}"`)
.join('.');
}
// Function call, possibly with args (use '=>' for named args)
else if (x.func) {
return renderFunc( x, options.toSql.dialect, a => renderArgs(a, '=>', env), true );
}
// Nested expression
else if (x.xpr) {
return renderHdbcdsConformingOnCondition(x.xpr, env);
}
// Sub-select
else if (x.SELECT) {
// renderQuery for SELECT does not bring its own parentheses (because it is also used in renderView)
return `(${renderQuery('<subselect>', x, increaseIndent(env))})`;
}
else if (x.SET) {
// renderQuery for SET always brings its own parentheses (because it is also used in renderViewSource)
return `${renderQuery('<union>', x, increaseIndent(env))}`;
}
else {
throw new Error('Unknown expression: ' + JSON.stringify(x));
}
}
// Not a literal value but part of an operator, function etc - just leave as it is
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
else {
return String(x).toUpperCase();
}
// Render a single path step 's' at path position 'idx', which can have filters or parameters or be a function
function renderPathStep(s, idx) {
// Simple id or absolute name
if (typeof(s) === 'string') {
// TODO: When is this actually executed and not handled already in renderExpr?
const magicForHana = {
'$now': 'CURRENT_TIMESTAMP',
'$user.id': "SESSION_CONTEXT('APPLICATIONUSER')",
'$user.locale': "SESSION_CONTEXT('LOCALE')",
}
// Some magic for first path steps
if (idx == 0) {
// HANA-specific translation of '$now' and '$user'
// FIXME: this is all not enough: we might need an explicit select item alias
if (magicForHana[s]) {
return magicForHana[s];
}
// Ignore initial $projection and initial $self
if (s === '$projection' || s === '$self') {
return '';
}
}
return quoteSqlId(s);
}
// ID with filters or parameters
else if (typeof s === 'object') {
// Sanity check
if (!s.func && !s.id) {
throw new Error('Unknown path step object: ' + JSON.stringify(s));
}
// Not really a path step but an object-like function call
if (s.func) {
return `${s.func}(${renderArgs(s.args, '=>', env)})`;
}
// Path step, possibly with view parameters and/or filters
let result = `"${quoteSqlId(s.id)}"`;
if (s.args) {
// View parameters
result += `(${renderArgs(s.args, '=>', env)})`;
}
if (s.where) {
// Filter, possibly with cardinality
// FIXME: Does SQL understand filter cardinalities?
result += `[${s.cardinality ? (s.cardinality.max + ': ') : ''}${renderHdbcdsConformingOnCondition(s.where, env)}]`;
}
return result;
}
else {
throw new Error('Unknown path step: ' + JSON.stringify(s));
}
}
}
// Render an expression (including paths and values) or condition 'x'.

@@ -1151,3 +1057,12 @@ // (no trailing LF, don't indent if inline)

else if (x.func) {
return renderFunc( x, options.toSql.dialect, a => renderArgs(a, '=>', env), true );
// test for non-regular HANA identifier that needs to be quoted
// identifier {letter}({letter_or_digit}|[#$])*
// letter [A-Za-z_]
// letter_or_digit [A-Za-z_0-9]
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
const isRegularId = regex.test(x.func);
const funcName = isRegularId ? x.func : quoteSqlId(x.func);
return renderFunc( funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env),
!isRegularId ? false : true );
}

@@ -1188,3 +1103,3 @@ // Nested expression

// Some magic for first path steps
if (idx == 0) {
if (idx === 0) {
// HANA-specific translation of '$now' and '$user'

@@ -1264,3 +1179,3 @@ // FIXME: this is all not enough: we might need an explicit select item alias

}
signal(warning`The identifier "${name}" is a SQLite keyword.`);
signal(warning`The identifier "${name}" is a SQLite keyword`);
}

@@ -1270,3 +1185,3 @@ if (options.toSql.names === 'plain') {

if (keywords.hana.includes(name.toUpperCase())) {
signal(warning`The identifier "${name}" is a HANA keyword.`);
signal(warning`The identifier "${name}" is a HANA keyword`);
}

@@ -1273,0 +1188,0 @@ }

@@ -33,3 +33,3 @@ function isAlreadyBraced(expression, start, end){

start -= 1;
}
}

@@ -68,2 +68,2 @@ if(!isAlreadyBraced(expression, start, end)){

}
module.exports = braceExpression;
module.exports = braceExpression;

@@ -340,7 +340,7 @@ 'use strict';

if(eltCount == 0) {
if (eltCount === 0) {
signal(error`Entity "${artifact.name.absolute}" must have at least one element`, artifact.location);
}
// Exposed non-abstract entities must have a key
if (keyCount == 0) {
if (keyCount === 0) {
signal(error`Entity "${artifact.name.absolute}" does not have a key: ODATA entities must have a key`, artifact.location);

@@ -367,3 +367,3 @@ }

if (!allowedTypes.includes(e[1]._finalType.type._artifact.name.absolute)) {
signal(error`"${artifact.name.absolute}.${e[0]}": Element annoted with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`, e[1].location);
signal(error`"${artifact.name.absolute}.${e[0]}": Element annotated with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`, e[1].location);
}

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

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

const transformUtils = require('./transformUtilsNew');
const { mergeOptions, copyAnnotations, getElementDatabaseNameOf } = require('../model/modelUtils');
const { mergeOptions, copyAnnotations } = require('../model/modelUtils');
const { getUtils,

@@ -15,4 +15,11 @@ cloneCsn,

forEachRef,
isEdmPropertyRendered } = require('../model/csnUtils');
isEdmPropertyRendered,
isBuiltinType,
getArtifactDatabaseNameOf,
getElementDatabaseNameOf } = require('../model/csnUtils');
const { checkCSNVersion } = require('../json/csnVersion');
const validator = require('../checks/csn/validator');
const { validateOnCondition, validateMixinOnCondition } = require('../checks/csn/onConditions');
const validateForeignKeys = require('../checks/csn/foreignKeys');
const validateAssociationsInArrayOf = require('../checks/csn/assocsInArrayOf');

@@ -76,3 +83,2 @@ // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA.

options = mergeOptions(inputModel.options, options);

@@ -86,5 +92,5 @@ setProp(csn, 'options', options);

const transformers = transformUtils.getTransformers(csn, options, '_');
const {
flattenForeignKeys, createForeignKeyElement,
flattenStructuredElement,
createForeignKeyElement,
flattenOnCond,

@@ -101,6 +107,6 @@ checkExposedAssoc, toFinalBaseType,

renameAnnotation
} = transformUtils.getTransformers(csn, options, '_');
} = transformers;
const csnUtils = getUtils(csn);
const {
getArtifactDatabaseNameOf,
getCsnDef,

@@ -114,8 +120,8 @@ getFinalType,

isAssociation,
isBuiltinType,
isManagedAssociationElement,
isStructured,
inspectRef,
artifactRef
} = getUtils(csn);
artifactRef,
effectiveType,
} = csnUtils;

@@ -135,4 +141,4 @@ // are we working with structured OData or not

let referenceFlattener;
if(!structuredOData) {
let ReferenceFlattener = require('./referenceFlattener');
if (!structuredOData) {
let ReferenceFlattener = require('./referenceFlattener');
referenceFlattener = new ReferenceFlattener();

@@ -178,2 +184,12 @@ referenceFlattener.attachPaths(csn);

validator(csn, {
error, warning, info, signal, inspectRef, effectiveType, artifactRef, csn,
},
/* Member Validators */ [ validateOnCondition, validateForeignKeys, validateAssociationsInArrayOf ],
/* artifact validators */ [],
/* query validators */ [ validateMixinOnCondition ]);
// Throw exception in case of errors
handleMessages(csn, options);
// (1.1) Unravel derived type chains for types and annotations (propagating annotations)

@@ -183,5 +199,5 @@ forEachDefinition(csn, (def, defName) => {

let finalTypeDef = def;
if(def.type && def.type.ref) {
if (def.type && def.type.ref) {
finalTypeDef = artifactRef(def.type);
if(!finalTypeDef.type) {
if (!finalTypeDef.type) {
signal(error`"${defName}" has no final type`, ['definitions', defName]);

@@ -194,3 +210,3 @@ return;

toFinalBaseType(def);
} catch(ex) {
} catch (ex) {
signal(error`"${defName}" final base type not found`, ['definitions', defName]);

@@ -247,4 +263,13 @@ return

// TODO: only for V2 or via special option???
if (!structuredOData) {
let structureFlattener = require('./odata/structureFlattener');
structureFlattener(csn, { csnUtils, cloneCsn, error, signal, forEachDefinition, setProp, referenceFlattener, copyAnnotations })
}
if (referenceFlattener) {
referenceFlattener.attachPaths(csn);
}
forEachDefinition(csn, (def, defName, propertyName, path) => {
// (1.5) Flatten structs - for entities and views only (might result in new elements)

@@ -259,16 +284,2 @@ if (def.kind === 'entity' || def.kind === 'view') {

exposeStructTypeOf(elem, serviceName, `${defNameWithoutServiceName(defName, serviceName).replace(/\./g, '_')}_${elemName}`, elemName);
} else {
// Remove the structured element, replace it by its flattened form
delete def.elements[elemName];
let ipath = path.concat('elements', elemName)
let flatElems = flattenStructuredElement(elem, elemName, [], ipath, (flatPath) => {
if (referenceFlattener)
referenceFlattener.registerFlattenedElement(flatPath)
});
for (let flatElemName in flatElems) {
if (def.elements[flatElemName]) {
signal(error`"${defName}.${elemName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`, path.concat(['elements', flatElemName]));
}
def.elements[flatElemName] = flatElems[flatElemName];
}
}

@@ -292,3 +303,3 @@ }

// flatten references, attach new paths
if(referenceFlattener) {
if (referenceFlattener) {
referenceFlattener.flattenAllReferences(csn);

@@ -298,8 +309,9 @@ referenceFlattener.attachPaths(csn);

// Second walk: Only through definitions
// (2.1) For exposed actions and functions that use non-exposed or anonymous structured types, create
// artificial exposing types
// For exposed actions and functions that use non-exposed or anonymous structured types, create
// artificial exposing types.
// Expose types for 'array of/many' declarations
forEachDefinition(csn, (def, defName) => {
let service = getServiceOfArtifact(defName, services);
if (service) {
// unbound actions
if (def.kind === 'action' || def.kind === 'function') {

@@ -309,2 +321,3 @@ exposeTypesForAction(def, defName, service);

// bound actions
for (let actionName in def.actions || {}) {

@@ -319,11 +332,17 @@ exposeTypesForAction(def.actions[actionName], `${defName}_${actionName}`, service);

forEachMemberRecursively(def, (member, memberName, propertyName, path) => {
// we do not apply array of exposure logic on actions/functions
// and on params and returns of action/function
// we do apply array of exposure logic on actions/functions
// and on params and returns of action/function always
if (member.kind === 'action' || member.kind === 'function') isAction = true;
// anonymous defined "array of"
// anonymous defined 'array of'
if (member.items || (member.type && getFinalTypeDef(member.type).items)) {
if (structuredOData)
exposeArrayOfTypeOf(member, service, `${defNameWithoutServiceName(defName, service)}_${memberName}`, memberName);
else if (!isAction) signal(error`"${memberName}": Element must not be an "array of" in flat mode`, path);
else if (options.toOdata.version === 'v4' && !isAction) {
// in flat mode only 'array of <scalar>' is allowed
if (member.items && !isStructured(member.items))
exposeArrayOfTypeOf(member, service, `${defNameWithoutServiceName(defName, service)}_${memberName}`, memberName);
else
signal(error`"${memberName}": Only "array of <scalar>" is allowed in OData V4 flat mode`, path);
}
}

@@ -342,11 +361,20 @@ }, ['definitions', defName]);

let sortByAssociationDependency = require('./sortByAssociationDependency');
let sortedArtifactNames = sortByAssociationDependency(csn);
const sortByAssociationDependency = require('./odata/sortByAssociationDependency');
const sortedAssociations = sortByAssociationDependency(csn);
let flattenedKeyArts = Object.create(null);
sortedArtifactNames.forEach(defName => {
let def = csn.definitions[defName];
flattenForeignKeysForArt(def, defName, flattenedKeyArts);
const { generateForeignKeys, flattenForeignKeys } = require('./odata/foreignKeys');
sortedAssociations.forEach(item => {
const { element } = item;
if (isManagedAssociationElement(element) && element.keys) {
flattenForeignKeys(element, csnUtils, referenceFlattener);
}
})
if (referenceFlattener)
referenceFlattener.attachPaths(csn);
generateForeignKeys(transformers, sortedAssociations);
forEachDefinition(csn, processOnCond);
// Fourth walk through the model: Now all artificially generated things are in place

@@ -406,5 +434,5 @@ // (4.1) Generate artificial draft fields if requested

// array of T is allowed, if T is in defining service or in namespace 'cds'
if (member.items.elements && !member.items.type) {
signal(error`"${defName}.${memberName}": Element must not be an "array of anonymous type"`, path);
}
// if (member.items.elements && !member.items.type) {
// signal(error`"${defName}.${memberName}": Element must not be an "array of anonymous type"`, path);
// }
}

@@ -461,7 +489,6 @@ }

if (isArtifactInSomeService(defName, services) && !def.abstract && (def.kind === 'entity' || def.kind === 'view')) {
let keyCount = 0;
/** @type {[string, CSN.Element][]} */
let mediaTypes = [];
// Walk the elements
let eltCount = 0;
for (let elemName in def.elements) {

@@ -478,7 +505,2 @@ let elem = def.elements[elemName];

// Count keys and elements annotated with @Core.MediaType
let ignore = elem['@cds.api.ignore'];
eltCount++;
if (elem.key && !ignore) {
keyCount++;
}
if (elem['@Core.MediaType']) {

@@ -489,21 +511,7 @@ mediaTypes.push([elemName, elem]);

if (eltCount == 0) {
signal(error`Entity "${defName}" must have at least one element`, ['definitions', defName]);
}
// Exposed non-abstract entities must have a key
if (keyCount == 0) {
signal(error`Entity "${defName}" does not have a key: ODATA entities must have a key`, ['definitions', defName]);
}
// Additional checks for ODATA V2 regarding remaining keys
if (options.toOdata.version === 'v2') {
// Elements that are annotated with @Core.HasStream are removed from the entity type.
// If these are all keys then this would end up with a key-less EntityType wich is illegal in V2
let mtkeys = mediaTypes.filter(e => e[1].key);
if (mtkeys.length > 0 && keyCount == mtkeys.length) {
signal(error`"${defName}: Key elements [${mtkeys.map(e => e[0]).join(', ')}] annotated with '@Core.MediaType' are removed from Odata V2 resulting in keyless EntityType`, ['definitions', defName]);
}
// Today only one MediaType is allowed in V2
if (mediaTypes.length > 1) {
signal(error`"${defName}: Elements [${mediaTypes.map(e => e[0]).join(', ')}] annotated with '@Core.MediaType', OData V2 allows only one`, ['definitions', defName]);
signal(error`"${defName}: Multiple elements [${mediaTypes.map(e => e[0]).join(', ')}] annotated with '@Core.MediaType', OData V2 allows only one`, ['definitions', defName]);
}

@@ -516,3 +524,3 @@ }

if (!allowedTypes.includes(e[1].type)) {
signal(error`"${defName}.${e[0]}": Element annoted with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`, ['definitions', defName, 'elements', e[0]]);
signal(error`"${defName}.${e[0]}": Element annotated with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`, ['definitions', defName, 'elements', e[0]]);
}

@@ -552,4 +560,2 @@ });

case 'enum-value-ref':
case 'empty-entity':
case 'empty-type':
case 'check-proper-type-of':

@@ -614,3 +620,3 @@ message.severity = 'Error';

// but '@Core.Immutable' for everything else.
if(rewriteCapabilities) {
if (rewriteCapabilities) {
if (name === '@readonly') {

@@ -725,4 +731,3 @@ if (node.kind === 'entity' || node.kind === 'view') {

if (!(typeId in exposedStructTypes)) {
signal(error`Cannot create artificial type "${typeId}" because the name is already used`);
return null;
signal(error`Cannot create artificial type "${typeId}" because the name is already used`, newType.$path);
}

@@ -758,7 +763,11 @@ return newType;

if ((csn.definitions[node.type] || node).elements
&& (!node.type || (!isArtifactInService(node.type, service) && (!node.type.startsWith('cds.') || node.type.startsWith('cds.foundation'))))) {
&& (!node.type || (!isArtifactInService(node.type, service) && !isBuiltinType(node.type)))) {
let typeDef = node.type ? getCsnDef(node.type) : /* anonymous type */ node;
if (typeDef && isStructured(typeDef) || (node.type && !node.type.startsWith(service))) {
let typeId = node.type ? `${node.type.replace(/\./g, '_')}` : artificialName;
let type = exposeStructType(typeId, typeDef.elements, service, parentName);
// With the redirection of sub elements, the element which is of named type with an association is now expanded and contains the association
// and the new target. Consequently, we now have both type and elements properties in this case, and the elements should be taken as a priority
// as the correct target is there and no longer in the type definition
let newTypeElements = (node.type && node.elements) ? node.elements : typeDef.elements;
let type = exposeStructType(typeId, newTypeElements, service, parentName);
if (!type) {

@@ -768,4 +777,6 @@ // Error already reported

}
if (node.$location) setProp(type, '$location', node.$location);
// Recurse into elements of 'type' (if any)
for (let elemName in type.elements) {
if (structuredOData && node.elements && node.elements[elemName].$location) setProp(type.elements[elemName], '$location', node.elements[elemName].$location);
exposeStructTypeOf(type.elements[elemName], service, `${typeId}_${elemName}`, parentName);

@@ -810,62 +821,7 @@ }

// Third walk: Process associations
// (3.1) Generate foreign key fields for managed associations (must be done
// after struct flattening, otherwise we might encounter already generated foreign
// key fields in types we have already processed)
// (3.2) Flatten on-conditions in unmanaged associations
function flattenForeignKeysForArt(def, defName, flattenedKeyArts) {
if (defName in flattenedKeyArts)
return; // already flattened
flattenedKeyArts[defName] = true;
function processOnCond(def, defName) {
const rootPath = ['definitions', defName];
forEachMemberRecursively(def, (member, memberName, prop, subpath, parent) => {
// Generate foreign key elements for managed associations.
// In the case of composition of aspect we might have an element which does not
// have on-cond and keys, therefore the check if a member has a 'keys'
if (isManagedAssociationElement(member) && member.keys) {
// Flatten foreign keys (replacing foreign keys that are managed associations by their respective foreign keys)
flattenForeignKeys(member, flattenedKeyArts, flattenForeignKeysForArt);
// Generate foreign key elements
for (let keyIndex in member.keys) {
let key = member.keys[keyIndex];
let keyPath = rootPath.concat(subpath).concat('keys', keyIndex);
if (!structuredOData) { // flatten the reference using RESOLVED references
let resolvedIsStructured = referenceFlattener.isStructured(keyPath)
if (resolvedIsStructured) {
let ref = key.ref;
let newref = []; // new flattened reference
let previousElementIsStructured=false;
ref.forEach((iref,i) => {
if(previousElementIsStructured == undefined) return; // missing information - skip processing
if(previousElementIsStructured) {
newref[newref.length-1] = newref[newref.length-1]+'_'+iref; // prevous element is sructured - concat last with current
} else {
newref.push(iref); // prevous element is not structured - do not flatten, just push it
}
previousElementIsStructured = resolvedIsStructured[i]; // detect structured elements
})
if(key.ref.length>newref.length) { // anything flattened?
key.ref = newref;
}
}
}
let foreignKeyElement = createForeignKeyElement(member, memberName, key, parent, defName, rootPath.concat(subpath).concat('keys', keyIndex));
toFinalBaseType(foreignKeyElement);
// Propagate the association's annotations to the foreign key element
// (Overwriting because they should win over the derived type unraveling)
copyAnnotations(member, foreignKeyElement, true);
}
// If the managed association is NOT NULL, we give it a target min cardinality of 1
// if it didn't already have an explicitly specified min cardinality.
// (No need to check again for min <= max cardinality, because max has already been checked to be > 0)
if (member.notNull) {
if (!member.cardinality) {
member.cardinality = {};
}
if (member.cardinality.min === undefined) {
member.cardinality.min = 1;
}
}
}
forEachMemberRecursively(def, (member, memberName, prop, subpath) => {
// (3.2) Flatten/normalize on-conditions in unmanaged associations

@@ -872,0 +828,0 @@ if (member.type && isAssocOrComposition(member.type) && member.on) {

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

this.structuredReference={};
this.generatedElementsForPath={};
}

@@ -87,7 +88,19 @@

registerFlattenedElement(path) {
registerFlattenedElement(path,originPath) {
let spath = path.join('/');
this.flattenedElementPaths[spath]=true;
if(originPath) {
let sOriginPath = originPath.join('/');
this.flattenedElementPaths[sOriginPath]=true;
}
}
registerGeneratedElementsForPath(path,elementNames) {
this.generatedElementsForPath[path.join("/")]=elementNames;
}
getGeneratedElementsForPath(path) {
return this.generatedElementsForPath[path.join("/")];
}
flattenAllReferences(csn) {

@@ -114,6 +127,7 @@ forEachRef(csn, (ref, node) => {

// check if this is a column and add alias if missing
if(isColumnInSelect(ref.$path))
if(isColumnInSelect(ref.$path)) {
if(!node.as) {
node.as = node.ref[node.ref.length-1];
}
}
node.ref=newRef;

@@ -120,0 +134,0 @@ }

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

setProp(artifact, '_finalType', artifact);
addStringAnnotationTo('@Common.Label', '{i18n>Draft_DraftAdministrativeData}', artifact);

@@ -974,3 +975,4 @@ // key DraftUUID : UUID

* @param {any} element Element to check
* @returns {Array[]} Array of arrays, first filed has an array with the element if it has @cds.valid.from, second field if it has @cds.valid.to. Default value is [] for each field.
* @returns {Array[]} Array of arrays, first filed has an array with the element if it has @cds.valid.from,
* second field if it has @cds.valid.to. Default value is [] for each field.
*/

@@ -977,0 +979,0 @@ function extractValidFromToKeyElement(element) {

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

const { copyAnnotations, printableName, hasBoolAnnotation, forEachDefinition } = require('../model/modelUtils');
const { dfilter } = require('./udict');
const { cloneCsn, forEachRef, getUtils } = require('../model/csnUtils');
const { cloneCsn, forEachRef, getUtils, isBuiltinType } = require('../model/csnUtils');

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

isStructured,
isBuiltinType
} = getUtils(model);
return {
resolvePath,
flattenPath,
addDefaultTypeFacets,
flattenForeignKeys,
createForeignKeyElement,

@@ -86,72 +85,2 @@ checkForeignKeys,

// For an array `keys` of foreign key infos, return an array in flattened form
// in one of the two cases:
// (1) replace all foreign keys that are managed associations themselves by
// their respective foreign keys, recursively, with names flattened using
// pathDelimiter between path components.
// (2) replace all foreign keys that are structured with their respective flattened form.
//
// Note: must be done after struct flattening(flattenStructuredElement method),
// otherwise we might encounter already generated foreign key fields in types
// we have already processed.
function flattenForeignKeys(assoc, flattenedKeyArts, callbackForUnflattenedArts) {
let fkSeparator = pathDelimiter;
let targetArt = getCsnDef(assoc.target);
if (callbackForUnflattenedArts && !(assoc.target in flattenedKeyArts)) {
callbackForUnflattenedArts(targetArt, assoc.target, flattenedKeyArts);
}
// get all the elements from the target that have 'key' identifier
let targetKeys = dfilter(targetArt.elements, elem => elem.key === true);
// in case we have explicitly defined FKs
Object.assign(targetKeys, dfilter(targetArt.elements, (elem, elemName) => {
if (elem._flatElementNameWithDots) {
// this is flattened elem -> keys still not flattened, have to check if starts with key ref
// FIXME: review why join('.')? what about join(fkSeparator)?
return assoc.keys.map(key => key.ref.join('.')).some(keyDotName => elemName.startsWith(`${keyDotName}${fkSeparator}`));
} else {
// exact match of the name
return assoc.keys.map(key => key.ref.join('.')).some(keyDotName => keyDotName === elemName);
}
}));
let result = [];
// this iteration assumes the elements in the tartgetArtifact are flattened
for (let key of assoc.keys) {
let fKeyName = key.ref.join(fkSeparator);
// The key is an association - (1)
if (Object.keys(targetKeys).includes(fKeyName) && targetKeys[fKeyName].type
&& targetKeys[fKeyName].type === 'cds.Association' && targetKeys[fKeyName].target
) {
// as there is no assurance that the target of the target has flattened keys already
// has to go through both of the associations
getCsnDef(targetKeys[fKeyName].target); // sanity check if the definition exists
targetKeys[fKeyName].keys.forEach(k => result.push({ ref: [`${fKeyName}${fkSeparator}${k.ref.join(fkSeparator)}`] }));
continue;
}
// collect potential flattened keys, which are the counterpart of the current key
let flattenedKeys = [];
for (let keyName in targetKeys) {
if (targetKeys[keyName].$viaTransform && keyName.startsWith(`${fKeyName}${fkSeparator}`))
flattenedKeys.push(keyName);
}
// The keys is structured element (2)
if (flattenedKeys.length) {
flattenedKeys.forEach(k => result.push({ ref: [k], as: k }));
} else {
// Otherwise simply take as it is
result.push(key);
}
}
assoc.keys = result;
}
// (1) Create an artificial foreign key element for association 'assoc' (possibly part

@@ -173,2 +102,3 @@ // of nested struct, i.e. containing dots) in 'artifact', using foreign key info

let fkArtifact = inspectRef(path).art;

@@ -198,3 +128,2 @@

function newForeignKey(fkArtifact,foreignKeyElementName) {
if(fkArtifact.type=='cds.Association' || fkArtifact.type=='cds.Composition' ) {

@@ -206,3 +135,2 @@ processAssociationOrComposition(fkArtifact,foreignKeyElementName)

let foreignKeyElement = Object.create(null);
// Transfer selected type properties from target key element

@@ -225,2 +153,4 @@ // FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property.

}
if (artifact.items) // proceed to items of such
artifact = artifact.items;
// Insert artificial element into artifact, with all cross-links (must not exist already)

@@ -237,4 +167,6 @@ if (artifact.elements[foreignKeyElementName]) {

foreignKey.$generatedFieldName = foreignKeyElementName;
setProp(foreignKey, "$path", path); // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
setProp(foreignKeyElement, '$path', path); // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
if(assoc.$location){
setProp(foreignKeyElement, '$location', assoc.$location);
}
result[foreignKeyElementName] = foreignKeyElement;

@@ -269,3 +201,3 @@ } // function newForeignKey

}
if (!target.elements || Object.keys(target.elements).length == 0) {
if (!target.elements || Object.keys(target.elements).length === 0) {
throw Error('Expecting target of association ' + assocName + ' to have elements');

@@ -306,6 +238,4 @@ }

// }
function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[], flattenCallback=undefined) {
function flattenStructuredElement(elem, elemName, parentElementPath=[], pathInCsn=[]) {
let elementPath=parentElementPath.concat(elemName); // elementPath contains only element names without the csn structure node names
if(flattenCallback)
flattenCallback(pathInCsn);
// in case the element is of user defined type => take the definition of the type

@@ -326,3 +256,3 @@ // for type of 'x' -> elem.type is an object, not a string -> use directly

result[eName] = e;
}
}
}

@@ -334,3 +264,3 @@

// Descend recursively into structured children
let grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName), flattenCallback);
let grandChildElems = flattenStructuredElement(childElem, childName, elementPath, pathInCsn.concat('elements',childName));
for (let grandChildName in grandChildElems) {

@@ -443,4 +373,4 @@ let flatElemName = elemName + pathDelimiter + grandChildName;

// $user.locale must not be flatten and they are not understand by inspectRef
if (ref.join('.') === '$user.locale')
// $user.locale & $user.id must not be flatten and they are not understand by inspectRef
if (/^(\$user\.locale|\$user\.id)$/.test(ref.join('.')))
return;

@@ -492,3 +422,3 @@

// .. or builtin already
if (node.type && typeof node.type === 'string' && node.type.startsWith('cds.') && !node.type.startsWith('cds.foundation.')) return;
if (node.type && isBuiltinType(node.type)) return;

@@ -532,3 +462,3 @@ // The type might already be a full fledged type def (array of)

// .. or builtin already
if (node.type && typeof node.type === 'string' && node.type.startsWith('cds.') && !node.type.startsWith('cds.foundation.')) return;
if (node.type && isBuiltinType(node.type)) return;

@@ -542,9 +472,9 @@ // The type might already be a full fledged type def (array of)

Object.assign(node, { enum: typeDef.enum });
if (!node.length && typeDef.length)
if (node.length === undefined && typeDef.length !== undefined)
Object.assign(node, { length: typeDef.length });
if (!node.precision && typeDef.precision)
if (node.precision === undefined && typeDef.precision !== undefined)
Object.assign(node, { precision: typeDef.precision });
if (!node.scale && typeDef.scale)
if (node.scale === undefined && typeDef.scale !== undefined)
Object.assign(node, { scale: typeDef.scale });
if (!node.srid && typeDef.srid)
if (node.srid === undefined && typeDef.srid !== undefined)
Object.assign(node, { srid: typeDef.srid });

@@ -633,2 +563,3 @@ node.type = typeDef.type;

elements: Object.create(null),
'@Common.Label': '{i18n>Draft_DraftAdministrativeData}',
}

@@ -920,3 +851,4 @@

* @param {Array} path path in CSN for error messages
* @returns {Array[]} Array of arrays, first filed has an array with the element if it has @cds.valid.from, second field if it has @cds.valid.to. Default value is [] for each field.
* @returns {Array[]} Array of arrays, first filed has an array with the element if it has @cds.valid.from,
* second field if it has @cds.valid.to. Default value is [] for each field.
*/

@@ -1045,2 +977,121 @@ function extractValidFromToKeyElement(element, path) {

}
/*
Resolve the type of an artifact
If art is undefined, stop
If art has elements or items.elements, stop
If art has a type and the type is scalar, stop
If art has a named type or a type ref, resolve it
*/
function resolveType(art) {
while(art &&
!((art.items && art.items.elements) || art.elements) &&
(art.type &&
((!art.type.ref && !isBuiltinType(art.type)) || art.type.ref))) {
if(art.type.ref)
art = resolvePath(art.type);
else
art = model.definitions[art.type];
}
return art;
}
/**
* Path resolution, attach artifact to each path step, if found,
* Dereference types and follow associations.
*
* @param {any} path ref object
* @param {any} art start environment
* @returns {any} path with resolved artifacts or artifact
* (if called with simple ref paths)
*/
function resolvePath(path, art=undefined) {
let notFound = false;
for(let i = 0; i < path.ref.length && !notFound; i++) {
const ps = path.ref[i];
const id = ps.id || ps;
if(art) {
if(art.target)
art = model.definitions[art.target].elements[id];
else if(art.items && art.items.elements || art.elements) {
art = (art.items && art.items.elements || art.elements)[id];
}
else
art = undefined;
}
else {
art = model.definitions[id];
}
art = resolveType(art);
// if path step has id, store art
if(ps.id && art)
ps._art = art;
notFound = !art;
}
// if resolve was called on constraint path, path has id.
// Store art and return path, if called recursively for model ref paths,
// return artifact only
if(path.ref[0].id) {
if(art)
path._art = art;
return path;
}
else return art;
}
/*
Flatten structured leaf types and return an array of paths.
Argument 'path' must be an object of the form
{ _art: <leaf_artifact>, ref: [...] }
with _art identifying ref[ref.length-1]
A produced path has the form { _art: <ref>, ref: [ <id> (, <id>)* ] }
Flattening stops on all non structured elements, if followMgdAssoc=false.
If fullRef is true, a path step is produced as { id: <id>, _art: <link> }
*/
function flattenPath(path, fullRef=false, followMgdAssoc=false) {
let art = path._art;
if(art) {
if(art && art.type && !((art.items && art.items.elements) || art.elements)) {
if(followMgdAssoc && art.target && art.keys) {
let rc = [];
for(const k of art.keys) {
const nps = { ref: k.ref.map(p => {
return ( fullRef ? { id: p } : p ) } ), _art: k._art };
const paths = flattenPath( nps, fullRef, followMgdAssoc );
// prepend prefix path
paths.forEach(p=>p.ref.splice(0, 0, ...path.ref));
rc.push(...paths);
}
return rc;
}
if(art.type.ref)
art = resolvePath(art.type);
else if(!isBuiltinType(art.type))
art = model.definitions[art.type];
}
const elements = art.items && art.items.elements || art.elements;
if(elements) {
let rc = []
for(const en in elements) {
const nps = { ref: [ (fullRef ? { id: en, _art: elements[en] } : en )], _art: elements[en] };
const paths = flattenPath( nps, fullRef, followMgdAssoc );
// prepend prefix path
paths.forEach(p=>p.ref.splice(0, 0, ...path.ref));
rc.push(...paths);
}
return rc;
}
else
path._art = art;
}
return [path];
}
}

@@ -1057,3 +1108,3 @@

// entity called 'type'
function transformModel(model, transformers) {
function transformModel(model, transformers, transformNonEnumerableElements=false) {

@@ -1080,3 +1131,8 @@ return transformNode(model, undefined, undefined, []);

// Iterate own properties of 'node' and transform them into 'resultNode'
for (let key of Object.keys(node)) {
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');
}
for (let key of toIterateOver) {
// Dictionary always use transformNode(), other objects their transformer according to key

@@ -1083,0 +1139,0 @@ let transformer = (proto == undefined) ? transformNode : transformers[key] || transformers[key.charAt(0)];

@@ -40,6 +40,8 @@ ///

bold: '\x1b[1m', // Bold/Bright
link: '\x1b[4m', // underline
underline: '\x1b[4m', // for links
red: '\x1b[31m', // Foreground Red
green: '\x1b[32m', // Foreground Green
yellow: '\x1b[33m' // Foreground Yellow
yellow: '\x1b[33m', // Foreground Yellow
magenta: '\x1b[35m', // Foreground Magenta
cyan: '\x1b[36m', // Foreground Cyan
}

@@ -54,3 +56,4 @@

const asInfo = module.exports.info = o => as(t.green, o)
module.exports.link = o => as(t.link, o)
const asHelp = module.exports.help = o => as(t.cyan, o)
module.exports.underline = o => as(t.underline, o)
module.exports.bold = o => as(t.bold, o)

@@ -63,2 +66,3 @@

case 'info': return asInfo(msg);
case 'help': return asHelp(msg);
// or e.g. 'none'

@@ -65,0 +69,0 @@ default: return msg;

/**
* A single TimeTrace encapsulates the runtime of a selected code frame.
*
*
* @class TimeTrace

@@ -9,4 +9,4 @@ */

* Creates an instance of TimeTrace.
* @param {string} id
*
* @param {string} id
*
* @memberOf TimeTrace

@@ -18,4 +18,4 @@ */

* Start measuring.
*
* @param {number} indent
*
* @param {number} indent
*/

@@ -27,7 +27,7 @@ this.start = function(indent){

}
/**
* Stop measuring and log the result
*
* @param {number} indent
*
* @param {number} indent
*/

@@ -42,10 +42,10 @@ this.stop = function(indent){

}
/**
* The main class to handle measuring the runtime of code blocks
*
*
* Results are logged to stderr
*
*
* To enable time tracing, set CDSC_TIMETRACE to true in the environment
*
*
* @class TimeTracer

@@ -56,3 +56,3 @@ */

* Creates an instance of TimeTracer.
*
*
* @memberOf TimeTracer

@@ -63,8 +63,8 @@ */

}
/**
* Start a new TimeTrace, using the given id for loggin etc.
*
*
* @param {string} id A short description of whats going on
*
*
* @memberOf TimeTracer

@@ -81,7 +81,7 @@ */

}
/**
* Stop the current TimeTrace and log the execution time.
*
*
*
*
* @memberOf TimeTracer

@@ -98,3 +98,3 @@ */

}
module.exports = (process && process.env && process.env.CDSC_TIMETRACING !== undefined) ? new TimeTracer() : { start: () => {}, stop: () => {}};

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

{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"^1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.26.2","license":"SEE LICENSE IN developer-license-3.1.txt"}
{
"name": "@sap/cds-compiler",
"version": "1.35.0",
"description": "CDS (Core Data Services) compiler and backends",
"homepage": "https://cap.cloud.sap/",
"author": "SAP SE (https://www.sap.com)",
"license": "See LICENSE file",
"bin": {
"cdsc": "bin/cdsc.js",
"cdshi": "bin/cdshi.js",
"cdsse": "bin/cdsse.js"
},
"main": "lib/main.js",
"keywords": [
"CDS"
],
"dependencies": {
"antlr4": "4.7.1",
"resolve": "1.8.1",
"sax": "^1.2.4"
},
"files": [
"bin",
"lib",
"doc",
"share",
"package.json",
"README.md",
"CHANGELOG.md",
"LICENSE"
]
}

@@ -8,4 +8,2 @@ # Getting started

[Installation and Usage](#installation-and-usage)
[Command invocation](#command-invocation)
[Build from source](#build-from-source)
[Documentation](#documentation)

@@ -15,3 +13,3 @@

Install via npm:
Install with npm:

@@ -30,36 +28,9 @@ ```

### Command Invocation
## Documentation
The compiler with its options is invoked like any other npm/Unix command:
Please refer to the [official CDS documentation](https://cap.cloud.sap/docs/cds/).
```bash
cdsc <command> [options] <file...>
```
See `cdsc --help` for commands and options.
The exit code of the process is:
## License
* `0`: successful compilation
* `1`: compiled with error (the command invocation itself is ok)
* `2`: commmand invocation error (invalid options, repeated file name)
### Build from source
We recommend to install cds-compiler using npm. However, if you want to use
the latest master (e.g. for testing purposes) then you need to set up the
compiler first:
```sh
git clone git@github.wdf.sap.corp:cdx/cds-compiler.git
cd cds-compiler
npm install
npm run download # Downloads Antlr (Java Dependency)
npm run gen # Generates the parser
./bin/cdsc.js --help
```
## Documentation
Please refer to the [official CDS documentation][capire].
[capire]: https://cap.cloud.sap/docs/cds/
This package is provided under the terms of the [SAP Developer License Agreement](https://tools.hana.ondemand.com/developer-license-3_1.txt).

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

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

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc