Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
3
Maintainers
3
Versions
99
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.17.1 to 1.19.2

lib/model/enrichCsn.js

16

bin/cdsc.js

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

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

@@ -23,2 +23,3 @@ var fs = require('fs');

var reveal = require('../lib/model/revealInternalProperties');
const enrichCsn = require('../lib/model/enrichCsn');
const { optionProcessor } = require('../lib/optionProcessor');

@@ -184,3 +185,10 @@ const { translatePathLocations } = require('../lib/base/messages')

function toHana( model ) {
let hanaResult = options.newTransformers ? toHanaWithCsn(compactModel(model, options), options) : compiler.toHana(model);
let hanaResult;
if(options.newTransformers) {
let csn = compactModel(model, options);
csn.messages = model.messages; // pass messages as compactModel misses that
hanaResult = toHanaWithCsn(csn, options)
} else {
hanaResult = compiler.toHana(model);
}
for (let name in hanaResult.hdbcds) {

@@ -244,3 +252,3 @@ writeToFileOrDisplay(options.out, name + '.hdbcds', hanaResult.hdbcds[name]);

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

@@ -328,2 +336,4 @@ ['hdbtabletype', 'hdbtable', 'hdbindex', 'hdbfulltextindex', 'hdbview', 'sql'].forEach(pluginName => {

let csn = compiler.toCsn(xsn, options);
if (options.enrichCsn)
enrichCsn( csn );
writeToFileOrDisplay(options.out, name + '.json', csn, true);

@@ -330,0 +340,0 @@ }

2

bin/cdshi.js

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

artref: 'm', paramname: 'b',
Entity: 'D', Enum: 'H', Index: 'J', AnnoDef: 'V', Extend: 'A', Annotate: 'A'
Entity: 'D', Enum: 'H', Index: 'J', AnnoDef: 'V', Extend: 'A', Annotate: 'A', Event: 'Y'
}

@@ -21,0 +21,0 @@

# API Migration
> Status Oct 2019: this document is still valid, but the recommended API will change (again) in the near future.
> The future version of this document (renamed to `API.md`) will basically explain the recommended API,
> the migration will only be a minor aspect and explained in a later section.
<!-- The option handling might also change: -->
<!-- the backend-specific structure is overly complex and not always appriopriate (e.g. naming mode). -->
With revision 1.0.24, the CDS compiler offers new API backend functions, i.e. new functions for the

@@ -4,0 +11,0 @@ generation of output from (augmented) CSN models. The new functions and their options are closely

# Command Line Migration
> Status Oct 2019: this document is still basically valid.
> The future version of this document (renamed to `CommandLine.md`) will basically explain the recommended CLI options,
> the migration will only be a minor aspect and explained in a later section.
<!-- The option handling might also change: -->
<!-- the backend-specific structure is overly complex and not always appriopriate (e.g. naming mode). -->
<!-- The placement of options should not depend on a not always apparent distinction between command-specific and general options. -->
With revision 1.5.1, the `cdsc` command line interface has been adapted to use commands with

@@ -4,0 +13,0 @@ options.

# Error Messages Explained
> Status Oct 2019: up-to-date
This document tries to explain some of the less-obvious error messages.

@@ -9,2 +11,5 @@

In most cases, you really have just used the same name twice when defining an artifact.
This section is about a situation where you are pretty sure that you have not done that.
```

@@ -17,4 +22,4 @@ node_modules/Base/index.cds:1:6-7: Error: Duplicate definition of artifact "T"

Here, the CDS Compiler does consider `…/Base/index.cds` to be different to `…/base/index.cds`,
and also considers the two `…/model/index.cds` files to be the different files.
Here, the CDS Compiler considers `…/Base/index.cds` to be different to `…/base/index.cds`,
and also considers the two `…/model/index.cds` files to be different files.
Why is that the case? Consider the following "top-level" file

@@ -68,9 +73,11 @@

### Extensions
### Nested extensions
If you use nested extensions, you might get messages like:
```
e.cds:3:20-26: Error: No `EXTEND artifact` within CONTEXT extensions
e.cds:4:20-28: Error: No `ANNOTATE artifact` within SERVICE extensions
e.cds:5:14-22: Error: Elements only exist in entities, types or typed constructs
e.cds:6:12-36: Error: Elements only exist in entities, types or typed constructs
nested-extensions.cds:3:20-26: Error: No `EXTEND artifact` within CONTEXT extensions
nested-extensions.cds:4:20-28: Error: No `ANNOTATE artifact` within SERVICE extensions
nested-extensions.cds:5:14-22: Error: Elements only exist in entities, types or typed constructs
nested-extensions.cds:6:12-36: Error: Elements only exist in entities, types or typed constructs
```

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

# Translation of Fiori annotations
> Status Oct 2019: too vague, old links, to be moved to internalDoc if we want to keep it.
Fiori annotations are translated in a generic way. Essentially, write down in CDS precisely what you want to get in edmx.

@@ -4,0 +6,0 @@

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

# Name Resolution in CDL (CDx/Language)
# Name Resolution in CDS
> Status Oct 2019: TODOs must be filled, say more about name resolution in CSN.
Name resolution refers to the resolution of names (identifiers) within expressions of the source to the intended artifact or member in the model.

@@ -8,5 +10,7 @@

This document presents the exact **semantics** of the resolution in CDL,
This document presents the exact **semantics** of the resolution in CDS
especially how it is influenced by the language constructs where the reference is embedded in.
In explanations, we have CDL as the main focus, but name resolution in CSN is covered as well.
The overall goal is that the name resolution is low on surprises throughout the complete life-cycle of any CDS model,

@@ -43,3 +47,3 @@ and robust concerning any model extensions.

If you look at typical examples given in introductionary documents about CDS,
you might wonder why there is a lengthy document about the name resolution in CDL.
you might wonder why there is a lengthy document about the name resolution.
So, let us start with such an example:

@@ -83,3 +87,3 @@

These are then "translated" by the name resolution into the absolute names.
This also allow us to easily change the common name prefix in the development phase.
This also allows us to easily change the common name prefix in the development phase.

@@ -104,3 +108,3 @@ In which area of the code do we assume which common name prefix?

Let is now see why the name resolution is not as obvious as it might seem to be…
Let us now look at the name resolution and why it is not as obvious as it might seem to be…

@@ -122,3 +126,3 @@ > What happens if an inner block introduces the same name as an outer block?

That is an easy one: the `using` declarations introduce a file-local alias name to an absolute name
for accessing artifacts in other files or in the current files (useful to refer to shadowd definitions).
for accessing artifacts in other files or in the current file (useful to refer to shadowed definitions).

@@ -132,3 +136,3 @@ > Can something bad happen if extensions come into play?

But we make sure that something real bad cannot happen:
an **extension cannot silently changes the semantics of a model** –
an **extension cannot silently change the semantics of a model** –
the name resolution is defined in such a way that a valid reference

@@ -164,3 +168,3 @@ does not silently (without errors or warnings) point to another artifact

sub structures, associations, extensions, …
* find argument positions which are "similar" to arguments positions with a given name resolution semantics –
* find argument positions which are "similar" to argument positions with given name resolution semantics –
we then apply the same semantics to the "new" argument positions

@@ -223,4 +227,4 @@

Any "convenience" declaration which "extends" lexical scoping
is usually soon be declared as obsolute,
because its **little convenience benefit of is not worth the addition issues**.
is usually soon to be declared as obsolete,
because its **little convenience benefit is not worth the additional issues**.
As an example, see the fate of the `with` statement in JavaScript.

@@ -269,3 +273,3 @@

Please note that these principles are ordered.
There are many cases where one principle cannot be fulfilled in order to fulfil a higher prioritized design principles.
There are __many cases where one principle cannot be fulfilled__ in order to fulfill a higher prioritized design principles.
The first design principle is therefore always fulfilled.

@@ -322,3 +326,3 @@ This can be seen in the following examples.

* An **navigation environment** of a construct is the dictionary for definitions within that construct or a type/entity referred by that construct.
* A **navigation environment** of a construct is the dictionary for definitions within that construct or a type/entity referred by that construct.

@@ -340,5 +344,5 @@ For contexts (and services), these are the sub artifacts defined within that context.

Name resolution name is **case sensitive**.
Name resolution is **case sensitive**.
In general, a model can contain artifacts and members whose name differ in case only;
there might be an linter check which informs model writers if they make use of this "feature".
there might be a linter check which informs model writers if they make use of this "feature".

@@ -349,3 +353,3 @@ While being case sensitive might be against the original intention of SQL,

for the semantics of quoted and non-quoted identifiers.
In CDL, we just do not transform non-quoted identifiers to all-upper names.
In CDL, we just do *not* transform non-quoted identifiers to all-upper names.

@@ -361,3 +365,3 @@ Also, CSN-processors are cumbersome to write if they have to deal with (partial/feigned) case-insensitivity.

In CDL, an identifier may be used before its definitions,
there is **no need of forward declarations**
there is **no need of forward declarations**.
Thus, the sequence of definitions inside a block does not matter for the scope rules:

@@ -460,3 +464,3 @@

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

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

* **type** (but not the reference after `type of`, see below)
* **annotation** for a annotation assignment,
* **annotation** for an annotation assignment,
* to-be-extended main artifact of an **outer extend**

@@ -475,3 +479,3 @@ * **structure include**,

The construction of the list of lexical search environments **starts at the innermost block** containing the current statement,
and then continue to the next outer block:
and then continues to the next outer block:

@@ -485,3 +489,3 @@ * As opposed to HANA-CDS, we skip blocks containing just element definitions

The last, non-lexical search environment is the environments for built-in artifacts.
The last, non-lexical search environment is the environment for built-in artifacts.
Currently, it contains `String` for `cds.String`, similarly `LargeString`,

@@ -680,3 +684,3 @@ `Integer`, `Integer64`, `Binary`, `LargeBinary`, `Decimal`, `DecimalFloat`, `Double`,

If there is _no annotation definition_ (there might be a warning for this),
If there is _no annotation definition_ (there might be a warning for this)
then the path cannot be resolved at all.

@@ -693,6 +697,6 @@ The same is true if the annotation type

If there is a annotation definition which allows to use paths
by specifying the type _`cds.ElementRef`_,
by specifying the type _`cds.ElementRef`_
then the path resolution works as described in
[Section "References sibling elements"](#references-to-sibling-elements).
If that annotation is assigned to a main artifact,
If that annotation is assigned to a main artifact
then _same main artifact_ mean the main artifact itself.

@@ -707,3 +711,3 @@

* Using constant values requires to prefix the path (referring to the constant) with a `:`.
* There is a new semantics for paths (without initial `:`) used in annotation assignments.
* There is a new semantic for paths (without initial `:`) used in annotation assignments.
* In the definitions of sub elements, accessing elements supplied by the corresponding main artifact

@@ -714,3 +718,3 @@ requires to prefix the path with `$self.`.

* In views with more than one source entity,
selecting an element `e` from one source without the use of a table alias (which is not recommented anyway!)
selecting an element `e` from one source without the use of a table alias (which is not recommended anyway!)
suddenly does not compile anymore if another source entity is extended by a new element `e`.

@@ -789,3 +793,3 @@

whether is has been defined externally:
via an extensions or as an element of the referenced type.
via an extension or as an element of the referenced type.

@@ -792,0 +796,0 @@ **Resolving the first name of a path when looking for artifacts**.

# ODATA Transformation
> Status Oct 2019: outdated, uses old-style CSN, to be reworked completely -> move to internalDoc/.
> For users, OData is a backend, they do not care too much that it works via a CSN transformation.
Prior to the generation of EDMX (Entity Data Model XML) files from a CDS model,

@@ -4,0 +7,0 @@ the following transformations are applied to the model. Most (but not all) of

# To Swagger transformation
> Status Oct 2019: outdated. As long as the `toSwagger` backend only works with `--beta-mode`, this doc should be in internalDoc/.
> Some JSON code snippets might be a bit too long.
"The OpenAPI Specification, originally known as the Swagger Specification, is

@@ -43,2 +46,3 @@ a specification for machine-readable interface files for describing, producing,

Three different syntaxes are available:
1. __@Swagger.GET : 200__ - a GET operation with response code 200 is generated

@@ -74,3 +78,3 @@ 2. __@Swagger.POST__ - a POST operation with the default response code is generated

The OpenAPI specification states that a parameter can have location([the property 'in' of a parameter object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject))
The OpenAPI specification states that a parameter can have location ([the property 'in' of a parameter object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#parameterObject))
with one of the following values:

@@ -90,3 +94,3 @@ - query

Given is the CDS model
````
```
...

@@ -102,6 +106,6 @@ actions {

...
````
```
the result will look like:
````json
```json
...

@@ -243,3 +247,3 @@ "paths": {

...
````
```

@@ -259,3 +263,3 @@ #### Request body

is a link to the next page of responses, or:
````json
```json
"200": {

@@ -282,3 +286,3 @@ "description": "Expected response to a valid request",

},
````
```

@@ -347,3 +351,3 @@ ### Schemas

````
```
service Bookstore {

@@ -369,5 +373,5 @@ entity Book as projection on BookstoreContext.Book;

};
````
```
will result in:
````json
```json
{

@@ -473,3 +477,3 @@ "openapi": "3.0.0",

}
````
```
The same redirection is performed for user-defined types, as the type declaration should be also exposed to the service in question.

@@ -486,3 +490,3 @@

````
```
...

@@ -493,5 +497,5 @@ entity MyEntity {

...
````
```
The output:
````json
```json
...

@@ -511,2 +515,2 @@ "components": {

...
````
```

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

function toSwagger(model, options) {
const { warning, signal } = alerts(model);
const { warning, error, signal } = alerts(model);

@@ -471,2 +471,10 @@ // In case of API usage the options are in the 'options' argument

options = mergeOptions(model.options, options);
// hide to swagger behind betaMode
if (options.betaMode) {
signal(warning`The to swagger backend is experimental`);
} else {
signal(error`The to swagger backend is only available in beta-mode and is experimental`);
}
// If neither 'json' nor 'csn' is specified as output option, produce 'json'

@@ -516,3 +524,3 @@ if (!options.toSwagger || (!options.toSwagger.json && !options.toSwagger.csn)) {

if (model.options && model.options.toSql &&(model.options.toSql.user || model.options.toSql.locale)) {
transforUserOption(model.options.toSql);
transformUserOption(model.options.toSql);
}

@@ -530,3 +538,3 @@

if (options && (options.toSql.user || options.toSql.locale)){
transforUserOption(options.toSql);
transformUserOption(options.toSql);
}

@@ -595,3 +603,3 @@

if (options.toSql.src) {
result = toSqlDdl(forSqlAugmented);
result = toSqlDdl(compactModel(forSqlAugmented), forSqlAugmented.options);
}

@@ -612,2 +620,119 @@ if (options.toSql.csn) {

// "id" and/or "locale" prop(s)
function transformUserOption(options) {
// move the user option value under user.id if specified as a string
if (options.user && typeof options.user === 'string' || options.user instanceof String) {
options.user = { id: options.user };
}
// move the locale option(if provided) under user.locale
if (options.locale) {
options.user
? Object.assign(options.user, { locale: options.locale })
: options.user = { locale: options.locale };
delete options.locale;
}
}
}
// The twin of the toSql function but using CSN as a model
function toSqlWithCsn(model, options) {
const { warning, error, signal } = alerts(model);
// when toSql is invoked via the CLI - toSql options are under model.options
// ensure the desired format of the user option
if (model.options && model.options.toSql &&(model.options.toSql.user || model.options.toSql.locale)) {
transforUserOption(model.options.toSql);
}
// In case of API usage the options are in the 'options' argument
// put the OData specific options under the 'options.toSql' wrapper
// and leave the rest under 'options'
if (options && !options.toSql) {
_wrapRelevantOptionsForCmd(options, 'toSql');
}
// when the API funtion is used directly - toSql options are in options
// ensure the desired format of the user option
if (options && (options.toSql.user || options.toSql.locale)){
transforUserOption(options.toSql);
}
// Provide defaults and merge options with those from model
options = mergeOptions({ toSql : getDefaultBackendOptions().toSql }, model.options, options);
// Provide something to generate if nothing else was given (conditional default)
if (!options.toSql.src && !options.toSql.csn) {
options.toSql.src = 'sql';
}
// Backward compatibility for old naming modes
// FIXME: Remove after a few releases
if (options.toSql.names == 'flat') {
signal(warning`Option "{ toSql.names: 'flat' }" is deprecated, use "{ toSql.names: 'plain' }" instead`);
options.toSql.names = 'plain';
}
else if (options.toSql.names == 'deep') {
signal(warning`Option "{ toSql.names: 'deep' }" is deprecated, use "{ toSql.names: 'quoted' }" instead`);
options.toSql.names = 'quoted';
}
// Verify options
optionProcessor.verifyOptions(options, 'toSql').map(complaint => signal(warning`${complaint}`));
// FIXME: Currently, '--to-sql' implies transformation for HANA (transferring the options to forHana)
let forHanaOptions = options.toSql;
// Special case: For naming variant 'hdbcds' in combination with 'toSql', 'forHana' must leave
// namespaces alone (but must still flatten structs because we need the leaf element names).
if (options.toSql.names == 'hdbcds') {
forHanaOptions.keepNamespaces = true;
}
// It doesn't make much sense to use 'sqlite' dialect with associations
if (options.toSql.dialect == 'sqlite' && options.toSql.associations != 'joins') {
signal(warning`Option "{ toSql.dialect: 'sqlite' }" should always be combined with "{ toSql.assocs: 'joins' }"`);
}
if(options.toSql.dialect != 'hana') {
// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if(['quoted', 'hdbcds'].includes(options.toSql.names)) {
signal(error`Option "{ toSql.dialect: '${options.toSql.dialect}' }" cannot be combined with "{ toSql.names: '${options.toSql.names}' }"`);
}
// No non-HANA SQL for HDI
if(options.toSql.src === 'hdi') {
signal(error`Option "{ toSql.dialect: '${options.toSql.dialect}' }" cannot be combined with "{ toSql.src: '${options.toSql.src}' }"`);
}
}
// Because (even HANA) SQL cannot deal with associations in mixins that are published in the same view,
// the association processing must at least be 'mixin', even if callers specified 'assocs'
if (forHanaOptions.associations == 'assocs') {
forHanaOptions.associations = 'mixin';
}
// FIXME: Should not be necessary
forHanaOptions.alwaysResolveDerivedTypes = true;
let forSqlCsn = transformForHanaWithCsn(model, mergeOptions(options, { forHana : forHanaOptions } ));
// Assemble result
let result = {};
if (options.toSql.src) {
result = toSqlDdl(forSqlCsn, forSqlCsn.options);
}
if (options.toSql.csn) {
result._augmentedCsn = options.testMode ? sortCsn(forSqlCsn) : forSqlCsn;
result.csn = options.newCsn === false || options.testMode? sortCsn(forSqlCsn) : forSqlCsn;
}
// Transfer warnings (errors would have resulted in an exception before we come here)
if (forSqlCsn.messages && forSqlCsn.messages.length > 0) {
result.messages = forSqlCsn.messages;
}
return result;
// If among the options user, user.id or user.locale are specified via the CLI or
// via the API, then ensure that at the end there is a user option, which is an object and has(have)
// "id" and/or "locale" prop(s)
function transforUserOption(options) {

@@ -629,3 +754,2 @@ // move the user option value under user.id if specified as a string

}
// ----------- toRename -----------

@@ -799,2 +923,3 @@

toSql,
toSqlWithCsn,
toCsn,

@@ -801,0 +926,0 @@ getDefaultBackendOptions,

@@ -46,2 +46,5 @@ // Implementation of alerts

signal.error = error;
signal.warning = warning;
signal.ino = info;
return {

@@ -48,0 +51,0 @@ info, warning, error, // tag functions for the different alerts

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

arg = arg._artifact;
if (arg._outer)
arg = arg._outer;
let name = arg.name;

@@ -227,0 +229,0 @@ if (!name)

@@ -25,6 +25,6 @@ //

// for details.
function forEachMember( construct, callback ) {
function forEachMember( construct, callback, target ) {
let obj = construct.returns || construct; // why the extra `returns` for actions?
obj = obj.items || obj;
forEachGeneric( obj, 'elements', callback );
forEachGeneric( target || obj, 'elements', callback );
forEachGeneric( obj, 'enum', callback );

@@ -119,3 +119,3 @@ forEachGeneric( obj, 'foreignKeys', callback );

// Simply return if node is to be ignored
if (node === undefined || node._ignore)
if (node._ignore)
return undefined;

@@ -122,0 +122,0 @@ // Transform arrays element-wise

@@ -376,2 +376,5 @@ 'use strict'

if(options.betaMode && !options.testMode) {
result.push('Option --beta-mode was used. This option should not be used in productive scenarios!')
}
if (options && options.newCsn === false) {

@@ -381,2 +384,12 @@ result.push(`Option --old-csn was used. This option is deprecated and should not be used in productive scenarios!`)

if(options) {
['length', 'precision', 'scale'].forEach(facet => {
if(options[facet] && isNaN(options[facet])) {
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`);
} else {
options[facet] = parseInt(options[facet]);
}
});
}
if (command) {

@@ -383,0 +396,0 @@ let cmd = optionProcessor.commands[command];

@@ -10,5 +10,5 @@ 'use strict';

function checkNotEmptyOrOnlyVirtualElems(art, model) {
const { error, signal } = alerts(model);
const { info, signal } = alerts(model);
if (!art.abstract && emptyOrOnlyVirtualElements(art)) {
signal(error`A type or an entity definition cannot be empty or contain only virtual elements`, art.location);
signal(info`Dubious entity or type without non-virtual elements`, art.location, undefined, 'empty-entity-or-type');
}

@@ -15,0 +15,0 @@

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

const alerts = require('../base/alerts');
const { getDefinerFunctions } = require('../compiler/definer');
const { isComposition } = require('../model/modelUtils.js')

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

// must be at least one implicit one (i.e. the target must have keys).
// Generally, the cardinality of a managed association should not be to-many.
// Cardinality of a managed association should not be to-many, except for partial keys.
function checkManagedAssoc(elem, model) {

@@ -30,3 +29,3 @@ const { error, warning, signal } = alerts(model);

// 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) {
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) {
return;

@@ -39,12 +38,58 @@ }

}
// Cardinality to-many => warning
let targetMax = (elem.cardinality && elem.cardinality.targetMax && elem.cardinality.targetMax.val);
if (targetMax == '*' || Number(targetMax) > 1) {
// FIXME: convenience function (reuse in forHana)?
const context = { EntityhasPersistenceSkipOrTrueOrAbstract: elem._parent.abstract || ((elem._parent['@cds.persistence.skip'] && elem._parent['@cds.persistence.skip'].val !== null && elem._parent['@cds.persistence.skip'].val !== false)|| (elem._parent['@cds.persistence.exists'] && elem._parent['@cds.persistence.exists'].val !== null && elem._parent['@cds.persistence.exists'].val !== false))};
let assocType = isComposition(elem.type) ? "composition" : "association";
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, elem.location, undefined, 'to-many-no-on', context);
if(doForeignKeysCoverTargetKeys(foreignKeys,target._artifact.elements)) {
// FIXME: convenience function (reuse in forHana)?
const isNoDb = elem._parent.abstract || ((elem._parent['@cds.persistence.skip'] && elem._parent['@cds.persistence.skip'].val !== null && elem._parent['@cds.persistence.skip'].val !== false)|| (elem._parent['@cds.persistence.exists'] && elem._parent['@cds.persistence.exists'].val !== null && elem._parent['@cds.persistence.exists'].val !== false));
let assocType = isComposition(elem.type) ? "composition" : "association";
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, elem.location, undefined, isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on');
}
}
}
// checks if the foreign keys cover completely the target keys
function doForeignKeysCoverTargetKeys(foreignKeys,targetElements) {
let keysOfTarget = collectKeys(targetElements)
let flattenedFKs = [];
// flatten foreign keys into flattenedFKs
for(let name in foreignKeys) {
let fk = foreignKeys[name];
let fullKeyName = fk.targetElement.path.map(X => X.id).join(".")
let hasChildren=false;
keysOfTarget.forEach(K => {
if(K.startsWith(fullKeyName+".")) { // structured key found
hasChildren=true;
flattenedFKs.push(K);
}
})
if(!hasChildren)
flattenedFKs.push(fullKeyName);
}
// check keysOfTarget are included into flattenedFKs
let allKeysCovered=true;
keysOfTarget.forEach(K => {
if(!flattenedFKs.includes(K))
allKeysCovered=false;
})
return allKeysCovered;
// function walks elements recursively and returns all keys, structured ones are joined with a dot
function collectKeys(elements,parent=[],insideKey=false) {
let keys=[]
if(!elements) return keys;
for(let elementName in elements) {
let element = elements[elementName];
let tkey = element.key;
let isKey=false;
if(tkey && tkey.val || insideKey) {
if(!element.elements)
keys.push(parent.concat(elementName).join("."))
isKey=true;
}
keys.push(...collectKeys(element.elements,parent.concat(elementName), isKey));
}
return keys;
}
}
// Check element 'elem' for semantical errors involving virtual elements

@@ -277,14 +322,8 @@ function checkVirtualElement(elem, model) {

const { signal, warning } = alerts(model);
const definerFcts = getDefinerFunctions(model);
// if it is directly a localized element
if (elem.localized && elem.localized.val) {
// check if type is the builtin type cds.String (direct or indirect e.g. via typedefs)
let isBuiltinString = definerFcts.getOriginRecursive(elem, (art) => {
if (art.type && art.type.path && art.type.path.length) {
let type = art.type.path.map((part) => part.id).join('.');
return ["cds.String", "String"].includes(type) && model.definitions[type] && model.definitions[type].builtin;
}
return false;
});
if (!isBuiltinString) {
let type = elem._finalType; // could be the element typed with String(5)
if (type && type.type)
type = type.type._artifact;
if (!type || type.name.absolute !== 'cds.String') {
signal(warning`Element "${elem.name.absolute}.${elem.name.id}": "localized" may only be used in combination with type "String"`);

@@ -291,0 +330,0 @@ }

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

function semanticCheck(model) {
let options = model.options;
const { error, info, signal } = alerts(model);

@@ -84,2 +83,3 @@ const { checkActionOrFunction, checkActionOrFunctionParameter} = getFunctionAndActionChecks(model);

aspect: nothingToCheckYet,
event: nothingToCheckYet,
package: nothingToCheckYet,

@@ -113,5 +113,5 @@ }

// exception: they can live in cds.foundation
if (options.toHana) {
checkNotEmptyOrOnlyVirtualElems(art, model);
}
// Only an INFO during compile time,
// reclassified to errors in the backends
checkNotEmptyOrOnlyVirtualElems(art, model);
}

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

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

'@sql_mapping', // TODO: it is time that a 'header' attribute replaces 'version'
'$withLocalized',
],

@@ -122,2 +123,3 @@ },

dependencies: { test: TODO }, // TODO: describe
fileDep: { test: TODO },
$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] },

@@ -140,4 +142,4 @@ messages: {

},
$localizedElements: { kind: 'entity', test: isArray(TODO), inherits: 'element' },
$localized: { kind: 'entitiy', test: isObject },
_localized: { kind: true, test: TODO }, // true or artifact
_assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve
$magicVariables: { test: TODO },

@@ -158,3 +160,3 @@ $builtins: { test: TODO },

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

@@ -262,3 +264,11 @@ extern: {

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

@@ -280,3 +290,3 @@ test: isArray( pathItem ),

'context', 'service', 'view', 'entity', 'type', 'const', 'annotation',
'element', 'enum', 'action', 'function', 'param', 'key',
'element', 'enum', 'action', 'function', 'param', 'key', 'event',
'annotate', 'extend',

@@ -453,2 +463,3 @@ 'query', 'mixin',

$extra: { parser: true, test: TODO }, // for unexpectex properties in CSN
$withLocalized: { test: isBoolean },
};

@@ -553,3 +564,3 @@ let _noSyntaxErrors = null;

// eslint-disable-next-line no-nested-ternary
const op = node.op && node.op.val
const op = node.op && node.op.val // NOSONAR (isObject ensures that node.op does not throw)
? (queryOps[node.op.val] || 'op')

@@ -715,3 +726,7 @@ : (node.path) ? 'path' : 'none';

else if (!art.name.absolute || !model.definitions[art.name.absolute]) {
throw new Error( `Expected definition${ at( [ art, parent ], prop, name ) }` );
// TODO: sign ignored artifacts with $inferred = 'IGNORED'
if (parent.kind === 'source' || art.name.absolute && art.name.absolute.startsWith('localized.'))
standard( art, parent, prop, spec, name );
else
throw new Error( `Expected definition${ at( [ art, parent ], prop, name ) }` );
}

@@ -718,0 +733,0 @@ }

@@ -59,3 +59,5 @@ // The builtin artifacts of CDS

SESSION_USER: {},
// SQL-92: also SYSTEM_USER, just USER (is with parens in HANA SQL), VALUE
SYSTEM_USER: {}, // not in HANA
// SQL-92: USER - intentionally omitted (useful element name), most DB have USER()
// SQL-92: VALUE - intentionally omitted (useful element name), which DB supports this?
$user: {

@@ -71,11 +73,3 @@ elements: { id: {}, locale: {} },

// const magicVariablesHana = {
// CURRENT_CONNECTION: {},
// CURRENT_SCHEMA: {},
// CURRENT_TRANSACTION_ISOLATION_LEVEL: {},
// CURRENT_UTCDATE: {},
// CURRENT_UTCTIME: {},
// CURRENT_UTCTIMESTAMP: {},
// SYSUUID: {},
// };
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function

@@ -82,0 +76,0 @@ function initBuiltins( model ) {

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

$navElement: { normalized: 'element', $navigation: true },
event: { elements: true },
type: { elements: propExists, enum: propExists },

@@ -81,5 +82,6 @@ annotation: { elements: propExists, enum: propExists },

typeOf: { next: '_$next', assoc: false }, // warn
include: { reject: rejectNonStruct },
context: { reject: rejectNonContext },
target: { reject: rejectNonEntity, noDep: true },
include: { reject: rejectNonStruct, envFn: artifactsEnv },
context: { reject: rejectNonContext, envFn: artifactsEnv },
target: { reject: rejectNonEntity, noDep: true, envFn: artifactsEnv },
compositionTarget: { reject: rejectNonTarget, noDep: true, envFn: artifactsEnv },
// TODO: dep for (explicit+implicit!) foreign keys

@@ -91,3 +93,3 @@ element: { next: '__none_' }, // foreign key element

expr: {
next: '_$next', escape: 'param', noDep: true, assoc: 'nav',
next: '_$next', escape: 'param', assoc: 'nav',
},

@@ -140,3 +142,3 @@ unmanaged: { next: '_$next', escape: 'param', noDep: true }, // TODO: special assoc for only on user

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

@@ -146,2 +148,6 @@ : 'expected-entity';

function rejectNonTarget( art ) {
return (options.betaMode && art.kind === 'type') ? rejectNonStruct( art ) : rejectNonEntity( art );
}
function rejectNonSource( art, path ) {

@@ -678,3 +684,3 @@ if ([ 'view', 'entity' ].includes( art.kind ))

if (prop) { // extension or structure include
// TODO: consider ARRAY OF and RETURNS
// TODO: consider ARRAY OF and RETURNS, COMPOSITION OF type
if (!(prop in parent))

@@ -681,0 +687,0 @@ parent[prop] = Object.create(null);

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

let experimental = {}; // take note of all experimental annos that have been used
let deprecated = {}; // take note of all deprecated annos that have been used

@@ -101,5 +102,9 @@ return {

if (dictTerm["$experimental"] && !experimental[termName] && !options.betaMode) {
warningMessage(context, termName + " is experimental and can be changed or removed at any time, do not use productively!");
warningMessage(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"]);
deprecated[termName] = true;
}
}

@@ -123,3 +128,3 @@ return dictTerm;

const { error, warning, signal } = alerts(csn);
const { error, warning, info, signal } = alerts(csn);

@@ -195,2 +200,16 @@ // global variable where we store all the generated annotations

// 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(info`${fullMessage}`);
}

@@ -197,0 +216,0 @@

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

let [services, options] = initializeModel(model, _options, signal, error, warning, info);
const Edm = require('./edm.js')(options, signal, error);
const Edm = require('./edm.js')(options, signal, error, warning, info);

@@ -159,2 +159,6 @@

}
if(properties.filter(p => p.isKey).length === 0) {
signal(error`EntityType "${serviceName}/${EntityTypeName}" has no primary key`, ['definitions',entityCsn.name]);
}
// construct EntityType attributes

@@ -357,7 +361,7 @@ let attributes = { Name : EntityTypeName };

// (undefined !== false) still evaluates to true
if (!elementCsn.target.abstract && elementCsn['@odata.navigable'] !== false)
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
{
let navProp = new Edm.NavigationProperty(v, {
Name: elementName,
Type: fullQualified(elementCsn.target.name)
Type: fullQualified(elementCsn._target.name)
}, elementCsn);

@@ -364,0 +368,0 @@

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

module.exports = function (options, signal, error) {
module.exports = function (options, signal, error, warning, info) {

@@ -83,3 +83,3 @@ class Node

}
// virtual

@@ -132,3 +132,3 @@ toJSONattributes(json)

}
for (let p in this._xmlOnlyAttributes)
for (let p in this._xmlOnlyAttributes)
{

@@ -156,3 +156,3 @@ if (typeof this._xmlOnlyAttributes[p] !== 'object')

this._children.forEach(e =>
this._children.forEach(e =>
xml += e.toXML(indent, what) + '\n');

@@ -249,3 +249,3 @@ return xml;

}
innerXML(indent, what)

@@ -380,3 +380,3 @@ {

}
setAnnotations(annotations, schemaIndex=0)

@@ -387,3 +387,3 @@ {

}
toJSON()

@@ -550,3 +550,3 @@ {

because in V2 Parameters need to be rendered as sub elements
to Function Import. The ReturnType property is set in the
to Function Import. The ReturnType property is set in the
assembly code above (the invisible returnType is left undefined)

@@ -572,3 +572,3 @@ */

edmUtils.addTypeFacets(this, csn);
this.set( { _type: type, _isCollection: csn.items != undefined, _nullable: true });

@@ -597,3 +597,3 @@

class TypeBase extends Node
class TypeBase extends Node
{

@@ -629,11 +629,14 @@ constructor(v, attributes, csn, typeName='Type')

// optionally add @odata.MaxLength but only in combination with @odata.Type
// In absence of checks restrict @odata.Type to 'Edm.String'
// In absence of checks restrict @odata.Type to 'Edm.String' and 'Edm.Int[16,32,64]'
let odataType = csn['@odata.Type'];
let odataTypeMaxLen = csn['@odata.MaxLength'];
if(odataType === 'Edm.String')
{
this[typeName] = odataType;
if(odataTypeMaxLen)
this['MaxLength'] = odataTypeMaxLen;
if(csn['@odata.MaxLength']) {
this['MaxLength'] = csn['@odata.MaxLength'];
}
} else if(['Edm.Int16', 'Edm.Int32', 'Edm.Int64'].includes(odataType)) {
this[typeName] = odataType;
} else if(odataType) {
signal(info`@odata.Type: '${odataType}' is ignored, only 'Edm.String' and 'Edm.Int[16,32,64]' are allowed`, csn.$location);
}

@@ -643,3 +646,3 @@

this.set( { _type : this[typeName] });
// decorate for XML (not for Complex/EntityType)

@@ -651,3 +654,3 @@ if(this._isCollection)

}
toJSONattributes(json)

@@ -774,3 +777,3 @@ {

// but not if Edm.DateTime is the result of a regular cds type mapping
if(this.Type == 'Edm.DateTime'
if(this.Type == 'Edm.DateTime'
&& (typecsn.type != 'cds.DateTime' && typecsn.type != 'cds.Timestamp'))

@@ -838,6 +841,6 @@ this.setXml( { 'sap:display-format' : "Date" } );

{
if (Property.SAP_Annotation_Attribute_WhiteList.includes(p))
if (Property.SAP_Annotation_Attribute_WhiteList.includes(p))
this.setXml( { ['sap:' + p.slice(5).replace(/\./g, '-')] : csn[p] });
}
}
}
this.set({isKey: csn.key != undefined });

@@ -895,3 +898,3 @@ }

_isCollection: this.isToMany(),
_targetCsn: csn.target
_targetCsn: csn._target
} );

@@ -934,3 +937,3 @@

if (this.v2 && this.isNotNullable()) {
// in V2 not null must be expressed with target cardinality of 1 or more,
// in V2 not null must be expressed with target cardinality of 1 or more,
// store Nullable=false and evaluate in determineMultiplicity()

@@ -1003,3 +1006,3 @@ delete this.Nullable;

return new NavigationPropertyBinding(this._v,
{ Path: this.Name, Target: this._csn.target.name.replace(namespace, '') }
{ Path: this.Name, Target: this._csn._target.name.replace(namespace, '') }
);

@@ -1035,3 +1038,3 @@ }

// No Kind: AnnotationBase is base class for Thing and ValueThing with dynamic kinds,
// this requires an explicit constructor as the kinds cannot be blacklisted in
// this requires an explicit constructor as the kinds cannot be blacklisted in
// Node.toJSON()

@@ -1055,3 +1058,3 @@ toJSON()

let constExpr = [ ...inlineConstExpr,
let constExpr = [ ...inlineConstExpr,
'AnnotationPath', 'ModelElementPath', 'NavigationPropertyPath', 'PropertyPath', 'Path',

@@ -1107,3 +1110,3 @@ 'EnumMember', 'EnumMember@odata.type' ];

super(v, { Target: target });
if (this.v2)
if (this.v2)
this.setXml( { xmlns : "http://docs.oasis-open.org/odata/ns/edm" } );

@@ -1225,3 +1228,3 @@ }

{
Object.defineProperty(this, 'kind',
Object.defineProperty(this, 'kind',
{ get: function() { return kind; }});

@@ -1231,3 +1234,3 @@ }

class ValueThing extends Thing
class ValueThing extends Thing
{

@@ -1393,4 +1396,1 @@ constructor(v, kind, value)

}

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

if(entityCsn._containerEntity) {
parameterCsn._containerEntity = [];
setProp(parameterCsn, '_containerEntity', []);
for(let c of entityCsn._containerEntity) {

@@ -130,10 +130,10 @@ parameterCsn._containerEntity.push((c==entityCsn.name)?parameterCsn.name:c);

name: parameterToOriginalAssocName,
target: entityCsn,
target: entityCsn.name,
type: 'cds.Association',
_partnerCsn: [],
cardinality: { src: 1, min: 0, max: '*' }
};
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn);
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_parameterCsn', []);
model.definitions[parameterCsn.name] = parameterCsn;
// modify the original parameter entity with backlink and new name

@@ -147,3 +147,3 @@ entityCsn.name = originalEntityName;

name: backlinkAssocName,
target: parameterCsn,
target: parameterCsn.name,
type: 'cds.Association',

@@ -153,2 +153,4 @@ _partnerCsn: [],

};
setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn);
setProp(entityCsn.elements[backlinkAssocName], '_parameterCsn', []);
}

@@ -239,6 +241,6 @@ }

return;
if(typeof element.target === "string") {
if(!element._target) {
let target = model.definitions[element.target];
if(target) {
element.target = target;
setProp(element, '_target', target);
}

@@ -266,8 +268,8 @@ else {

return;
if(typeof element.target === "string") {
if(!element._target) {
throw Error('Expect target to be resolved, parent: ' + struct.name + ', assoc: ' + element.name + ', target: ' + element.target);
}
// in case this is a forward assoc, store the backlink partneres here, _partnerCsn.length > 1 => error
element._partnerCsn = [];
element.target.$proxies = [];
setProp(element, '_partnerCsn', []);
setProp(element._target, '$proxies', []);

@@ -302,16 +304,16 @@ //forward annotations from managed association element to its foreign keys

// (array because the contanee may contained more then once)
if (!element.target._containerEntity) {
element.target._containerEntity = [];
if (!element._target._containerEntity) {
setProp(element._target, '_containerEntity', []);
}
// add container only once per containee
if (!element.target._containerEntity.includes(container.name)) {
element.target._containerEntity.push(container.name);
if (!element._target._containerEntity.includes(container.name)) {
element._target._containerEntity.push(container.name);
// Mark associations in the containee pointing to the container (i.e. to this entity)
for (let containeeElementName in element.target.elements) {
let containeeElement = element.target.elements[containeeElementName];
if (containeeElement.target && containeeElement.target.name) {
for (let containeeElementName in element._target.elements) {
let containeeElement = element._target.elements[containeeElementName];
if (containeeElement._target && containeeElement._target.name) {
// If this is an association that points to a container (but is not by itself contained,
// which would indicate the top role in a hierarchy) mark it with '_isToContainer'
if (containeeElement.target.name == container.name && !containeeElement['@odata.contained']) {
containeeElement._isToContainer = true;
if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) {
setProp(containeeElement, '_isToContainer', true);
}

@@ -328,3 +330,3 @@ }

if (element._ignore) return;
element._constraints = getReferentialConstraints(element, signal, warning);
setProp(element, '_constraints', getReferentialConstraints(element, signal, warning));

@@ -375,3 +377,3 @@ // only in V2 we must set the target cardinality of the backlink to the forward:

/*
if(element.target['@cds.autoexpose'] === false) {
if(element._target['@cds.autoexpose'] === false) {
// :TODO: Also _ignore foreign keys to association?

@@ -383,3 +385,3 @@ foreach(struct.elements,

element._ignore = true;
signal(info`${element.type.replace('cds.', '')} "${element.name}" excluded, target "${element.target.name}" is annotated '@cds.autoexpose: ${element.target['@cds.autoexpose']}'`,
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]);

@@ -390,42 +392,52 @@ return;

// if target is outside defining service, create/reuse proxy
if(myServiceName !== whatsMyServiceName(element.target.name)) {
// search for eventually existing proxy
let proxy = element.target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0];
if(!proxy) {
let name = myServiceName + '.' + element.target.name.split('.').join('_') + '_Proxy_0';
proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) };
let hasKeys = false;
foreach(element.target.elements, e => e.key, e => {
// :TODO: getFinalBaseType, resolve structs
// Omit associations (no navigation properties)
if (isAssocOrComposition(e.type)) {
e._ignore = true;
}
if(isStructured(e)) {
e._ignore = true;
// parameters/elements
signal(info`Structured types not yet supported as primary keys of proxy entity type "${name}" for unexposed association target "${element.target.name}"`,
if(myServiceName !== whatsMyServiceName(element._target.name)) {
if(options.betaModeProxy) {
// search for eventually existing proxy
let proxy = element._target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0];
if(!proxy) {
let name = myServiceName + '.' + element._target.name.split('.').join('_') + '_Proxy_0';
proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) };
let hasKeys = false;
foreach(element._target.elements, e => e.key, e => {
// :TODO: getFinalBaseType, resolve structs
// Omit associations (no navigation properties)
let ignore = false;
if (isAssocOrComposition(e.type)) {
ignore = true;
}
if(isStructured(e)) {
ignore = true;
// parameters/elements
signal(info`Structured types not yet supported as primary keys of proxy entity type "${name}" for unexposed association target "${element._target.name}"`,
['definitions', struct.name, 'elements', element.name]);
}
if(!ignore) {
// clone elements and strip of all annotations
proxy.elements[e.name] = cloneCsn(e);
Object.keys(proxy.elements[e.name]).forEach(k => { if(k[0] === '@') delete proxy.elements[e.name][k] } );
hasKeys = true;
}
});
if(!hasKeys) {
element._ignore = true;
signal(info`Could not create proxy entity type "${name}" for unexposed association target "${element._target.name}", because target has no primary keys`,
['definitions', struct.name, 'elements', element.name]);
return;
}
// clone elements and strip of all annotations
proxy.elements[e.name] = cloneCsn(e);
hasKeys = true;
});
if(!hasKeys) {
element._ignore = true;
delete element.target.$proxies;
signal(info`Could not create proxy entity type "${name}" for unexposed association target "${element.target.name}", because target has no primary keys"`,
signal(info`Created proxy entity type "${name}" for unexposed association target "${element._target.name}"`,
['definitions', struct.name, 'elements', element.name]);
return;
// wire up proxy
model.definitions[name] = proxy;
element._target.$proxies.push(proxy);
}
signal(info`Created proxy entity type "${name}" for unexposed association target "${element.target.name}"`,
element._target = proxy;
// remove referential constraints
element._constraints.constraints = Object.create(null);
}
else {
element._ignore = true;
signal(info`Association is auto-excluded, as target "${element._target.name}" is outside any service`,
['definitions', struct.name, 'elements', element.name]);
// wire up proxy
model.definitions[name] = proxy;
element.target.$proxies.push(proxy);
return;
}
element.target = proxy;
// remove referential constraints
element._constraints.constraints = Object.create(null);
}

@@ -487,2 +499,18 @@ });

}
// etag requires Core.OptimisticConcurrency to be set in V4 (cap/issues#2641)
// Oliver Heinrich mentions in the issue that the Okra runtime must be set to a
// concurrent runtime mode by the caller, if the annotation is added this late,
// it doesn't appear in the forOData processed CSN, meaning that the
// runtime cannot set that okra flag (alternatively the runtime has to search
// for @[odata|cds].etag anntotions...
if(options.isV4())
{
if(element['@odata.etag'] == true || element['@cds.etag'] == true) {
// don't put element name into collection as per advice from Ralf Handl, as
// no runtime is interested in the property itself, it is sufficient to mark
// the entity set.
struct['@Core.OptimisticConcurrency'] = (struct['@Core.OptimisticConcurrency'] || []);//.push(element.name);
}
}

@@ -562,24 +590,5 @@ // nested functions begin

}
mapAnnotationAssignment(struct, undefined, PDMSemantics());
}
// nested functions begin
// nested functions begin
function PDMSemantics()
{
let dict = Object.create(null);
/*
dict['@PDM.xxx1'] = [ '@sap.pdm-semantics' ];
dict['@PDM.xxx2'] = [ '@sap.pdm-business-object' ];
dict['@PDM.xxx3'] = [ '@sap.pdm-business-node' ];
dict['@PDM.xxx8'] = [ '@sap.deletable' ];
dict['@PDM.xxx8'] = [ '@sap.updatable' ];
// respect flattened anntotation $value
Object.keys(dict).forEach(k => dict[k+'.$value'] = dict[k]);
*/
return dict;
}
function transformAnalyticalModel(struct)

@@ -593,3 +602,3 @@ {

let elements = Object.create(null);
elements[keyName] = { key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false };
elements[keyName] = { key : true, type : 'cds.String', '@sap.sortable':false, '@sap.filterable':false, '@UI.Hidden': true };
forAll(struct.elements, (e,n) =>

@@ -596,0 +605,0 @@ {

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

if(partner) {
let originAssocCsn = assocCsn.target.elements[partner];
let originAssocCsn = assocCsn._target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)

@@ -155,4 +155,5 @@ originAssocCsn = assocCsn.originalTarget.elements[partner];

if(originAssocCsn) {
if(originAssocCsn.target != assocCsn._parent) {
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn.target.name}"`, ['definitions', parentArtifactName]);
if(originAssocCsn._target != assocCsn._parent) {
isBacklink = false;
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn._target.name}"`, ['definitions', parentArtifactName]);
}

@@ -162,3 +163,3 @@ if(isAssociationOrComposition(originAssocCsn)) {

// as they are primary keys of the other entity as well
if(!assocCsn.target.isParamEntity && originAssocCsn.key) {
if(!assocCsn._target.isParamEntity && originAssocCsn.key) {
if(originAssocCsn.keys) {

@@ -196,3 +197,3 @@ for(let fk of originAssocCsn.keys) {

{
signal(warning`Cannot resolve backlink to ${assocCsn.target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]);
signal(warning`Cannot resolve backlink to ${assocCsn._target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]);
}

@@ -206,9 +207,9 @@ }

if(!assocCsn.target.isParamEntity) {
if(!assocCsn._target.isParamEntity) {
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
let dependentEntity = assocCsn._parent;
let principalEntity = assocCsn.target;
let principalEntity = assocCsn._target;
if(assocCsn.type == 'cds.Composition') {
principalEntity = assocCsn._parent;
dependentEntity = assocCsn.target;
dependentEntity = assocCsn._target;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]

@@ -238,6 +239,6 @@ Object.keys(result.constraints).forEach(cn => {

// FIXME: If path is something structured, perform a path resolution (or use augmented CSN)
if(!assocCsn.target.isParamEntity && assocCsn.keys) {
if(!assocCsn._target.isParamEntity && assocCsn.keys) {
for(let fk of assocCsn.keys) {
let realFk = assocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn.target.elements[fk.ref[0]];
let pk = assocCsn._target.elements[fk.ref[0]];
if(pk && pk.key && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore']))

@@ -254,3 +255,3 @@ {

// continue with multiplicity
if(assocCsn.target.isParamEntity)
if(assocCsn._target.isParamEntity)
{

@@ -451,2 +452,4 @@ result.constraints = Object.create(null);

}
if(cdsType === 'cds.DecimalFloat' || cdsType === 'cds.hana.SMALLDECIMAL')
signal(signal.warning`"OData V2 does not support ${cdsType}"`, csn.$location);
}

@@ -462,16 +465,22 @@ else // isV4

function addTypeFacets(node, csn, isV2=false)
function addTypeFacets(node, csn)
{
if (csn.length)
// const isV2 = node.v2;
if (csn.length != null)
node.MaxLength = csn.length;
if (csn.scale)
if (csn.scale !== undefined)
node.Scale = csn.scale;
else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
node.Scale = 'floating';
if (csn.precision)
// else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
// node.Scale = 'floating';
if (csn.precision != null)
node.Precision = csn.precision;
else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
node.Precision = 16;
// else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2)
// node.Precision = 16;
else if (csn.type === 'cds.Timestamp' && node.Type === 'Edm.DateTimeOffset')
node.Precision = 7;
// else if(csn.type === 'cds.DecimalFloat' && !isV2) {
// node.Scale = 'floating'; // only upcoming OData v4.01
// node.Precision = 34;
// }
// Unicode unused today

@@ -478,0 +487,0 @@ if(csn.unicode)

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

}
if(getLastElement(PATH) === "payload") {
transformers.augmentElement(key, node, PATH);
}
if(getLastElement(PATH) === "enum") {

@@ -43,0 +46,0 @@ transformers.augmentEnumItem(key, node, PATH);

@@ -226,2 +226,4 @@ // contains query relevant augmentor functions

value = newValue(X.val, orderByPath.concat(iX,"val"));
} else if(X.func) {
value = augmentExpressionItem(X, iX, orderByPath);
} else {

@@ -389,3 +391,3 @@ throw Error("Unknown orderBy structure: " + JSON.stringify(X))

op:{val: "call", location},
func: {path:[{id:val.func, location}]}, // to-csn.js has TODO XSN: remove op: 'call', func is no path
func: {path:[{id:val.func, location:U.newLocation(path.concat("func"))}]}, // to-csn.js has TODO XSN: remove op: 'call', func is no path
location

@@ -392,0 +394,0 @@ };

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

function modifyPayload(node) {
node["elements"]=node["payload"]
delete node["payload"]
}
////////////////////////////////////
function augmentElement(name, node, Path) {

@@ -394,2 +399,3 @@ let path = Path.concat(name)

elements: ignore,
payload:modifyPayload,
enum: ignore,

@@ -396,0 +402,0 @@ keys: (node, name, path) => arrayAsDict(node, name, path, 'foreignKeys'),

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

function cbPayload(O) {
return [
nullProto(O.elements, cbPayload),
nullProto(O.enum, cbEnum)
].concat(directItems(O.items))
}
function directItems(items) {

@@ -61,2 +68,3 @@ if(items!==undefined) {

nullProto(O.elements, cbElement),
nullProto(O.payload, cbPayload),
nullProto(O.enum, cbEnum),

@@ -63,0 +71,0 @@ nullProto(O.actions, cbAction),

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

cardinality: standard, // also for pathItem: after 'id', before 'where'
target: artifactRef,
target,
foreignKeys: renameTo( 'keys', dictAsArray ), // XSN: rename?

@@ -63,3 +63,3 @@ enum: insertOrderDict,

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

@@ -78,3 +78,3 @@ returns: standard, // storing the return type of actions

definitions: sortedDict,
extensions: standard, // is array - TODO: sort
extensions, // is array
messages, // consider compactQuery / compactExpr

@@ -121,6 +121,10 @@ options: ignore,

op: ['join','func','xpr'],
quantifier: ['some','any','distinct', 'ref','param', 'val','literal', 'SELECT','SET'], // 'all' explicitly listed
quantifier: ['some','any','distinct', 'ref','_links','_art','_scope', 'param', 'val','literal', 'SELECT','SET'], // 'all' explicitly listed
type: ['_type'],
target: ['_target'],
includes: ['_includes'],
foreignKeys: ['keys'],
exclude: ['excluding'],
limit: ['rows'], //'offset',
elements: ['payload', '$elements'],
sourceMax: ['src'],

@@ -164,3 +168,3 @@ targetMin: ['min'],

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

@@ -204,6 +208,5 @@

set( 'definitions', csn, model );
if (!csn_gensrc)
set( 'extensions', csn, model );
else
extensions( csn, model );
const exts = extensions( model.extensions || [], csn, model );
if (exts.length)
csn.extensions = exts;
set( 'messages', csn, model );

@@ -236,5 +239,7 @@ if (model.version)

function extensions( csn, model ) {
let extensions = (model.extensions) ? standard( model.extensions ) : [];
for (let name in model.definitions) {
function extensions( node, csn, model ) {
const exts = node.map( standard ).sort( (a, b) => a.annotate.localeCompare( b.annotate ) );
if (!csn_gensrc)
return exts;
for (let name of Object.keys( model.definitions ).sort()) {
let art = model.definitions[name];

@@ -250,7 +255,6 @@ // in definitions (without redef) with potential inferred elements:

if (Object.keys( annotate ).length > 1)
extensions.push( annotate );
exts.push( annotate );
}
}
if (extensions.length)
csn.extensions = extensions;
return exts;
}

@@ -304,9 +308,20 @@

function target( val ) {
if (!csn_gensrc)
// target._artifact is different to _artifact from path with explicit target
// to model entity with @cds.autoexpose, also different for COMPOSITION OF type/{}
return val._artifact && val._artifact.name.absolute;
else if (!val.elements)
return artifactRef( val, true );
else
return standard( val );
}
function elements( dict, csn, node ) {
if (csn.from) // with SELECT
if (csn.from || csn_gensrc && (node.query || node.type)) // with SELECT or inferred elements with gensrc
return undefined;
if (!csn_gensrc || !node.query && !node.type)
if (node.kind !== 'event')
return insertOrderDict( dict );
else
return undefined;
csn.payload = insertOrderDict( dict );
return undefined;
}

@@ -340,3 +355,4 @@

function location( loc, csn, xsn ) {
if (xsn.kind && xsn.kind.charAt() !== '$' && !xsn.$inferred && xsn.kind !== 'query') {
if (xsn.kind && xsn.kind.charAt() !== '$' && xsn.kind !== 'query' &&
(!xsn.$inferred || !xsn._main)) {
// Also include $location for elements in queries (if not via '*')

@@ -420,3 +436,3 @@ let l = xsn.name && xsn.name.location || loc;

return 'entity';
if (['element', 'key', 'enum', 'annotate', 'query', '$tableAlias'].includes(k))
if (['element', 'key', 'param', 'enum', 'annotate', 'query', '$tableAlias'].includes(k))
return undefined;

@@ -444,3 +460,3 @@ return k;

// to model entity with @cds.autoexpose
let art = !csn_gensrc && index===length && node._artifact || path[ index-1 ]._artifact;
let art = path[ index-1 ]._artifact;
id = (art instanceof Array ? art[0] : art).name.absolute;

@@ -515,3 +531,3 @@ }

const magicFunctions = // TODO: calculate from compiler/builtins.js (more with HANA?):
['CURRENT_DATE','CURRENT_TIME','CURRENT_TIMESTAMP','CURRENT_USER','SESSION_USER'];
['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

@@ -522,3 +538,3 @@

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

@@ -620,5 +636,5 @@ ? nav.name.alias

const select = { SELECT: standard( node ) };
const elements = node.elements;
if (elements && node._main && node._main.$queries && node !== node._main.$queries[0])
setHidden( select, 'elements', elements );
const elems = node.elements;
if (elems && node._main && node._main.$queries && node !== node._main.$queries[0])
setHidden( select, 'elements', elements( elems, select, node ) );
return addLocation( node.location, select );

@@ -625,0 +641,0 @@ }

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

function intervalSetToArray( recognizer, expected ) {
function intervalSetToArray( recognizer, expected, excludesForNextToken ) {
// similar to `IntervalSet#toTokenString`

@@ -327,6 +327,11 @@ var names = [];

}
if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken)
names = names.filter( n => !recognizer.$adaptExpectedExcludes.includes( n ) );
else if (names.includes("';'"))
if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) {
let excludes = (excludesForNextToken && recognizer.$adaptExpectedExcludes[0] instanceof Array)
? recognizer.$adaptExpectedExcludes[0]
: recognizer.$adaptExpectedExcludes;
names = names.filter( n => !excludes.includes( n ) );
}
else if (names.includes("';'")) {
names = names.filter( n => n !== "'}'" );
}
names.sort( (a, b) => tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1 );

@@ -408,2 +413,3 @@ return names;

expected, lookBusy, calledRules, true, true );
return intervalSetToArray( recognizer, expected, true );
}

@@ -410,0 +416,0 @@ else if (offendingToken && recognizer.$nextTokensContext &&

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

setMaxCardinality,
handleComposition,
hanaFlavorOnly,

@@ -153,2 +154,5 @@ betaModeOnly,

// Using this function "during ATN decision making" has no effect
// In front of an ATN decision, you might specify dedicated excludes
// for non-LA1 tokens via a sub-array in excludes[0].
function excludeExpected( excludes ) {

@@ -160,2 +164,3 @@ if (excludes) {

this.$nextTokensToken = t;
this.$nextTokensContext = null;
}

@@ -469,11 +474,25 @@ }

function setMaxCardinality( art, token, max ) {
function setMaxCardinality( art, token, max, inferred ) {
let location = this.tokenLocation( token );
if (art.cardinality) {
if (!art.cardinality) {
art.cardinality = { targetMax: Object.assign( {location}, max ), location };
if (inferred)
art.cardinality.$inferred = inferred;
}
else if (!inferred) {
this.message( 'syntax-repeated-cardinality', location, { token: token.text },
'Warning', 'The target cardinality has already been specified - ignored $(TOKEN)' );
}
else {
art.cardinality = { targetMax: Object.assign( {location}, max ), location };
}
function handleComposition( cardinality, isComposition) {
if (isComposition && !cardinality) {
const lt1 = this._input.LT(1).type;
const la2 = this._input.LT(2);
if (la2.text === '{' && (lt1 === this.constructor.MANY || lt1 === this.constructor.ONE))
la2.type = this.constructor.COMPOSITIONofBRACE;
}
const brace1 = (isComposition) ? 'COMPOSITIONofBRACE' : "'{'";
const manyOne = (cardinality) ? ['MANY', 'ONE'] : [];
this.excludeExpected( [["'}'", 'COMPOSITIONofBRACE'], brace1, ...manyOne] );
}

@@ -480,0 +499,0 @@

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

const extensions = ['.cds', '.json'];
const extensions = ['.cds', '.csn', '.csn.json', '.json'];

@@ -68,0 +68,0 @@ function packageFilter( pkg ) {

@@ -19,5 +19,5 @@ // CSN functionality for resolving references

// used as reference
// - csnPath: an array of strings (e.g. ['definitions', 'S.E', 'query',
// 'SELECT', 'from']); they are the property names which navigate from the
// CSN root to the current node in the CSN
// - csnPath: an array of strings and numbers (e.g. ['definitions', 'S.E',
// 'query', 'SELECT', 'from', 'ref', 0]); they are the property names and
// array indexes which navigate from the CSN root to the current node.

@@ -29,10 +29,10 @@ 'use strict'

// Properties in which artifact or members are defined - next property in the
// "cPath" (array of property names starting at the CSN) 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, also includes 'args'
// because the value can be a dictionary
const artifactProperties
= ['elements', 'columns', 'keys', 'enum', 'params', 'actions', 'definitions', 'extensions'];
= ['elements', 'columns', 'keys', 'enum', 'params', 'actions', 'payload', 'definitions', 'extensions'];
function csnRefs( csn ) {
const views = Object.create(null); // cache for views - OK to add it to CSN?
return { effectiveType, artifactRef, inspectRef };
return { effectiveType, artifactRef, inspectRef, queryOrMain };

@@ -72,3 +72,3 @@ // Return the type relevant for name resolution, i.e. the object which has a

const art = csn.definitions[ ref ] || notFound;
if (art)
if (art !== undefined)
return art;

@@ -84,3 +84,3 @@ }

return art;
else if (!tail.length && notFound)
else if (!tail.length && notFound !== undefined)
return notFound;

@@ -130,4 +130,9 @@ }

// it here would be too expensive)
if (scope === 'ref-where')
if (scope === 'ref-where'){
if(!whereEntity){
// Caller SHOULD catch this and then try again with a correct whereEntity
throw new Error("Scope '" + scope + "' but no entity was provided.");
}
return expandRefPath( path, whereEntity.elements[ head ], scope );
}
// 5,6,7:

@@ -141,2 +146,3 @@ if (!query)

// With explicitly provided $env:
if (typeof obj.$env === 'number') { // head is mixin or table alias name

@@ -147,2 +153,7 @@ const s = (obj.$env) ? queries[ obj.$env - 1 ].SELECT : select;

}
else if (typeof obj.$env === 'string') {
const source = queryOrMain( select._sources[ obj.$env ], main );
return expandRefPath( path, source.elements[ head ], 'source' );
}
// 8:

@@ -165,6 +176,4 @@ if (scope !== 'from-on' && select.mixin) {

}
if (typeof select.$alias === 'string' || typeof obj.$env === 'string') {
const source = select._sources[ obj.$env || select.$alias ];
if (!source.elements)
throw new Error( 'Source elements not available: ' + Object.keys( source ).join('+'))
if (typeof select.$alias === 'string') { // with unique source
const source = queryOrMain( select._sources[ select.$alias ], main );
return expandRefPath( path, source.elements[ head ], 'source' );

@@ -282,8 +291,9 @@ }

for (const prop of csnPath) {
if (isName) { // name/index of artifact/member
isName = false;
if (isName !== 'args') {
// if (isName || Array.isArray( obj )) { // array item, name/index of artifact/member, (named) argument
if (isName || typeof prop !== 'string') { // array item, name/index of artifact/member, (named) argument
if (typeof isName === 'string') {
parent = art;
art = obj[ prop ];
}
isName = false;
}

@@ -295,3 +305,3 @@ else if (artifactProperties.includes( prop )) {

else if (prop === 'args') {
isName = prop;
isName = true; // for named arguments
if (scope === 'orderBy')

@@ -310,14 +320,18 @@ scope = 'orderBy-xpr'; // no need to extra 'orderBy-args'

}
else if (typeof prop === 'string') {
if (prop !== 'xpr')
scope = prop;
else if (scope === 'orderBy')
scope = 'orderBy-xpr';
else if (prop !== 'xpr') {
scope = prop;
}
else if (scope === 'orderBy') {
scope = 'orderBy-xpr';
}
obj = obj[ prop ];
}
// console.log( 'CPATH:', csnPath, scope, obj );
return { obj, parent, query, scope };
}
csnRefs.traverseQuery = traverseQuery;
csnRefs.artifactProperties = artifactProperties;
csnRefs.implicitAs = implicitAs;
module.exports = csnRefs;

@@ -258,12 +258,2 @@ 'use strict'

/**
* Returns true if the element has a specific annotation set to the given value.
* @param artifact - the artifact object
* @param annotationName - the name of the annotation (including the @)
* @param value - the value
*/
function hasBoolAnnotation(artifact, annotationName, value = true) {
return artifact[annotationName] === value;
}
/**
* Clone 'node', transforming nodes therein recursively. Object 'transformers' is expected

@@ -476,2 +466,3 @@ * to contain a mapping of property 'key' names to transformer functions. The node's properties

forEachGeneric( obj, 'elements', callback, obj_path );
forEachGeneric( obj, 'payload', callback, obj_path );
forEachGeneric( obj, 'enum', callback, obj_path );

@@ -501,3 +492,3 @@ forEachGeneric( obj, 'foreignKeys', callback, obj_path );

let dictObj = dict[name];
if (dictObj instanceof Array) // redefinitions
if (dictObj instanceof Array) // redefinitions - not in CSN!
dictObj.forEach( (o) => callback( o, name, prop, path.concat([prop, name])) )

@@ -515,9 +506,21 @@ else

}
for (let name in node) {
// If ref found within a non-dictionary, call callback
if (name === 'ref' && Object.getPrototypeOf(node)) {
callback(node.ref, node, path);
if(node._ignore){
return;
}
if(Array.isArray(node)){
for (let i = 0; i < node.length; i++) {
// Descend recursively
forEachRef(node[i], callback, path.concat([i]));
}
// Descend recursively
forEachRef(node[name], callback, path.concat([name]));
} else {
for (let name in node) {
// If ref found within a non-dictionary, call callback
if (name === 'ref' && Object.getPrototypeOf(node)) {
callback(node.ref, node, path);
}
// Descend recursively
forEachRef(node[name], callback, path.concat([name]));
}
}

@@ -564,3 +567,3 @@ }

if(Array.isArray(expr)){
for(let i = 0; i < expr.length -1; i++){
for(let i = 0; i < expr.length; i++){
traverseQuery(expr[i], cb, p.concat([prop, i]));

@@ -581,3 +584,3 @@ }

else if (from.args){ // join
for(let i = 0; i < from.args.length-1; i++){
for(let i = 0; i < from.args.length; i++){
traverseFrom(from.args[i], callback, path.concat(['args', i]));

@@ -591,3 +594,13 @@ }

/**
* Returns true if the element has a specific annotation set to the given value.
* @param artifact - the artifact object
* @param annotationName - the name of the annotation (including the @)
* @param value - the value
*/
function hasBoolAnnotation(artifact, annotationName, value = true) {
return artifact[annotationName] === value;
}
module.exports = {

@@ -602,3 +615,4 @@ getUtils,

forEachRef,
forAllQueries
forAllQueries,
hasBoolAnnotation
};

@@ -58,2 +58,5 @@ // Make internal properties of the XSN / augmented CSN visible

_entities: artifactIdentifier, // array
_localized: artifactIdentifier, // or true
_assocSources: artifactIdentifier, // array
_oldAssocSources: artifactIdentifier, // array
$compositionTargets: d => d, // dictionary( boolean )

@@ -60,0 +63,0 @@ _ancestors: artifactIdentifier, // array

@@ -15,3 +15,3 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper');

.option('-o, --out <dir>')
.option('-l, --lint-mode')
.option(' --lint-mode')
.option(' --fuzzy-csn-error')

@@ -21,2 +21,3 @@ .option(' --trace-parser')

.option(' --trace-fs')
.option('-E, --enrich-csn')
.option('-R, --raw-output')

@@ -32,3 +33,5 @@ .option(' --internal-msg')

.option(' --test-mode')
.option(' --old-localized-conv')
.option('--precision <prec>')
.option('--scale <scale>')
.option('--length <length>')
.help(`

@@ -47,12 +50,18 @@ Usage: cdsc <command> [options] <file...>

-v, --version Display version number and exit
-w, --warning <level> Show warnings up to <level>
-w, --warning <level> Show messages up to <level>
0: Error
1: Warnings
2: (default) Info
3: Debug
--show-message-id Show message ID in error, warning and info messages
-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
-l, --lint-mode Generate nothing, just produce messages if any (for use by editors)
--lint-mode Generate nothing, just produce messages if any (for use by editors)
--fuzzy-csn-error Report free-style CSN properties as errors
-- Indicate the end of options (helpful if source names start with "-")
Type options
--precision <prec> Default precision for 'cds.Decimal'
--scale <scale> Default scale for 'cds.Decimal'
--length <length> Default 'length' for 'cds.String'
Diagnostic options

@@ -64,2 +73,3 @@ --trace-parser Trace parser

Internal options (for testing only, may be changed/removed at any time)
-E, --enrich-csn Show non-enumerable CSN properties and locations of references
-R, --raw-output Write raw augmented CSN and error output to <stdout>, long!

@@ -75,3 +85,2 @@ --internal-msg Write raw messages with call stack to <stdout>/<stderr>

in errors, sort properties in CSN, omit version in CSN)
--old-localized-conv Create localized convenience views on top of the original view

@@ -78,0 +87,0 @@ Backward compatibility options (deprecated, do not use)

@@ -0,3 +1,35 @@

// Common render functions for toCdl.js and toSql.js
const functionsWithoutParams = {
hana: {
CURRENT_CONNECTION: {},
CURRENT_SCHEMA: {},
CURRENT_TRANSACTION_ISOLATION_LEVEL: {},
CURRENT_UTCDATE: {},
CURRENT_UTCTIME: {},
CURRENT_UTCTIMESTAMP: {},
SYSUUID: {},
}
}
// Dialect = 'hana' (only relevance at the moment) | 'cap' | 'sqlite'
function renderFunc( node, dialect, renderArgs, parenNameToUpper = false ) {
if (funcWithoutParen( node, dialect ))
return node.func;
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 )})`;
}
function funcWithoutParen( node, dialect ) {
if (!node.args)
return true;
if (!Array.isArray( node.args ) || node.args.length)
return false;
const specials = functionsWithoutParams[dialect];
return specials && specials[node.func.toUpperCase()];
}
/**
* Get the $location from an object and make it look like the XSN location
* Get the $location from an object and make it look like the XSN location - delete - not necessary
*

@@ -21,3 +53,4 @@ * @param {any} location

module.exports = {
renderFunc,
transformLocation
}
}

@@ -10,3 +10,3 @@ "use strict";

const alerts = require('../base/alerts');
const { transformLocation } = require('./renderUtil');
const { transformLocation, renderFunc } = require('./renderUtil'); // TODO: transformLocation should not be necessary
const DuplicateChecker = require('./DuplicateChecker');

@@ -484,8 +484,10 @@

// One join operation, possibly with ON-condition
// FIXME: Clarify if join operators can be n-ary (assuming binary here)
let result = `(${renderViewSource(source.args[0], env)} ${source.join} join ${renderViewSource(source.args[1], env)}`;
if (source.on) {
result += ` on ${renderExpr(source.on, env)}`;
let result = `${renderViewSource(source.args[0], env)}`;
for (let i = 1; i < source.args.length; i++) {
result = `(${result} ${source.join} join ${renderViewSource(source.args[i], env)}`;
if (source.on) {
result += ` on ${renderExpr(source.on, env)}`;
}
result += `)`;
}
result += `)`;
return result;

@@ -1008,6 +1010,3 @@ }

else if (x.func) {
if (x.args)
return `${x.func}(${renderArgs(x.args, '=>', env)})`;
else
return x.func;
return renderFunc( x, (options.forHana ? 'hana' : 'cap'), a => renderArgs(a, '=>', env) );
}

@@ -1014,0 +1013,0 @@ // Nested expression

@@ -8,4 +8,4 @@

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

@@ -76,8 +76,5 @@

// }
function toSqlDdl(model) {
const { error, signal, warning, info } = alerts(model);
function toSqlDdl(csn, options = csn.options) {
const { error, signal, warning, info } = alerts(csn);
// Use model options
let options = model.options;
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect

@@ -88,5 +85,2 @@ if (!options.forHana) {

// FIXME: This should happen in the caller
let csn = compactModel(model);
// Create artificial namespace objects, so that each artifact has parents up to top-level.

@@ -131,4 +125,4 @@ // FIXME: This is actually only necessary to make 'getParentNameOf' work - should be reworked

// Throw exception in case of errors
if (hasErrors(model.messages)) {
throw new CompilationError(sortMessages(model.messages), model);
if (hasErrors(csn.messages)) {
throw new CompilationError(sortMessages(csn.messages), csn);
}

@@ -197,2 +191,3 @@

case 'function':
case 'event':
// Ignore: not SQL-relevant

@@ -438,8 +433,10 @@ return;

// One join operation, possibly with ON-condition
// FIXME: Clarify if join operators can be n-ary (assuming binary here)
let result = `(${renderViewSource(artifactName, source.args[0], env)} ${source.join.toUpperCase()} JOIN ${renderViewSource(artifactName, source.args[1], env)}`;
if (source.on) {
result += ` ON ${renderExpr(source.on, env)}`;
let result = `${renderViewSource(artifactName, source.args[0], env)}`;
for (let i = 1; i < source.args.length; i++) {
result = `(${result} ${source.join.toUpperCase()} JOIN ${renderViewSource(artifactName, source.args[i], env)}`
if (source.on) {
result += ` ON ${renderExpr(source.on, env)}`;
}
result += `)`;
}
result += `)`;
return result;

@@ -924,6 +921,3 @@ }

else if (x.func) {
if (x.args)
return `${x.func.toUpperCase()}(${renderArgs(x.args, '=>', env)})`;
else
return x.func;
return renderFunc( x, options.toSql.dialect, a => renderArgs(a, '=>', env), true );
}

@@ -930,0 +924,0 @@ // Nested expression

@@ -5,3 +5,3 @@ const schemaObjects = require('./swaggerSchemaObjects');

const { compactSorted } = require('../json/compactor');
const { compactModel } = require('../json/to-csn')
const { compactModel } = require('../json/to-csn');
let swaggerJson = null;

@@ -8,0 +8,0 @@

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

const { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const { isManagedAssociationElement, isStructuredElement, isAssociation, isComposition, isAssocOrComposition, isElementWithType,
const { isManagedAssociationElement, isStructuredElement, isAssociation, isAssocOrComposition, isElementWithType,
renameAnnotation, addBoolAnnotationTo, addStringAnnotationTo, addRefAnnotationTo, copyAnnotations,

@@ -221,6 +221,2 @@ foreachPath, hasBoolAnnotation, getElementDatabaseNameOf, getArtifactDatabaseNameOf } = require('../model/modelUtils');

checkForeignKeys(elem);
if(options.betaMode && isComposition(elem.type) &&
options.toOdata.version == 'v4' &&
!hasBoolAnnotation(elem, '@odata.contained'))
elem['@odata.contained'] = { val: true };
}

@@ -362,4 +358,22 @@

/**
* Check if any warnings were raised in earlier steps that need to be reclassified - i.e. as errors
*
* @param {any} model The model
* @returns {Array} Reclassified messages-Array
*/
function reclassifyWarnings(model){
return model.messages.map(message => {
switch(message.messageId){
case 'empty-entity-or-type':
message.severity = 'Error';
break;
}
return message;
});
}
// Throw exception in case of errors
if (hasErrors(model.messages)) {
model.messages = reclassifyWarnings(model);
throw new CompilationError(sortMessages(model.messages), model);

@@ -680,2 +694,3 @@ }

'@title': '@Common.Label',
'@description': '@Core.Description',
'@ValueList.entity': '@Common.ValueList.entity',

@@ -682,0 +697,0 @@ '@ValueList.type': '@Common.ValueList.type',

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

isAssociation,
isComposition,
isStructured,

@@ -278,7 +277,2 @@ hasBoolAnnotation,

checkForeignKeys(elem, elemName, defName);
if(options.betaMode &&
isComposition(elem.type) &&
options.toOdata.version == 'v4' &&
elem['@odata.contained'] === undefined)
elem['@odata.contained'] = true;
}

@@ -424,4 +418,22 @@

/**
* Check if any warnings were raised in earlier steps that need to be reclassified - i.e. as errors
*
* @param {any} csn The csn
* @returns {Array} Reclassified messages-Array
*/
function reclassifyWarnings(csn) {
return csn.messages.map(message => {
switch (message.messageId) {
case 'empty-entity-or-type':
message.severity = 'Error';
break;
}
return message;
});
}
// Throw exception in case of errors
if (hasErrors(csn.messages)) {
csn.messages = reclassifyWarnings(csn);
throw new CompilationError(csn.messages, csn);

@@ -449,2 +461,3 @@ }

'@title': '@Common.Label',
'@description': '@Core.Description',
'@ValueList.entity': '@Common.ValueList.entity',

@@ -451,0 +464,0 @@ '@ValueList.type': '@Common.ValueList.type',

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

return {
addDefaultTypeFacets,
flattenForeignKeys,

@@ -44,2 +45,19 @@ createForeignKeyElement,

// Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
// If 'obj' has primitive type 'cds.String' and no length (and it was not previously a UUID), add default length 5000 if no option is available.
// if 'obj' has primitive type 'cds.Decimal' and no precision or scale try to apply precision, scale from options if available.
function addDefaultTypeFacets(element) {
if (element && element.type && element.type._artifact && !element.type._artifact.name.$renamed) {
if(element.type._artifact.name.absolute == 'cds.String' && element.length === undefined) {
element.length = { literal: 'number', val: (model.options && model.options.length ? model.options.length : 5000) }
}
if(element.type._artifact.name.absolute == 'cds.Decimal' && element.precision === undefined && model.options.precision) {
element.precision = { literal: 'number', val: model.options.precision }
}
if(element.type._artifact.name.absolute == 'cds.Decimal' && element.scale === undefined && model.options.scale) {
element.scale = { literal: 'number', val: model.options.scale }
}
}
}
// For a dictionary `foreignKeys` of foreign key infos, return a dictionary in flattened form, i.e.

@@ -46,0 +64,0 @@ // replace all foreign keys that are managed associations themselves by their respective foreign keys,

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

const { hasErrors } = require('../base/messages');
const { setProp, cloneWithTransformations } = require('../base/model');
const { setProp } = require('../base/model');
// eslint-disable-next-line no-unused-vars
const { copyAnnotations, printableName, hasBoolAnnotation, forEachDefinition } = require('../model/modelUtils');
const { dfilter } = require('./udict');
const { getUtils } = require('../model/csnUtils');
const { getUtils, cloneCsn } = require('../model/csnUtils');

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

return {
addDefaultTypeFacets,
flattenForeignKeys,

@@ -62,2 +63,21 @@ createForeignKeyElement,

// Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'.
// If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default 5000.
// if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available.
function addDefaultTypeFacets(element) {
if(!element || !element.type)
return;
if (element.type === 'cds.String' && element.length === undefined) {
element.length = model.options && model.options.length ? model.options.length : 5000;
}
if(element.type === 'cds.Decimal' && element.precision === undefined && model.options.precision) {
element.precision = model.options.precision;
}
if(element.type === 'cds.Decimal' && element.scale === undefined && model.options.scale) {
element.scale = model.options.scale;
}
}
// For an array `keys` of foreign key infos, return an array in flattened form

@@ -139,3 +159,3 @@ // in one of the two cases:

// Return the newly generated foreign key element.
function createForeignKeyElement(assoc, assocName, foreignKey, artifact, artifactName) {
function createForeignKeyElement(assoc, assocName, foreignKey, artifact, artifactName, path) {
let fkSeparator = pathDelimiter;

@@ -150,3 +170,10 @@

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

@@ -168,3 +195,4 @@ // In case of compiler errors the foreign key might be missing

copyAnnotations(assoc, foreignKeyElement, true);
if (model.options && !model.options.forHana)
copyAnnotations(assoc, foreignKeyElement, true);

@@ -322,3 +350,3 @@ // If the association is non-fkArtifact resp. key, so should be the foreign key field

let flatElemName = elemName + pathDelimiter + childName;
let flatElem = cloneWithTransformations(childElem, {}, false);
let flatElem = cloneCsn(childElem);
flatElem.viaTransform = true; // FIXME: This name is not ideal but used elsewhere, too)

@@ -351,24 +379,45 @@ setProp(flatElem, '_flatElementNameWithDots', elemName + '.' + childName);

function flattenStructStepsInRef(ref, path) {
let result = [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
const { links, scope } = inspectRef( path );
if (scope === "$magic")
return ref;
let flattenStep = false;
links.forEach( (value, idx) => {
if (flattenStep)
result[result.length-1] += pathDelimiter + ref[idx];
else {
result.push(ref[idx]);
// Refs of length 1 cannot contain steps - no need to check
if(ref.length < 2){
return ref;
}
try{
return flatten(ref, path);
} catch(e){
if(e.message && e.message == "Scope 'ref-where' but no entity was provided."){
const main = path.slice(0, path.lastIndexOf("ref"));
const { links } = inspectRef(main);
const whereEntity = links[path[path.lastIndexOf("ref")+1]].art;
return flatten(ref, path, whereEntity.target ? getCsnDef(whereEntity.target) : whereEntity );
} else {
throw e;
}
}
function flatten(ref, path, whereEntity){
let result = [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
const { links, scope } = inspectRef( path, whereEntity );
if (scope === "$magic")
return ref;
let flattenStep = false;
links.forEach( (value, idx) => {
if (flattenStep)
result[result.length-1] += pathDelimiter + ref[idx];
else {
result.push(ref[idx]);
}
flattenStep = value.art && !(value.art.kind === 'entity') && !value.art.SELECT && (value.art._effectiveType && value.art._effectiveType.elements || value.art.elements);
}
});
});
// If the path starts with '$self', this is now redundant (because of flattening) and can be omitted,
// making life easier for consumers
if (result[0] == '$self' && result.length > 1) {
result = result.slice(1);
if (result[0] == '$self' && result.length > 1) {
result = result.slice(1);
}
return result;
}
return result;
}
// After flattening of elements we need to flatten the on-conditions of

@@ -494,3 +543,3 @@ // unmanaged associations using those newly created elements

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

@@ -674,3 +723,3 @@ if (typeDef.items || typeDef.elements) return;

// Create an articial element 'elemName' of type 'cds.Association',
// Create an artificial element 'elemName' of type 'cds.Association',
// having association target 'target'. If 'isManaged' is true, take all keys

@@ -744,9 +793,9 @@ // of 'target' as foreign keys.

// Add element 'elem' to 'artifact'
//
// 'elem' is in form:
// { b: { type: 'cds.String' } }
//
// 'artifact' is:
// { kind: 'entity', elements: { a: { type: 'cds.Integer' } ... } }
/**
* Add element 'elem' to 'artifact'
*
* @param {any} elem is in form: { b: { type: 'cds.String' } }
* @param {any} artifact is: { kind: 'entity', elements: { a: { type: 'cds.Integer' } ... } }
* @returns {undefined}
*/
function addElement(elem, artifact) {

@@ -814,3 +863,2 @@ // Sanity check

action.params[paramName] = {
kind: 'param',
type: paramTypeName

@@ -817,0 +865,0 @@ }

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

const alerts = require('../base/alerts');
const {compactModel} = require('../json/to-csn');

@@ -14,2 +15,27 @@ // Paths that start with an artifact of protected kind are special

function translateAssocsToJoinsCSN(csn, options){
let main = require('../main');
const model = main.compileSources({ '<stdin>.json' : JSON.stringify(csn, null, 2) }, options);
forEachDefinition(model, art => {
if (art.$queries) {
for (let query of art.$queries) {
if (query.columns) {
for (let col of query.columns) {
if (col.val === "*") {
// If column contains a "*" set columns to null, which forces columns to be computed from elements
query.columns = Object.values(query.elements);
break;
}
}
}
// else {
// // FIXME: is this really correct?
// query.columns = Object.values(query.elements);
// }
}
}
});
return compactModel(translateAssocsToJoins(model));
}
function translateAssocsToJoins(model)

@@ -68,101 +94,107 @@ {

{
let queries = art.$queries;
if(art.$queries === undefined)
return;
if(queries && queries.length > 0)
{
let fullJoins = fullJoinOption;
let env = {
fullJoins,
aliasCount: 0,
walkover: { from: true, onCondFrom:true, select:true, filter: true },
};
/*
Setup QAs for mixins
Mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
This QA is required to detect mixin assoc usages to decide wether a minimum or full
join needs to be done
*/
queries.forEach(q => createQAForMixinAssoc(q, env));
function forEachQuery(callback, env) {
art.$queries.forEach((q,i) => {
if(env !== undefined)
env.queryIndex = i;
callback(q, env);
});
}
/*
HANA cannot process associations with parameters, filters in FROM paths
Filters will lead to a minimum join translation (only FROM clause and MIXIN usages)
A full join conversion is required if an assoc path step meets the following conditions:
1) Argument List && path step is an association
2) Argument List && path step is not at leaf if path step is not an association
3) Filter has cardinality ':1'
4) Association is a mixin usage and not at leaf position (no exposure of the mixin but acutal usage)
*/
if(!fullJoinOption) {
let env = {
minimum : false,
full: false,
walkover : { from:true, onCondFrom: false, select: true, filter: false },
callback :
(pathDict, env) => {
env.minimum = env.minimum ||
(env.location == 'from' ? pathDict.path.some(p => p.where /*filter*/)
: (pathDict.path[0]._navigation && pathDict.path[0]._navigation.kind == 'element' && pathDict.path.length > 1));
let fullJoins = fullJoinOption;
let env = {
fullJoins,
aliasCount: 0,
walkover: { from: true, onCondFrom:true, select:true, filter: true },
};
/*
Setup QAs for mixins
Mark all mixin assoc definitions with a pseudo QA that points to the assoc target.
This QA is required to detect mixin assoc usages to decide wether a minimum or full
join needs to be done
*/
forEachQuery(createQAForMixinAssoc, env);
env.full =
env.full
|| pathDict.path.some((e, i, a) => {
/*
HANA cannot process associations with parameters, filters in FROM paths
Filters will lead to a minimum join translation (only FROM clause and MIXIN usages)
A full join conversion is required if an assoc path step meets the following conditions:
1) Argument List && path step is an association
2) Argument List && path step is not at leaf if path step is not an association
3) Filter has cardinality ':1'
4) Association is a mixin usage and not at leaf position (no exposure of the mixin but acutal usage)
*/
if(!fullJoinOption) {
let env = {
minimum : false,
full: false,
walkover : { from:true, onCondFrom: false, select: true, filter: false },
callback :
(pathDict, env) => {
env.minimum = env.minimum ||
(env.location == 'from' ? pathDict.path.some(p => p.where /*filter*/)
: (pathDict.path[0]._navigation && pathDict.path[0]._navigation.kind == 'element' && pathDict.path.length > 1));
let hasArgs = !!e.namedArgs;
let hasCardinality = e.cardinality;
let isAssocStep = e._artifact.target;
let isNotTail = a.length-1 > i;
let isMixinUsage = e._navigation && e._navigation.$QA && e._navigation.$QA.mixin;
return (hasArgs && (isAssocStep || isNotTail)) || hasCardinality || (isMixinUsage && isNotTail);
});
}
}
queries.forEach(q => walkQuery(q, env));
env.full =
env.full
|| pathDict.path.some((e, i, a) => {
if(!env.minimum && !env.full)
return;
fullJoins = env.full;
let hasArgs = !!e.namedArgs;
let hasCardinality = e.cardinality;
let isAssocStep = e._artifact.target;
let isNotTail = a.length-1 > i;
let isMixinUsage = e._navigation && e._navigation.$QA && e._navigation.$QA.mixin;
return (hasArgs && (isAssocStep || isNotTail)) || hasCardinality || (isMixinUsage && isNotTail);
});
}
}
env.fullJoins = fullJoins;
forEachQuery(walkQuery, env);
/*
Setup QATs and leaf QAs (@ query and subqueries in from clause)
a) For all paths in a query create the path prefix trees aka QATs.
Paths that start with a mixin assoc are Qat'ed into the mixin definition.
If a mixin assoc is published, its leaf Qat receives the pseudo QA(view) from the rootQat,
which is the mixin definition itself. See 1a)
b) Create QAs for FROM clause subqueries, as they are not yet swept by the path walk
*/
env.callback = mergePathIntoQAT;
queries.forEach(q => walkQuery(q, env));
queries.forEach(q => createQAForFromClauseSubQuery(q, env));
if(!env.minimum && !env.full)
return;
fullJoins = env.full;
}
env.fullJoins = fullJoins;
// 2) Walk over each from table path, transform it into a join tree
env.walkover = { from:true, onCondFrom:false, select: false, filter: false };
env.callback = createInnerJoins;
queries.forEach(q => walkQuery(q, env));
/*
Setup QATs and leaf QAs (@ query and subqueries in from clause)
a) For all paths in a query create the path prefix trees aka QATs.
Paths that start with a mixin assoc are Qat'ed into the mixin definition.
If a mixin assoc is published, its leaf Qat receives the pseudo QA(view) from the rootQat,
which is the mixin definition itself. See 1a)
b) Create QAs for FROM clause subqueries, as they are not yet swept by the path walk
*/
env.callback = mergePathIntoQAT;
forEachQuery(walkQuery, env);
// 3) Transform toplevel FROM block into cross join
queries.forEach(q => createCrossJoins(q));
forEachQuery(createQAForFromClauseSubQuery, env);
// 4) Transform all remaining join relevant paths into left outer joins and connect with
// FROM block join tree. Instead of walking paths it is sufficient to process the $qat
// of each $tableAlias.
queries.forEach(q => createLeftOuterJoins(q, env));
// 5) Rewrite ON condition paths that are part of the original FROM block
// (same rewrite as (injected) assoc ON cond paths but with different table alias).
// 6) Prepend table alias to all remaining paths
env.walkover = { from:false, onCondFrom:true, select: true, filter: false };
env.callback = rewriteGenericPaths;
queries.forEach(q => walkQuery(q, env));
// 2) Walk over each from table path, transform it into a join tree
env.walkover = { from:true, onCondFrom:false, select: false, filter: false };
env.callback = createInnerJoins;
forEachQuery(walkQuery, env);
// 7) Attach firstFilterConds to Where Condition.
queries.forEach(q => attachFirstFilterConditions(q));
// 3) Transform toplevel FROM block into cross join
forEachQuery(createCrossJoins);
// TODO: support parameters
}
// 4) Transform all remaining join relevant paths into left outer joins and connect with
// FROM block join tree. Instead of walking paths it is sufficient to process the $qat
// of each $tableAlias.
forEachQuery(createLeftOuterJoins, env);
// 5) Rewrite ON condition paths that are part of the original FROM block
// (same rewrite as (injected) assoc ON cond paths but with different table alias).
// 6) Prepend table alias to all remaining paths
env.walkover = { from:false, onCondFrom:true, select: true, filter: false };
env.callback = rewriteGenericPaths;
forEachQuery(walkQuery, env);
// 7) Attach firstFilterConds to Where Condition.
forEachQuery(attachFirstFilterConditions);
}
// ======= Inner functions ===========
function createCrossJoins(query)

@@ -244,2 +276,3 @@ {

aliases.
*/

@@ -257,2 +290,10 @@ function createQAForMixinAssoc(query, env)

art.$QA.mixin = true;
/* Mark mixin definition to be _ignored:
- If the mixin is used, it is now resolved into a join => definition vaporizes
- If the mixin is published, forHana backend must create a __copy with rewritten
$projection ON conditon and publish it with alias.
- If the mixin is neither be used nor published it shall not be visible to the database
(internal mixin).
*/
art._ignore = true;
}

@@ -314,3 +355,7 @@ });

if(QA) {
pathValue = constructPathNode([ { id: QA.name.id, _artifact: QA._artifact }, ...path.slice(path.indexOf(ps)) ]);
// if the found QA is the mixin QA and if the path length is one,
// this indicates the publishing of a mixin assoc, don't rewrite the path
if(QA.mixin && path.length == 1)
return;
pathValue = constructPathNode([ constructTableAliasPathStep(QA), ...path.slice(path.indexOf(ps)) ]);
}

@@ -385,3 +430,3 @@ else {

rewritePathsInExpression(filter, function(pathNode) {
return [ /* tableAlias=> */ { id: childQat.$QA.name.id, _artifact: childQat.$QA._artifact },
return [ /* tableAlias=> */ constructTableAliasPathStep(childQat.$QA),
/* filterPath=> */ pathNode.path ]; // eslint-disable-line indent-legacy

@@ -438,4 +483,4 @@ }, env);

// 'path steps' for the src/tgt table alias
let srcTableAlias = { id: assocSourceQA.name.id, _artifact: assocSourceQA._artifact };
let tgtTableAlias = { id: assocQAT.$QA.name.id, _artifact: assocQAT.$QA._artifact };
let srcTableAlias = constructTableAliasPathStep(assocSourceQA);
let tgtTableAlias = constructTableAliasPathStep(assocQAT.$QA);

@@ -567,2 +612,3 @@ node.on = createOnCondition(assocQAT.origin._artifact, srcTableAlias, tgtTableAlias);

newTgtAlias._artifact = fwdAssoc._redirected[fwdAssoc._redirected.length-1]._finalType;
newTgtAlias._navigation = fwdAssoc._redirected[fwdAssoc._redirected.length-1].$QA._navigation;
}

@@ -577,2 +623,3 @@ else {

newTgtAlias._artifact = srcQA._artifact;
newTgtAlias._navigation = srcQA._navigation;
}

@@ -717,3 +764,9 @@ else {

let node = constructPathNode([ { id: artifact.name.absolute, _artifact: artifact, namedArgs } ], alias);
let node = constructPathNode(
[ {
id: artifact.name.absolute,
_artifact: artifact,
namedArgs,
_navigation : { name: { query: env.queryIndex } }
} ], alias);
return node;

@@ -726,2 +779,4 @@ }

{
// Debug only:
// QA.name.id += '_' + (QA.path[0]._navigation === undefined ? '***navigation_missing***' : QA.path[0]._navigation.name.query) + '_' + env.aliasCount++;
QA.name.id += '_' + env.aliasCount++;

@@ -740,3 +795,3 @@ QA.numberedAlias = true;

tableAlias = [ aliasName, _artifact ]
tableAlias = [ aliasName, _artifact, _navigation ]
path = [ { id: ..., _artifact: ... (unused) } ]

@@ -841,5 +896,8 @@ */

return [ /* tableAlias => */ { id: QA.name.id, _artifac: QA._artifact }, path ];
return [ constructTableAliasPathStep(QA), path ];
}
function constructTableAliasPathStep(QA) {
return { id: QA.name.id, _artifact: QA._artifact, _navigation: QA.path[0]._navigation };
}
/*

@@ -1042,3 +1100,7 @@ Translate ON cond paths and substitute FK aliases

if(['$projection', '$self'].includes(head.id) && tail.length) {
[head, ...tail] = tail;
// make sure not to truncate tail
if(tail.length > 1)
[head, ...tail] = tail;
else
head = tail[0];
/*

@@ -1162,4 +1224,2 @@ if the head is a path (it better be;) then use it as

if(qat.origin._artifact.$QA) {
// mark mixin assoc definition to be ignored in later rendering step
qat.origin._artifact._ignore = true;
qat.$QA = clone(qat.origin._artifact.$QA);

@@ -1173,2 +1233,3 @@ if(qat._namedArgs)

}
qatChildren = createQATChildren(qat);

@@ -1321,2 +1382,4 @@ qatParent = qat; // Current qat becomes parent to the next level of children

setProp(node, '_artifact', pathSteps[pathSteps.length-1]._artifact);
if(pathSteps[0]._navigation)
setProp(node.path[0], '_navigation', pathSteps[0]._navigation);
return node;

@@ -1471,2 +1534,2 @@ }

module.exports = { translateAssocsToJoins, constructPathNode, walkQuery };
module.exports = { translateAssocsToJoins, translateAssocsToJoinsCSN, constructPathNode, walkQuery };
{
"name": "@sap/cds-compiler",
"version": "1.17.1",
"version": "1.19.2",
"lockfileVersion": 1,

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

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

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

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

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

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

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

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc