Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
105
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 4.5.0 to 4.6.0

lib/transform/addTenantFields.js

24

bin/cdsc.js

@@ -30,6 +30,8 @@ #!/usr/bin/env node

const {
explainMessage, hasMessageExplanation, sortMessages, messageIdsWithExplanation,
explainMessage, hasMessageExplanation, sortMessages,
messageIdsWithExplanation, makeMessageFunction,
} = require('../lib/base/messages');
const { term } = require('../lib/utils/term');
const { addLocalizationViews } = require('../lib/transform/localized');
const { addTenantFields } = require('../lib/transform/addTenantFields');
const { availableBetaFlags } = require('../lib/base/model');

@@ -67,13 +69,9 @@ const { alterConstraintsWithCsn } = require('../lib/render/manageConstraints');

case 'user':
if (!options.variableReplacements)
options.variableReplacements = {};
if (!options.variableReplacements.$user)
options.variableReplacements.$user = {};
options.variableReplacements ??= {};
options.variableReplacements.$user ??= {};
options.variableReplacements.$user.id = value;
break;
case 'locale':
if (!options.variableReplacements)
options.variableReplacements = {};
if (!options.variableReplacements.$user)
options.variableReplacements.$user = {};
options.variableReplacements ??= {};
options.variableReplacements.$user ??= {};
options.variableReplacements.$user.locale = value;

@@ -396,3 +394,4 @@ break;

const csn = options.directBackend ? model : compactModel(model, options);
const renameResult = _toRename(csn, options);
const messageFunctions = makeMessageFunction(csn, options, 'to.rename');
const renameResult = _toRename(csn, options, messageFunctions);
let storedProcedure = `PROCEDURE RENAME_${renameResult.options.sqlMapping.toUpperCase()}_TO_PLAIN LANGUAGE SQLSCRIPT AS BEGIN\n`;

@@ -412,3 +411,4 @@ for (const name in renameResult.rename) {

const { src } = options || {};
const alterConstraintsResult = alterConstraintsWithCsn(csn, options);
const messageFunctions = makeMessageFunction(csn, options, 'alterConstraints');
const alterConstraintsResult = alterConstraintsWithCsn(csn, options, messageFunctions);
Object.keys(alterConstraintsResult).forEach((id) => {

@@ -566,2 +566,4 @@ const renderedConstraintStatement = alterConstraintsResult[id];

const csn = compactModel(xsn, options);
if (command === 'toCsn' && options.tenantAsColumn)
addTenantFields(csn, options);
if (command === 'toCsn' && options.withLocalized)

@@ -568,0 +570,0 @@ addLocalizationViews(csn, options);

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

## Version 4.6.0 - 2024-01-26
### Added `vectorType`
Using this beta flag, the new type `cds.Vector` is available for modeling.
## Version 4.5.0 - 2023-12-08

@@ -13,0 +19,0 @@

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

const baseError = lazyload('../base/error');
const edmToCsn = lazyload('../edm/csn2edm');
const csnToEdm = lazyload('../edm/csn2edm');
const trace = lazyload('./trace');

@@ -30,2 +30,3 @@

const { checkRemovedDeprecatedFlags } = require('../base/model');
const { makeMessageFunction } = require('../base/messages');

@@ -91,13 +92,15 @@ /**

* @param {string[]} warnAboutMismatch Option names to warn about, but not error on
* @param {string} module Name of the module that calls this function, e.g. `for.odata`
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
*/
function checkPreTransformedCsn( csn, options, relevantOptionNames, warnAboutMismatch, module ) {
if (!csn.meta) {
function checkPreTransformedCsn( csn, options,
relevantOptionNames, warnAboutMismatch,
messageFunctions ) {
if (!csn.meta?.options) {
// Not able to check
return;
}
const { error, warning, throwWithAnyError } = messages.makeMessageFunction(csn, options, module);
const { error, warning, throwWithAnyError } = messageFunctions;
for (const name of relevantOptionNames ) {
if (options[name] !== csn.meta.options?.[name]) {
if (options[name] !== csn.meta.options[name]) {
error('api-invalid-option-preprocessed', null, { prop: name, value: options[name], othervalue: csn.meta.options[name] },

@@ -136,7 +139,9 @@ 'Expected pre-processed CSN to have option $(PROP) set to $(VALUE). Found: $(OTHERVALUE)');

* @param {object} internalOptions processed options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} Return an oData-pre-processed CSN
*/
function odataInternal( csn, internalOptions ) {
function odataInternal( csn, internalOptions, messageFunctions ) {
internalOptions.transformation = 'odata';
const oDataCsn = forOdataNew.transform4odataWithCsn(csn, internalOptions);
const oDataCsn = forOdataNew.transform4odataWithCsn(csn, internalOptions, messageFunctions);
messageFunctions.setModel(oDataCsn);
attachTransformerCharacteristics(oDataCsn, 'odata', internalOptions, relevantOdataOptions, warnAboutMismatchOdata);

@@ -150,9 +155,10 @@ return oDataCsn;

* @param {CSN.Model} csn Clean input CSN
* @param {ODataOptions} [options] Options
* @param {ODataOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {oDataCSN} Return an oData-pre-processed CSN
*/
function odata( csn, options = {} ) {
function odata( csn, options, messageFunctions ) {
trace.traceApi('for.odata', options);
const internalOptions = prepareOptions.for.odata(options);
return odataInternal(csn, internalOptions);
return odataInternal(csn, internalOptions, messageFunctions);
}

@@ -164,9 +170,10 @@

* @param {object} csn CSN to process
* @param {object} [options] Options
* @param {object} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} { model: string, namespace: string }
*/
function cdl( csn, options = {} ) {
function cdl( csn, options, messageFunctions ) {
trace.traceApi('to.cdl', options);
const internalOptions = prepareOptions.to.cdl(options);
return toCdl.csnToCdl(csn, internalOptions);
return toCdl.csnToCdl(csn, internalOptions, messageFunctions);
}

@@ -180,9 +187,11 @@

* @param {SqlOptions} internalOptions Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {CSN.Model} CSN transformed like to.sql
* @private
*/
function csnForSql( csn, internalOptions ) {
function csnForSql( csn, internalOptions, messageFunctions ) {
internalOptions.transformation = 'sql';
const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.sql');
const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(
csn, internalOptions, messageFunctions
);
return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;

@@ -196,9 +205,10 @@ }

* @param {CSN.Model} csn Plain input CSN
* @param {SqlOptions} [options] Options
* @param {SqlOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {CSN.Model} CSN transformed like to.sql
* @private
*/
function forSql( csn, options = {} ) {
function forSql( csn, options, messageFunctions ) {
const internalOptions = prepareOptions.to.sql(options);
return csnForSql(csn, internalOptions);
return csnForSql(csn, internalOptions, messageFunctions);
}

@@ -210,11 +220,13 @@

* @param {CSN.Model} csn Plain input CSN
* @param {HdiOptions} [options] Options
* @param {HdiOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {CSN.Model} CSN transformed like to.hdi
* @private
*/
function forHdi( csn, options = {} ) {
function forHdi( csn, options, messageFunctions ) {
const internalOptions = prepareOptions.to.hdi(options);
internalOptions.transformation = 'sql';
const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.hdi');
const transformedCsn = forRelationalDB.transformForRelationalDBWithCsn(
csn, internalOptions, messageFunctions
);
return internalOptions.testMode ? toCsn.sortCsn(transformedCsn, internalOptions) : transformedCsn;

@@ -226,12 +238,13 @@ }

* @param {CSN.Model} csn Plain input CSN
* @param {HdbcdsOptions} [options] Options
* @param {HdbcdsOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {CSN.Model} CSN transformed like to.hdbcds
* @private
*/
function forHdbcds( csn, options = {} ) {
function forHdbcds( csn, options, messageFunctions ) {
const internalOptions = prepareOptions.to.hdbcds(options);
internalOptions.transformation = 'hdbcds';
const hanaCsn = forRelationalDB.transformForRelationalDBWithCsn(csn, internalOptions, 'to.hdbcds');
const hanaCsn = forRelationalDB.transformForRelationalDBWithCsn(
csn, internalOptions, messageFunctions
);
return internalOptions.testMode ? toCsn.sortCsn(hanaCsn, internalOptions) : hanaCsn;

@@ -244,12 +257,12 @@ }

* @param {CSN.Model} csn Plain input CSN
* @param {EffectiveCsnOptions} [options={}] Options
* @param {EffectiveCsnOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {CSN.Model} CSN transformed
* @private
*/
function forEffective( csn, options = {} ) {
function forEffective( csn, options, messageFunctions ) {
const internalOptions = prepareOptions.to.sql(options);
internalOptions.transformation = 'effective';
const eCsn = effective.effectiveCsn(csn, internalOptions);
const eCsn = effective.effectiveCsn(csn, internalOptions, messageFunctions);
return internalOptions.testMode ? toCsn.sortCsn(eCsn, internalOptions) : eCsn;

@@ -262,6 +275,7 @@ }

* @param {CSN.Model} csn A clean input CSN
* @param {SqlOptions} [options] Options
* @param {SqlOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {SQL[]} Array of SQL statements, tables first, views second
*/
function sql( csn, options = {} ) {
function sql( csn, options, messageFunctions ) {
trace.traceApi('to.sql', options);

@@ -272,4 +286,5 @@ const internalOptions = prepareOptions.to.sql(options);

// we need the CSN for view sorting
const transformedCsn = csnForSql(csn, internalOptions);
const sqls = toSql.toSqlDdl(transformedCsn, internalOptions);
const transformedCsn = csnForSql(csn, internalOptions, messageFunctions);
messageFunctions.setModel(transformedCsn);
const sqls = toSql.toSqlDdl(transformedCsn, internalOptions, messageFunctions);

@@ -287,6 +302,7 @@ const result = sortViews({ csn: transformedCsn, sql: sqls.sql });

* @param {CSN.Model} csn A clean input CSN
* @param {HdiOptions} [options] Options
* @param {HdiOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {HDIArtifacts} { <filename>:<content>, ...}
*/
function hdi( csn, options = {} ) {
function hdi( csn, options, messageFunctions ) {
trace.traceApi('to.hdi', options);

@@ -296,4 +312,5 @@ const internalOptions = prepareOptions.to.hdi(options);

// we need the CSN for view sorting
const sqlCSN = forHdi(csn, options);
const sqls = toSql.toSqlDdl(sqlCSN, internalOptions);
const sqlCSN = forHdi(csn, options, messageFunctions);
messageFunctions.setModel(sqlCSN);
const sqls = toSql.toSqlDdl(sqlCSN, internalOptions, messageFunctions);

@@ -388,2 +405,3 @@ if (internalOptions.testMode) {

* @param {HdiOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {CSN.Model} beforeImage A db-transformed CSN representing the "before-image", or null in case no such image

@@ -396,23 +414,19 @@ * is known, i.e. for the very first migration step.

*/
function sqlMigration( csn, options, beforeImage ) {
function sqlMigration( csn, options, messageFunctions, beforeImage ) {
trace.traceApi('to.sql.migration', options);
const internalOptions = prepareOptions.to.sql(options);
const {
error, warning, info, throwWithError, message,
} = messages.makeMessageFunction(csn, internalOptions, 'to.sql.migration');
const { error, throwWithError } = messageFunctions;
// Prepare after-image.
const afterImage = internalOptions.filterCsn
? diffFilter.csn(csnForSql(csn, internalOptions)) : csnForSql(csn, internalOptions);
let afterImage = csnForSql(csn, internalOptions, messageFunctions);
if (internalOptions.filterCsn)
afterImage = diffFilter.csn(afterImage);
// Compare both images.
const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
messageFunctions.setModel(diff);
const diffFilterObj = diffFilter[internalOptions.sqlDialect];
if (diffFilterObj) {
diff.extensions = diff.extensions.filter(ex => diffFilterObj.extension(ex, {
error, warning, info, throwWithError, message,
}));
diff.migrations.forEach(migration => diffFilterObj.migration(migration, {
error, warning, info, throwWithError, message,
}));
diff.extensions = diff.extensions.filter(ex => diffFilterObj.extension(ex, messageFunctions));
diff.migrations.forEach(migration => diffFilterObj.migration(migration, messageFunctions));
Object.entries(diff.deletions).forEach(entry => diffFilterObj.deletion(entry, error));

@@ -495,3 +509,3 @@ diff.changedPrimaryKeys = diff.changedPrimaryKeys

deletions, constraintDeletions, migrations, constraints, ...hdbkinds
} = toSql.toSqlDdl(diff, internalOptions);
} = toSql.toSqlDdl(diff, internalOptions, messageFunctions);

@@ -549,2 +563,3 @@ cleanup.forEach(fn => fn());

* @param {HdiOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {CSN.Model} beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image

@@ -554,3 +569,3 @@ * is known, i.e. for the very first migration step

*/
function hdiMigration( csn, options, beforeImage ) {
function hdiMigration( csn, options, messageFunctions, beforeImage ) {
trace.traceApi('to.hdi.migration', options);

@@ -560,5 +575,6 @@ const internalOptions = prepareOptions.to.hdi(options);

// Prepare after-image.
const afterImage = forHdi(csn, options);
const afterImage = forHdi(csn, options, messageFunctions);
const diff = modelCompare.compareModels(beforeImage || afterImage, afterImage, internalOptions);
messageFunctions.setModel(diff);

@@ -575,3 +591,3 @@ // Convert the diff to SQL.

deletions, migrations, constraintDeletions, ...hdbkinds
} = toSql.toSqlDdl(diff, internalOptions);
} = toSql.toSqlDdl(diff, internalOptions, messageFunctions);

@@ -639,6 +655,7 @@ return {

* @param {any} csn A clean input CSN
* @param {HdbcdsOptions} [options] Options
* @param {HdbcdsOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {HDBCDS} { <filename>:<content>, ...}
*/
function hdbcds( csn, options = {} ) {
function hdbcds( csn, options, messageFunctions ) {
trace.traceApi('to.hdbcds', options);

@@ -648,17 +665,18 @@ const internalOptions = prepareOptions.to.hdbcds(options);

const hanaCsn = forHdbcds(csn, internalOptions);
const result = flattenResultStructure(toHdbcds.toHdbcdsSource(hanaCsn, internalOptions));
return result;
const hanaCsn = forHdbcds(csn, internalOptions, messageFunctions);
messageFunctions.setModel(hanaCsn);
const result = toHdbcds.toHdbcdsSource(hanaCsn, internalOptions, messageFunctions);
return flattenResultStructure(result);
}
/**
* Generate a edm document for the given service
* Generate an edm document for the given service
*
* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options] Options
* @param {ODataOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {edm} The JSON representation of the service
*/
function edm( csn, options = {} ) {
function edm( csn, options, messageFunctions ) {
trace.traceApi('to.edm', options);
// If not provided at all, set service to undefined to trigger validation
// If not provided at all, set service to 'undefined' to trigger validation
const internalOptions = prepareOptions.to.edm(

@@ -673,8 +691,10 @@ // eslint-disable-next-line comma-dangle

if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
servicesEdmj = preparedCsnToEdm(csn, service, internalOptions);
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
servicesEdmj = preparedCsnToEdm(csn, service, internalOptions, messageFunctions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
servicesEdmj = preparedCsnToEdm(oDataCsn, service, internalOptions);
const oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
messageFunctions.setModel(oDataCsn);
servicesEdmj = preparedCsnToEdm(oDataCsn, service, internalOptions, messageFunctions);
}

@@ -690,9 +710,10 @@ return servicesEdmj.edmj;

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options] Options
* @param {ODataOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {edms} { <service>:<JSON representation>, ...}
*/
function edmall( csn, options = {} ) {
function edmall( csn, options, messageFunctions ) {
trace.traceApi('to.edm.all', options);
const internalOptions = prepareOptions.to.edm(options);
const { error } = messages.makeMessageFunction(csn, internalOptions, 'for.odata');
const { error } = messageFunctions;

@@ -705,9 +726,12 @@ if (internalOptions.odataVersion === 'v2')

if (isPreTransformed(csn, 'odata'))
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
}
else {
oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
}
else
oDataCsn = odataInternal(csn, internalOptions);
const servicesJson = preparedCsnToEdmAll(oDataCsn, internalOptions);
messageFunctions.setModel(oDataCsn);
const servicesJson = preparedCsnToEdmAll(oDataCsn, internalOptions, messageFunctions);
const services = servicesJson.edmj;

@@ -720,11 +744,12 @@ for (const serviceName in services)

/**
* Generate a edmx document for the given service
* Generate an edmx document for the given service
*
* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options] Options
* @param {ODataOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {edmx} The XML representation of the service
*/
function edmx( csn, options = {} ) {
function edmx( csn, options, messageFunctions ) {
trace.traceApi('to.edmx', options);
// If not provided at all, set service to undefined to trigger validation
// If not provided at all, set service to 'undefined' to trigger validation
const internalOptions = prepareOptions.to.edmx(

@@ -739,8 +764,10 @@ // eslint-disable-next-line comma-dangle

if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
services = preparedCsnToEdmx(csn, service, internalOptions);
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
services = preparedCsnToEdmx(csn, service, internalOptions, messageFunctions);
}
else {
const oDataCsn = odataInternal(csn, internalOptions);
services = preparedCsnToEdmx(oDataCsn, service, internalOptions);
const oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
messageFunctions.setModel(oDataCsn);
services = preparedCsnToEdmx(oDataCsn, service, internalOptions, messageFunctions);
}

@@ -757,6 +784,7 @@

* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options] Options
* @param {ODataOptions} options Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {edmxs} { <service>:<XML representation>, ...}
*/
function edmxall( csn, options = {} ) {
function edmxall( csn, options, messageFunctions ) {
trace.traceApi('to.edmx.all', options);

@@ -768,9 +796,12 @@ const internalOptions = prepareOptions.to.edmx(options);

if (isPreTransformed(csn, 'odata'))
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions, warnAboutMismatchOdata, 'for.odata');
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
}
else {
oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
}
else
oDataCsn = odataInternal(csn, internalOptions);
const servicesEdmx = preparedCsnToEdmxAll(oDataCsn, internalOptions);
messageFunctions.setModel(oDataCsn);
const servicesEdmx = preparedCsnToEdmxAll(oDataCsn, internalOptions, messageFunctions);
const services = servicesEdmx.edmx;

@@ -787,2 +818,93 @@ // Create annotations and metadata once per service

/**
* Generate an EDM document for the given service in XML and JSON representation
* If odataVersion is not 'v4', then no JSON is rendered
*
* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options={}] Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} { <protocol> : { <ServiceName>: { edmx: <XML representation>, edm: <JSON representation> } } }
*/
// @ts-ignore
function odata2( csn, options = {}, messageFunctions ) {
trace.traceApi('to.odata', options);
// If not provided at all, set service to undefined to trigger validation
const internalOptions = prepareOptions.to.odata(
// eslint-disable-next-line comma-dangle
options.service ? options : Object.assign({ service: undefined }, options)
);
const { service } = options;
let oDataCsn = csn;
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
}
else {
oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
messageFunctions.setModel(oDataCsn);
}
const edmIR = csnToEdm.csn2edm(oDataCsn, service, internalOptions, messageFunctions);
const version = internalOptions.odataVersion;
const result = { [version]: { [service]: {} } };
if (edmIR) {
result[version][service].edmx = edmIR.toXML();
if (version === 'v4')
result[version][service].edm = edmIR.toJSON();
}
return result;
}
odata2.all = odataall;
/**
* Generate EDM documents for all services in XML and JSON representation
* If odataVersion is not 'v4', then no JSON is rendered
*
* @param {CSN|oDataCSN} csn Clean input CSN or a pre-transformed CSN
* @param {ODataOptions} [options={}] Options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} { <protocol>: { <serviceName>: { edmx: <XML representation>, edm: <JSON representation> } } }
*/
// @ts-ignore
function odataall( csn, options = {}, messageFunctions ) {
trace.traceApi('to.odata.all', options);
const internalOptions = prepareOptions.to.odata(options);
const { error } = messageFunctions;
if (internalOptions.odataVersion === 'v2')
error(null, null, {}, 'OData JSON output is not available for OData V2');
let oDataCsn = csn;
if (isPreTransformed(csn, 'odata')) {
checkPreTransformedCsn(csn, internalOptions, relevantOdataOptions,
warnAboutMismatchOdata, messageFunctions);
}
else {
oDataCsn = odataInternal(csn, internalOptions, messageFunctions);
messageFunctions.setModel(oDataCsn);
}
const edmIR = csnToEdm.csn2edmAll(oDataCsn, internalOptions, undefined, messageFunctions);
const version = internalOptions.odataVersion;
const result = {};
result[version] = {};
if (edmIR) {
for (const serviceName in edmIR) {
result[version][serviceName] = { edmx: edmIR[serviceName].toXML() };
if (internalOptions.odataVersion === 'v4')
result[version][serviceName].edm = edmIR[serviceName].toJSON();
}
}
return result;
}
/**
* Generate edmx for given 'service' based on 'csn' (new-style compact, already prepared for OData)

@@ -794,7 +916,8 @@ * using 'options'

* @param {ODataOptions} options OData / EDMX specific options.
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} Rendered EDMX string for the given service.
*/
function preparedCsnToEdmx( csn, service, options ) {
function preparedCsnToEdmx( csn, service, options, messageFunctions ) {
timetrace.timetrace.start('EDMX rendering');
const e = edmToCsn.csn2edm(csn, service, options)?.toXML('all');
const e = csnToEdm.csn2edm(csn, service, options, messageFunctions)?.toXML('all');
timetrace.timetrace.stop('EDMX rendering');

@@ -810,7 +933,8 @@ return { edmx: e };

* @param {ODataOptions} options OData / EDMX specific options.
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @returns {object} Dictionary of rendered EDMX strings for each service.
*/
function preparedCsnToEdmxAll( csn, options ) {
function preparedCsnToEdmxAll( csn, options, messageFunctions ) {
timetrace.timetrace.start('EDMX all rendering');
const edmxResult = edmToCsn.csn2edmAll(csn, options);
const edmxResult = csnToEdm.csn2edmAll(csn, options, undefined, messageFunctions);
for (const service in edmxResult)

@@ -829,9 +953,10 @@ edmxResult[service] = edmxResult[service].toXML('all');

* @param {ODataOptions} options OData / EDMX specific options.
* @param {object} [messageFunctions] Message functions such as `error()`, `info()`, …
* @returns {object} Rendered EDM JSON object for of the given service.
*/
function preparedCsnToEdm( csn, service, options ) {
function preparedCsnToEdm( csn, service, options, messageFunctions ) {
timetrace.timetrace.start('EDM rendering');
// Override OData version as edm json is always v4
options.odataVersion = 'v4';
const e = edmToCsn.csn2edm(csn, service, options)?.toJSON();
const e = csnToEdm.csn2edm(csn, service, options, messageFunctions)?.toJSON();
timetrace.timetrace.stop('EDM rendering');

@@ -847,9 +972,10 @@ return { edmj: e };

* @param {ODataOptions} options OData / EDMX specific options.
* @param {object} [messageFunctions] Message functions such as `error()`, `info()`, …
* @returns {object} Dictionary of rendered EDM JSON objects for each service.
*/
function preparedCsnToEdmAll( csn, options ) {
function preparedCsnToEdmAll( csn, options, messageFunctions ) {
timetrace.timetrace.start('EDM all rendering');
// Override OData version as edm json is always v4
options.odataVersion = 'v4';
const edmj = edmToCsn.csn2edmAll(csn, options);
const edmj = csnToEdm.csn2edmAll(csn, options, undefined, messageFunctions);
for (const service in edmj)

@@ -894,2 +1020,3 @@ edmj[service] = edmj[service].toJSON();

edmx: publishCsnProcessor(edmx, 'to.edmx'),
odata2: publishCsnProcessor(odata2, 'to.odata'),
/** Internal only */

@@ -900,5 +1027,11 @@ for_sql: publishCsnProcessor(forSql, 'for.sql'),

for_effective: publishCsnProcessor(forEffective, 'for.effective'),
/** Deprecated, will be removed in cds-compiler@v4 */
preparedCsnToEdmx,
preparedCsnToEdm,
/* Deprecated, will be removed in cds-compiler@v5 */ // TODO(v5): Remove
preparedCsnToEdmx(csn, service, options) {
preparedCsnToEdmx(csn, service, options, makeMessageFunction( csn, options, 'to.edmx' ));
},
/* Deprecated, will be removed in cds-compiler@v5 */ // TODO(v5): Remove
preparedCsnToEdm(csn, service, options) {
preparedCsnToEdm(csn, service, options, makeMessageFunction( csn, options, 'to.edm' ));
},
};

@@ -932,4 +1065,5 @@

function api( csn, options = {}, ...args ) {
const originalMessageLength = options.messages?.length;
try {
const messageFunctions = messages.makeMessageFunction(csn, options, 'api');
const messageFunctions = messages.makeMessageFunction(csn, options, _name);
if (options.deprecated)

@@ -941,5 +1075,6 @@ checkRemovedDeprecatedFlags( options, messageFunctions );

messageFunctions.throwWithError();
messageFunctions.setModel(csn);
timetrace.timetrace.start(_name);
const result = processor( csn, options, ...args );
const result = processor( csn, options, messageFunctions, ...args );
timetrace.timetrace.stop(_name);

@@ -957,8 +1092,14 @@ return result;

const { info } = messages.makeMessageFunction( csn, options, 'compile' );
const msg = info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {}, 'CSN input had to be recompiled' );
// Reset messages to what we had before the backend crashed.
// Backends may report the same issues again after compilation.
if (originalMessageLength !== undefined)
options.messages.length = originalMessageLength;
const messageFunctions = messages.makeMessageFunction( csn, options, _name );
const recompileMsg = messageFunctions.info( 'api-recompiled-csn', location.emptyLocation('csn.json'), {},
'CSN input had to be recompiled' );
if (options.internalMsg || options.testMode)
msg.error = err; // Attach original error;
recompileMsg.error = err; // Attach original error;
if (options.testMode) // Attach recompilation reason in testMode
msg.message += `\n ↳ cause: ${ err.message }`;
recompileMsg.message += `\n ↳ cause: ${ err.message }`;

@@ -968,3 +1109,4 @@ // next line to be replaced by CSN parser call which reads the CSN object

const recompiledCsn = toCsn.compactModel(xsn);
return processor( recompiledCsn, options, ...args );
messageFunctions.setModel(recompiledCsn);
return processor( recompiledCsn, options, messageFunctions, ...args );
}

@@ -990,3 +1132,2 @@ }

for (const name of oldBackendOptionNames) {

@@ -993,0 +1134,0 @@ if (typeof options[name] === 'object') // may be a boolean due to internal options

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

'disableHanaComments', // in case of issues with hana comment rendering
'tenantAsColumn', // not published yet
'localizedWithoutCoalesce', // deprecated version of 'localizedLanguageFallback', TODO(v5): Remove option

@@ -162,2 +163,9 @@ ];

},
odata: (options) => {
const hardOptions = { combined: true, toOdata: true };
const defaultOptions = {
odataVersion: 'v4', odataFormat: 'flat',
};
return translateOptions(options, defaultOptions, hardOptions, undefined, [ 'valid-structured' ], 'to.odata');
},
},

@@ -164,0 +172,0 @@ for: { // TODO: Rename version to oDataVersion

@@ -41,2 +41,3 @@ // module- and csn/XSN-independent definitions

calcAssoc: true,
vectorType: true,
// disabled by --beta-mode

@@ -43,0 +44,0 @@ nestedServices: false,

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

* .option('-y --y-in-long-form')
* .option(' --bar-wiz <w>', ['bla', 'foo'])
* .option(' --bar-wiz <w>', { valid: ['bla', 'foo'] })
* .option('-z --name-in-cdsc', { optionName: 'nameInOptions' })
* ```

@@ -38,3 +39,3 @@ *

command,
positionalArgument: (argumentDefinition) => {
positionalArgument(argumentDefinition) {
// Default positional arguments; may be overwritten by commands.

@@ -46,7 +47,2 @@ _setPositionalArguments(argumentDefinition);

processCmdLine,
verifyOptions,
camelOptionsForCommand,
// TODO: Why exported?
_parseCommandString,
_parseOptionString,
};

@@ -58,7 +54,10 @@ return optionProcessor;

* @param {string} optString Option string describing the command line option.
* @param {string[]} [validValues] Array of valid values for the options.
* @param {object} [options] Further options such as `ignoreCase: true`
* @param {object} [options] Further options such as `ignoreCase: true` and `valid: []`.
* @param {string[]} [options.valid] Valid values for the option.
* @param {string[]} [options.ignoreCase] Ignore the case for "options.valid".
* @param {string[]} [options.optionName] Name of the option after parsing CLI arguments.
* Defaults to the camelified name of the long name.
*/
function option( optString, validValues, options ) {
return _addOption(optionProcessor, optString, validValues, options);
function option( optString, options ) {
return _addOption(optionProcessor, optString, options);
}

@@ -85,3 +84,3 @@

option: commandOption,
positionalArgument: (argumentDefinition) => {
positionalArgument(argumentDefinition) {
_setPositionalArguments(argumentDefinition, cmd.positionalArguments);

@@ -107,4 +106,4 @@ return cmd;

// Command API: Define a command option
function commandOption( optString, validValues, options ) {
return _addOption(cmd, optString, validValues, options);
function commandOption( optString, options ) {
return _addOption(cmd, optString, options);
}

@@ -133,3 +132,2 @@

const registeredNames = argList.map(arg => arg.name);

@@ -140,3 +138,6 @@ const args = argumentDefinition.split(' ');

// Remove braces, dots and camelify.
const argName = arg.replace('<', '').replace('>', '').replace('...', '').replace(/[ -]./g, s => s.substring(1).toUpperCase());
const argName = arg.replace('<', '')
.replace('>', '')
.replace('...', '')
.replace(/[ -]./g, s => s.substring(1).toUpperCase());

@@ -149,3 +150,2 @@ if (registeredNames.includes(argName))

argList.push({

@@ -169,11 +169,11 @@ name: argName,

*/
function _addOption( cmd, optString, validValues, options ) {
const cliOpt = _parseOptionString(optString, validValues);
function _addOption( cmd, optString, options ) {
const cliOpt = _parseOptionString(optString, options);
Object.assign(cliOpt, options);
_addLongOption(cmd, cliOpt.longName, cliOpt);
_addShortOption(cmd, cliOpt.shortName, cliOpt);
_addOptionName(cmd, cliOpt.longName, cliOpt);
_addOptionName(cmd, cliOpt.shortName, cliOpt);
for (const alias of cliOpt.aliases || []) {
const aliasOpt = Object.assign({ }, cliOpt, { isAlias: true });
_addLongOption(cmd, alias, aliasOpt); // use same camelName, etc. for alias
_addOptionName(cmd, alias, aliasOpt); // use same optionName, etc. for alias
}

@@ -185,6 +185,5 @@

/**
* Internal: Add longName to the list of options.
* Internal: Add a name to the list of options.
* Throws if the option is already registered in the given command context.
* or in the given command.
* `longName` may differ from `opt.longName`, e.g. for aliases.
*

@@ -194,38 +193,16 @@ * @private

*/
function _addLongOption( cmd, longName, opt ) {
if (cmd.options[longName]) {
throw new Error(`Duplicate assignment for long option ${ longName }`);
}
else if (optionProcessor.options[longName]) {
// This path is only taken if optString is for commands
optionProcessor.optionClashes.push({
option: longName,
description: `Command '${ cmd.longName }' has option clash with general options for: ${ longName }`,
});
}
cmd.options[longName] = opt;
}
/**
* Internal: Add shortName to the list of options.
* Throws if the option is already registered in the given command context.
* or in the given command.
* `longName` may differ from `opt.longName`, e.g. for aliases.
*
* @private
* @see _addOption()
*/
function _addShortOption( cmd, shortName, opt ) {
if (!shortName)
function _addOptionName( cmd, name, opt ) {
if (!name)
return;
if (cmd.options[shortName]) {
throw new Error(`Duplicate assignment for short option ${ shortName }`);
if (cmd.options[name]) {
throw new Error(`Duplicate assignment for option ${ name }`);
}
else if (optionProcessor.options[shortName]) {
else if (optionProcessor.options[name]) {
// This path is only taken if optString is for commands
optionProcessor.optionClashes.push({
option: shortName,
description: `Command '${ cmd.longName }' has option clash with general options for: ${ shortName }`,
option: name,
description: `Command '${ cmd.longName }' has option clash with general options for: ${ name }`,
});
}
cmd.options[shortName] = opt;
cmd.options[name] = opt;
}

@@ -266,7 +243,7 @@

// shortName: '-f',
// camelName: 'fooBar',
// optionName: 'fooBar', // or options.optionName if provided
// param: '<p>'
// validValues
// valid
// }
function _parseOptionString( optString, validValues ) {
function _parseOptionString( optString, options ) {
let longName;

@@ -310,7 +287,7 @@ let shortName;

if (!param && validValues)
if (!param && options?.valid)
throw new Error(`Option description has valid values but no param: ${ optString }`);
if (validValues) {
validValues.forEach((value) => {
if (options?.valid) {
options.valid.forEach((value) => {
if (typeof value !== 'string')

@@ -324,5 +301,5 @@ throw new Error(`Valid values must be of type string: ${ optString }`);

shortName,
camelName: camelifyLongOption(longName),
optionName: options?.option ?? camelifyLongOption(longName),
param,
validValues,
valid: options?.valid,
isAlias: false, // default

@@ -538,6 +515,6 @@ };

result.options[currentCommand] = {};
result.options[currentCommand][currentOption.camelName] = val;
result.options[currentCommand][currentOption.optionName] = val;
}
else {
result.options[currentOption.camelName] = val;
result.options[currentOption.optionName] = val;
}

@@ -561,3 +538,3 @@ }

const errors = currentCommand ? result.cmdErrors : result.errors;
errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.validValues }]`);
errors.push(`Invalid value "${ value }" for option "${ shortOption }${ opt.longName }" - use one of [${ opt.valid }]`);
}

@@ -600,111 +577,11 @@

// Assuming that 'options' came from this option processor, verify options therein.
// If 'command' is supplied, check only 'options.command', otherwise check
// only top-level options
// Return an array of complaints (possibly empty)
function verifyOptions( options, commandName = undefined, silent = false ) {
const result = [];
let opts;
if ((options.betaMode || options.beta) && !options.testMode && !silent) {
const mode = options.beta ? 'beta' : 'beta-mode';
result.push(`Option --${ mode } was used. This option should not be used in productive scenarios!`);
}
if (options) {
[
'defaultBinaryLength', 'defaultStringLength',
/* 'length', 'precision', 'scale' */
].forEach((facet) => {
if (options[facet] && isNaN(options[facet]))
result.push(`Invalid value "${ options[facet] }" for option "--${ facet }" - not an Integer`);
else
options[facet] = parseInt(options[facet]);
});
}
if (commandName) {
const cmd = optionProcessor.commands[commandName];
if (!cmd)
throw new Error(`Expected existing command: "${ cmd }"`);
opts = cmd.options;
options = options[cmd] || {};
if (typeof options === 'boolean') {
// Special case: command without options
options = {};
}
}
else {
opts = optionProcessor.options;
}
// Look at each supplied option
for (const camelName in options) {
const opt = opts[uncamelifyLongOption(camelName)];
let error;
if (!opt) {
// Don't report commands in top-level options
if ((commandName || !optionProcessor.commands[camelName]) && !silent) {
const prefix = commandName ? `${commandName}.` : '';
error = `Unknown option "${prefix}${camelName}"`;
}
}
else {
const param = options[camelName];
error = verifyOptionParam(param, opt, commandName ? `${ commandName }.` : '');
}
if (error)
result.push(error);
}
// hard-coded option dependencies (they disappear with command)
return result;
/**
* Verify parameter value 'param' against option definition 'opt'. Return an error
* string or false for an accepted param. Use 'prefix' when mentioning the option name.
*
* @param param
* @param opt
* @param prefix
* @return {string|boolean}
*/
function verifyOptionParam( param, opt, prefix ) {
if (opt.param) {
// Parameter is required for this option
if (typeof param === 'boolean')
return `Missing value for option "${ prefix }${ opt.camelName }"`;
else if (!isValidOptionValue(opt, param))
return `Invalid value "${ param }" for option "${ prefix }${ opt.camelName }" - use one of [${ opt.validValues }]`;
return false;
}
// Option does not expect a parameter
if (typeof param !== 'boolean') {
// FIXME: Might be a bit too strict in case of internal sub-options like 'forHana' etc...
return `Expecting boolean value for option "${ prefix }${ opt.camelName }"`;
}
return false;
}
}
function isValidOptionValue( opt, value ) {
// Explicitly convert to string, input 'value' may be boolean
value = String(value);
if (!opt.validValues || !opt.validValues.length)
if (!opt.valid?.length)
return true;
if (opt.ignoreCase)
return opt.validValues.some( valid => valid.toLowerCase() === value.toLowerCase() );
return opt.validValues.includes(value);
return opt.valid.some( valid => valid.toLowerCase() === value.toLowerCase() );
return opt.valid.includes(value);
}
// Return an array of unique camelNames of the options for the specified command
// If invalid command -> an empty array
function camelOptionsForCommand( cmd ) {
if (!cmd || !optionProcessor.commands[cmd])
return [];
cmd = optionProcessor.commands[cmd];
const names = Object.keys(cmd.options).map(name => cmd.options[name].camelName);
return [ ...new Set(names) ];
}
}

@@ -735,10 +612,2 @@

/**
* Return a long option name like "--foo-bar" for a camel-case name "fooBar"
*/
function uncamelifyLongOption( opt ) {
const longForm = opt.replace(/[A-Z]/g, s => `-${ s.toLowerCase() }`);
return `--${ longForm }`;
}
/**
* Check if 'opt' looks like a "-f" short option

@@ -745,0 +614,0 @@ */

@@ -115,38 +115,21 @@ 'use strict';

*
* @param {CSN.Artifact} art The artifact
* @todo this is a member validator, is it not?
* @param {CSN.Artifact} member The member (e.g. element artifact)
*/
function checkManagedAssoc( art ) {
forEachMemberRecursively(art, (member) => {
if (this.csnUtils.isAssocOrComposition(member) &&
!isManagedComposition.bind(this)(member)) {
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
// foreign key array, we won't emit a warning to avoid spamming the user.
const max = member.cardinality?.max ? member.cardinality.max : 1;
if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
const isNoDb = art['@cds.persistence.skip'] || art['@cds.persistence.exists'];
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
value: cardinality2str(member, false),
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
}, {
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
});
}
}
});
function checkManagedAssoc( member ) {
if (!member.target || isManagedComposition.bind(this)(member))
return;
/**
*
* @param {CSN.Element} member The member
* @returns {boolean} Whether the member is managed composition
*/
function isManagedComposition( member ) {
if (member.targetAspect)
return true;
if (!member.target)
return false;
const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
return target.kind !== 'entity';
// Implementation note: Imported services (i.e. external ones) may contain to-many associations
// with an empty foreign key list. If the user (in this case importer) explicitly sets an empty
// foreign key array, we won't emit a warning to avoid spamming the user.
const max = member.cardinality?.max ? member.cardinality.max : 1;
if (max !== 1 && !member.on && (!member.keys || member.keys.length > 0)) {
const isNoDb = this.artifact['@cds.persistence.skip'] || this.artifact['@cds.persistence.exists'];
this.warning(isNoDb ? 'to-many-no-on-noDB' : 'to-many-no-on', member.cardinality ? member.cardinality.$path : member.$path, {
value: cardinality2str(member, false),
'#': this.csnUtils.isComposition(member) ? 'comp' : 'std',
}, {
std: 'Expected association with target cardinality $(VALUE) to have an ON-condition',
comp: 'Expected composition with target cardinality $(VALUE) to have an ON-condition',
});
}

@@ -156,2 +139,17 @@ }

/**
*
* @param {CSN.Element} member The member
* @returns {boolean} Whether the member is managed composition
*/
function isManagedComposition( member ) {
if (member.targetAspect)
return true;
if (!member.target)
return false;
const target = typeof member.target === 'object' ? member.target : this.csnUtils.getCsnDef(member.target);
return target.kind !== 'entity';
}
/**
* All DB & OData flat mode must reject recursive type usages in entities

@@ -158,0 +156,0 @@ * 'items' break recursion as 'items' will turn into an NCLOB and the path

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

const { setProp } = require('../base/model');
const { xprInAnnoProperties } = require('../compiler/builtins');
/**

@@ -20,5 +22,6 @@ * The following properties are attached as non-enumerable where appropriate:

* @param {CSN.Model} csn CSN to enrich in-place
* @param {enrichCsnOptions} [options={}]
* @returns {{ csn: CSN.Model, cleanup: () => void }} CSN with all ref's pre-resolved
*/
function enrichCsn( csn ) {
function enrichCsn( csn, options ) {
const transformers = {

@@ -36,4 +39,3 @@ elements: dictionary,

columns,
// Annotations are ignored.
'@': () => { /* ignore annotations */ },
'@': annotation,
};

@@ -89,2 +91,31 @@ let cleanupCallbacks = [];

/**
* Transformer for things that are annotations. When we have a "=" plus an expression of some sorts,
* we treat it like a "standard" thing.
*
* @param {object | Array} _parent the thing that has _prop
* @param {string|number} _prop the name of the current property or index
* @param {object} node The value of node[_prop]
*/
function annotation( _parent, _prop, node ) {
if (options.processAnnotations) {
if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
standard(_parent, _prop, node);
}
else if (node && typeof node === 'object') {
csnPath.push(_prop);
if (Array.isArray(node)) {
node.forEach( (n, i) => annotation( node, i, n ) );
}
else {
for (const name of Object.getOwnPropertyNames( node ))
annotation( node, name, node[name] );
}
csnPath.pop();
}
}
}
// eslint-disable-next-line jsdoc/require-jsdoc

@@ -185,1 +216,6 @@ function columns( parent, prop, node ) {

module.exports = enrichCsn;
/**
* @typedef {object} enrichCsnOptions
* @property {boolean} [processAnnotations=false] Wether to process annotations and call custom transformers on them
*/

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

validateAssociationsInItems, checkForInvalidTarget,
checkVirtualElement ];
checkVirtualElement, checkManagedAssoc ];

@@ -130,3 +130,2 @@ // TODO: checkManagedAssoc is a forEachMemberRecursively!

checkTypeDefinitionHasType,
checkManagedAssoc,
checkRecursiveTypeUsage,

@@ -155,3 +154,3 @@ ];

iterateOptions = {} ) {
const { cleanup } = enrich(csn);
const { cleanup } = enrich(csn, { processAnnotations: that.options.tranformation === 'odata' });

@@ -158,0 +157,0 @@ applyTransformations(csn, mergeCsnValidators(csnValidators, that), [], { drillRef: true });

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

// expressions as annotation values
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type',
'$tokenTexts', 'op', 'args', 'func', '_artifact', 'type', '$typeArgs',
'scale', 'srid', 'length', 'precision',
],

@@ -486,0 +487,0 @@ // TODO: restrict path to #simplePath

@@ -35,2 +35,3 @@ // The builtin artifacts of CDS

UUID: { category: 'string' },
Vector: { parameters: [ 'length' /* , 'type' */ ], category: 'vector' },
Association: { internal: true, category: 'relation' },

@@ -324,5 +325,3 @@ Composition: { internal: true, category: 'relation' },

function isAnnotationExpression( val ) {
// TODO: we might allow `'=': true`, not just a string, for expressions, to be
// decided → just check truthy at the moment
return val['='] && xprInAnnoProperties.some( prop => val[prop] !== undefined );
return val?.['='] !== undefined && xprInAnnoProperties.some( prop => val[prop] !== undefined );
}

@@ -359,2 +358,3 @@

geo: [],
vector: [],
};

@@ -406,2 +406,12 @@ // Fill type categories with `cds.*` types

/**
* Tell if a name is a magic variable
*
* @param {string} name
* @returns {boolean}
*/
function isMagicVariable( name ) {
return typeof name === 'string' && Object.prototype.hasOwnProperty.call(magicVariables, name);
}
/**
* Add CDS builtins like the `cds` namespace with types like `cds.Integer` to

@@ -418,5 +428,10 @@ * `definitions` of the XSN model as well as to `$builtins`.

model.definitions.cds = cds;
// Also add the core artifacts to model.definitions`
model.$builtins = env( core, 'cds.', cds );
const c = { ...core };
if (!isBetaEnabled( model.options, 'vectorType' ))
delete c.Vector;
model.$builtins = env( c, 'cds.', cds );
model.$builtins.cds = cds;
// namespace:"cds.hana" stores HANA-specific builtins ---

@@ -542,3 +557,4 @@ const hana = createNamespace( 'cds.hana', 'reserved' );

isBuiltinType,
isMagicVariable,
isGeoTypeName,
};

@@ -18,2 +18,3 @@ // Checks on XSN performed during compile() that are useful for the user

forEachMemberRecursively,
isDeprecatedEnabled,
} = require('../base/model');

@@ -157,9 +158,14 @@ const { CompilerAssertion } = require('../base/error');

if (!effectiveType) {
return; // e.g. illegal definition references, cycles, ...
if (!effectiveType || (effectiveType.type && !effectiveType.type._artifact)) {
return; // e.g. illegal definition references, cycles, unknown artifacts, …
}
else if (!art.type && !effectiveType.type && !effectiveType?.builtin) {
error( 'type-missing-type', [ art.location, user ],
{ otherprop: 'type', prop: actualParams[0] },
'Missing $(OTHERPROP) property next to $(PROP)' );
// Special case for deprecated flag "ignore specified elements": The `type` property
// is lost in columns, but `length`,… are kept -> mismatch. This behavior is the
// same as in cds-compiler v3. See #12169 for details.
if (!isDeprecatedEnabled( model.options, 'ignoreSpecifiedQueryElements' )) {
error( 'type-missing-type', [ art.location, user ],
{ otherprop: 'type', prop: actualParams[0] },
'Missing $(OTHERPROP) property next to $(PROP)' );
}
return;

@@ -820,2 +826,5 @@ }

function checkAnnotationAssignment1( art, anno ) {
if (art.$contains?.$annotation)
checkAnnotationExpressions( anno, art );
// Sanity checks (ignore broken assignments)

@@ -888,3 +897,2 @@ if (!anno.name?.id)

}
return;

@@ -897,2 +905,18 @@ }

/**
* Check the expressions inside annotations.
*/
function checkAnnotationExpressions( anno, art ) {
if (anno.$tokenTexts) {
checkGenericExpression( anno, art );
}
else if (anno.literal === 'array') {
anno.val.forEach( val => checkAnnotationExpressions( val, art ) );
}
else if (anno.literal === 'struct') {
const struct = Object.values(anno.struct);
struct.forEach(val => checkAnnotationExpressions( val, art ));
}
}
// Check that annotation assignment 'value' (having 'path or 'literal' and

@@ -899,0 +923,0 @@ // 'val') is potentially assignable to element 'element'. Complain on 'loc'

@@ -943,2 +943,4 @@ // Compiler phase 1 = "define": transform dictionary of AST-like XSNs into XSN

}
initItemsLinks( col, parent._block );
}

@@ -1003,11 +1005,3 @@

const isQueryExtension = construct.kind === 'extend' && main.query;
let obj = construct;
let { items } = obj;
while (items) {
setLink( items, '_outer', obj );
setLink( items, '_parent', obj._parent );
setLink( items, '_block', block );
obj = items;
items = obj.items;
}
let obj = initItemsLinks( construct, block );
if (obj.target && targetIsTargetAspect( obj )) {

@@ -1177,2 +1171,10 @@ obj.targetAspect = obj.target;

createAndLinkCalcDepElement( elem );
// Special case (hack) for calculated elements that use associations+filter:
// See "Notes on `$filtered`" in `ExposingAssocWithFilter.md` for details.
if (elem.target && elem.value.path?.[elem.value.path.length - 1]?.where) {
delete elem.type;
delete elem.on;
delete elem.target;
}
}

@@ -1205,2 +1207,22 @@ }

/**
* Initialize artifact links inside `obj.items` (for nested ones as well).
* Does nothing, it `obj.items` does not exist.
*
* @param {XSN.Artifact} obj
* @param {object} block
* @return {XSN.Artifact}
*/
function initItemsLinks( obj, block ) {
let { items } = obj;
while (items) {
setLink( items, '_outer', obj );
setLink( items, '_parent', obj._parent );
setLink( items, '_block', block );
obj = items;
items = obj.items;
}
return obj;
}
// To be reworked -------------------------------------------------------------

@@ -1207,0 +1229,0 @@

@@ -381,2 +381,6 @@ // Tweak associations: rewrite keys and on conditions

function rewriteKeys( elem, assoc ) {
addConditionFromAssocPublishing( elem, assoc, null );
if (elem.on)
return; // foreign keys were transformed into ON-condition
// TODO: split this function: create foreign keys without `targetElement`

@@ -403,4 +407,2 @@ // already in Phase 2: redirectImplicitly()

elem.foreignKeys[$inferred] = 'rewrite';
addConditionFromAssocPublishing( elem, assoc );
}

@@ -428,8 +430,5 @@

: { navigation: assoc }; // redirected user-provided
const cond = copyExpr( assoc.on,
elem.on = copyExpr( assoc.on,
// replace location in ON except if from mixin element
nav.tableAlias && elem.name.location );
elem.on = cond;
addConditionFromAssocPublishing( elem, assoc );
// `cond` still points to the original condition; does not include possible assoc filter
nav.tableAlias && elem.name.location );
elem.on.$inferred = 'copy';

@@ -440,2 +439,3 @@

return; // should not happen: $projection, $magic, or ref to const
// Currently, having an unmanaged association inside a struct is not

@@ -447,6 +447,5 @@ // supported by this function:

error( 'rewrite-not-supported', [ elem.target.location, elem ] );
return;
}
if (!nav.tableAlias || nav.tableAlias.path) {
traverseExpr( cond, 'rewrite-on', elem,
else if (!nav.tableAlias || nav.tableAlias.path) {
traverseExpr( elem.on, 'rewrite-on', elem,
expr => rewriteExpr( expr, elem, nav.tableAlias ) );

@@ -459,2 +458,8 @@ }

}
// filter was copied in original element already
// TODO: not correct for e.g. composition-of-inline-aspect (#12223)
if (elem.$inferred !== 'include')
addConditionFromAssocPublishing( elem, assoc, nav );
elem.on.$inferred = 'rewrite';

@@ -471,3 +476,3 @@ }

*/
function addConditionFromAssocPublishing( elem, assoc ) {
function addConditionFromAssocPublishing( elem, assoc, nav ) {
const publishAssoc = (elem._main?.query || elem.$syntax === 'calc') &&

@@ -478,12 +483,14 @@ elem.value?.path?.length > 0;

nav ??= (elem._main?.query && elem.value)
? pathNavigation( elem.value ) // redirected source elem or mixin
: { navigation: assoc }; // redirected user-provided
const { location } = elem.name;
let isRestricted = false;
const lastStep = elem.value.path[elem.value.path.length - 1];
if (!lastStep)
if (!lastStep || !lastStep.where)
return;
if (lastStep.cardinality) {
isRestricted = true;
if (!elem.cardinality)
elem.cardinality = { ...assoc.cardinality } || { location };
elem.cardinality ??= { ...assoc.cardinality };
elem.cardinality.location = location;
for (const card of [ 'sourceMin', 'targetMin', 'targetMax' ]) {

@@ -495,44 +502,39 @@ if (lastStep.cardinality[card])

if (lastStep.where) {
// If there are foreign keys, transform them into an ON-condition first.
if (assoc.foreignKeys) {
const cond = foreignKeysToOnCondition( elem );
if (cond) {
elem.on = cond;
elem.foreignKeys = undefined;
}
// If there are foreign keys, transform them into an ON-condition first.
if (assoc.foreignKeys) {
const cond = foreignKeysToOnCondition( elem, assoc, nav );
if (cond) {
elem.on = cond;
elem.foreignKeys = undefined;
}
}
if (elem.on) {
isRestricted = true;
elem.on = {
op: { val: 'and', location },
args: [
// TODO: Get rid of $parens
{ ...elem.on, $parens: [ assoc.location ] },
filterToCondition( lastStep, elem ),
],
location,
$inferred: 'copy',
};
}
if (isRestricted) {
elem.$filtered = {
val: true,
literal: 'boolean',
location,
$inferred: '$generated',
};
}
}
elem.on = {
op: { val: 'and', location },
args: [
// TODO: Get rid of $parens
{ ...elem.on, $parens: [ assoc.location ] },
filterToCondition( lastStep, elem, nav ),
],
location,
$inferred: 'copy',
};
elem.$filtered = {
val: true,
literal: 'boolean',
location,
$inferred: '$generated',
};
}
/**
* Transform a filter on `assoc` into an ON-condition.
* Paths inside the filter are rewritten relative to `elem`.
* Transform a filter on `assocPathStep` into an ON-condition.
* Paths inside the filter are rewritten relative to `assoc`, so they can be redirected
* using `rewriteExpr()` later on. `$self` paths remain unchanged.
*/
function filterToCondition( assoc, elem ) {
const cond = copyExpr( assoc.where );
function filterToCondition( assocPathStep, elem, nav ) {
const cond = copyExpr( assocPathStep.where );
// TODO: Get rid of $parens
cond.$parens = [ assoc.location ];
cond.$parens = [ assocPathStep.location ];
traverseExpr( cond, 'rewrite-filter', elem, (expr) => {

@@ -551,11 +553,13 @@ if (!expr.path || expr.path.length === 0)

expr.path.unshift({
id: elem.name.id,
id: assocPathStep.id,
location: elem.name.location,
});
setLink( expr.path[0], '_artifact', elem );
// _navigation link not necessary because this condition is not rewritten
// inside the same view (would otherwise be needed for mixins).
if (elem.name.id.charAt(0) === '$')
prependSelfToPath( expr.path, elem );
setLink( expr.path[0], '_artifact', assocPathStep._artifact );
if (assocPathStep._navigation?.kind === 'mixin') {
// _navigation link necessary because condition is rewritten
// inside the same view (needed for mixins).
setLink( expr.path[0], '_navigation', assocPathStep._navigation );
}
// up to here, filter is relative to original association
rewriteExpr( expr, elem, nav?.tableAlias );
}

@@ -569,6 +573,3 @@ } );

// Caller must ensure ON-condition correctness via rewriteExpr()!
function foreignKeysToOnCondition( elem ) {
const nav = elem.$syntax === 'calc'
? calcPathNavigation( elem.value )
: pathNavigation( elem.value );
function foreignKeysToOnCondition( elem, assoc, nav ) {
if (model.options.testMode && !nav.tableAlias && !elem._pathHead && elem.$syntax !== 'calc')

@@ -578,3 +579,3 @@ throw new CompilerAssertion('rewriting keys to cond: no tableAlias but not inline/calc');

if ((!nav.tableAlias && elem.$syntax !== 'calc') || elem._parent?.kind === 'element' ||
(nav && nav.item !== elem.value.path[elem.value.path.length - 1])) {
(nav && nav.item && nav.item !== elem.value.path[elem.value.path.length - 1])) {
// - no nav.tableAlias for mixins or inside inline; mixins can't have managed assocs, though.

@@ -589,10 +590,11 @@ // - _parent is element for expand

let cond = [];
forEachInOrder( elem, 'foreignKeys', function keyToCond( fKey ) {
forEachInOrder( assoc, 'foreignKeys', function keyToCond( fKey ) {
// Format: lhs = rhs
// assoc.id = assoc_id
// lhs and rhs look the same, but have different ids and _artifact links.
// rhs is rewritten further down to ensure that the foreign key is projected.
// lhs and rhs look the same but are rewritten differently. We must ensure that
// the rhs is rewritten to a projected element (or it must remain the assoc's
// foreign key in case of calc elements).
const lhs = {
path: [
{ id: elem.name.id, location: elem.name.location },
{ id: assoc.name.id, location: elem.name.location },
...copyExpr( fKey.targetElement.path ),

@@ -602,13 +604,11 @@ ],

};
setLink( lhs.path[0], '_artifact', elem ); // different to rhs!
setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1] );
setLink( lhs.path[0], '_artifact', assoc );
setLink( lhs, '_artifact', lhs.path[lhs.path.length - 1]._artifact );
if (elem.name.id.charAt(0) === '$')
prependSelfToPath( lhs.path, elem );
rewritePath( lhs, lhs.path[0], assoc, elem, elem.value.location ); // different to rhs!
const elemOrigin = getOrigin( elem );
const rhs = {
path: [
// use origin's name; elem could have alias
{ id: elemOrigin.name.id, location: elemOrigin.name.location },
{ id: assoc.name.id, location: elem.name.location },
...copyExpr( fKey.targetElement.path ),

@@ -618,14 +618,10 @@ ],

};
setLink( rhs.path[0], '_artifact', elemOrigin ); // different to lhs!
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1] );
setLink( rhs.path[0], '_artifact', assoc );
setLink( rhs, '_artifact', rhs.path[rhs.path.length - 1]._artifact );
if (elem.$syntax !== 'calc') {
// Can't use rewriteExpr as that would use `assoc[…]` itself as well.
if (elem.$syntax !== 'calc') { // different to lhs!
const projectedFk = firstProjectionForPath( rhs.path, nav.tableAlias, elem );
rewritePath( rhs, projectedFk.item, elem, projectedFk.elem, elem.value.location );
}
else {
// TODO: calc elements: Do we need to rewrite? Shouldn't all elements exist, since
// the calc element is right next to all others?
}
const fkCond = {

@@ -717,11 +713,6 @@ op: { val: 'ixpr', location: elem.name.location },

let elem = (assoc._main.items || assoc._main).elements[item.id];
if (elem !== assoc && assoc.$syntax === 'calc') {
// … special case for calc elements where "elem" points to the referenced
// (possibly included) association. For _included_ calc elements of the form,
// `calc = assoc[…]` it holds: `elem === assoc`
// TODO: expr could be the transformed fkey->ON-condition, where this statement is not true
// And for the reference to the _foreign key_, we must _not_ rewrite it.
const calcRoot = assoc.value.path[0]?._navigation;
if (assoc.value.path[calcRoot?.kind === '$self' ? 1 : 0]?._artifact === elem)
elem = assoc;
if (assoc.$syntax === 'calc' && assoc._origin === elem) {
// … calc element where "elem" points to the referenced (possibly included)
// sibling element (association).
elem = assoc;
}

@@ -804,5 +795,5 @@ if (!elem)

const root = { id: '$self', location: path[0].location };
path.unshift( root );
setLink( root, '_navigation', elem._parent.$tableAliases.$self );
setArtifactLink( root, elem._parent );
path.unshift( root );
}

@@ -951,18 +942,2 @@

// TODO: Maybe unify with pathNavigation?
function calcPathNavigation( ref ) {
if (!ref._artifact)
return {};
let item = ref.path && ref.path[0];
const root = item && item._artifact;
if (!root)
return {};
if (root.kind === 'element')
return { navigation: root, item, tableAlias: root._parent };
item = ref.path[1];
if (root.kind === '$self')
return { item, tableAlias: root };
return { navigation: root.elements[item.id], item, tableAlias: root };
}
module.exports = tweakAssocs;

@@ -32,3 +32,4 @@ // Simple compiler utility functions

function annotationVal( anno ) {
return anno && (anno.val === undefined || anno.val); // XSN TODO: set val for anno short form
// XSN TODO: set val but no location for anno short form
return anno && (anno.val === undefined || anno.val);
}

@@ -35,0 +36,0 @@ function annotationIsFalse( anno ) { // falsy, but not null (unset)

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

} = require('../EdmPrimitiveTypeDefinitions.js');
const { isBuiltinType } = require('../../model/csnUtils');
const { isBuiltinType, isAnnotationExpression } = require('../../model/csnUtils');
const { transformExpression } = require('../../transform/db/applyTransformations.js');
const { xprInAnnoProperties } = require('../../compiler/builtins');

@@ -84,3 +83,3 @@ /**

error('odata-anno-xpr', location, {
anno, op: txt ??= op, '#': 'notadynexpr',
anno, op: txt ?? op, '#': 'notadynexpr',
});

@@ -113,3 +112,3 @@ delete parent[op];

parentparent[parentprop] = xpr.filter(a => a);
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
};

@@ -120,3 +119,3 @@ // XPR

parentparent[parentprop] = xpr;
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
};

@@ -152,5 +151,7 @@ //----------------------------------

delete parent.case;
transformExpression(parent, transform);
transformExpression(parent, undefined, transform);
};
transform.$If = noOp;
transform.$If = (_parent, _prop, expr) => {
transformExpression(expr, undefined, transform);
};
//----------------------------------

@@ -193,3 +194,3 @@ // Cast => $Cast

parentparent[parentprop] = castFunc;
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
};

@@ -230,3 +231,3 @@ //----------------------------------

delete parent[prop];
transformExpression(parent, transform);
transformExpression(parent, undefined, transform);
};

@@ -260,3 +261,3 @@ //----------------------------------

delete parent[prop];
transformExpression(parent, transform);
transformExpression(parent, undefined, transform);
};

@@ -266,3 +267,3 @@ transform.$In = noOp;

evalArgs({ exact: 2 }, xpr.slice(1), prop);
transformExpression(xpr, transform);
transformExpression(xpr, undefined, transform);
delete parent[prop];

@@ -279,3 +280,3 @@ parentparent[parentprop]

evalArgs({ exact: 2 }, xpr, prop);
transformExpression(xpr, transform);
transformExpression(xpr, undefined, transform);
delete parent[prop];

@@ -293,3 +294,3 @@ parentparent[parentprop].$Apply = [ { $Function: 'odata.concat' }, ...xpr ];

parentparent[parentprop] = xpr;
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
}

@@ -605,3 +606,3 @@ };

const exactArgs = (tgt = parent, x = xpr, count) => {
const exactArgs = (tgt = parent, x = xpr, count = undefined) => {
standard(tgt, x);

@@ -710,3 +711,3 @@ evalArgs({ exact: count }, tgt[x], xpr);

standard(parentparent, parentprop);
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
},

@@ -721,3 +722,3 @@ $Path: () => {

}
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
},

@@ -742,3 +743,3 @@ $Null: () => {

funcDef();
transformExpression(parent, transform);
transformExpression(parent, undefined, transform);
}

@@ -759,3 +760,3 @@ else {

delete parentparent[parentprop].args;
transformExpression(parentparent, transform);
transformExpression(parentparent, parentprop, transform);
}

@@ -772,10 +773,10 @@ }

return transformExpression({ annoVal }, {
return transformExpression(carrier, anno, {
'=': (parent, prop, xpr, csnPath, parentparent, parentprop) => {
if (parent?.['='] !== undefined && xprInAnnoProperties.some(xProp => parent[xProp] !== undefined)) {
if (isAnnotationExpression(parent)) {
delete parent['='];
parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, transform);
parentparent[parentprop] = transformExpression({ $edmJson: parseExpr( parent, { array: false }) }, undefined, transform);
}
},
}).annoVal;
});
}

@@ -782,0 +783,0 @@

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

const { checkCSNVersion } = require('../json/csnVersion');
const { makeMessageFunction } = require('../base/messages');
const {

@@ -27,17 +26,30 @@ EdmTypeFacetMap,

/*
OData V2 spec 06/01/2017 PDF version is available from here:
OData V2 spec 06/01/2017 PDF version is available here:
https://msdn.microsoft.com/en-us/library/dd541474.aspx
*/
function csn2edm( _csn, serviceName, _options ) {
return csn2edmAll(_csn, _options, [ serviceName ])[serviceName];
/**
* @param {CSN.Model} _csn
* @param {string} serviceName
* @param {CSN.Options} _options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @return {any}
*/
function csn2edm( _csn, serviceName, _options, messageFunctions ) {
return csn2edmAll(_csn, _options, [ serviceName ], messageFunctions)[serviceName];
}
function csn2edmAll( _csn, _options, serviceNames = undefined ) {
/**
* @param {CSN.Model} _csn
* @param {CSN.Options} _options
* @param {string[]|undefined} serviceNames
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @return {any}
*/
function csn2edmAll( _csn, _options, serviceNames, messageFunctions ) {
// get us a fresh model copy that we can work with
const csn = cloneCsnNonDict(_csn, _options);
const special$self = !csn?.definitions?.$self && '$self';
messageFunctions.setModel(csn);
// use original options for messages; cloned CSN for semantic location
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
const {

@@ -635,11 +647,13 @@ info, warning, error, message, throwWithError,

// V2: filter @Core.MediaType
if ( options.isV2() && elementCsn['@Core.MediaType']) {
if (options.isV2() && elementCsn['@Core.MediaType']) {
hasStream = elementCsn['@Core.MediaType'];
delete elementCsn['@Core.MediaType'];
elementCsn['@cds.api.ignore'] = true;
// CDXCORE-CDXCORE-177:
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
// V4: render property type 'Edm.Stream'
streamProps.push(elementName);
}
else {
// V4: render property type 'Edm.Stream' but don't add '@Core.IsURL'
if ( elementCsn['@Core.MediaType'])
delete elementCsn['@Core.IsURL'];
collectUsedType(elementCsn);

@@ -646,0 +660,0 @@ props.push(new Edm.Property(v, { Name: elementName }, elementCsn));

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

if (type.type === 'cds.UUID') {
if (type.type === 'cds.UUID' && elt['@odata.foreignKey4'] == null) {
if (path[1] === defName)
assignAnnotation(elt, anno, true);
else if (elt[anno] == null)

@@ -80,0 +79,0 @@ warning('odata-key-uuid-default-anno', path, { type: type.type, anno, id: `${defName}:${eltPath.join('.')}` });

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

}[cdsType];
if (!edmType)
error(null, location, { type: cdsType }, 'No EDM type available for $(TYPE)');
if (!edmType) {
error('ref-unsupported-type', location, { type: cdsType, '#': 'odata' });
edmType = 'Edm.PrimitiveType';
}
if (isV2) {

@@ -559,0 +562,0 @@ if (edmType === 'Edm.Date')

@@ -209,2 +209,6 @@ // Transform XSN (augmented CSN) into CSN

];
// Properties which cause a `cast` property to be rendered
const castProperties = [
'target', 'enum', 'items', 'type',
];

@@ -1499,3 +1503,5 @@ const csnDictionaries = [

setHidden( col, 'elements', expr.elements );
if (elem.type && !elem.type.$inferred || elem.target && !elem.target.$inferred)
// CDL-style cast with explicit type properties
if (castProperties.findIndex(prop => (elem[prop] &&
!elem[prop].$inferred && !elem[prop][$inferred])) > -1)
cast( col, elem );

@@ -1502,0 +1508,0 @@ }

@@ -81,5 +81,7 @@ // @ts-nocheck : Issues with Tokens on `this`, e.g. `this.DOT`.

ts.BRACE = tokenTypeOf( recognizer, "'{'" );
ts.BRACE_CLOSE = tokenTypeOf( recognizer, "'}'" );
ts.DOT = tokenTypeOf( recognizer, "'.'" );
ts.ASTERISK = tokenTypeOf( recognizer, "'*'" );
ts.AT = tokenTypeOf( recognizer, "'@'" );
ts.SEMICOLON = tokenTypeOf( recognizer, "';'" );
ts.NEW = Parser.NEW;

@@ -94,5 +96,20 @@ ts.Identifier = Parser.Identifier;

}
// console.log( ts.DOTbeforeBRACE, ts.BRACE, ts.DOT, recognizer.tokenRewrite );
}
function initCodeCompletionTokenArrays( parser ) {
// Set of top-level keywords used for code completion after token '}'
// belonging to a top-level definition
const startRuleIndex = parser.ruleNames.indexOf('start');
const startState = parser.atn.ruleToStartState[startRuleIndex].stateNumber;
const tokens = parser.atn.nextTokens(parser.atn.states[startState]);
tokens.removeOne(parser.symbolicNames.indexOf('NAMESPACE'));
tokens.removeOne(parser.symbolicNames.indexOf('HideAlternatives'));
parser.topLevelKeywords = [];
for (const interval of tokens.intervals) {
for (let i = interval.start; i < interval.stop; i++)
parser.topLevelKeywords.push(i);
}
}
function tokenTypeOf( recognizer, literalName ) {

@@ -128,2 +145,3 @@ const r = recognizer.literalNames.indexOf( literalName );

initTokenRewrite( parser, tokenStream );
initCodeCompletionTokenArrays(parser);
// comment the following 2 lines if you want to output the parser errors directly:

@@ -130,0 +148,0 @@ parser.messageErrorListener = errorListener;

@@ -450,12 +450,29 @@ // Error strategy with special handling for (non-reserved) keywords

// which are function name and argument position dependent:
if (j === pc.GenericExpr)
if (j === pc.GenericExpr) {
names.push( ...recognizer.$genericKeywords.expr );
else if (j === pc.GenericSeparator)
}
else if (j === pc.GenericSeparator) {
names.push( ...recognizer.$genericKeywords.separator );
else if (j === pc.GenericIntro)
}
else if (j === pc.GenericIntro) {
names.push( ...recognizer.$genericKeywords.introMsg );
}
else if (j === pc.SemicolonTopLevel) {
// We only insert a semikolon (i.e. make it optional) after a closing brace.
// If the previous token is not `}`, don't propose these keywords, as ';' is required.
if (recognizer._input.LA(-1) === recognizer._input.BRACE_CLOSE) {
const name = recognizer.topLevelKeywords.map(i => expected
.elementName(recognizer.literalNames, recognizer.symbolicNames, i));
names.push(...name);
if (recognizer._ctx.outer?.kind !== 'source') {
if (names.includes('<EOF>'))
names.splice(names.indexOf('<EOF>'), 1);
}
}
}
// other expected tokens usually appear in messages, except the helper tokens
// which are used to solve ambiguities via the parser method setLocalToken():
else if (j !== pc.HelperToken1 && j !== pc.HelperToken2)
else if (j !== pc.HelperToken1 && j !== pc.HelperToken2) {
names.push( expected.elementName(recognizer.literalNames, recognizer.symbolicNames, j ) );
}
}

@@ -462,0 +479,0 @@ }

@@ -1250,21 +1250,18 @@ // Generic ANTLR parser class with AST-building functions

function insertSemicolon() {
// hand-selected keyword list: first() set of `artifactDefOrExtend`
const allowedKeywords = [ this.constructor.ACTION, this.constructor.ABSTRACT,
this.constructor.ANNOTATE, this.constructor.ANNOTATION, this.constructor.ASPECT,
this.constructor.CONTEXT, this.constructor.DEFINE, this.constructor.ENTITY,
this.constructor.EOF, this.constructor.EVENT, this.constructor.EXTEND,
this.constructor.FUNCTION, this.constructor.SERVICE,
this.constructor.TYPE, this.constructor.VIEW, this.literalNames.indexOf( "'@'" ) ];
const currentToken = this._input.tokens[this._input.index];
const requireSemicolon = this.topLevelKeywords.includes(currentToken.type);
if (allowedKeywords.includes(currentToken.type)) {
if (requireSemicolon) {
this.noAssignmentInSameLine();
const prev = this._input.LT(-1);
const t = CommonTokenFactory.create(
currentToken.source, this.literalNames.indexOf( "';'" ),
'', currentToken.channel,
currentToken.source,
this.literalNames.indexOf( "';'" ),
'', antlr4.Token.DEFAULT_CHANNEL,
prev.stop, prev.stop,
prev.line, prev.column
);
t.tokenIndex = prev.tokenIndex + 1;
this._input.tokens.splice(t.tokenIndex, 0, t);

@@ -1275,2 +1272,3 @@

this._input.tokens[tokenIndex].tokenIndex += 1;
this._input.index = t.tokenIndex;

@@ -1277,0 +1275,0 @@ }

@@ -30,5 +30,5 @@ // Official cds-compiler API.

* Dictionary of message-ids and their reclassified severity. This option
* can be used to increase the severity of messages. The compiler will
* ignore decreased severities as this may lead to issues during
* compilation otherwise.
* can be used to increase the severity of messages. The compiler may
* ignore decreased severities of error messages as this may lead to issues
* during compilation otherwise.
*/

@@ -1028,2 +1028,27 @@ severities?: { [messageId: string]: MessageSeverity}

/**
* Renders the given CSN into EDM in the JSON format _and_ XML format.
* That is, it is a combination of `to.edm()` and `to.edmx()`.
* Requires `options.service` to be set.
*
* Not to be confused with `for.odata()`, which returns an OData transformed CSN.
*
* @returns An object `'<protocol>': object` where the value is `'<serviceName>': object` entry
* which consists of `{edmx: string, edm?: object}`.
* @since v4.6.0
*/
function odata(csn: CSN, options?: ODataOptions): Record<string, object>;
namespace odata {
/**
* Renders the given CSN into EDM in JSON format _and_ XML format for each service.
* That is, it is a combination of `to.edm.all()` and `to.edmx.all()`.
* If `options.serviceNames` is not set, all services will be rendered.
*
* @returns A map of `'<protocol>': object` where each entry is `'<serviceName>': object` entries where
* each entry consists of `{edmx: string, edm?: object}`.
* @since v4.6.0
*/
function all(csn: CSN, options: ODataOptions): Record<string, object>;
}
/**
* Renders the given CSN into EDM (JSON format). Requires `options.service` to be set.

@@ -1030,0 +1055,0 @@ *

@@ -161,2 +161,5 @@ // Main entry point for the CDS Compiler

}),
odata: Object.assign((...args) => snapi.odata2(...args), {
all: (...args) => snapi.odata2.all(...args)
}),
},

@@ -163,0 +166,0 @@ // Convenience for hdbtabledata calculation in @sap/cds

@@ -616,3 +616,3 @@ // CSN functionality for resolving references

}
const mixin = ncache._select.mixin && ncache._select.mixin[head];
const mixin = ncache._select.mixin?.[head];
if (mixin && {}.hasOwnProperty.call( ncache._select.mixin, head )) {

@@ -822,2 +822,5 @@ setCache( mixin, '_parent', qcache._select );

let type = parentElementOrQueryCache;
if (col.cast)
traverseType( col.cast, col, 'column', colIndex, initNode );
while (type.items)

@@ -824,0 +827,0 @@ type = type.items;

'use strict';
const { csnRefs, implicitAs } = require('../model/csnRefs');
const { csnRefs, implicitAs, pathId } = require('./csnRefs');
const { applyTransformations, applyTransformationsOnNonDictionary, applyTransformationsOnDictionary } = require('../transform/db/applyTransformations');
const { isBuiltinType } = require('../compiler/builtins.js');
const { isBuiltinType, isMagicVariable, isAnnotationExpression } = require('../compiler/builtins.js');
const {

@@ -1091,4 +1091,6 @@ sortCsn,

* @param {object} excludes
* @param {array} annoNames (copy only these annotations or all if undefined)
* @returns {array} copiedAnnoNames
*/
function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {} ) {
function copyAnnotations( fromNode, toNode, overwrite = false, excludes = {}, annoNames = undefined ) {
// Ignore if no toNode (in case of errors)

@@ -1098,8 +1100,9 @@ if (!toNode)

const annotations = Object.keys(fromNode).filter(key => key.startsWith('@'));
if (annoNames == null)
annoNames = Object.keys(fromNode).filter(key => key.startsWith('@'));
for (const anno of annotations) {
annoNames.forEach((anno) => {
if ((toNode[anno] === undefined || overwrite) && !excludes[anno])
toNode[anno] = cloneAnnotationValue(fromNode[anno]);
}
});
}

@@ -1451,3 +1454,3 @@

function pathName( ref ) {
return ref ? ref.map( id => id?.id || id ).join( '.' ) : '';
return ref ? ref.map( pathId ).join( '.' ) : '';
}

@@ -1462,2 +1465,4 @@

isBuiltinType,
isMagicVariable,
isAnnotationExpression,
applyAnnotationsFromExtensions,

@@ -1464,0 +1469,0 @@ forEachGeneric,

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

.option(' --options <file>')
.option('-w, --warning <level>', ['0', '1', '2', '3'])
.option('-w, --warning <level>', { valid: ['0', '1', '2', '3'] })
.option(' --quiet')

@@ -22,3 +22,3 @@ .option(' --show-message-id')

.option(' --no-message-context')
.option(' --color <mode>', ['auto', 'always', 'never'])
.option(' --color <mode>', { valid: ['auto', 'always', 'never'] })
.option('-o, --out <dir>')

@@ -39,3 +39,3 @@ .option(' --cds-home <dir>')

.option(' --direct-backend')
.option(' --fallback-parser <type>', ['cdl', 'csn', 'csn!'])
.option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
.option(' --shuffle <seed>') // 0 | 1..4294967296

@@ -47,2 +47,3 @@ .option(' --test-mode')

.option(' --localized-without-coalesce')
.option(' --tenant-as-column')
.option(' --default-binary-length <length>')

@@ -107,2 +108,3 @@ .option(' --default-string-length <length>')

with name = "+", write complete XSN, long!
--tenant-as-column Add tenant fields to entities
--internal-msg Write raw messages with call stack to <stdout>/<stderr>

@@ -172,3 +174,3 @@ --beta-mode Enable all unsupported, incomplete (beta) features

.option('-h, --help')
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
.option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: ['--names'] })
.option(' --render-virtual')

@@ -181,4 +183,4 @@ .option(' --joinfk')

.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
.option(' --assert-integrity <mode>', { valid: ['true', 'false', 'individual'] })
.option(' --assert-integrity-type <type>', { valid: ['RT', 'DB'], ignoreCase: true })
.option(' --pre2134ReferentialConstraintNames')

@@ -226,3 +228,3 @@ .option(' --disable-hana-comments')

.option('-h, --help')
.option('-v, --odata-version <version>', ['v2', 'v4', 'v4x'], { aliases: ['--version'] })
.option('-v, --odata-version <version>', { valid: ['v2', 'v4', 'v4x'], aliases: ['--version'] })
.option('-x, --xml')

@@ -239,4 +241,4 @@ .option('-j, --json')

.option('-c, --csn')
.option('-f, --odata-format <format>', ['flat', 'structured'])
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
.option('-f, --odata-format <format>', { valid: ['flat', 'structured'] })
.option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
.option('-s, --service-names <list>')

@@ -297,4 +299,4 @@ .option(' --fewer-localized-views')

.option('-h, --help')
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
.option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: [ '--names' ] })
.option('-d, --sql-dialect <dialect>', { valid: ['hana', 'sqlite', 'plain', 'postgres', 'h2'], aliases: [ '--dialect' ] })
.option(' --render-virtual')

@@ -304,8 +306,8 @@ .option(' --joinfk')

.option('-l, --locale <locale>')
.option('-s, --src <style>', ['sql', 'hdi'])
.option('-s, --src <style>', { valid: ['sql', 'hdi'] })
.option('-c, --csn')
.option(' --integrity-not-validated')
.option(' --integrity-not-enforced')
.option(' --assert-integrity <mode>', ['true', 'false', 'individual'])
.option(' --assert-integrity-type <type>', ['RT', 'DB'], { ignoreCase: true })
.option(' --assert-integrity <mode>', { valid: ['true', 'false', 'individual'] })
.option(' --assert-integrity-type <type>', { valid: ['RT', 'DB'], ignoreCase: true })
.option(' --constraints-in-create-table')

@@ -378,3 +380,3 @@ .option(' --pre2134ReferentialConstraintNames')

.option('-h, --help')
.option('-n, --sql-mapping <style>', ['quoted', 'hdbcds'], { aliases: ['--names'] })
.option('-n, --sql-mapping <style>', { valid: ['quoted', 'hdbcds'], aliases: ['--names'] })
.help(`

@@ -401,4 +403,4 @@ Usage: cdsc toRename [options] <files...>

.option('-h, --help')
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: ['--names'] })
.option('-s, --src <style>', ['sql', 'hdi'])
.option('-n, --sql-mapping <style>', { valid: ['plain', 'quoted', 'hdbcds'], aliases: ['--names'] })
.option('-s, --src <style>', { valid: ['sql', 'hdi'] })
.option(' --drop')

@@ -409,3 +411,3 @@ .option(' --alter')

.option(' --integrity-not-enforced')
.option('-d, --sql-dialect <dialect>', ['hana', 'sqlite', 'plain', 'postgres', 'h2'], { aliases: [ '--dialect' ] })
.option('-d, --sql-dialect <dialect>', { valid: ['hana', 'sqlite', 'plain', 'postgres', 'h2'], aliases: [ '--dialect' ] })
.help(`

@@ -449,3 +451,3 @@ Usage: cdsc manageConstraints [options] <files...>

.option('-h, --help')
.option('-f, --csn-flavor <flavor>', ['client', 'gensrc', 'universal'], { aliases: ['--flavor'] })
.option('-f, --csn-flavor <flavor>', { valid: ['client', 'gensrc', 'universal'], aliases: ['--flavor'] })
.option(' --with-localized')

@@ -452,0 +454,0 @@ .option(' --with-locations')

@@ -9,4 +9,2 @@

const { forEach } = require('../utils/objectUtils');
const { makeMessageFunction } = require('../base/messages');
const { optionProcessor } = require('../optionProcessor');
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');

@@ -24,10 +22,11 @@

* @param {CSN.Model} csn
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {CSN.Options} options
*/
function alterConstraintsWithCsn( csn, options ) {
const { error, warning } = makeMessageFunction(csn, options, 'manageConstraints');
function alterConstraintsWithCsn( csn, options, messageFunctions ) {
const { error, warning } = messageFunctions;
const {
drop, alter, src, violations, sqlDialect,
} = options || {};
} = options;

@@ -40,8 +39,7 @@ if (!sqlDialect || sqlDialect === 'h2' || sqlDialect === 'plain')

// Of course we want the database constraints
// Of course, we want the database constraints
options.assertIntegrityType = options.assertIntegrityType || 'DB';
const transformedOptions = _transformSqlOptions(csn, options);
const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, 'to.sql');
const forSqlCsn = transformForRelationalDBWithCsn(csn, transformedOptions, messageFunctions);

@@ -62,28 +60,14 @@ if (violations && src && src !== 'sql') {

function _transformSqlOptions( model, options ) {
// TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
function _transformSqlOptions( csn, options ) {
const { src } = options;
// eslint-disable-next-line global-require
const prepareOptions = require('../api/options');
options = prepareOptions.to.sql(options);
options.src = src;
// Merge options with defaults.
options = Object.assign({ sqlMapping: 'plain', sqlDialect: 'plain' }, options);
options.toSql = true;
if (!options.src && !options.csn)
options.src = 'sql';
const { warning, error } = makeMessageFunction(model, options, 'to.sql');
optionProcessor.verifyOptions(options, 'toSql', true)
// eslint-disable-next-line cds-compiler/message-template-string
.forEach(complaint => warning(null, null, `${complaint}`));
if (options.sqlDialect !== 'hana') {
// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds') {
error(null, null, { value: options.sqlDialect, othervalue: options.sqlMapping },
'Option sqlDialect: $(VALUE) can\'t be combined with sqlMapping: $(OTHERVALUE)');
}
// No non-HANA SQL for HDI
if (options.src === 'hdi')
error(null, null, { value: options.sqlDialect }, 'Option sqlDialect: $(VALUE) can\'t be used for SAP HANA HDI');
}
return options;

@@ -90,0 +74,0 @@ }

'use strict';
const { makeMessageFunction } = require('../base/messages');
const { checkCSNVersion } = require('../json/csnVersion');
const { forEachDefinition } = require('../model/csnUtils');
const { optionProcessor } = require('../optionProcessor');
const { transformForRelationalDBWithCsn } = require('../transform/forRelationalDB');

@@ -28,14 +26,11 @@ const { getIdentifierUtils } = require('./utils/sql');

* @param {CSN.Options} options Transformation options
* @param {object} messageFunctions
* @returns {object} A dictionary of name: rename statement
*/
function toRename( inputCsn, options ) {
const { warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
function toRename( inputCsn, options, messageFunctions ) {
const { warning, throwWithError } = messageFunctions;
// Merge options with defaults.
// TODO: Use api/options.js if this ever becomes an official API.
options = Object.assign({ sqlMapping: 'hdbcds', sqlDialect: 'hana' }, options);
// Verify options
optionProcessor.verifyOptions(options, 'toRename')
// eslint-disable-next-line cds-compiler/message-template-string
.forEach(complaint => warning(null, null, `${complaint}`));
checkCSNVersion(inputCsn, options);

@@ -47,3 +42,4 @@

// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forRelationalDB)
const csn = transformForRelationalDBWithCsn(inputCsn, options, 'to.rename');
const csn = transformForRelationalDBWithCsn(inputCsn, options, messageFunctions);
messageFunctions.setModel(csn);
const hdbcdsOrQuotedIdentifiers = getIdentifierUtils(csn, options);

@@ -50,0 +46,0 @@ const plainIdentifiers = getIdentifierUtils(csn, { sqlDialect: 'hana', sqlMapping: 'plain' });

@@ -265,2 +265,3 @@ // Common render functions for toCdl.js, toHdbcds.js and toSql.js

'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
'cds.Vector': 'NVARCHAR', // Not supported; see #11725
},

@@ -272,2 +273,3 @@ hana: {

'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
'cds.Vector': 'REAL_VECTOR', // FIXME: test me
},

@@ -282,2 +284,3 @@ sqlite: {

'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
'cds.Vector': 'BINARY_BLOB', // Not supported; see #11725
},

@@ -304,2 +307,3 @@ plain: {

'cds.UInt8': 'INTEGER', // Not equivalent
'cds.Vector': 'VARCHAR', // Not supported; see #11725
},

@@ -432,2 +436,14 @@ };

/**
* Whether a replacement is required for the given variable (e.g. '$user.id').
* Some variables such as `$user.id` are not required to have replacement values, even if
* there is no proper fallback via `variableForDialect(…)` (for example in sqlDialect 'plain').
*
* @param {string} name
* @return {boolean}
*/
function isVariableReplacementRequired( name ) {
const notRequired = [ '$user.id', '$user.locale', '$tenant' ];
return !notRequired.includes(name);
}

@@ -716,2 +732,3 @@ /**

variableForDialect,
isVariableReplacementRequired,
hasHanaComment,

@@ -718,0 +735,0 @@ getHanaComment,

{
"rules": {
"no-shadow": "off"
}
},
"overrides": [
{
"files": [
"addTenantFields.js"
],
"extends": "../../.eslintrc-ydkjsi.json"
}
]
}

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

const { setProp } = require('../../base/model');
const { xprInAnnoProperties } = require('../../compiler/builtins');
const { isAnnotationExpression } = require('../../compiler/builtins');

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

if (options.processAnnotations) {
if (node?.['='] !== undefined && xprInAnnoProperties.some(xProp => node[xProp] !== undefined)) {
if (isAnnotationExpression(node)) {
standard(_parent, _prop, node);

@@ -304,43 +304,39 @@ }

* used primarily to transform annotation expressions.
* If propName is undefined, all properties of parent are transformed.
* @param {object} parent Start node
* @param {object} customTransformers Map of callback functions
* @param {string} propName Start at specific property of parent
* @param {object} transformers Map of callback functions
* @param {CSN.Path} path Path to parent
* @returns {object} transformed parent
* @returns {object} transformed node
*/
function transformExpression( parent, customTransformers, path = [] ) {
const csnPath = [ ...path ];
/**
*
* @param {object} _parent
* @param {string} _prop
* @param {object} node
*/
function standard( _parent, _prop, node ) {
if (!node || typeof node !== 'object' ||
!{}.propertyIsEnumerable.call( _parent, _prop ) ||
(typeof _prop === 'string' && _prop.startsWith('@')))
return;
function transformExpression( parent, propName, transformers, path = [] ) {
if (propName != null) {
const child = parent[propName];
if (!child || typeof child !== 'object' ||
!{}.propertyIsEnumerable.call( parent, propName ))
return parent;
csnPath.push(_prop);
if (Array.isArray(node)) {
node.forEach( (n, i) => standard( node, i, n ) );
path = [ ...path, propName ];
if (Array.isArray(child)) {
child.forEach( (n, i) => transformExpression( child, i, transformers, path ) );
}
else {
for (const name of Object.getOwnPropertyNames( node )) {
const ct = customTransformers[name];
for (const cpn of Object.getOwnPropertyNames( child )) {
const ct = transformers[cpn];
if (ct) {
const ppn = propName;
if (Array.isArray(ct))
ct.forEach(cti => cti(node, name, node[name], csnPath, _parent, _prop));
ct.forEach(cti => cti(child, cpn, child[cpn], path, parent, ppn));
else
ct(node, name, node[name], csnPath, _parent, _prop);
ct(child, cpn, child[cpn], path, parent, ppn);
}
standard(node, name, node[name]);
transformExpression(child, cpn, transformers, path);
}
}
csnPath.pop();
}
for (const name of Object.getOwnPropertyNames( parent ))
standard( parent, name, parent[name] );
else {
for (propName of Object.getOwnPropertyNames( parent ))
transformExpression( parent, propName, transformers, path );
}
return parent;

@@ -347,0 +343,0 @@ }

@@ -19,8 +19,8 @@ 'use strict';

* @param {CSN.Options} options Options
* @param {Function} error Message function for errors
* @param {Function} info Message function for info
* @param {object} messageFunctions Message functions (error(), info(), …)
* @returns {Function} forEachDefinition callback
*/
function processAssertUnique( csn, options, error, info ) {
const { resolvePath, flattenPath } = getTransformers(csn, options);
function processAssertUnique( csn, options, messageFunctions ) {
const { resolvePath, flattenPath } = getTransformers(csn, options, messageFunctions);
const { error, info } = messageFunctions;

@@ -27,0 +27,0 @@ return handleAssertUnique;

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

recurseElements,
} = transformUtils.getTransformers(csn, options, '_');
} = transformUtils.getTransformers(csn, options, messageFunctions, '_');

@@ -112,0 +112,0 @@ return handleQueryish;

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

const { forEach } = require('../../utils/objectUtils');
const { cardinality2str } = require('../../model/csnUtils');
const { cardinality2str, isAnnotationExpression } = require('../../model/csnUtils');
const { transformExpression } = require('./applyTransformations');
/**

@@ -46,2 +46,3 @@ * Strip off leading $self from refs where applicable

* @param {CSN.Options} options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {WeakMap} resolved Cache for resolved refs

@@ -51,3 +52,3 @@ * @param {string} pathDelimiter

*/
function resolveTypeReferences( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
function resolveTypeReferences( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
/**

@@ -73,3 +74,3 @@ * Remove .localized from the element and any sub-elements

}
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { toFinalBaseType, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
const { getServiceName, getFinalTypeInfo } = csnUtils;

@@ -102,14 +103,13 @@

// Drill down to ensure this.
let nextElements = node.elements || node.items?.elements;
if (nextElements) {
const stack = [ nextElements ];
while (stack.length > 0) {
const elements = stack.pop();
for (const e of Object.values(elements)) {
if (e.type && !isBuiltinType(e.type))
toFinalBaseType(e, resolved, true);
nextElements = e.elements || e.items?.elements;
if (nextElements)
stack.push(nextElements);
}
const nextElements = node.elements || node.items?.elements;
const stack = nextElements ? [ nextElements ] : [];
while (stack.length > 0) {
const elements = stack.pop();
for (const e of Object.values(elements)) {
toFinalBaseType(e, resolved, true);
if (!options.toOdata && e.items) // items could have unresolved types
toFinalBaseType(e.items, resolved, true);
const next = e.elements || e.items?.elements;
if (next)
stack.push(next);
}

@@ -172,3 +172,3 @@ }

const typeDef = csn.definitions[typeName];
return !!(options.toOdata && typeDef && typeDef.items);
return !!(options.toOdata && typeDef && typeDef?.items);
}

@@ -180,2 +180,3 @@ }

* @param {CSN.Options} options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {WeakMap} resolved Cache for resolved refs

@@ -185,5 +186,5 @@ * @param {string} pathDelimiter

*/
function flattenAllStructStepsInRefs( csn, options, resolved, pathDelimiter, iterateOptions = {} ) {
function flattenAllStructStepsInRefs( csn, options, messageFunctions, resolved, pathDelimiter, iterateOptions = {} ) {
const { inspectRef, effectiveType } = csnRefs(csn);
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { flattenStructStepsInRef } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
const adaptRefs = [];

@@ -269,8 +270,9 @@

* @param {CSN.Options} options
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {string} pathDelimiter
* @param {Function} error
* @param {object} iterateOptions
*/
function flattenElements( csn, options, pathDelimiter, error, iterateOptions = {} ) {
const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, pathDelimiter);
function flattenElements( csn, options, messageFunctions, pathDelimiter, iterateOptions = {} ) {
const { error } = messageFunctions;
const { flattenStructuredElement, csnUtils } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
const { isAssocOrComposition, effectiveType } = csnUtils;

@@ -321,29 +323,24 @@ const transformers = {

if (flatElement.type && isAssocOrComposition(flatElement) && flatElement.on) {
// unmanaged relations can't be primary key
delete flatElement.key;
// Make refs resolvable by fixing the first ref step
for (const onPart of flatElement.on) {
if (onPart.ref) {
const firstRef = onPart.ref[0];
transformExpression(flatElement, 'on', {
ref: (_parent, _prop, xpr) => {
const prefix = flatElement._flatElementNameWithDots.split('.').slice(0, -1).join(pathDelimiter);
const possibleFlatName = prefix + pathDelimiter + xpr[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
*/
if (flatElems[possibleFlatName])
onPart.ref[0] = possibleFlatName;
}
}
xpr[0] = possibleFlatName;
},
});
}

@@ -447,4 +444,3 @@ parent[prop].$orderedElements.push([ flatElemName, flatElement ]);

* @param {CSN.Options} options
* @param {Function} error
* @param {Function} warning
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @param {string} pathDelimiter

@@ -455,5 +451,6 @@ * @param {boolean} flattenKeyRefs

*/
function handleManagedAssociationsAndCreateForeignKeys( csn, options, error, warning, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
function handleManagedAssociationsAndCreateForeignKeys( csn, options, messageFunctions, pathDelimiter, flattenKeyRefs, csnUtils, iterateOptions = {} ) {
const { error, warning } = messageFunctions;
const { isManagedAssociation, inspectRef, isStructured } = csnUtils;
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, pathDelimiter);
const { flattenStructStepsInRef, flattenStructuredElement } = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter);
if (flattenKeyRefs) {

@@ -829,2 +826,25 @@ applyTransformations(csn, {

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) {
if (options.transformation !== 'effective')
fk[1]['@odata.foreignKey4'] = prefix;
if (options.transformation === 'odata' || options.transformation === 'effective') {
const validAnnoNames = Object.keys(element).filter(pn => pn[0] === '@' && !isAnnotationExpression(element[pn]));
copyAnnotations(element, fk[1], true, {}, validAnnoNames);
}
// 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;
/**

@@ -850,23 +870,2 @@ * Get the path to continue resolving references

}
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) {
if (options.transformation !== 'effective')
fk[1]['@odata.foreignKey4'] = prefix;
if (options.transformation === 'odata' || options.transformation === 'effective')
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;
}

@@ -873,0 +872,0 @@

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

extractValidFromToKeyElement, checkAssignment, checkMultipleAssignments, recurseElements,
} = getTransformers(csn, options, pathDelimiter);
} = getTransformers(csn, options, messageFunctions, pathDelimiter);

@@ -177,0 +177,0 @@ return handleTemporalAnnotations;

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

addElement, copyAndAddElement, createAssociationPathComparison, csnUtils,
} = getTransformers(csn, options, pathDelimiter);
} = getTransformers(csn, options, messageFunctions, pathDelimiter);
const { getCsnDef, isComposition } = csnUtils;

@@ -126,22 +126,8 @@ const { error, warning } = messageFunctions;

// extract keys for UUID inspection
const keys = [];
// TODO: Do we really need this? Is this possibly done by a validator earlier?
forEachMemberRecursively(artifact, (elt, name, prop, path) => {
if (!elt.elements && !elt.type && !elt.virtual) // only check leafs
error(null, path, 'Expecting element to have a type when used in a draft-enabled artifact');
if (elt.key && elt.key === true && !elt.virtual)
keys.push(elt);
}, [ 'definitions', artifactName ], false, { elementsOnly: true });
// In contrast to EDM, the DB entity may have more than one technical keys but should have ideally exactly one key of type cds.UUID
if (keys.length !== 1) {
warning(null, [ 'definitions', artifactName ], { count: keys.length },
'Entity annotated with “@odata.draft.enabled” should have exactly one key element, but found $(COUNT)');
}
else {
const uuidCount = keys.reduce((acc, k) => ((k.type === 'cds.UUID' || k.type === 'cds.String' && k.$renamed === 'cds.UUID' && k.length === 36) ? acc + 1 : acc), 0);
if (uuidCount === 0)
warning(null, [ 'definitions', artifactName ], 'Entity annotated with “@odata.draft.enabled” should have one key element of type “cds.UUID”');
}
// Ignore boolean return value. We know that we're inside a service or else we wouldn't have reached this code.

@@ -148,0 +134,0 @@ const matchingService = getMatchingService(artifactName) || '';

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

function generateDrafts( csn, options, services ) {
const messageFunctions = makeMessageFunction(csn, options, 'for.odata');
const { error, info } = messageFunctions;
const {

@@ -37,3 +39,3 @@ createAndAddDraftAdminDataProjection, createScalarElement,

csnUtils,
} = getTransformers(csn, options);
} = getTransformers(csn, options, messageFunctions);
const {

@@ -45,4 +47,2 @@ getServiceName,

const { error, info } = makeMessageFunction(csn, options, 'for.odata');
if (!services)

@@ -49,0 +49,0 @@ services = getServiceNames(csn);

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

* @param {object} csnUtils
* @param {object} messageFunctions
* @param {Function} messageFunctions.error
* @param {Function} messageFunctions.warning
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
* @todo Remove .keys afterwards

@@ -28,3 +26,3 @@ * @todo Add created foreign keys into .columns in case of a query?

*/
function turnAssociationsIntoUnmanaged( csn, options, csnUtils, { error, warning } ) {
function turnAssociationsIntoUnmanaged( csn, options, csnUtils, messageFunctions ) {
// TODO: Do we really need this?

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

// Flatten out the fks and create the corresponding elements
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', true, csnUtils, { allowArtifact: () => true, skipDict: {} });

@@ -41,0 +39,0 @@ // Add the foreign keys also to the columns if the association itself was explicitly selected

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

const { CompilerAssertion } = require('../../base/error');
const { makeMessageFunction } = require('../../base/messages');
const { cloneCsnNonDict, getUtils, isAspect } = require('../../model/csnUtils');

@@ -30,5 +29,6 @@ const transformUtils = require('../transformUtils');

* @param {CSN.Options} options
* @param {object} messageFunctions
* @returns {CSN.Model}
*/
function effectiveCsn( model, options ) {
function effectiveCsn( model, options, messageFunctions ) {
if (!isBetaEnabled(options, 'effectiveCsn'))

@@ -38,10 +38,9 @@ throw new CompilerAssertion('effective CSN is only supported with beta flag `effectiveCsn`!');

const csn = cloneCsnNonDict(model, options);
delete csn.vocabularies; // must not be set for effective CSN
messageFunctions.setModel(csn);
const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, '_');
const { expandStructsInExpression } = transformUtils.getTransformers(csn, options, messageFunctions, '_');
queries.projectionToSELECTAndAddColumns(csn);
let csnUtils = getUtils(csn, 'init-all');
const messageFunctions = makeMessageFunction(csn, options, 'for.effective');

@@ -71,9 +70,7 @@ // Run validations on CSN - each validator function has access to the message functions and the inspect ref via this

csnUtils = getUtils(csn, 'init-all');
// Remove properties attached by validator - they do not "grow" as the model grows.
cleanup();
flattening.flattenAllStructStepsInRefs(csn, options, new WeakMap(), '_');
flattening.flattenElements(csn, options, '_', messageFunctions.error);
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, new WeakMap(), '_');
flattening.flattenElements(csn, options, messageFunctions, '_');

@@ -80,0 +77,0 @@ resolveTypesInActionsAfterFlattening();

'use strict';
const { makeMessageFunction } = require('../base/messages');
const { isDeprecatedEnabled, isBetaEnabled } = require('../base/model');

@@ -27,2 +26,3 @@ const transformUtils = require('./transformUtils');

const { addTenantFields } = require('./addTenantFields');
const { addLocalizationViews } = require('./localized');

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

function transform4odataWithCsn(inputModel, options) {
function transform4odataWithCsn(inputModel, options, messageFunctions) {
timetrace.start('OData transformation');

@@ -78,4 +78,5 @@

let csn = cloneCsnNonDict(inputModel, options);
messageFunctions.setModel(csn);
const { message, error, warning, info, throwWithAnyError } = makeMessageFunction(csn, options, 'for.odata');
const { message, error, warning, info, throwWithAnyError } = messageFunctions;
throwWithAnyError();

@@ -86,3 +87,3 @@

const transformers = transformUtils.getTransformers(csn, options, '_');
const transformers = transformUtils.getTransformers(csn, options, messageFunctions, '_');
const {

@@ -124,2 +125,5 @@ addDefaultTypeFacets, checkMultipleAssignments,

if (options.tenantAsColumn)
addTenantFields(csn, options);
const keepLocalizedViews = isDeprecatedEnabled(options, '_createLocalizedViews');

@@ -186,11 +190,11 @@

flattening.flattenAllStructStepsInRefs(csn, options, resolved, '_', { skipArtifact: isExternalServiceMember });
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, '_', { skipArtifact: isExternalServiceMember });
// No type references exist anymore
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
// OData doesn't resolve type chains after the first 'items'
flattening.resolveTypeReferences(csn, options, resolved, '_',
flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, '_',
{ skip: [ 'action', 'aspect', 'event', 'function', 'type'],
skipArtifact: isExternalServiceMember, skipStandard: { items: true } });
// No structured elements exists anymore
flattening.flattenElements(csn, options, '_', error,
flattening.flattenElements(csn, options, messageFunctions, '_',
{ skip: ['action', 'aspect', 'event', 'function', 'type'],

@@ -204,3 +208,3 @@ skipArtifact: isExternalServiceMember,

// see db/views.js::addForeignKeysToColumns
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });
flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, '_', !structuredOData, csnUtils,{ skipArtifact: isExternalServiceMember });

@@ -331,3 +335,3 @@ // Allow using managed associations as steps in on-conditions to access their fks

const renamePrefix = (name in renameMappings)
? name
? name
: renameShortCuts.find(p => name.startsWith(p + '.'));

@@ -334,0 +338,0 @@ if(renamePrefix) {

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

} = require('../model/csnUtils');
const { makeMessageFunction } = require('../base/messages');
const transformUtils = require('./transformUtils');

@@ -17,2 +16,3 @@ const { translateAssocsToJoinsCSN } = require('./translateAssocsToJoins');

const { rejectManagedAssociationsAndStructuresForHdbcdsNames } = require('../checks/selectItems');
const { addTenantFields } = require('../transform/addTenantFields');
const { addLocalizationViewsWithJoins, addLocalizationViews } = require('../transform/localized');

@@ -105,5 +105,5 @@ const { timetrace } = require('../utils/timetrace');

* @param {CSN.Options} options
* @param {string} moduleName The calling compiler module name, e.g. `to.hdi` or `to.hdbcds`.
* @param {object} messageFunctions Message functions such as `error()`, `info()`, …
*/
function transformForRelationalDBWithCsn(csn, options, moduleName) {
function transformForRelationalDBWithCsn(csn, options, messageFunctions) {
// copy the model as we don't want to change the input model

@@ -117,2 +117,5 @@ timetrace.start('HANA transformation');

if (options.tenantAsColumn)
addTenantFields(csn, options);
checkCSNVersion(csn, options);

@@ -127,8 +130,7 @@

/** @type {object} */
let messageFunctions;
let message, error, warning, info; // message functions
let message, error, warning; // message functions
/** @type {() => void} */
let throwWithAnyError;
// transformUtils
let addDefaultTypeFacets,
let addDefaultTypeFacets,
expandStructsInExpression,

@@ -188,3 +190,3 @@ flattenStructuredElement,

// check unique constraints - further processing is done in rewriteUniqueConstraints
assertUnique.prepare(csn, options, error, info)
assertUnique.prepare(csn, options, messageFunctions)
]);

@@ -213,11 +215,11 @@

// No refs with struct-steps exist anymore
flattening.flattenAllStructStepsInRefs(csn, options, resolved, pathDelimiter);
flattening.flattenAllStructStepsInRefs(csn, options, messageFunctions, resolved, pathDelimiter);
// No type references exist anymore
// Needs to happen exactly between flattenAllStructStepsInRefs and flattenElements to keep model resolvable.
flattening.resolveTypeReferences(csn, options, resolved, pathDelimiter);
flattening.resolveTypeReferences(csn, options, messageFunctions, resolved, pathDelimiter);
// No structured elements exists anymore
flattening.flattenElements(csn, options, pathDelimiter, error);
flattening.flattenElements(csn, options, messageFunctions, pathDelimiter);
} else {
// For to.hdbcds with naming mode hdbcds we also need to resolve the types
flattening.resolveTypeReferences(csn, options, new WeakMap(), pathDelimiter);
flattening.resolveTypeReferences(csn, options, messageFunctions, new WeakMap(), pathDelimiter);
}

@@ -295,3 +297,3 @@

// eliminate the doA2J in the functions 'handleManagedAssociationFKs' and 'createForeignKeyElements'
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, error, warning, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });
doA2J && flattening.handleManagedAssociationsAndCreateForeignKeys(csn, options, messageFunctions, pathDelimiter, true, csnUtils, { skipDict: { actions: true }, allowArtifact: artifact => (artifact.kind === 'entity') });

@@ -369,2 +371,5 @@ doA2J && forEachDefinition(csn, flattenIndexes);

// some errors can't be handled in the subsequent processing steps for e.g. HDBCDS
messageFunctions.throwWithError();
// Apply view-specific transformations

@@ -461,4 +466,4 @@ // (160) Projections now finally become views

function bindCsnReference(){
messageFunctions = makeMessageFunction(csn, options, moduleName);
({ error, warning, info, message, throwWithAnyError } = messageFunctions);
messageFunctions.setModel(csn);
({ error, warning, message, throwWithAnyError } = messageFunctions);

@@ -470,3 +475,3 @@ ({ flattenStructuredElement,

csnUtils
} = transformUtils.getTransformers(csn, options, pathDelimiter));
} = transformUtils.getTransformers(csn, options, messageFunctions, pathDelimiter));
}

@@ -628,6 +633,6 @@

delete member.masked;
// For HANA: Report an error on
// Report an error on
// - view with parameters that has an element of type association/composition
// - association that points to entity with parameters
if (options.sqlDialect === 'hana' && member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
if (member.target && csnUtils.isAssocOrComposition(member) && !isBetaEnabled(options, 'assocsWithParams')) {
if (artifact.params) {

@@ -877,2 +882,16 @@ // HANA does not allow 'WITH ASSOCIATIONS' on something with parameters:

}
case 'cds.Vector': {
if (options.sqlDialect !== 'hana') {
error('ref-unsupported-type', path, {
'#': 'hana', type: node.type, value: 'hana',
othervalue: options.sqlDialect
});
}
else if (options.transformation === 'hdbcds') {
error('ref-unsupported-type', path, {
'#': 'hdbcds', type: node.type, value: options.sqlDialect
});
}
break;
}
}

@@ -879,0 +898,0 @@ }

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

function expandToFinalBaseType(csn, transformers, csnUtils, services, options, isExternalServiceMember) {
function expandToFinalBaseType(csn, transformers, csnUtils, services, options) {
const isV4 = options.odataVersion === 'v4';

@@ -67,3 +67,3 @@ const special$self = !csn?.definitions?.$self && '$self';

}
}, { skipArtifact: isExternalServiceMember });
});

@@ -80,3 +80,3 @@ if(isBetaEnabled(options, 'odataTerms')) {

expandToFinalBaseType(def.items, defName);
}, [], { skipArtifact: isExternalServiceMember });
}, []);
}

@@ -83,0 +83,0 @@ // In case we have in the model something like:

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

const { setProp } = require('../../base/model');
const { setProp, isBetaEnabled } = require('../../base/model');
const { defNameWithoutServiceOrContextName, isArtifactInService } = require('./utils');
const { cloneCsnNonDict, isBuiltinType, forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
const { copyAnnotations, getNamespace } = require('../../model/csnUtils');
const { isBetaEnabled } = require('../../base/model.js');
const { getNamespace, copyAnnotations, cloneCsnNonDict,
isBuiltinType, isAnnotationExpression,
forEachDefinition, forEachMember, forEachGeneric } = require('../../model/csnUtils');
const { CompilerAssertion } = require('../../base/error');

@@ -223,3 +223,12 @@

});
copyAnnotations(typeDef, newType);
// expression annos and their sub annotations are not propagated to type
let [ xprANames, nxprANames ] = Object.keys(typeDef).reduce((acc, pn) => {
if (pn[0] === '@')
acc[isAnnotationExpression(typeDef[pn]) ? 0 : 1].push(pn);
return acc;
}, [ [], [] ]);
nxprANames = nxprANames.filter(an => !xprANames.some(ean => an.startsWith(`${ean}.`)));
copyAnnotations(typeDef, newType, false, {}, nxprANames);
// if the origin type had items, add items to exposed type

@@ -226,0 +235,0 @@ if(typeDef.kind === 'type') {

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

const { makeMessageFunction } = require('../base/messages');
const { setProp } = require('../base/model');

@@ -24,4 +23,4 @@

// TODO: check the situation with assocs with values. In compacted CSN such elements have only "@Core.Computed": true
function getTransformers(model, options, pathDelimiter = '_') {
const { message, error, warning, info } = makeMessageFunction(model, options);
function getTransformers(model, options, msgFunctions, pathDelimiter = '_') {
const { message, error, warning, info } = msgFunctions;
const csnUtils = getUtils(model);

@@ -326,5 +325,6 @@ const {

* @param {WeakMap} [resolvedLinkTypes=new WeakMap()] A WeakMap with already resolved types for each link-step - safes an `artifactRef` call
* @param {bool} [refParentIsItems] relative ref has an items root (suspend flattening by caller)
* @returns {string[]}
*/
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap()) {
function flattenStructStepsInRef(ref, path, links, scope, resolvedLinkTypes=new WeakMap(), refParentIsItems=false) {
// Refs of length 1 cannot contain steps - no need to check

@@ -335,36 +335,49 @@ if (ref.length < 2 || (scope === '$self' && ref.length === 2)) {

return flatten(ref, path);
const result = scope === '$self' ? [ref[0]] : [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
if(!links && !scope) { // calculate JIT if not supplied
const res = inspectRef(path);
links = res.links;
scope = res.scope;
}
if (scope === '$magic')
return ref;
function flatten(ref, path) {
let result = scope === '$self' ? [ref[0]] : [];
//let stack = []; // IDs of path steps not yet processed or part of a struct traversal
if(!links && !scope) { // calculate JIT if not supplied
const res = inspectRef(path);
links = res.links;
scope = res.scope;
}
if (scope === '$magic')
return ref;
// Don't process a leading $self - it will a .art with .elements!
const startIndex = scope === '$self' ? 1 : 0;
let flattenStep = false;
for(let i = startIndex; i < links.length; i++) {
const value = links[i];
if (flattenStep) {
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[i].id) {
ref[i].id = result[result.length-1];
result[result.length-1] = ref[i];
}
// Don't process a leading $self - it will a .art with .elements!
let i = scope === '$self' ? 1 : 0;
// read property from resolved path link
const art = (propName) =>
(links[i].art?.[propName] ||
effectiveType(links[i].art)[propName] ||
(resolvedLinkTypes.get(links[i])||{})[propName]);
let flattenStep = false;
let nextIsItems = !!art('items') || (refParentIsItems && i === 0);
for(; i < links.length; i++) {
if (flattenStep && !nextIsItems) {
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[i].id) {
ref[i].id = result[result.length-1];
result[result.length-1] = ref[i];
}
else {
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);
// suspend flattening if the next path step has some 'items'
nextIsItems = !!art('items');
}
else {
result.push(ref[i]);
}
// revoke items suspension for next assoc step
if(nextIsItems && art('target'))
nextIsItems = false;
return result;
flattenStep = !links[i].art?.kind &&
!links[i].art?.SELECT &&
!links[i].art?.from &&
art('elements');
}
return result;
}

@@ -371,0 +384,0 @@

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

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

"coverage": "cross-env nyc mocha --reporter-option maxDiffSize=0 test/ test3/testRefFiles.js && nyc report --reporter=lcov",
"coverage:piper": "cross-env nyc mocha --reporter mocha-junit-reporter --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
"coverage:piper": "cross-env nyc mocha --reporter test/TestMochaReporter.js --reporter-options mochaFile=./coverage/TEST-results.xml --reporter-option maxDiffSize=0 --timeout 10000 test/ test3/ && nyc report --reporter=cobertura && nyc report --reporter=lcov",
"lint": "eslint bin/ benchmark/ lib/ test/ test3/ scripts/ types/ && node scripts/linter/lintGrammar.js && node scripts/linter/lintTests.js test3/ && node scripts/linter/lintMessages.js && node scripts/linter/lintMessageIdCoverage.js lib/ && markdownlint README.md CHANGELOG.md doc/ internalDoc/ && cd share/messages && markdownlint .",

@@ -33,0 +33,0 @@ "tslint": "tsc --pretty -p .",

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

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

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

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

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

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

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

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

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

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

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