Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
108
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.39.0 to 1.42.2

lib/checks/csn/defaultValues.js

1

bin/cdsc.js

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

'dontRenderVirtualElements': true,
'ignoreManagedAssocPublishingInUnion': true
}

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

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

## Version 1.42.2 - 2020-09-29
### Fixed
- CDL: Action blocks can now be empty, e.g. `entity E {…} actions { }`.
- An info message is emitted if builtin types are annotated. Use a custom type instead.
Annotating builtins in CDL is possible but when transformed into CSN the annotation was silently lost.
It is now put into the "extensions" property of the CSN.
- Fixed `cast()` for simple values like numbers and strings.
- to.sql:
+ Remove simple default value checks and allow the database to reject default values upon activation.
+ Render empty actual parameter list when selecting from a view with parameters which are fully covered with
default values and no actual parameters are provided in the query itself.
- OData:
+ Correctly render unary operator of default values in EDM.
## Version 1.42.0 - 2020-09-25
### Added
- The compiler now supports the `cast(element as Type)` function in queries.
Using this function will also result in a `CAST` SQL function call.
- A top-level property `i18n` is now supported. The property can contain translated texts.
The compiler expects its entries to be objects where each text value is a string.
- CDL: Empty selection lists in views/projections are now allowed and make it possible to extend
empty projections. Note that views/projections without any elements are not deployable.
- For CSNs as input, the compiler returns properties as they are (without checks)
if their name does not match the regexp `/[_$]?[a-zA-Z]+[0-9]*/` and does not start with `@`.
With more than one CSN input,
the compiler only returns the top-level CSN properties of the first source.
### Changed
- to.cdl: Smart type references are now explicitly rendered via ":"-syntax
### Removed
### Fixed
- Annotating an _unknown_ element _twice_ now results in a duplicate annotation error instead
of silently loosing the annotation.
- Service/context extensions that reference a non-service/non-context now result in a compiler error
instead of silently loosing the context/service extension.
- to.hdbcds/sql/hdi:
+ fix a bug, which resulted in a malformed on-condition, if an association key
was another association pointing to an entitiy with a structured key.
+ in conjunction with assoc-to-joins, the internal CSN reference broke
causing missing locations and even internal errors when logging messages
+ managed associations in UNION are now correctly processed
- The parseCdl mode now correctly resolves type arguments of "many" types.
- OData: The annotation `@Capabilities.Readable` is now correctly
translated to `@Capabilities.ReadRestrictions.Readable`.
## Version 1.41.4 - 2020-09-18
### Removed
- The length of HANA identifiers are not checked anymore: no more warnings are issued for long identifiers.
### Fixed
- The check for ignored "localized" keywords in sub-elements has been extended to also
include references to structured types.
- A warning was added if views/projections are used as element types.
- An info message is emitted if a namespace is annotated.
Annotating namespaces is not possible. Previously the annotation was silently lost.
It is now put into the "extensions" property of the CSN.
## Version 1.41.2 - 2020-09-15
### Fixed
- OData: correctly render primary key associations targeting a composition parent but are not
the composition enabling association.
- to.hdbcds/sql/hdi: Do not dump if artifact doesn't exist anymore after association to join translation
- Only check for unmanaged associations inside of "many"/"array of" in the elements of views and entities,
not inside of actions and other members.
## Version 1.41.0 - 2020-09-11
### Added
- OData: Allow the relational comparison of structures or managed associations in an ON condition as described in
version 1.32.0 - 2020-07-10 (forHana).
- Allow `Struct:elem` with and without preceeding `type of` as type reference.
### Fixed
- to.cdl: Only render enums if they were directly defined there
- The parseCdl mode now checks for redefinitions to avoid generating invalid CSN.
- OData: An error is thrown if a redirected target has fewer keys than the original one.
- OData: Empty structured elements are now handled correctly in `flat` format.
## Version 1.40.0 - 2020-09-04
### Added
- to.hdi/sql: Support default values for view parameters.
- OData: lower message severity from Error to Warning for
`<entity type> has no primary key` and `<type> has no properties`.
### Changed
- OData: The foreign key references in associations are not flattened any more with format `structured`.
### Fixed
- parse.cdl: Properly handle type arguments, most likely relevant for HANA types.
- OData: Multilevel anonymously defined `composition of <aspect>` is now processed successfully with the OData backend.
- OData: Fix a bug in EDM generation that caused a dump.
- Update ANTLR dependency to version 4.8.
## Version 1.39.0 - 2020-08-26

@@ -11,0 +125,0 @@

@@ -6,12 +6,17 @@ # ChangeLog of Beta Features for cdx compiler and backends

Note: `beta` fixes, changes and features are listed in this ChangeLog.
Note: `beta` fixes, changes and features are listed in this ChangeLog just for information.
The compiler behaviour concerning `beta` features can change at any time without notice.
**Don't use `beta` fixes, changes and features in productive mode.**
## Version 1.36.0 - 2020-08-xx
## Version 1.42.0
### Added
### Added `ignoreManagedAssocPublishingInUnion`
#### `mapAssocToJoinCardinality`
For `to.hdbcds`, with beta flag `ignoreManagedAssocPublishingInUnion` in conjunction with dialect
`hanaJoins`, managed associations in UNIONs are replaced by their foreign keys and silently ignored
## Version 1.36.0 - 2020-08-07
### Added `mapAssocToJoinCardinality`
Analog to the feature `cardinality for explicit joins`, the association to

@@ -22,7 +27,7 @@ join transformation algorithm now experimentally supports join cardinalities as well.

#### `odataDefaultValues`
### Added `odataDefaultValues`
OData: Enables the rendering of default values for EntityType properties.
#### `originalKeysForTemporal`
### Added `originalKeysForTemporal`

@@ -32,3 +37,3 @@ OData: The original entity keys are not enhanced with `@cds.valid.from` or replaced with

#### `dontRenderVirtualElements`
### Added `dontRenderVirtualElements`

@@ -38,11 +43,51 @@ Virtual elements are no longer rendered in views as `null as <id>` or added to potentially generated

### Removed
### Removed `noJoinsForForeignKeys`
#### `noJoinsForForeignKeys`
The association to join transformation treats foreign key accesses with priority now.
#### `uniqueconstraints`
### Removed `uniqueconstraints`
Unique constraints are now generally available.
## Version 1.32.0 - 2020-07-10
### Removed `aspectCompositions`
Aspect compositions aka managed compositions are now avaible without beta option.
_Warning_: the CSN representation can still change.
## Version 1.31.0 - 2020-06-26
### Changed `subElemRedirections`
Signal an error
if an unmanaged association as sub element is to be implicitly redirected,
as we do not automatically rewrite the `on` condition in that situation yet.
## Version 1.30.0 - 2020-06-12
### Added `subElemRedirections`
When the beta option `subElemRedirections` is set to true,
_all_ structure types are expanded when referenced:
* managed associations (and compositions to entities) are implicitly redirected
when necessary,
* sub elements of referred structure types can be annotated individually,
* the resulting CSN is bigger (will be reduced in the future if possible)
as `type` references to structures will now have a sibling `elements`.
This option does not enable:
* rewriting the `on` conditions of associations in sub elements,
* aspect compositions as sub elements,
* `localized` sub elements,
* `key` property on sub elements.
## Version 1.23.0
### Added `keyRefError`
Always signal an error (instead of just a warning in some cases),
if not all references in the `keys` of an managed associations
are projected in the new target.

66

doc/NameResolution.md
# Name Resolution in CDS
> Status Oct 2019: TODOs must be filled, say more about name resolution in CSN.
> Status Sep 2020: TODOs must be filled, say more about name resolution in CSN.

@@ -50,3 +50,3 @@ Name resolution refers to the resolution of names (identifiers) within expressions of the source to the intended artifact or member in the model.

```
nameprefix sap.core;
namespace sap.core;

@@ -59,14 +59,14 @@ context types {

type Amount: Decimal(5,3);
}
};
type CurrencySymbol: String(3);
view Products as select from ProductsInternal {
productId,
salesPrice
};
entity ProductsInternal {
productId: Integer;
retailPrice: types.Price;
salesPrice = retailPrice; // we give no reduction
}
view Products as select from ProductsInternal {
productId,
salesPrice
}
salesPrice = retailPrice; // calculated fields are not supported yet
};
```

@@ -94,2 +94,7 @@

That being said, name resolution does **not depend on the order of definitions**.
In the example above, the element `amount` has a type `Amount`
which is defined _after_ the element definition.
Similar for the view `Products` whose source entity `ProductsInternal` is defined after the view.
In the view, we also refer to **elements** of (another) artifact.

@@ -135,3 +140,3 @@ There is no special language construct for such references –

an **extension cannot silently change the semantics of a model** –
the name resolution is defined in such a way that a valid reference
the name resolution is defined in such a way that a valid and potentially unrelated reference
does not silently (without errors or warnings) point to another artifact

@@ -189,3 +194,3 @@ when the extension is applied to the model.

entity E : B {
e = a;
e = a; // calculated fields are not supported yet
}

@@ -200,3 +205,3 @@ ```

see the first example in Section ["Design Principles"](#design-principles).
To avoid this situation in CDx/Language, we are a bit incompatible in this case.
To avoid this situation in CDL, we are a bit incompatible in this case.

@@ -352,6 +357,2 @@

In a future version of this project,
we or others might provide a "use at your own risk" backend
which produce SQL DDL statements without quoted identifiers
---

@@ -402,3 +403,3 @@

following the lexical block structure of the source,
or a small, fixed number of predefined names (e.g. `$projection`.)
or a small, fixed number of predefined names (e.g. `$self`.)
We will call such an environment a **lexical search environment**.

@@ -429,6 +430,13 @@

annotation A : array of { e: Integer; };
annotate A with {
annotate e with @lineElement;
entity E {
items: many { i: Integer; };
}
annotation B: type of :A.e; // valid = Integer
type T: type of E:items.i; // valid = Integer
annotate E with {
items { i @lineElement; }; // valid annotation
}
view V as select from E {
items.i // not valid (yet)
}
```

@@ -443,3 +451,3 @@

annotate A with {
@targetElem i; // error: do not follow associations
@targetElem i; // err(info): do not follow associations
}

@@ -449,3 +457,3 @@ type S { e: Integer; }

annotate T with {
@derivedElem e; // ok: follow derived type
@derivedElem e; // ok: follow derived type (not yet without beta)
}

@@ -487,4 +495,3 @@ ```

`Integer`, `Integer64`, `Binary`, `LargeBinary`, `Decimal`, `DecimalFloat`, `Double`,
`Date`, `Time`, `Timestamp`, `DateTime`, and `Boolean`.
More artifacts are defined with the options `--hana-flavor`.
`Date`, `Time`, `Timestamp`, `DateTime`, and `Boolean` and `hana`, also the namespace `cds`.

@@ -631,3 +638,4 @@ When searching for an annotation (after the initial `@`), the last search environment

The reason for the `$self` references is visible in an example with subelements:
The reason for the `$self` references is visible in an example with subelements
(calculated fields are not supported yet):

@@ -699,3 +707,3 @@ ```

The most visible differences in the name resolution semantics of CDx/Language compared to HANA CDS are:
The most visible differences in the name resolution semantics of CDL compared to HANA CDS are:

@@ -715,3 +723,3 @@ * Using constant values requires to prefix the path (referring to the constant) with a `:`.

It is also compatible to the "pre-extension" name resolution semantics of HANA-CDS.
This is nice! Why do we specify a different name resolution semantics for CDx/Language?
This is nice! Why do we specify a different name resolution semantics for CDL?

@@ -731,3 +739,3 @@ The reason is:

These are properties which do not hold for consumers of the CDx Compiler.
These are properties which do not hold for consumers of the CAP CDS Compiler.

@@ -749,3 +757,3 @@ Additionally, while direct changes in base packages can always lead to semantic changes,

extend b with {
z = a; // in CDx/Language: $self.a
z = a; // in CDL: $self.a, calculated fields are not supported yet
}

@@ -752,0 +760,0 @@ }

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

catch (err) {
if (err instanceof CompilationError || options.testMode)
if (err instanceof CompilationError || options.noRecompile)
throw err;

@@ -602,0 +602,0 @@

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

'testMode',
'noRecompile',
'internalMsg',

@@ -41,0 +42,0 @@ 'localizedWithoutCoalesce', // experiment version of 'localizedLanguageFallback',

@@ -85,8 +85,7 @@ // Implementation of alerts

* Link an alert to a location (if provided). If no explicit severity is given, the severity is taken
* from the 'msg' itself (if provided). The alert is added to the list of alerts. If the alert is an
* exception, the corresponding error is thrown.
* from the 'msg' itself (if provided). The alert is added to the list of alerts.
*
* @param {any} msg Message text
* @param {XSN.Location|CSN.Location|any[]} [location] Location information (possibly semantic location)
* @param {string} [severity] severity: Info, Warning, Error
* @param {CSN.MessageSeverity} [severity] severity: Info, Warning, Error, Debug
* @param {string} [message_id=''] Message ID

@@ -101,4 +100,2 @@ * @returns {Boolean} true, if no 'Error' alert was handled.

switch (err.severity) {
case 'Exception':
throw err;
case 'Error':

@@ -105,0 +102,0 @@ return false;

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

function combinedLocation( start, end ) {
if (!start)
return end.location;
else if (!end)
return start.location;
return {

@@ -12,0 +16,0 @@ filename: start.location.filename,

@@ -8,3 +8,3 @@ const { CompileMessage, DebugCompileMessage } = require('../base/messages');

* @param {XSN.Location|CSN.Location|CSN.Path} location
* @param {string} severity
* @param {CSN.MessageSeverity} severity
* @param {string} message_id

@@ -93,2 +93,5 @@ * @param {boolean} useDebugMsg

let inCsnDict, inElement, inAction, inParam, inKeys, inRef, inEnum, inQuery, inColumn, inMixin, inItems = false;
// for top level actions
if(currentThing.kind === 'action')
inAction = true;
for(const [index, step] of csnPath.entries()){

@@ -217,4 +220,4 @@ currentThing = currentThing[step];

}
if( inElement ) result += element();
if( inItems ) result += element() + '/items';
else if( inElement ) result += element();
return result;

@@ -221,0 +224,0 @@

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

'expected-type': 'A type or an element of a type is expected here',
'expected-no-query': 'A type or an element of a type is expected here',
'expected-entity': 'A non-abstract entity, projection or view is expected here',
'expected-source': 'A query source must be a non-abstract entity or an association to an entity',
'i18n-different-value': 'Different translation for key $(PROP) of language $(OTHERPROP) in unrelated layers'
}

@@ -76,4 +79,7 @@

'check-proper-type-of': true,
'extend-repeated-intralayer': true,
'extend-unrelated-layer': true,
'redirected-to-ambiguous': true,
'redirected-to-unrelated': true,
'rewrite-not-supported': true,
};

@@ -172,3 +178,3 @@

* @param {string} msg The message text
* @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {CSN.MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {string} [id] The ID of the message - visible as property messageId

@@ -205,3 +211,3 @@ * @param {any} [home]

* @param {string} msg The message text
* @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {CSN.MessageSeverity} [severity='Error'] Severity: Debug, Info, Warning, Error
* @param {string} [id] The ID of the message - visible as property messageId

@@ -421,4 +427,4 @@ * @param {any} [home]

* Return message hash which is either the message string without the file location,
* or the full message string if no semantic location is provided.
*
* or the full message string if no semantic location is provided.
*
* @param {CSN.Message} msg

@@ -566,3 +572,3 @@ * @returns {string} can be used to uniquely identify a message

// message already present, replace only if current msg is the more precise one
if( msg.location.end.column > msg.location.start.column )
if( msg.location.end.column > msg.location.start.column )
seen.set(hash, msg);

@@ -611,2 +617,4 @@ }

return 'using:' + quoted( art.name.id );
else if (art.kind === 'extend')
return homeNameForExtend ( art );
else if (art.name._artifact) // block, extend, annotate

@@ -618,2 +626,34 @@ return homeName( art.name._artifact ); // use corresponding definition

// The "home" for extensions is handled differently because `_artifact` is not
// set for unknown extensions and we could have nested extensions.
function homeNameForExtend( art ) {
const absoluteName = (art.name.id ? art.name.id :
art.name.path.map(s => s && s.id).join('.'));
// Surrounding parent may be another extension.
const parent = art._parent;
if (!parent)
return 'extend:' + quoted(absoluteName);
// And that extension's artifact could have been found.
const parentArt = parent.name && parent.name._artifact;
if (!parentArt)
return artName(parent) + '/' + quoted(absoluteName);
let extensionName;
if (parentArt.enum || parentArt.elements) {
const fakeArt = {
kind: parentArt.enum ? 'enum' : 'element',
name: { element: absoluteName }
};
extensionName = artName(fakeArt);
}
else {
extensionName = 'extend:' + quoted(absoluteName);
}
// Even though the parent artifact was found, we use kind 'extend'
// to make it clear that we are inside an (element) extension.
return 'extend:' + artName(parentArt) + '/' + extensionName;
}
function quoted( name ) {

@@ -620,0 +660,0 @@ return (name) ? '"' + name.replace( /"/g, '""' ) + '"' : '<?>'; // sync ";

@@ -57,11 +57,2 @@ 'use strict';

function checkLocalizedSubElement(element) {
if (!element.localized || element.localized.val !== true)
return;
if (element._parent && element._parent.kind === 'element') {
message('localized-sub-element', element.localized.location, element, {}, 'Warning', 'Keyword "localized" is ignored for sub elements');
}
}
if(elem.key && elem.key.val === true){

@@ -75,5 +66,63 @@ if(['cds.hana.ST_POINT', 'cds.hana.ST_GEOMETRY'].includes(type)){

}
checkLocalizedSubElement(elem);
}
/**
* Non-recursive check if sub-elements have a "localized" keyword since this is
* not yet supported.
*
* This check is not recursive to avoid a runtime overhead. Because of this it fails
* to detect scenarios with indirections, e.g.
*
* type L : localized String;
* type L1 : L;
* type L2 : L1;
*
* entity E {
* struct : {
* subElement : L2;
* }
* }
*
* @param {XSN.Artifact} element
* @param {XSN.Model} model
*/
function checkLocalizedSubElement(element, model) {
// TODO: Recursive check
function hasTypeLocalizedElements(type) {
if (!type)
return false;
for (const elementName in type.elements) {
const element = type.elements[elementName];
if (element.localized && element.localized.val === true)
return true;
if (isTypeLocalized(element.type))
return true;
}
return false;
}
// TODO: Recursive check
function isTypeLocalized(type) {
return (type && type._artifact && type._artifact.localized &&
type._artifact.localized.val === true);
}
const message = getMessageFunction(model);
const isSubElement = element._parent && element._parent.kind === 'element';
if (element.localized && element.localized.val === true && isSubElement) {
message('localized-sub-element', element.localized.location, element, {},
'Warning', 'Keyword "localized" is ignored for sub elements');
return;
}
if (!element.type)
return;
if ((isTypeLocalized(element.type) && isSubElement) || hasTypeLocalizedElements(element._finalType)) {
message('localized-sub-element', element.type.location, element, { type: element.type },
'Warning', 'Keyword "localized" in type $(TYPE) is ignored for sub elements');
}
}
// Perform checks for element (or type) 'elem' concerning managed associations,

@@ -94,3 +143,3 @@ // only for managed assocs directly declared on 'elem', not those from derived

if (!foreignKeys) {
signal(error`The target "${target._artifact.name.absolute}" of the managed association "${elem.name.id}" does not have keys`, elem.location);
signal(error`The target "${target._artifact.name.absolute}" of the managed association "${elem.name.id}" does not have keys`, target.location);
}

@@ -174,10 +223,15 @@ const targetMax = (elem.cardinality && elem.cardinality.targetMax && elem.cardinality.targetMax.val);

function checkStructureCasting(elem, model) {
const { error, signal } = alerts(model);
if (elem.type && !elem.type.$inferred) {
const message = getMessageFunction( model );
const loc = elem.type.location || elem.location;
if (elem._finalType && elem._finalType.elements)
signal(error`Cannot cast to structured element`, elem.location);
message('type-cast-structured', loc, elem, {}, 'Error', `Cannot cast to structured element`);
else if (elem.value && elem.value._artifact && elem.value._artifact._finalType && elem.value._artifact._finalType.elements)
signal(error`Structured element cannot be casted to a different type`, elem.location);
message('type-cast-structured', loc, elem, {}, 'Error', `Structured element cannot be casted to a different type`);
}
if (elem.value && elem.value.args) {
elem.value.args.forEach(arg => checkStructureCasting(arg, model));
}
}

@@ -499,2 +553,3 @@

checkPrimaryKey,
checkLocalizedSubElement,
checkManagedAssoc,

@@ -501,0 +556,0 @@ checkVirtualElement,

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

const { error, signal } = alerts(model);
const { isAssociationOperand, isDollarSelfOperand } = transformUtils.getTransformers(model);
const { isAssociationOperand, isDollarSelfOrProjectionOperand } = transformUtils.getTransformers(model);

@@ -66,3 +66,3 @@ // No further checks regarding associations and $self required if this is a backlink-like expression

}
if (isDollarSelfOperand(arg)) {
if (isDollarSelfOrProjectionOperand(arg)) {
signal(error`"${arg.path[0].id}" can only be used as a value in a comparison to an association`, arg.location);

@@ -90,4 +90,4 @@ }

// Tree-ish expression from the compiler (not augmented)
return (isAssociationOperand(xpr.args[0]) && isDollarSelfOperand(xpr.args[1])
|| isAssociationOperand(xpr.args[1]) && isDollarSelfOperand(xpr.args[0]));
return (isAssociationOperand(xpr.args[0]) && isDollarSelfOrProjectionOperand(xpr.args[1])
|| isAssociationOperand(xpr.args[1]) && isDollarSelfOrProjectionOperand(xpr.args[0]));
}

@@ -94,0 +94,0 @@

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

}
if(this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items){
if(this.artifact && ( this.artifact.kind === 'entity' || this.artifact.query ) && member && member.items && member.$path[2] == 'elements'){
if(member.items.type && member.items.type.ref){

@@ -35,0 +35,0 @@ validate(this.artifactRef(member.items.type));

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

checkLocalizedElement, checkStructureCasting, checkForItemsChain, checkTypeStructure,
checkElementHasValidTypeOf } = require('./checkElements');
checkElementHasValidTypeOf, checkLocalizedSubElement } = require('./checkElements');
const { checkExpression } = require('./checkExpressions');

@@ -270,7 +270,7 @@ const { foreachPath } = require('../model/modelUtils');

const finalTypeParams = targetFinalType ? targetFinalType.params : null;
compareArgs(pathStep.namedArgs, pathStep.args, finalTypeParams);
compareActualNamedArgsWithFormalNamedArgs(pathStep.namedArgs, finalTypeParams);
} else {
// Parameters can only be provided when navigating along associations, so because this path
// is for non-associations, checking arguments along a navigation is unnecessary and faulty.
compareArgs(pathStep.namedArgs, pathStep.args, pathStep._artifact.params);
compareActualNamedArgsWithFormalNamedArgs(pathStep.namedArgs, pathStep._artifact.params);
}

@@ -280,32 +280,38 @@

* Compare two argument dictionaries for correct argument count.
* @param {object} namedArgsGiven
* @param {object[]} unnamedArgsGiven
* @param {object} argsExpected
* @param {object} actualArgs
* @param {object} formalArgs
*/
function compareArgs(namedArgsGiven, unnamedArgsGiven, argsExpected) {
namedArgsGiven = namedArgsGiven || {};
unnamedArgsGiven = unnamedArgsGiven || [];
argsExpected = argsExpected || {};
function compareActualNamedArgsWithFormalNamedArgs(actualArgs, formalArgs) {
actualArgs = actualArgs || {};
formalArgs = formalArgs || {};
const givenNames = Object.keys(namedArgsGiven);
const expectedNames = Object.keys(argsExpected);
const aArgsCount = Object.keys(actualArgs).length;
const expectedNames = Object.keys(formalArgs);
if (unnamedArgsGiven.length) {
if (unnamedArgsGiven.length != expectedNames.length) {
message(undefined, pathStep.location, pathStep,
{ expected: expectedNames.length, given: givenNames.length },
'Error',
'Expected $(EXPECTED) arguments but $(GIVEN) given'
);
}
} else {
if (givenNames.length != expectedNames.length) {
const missingArguments = expectedNames.filter((name) => !givenNames.includes(name));
message(undefined, pathStep.location, pathStep,
{ names: missingArguments, expected: expectedNames.length, given: givenNames.length },
'Error',
'Expected $(EXPECTED) arguments but $(GIVEN) given; missing: $(NAMES)'
);
}
const missingArgs = [];
const unknownArgs = [];
for(const fAName in formalArgs) {
if(!actualArgs[fAName] && !formalArgs[fAName].default)
missingArgs.push(fAName);
}
for(const aAName in actualArgs) {
if(!formalArgs[aAName])
unknownArgs.push(aAName);
}
if(missingArgs.length) {
message(undefined, pathStep.location, pathStep,
{ names: missingArgs, expected: expectedNames.length, given: aArgsCount },
'Error',
'Expected $(EXPECTED) arguments but $(GIVEN) given; missing: $(NAMES)'
);
}
// already checked in resolver
if(unknownArgs.length) {
message(undefined, pathStep.location, pathStep,
{ names: unknownArgs, expected: expectedNames.length, given: aArgsCount },
'Error',
'Expected $(EXPECTED) arguments but $(GIVEN) given; unknown: $(NAMES)'
);
}
}

@@ -325,2 +331,3 @@ }

checkPrimaryKey(elem, model);
checkLocalizedSubElement(elem, model);
checkVirtualElement(elem, model);

@@ -327,0 +334,0 @@ checkManagedAssoc(elem, model);

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

// Properties that can appear where a type can have type arguments.
const typeProperties = [
'type', 'typeArguments', 'length', 'precision', 'scale', 'srid',
];
function assertConsistency( model, stage ) {

@@ -85,2 +91,3 @@ const stageParser = typeof stage === 'object';

'extensions',
'i18n',
'version', '$version', // version without --test-mode

@@ -100,3 +107,4 @@ 'meta',

optional: [
'messages', 'options', 'definitions', 'extensions',
'messages', 'options', 'definitions',
'extensions', 'i18n',
'artifacts', 'artifacts_', 'namespace', 'usings', // CDL parser

@@ -166,2 +174,11 @@ 'filename', 'dirname', // TODO: move filename into a normal location? Only in model.sources

},
i18n: {
test: isDictionary( ( val, parent, prop, spec, lang ) => {
const textValueIsString = (v, p, textProp, s, textKey) => {
isString(v.val, p, textKey, s);
};
const innerDict = isDictionary( textValueIsString );
return innerDict( val, parent, lang, spec );
} ),
},
_localized: { kind: true, test: TODO }, // true or artifact

@@ -417,3 +434,7 @@ _assocSources: { kind: true, test: TODO }, // just null: isArray( inDefinitions ) during resolve

struct: { inherits: 'val', test: isDictionary( definition ) }, // def because double @
args: { inherits: 'value', test: args },
args: {
inherits: 'value',
test: args,
optional: [ '_typeIsExplicit', ...typeProperties ], // for cast() in expressions
},
namedArgs: { inherits: 'value', optional: [ 'name', '$duplicate' ], test: args },

@@ -472,6 +493,9 @@ onCond: { kind: true, inherits: 'value' },

optional: [
'type', 'typeArguments', 'length', 'precision', 'scale', 'srid', 'enum',
'enum',
'elements', 'cardinality', 'target', 'on', 'onCond', 'foreignKeys', 'items',
'_outer', '_finalType', 'notNull',
'origin', '_block', '$inferred', '_deps',
'elements_', '_elementsIndexNo', '$syntax', // TODO: remove
'_foreignKeysIndexNo', '_status',
...typeProperties,
],

@@ -478,0 +502,0 @@ },

@@ -145,5 +145,14 @@ //

function always( prop, target, source ) {
target[prop] = Object.assign( { $inferred: 'prop' }, source[prop] );
if ('_artifact' in source[prop])
setProp( target[prop], '_artifact', source[prop]._artifact );
const val = source[prop];
if (Array.isArray( val )) {
target[prop] = [ ...val ];
target[prop].$inferred = 'prop';
}
else if ('_artifact' in val) {
target[prop] = Object.assign( { $inferred: 'prop' }, val );
setProp( target[prop], '_artifact', val._artifact );
}
else {
target[prop] = Object.assign( { $inferred: 'prop' }, val );
}
}

@@ -150,0 +159,0 @@

@@ -91,3 +91,7 @@ // Compiler functions and utilities shared across all phases

},
type: { reject: rejectNonType }, // TODO: more detailed later (e.g. for enum base type?)
type: { // TODO: more detailed later (e.g. for enum base type?)
reject: rejectNonType,
deprecateSmart: true,
warn: warnAboutElementWithNonType,
},
// if we want to disallow assoc nav for TYPE, do not do it her

@@ -107,4 +111,9 @@ typeOf: { next: '_$next' },

filter: { next: '_$next', lexical: 'main' },
from: { reject: rejectNonSource, assoc: 'from', argsSpec: 'expr' },
const: { next: '_$next', reject: rejectNonConst },
from: {
reject: rejectNonSource,
assoc: 'from',
argsSpec: 'expr',
deprecateSmart: true,
},
const: { next: '_$next', reject: rejectNonConst }, // DEFAULT
expr: { // in: from-on,

@@ -115,3 +124,2 @@ next: '_$next', escape: 'param', assoc: 'nav',

noAliasOrMixin: true, // TODO: some headReject or similar
escape: 'param', // meaning of ':' in front of path? search in 'params'
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment

@@ -122,3 +130,3 @@ rootEnv: 'elements', // the final environment for the path root

'mixin-on': {
escape: 'param', // meaning of ':' in front of path? search in 'params'
escape: 'param', // TODO: extra check that assocs containing param in ON is not published
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment

@@ -181,2 +189,14 @@ noDep: true, // do not set dependency for circular-check

/**
* Warns about artifacts that cannot be used as an element type.
*
* @param {XSN.Artifact} art
* @param {XSN.Artifact} user
*/
function warnAboutElementWithNonType( art, user ) {
return (user.kind === 'element' && art.query)
? 'expected-no-query'
: undefined;
}
function rejectNonEntity( art ) {

@@ -254,8 +274,4 @@ return ([ 'view', 'entity' ].includes( art.kind ) && !(art.abstract && art.abstract.val))

if (!spec.escape) {
const variant = (env.$frontend && env.$frontend !== 'cdl') ? 'std' : 'cdl';
message( 'ref-unexpected-scope', head.location, user, { name: head.id, '#': variant },
'Error', {
std: 'Unexpected parameter scope for name $(NAME)',
cdl: 'Unexpected `:` before name $(NAME)',
} );
message( 'ref-unexpected-scope', ref.location, user, {},
'Error', 'Unexpected parameter reference' );
return setLink( ref, null );

@@ -362,2 +378,7 @@ }

}
if (spec.warn) {
const msgId = spec.warn( art, user );
if (msgId)
message( msgId, ref.location, user, {}, 'Warning' );
}
if (user && (!spec.noDep ||

@@ -380,5 +401,31 @@ spec.noDep === 'only-entity' && art.kind !== 'entity' && art.kind !== 'view')) {

}
// Warning for CDL TYPE OF references without ':' or shifted ':'
if (spec.deprecateSmart && typeof ref.scope === 'number' &&
!(env.$frontend && env.$frontend !== 'cdl'))
deprecateSmart( ref, art, user );
return setLink( ref, art );
}
// Issue warnings for deprecates "smart" element-in-artifact references
// without a colon; and warnings for misplaced colons in references.
// This function likely disappears again in cds-compiler v2.x.
function deprecateSmart( ref, art, user ) {
const { path } = ref;
const scope = path.findIndex( i => i._artifact._main );
if (ref.scope) { // provided a ':' in the ref path
if (scope === ref.scope) // correctly between main artifact and element
return;
const item = path[ref.scope];
message( 'ref-unexpected-colon', item.location, user, { id: item.id },
'Warning', 'Replace the colon before $(ID) by a dot' );
ref.scope = 0; // correct (otherwise CSN refs are wrong)
}
if (scope >= 0) { // we have a element-in-artifact reference
const item = path[scope];
message( 'ref-missing-colon', item.location, user, { id: item.id },
'Warning', 'Replace the dot before $(ID) by a colon' );
ref.scope = scope; // no need to recalculate in to-csn.js
}
}
// Resolve the type arguments provided with a type referenced for artifact or

@@ -509,3 +556,3 @@ // element `artifact`. This function does nothing if the referred type

if (spec.noMessage || msgArt === true && extDict === model.definitions)
return setLink( head, null );
return null;

@@ -690,6 +737,18 @@ const valid = [];

function defineAnnotations( construct, art, block, priority ) {
// namespaces cannot be annotated but we check for it because of
// builtin contexts that appear as 'namespace'
if (art.kind === 'namespace')
return;
if (!options.parseCdl && construct.kind === 'annotate') {
// Namespaces cannot be annotated in CSN but because they exist as XSN artifacts
// they can still be applied. Namespace annotations are extracted in to-csn.js
// In parseCdl mode USINGs and other unknown references are generated as
// namespaces which would lead to false positives.
if (art.kind === 'namespace') {
message( 'anno-namespace', construct.name.location, construct, {}, 'Info',
'Namespaces cannot be annotated' );
}
// Builtin annotations would also get lost. Same as for namespaces:
// extracted in to-csn.js
else if (art.builtin === true) {
message( 'anno-builtin', construct.name.location, construct, {}, 'Info',
'Builtin types should not be annotated. Use custom type instead.' );
}
}
// TODO: block should be construct._block

@@ -701,2 +760,4 @@ if (construct.annotationAssignments && construct.annotationAssignments.doc )

return;
// annotationAssignments is set if parsed from CDL but may not be set if
// the input is CSN so we extract them.
for (const annoProp in construct) {

@@ -709,3 +770,3 @@ if (annoProp.charAt(0) === '@') {

setProp( a, '_block', block );
addToDict( art, annoProp, a );
setAnnotation(art, annoProp, a, priority);
}

@@ -757,4 +818,2 @@ }

};
if (priority)
anno.priority = priority;
setProp( anno, '_block', block );

@@ -809,2 +868,3 @@ // TODO: _parent, _main is set later (if we have ElementRef), or do we

// TODO: make this just elem._origin, remove elem.origin
setProp( elem, '_deps', [ { art: origin, location } ] );
return elem;

@@ -811,0 +871,0 @@ }

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

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

@@ -440,3 +440,3 @@

if(properties.length === 0) {
signal(signal.error`ComplexType "${structuredTypeCsn.name}" has no properties`, ['definitions', structuredTypeCsn.name]);
signal(signal.warning`ComplexType "${structuredTypeCsn.name}" has no properties`, ['definitions', structuredTypeCsn.name]);
}

@@ -504,3 +504,3 @@ complexType.append(...(properties));

if(fromRole === toRole) {
if(constraints._originAssocCsn)
if(constraints._partnerCsn)
fromRole += '1';

@@ -525,3 +525,3 @@ else

/*
If NavigationProperty is a backlink association (constraints._originAssocCsn is set), then there are two options:
If NavigationProperty is a backlink association (constraints._partnerCsn is set), then there are two options:
1) Counterpart NavigationProperty exists and is responsible to create the edm:Association element which needs to

@@ -534,3 +534,3 @@ be reused by this backlink association. This is save because at this point of the processing all NavProps are created.

let reuseAssoc = false;
let forwardAssocCsn = constraints._originAssocCsn;
let forwardAssocCsn = constraints._partnerCsn;
if(forwardAssocCsn)

@@ -553,3 +553,3 @@ {

reuseAssoc = !!forwardAssocCsn._NavigationProperty;
constraints = edmUtils.getReferentialConstraints(forwardAssocCsn, signal, options);
constraints = forwardAssocCsn._constraints;
constraints._multiplicity = edmUtils.determineMultiplicity(forwardAssocCsn);

@@ -556,0 +556,0 @@ }

@@ -834,6 +834,22 @@ // @ts-nocheck

if (csn.default && isBetaEnabled(options, 'odataDefaultValues'))
// OData only allows simple values, no complex expressions or function calls
// This is a poor man's expr renderer, assuming that this value has passed
// the defaultValues validator
if (csn.default && isBetaEnabled(options, 'odataDefaultValues')) {
let defVal = csn.default.val;
if(csn.default.xpr) {
defVal = csn.default.xpr.map(i => {
if(i.val !== undefined) {
if(csn.type === 'cds.Boolean')
return i.val ? 'true' : 'false';
return i.val;
}
return i;
}).join('');
}
this[`Default${this.v4 ? 'Value' : ''}`] = ['cds.Boolean', 'cds.Binary', 'cds.LargeBinary', 'cds.Integer64', 'cds.Integer'].includes(csn.type)
? csn.default.val
: edmUtils.escapeString(csn.default.val);
? defVal
: edmUtils.escapeString(defVal);
}
}

@@ -889,4 +905,4 @@

let [src, tgt] = edmUtils.determineMultiplicity(csn._constraints._originAssocCsn || csn);
csn._constraints._multiplicity = csn._constraints._originAssocCsn ? [tgt, src] : [src, tgt];
let [src, tgt] = edmUtils.determineMultiplicity(csn._constraints._partnerCsn || csn);
csn._constraints._multiplicity = csn._constraints._partnerCsn ? [tgt, src] : [src, tgt];

@@ -910,3 +926,3 @@ this.set( {

}
let partner = (csn._partnerCsn.length > 0) ? csn._partnerCsn[0] : csn._constraints._originAssocCsn;
let partner = (csn._selfReferences.length > 0) ? csn._selfReferences[0] : csn._constraints._partnerCsn;
if(partner && partner['@odata.navigable'] !== false) {

@@ -913,0 +929,0 @@ this.Partner = partner.name;

'use strict';
/* eslint max-statements-per-line:off */
const { setProp, isBetaEnabled } = require('../base/model');
const { forEachDefinition, isEdmPropertyRendered, forEachMemberRecursively, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
const { forEachDefinition, forEachGeneric, forEachMember, forEachMemberRecursively,
isEdmPropertyRendered, getUtils, cloneCsn, isBuiltinType } = require('../model/csnUtils');
const alerts = require('../base/alerts');

@@ -16,7 +17,7 @@ const edmUtils = require('./edmUtils.js')

isStructuredArtifact,
isEntityOrView,
isParameterizedEntityOrView,
isActionOrFunction,
getReferentialConstraints,
resolveOnConditionAndPrepareConstraints,
finalizeReferentialConstraints,
isSimpleIdentifier,
isEntityOrView,
} = require('./edmUtils.js');

@@ -52,10 +53,16 @@

// First attach names to all definitions in the model
forAll(csn.definitions, (a, n) => {
assignProp (a, 'name', n);
});
foreach(csn.definitions, isActionOrFunction, a => {
forAll(a.params, (p,n) => {
setProp (p, 'name', n);
// First attach names to all definitions (and actions/params) in the model
// elements are done in initializeStruct
forEachDefinition(csn, (def, defName) => {
assignProp (def, 'name', defName);
// Attach name to bound actions, functions and parameters
forEachGeneric(def, 'actions', (a, n) => {
assignProp(a, 'name', n);
forEachGeneric(a, 'params', (p, n) => {
assignProp(p, 'name', n);
});
});
// Attach name unbound action parameters
forEachGeneric(def, 'params', (p,n) => {
assignProp(p, 'name', n);
})

@@ -65,4 +72,4 @@ });

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

@@ -74,72 +81,62 @@ services.push(art);

let serviceNames = services.map(s => s.name);
function whatsMyServiceName(n) {
return serviceNames.reduce((rc, sn) => n.startsWith(sn + '.') ? rc = sn : rc, undefined);
}
const serviceNames = services.map(s => s.name);
if(serviceNames.length) {
services.forEach(initializeService);
// Set myServiceName for later reference and indication of a service member
// First attach names to all definitions in the model
// Link association targets and spray @odata.contained over untagged compositions
foreach(csn.definitions, isStructuredArtifact, linkAssociationTarget);
forEachDefinition(csn, [ (def, defName) => {
setProp(def, '$myServiceName', whatsMyServiceName(defName)) }, linkAssociationTarget ]);
// Create data structures for containments
foreach(csn.definitions, isStructuredArtifact, initializeContainments);
forEachDefinition(csn, initializeContainments);
// Initialize entities with parameters (add Parameter entity)
foreach(csn.definitions, isParameterizedEntityOrView, initializeParameterizedEntityOrView);
forEachDefinition(csn, initializeParameterizedEntityOrView);
// Initialize structures
foreach(csn.definitions, isStructuredArtifact, initializeStructure);
// Initialize associations
foreach(csn.definitions, isStructuredArtifact, initializeAssociation);
// get constraints for associations
foreach(csn.definitions, isStructuredArtifact, initializeConstraints);
forEachDefinition(csn, initializeStructure);
// Initialize associations after _parent linking
forEachDefinition(csn, prepareConstraints);
// Mute V4 elements depending on constraint preparation
if(options.isV4())
forEachDefinition(csn, ignoreProperties);
// calculate constraints based on mutePropertiesForV4 and prepareConstraints
forEachDefinition(csn, finalizeConstraints);
// create association target proxies
foreach(csn.definitions, isStructuredArtifact, redirectDanglingAssociationsToProxyTargets);
// create edmKeyRefPaths
foreach(csn.definitions, isEntityOrView, initializeEdmKeyRefPaths);
forEachDefinition(csn, redirectDanglingAssociationsToProxyTargets);
// decide if an entity set needs to be constructed or not
foreach(csn.definitions, isStructuredArtifact, determineEntitySet);
// Check the artifact identifier for compliance with the odata specification
forAll(csn.definitions, checkArtifactIdentifier);
// 1. let all doc props become @Core.Descriptions
// 2. mark a member that will become a collection
// 3. assign the edm primitive type to elements, to be used in the rendering later
forEachDefinition(csn, artifact => {
assignAnnotation(artifact, '@Core.Description', artifact.doc);
markCollection(artifact);
mapCdsToEdmProp(artifact);
if (artifact.returns) {
markCollection(artifact.returns);
mapCdsToEdmProp(artifact.returns);
}
forEachMemberRecursively(artifact,
member => {
assignAnnotation(member, '@Core.Description', member.doc);
markCollection(member);
mapCdsToEdmProp(member);
if (member.returns) {
markCollection(member.returns);
mapCdsToEdmProp(member.returns);
}
}
);
});
// Things that can be done in one pass
// Create edmKeyRefPaths
// Decide if an entity set needs to be constructed or not
// Map /** doc comments */ to @CoreDescription
// Artifact identifier spec compliance check (should be run last)
forEachDefinition(csn,
[ initializeEdmKeyRefPaths, determineEntitySet, mapDocCommentToCoreDescription, checkArtifactIdentifier ]);
}
return [services, options];
//////////////////////////////////////////////////////////////////////
//
// Service initialization starts here
//
// initialize the service itself
function initializeService(service) {
checkServiceIdentifier(service.name);
setSAPSpecificV2AnnotationsToEntityContainer(options, service);
}
function checkServiceIdentifier(serviceName) {
if (serviceName.length > 511) {
// check service name
if (service.name.length > 511) {
// don't show long service name in message
signalIllegalIdentifier(false, ['definitions', serviceName], 'namespace', 'must not exceed 511 characters');
signalIllegalIdentifier(false, ['definitions', service.name], 'namespace', 'must not exceed 511 characters');
}
const simpleIdentifiers = serviceName.split('.');
const simpleIdentifiers = service.name.split('.');
simpleIdentifiers.forEach((identifier) => {
if (!isSimpleIdentifier(identifier)) {
// don't show long service name in message
signalIllegalIdentifier(false, ['definitions', serviceName], 'namespace',
signalIllegalIdentifier(false, ['definitions', service.name], 'namespace',
'must consist of one or more dot separated simple identifiers (each starting with a letter or underscore, followed by at most 127 letters)');
}
});
setSAPSpecificV2AnnotationsToEntityContainer(options, service);
}

@@ -149,20 +146,20 @@

function linkAssociationTarget(struct) {
foreach(struct.elements, isAssociationOrComposition, (element, name) => {
if (element._ignore)
return;
if(!element._target) {
let target = csn.definitions[element.target];
if(target) {
setProp(element, '_target', target);
forEachMemberRecursively(struct, (element, name, prop, subpath) => {
if(isAssociationOrComposition(element) && !element._ignore) {
if(!element._target) {
let target = csn.definitions[element.target];
if(target) {
setProp(element, '_target', target);
// If target has parameters, xref assoc at target for redirection
if(isParameterizedEntityOrView(target)) {
if(!target.$sources) {
setProp(target, '$sources', Object.create(null));
if(isParameterizedEntityOrView(target)) {
if(!target.$sources) {
setProp(target, '$sources', Object.create(null));
}
target.$sources[struct.name + '.' + name] = element;
}
target.$sources[struct.name + '.' + name] = element;
}
else {
signal(signal.error`Target ${element.target} cannot be found in the model`, subpath);
}
}
else {
signal(signal.error`Target ${element.target} cannot be found in the model`, [ 'definitions', struct.name, 'elements', element.name ]);
}
}

@@ -193,28 +190,28 @@ // in V4 tag all compositions to be containments

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

@@ -224,32 +221,2 @@ });

function signalIllegalIdentifier(identifier, path, kind, msg) {
signal(signal.warning`OData ${kind} ${identifier ? `: "${identifier}"` : ''} ${ msg ? msg : 'must start with a letter or underscore, followed by at most 127 letters, underscores or digits' }`, path);
}
// Check the artifact identifier for compliance with the odata specification
function checkArtifactIdentifier(artifact) {
const serviceName = whatsMyServiceName(artifact.name);
if(serviceName) {
const artifactName = artifact.name.replace(serviceName + '.', '');
if(artifact.kind === 'action' || artifact.kind === 'function'){
checkActionOrFunctionIdentifier(artifact, artifactName);
} else if(!isSimpleIdentifier(artifactName)){
signalIllegalIdentifier(artifactName, ['definitions', artifact.name], 'entity name');
}
}
function checkActionOrFunctionIdentifier(actionOrFunction, actionOrFunctionName) {
if(!isSimpleIdentifier(actionOrFunctionName)){
signalIllegalIdentifier(actionOrFunctionName, actionOrFunction.$path, 'function or action name');
}
if(actionOrFunction.params) {
forAll(actionOrFunction.params, (param) => {
if(!isSimpleIdentifier(param.name)){
signalIllegalIdentifier(param.name, param.$path, 'function or action parameter name');
}
});
}
}
}
// Split an entity with parameters into two entity types with their entity sets,

@@ -262,4 +229,9 @@ // one named <name>Parameter and one named <name>Type. Parameter contains Type.

// must be called.
// As a param entity is a potential proxy candidate, this split must be performed on
// all definitions
function initializeParameterizedEntityOrView(entityCsn, entityName) {
if(!isParameterizedEntityOrView(entityCsn))
return;
// Naming rules for aggregated views with parameters

@@ -308,2 +280,3 @@ // Parameters: EntityType <ViewName>Parameters, EntitySet <ViewName>

setProp(parameterCsn, '$isParamEntity', true);
setProp(parameterCsn, '$myServiceName', entityCsn.$myServiceName);

@@ -319,3 +292,3 @@ // propagate containment information, if containment is recursive, use parameterCsn.name as _containerEntity

forAll(entityCsn.params, (p,n) => {
forEachGeneric(entityCsn, 'params', (p,n) => {
let elt = cloneCsn(p);

@@ -353,3 +326,3 @@ elt.name = n;

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

@@ -369,2 +342,4 @@ }

Object.keys(entityCsn.$sources || {}).forEach(n => {
// preserve the original target for constraint calculation
setProp(entityCsn.$sources[n], '_originalTarget', entityCsn.$sources[n]._target);
entityCsn.$sources[n]._target = parameterCsn;

@@ -376,9 +351,2 @@ entityCsn.$sources[n].target = parameterCsn.name;

// Initialize structured artifact (type or entity) 'struct' by doing the
// following:
// - attach attributes 'name', 'Name' to elements (FIXME: We currently really require both 'Name' and 'name'!)
// - create a property 'keys' with all its primary key elements
// - optionally add the magic ValueList association
// - call 'initializeAssociation' for each element that has an association type
// - attach attribute 'name' to all actions and their parameters.

@@ -390,3 +358,8 @@ function initElement(element, name, struct) {

function initializeStructure(struct) {
// Initialize a structured artifact
function initializeStructure(def) {
if(!isStructuredArtifact(def))
return;
let keys = Object.create(null);

@@ -396,4 +369,4 @@ let validFrom = [], validKey = [];

// Iterate all struct elements
forAll(struct.elements, (element, elementName) => {
initElement(element, elementName, struct);
forEachGeneric(def, 'elements', (element, elementName) => {
initElement(element, elementName, def);

@@ -403,6 +376,7 @@ if(!isSimpleIdentifier(elementName)) {

signal.warning`OData property name: "${elementName}" must start with a letter or underscore, followed by at most 127 letters, underscores or digits`,
['definitions', struct.name, 'elements', elementName]
['definitions', def.name, 'elements', elementName]
);
}
// collect temporal information
if(element['@cds.valid.key']) {

@@ -415,61 +389,23 @@ validKey.push(element);

if(options.isV4()) {
/* Do not expose
1) foreign keys in structured mode (always)
1a) foreign keys of associations to container
(but only for containment establishing
association, see FIXME below)
2) elements that are tagged with @odata.containment.ignore
and parent is containee
*/
if(!element.target) {
if(element['@odata.foreignKey4']) {
let isContainerAssoc = false;
let elements = struct.elements;
let assoc = undefined;
let paths = element['@odata.foreignKey4'].split('.')
for(let p of paths) {
assoc = elements[p];
if(assoc) // could be that the @odata.foreignKey4 was propagated...
elements = assoc.elements;
}
// initialize an association
if(isAssociationOrComposition(element) || element._ignore) {
if(!element._target) {
throw Error('Expect target to be resolved, parent: ' + def.name + ', assoc: ' + element.name + ', target: ' + element.target);
}
if(assoc)
isContainerAssoc = assoc._isToContainer || assoc['@odata.contained'];
/* FIXME:
In combination flat/containment, keys of non-parent association shall
be rendered:
entity Orders {
...;
items: composition of many Items on $self = items.parent;
}
entity Items {
...;
parent: association to Orders;
// FKs shall be rendered as this is not the assoc that establishes the composition
// in flat mode only
leadOrder: association to Orders;
};
*/
// in case this is a forward assoc, store the backlink partneres here, _selfReferences.length > 1 => error
assignProp(element, '_selfReferences', []);
assignProp(element._target, '$proxies', []);
/*
If this foreign key is NOT a container fk, let isEdmPropertyRendered() decide
Else, if fk is container fk, omit it if it wasn't requested in structured mode
*/
if((!isContainerAssoc && !isEdmPropertyRendered(element, options)) ||
(isContainerAssoc && !options.renderForeignKeys))
assignAnnotation(element, '@cds.api.ignore', true);
//forward annotations from managed association element to its foreign keys
if(element.keys && options.isFlatFormat) {
for(let fk of element.keys) {
forAll(element, (attr, attrName) => {
if(attrName[0] === '@')
def.elements[fk.$generatedFieldName][attrName] = attr;
});
}
// if this is an containment ignore tagged element,
// ignore it if option odataContainment is true and no foreign keys should be rendered
if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys)
assignAnnotation(element, '@cds.api.ignore', true);
}
// it's an association
else if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys) {
// if this is an explicitly containment ignore tagged association,
// ignore it if option odataContainment is true and no foreign keys should be rendered
assignAnnotation(element, '@odata.navigable', false);
}
// and afterwards eventually remove some :)
setSAPSpecificV2AnnotationsToAssociation(options, element, def);
}

@@ -481,3 +417,3 @@

}
applyAppSpecificLateCsnTransformationOnElement(options, element, struct);
applyAppSpecificLateCsnTransformationOnElement(options, element, def);
});

@@ -490,3 +426,3 @@

validKey.forEach(vk => altKeys[0].Key.push( { Name: vk.name, Alias: vk.name } ) );
assignAnnotation(struct, '@Core.AlternateKeys', altKeys);
assignAnnotation(def, '@Core.AlternateKeys', altKeys);
}

@@ -507,3 +443,3 @@ }

});
assignAnnotation(struct, '@Core.AlternateKeys', altKeys);
assignAnnotation(def, '@Core.AlternateKeys', altKeys);
keys = Object.create(null);

@@ -522,87 +458,119 @@ validKey.forEach(e => {

}
assignProp(struct, '_SetAttributes', Object.create(null));
assignProp(struct, '$keys', keys);
applyAppSpecificLateCsnTransformationOnStructure(options, struct);
setSAPSpecificV2AnnotationsToEntitySet(options, struct);
// initialize bound actions and functions
// prepare the structure itself
if(isEntityOrView(def)) {
assignProp(def, '_SetAttributes', Object.create(null));
assignProp(def, '$keys', keys);
applyAppSpecificLateCsnTransformationOnStructure(options, def);
setSAPSpecificV2AnnotationsToEntitySet(options, def);
}
}
// Attach name to actions and their parameters
forAll(struct.actions, (a, n) => {
a.name = n;
forAll(a.params, (p, n) => {
p.name = n;
});
// Prepare the associations for the subsequent steps
function prepareConstraints(struct) {
forEachMember(struct, element => {
if (isAssociationOrComposition(element) && !element._ignore) {
// setup the constraints object
setProp(element, '_constraints', { constraints: Object.create(null), selfs: [], _origins: [], termCount: 0 });
// and crack the ON condition
resolveOnConditionAndPrepareConstraints(element, signal);
}
});
}
// Resolve the association type of 'element' in 'struct' by doing the following:
// - collect the foreign key elements for the target into attribute 'elements'
function initializeAssociation(struct) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore)
return;
if(!element._target) {
throw Error('Expect target to be resolved, parent: ' + struct.name + ', assoc: ' + element.name + ', target: ' + element.target);
}
/*
Do not render (ignore) elements as properties
In V4:
1) If this is a foreign key of an association to a container which *is* used
to establish the containment via composition and $self comparison, then
do not render this foreign key. The $self comparison can only be evaluated
after the ON conditions have been parsed in prepareConstraints().
2) For all other foreign keys let isEdmPropertyRendered() decide.
3) If an element/association is annotated with @odata.containment.ignore and containment is
active, assign @cds.api.ignore or @odata.navigable: false
4) All of this can be revoked with options.renderForeignKeys.
*/
function ignoreProperties(struct) {
forEachGeneric(struct, 'elements', (element) => {
if(!element.target) {
if(element['@odata.foreignKey4']) {
let isContainerAssoc = false;
let elements = struct.elements;
let assoc = undefined;
let paths = element['@odata.foreignKey4'].split('.')
for(let p of paths) {
assoc = elements[p];
if(assoc) // could be that the @odata.foreignKey4 was propagated...
elements = assoc.elements;
}
// in case this is a forward assoc, store the backlink partneres here, _partnerCsn.length > 1 => error
setProp(element, '_partnerCsn', []);
setProp(element._target, '$proxies', []);
if(assoc)
isContainerAssoc = assoc._isToContainer && assoc._selfReferences.length || assoc['@odata.contained'];
/*
If this foreign key is NOT a container fk, let isEdmPropertyRendered() decide
Else, if fk is container fk, omit it if it wasn't requested in structured mode
*/
if((!isContainerAssoc && !isEdmPropertyRendered(element, options)) ||
(isContainerAssoc && !options.renderForeignKeys))
assignAnnotation(element, '@cds.api.ignore', true);
//forward annotations from managed association element to its foreign keys
if(element.keys && options.isFlatFormat) {
for(let fk of element.keys) {
forAll(element, (attr, attrName) => {
if(attrName[0] === '@')
struct.elements[fk.$generatedFieldName][attrName] = attr;
});
}
// if this is an containment ignore tagged element,
// ignore it if option odataContainment is true and no foreign keys should be rendered
if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys)
assignAnnotation(element, '@cds.api.ignore', true);
}
// and afterwards eventually remove some :)
setSAPSpecificV2AnnotationsToAssociation(options, element, struct);
// it's an association
else if(element['@odata.containment.ignore'] && options.odataContainment && !options.renderForeignKeys) {
// if this is an explicitly containment ignore tagged association,
// ignore it if option odataContainment is true and no foreign keys should be rendered
assignAnnotation(element, '@odata.navigable', false);
}
});
}
function initializeConstraints(struct) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore) return;
setProp(element, '_constraints', getReferentialConstraints(element, signal, options));
/*
Calculate the final referential constraints based on the assignments done in mutePropertiesForV4()
It may be that now a number of properties are not rendered and cannot act as constraints (see isConstraintCandidate())
in edmUtils
*/
function finalizeConstraints(struct) {
forEachMember(struct, element => {
if (isAssociationOrComposition(element) && !element._ignore) {
finalizeReferentialConstraints(element, options);
// only in V2 we must set the target cardinality of the backlink to the forward:
if(element._constraints._originAssocCsn && element.cardinality && element.cardinality.max) {
if(element._constraints._originAssocCsn.cardinality) {
if(element._constraints._originAssocCsn.cardinality.src) {
let srcMult = (element._constraints._originAssocCsn.cardinality.src == 1) ? '0..1' : '*';
let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
if(options.isV2() && srcMult != newMult) {
if(element._constraints._partnerCsn && element.cardinality && element.cardinality.max) {
// if this is a partnership and this assoc has a set target cardinality, assign it as source cardinality to the partner
if(element._constraints._partnerCsn.cardinality) {
if(element._constraints._partnerCsn.cardinality.src) {
let srcMult = (element._constraints._partnerCsn.cardinality.src == 1) ? '0..1' : '*';
let newMult = (element.cardinality.max > 1) ? '*' : '0..1';
if(options.isV2() && srcMult != newMult) {
// Association 'E_toF': Multiplicity of Role='E' defined to '*', conflicting with target multiplicity '0..1' from
signal(signal.warning`Source cardinality "${element._constraints._originAssocCsn.cardinality.src}" of "${element._constraints._originAssocCsn._parent.name}/${element._constraints._originAssocCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
signal(signal.warning`Source cardinality "${element._constraints._partnerCsn.cardinality.src}" of "${element._constraints._partnerCsn._parent.name}/${element._constraints._partnerCsn.name}" conflicts with target cardinality "${element.cardinality.max}" of association "${element._parent.name}/${element.name}"`);
}
}
else {
element._constraints._partnerCsn.cardinality.src = element.cardinality.max;
}
}
else {
element._constraints._originAssocCsn.cardinality.src = element.cardinality.max;
element._constraints._partnerCsn.cardinality = { src: element.cardinality.max };
}
}
else {
element._constraints._originAssocCsn.cardinality = { src: element.cardinality.max };
}
}
});
}
function whatsMyServiceName(n) {
return serviceNames.reduce((rc, sn) => n.startsWith(sn + '.') ? rc = sn : rc, undefined);
}
// For defining service create proxy target, if original target is outside of defining service
function redirectDanglingAssociationsToProxyTargets(struct) {
let myServiceName = whatsMyServiceName(struct.name);
// myServiceName is used in closure
const myServiceName = struct.$myServiceName;
// if this artifact is a service member check its associations
if(myServiceName) {
foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore || element['@odata.navigable'] === false) {
forEachGeneric(struct, 'elements', element => {
if(!isAssociationOrComposition(element) || element._ignore || element['@odata.navigable'] === false)
return;
}
/*
* Consider everthing @cds.autoexpose: falsy to be a proxy candidate for now
* Consider everything @cds.autoexpose: falsy to be a proxy candidate for now
*/

@@ -631,2 +599,3 @@ /*

proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) };
setProp(proxy, '$myServiceName', myServiceName);
setProp(proxy, '$proxy', true);

@@ -798,23 +767,24 @@ setProp(proxy, '$keys', Object.create(null));

function initializeEdmKeyRefPaths(struct) {
setProp(struct, '$edmKeyPaths', []);
// for all key elements that shouldn't be ignored produce the paths
foreach(struct.$keys, k => !k._ignore && !k._isToContainer, (k, kn) => {
if(isEdmPropertyRendered(k, options) &&
if(struct.$myServiceName && struct.$keys) {
setProp(struct, '$edmKeyPaths', []);
// for all key elements that shouldn't be ignored produce the paths
foreach(struct.$keys, k => !k._ignore && !(k._isToContainer && k._selfReferences.length), (k, kn) => {
if(isEdmPropertyRendered(k, options) &&
!(options.isV2() && k['@Core.MediaType'])) {
if(options.isV4() && options.isStructFormat) {
if(options.isV4() && options.isStructFormat) {
// This is structured OData ONLY
// if the foreign keys are explictly requested, ignore associations and use the flat foreign keys instead
if(options.renderForeignKeys && !k.target)
struct.$edmKeyPaths.push([kn]);
if(options.renderForeignKeys && !k.target)
struct.$edmKeyPaths.push([kn]);
// else produce paths (isEdmPropertyRendered() has filtered @odata.foreignKey4 already)
else if(!options.renderForeignKeys)
struct.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
}
else if(!options.renderForeignKeys)
struct.$edmKeyPaths.push(...produceKeyRefPaths(k, kn));
}
// In v2/v4 flat, associations are never rendered
else if(!k.target) {
struct.$edmKeyPaths.push([kn]);
else if(!k.target) {
struct.$edmKeyPaths.push([kn]);
}
}
}
});
});
}
/*

@@ -880,4 +850,4 @@ Produce the list of paths for this element

// No annos are rendered for non-existing EntitySet targets.
if(struct.hasEntitySet === undefined) {
let hasEntitySet = ['entity', 'view'].includes(struct.kind) && !(options.isV4() && edmUtils.isContainee(struct)) && !struct.$proxy;
if(struct.$myServiceName && struct.hasEntitySet === undefined) {
let hasEntitySet = isEntityOrView(struct) && !(options.isV4() && edmUtils.isContainee(struct)) && !struct.$proxy;
setProp(struct, 'hasEntitySet', hasEntitySet);

@@ -887,2 +857,74 @@ }

// check Identifier length
function signalIllegalIdentifier(identifier, path, kind, msg) {
signal(signal.warning`OData ${kind} ${identifier ? `: "${identifier}"` : ''} ${ msg ? msg : 'must start with a letter or underscore, followed by at most 127 letters, underscores or digits' }`, path);
}
// Check the artifact identifier for compliance with the odata specification
function checkArtifactIdentifier(artifact) {
if(artifact.$myServiceName) {
const artifactName = artifact.name.replace(artifact.$myServiceName + '.', '');
if(artifact.kind === 'action' || artifact.kind === 'function'){
checkActionOrFunctionIdentifier(artifact, artifactName);
} else if(!isSimpleIdentifier(artifactName)){
signalIllegalIdentifier(artifactName, ['definitions', artifact.name], 'entity name');
}
}
function checkActionOrFunctionIdentifier(actionOrFunction, actionOrFunctionName) {
if(!isSimpleIdentifier(actionOrFunctionName)){
signalIllegalIdentifier(actionOrFunctionName, actionOrFunction.$path, 'function or action name');
}
if(actionOrFunction.params) {
forEachGeneric(actionOrFunction, 'params', (param) => {
if(!isSimpleIdentifier(param.name)){
signalIllegalIdentifier(param.name, param.$path, 'function or action parameter name');
}
});
}
}
}
function mapDocCommentToCoreDescription(artifact) {
// 1. let all doc props become @Core.Descriptions
// 2. mark a member that will become a collection
// 3. assign the edm primitive type to elements, to be used in the rendering later
assignAnnotation(artifact, '@Core.Description', artifact.doc);
markCollection(artifact);
mapCdsToEdmProp(artifact);
if (artifact.returns) {
markCollection(artifact.returns);
mapCdsToEdmProp(artifact.returns);
}
forEachMemberRecursively(artifact,member => {
assignAnnotation(member, '@Core.Description', member.doc);
markCollection(member);
mapCdsToEdmProp(member);
if (member.returns) {
markCollection(member.returns);
mapCdsToEdmProp(member.returns);
}
});
// mark members that need to be rendered as collections
function markCollection(obj) {
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
if (items) {
assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
assignProp(obj, '_isCollection', true);
}
}
}
//
// Service initialization ends here
//
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
//
// Helper section starts here
//
// If containment in V4 is active, annotations that would be assigned to the containees

@@ -934,11 +976,2 @@ // entity set are not renderable anymore. In such a case try to reassign the annotations to

// mark members that need to be rendered as collections
function markCollection(obj) {
const items = obj.items || csn.definitions[obj.type] && csn.definitions[obj.type].items;
if (items) {
assignProp(obj, '_NotNullCollection', items.notNull !== undefined ? items.notNull : true);
assignProp(obj, '_isCollection', true);
}
}
function mapCdsToEdmProp(obj) {

@@ -1120,4 +1153,4 @@ if (obj.type && isBuiltinType(obj.type) && !isAssociationOrComposition(obj) && !obj.targetAspect) {

elements[keyName] = key;
struct.$keys = { [keyName] : key };
forAll(struct.elements, (e,n) =>
setProp(struct, '$keys',{ [keyName] : key } );
forEachGeneric(struct, 'elements', (e,n) =>
{

@@ -1124,0 +1157,0 @@ if(e.key) delete e.key;

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

function getReferentialConstraints(assocCsn, signal, options)
{
let result = { constraints: Object.create(null), selfs: [], termCount: 0 };
function resolveOnConditionAndPrepareConstraints(assocCsn, signal) {
if(!assocCsn._constraints)
throw Error('Please debug me: need _constraints');

@@ -158,5 +158,5 @@ if(assocCsn.on)

// for all $self conditions, fill constraints of partner (if any)
let isBacklink = result.selfs.length == 1 && result.termCount == 1;
let isBacklink = assocCsn._constraints.selfs.length == 1 && assocCsn._constraints.termCount == 1;
/* example for originalTarget:
/* example for _originalTarget:
entity E (with parameters) {

@@ -169,12 +169,10 @@ ... keys and all the stuff ...

back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail)
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
ON Condition back.toE => parter=toE cannot be resolved in EParameters, _originalTarget 'E' is
required for that
*/
result.selfs.filter(p => p).forEach(partner => {
let originAssocCsn = assocCsn._target.elements[partner];
if(originAssocCsn == undefined && assocCsn.originalTarget)
originAssocCsn = assocCsn.originalTarget.elements[partner];
let parentArtifactName = assocCsn._parent.name;
assocCsn._constraints.selfs.filter(p => p).forEach(partner => {
const originAssocCsn = (assocCsn._originalTarget || assocCsn._target).elements[partner];
const parentArtifactName = assocCsn._parent.name;
if(originAssocCsn) {
if(originAssocCsn._target != assocCsn._parent) {
if(originAssocCsn._originalTarget !== assocCsn._parent && originAssocCsn._target !== assocCsn._parent) {
isBacklink = false;

@@ -184,16 +182,2 @@ signal(signal.info`"${originAssocCsn._parent.name}:${partner}" with target "${originAssocCsn._target.name}" is compared with $self which represents "${parentArtifactName}"`, ['definitions', parentArtifactName, 'elements', assocCsn.name]);

if(isAssociationOrComposition(originAssocCsn)) {
// if the origin assoc is marked as primary key and if it's managed, add all its foreign keys as constraint
// as they are also primary keys of the origin entity as well
if(!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
for(let fk of originAssocCsn.keys) {
let realFk = originAssocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn._parent.elements[fk.ref[0]];
if(isConstraintCandidate(pk) && isConstraintCandidate(realFk))
{
const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
const key = c.join(',');
result.constraints[key] = c;
}
}
}
// Mark this association as backlink if $self appears exactly once

@@ -203,4 +187,4 @@ // to surpress edm:Association generation in V2 mode

// use first backlink as partner
if(originAssocCsn._partnerCsn.length === 0) {
result._originAssocCsn = originAssocCsn;
if(originAssocCsn._selfReferences.length === 0) {
assocCsn._constraints._partnerCsn = originAssocCsn;
}

@@ -211,4 +195,5 @@ else {

// collect all backlinks at forward association
originAssocCsn._partnerCsn.push(assocCsn);
originAssocCsn._selfReferences.push(assocCsn);
}
assocCsn._constraints._origins.push(originAssocCsn);
}

@@ -229,75 +214,4 @@ else {

});
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;
if(assocCsn.type === 'cds.Composition') {
principalEntity = assocCsn._parent;
dependentEntity = assocCsn._target;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
Object.keys(result.constraints).forEach(cn => {
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } );
}
// Remove all arget elements that are not key in the principal entity
// and all elements that annotated with '@cds.api.ignore'
foreach(result.constraints,
c => {
// concatenate all paths in flat mode to identify the correct element
// in structured mode only resolve top level element (path rewriting is done elsewhere)
let fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
let pk = principalEntity.$keys[ ( options.isFlatFormat ? c[1].join('_') : c[1][0] )];
return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));
},
(c, cn) => { delete result.constraints[cn]; } );
}
}
// Handle managed association, a managed composition is treated as association
else
{
// If FK is key in target => constraint
// Don't consider primary key associations (fks become keys on the source entity) as
// this would impose a constraint against the target.
// Filter out all elements that annotated with '@cds.api.ignore'
// In structured format, foreign keys of managed associations are never rendered, so
// there are no constraints for them.
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]];
if(pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk))
{
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
const key = c.join(',');
result.constraints[key] = c;
}
}
}
}
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
// continue with multiplicity
if(assocCsn._target.$isParamEntity)
{
result.constraints = Object.create(null);
}
return result;
/*
* In Flat Mode an element is a constraint candidate if it is of scalar type.
* In Structured mode, it eventually can be of a named type (which is
* by the construction standards for OData either a complex type or a
* type definition (alias to a scalar type).
* The element must never be an association or composition and be renderable.
*/
function isConstraintCandidate(elt) {
let rc= (elt &&
elt.type &&
(!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
!['cds.Association', 'cds.Composition'].includes(elt.type) &&
isEdmPropertyRendered(elt, options));
return rc;
}
// nested functions

@@ -333,3 +247,3 @@ function getExpressionArguments(expr)

{
result.termCount++;
assocCsn._constraints.termCount++;
if(lhs.ref && rhs.ref) // ref is a path

@@ -362,6 +276,6 @@ {

if(c[0][0] === '$self' && c[0].length === 1) {
result.selfs.push(c[1][0]);
assocCsn._constraints.selfs.push(c[1][0]);
} else {
const key = c.join(',');
result.constraints[key] = c;
assocCsn._constraints.constraints[key] = c;
}

@@ -376,2 +290,110 @@ }

function finalizeReferentialConstraints(assocCsn, options)
{
if(!assocCsn._constraints)
throw Error('Please debug me: need _constraints');
if(assocCsn.on)
{
/* example for originalTarget:
entity E (with parameters) {
... keys and all the stuff ...
toE: association to E;
back: association to E on back.toE = $self
}
toE target 'E' is redirected to 'EParameters' (must be as the new parameter list is required)
back target 'E' is also redirected to 'EParameters' (otherwise backlink would fail)
ON Condition back.toE => parter=toE cannot be resolved in EParameters, originalTarget 'E' is
required for that
*/
assocCsn._constraints._origins.forEach(originAssocCsn => {
// if the origin assoc is marked as primary key and if it's managed, add all its foreign keys as constraint
// as they are also primary keys of the origin entity as well
if(!assocCsn._target.$isParamEntity && originAssocCsn.key && originAssocCsn.keys) {
for(let fk of originAssocCsn.keys) {
let realFk = originAssocCsn._parent.elements[fk.$generatedFieldName];
let pk = assocCsn._parent.elements[fk.ref[0]];
if(isConstraintCandidate(pk) && isConstraintCandidate(realFk))
{
const c = [ [ fk.ref[0] ], [ fk.$generatedFieldName ] ];
const key = c.join(',');
assocCsn._constraints.constraints[key] = c;
}
}
}
});
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;
if(assocCsn.type === 'cds.Composition') {
principalEntity = assocCsn._parent;
dependentEntity = assocCsn._target;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
Object.keys(assocCsn._constraints.constraints).forEach(cn => {
assocCsn._constraints.constraints[cn] = [ assocCsn._constraints.constraints[cn][1], assocCsn._constraints.constraints[cn][0] ] } );
}
// Remove all arget elements that are not key in the principal entity
// and all elements that annotated with '@cds.api.ignore'
foreach(assocCsn._constraints.constraints,
c => {
// concatenate all paths in flat mode to identify the correct element
// in structured mode only resolve top level element (path rewriting is done elsewhere)
let fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
let pk = principalEntity.$keys[ ( options.isFlatFormat ? c[1].join('_') : c[1][0] )];
return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));
},
(c, cn) => { delete assocCsn._constraints.constraints[cn]; } );
}
}
// Handle managed association, a managed composition is treated as association
else
{
// If FK is key in target => constraint
// Don't consider primary key associations (fks become keys on the source entity) as
// this would impose a constraint against the target.
// Filter out all elements that annotated with '@cds.api.ignore'
// In structured format, foreign keys of managed associations are never rendered, so
// there are no constraints for them.
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]];
if(pk && pk.key && isConstraintCandidate(pk) && isConstraintCandidate(realFk))
{
const c = [ [ fk.$generatedFieldName ], [ fk.ref[0] ] ];
const key = c.join(',');
assocCsn._constraints.constraints[key] = c;
}
}
}
}
// If this association points to a redirected Parameter EntityType, do not calculate any constraints,
// continue with multiplicity
if(assocCsn._target.$isParamEntity)
{
assocCsn._constraints.constraints = Object.create(null);
}
return assocCsn._constraints;
/*
* In Flat Mode an element is a constraint candidate if it is of scalar type.
* In Structured mode, it eventually can be of a named type (which is
* by the construction standards for OData either a complex type or a
* type definition (alias to a scalar type).
* The element must never be an association or composition and be renderable.
*/
function isConstraintCandidate(elt) {
let rc= (elt &&
elt.type &&
(!options.isFlatFormat || options.isFlatFormat && isBuiltinType(elt.type)) &&
!['cds.Association', 'cds.Composition'].includes(elt.type) &&
isEdmPropertyRendered(elt, options));
return rc;
}
}
function determineMultiplicity(csn)

@@ -591,3 +613,4 @@ {

isActionOrFunction,
getReferentialConstraints,
resolveOnConditionAndPrepareConstraints,
finalizeReferentialConstraints,
determineMultiplicity,

@@ -594,0 +617,0 @@ mapCdsToEdmType,

@@ -21,6 +21,10 @@ // CSN frontend - transform CSN into XSN

* @property {Function} [arrayOf] Alternative to "type". The property should be an array.
* Value is passed to arrayOf()
* Value is passed to arrayOf().
* Value is ignored if "type" is set. Then it is only used
* for better error messages.
* @property {Function} [dictionaryOf] Alternative to "type". The property should be an object
* in dictionary form (i.e. Object.<string, type>).
* Value is passed to arrayOf()
* Value is passed to dictionaryOf().
* Value is ignored if "type" is set. Then it is only used
* for better error messages.
* @property {Object.<string, SchemaSpec>} [schema] If some sub-properties have a different

@@ -31,2 +35,4 @@ * semantic in this property than the default then

* dictionary key by default.
* @property {string} [msgProp] Display name of the property. compileSchema() sets it to
* the dictionary key (+ optional '[]') by default.
* @property {string} [msgId] Use this message id instead of the default one.

@@ -92,2 +98,5 @@ * Allows more precise and detailed error messages.

// CSN property names reserved for CAP
const ourpropsRegex = /^[_$]?[a-zA-Z]+[0-9]*$/;
// Sync with definition in to-csn.js:

@@ -103,3 +112,3 @@ const typeProperties = [

'ref', 'xpr', 'val', '#', 'func', 'SELECT', 'SET', // Core Compiler checks SELECT/SET
'param', 'global', 'literal', 'args', // only with 'ref'/'ref'/'val'/'func'
'param', 'global', 'literal', 'args', 'cast', // only with 'ref'/'ref'/'val'/'func'
];

@@ -146,2 +155,5 @@

},
i18n: {
dictionaryOf: i18nLang,
},
// definitions: ------------------------------------------------------------

@@ -175,4 +187,4 @@ definitions: {

payload: { // keep it for a while, TODO: remove with v2
dictionaryOf: definition,
type: renameTo( 'elements', dictionary ),
dictionaryOf: definition, // duplicate of line below only for better error message
type: renameTo( 'elements', dictionaryOf( definition ) ),
defaultKind: 'element',

@@ -529,3 +541,8 @@ validKinds: [],

cast: {
type: embed,
type: cast,
// cast can be:
// 1. Inside "columns" => not in value
// 2. Inside "xpr" => inside expressions
// Because of (1) we have to set this property to false.
inValue: false,
optional: typeProperties,

@@ -601,3 +618,3 @@ inKind: [ '$column' ],

optional: [
'requires', 'definitions', 'extensions',
'requires', 'definitions', 'extensions', 'i18n',
'namespace', 'version', 'messages', 'meta', 'options', '@', '$location',

@@ -656,3 +673,3 @@ ],

else if (s.dictionaryOf)
s.type = dictionary;
s.type = dictionaryOf( s.dictionaryOf );
else

@@ -722,8 +739,12 @@ throw new Error( `Missing type specification for property "${ p }` );

function embed( obj, spec, xsn, csn ) {
if (spec.prop === 'cast') // XSN TODO: make sure that $inferred is enough
xsn[csn.cast.target ? 'redirected' : '_typeIsExplicit'] = true;
function embed( obj, spec, xsn ) {
Object.assign( xsn, object( obj, spec ) ); // TODO: $location?
}
function cast( obj, spec, xsn, csn ) {
// XSN TODO: make sure that $inferred is enough
xsn[csn.cast.target ? 'redirected' : '_typeIsExplicit'] = true;
// embed all other properties, e.g. "type" and type properties
embed( obj, spec, xsn );
}
function extra( node, spec, xsn ) {

@@ -846,25 +867,28 @@ if (!xsn.$extra)

function dictionary( dict, spec, xsn, csn ) {
if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
message( 'syntax-csn-expected-object', location(true), null,
{ prop: spec.prop }, 'Error' ); // spec.prop, not spec.msgProp!
return ignore( dict );
}
if (csn.SELECT) // do not augment hidden 'elements' for 'SELECT'
return undefined;
const r = Object.create(null);
const allNames = Object.keys( dict );
if (!allNames.length)
return r; // {} in one JSON line
++virtualLine;
for (const name of allNames) {
if (!name) {
message( 'syntax-csn-empty-name', location(true), null,
{ prop: spec.prop }, 'Warning', // TODO: Error
'Property names in dictionary $(PROP) must not be empty' );
// A dictionary is expected. Uses spec.dictionaryOf. If unset, default is "definition".
function dictionaryOf( elementFct ) {
return function dictionary( dict, spec ) {
if (!dict || typeof dict !== 'object' || Array.isArray( dict )) {
message( 'syntax-csn-expected-object', location(true), null,
{ prop: spec.prop }, 'Error' ); // spec.prop, not spec.msgProp!
return ignore( dict );
}
r[name] = definition( dict[name], spec, r, dict, name );
const r = Object.create(null);
const allNames = Object.keys( dict );
if (!allNames.length)
return r; // {} in one JSON line
++virtualLine;
}
return r;
for (const name of allNames) {
if (!name) {
message( 'syntax-csn-empty-name', location(true), null,
{ prop: spec.prop }, 'Warning', // TODO: Error
'Property names in dictionary $(PROP) must not be empty' );
}
const val = elementFct( dict[name], spec, r, dict, name );
if (val !== undefined)
r[name] = val;
++virtualLine;
}
return r;
};
}

@@ -1255,2 +1279,19 @@

// i18n ------------------------------
function i18nLang( val, spec, xsn, csn, langKey ) {
/** @type {SchemaSpec} */
const keySpec = { dictionaryOf: translations, prop: langKey };
return dictionaryOf( translations )( val, keySpec, xsn, csn );
}
function translations( keyVal, spec, xsn, csn, textKey ) {
if (typeof keyVal === 'string') // allow empty string
return { val: keyVal, literal: 'string', location: location() };
message( 'syntax-csn-expected-translation', location(true), null,
{ prop: textKey, otherprop: spec.prop }, 'Error',
'Expected string for text key $(PROP) of language $(OTHERPROP)' );
return ignore( keyVal );
}
// Helper functions for objects and definitions ------------------------------

@@ -1262,4 +1303,10 @@

if (!s || s.noPrefix && prop !== p0 ) {
message( 'syntax-csn-unknown-property', location(true), null, { prop },
'Warning', 'Unknown CSN property $(PROP)' );
if (ourpropsRegex.test( prop )) {
// TODO v2: Warning only with --sloppy
message( 'syntax-csn-unknown-property', location(true), null, { prop },
'Warning', 'Unknown CSN property $(PROP)' );
}
else { // TODO v2: always (i.e. also with message) add to $extra
return { prop, type: extra };
}
}

@@ -1444,3 +1491,3 @@ else if (!expected( p0, s )) {

message( 'syntax-csn-expected-object', location(true), null, { prop: '$location' },
'Error', 'Expected object for property $(PROP)' );
'Error' );
}

@@ -1447,0 +1494,0 @@ // hidden feature: string $location

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

const { locationString } = require('../base/messages');
const { forEachGeneric } = require('../base/model');

@@ -43,3 +44,3 @@ const compilerVersion = require('../../package.json').version;

localized: value,
type: artifactRef,
type: t => artifactRef( t, !t.$extra ),
length: value,

@@ -262,2 +263,4 @@ precision: value,

csn.extensions = exts;
if (model.i18n)
csn.i18n = i18n( model.i18n );
set( 'messages', csn, model );

@@ -321,22 +324,77 @@ const [ src ] = Object.keys( model.sources );

);
if (!gensrcFlavor)
return exts;
for (const name of Object.keys( model.definitions ).sort()) {
const art = model.definitions[name];
// in definitions (without redef) with potential inferred elements:
if (!(art instanceof Array) && art.elements &&
(art.query || art.includes || art.$inferred)) {
const annos = art.$inferred && annotations( art, true );
const elems = inferred( art.elements, art.$inferred );
/** @type {object} */
const annotate = Object.assign( { annotate: name }, annos );
if (Object.keys( elems ).length)
annotate.elements = elems;
if (Object.keys( annotate ).length > 1)
exts.push( annotate );
// For namespaces and builtins: Extract annotations since they cannot be represented
// in CSN. For all other artifacts, check whether they may be auto-exposed,
// $inferred, etc. and extract their annotations.
// In parseCdl mode extensions were already put into "extensions".
if (!model.options.parseCdl && art.kind === 'namespace') {
extractAnnotationsToExtension( art );
if (art.builtin === 'reserved')
forEachGeneric( art, 'artifacts', extractAnnotationsToExtension);
}
else if (gensrcFlavor) {
// From definitions (without redefinitions) with potential inferred elements:
if (!(art instanceof Array) && art.elements &&
(art.query || art.includes || art.$inferred)) {
const annos = art.$inferred && annotations( art, true );
const elems = inferred( art.elements, art.$inferred );
/** @type {object} */
const annotate = Object.assign( { annotate: name }, annos );
if (Object.keys( elems ).length)
annotate.elements = elems;
if (Object.keys( annotate ).length > 1)
exts.push( annotate );
}
}
}
return exts;
// extract namespace/builtin annotations
function extractAnnotationsToExtension( art ) {
const name = art.name.absolute;
// 'true' because annotations on namespaces and builtins can only
// happen through extensions.
const annos = annotations( art, true );
const annotate = Object.assign( { annotate: name }, annos );
if (Object.keys( annotate ).length > 1) {
const loc = locationForAnnotationExtension();
if (loc)
location( loc, annotate, art );
exts.push( annotate );
}
// Either the artifact's name's location or (for builtin types) the location
// of its first annotation.
function locationForAnnotationExtension() {
if (art.location)
return art.location;
for (const key in art) {
if (key.charAt(0) === '@' && art[key].name)
return art[key].name.location;
}
return null;
}
}
}
/**
* @param {XSN.i18n} i18nNode
* @returns {CSN.i18n}
*/
function i18n( i18nNode ) {
const csn = Object.create( null );
for (const langKey in i18nNode) {
const langDict = i18nNode[langKey];
if (!csn[langKey])
csn[langKey] = Object.create( null );
for (const textKey in langDict)
csn[langKey][textKey] = langDict[textKey].val;
}
return csn;
}
function inferred( elems, inferredParent ) {

@@ -416,3 +474,4 @@ const ext = Object.create(null);

// for gensrcFlavor: return annotations from definition (annotated==false)
// for gensrcFlavor and namespace/builtin annotation extraction:
// return annotations from definition (annotated==false)
// or annotations (annotated==true)

@@ -425,3 +484,5 @@ function annotations( node, annotated ) {

const val = node[prop];
if ((val.priority && val.priority !== 'define') === annotated) {
// val.priority isn't set for computed annotations like @Core.Computed
// and @odata.containment.ignore
if (val.priority && (val.priority !== 'define') === annotated) {
// transformer (= value) takes care to exclude $inferred annotation assignments

@@ -546,3 +607,3 @@ const sub = transformer( val );

throw new Error( `Unexpected TYPE OF in ${ locationString(node.location) }`);
return renderArtifactPath( path, terse, node.scope );
return renderArtifactPath( node, path, terse, node.scope );
}

@@ -553,5 +614,6 @@ const { absolute } = root.name;

if (absolute === path[0].id) // normal case (no localization view)
return renderArtifactPath( path, terse ); // scope:param is not valid (and would be lost)
return renderArtifactPath( node, path, terse );
// scope:param is not valid (and would be lost)
const head = Object.assign( {}, path[0], { id: absolute } );
return renderArtifactPath( [ head, ...path.slice(1) ], terse );
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse );
}

@@ -565,7 +627,7 @@ if (node.scope === 'typeOf') { // TYPE OF without ':' in path

// TODO: forbid TYPE OF elem / TYPE OF $self.elem in queries
return renderArtifactPath( [ { id: absolute }, ...path.slice(1) ], terse );
return renderArtifactPath( node, [ { id: absolute }, ...path.slice(1) ], terse );
}
const parent = root._parent;
const structs = parent.name.element ? parent.name.element.split('.') : [];
return { ref: [ absolute, ...structs, ...path.map( pathItem ) ] };
return extra( { ref: [ absolute, ...structs, ...path.map( pathItem ) ] }, node );
}

@@ -586,6 +648,6 @@ let { scope } = node;

const head = Object.assign( {}, path[0], { id: absolute } );
return renderArtifactPath( [ head, ...path.slice(1) ], terse, scope );
return renderArtifactPath( node, [ head, ...path.slice(1) ], terse, scope );
}
function renderArtifactPath( path, terse, scope ) {
function renderArtifactPath( node, path, terse, scope ) {
if (scope === 0) {

@@ -606,3 +668,3 @@ // try to find ':' position syntactically for FROM

return (!terse || ref.length !== 1 || typeof ref[0] !== 'string')
? { ref }
? extra( { ref }, node )
: ref[0];

@@ -698,4 +760,4 @@ }

if (node.path)
return extra( { ref: node.path.map( pathItem ), param: true }, en );
return extra( { ref: [ node.param.val ], param: true }, en );
return extra( typeCast({ ref: node.path.map( pathItem ), param: true }, en), en );
return extra( typeCast({ ref: [ node.param.val ], param: true }, en), en );
}

@@ -705,3 +767,3 @@ if (node.path) {

if (node.path.length !== 1)
return extra( pathRef( node.path ), en );
return extra( typeCast( pathRef( node.path ), en ), en );
const item = pathItem( node.path[0] );

@@ -711,15 +773,17 @@ if (typeof item === 'string' && !node.path[0].quoted &&

magicFunctions.includes( item.toUpperCase() ))
return extra( { func: item }, en );
return extra( typeCast( { func: item }, en ), en );
return extra( pathRef( node.path ), en );
return extra( typeCast( pathRef( node.path ), en), en );
}
if (node.literal) {
if (typeof node.val === node.literal || node.val === null)
return extra( { val: node.val }, en );
return extra(typeCast( { val: node.val }, en ), en );
else if (node.literal === 'enum')
return extra( { '#': node.symbol.id }, en );
return extra(typeCast( { '#': node.symbol.id }, en ), en );
else if (node.literal === 'token')
return node.val; // * in COUNT(*)
return extra( { val: node.val, literal: (node.literal === 'hex') ? 'x' : node.literal },
en );
return extra(
typeCast( { val: node.val, literal: (node.literal === 'hex') ? 'x' : node.literal }, en ),
en
);
}

@@ -748,4 +812,4 @@ if (node.func) { // TODO XSN: remove op: 'call', func is no path

// do not use xpr() for xpr, as it would flatten inner xpr's (semantically ok)
return extra( { xpr: node.args.map( expression ) }, node );
return { xpr: xpr( node ) };
return extra( typeCast({ xpr: node.args.map( expression ) }, node ), node );
return typeCast({ xpr: xpr( node ) }, node);
}

@@ -916,7 +980,3 @@

elem.name, neqPath( elem.value ) );
if (elem._typeIsExplicit || elem.redirected) { // TODO XSN: introduce $inferred
col.cast = {}; // TODO: what about $extra in cast?
for (const prop of typeProperties)
set( prop, col.cast, elem );
}
typeCast(col, elem);
}

@@ -953,13 +1013,17 @@ finally {

function $extra( obj, csn ) {
for (const prop of Object.keys( obj ).sort())
csn[prop] = obj[prop];
}
function extra( csn, node ) {
if (node && node.$extra)
$extra( node.$extra, csn );
Object.assign( csn, node.$extra );
return csn;
}
function typeCast( csn, node ) {
if (node._typeIsExplicit || node.redirected) { // TODO: XSN: introduce $inferred
csn.cast = {}; // TODO: what about $extra in cast?
for (const prop of typeProperties)
set( prop, csn.cast, node );
}
return csn;
}
function setHidden( obj, prop, val ) {

@@ -966,0 +1030,0 @@ Object.defineProperty( obj, prop, {

@@ -506,2 +506,9 @@ // Generic ANTLR parser class with AST-building functions

function assignProps( target, annos = [], props, location ) {
while (Array.isArray( props )) {
// XSN TODO: change representation of parentheses around expressions
// Then this check can be removed
this.message( null, props.location || location || this.startLocation( this._ctx.start ), {},
'Error', 'Remove the parentheses around the expression' );
props = props[0];
}
if (annos === true)

@@ -508,0 +515,0 @@ return Object.assign( target, props );

@@ -91,6 +91,6 @@ // CSN functionality for resolving references

return art;
else if (!tail.length && notFound !== undefined)
else if (notFound !== undefined)
return notFound;
}
throw new Error( 'Undefined reference ');
throw new Error( 'Undefined reference' );
}

@@ -364,4 +364,5 @@

csnRefs.implicitAs = implicitAs;
csnRefs.analyseCsnPath = analyseCsnPath
csnRefs.analyseCsnPath = analyseCsnPath;
csnRefs.pathId = pathId;
module.exports = csnRefs;

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

* Loop through the model, applying the custom transformations on the node's matching.
*
*
* Each transformer gets:

@@ -745,8 +745,10 @@ * - the parent having the property

* - the path to the property
*
*
* @param {object} csn CSN to enrich in-place
* @param {Map} customTransformers Map of prop to transform and function to apply
* @param {object} customTransformers Map of prop to transform and function to apply
* @param {Function[]} artifactTransformers Transformations to run on the artifacts, like forEachDefinition
* @param {Boolean} skipIgnore Wether to skip _ignore elements or not
* @returns {object} CSN with transformations applied
*/
function applyTransformations( csn, customTransformers={}, artifactTransformers=[] ) {
function applyTransformations( csn, customTransformers={}, artifactTransformers=[], skipIgnore = true ) {
const transformers = {

@@ -771,3 +773,3 @@ elements: dictionary,

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

@@ -774,0 +776,0 @@

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

function refLocation( art ) {
return (!art) ? '<illegal link>' : art.$location || '<no location>';
if (art)
return art.$location || '<no location>';
if (!options.testMode)
return '<illegal link>';
throw new Error( 'Undefined reference' );
}

@@ -88,0 +92,0 @@

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

// Even the first step might have parameters and/or a filter
if (path.ref[0].args) {
result += `(${renderArgs(path.ref[0].args, '=>', env, syntax)})`;
// Render the actual parameter list. If the path has no actual parameters,
// the ref is not rendered as { id: ...; args: } but as short form of ref[0] ;)
// An empty actual parameter list is rendered as `()`.
const ref = csn.definitions[path.ref[0].id] || csn.definitions[path.ref[0]];
if (ref && ref.params) {
result += `(${renderArgs(path.ref[0].args || {}, '=>', env, syntax)})`;
}

@@ -715,6 +719,14 @@ else if (['udf'].includes(syntax)) {

function renderParameterDefinitions(artifactName, params) {
let result = Object.keys(params || {}).map(name => 'IN ' + quoteSqlId(name) + ' ' + renderTypeReference(artifactName, name, params[name]))
.join(', ');
if (result !== '') {
result = '(' + result + ')';
let result = '';
if(params) {
let parray = [];
for(const pn in params) {
const p = params[pn];
let pstr = 'IN ' + quoteSqlId(pn) + ' ' + renderTypeReference(artifactName, pn, p);
if(p.default) {
pstr += ' DEFAULT ' + renderExpr(p.default);
}
parray.push(pstr);
}
result = '(' + parray.join(', ') + ')';
}

@@ -938,3 +950,3 @@ return result;

// (no trailing LF, don't indent if inline)
function renderExpr(x, env, inline=true) {
function renderExpr(x, env, inline=true, nestedExpr=false) {
// Compound expression

@@ -944,3 +956,3 @@ if (x instanceof Array) {

// FIXME: Take this for `toCdl`, too
let tokens = x.map(item => renderExpr(item, env, inline));
let tokens = x.map(item => renderExpr(item, env, inline, nestedExpr));
let result = '';

@@ -955,6 +967,17 @@ for (let i = 0; i < tokens.length; i++) {

return result;
// return x.map(item => renderExpr(item, env, inline)).join(' ');
// return x.map(item => renderExpr(item, env, inline, nestedExpr)).join(' ');
}
else if (typeof x === 'object' && x !== null) {
if (options.forHana && nestedExpr && x.cast && x.cast.type)
return renderExplicitTypeCast(renderExprObject());
return renderExprObject();
}
// Not a literal value but part of an operator, function etc - just leave as it is
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
else {
return String(x).toUpperCase();
}
// Various special cases represented as objects
else if (typeof x === 'object' && x !== null) {
function renderExprObject() {
// Literal value, possibly with explicit 'literal' property

@@ -1070,3 +1093,3 @@ if (x.val !== undefined) {

else if (x.xpr) {
return renderExpr(x.xpr, env);
return renderExpr(x.xpr, env, inline, true);
}

@@ -1086,6 +1109,10 @@ // Sub-select

}
// Not a literal value but part of an operator, function etc - just leave as it is
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
else {
return String(x).toUpperCase();
/**
* Renders an explicit `cast()` inside an 'xpr'.
* @param {string} value
*/
function renderExplicitTypeCast(value) {
const typeRef = renderBuiltinType(x.cast.type) + renderTypeParameters(x.cast);
return `CAST(${value} AS ${typeRef})`;
}

@@ -1092,0 +1119,0 @@

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

const validateForeignKeys = require('../checks/csn/foreignKeys');
const { validateDefaultValues }= require('../checks/csn/defaultValues');
const validateAssociationsInArrayOf = require('../checks/csn/assocsInArrayOf');

@@ -109,3 +110,4 @@

setAnnotation,
renameAnnotation
renameAnnotation,
expandStructsInOnConditions,
} = transformers;

@@ -181,6 +183,14 @@

},
/* Member Validators */ [ validateOnCondition, validateForeignKeys, validateAssociationsInArrayOf ],
/* Member Validators */ [ validateOnCondition, validateForeignKeys, validateAssociationsInArrayOf, validateDefaultValues ],
/* artifact validators */ [],
/* query validators */ [ validateMixinOnCondition ]);
// Check if structured elements and managed associations are compared in an ON condition
// and expand these structured elements. This tuple expansion allows all other
// subsequent procession steps (especially a2j) to see plain paths in ON conditions.
// If errors are detected, handleMessages will return from further processing
forEachDefinition(csn, expandStructsInOnConditions);
// Throw exception in case of errors

@@ -275,3 +285,4 @@ handleMessages(csn, options);

// Process associations - expand, generate foreign keys
processForeignKeys(csn, { referenceFlattener, csnUtils, transformers })
let flatKeys = !structuredOData || (structuredOData && options.toOdata.odataForeignKeys);
processForeignKeys(csn, flatKeys, { referenceFlattener, csnUtils, transformers })

@@ -458,2 +469,4 @@ // Flatten on-conditions in unmanaged associations

case 'check-proper-type-of':
case 'rewrite-not-supported':
case 'rewrite-undefined-key':
message.severity = 'Error';

@@ -496,2 +509,3 @@ break;

'@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable',
'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable',
}

@@ -498,0 +512,0 @@

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

const { forEach } = require('../udict');
const { collectAllManagedAssociations } = require('./utils');
const { forEachManagedAssociation } = require('./utils');
const sortByAssociationDependency = require('./sortByAssociationDependency');

@@ -115,3 +115,3 @@

function processSortedForeignKeys(sortedAssociations, functions) {
function processSortedForeignKeys(sortedAssociations, flatKeys, functions) {

@@ -128,3 +128,3 @@ const { csnUtils, transformers } = functions;

if (csnUtils.isManagedAssociationElement(element) && element.keys) {
takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath, functions);
if (flatKeys) takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath, functions);
fixCardinality(element);

@@ -142,7 +142,3 @@ }

let managedAssociations = collectAllManagedAssociations(csn);
managedAssociations.forEach(item => {
const { element } = item;
forEachManagedAssociation(csn, (element) => {
if (element.keys) {

@@ -152,5 +148,6 @@ expandStructuredKeysForElement(element, referenceFlattener);

})
}
function processForeignKeys(csn, functions) {
function processForeignKeys(csn, flatKeys, functions) {

@@ -169,6 +166,5 @@ let { referenceFlattener, csnUtils, transformers } = functions;

// generate foreign keys
processSortedForeignKeys(sortedAssociations, { csnUtils, transformers, referenceFlattener });
processSortedForeignKeys(sortedAssociations, flatKeys, { csnUtils, transformers, referenceFlattener });
}
module.exports = processForeignKeys;

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

if (isNode) {
const descr = Object.getOwnPropertyDescriptor(node,'$path')
if(descr && descr.enumerable) // check if it is an element -> do not overwrite it
return;
if (!pathPrefix)

@@ -72,0 +75,0 @@ setProp(node, '$path', path);

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

if (propertyName === 'elements') {
exposeStructTypeOf(element, `${defName}.${elementName}`, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`);
exposeStructTypeOf(element, `${defName}.${elementName}`, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`, structuredOData, path);
// TODO: use the next line once the array of logic is reworked

@@ -41,7 +41,7 @@ // exposeTypeOf(element, elementName, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`);

if (def.kind === 'action' || def.kind === 'function') {
exposeTypesOfAction(def, defName, serviceName);
exposeTypesOfAction(def, defName, serviceName, path);
}
// bound actions
for (let actionName in def.actions || {}) {
exposeTypesOfAction(def.actions[actionName], `${defName}_${actionName}`, serviceName);
exposeTypesOfAction(def.actions[actionName], `${defName}_${actionName}`, serviceName, path.concat(['actions', actionName]));
}

@@ -52,4 +52,4 @@

if (propertyName === 'elements') {
if (structuredOData && csnUtils.isStructured(element)) {
exposeStructTypeOf(element, elementName, serviceName, `${defNameWithoutServiceName(defName, serviceName).replace(/\./g, '_')}_${elementName}`);
if (csnUtils.isStructured(element)) {
exposeStructTypeOf(element, elementName, serviceName, `${defNameWithoutServiceName(defName, serviceName).replace(/\./g, '_')}_${elementName}`, structuredOData, path);
// TODO: use the next line once the array of logic is reworked

@@ -90,7 +90,7 @@ // exposeTypeOf(element, elementName, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`);

// still WIP function
function exposeTypeOf(node, memberName, service, artificialName) {
function exposeTypeOf(node, memberName, service, artificialName, path) {
if (isArrayed(node))
exposeArrayOfTypeOf(node, memberName, service, artificialName);
exposeArrayOfTypeOf(node, memberName, service, artificialName, path);
else
exposeStructTypeOf(node, memberName, service, artificialName);
exposeStructTypeOf(node, memberName, service, artificialName, structuredOData, path);
}

@@ -111,8 +111,8 @@

*/
function exposeTypesOfAction(action, actionName, service) {
function exposeTypesOfAction(action, actionName, service, path) {
if (action.returns)
exposeTypeOf(action.returns, actionName, service, `return_${actionName.replace(/\./g, '_')}`);
exposeTypeOf(action.returns, actionName, service, `return_${actionName.replace(/\./g, '_')}`, path.concat(['returns']));
for (let paramName in action.params || {}) {
exposeTypeOf(action.params[paramName], actionName, service, `param_${actionName.replace(/\./g, '_')}_${paramName}`);
exposeTypeOf(action.params[paramName], actionName, service, `param_${actionName.replace(/\./g, '_')}_${paramName}`, path.concat(['params', paramName]));
}

@@ -130,3 +130,3 @@ }

*/
function exposeStructTypeOf(node, memberName, service, artificialName, deleteElems = structuredOData) {
function exposeStructTypeOf(node, memberName, service, artificialName, deleteElems = structuredOData, path) {
if (!node) {

@@ -139,3 +139,3 @@ // TODO: when node will be undefined, if node is undefined this should not be reached

// TODO: call exposure of Arrayed types?
if (node.items) exposeStructTypeOf(node.items, memberName, service, artificialName, deleteElems);
if (node.items) exposeStructTypeOf(node.items, memberName, service, artificialName, deleteElems, path);

@@ -151,3 +151,3 @@ if (isExposableStructure(node)) {

let newType = exposeStructType(newTypeFullName, newTypeElements, memberName);
let newType = exposeStructType(newTypeFullName, newTypeElements, memberName, path);
if (!newType) {

@@ -163,3 +163,3 @@ // Error already reported

if (node.elements && node.elements[elemName].$location) setProp(newType.elements[elemName], '$location', node.elements[elemName].$location);
exposeStructTypeOf(newType.elements[elemName], memberName, service, `${newTypeId}_${elemName}`, deleteElems);
exposeStructTypeOf(newType.elements[elemName], memberName, service, `${newTypeId}_${elemName}`, deleteElems, path);
}

@@ -194,3 +194,3 @@ typeDef.kind === 'type' ? copyAnnotations(typeDef, newType) : copyAnnotations(node, newType);

*/
function exposeStructType(typeName, elements, parentName) {
function exposeStructType(typeName, elements, parentName, path) {
// If type already exists, reuse it (complain if not created here)

@@ -200,3 +200,3 @@ let type = csn.definitions[typeName];

if (!exposedStructTypes.includes(typeName)) {
signal(signal.error`Cannot create artificial type "${typeName}" for "${parentName}" because the name is already used`, ['definitions', parentName]);
signal(signal.error`Cannot create artificial type "${typeName}" for "${parentName}" because the name is already used`, path);
return null;

@@ -234,3 +234,3 @@ }

// like we expose structures in structured mode
function exposeArrayOfTypeOf(node, memberName, service, artificialName) {
function exposeArrayOfTypeOf(node, memberName, service, artificialName, path) {
// if anonymously defined in place -> we always expose the type

@@ -240,3 +240,3 @@ // this would be definition like 'elem: array of { ... }'

if (node.items && !node.type) {
exposeStructTypeOf(node, memberName, service, artificialName, true);
exposeStructTypeOf(node, memberName, service, artificialName, true, path);
}

@@ -243,0 +243,0 @@ // we can have both of the 'type' and 'items' in the cases:

@@ -25,12 +25,8 @@ const {

function collectAllManagedAssociations(csn) {
function forEachManagedAssociation(csn, callback) {
let associations = [];
forEachDefinition(csn, (def, definitionName) => {
let root = ['definitions', definitionName];
forEachMemberRecursively(def, (element, elementName, _prop, subpath, _parent) => {
forEachDefinition(csn, (def) => {
forEachMemberRecursively(def, (element) => {
if (isAssociationOrComposition(element) && !element.on) {
let path = root.concat(subpath);
associations.push({ definitionName, elementName, element, path });
callback(element)
}

@@ -40,3 +36,2 @@ })

return associations;
}

@@ -97,3 +92,3 @@

module.exports = {
collectAllManagedAssociations,
forEachManagedAssociation,
defNameWithoutServiceName,

@@ -100,0 +95,0 @@ getServiceOfArtifact,

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

isAssociationOperand,
isDollarSelfOperand,
isDollarSelfOrProjectionOperand,
createExposingProjection,

@@ -570,4 +570,4 @@ createAndAddDraftAdminDataProjection,

// Return true if 'arg' is an expression argument denoting "$self"
function isDollarSelfOperand(arg) {
// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
function isDollarSelfOrProjectionOperand(arg) {
return arg.path && arg.path.length == 1 && (arg.path[0].id === '$self' || arg.path[0].id === '$projection');

@@ -574,0 +574,0 @@ }

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

const { setProp } = require('../base/model');
const csnRefs = require('../model/csnRefs');
// eslint-disable-next-line no-unused-vars
const { copyAnnotations, printableName, hasBoolAnnotation, forEachDefinition } = require('../model/modelUtils');
const { cloneCsn, forEachRef, getUtils, isBuiltinType } = require('../model/csnUtils');
const { cloneCsn, forEachMemberRecursively, forEachGeneric, forAllQueries,
forEachRef, getUtils, isBuiltinType } = require('../model/csnUtils');

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

const {
effectiveType,
} = csnRefs(model);
return {

@@ -44,3 +52,3 @@ resolvePath,

isAssociationOperand,
isDollarSelfOperand,
isDollarSelfOrProjectionOperand,
getFinalBaseType,

@@ -64,2 +72,3 @@ createExposingProjection,

setAnnotation,
expandStructsInOnConditions,
};

@@ -111,3 +120,3 @@

newForeignKey(fkArtifact,foreignKeyElementName)
newForeignKey(fkArtifact,foreignKeyElementName);

@@ -122,3 +131,3 @@ function processAssociationOrComposition(fkArtifact,foreignKeyElementName) {

if(iKey.ref.length>1)
throw Error(`createForeignKeyElement(${artifactName},${assocName},${iKey.$path.join('/')}) unexpected reference: `+iKey.ref)
throw Error(`createForeignKeyElement(${artifactName},${assocName},${iKey.$path.join('/')}) unexpected reference: `+ iKey.ref)
newForeignKey(iKeyArtifact,foreignKeyElementName+'_'+iKey.ref[0])

@@ -129,5 +138,5 @@ })

// compose new foreign key out of 'fkArtifact' named 'foreignKeyElementName'
function newForeignKey(fkArtifact,foreignKeyElementName) {
if(fkArtifact.type=='cds.Association' || fkArtifact.type=='cds.Composition' ) {
processAssociationOrComposition(fkArtifact,foreignKeyElementName)
function newForeignKey(fkArtifact, foreignKeyElementName) {
if (fkArtifact.type === 'cds.Association' || fkArtifact.type === 'cds.Composition') {
processAssociationOrComposition(fkArtifact, foreignKeyElementName)
return;

@@ -158,3 +167,4 @@ }

if (artifact.elements[foreignKeyElementName]) {
signal(error`Generated foreign key element "${foreignKeyElementName}" for association "${assocName}" conflicts with existing element`, ['definitions', artifactName, 'elements', foreignKeyElementName]);
signal(error`Generated foreign key element "${foreignKeyElementName}" for association "${assocName}" conflicts with existing element`,
artifact.elements.$path ? artifact.elements.$path.concat([foreignKeyElementName]) : ['definitions', artifactName, 'elements', foreignKeyElementName]);
}

@@ -169,3 +179,3 @@ artifact.elements[foreignKeyElementName] = foreignKeyElement;

setProp(foreignKeyElement, '$path', path); // attach $path to the newly created element - used for inspectRef in processAssociationOrComposition
if(assoc.$location){
if (assoc.$location) {
setProp(foreignKeyElement, '$location', assoc.$location);

@@ -400,5 +410,6 @@ }

// then the 'assocDef' does not have 'target' property, but only 'targetAspect' property
// or the targetAspect might be an object, when the managed composition was defined anonymously
let assocTarget = assocDef.target || assocDef.targetAspect;
let assocTargetDef = getCsnDef(assocTarget);
if (!assocDef._ignore && assocTarget && assocTargetDef && !assocTarget.startsWith(service)) {
let assocTargetDef = typeof assocTarget === 'string' ? getCsnDef(assocTarget) : assocDef.targetAspect.elements;
if (!assocDef._ignore && assocTarget && assocTargetDef && (typeof assocTarget === 'string' && !assocTarget.startsWith(service))) {
// If we have a 'preserved dotted name' -> a result of flattening -> This scenario is not supported yet

@@ -642,5 +653,5 @@ if (assocDef._flatElementNameWithDots)

// Return true if 'arg' is an expression argument denoting "$self"
function isDollarSelfOperand(arg) {
return arg.ref && arg.ref.length == 1 && (arg.ref[0] === '$self');
// Return true if 'arg' is an expression argument denoting "$self" || "$projection"
function isDollarSelfOrProjectionOperand(arg) {
return arg.ref && arg.ref.length == 1 && (arg.ref[0] === '$self' || arg.ref[0] === '$projection');
}

@@ -1060,4 +1071,4 @@

for(const k of art.keys) {
const nps = { ref: k.ref.map(p => {
return ( fullRef ? { id: p } : p ) } ), _art: k._art };
const nps = { ref: k.ref.map(p => fullRef ? { id: p } : p ) };
setProp(nps, '_art', k._art);
const paths = flattenPath( nps, fullRef, followMgdAssoc );

@@ -1079,3 +1090,4 @@ // prepend prefix path

for(const en in elements) {
const nps = { ref: [ (fullRef ? { id: en, _art: elements[en] } : en )], _art: elements[en] };
const nps = { ref: [ (fullRef ? { id: en, _art: elements[en] } : en )] };
setProp(nps, '_art', elements[en]);
const paths = flattenPath( nps, fullRef, followMgdAssoc );

@@ -1089,3 +1101,3 @@ // prepend prefix path

else
path._art = art;
setProp(path, '_art', art);
}

@@ -1095,3 +1107,162 @@ return [path];

/*
* Expand structured ON condition arguments to flat reference paths.
* Structured elements are real sub element lists and managed associations.
* All unmanaged association definitions are rewritten if applicable (elements/mixins).
*
* TODO: Check if can be skipped for abstract entity and or cds.persistence.skip ?
*/
function expandStructsInOnConditions(artifact, artifactName, prop, path) {
forEachMemberRecursively(artifact,
(elem, elemName, prop, path) => {
if(prop === 'elements') {
if(elem.target && elem.on) {
elem.on = expand(elem.on, path)
}
}
}, path);
if(artifact.query) {
forAllQueries(artifact.query, (query) => {
if(query.SELECT && query.SELECT.mixin) {
forEachGeneric(query.SELECT, 'mixin', (mixin, mixinName, prop, path) => {
if(mixin.target && mixin.on) {
mixin.on = expand(mixin.on, path);
}
},
path);
}
}, path.concat([ 'query' ]));
}
/*
flatten structured leaf types and return array of paths
Flattening stops on all non-structured types.
*/
function expand(expr, location) {
let rc = [];
for(let i = 0; i < expr.length; i++)
{
if(Array.isArray(expr[i]))
rc.push(expr[i].map(expand, location));
if(i < expr.length-2)
{
const [lhs, op, rhs] = expr.slice(i);
// lhs & rhs must be expandable types (structures or managed associations)
if(lhs._art && rhs._art &&
lhs.ref && rhs.ref &&
isExpandable(lhs._art) && isExpandable(rhs._art) &&
['=', '<', '>', '>=', '<=', '!=', '<>'].includes(op) &&
!(isDollarSelfOrProjectionOperand(lhs) || isDollarSelfOrProjectionOperand(rhs))) {
// if path is scalar and no assoc or has no type (@Core.Computed) use original expression
// only do the expansion on (managed) assocs and (items.)elements, array of check in ON cond is done elsewhere
const lhspaths = /*isScalarOrNoType(lhs._art) ? [ lhs ] : */ flattenPath({ _art: lhs._art, ref: lhs.ref }, false, true );
const rhspaths = /*isScalarOrNoType(rhs._art) ? [ rhs ] : */ flattenPath({ _art: rhs._art, ref: rhs.ref }, false, true );
// mapping dict for lhs/rhs for mismatch check
// strip lhs/rhs prefix from flattened paths to check remaining common trailing path
// if path is idempotent, it doesn't produce new flattened paths (ends on scalar type)
// key is then empty string on both sides '' (=> equality)
// Path matches if lhs/rhs are available
const xref = lhspaths.reduce((a, v) => {
a[v.ref.slice(lhs.ref.length).join('.')] = { lhs: v };
return a;
}, Object.create(null));
rhspaths.forEach(v => {
const k = v.ref.slice(rhs.ref.length).join('.');
if(xref[k])
xref[k].rhs = v;
else
xref[k] = { rhs: v };
});
let cont = true;
for(const xn in xref) {
const x = xref[xn];
// do the paths match?
if(!(x.lhs && x.rhs)) {
if(xn.length)
signal(signal.error`'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Sub path '${xn}' not found in ${((x.lhs ? rhs : lhs).ref.join('.'))}`, location)
else
signal(signal.error`'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${((x.lhs ? lhs : rhs).ref.join('.'))}' does not match ${((x.lhs ? rhs : lhs).ref.join('.'))}`, location)
cont = false;
}
// lhs && rhs are present, consistency checks that affect both ends
else {
// is lhs scalar?
if(!isScalarOrNoType(x.lhs._art)) {
signal(signal.error`'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.lhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`, location)
cont = false;
}
// is rhs scalar?
if(!isScalarOrNoType(x.rhs._art)) {
signal(signal.error`'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Path '${x.rhs.ref.join('.')}${(xn.length ? '.' + xn : '')}' must end on a scalar type`, location)
cont = false;
}
// info about type incompatibility if no other errors occured
if(xn && cont) {
const lhst = getType(x.lhs._art);
const rhst = getType(x.rhs._art);
if(lhst !== rhst) {
signal(signal.info`'${lhs.ref.join('.')} ${op} ${rhs.ref.join('.')}': Types for sub path '${xn}' don't match`, location)
}
}
}
}
// don't continue if there are path errors
if(!cont)
return expr;
Object.keys(xref).forEach((k, i) => {
const x = xref[k];
if(i>0)
rc.push('and');
rc.push(x.lhs);
rc.push(op);
rc.push(x.rhs);
});
i += 2;
}
else
rc.push(expr[i]);
}
else
rc.push(expr[i]);
}
return rc;
function getType(art) {
const effart = effectiveType(art);
return Object.keys(effart).length ? effart : art.type;
}
function isExpandable(art) {
art = effectiveType(art);
if(art) {
// items in ON conds are illegal but this should be checked elsewere
const elements = art.elements || (art.items && art.items.elements);
return (elements || art.target && art.keys)
}
return false;
}
function isScalarOrNoType(art) {
art = effectiveType(art);
if(art) {
const type = art.type || (art.items && art.items.type);
// items in ON conds are illegal but this should be checked elsewere
const elements = art.elements || (art.items && art.items.elements);
// @Core.Computed has no type
return(!elements && !type ||
(type && isBuiltinType(type) &&
!['cds.Association', 'cds.Composition'].includes(type)))
}
return false;
}
}
}
}

@@ -1098,0 +1269,0 @@

{
"name": "@sap/cds-compiler",
"version": "1.39.0",
"version": "1.42.2",
"description": "CDS (Core Data Services) compiler and backends",

@@ -18,3 +18,3 @@ "homepage": "https://cap.cloud.sap/",

"dependencies": {
"antlr4": "4.7.1",
"antlr4": "4.8.0",
"resolve": "1.8.1",

@@ -21,0 +21,0 @@ "sax": "^1.2.4"

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc