Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 2.12.0 to 2.13.6

lib/base/error.js

26

bin/cdsc.js

@@ -166,10 +166,2 @@ #!/usr/bin/env node

// remap string values for `constraintsAsAlter` option to boolean
if (cmdLine.options.constraintsAsAlter &&
cmdLine.options.constraintsAsAlter === 'true' ||
cmdLine.options.constraintsAsAlter === 'false'
)
cmdLine.options.constraintsAsAlter = cmdLine.options.constraintsAsAlter === 'true';
// Enable all beta-flags if betaMode is set to true

@@ -456,3 +448,3 @@ if (cmdLine.options.betaMode)

* @param {CSN.Model | XSN.Model} model
* @param {CSN.Message[]} messages
* @param {CompileMessage[]} messages
*/

@@ -488,3 +480,2 @@ function displayMessages( model, messages = options.messages ) {

const fullFilePath = name ? path.resolve('', name) : undefined;
const context = fullFilePath ? sourceLines(fullFilePath) : [];
log(main.messageStringMultiline(msg, {

@@ -497,4 +488,8 @@ normalizeFilename,

}));
if (context)
if (fullFilePath && msg.location.col) {
// A message context only makes sense, if we have at least a medium precision,
// i.e. line and column for start position.
const context = sourceLines(fullFilePath);
log(main.messageContext(context, msg, { color: options.color }));
}
log(); // newline

@@ -564,4 +559,9 @@ });

if (dir === '-') {
if (!omitHeadline)
process.stdout.write(`// ------------------- ${fileName} -------------------\n`);
if (!omitHeadline) {
const sqlTypes = {
sql: true, hdbconstraint: true, hdbtable: true, hdbview: true,
};
const commentStarter = fileName.split('.').pop() in sqlTypes ? '--$' : '//';
process.stdout.write(`${commentStarter} ------------------- ${fileName} -------------------\n`);
}

@@ -568,0 +568,0 @@ process.stdout.write(`${content}\n`);

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

function tokensAt( buf, offset, col, symbol ) {
const src = `${buf.substring( 0, offset )}≠${buf.substring( offset )}`;
function tokensAt( buf, _offset, col, symbol ) {
const src = `${buf.substring( 0, _offset )}≠${buf.substring( _offset )}`;
const et = messageAt( compiler.parseX( src, frel, { messages: [] } ), 'expectedTokens', col ) || [];

@@ -151,0 +151,0 @@ for (const n of et) {

@@ -11,2 +11,9 @@ # ChangeLog of Beta Features for cdx compiler and backends

## Version 2.XX.YY
### Removed `assocsWithParams`
Instead, of using the beta flag `assocsWithParams`, you can change the severity of the messages
`def-unexpected-paramview-assoc` and `def-unexpected-calcview-assoc`.
## Version 2.12.0 - 2022-01-25

@@ -18,7 +25,7 @@

## Version 2.11.0
## Version 2.11.0 - 2021-12-02
### Removed `foreignKeyConstraints`
## Version 2.10.4
## Version 2.10.4 - 2021-11-05

@@ -29,3 +36,3 @@ ### Fixed `nestedProjections`

## Version 2.6.0
## Version 2.6.0 - 2021-08-23

@@ -54,3 +61,3 @@ ### Removed `pretransformedCSN`

## Version 2.4.4
## Version 2.4.4 - 2021-07-02

@@ -66,3 +73,3 @@ ### Added `nestedProjections`

## Version 2.4.2
## Version 2.4.2 - 2021-07-01

@@ -79,3 +86,3 @@ ### Added `keylessManagedAssoc`

## Version 2.4.0
## Version 2.4.0 - 2021-06-28

@@ -82,0 +89,0 @@ ### Changed `foreignKeyConstraints`

@@ -14,5 +14,21 @@ # ChangeLog of deprecated Features for cdx compiler and backends

## Version 2.XX.YY - 2022-MM-DD
### Added `redirectInSubQueries`
When this option is set, we auto-redirect associations and composition also in
non-main queries, sometimes without rewriting the `keys`/`on` (there will be no
fix for this).
### Added `oldVirtualNotNullPropagation`
When this option is set, we do not propagate `notNull` along types.
Additionally, we propagate `notNull` and `virtual` from a query source element
to the sub elements of a query entity element, even if the property is not
propagated to the query entity element itself (like with type references).
## Version 2.2.0
## Added `noScopedRedirections`
### Added `noScopedRedirections`

@@ -25,3 +41,3 @@ When this option is set, the definition scope is not taken into account when

## Added `noInheritedAutoexposeViaComposition`
### Added `noInheritedAutoexposeViaComposition`

@@ -33,7 +49,7 @@ When this option is set, only entities directly specified after `Composition of` are

## Added `downgradableErrors`
### Added `downgradableErrors`
Allow to change the severity of some errors which should stay to be an error.
## Added `shortAutoexposed`
### Added `shortAutoexposed`

@@ -46,3 +62,3 @@ When this option is set (and `generatedEntityNameWithUnderscore`), the names of

## Added `longAutoexposed`
### Added `longAutoexposed`

@@ -53,3 +69,3 @@ When this option is set (and `generatedEntityNameWithUnderscore`),

## Added `generatedEntityNameWithUnderscore`
### Added `generatedEntityNameWithUnderscore`

@@ -56,0 +72,0 @@ Keep using `_` is separator for generated autoexposed entities and for entities

@@ -25,20 +25,25 @@ # Name Resolution in CDS

1. [Introduction](#introduction)
<br/>  –  [Background: SQL](#background-sql)
<br/>  –  [Background: modern programming languages](#background-modern-programming-languages)
2. [Design Principles](#design-principles)
3. [Name Resolution - the Basics](#name-resolution---the-basics)
<br/>  –  [Common rules](#common-rules)
<br/>  –  [Resolving paths](#resolving-paths)
<br/>  –  [Navigation environment](#navigation-environment)
4. [References to main artifacts](#references-to-main-artifacts)
5. [Values and references to elements](#values-and-references-to-elements)
<br/>  –  [References in queries](#references-in-queries)
<br/>  –  [References to sibling elements](#references-to-sibling-elements)
<br/>  –  [Other element references](#other-element-references)
6. [Paths as Annotation Values](#paths-as-annotation-values)
7. [Differences to HANA-CDS](#differences-to-hana-cds)
8. [Summary](#summary)
<!-- toc: start -->
1. [Table of Contents](#table-of-contents)
2. [Introduction](#introduction)
1. [Background: SQL](#background-sql)
2. [Background: modern programming languages](#background-modern-programming-languages)
3. [Design Principles](#design-principles)
4. [Name Resolution - the Basics](#name-resolution---the-basics)
1. [Common rules](#common-rules)
2. [Resolving paths](#resolving-paths)
3. [Navigation environment](#navigation-environment)
5. [References to main artifacts](#references-to-main-artifacts)
6. [Values and references to elements](#values-and-references-to-elements)
1. [References in queries](#references-in-queries)
2. [References to sibling elements](#references-to-sibling-elements)
3. [Other element references](#other-element-references)
7. [Paths as annotation values](#paths-as-annotation-values)
8. [Differences to HANA-CDS](#differences-to-hana-cds)
9. [Summary](#summary)
<!-- toc: end -->
## Introduction

@@ -45,0 +50,0 @@

@@ -38,2 +38,3 @@ /** @module API */

const { toHdbcdsSource } = require('../render/toHdbcds');
const { ModelError } = require('../base/error');

@@ -119,3 +120,3 @@ const relevantGeneralOptions = [ /* for future generic options */ ];

function isPreTransformed(csn, transformation) {
return csn.meta && csn.meta.transformation === transformation;
return csn && csn.meta && csn.meta.transformation === transformation;
}

@@ -178,3 +179,3 @@

* @param {CSN.Model} csn Plain input CSN
* @param {hdiOptions} [options={}] Options
* @param {HdiOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.hdi

@@ -192,3 +193,3 @@ * @private

* @param {CSN.Model} csn Plain input CSN
* @param {hdbcdsOptions} [options={}] Options
* @param {HdbcdsOptions} [options={}] Options
* @returns {CSN.Model} CSN transformed like to.hdbcds

@@ -231,3 +232,3 @@ * @private

* @param {CSN.Model} csn A clean input CSN
* @param {hdiOptions} [options={}] Options
* @param {HdiOptions} [options={}] Options
* @returns {HDIArtifacts} { <filename>:<content>, ...}

@@ -255,4 +256,4 @@ */

const nameMapping = Object.create(null);
const sqlsWithCSNNamesToSort = Object.create(null);
const sqlsNotToSort = Object.create(null);
const sqlArtifactsWithCSNNamesToSort = Object.create(null);
const sqlArtifactsNotToSort = Object.create(null);

@@ -263,8 +264,8 @@ Object.keys(flat).forEach((key) => {

if (key.endsWith('.hdbtable') || key.endsWith('.hdbview'))
sqlsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
sqlArtifactsWithCSNNamesToSort[artifactNameLikeInCsn] = flat[key];
else
sqlsNotToSort[key] = flat[key];
sqlArtifactsNotToSort[key] = flat[key];
});
const sorted = sortViews({ sql: sqlsWithCSNNamesToSort, csn: sqlCSN })
const sorted = sortViews({ sql: sqlArtifactsWithCSNNamesToSort, csn: sqlCSN })
.filter(obj => obj.sql)

@@ -277,5 +278,5 @@ .reduce((previous, current) => {

// now add the not-sorted stuff, like indizes
Object.keys(sqlsNotToSort).forEach((key) => {
sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlsNotToSort[key];
// now add the not-sorted stuff, like indices
Object.keys(sqlArtifactsNotToSort).forEach((key) => {
sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlArtifactsNotToSort[key];
});

@@ -336,3 +337,3 @@

* @param {CSN.Model} csn A clean input CSN representing the desired "after-image"
* @param {hdiOptions} options Options
* @param {HdiOptions} options Options
* @param {CSN.Model} beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image

@@ -358,4 +359,4 @@ * is known, i.e. for the very first migration step

* @todo Remove in cds-compiler@2.x
* @param {hdiOptions|CSN.Model} inputOptions Options or CSN image
* @param {CSN.Model|hdiOptions} inputBeforeImage CSN image or options
* @param {HdiOptions|CSN.Model} inputOptions Options or CSN image
* @param {HdiOptions|CSN.Model} inputBeforeImage CSN image or options
* @returns {Array} Array where the real options come first

@@ -451,3 +452,3 @@ */

* @param {any} csn A clean input CSN
* @param {hdbcdsOptions} [options={}] Options
* @param {HdbcdsOptions} [options={}] Options
* @returns {HDBCDS} { <filename>:<content>, ...}

@@ -657,8 +658,14 @@ */

catch (err) {
if (err instanceof CompilationError || options.noRecompile)
// options.testMode && err instanceof RangeError) // stack overflow
if (err instanceof CompilationError || options.noRecompile || isPreTransformed(csn, 'odata')) // we cannot recompile a pre-transformed CSN
throw err;
if (options.testMode && !(err instanceof TypeError) &&
!(err instanceof ModelError))
throw err;
const { info } = makeMessageFunction( csn, options, 'compile' );
info( 'api-recompiled-csn', emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
const msg = info( 'api-recompiled-csn', emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
if (options.internalMsg)
msg.error = err; // Attach original error
// next line to be replaced by CSN parser call which reads the CSN object

@@ -698,8 +705,2 @@ const xsn = recompileX(csn, options);

/**
* Available SQL change modes
*
* @typedef {'alter' | 'drop' } SqlChangeMode
*/
/**
* Available oData versions

@@ -717,38 +718,2 @@ *

/**
* Generally available options
*
* @typedef {object} Options
* @property {object} [beta] Enable experimental features - not for productive use!
* @property {boolean} [dependentAutoexposed=false] For dependent autoexposed entities (managed compositions, texts entity), follow name of base entity
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
* @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
* @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
*/
/**
* Options available for to.hdi
*
* @typedef {object} hdiOptions
* @property {NamingMode} [sqlMapping='plain'] Naming mode to use
* @property {SqlChangeMode} [sqlChangeMode='alter'] SQL change mode to use (for changed columns)
* @property {boolean} [allowCsnDowngrade=false] Allow downgrades of CSN major version (for modelCompare)
* @property {object} [beta] Enable experimental features - not for productive use!
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
* @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
* @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
*/
/**
* Options available for to.hdbcds
*
* @typedef {object} hdbcdsOptions
* @property {NamingMode} [sqlMapping='plain'] Naming mode to use
* @property {object} [beta] Enable experimental features - not for productive use!
* @property {boolean} [longAutoexposed=false] Deprecated: Produce long names (with underscores) for autoexposed entities
* @property {Map<string, number>} [severities={}] Map of message-id and severity that allows setting the severity for the given message
* @property {Array} [messages] Allows collecting all messages in the options instead of printing them to stderr.
*/
/**
* A fresh (just compiled, not transformed) CSN

@@ -772,3 +737,3 @@ *

/**
* A map of { <file.hdbcds>:<content> }.
* A map of { <file.hdbcds|hdbconstraint>:<content> }.
*

@@ -779,3 +744,3 @@ * @typedef {object} HDBCDS

/**
* A map of { <file.hdbtable/view...>:<content> }.
* A map of { <file.hdbtable|hdbview|hdbconstraint...>:<content> }.
*

@@ -782,0 +747,0 @@ * @typedef {object} HDIArtifacts

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

'testSortCsn',
'constraintsAsAlter',
'constraintsInCreateTable',
'integrityNotEnforced',

@@ -162,3 +162,3 @@ 'integrityNotValidated',

const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'plain' };
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming', 'constraints-as-alter-sqlite' ], 'to.sql');
const processed = translateOptions(options, defaultOptions, hardOptions, undefined, [ 'sql-dialect-and-naming' ], 'to.sql');

@@ -171,3 +171,3 @@ const result = Object.assign({}, processed);

hdi: (options) => {
const hardOptions = { src: 'hdi', constraintsAsAlter: false };
const hardOptions = { src: 'hdi' };
const defaultOptions = { sqlMapping: 'plain', sqlDialect: 'hana' };

@@ -174,0 +174,0 @@ const processed = translateOptions(options, defaultOptions, hardOptions, { sqlDialect: generateStringValidator([ 'hana' ]) }, undefined, 'to.hdi');

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

},
'constraints-as-alter-sqlite': {
validate: options => options.constraintsAsAlter && options.sqlDialect && options.sqlDialect === 'sqlite',
severity: 'warning',
getMessage: options => `Option 'constraintsAsAlter' is ignored for sqlDialect '${ options.sqlDialect }'`,
},
};

@@ -152,0 +147,0 @@ /* eslint-disable jsdoc/no-undefined-types */

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

// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if(['quoted', 'hdbcds'].includes(options.toSql.names)) {
if (options.toSql.names === 'quoted' || options.toSql.names === 'hdbcds') {
error(null, null, `Option "{ toSql.dialect: '${options.toSql.dialect}' }" can't be combined with "{ toSql.names: '${options.toSql.names}' }"`);

@@ -300,13 +300,13 @@ }

// "id" and/or "locale" prop(s)
function transformUserOption(options) {
function transformUserOption(userOptions) {
// move the user option value under user.id if specified as a string
if (options.user && typeof options.user === 'string' || options.user instanceof String) {
options.user = { id: options.user };
if (userOptions.user && typeof userOptions.user === 'string' || userOptions.user instanceof String) {
userOptions.user = { id: userOptions.user };
}
// move the locale option(if provided) under user.locale
if (options.locale) {
options.user = options.user
? Object.assign(options.user, { locale: options.locale })
: { locale: options.locale };
delete options.locale;
if (userOptions.locale) {
userOptions.user = userOptions.user
? Object.assign(userOptions.user, { locale: userOptions.locale })
: { locale: userOptions.locale };
delete userOptions.locale;
}

@@ -394,3 +394,3 @@ }

forEachDefinition(csn, (artifact, artifactName) => {
if(['context', 'service'].includes(artifact.kind) && forHanaCsn.definitions[artifactName] === undefined) {
if((artifact.kind === 'context' || artifact.kind === 'service') && forHanaCsn.definitions[artifactName] === undefined) {
forHanaCsn.definitions[artifactName] = artifact;

@@ -422,2 +422,6 @@ }

// Also set new-style options
options.sqlDialect = 'hana';
options.sqlMapping = names || 'plain';
// Of course we want the database constraints

@@ -439,15 +443,3 @@ options.assertIntegrityType = 'DB';

if(options.testMode !== true)
return intermediateResult;
// if in testmode, return a string containing all the artifacts
let resultString = '';
const extension = src && src === 'hdi' ? 'hdbconstraint' : 'sql';
for(const id in intermediateResult){
const initialComment = `--$ --- ${id}.${extension} ---\n\n`;
resultString += initialComment;
resultString += intermediateResult[id];
resultString += '\n\n'
}
return resultString;
return intermediateResult;
}

@@ -454,0 +446,0 @@

@@ -92,9 +92,2 @@ // Functions for dictionaries (Objects without prototype)

function forEachInDict( dict, callback ) {
let r = Object.create(null);
for (let name of Object.keys(dict))
r[name] = callback( dict[name], name, dict );
return r;
}
module.exports = {

@@ -104,4 +97,3 @@ dictAdd, dictForEach,

pushToDict,
forEachInDict,
}

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

'use strict';
const { functionsWithoutParens } = require("../compiler/builtins");
module.exports = {

@@ -33,17 +37,3 @@ // CDL reserved keywords, used for automatic quoting in 'toCdl' renderer

// only relevant for element references of path length 1.
cdl_functions: [
'CURRENT_CONNECTION',
'CURRENT_DATE',
'CURRENT_SCHEMA',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_TRANSACTION_ISOLATION_LEVEL',
'CURRENT_USER',
'CURRENT_UTCDATE',
'CURRENT_UTCTIME',
'CURRENT_UTCTIMESTAMP',
'SESSION_USER',
'SYSTEM_USER',
'SYSUUID',
],
cdl_functions: functionsWithoutParens,
// SQLite keywords, used to warn in 'toSql' renderer with dialect 'sqlite'

@@ -199,3 +189,3 @@ // Taken from http://www.sqlite.org/draft/lang_keywords.html

],
// HANA keywords, used for smart quoting in to-hdi.plain
// SAP HANA keywords, used for smart quoting in to-hdi.plain
// Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html

@@ -680,3 +670,3 @@ // Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those

],
// HANA CDS keywords, used for smart quoting in to-hdbcds.plain
// SAP HANA CDS keywords, used for smart quoting in to-hdbcds.plain
hdbcds: [

@@ -683,0 +673,0 @@ 'ALL', 'ALTER', 'AS',

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

* @returns {CSN.Location}
*
* TODO: make this function a CDL parser-only function (i.e. there should be
* no need to use it outside), it is XSN-only anyway already now
*/

@@ -37,2 +40,4 @@ function combinedLocation( start, end ) {

* @returns {CSN.Location}
*
* TODO: make this function redundant (XSN sparse locations project)
*/

@@ -55,2 +60,4 @@ function emptyLocation(filename) {

* @returns {CSN.Location}
*
* TODO: make this function redundant (XSN sparse locations project)
*/

@@ -69,2 +76,4 @@ function emptyWeakLocation(filename) {

* @returns {CSN.Location}
*
* TODO: make this function redundant (XSN sparse locations project)
*/

@@ -147,6 +156,2 @@ function builtinLocation() {

}
dictLocation.end = (dict) => {
const loc = dictLocation( dict );
return loc && { file: loc.file, line: loc.endLine, col: loc.endCol };
};

@@ -153,0 +158,0 @@ function _objLocations( obj ) {

@@ -45,6 +45,7 @@ // Central registry for messages.

'anno-duplicate-unrelated-layer': { severity: 'Error', configurableFor: true }, // does not hurt us
'anno-invalid-sql-element': { severity: 'Error'}, // @sql.prepend/append
'anno-invalid-sql-struct': { severity: 'Error'}, // @sql.prepend/append
'anno-invalid-sql-view': { severity: 'Error' }, // @sql.prepend/append
'anno-invalid-sql-view-element': { severity: 'Error'}, // @sql.prepend/append
'anno-invalid-sql-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-struct': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-kind': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-view': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-invalid-sql-view-element': { severity: 'Error', configurableFor: true }, // @sql.prepend/append - configurable for "I know what I'm doing"
'anno-undefined-action': { severity: 'Info' },

@@ -55,2 +56,3 @@ 'anno-undefined-art': { severity: 'Info' }, // for annotate statement (for CDL path root)

'anno-undefined-param': { severity: 'Info' },
'anno-unexpected-ellipsis-layers': { severity: 'Error', configurableFor: true }, // TODO(v3): Merge with anno-unexpected-ellipsis and make non-configurable

@@ -63,2 +65,4 @@ 'args-expected-named': { severity: 'Error', configurableFor: 'deprecated' }, // future --sloppy

'assoc-as-type': { severity: 'Error', configurableFor: 'deprecated' }, // TODO: allow more, but not all
'def-unexpected-paramview-assoc': { severity: 'Error' },
'def-unexpected-calcview-assoc': { severity: 'Error' },

@@ -137,2 +141,4 @@ 'check-proper-type': { severity: 'Error', configurableFor: [ 'compile' ] },

'syntax-invalid-extend': { severity: 'Error' },
'syntax-invalid-text-block' : { severity: 'Error' },

@@ -163,4 +169,6 @@ 'syntax-unknown-escape': { severity: 'Error', configurableFor: true },

const centralMessageTexts = {
'anno-duplicate': 'Duplicate assignment with $(ANNO)',
'anno-mismatched-ellipsis': 'An array with $(CODE) can only be used if there is an assignment below with an array value',
'anno-unexpected-ellipsis': 'No base annotation available to apply $(CODE)',
'anno-unexpected-ellipsis-layers': 'No base annotation available to apply $(CODE)',
'missing-type-parameter': 'Missing value for type parameter $(NAME) in reference to type $(ID)',

@@ -187,2 +195,3 @@ 'syntax-csn-expected-object': 'Expected object for property $(PROP)',

},
'syntax-invalid-extend': 'Can\'t extend an element with $(KIND)',
'syntax-invalid-text-block': 'Missing newline in text block',

@@ -218,2 +227,3 @@ 'syntax-unknown-escape': 'Unknown escape sequence $(CODE)',

},
'ref-unexpected-draft-enabled': 'Composition in draft-enabled entity can\'t lead to another entity with $(ANNO)',
'ref-rejected-on': {

@@ -248,2 +258,13 @@ std: 'Do not refer to a artefact like $(ID) in the explicit ON of a redirection', // Not used

'def-unexpected-paramview-assoc': {
std: 'SAP HANA does no support associations in/to parameterized entities',
view: 'SAP HANA does no support associations in parameterized entities',
target: 'SAP HANA does no support associations to parameterized entities',
},
'def-unexpected-calcview-assoc': {
std: 'SAP HANA does not allow associations in/to entities annotated with $(ANNO)',
'entity-persistence': 'SAP HANA does not allow associations in entities annotated with $(ANNO)',
'target-persistence': 'SAP HANA does not allow associations pointing to entities annotated with $(ANNO)',
},
'def-missing-element': {

@@ -250,0 +271,0 @@ std: 'Expecting entity to have at least one non-virtual element',

@@ -128,2 +128,3 @@ // Functions and classes for syntax messages

* @param {any} [home]
* @param {string} [moduleName] Name of the module that created this message
*

@@ -179,18 +180,2 @@ * @memberOf CompileMessage

/**
* Handle compiler messages, i.e. throw a compiler exception if there are errors.
*
* @param {object} model CSN or XSN
* @param {CSN.Options} [options]
* @deprecated Use throwWithError() from makeMessageFunction instead.
*/
function handleMessages( model, options = {} ) {
const messages = options.messages;
if (messages && messages.length) {
if (hasErrors( messages ))
throw new CompilationError( messages, options.attachValidNames && model );
}
return model;
}
const severitySpecs = {

@@ -287,5 +272,5 @@ error: { name: 'Error', level: 0 },

* @param {CSN.Model} model
* @param {CSN.Path} path
* @param {CSN.Path} csnPath
*/
function searchForLocation( model, path ) {
function searchForLocation( model, csnPath ) {
if (!model)

@@ -297,3 +282,3 @@ return null;

let currentStep = model;
for (const step of path) {
for (const step of csnPath) {
if (!currentStep)

@@ -1108,3 +1093,3 @@ return lastLocation;

r.push( memberActionName(art) + ':' + quoted( name.action ) );
if (name.alias)
if (name.alias && art.kind !== '$self')
r.push( (art.kind === 'mixin' ? 'mixin:' : 'alias:') + quoted( name.alias ) )

@@ -1116,2 +1101,4 @@ if (name.param != null && omit !== 'param')

r.push( (art.kind === 'enum' ? 'enum:' : 'element:') + quoted( name.element ) );
if (art.kind === '$self')
r.push( 'alias:' + quoted( name.alias ) ) // should be late due to $self in anonymous aspect
return r.join('/');

@@ -1129,2 +1116,3 @@ }

// TODO: XSN-specific things should probably move out
function homeName( art, absoluteOnly ) {

@@ -1145,4 +1133,6 @@ if (!art)

return art.name.absolute;
else
return (art._main ? art._main.kind : art.kind) + ':' + artName( art );
let main = art._main || art;
while (main._outer) // anonymous aspect
main = main._outer._main;
return main.kind + ':' + artName( art );
}

@@ -1201,5 +1191,5 @@

csnPath.shift();
const artName = csnPath.shift();
let currentThing = model.definitions[artName];
let result = `${ (currentThing && currentThing.kind) ? currentThing.kind : 'artifact' }:${ _quoted(artName) }`;
const artifactName = csnPath.shift();
let currentThing = model.definitions[artifactName];
let result = `${ (currentThing && currentThing.kind) ? currentThing.kind : 'artifact' }:${ _quoted(artifactName) }`;

@@ -1456,3 +1446,2 @@ if (!currentThing)

artName,
handleMessages,
sortMessages: (m => m.sort(compareMessage)),

@@ -1463,2 +1452,3 @@ sortMessagesSeverityAware: (m => m.sort(compareMessageSeverityAware)),

CompilationError,
isMessageDowngradable: isDowngradable,
explainMessage,

@@ -1465,0 +1455,0 @@ hasMessageExplanation,

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

enableUniversalCsn: true,
sqlSnippets: true,
// disabled by --beta-mode

@@ -68,3 +67,3 @@ nestedServices: false,

const { deprecated } = options;
if(!feature)
if (!feature)
return !!deprecated;

@@ -99,59 +98,2 @@ return deprecated && typeof deprecated === 'object' && deprecated[feature];

// Apply function `callback(member, memberName, prop)` to each member in
// `construct`, recursively (i.e. also for sub-elements of elements).
function forEachMemberRecursively( construct, callback ) {
forEachMember( construct, ( member, memberName, prop ) => {
callback( member, memberName, prop );
// Descend into nested members, too
forEachMemberRecursively( member, callback );
});
// If 'construct' has more than one query, descend into the elements of the remaining ones, too
if (construct.$queries && construct.$queries.length > 1) {
construct.$queries.slice(1).forEach(query => forEachMemberRecursively(query, callback));
}
}
/**
* Apply function `callback` to all members of object `obj` (main artifact or
* parent member). Members are considered those in dictionaries `elements`,
* `enum`, `actions` and `params` of `obj`, `elements` and `enums` are also
* searched inside property `items` (array of). `$queries`, `mixin` and
* `columns` are also visited in contrast to `forEachMember()`.
* See function `forEachGeneric()` for details.
*
* @param {XSN.Artifact} construct
* @param {(member: object, memberName: string, prop: string) => any} callback
* @param {object} [target]
*/
function forEachMemberWithQuery( construct, callback, target ) {
let obj = construct.returns || construct; // why the extra `returns` for actions?
obj = obj.items || obj;
forEachGeneric( target || obj, 'elements', callback );
forEachGeneric( obj, 'enum', callback );
forEachGeneric( obj, 'foreignKeys', callback );
forEachGeneric( construct, 'actions', callback );
forEachGeneric( construct, 'params', callback );
// For Queries
forEachGeneric( construct, '$queries', callback );
forEachGeneric( construct, 'mixin', callback );
forEachGeneric( construct, 'columns', callback );
}
/**
* Apply function `callback(member, memberName, prop)` to each member in
* `construct`, recursively (i.e. also for sub-elements of elements).
* In contrast to `forEachMemberRecursively()` this function also traverses
* queries and mixins.
*
* @param {XSN.Artifact} construct
* @param {(member: object, memberName: string, prop: string) => any} callback
*/
function forEachMemberRecursivelyWithQuery( construct, callback ) {
forEachMemberWithQuery( construct, ( member, memberName, prop ) => {
callback( member, memberName, prop );
// Descend into nested members, too
forEachMemberRecursivelyWithQuery( member, callback );
});
}
// Apply function `callback` to all objects in dictionary `dict`, including all

@@ -164,3 +106,3 @@ // duplicates (found under the same name). Function `callback` is called with

for (let name in dict) {
let obj = dict[name];
obj = dict[name];
callback( obj, name, prop );

@@ -197,5 +139,2 @@ if (Array.isArray(obj.$duplicates)) // redefinitions

forEachMember,
forEachMemberRecursively,
forEachMemberWithQuery,
forEachMemberRecursivelyWithQuery,
forEachGeneric,

@@ -202,0 +141,0 @@ forEachInOrder,

@@ -77,35 +77,35 @@ 'use strict'

/** @type {object} */
const command = {
const cmd = {
options: {},
positionalArguments: [],
option,
option: commandOption,
positionalArgument: (argumentDefinition) => {
_setPositionalArguments(argumentDefinition, command.positionalArguments);
return command;
_setPositionalArguments(argumentDefinition, cmd.positionalArguments);
return cmd;
},
help,
help: commandHelp,
..._parseCommandString(cmdString)
};
if (optionProcessor.commands[command.longName]) {
throw new Error(`Duplicate assignment for long command ${command.longName}`);
if (optionProcessor.commands[cmd.longName]) {
throw new Error(`Duplicate assignment for long command ${cmd.longName}`);
}
optionProcessor.commands[command.longName] = command;
optionProcessor.commands[cmd.longName] = cmd;
if (command.shortName) {
if (optionProcessor.commands[command.shortName]) {
throw new Error(`Duplicate assignment for short command ${command.shortName}`);
if (cmd.shortName) {
if (optionProcessor.commands[cmd.shortName]) {
throw new Error(`Duplicate assignment for short command ${cmd.shortName}`);
}
optionProcessor.commands[command.shortName] = command;
optionProcessor.commands[cmd.shortName] = cmd;
}
return command;
return cmd;
// Command API: Define a command option
function option(optString, validValues, options) {
return _addOption(command, optString, validValues, options);
function commandOption(optString, validValues, options) {
return _addOption(cmd, optString, validValues, options);
}
// Command API: Define the command help text
function help(text) {
command.helpText = text;
return command;
function commandHelp(text) {
cmd.helpText = text;
return cmd;
}

@@ -160,7 +160,7 @@ }

*/
function _addOption(command, optString, validValues, options) {
function _addOption(cmd, optString, validValues, options) {
const opt = _parseOptionString(optString, validValues);
Object.assign(opt, options);
if (command.options[opt.longName]) {
if (cmd.options[opt.longName]) {
throw new Error(`Duplicate assignment for long option ${opt.longName}`);

@@ -171,8 +171,8 @@ } else if (optionProcessor.options[opt.longName]) {

option: opt.longName,
description: `Command '${command.longName}' has option clash with general options for: ${opt.longName}`
description: `Command '${cmd.longName}' has option clash with general options for: ${opt.longName}`
});
}
command.options[opt.longName] = opt;
cmd.options[opt.longName] = opt;
if (opt.shortName) {
if (command.options[opt.shortName]) {
if (cmd.options[opt.shortName]) {
throw new Error(`Duplicate assignment for short option ${opt.shortName}`);

@@ -183,8 +183,8 @@ } else if (optionProcessor.options[opt.shortName]) {

option: opt.shortName,
description: `Command '${command.longName}' has option clash with general options for: ${opt.shortName}`
description: `Command '${cmd.longName}' has option clash with general options for: ${opt.shortName}`
});
}
command.options[opt.shortName] = opt;
cmd.options[opt.shortName] = opt;
}
return command;
return cmd;
}

@@ -347,42 +347,3 @@

else if (!seenDashDash && arg.startsWith('-')) {
if (result.command) {
// We already have a command
const opt = optionProcessor.commands[result.command].options[arg];
if (opt) {
// Found as a command option
i += processOption(i, opt, result.command);
} else {
// No command option, try general options as fallback
const opt = optionProcessor.options[arg];
if (opt) {
i += processOption(i, opt, false);
} else {
// Not found at all, put into unknownOptions if it is an option
// for another cdsc command.
// We dig into the other cdsc commands in order to check if
// the option expects a parameter and if so to take the next argument as a value
if (Object.keys(optionProcessor.commands).some(cmd => optionProcessor.commands[cmd].options[arg])) {
const cmd = optionProcessor.commands[
Object.keys(optionProcessor.commands).find(cmd => optionProcessor.commands[cmd].options[arg])
];
i += processOption(i, cmd.options[arg], optionProcessor.commands[result.command], true);
} else { // still add it to the unknownOptions
result.unknownOptions.push(`Unknown option "${arg}" for the command "${result.command}"`);
// if the next argument looks like an argument for this unknown option => skip it
if ((i + 1) < argv.length && !argv[i + 1].match('(^[.-])|[.](csn|cds|json)$'))
i++;
}
}
}
} else {
// We don't have a command
const opt = optionProcessor.options[arg];
if (opt) {
// Found as a general option
i += processOption(i, opt, false);
} else {
// Not found, complain
result.unknownOptions.push(`Unknown option "${arg}"`);
}
}
i += processOption(i);
}

@@ -415,3 +376,7 @@ else {

const forCommand = result.command ? ` for '${ result.command }'` : '';
result.errors.push(`Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`)
const errorMsg = `Missing positional argument${forCommand}: <${missingArg.name}${missingArg.isDynamic ? '...' : ''}>`;
if (forCommand)
result.cmdErrors.push(errorMsg)
else
result.errors.push(errorMsg)
}

@@ -457,53 +422,123 @@

// Process 'argv[i]' as an option.
// Check the option definition in 'opt' to see if a parameter is expected.
// Check the option definition to see if a parameter is expected.
// If so, take it (complain if one is found in 'argv').
// Populate 'result.options' with the result. Return the number params found (0 or 1).
function processOption(i, opt, command, unknownOption = false) {
// Does this option expect a parameter?
if (opt.param) {
if (i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
// There should be a param but isn't - complain
let error = `Missing param "${opt.param}" for option "${opt.shortName ? opt.shortName + ', ': ''}${opt.longName}"`;
if (command && unknownOption) {
result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
} else if (command) {
error = `${error} of command "${command}"`;
result.cmdErrors.push(error);
} else {
result.errors.push(error);
function processOption(i) {
const arg = argv[i];
let currentCommand = result.command;
// First check top-level options
let currentOption = optionProcessor.options[arg];
if (currentCommand) {
// If there is a command and it has an option that overrides it, use it instead.
const cmdOpt = optionProcessor.commands[currentCommand].options[arg];
if (cmdOpt)
currentOption = cmdOpt;
else if (currentOption)
// Otherwise, if there exist a top-level option, set 'command' to null.
currentCommand = null;
}
if (!currentOption)
return reportUnknown();
if (!currentOption.param) {
setCurrentOption(true);
return 0;
}
const param = paramForOption(currentOption);
if (param === null)
return 0;
setCurrentOption(param);
return 1;
/**
* Report that an option is unknown. If the option exists for other
* commands or if the next argument looks like a param, return 1,
* otherwise 0, indicating how many argv fields have been consumed.
*
* @returns {number}
*/
function reportUnknown() {
if (currentCommand)
result.unknownOptions.push(`Unknown option "${arg}" for the command "${currentCommand}"`);
else
result.unknownOptions.push(`Unknown option "${arg}"`);
if (currentCommand) {
// Not found at all. We dig into the other cdsc commands in order to check if
// the option expects a parameter and if so to take the next argument as a value
const otherCmd = Object.keys(optionProcessor.commands).find(cmd => optionProcessor.commands[cmd].options[arg]);
const otherCmdOpt = otherCmd && optionProcessor.commands[otherCmd].options[arg];
if (otherCmdOpt && hasParamForUnknown(otherCmdOpt)) {
return 1
}
return 0;
}
else {
// Take the option with the parameter
const value = argv[i + 1];
const shortOption = opt.shortName ? `${opt.shortName}, ` : ''
if (command) {
// if an unknown option for a command => add it to the array and warn about
if (unknownOption) {
result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`);
} else {
result.options[command][opt.camelName] = value;
if (!isValidOptionValue(opt, value)) {
result.cmdErrors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
}
}
} else {
result.options[opt.camelName] = value;
if (!isValidOptionValue(opt, value)) {
result.errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
}
}
if (hasParamForUnknown(null))
return 1;
return 0;
}
function setCurrentOption(val) {
if (currentCommand) {
if (!result.options[currentCommand])
result.options[currentCommand] = {};
result.options[currentCommand][currentOption.camelName] = val;
}
else {
result.options[currentOption.camelName] = val;
}
}
// No parameter, take option as bool
if (command) {
unknownOption
? result.unknownOptions.push(`Unknown option "${argv[i]}" for the command "${command.longName}"`)
: result.options[command][opt.camelName] = true;
} else {
result.options[opt.camelName] = true;
function reportMissingParam(opt) {
let error = `Missing param "${opt.param}" for option "${opt.shortName ? opt.shortName + ', ': ''}${opt.longName}"`;
if (currentCommand) {
error = `${error} of command "${currentCommand}"`;
result.cmdErrors.push(error);
} else {
result.errors.push(error);
}
}
return 0;
function reportInvalidValue(opt, value) {
const shortOption = opt.shortName ? `${opt.shortName}, ` : ''
const errors = currentCommand ? result.cmdErrors : result.errors;
errors.push(`Invalid value "${value}" for option "${shortOption}${opt.longName}" - use one of [${opt.validValues}]`);
}
/**
* Get the value for the option's parameter. If the option does not require one,
* returns `null`. Reports missing parameters and invalid values.
*
* @returns {null|*}
*/
function paramForOption(opt, reportMissing = true) {
if (i + 1 >= argv.length || argv[i + 1].startsWith('-')) {
if (reportMissing)
reportMissingParam(opt)
return null;
}
const value = argv[i + 1];
if (!isValidOptionValue(opt, value) && reportMissing) {
reportInvalidValue(opt, value);
}
return value;
}
/**
* Returns true if:
* - we didn't find an option (opt === null) _or_
* - we found an option and it requires a param
* _and_ if the next arg looks like an argument.
*
* @param {object|null} opt
* @returns {boolean}
*/
function hasParamForUnknown(opt) {
return ((!opt || opt.param) && (i + 1) < argv.length && !argv[i + 1].match('(^[.-])|[.](csn|cdl|cds|json)$'));
}
}

@@ -516,7 +551,7 @@ }

// Return an array of complaints (possibly empty)
function verifyOptions(options, command = undefined, silent = false) {
function verifyOptions(options, commandName = undefined, silent = false) {
const result = [];
let opts;
if((options.betaMode || options.beta) && !options.testMode && !silent) {
if ((options.betaMode || options.beta) && !options.testMode && !silent) {
const mode = options.beta ? 'beta' : 'beta-mode';

@@ -526,3 +561,3 @@ result.push(`Option --${mode} was used. This option should not be used in productive scenarios!`)

if(options) {
if (options) {
[

@@ -540,9 +575,9 @@ 'defaultBinaryLength', 'defaultStringLength',

if (command) {
const cmd = optionProcessor.commands[command];
if (commandName) {
const cmd = optionProcessor.commands[commandName];
if (!cmd) {
throw new Error(`Expected existing command: "${command}"`);
throw new Error(`Expected existing command: "${cmd}"`);
}
opts = cmd.options;
options = options[command] || {};
options = options[cmd] || {};
if (typeof options === 'boolean') {

@@ -561,8 +596,8 @@ // Special case: command without options

// Don't report commands in top-level options
if ((command || !optionProcessor.commands[camelName]) && !silent) {
error = `Unknown option "${command ? command + '.' : ''}${camelName}"`;
if ((commandName || !optionProcessor.commands[camelName]) && !silent) {
error = `Unknown option "${commandName ? commandName + '.' : ''}${camelName}"`;
}
} else {
const param = options[camelName];
error = verifyOptionParam(param, opt, command ? command + '.' : '');
error = verifyOptionParam(param, opt, commandName ? commandName + '.' : '');
}

@@ -610,6 +645,6 @@ if (error) {

// If invalid command -> an empty array
function camelOptionsForCommand(command) {
if (!command || !optionProcessor.commands[command])
function camelOptionsForCommand(cmd) {
if (!cmd || !optionProcessor.commands[cmd])
return []
const cmd = optionProcessor.commands[command];
cmd = optionProcessor.commands[cmd];
const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);

@@ -616,0 +651,0 @@ return [...new Set(names)];

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

return;
if (artifact.kind && [ 'entity', 'view' ].includes(artifact.kind) && artifact['@readonly'] && artifact['@insertonly'])
if (artifact.kind === 'entity' && artifact['@readonly'] && artifact['@insertonly'])
this.warning(null, artifact.$path, 'Annotations “@readonly” and “@insertonly” can\'t be assigned in combination');

@@ -88,0 +88,0 @@ }

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

// filter for 'table', 'udf', 'calcview' === true
const TableUdfCv = Object.keys(artifact).filter(p => [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ].includes(p) && artifact[p]);
const persistenceAnnos = [ '@cds.persistence.table', '@cds.persistence.udf', '@cds.persistence.calcview' ];
const TableUdfCv = Object.keys(artifact).filter(p => persistenceAnnos.includes(p) && artifact[p]);
if (TableUdfCv.length > 1)

@@ -19,0 +20,0 @@ this.error(null, path, `Annotations ${ TableUdfCv.join(', ') } can't be used in combination`);

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

includes: simpleRef,
columns,
// Annotations are ignored.

@@ -44,3 +45,3 @@ '@': () => { /* ignore annotations */ },

const { inspectRef, artifactRef } = csnRefs( csn );
const { inspectRef, artifactRef, getElement } = csnRefs( csn );
const csnPath = [];

@@ -88,2 +89,17 @@ if (csn.definitions)

// eslint-disable-next-line jsdoc/require-jsdoc
function columns( parent, prop, node ) {
// Establish the link relationships
parent[prop].forEach((column) => {
const element = getElement(column);
if (element) {
setProp(column, '_element', element);
cleanupCallbacks.push(() => delete column._element);
setProp(element, '_column', column);
cleanupCallbacks.push(() => delete element._column);
}
});
standard(parent, prop, node);
}
// eslint-disable-next-line jsdoc/require-jsdoc
function simpleRef( node, prop ) {

@@ -90,0 +106,0 @@ setProp(node, '$path', [ ...csnPath ]);

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

const { ModelError } = require('../base/error');
/**

@@ -26,3 +28,3 @@ * Assert that targets of associations and compositions are entities.

if (!target)
throw new Error(`Expected target ${ mem.target }`);
throw new ModelError(`Expected target ${ mem.target }`);
if (target.kind !== 'entity') {

@@ -29,0 +31,0 @@ const isAssoc = this.csnUtils.getFinalBaseType(member.type) !== 'cds.Composition';

'use strict';
const { ModelError } = require('../base/error');
/**

@@ -13,3 +15,3 @@ * Trigger a recompilation in case of an association without .keys and without .on

if (prop === 'elements' && member.target && !member.keys && !member.on) { // trigger recompilation
throw new Error('Expected association to have either an on-condition or foreign keys.');
throw new ModelError('Expected association to have either an on-condition or foreign keys.');
}

@@ -16,0 +18,0 @@ }

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

// .call() with 'this' to ensure we have access to the options
rejectManagedAssociationsAndStructuresForHdbcsNames.call(this, SELECT, SELECT.$path);
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(this, SELECT, SELECT.$path);
}

@@ -45,6 +45,6 @@

*/
function rejectManagedAssociationsAndStructuresForHdbcsNames(queryArtifact, artifactPath) {
function rejectManagedAssociationsAndStructuresForHdbcdsNames(queryArtifact, artifactPath) {
if (this.options.transformation === 'hdbcds' && this.options.sqlMapping === 'hdbcds') {
forEachGeneric(queryArtifact, 'elements', (selectItem, elemName, prop, elementPath) => {
if (this.csnUtils.isManagedAssociationElement(selectItem))
if (this.csnUtils.isManagedAssociation(selectItem))
this.error('query-unexpected-assoc-hdbcds', elementPath);

@@ -57,2 +57,2 @@ if (this.csnUtils.isStructured(selectItem))

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

@@ -17,16 +15,14 @@

function checkSqlAnnotationOnElement(member, memberName, prop, path) {
if (isBetaEnabled(this.options, 'sqlSnippets')) {
if (member['@sql.replace'])
this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
if (member['@sql.prepend'])
this.error('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
if (member['@sql.replace'])
this.error(null, path, { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
if (member['@sql.prepend'])
this.message('anno-invalid-sql-element', path, { anno: 'sql.prepend' }, `Annotation $(ANNO) can't be used on elements` );
if (member['@sql.append']) {
if (this.artifact.query)
this.error('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
else if (this.csnUtils.isStructured(member))
this.error('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
else
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
}
if (member['@sql.append']) {
if (this.artifact.query)
this.message('anno-invalid-sql-view-element', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on elements in views` );
else if (this.csnUtils.isStructured(member))
this.message('anno-invalid-sql-struct', path, { anno: 'sql.append' }, `Annotation $(ANNO) can't be used on structured elements` );
else
checkValidAnnoValue(member, '@sql.append', path, this.error, this.options);
}

@@ -58,15 +54,20 @@ }

function checkSqlAnnotationOnArtifact(artifact, artifactName) {
if (isBetaEnabled(this.options, 'sqlSnippets')) {
if (artifact['@sql.prepend']) {
if (artifact.query)
this.error('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
else
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
}
if (artifact.kind !== 'entity') {
if (artifact['@sql.prepend'])
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.prepend', kind: artifact.kind }, `Annotation $(NAME) can't be used on an artifact of kind $(KIND)` );
if (artifact['@sql.append'])
this.message('anno-invalid-sql-kind', [ 'definitions', artifactName ], { name: '@sql.append', kind: artifact.kind }, `Annotation $(NAME) can't be used on an artifact of kind $(KIND)` );
}
else if (artifact['@sql.prepend']) {
if (artifact.query)
this.message('anno-invalid-sql-view', [ 'definitions', artifactName ], { name: '@sql.prepend' }, `Annotation $(NAME) can't be used on views` );
else
checkValidAnnoValue(artifact, '@sql.prepend', [ 'definitions', artifactName ], this.error, this.options);
}
if (artifact['@sql.replace'])
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
}
if (artifact['@sql.replace'])
this.error(null, [ 'definitions', artifactName ], { anno: 'sql.replace' }, `Annotation $(ANNO) is reserved and must not be used`);
checkValidAnnoValue(artifact, '@sql.append', [ 'definitions', artifactName ], this.error, this.options);
}

@@ -73,0 +74,0 @@

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

return;
if (member.scale && [ 'variable', 'floating' ].includes(member.scale))
if (member.scale && (member.scale === 'variable' || member.scale === 'floating'))
this.error(null, path, { name: member.scale }, 'Unexpected scale $(NAME)');

@@ -25,0 +25,0 @@ }

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

applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], true, { drillRef: true });
applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });

@@ -208,8 +208,5 @@ forEachDefinition(csn, (artifact, artifactName, prop, path) => {

{
skipArtifact: artifact => artifact.abstract || hasAnnotationValue(artifact, '@cds.persistence.skip'),
skip: [
'action',
'function',
'event',
],
skipArtifact: artifact => artifact.abstract ||
hasAnnotationValue(artifact, '@cds.persistence.skip') ||
[ 'action', 'function', 'event' ].includes(artifact.kind),
});

@@ -216,0 +213,0 @@ }

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

inline: { kind: [ 'element' ], inherits: 'columns' },
$noOrigin: { kind: [ 'element' ], test: TODO },
excludingDict: {

@@ -329,4 +330,4 @@ kind: 'element',

optional: [
'path', 'elements', '_outer',
'scope', '_artifact', '$inferred', '$expand',
'path', 'elements', '_outer', '_parent', '_main', '_block', 'kind',
'scope', '_artifact', '$inferred', '$expand', '$tableAliases', '_$next',
'_effectiveType', // by propagation

@@ -646,3 +647,4 @@ ],

function builtin( node, parent, prop, spec, name ) {
if (![ 'string', 'boolean' ].includes(typeof node))
const type = typeof node;
if (type !== 'string' && type !== 'boolean')
throw new Error(`Property '${ prop }' must be a boolean or string but was '${ typeof node }'${ at( [ node, parent ], prop, name ) }` );

@@ -649,0 +651,0 @@

// The builtin artifacts of CDS
// TODO: split this file
// - in base/: common definitions
// - in compiler/: XSN-specific
// - in ?: CSN-specific
'use strict';
const { builtinLocation } = require('../base/location');
const { setProp } = require('./utils');
const { setLink: setProp } = require('./utils');

@@ -59,3 +64,2 @@ const core = {

*/
const functionsWithoutParens = [

@@ -180,3 +184,4 @@ 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',

!absolute.match(/^cds\.foundation(\.|$)/) &&
!absolute.match(/^cds\.outbox(\.|$)/); // Requested by Node runtime
!absolute.match(/^cds\.outbox(\.|$)/) && // Requested by Node runtime
!absolute.match(/^cds\.xt(\.|$)/); // Requested by Mtx
}

@@ -219,5 +224,2 @@

env( coreHana, 'cds.hana.', hana );
// namespace:"localized" stores localized convenience views ---
const localized = createNamespace( 'localized', true );
model.definitions.localized = localized;
model.$internal = { $frontend: '$internal' };

@@ -224,0 +226,0 @@ return;

@@ -462,2 +462,6 @@ // Checks on XSN performed during compile()

*
* TODO: remove this function - it also checks for parameter in type
* references. We could have a warning, see also configurable errors
* 'args-no-params', 'args-undefined-param'.
*
* @param {object} pathStep The expression to check

@@ -498,4 +502,11 @@ */

for (const fAName in formalArgs) {
if (!actualArgs[fAName] && !formalArgs[fAName].default)
missingArgs.push(fAName);
if (!actualArgs[fAName]) {
// Note: _effectiveType points to cds.String for `type T : DefaultString`.
// And `default` may appear at any `type` in the hierarchy.
let fArg = formalArgs[fAName];
while (fArg.type && !fArg.default)
fArg = fArg.type._artifact;
if (!fArg.default)
missingArgs.push(fAName);
}
}

@@ -640,3 +651,3 @@

/**
* Check wether the supplied argument is a virtual element
* Check whether the supplied argument is a virtual element
*

@@ -643,0 +654,0 @@ * TO CLARIFY: do we want the "no virtual element" check for virtual elements/columns, too?

@@ -19,3 +19,3 @@ // Detect cycles in the dependencies between nodes (artifacts and elements)

const { setProp } = require('../base/model');
const { setLink: setProp } = require('./utils'); // check enum/non-enum

@@ -22,0 +22,0 @@ // Detect cyclic dependencies between all nodes reachable from `definitions`.

@@ -24,4 +24,9 @@ // Main XSN-based compiler functions

const { fns } = require('./shared');
const { define } = require('./definer');
const resolve = require('./resolver');
const define = require('./define');
const finalizeParseCdl = require('./finalize-parse-cdl');
const extend = require('./extend');
const kickStart = require('./kick-start');
const populate = require('./populate');
const resolve = require('./resolve');
const tweakAssocs = require('./tweak-assocs');
const propagator = require('./propagator');

@@ -38,2 +43,4 @@ const check = require('./checks');

const csnExtensions = [ '.json', '.csn' ];
const cdlExtensions = [ '.cds', '.hdbcds', '.hdbdd', '.cdl' ];

@@ -61,4 +68,4 @@ // Class for command invocation errors. Additional members:

* Parse the given source with the correct parser based on the file name's
* extension. For example use edm2csn for `.xml` files and the CDL parser
* for `.cds` files.
* extension. For example uses CDL parser for `.cds` files.
* Respects the value of `options.fallbackParser`.
*

@@ -74,5 +81,5 @@ * @param {string} source Source code of the file.

const ext = path.extname( filename ).toLowerCase();
if ([ '.json', '.csn' ].includes(ext) || options.fallbackParser === 'csn!')
if (csnExtensions.includes(ext) || options.fallbackParser === 'csn!')
return parseCsn.parse( source, filename, options, messageFunctions );
if ([ '.cds', '.hdbcds', '.hdbdd', '.cdl' ].includes(ext))
if (cdlExtensions.includes(ext))
return parseLanguage( source, filename, options, messageFunctions );

@@ -96,4 +103,4 @@ if (options.fallbackParser === 'csn')

// relative to the working directory `process.cwd()`. With argument `dir`, the
// file names are relative to `process.cwd()+dir`. Options can have the
// following properties:
// file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
// Options can have the following properties:
// - Truthy `parseOnly`: stop compilation after parsing.

@@ -110,3 +117,3 @@ // - Truthy `lintMode`: do not do checks and propagation

// errors. The fulfillment value is an augmented CSN (see
// ./compiler/definer.js).
// ./compiler/define.js).
//

@@ -335,5 +342,6 @@ // If there are errors, the promise is rejected. If there was an invocation

*
* Argument `sourcesDict` is a dictionary (it could actually be a ordinary object)
* mapping filenames to either source texts (string) or XSN objects (AST-like
* augmented CSNs). It could also be a simple string, which is then considered
* Argument `sourcesDict` is a dictionary (it could actually be an ordinary object)
* mapping filenames to either source texts (string) or JS objects; the objects
* are usually CSNs, or XSNs (AST-like augmented CSNs) with option `$xsnObjects`.
* It could also be a simple string, which is then considered
* to be the source text of a file named `<stdin>.cds`.

@@ -366,5 +374,11 @@ *

}
else { // source is a XSN object (CSN/CDL parser output)
else if (options.$xsnObjects) { // source is a XSN object with option $xsnObjects
sources[filename] = source;
}
else { // source is a CSN object
const ast = parseCsn.augment( source, filename, options, model.$messageFunctions );
sources[filename] = ast;
ast.location = { file: filename };
assertConsistency( ast, options );
}
}

@@ -431,6 +445,11 @@ moduleLayers.setLayers( sources );

if (options.parseCdl) {
finalizeParseCdl( model );
throwWithError();
return model;
}
extend( model );
kickStart( model );
populate( model );
resolve( model );
tweakAssocs( model );
assertConsistency( model );

@@ -437,0 +456,0 @@ throwWithError();

@@ -6,3 +6,3 @@ // Module handling, layers and packages

const detectCycles = require('./cycle-detector');
const { setProp } = require('../base/model');
const { setLink } = require('./utils');

@@ -14,3 +14,3 @@ function setLayers( sources ) {

ast.realname = name;
setProp( ast, '_deps', [] );
setLink( ast, '_deps', [] );
for (const d of ast.dependencies || []) {

@@ -29,3 +29,3 @@ const art = sources[d.realname];

function setExtends( node, representative, sccDeps = Object.create(null) ) {
setProp( node, '_layerRepresentative', representative );
setLink( node, '_layerRepresentative', representative );
if (layerRepresentative !== representative) {

@@ -46,3 +46,3 @@ layerRepresentative = representative;

Object.assign( sccDeps, ...exts );
setProp( representative, '_layerExtends', sccDeps );
setLink( representative, '_layerExtends', sccDeps );
// console.log ('SCC:', node.realname)

@@ -49,0 +49,0 @@ }

@@ -8,6 +8,6 @@ //

forEachMember,
setProp,
forEachGeneric,
isDeprecatedEnabled,
} = require( '../base/model');
const { linkToOrigin, withAssociation } = require('./utils');
const { setLink, linkToOrigin, withAssociation } = require('./utils');
// const { refString } = require( '../base/messages')

@@ -23,4 +23,5 @@

'@cds.persistence.skip': notWithPersistenceTable,
'@sql.append': never,
'@sql.prepend': never,
'@sql.append': never,
'@sql.replace': never,
'@Analytics.hidden': never,

@@ -32,7 +33,7 @@ '@Analytics.visible': never,

'@fiori.draft.enabled': onlyViaArtifact,
'@': withKind, // always except in 'returns' and 'items'
doc: withKind, // always except in 'returns' and 'items'
'@': annotation, // always except in 'returns' and 'items'
doc: annotation, // always except in 'returns' and 'items'
default: withKind, // always except in 'returns' and 'items'
virtual: notViaType,
notNull, // a variant of notViaType()
virtual,
notNull,
targetElement: onlyViaParent, // in foreign keys

@@ -43,3 +44,3 @@ value: onlyViaParent, // enum symbol value

// actions: struct includes & primary source = in definer/resolver
type: always,
type: notWithExpand,
length: always,

@@ -50,9 +51,7 @@ precision: always,

localized: always,
target: always,
targetAspect: always,
cardinality: always,
on: ( prop, target, source ) => {
target[prop] = source[prop];
}, // TODO: get rid of this soon!
foreignKeys: expensive, // actually always, but dictionary copy
target: notWithExpand,
targetAspect: notWithExpand,
cardinality: notWithExpand,
on: notWithExpand,
foreignKeys: expensive, // includes "notWithExpand", dictionary copy
items,

@@ -66,4 +65,13 @@ elements: expensive,

const enableExpandElements = !isDeprecatedEnabled( options, 'noElementsExpansion' );
// eslint-disable-next-line max-len
const oldVirtualNotNullPropagation = isDeprecatedEnabled( options, 'oldVirtualNotNullPropagation' );
forEachDefinition( model, run );
const { warning, throwWithError } = model.$messageFunctions;
// TODO: move 'virtual' handling/checks to resolver if
// 'deprecated.oldVirtualNotNullPropagation' is gone
if (!oldVirtualNotNullPropagation) // check would always be right, but to be ultra compatible…
forEachDefinition( model, checkVirtual );
throwWithError();
return model;

@@ -125,8 +133,9 @@

run( obj.items );
setProp( art, '_status', 'propagated' );
setLink( art, '_status', 'propagated' );
}
function step({ target, source }) {
// console.log('PROPS:',source&&refString(source),'->',refString(target))
const viaType = target.type && !target.type.$inferred;
// console.log('PROPS:',source&&source.name,'->',target.name)
const viaType = target.type && // TODO: falsy $inferred value instead 'cast'?
(!target.type.$inferred || target.type.$inferred === 'cast');
const keys = Object.keys( source );

@@ -140,4 +149,6 @@ for (const prop of keys) {

}
// propagate NOT NULL and VIRTUAL from sub elements:
if (target.$inferred !== 'proxy' &&
// propagate NOT NULL and VIRTUAL from sub elements with
// 'deprecated.oldVirtualNotNullPropagation':
if (oldVirtualNotNullPropagation &&
target.$inferred !== 'proxy' &&
target.kind === 'element' && source.kind === 'element') {

@@ -154,3 +165,3 @@ let elem = source; // the outer element

}
// setProp( target, '_status', 'shallow-propagated' );
// setLink( target, '_status', 'shallow-propagated' );
}

@@ -166,8 +177,12 @@

}
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 );
target[prop] = Object.assign( {}, val, { $inferred: 'prop' } );
if ('_artifact' in val)
setLink( target[prop], '_artifact', val._artifact );
if ('_outer' in val)
setLink( target[prop], '_outer', val._outer );
if ('_parent' in val)
setLink( target[prop], '_parent', val._parent );
if ('_main' in val)
setLink( target[prop], '_main', val._main );
}

@@ -191,2 +206,8 @@ }

// accessed at their type being a main artifact
// Expensive properties are also not propagated with `expand`:
// * `elements`: the compiler calculates its own `elements` for a structure
// ref with `expand`.
// * `params`: no element has parameters
// * `enum`: an enum cannot be used with `expand`
// * `keys`: should also not be propagated with `expand`
function expensive( prop, target, source ) {

@@ -196,2 +217,4 @@ // console.log(prop,source.name,'->',target.kind,target.name);

return;
if (target.expand) // do not propagate `keys` with expand
return;
if (prop !== 'foreignKeys' && availableAtType( prop, target, source ))

@@ -215,15 +238,2 @@ // foreignKeys must always be copied with target to avoid any confusion

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

@@ -235,8 +245,25 @@ if (target.$inferred === 'proxy' || target.$inferred === 'expand-element')

function notWithExpand( prop, target, source ) {
if (!target.expand)
always( prop, target, source );
}
function notWithPersistenceTable( prop, target, source ) {
const tableAnno = target['@cds.persistence.table'];
if (!tableAnno || tableAnno.val === null)
annotation( prop, target, source );
}
function annotation( prop, target, source ) {
const anno = source[prop];
if (anno.val !== null)
withKind( prop, target, source );
}
function onlyViaArtifact( prop, target, source ) {
const from = target._from && target._from[0].path;
if (!(from ? from[from.length - 1]._artifact : source)._main)
annotation( prop, target, source );
}
function withKind( prop, target, source ) {

@@ -248,6 +275,43 @@ if (target.kind && (!target._parent || target._parent.returns !== target))

function notNull( prop, target, source, viaType ) {
if (!(viaType || target.value && withAssociation( target.value, targetMinZero )))
// Really "reset" NOT NULL when ref has assoc with cardinality min: 0 (TODO: Universal CSN)
if (oldVirtualNotNullPropagation && viaType)
return; // strange propagation not supported with Universal CSN
if (target.value && withAssociation( target.value, targetMinZero ))
target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in Universal CSN
// $inferred: 'NULL' is only an issue for sub elements with a 'value' property;
// it only exists with nested projections, i.e. never with deprecated option enabled
else
always( prop, target, source );
}
function virtual( prop, target, source, viaType ) {
if (!viaType)
always( prop, target, source );
else if (!oldVirtualNotNullPropagation) // NULL would block strange propagation to sub element
target[prop] = { $inferred: 'NULL', val: undefined }; // set null value in Univeral CSN
}
function checkVirtual( view ) {
if (view.query)
forEachGeneric( view, 'elements', checkNonVirtualElement );
}
function checkNonVirtualElement( elem ) {
// Not enough at all, but so are the current checks - a complete expression
// must be checked. Here we just check what might have worked before.
// TODO: Propagate 'virtual' in resolver if 'deprecated.oldVirtualNotNullPropagation' is gone.
const path = !elem.virtual && elem.value && elem.value.path;
if (!path || path.broken)
return;
for (const item of path) {
const art = item && item._artifact;
if (art && art.virtual && art.virtual.val) {
warning( 'def-missing-virtual', [ item.location, elem ], { art, keyword: 'virtual' },
// eslint-disable-next-line max-len
'Prepend $(KEYWORD) to current select item - referred element $(ART) is virtual which is not inherited' );
return;
}
}
}
function returns( prop, target, source, ok ) {

@@ -257,4 +321,4 @@ if (ok || target.$inferred === 'proxy' || target.$inferred === 'include' ) {

setEffectiveType( target[prop], source[prop] );
setProp( target[prop], '_origin', source[prop] );
setProp( target[prop], '_outer', target._outer || target ); // for setMemberParent
setLink( target[prop], '_origin', source[prop] );
setLink( target[prop], '_outer', target._outer || target ); // for setMemberParent
}

@@ -281,3 +345,4 @@ }

if (art._origin)
return !art.expand && art._origin;
// Do not consider _origin if due to expand of table alias ref
return (!art.expand || art._origin.kind === 'element') && art._origin;
// Remark: a column with an 'inline' is never an element -> no need to check

@@ -293,3 +358,3 @@ // art.inline

return (art.type && !art.type.$inferred)
return (art.type && (!art.type.$inferred || art.type.$inferred === 'cast'))
? art.type._artifact

@@ -302,3 +367,3 @@ : art._origin;

return false;
setProp( art, '_status', 'propagating' );
setLink( art, '_status', 'propagating' );
return true;

@@ -309,3 +374,3 @@ }

if ('_effectiveType' in source)
setProp( target, '_effectiveType', source._effectiveType);
setLink( target, '_effectiveType', source._effectiveType);
}

@@ -312,0 +377,0 @@

@@ -8,5 +8,9 @@ // Compiler functions and utilities shared across all phases

const { dictAddArray } = require('../base/dictionaries');
const { setProp } = require('../base/model');
const { setLink, dependsOn, pathName } = require('./utils');
const {
setLink,
setArtifactLink,
dependsOn,
pathName,
} = require('./utils');

@@ -165,3 +169,3 @@ function artifactsEnv( art ) {

function checkConstRef( art ) {
return ![ 'builtin', 'param' ].includes( art.kind );
return art.kind !== 'builtin' && art.kind !== 'param';
}

@@ -270,5 +274,5 @@

// incomplete type AST or empty env (already reported)
return setLink( ref, undefined );
return setArtifactLink( ref, undefined );
}
setLink( ref, 0 ); // avoid cycles for type T: association to T.m;
setArtifactLink( ref, 0 ); // avoid cycles for type T: association to T.m;

@@ -286,3 +290,3 @@ let spec = specExpected[expected];

'Unexpected parameter reference' );
return setLink( ref, null );
return setArtifactLink( ref, null );
}

@@ -305,3 +309,3 @@ spec = specExpected[spec.escape];

env = (spec.lexical === 'from') ? query._parent : query || user._main || user;
// queries: first tabaliases, then $magic - value refs: first $self, then $magic
// queries: first table aliases, then $magic - value refs: first $self, then $magic
if (!extDict && !spec.noExt) {

@@ -322,3 +326,3 @@ // TODO: change to name restriction for $joins, not own environments

if (!art) {
return setLink( ref, art );
return setArtifactLink( ref, art );
}

@@ -329,8 +333,24 @@ else if (!spec.envFn && user._pathHead) {

else if (art.kind === 'using') {
art = model.definitions[art.name.absolute];
if (!art)
return setLink( ref, art );
else if (art.$duplicates) // redefined art referenced by using proxy
return setLink( ref, false );
setLink( head, art ); // we do not want to see the using
const def = model.definitions[art.name.absolute];
if (!def) {
// It could be that the artifact was removed and that the using-proxy needs to be reported.
// The check for $inferred is required to avoid consequential errors for cases such as:
// using unknown.abc;
// entity P as projection on abc; // <-- no consequential error here
if (art.$inferred === 'path-prefix') {
// head._artifact referred to the `using`. Remove the reference,
// so that getPathItem() below emits an error.
setArtifactLink( head, false );
setArtifactLink( ref, false );
}
else {
return setArtifactLink( ref, false );
}
}
else if (def.$duplicates) { // redefined art referenced by using proxy
return setArtifactLink( ref, false );
}
else {
setArtifactLink( head, def ); // we do not want to see the using
}
}

@@ -343,10 +363,10 @@ else if (art.kind === 'mixin') {

// also set link on head?
return setLink( ref, false );
return setArtifactLink( ref, false );
}
// console.log(message( null, art.location, art, {}, 'Info','MIX').toString())
setLink( head, art, '_navigation' );
setLink( head, '_navigation', art );
}
else if (art.kind === '$navElement') {
setLink( head, art, '_navigation' );
setLink( head, art._origin );
setLink( head, '_navigation', art );
setArtifactLink( head, art._origin );
// TODO: set art?

@@ -360,9 +380,13 @@ }

// also set link on head?
return setLink( ref, false );
return setArtifactLink( ref, false );
}
setLink( head, art, '_navigation' );
setLink( head, art._origin ); // query source or leading query in FROM
setLink( head, '_navigation', art );
setArtifactLink( head, art._origin ); // query source or leading query in FROM
// require('../model/revealInternalProperties').log(model, 'foo.bar.S.V1a')
if (!art._origin)
return setLink( ref, art._origin );
return setArtifactLink( ref, art._origin );
// if just table alias (with expand), mark `user` with `$noOrigin` to indicate
// that the corresponding entity should not be put as $origin into the CSN
if (path.length === 1 && user && art.kind === '$tableAlias')
user.$noOrigin = true;
}

@@ -377,3 +401,3 @@

if (!art)
return setLink( ref, art );
return setArtifactLink( ref, art );

@@ -384,3 +408,3 @@ if (art.$autoElement) {

art = art.elements[step.id];
setLink( step, art );
setArtifactLink( step, art );
path.push( step );

@@ -392,3 +416,3 @@ }

signalNotFound( spec.expectedMsgId, [ ref.location, user ], null );
return setLink( ref, false );
return setArtifactLink( ref, false );
}

@@ -425,3 +449,3 @@ else if (fail) {

// TODO: follow FROM here, see csnRef - fromRef
return setLink( ref, art );
return setArtifactLink( ref, art );
}

@@ -491,2 +515,4 @@

return user._pathHead._origin;
// const { _origin } = user._pathHead;
// return (_origin && _origin.kind === '$tableAlias') ? _origin._origin : _origin;
}

@@ -518,3 +544,3 @@ const head = path[0];

if (Array.isArray(r)) { // redefinitions
setLink( head, r );
setArtifactLink( head, r );
return false;

@@ -530,3 +556,3 @@ }

// TODO: replace it in to-csn correspondingly
return setLink( head, r );
return setArtifactLink( head, r );
}

@@ -537,3 +563,3 @@ }

// TODO: $projection only if not delimited _and_ length > 1
return setLink( head, r );
return setArtifactLink( head, r );
}

@@ -543,3 +569,3 @@ else if (r.kind !== '$tableAlias' || path.length > 1 || user.expand || user.inline) {

// TODO: $projection only if not delimited _and_ length > 1
return setLink( head, r );
return setArtifactLink( head, r );
}

@@ -561,7 +587,7 @@ }

}
setLink( head, r );
setArtifactLink( head, r );
return false;
}
else if (r) {
return setLink( head, r );
return setArtifactLink( head, r );
}

@@ -620,3 +646,3 @@ }

}
return setLink( head, null );
return setArtifactLink( head, null );
}

@@ -629,2 +655,3 @@

function getPathItem( path, spec, user, artItemsCount, headArt ) {
// let art = (headArt && headArt.kind === '$tableAlias') ? headArt._origin : headArt;
let art = headArt;

@@ -646,3 +673,3 @@ let nav = spec.assoc !== '$keys' && null; // false for '$keys'

const env = fn( art, item.location, user, spec.assoc );
const sub = setLink( item, env && env[item.id] );
const sub = setArtifactLink( item, env && env[item.id] );

@@ -709,3 +736,3 @@ if (!sub) {

// after rewriteKeys() is the one with the same name.id
setLink( item, node._artifact, '_navigation' );
setLink( item, '_navigation', node._artifact );
if (item === last)

@@ -843,3 +870,3 @@ return;

for (const a of annos) {
setProp( a, '_block', block );
setLink( a, '_block', block );
a.$priority = priority;

@@ -894,3 +921,3 @@ if (construct !== art)

};
setProp( anno, '_block', block );
setLink( anno, '_block', block );
// TODO: _parent, _main is set later (if we have ElementRef), or do we

@@ -897,0 +924,0 @@ // set _artifact?

@@ -9,2 +9,4 @@ // Simple compiler utility functions

// TODO: probably split this file into utils/….js
'use strict';

@@ -35,2 +37,4 @@

/**
* Set compiler-calculated annotation value.
*
* @param {XSN.Artifact} art

@@ -49,2 +53,3 @@ * @param {string} anno

literal,
$inferred: '$generated',
location,

@@ -54,3 +59,2 @@ };

// TODO: define setLink() like the current setProp(), we might have setArtifactLink()
// Do not share this function with CSN processors!

@@ -64,24 +68,9 @@

// - 0 (for _effectiveType only): circular reference
function setLink( obj, value = null, prop = '_artifact' ) {
function setLink( obj, prop, value ) {
Object.defineProperty( obj, prop, { value, configurable: true, writable: true } );
return value;
}
/**
* Like `obj.prop = value`, but not contained in JSON / CSN
* It's important to set enumerable explicitly to false (although 'false' is the default),
* as else, if the property already exists, it keeps the old setting for enumerable.
*
* @param {object} obj
* @param {string} prop
* @param {any} value
*/
function setProp(obj, prop, value) {
const descriptor = {
value,
configurable: true,
writable: true,
enumerable: false,
};
Object.defineProperty( obj, prop, descriptor );
// And a variant with the most common `prop`:
function setArtifactLink( obj, value ) {
Object.defineProperty( obj, '_artifact', { value, configurable: true, writable: true } );
return value;

@@ -100,3 +89,3 @@ }

setMemberParent( elem, name, parent, prop ); // TODO: redef in template
setProp( elem, '_origin', origin );
setLink( elem, '_origin', origin );
// TODO: should we use silent dependencies also for other things, like

@@ -119,7 +108,8 @@ // included elements? (Currently for $inferred: 'expand-element' only)

}
if (parent._outer)
if (parent._outer && parent._outer.items) // TODO: remove for items, too
parent = parent._outer;
setProp( elem, '_parent', parent );
setProp( elem, '_main', parent._main || parent );
elem.name.absolute = elem._main.name.absolute;
setLink( elem, '_parent', parent );
setLink( elem, '_main', parent._main || parent );
const parentName = parent.name || parent._outer.name;
elem.name.absolute = parentName.absolute;
if (name == null)

@@ -130,6 +120,6 @@ return;

if (normalized === kind)
elem.name[kind] = (parent.name[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parent.name[kind] }.${ name }` : name;
elem.name[kind] = (parentName[kind] != null && kind !== 'select' && kind !== 'alias') ? `${ parentName[kind] }.${ name }` : name;
else if (parent.name[kind] != null)
elem.name[kind] = parent.name[kind];
else if (parentName[kind] != null)
elem.name[kind] = parentName[kind];

@@ -151,3 +141,3 @@ else

if (!user._deps)
setProp( user, '_deps', [] );
setLink( user, '_deps', [] );
user._deps.push( { art, location } );

@@ -165,3 +155,3 @@ }

if (!user._deps)
setProp( user, '_deps', [] );
setLink( user, '_deps', [] );
user._deps.push( { art } );

@@ -173,6 +163,6 @@ }

prop = 'elements';
setProp( elem, '_block', block );
setLink( elem, '_block', block );
const kind = `_${ elem.kind }`; // _extend or _annotate
if (!parent[kind])
setProp( parent, kind, {} );
setLink( parent, kind, {} );
// if (name === '' && prop === 'params') {

@@ -232,3 +222,171 @@ // pushToDict( parent[kind], 'returns', elem ); // not really a dict

function copyExpr( expr, location, skipUnderscored, rewritePath ) {
if (!expr || typeof expr !== 'object')
return expr;
else if (Array.isArray(expr))
return expr.map( e => copyExpr( e, location, skipUnderscored, rewritePath ) );
const proto = Object.getPrototypeOf( expr );
if (proto && proto !== Object.prototype) // do not copy object from special classes
return expr;
const r = Object.create( proto );
for (const prop of Object.getOwnPropertyNames( expr )) {
const pd = Object.getOwnPropertyDescriptor( expr, prop );
if (!pd.enumerable) { // should include all properties starting with _
if (!skipUnderscored ||
prop === '_artifact' || prop === '_navigation' || prop === '_effectiveType')
Object.defineProperty( r, prop, pd );
}
else if (!proto) {
r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
}
else if (prop === 'location') {
r[prop] = location || pd.value;
}
else if (prop.charAt(0) !== '$' || prop === '$inferred') {
r[prop] = copyExpr( pd.value, location, skipUnderscored, rewritePath );
}
else if (!skipUnderscored) { // skip $ properties
Object.defineProperty( r, prop, pd );
}
}
return r;
}
function testExpr( expr, pathTest, queryTest ) {
// TODO: also check path arguments/filters
if (!expr || typeof expr === 'string') { // parse error or keywords in {xpr:...}
return false;
}
else if (Array.isArray(expr)) {
return expr.some( e => testExpr( e, pathTest, queryTest ) );
}
else if (expr.path) {
return pathTest( expr );
}
else if (expr.query) {
return queryTest( expr.query );
}
else if (expr.op && expr.args) {
// unnamed args => array
if (Array.isArray(expr.args))
return expr.args.some( e => testExpr( e, pathTest, queryTest ) );
// named args => dictionary
for (const namedArg of Object.keys(expr.args)) {
if (testExpr(expr.args[namedArg], pathTest, queryTest))
return true;
}
}
return false;
}
// Return true if the path `item` with a final type `assoc` has a max target
// cardinality greater than one - either specified on the path item or assoc type.
function targetMaxNotOne( assoc, item ) {
// Semantics of associations without provided cardinality: [*,0..1]
const cardinality = item.cardinality || assoc.cardinality;
return cardinality && cardinality.targetMax && cardinality.targetMax.val !== 1;
}
// Query tree post-order traversal - called for everything which contributes to the query
// i.e. is necessary to calculate the elements of the query
// except "real ones": operands of UNION etc, JOIN with ON, and sub queries in FROM
// NOTE: does not run on non-referred sub queries! Consider using ‹main›.$queries instead!
function traverseQueryPost( query, simpleOnly, callback ) {
if (!query) // parser error
return;
if (!query.op) { // in FROM (not JOIN)
if (query.query) // subquery
traverseQueryPost( query.query, simpleOnly, callback );
return;
}
if (simpleOnly) {
const { from } = query;
if (!from || from.join) // parse error or join
return; // ok are: path or simple sub query (!)
}
if (query.from) { // SELECT
traverseQueryPost( query.from, simpleOnly, callback );
// console.log('FC:')
callback( query );
// console.log('FE:')
}
else if (query.args) { // JOIN, UNION, INTERSECT
if (!query.join && simpleOnly == null) {
// enough for elements: traverse only first args for UNION/INTERSECT
// TODO: we might use this also when we do not rewrite associations
// in non-referred sub queries
traverseQueryPost( query.args[0], simpleOnly, callback );
}
else {
for (const q of query.args)
traverseQueryPost( q, simpleOnly, callback );
// The ON condition has to be traversed extra, because it must be evaluated
// after the complete FROM has been traversed. It is also not necessary to
// evaluate it in populateQuery().
}
}
// else: with parse error (`select from <EOF>`, `select distinct from;`)
}
// Call callback on all queries in dependency order, i.e. starting with query Q
// 1. sub queries in FROM sources of Q
// 2. Q itself, except if non-referred query, but with right UNION parts
// 3. sub queries in ON in FROM of Q
// 4. sub queries in columns, WHERE, HAVING
function traverseQueryExtra( main, callback ) {
if (!main.$queries)
return;
// with a top-level UNION, $queries[0] is just the left
traverseQueryPost( main.query, false, (q) => { // also with right of UNION (to be compatible)
setLink( q, '_status', 'extra' );
callback( q );
} );
for (const query of main.$queries.slice(1)) {
if (query._status === 'extra' || query._parent.kind === '$tableAlias')
continue; // if parent is alias, query is FROM source -> run by traverseQueryPost
// we are now in the top-level (parent is entity) or a non-referred query (parent is query)
setLink( query, '_status', 'extra' ); // do not call callback() in non-referred query
// console.log( 'A:', query.name,query._status)
traverseQueryPost( query, null, (q) => {
if (q._status !== 'extra') {
// console.log( 'T:', q.name)
setLink( q, '_status', 'extra' );
callback( q );
}
// else console.log( 'E:', q.name)
} );
}
}
// About Helper property $expand for faster the XSN-to-CSN transformation
// - null/undefined: artifact, member, items does not contain expanded members
// - 'origin': all expanded (sub) elements have no new target/on and no new annotations
// that value is only on elements, types, and params -> no other members
// when set, only on elem/art with expanded elements
// - 'target': all expanded (sub) elements might only have new target/on, but
// no indivual annotations on any (sub) member
// when set, traverse all parents where the value has been 'origin' before
// - 'annotate': at least one inferred (sub) member has an individual annotation,
// not counting propagated ones; set up to the definition (main artifact)
// (only set with anno on $inferred elem), annotate “beats” target
// Usage according to CSN flavor:
// - gensrc: do not render inferred elements (including expanded elements),
// collect annotate statements with value 'annotate'
// - client: do not render expanded sub elements if artifact/member is no type, has a type,
// has $expand = 'origin', and all its _origin also have $expand = 'origin'
// (might sometimes render the elements unnecessarily, which is not wrong)
// - universal: do not render expanded sub elements if $expand = 'origin'
function setExpandStatus( elem, status ) {
// set on element
while (elem._main) {
elem = elem._parent;
if (status === 'annotate' ? elem.$expand === 'annotate' : elem.$expand !== 'origin')
return;
elem.$expand = status; // meaning: expanded, containing assocs
for (let line = elem.items; line; line = line.items)
line.$expand = status; // to-csn just uses the innermost $expand
}
}
module.exports = {

@@ -240,3 +398,3 @@ pushLink,

setLink,
setProp,
setArtifactLink,
linkToOrigin,

@@ -251,2 +409,8 @@ dependsOn,

splitIntoPath,
copyExpr,
testExpr,
targetMaxNotOne,
traverseQueryPost,
traverseQueryExtra,
setExpandStatus,
};

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

if (carrier.kind === 'entity' || carrier.kind === 'view') {
if (carrier.kind === 'entity') {
// If AppliesTo=[EntitySet/Singleton/Collection, EntityType], EntitySet/Singleton/Collection has precedence

@@ -558,3 +558,3 @@ testToAlternativeEdmTarget = (x => {

// this might be more precise if handleAnnotation would know more about the carrier
testToStandardEdmTarget = (x => x
testToStandardEdmTarget = (x => x
? ['Parameter', 'Property'].some(y => x.includes(y) ||

@@ -953,3 +953,3 @@ carrier._isCollection && x.includes('Collection'))

typeName = 'Bool';
if (!['true','false'].includes(val)) {
if (val !== 'true' && val !== 'false') {
message(warning, context, `found String, but expected type ${ dTypeName }`);

@@ -1290,10 +1290,6 @@ }

else { // literal
let escaped = obj;
if (typeof escaped === 'string') {
escaped = escaped.replace(/&/g, '&amp;')
}
edmNode = new Edm.ValueThing(v,
exprDef && exprDef.valueThingName || getXmlTypeName(escaped), escaped);
exprDef && exprDef.valueThingName || getXmlTypeName(obj), obj);
// typename for static expression rendering
edmNode.setJSON( { [getJsonTypeName(escaped)]: escaped } );
edmNode.setJSON( { [getJsonTypeName(obj)]: obj } );
}

@@ -1300,0 +1296,0 @@ }

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

function draftAnnotations(carrier, aName, aNameWithoutQualifier) {
if ((carrier.kind === 'entity' || carrier.kind === 'view') &&
if ((carrier.kind === 'entity') &&
(aNameWithoutQualifier === '@Common.DraftRoot.PreparationAction' ||

@@ -140,3 +140,3 @@ aNameWithoutQualifier === '@Common.DraftRoot.ActivationAction' ||

if (carrier.kind === 'entity' || carrier.kind === 'view') {
if (carrier.kind === 'entity') {
warning(null, null, `annotation preprocessing/${aNameWithoutQualifier}: annotation must not be used for an entity, ${ctx}`);

@@ -143,0 +143,0 @@ return false;

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

whatsMyServiceRootName,
autoexposeSchemaName,
options ] = initializeModel(csn, _options, messageFunctions);

@@ -192,5 +193,5 @@

// Bring the schemas in alphabetical order, service first, root last
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== 'root' && n !== serviceCsn.name).sort();
if(subSchemaDictionary.root)
sortedSchemaNames.push('root');
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== autoexposeSchemaName && n !== serviceCsn.name).sort();
if(subSchemaDictionary[autoexposeSchemaName])
sortedSchemaNames.push(autoexposeSchemaName);

@@ -239,3 +240,3 @@ // Finally create the schemas and register them in the service.

// to let the internal object.name have the sub-schema name.
// With nested services we must do a longest path match and check wether
// With nested services we must do a longest path match and check whether
// the current definition belongs to the current toplevel service definition.

@@ -252,3 +253,3 @@

serviceCsn.name === whatsMyServiceRootName(fqName, false) &&
![ 'context', 'service' ].includes(art.kind)) {
art.kind !== 'context' && art.kind !== 'service') {

@@ -581,3 +582,3 @@ // Strip the toplevel serviceName from object.name

const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
if(!param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
if(param._type && !param._type.startsWith('Edm.') && !edmUtils.isStructuredType(csn.definitions[param._type])) {
warning('odata-spec-violation-param', paramLoc, { api: 'OData V2' });

@@ -599,4 +600,4 @@ }

let type = returns.type;
if(type){
if(!isBuiltinType(type) && !['entity', 'view', 'type'].includes(csn.definitions[type].kind)){
if (type){
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
const returnsLoc = [ ...actLoc, 'returns'];

@@ -603,0 +604,0 @@ warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });

@@ -140,7 +140,7 @@ // @ts-nocheck

if (typeof this[p] !== 'object')
tmpStr += ' ' + p + '="' + edmUtils.escapeString(v) + '"'
tmpStr += ' ' + p + '="' + edmUtils.escapeStringForAttributeValue(v) + '"'
});
edmUtils.forAll(this._xmlOnlyAttributes, (v,p) => {
if (typeof v !== 'object')
tmpStr += ' ' + p + '="' + edmUtils.escapeString(v) + '"'
tmpStr += ' ' + p + '="' + edmUtils.escapeStringForAttributeValue(v) + '"'
});

@@ -603,3 +603,3 @@ return tmpStr;

if(typecsn.type) { // this thing has a type
// check wether this is a scalar type (or array of scalar type) or a named type
// check whether this is a scalar type (or array of scalar type) or a named type
let scalarType = undefined;

@@ -884,7 +884,8 @@ if(typecsn.items && typecsn.items.type &&

Property.SAP_Annotation_Attribute_WhiteList = [
'@sap.hierarchy.node.for', //-> sap:hierarchy-node-for
'@sap.hierarchy.parent.node.for', // -> sap:hierarchy-parent-node-for
'@sap.hierarchy.level.for', // -> sap:hierarchy-level-for
'@sap.hierarchy.drill.state.for', // -> sap:hierarchy-drill-state-for
'@sap.hierarchy.node.descendant.count.for' // -> sap:hierarchy-node-descendant-count-for
'@sap.hierarchy.node.for', // -> sap:hierarchy-node-for
'@sap.hierarchy.parent.node.for', // -> sap:hierarchy-parent-node-for
'@sap.hierarchy.level.for', // -> sap:hierarchy-level-for
'@sap.hierarchy.drill.state.for', // -> sap:hierarchy-drill-state-for
'@sap.hierarchy.node.descendant.count.for', // -> sap:hierarchy-node-descendant-count-for
'@sap.parameter'
];

@@ -933,5 +934,3 @@

if(this.v4)
this[`Default${this.v4 ? 'Value' : ''}`] = ['cds.Boolean', 'cds.Binary', 'cds.LargeBinary', 'cds.Integer64', 'cds.Integer'].includes(csn.type)
? defVal
: edmUtils.escapeString(defVal);
this[`Default${this.v4 ? 'Value' : ''}`] = defVal;
}

@@ -1363,3 +1362,3 @@ }

let xml = indent + '<' + kind + this.toXMLattributes();
xml += (this._value !== undefined ? '>' + edmUtils.escapeString(this._value) + '</' + kind + '>' : '/>');
xml += (this._value !== undefined ? '>' + edmUtils.escapeStringForText(this._value) + '</' + kind + '>' : '/>');
return xml;

@@ -1366,0 +1365,0 @@ }

'use strict';
const { setProp } = require('../base/model');
const { isBuiltinType, isEdmPropertyRendered } = require('../model/csnUtils');
const { escapeString, hasControlCharacters, hasUnpairedUnicodeSurrogate } = require("../render/utils/stringEscapes");

@@ -25,3 +26,3 @@ /* eslint max-statements-per-line:off */

options.odataV2PartialConstr = options.toOdata.odataV2PartialConstr;
// global flag that indicates wether or not FKs shall be rendered in general
// global flag that indicates whether or not FKs shall be rendered in general
// V2/V4 flat: yes

@@ -127,3 +128,3 @@ // V4/struct: depending on odataForeignKeys

{
return ['entity'].includes(artifact.kind);
return artifact.kind === 'entity';
}

@@ -143,11 +144,11 @@

function isStructuredType(artifact) {
return ['type'].includes(artifact.kind) && isStructuredArtifact(artifact);
return artifact.kind === 'type' && isStructuredArtifact(artifact);
}
function isDerivedType(artifact) {
return ['type'].includes(artifact.kind) && !isStructuredArtifact(artifact);
return artifact.kind === 'type' && !isStructuredArtifact(artifact);
}
function isActionOrFunction(artifact) {
return ['action', 'function'].includes(artifact.kind);
return artifact.kind === 'action' || artifact.kind === 'function';
}

@@ -185,3 +186,3 @@

const parent = csn.definitions[parentName];
if(originAssocCsn) {
if(originAssocCsn && originAssocCsn.$abspath) {
const originParentName = originAssocCsn.$abspath[0];

@@ -260,3 +261,3 @@ if(parent.$mySchemaName && originAssocCsn._originalTarget !== parent && originAssocCsn._target !== parent) {

let rhs = expr[pos+1];
if(['='].includes(arg))
if(arg === '=')
{

@@ -564,3 +565,3 @@ assocCsn._constraints.termCount++;

'cds.Time': 'Edm.TimeOfDay',
// For a very long time it was unclear wether or not to map the Date types to a different Edm Type in V2,
// For a very long time it was unclear whether or not to map the Date types to a different Edm Type in V2,
// no one has ever asked about it in the meantime. The falsy if is just there to remember the eventual mapping.

@@ -673,17 +674,109 @@ 'cds.DateTime': 'Edm.DateTimeOffset', // (isV2 && false) ? 'Edm.DateTime'

function escapeString(str) {
// first regex: replace & if not followed by apos; or quot; or gt; or lt; or amp; or #
// Do not always escape > as it is a marker for {i18n>...} translated string values
let result = str;
if (typeof str === 'string') {
result = str.replace(/&(?!(?:apos|quot|[gl]t|amp);|#)/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/"/g, '&quot;')
.replace(/\r\n|\n/g, '&#xa;');
if (!result.startsWith('{i18n>') && !result.startsWith('{bi18n'))
result = result.replace(/>/g, '&gt;')
}
return result;
/**
* Escape the given string for attribute values. We follow the spec as
* described in §2.3 <https://www.w3.org/TR/xml/#NT-AttValue>:
*
* AttValue ::= '"' ([^<&"] | Reference)* '"'
* | "'" ([^<&'] | Reference)* "'"
*
* This function assumes that the attribute value is surrounded by double quotes ("),
* hence single quotes are not escaped.
*
* Note that even though certain special characters such as newline (LF) are allowed,
* they may be normalized to something different. For example LF is normalized
* to a space. Therefore we need to escape it.
* See §3.3.3 <https://www.w3.org/TR/xml/#AVNormalize>.
*
* Furthermore, control characters need to be escaped, see §2.2:
* <https://www.w3.org/TR/xml/#charsets>
* We also encode LF (#xA), etc. because of XML normalization in XML parsers.
*
* @param {string} str
* @returns {string}
*/
function escapeStringForAttributeValue(str) {
if (typeof str !== 'string')
return str;
if (!/[&<"]/.test(str) && !hasControlCharacters(str) && !hasUnpairedUnicodeSurrogate(str))
return str;
str = escapeString(str, {
'&': '&amp;',
'<': '&lt;',
'"': '&quot;',
control: encodeNonCharacters,
unpairedSurrogate: encodeNonCharacters,
});
// Notes
// -----
// According to the specification, "§2.11: End-of-Line Handling", we should normalize line endings:
// > ... by translating both the two-character sequence #xD #xA and any #xD that is not
// > followed by #xA to a single #xA character.
// However, line endings were already normalized in the CDL parser.
// If we were to normalize it again, it would be work done twice, possibly resulting in
// unwanted normalization (once is expected, twice is not).
// If we were to ever change this, use this RegEx:
// /\r\n?|\n/g => '&#xA;'
return str;
}
/**
* Escape the given string for element content. We follow the spec as
* described in §3.1 <https://www.w3.org/TR/xml/#NT-content>:
*
* content ::= CharData? ((element | Reference | CDSect | PI | Comment) CharData?)*
* CharData ::= [^<&]* - ([^<&]* ']]>' [^<&]*)
*
* i.e., we need to escape '<', '&' as well as `>` if it is preceded by `]]`.
* See also $2.4: “'>' MUST be replaced for compatibility reasons if it appears as ]]>”
*
* Furthermore, control characters need to be escaped, see §2.2:
* <https://www.w3.org/TR/xml/#charsets>
* We also encode LF (#xA), etc. because of XML normalization in XML parsers.
*
* In contrast to `escapeStringForAttributeValue()`, newlines do
* not need to be escaped.
*
* @param {string} str
* @returns {string}
*/
function escapeStringForText(str) {
if (typeof str !== 'string')
return str;
if (!/[&<>]/.test(str) && !hasControlCharacters(str) && !hasUnpairedUnicodeSurrogate(str))
return str;
str = escapeString(str, {
'&': '&amp;',
'<': '&lt;',
control: encodeNonCharacters,
unpairedSurrogate: encodeNonCharacters,
});
// Note: You can test this with <https://www.w3schools.com/xml/xml_validator.asp>:
// This sequence is allowed in attribute values but not element content.
str = str.replace(/]]>/g, ']]&gt;');
return str;
}
/**
* Control characters need to be escaped, see §2.2:
* <https://www.w3.org/TR/xml/#charsets>
*
* Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
* --> any Unicode character, excluding the surrogate blocks, FFFE, and FFFF.
*
* @param {number} codePoint
* @returns {string}
*/
function encodeNonCharacters(codePoint) {
const hex = codePoint.toString(16).toUpperCase();
return `&#x${hex};`;
}
// return the path prefix of a given name or if no prefix available 'root'

@@ -738,5 +831,6 @@ function getSchemaPrefix(name) {

isODataSimpleIdentifier,
escapeString,
escapeStringForAttributeValue,
escapeStringForText,
getSchemaPrefix,
getBaseName
}

@@ -92,2 +92,4 @@ // CSN frontend - transform CSN into XSN

const $location = Symbol.for('cds.$location');
let inExtensions = null;

@@ -665,2 +667,5 @@

// TODO: should we keep $parens ?
$generated: {
type: string,
},
$: { type: ignore, ignore: true }, // including $origin

@@ -936,4 +941,6 @@ _: { type: ignore, ignore: true },

return r;
if (!vocabInDefinitions)
if (!vocabInDefinitions) {
vocabInDefinitions = Object.create(null);
vocabInDefinitions[$location] = location();
}
vocabInDefinitions[name] = r; // deprecated: anno def in 'definitions'

@@ -963,2 +970,3 @@ return undefined;

const r = Object.create(null);
r[$location] = location();
const allNames = Object.keys( dict );

@@ -987,2 +995,3 @@ if (!allNames.length)

const r = Object.create(null);
r[$location] = location();
++virtualLine;

@@ -1185,2 +1194,3 @@ for (const def of array) {

if (Array.isArray( val )) {
/** @type {string|boolean} */
let seenEllipsis = false;

@@ -1444,2 +1454,3 @@ if (arrayLevelCount > 0) { // TODO: also inside structure (possible in CSN!)

const r = Object.create(null);
r[$location] = location();
++virtualLine;

@@ -1446,0 +1457,0 @@ for (const ex of array) {

@@ -60,3 +60,3 @@ // Transform XSN (augmented CSN) into CSN

masked: value,
params: insertOrderDict,
params,
// early expression / query properties -------------------------------------

@@ -189,3 +189,3 @@ op: o => ((o.val !== 'SELECT' && o.val !== '$query') ? o.val : undefined),

const typeProperties = [
'target', 'elements', 'enum', 'items',
'target', 'elements', 'enum', 'items', // TODO: notNull?
'type', 'length', 'precision', 'scale', 'srid', 'localized',

@@ -209,3 +209,6 @@ 'foreignKeys', 'on', // for explicit ON/keys with REDIRECTED

over: exprs => [ 'over', { xpr: [].concat( ...exprs ) } ],
orderBy: exprs => [
orderBy: exprs => [ // ORDER BY in generic functions
...exprs[0], ...operators.overOrderBy(exprs.slice(1)),
],
overOrderBy: exprs => [ // ORDER BY in OVER() clause
'order', 'by', ...exprs[0].concat( ...exprs.slice(1).map( e => [ ',', ...e ] ) ),

@@ -283,3 +286,3 @@ ],

/**
* Check wether the given object has non enumerable property.
* Check whether the given object has non enumerable property.
* Ensure that we don't take it from the prototype, only "directly" - we accidentally

@@ -628,6 +631,3 @@ * cloned elements with a cds.linked input otherwise.

if (node.target) {
csn.$origin = { type: 'cds.Composition' };
if (node.cardinality)
csn.$origin.cardinality = standard( node.cardinality );
csn.$origin.target = (val.elements) ? standard( val ) : artifactRef( val, true );
csn.$origin = { target: (val.elements) ? standard( val ) : artifactRef( val, true ) };
return undefined;

@@ -651,3 +651,3 @@ }

return standard( val ); // elements in target (parse-cdl)
if (!universalCsn || node.on)
if (!universalCsn || gensrcFlavor || node.on)
return artifactRef( val, true );

@@ -788,3 +788,3 @@ const tref = artifactRef( val, true );

// is often not the reason for an error or warning. So we gain little benefit for
// two more properties.
// two more properties. It is also an indication that the location is not exact.
const val = { file: loc.file, line: loc.line, col: loc.col };

@@ -817,2 +817,9 @@ Object.defineProperty( csn, '$location', {

function params( dict ) {
const keys = Object.keys( dict );
return (keys.length)
? insertOrderDict( dict )
: undefined;
}
function dictionary( dict, keys, prop ) {

@@ -874,3 +881,3 @@ const csn = Object.create( dictionaryPrototype );

// precondition already fulfilled: art.kind !== 'key'
addOrigin( c, art, art._origin );
addOrigin( c, art, art );
return c;

@@ -888,3 +895,4 @@ }

for (const prop in aspect) {
if (prop.charAt(0) === '@' && (!art[prop] || art[prop].$inferred)) {
if ((prop.charAt(0) === '@' || prop === 'doc') &&
(!art[prop] || art[prop].$inferred)) {
const anno = aspect[prop];

@@ -900,5 +908,19 @@ if (anno.val !== null)

function addOrigin( csn, xsn, origin ) {
if (!universalCsn || hasExplicitProp( xsn.type ))
function addOrigin( csn, xsn, node ) {
if (!universalCsn)
return;
if (hasExplicitProp( xsn.type, 'cast' )) {
const main = xsn._main || xsn;
let count = 0;
let source = xsn;
while (source && source._main === main) {
source = source.value && source.value._artifact;
++count;
}
if (count > 0 && source && source.kind !== 'builtin')
csn.$source = originRef( source, xsn );
else if (count > 1)
csn.$source = null;
return;
}
if (xsn._from) {

@@ -917,19 +939,18 @@ const source = xsn._from[0]._origin;

}
else if (!xsn._main || xsn.kind === 'select') {
let origin = getOrigin( node );
if (xsn.$inferred === 'composition-entity') {
csn.$origin = originRef( origin, xsn );
return;
}
else if (!isMember( xsn ) || xsn.kind === 'select') {
return;
}
// from here on: member:
const parent = getParent( xsn );
const parentOrigin = getOrigin( parent );
if (!xsn._origin || xsn._origin.kind === 'builtin') { // or $dollarVariable
if (parentOrigin && (!parent.enum || parent.$origin || !parent.type))
if (!origin) {
if (parentOrigin && !(parent.enum && !parent.$origin && parent.type))
csn.$origin = null;
return;
}
// Skip all proxies which do not make it into the CSN, as there are no
// individual annotations or redirection targets on it:
while (origin._parent && origin._parent.$expand === 'origin')
origin = origin._origin || origin.type._artifact;
// The while loop is not only for the else case below: when setting implicit
// prototypes, it is important that we do not have to follow the prototypes of
// other object; we would need to ensure a right order to avoid issues otherwise.
if (parentOrigin === getParent( origin )) {

@@ -943,6 +964,11 @@ // implicit prototype or shortened reference

if (origin.kind === 'mixin') {
// currently, target and on are always set - nothing to do here, just set type
csn.type = 'cds.Association';
set( 'type', csn, origin );
set( 'cardinality', csn, origin );
// currently, target and on are always set - nothing to do here
return;
}
// Skip all proxies which do not make it into the CSN, as there are no
// individual annotations or redirection targets on it:
while (origin._parent && origin._parent.$expand === 'origin')
origin = origin._origin || origin.type._artifact;
const ref = originRef( origin, xsn );

@@ -953,3 +979,3 @@ if (ref) {

}
// An element of a query with a query in FROM:
// An element of a query with a query in FROM: -----------------------------
const anon = definition( origin ); // use $origin: {...} if necessary

@@ -962,12 +988,23 @@ // as there are no implicit $origin prototypes on sub query elements (yet),

csn.$origin = Object.assign( $origin, anon );
return;
}
else if (Object.keys( anon )
// (we can use the properties in `csn`, because addOrigin() is called last)
.every( p => p in csn || p === '$origin' || p === '$location')) {
// nothing new in $origin: {...}
addOrigin( csn, xsn, origin._origin );
// Annotations and 'doc' must keep the distinction between direct or inherited,
// other properties can as well be set as direct element properties
const annos = {};
for (const prop of Object.keys( anon )) {
if (prop.charAt(0) === '@' || prop === 'doc')
annos[prop] = anon[prop];
else if (prop === '$source')
csn[prop] = anon[prop]; // overwrite from inner
else if (prop !== '$location' && prop !== '$origin' && !(prop in csn))
csn[prop] = anon[prop];
}
else {
csn.$origin = anon;
if (Object.keys( annos ).length) {
if (!csn.type && $origin)
annos.$origin = $origin;
csn.$origin = annos;
}
else if (!csn.type) {
addOrigin( csn, xsn, origin );
}
}

@@ -981,6 +1018,23 @@

function isMember( art ) {
// TODO: introduce art.kind = '$aspect' for anonymous aspect (is a member) ?
return !!(art._main || art._outer);
}
// function getDefinition( art ) {
// let main = art._main || art;
// while (main._outer) // anonymous aspect
// main = main._outer._main;
// return main;
// }
// XSN `_origin` is (currently?) not the same as _origin in Universal CSN...
// TODO: at least with expand, set it correctly (alias: keep, assoc: to entity, $builtin: no)
function getOrigin( art ) {
if (art._origin)
return art._origin;
if (hasExplicitProp( art.type ))
if (art.$noOrigin)
return undefined;
const { _origin } = art;
if (_origin)
return (_origin.kind === 'builtin') ? undefined : _origin; // not $dollarVariable
if (hasExplicitProp( art.type, 'cast' ))
return art.type._artifact;

@@ -994,4 +1048,4 @@ if (art.includes)

function hasExplicitProp( ref ) {
return ref && !ref.$inferred;
function hasExplicitProp( ref, alsoLikeExplicit ) {
return ref && (!ref.$inferred || ref.$inferred === alsoLikeExplicit );
}

@@ -1001,10 +1055,13 @@

const r = [];
// do not use name.element, as we allow `.`s in name
// do not use name.element, as we might allow `.`s in name
let parent = art;
while (parent._main && parent.kind !== 'select') {
if (parent._outer && parent.kind === 'aspect')
r.push( { target: true } );
while (isMember( parent ) && parent.kind !== 'select') {
const nkind = normalizedKind[parent.kind];
if (parent.name.id || !r.length)
const name = parent.name || parent._outer.name;
if (name.id || !r.length)
// Return parameter is in XSN - kind: 'param', name.id: ''
// eslint-disable-next-line no-nested-ternary, max-len
r.push( !nkind ? parent.name.id : parent.name.id ? { [nkind]: parent.name.id } : { return: true } );
r.push( !nkind ? name.id : name.id ? { [nkind]: name.id } : { returns: true } );
parent = parent._parent;

@@ -1014,9 +1071,6 @@ }

// well, an element of an query in FROM (TODO: try with sub elem), but not the leading query
return null; // probably use $origin: {...}
return false; // do not write, probably use $origin: {...}
// for sub query in FROM in sub query in FROM, we could condense the info
// Now the ref, with ["absolute", "action"] instead of ["absolute", {action:"action"}]
if (r.length === 1 && normalizedKind[art.kind] === 'action')
return [ art.name.absolute, art.name.id ];
r.push( art.name.absolute );
r.push( parent.name.absolute );
r.reverse();

@@ -1040,2 +1094,5 @@ return r;

}
else if (k === 'aspect' && (node._outer || node.$inferred)) {
return; // do not show kind for anonymous aspect
}
else if (![

@@ -1055,3 +1112,3 @@ 'element', 'key', 'param', 'enum', 'select', '$join',

return artifactRef( node, !node.$extra );
if (node.$inferred)
if (node.$inferred && node.$inferred !== 'cast')
return undefined;

@@ -1061,17 +1118,11 @@ if (xsn._origin) {

csn.$origin = definition( xsn._origin );
return undefined;
}
}
else if ( xsn.targetAspect && xsn.target ) {
// type moved to $origin: { type: … }
return undefined;
}
return artifactRef( node, !node.$extra );
}
function cardinality( node, csn, xsn ) {
function cardinality( node ) {
if (!universalCsn)
return standard( node );
// cardinality might be moved to $origin: { cardinality: … }
if (node.$inferred || xsn.targetAspect && !xsn.targetAspect.$inferred && xsn.target)
if (node.$inferred)
return undefined;

@@ -1184,4 +1235,8 @@ return standard( node );

return true; // `@aBool` short for `@aBool: true`
if (universalCsn && node.$inferred === 'prop') // via propagator.js
return undefined;
if (universalCsn && node.$inferred) {
if (node.$inferred === 'prop' || node.$inferred === '$generated') // via propagator.js
return undefined;
else if (node.$inferred === 'NULL')
return null;
}
if (node.$inferred && gensrcFlavor)

@@ -1355,3 +1410,3 @@ return undefined;

node.from && node.from.path && !projectionAsQuery) {
csn.projection = standard( node );
csn.projection = addLocation( node.location, standard( node ) );
return undefined;

@@ -1378,2 +1433,5 @@ }

}
// the $location is better put inside the SELECT value, not as sibling (but
// we keep it as sibling also for compatibility):
addLocation( node.location, select.SELECT );
return addLocation( node.location, select );

@@ -1427,7 +1485,9 @@ }

}
else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
return extra( addExplicitAs( artifactRef( node, false ), node.name ), node );
}
return extra( addExplicitAs( artifactRef( node, false ), node.name, (id) => {
const name = node._artifact.name.absolute;
const ref = artifactRef( node, false );
return extra( addExplicitAs( ref, node.name, (id) => {
let name = ref.ref ? ref.ref[ref.ref.length - 1] : ref;
name = name && name.id || name;
if (!name)
return false;
const dot = name.lastIndexOf('.');

@@ -1481,3 +1541,3 @@ return name.substring( dot + 1 ) !== id;

function orderBy( node ) {
const expr = expression( node, 'ignoreExtra' );
const expr = expression( node );
if (node.sort)

@@ -1484,0 +1544,0 @@ expr.sort = node.sort.val;

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

function isFencedComment(lines) {
const index = lines.findIndex((line, index) => {
const exclude = (index === 0 || index === lines.length - 1);
const index = lines.findIndex((line, i) => {
const exclude = (i === 0 || i === lines.length - 1);
return !exclude && !(/^\s*[*]/.test(line));

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

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

const $location = Symbol.for('cds.$location');

@@ -39,2 +40,15 @@ // Push message `msg` with location `loc` to array of errors:

this.buildParseTrees = false;
// Common properties.
// We set them here so that they are available in the prototype.
// This improved performance by 25% for certain scenario tests.
// Probably because there was no need to look up the prototype chain anymore.
this.$adaptExpectedToken = null;
this.$adaptExpectedExcludes = [ ];
this.$nextTokensToken = null;
this.$nextTokensContext = null;
this.prepareGenericKeywords();
this.options = {};
return this;

@@ -60,2 +74,4 @@ }

combinedLocation,
createDict,
setDictEndLocation,
surroundByParens,

@@ -76,2 +92,3 @@ unaryOpForParens,

addItem,
artifactForElementAnnotateOrExtend,
assignProps,

@@ -86,2 +103,3 @@ createPrefixOp,

csnParseOnly,
disallowElementExtension,
noAssignmentInSameLine,

@@ -221,2 +239,18 @@ noSemicolonHere,

/**
* For element extensions (`extend E:elem` syntax).
* If `elemName.path` is set, remove the last extension from `$outer` and
* emit an error that the extension is invalid.
*
* @param {object} elemName
* @param {object} outer
* @param {string} extensionVariant
*/
function disallowElementExtension(elemName, outer, extensionVariant) {
if (elemName.path) {
this.message( 'syntax-invalid-extend', this.tokenLocation(this.getCurrentToken()), { 'kind': extensionVariant } );
outer.extensions.length = outer.extensions.length - 1; // remove last, i.e. new extension
}
}
function noAssignmentInSameLine() {

@@ -382,2 +416,16 @@ const t = this.getCurrentToken();

function createDict( location = null ) {
const dict = Object.create(null);
dict[$location] = location || this.startLocation( this._input.LT(-1) );
return dict;
}
function setDictEndLocation( dict ) {
const stop = this._input.LT(-1);
Object.assign( dict[$location], {
endLine: stop.line,
endCol: stop.stop - stop.start + stop.column + 2,
} );
}
function surroundByParens( expr, open, close, asQuery = false ) {

@@ -674,3 +722,3 @@ if (!expr)

else if (name && name.id == null) {
name.id = pathName(name.path ); // A.B.C -> 'A.B.C'
name.id = pathName( name.path ); // A.B.C -> 'A.B.C'
}

@@ -680,4 +728,4 @@ const art = this.assignProps( { name }, annos, props, location );

art.kind = kind;
if (!parent[env])
parent[env] = Object.create(null);
if (!parent[env]) // TODO: dump with --test-mode, env !== 'artifacts'
parent[env] = env === 'args' ? Object.create(null) : this.createDict( { ...location } );
if (!art.name || art.name.id == null) {

@@ -739,2 +787,27 @@ // no id was parsed, but with error recovery: no further error

/**
* For `annotate/extend E:elem.sub`, create the `elements` structure
* that can be used by the core compiler to annotate/extend elements.
*
* @param {string} kind Either `annotate` or `extend`
* @param {object} artifact Main artifact that shall have `elements`.
* @param {XSN.Path} elementPath Path as returned by `simplePath` token.
* @param {object[]} annos Existing annotations that shall be added to the _last_ path step.
* @param {XSN.Location} artifactLocation Start location of the `annotate` statement.
* @returns {object} Deepest element
*/
function artifactForElementAnnotateOrExtend(kind, artifact, elementPath, annos, artifactLocation ) {
if (!Array.isArray(elementPath) || elementPath.broken || elementPath.length < 1)
return artifact;
for (const seg of elementPath.slice(0, -1)) {
artifact = this.addDef( artifact, 'elements', kind,
{ path: [seg], location: seg.location }, null, {}, artifactLocation );
}
const last = elementPath[elementPath.length - 1];
artifact = this.addDef( artifact, 'elements', kind,
{ path: [ last ], location: last.location }, annos, {}, artifactLocation );
return artifact;
}
/** Assign all non-empty (undefined, null, {}, []) properties in argument

@@ -741,0 +814,0 @@ * `props` and argument `annos` as property `$annotations` to `target`

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

const min = lines.reduce((min, line, index) => {
const minIndent = lines.reduce((min, line, index) => {
// Note: Last line is the line containing ```. There, we always count the indentation,

@@ -91,3 +91,3 @@ // even if blank. For all other lines, blank lines are ignored.

// In that case, slice() returns an empty string.
lines[i] = lines[i].slice(min);
lines[i] = lines[i].slice(minIndent);
}

@@ -99,3 +99,3 @@

return [lines.join('\n'), min];
return [lines.join('\n'), minIndent];
}

@@ -519,4 +519,4 @@

const newLineRegEx = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
return code.replace(newLineRegEx, '\u{23CE}');
const allPossibleNewLineCharacters = /[\u{000A}\u{000B}\u{000C}\u{000D}\u{0085}\u{2028}\u{2029}]/ug;
return code.replace(allPossibleNewLineCharacters, '\u{23CE}');
}

@@ -523,0 +523,0 @@ }

@@ -8,3 +8,3 @@ // Official cds-compiler API.

// Author's note: All "options" interfaces should actually be types. However, due to
// https://github.com/TypeStrong/typedoc/issues/1519 we can't use
// <https://github.com/TypeStrong/typedoc/issues/1519> we can't use
// intersection types at the moment.

@@ -63,5 +63,35 @@

dictionaryPrototype?: any
/**
* CSN Flavor: The compiler supports different CSN flavors. Backends may support
* different flavors. This option is mainly used in `compile()`.
* Flavors are:
* - client : (default) Standard CSN consumable by clients and backends.
* - gensrc : CSN specifically for use as a source, e.g. for combination with
* additional "extend" or "annotate" statements, but not suitable
* for consumption by clients or backends.
* - universal : In development (BETA)
*/
csnFlavor?: string | 'client' | 'gensrc' | 'universal'
}
/**
* Options relevant for compilation and parsing of CDL and CSN files.
*/
export interface CompileOptions extends Options {
/**
* If the given filename does not have a known file extension,
* use this frontend as the fallback parser.
*/
fallbackParser?: string | 'cdl' | 'csn'
/**
* Option for {@link compileSources}. If set, all objects inside the
* provided sources dictionary are interpreted as XSN structures instead
* of CSN, i.e. a compiler-internal representation.
*
* @since v2.12.1
*/
$xsnObjects?: boolean
}
/**
* Options used by OData backends. Includes options for the OData

@@ -241,2 +271,86 @@ * transformer as well as for rendering EDM and EDMX.

/**
* Options used by to.hdbcds()
*/
export interface HdbcdsOptions extends Options {
/**
* The SQL naming mode decides how names are represented.
* Among others, this includes whether identifiers are quoted or not.
*
* - `plain`:
* In this naming mode, dots are replaced by underscores.
* Identifiers are always uppercased. If "smart-quoting" is used, they are also quoted
if they are also HDBCDS keywords.
* - `quoted`:
* In this mode, all identifiers are quoted. Dots are not replaced in table
* and view names but are still replaced by underscores in element names.
* Identifier casing is kept as specified in the source.
* - `hdbcds`:
* This mode uses names that are compatible to SAP HANA CDS.
* In this mode, all identifiers are quoted. Dots are neither replaced in table
* nor element names: Structured elements persist, contexts are nested.
* Managed associations/compositions are left as-is. No association-to-join-translation
* is done.
*
* @default 'plain'
*/
sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
/**
* For to.hdbcds(), the SQL dialect is always set to 'hana'.
*/
sqlDialect?: 'hana'
}
/**
* Options used by to.hdi() and to.hdi.migration()
*/
export interface HdiOptions extends Options {
/**
* The SQL naming mode decides how names are represented.
* Among others, this includes whether identifiers are quoted or not.
*
* - `plain`:
* In this naming mode, dots are replaced by underscores.
* Names are neither upper-cased nor quoted, unless "smart-quoting" is used.
* - `quoted`:
* In this mode, all identifiers are quoted. Dots are not replaced in table
* and view names but are still replaced by underscores in element names.
* - `hdbcds`:
* This mode uses names that are compatible to SAP HANA CDS.
* In this mode, all identifiers are quoted. Dots are neither replaced in table
* nor element names. Namespace identifiers are separated from the remaining
* identifier by `::`, i.e. the dot is replaced. For example `Ns.Books`
* becomes `"Ns::Books"`.
*
* @default 'plain'
*/
sqlMapping?: string | 'plain' | 'quoted' | 'hdbcds'
/**
* For to.hdi(), the SQL dialect is always set to 'hana'.
*/
sqlDialect?: 'hana'
/**
* Only for to.hdi.migration().
* SQL change mode to use (for changed columns).
*
* @default 'alter'
*/
sqlChangeMode?: string | 'alter' | 'drop'
/**
* Only for `to.hdi.migration`. If `true`, `to.hdi.migration` allows that the two
* passed CSNs are of different CSN versions. Use at own risk.
*
* @default false
*/
allowCsnDowngrade?: boolean
}
/**
* Options used by `to.cdl()` backend.
*
* @note `to.cdl()` currently has no specific options.
* @see to.cdl()
*/
export interface CdlOptions extends Options {}
/**
* The compiler's package version.

@@ -251,3 +365,3 @@ * For more details on versioning and SemVer, see `doc/Versioning.md`

* relative to the working directory `process.cwd()`. With argument `dir`, the
* file names are relative to `process.cwd()+dir`.
* file names are relative to `process.cwd()+dir` (or just `dir` if it is absolute).
*

@@ -270,13 +384,30 @@ * This function returns a Promise and can be used with `await`. For an example

* @param options Compiler options. If you do not set `messages`, they will be printed to console.
* @param fileCache A dictionary of absolute file names to the file content with values:
* - false: the file does not exist
* - true: file exists (fstat), no further knowledge yet - i.e. value will change!
* - 'string' or Buffer: the file content
* - { realname: fs.realpath(filename) }: if filename is not canonicalized
* @param fileCache
*/
export function compile(filenames: string[], dir?: string, options?: Options, fileCache?: Record<string, any>): Promise<any>;
export function compileSync(filenames: string[], dir?: string, options?: Options, fileCache?: Record<string, any>): any;
export function compileSources(sourcesDict: any, options?: Options): any;
export function compile(filenames: string[], dir?: string, options?: CompileOptions, fileCache?: FileCache): Promise<CSN>;
/**
* Synchronous version of {@link compile}.
* Usage is discouraged. Use the asynchronous version if possible.
*
* @see compile
*/
export function compileSync(filenames: string[], dir?: string, options?: CompileOptions, fileCache?: FileCache): CSN;
/**
* Synchronously compiles the given sources.
*
* Argument `sourcesDict` is a dictionary mapping filenames to either source texts (string)
* or JavaScript objects, which are usually CSNs, or XSNs (compiler internal AST-like augmented CSNs).
* The latter requires option `$xsnObjects` to be set to `true`.
* It could also be a simple string, which is then considered to be the source
* text of a file named `<stdin>.cds`.
*
* See function {@link compile} for the meaning of the argument `options`. If there
* are parse or other compilation errors, throws an exception {@link CompilationError}
* containing a list of individual errors.
*/
export function compileSources(sourcesDict: Record<string, string|object> | string, options?: CompileOptions): CSN;
/**
* In version 2 of cds-compiler, this is an identity function and

@@ -288,7 +419,13 @@ * is only kept for backwards compatibility.

*/
export function compactModel(csn: CSN): any;
export function compactModel(csn: CSN): CSN;
/**
* Exception thrown by the compiler if errors are encountered.
*
* Note that compiler functions (e.g. renderers) may not always throw if errors are encountered.
* You always need to check the option's `messages` array for a {@link CompileMessage} of severity 'Error'.
*/
export class CompilationError extends Error {
constructor(messages: any, model: any, text: any, ...args);
messages: any[];
constructor(messages: CompileMessage[], model?: any, text?: string, ...args: any[]);
messages: CompileMessage[];
toString(): string;

@@ -308,2 +445,11 @@ /**

/**
* Exception thrown by the compiler if {@link compile} and its variants are invoked incorrectly.
* For example, this error is thrown if the same file is passed to {@link compile} twice.
*/
export class InvocationError extends Error {
constructor(errs: any, ...args: any[]);
errors: any[]
}
/**
* Sort the given messages according to their location. Messages are sorted

@@ -406,3 +552,3 @@ * in ascending order according to their:

* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
* unset.

@@ -439,3 +585,3 @@ */

* coloring will be used. If 'auto', we will decide based on certain factors such
* as whether the shell is a TTY and whether the environment variable 'NO_COLOR' is
* as whether the shell is a TTY and whether the environment variable `NO_COLOR` is
* unset.

@@ -454,3 +600,3 @@

* @throws May throw an ENOENT error if the message explanation cannot be found.
* @see {@link hasMessageExplanation}
* @see hasMessageExplanation
*/

@@ -460,19 +606,30 @@ export function explainMessage(messageId: string): string;

* Returns `true` if the given messageId has an explanatory text.
* Contrary to {@link explainMessage}, this function does not make
* a file lookup.
* Contrary to {@link explainMessage}, this function does not do
* any file lookup.
*/
export function hasMessageExplanation(messageId: string): boolean;
export class InvocationError extends Error {
constructor(errs: any, ...args);
errors: any[]
}
/**
* Returns true if at least one of the given messages is of severity "Error"
* Returns true if at least one of the given messages is of severity "Error".
*/
export function hasErrors(messages: CompileMessage[]): boolean;
export function preparedCsnToEdm(csn: CSN, service: string, options: any): any;
export function preparedCsnToEdmx(csn: CSN, service: string, options: any): any;
/**
* Same as {@link to.edm} but expects a {@link for.odata} transformed CSN.
*
* Note that parameter `service` is the same as `ODataOptions.service`.
* The latter is not used. OData specific options have an internal format.
*
* @deprecated Use {@link to.edm} instead. If it detects a pre-transformed CSN, it won't run for.odata().
*/
export function preparedCsnToEdm(csn: CSN, service: string, options: ODataOptions): object;
/**
* Same as {@link to.edm} but expects a {@link for.odata} transformed CSN.
*
* Note that parameter `service` is the same as `ODataOptions.service`.
* The latter is not used. OData specific options have an outdated format.
*
* @deprecated Use {@link to.edmx} instead. If it detects a pre-transformed CSN, it won't run for.odata().
*/
export function preparedCsnToEdmx(csn: CSN, service: string, options: ODataOptions): string;

@@ -487,6 +644,6 @@ export namespace parse {

*/
function cdl(cdl: string, filename: string, options?: Options): any;
function cdl(cdl: string, filename: string, options?: Options): CSN;
/**
* Parse the given CQL and return its corresponding CSN representation.
* Parse the given CQL and return its corresponding CQN representation.
*

@@ -496,7 +653,8 @@ * @param cdl CDL source as string.

* @param options Compiler options. Note that if `options.messages` is not set, messages will be printed to stderr.
* @returns Returns the CSN representation of the expression, i.e. CDS Query Notation.
*/
function cql(cdl: string, filename?: string, options?: Options): any;
function cql(cdl: string, filename?: string, options?: Options): CQN;
/**
* Parse the given CDL expression and return its corresponding CSN representation.
* Parse the given CDL expression and return its corresponding CXN representation.
*

@@ -506,4 +664,5 @@ * @param cdl CDL source as string.

* @param options Compiler options. Note that if `options.messages` is not set, messages will be printed to stderr.
* @returns Returns the CSN representation of the expression, i.e. CDS Expression Notation.
*/
function expr(cdl: string, filename?: string, options?: Options): any;
function expr(cdl: string, filename?: string, options?: Options): CXN;
}

@@ -514,10 +673,12 @@

*/
export function parseToCqn(cdl: string, filename?: string, options?: Options): any;
export function parseToCqn(cql: string, filename?: string, options?: Options): CQN;
/**
* @deprecated Use {@link parse.expr} instead.
*/
export function parseToExpr(cdl: string, filename?: string, options?: Options): any;
export function parseToExpr(expr: string, filename?: string, options?: Options): CXN;
/**
* @note Actual name is "for" which can't be used directly in the documentation as it is a reserved name.
* @note Actual name is "for" which can't be used directly in the documentation
* as it is a reserved name in TypeScript.
*
* @see for

@@ -531,3 +692,3 @@ */

*/
function odata(csn: CSN, options: ODataOptions): any;
function odata(csn: CSN, options?: ODataOptions): CSN;
}

@@ -538,41 +699,282 @@

export namespace to {
function cdl(csn: CSN, options: Options): object;
function sql(csn: CSN, options: SqlOptions): any;
/**
* Renders the given CSN into a CDL source representation.
*
* @returns Object containing the rendered model.
*/
function cdl(csn: CSN, options?: CdlOptions): CdlResult;
namespace cdl {
/**
* Immutable list of reserved keywords in CDL.
* These keywords are used for automatic quoting in {@link to.cdl}.
*/
const keywords: string[];
/**
* Immutable list of CDL functions, used for automatic quoting in {@link to.cdl}.
* Only relevant for element references of path length 1.
*/
const functions: string[];
}
function edm(csn: CSN, options: ODataOptions): any;
namespace edm {
function all(csn: CSN, options: ODataOptions): any;
/**
* Renders the given CSN into SQL statements such as `CREATE TABLE`, `CREATE VIEW`, etc.
*
* @returns Array of SQL statements as strings, tables first, views second and optionally table constraints last.
*/
function sql(csn: CSN, options?: SqlOptions): string[];
namespace sql {
namespace sqlite {
/**
* Immutable list of reserved keywords for SQLite. The list is used by {@link to.sql}.
* Taken from <http://www.sqlite.org/draft/lang_keywords.html>.
*/
const keywords: string[];
}
function smartId(name: string, dialect: string) : string;
function smartFunctionId(name: string, dialect: string) : string;
function delimitedId(name: string, dialect: string) : string;
}
function edmx(csn: CSN, options: ODataOptions): any;
namespace edmx {
function all(csn: CSN, options: ODataOptions): any;
}
/**
* Renders the given CSN into EDM (JSON format). Requires `options.service` to be set.
*
* @returns Rendered EDM as JSON structure.
*/
function edm(csn: CSN, options?: ODataOptions): object;
namespace edm {
/**
* Renders the given CSN into EDM (JSON format) for each service.
* If `options.serviceNames` is not set, all services will be rendered.
*
* @returns A map of `{ '<serviceName>': object }` entries.
*/
function all(csn: CSN, options: ODataOptions): Record<string, object>;
}
function hdbcds(csn: CSN, options: Options): any;
function hdi(csn: CSN, options: Options): any;
namespace hdi {
function migration(csn: CSN, options: Options, beforeImage: any): any;
}
/**
* Renders the given CSN into EDMX. Requires `options.service` to be set.
*
* @returns Rendered EDMX as string.
*/
function edmx(csn: CSN, options: ODataOptions): string;
namespace edmx {
/**
* Renders the given CSN into EDMX for each service.
* If `options.serviceNames` is not set, all services will be rendered.
*
* @returns A map of `{ '<serviceName>': '<xml>' }` entries.
*/
function all(csn: CSN, options?: ODataOptions): Record<string, string>;
}
/**
* Renders the given CSN into HDBCDS artifacts.
*
* @returns A map of `{ '<artifactName>.hdbcds|hdbconstraint>': '<content>' }` entries.
*/
function hdbcds(csn: CSN, options?: HdbcdsOptions): Record<string, string>;
namespace hdbcds {
/**
* Immutable list of SAP HANA CDS keywords, used for smart quoting in
* {@link to.hdbcds} with option `sqlMapping: 'plain'`.
*/
const keywords: string[];
}
/**
* Renders the given CSN into HDI statements such as `COLUMN TABLE`, `VIEW`, etc.
*
* @returns A map of `{ '<artifactName>.hdbtable|hdbview|hdbconstraint>': '<content>' }` entries.
*/
function hdi(csn: CSN, options?: HdiOptions): Record<string, string>;
namespace hdi {
/**
* Immutable list of SAP HANA keywords, used for smart quoting in
* {@link to.hdi} with option `sqlMapping: 'plain'`.
* Taken from <https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html>.
*/
const keywords: string[];
/**
* Return all changes in artifacts between two given models.
* Note: Only supports changes in entities (not views etc.) compiled/rendered as HANA-CSN/SQL.
*
* @param csn A clean input CSN representing the desired "after-image"
* @param options Options
* @param beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image
* is known, i.e. for the very first migration step
* @returns See {@link HdiMigrationResult} for details.
*/
function migration(csn: CSN, options: HdiOptions, beforeImage: CSN): HdiMigrationResult;
}
}
export function getArtifactCdsPersistenceName(artifactName: string, namingConvention: any, csn: CSN): any;
export function getElementCdsPersistenceName(elemName: string, namingConvention: any): any;
/**
* Result of the `to.cdl()` renderer.
*/
export type CdlResult = {
/**
* Rendered model, excluding not-applied extensions.
*/
model?: string
/**
* Rendered extend/annotate statements of `csn.extensions`
*/
unappliedExtensions?: string
/**
* Rendered csn.namespace property + using directives.
*/
[namespace: string]: string
}
/**
* Result type of {@link to.hdi.migration}.
*/
export type HdiMigrationResult = {
/**
* The desired after-image in HANA-CSN format
*/
afterImage: CSN
/**
* An array of objects with all artifacts in the after-image. Each object specifies
* the artifact filename, the suffix, and the corresponding SQL statement to create
* the artifact.
*/
definitions: object[],
/**
* An array of objects with the deleted artifacts. Each object specifies the artifact
* filename and the suffix.
*/
deletions: object[],
/**
* An array of objects with the changed (migrated) artifacts. Each object specifies the
* artifact filename, the suffix, and the changeset (an array of changes, each specifying
* whether it incurs potential data loss, and its respective SQL statement(s), with
* multiple statements concatenated as a multi-line string in case the change e.g.
* consists of a column drop and add).
*/
migrations: Array<{
name: string
suffix: string
changeset: object[]
}>,
}
/**
* Return the resulting database name for (absolute) 'artifactName', depending on the current naming
* mode.
*
* - For the 'hdbcds' naming mode, this means converting `.` to `::` on
* the border between namespace and top-level artifact and correctly replacing some `.` with `_`.
* - For the 'plain' naming mode, it means converting all `.` to `_` and upper-casing.
* - For the 'quoted' naming mode, this means correctly replacing some `.` with `_`.
*
* @param artifactName The fully qualified name of the artifact
* @param sqlMapping The naming mode to use. See {@link SqlOptions.sqlMapping} for more details.
* @param csn
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
* @since v2.1.0
*/
export function getArtifactCdsPersistenceName(artifactName: string, sqlMapping: string, csn: CSN): string;
/**
* This is an old function signature. If it is used - with a namespace as the third argument - the result might be wrong,
* since the `.` -> `_` conversion for 'quoted'/'hdbcds' is missing.
*
* @deprecated Use the other overload with CSN instead.
*/
export function getArtifactCdsPersistenceName(artifactName: string, sqlMapping: string, namespace: string): string;
/**
* Return the resulting database element name for `elemName`, depending on the current
* naming mode.
* - For the 'hdbcds' naming mode, this is just 'elemName'.
* - For the 'plain' naming mode, it means converting all `.` to `_` and upper-casing.
* - For the 'quoted' naming mode, it means converting all `.` to `_`.
* No other naming modes are accepted!
*
* @param elemName The name of the element. For structured elements, concat by dot, e.g. `sub.elem`.
* @param sqlMapping The naming mode to use. See {@link SqlOptions.sqlMapping} for more details.
* @returns The resulting database element name for 'elemName', depending on the current naming mode.
*/
export function getElementCdsPersistenceName(elemName: string, sqlMapping: string): string;
/**
* Traverse the CSN node `csn`.
*
* If `csn` is an array, call it recursively on each array item.
* If `csn` is an(other) object, call a function on each property:
* - The property name is a used as key in argument `userFunctions` and the
* constant `defaultFunctions` above to get the function which is called on
* the property value, see `defaultFunctions` for details.
* - If no function is found with the property name, try to find one with the
* first char, which is useful for annotations.
* - If still not found, call `traverseCsn` recursively.
*
* The functions in `userFunctions` are usually transformer functions, which
* change the input CSN destructively.
*/
export function traverseCsn(userFunctions: Record<string, Function>, csn: object|any[]);
/**
* @private
*/
export namespace $lsp {
function compile(filenames: string[], dir?: string, options?: Options, fileCache?: Record<string, any>): Promise<any>;
function parse(source: string, filename?: string, options?: Options): any;
/**
* Compiler internal notation.
* @private
*/
type XSN = any;
/**
* Compile the given files with the given options. The return object uses an internal object
* format that must not be used outside of the cds-lsp.
* Respects the value of `options.fallbackParser`.
*
* @param filenames Array of files that should be compiled.
* @param dir Working directory. Relative paths in `filenames` will be resolved relatively to this directory.
* @param options Compiler options. If you do not set `messages`, they will be printed to console.
* @param fileCache
* @private
*/
function compile(filenames: string[], dir?: string, options?: CompileOptions, fileCache?: FileCache): Promise<XSN>;
/**
* Parse the given source with the correct parser based on the file name's
* extension. For example uses CDL parser for `.cds` files.
* Respects the value of `options.fallbackParser`.
*
* @param {string} source Source code of the file.
* @param {string} filename Filename including its extension, e.g. "file.cds"
* @param {object} options Compile options
* @param {object} messageFunctions If not provided, parse errors will not lead to an exception
* @private
*/
function parse(source: string, filename: string, options?: CompileOptions, messageFunctions?: object): XSN;
/**
* Get the name of the given artifact. This function is internal and does not work with CSN.
* It should be used to retrieve an artifact's name instead of relying on `artifact.name`
* as that object may be modified in the future.
*
* @private
*/
function getArtifactName(artifact: object): object;
}
/**
* CSN object. Not yet specified in this TypeScript declaration file.
* CDS Schema Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/csn> for more.
*/
export type CSN = any;
/**
* CDS Query Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/cqn> for more.
*/
export type CQN = any;
/**
* CDS Expression Notation. Not yet specified in this TypeScript declaration file.
* See <https://pages.github.tools.sap/cap/docs/cds/cxn> for more.
*/
export type CXN = any;
export class CompileMessage {
constructor(location: Location, msg: string, severity?: MessageSeverity, id?: string | null, home?: string | null, moduleName?: string | null);
/**

@@ -591,3 +993,2 @@ * Optional ID of the message. Can be used to reclassify messages.

location: Location
/**

@@ -608,3 +1009,2 @@ * Location information like file and line/column of the message.

message: string
/**

@@ -625,3 +1025,2 @@ * A string describing the path to the artifact, e.g. `entity:"E"/element:"x"`.

error?: Error
/**

@@ -637,3 +1036,3 @@ * Returns a human readable string of the compiler message. Uses {@link messageString} to render

*/
export type MessageSeverity = 'Error' | 'Warning' | 'Info' | 'Debug';
export type MessageSeverity = string | 'Error' | 'Warning' | 'Info' | 'Debug';

@@ -655,2 +1054,12 @@ /**

/**
* File cache for compile() functions.
* This cache is a dictionary of absolute file names to the file content with values:
* - `false`: the file does not exist
* - `true`: file exists (fstat), no further knowledge yet - i.e. value will change!
* - `string` or `Buffer`: the file content
* - `{ realname: fs.realpath(filename) }`: if filename is not canonicalized
*/
export type FileCache = Record<string, boolean | string | Buffer | { realname: string }>;
}

@@ -21,2 +21,4 @@ // Main entry point for the CDS Compiler

const { createMessageFunctions, sortMessages, sortMessagesSeverityAware, deduplicateMessages } = require('./base/messages');
const { smartId, smartFuncId, delimitedId } = require('./sql-identifier');
const keywords = require( './base/keywords' );

@@ -26,3 +28,4 @@ const parseLanguage = require('./language/antlrParser');

const { fns } = require('./compiler/shared');
const { define } = require('./compiler/definer');
const define = require('./compiler/define');
const finalizeParseCdl = require('./compiler/finalize-parse-cdl');

@@ -46,3 +49,3 @@ // The compiler version (taken from package.json)

function parseCdl( cdl, filename, options = {} ) {
function parseCdl( cdlSource, filename, options = {} ) {
options = Object.assign( {}, options, { parseCdl: true } );

@@ -55,3 +58,3 @@ const sources = Object.create(null);

const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
const xsn = parseLanguage( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
messageFunctions );

@@ -61,2 +64,3 @@ sources[filename] = xsn;

define( model );
finalizeParseCdl( model );
messageFunctions.throwWithError();

@@ -66,5 +70,5 @@ return compactModel( model );

function parseCql( cdl, filename = '<query>.cds', options = {} ) {
function parseCql( cdlSource, filename = '<query>.cds', options = {} ) {
const messageFunctions = createMessageFunctions( options, 'parse' );
const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
const xsn = parseLanguage( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
messageFunctions, 'query' );

@@ -75,5 +79,5 @@ messageFunctions.throwWithError();

function parseExpr( cdl, filename = '<expr>.cds', options = {} ) {
function parseExpr( cdlSource, filename = '<expr>.cds', options = {} ) {
const messageFunctions = createMessageFunctions( options, 'parse' );
const xsn = parseLanguage( cdl, filename, Object.assign( { parseOnly: true }, options ),
const xsn = parseLanguage( cdlSource, filename, Object.assign( { parseOnly: true }, options ),
messageFunctions, 'expr' );

@@ -117,6 +121,28 @@ messageFunctions.throwWithError();

parseToCqn: parseCql,
/**
* @deprecated Use parse.expr instead
*/
parseToExpr: parseExpr, // deprecated name
// SNAPI
for: { odata },
to: { cdl, sql, hdi, hdbcds, edm, edmx },
to: {
cdl: Object.assign(cdl, {
keywords: Object.freeze([ ...keywords.cdl ]),
functions: Object.freeze([ ...keywords.cdl_functions ]),
}),
sql: Object.assign(sql, {
sqlite: { keywords: Object.freeze([ ...keywords.sqlite ] )},
smartId,
smartFunctionId: smartFuncId,
delimitedId,
}),
hdi: Object.assign(hdi, {
keywords: Object.freeze([ ...keywords.hana ]),
}),
hdbcds: Object.assign(hdbcds, {
keywords: Object.freeze([ ...keywords.hdbcds ]),
}),
edm,
edmx
},
// Convenience for hdbtabledata calculation in @sap/cds

@@ -132,3 +158,3 @@ getArtifactCdsPersistenceName: getArtifactDatabaseNameOf,

// new releases (even having the same major version):
$lsp: { parse: parseX, compile: compileX },
$lsp: { parse: parseX, compile: compileX, getArtifactName: a => a.name },
};

@@ -31,2 +31,3 @@ // Miscellaneous CSN functions we put into our compiler API

* Traverse the CSN node `csn`.
*
* If `csn` is an array, call it recursively on each array item.

@@ -37,3 +38,4 @@ * If `csn` is an(other) object, call a function on each property:

* the property value, see `defaultFunctions` for details.
* - If no function is found with the property name, try to find one with the first char.
* - If no function is found with the property name, try to find one with the
* first char, which is useful for annotations.
* - If still not found, call `traverseCsn` recursively.

@@ -40,0 +42,0 @@ *

@@ -184,2 +184,3 @@ // CSN functionality for resolving references

const { locationString } = require('../base/location');
const { ModelError, CompilerAssertion } = require("../base/error");

@@ -195,8 +196,12 @@ // Properties in which artifact or members are defined - next property in the

// - dynamic: String - describes the dynamic environment (if in query)
// - assoc: String, with dynamic: 'global' - what to do with assoc steps
// * 'static': visit elements of anonymous aspect if not last ref item
// * 'target': always follow target, including last ref item
// * other (& not provided) = follow target if not last ref item
const referenceSemantics = {
type: { lexical: false, dynamic: 'global' },
includes: { lexical: false, dynamic: 'global' },
target: { lexical: false, dynamic: 'global' },
targetAspect: { lexical: false, dynamic: 'global' },
from: { lexical: false, dynamic: 'global' },
type: { lexical: false, dynamic: 'global' }, // TODO: assoc: 'static', see Issue #8458
includes: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
target: { lexical: false, dynamic: 'global', assoc: 'static' }, // no elem ref anyway
targetAspect: { lexical: false, dynamic: 'global', assoc: 'static' },
from: { lexical: false, dynamic: 'global', assoc: 'target' },
keys: { lexical: false, dynamic: 'target' },

@@ -221,6 +226,11 @@ keys_origin: { lexical: false, dynamic: 'target' },

* @param {CSN.Model} csn
* @param {boolean|string} [universalReady]
*/
function csnRefs( csn ) {
function csnRefs( csn, universalReady ) {
const cache = new WeakMap();
setCache( BUILTIN_TYPE, '_origin', null );
if (universalReady === 'init-all') {
for (const art of Object.values( csn.definitions || {}))
initDefinition( art );
}
// Functions which set the new `baseEnv`:

@@ -237,3 +247,11 @@ resolveRef.expandInline = function resolve_expandInline( ref, ...args ) {

return {
effectiveType, artifactRef, getOrigin, inspectRef, queryOrMain,
effectiveType,
artifactRef,
getOrigin,
inspectRef,
queryOrMain,
getColumn: ( elem ) => getCache( elem, '_column' ),
getElement: ( col ) => getCache( col, '_element' ),
initDefinition,
targetAspect,
__getCache_forEnrichCsnDebugging: obj => cache.get( obj ),

@@ -267,3 +285,3 @@ };

if (getCache( art, '_effectiveType' ) === 0)
throw new Error( 'Circular type reference');
throw new ModelError( 'Circular type reference');
const type = getCache( art, '_effectiveType' ) || art;

@@ -277,5 +295,4 @@ chain.forEach( a => setCache( a, '_effectiveType', type ) );

*/
function navigationEnv( art ) {
function navigationEnv( art, staticAssoc ) {
let env = effectiveType( art );
getOrigin( env ); // also set implicit origins
// here, we do not care whether it is semantically ok to navigate into sub

@@ -286,7 +303,8 @@ // elements of array items (that is the task of the core compiler /

env = effectiveType( env.items );
if (!env.target)
return env;
const target = csn.definitions[env.target];
getOrigin( target );
return target;
const target = (staticAssoc ? targetAspect( env ) : env.target);
if (typeof target !== 'string')
return target || env;
const def = csn.definitions[target];
initDefinition( def );
return def;
}

@@ -303,10 +321,19 @@

function artifactRef( ref, notFound ) {
const art = (typeof ref === 'string')
? csn.definitions[ref]
: cached( ref, '_ref', artifactPathRef );
if (art)
return art;
// TODO: what about type ref?
if (typeof ref === 'string') {
const main = csn.definitions[ref];
if (main)
return initDefinition( main );
}
else {
const art = cached( ref, '_ref', artifactPathRef );
if (art)
return art;
}
if (notFound !== undefined && typeof ref === 'string')
return notFound; // is only meant for builtins
// Backend bug workaround, TODO: delete next 2 lines
if (notFound !== undefined)
return notFound;
throw new Error( 'Undefined reference' );
throw new ModelError( 'Undefined reference' );
}

@@ -317,4 +344,5 @@

let art = csn.definitions[pathId( head )];
initDefinition( art );
for (const elem of tail) {
const env = navigationEnv( art );
const env = navigationEnv( art ); // TODO: second argument true, see Issue #8458
art = env.elements[pathId( elem )];

@@ -325,2 +353,13 @@ }

function artifactFromRef( ref ) {
const [ head, ...tail ] = ref.ref;
let art = csn.definitions[pathId( head )];
initDefinition( art );
for (const elem of tail) {
const env = navigationEnv( art );
art = env.elements[pathId( elem )];
}
return navigationEnv( art );
}
// Return target when resolving references in 'keys'

@@ -339,3 +378,3 @@ function assocTarget( art, refCtx ) {

const target = csn.definitions[targetName];
getOrigin( target ); // induce implicit $origin on target
initDefinition( target );
return target;

@@ -345,6 +384,3 @@ }

function getOrigin( art ) {
const origin = cached( art, '_origin', getOriginRaw );
if (origin && origin !== BUILTIN_TYPE)
cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
return origin;
return cached( art, '_origin', getOriginRaw );
}

@@ -355,101 +391,89 @@

return artifactRef( art.type, BUILTIN_TYPE );
if (!art.$origin) // implicit $origin should have been set
if (typeof art.$origin === 'object') // null, […], {…}
return getOriginExplicit( art.$origin );
const parent = getCache( art, '_parent' );
if (parent === undefined && universalReady) {
const { $location } = art;
const location = $location &&
(typeof $location === 'string' ? $location : locationString( $location ));
const def = Object.keys( art ).join('+') + (location ? ':' + location : '');
throw new Error( `Inspecting non-initialized CSN node {${def}}` );
}
const step = getCache( art, '$origin$step' );
if (!step)
return null;
// art.$origin must not be a string here - shortened refs should already
// have been used to set the _origin cache
// If $origin object, return $origin of that:
if (!Array.isArray( art.$origin )) // anonymous prototype in $origin
return cached( art.$origin, '_origin', getOriginRaw );
const [ head, ...tail ] = art.$origin;
let origin = csn.definitions[head];
// allow shorter $origin ref for actions/functions, just using a string:
let isAction = art.kind === 'action' || art.kind === 'function';
for (const elem of tail) {
origin = originNavigation( origin, elem, isAction );
isAction = false;
}
return origin;
const origin = cached( parent, '_origin', getOriginRaw );
return originNavigation( origin, step );
}
function originNavigation( art, elem, isAction ) {
if (typeof elem !== 'string') {
if (elem.action)
return art.actions[elem.action];
if (elem.param)
return art.params[elem.param];
if (elem.returns)
return art.returns;
}
if (isAction)
return art.actions[elem];
// TODO: if we use effectiveType(), we might have more implicit prototypes
// we might then need a function like effectiveArtifact,
// which cares about actions/params
// let origin = cached( art, '_origin', getOriginRaw );
// while (art.items) {
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
// art = art.items;
// origin = cached( art, '_origin', getOriginRaw );
// }
// if (origin)
// cached( art, '$origin', _a => setImplicitOrigin( art, origin ) );
// console.log(art)
if (art.returns) // for ["main", {action: "act"}, "elem"]
art = art.returns;
return (art.elements || art.enum || targetAspect( art ).elements)[elem];
function getOriginExplicit( $origin ) { // null, […], {…}
if (!$origin)
return null;
if (!Array.isArray( $origin )) // anonymous prototype in $origin
return getOriginExplicit( $origin.$origin );
const [ head, ...tail ] = $origin;
const main = csn.definitions[head];
initDefinition( main );
return tail.reduce( originNavigation, main );
}
function targetAspect( art ) {
const { $origin } = art;
return art.targetAspect ||
$origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
art.target;
function originNavigation( art, step ) {
if (!step)
return null;
if (!effectiveType( art ))
throw new TypeError( 'Cyclic type definition' );
if (typeof step === 'string')
return navigationEnv( art, true ).elements[step];
if (step.action)
return effectiveArtFor( art, 'actions' )[step.action];
if (step.param)
return effectiveArtFor( art, 'params' )[step.param];
if (step.returns)
return effectiveArtFor( art, 'returns' );
if (step.enum)
return navigationEnv( art, true ).enum[step.enum];
if (step.items)
return effectiveType( art ).items;
if (step.target)
return targetAspect( effectiveType( art ) );
throw Error( `Illegal navigation step ${ Object.keys(step)[0]}` );
}
// From the current CSN object, set implicit origin for the next navigation step
function setImplicitOrigin( art, origin ) {
setMembersImplicit( art.actions, origin.actions );
setMembersImplicit( art.params, origin.params );
if (art.returns) {
art = art.returns;
if (art.type || typeof art.$origin === 'object') // null, […], {…}
return true; // not implicit or shortened
origin = effectiveType( origin.returns );
setCache( art, '_origin', origin );
return true;
}
while (art.items) {
art = art.items;
if (art.type || typeof art.$origin === 'object') // null, […], {…}
return true; // not implicit or shortened
origin = effectiveType( origin.items );
setCache( art, '_origin', origin );
}
setMembersImplicit( art.elements, origin.elements );
// The enum base type is _not_ where we find the origins of the enum symbols.
// A derived type of an enum type with individual annotations on a symbol
// has both 'type' and '$origin'.
if (!art.type || art.$origin)
setMembersImplicit( art.enum, origin.enum );
return true;
function effectiveArtFor( art, property ) {
while (!art[property])
art = getOrigin( art );
return art[property];
}
function setMembersImplicit( members, originMembers ) {
if (!members)
return;
for (const name in members) {
const elem = members[name];
if (!elem.type && typeof elem.$origin !== 'object') // undefined or string
setCache( elem, '_origin', originMembers[elem.$origin || name] || false );
function initDefinition( main ) {
// TODO: some --test-mode check that the argument is in ‹csn›.definitions ?
if (getCache( main, '$queries' ) !== undefined) // already computed
return main;
traverseDef( main, null, null, null, initNode );
const queries = cached( main, '$queries', allQueries );
for (const qcache of queries || []) {
const { _select } = qcache;
traverseType( _select, main, null, null, initNode ); // also inits elements
if (_select.mixin) {
for (const n of Object.keys( _select.mixin ))
setCache( _select.mixin[n], '_parent', _select ); // relevant initNode() part
}
}
return main;
}
/**
* Return the entity we select from
*
* @param {CSN.ArtifactReferencePath} ref
* @returns {CSN.Definition}
*/
function fromRef( ref ) {
return navigationEnv( artifactRef( ref ));
function initNode( art, parent, kind, name ) {
setCache( art, '_parent', parent );
if (art.type || !kind || kind === 'target') // with type, top-level, query or mixin
return;
const { $origin } = art;
if (typeof $origin === 'object') // null, […], {…}
return;
const step = $origin || name;
if (parent.$origin ||
parent.type && kind !== 'enum' && parent.$origin !== null ||
getCache( parent, '$origin$step' ))
setCache( art, '$origin$step', (kind === 'element' ? step : { [kind]: step }) );
}

@@ -495,5 +519,7 @@

if (!Array.isArray( path ))
throw new Error( 'References must look like {ref:[...]}' );
throw new ModelError( 'References must look like {ref:[...]}' );
const head = pathId( path[0] );
if (main) // TODO: improve, for csnpath starting with art
initDefinition( main );
if (ref.param)

@@ -504,29 +530,21 @@ return resolvePath( path, main.params[head], main, 'param' );

if (semantics.dynamic === 'global' || ref.global)
return resolvePath( path, csn.definitions[head], null, 'global', refCtx === 'from' );
return resolvePath( path, csn.definitions[head], null, 'global', semantics.assoc );
if (main) {
getOrigin( main );
cached( main, '$queries', allQueries );
}
let qcache = query && cache.get( query.projection || query );
// BACKEND ISSUE: you cannot call csnRefs(), inspect some refs, change the
// CSN and again inspect some refs without calling csnRefs() before!
// WORKAROUND: if no cached query, a backend has changed the CSN - re-eval cache
if (query && !qcache) {
setCache( main, '$queries', allQueries( main ) );
qcache = cache.get( query.projection || query );
}
// first the lexical scopes (due to query hierarchy) and $magic: ---------
if (semantics.lexical !== false) {
const tryAlias = path.length > 1 || ref.expand || ref.inline;
let cache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
while (cache) {
const alias = tryAlias && cache.$aliases[head];
let ncache = qcache && (semantics.lexical ? semantics.lexical( qcache ) : qcache);
while (ncache) {
const alias = tryAlias && ncache.$aliases[head];
if (alias)
return resolvePath( path, alias._select || alias._ref, null,
'alias', cache.$queryNumber );
const mixin = cache._select.mixin && cache._select.mixin[head];
if (mixin && {}.hasOwnProperty.call( cache._select.mixin, head ))
return resolvePath( path, mixin, null, 'mixin', cache.$queryNumber );
cache = cache.$next;
'alias', ncache.$queryNumber );
const mixin = ncache._select.mixin && ncache._select.mixin[head];
if (mixin && {}.hasOwnProperty.call( ncache._select.mixin, head )) {
setCache( mixin, '_parent', qcache._select );
return resolvePath( path, mixin, null, 'mixin', ncache.$queryNumber );
}
ncache = ncache.$next;
}

@@ -560,3 +578,3 @@ if (head.charAt(0) === '$') {

// console.log(query.SELECT,qcache,qcache.$next,main)
throw new Error ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
throw new ModelError ( `Path item ${ 0 }=${ head } refers to nothing, refCtx: ${ refCtx }` );
}

@@ -571,2 +589,3 @@

function resolvePath( path, art, parent, scope, extraInfo ) {
const staticAssoc = extraInfo === 'static' && scope === 'global';
/** @type {{idx, art?, env?}[]} */

@@ -576,6 +595,6 @@ const links = path.map( (_v, idx) => ({ idx }) );

// if (!art) // does not work with test3/Associations/KeylessManagedAssociation/
// throw new Error ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
// throw new ModelError ( `Path item ${ 0 }=${ pathId( path[0] ) } refers to nothing, scope: ${ scope }`);
links[0].art = art;
for (let i = 1; i < links.length; ++i) { // yes, starting at 1, links[0] is set above
parent = navigationEnv( art );
parent = navigationEnv( art, staticAssoc );
links[i - 1].env = parent;

@@ -588,3 +607,3 @@ if (typeof path[i - 1] !== 'string')

const loc = env.name && env.name.$location || env.$location;
throw new Error ( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
throw new ModelError ( `Path item ${ i }=${ pathId( path[i] ) } on ${ locationString( loc ) } refers to nothing` );
}

@@ -594,3 +613,3 @@ links[i].art = art;

const last = path[path.length - 1];
const fromRef = scope === 'global' && extraInfo;
const fromRef = scope === 'global' && extraInfo === 'target';
if (fromRef || typeof last !== 'string') {

@@ -606,3 +625,3 @@ const env = navigationEnv( art );

}
return (extraInfo && !fromRef)
return (extraInfo && scope !== 'global')
? { links, art, parent, scope, $env: extraInfo }

@@ -628,4 +647,4 @@ : { links, art, parent, scope };

const as = query.as || implicitAs( query.ref );
const _ref = fromRef( query );
getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements };
const _ref = cached( query, '_from', artifactFromRef )
getCache( fromSelect, '$aliases' )[as] = { _ref, elements: _ref.elements, _parent: query };
}

@@ -643,2 +662,3 @@ else {

qcache._select = select;
qcache._parent = main;
all.push( qcache );

@@ -649,6 +669,8 @@ }

all.forEach( function initElements( qcache, index ) {
qcache._parent = main;
qcache.$queryNumber = index + 1;
qcache.elements = (index ? qcache._select : main).elements;
const columns = qcache._select.columns;
if (qcache.elements && columns)
const { elements } = (index ? qcache._select : main);
qcache.elements = elements;
const { columns } = qcache._select;
if (elements && columns)
columns.map( c => initColumnElement( c, qcache ) );

@@ -692,3 +714,4 @@ } );

const elem = setCache( col, '_element', type.elements[as] );
// if requested, we could set a _column link in element
if (elem) // TODO to.sql: something is strange if this is not set
setCache( elem, '_column', col );
if (col.expand)

@@ -751,3 +774,3 @@ col.expand.map( c => initColumnElement( c, elem ) );

return main;
throw new Error( `Query elements not available: ${ Object.keys( query ).join('+') }`);
throw new ModelError( `Query elements not available: ${ Object.keys( query ).join('+') }`);
}

@@ -816,2 +839,48 @@

function traverseDef( node, parent, kind, name, callback ) {
callback ( node, parent, kind, name );
if (node.params) {
for (const n of Object.keys( node.params ))
traverseType( node.params[n], node, 'param', n, callback );
}
if (node.returns)
traverseType( node.returns, node, 'returns', true, callback );
traverseType( node, parent, kind, name, callback );
if (node.actions) {
for (const n of Object.keys( node.actions ))
traverseDef( node.actions[n], node, 'action', n, callback )
}
}
function traverseType( node, parent, kind, name, callback ) {
callback ( node, parent, kind, name );
const target = targetAspect( node );
if (target && typeof target === 'object' && target.elements) {
callback ( target, node, 'target', true );
node = target;
}
else if (node.items) {
let items = 0;
while (node.items) {
callback ( node.items, node, 'items', ++items );
node = node.items;
}
}
if (node.elements) {
for (const n of Object.keys( node.elements ))
traverseDef( node.elements[n], node, 'element', n, callback )
}
if (node.enum) {
for (const n of Object.keys( node.enum ))
traverseDef( node.enum[n], node, 'enum', n, callback )
}
}
function targetAspect( art ) {
const { $origin } = art;
return art.targetAspect ||
$origin && typeof $origin === 'object' && !Array.isArray( $origin ) && $origin.target ||
art.target;
}
function pathId( item ) {

@@ -833,3 +902,3 @@ return (typeof item === 'string') ? item : item.id;

if (csnPath.length < 2 || csnPath[0] !== 'definitions')
throw new Error( 'References outside definitions not supported yet');
throw new CompilerAssertion( 'References outside definitions not supported yet');
const art = csn.definitions[csnPath[1]];

@@ -861,5 +930,5 @@ return { index: 2, main: art, parent: null, art };

// array item, name/index of artifact/member, (named) argument
if (isName || Array.isArray( obj )) {
if (isName || Array.isArray( obj ) || prop === 'returns') {
// TODO: call some kind of resolve.setOrigin()
if (typeof isName === 'string') {
if (typeof isName === 'string' || prop === 'returns') {
parent = art;

@@ -866,0 +935,0 @@ art = obj[prop];

'use strict';
const { csnRefs } = require('../model/csnRefs');
const { csnRefs, implicitAs } = require('../model/csnRefs');
const { applyTransformations, applyTransformationsOnNonDictionary } = require('../transform/db/applyTransformations');
const { isBuiltinType } = require('../compiler/builtins.js')
const { sortCsn, cloneCsnDictionary: _cloneCsnDictionary } = require('../json/to-csn');
const { ModelError } = require("../base/error");
const version = require('../../package.json').version;

@@ -39,4 +40,4 @@

*/
function getUtils(model) {
const { artifactRef, inspectRef, effectiveType, getOrigin } = csnRefs(model);
function getUtils(model, universalReady) {
const { artifactRef, inspectRef, effectiveType, getOrigin, targetAspect, getColumn, getElement, initDefinition } = csnRefs(model, universalReady);

@@ -48,3 +49,3 @@ return {

getFinalTypeDef,
isManagedAssociationElement,
isManagedAssociation,
isAssocOrComposition,

@@ -67,2 +68,6 @@ isAssociation,

getQueryPrimarySource,
targetAspect,
getColumn,
getElement,
initDefinition
};

@@ -77,121 +82,121 @@

function get$combined(query) {
const sources = getSources(query);
return sources;
return getSources(query);
}
/**
* Get the union of all elements from the from clause
* - descend into unions, following the lead query
* - merge all queries in case of joins
* - follow subqueries
*
* @param {CSN.Query} query Query to check
* @returns {object} Map of sources
*/
function getSources(query, isSubquery=false) {
// Remark CW: better just a while along query.SET.args[0]
if (query.SET) {
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
/**
* Get the union of all elements from the from clause
* - descend into unions, following the lead query
* - merge all queries in case of joins
* - follow subqueries
*
* @param {CSN.Query} query Query to check
* @param {boolean} [isSubquery]
* @returns {object} Map of sources
*/
function getSources(query, isSubquery=false) {
// Remark CW: better just a while along query.SET.args[0]
if (query.SET) {
if (query.SET.args[0].SELECT && query.SET.args[0].SELECT.elements)
return mergeElementsIntoMap(Object.create(null), query.SET.args[0].SELECT.elements, query.SET.args[0].$location);
return getSources(query.SET.args[0], isSubquery);
return getSources(query.SET.args[0], isSubquery);
}
else if (query.SELECT) {
if (query.SELECT.from.args) {
return walkArgs(query.SELECT.from.args);
}
else if (query.SELECT) {
if (query.SELECT.from.args) {
return walkArgs(query.SELECT.from.args);
}
else if (query.SELECT.from.ref) {
let art = artifactRef(query.SELECT.from);
else if (query.SELECT.from.ref) {
let art = artifactRef(query.SELECT.from);
if(art.target)
art = artifactRef(art.target);
if(art.target)
art = artifactRef(art.target);
if(isSubquery && !query.SELECT.elements)
throw new Error('Expected subquery to have .elements');
if(isSubquery && !query.SELECT.elements)
throw new ModelError('Expected subquery to have .elements');
return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
}
else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
return getSources(query.SELECT.from, true);
}
return mergeElementsIntoMap(Object.create(null), isSubquery ? query.SELECT.elements : art.elements, art.$location,
query.SELECT.from.as || query.SELECT.from.ref[query.SELECT.from.ref.length - 1],
query.SELECT.from.ref[query.SELECT.from.ref.length - 1] || query.SELECT.from.as );
}
else if (query.SELECT.from.SET || query.SELECT.from.SELECT) {
return getSources(query.SELECT.from, true);
}
}
function walkArgs(args) {
let elements = Object.create(null);
for (const arg of args) {
if (arg.args) {
elements = mergeElementMaps(elements, walkArgs(arg.args));
}
else if (arg.ref) {
const art = artifactRef(arg);
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
}
else if (arg.SELECT || arg.SET) {
elements = mergeElementMaps(elements, getSources(arg));
}
}
return {};
}
return elements;
function walkArgs(args) {
let elements = Object.create(null);
for (const arg of args) {
if (arg.args) {
elements = mergeElementMaps(elements, walkArgs(arg.args));
}
else if (arg.ref) {
const art = artifactRef(arg);
elements = mergeElementsIntoMap(elements, art.elements, art.$location, arg.as || arg.ref[arg.ref.length - 1], arg.ref[arg.ref.length - 1] || arg.as);
}
else if (arg.SELECT || arg.SET) {
elements = mergeElementMaps(elements, getSources(arg));
}
}
return {};
return elements;
}
/**
* Merge two maps of elements together
*
* @param {object} mapA Map a - will be returned
* @param {object} mapB Map b - will not be returned
* @returns {object} mapA
*/
function mergeElementMaps(mapA, mapB) {
for (const elementName in mapB) {
if (!mapA[elementName])
mapA[elementName] = [];
/**
* Merge two maps of elements together
*
* @param {object} mapA Map a - will be returned
* @param {object} mapB Map b - will not be returned
* @returns {object} mapA
*/
function mergeElementMaps(mapA, mapB) {
for (const elementName in mapB) {
if (!mapA[elementName])
mapA[elementName] = [];
mapB[elementName].forEach(e => mapA[elementName].push(e));
}
mapB[elementName].forEach(e => mapA[elementName].push(e));
}
return mapA;
}
return mapA;
}
/**
* Merge elements into an existing map
*
* @param {any} existingMap map to merge into - will be returned
* @param {object} elements elements to merge into the map
* @param {CSN.Location} $location $location of the elements - where they come from
* @param {any} [parent] Name of the parent of the elements, alias before ref
* @param {any} [error_parent] Parent name to use for error messages, ref before alias
* @returns {object} existingMap
*/
function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
for (const elementName in elements) {
const element = elements[elementName];
if (!existingMap[elementName])
existingMap[elementName] = [];
/**
* Merge elements into an existing map
*
* @param {any} existingMap map to merge into - will be returned
* @param {object} elements elements to merge into the map
* @param {CSN.Location} $location $location of the elements - where they come from
* @param {any} [parent] Name of the parent of the elements, alias before ref
* @param {any} [error_parent] Parent name to use for error messages, ref before alias
* @returns {object} existingMap
*/
function mergeElementsIntoMap(existingMap, elements, $location, parent, error_parent) {
for (const elementName in elements) {
const element = elements[elementName];
if (!existingMap[elementName])
existingMap[elementName] = [];
existingMap[elementName].push({
element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
});
}
return existingMap;
}
existingMap[elementName].push({
element, name: elementName, source: $location, parent: getBaseName(parent), error_parent,
});
}
/**
* Return the name part of the artifact name - no namespace etc.
* @param {string|object} name Absolute name of the artifact
*/
function getBaseName(name) {
if (!name)
return name;
return existingMap;
}
if (name.id)
return name.id.substring( name.id.lastIndexOf('.')+1 );
/**
* Return the name part of the artifact name - no namespace etc.
* @param {string|object} name Absolute name of the artifact
*/
function getBaseName(name) {
if (!name)
return name;
return name.substring( name.lastIndexOf('.')+1 )
}
if (name.id)
return name.id.substring( name.id.lastIndexOf('.')+1 );
return name.substring( name.lastIndexOf('.')+1 )
}

@@ -223,7 +228,7 @@

* Create an object to track visited objects identified by a unique string.
* @param {string} [id] Initial entry (optional)
* @param {string} [initialId] Initial entry (optional)
*/
function createVisited(id) {
function createVisited(initialId) {
let visited = Object.create(null);
check(id);
check(initialId);
return { check };

@@ -239,3 +244,3 @@

if (visited[id]) {
throw new Error('Circular dependency');
throw new ModelError('Circular dependency');
}

@@ -254,3 +259,3 @@ visited[id] = true;

else
throw new Error(`Nonexistent definition in the model: '${defName}'`);
throw new ModelError(`Nonexistent definition in the model: '${defName}'`);
}

@@ -308,3 +313,3 @@

// TODO: what about elements having a type, which (finally) is an assoc?
function isManagedAssociationElement(node) {
function isManagedAssociation(node) {
return node.target !== undefined && node.on === undefined && node.keys;

@@ -547,3 +552,3 @@ }

if (cycleCheck[visited])
throw new Error('Circular type chain on type ' + type);
throw new ModelError('Circular type chain on type ' + type);
else

@@ -661,3 +666,3 @@ cycleCheck[visited] = true;

path = [...path]; // Copy
const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'foreignKeys', 'actions', 'params']);
const propsWithMembers = (iterateOptions.elementsOnly ? ['elements'] : ['elements', 'enum', 'actions', 'params']);
propsWithMembers.forEach((prop) => forEachGeneric( construct, prop, callback, path, iterateOptions ));

@@ -719,41 +724,2 @@ }

/**
* For each property named 'ref' in 'node' (recursively), call callback(ref, node, path)
*
* @param {object} node
* @param {refCallback|refCallback[]} callback
* @param {CSN.Path} path
*/
function forEachRef(node, callback, path = []) {
if (node === null || typeof node !== 'object') {
// Primitive node
return;
}
if(node._ignore){
return;
}
if(Array.isArray(node)){
for (let i = 0; i < node.length; i++) {
// Descend recursively
forEachRef(node[i], callback, path.concat([i]));
}
} else {
for (let name in node) {
if (!Object.hasOwnProperty.call( node, name ))
continue;
// If ref found within a non-dictionary, call callback
if (name === 'ref' && Object.getPrototypeOf(node)) {
if(Array.isArray(callback))
callback.forEach(cb => cb( node.ref, node, path ));
else
callback( node.ref, node, path );
}
// Descend recursively
forEachRef(node[name], callback, path.concat([name]));
}
}
}
// Like Object.assign() but copies also non enumerable properties

@@ -844,35 +810,2 @@ function assignAll(target, ...sources) {

function forAllElements(artifact, artifactName, cb, includeActions = false){
if(artifact.elements) {
cb(artifact, artifact.elements, ['definitions', artifactName, 'elements']);
}
if(includeActions && artifact.actions) {
Object.entries(artifact.actions).forEach( ([actionName, action]) => {
const path = ['definitions', artifactName, 'actions', actionName];
if(action.params) {
Object.entries(action.params).forEach( ([paramName, param]) => {
if(param.elements)
cb(param, param.elements, path.concat(['params', paramName, 'elements']));
});
}
if(action.returns && action.returns.elements)
cb(action.returns, action.returns.elements,path.concat(['returns', 'elements']));
});
}
if(artifact.query) {
forAllQueries(artifact.query, (q, p) => {
const s = q.SELECT;
if(s) {
if(s.elements) {
cb(s, s.elements, [...p, 'elements']);
} else if(s.$elements) { // huh?, is just refloc output
cb(s, s.$elements, [...p, '$elements']);
}
}
}, ['definitions', artifactName, 'query'])
}
}
/**

@@ -940,8 +873,8 @@ * Compare a given annotation value with an expectation value and return

* Return the resulting database name for (absolute) 'artifactName', depending on the current naming
* convention.
* mode.
*
* - For the 'hdbcds' naming convention, this means converting '.' to '::' on
* - For the 'hdbcds' naming mode, this means converting '.' to '::' on
* the border between namespace and top-level artifact and correctly replacing some '.' with '_'.
* - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
* - For the 'quoted' naming convention, this means correctly replacing some '.' with '_'.
* - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
* - For the 'quoted' naming mode, this means correctly replacing some '.' with '_'.
*

@@ -951,20 +884,20 @@ * If the old function signature is used - with a namespace as the third argument - the result might be wrong,

*
* @param {string} artifactName The name of the artifact
* @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
* @param {string} artifactName The fully qualified name of the artifact
* @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
* @param {CSN.Model|string|undefined} csn
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming convention.
* @returns {string} The resulting database name for (absolute) 'artifactName', depending on the current naming mode.
*/
function getArtifactDatabaseNameOf(artifactName, namingConvention, csn) {
function getArtifactDatabaseNameOf(artifactName, sqlMapping, csn) {
if(csn && typeof csn === 'object' && csn.definitions)
if (namingConvention === 'quoted' || namingConvention === 'hdbcds') {
return getResultingName(csn, namingConvention, artifactName);
if (sqlMapping === 'quoted' || sqlMapping === 'hdbcds') {
return getResultingName(csn, sqlMapping, artifactName);
}
else if (namingConvention === 'plain') {
else if (sqlMapping === 'plain') {
return artifactName.replace(/\./g, '_').toUpperCase();
} else {
throw new Error('Unknown naming convention: ' + namingConvention);
throw new Error('Unknown naming mode: ' + sqlMapping);
}
else {
console.error(`This invocation of "getArtifactCdsPersistenceName" is deprecated, as it doesn't produce correct output with definition names containing dots - please provide a CSN as the third parameter.`);
if (namingConvention === 'hdbcds') {
if (sqlMapping === 'hdbcds') {
if (csn) {

@@ -976,10 +909,10 @@ const namespace = String(csn);

}
else if (namingConvention === 'plain') {
else if (sqlMapping === 'plain') {
return artifactName.replace(/\./g, '_').toUpperCase();
}
else if (namingConvention === 'quoted') {
else if (sqlMapping === 'quoted') {
return artifactName;
}
else {
throw new Error('Unknown naming convention: ' + namingConvention);
throw new Error('Unknown naming mode: ' + sqlMapping);
}

@@ -1070,25 +1003,25 @@ }

/**
* Return the resulting database element name for 'elemName', depending on the current naming
* convention.
* - For the 'hdbcds' naming convention, this is just 'elemName'.
* - For the 'plain' naming convention, it means converting all '.' to '_' and uppercasing.
* - For the 'quoted' naming convention, it means converting all '.' to '_'.
* No other naming conventions are accepted
* Return the resulting database element name for 'elemName', depending on the current
* naming mode.
* - For the 'hdbcds' naming mode, this is just 'elemName'.
* - For the 'plain' naming mode, it means converting all '.' to '_' and upper-casing.
* - For the 'quoted' naming mode, it means converting all '.' to '_'.
* No other naming modes are accepted!
*
* @param {string} elemName Name of the element
* @param {('plain'|'quoted'|'hdbcds')} namingConvention The naming convention to use
* @returns {string} The resulting database element name for 'elemName', depending on the current naming convention.
* @param {string} elemName The name of the element
* @param {('plain'|'quoted'|'hdbcds')} sqlMapping The naming mode to use
* @returns {string} The resulting database element name for 'elemName', depending on the current naming mode.
*/
function getElementDatabaseNameOf(elemName, namingConvention) {
if (namingConvention === 'hdbcds') {
function getElementDatabaseNameOf(elemName, sqlMapping) {
if (sqlMapping === 'hdbcds') {
return elemName;
}
else if (namingConvention === 'plain') {
else if (sqlMapping === 'plain') {
return elemName.replace(/\./g, '_').toUpperCase();
}
else if (namingConvention === 'quoted') {
else if (sqlMapping === 'quoted') {
return elemName.replace(/\./g, '_');
}
else {
throw new Error('Unknown naming convention: ' + namingConvention);
throw new Error('Unknown naming mode: ' + sqlMapping);
}

@@ -1171,3 +1104,3 @@ }

function isPersistedOnDatabase(art) {
return !([ 'entity', 'view' ].includes(art.kind) && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
return !('entity' === art.kind && (art.abstract || hasAnnotationValue(art, '@cds.persistence.skip')));
}

@@ -1240,6 +1173,6 @@

if (isObject(left) && !Array.isArray(left) && (Array.isArray(right) || isScalar(right))) {
throw new Error(`Cannot overwrite structured option "${name}" with array or scalar value`);
throw new ModelError(`Cannot overwrite structured option "${name}" with array or scalar value`);
}
if ((isScalar(left) && typeof left !== 'boolean' || Array.isArray(left)) && isObject(right) && !Array.isArray(right)) {
throw new Error(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
throw new ModelError(`Cannot overwrite non-boolean scalar or array option "${name}" with structured value`);
}

@@ -1399,3 +1332,3 @@

function hasValidSkipOrExists(artifact) {
return (artifact.kind === 'entity' || artifact.kind === 'view') &&
return artifact.kind === 'entity' &&
(hasAnnotationValue(artifact, '@cds.persistence.exists', true) || hasAnnotationValue(artifact, '@cds.persistence.skip', true))

@@ -1469,3 +1402,3 @@

/**
* Check wether the artifact is @cds.persistence.skip
* Check whether the artifact is @cds.persistence.skip
*

@@ -1524,2 +1457,38 @@ * @param {CSN.Artifact} artifact

/**
*
* @param {object} obj
* @param {*} other
* @param {boolean} noExtendedProps
* @returns {boolean} returns equality
*
* noExtendedProps remove '$', '_' and '@' properties from
* the comparision. This eliminates false negatives such as
* mismatching $locations or @odata.foreignKey4.
*/
function isDeepEqual(obj, other, noExtendedProps) {
let objectKeys = Object.keys(obj);
let otherKeys = Object.keys(other);
if(noExtendedProps) {
objectKeys = objectKeys.filter(k => !['@', '$', '_'].includes(k[0]));
otherKeys = otherKeys.filter(k => !['@', '$', '_'].includes(k[0]));
}
if (objectKeys.length !== otherKeys.length)
return false;
for (let key of objectKeys) {
const areValuesObjects = (obj[key] != null && typeof obj[key] === 'object')
&& (other[key] !== null && typeof other[key] === 'object');
if (areValuesObjects) {
if (!isDeepEqual(obj[key], other[key], noExtendedProps))
return false;
} else if (obj[key] !== other[key]) {
return false;
}
}
return true;
}
module.exports = {

@@ -1535,5 +1504,3 @@ getUtils,

forEachMemberRecursively,
forEachRef,
forAllQueries,
forAllElements,
hasAnnotationValue,

@@ -1566,3 +1533,5 @@ isEdmPropertyRendered,

walkCsnPath,
getVariableReplacement
getVariableReplacement,
implicitAs,
isDeepEqual,
};

@@ -64,6 +64,10 @@ // For testing: reveal non-enumerable properties in CSN, display result of csnRefs

}
// options.enrichCsn = 'DEBUG';
let $$cacheObjectNumber = 0; // for debugging
const debugLocationInfo = options.enrichCsn === 'DEBUG' && Object.create(null);
setLocations( csn, false, null );
const { inspectRef, artifactRef, getOrigin, __getCache_forEnrichCsnDebugging } =
csnRefs( csn );
let $$cacheObjectNumber = 0; // for debugging
const { inspectRef, artifactRef, getOrigin, initDefinition, __getCache_forEnrichCsnDebugging } =
csnRefs( csn, true );
const csnPath = [];

@@ -111,2 +115,4 @@ if (csn.definitions)

for (let name of Object.getOwnPropertyNames( dict )) {
if (prop === 'definitions')
initDefinition( dict[name] );
definition( dict, name, dict[name] );

@@ -120,7 +126,15 @@ }

function refLocation( art ) {
if (art && typeof art === 'object')
return art.$location || '<no location>';
if (!art || typeof art !== 'object' || Array.isArray( art )) {
if (!options.testMode)
return (typeof art === 'string')
? `<illegal ref = ${art}>`
: `<illegal ref: ${typeof art}>`;
throw new Error( 'Illegal reference' );
}
else if (art.$location)
return art.$location;
if (!options.testMode)
return art || '<illegal link>';
throw new Error( 'Undefined reference' );
return `<${Object.keys( art ).join('+')}+!$location>`;
throw new Error( 'Reference to object without $location' );
}

@@ -248,2 +262,6 @@

}
else if (name === '$origin$step') { // string value handled above
const kind = Object.keys( val )[0];
obj.$$cacheObject[name] = `${kind}: ${ val[kind] }`;
}
else if (Array.isArray( val )) {

@@ -264,4 +282,49 @@ obj.$$cacheObject[name] = val.map( item => {

}
function debugLocation( loc, userProvided ) {
if (debugLocationInfo && !userProvided) {
loc = loc.replace( /\([0-9]+\)\^/, '^' );
debugLocationInfo[loc] = (debugLocationInfo[loc] || 0) + 1;
loc = `${loc}(${debugLocationInfo[loc]})`;
}
return loc;
}
function setLocations( node, prop, loc ) {
if (!node || typeof node !== 'object')
return;
const isMember = artifactProperties.includes( prop );
if (!isMember && node.$location) {
if (typeof node.$location === 'string') // already set for nested 'items'
return;
loc = locationString( node.$location, true );
if (!node.SELECT) // compatibility: $location of query both inside and as sibling of SELECT
reveal( node, '$location', debugLocation( loc, !node.$generated ) );
}
else if (prop === true || prop === 'returns') { // in dictionary or returns
loc = debugLocation( loc + '^' );
node.$location = loc;
}
else if (prop === 'items') {
let iloc = loc + '[]';
let obj = node;
while (obj) {
// should not appear in --enrich-csn, only for _origin info
Object.defineProperty( obj, '$location', { value: iloc, enumerable: false } );
obj = obj.items;
iloc += '[]';
}
}
if (Array.isArray( node )) {
for (const item of node)
setLocations( item, isMember, loc );
}
else {
for (const name of Object.getOwnPropertyNames( node ))
setLocations( node[name], isMember || name, loc );
}
}
}
function reveal( node, prop, value ) {

@@ -276,24 +339,2 @@ Object.defineProperty( node, prop, {

function setLocations( node, prop, loc ) {
if (!node || typeof node !== 'object')
return;
const isMember = artifactProperties.includes( prop );
if (!isMember && node.$location) {
loc = locationString( node.$location, true );
reveal( node, '$location', loc );
}
else if (prop === true) {
loc += '^';
node.$location = loc;
}
if (Array.isArray( node )) {
for (const item of node)
setLocations( item, isMember, loc );
}
else {
for (const name of Object.getOwnPropertyNames( node ))
setLocations( node[name], isMember || name, loc );
}
}
module.exports = enrichCsn;

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

function revealInternalProperties( model, name ) {
/**
* Reveal internal properties of `model` for the given artifact name (or path).
* `path` could be a definition name or a `/`-separated XSN path such as
* `name.space/S/E/elements/a/type/scope/`.
*
* @param {XSN.Model} model
* @param {string} [nameOrPath]
* @returns {string}
*/
function revealInternalProperties( model, nameOrPath ) {
const transformers = {

@@ -80,2 +89,3 @@ messages: m => m,

$keysNavigation: dictionary,
targetAspect,
$layerNumber: n => n,

@@ -94,3 +104,3 @@ $extra: e => e,

unique_id = 1;
return revealXsnPath(name, model);
return revealXsnPath(nameOrPath, model);

@@ -265,2 +275,8 @@ // Returns the desired artifact/dictionary in the XSN.

function targetAspect( node, parent ) {
if (node.elements && unique_id && node.__unique_id__ == null)
Object.defineProperty( node, '__unique_id__', { value: ++unique_id } );
return reveal( node, parent );
}
function duplicates( node, parent ) {

@@ -279,6 +295,8 @@ return reveal( node, parent, parent.name && parent.name.id );

if (node.$inferred === 'REDIRECTED')
outer = '/redirected';
outer = '/redirected' + outer;
else
outer = (node._outer.items === node) ? '/items'
: (node._outer.returns === node) ? '/returns' : '/returns/items';
outer = (node._outer.items === node) ? '/items' + outer
: (node._outer.returns === node) ? '/returns' + outer
: (node._outer.targetAspect === node) ? '/target' + outer
: '/returns/items' + outer;
node = node._outer;

@@ -313,3 +331,6 @@ }

default: {
return ((node._main || node).kind || '<kind>') + ':' + msg.artName( node ) + outer;
let main = node._main;
while (main && main._outer) // anonymous aspect
main = main._outer._main;
return ((main || node).kind || '<kind>') + ':' + msg.artName( node ) + outer;
}

@@ -316,0 +337,0 @@ }

'use strict';
const {setDependencies} = require('./csnUtils');
const { ModelError } = require("../base/error");

@@ -94,3 +95,3 @@ /**

if(leftover.length > 0)
throw new Error('Unable to build a correct dependency graph! Are there cycles?');
throw new ModelError('Unable to build a correct dependency graph! Are there cycles?');

@@ -97,0 +98,0 @@ const result = [];

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

} = require('../model/csnUtils');
const { isBetaEnabled } = require('../base/model');

@@ -16,3 +17,3 @@ /**

* @param afterModel the after-model
* @param {import('../api/main.js').hdiOptions|false} options
* @param {HdiOptions|false} options
* @returns {object} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model

@@ -31,4 +32,4 @@ * to the after-model, together with all the definitions of the after-model

// There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
forEachDefinition(afterModel, getArtifactComparator(beforeModel, null, null, elementAdditions, migrations));
forEachDefinition(beforeModel, getArtifactComparator(afterModel, null, deletedEntities, null, null));
forEachDefinition(afterModel, getArtifactComparator(beforeModel, options, null, null, elementAdditions, migrations));
forEachDefinition(beforeModel, getArtifactComparator(afterModel, options, null, deletedEntities, null, null));

@@ -66,3 +67,3 @@ const returnObj = Object.create(null);

function getArtifactComparator(otherModel, addedEntities, deletedEntities, elementAdditions, migrations) {
function getArtifactComparator(otherModel, options, addedEntities, deletedEntities, elementAdditions, migrations) {
return function compareArtifacts(artifact, name) {

@@ -77,3 +78,7 @@ function addElements() {

function changePropsOrRemoveOrChangeElements() {
const relevantProperties = ['doc'];
const relevantProperties = [
{ name: 'doc' },
{ name: '@sql.prepend' },
{ name: '@sql.append' },
];
const changedProperties = {};

@@ -87,4 +92,4 @@

relevantProperties.forEach(prop => {
if (artifact[prop] !== otherArtifact[prop]) {
changedProperties[prop] = changedElement(artifact[prop], otherArtifact[prop] || null);
if (artifact[prop.name] !== otherArtifact[prop.name] && (!prop.beta || isBetaEnabled(options, prop.beta))) {
changedProperties[prop.name] = changedElement(artifact[prop.name], otherArtifact[prop.name] || null);
}

@@ -159,3 +164,3 @@ });

function getElementComparator(otherArtifact, addedElements = null, changedElements = null) {
function getElementComparator(otherArtifact, addedElementsDict = null, changedElementsDict = null) {
return function compareElements(element, name) {

@@ -169,3 +174,3 @@ if (element._ignore) {

// Element type changed?
if (!changedElements) {
if (!changedElementsDict) {
return;

@@ -175,3 +180,3 @@ }

// Type or parameters, e.g. association target, changed.
changedElements[name] = changedElement(element, otherElement);
changedElementsDict[name] = changedElement(element, otherElement);
}

@@ -182,4 +187,4 @@

if (addedElements) {
addedElements[name] = element;
if (addedElementsDict) {
addedElementsDict[name] = element;
}

@@ -186,0 +191,0 @@ }

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

.option(' --assert-integrity-type <type>', [ 'RT', 'DB' ], { ignoreCase: true })
.option(' --constraints-as-alter <boolean>')
.option(' --constraints-in-create-table')
.option(' --deprecated <list>')

@@ -116,5 +116,5 @@ .option(' --hana-flavor')

DB : Create database constraints for associations
--constraints-as-alter <boolean> If set to 'true', the foreign key constraints will be rendered as
"ALTER TABLE ADD CONSTRAINT" statement rather than being part of the
"CREATE TABLE" statement
--constraints-in-create-table If set, the foreign key constraints will be rendered as
part of the "CREATE TABLE" statements rather than as separate
"ALTER TABLE ADD CONSTRAINT" statements
--deprecated <list> Comma separated list of deprecated options.

@@ -129,2 +129,3 @@ Valid values are:

createLocalizedViews
redirectInSubQueries
--hana-flavor Compile with backward compatibility for HANA CDS (incomplete)

@@ -131,0 +132,0 @@ --parse-only Stop compilation after parsing and write result to <stdout>

@@ -8,2 +8,3 @@

} = require('../model/csnUtils');
const { forEach } = require('../utils/objectUtils');

@@ -17,3 +18,3 @@ const {

* Moreover, it can be used to generate .hdbconstraint artifacts.
* Depending on the options.manageConstraints provided,the VALIDATED / ENFORCED flag of the constraints can be adjusted.
* Depending on the options.manageConstraints provided, the VALIDATED / ENFORCED flag of the constraints can be adjusted.
*

@@ -34,21 +35,20 @@ * @param {CSN.Model} csn

if (artifact.$tableConstraints && artifact.$tableConstraints.referential) {
Object.entries(artifact.$tableConstraints.referential)
.forEach(([ fileName, constraint ]) => {
const renderAlterConstraintStatement = alter && src !== 'hdi';
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
if (src === 'hdi') {
resultArtifacts[fileName] = renderedConstraint;
return;
}
let alterTableStatement = '';
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.toSql.names, constraint.dependentTable))}`;
if (renderAlterConstraintStatement)
alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
else if (drop)
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
else
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
forEach(artifact.$tableConstraints.referential, (fileName, constraint) => {
const renderAlterConstraintStatement = alter && src !== 'hdi';
const renderedConstraint = renderReferentialConstraint(constraint, indent, false, csn, options, renderAlterConstraintStatement);
if (src === 'hdi') {
resultArtifacts[fileName] = renderedConstraint;
return;
}
let alterTableStatement = '';
alterTableStatement += `${indent}ALTER TABLE ${quoteSqlId(getResultingName(csn, options.toSql.names, constraint.dependentTable))}`;
if (renderAlterConstraintStatement)
alterTableStatement += `\n${indent}ALTER ${renderedConstraint};`;
else if (drop)
alterTableStatement += `${indent} DROP CONSTRAINT ${quoteSqlId(constraint.identifier)};`;
else
alterTableStatement += `\n${indent}ADD ${renderedConstraint};`;
resultArtifacts[fileName] = alterTableStatement;
});
resultArtifacts[fileName] = alterTableStatement;
});
}

@@ -77,7 +77,10 @@ });

? `${prev} AND
${increaseIndent(indent)}${mainQueryAlias}.${quoteSqlId(constraint.foreignKey[index])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[index])}`
${increaseIndent(indent)}"${mainQueryAlias}".${quoteSqlId(constraint.foreignKey[index])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[index])}`
: increaseIndent(increaseIndent(indent)) + prev);
Object.entries(referentialConstraints).forEach(([ identifier, constraint ]) => {
Object.entries(referentialConstraints).forEach(([ identifier, constraint ], index) => {
let selectViolations = 'SELECT\n';
// this column indicates which SELECT revealed the integrity violation
// and helps to identify the corrupted table
selectViolations += `${index} as "SELECT-ID",\n`;
// SELECT <primary_key>,

@@ -89,4 +92,5 @@ const primaryKeyList = selectPrimaryKeyColumns(constraint);

selectViolations += selectForeignKeyColumns(constraint);
// ... FROM <dependent table> AS "MAIN"
selectViolations += `\nFROM ${quoteAndGetResultingName(constraint.dependentTable)} AS "MAIN"\n`;
const mainQueryAlias = `MAIN_${index}`;
// ... FROM <dependent table> AS "${index}"
selectViolations += `\nFROM ${quoteAndGetResultingName(constraint.dependentTable)} AS "${mainQueryAlias}"\n`;
// ... WHERE NOT (<(part of) foreign key is null>)

@@ -99,3 +103,3 @@ selectViolations += whereNotForeignKeyIsNull(constraint);

*/
selectViolations += andNoMatchingPrimaryKeyExists(constraint);
selectViolations += andNoMatchingPrimaryKeyExists(constraint, mainQueryAlias);
resultArtifacts[identifier] = selectViolations;

@@ -156,5 +160,6 @@ });

* @param {CSN.ReferentialConstraint} constraint
* @param {string} mainQueryAlias
* @returns AND NOT EXISTS ( SELECT * FROM <parent_table> WHERE <dependent_table>.<foreign_key> = <parent_table>.<parent_key> ) statement
*/
function andNoMatchingPrimaryKeyExists(constraint) {
function andNoMatchingPrimaryKeyExists(constraint, mainQueryAlias) {
let andNotExists = `\n${indent}AND NOT EXISTS (\n`;

@@ -164,3 +169,2 @@ andNotExists += `${increaseIndent(indent)}SELECT * FROM ${quoteAndGetResultingName(constraint.parentTable)}`;

const subQueryAlias = '"SUB"';
const mainQueryAlias = '"MAIN"';
andNotExists += ` AS ${subQueryAlias}`;

@@ -172,3 +176,3 @@ andNotExists += '\n';

.reduce(joinListReducer,
`${mainQueryAlias}.${quoteSqlId(constraint.foreignKey[0])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[0])}`);
`"${mainQueryAlias}".${quoteSqlId(constraint.foreignKey[0])} = ${subQueryAlias}.${quoteSqlId(constraint.parentKey[0])}`);
andNotExists += `\n${increaseIndent(indent)})`;

@@ -191,6 +195,5 @@ andNotExists += `\n${indent});`;

if (artifact.$tableConstraints && artifact.$tableConstraints.referential) {
Object.entries(artifact.$tableConstraints.referential)
.forEach(([ identifier, referentialConstraint ]) => {
referentialConstraints[identifier] = referentialConstraint;
});
forEach(artifact.$tableConstraints.referential, (identifier, referentialConstraint) => {
referentialConstraints[identifier] = referentialConstraint;
});
}

@@ -197,0 +200,0 @@ });

@@ -22,5 +22,3 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

const { implicitAs } = require('../../model/csnRefs');
const { isBetaEnabled } = require('../../base/model');
/**

@@ -42,3 +40,3 @@ * Render the given function

/**
* Checks wether the given function is to be rendered without parentheses
* Checks whether the given function is to be rendered without parentheses
*

@@ -337,3 +335,3 @@ * @param {object} node Content of the function

/**
* Check wether the given artifact or element has a comment that needs to be rendered.
* Check whether the given artifact or element has a comment that needs to be rendered.
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.

@@ -370,9 +368,6 @@ *

function getSqlSnippets(options, obj) {
if (isBetaEnabled(options, 'sqlSnippets')) {
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
return { front, back };
}
return { front: '', back: '' };
return { front, back };
}

@@ -379,0 +374,0 @@

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

const { smartId, delimitedId } = require('../../sql-identifier');
const { ModelError } = require('../../base/error');

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

* @param {string} indent Indent to render the SQL with
* @param {boolean} toUpperCase Wether to uppercase the identifier
* @param {boolean} toUpperCase Whether to uppercase the identifier
* @param {CSN.Model} csn CSN

@@ -126,3 +127,3 @@ * @param {CSN.Options} options is needed for the naming mode and the sql dialect

if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
throw new Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);

@@ -138,3 +139,3 @@

default:
throw new Error(`No matching rendering found for naming mode ${options.toSql.names}`);
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
}

@@ -141,0 +142,0 @@ }

@@ -12,3 +12,3 @@ // API functions returning the SQL identifier token text for a name

// - 'quoted' and 'hdbcds' for HANA only: similar to the non-provided previous
// mode, with different adoptations to HANA CDS and XS (classic) restrictions.
// mode, with different adaptations to HANA CDS and XS (classic) restrictions.

@@ -15,0 +15,0 @@ // The main objective of this file is to support the 'plain' mode in a “smart”

@@ -16,2 +16,4 @@ {

"jsdoc/require-returns-description": "off",
// Sometimes if-else's are more specific
"sonarjs/prefer-single-boolean-return": "off",
// Very whiny and nitpicky

@@ -18,0 +20,0 @@ "sonarjs/cognitive-complexity": "off",

@@ -21,8 +21,7 @@ 'use strict';

* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
* @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
* @param {applyTransformationsOptions} [options={}]
* @param {CSN.Path} path Path to parent
* @returns {object} parent with transformations applied
*/
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, skipIgnore, options, path = []) {
function applyTransformationsInternal(parent, prop, customTransformers, artifactTransformers, options, path = []) {
const transformers = {

@@ -36,2 +35,3 @@ elements: dictionary,

ref: pathRef,
$origin: () => {}, // no-op
};

@@ -55,3 +55,8 @@

function standard( _parent, _prop, node ) {
if (!node || typeof node !== 'object' || !{}.propertyIsEnumerable.call( _parent, _prop ) || (typeof _prop === 'string' && _prop.startsWith('@')) || (skipIgnore && node._ignore))
if (!node || typeof node !== 'object' ||
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
(typeof _prop === 'string' && _prop.startsWith('@')) ||
(options.skipIgnore && node._ignore) ||
(options.skipStandard && options.skipStandard[_prop])
)
return;

@@ -86,3 +91,3 @@

// Allow skipping dicts like actions in forHanaNew
if (options.skipDict && options.skipDict[_prop])
if (options.skipDict && options.skipDict[_prop] || dict === null || dict === undefined) // with universal CSN, dicts might be null
return;

@@ -108,3 +113,6 @@ csnPath.push( _prop );

for (const name of Object.getOwnPropertyNames( dict )) {
const skip = options && options.skipArtifact && options.skipArtifact(dict[name], name) || false;
const skip = (options && options.allowArtifact && !options.allowArtifact(dict[name], name)) ||
(options && options.skipArtifact && options.skipArtifact(dict[name], name)) ||
(options && options.skip && options.skip.includes(dict[name].kind)) ||
false;
if (!skip) {

@@ -160,9 +168,11 @@ artifactTransformers.forEach(fn => fn(dict, name, dict[name]));

* @param {Function[]} [artifactTransformers=[]] Transformations to run on the artifacts, like forEachDefinition
* @param {boolean} [skipIgnore=true] Wether to skip _ignore elements or not
* @param {object} [options={}] "skipArtifact": (artifact, name) => Boolean to skip certain artifacts, drillRef: boolean - whether to drill into infix/args
* @param {applyTransformationsOptions} [options={}]
* @returns {object} CSN with transformations applied
*/
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], skipIgnore = true, options = {} ) {
function applyTransformations( csn, customTransformers = {}, artifactTransformers = [], options = { } ) {
if (options.skipIgnore === undefined)
options.skipIgnore = true;
if (csn && csn.definitions)
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, skipIgnore, options);
return applyTransformationsInternal(csn, 'definitions', customTransformers, artifactTransformers, options);
return csn;

@@ -185,7 +195,8 @@ }

* @param {object} customTransformers Map of prop to transform and function to apply
* @param {applyTransformationsOptions} [options={}]
* @param {CSN.Path} path Path pointing to parent
* @returns {object} parent[prop] with transformations applied
*/
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, path = []) {
return applyTransformationsInternal(parent, prop, customTransformers, [], true, {}, path)[prop];
function applyTransformationsOnNonDictionary(parent, prop, customTransformers = {}, options = {}, path = []) {
return applyTransformationsInternal(parent, prop, customTransformers, [], options, path)[prop];
}

@@ -197,1 +208,13 @@

};
/**
* @typedef {object} applyTransformationsOptions
* @property {(artifact, name) => boolean} [allowArtifact] to only allow certain artifacts
* @property {(artifact, name) => boolean} [skipArtifact] to skip certain artifacts
* @property {boolean} [drillRef] whether to drill into infix/args
* @property {string[]} [skip] skip definitions from certain kind
* @property {object} [skipStandard] stop drill-down on certain "standard" props
* @property {object} [skipDict] stop drill-down on certain "dictionary" props
* @property {boolean} [skipIgnore=true] Whether to skip _ignore elements or not
*/

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

const [ head, ...tail ] = ref;
if ([ '$self', '$projection' ].includes(head))
if (head === '$self' || head === '$projection')
ref = tail;

@@ -164,0 +164,0 @@ return {

'use strict';
const {
cloneCsn, forEachGeneric,
forAllElements,
applyTransformationsOnNonDictionary,
applyTransformations,
getUtils,
} = require('../../model/csnUtils');
const transformUtils = require('../transformUtilsNew');
const { setProp } = require('../../base/model');
/**
* Return a callback function for forEachDefinition that flattens the foreign keys of managed associations.
* Foreign keys that are managed associations are replaced by their respective foreign keys.
* In all .elements of entities and views (and their bound actions/functions), create the on-condition for
* a managed associations. This needs to happen after the .keys are expanded and the corresponding elements are created.
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
* @param {string} pathDelimiter
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
* @returns {CSN.Model} Return the input csn, with the transformations applied
*/
function getForeignKeyFlattener(csn, options, pathDelimiter) {
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
function attachOnConditions(csn, pathDelimiter) {
const {
isManagedAssociationElement,
isStructured,
inspectRef,
isManagedAssociation,
} = getUtils(csn);
const {
flattenStructuredElement,
flattenStructStepsInRef,
} = transformUtils.getTransformers(csn, options, pathDelimiter);
return handleManagedAssociationFKs;
/**
* Flatten and create the foreign key elements of managed associaitons
*
* @param {CSN.Artifact} art
* @param {string} artName
*/
function handleManagedAssociationFKs(art, artName) {
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
forAllElements(art, artName, (parent, elements, pathToElements) => {
forEachGeneric(parent, 'elements', (element, elemName) => {
if (element.keys && isManagedAssociationElement(element)) {
// replace foreign keys that are managed associations by their respective foreign keys
flattenFKs(element, elemName, [ ...pathToElements, elemName ]);
}
});
});
}
}
/**
* Flattens all foreign keys
*
* Structures will be resolved to individual elements with scalar types
*
* Associations will be replaced by their respective foreign keys
*
* If a structure contains an assoc, this will also be resolved and vice versa
*
* @param {CSN.Element} assoc
* @param {string} assocName
* @param {CSN.Path} path
*/
function flattenFKs(assoc, assocName, path) {
let finished = false;
while (!finished) {
const newKeys = [];
finished = processKeys(assoc, assocName, path, newKeys);
assoc.keys = newKeys;
}
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
}
/**
* Walk over the keys and replace structures by their leafs, managed associations by their foreign keys and keep scalar values as-is.
*
* @param {CSN.Element} assoc
* @param {string} assocName
* @param {CSN.Path} path
* @param {object[]} collector New keys array to collect the flattened stuff in
* @returns {boolean} True if all keys are scalar - false if there are things that still need to be processed.
*/
function processKeys(assoc, assocName, path, collector) {
let finished = true;
for (let i = 0; i < assoc.keys.length; i++) {
const pathToKey = path.concat([ 'keys', i ]);
const { art } = inspectRef(pathToKey);
const { ref } = assoc.keys[i];
if (isStructured(art)) {
finished = false;
// Mark this element to filter it later - not needed after expansion
setProp(assoc.keys[i], '$toDelete', true);
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
Object.keys(flat).forEach((flatElemName) => {
const key = assoc.keys[i];
const clone = cloneCsn(assoc.keys[i], options);
if (clone.as) {
const lastRef = clone.ref[clone.ref.length - 1];
// Cut off the last ref part from the beginning of the flat name
const flatBaseName = flatElemName.slice(lastRef.length);
// Join it to the existing table alias
clone.as += flatBaseName;
// do not loose the $ref for nested keys
if (key.$ref) {
let aliasedLeaf = key.$ref[key.$ref.length - 1];
aliasedLeaf += flatBaseName;
setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
}
}
if (clone.ref) {
clone.ref[clone.ref.length - 1] = flatElemName;
// Now we need to properly flatten the whole ref
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
}
if (!clone.as)
clone.as = flatElemName;
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
// Add the newly generated foreign keys to the end - they will be picked up later on
// Recursive solutions run into call stack issues
collector.push(clone);
});
const alreadyHandled = new WeakMap();
applyTransformations(csn, {
elements: (parent, prop, elements) => {
for (const elemName in elements) {
const elem = elements[elemName];
// (140) Generate the ON-condition for managed associations
if (isManagedAssociation(elem))
transformManagedAssociation(elem, elemName);
}
else if (art.target) {
finished = false;
// Mark this element to filter it later - not needed after expansion
setProp(assoc.keys[i], '$toDelete', true);
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
// Add the newly generated foreign keys to the end - they will be picked up later on
// Recursive solutions run into call stack issues
art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i])));
}
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
collector.push(assoc.keys[i]);
}
else {
collector.push(assoc.keys[i]);
}
}
return finished;
}
}, /* only for views and entities */
}, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity') });
/**
* Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
*
* @param {object} key A foreign key entry (of a managed assoc as a fk of another assoc)
* @param {object} base The fk-ref that has key as a fk
* @returns {object} The clone of base
*/
function cloneAndExtendRef(key, base) {
const { ref } = base;
const clone = cloneCsn(base, options);
if (key.ref) {
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
// Later on, after we know that these foreign key elements are created, we replace ref with this $ref
let $ref;
if (base.$ref) {
// if a base $ref is provided, use it to correctly resolve association chains
const refChain = [ base.$ref[base.$ref.length - 1] ].concat(key.as || key.ref);
$ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain);
}
else {
$ref = base.ref.concat( key.as || key.ref); // Keep along the aliases
}
setProp(clone, '$ref', $ref);
clone.ref = clone.ref.concat(key.ref);
}
return csn;
if (!clone.as && clone.ref && clone.ref.length > 0)
clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
else
clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
return clone;
}
}
/**
* Return a callback function for forEachDefinition that
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
* @param {string} pathDelimiter
* @param {object} messageFunctions
* @param {Function} messageFunctions.error
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
*/
function getForeignKeyElementCreator(csn, options, pathDelimiter, messageFunctions) {
const { error } = messageFunctions;
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
const {
isManagedAssociationElement,
} = getUtils(csn);
const {
flattenStructStepsInRef, getForeignKeyArtifact,
} = transformUtils.getTransformers(csn, options, pathDelimiter);
return createForeignKeyElements;
/**
* Create the foreign key elements for managed associations.
* Create them in-place, right after the corresponding association.
*
*
* @param {CSN.Artifact} art
* @param {string} artName
*/
function createForeignKeyElements(art, artName) {
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
forAllElements(art, artName, (parent, elements, pathToElements) => {
const elementsArray = [];
forEachGeneric(parent, 'elements', (element, elemName) => {
elementsArray.push([ elemName, element ]);
if (element.keys && isManagedAssociationElement(element)) {
for (let i = 0; i < element.keys.length; i++) {
const foreignKey = element.keys[i];
const path = [ ...pathToElements, elemName, 'keys', i ];
foreignKey.ref = flattenStructStepsInRef(foreignKey.ref, path);
const [ fkName, fkElem ] = getForeignKeyArtifact(element, elemName, foreignKey, path);
if (parent.elements[fkName]) {
error(null, [ ...pathToElements, elemName ], { name: fkName, art: elemName },
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
}
else {
elementsArray.push([ fkName, fkElem ]);
}
applyCachedAlias(foreignKey);
// join ref array as the struct / assoc steps are not necessary anymore
foreignKey.ref = [ foreignKey.ref.join(pathDelimiter) ];
}
}
});
// Don't fake consistency of the model by adding empty elements {}
if (elementsArray.length === 0)
return;
parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
previous[name] = element;
return previous;
}, Object.create(null));
});
}
/**
* We save the aliased representation of the fk in $ref - I suppose to stay resolvable?
*
* We now use this $ref for ref and delete it.
*
* @param {object} foreignKey
* @todo With nested projections, we solve similar problems, maybe adapt?
*/
function applyCachedAlias(foreignKey) {
// If we have a $ref use that - it resolves aliased FKs correctly
if (foreignKey.$ref) {
foreignKey.ref = foreignKey.$ref;
delete foreignKey.$ref;
}
}
}
}
/**
* Return a callback function for forEachDefinition that
*
* @param {CSN.Model} csn
* @param {CSN.Options} options
* @param {string} pathDelimiter
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
*/
function getManagedAssociationTransformer(csn, options, pathDelimiter) {
const doA2J = !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds');
const {
isManagedAssociationElement,
} = getUtils(csn);
return handleAssociations;
/**
*
* Generate foreign keys for managed associations
* Forbid aliases for foreign keys
*
* @param {CSN.Artifact} artifact
* @param {string} artifactName
*/
function handleAssociations(artifact, artifactName) {
// Do things specific for entities and views (pass 1)
if (artifact.kind === 'entity' || artifact.kind === 'view') {
const alreadyHandled = new WeakMap();
forAllElements(artifact, artifactName, (parent, elements) => {
for (const elemName in elements) {
const elem = elements[elemName];
// (140) Generate foreign key elements and ON-condition for managed associations
// (unless explicitly asked to keep assocs unchanged)
if (doA2J && isManagedAssociationElement(elem))
transformManagedAssociation(parent, artifactName, elem, elemName, alreadyHandled);
}
});
}
}
/**
* Create the foreign key elements for a managed association and build the on-condition
*
* @param {CSN.Artifact} artifact
* @param {string} artifactName
* @param {Object} elem The association to process
* @param {string} elemName
* @param {WeakMap} alreadyHandled To cache which elements were already processed
* @returns {void}
*/
function transformManagedAssociation(artifact, artifactName, elem, elemName, alreadyHandled) {
function transformManagedAssociation(elem, elemName) {
// No need to run over this - we already did, possibly because it was referenced in the ON-Condition

@@ -327,6 +49,6 @@ // of another association - see a few lines lower

return;
// Generate foreign key elements for managed associations, and assemble an ON-condition with them
// Assemble an ON-condition with the foreign keys created in earlier steps
const onCondParts = [];
let joinWithAnd = false;
if (elem.keys.length === 0) {
if (elem.keys.length === 0) { // TODO: really kill instead of _ignore?
elem._ignore = true;

@@ -370,3 +92,2 @@ }

// If the managed association has a 'not null' property => remove it

@@ -376,6 +97,6 @@ if (elem.notNull)

// The association is now unmanaged, i.e. actually it should no longer have foreign keys
// at all. But the processing of backlink associations below expects to have them, so
// we don't delete them (but mark them as implicit so that toCdl does not render them)
// we don't delete them
// TODO: maybe make non-enumerable, so we become recompilable in the future?

@@ -387,7 +108,84 @@ // Remember that we already processed this

/**
* @param {CSN.Model} csn
* @param {string} pathDelimiter
* @returns {(artifact: CSN.Artifact, artifactName: string) => void} Callback for forEachDefinition
*/
function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
const {
inspectRef,
} = getUtils(csn);
return handleManagedAssocStepsInOnCondition;
/**
* Loop over all elements and for all unmanaged associations translate
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
*
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
*
* @param {CSN.Artifact} artifact Artifact to check
* @param {string} artifactName Name of the artifact
*/
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
for (const elemName in artifact.elements) {
const elem = artifact.elements[elemName];
// The association is an unmanaged on
if (!elem.keys && elem.target && elem.on) {
applyTransformationsOnNonDictionary(elem, 'on', {
ref: (refOwner, prop, ref, path) => {
// [<assoc base>.]<managed assoc>.<field>
if (ref.length > 1) {
const { links } = inspectRef(path);
if (links) {
// eslint-disable-next-line for-direction
for (let i = links.length - 1; i >= 0; i--) {
const link = links[i];
// We found the latest managed assoc path step
if (link.art && link.art.target && link.art.keys &&
// Doesn't work when ref-target (filter condition) or similar is used
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
// We join the managed assoc with everything following it
const sourceElementName = ref.slice(i).join(pathDelimiter);
const source = findSource(links, i - 1) || artifact;
// allow specifying managed assoc on the source side
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
if (fks && fks.length >= 1) {
const fk = fks[0];
const managedAssocStepName = refOwner.ref[i];
const fkName = `${managedAssocStepName}${pathDelimiter}${fk.as}`;
if (source && source.elements[fkName])
refOwner.ref = [ ...ref.slice(0, i), fkName ];
}
}
}
}
}
},
}, {}, [ 'definitions', artifactName, 'elements', elemName ]);
}
}
/**
* Find out where the managed association is
*
* @param {Array} links
* @param {number} startIndex
* @returns {Object| undefined} CSN definition of the source of the managed association
*/
function findSource(links, startIndex) {
for (let i = startIndex; i >= 0; i--) {
const link = links[i];
// We found the latest assoc step - now check where that points to
if (link.art && link.art.target)
return csn.definitions[link.art.target];
}
return undefined;
}
}
}
module.exports = {
getForeignKeyFlattener,
getForeignKeyElementCreator,
getManagedAssociationTransformer,
attachOnConditions,
getManagedAssocStepsInOnConditionFinalizer,
};

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

function handleCdsPersistence(artifact) {
const ignoreArtifact = (artifact.kind === 'entity' || artifact.kind === 'view') &&
const ignoreArtifact = (artifact.kind === 'entity') &&
(artifact.abstract ||

@@ -95,3 +95,3 @@ hasAnnotationValue(artifact, '@cds.persistence.skip') ||

/**
* Check wether the given artifact is an unreachable association target because it will not "realy" hit the database:
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
* - @cds.persistence.skip/exists

@@ -98,0 +98,0 @@ * - abstract

'use strict';
const { forEachDefinition } = require('../../base/model');
const { forAllElements, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
const { applyTransformations, hasAnnotationValue, getResultingName } = require('../../model/csnUtils');
const { csnRefs } = require('../../model/csnRefs');
const { forEach, forEachKey } = require('../../utils/objectUtils');

@@ -31,30 +32,32 @@ const COMPOSITION = 'cds.Composition';

const associations = [];
forEachDefinition(csn, (artifact, artifactName) => {
if (!artifact.query && artifact.kind === 'entity') {
forAllElements(artifact, artifactName, (parent, elements, path) => {
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
for (const elementName in elements) {
const element = elements[elementName];
if (element.type === COMPOSITION && element.$selfOnCondition) {
compositions.push({
fn: () => {
foreignKeyConstraintForUpLinkOfComposition(element, parent, path.concat([ elementName ]));
},
});
}
applyTransformations(csn, {
elements: (parent, prop, elements, path) => {
// Step I: iterate compositions, enrich dependent keys for <up_> association in target entity of composition
for (const elementName in elements) {
const element = elements[elementName];
const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
if (element.type === COMPOSITION && element.$selfOnCondition) {
compositions.push({
fn: () => {
foreignKeyConstraintForUpLinkOfComposition(element, parent, ePath);
},
});
}
// Step II: iterate associations, enrich dependent keys (in entity containing the association)
for (const elementName in elements) {
const element = elements[elementName];
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
associations.push({
fn: () => {
foreignKeyConstraintForAssociation(element, path.concat([ elementName ]));
},
});
}
}
// Step II: iterate associations, enrich dependent keys (in entity containing the association)
for (const elementName in elements) {
const element = elements[elementName];
const ePath = path.concat([ 'elements', elementName ]); // Save a copy in this scope for the late callback
if (element.keys && isToOne(element) && element.type === ASSOCIATION || element.type === COMPOSITION && treatCompositionLikeAssociation(element)) {
associations.push({
fn: () => {
foreignKeyConstraintForAssociation(element, ePath );
},
});
}
});
}
});
}
},
}, [], { skipIgnore: false, skipArtifact: a => a.query || a.kind !== 'entity' });
// create constraints on foreign keys

@@ -219,4 +222,3 @@ // always process unmanaged first, up_ links must be flagged

* - a query
* - TODO: Revisit -- annotated with '@cds.persistence.skip:true'
* - TODO: Revisit -- annotated with '@cds.persistence.exists:true'
* - annotated with '@cds.persistence.skip:true'
*

@@ -496,14 +498,16 @@ * The following decision table reflects the current implementation:

onDeleteRules.add($foreignKeyConstraint.onDelete);
// find all other $foreignKeyConstraint with same $sourceAssociation and same parentTable
Object.entries(artifact.elements)
.filter(([ , e ]) => e.$foreignKeyConstraint &&
e.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
e.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable)
.forEach(([ foreignKeyName, foreignKey ]) => {
const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
delete foreignKey.$foreignKeyConstraint;
parentKey.push($foreignKeyConstraintCopy.parentKey);
dependentKey.push(foreignKeyName);
onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
});
forEach(artifact.elements, (foreignKeyName, foreignKey) => {
// find all other `$foreignKeyConstraint`s with same `$sourceAssociation` and same `parentTable`
const matchingForeignKeyFound = foreignKey.$foreignKeyConstraint &&
foreignKey.$foreignKeyConstraint.sourceAssociation === $foreignKeyConstraint.sourceAssociation &&
foreignKey.$foreignKeyConstraint.parentTable === $foreignKeyConstraint.parentTable;
if (!matchingForeignKeyFound)
return;
const $foreignKeyConstraintCopy = Object.assign({}, foreignKey.$foreignKeyConstraint);
delete foreignKey.$foreignKeyConstraint;
parentKey.push($foreignKeyConstraintCopy.parentKey);
dependentKey.push(foreignKeyName);
onDeleteRules.add($foreignKeyConstraintCopy.onDelete);
});
// onDelete Rule is the "weakest" rule applicable. Precedence: RESTRICT > SET NULL > CASCADE

@@ -516,3 +520,4 @@ const onDelete = onDeleteRules.has('RESTRICT') ? 'RESTRICT' : 'CASCADE';

referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
identifier: `${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
// constraint identifier start with `c__` to avoid name clashes
identifier: `c__${getResultingName(csn, options.forHana.names, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
foreignKey: dependentKey,

@@ -524,3 +529,2 @@ parentKey,

onDeleteRemark, // explain why this particular rule is chosen
// TODO: do we want to switch off validation / enforcement via annotation on association?
validated: $foreignKeyConstraint.validated,

@@ -553,13 +557,12 @@ enforced: $foreignKeyConstraint.enforced,

Object.keys(artifact.$tableConstraints.unique)
.map(id => `${artifactName}_${id}`) // final unique constraint identifier will be generated in renderer likewise
.forEach((uniqueConstraintIdentifier) => {
if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
error(null, path,
{ name: uniqueConstraintIdentifier, art: artifactName },
'Duplicate constraint name $(NAME) in artifact $(ART)');
}
});
forEachKey(artifact.$tableConstraints.unique, (uniqueConstraintKey) => {
const uniqueConstraintIdentifier = `${artifactName}_${uniqueConstraintKey}`; // final unique constraint identifier will be generated in renderer likewise
if (artifact.$tableConstraints.referential[uniqueConstraintIdentifier]) {
error(null, path,
{ name: uniqueConstraintIdentifier, art: artifactName },
'Duplicate constraint name $(NAME) in artifact $(ART)');
}
});
}
module.exports = { createReferentialConstraints, assertConstraintIdentifierUniqueness };

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

* @param {Function} messageFunctions.throwWithError
* @param {object} iterateOptions
*/
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }) {
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithError }, iterateOptions = {}) {
const {

@@ -43,3 +44,7 @@ isStructured, get$combined, getFinalBaseType, getServiceName,

const root = get$combined({ SELECT: parent });
parent.columns = replaceStar(root, columns, parent.excluding);
// TODO: replace with the correct options.transformation?
// Do not expand the * in OData for a moment, not to introduce changes
// while the OData CSN is still official
if (!options.toOdata)
parent.columns = replaceStar(root, columns, parent.excluding);
parent.columns = expand(parent.columns, path.concat('columns'), true);

@@ -54,3 +59,3 @@ }

},
});
}, [], iterateOptions);

@@ -77,3 +82,6 @@ /**

parent.columns = rewritten.columns;
if (rewritten.toMany.length > 0) {
/*
* Do not remove unexpandable many columns in OData
*/
if (rewritten.toMany.length > 0 && !options.toOdata) {
markAsToDummyfy(artifact, path[1]);

@@ -83,2 +91,5 @@ if (getServiceName(path[1]) === null)

}
else {
parent.columns = rewritten.columns;
}
}

@@ -88,3 +99,6 @@ },

dummyfy();
// OData must keep @cds.persistence.skip definitions
// to present them in the API (and CSN)
if (!options.toOdata)
dummyfy();

@@ -97,21 +111,24 @@ cleanup.forEach(fn => fn());

const publishing = [];
// OData must allow navigations to @cds.persistence.skip targets
// as valid navigations in the API
if (!options.toOdata) {
applyTransformations(csn, {
target: (parent, name, target, path) => {
if (toDummyfy.indexOf(target) !== -1) {
publishing.push({
parent, name, target, path: [ ...path ],
});
}
},
from: check,
columns: check,
where: check,
groupBy: check,
orderBy: check,
having: check,
limit: check,
});
}
applyTransformations(csn, {
target: (parent, name, target, path) => {
if (toDummyfy.indexOf(target) !== -1) {
publishing.push({
parent, name, target, path: [ ...path ],
});
}
},
from: check,
columns: check,
where: check,
groupBy: check,
orderBy: check,
having: check,
limit: check,
});
/**

@@ -279,3 +296,3 @@ * Check for usage of associations to skipped.

/**
* Check wether the given object is a to-many association
* Check whether the given object is a to-many association
*

@@ -312,2 +329,7 @@ * @param {CSN.Element} obj

if (isToMany(current) && current.expand) {
expanded.push({
expand: current.expand,
ref: currentRef,
as: currentAlias.join(pathDelimiter),
});
toManys.push({ art: current, ref: currentRef, as: currentAlias.join(pathDelimiter) });

@@ -447,3 +469,3 @@ }

* @param {CSN.Path} path
* @param {boolean} [withAlias=false] Wether to "expand" the (implicit) alias aswell.
* @param {boolean} [withAlias=false] Whether to "expand" the (implicit) alias aswell.
* @returns {Array} New array - with all structured things expanded

@@ -450,0 +472,0 @@ */

'use strict';
const {
forEachDefinition, getUtils,
applyTransformations, forAllElements, isBuiltinType,
getUtils, walkCsnPath,
applyTransformations, applyTransformationsOnNonDictionary,
isBuiltinType, cloneCsn,
copyAnnotations, implicitAs, isDeepEqual,
} = require('../../model/csnUtils');

@@ -18,22 +20,17 @@ const transformUtils = require('../transformUtilsNew');

const magicVars = [ '$now', '$self', '$projection', '$user', '$session', '$at' ];
forEachDefinition(csn, (artifact, artifactName) => {
if (artifact.kind === 'entity' || artifact.kind === 'view') {
forAllElements(artifact, artifactName, (parent, elements) => {
for (const [ elementName, element ] of Object.entries(elements)) {
if (element.on) {
// applyTransformations expects the first thing to have a "definitions"
const fakeDefinitions = { definitions: {} };
fakeDefinitions.definitions[elementName] = element;
applyTransformations( fakeDefinitions, {
ref: (root, name, ref) => {
// Renderers seem to expect it to not be there...
if (ref[0] === '$self' && ref.length > 1 && !magicVars.includes(ref[1]))
root.ref = ref.slice(1);
},
});
}
applyTransformations(csn, {
elements: (parent, prop, elements) => {
for (const [ elementName, element ] of Object.entries(elements)) {
if (element.on) {
applyTransformationsOnNonDictionary(elements, elementName, {
ref: (root, name, ref) => {
// Renderers seem to expect it to not be there...
if (ref[0] === '$self' && ref.length > 1 && !magicVars.includes(ref[1]))
root.ref = ref.slice(1);
},
});
}
});
}
});
}
}, /* only for kind entity and view */ /* do not go into .actions */
}, [], { skipIgnore: false, allowArtifact: artifact => (artifact.kind === 'entity'), skipDict: { actions: true } });
}

@@ -50,4 +47,5 @@

* @param {string} pathDelimiter
* @param {object} iterateOptions
*/
function resolveTypeReferences(csn, options, resolved, pathDelimiter) {
function resolveTypeReferences(csn, options, resolved, pathDelimiter, iterateOptions = {}) {
/**

@@ -74,2 +72,9 @@ * Remove .localized from the element and any sub-elements

const { toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { getServiceName, getFinalBaseType } = getUtils(csn);
// We don't want to iterate over actions
if (iterateOptions.skipDict && !iterateOptions.skipDict.actions)
iterateOptions.skipDict.actions = true;
else
iterateOptions.skipDict = { actions: true };
applyTransformations(csn, {

@@ -81,5 +86,7 @@ cast: (parent) => {

},
type: (parent, prop, type) => {
if (!isBuiltinType(type)) {
const directLocalized = parent.localized || false;
// @ts-ignore
type: (parent, prop, type, csnPath) => {
if (options.toOdata && parent.kind && [ 'aspect', 'event', 'type' ].includes(parent.kind))
return;
if (!isBuiltinType(type) && (!options.toOdata || options.toOdata && !isODataV4BuiltinFromService(type) && !isODataItems(type))) {
toFinalBaseType(parent, resolved);

@@ -101,16 +108,53 @@ // structured types might not have the child-types replaced.

}
if (!directLocalized)
const directLocalized = parent.localized || false;
if (!directLocalized && !options.toOdata)
removeLocalized(parent);
}
// HANA/SQLite do not support array-of - turn into CLOB/Text
if (parent.items) {
if (parent.items && !options.toOdata) {
parent.type = 'cds.LargeString';
delete parent.items;
}
/**
* OData V4 only:
* Do not replace a type ref if:
* The type definition is terminating on a scalar type (that can also be a derived type chain)
* AND the typeName (that is the start of that (derived) type chain is defined within the same
* service as the artifact from which the type reference has to be resolved.
*
* @param {string} typeName
* @returns {boolean}
*/
function isODataV4BuiltinFromService(typeName) {
if (!options.toOdata || (options.toOdata && options.toOdata.version === 'v2'))
return false;
const typeServiceName = getServiceName(typeName);
const finalBaseType = getFinalBaseType(typeName);
// we need the service of the current definition
const currDefServiceName = getServiceName(csnPath[1]);
return typeServiceName === currDefServiceName && isBuiltinType(finalBaseType);
}
/**
* OData stops replacing types @ 'items', if the type ref is a user defined type
* AND that type has items, don't do toFinalBaseType
*
* @param {string} typeName
* @returns {boolean}
*/
function isODataItems(typeName) {
const typeDef = csn.definitions[typeName];
return !!(options.toOdata && typeDef && typeDef.items);
}
},
// HANA/SQLite do not support array-of - turn into CLOB/Text
items: (parent) => {
parent.type = 'cds.LargeString';
delete parent.items;
// OData has no LargeString substitution and doesn't expand types under items
if (!options.toOdata) {
parent.type = 'cds.LargeString';
delete parent.items;
}
},

@@ -120,3 +164,7 @@ }, [ (definitions, artifactName, artifact) => {

// and that way they contain no references and don't hurt.
if (artifact.kind === 'action' || artifact.kind === 'function' || artifact.kind === 'event') {
// Do not do for OData
// TODO:factor out somewhere else
if (!options.toOdata &&
([ 'action', 'function', 'event' ].includes(artifact.kind))) {
const dummy = { kind: artifact.kind };

@@ -128,3 +176,4 @@ if (artifact.$location)

}
} ], true, { skipDict: { actions: true } });
// TODO: skipDict options as default function arguments not via Object.assign
} ], iterateOptions);
}

@@ -137,4 +186,5 @@

* @param {string} pathDelimiter
* @param {object} iterateOptions
*/
function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter) {
function flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter, iterateOptions = {}) {
const { inspectRef, effectiveType } = csnRefs(csn);

@@ -164,2 +214,3 @@ const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);

applyTransformations(csn, {
// @ts-ignore
ref: (parent, prop, ref, path) => {

@@ -173,3 +224,3 @@ const { links, art, scope } = inspectRef(path);

parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes );
parent.ref = flattenStructStepsInRef(ref, scopedPath, links, scope, resolvedLinkTypes);
resolved.set(parent, { links, art, scope });

@@ -191,3 +242,3 @@ // Explicitly set implicit alias for things that are now flattened - but only in columns

},
});
}, [], iterateOptions);

@@ -221,84 +272,98 @@ adaptRefs.forEach(fn => fn());

* @param {Function} error
* @param {Object} iterateOptions
*/
function flattenElements(csn, options, pathDelimiter, error) {
function flattenElements(csn, options, pathDelimiter, error, iterateOptions = {}) {
const { isAssocOrComposition } = getUtils(csn);
const { flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { effectiveType } = csnRefs(csn);
forEachDefinition(csn, flattenStructuredElements);
const transformers = {
elements: flatten,
};
if (options.toOdata) // Odata needs to flatten the .params as if it was a .elements
transformers.params = flatten;
applyTransformations(csn, transformers, [], iterateOptions);
/**
* Flatten structures
* Flatten a given .elements or .params dictionary - keeping the order consistent.
*
* @param {CSN.Artifact} art Artifact
* @param {string} artName Artifact Name
* @param {object} parent The parent object having dict at prop - parent[prop] === dict
* @param {string} prop
* @param {object} dict
* @param {CSN.Path} path
*/
function flattenStructuredElements(art, artName) {
forAllElements(art, artName, (parent, elements, pathToElements) => {
const elementsArray = [];
for (const elemName in elements) {
const pathToElement = pathToElements.concat([ elemName ]);
const elem = parent.elements[elemName];
elementsArray.push([ elemName, elem ]);
if (elem.elements) {
elementsArray.pop();
// Ignore the structured element, replace it by its flattened form
// TODO: use $ignore - _ is for links
elem._ignore = true;
function flatten(parent, prop, dict, path) {
if (!parent[prop].$orderedElements)
setProp(parent[prop], '$orderedElements', []);
Object.entries(dict).forEach(([ elementName, element ]) => {
if (element.elements) {
// Ignore the structured element, replace it by its flattened form
// TODO: use $ignore - _ is for links
element._ignore = true;
const branches = getBranches(elem, elemName);
const flatElems = flattenStructuredElement(elem, elemName, [], pathToElement);
const branches = getBranches(element, elementName);
const flatElems = flattenStructuredElement(element, elementName, [], path.concat([ 'elements', elementName ]));
for (const flatElemName in flatElems) {
if (parent.elements[flatElemName])
error(null, pathToElement, `"${artName}.${elemName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
for (const flatElemName in flatElems) {
if (parent[prop][flatElemName])
// TODO: combine message ID with generated FK duplicate
// do the duplicate check in the consruct callback, requires to mark generated flat elements,
// check: Error location should be the existing element like @odata.foreignKey4
error(null, path.concat([ 'elements', elementName ]), `"${path[1]}.${elementName}": Flattened struct element name conflicts with existing element: "${flatElemName}"`);
const flatElement = flatElems[flatElemName];
const flatElement = flatElems[flatElemName];
// Check if we have a valid notNull chain
const branch = branches[flatElemName];
if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
flatElement.notNull = true;
// Check if we have a valid notNull chain
const branch = branches[flatElemName];
if (flatElement.notNull !== false && !branch.some(s => !s.notNull))
flatElement.notNull = true;
if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
// Make refs resolvable by fixing the first ref step
for (const onPart of flatElement.on) {
if (onPart.ref) {
const firstRef = onPart.ref[0];
if (flatElement.type && isAssocOrComposition(flatElement.type) && flatElement.on) {
// Make refs resolvable by fixing the first ref step
for (const onPart of flatElement.on) {
if (onPart.ref) {
const firstRef = onPart.ref[0];
/*
when element is defined in the current name resolution scope, like
entity E {
key x: Integer;
s : {
y : Integer;
a3 : association to E on a3.x = y;
}
}
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
*/
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
const possibleFlatName = prefix + pathDelimiter + firstRef;
/*
when element is defined in the current name resolution scope, like
entity E {
key x: Integer;
s : {
y : Integer;
a3 : association to E on a3.x = y;
}
}
We need to replace y with s_y and a3 with s_a3 - we must take care to not escape our local scope
*/
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
const possibleFlatName = prefix + pathDelimiter + firstRef;
if (flatElems[possibleFlatName])
onPart.ref[0] = possibleFlatName;
}
if (flatElems[possibleFlatName])
onPart.ref[0] = possibleFlatName;
}
}
elementsArray.push([ flatElemName, flatElement ]);
// Still add them - otherwise we might not detect collisions between generated elements.
parent.elements[flatElemName] = flatElement;
}
parent[prop].$orderedElements.push([ flatElemName, flatElement ]);
// Still add them - otherwise we might not detect collisions between generated elements.
parent[prop][flatElemName] = flatElement;
}
}
// Don't fake consistency of the model by adding empty elements {}
if (elementsArray.length === 0)
return;
else {
parent[prop].$orderedElements.push([ elementName, element ]);
}
});
parent.elements = elementsArray.reduce((previous, [ name, element ]) => {
previous[name] = element;
return previous;
}, Object.create(null));
}, true);
// $orderedElements is removed by reducing and assigning a new dictionary
parent[prop] = parent[prop].$orderedElements.reduce((elements, [ name, element ]) => {
// rewrite $path to match the flattened dictionary entry
// ([ 'definitions', artName ] remain constant
setProp(element, '$path', [ ...path, prop, name ]);
elements[name] = element;
return elements;
}, Object.create(null));
}
/**

@@ -347,2 +412,386 @@ * Get not just the leafs, but all the branches of a structured element

/**
* @param {CSN.Model} csn
* @param {CSN.Options} options
* @param {Function} error
* @param {string} pathDelimiter
* @param {boolean} flattenKeyRefs
* @param {object} iterateOptions
*/
function handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, flattenKeyRefs, iterateOptions = {}) {
const { isManagedAssociation, inspectRef, isStructured } = getUtils(csn);
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
if (flattenKeyRefs) {
applyTransformations(csn, {
elements: (parent, prop, elements, path) => {
Object.entries(elements).forEach(([ elementName, element ]) => {
if (isManagedAssociation(element)) {
// replace foreign keys that are managed associations by their respective foreign keys
flattenFKs(element, elementName, [ ...path, 'elements', elementName ]);
}
});
},
}, [], {
skipIgnore: false,
allowArtifact: artifact => (artifact.kind === 'entity' || artifact.kind === 'type'),
skipDict: { actions: true },
});
}
createForeignKeyElements();
/**
* Flattens all foreign keys
*
* Structures will be resolved to individual elements with scalar types
*
* Associations will be replaced by their respective foreign keys
*
* If a structure contains an assoc, this will also be resolved and vice versa
*
* @param {*} assoc
* @param {*} assocName
* @param {*} path
*/
function flattenFKs(assoc, assocName, path) {
let finished = false;
while (!finished) {
const newKeys = [];
finished = processKeys(newKeys);
assoc.keys = newKeys;
}
// @ts-ignore
/**
* Walk over the keys and replace structures by their leafs, managed associations by their foreign keys and keep scalar values as-is.
*
* @param {object[]} collector New keys array to collect the flattened stuff in
* @returns {boolean} True if all keys are scalar - false if there are things that still need to be processed.
*/
function processKeys(collector) {
const inferredAlias = '$inferredAlias';
let done = true;
for (let i = 0; i < assoc.keys.length; i++) {
const pathToKey = path.concat([ 'keys', i ]);
const { art } = inspectRef(pathToKey);
const { ref } = assoc.keys[i];
if (isStructured(art)) {
done = false;
// Mark this element to filter it later - not needed after expansion
setProp(assoc.keys[i], '$toDelete', true);
const flat = flattenStructuredElement(art, ref[ref.length - 1], [], pathToKey);
Object.keys(flat).forEach((flatElemName) => {
const key = assoc.keys[i];
const clone = cloneCsn(assoc.keys[i], options);
if (clone.as) {
const lastRef = clone.ref[clone.ref.length - 1];
// Cut off the last ref part from the beginning of the flat name
const flatBaseName = flatElemName.slice(lastRef.length);
// Join it to the existing table alias
clone.as += flatBaseName;
// do not loose the $ref for nested keys
if (key.$ref) {
let aliasedLeaf = key.$ref[key.$ref.length - 1];
aliasedLeaf += flatBaseName;
setProp(clone, '$ref', key.$ref.slice(0, key.$ref.length - 1).concat(aliasedLeaf));
}
}
if (clone.ref) {
clone.ref[clone.ref.length - 1] = flatElemName;
// Now we need to properly flatten the whole ref
clone.ref = flattenStructStepsInRef(clone.ref, pathToKey);
}
if (!clone.as) {
clone.as = flatElemName;
// TODO: can we use $inferred? Does it have other weird side-effects?
setProp(clone, inferredAlias, true);
}
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
// Add the newly generated foreign keys to the end - they will be picked up later on
// Recursive solutions run into call stack issues
collector.push(clone);
});
}
else if (art.target) {
done = false;
// Mark this element to filter it later - not needed after expansion
setProp(assoc.keys[i], '$toDelete', true);
// Directly work on csn.definitions - this way the changes take effect in csnRefs/inspectRef immediately
// Add the newly generated foreign keys to the end - they will be picked up later on
// Recursive solutions run into call stack issues
art.keys.forEach(key => collector.push(cloneAndExtendRef(key, assoc.keys[i], ref)));
}
else if (assoc.keys[i].ref && !assoc.keys[i].as) {
setProp(assoc.keys[i], inferredAlias, true);
if (!(options.toOdata && assoc.keys[i].ref.length === 1))
// In OData backend there are no aliases assigned when the same as the ref
// TODO: remove the if after the new flattening in OData has been compleated
assoc.keys[i].as = assoc.keys[i].ref[assoc.keys[i].ref.length - 1];
collector.push(assoc.keys[i]);
}
else {
collector.push(assoc.keys[i]);
}
}
return done;
}
assoc.keys = assoc.keys.filter(o => !o.$toDelete);
/**
* Clone base and extend the .ref and .as of the clone with the .ref and .as of ref.
*
* @param {object} key A foreign key entry (of a managed assoc as a fk of another assoc)
* @param {object} base The fk-ref that has key as a fk
* @param {Array} ref
* @returns {object} The clone of base
*/
function cloneAndExtendRef(key, base, ref) {
const clone = cloneCsn(base, options);
if (key.ref) {
// We build a ref that contains the aliased fk - that element will be created later on, so this ref is not resolvable yet
// Therefore we keep it as $ref - ref is the non-aliased, resolvable "clone"
// Later on, after we know that these foreign key elements are created, we replace ref with this $ref
let $ref;
if (base.$ref) {
// if a base $ref is provided, use it to correctly resolve association chains
const refChain = [ base.$ref[base.$ref.length - 1] ].concat(key.as || key.ref);
$ref = base.$ref.slice(0, base.$ref.length - 1).concat(refChain);
}
else {
$ref = base.ref.concat(key.as || key.ref); // Keep along the aliases
}
setProp(clone, '$ref', $ref);
clone.ref = clone.ref.concat(key.ref);
}
if (!clone.as && clone.ref && clone.ref.length > 0) {
clone.as = ref[ref.length - 1] + pathDelimiter + (key.as || key.ref.join(pathDelimiter));
// TODO: can we use $inferred? Does it have other weird side-effects?
setProp(clone, '$inferredAlias', true);
}
else {
clone.as += pathDelimiter + (key.as || key.ref.join(pathDelimiter));
}
return clone;
}
}
/**
* Create the foreign key elements in all .elements things
*/
function createForeignKeyElements() {
const transformers = {
elements: createFks,
};
if (options.toOdata)
transformers.params = createFks;
// Do not respect _ignore flag for toOdata and toRename (we may have facades for existing HANA CDS
// artifacts that shall still be maintained from outside, filter them away in the rename script)
iterateOptions.skipIgnore = !(options.toOdata || options.toRename);
applyTransformations(csn, transformers, [], iterateOptions);
/**
* Process a given .elements or .params dictionary and create foreign key elements
*
* @param {object} parent The thing HAVING params or elements
* @param {string} prop
* @param {object} dict The params or elements thing
* @param {CSN.Path} path
*/
function createFks(parent, prop, dict, path) {
const orderedElements = [];
Object.entries(dict).forEach(([ elementName, element ]) => {
orderedElements.push([ elementName, element ]);
const eltPath = path.concat(prop, elementName);
const fks = createForeignKeysInternal(eltPath, element, elementName, csn, options, pathDelimiter);
// finalize the generated foreign keys
const refCount = fks.reduce((acc, fk) => {
// count duplicates
if (acc[fk[0]])
acc[fk[0]]++;
else
acc[fk[0]] = 1;
// check for name clash with existing elements
if ((parent[prop][fk[0]]) &&
((options.toOdata && isDeepEqual(element, parent[prop][fk[0]], true)) ||
!options.toOdata)) {
// error location is the colliding element
error(null, eltPath, { name: fk[0], art: elementName },
'Generated foreign key element $(NAME) for association $(ART) conflicts with existing element');
}
// attach a proper $path
setProp(element, '$path', eltPath);
return acc;
}, Object.create(null));
// check for duplicate foreign keys
Object.entries(refCount).forEach(([ name, occ ]) => {
if (occ > 1) {
error(null, eltPath, { name },
'Duplicate definition of foreign key element $(NAME)');
}
});
if (element.keys) {
element.keys.forEach((key, i) => {
// Assumption: If all key refs have been flattened, there is a
// 1:1 match to the corresponding foreign key element. Order is the
// same, so an index access should work
if (flattenKeyRefs) {
key.$generatedFieldName = fks[i][0];
key.ref = [ (key.$ref || key.ref).join(pathDelimiter) ];
delete key.$ref;
// TODO: remove the if after the new flattening in OData has been completed
if (options.toOdata && key.as && key.as === key.ref[0])
delete key.as;
}
});
// OData specific:
// Not Null sets min cardinality to 1
if (options.toOdata && element.notNull) {
if (element.cardinality === undefined)
element.cardinality = {};
// min=0 is falsy => check for undefined
if (element.cardinality.min === undefined)
element.cardinality.min = 1;
}
}
orderedElements.push(...fks);
});
parent[prop] = orderedElements.reduce((elementsAccumulator, [ name, element ]) => {
elementsAccumulator[name] = element;
return elementsAccumulator;
}, Object.create(null));
}
}
}
/**
* This is the internal version of the foreign key procedure.
*
* If element is not a managed association, an empty array is returned
*
* @param {Array|object} path CSN path pointing to element or the result of a previous call to inspectRef
* @param {CSN.Element} element
* @param {string} prefix Element name
* @param {CSN.Model} csn
* @param {object} options
* @param {string} pathDelimiter
* @param {number} lvl
* @returns {Array[]} First element of every sub-array is the foreign key name, second is the foreign key definition
*/
function createForeignKeysInternal(path, element, prefix, csn, options, pathDelimiter, lvl = 0) {
const {
effectiveType,
inspectRef,
} = getUtils(csn);
const isInspectRefResult = !Array.isArray(path);
let fks = [];
if (!element)
return fks;
let finalElement = element;
// TODO: effectiveType's return value is 'path' for the next inspectRef
if (element.type && !isBuiltinType(element.type)) {
const tmpElt = effectiveType(element);
// effective type resolves to structs and enums only but not scalars
if (Object.keys(tmpElt).length) {
finalElement = tmpElt;
}
else {
// unwind a derived type chain to a scalar type
while (finalElement.type && !isBuiltinType(finalElement.type))
finalElement = csn.definitions[finalElement.type];
}
}
if (finalElement.target && !finalElement.on) {
const hasKeys = !!finalElement.keys;
if (!hasKeys) {
const target = csn.definitions[finalElement.target];
setProp(finalElement, 'keys', [ ] );
if (target && target.elements) {
finalElement.keys = Object.entries(target.elements).filter(([ _n, e ]) => e.key)
. map(([ n, _e ]) => ({ ref: [ n ], as: n }));
}
}
// TODO: has managed assoc keys?
finalElement.keys.forEach((key, keyIndex) => {
const continuePath = isInspectRefResult ? [ path, 'keys', keyIndex ] : [ ...path, 'keys', keyIndex ];
const alias = key.as || implicitAs(key.ref);
const result = inspectRef(continuePath);
fks = fks.concat(createForeignKeysInternal(result, result.art, alias, csn, options, pathDelimiter, lvl + 1));
});
if (!hasKeys)
delete finalElement.keys;
}
// return if the toplevel element is not a managed association
else if (lvl === 0) {
return fks;
}
// we have reached a leaf element, create a foreign key
else if (finalElement && isBuiltinType(finalElement.type)) {
const newFk = Object.create(null);
for (const prop of [ 'type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type' ]) {
// copy props from original element to preserve derived types!
if (element[prop] !== undefined)
newFk[prop] = element[prop];
}
return [ [ prefix, newFk ] ];
}
else if (finalElement.elements) {
Object.entries(finalElement.elements).forEach(([ elemName, elem ]) => {
// Skip already produced foreign keys
if (!elem['@odata.foreignKey4']) {
const continuePath = isInspectRefResult ? [ path, 'elements', elemName ] : [ ...path, 'elements', elemName ];
fks = fks.concat(createForeignKeysInternal(continuePath, elem, elemName, csn, options, pathDelimiter, lvl + 1));
}
});
}
fks.forEach((fk) => {
// prepend current prefix
fk[0] = `${prefix}${pathDelimiter}${fk[0]}`;
// if this is the entry association, decorate the final foreign keys with the association props
if (lvl === 0) {
fk[1]['@odata.foreignKey4'] = prefix;
if (!options.forHana)
copyAnnotations(element, fk[1], true);
// propagate not null to final foreign key
for (const prop of [ 'notNull', 'key' ]) {
if (element[prop] !== undefined)
fk[1][prop] = element[prop];
}
if (element.$location)
setProp(fk[1], '$location', element.$location);
}
});
return fks;
}
/**
* This is the public createForeignKeys function that has no side effects
*
* @param {CSN.Path} path Path to the managed association
* @param {CSN.Model} csn
* @param {string} pathDelimiter
* @returns {Array}
*/
function createForeignKeys(path, csn, pathDelimiter = '_') {
if (!path || !Array.isArray(path))
throw Error('path must be a CSN path array');
if (!csn || typeof csn !== 'object')
throw Error('csn not provided');
return createForeignKeysInternal(path, walkCsnPath(csn, path), path[path.length - 1], csn, {}, pathDelimiter);
}
module.exports = {

@@ -353,2 +802,4 @@ resolveTypeReferences,

removeLeadingSelf,
handleManagedAssociationsAndCreateForeignKeys,
createForeignKeys,
};
'use strict';
const { ModelError } = require('../../base/error');
/**

@@ -92,3 +94,3 @@ * Replace (formerly) managed association in a GROUP BY/ORDER BY with its foreign keys.

if (!fk.$generatedFieldName)
throw new Error(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);
throw new ModelError(`Expecting generated field name for foreign key: ${JSON.stringify(fk)}`);

@@ -95,0 +97,0 @@ return { ref: [ fk.$generatedFieldName ] };

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

const { csnRefs } = require('../../model/csnRefs');
const { ModelError } = require('../../base/error');

@@ -52,3 +53,3 @@ /**

function handleExists(csn, options, error) {
const { inspectRef } = csnRefs(csn);
let { inspectRef } = csnRefs(csn);
const generatedExists = new WeakMap();

@@ -86,2 +87,4 @@ forEachDefinition(csn, (artifact, artifactName) => {

walkCsnPath(csn, exprPath.slice(0, -1))[exprPath[exprPath.length - 1]] = result;
if (leftovers.length > 0)
inspectRef = csnRefs(csn).inspectRef; // Refresh caches - we need to resolve stuff in the newly created subquery
toProcess.push(...leftovers.reverse()); // any leftovers - schedule for further processing

@@ -502,7 +505,12 @@ }

else if (part.$scope === '$self') { // source side - "absolute" scope
// cut off the $self, as we prefix the entity name now
whereExtension.push({ ref: [ base, ...part.ref.slice(1) ] });
const column = part._art._column;
if (column && column.as) { // Replace with the "original" expression (the .ref, .xpr etc.)
whereExtension.push(translateToSourceSide(column));
}
else {
whereExtension.push(assignAndDeleteAs({}, part, { ref: [ base, ...part.ref.slice(1) ] }));
}
}
else if (art) { // source side - with local scope
if (isPrefixedWithTableAlias)
if (isPrefixedWithTableAlias || part.$scope === 'alias')
whereExtension.push({ ref: [ ...current.ref.slice(0, -1), ...part.ref ] });

@@ -519,3 +527,48 @@ else

/**
* Run Object.assign on all of the passed in parameters and delete a .as at the end
*
* @param {...any} args
* @returns {object} The merged args without an .as property
*/
function assignAndDeleteAs(...args) {
const obj = Object.assign.apply(null, args);
delete obj.as;
return obj;
}
/**
* Translate the given obj (a column-like thing) into an expression that we can use in the WHERE.
* - Strip off $self/$projection and correctly replace with source expression
* - Drill further down into .xpr
* - Correctly set table alias in front of ref
*
* @param {object} obj
* @returns {object}
*/
function translateToSourceSide(obj) {
if (obj.ref) {
if (obj.$scope === '$self') { // TODO: Check with this way down, do we keep the links?
const column = obj._art._column;
if (column && column.as)
return translateToSourceSide(column);
return assignAndDeleteAs({}, obj, { ref: [ base, ...obj.ref.slice(1) ] });
}
else if (typeof obj.$env === 'string') {
return assignAndDeleteAs({}, obj, { ref: [ obj.$env, ...obj.ref ] });
}
return assignAndDeleteAs({}, obj, { ref: [ ...obj.ref ] });
}
else if (obj.xpr) { // we need to drill further down into .xpr
return assignAndDeleteAs({}, obj, { xpr: obj.xpr.map(translateToSourceSide) });
}
else if (obj.args) {
return assignAndDeleteAs({}, obj, { args: obj.args.map(translateToSourceSide) });
}
return obj;
}
/**
* Check that an expression triple is a valid $self

@@ -592,3 +645,3 @@ *

/**
* Check (using inspectRef -> links), wether the first path step is an entity or query source
* Check (using inspectRef -> links), whether the first path step is an entity or query source
*

@@ -643,3 +696,3 @@ * @param {CSN.Path} path

else if (xpr.ref) {
throw new Error('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
throw new ModelError('Missing $env and missing leading artifact ref - throwing to trigger recompilation!');
}

@@ -646,0 +699,0 @@ }

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

const { isBetaEnabled } = require('../../base/model');
const { ModelError } = require('../../base/error');

@@ -42,3 +43,3 @@ /**

/**
* Check wether the given artifact uses the given mixin association.
* Check whether the given artifact uses the given mixin association.
*

@@ -187,6 +188,6 @@ * We can rely on the fact that there can be no usage starting with $self/$projection,

if (matchingCombined.length > 1) { // should already be caught by compiler
throw new Error(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
throw new ModelError(`Ambiguous name - can't be resolved: ${elemName}. Found in: ${matchingCombined.map(o => o.parent)}`);
}
else if (matchingCombined.length === 0) { // no clue how this could happen? Invalid CSN?
throw new Error(`No matching entry found in UNION of all elements for: ${elemName}`);
throw new ModelError(`No matching entry found in UNION of all elements for: ${elemName}`);
}

@@ -310,3 +311,3 @@ alias = matchingCombined[0].parent;

},
}, elementsPath.concat(elemName));
}, {}, elementsPath.concat(elemName));
}

@@ -313,0 +314,0 @@

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

const { getUtils, cloneCsn,
forEachMemberRecursively, forEachRef, forAllQueries,
forEachMemberRecursively, forAllQueries, applyTransformationsOnNonDictionary,
getArtifactDatabaseNameOf, getElementDatabaseNameOf, isBuiltinType, applyTransformations,

@@ -16,3 +16,3 @@ isAspect, walkCsnPath,

const validate = require('../checks/validator');
const { rejectManagedAssociationsAndStructuresForHdbcsNames } = require('../checks/selectItems');
const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');

@@ -28,4 +28,4 @@ const { timetrace } = require('../utils/timetrace');

const assertUnique = require('./db/assertUnique');
const generateDrafts = require('./db/draft');
const enrichUniversalCsn = require('./universalCsnEnricher');
const generateDrafts = require('./draft/db');
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
const { getViewTransformer } = require('./db/views');

@@ -35,2 +35,3 @@ const cdsPersistence = require('./db/cdsPersistence');

const associations = require('./db/associations')
const { ModelError } = require("../base/error");

@@ -120,8 +121,8 @@ // By default: Do not process non-entities/views

let error, warning, info; // message functions
let message, error, warning, info; // message functions
/** @type {() => void} */
let throwWithError;
let artifactRef, inspectRef, effectiveType, // csnRefs
addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType, getFinalBaseType, // transformUtils
get$combined; // csnUtils
let artifactRef, inspectRef, effectiveType, get$combined,
getFinalBaseType, // csnUtils (csnRefs)
addDefaultTypeFacets, expandStructsInExpression; // transformUtils

@@ -144,3 +145,3 @@ bindCsnReference();

const cleanup = validate.forHana(csn, {
error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
message, error, warning, info, inspectRef, effectiveType, artifactRef, csnUtils: getUtils(csn), csn, options, getFinalBaseType, isAspect
});

@@ -241,30 +242,6 @@

transformCsn(csn, {
type: (val, node, key, path) => {
// Resolve type-of chains
function fn() {
// val can be undefined: books as myBooks : redirected to Model.MyBooks
if (val && val.ref) {
const { art } = inspectRef(path);
if (art && art.type) {
val = art.type;
// This is somehow needed to update the ref so that inspectRef sees it
node[key] = val;
if (val.ref)
fn();
}
else {
// Doesn't seem to ever ocurr
}
}
}
fn();
type: (val, node, key) => {
renamePrimitiveTypesAndUuid(val, node, key);
addDefaultTypeFacets(node);
},
cast: (val) => {
if (options.forHana.names === 'plain' || options.toSql )
toFinalBaseType(val);
renamePrimitiveTypesAndUuid(val.type, val, 'type');
addDefaultTypeFacets(val);
},
// HANA/SQLite do not support array-of - turn into CLOB/Text

@@ -280,3 +257,3 @@ items: (val, node) => {

// These entities are not removed from the csn, but flagged as "to be ignored"
forEachDefinition(csn, cdsPersistence.getAnnoProcessor(csn, options, {warning}));
forEachDefinition(csn, cdsPersistence.getAnnoProcessor());

@@ -286,16 +263,11 @@

// Views are checked in (001), unbalanced valid.from/to's or mismatching origins
// Temporal only in beta-mode
forEachDefinition(csn, temporal.getAnnotationHandler(csn, options, pathDelimiter, {error}));
handleManagedAssociationsAndCreateForeignKeys();
// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, pathDelimiter, true, { allowArtifact: artifact => (artifact.kind === 'entity') });
function handleManagedAssociationsAndCreateForeignKeys() {
forEachDefinition(csn, associations.getForeignKeyFlattener(csn, options, pathDelimiter));
forEachDefinition(csn, associations.getForeignKeyElementCreator(csn, options, pathDelimiter, { error }));
}
doA2J && forEachDefinition(csn, flattenIndexes);
// Managed associations get an on-condition - in views and entities
doA2J && associations.attachOnConditions(csn, pathDelimiter);
forEachDefinition(csn, flattenIndexes);
// Basic handling of associations in views and entities
forEachDefinition(csn, associations.getManagedAssociationTransformer(csn, options, pathDelimiter));
// (045) Strip all query-ish properties from views and projections annotated with '@cds.persistence.table',

@@ -308,6 +280,7 @@ // and make them entities

// are part of the elements
forEachDefinition(csn, handleManagedAssocStepsInOnCondition);
if (doA2J)
forEachDefinition(csn, associations.getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter));
// Create convenience views for localized entities/views.
// To be done after handleManagedAssocStepsInOnCondition because associations are
// To be done after getManagedAssocStepsInOnConditionFinalizer because associations are
// handled and before handleDBChecks which removes the localized attribute.

@@ -321,7 +294,7 @@ // Association elements of localized convenience views do not have hidden properties

forEachDefinition(csn, (definition, artName, prop, path) => {
!doA2J && forEachDefinition(csn, (definition, artName, prop, path) => {
if (definition.query) {
// reject managed association and structure publishing for to-hdbcds.hdbcds
const that = { csnUtils: getUtils(csn), options, error };
rejectManagedAssociationsAndStructuresForHdbcsNames.call(that, definition, path)
rejectManagedAssociationsAndStructuresForHdbcdsNames.call(that, definition, path)
}

@@ -350,14 +323,10 @@ });

if(options.forHana){
/**
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
* hence we do not generate the referential constraints for them.
*/
const validOptionsForConstraint = () => {
return (options.forHana.dialect === 'sqlite' || options.forHana.dialect === 'hana') && doA2J;
}
if(validOptionsForConstraint())
createReferentialConstraints(csn, options);
}
/**
* Referential Constraints are only supported for sql-dialect "hana" and "sqlite".
* For to.hdbcds with naming mode "hdbcds", no foreign keys are calculated,
* hence we do not generate the referential constraints for them.
*/
if((options.sqlDialect === 'hana' || options.sqlDialect === 'sqlite') && doA2J)
createReferentialConstraints(csn, options);
// no constraints for drafts

@@ -385,16 +354,2 @@ generateDrafts(csn, options, pathDelimiter, { info, warning, error });

};
const removeNamespaces = (artifact, artifactName) => {
if (artifact.kind === 'namespace')
delete csn.definitions[artifactName];
};
const ignoreNonPersistedArtifactsWithAnonymousAspectComposition = (artifact) => {
if(artifact.kind === 'type' || artifact.kind === 'aspect' || artifact.kind === 'entity' && artifact.abstract){
if(artifact.elements && Object.keys(artifact.elements).some((elementName) => {
const element = artifact.elements[elementName];
return !element.target && element.targetAspect && typeof element.targetAspect !== 'string';
})) {
artifact._ignore = true;
}
}
};

@@ -404,8 +359,4 @@ forEachDefinition(csn, [

checkConstraintIdentifiers,
/* (250) Remove all namespaces from definitions */
removeNamespaces,
/* Check Type Parameters (precision, scale, length ...) */
checkTypeParameters,
/* Filter out aspects/types/abstract entities containing managed compositions of anonymous aspects */
ignoreNonPersistedArtifactsWithAnonymousAspectComposition,
// (200) Strip 'key' property from type elements

@@ -450,3 +401,3 @@ removeKeyPropInType,

applyTransformations(csn, killers, [], false);
applyTransformations(csn, killers, [], { skipIgnore: false});

@@ -460,6 +411,5 @@ redoProjections.forEach(fn => fn());

function bindCsnReference(){
({ error, warning, info, throwWithError } = makeMessageFunction(csn, options, moduleName));
({ artifactRef, inspectRef, effectiveType } = csnRefs(csn));
({ getFinalBaseType, get$combined } = getUtils(csn));
({ addDefaultTypeFacets, expandStructsInExpression, toFinalBaseType } = transformUtils.getTransformers(csn, options, pathDelimiter));
({ error, warning, info, message, throwWithError } = makeMessageFunction(csn, options, moduleName));
({ artifactRef, inspectRef, effectiveType, getFinalBaseType, get$combined } = getUtils(csn));
({ addDefaultTypeFacets, expandStructsInExpression } = transformUtils.getTransformers(csn, options, pathDelimiter));
}

@@ -491,4 +441,4 @@

}
}
, [ 'definitions', artifactName, 'query' ]);
}, [ 'definitions', artifactName, 'query' ]);
function getResolvedMixinOnCondition(csn, mixinAssociation, query, assocName, path){

@@ -544,3 +494,3 @@ const { inspectRef } = csnRefs(csn);

// Do things specific for entities and views (pass 2)
if ((artifact.kind === 'entity' || artifact.kind === 'view') && artifact.query) {
if ((artifact.kind === 'entity') && artifact.query) {
forAllQueries(artifact.query, (q, p) => {

@@ -560,3 +510,3 @@ transformEntityOrViewPass2(q, artifact, artifactName, p)

if (!artifact._ignore) {
if (![ 'service', 'context', 'namespace', 'annotation', 'action', 'function' ].includes(artifact.kind))
if (artifact.kind !== 'service' && artifact.kind !== 'context')
addStringAnnotationTo('@cds.persistence.name', getArtifactDatabaseNameOf(artifactName, options.forHana.names, csn), artifact);

@@ -580,5 +530,5 @@

function removeKeyPropInType(artifact, artifactName) {
if (!artifact._ignore) {
if (!artifact._ignore && artifact.kind === 'type') {
forEachMemberRecursively(artifact, (member) => {
if (artifact.kind === 'type' && member.key)
if (member.key)
delete member.key;

@@ -632,7 +582,8 @@ }, [ 'definitions', artifactName ]);

// SAP DBTech JDBC: [7]: feature not supported: parameterized sql view cannot support association: line 1 col 1 (at pos 0)
error(null, path, 'Unexpected association in parameterized view');
message('def-unexpected-paramview-assoc', path, { '#': 'view' });
}
else if(artifact['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
// UDF/CVs w/o params don't support 'WITH ASSOCIATIONS'
error(null, path, `Associations are not allowed in entities annotated with @cds.persistence { udf, calcview }`);
const anno = artifact['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
message('def-unexpected-calcview-assoc', path, { '#': 'entity-persistence', anno });
}

@@ -642,5 +593,5 @@ if (csn.definitions[member.target].params) {

// SAP DBTech JDBC: [7]: feature not supported: cannot support create association to a parameterized view
error(null, path, 'Unexpected parameterized association target');
message('def-unexpected-paramview-assoc', path, { '#': 'target' });
}
else if(csn.definitions[member.target]['@cds.persistence.udf'] || artifact['@cds.persistence.calcview']) {
else if(csn.definitions[member.target]['@cds.persistence.udf'] || csn.definitions[member.target]['@cds.persistence.calcview']) {
// HANA won't check the assoc target but when querying an association with target UDF, this is the error:

@@ -652,3 +603,4 @@ // SAP DBTech JDBC: [259]: invalid table name: target object SYSTEM.UDF does not exist: line 3 col 6 (at pos 43)

// CREATE VIEW U AS SELECT id, toUDF.a FROM Y;
error(null, path, `Associations can't point to entities annotated with @cds.persistence { udf, calcview }`);
const anno = csn.definitions[member.target]['@cds.persistence.udf'] ? '@cds.persistence.udf' : '@cds.persistence.calcview';
message('def-unexpected-calcview-assoc', path, { '#': 'target-persistence', anno });
}

@@ -664,3 +616,3 @@ }

function handleChecksForWithParameters(artifact, artifactName) {
if (!artifact._ignore && artifact.params && (artifact.kind === 'entity' || artifact.kind === 'view')) {
if (!artifact._ignore && artifact.params && (artifact.kind === 'entity')) {
if (!artifact.query) { // table entity with params

@@ -935,3 +887,3 @@ // Allow with plain

throw new Error(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
throw new ModelError(`Expected either managed or unmanaged association in $self-comparison: ${ JSON.stringify(elem.on) }`);
}

@@ -991,19 +943,20 @@

});
// goes through the the newOnCond and transform all the 'path' elements
forEachRef(newOnCond, (ref) => {
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
{
ref.shift();
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
// We could also have a $self infront of the assoc name - so we would need to shift twice
ref.shift();
ref.shift();
applyTransformationsOnNonDictionary({on: newOnCond}, 'on', {
ref: (parent, prop, ref) => {
if (ref[0] === assocName) // we are in the "path" from the forwarding assoc => need to remove the first part of the path
{
ref.shift();
} else if(ref && ref.length > 1 && ref[0] === '$self' && ref[1] === assocName) {
// We could also have a $self infront of the assoc name - so we would need to shift twice
ref.shift();
ref.shift();
}
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
ref.unshift(elemName);
// if there was a $self identifier in the forwarding association onCond
// we do not need it any more, as we prepended in the previous step the back association's id
if (ref[1] === '$self')
ref.splice(1, 1);
}
}
else { // we are in the backlink assoc "path" => need to push at the beginning the association's id
ref.unshift(elemName);
// if there was a $self identifier in the forwarding association onCond
// we do not need it any more, as we prepended in the previous step the back association's id
if (ref[1] === '$self')
ref.splice(1, 1);
}
});

@@ -1119,3 +1072,3 @@ return newOnCond;

const tc = art.technicalConfig;
if ((art.kind === 'entity' || art.kind === 'view') && doA2J) {
if (art.kind === 'entity') {
if (tc && tc[dialect]) {

@@ -1174,71 +1127,2 @@ // Secondary and fulltext indexes

}
/**
* Loop over all elements and for all unmanaged associations translate
* <assoc base>.<managed assoc>.<fk> to <assoc base>.<managed assoc>_<fk>
*
* Or in other words: Allow using the foreign keys of managed associations in on-conditions
*
* @param {CSN.Artifact} artifact Artifact to check
* @param {string} artifactName Name of the artifact
*/
function handleManagedAssocStepsInOnCondition(artifact, artifactName) {
for (const elemName in artifact.elements) {
const elem = artifact.elements[elemName];
if (doA2J) {
// The association is an unmanaged on
if (!elem.keys && elem.target && elem.on) {
forEachRef(elem.on, (ref, refOwner, path) => {
// [<assoc base>.]<managed assoc>.<field>
if (ref.length > 1) {
const { links } = inspectRef(path);
if (links) {
// eslint-disable-next-line for-direction
for (let i = links.length - 1; i >= 0; i--) {
const link = links[i];
// We found the latest managed assoc path step
if (link.art && link.art.target && link.art.keys) {
// Doesn't work when ref-target (filter condition) or similar is used
if (!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
// We join the managed assoc with everything following it
const sourceElementName = ref.slice(i).join(pathDelimiter);
const source = findSource(links, i - 1) || artifact;
// allow specifying managed assoc on the source side
const fks = link.art.keys.filter(fk => ref[i] + pathDelimiter + fk.ref[0] === sourceElementName);
if(fks && fks.length >= 1){
const fk = fks[0];
const managedAssocStepName = refOwner.ref[i];
const fkName = `${ managedAssocStepName }${ pathDelimiter }${ fk.as }`;
if(source && source.elements[fkName])
refOwner.ref = [ ...ref.slice(0, i), fkName ];
}
}
}
}
}
}
}, [ 'definitions', artifactName, 'elements', elemName, 'on' ]);
}
}
}
/**
* Find out where the managed association is
*
* @param {Array} links
* @param {Number} startIndex
* @returns {Object| undefined} CSN definition of the source of the managed association
*
*/
function findSource(links, startIndex) {
for (let i = startIndex; i >= 0; i--) {
const link = links[i];
// We found the latest assoc step - now check where that points to
if (link.art && link.art.target)
return csn.definitions[link.art.target];
}
return undefined;
}
}
}

@@ -1245,0 +1129,0 @@

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

forEachMemberRecursively,
forEachRef,
applyTransformationsOnNonDictionary,
getArtifactDatabaseNameOf,

@@ -20,3 +20,3 @@ getElementDatabaseNameOf,

const validate = require('../checks/validator');
const { isArtifactInSomeService, getServiceOfArtifact, isLocalizedArtifactInService } = require('./odata/utils');
const { isArtifactInSomeService, isLocalizedArtifactInService } = require('./odata/utils');
const ReferenceFlattener = require('./odata/referenceFlattener');

@@ -29,3 +29,4 @@ const { flattenCSN } = require('./odata/structureFlattener');

const { attachPath } = require('./odata/attachPath');
const enrichUniversalCsn = require('./universalCsnEnricher');
const enrichUniversalCsn = require('./universalCsn/universalCsnEnricher');
const generateDrafts = require('./draft/odata');

@@ -81,3 +82,3 @@ const { addLocalizationViews } = require('./localized');

const { error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
const { message, error, warning, info, throwWithError } = makeMessageFunction(csn, options, 'for.odata');
throwWithError();

@@ -91,9 +92,5 @@

addDefaultTypeFacets,
createForeignKeyElement,
createAndAddDraftAdminDataProjection, createScalarElement,
createAssociationElement, createAssociationPathComparison,
addElement, createAction, assignAction,
extractValidFromToKeyElement,
checkAssignment, checkMultipleAssignments,
recurseElements, setAnnotation, resetAnnotation, renameAnnotation,
recurseElements, setAnnotation, renameAnnotation,
expandStructsInExpression

@@ -105,5 +102,3 @@ } = transformers;

getCsnDef,
getFinalType,
getServiceName,
hasAnnotationValue,
isAssocOrComposition,

@@ -142,3 +137,3 @@ isAssociation,

validate.forOdata(csn, {
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
message, error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect, isExternalServiceMember
});

@@ -219,15 +214,3 @@

// - Perform checks for exposed non-abstract entities and views - check media type and key-ness
let visitedArtifacts = Object.create(null);
forEachDefinition(csn, (def, defName) => {
if (def.kind === 'entity' || def.kind === 'view') {
// Generate artificial draft fields if requested
if (def['@odata.draft.enabled']) {
// Ignore if not part of a service
if (isArtifactInSomeService(defName, services)) {
generateDraftForOdata(def, defName, def, visitedArtifacts);
}
}
}
visitedArtifacts[defName] = true;
}, { skipArtifact: isExternalServiceMember });
generateDrafts(csn, options, services)

@@ -353,3 +336,3 @@ // Deal with all kind of annotations manipulations here

if (name === '@readonly' && node[name] !== null) {
if (node.kind === 'entity' || node.kind === 'view') {
if (node.kind === 'entity') {
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);

@@ -364,3 +347,3 @@ setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false);

else if (name === '@insertonly' && node[name] !== null) {
if (node.kind === 'entity' || node.kind === 'view') {
if (node.kind === 'entity') {
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false);

@@ -436,6 +419,9 @@ setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false);

if (!assoc.on) return; // nothing to do
forEachRef(assoc, (ref, node) => {
// remove leading $self when at the begining of a ref
if (ref.length > 1 && ref[0] === '$self')
node.ref.splice(0, 1);
// TODO: Shouldn't this only run on the on-condition and not the whole assoc-node?
applyTransformationsOnNonDictionary({ assoc }, 'assoc', {
ref: (node, prop, ref) => {
// remove leading $self when at the begining of a ref
if (ref.length > 1 && ref[0] === '$self')
node.ref.splice(0, 1);
}
});

@@ -445,142 +431,2 @@ }

// Generate all that is required in ODATA for draft enablement of 'artifact' into the artifact,
// into its transitively reachable composition targets, and into the model.
// 'rootArtifact' is the root artifact where composition traversal started.
// Constraints
// Draft Root: Exactly one PK of type UUID
// Draft Node: One PK of type UUID + 0..1 PK of another type
// Draft Node: Must not be reachable from multiple draft roots
function generateDraftForOdata(artifact, artifactName, rootArtifact, visitedArtifacts) {
// Sanity check
// @ts-ignore
if (!isArtifactInSomeService(artifactName, services)) {
throw new Error('Expecting artifact to be part of a service: ' + JSON.stringify(artifact));
}
// Nothing to do if already draft-enabled (composition traversal may have circles)
if ((artifact['@Common.DraftRoot.PreparationAction'] || artifact['@Common.DraftNode.PreparationAction'])
&& artifact.actions && artifact.actions.draftPrepare) {
return;
}
// Generate the DraftAdministrativeData projection into the service, unless there is already one
// @ts-ignore
let draftAdminDataProjectionName = `${getServiceOfArtifact(artifactName, services)}.DraftAdministrativeData`;
let draftAdminDataProjection = csn.definitions[draftAdminDataProjectionName];
if (!draftAdminDataProjection) {
// @ts-ignore
draftAdminDataProjection = createAndAddDraftAdminDataProjection(getServiceOfArtifact(artifactName, services));
}
// Report an error if it is not an entity or not what we expect
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) {
error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName },
`Generated entity $(NAME) conflicts with existing artifact`);
}
// Generate the annotations describing the draft actions (only draft roots can be activated/edited)
if (artifact == rootArtifact) {
resetAnnotation(artifact, '@Common.DraftRoot.ActivationAction', 'draftActivate', info, ['definitions', draftAdminDataProjectionName]);
resetAnnotation(artifact, '@Common.DraftRoot.EditAction', 'draftEdit', info, ['definitions', draftAdminDataProjectionName]);
resetAnnotation(artifact, '@Common.DraftRoot.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
} else {
resetAnnotation(artifact, '@Common.DraftNode.PreparationAction', 'draftPrepare', info, ['definitions', draftAdminDataProjectionName]);
}
artifact.elements && Object.values(artifact.elements).forEach( elem => {
// Make all non-key elements nullable
if (elem.notNull && elem.key !== true) {
delete elem.notNull;
}
});
// Generate the additional elements into the draft-enabled artifact
// key IsActiveEntity : Boolean default true
let isActiveEntity = createScalarElement('IsActiveEntity', 'cds.Boolean', true, true, false);
isActiveEntity.IsActiveEntity['@UI.Hidden'] = true;
addElement(isActiveEntity, artifact, artifactName);
// HasActiveEntity : Boolean default false
let hasActiveEntity = createScalarElement('HasActiveEntity', 'cds.Boolean', false, false, true);
hasActiveEntity.HasActiveEntity['@UI.Hidden'] = true;
addElement(hasActiveEntity, artifact, artifactName);
// HasDraftEntity : Boolean default false;
let hasDraftEntity = createScalarElement('HasDraftEntity', 'cds.Boolean', false, false, true);
hasDraftEntity.HasDraftEntity['@UI.Hidden'] = true;
addElement(hasDraftEntity, artifact, artifactName);
// @odata.contained: true
// DraftAdministrativeData : Association to one DraftAdministrativeData;
let draftAdministrativeData = createAssociationElement('DraftAdministrativeData', draftAdminDataProjectionName, true);
draftAdministrativeData.DraftAdministrativeData.cardinality = { max: 1, };
draftAdministrativeData.DraftAdministrativeData['@odata.contained'] = true;
draftAdministrativeData.DraftAdministrativeData['@UI.Hidden'] = true;
addElement(draftAdministrativeData, artifact, artifactName);
// Note that we need to do the ODATA transformation steps for managed associations
// (foreign key field generation, generatedFieldName) by hand, because the corresponding
// transformation steps have already been done on all artifacts when we come here)
let uuidDraftKey = draftAdministrativeData.DraftAdministrativeData.keys.filter(key => key.ref && key.ref.length === 1 && key.ref[0] === 'DraftUUID');
if (uuidDraftKey && uuidDraftKey[0]) {
uuidDraftKey = uuidDraftKey[0]; // filter returns an array, but it has only one element
let path = ['definitions', artifactName, 'elements', 'DraftAdministrativeData', 'keys', 0];
createForeignKeyElement(draftAdministrativeData.DraftAdministrativeData, 'DraftAdministrativeData', uuidDraftKey, artifact, artifactName, path);
}
// SiblingEntity : Association to one <artifact> on (... IsActiveEntity unequal, all other key fields equal ...)
let siblingEntity = createAssociationElement('SiblingEntity', artifactName, false);
siblingEntity.SiblingEntity.cardinality = { max: 1 };
addElement(siblingEntity, artifact, artifactName);
// ... on SiblingEntity.IsActiveEntity != IsActiveEntity ...
siblingEntity.SiblingEntity.on = createAssociationPathComparison('SiblingEntity', 'IsActiveEntity', '!=', 'IsActiveEntity');
// Iterate elements
artifact.elements && Object.entries(artifact.elements).forEach( ([elemName, elem]) => {
if (elemName !== 'IsActiveEntity' && elem.key) {
// Amend the ON-condition above:
// ... and SiblingEntity.<keyfield> = <keyfield> ... (for all key fields except 'IsActiveEntity')
let cond = createAssociationPathComparison('SiblingEntity', elemName, '=', elemName);
cond.push('and');
cond.push(...siblingEntity.SiblingEntity.on);
siblingEntity.SiblingEntity.on = cond;
}
// Draft-enable the targets of composition elements (draft nodes), too
// TODO rewrite
if (elem.target && elem.type && getFinalType(elem.type) === 'cds.Composition') {
let draftNode = csn.definitions[elem.target];
// Ignore if that is our own draft root
if (draftNode != rootArtifact) {
// Report error when the draft node has @odata.draft.enabled itself
if (hasAnnotationValue(draftNode, '@odata.draft.enabled', true)) {
error(null, ['definitions', artifactName, 'elements', elemName], 'Composition in draft-enabled entity can\'t lead to another entity with “@odata.draft.enabled”');
}
// Ignore composition if not part of a service or explicitly draft disabled
else if (!getServiceName(elem.target) || hasAnnotationValue(draftNode, '@odata.draft.enabled', false)) {
return;
}
else {
// Generate draft stuff into the target
generateDraftForOdata(draftNode, elem.target, rootArtifact, visitedArtifacts);
}
}
}
});
// Generate the actions into the draft-enabled artifact (only draft roots can be activated/edited)
// action draftPrepare (SideEffectsQualifier: String) return <artifact>;
let draftPrepare = createAction('draftPrepare', artifactName, 'SideEffectsQualifier', 'cds.String');
assignAction(draftPrepare, artifact);
if (artifact == rootArtifact) {
// action draftActivate() return <artifact>;
let draftActivate = createAction('draftActivate', artifactName);
assignAction(draftActivate, artifact);
// action draftEdit (PreserveChanges: Boolean) return <artifact>;
let draftEdit = createAction('draftEdit', artifactName, 'PreserveChanges', 'cds.Boolean');
assignAction(draftEdit, artifact);
}
}
// CDXCORE-481

@@ -587,0 +433,0 @@ // (4.5) If the member is an association whose target has @cds.odata.valuelist annotate it

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

* 1. "direct ones" using coalesce() for the table entities with localized
* elements: as projection on the original (created in definer.js)
* elements: as projection on the original (created in extend.js)
* 2. for table entities with associations to entities which have a localized

@@ -78,7 +78,6 @@ * convenience views or redirections thereon: as projection on the original

if (hasExistingLocalizationViews(csn, options))
const messageFunctions = makeMessageFunction(csn, options);
if (hasExistingLocalizationViews(csn, options, messageFunctions))
return csn;
const { info } = makeMessageFunction(csn, options);
const noCoalesce = (options.localizedLanguageFallback === 'none' ||

@@ -107,3 +106,3 @@ options.localizedWithoutCoalesce);

if (isInLocalizedNamespace(artName))
// We already issued a warning for it in warnAboutExistingLocalizationViews()
// We already issued a warning for it in hasExistingLocalizationViews()
return;

@@ -133,3 +132,3 @@

// Already exists, skip creation.
info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
messageFunctions.info( null, artPath, null, 'Convenience view can\'t be created due to conflicting names' );
return;

@@ -346,3 +345,3 @@ }

// TODO: Already warned about in definer.js
// TODO: Already warned about in extend.js
// if (elem.key && isLocalized)

@@ -357,3 +356,3 @@ // warning( 'localized-key', path, {}, 'Keyword "localized" is ignored for primary keys' );

if (!isEntityPreprocessed( art )) {
info( null, artPath, { name: artName },
messageFunctions.info( null, artPath, { name: artName },
'Skipped creation of convenience view for $(NAME) because the artifact is missing localization elements' );

@@ -367,3 +366,3 @@ return null;

if (!textsEntity) {
info( null, artPath, { name: artName },
messageFunctions.info( null, artPath, { name: artName },
'Skipped creation of convenience view for $(NAME) because its texts entity could not be found' );

@@ -374,3 +373,3 @@ return null;

if (!isValidTextsEntity( textsEntity )) {
info( null, [ 'definitions', textsName ], { name: artName },
messageFunctions.info( null, [ 'definitions', textsName ], { name: artName },
'Skipped creation of convenience view for $(NAME) because its texts entity does not appear to be valid' );

@@ -696,14 +695,28 @@ return null;

* @param {CSN.Options} options
* @param {object} messageFunctions
*/
function hasExistingLocalizationViews(csn, options) {
function hasExistingLocalizationViews(csn, options, messageFunctions) {
if (!csn || !csn.definitions)
return false;
const firstLocalizedView = Object.keys(csn.definitions).find(isInLocalizedNamespace);
if (firstLocalizedView) {
const { info } = makeMessageFunction(csn, options);
info( null, [ 'definitions', firstLocalizedView ], {},
'Input CSN already contains expansions for localized data' );
return true;
let hasExistingViews = false;
let hasNonViews = false;
for (const name in csn.definitions) {
const art = csn.definitions[name];
if (isInLocalizedNamespace(name) || name === 'localized') {
if (!art.query && !art.projection) {
if (!name.endsWith('.texts')) {
hasNonViews = true;
messageFunctions.error('reserved-namespace-localized', ['definitions', name], {},
'The namespace "localized" is reserved for localization views');
}
} else if (!hasExistingViews) {
hasExistingViews = true;
messageFunctions.info( null, [ 'definitions', name ], {},
'Input CSN already contains localization views, no further ones will be created' );
}
}
}
return false;
return hasExistingViews || hasNonViews;
}

@@ -740,5 +753,6 @@

* @param {string} name
* @returns {boolean}
*/
function isInLocalizedNamespace(name) {
return name.startsWith('localized.');
return name === 'localized' || name.startsWith('localized.');
}

@@ -751,2 +765,3 @@

* @param {string} artifactName
* @returns {boolean}
*/

@@ -753,0 +768,0 @@ function hasLocalizedConvenienceView(csn, artifactName) {

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

if (csnUtils.isManagedAssociationElement(element) && element.keys) {
if (csnUtils.isManagedAssociation(element) && element.keys) {
if (flatKeys) // tackling the ref value in assoc.keys

@@ -43,0 +43,0 @@ takeoverForeignKeysOfTargetAssociations(element, path, generatedForeignKeyNamesForPath);

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

const { forEachRef } = require('../../model/csnUtils');
const { applyTransformations } = require('../../model/csnUtils');
const { setProp } = require('../../base/model');

@@ -55,28 +55,30 @@ const { implicitAs } = require('../../model/csnRefs');

resolveAllReferences(csn, inspectRef, isStructured) {
forEachRef(csn, (_ref, node, path) => {
if (!path) return;
let resolved;
try {
resolved = inspectRef(path);
} catch (ex) {
return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
applyTransformations(csn, {
ref: (node, prop, _ref, path) => {
if (!path) return;
let resolved;
try {
resolved = inspectRef(path);
} catch (ex) {
return; // TODO: fix tests: throw Error("Could not inspectRef: " + path.join("/"));
}
if (!resolved)
return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
if (!resolved.links)
return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
let paths = [];
resolved.links.forEach((element) => {
if (!element.art)
paths = undefined; // not resolved -> no paths
if (paths) {
paths.push(element.art.$path);
}
});
if (paths)
setProp(node, '$paths', paths);
// cache if structured or not
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
this.structuredReference[path.join('/')] = structured;
}
if (!resolved)
return; // TODO: fix tests: throw Error("Could not resolve: " + path.join("/"));
if (!resolved.links)
return; // TODO: fix tests: throw Error("Could not resolve links: " + path.join("/"));
let paths = [];
resolved.links.forEach((element) => {
if (!element.art)
paths = undefined; // not resolved -> no paths
if (paths) {
paths.push(element.art.$path);
}
});
if (paths)
setProp(node, '$paths', paths);
// cache if structured or not
let structured = resolved.links.map(link => link.art ? (isStructured(link.art)) : undefined);
this.structuredReference[path.join('/')] = structured;
});
})
}

@@ -176,45 +178,47 @@

flattenAllReferences(csn) {
forEachRef(csn, (ref, node, path) => {
if (node.$paths) {
let newRef = []; // flattened reference
let flattenWithPrevious = false;
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
node.$paths.forEach((path, i) => {
if (path === undefined || !ref[i]) return;
let spath = path.join('/');
let movedTo = this.elementTransitions[spath]; // detect element transition
let flattened = this.flattenedElementPaths[spath];
if (flattenWithPrevious) {
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
// if we have a filter or args in an assoc, it needs to be kept, therefore
// the id of the ref is updated with the flattened version
if (ref[i].id) {
ref[i].id = newRef[newRef.length - 1];
newRef[newRef.length - 1] = ref[i];
applyTransformations(csn, {
ref: (node, prop, ref, path) => {
if (node.$paths) {
let newRef = []; // flattened reference
let flattenWithPrevious = false;
let lastFlattenedID = null; // The variable will be set to the index of the last flattened path
node.$paths.forEach((path, i) => {
if (path === undefined || !ref[i]) return;
let spath = path.join('/');
let movedTo = this.elementTransitions[spath]; // detect element transition
let flattened = this.flattenedElementPaths[spath];
if (flattenWithPrevious) {
newRef[newRef.length - 1] = newRef[newRef.length - 1] + '_' + (ref[i].id || ref[i]);
// if we have a filter or args in an assoc, it needs to be kept, therefore
// the id of the ref is updated with the flattened version
if (ref[i].id) {
ref[i].id = newRef[newRef.length - 1];
newRef[newRef.length - 1] = ref[i];
}
lastFlattenedID = i;
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
let movedToElementName = movedTo[movedTo.length-1];
newRef.push(movedToElementName);
lastFlattenedID = i;
} else {
newRef.push(ref[i]);
}
lastFlattenedID = i;
} else if(movedTo && i===0) { // handle local scope reference which is transitioned - replace first item in reference
let movedToElementName = movedTo[movedTo.length-1];
newRef.push(movedToElementName);
lastFlattenedID = i;
} else {
newRef.push(ref[i]);
flattenWithPrevious = flattened;
});
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
// check if this is a column and add alias if missing
let structural = structuralPath(csn, path);
if (isColumnInSelectOrProjection(structural)) {
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
node.as = node.ref[node.ref.length - 1];
}
setProp(newRef, '$path', path.concat('ref'));
if (!node.as) {
if (isPartOfKeysStructure(structural))
node.as = implicitAs(node.ref);
else
setProp(node, 'as', implicitAs(node.ref))
}
node.ref = newRef;
}
flattenWithPrevious = flattened;
});
if (newRef !== undefined && lastFlattenedID !== null) { // make sure the reference changed and only then replace it with the new one
// check if this is a column and add alias if missing
let structural = structuralPath(csn, path);
if (isColumnInSelectOrProjection(structural)) {
if (!node.as && lastFlattenedID === ref.length-1) // attach alias only if there is none and it is the last item in the reference that was flattened
node.as = node.ref[node.ref.length - 1];
}
setProp(newRef, '$path', path.concat('ref'));
if (!node.as) {
if (isPartOfKeysStructure(structural))
node.as = implicitAs(node.ref);
else
setProp(node, 'as', implicitAs(node.ref))
}
node.ref = newRef;
}

@@ -226,27 +230,29 @@ }

applyAliasesInOnCond(csn, inspectRef) {
forEachRef(csn, (ref, node, path) => {
// Process only on-conditions of associations
let structural = structuralPath(csn, path);
if(!isOnCondition(structural)) return;
let { links } = inspectRef(path);
if(!links) return; // $user not resolvable
let keysOfPreviousStepWhenManagedAssoc = undefined;
let aliasedRef = [...ref];
for (let idx = 0; idx < ref.length; idx++) {
const currArt = links[idx].art;
if (keysOfPreviousStepWhenManagedAssoc) {
const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
if (usedKey && usedKey.as) {
aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
idx += usedKey.ref.length - 1;
keysOfPreviousStepWhenManagedAssoc = undefined;
applyTransformations(csn, {
ref: (node, prop, ref, path) => {
// Process only on-conditions of associations
let structural = structuralPath(csn, path);
if(!isOnCondition(structural)) return;
let { links } = inspectRef(path);
if(!links) return; // $user not resolvable
let keysOfPreviousStepWhenManagedAssoc = undefined;
let aliasedRef = [...ref];
for (let idx = 0; idx < ref.length; idx++) {
const currArt = links[idx].art;
if (keysOfPreviousStepWhenManagedAssoc) {
const usedKey = keysOfPreviousStepWhenManagedAssoc.find(key => key.ref[0] === ref[idx]);
if (usedKey && usedKey.as) {
aliasedRef.splice(idx, usedKey.ref.length, usedKey.as);
idx += usedKey.ref.length - 1;
keysOfPreviousStepWhenManagedAssoc = undefined;
}
} else {
keysOfPreviousStepWhenManagedAssoc =
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
}
} else {
keysOfPreviousStepWhenManagedAssoc =
(currArt.type === 'cds.Association' || currArt.type === 'cds.Composition') && currArt.keys;
}
node.ref = aliasedRef;
}
node.ref = aliasedRef;
})

@@ -253,0 +259,0 @@ }

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

function flattenDefinition(definition, definitionPath, csnUtils, options, referenceFlattener, error) {
if (definition.kind !== 'entity' && definition.kind !== 'view')
if (definition.kind !== 'entity')
return;

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

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

forEachDefinition, forEachMemberRecursively,
isBuiltinType, cloneCsnDictionary,
isBuiltinType, cloneCsnDictionary, cloneCsn,
} = require('../../model/csnUtils');

@@ -90,17 +90,91 @@ const { isArtifactInSomeService, isArtifactInService } = require('./utils');

if (isExpandable(node, defName) || node.kind === 'type') {
transformers.toFinalBaseType(node);
if (node.type && !isBuiltinType(node.type)) {
// handle array of defined via a named type
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
const currService = csnUtils.getServiceName(defName);
const finalType = csnUtils.getFinalTypeDef(node.type);
if (finalType.items && isBuiltinType(finalType.items.type)) {
if (!isArtifactInService(node.type, currService) || !isV4) {
node.items = finalType.items;
// elements have precedence over type
if (node.type && (!isBuiltinType(node.type) &&isExpandable(node, defName) || node.kind === 'type')) {
// 1. Get the final type of the node (resolve derived type chain)
const finalType = csnUtils.getFinalBaseType(node.type);
if (finalType) {
// The type replacement depends on whether 'node' is a definition or a member[element].
if (node.kind) {
// It is a definition and we expand to builtin type and to elements
// type T: S; --> Integer;
// type S: X; --> Integer;
// type X: Integer;
//
// type A: B; -> {...}
// type B: C; -> { ... }
// type C { .... };
if (isBuiltinType(finalType)) {
// use transformUrilsNew::toFinalBaseType for the moment,
// as it is collects along the chain of types
// attributes that need to be propagated
// enum, length, scale, etc.
transformers.toFinalBaseType(node);
// node.type = finalType;
}
else if (csnUtils.isStructured(finalType)) {
cloneElements(finalType);
}
else if (node.type && node.items)
delete node.type;
}
else {
// this is a member and we expand to final base only if builtin
// type T: S; --> Integer;
// type S: X; --> Integer;
// type X: Integer;
//
// type {
// struct_elt: many A; ---> stays the same
// scalar_elt: T; ---> Integer;
// type_ref_elt: type of struct_elt;
// };
// type A: B; -> {...}
// type B: C; -> { ... }
// type C { .... };
if (isBuiltinType(finalType)) {
// use transformUrilsNew::toFinalBaseType for the moment,
// as it is collects along the chain of types
// attributes that need to be propagated
// enum, length, scale, etc.
transformers.toFinalBaseType(node);
// node.type = finalType;
}
else if (node.type && node.type.ref) {
cloneElements(finalType);
}
}
}
}
if (node.type && !isBuiltinType(node.type)) {
// handle array of defined via a named type
// example in actions: 'action act() return Primitive; type Primitive: array of String;'
const currService = csnUtils.getServiceName(defName);
const finalType = csnUtils.getFinalTypeDef(node.type);
if (finalType.items && isBuiltinType(finalType.items.type)) {
if (!isArtifactInService(node.type, currService) || !isV4) {
node.items = finalType.items;
delete node.type;
}
}
}
function cloneElements(finalType) {
// cloneCsn only works correctly if we start "from the top"
let clone;
// do the clone only if really needed
if((finalType.items && !node.items) ||
(finalType.elements && !node.elements))
clone = cloneCsn({ definitions: { 'TypeDef': finalType } }, options);
if (finalType.items) {
delete node.type;
if(!node.items)
Object.assign(node, { items: clone.definitions.TypeDef.items });
}
if (finalType.elements) {
if(!finalType.items)
delete node.type;
if(!node.elements)
Object.assign(node, { elements: clone.definitions.TypeDef.elements });
}
}
}

@@ -124,2 +198,2 @@

module.exports = expandToFinalBaseType;
module.exports = expandToFinalBaseType;

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

*/
function typesExposure(csn, whatsMyServiceName, options, csnUtils, message) {
function typesExposure(csn, whatsMyServiceName, autoexposeSchemaName, options, csnUtils, message) {
const { error } = message;

@@ -34,5 +34,5 @@ // are we working with OData proxies or cross-service refs

if (serviceName) {
if (['type', 'entity', 'view'].includes(def.kind)) {
if (def.kind === 'type' || def.kind === 'entity') {
forEachMember(def, (element, elementName, propertyName, path) => {
if (['elements', 'params'].includes(propertyName)) {
if (propertyName === 'elements' || propertyName === 'params') {
const artificialtName = `${isMultiSchema ?

@@ -195,3 +195,3 @@ defNameWithoutServiceOrContextName(defName, serviceName)

const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
const newSchemaName = `${service}.${typeContext || typeNamespace || 'root'}`;
const newSchemaName = `${service}.${typeContext || typeNamespace || autoexposeSchemaName}`;
// new type name without any prefixes

@@ -215,3 +215,3 @@ const typePlainName = typeContext ? defNameWithoutServiceOrContextName(typeName, typeContext)

let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
const newSchemaName = currPrefix || 'root';
const newSchemaName = currPrefix || autoexposeSchemaName;
// new type name without any prefixes

@@ -218,0 +218,0 @@ const typePlainName = defNameWithoutServiceOrContextName(typeName, newSchemaName);

@@ -21,3 +21,3 @@ const {

function isManagedAssociationElement(artifact) {
function isManagedAssociation(artifact) {
return artifact.target !== undefined && artifact.on === undefined;

@@ -98,3 +98,3 @@ }

isLocalizedArtifactInService,
isManagedAssociationElement,
isManagedAssociation,
}

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

const { cloneCsn, cloneCsnDictionary, getUtils, isBuiltinType } = require('../model/csnUtils');
const { ModelError } = require("../base/error");

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

// 'model' is compacted new style CSN
// TODO: Error and warnings handling with compacted CSN? - currently just throw new Error for everything
// TODO: Error and warnings handling with compacted CSN? - currently just throw new ModelError for everything
// TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true

@@ -251,2 +252,4 @@ function getTransformers(model, options, pathDelimiter = '_') {

// Collect all child elements (recursively) into 'result'
// TODO: Do not report collisions in the generated elements here, but instead
// leave that work to the receiver of this result
let result = Object.create(null);

@@ -292,3 +295,7 @@ const addGeneratedFlattenedElement = (e, eName) => {

// Copy selected type properties
for (let p of ['key', 'virtual', 'masked', 'viaAll']) {
const props = ['key', 'virtual', 'masked', 'viaAll'];
// 'localized' is needed for OData
if(options.toOdata)
props.push('localized');
for (let p of props) {
if (elem[p]) {

@@ -320,2 +327,4 @@ flatElem[p] = elem[p];

return ref;
} else if(scope === '$self' && ref.length === 2) {
return ref;
}

@@ -326,3 +335,3 @@

function flatten(ref, path) {
let result = [];
let result = scope === '$self' ? [ref[0]] : [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal

@@ -336,19 +345,21 @@ if(!links && !scope) { // calculate JIT if not supplied

return ref;
// Don't process a leading $self - it will a .art with .elements!
const startIndex = scope === '$self' ? 1 : 0;
let flattenStep = false;
links.forEach((value, idx) => {
for(let i = startIndex; i < links.length; i++) {
const value = links[i];
if (flattenStep) {
result[result.length - 1] += pathDelimiter + (ref[idx].id ? ref[idx].id : ref[idx]);
result[result.length - 1] += pathDelimiter + (ref[i].id ? ref[i].id : ref[i]);
// if we had a filter or args, we had an assoc so this step is done
// we then keep along the filter/args by updating the id of the current ref
if(ref[idx].id) {
ref[idx].id = result[result.length-1];
result[result.length-1] = ref[idx];
if(ref[i].id) {
ref[i].id = result[result.length-1];
result[result.length-1] = ref[i];
}
}
else {
result.push(ref[idx]);
result.push(ref[i]);
}
flattenStep = value.art && !value.art.kind && !value.art.SELECT && !value.art.from && (value.art.elements || effectiveType(value.art).elements || (resolvedLinkTypes.get(value)||{}).elements);
});
}

@@ -399,3 +410,3 @@ return result;

* @param {WeakMap} [resolved] WeakMap containing already resolved refs
* @param {boolean} [keepLocalized=false] Wether to clone .localized from a type def
* @param {boolean} [keepLocalized=false] Whether to clone .localized from a type def
* @returns {void}

@@ -431,2 +442,3 @@ */

if (typeDef.items || typeDef.elements) {
// Expand structured types on entity elements for flat OData
if(!(options.transformation === 'hdbcds' || options.toSql))

@@ -437,6 +449,7 @@ return;

const cloneTypeDef = cloneCsn(typeDef, options);
// With hdbcds-hdbcds, don't resolve structured types - but propagrate ".items", to turn into LargeString later on.
// With hdbcds-hdbcds, don't resolve structured types - but propagate ".items", to turn into LargeString later on.
if(typeDef.items) {
delete node.type;
Object.assign(node, {items: cloneTypeDef.items});
if(!node.items)
Object.assign(node, {items: cloneTypeDef.items});
}

@@ -446,3 +459,4 @@ if(typeDef.elements && !(options.transformation === 'hdbcds' && options.sqlMapping === 'hdbcds')) {

delete node.type;
Object.assign(node, {elements: cloneTypeDef.elements});
if(!node.elements)
Object.assign(node, {elements: cloneTypeDef.elements});
}

@@ -607,3 +621,3 @@

if (!isBuiltinType(typeName) && !model.definitions[typeName]) {
throw new Error('Expecting valid type name: ' + typeName);
throw new ModelError('Expecting valid type name: ' + typeName);
}

@@ -699,3 +713,3 @@ let result = {

if (!elem.target || !elem.keys) {
throw new Error('Expecting managed association element with foreign keys');
throw new ModelError('Expecting managed association element with foreign keys');
}

@@ -719,3 +733,3 @@

if (!artifact.elements) {
throw new Error('Expecting artifact with elements: ' + JSON.stringify(artifact));
throw new ModelError('Expecting artifact with elements: ' + JSON.stringify(artifact));
}

@@ -751,3 +765,3 @@ let elemName = Object.keys(elem)[0];

if (!artifact.elements) {
throw new Error('Expected structured artifact');
throw new ModelError('Expected structured artifact');
}

@@ -783,3 +797,3 @@ // Must not already have such an element

if (!isBuiltinType(returnTypeName) && !model.definitions[returnTypeName])
throw new Error('Expecting valid return type name: ' + returnTypeName);
throw new ModelError('Expecting valid return type name: ' + returnTypeName);
action.returns = { type: returnTypeName };

@@ -791,3 +805,3 @@ }

if (!isBuiltinType(paramTypeName) && !model.definitions[paramTypeName])
throw new Error('Expecting valid parameter type name: ' + paramTypeName);
throw new ModelError('Expecting valid parameter type name: ' + paramTypeName);

@@ -1125,3 +1139,3 @@ action.params = Object.create(null);

}
}, undefined, undefined, options);
}, [], options);

@@ -1128,0 +1142,0 @@ /*

@@ -45,2 +45,30 @@ 'use strict';

/**
* Loops over all elements in an object and calls the specified callback(o[key]) for each key
* --> can be used as substitute for `Object.values(…).forEach(…)`
*
* @param {object} o the object which values should be iterated
* @param {Function} callback
*/
function forEachValue(o, callback) {
for (const key in o) {
if (Object.hasOwnProperty.call(o, key))
callback(o[key]);
}
}
/**
* Loops over all elements in an object and calls the specified callback(key) for each key
* --> can be used as substitute for `Object.keys(…).forEach(…)`
*
* @param {object} o the object which keys should be iterated
* @param {Function} callback
*/
function forEachKey(o, callback) {
for (const key in o) {
if (Object.hasOwnProperty.call(o, key))
callback(key);
}
}
module.exports = {

@@ -50,2 +78,4 @@ copyPropIfExist,

forEach,
forEachValue,
forEachKey,
};
{
"name": "@sap/cds-compiler",
"version": "2.12.0",
"version": "2.13.6",
"description": "CDS (Core Data Services) compiler and backends",

@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/",

@@ -33,1 +33,27 @@ # Message Explanations

short.
## Example Structure
```markdown
# ‹message-id›
Longer message text. Some background info.
Usually mentions the severity.
## Example
Code which also lead to the current message,
with an explanation why it is problematic.
## How to Fix
Description of options how the issue can be fixed, using the example.
## Notes on …
Optional: more background info.
## Related messages
- Optional: message ids for similar issues.
```

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

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc