Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
3
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.8.1 to 1.10.0

lib/model/revealInternalProperties.js

11

bin/cdsc.js

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

var path = require('path');
var reveal = require('./raw-output');
var reveal = require('../lib/model/revealInternalProperties');
const { optionProcessor } = require('../lib/backends');

@@ -204,5 +204,8 @@

let sqlResult = compiler.toSql(model);
for (let name in sqlResult.sql) {
writeToFileOrDisplay(options.out, name + '.sql', sqlResult.sql[name] + '\n', true);
}
['hdbtabletype', 'hdbtable', 'hdbindex', 'hdbfulltextindex', 'hdbview', 'sql'].forEach(pluginName => {
for(let name in sqlResult[pluginName]) {
writeToFileOrDisplay(options.out, name + '.' + pluginName, sqlResult[pluginName][name] + '\n', true);
}
});
if (sqlResult.csn) {

@@ -209,0 +212,0 @@ displayNamedCsn(sqlResult._augmentedCsn, 'sql_csn', options);

# ChangeLog for cdx compiler and backends
## Version 1.10.0
Features
* Annotate entities with '@cds.autoexposed' that are autoexposed in a service.
* Always autoexpose composition targets without annotating them with '@cds.autoexpose'.
* For associations in a service with targets which are not in a service:
+ automatically exclude them if the associaiton is inferred (via select * or include),
+ signal an error if the association is explicitly defined in the service.
* Support the OData annotation vocabulary 'Authorization'.
Changes
* Generate 'null as ...' for virtual view elements.
* Update OData annotation vocabulary 'Core'.
* Change the tranlation of annotation '@readonly' at an element from '@Core.Immutable' to
'@Core.Computed' when processing for OData.
Fixes
* Avoid unnecessary aliases for paths that terminate on an association in the FROM clause.
* Fix an issue with table alias handling in Association to Join translation.
* Translate type 'Cds.DateTime' to SQL type 'TIMESTAMP' for Sqlite.
* Fix an internal error when parsing 'view V as select distinct from E'
* Raise an error that an empty service cannot be used to generate an OData metadata document
* Correctly set the OData principal in a referential constraint for compositions with
free defined ON conditions.
## Version 1.9.0
Changes
* Always use quotes around identifiers for `toSql` and `toHana` with `quoted` or
`hdbcds` names.
* Never use quotes around identifiers for `toSql` and `toHana` with `plain` names.
Issue a warning if an identifier may conflict with a language keyword.
* Generate `.hdbtable`, `.hdbview` etc. files if option `toSql.src` is `hdi` (default
`sql` generates `.sql` files).
Features
* Allow `select` clauses with standard SQL syntax (i.e. also accept `select ... from ...`
in addition to the CDS-specific form `select from ... { ... }`).
* Support `count(*)` etc.
* Support function calls with names arguments.
* Support `aspect` definitions.
Fixes
* Omit unused vocabularies in OData-generated EDMX files.
* For `toOdata`, handle nested anonymous types correctly (also with arrays, e.g. in
action/function parameters)
* Handle mixins correctly when transforming associations to joins.
## Version 1.8.1

@@ -15,3 +64,3 @@

Fixes:
Fixes
* With `--new-redirect-impl`, the navigation along implicitly redirected associations now

@@ -73,3 +122,3 @@ properly considers that elements could have been renamed in the new association target.

Changes:
Changes
* With `--new-csn`, consider `redirected to` on projected associations and

@@ -76,0 +125,0 @@ adapt the `on` condition and the `keys` specification accordingly. There are

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

const csn2edm = require('./edm/csn2edm');
const { mergeOptions } = require('./model/modelUtils');
const { mergeOptions, verifyModelSignature } = require('./model/modelUtils');
const { transformTntExtensions } = require('./transform/tntSpecific');

@@ -40,2 +40,3 @@ const alerts = require('./base/alerts');

.option(' --trace-fs')
.option(' --verify-model')
.option('-R, --raw-output')

@@ -78,2 +79,3 @@ .option(' --beta-mode')

--trace-fs Trace file system access caused by "using from"
--verify-model Verify in backend that augmented CSN was created by compiler

@@ -198,2 +200,5 @@ Internal options (for testing only, may be changed/removed at any time)

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toHana');
// Prepare model for HANA (transferring the options to forHana, and setting 'dialect' to 'hana', because 'toHana' is only used for that)

@@ -333,2 +338,5 @@ let forHanaAugmented = transformForHana(model, mergeOptions(options, { forHana: { dialect: 'hana' } }, { forHana : options.toHana } ));

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toOdata');
// Perform extra-magic for TNT if requested

@@ -444,2 +452,4 @@ if (model.options.tntFlavor) {

optionProcessor.verifyOptions(options, 'toCdl').map(complaint => signal(warning`${complaint}`));
// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toCdl');
return toCdsSource(model, options);

@@ -507,2 +517,4 @@ }

optionProcessor.verifyOptions(options, 'toSwagger').map(complaint => signal(warning`${complaint}`));
// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toSwagger');
// Actual implementation

@@ -521,3 +533,3 @@ return csnToSwagger(model, options);

.option('-l, --locale <locale>')
.option('-s, --src')
.option('-s, --src <style>', ['sql', 'hdi'])
.option('-c, --csn')

@@ -554,3 +566,7 @@ .help(`

-l, --locale <locale> Value for the "$user.locale" variable in "sqlite" dialect
-s, --src (default) Generate SQL source files as "<artifact>.sql"
-s, --src <style> Generate SQL source files as <artifact>.<suffix>
sql : (default) <suffix> is "sql"
hdi : HANA Deployment Infrastructure source files, <suffix> is
the HDI plugin name. Can only be used in combination with
"hana" dialect.
-c, --csn Generate "sql_csn.json" with SQL-preprocessed model

@@ -610,3 +626,3 @@ `);

if (!options.toSql.src && !options.toSql.csn) {
options.toSql.src = true;
options.toSql.src = 'sql';
}

@@ -628,2 +644,5 @@

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toSql');
// FIXME: Currently, '--to-sql' implies transformation for HANA (transferring the options to forHana)

@@ -643,5 +662,11 @@ let forHanaOptions = options.toSql;

// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if(options.toSql.dialect != 'hana' && ['quoted', 'hdbcds'].includes(options.toSql.names)) {
signal(error`Option "{ toSql.dialect: '${options.toSql.dialect}' }" cannot be combined with "{ toSql.names: '${options.toSql.names}' }"`);
if(options.toSql.dialect != 'hana') {
// CDXCORE-465, 'quoted' and 'hdbcds' are to be used in combination with dialect 'hana' only
if(['quoted', 'hdbcds'].includes(options.toSql.names)) {
signal(error`Option "{ toSql.dialect: '${options.toSql.dialect}' }" cannot be combined with "{ toSql.names: '${options.toSql.names}' }"`);
}
// No non-HANA SQL for HDI
if(options.toSql.src === 'hdi') {
signal(error`Option "{ toSql.dialect: '${options.toSql.dialect}' }" cannot be combined with "{ toSql.src: '${options.toSql.src}' }"`);
}
}

@@ -767,2 +792,5 @@

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toRename');
// Requires beta mode

@@ -837,2 +865,5 @@ if (!options.betaMode) {

// Verify model signature (if enabled in options)
verifyModelSignature(model, 'toCsn');
if (options.toCsn.gensrc && !options.newCsn) {

@@ -849,3 +880,3 @@ signal(error`CSN in "gensrc" flavor can only be generated as new-style CSN (option "newCsn" required)`);

// that depend in any way on other options (e.g. toSql provides 'src' if neither 'src' nor
// 'csn' is given: this is a conditional default).
// 'csn' is given: this is a conditional default).
function getDefaultBackendOptions() {

@@ -852,0 +883,0 @@ return {

module.exports = {
// CDL reserved keywords, used during toCdl transformation
// CDL reserved keywords, used for automatic quoting in 'toCdl' renderer
cdl: [
'ALL', 'ANY', 'AS',
'BY', 'CASE', 'CAST',
'DISTINCT', 'EXISTS', 'EXTRACT',
'FALSE', 'FROM', 'GROUP',
'IN', 'KEY', 'NEW',
'NOT', 'NULL', 'OF',
'ON', 'ORDER', 'SELECT',
'SOME', 'TRIM', 'TRUE',
'WHEN', 'WHERE', 'WITH'
'ALL',
'ANY',
'AS',
'BY',
'CASE',
'CAST',
'DISTINCT',
'EXISTS',
'EXTRACT',
'FALSE',
'FROM',
'GROUP',
'IN',
'KEY',
'NEW',
'NOT',
'NULL',
'OF',
'ON',
'ORDER',
'SELECT',
'SOME',
'TRIM',
'TRUE',
'WHEN',
'WHERE',
'WITH',
],
// this group of keywords is relevant for both toHana and toSql
sql92: [
'ABSOLUTE', 'ACTION', 'ADD',
'ALL', 'ALLOCATE', 'ALTER',
'AND', 'ANY', 'ARE',
'AS', 'ASC', 'ASSERTION',
'AT', 'AUTHORIZATION', 'AVG',
'BEGIN', 'BETWEEN', 'BIT',
'BIT_LENGTH', 'BOTH', 'BY',
'CASCADE', 'CASCADED', 'CASE',
'CAST', 'CATALOG', 'CHAR',
'CHARACTER', 'CHARACTER_LENGTH', 'CHAR_LENGTH',
'CHECK', 'CLOSE', 'COALESCE',
'COLLATE', 'COLLATION', 'COLUMN',
'COMMIT', 'CONNECT', 'CONNECTION',
'CONSTRAINT', 'CONSTRAINTS', 'CONTINUE',
'CONVERT', 'CORRESPONDING', 'COUNT',
'CREATE', 'CROSS', 'CURRENT',
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
'CURRENT_USER', 'CURSOR', 'DATE',
'DAY', 'DEALLOCATE', 'DEC',
'DECIMAL', 'DECLARE', 'DEFAULT',
'DEFERRABLE', 'DEFERRED', 'DELETE',
'DESC', 'DESCRIBE', 'DESCRIPTOR',
'DIAGNOSTICS', 'DISCONNECT', 'DISTINCT',
'DOMAIN', 'DOUBLE', 'DROP',
'ELSE', 'END', 'END-EXEC',
'ESCAPE', 'EXCEPT', 'EXCEPTION',
'EXEC', 'EXECUTE', 'EXISTS',
'EXTERNAL', 'EXTRACT', 'FALSE',
'FETCH', 'FIRST', 'FLOAT',
'FOR', 'FOREIGN', 'FOUND',
'FROM', 'FULL', 'GET',
'GLOBAL', 'GO', 'GOTO',
'GRANT', 'GROUP', 'HAVING',
'HOUR', 'IDENTITY', 'IMMEDIATE',
'IN', 'INDICATOR', 'INITIALLY',
'INNER', 'INPUT', 'INSENSITIVE',
'INSERT', 'INT', 'INTEGER',
'INTERSECT', 'INTERVAL', 'INTO',
'IS', 'ISOLATION', 'JOIN',
'KEY', 'LANGUAGE', 'LAST',
'LEADING', 'LEFT', 'LEVEL',
'LIKE', 'LOCAL', 'LOWER',
'MATCH', 'MAX', 'MIN',
'MINUTE', 'MODULE', 'MONTH',
'NAMES', 'NATIONAL', 'NATURAL',
'NCHAR', 'NEXT', 'NO',
'NOT', 'NULL', 'NULLIF',
'NUMERIC', 'OCTET_LENGTH', 'OF',
'ON', 'ONLY', 'OPEN',
'OPTION', 'OR', 'ORDER',
'OUTER', 'OUTPUT', 'OVERLAPS',
'PAD', 'PARTIAL', 'POSITION',
'PRECISION', 'PREPARE', 'PRESERVE',
'PRIMARY', 'PRIOR', 'PRIVILEGES',
'PROCEDURE', 'PUBLIC', 'READ',
'REAL', 'REFERENCES', 'RELATIVE',
'RESTRICT', 'REVOKE', 'RIGHT',
'ROLLBACK', 'ROWS', 'SCHEMA',
'SCROLL', 'SECOND', 'SECTION',
'SELECT', 'SESSION', 'SESSION_USER',
'SET', 'SIZE', 'SMALLINT', 'SOME',
'SPACE', 'SQL', 'SQLCODE',
'SQLERROR', 'SQLSTATE', 'SUBSTRING',
'SUM', 'SYSTEM_USER', 'TABLE',
'TEMPORARY', 'THEN', 'TIME',
'TIMESTAMP', 'TIMEZONE_HOUR', 'TIMEZONE_MINUTE',
'TO', 'TRAILING', 'TRANSACTION',
'TRANSLATE', 'TRANSLATION', 'TRIM',
'TRUE', 'UNION', 'UNIQUE',
'UNKNOWN', 'UPDATE', 'UPPER',
'USAGE', 'USER', 'USING',
'VALUE', 'VALUES', 'VARCHAR',
'VARYING', 'VIEW', 'WHEN',
'WHENEVER', 'WHERE', 'WITH',
'WORK', 'WRITE', 'YEAR', 'ZONE'
// CDL functions, used for automatic quoting in 'toCdl' renderer
cdl_functions: [
'CURRENT_CONNECTION',
'CURRENT_DATE',
'CURRENT_SCHEMA',
'CURRENT_TIME',
'CURRENT_TIMESTAMP',
'CURRENT_TRANSACTION_ISOLATION_LEVEL',
'CURRENT_USER',
'CURRENT_UTCDATE',
'CURRENT_UTCTIME',
'CURRENT_UTCTIMESTAMP',
'SESSION_USER',
'SYSUUID',
],
functions: [
'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP',
'CURRENT_USER', 'SESSION_USER', 'CURRENT_CONNECTION',
'CURRENT_SCHEMA', 'CURRENT_TRANSACTION_ISOLATION_LEVEL', 'CURRENT_UTCDATE',
'CURRENT_UTCTIME', 'CURRENT_UTCTIMESTAMP', 'SYSUUID'
// SQLite keywords, used to warn in 'toSql' renderer with dialect 'sqlite'
// Taken from http://www.sqlite.org/draft/lang_keywords.html
sqlite: [
"ABORT",
"ACTION",
"ADD",
"AFTER",
"ALL",
"ALTER",
"ANALYZE",
"AND",
"AS",
"ASC",
"ATTACH",
"AUTOINCREMENT",
"BEFORE",
"BEGIN",
"BETWEEN",
"BY",
"CASCADE",
"CASE",
"CAST",
"CHECK",
"COLLATE",
"COLUMN",
"COMMIT",
"CONFLICT",
"CONSTRAINT",
"CREATE",
"CROSS",
"CURRENT",
"CURRENT_DATE",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"DATABASE",
"DEFAULT",
"DEFERRABLE",
"DEFERRED",
"DELETE",
"DESC",
"DETACH",
"DISTINCT",
"DO",
"DROP",
"EACH",
"ELSE",
"END",
"ESCAPE",
"EXCEPT",
"EXCLUSIVE",
"EXISTS",
"EXPLAIN",
"FAIL",
"FILTER",
"FOLLOWING",
"FOR",
"FOREIGN",
"FROM",
"FULL",
"GLOB",
"GROUP",
"HAVING",
"IF",
"IGNORE",
"IMMEDIATE",
"IN",
"INDEX",
"INDEXED",
"INITIALLY",
"INNER",
"INSERT",
"INSTEAD",
"INTERSECT",
"INTO",
"IS",
"ISNULL",
"JOIN",
"KEY",
"LEFT",
"LIKE",
"LIMIT",
"MATCH",
"NATURAL",
"NO",
"NOT",
"NOTHING",
"NOTNULL",
"NULL",
"OF",
"OFFSET",
"ON",
"OR",
"ORDER",
"OUTER",
"OVER",
"PARTITION",
"PLAN",
"PRAGMA",
"PRECEDING",
"PRIMARY",
"QUERY",
"RAISE",
"RANGE",
"RECURSIVE",
"REFERENCES",
"REGEXP",
"REINDEX",
"RELEASE",
"RENAME",
"REPLACE",
"RESTRICT",
"RIGHT",
"ROLLBACK",
"ROW",
"ROWS",
"SAVEPOINT",
"SELECT",
"SET",
"TABLE",
"TEMP",
"TEMPORARY",
"THEN",
"TO",
"TRANSACTION",
"TRIGGER",
"UNBOUNDED",
"UNION",
"UNIQUE",
"UPDATE",
"USING",
"VACUUM",
"VALUES",
"VIEW",
"VIRTUAL",
"WHEN",
"WHERE",
"WINDOW",
"WITH",
"WITHOUT",
],
sqlite: [
'ADD', 'ALL', 'ALTER',
'AND', 'AS', 'AUTOINCREMENT',
'BETWEEN', 'CASE', 'CHECK',
'COLLATE', 'COMMIT', 'CONSTRAINT',
'CREATE', 'CROSS', 'DEFAULT',
'DEFERRABLE', 'DELETE', 'DISTINCT',
'DROP', 'ELSE', 'ESCAPE',
'EXCEPT', 'EXISTS', 'FOREIGN',
'FROM', 'FULL', 'GROUP',
'HAVING', 'IN', 'INDEX',
'INNER', 'INSERT', 'INTERSECT',
'INTO', 'IS', 'ISNULL',
'JOIN', 'LEFT', 'LIMIT',
'NATURAL', 'NOT', 'NOTNULL',
'NULL', 'ON', 'OR',
'ORDER', 'OUTER', 'PRIMARY',
'REFERENCES', 'RIGHT', 'ROLLBACK',
'SELECT', 'SET', 'TABLE',
'THEN', 'TO', 'TRANSACTION',
'UNION', 'UNIQUE', 'UPDATE',
'USING', 'VALUES', 'WHEN',
'WHERE'
// HANA keywords, used to warn in 'toSql' renderer with dialect 'hana' or in 'toHana' renderer (both with 'plain' names only)
// Taken from https://help.sap.com/viewer/7c78579ce9b14a669c1f3295b0d8ca16/Cloud/en-US/28bcd6af3eb6437892719f7c27a8a285.html
hana: [
"ALL",
"ALTER",
"AS",
"BEFORE",
"BEGIN",
"BOTH",
"CASE",
"CHAR",
"CONDITION",
"CONNECT",
"CROSS",
"CUBE",
"CURRENT_CONNECTION",
"CURRENT_DATE",
"CURRENT_SCHEMA",
"CURRENT_TIME",
"CURRENT_TIMESTAMP",
"CURRENT_TRANSACTION_ISOLATION_LEVEL",
"CURRENT_USER",
"CURRENT_UTCDATE",
"CURRENT_UTCTIME",
"CURRENT_UTCTIMESTAMP",
"CURRVAL",
"CURSOR",
"DECLARE",
"DISTINCT",
"ELSE",
"ELSEIF",
"END",
"EXCEPT",
"EXCEPTION",
"EXEC",
"FALSE",
"FOR",
"FROM",
"FULL",
"GROUP",
"HAVING",
"IF",
"IN",
"INNER",
"INOUT",
"INTERSECT",
"INTO",
"IS",
"JOIN",
"LEADING",
"LEFT",
"LIMIT",
"LOOP",
"MINUS",
"NATURAL",
"NCHAR",
"NEXTVAL",
"NULL",
"ON",
"ORDER",
"OUT",
"PRIOR",
"RETURN",
"RETURNS",
"REVERSE",
"RIGHT",
"ROLLUP",
"ROWID",
"SELECT",
"SESSION_USER",
"SET",
"SQL",
"START",
"SYSUUID",
"TABLESAMPLE",
"TOP",
"TRAILING",
"TRUE",
"UNION",
"UNKNOWN",
"USING",
"UTCTIMESTAMP",
"VALUES",
"WHEN",
"WHERE",
"WHILE",
"WITH",
]
}

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

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

@@ -297,3 +297,3 @@ },

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

@@ -531,2 +531,4 @@ symbol: { requires: ['location','id'], optional: ['quoted','augmented'] },

}
if (node === null && !noSyntaxErrors())
return; // select from <EOF> produces from: [null]
isObject( node, parent, prop, spec, idx );

@@ -562,2 +564,4 @@

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

@@ -605,3 +609,3 @@ // TODO: also check getOwnPropertyNames(node)

else if (spec.args === 'positional')
throw new Error( `Expected array ${at( [null, parent], prop )}` );
throw new Error( `Expected array${at( [null, parent], prop )}` );
else if (node && typeof node === 'object' && !Object.getPrototypeOf( node ))

@@ -608,0 +612,0 @@ {

@@ -545,3 +545,3 @@ // Compiler phase "define": transform dictionary of AST-like CSNs into augmented CSN

}
else if (query.op && query.op.val === 'query') { // select
else if (query.from) { // select
addQuery();

@@ -594,3 +594,3 @@ query._main.$queries.push( query ); // TODO: set number with it

}
// else: with parse error (`select from <EOF>`)
// else: with parse error (`select from <EOF>`, `select from E { *, ( select }`)
return query._leadingQuery || query;

@@ -846,3 +846,3 @@

if (sp) {
if (sp.default && !pp.default)
if (sp.default && !pp.default) // param DEFAULT clause not supported yet
return null; // param is not optional anymore

@@ -1153,6 +1153,5 @@ let pt = pp.type && resolveUncheckedPath( pp.type, 'type', pp );

return;
let textsName = art.name.absolute + '_txts';
let textsName = art.name.absolute + '_texts';
// If we re-introduce '::', search for '.' after '::'...
let dot = textsName.lastIndexOf('.') + 1;
let viewName = textsName.substring( 0, dot ) + 'localized_' + art.name.absolute.substring( dot );
let viewName = art.name.absolute + '_localized';
let localized = localizedData( art, textsName, viewName );

@@ -1295,3 +1294,3 @@ if (localized) {

function addTextsAssociations( art, textsName, textElems ) {
// texts : Composition of many Books_txts on texts.ID=ID;
// texts : Composition of many Books_texts on texts.ID=ID;
let keys = textElems.filter( e => e.key && e.key.val );

@@ -1311,3 +1310,3 @@ let location = art.name.location;

setProp( texts, '_block', model.$internal );
// localized : Association to Books_txts on
// localized : Association to Books_texts on
// localized.ID=ID and localized.locale = $user.locale;

@@ -1314,0 +1313,0 @@ keys.push( ['localized.locale', '$user.locale'] );

@@ -43,2 +43,10 @@ {

},
"Auth.SecuritySchemes": {
"Type": "Collection(Auth.SecurityScheme)",
"AppliesTo": "EntityContainer"
},
"Auth.Authorizations": {
"Type": "Collection(Auth.Authorization)",
"AppliesTo": "EntityContainer"
},
"Capabilities.ConformanceLevel": {

@@ -448,2 +456,6 @@ "Type": "Capabilities.ConformanceLevelType",

},
"Core.Example": {
"Type": "Core.ExampleValue",
"AppliesTo": "EntityType ComplexType TypeDefinition Term Property NavigationProperty Parameter ReturnType"
},
"Core.Messages": {

@@ -562,2 +574,36 @@ "Type": "Collection(Core.MessageType)"

},
"PersonalData.EntitySemantics": {
"Type": "PersonalData.EntitySemanticsType",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.DataSubjectRole": {
"Type": "Edm.String",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.DataSubjectRoleDescription": {
"Type": "Edm.String",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.FieldSemantics": {
"Type": "PersonalData.FieldSemanticsType",
"AppliesTo": "Property",
"$experimental": true
},
"PersonalData.IsPotentiallyPersonal": {
"Type": "Core.Tag",
"AppliesTo": "Property",
"$experimental": true
},
"PersonalData.IsPotentiallySensitive": {
"Type": "Core.Tag",
"AppliesTo": "Property"
},
"PersonalData.IsUserID": {
"Type": "Core.Tag",
"AppliesTo": "Property",
"$experimental": true
},
"UI.HeaderInfo": {

@@ -757,36 +803,2 @@ "Type": "UI.HeaderInfoType",

"AppliesTo": "Collection"
},
"PersonalData.EntitySemantics": {
"Type": "PersonalData.EntitySemanticsType",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.DataSubjectRole": {
"Type": "Edm.String",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.DataSubjectRoleDescription": {
"Type": "Edm.String",
"AppliesTo": "EntitySet",
"$experimental": true
},
"PersonalData.FieldSemantics": {
"Type": "PersonalData.FieldSemanticsType",
"AppliesTo": "Property",
"$experimental": true
},
"PersonalData.IsPotentiallyPersonal": {
"Type": "Core.Tag",
"AppliesTo": "Property",
"$experimental": true
},
"PersonalData.IsPotentiallySensitive": {
"Type": "Core.Tag",
"AppliesTo": "Property"
},
"PersonalData.IsUserID": {
"Type": "Core.Tag",
"AppliesTo": "Property",
"$experimental": true
}

@@ -827,2 +839,117 @@ },

},
"Auth.SecurityScheme": {
"$kind": "ComplexType",
"Properties": {
"Authorization": "Edm.String",
"RequiredScopes": "Collection(Edm.String)"
}
},
"Auth.Authorization": {
"$kind": "ComplexType",
"Abstract": "true",
"Properties": {
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OpenIDConnect": {
"$kind": "ComplexType",
"BaseType": "Auth.Authorization",
"Properties": {
"IssuerUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.Http": {
"$kind": "ComplexType",
"BaseType": "Auth.Authorization",
"Properties": {
"Scheme": "Edm.String",
"BearerFormat": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OAuthAuthorization": {
"$kind": "ComplexType",
"BaseType": "Auth.Authorization",
"Abstract": "true",
"Properties": {
"Scopes": "Collection(Auth.AuthorizationScope)",
"RefreshUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OAuth2ClientCredentials": {
"$kind": "ComplexType",
"BaseType": "Auth.OAuthAuthorization",
"Properties": {
"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"RefreshUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OAuth2Implicit": {
"$kind": "ComplexType",
"BaseType": "Auth.OAuthAuthorization",
"Properties": {
"AuthorizationUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"RefreshUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OAuth2Password": {
"$kind": "ComplexType",
"BaseType": "Auth.OAuthAuthorization",
"Properties": {
"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"RefreshUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.OAuth2AuthCode": {
"$kind": "ComplexType",
"BaseType": "Auth.OAuthAuthorization",
"Properties": {
"AuthorizationUrl": "Edm.String",
"TokenUrl": "Edm.String",
"Scopes": "Collection(Auth.AuthorizationScope)",
"RefreshUrl": "Edm.String",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.AuthorizationScope": {
"$kind": "ComplexType",
"Properties": {
"Scope": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.ApiKey": {
"$kind": "ComplexType",
"BaseType": "Auth.Authorization",
"Properties": {
"KeyName": "Edm.String",
"Location": "Auth.KeyLocation",
"Name": "Edm.String",
"Description": "Edm.String"
}
},
"Auth.KeyLocation": {
"$kind": "EnumType",
"Members": [
"Header",
"QueryOption",
"Cookie"
]
},
"Capabilities.ConformanceLevelType": {

@@ -1364,2 +1491,40 @@ "$kind": "EnumType",

},
"Core.ExampleValue": {
"$kind": "ComplexType",
"Properties": {
"Description": "Edm.String"
}
},
"Core.PrimitiveExampleValue": {
"$kind": "ComplexType",
"BaseType": "Core.ExampleValue",
"Properties": {
"Value": "Edm.PrimitiveType",
"Description": "Edm.String"
}
},
"Core.ComplexExampleValue": {
"$kind": "ComplexType",
"BaseType": "Core.ExampleValue",
"Properties": {
"Value": "Edm.ComplexType",
"Description": "Edm.String"
}
},
"Core.EntityExampleValue": {
"$kind": "ComplexType",
"BaseType": "Core.ExampleValue",
"Properties": {
"Value": "Edm.EntityType",
"Description": "Edm.String"
}
},
"Core.ExternalExampleValue": {
"$kind": "ComplexType",
"BaseType": "Core.ExampleValue",
"Properties": {
"ExternalValue": "Edm.String",
"Description": "Edm.String"
}
},
"Core.MessageType": {

@@ -1426,2 +1591,6 @@ "$kind": "ComplexType",

},
"Core.QualifiedTermName": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
},
"Core.QualifiedTypeName": {

@@ -1431,2 +1600,14 @@ "$kind": "TypeDefinition",

},
"Core.LocalDateTime": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
},
"PersonalData.EntitySemanticsType": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
},
"PersonalData.FieldSemanticsType": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
},
"UI.HeaderInfoType": {

@@ -2048,12 +2229,4 @@ "$kind": "ComplexType",

}
},
"PersonalData.EntitySemanticsType": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
},
"PersonalData.FieldSemanticsType": {
"$kind": "TypeDefinition",
"UnderlyingType": "Edm.String"
}
}
}

@@ -9,4 +9,54 @@ 'use strict';

const knownVocabularies = ['Aggregation', 'Analytics', 'Core', 'Common', 'UI', 'Communication', 'Capabilities', 'Measures', 'Validation', 'PersonalData'];
var vocabularyDefinitions = {
'Aggregation': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Aggregation.V1.xml" },
'inc': { Alias: "Aggregation", Namespace: "Org.OData.Aggregation.V1" }
},
'Analytics': {
'ref': { Uri: "https://wiki.scn.sap.com/wiki/download/attachments/462030211/Analytics.xml?api=v2" },
'inc': { Alias: "Analytics", Namespace: "com.sap.vocabularies.Analytics.v1" }
},
'Authorization': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Authorization.V1.xml" },
'inc': { Alias: "Authorization", Namespace: "Org.OData.Authorization.V1" }
},
'Capabilities': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml" },
'inc': { Alias: "Capabilities", Namespace: "Org.OData.Capabilities.V1" }
},
'Common': {
'ref': { Uri: "https://wiki.scn.sap.com/wiki/download/attachments/448470974/Common.xml?api=v2" },
'inc': { Alias: "Common", Namespace: "com.sap.vocabularies.Common.v1" }
},
'Communication': {
'ref': { Uri: "https://wiki.scn.sap.com/wiki/download/attachments/448470971/Communication.xml?api=v2" },
'inc': { Alias: "Communication", Namespace: "com.sap.vocabularies.Communication.v1" }
},
'Core': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml" },
'inc': { Alias: "Core", Namespace: "Org.OData.Core.V1" }
},
'Measures': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml" },
'inc': { Alias: "Measures", Namespace: "Org.OData.Measures.V1" }
},
'PersonalData': {
'ref': { Uri: "https://wiki.scn.sap.com/wiki/download/attachments/496435637/PersonalData.xml?api=v2" },
'inc': { Alias: "PersonalData", Namespace: "com.sap.vocabularies.PersonalData.v1" }
},
'UI': {
'ref': { Uri: "https://wiki.scn.sap.com/wiki/download/attachments/448470968/UI.xml?api=v2" },
'inc': { Alias: "UI", Namespace: "com.sap.vocabularies.UI.v1" }
},
'Validation': {
'ref': { Uri: "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Validation.V1.xml" },
'inc': { Alias: "Validation", Namespace: "Org.OData.Validation.V1" }
},
};
const knownVocabularies = Object.keys(vocabularyDefinitions);
/**************************************************************************************************

@@ -29,5 +79,10 @@ * csn2annotationEdm

let usedVocabs = {};
// we take note of which vocabularies are actually used in a service in order to avoid
// producing useless references; reset everything to "unused"
knownVocabularies.forEach(n => {
vocabularyDefinitions[n].used = false;
});
// use closure to avoid making "dict" and "experimental" global variables
// provide functions for dictionary lookup
// use closure to avoid making "dict" and "experimental" global variables
let { getDictTerm, getDictType } = function(){

@@ -39,4 +94,4 @@ let dict = options.dictReplacement || oDataDictionary; // tests can set different dictionary via options

// called to look-up a term in the dictionary
// in addition, note usage of the respective vocabulary and issue a warning if the term
// is flagged as "experimental"
// in addition: - note usage of the respective vocabulary
// - issue a warning if the term is flagged as "experimental"
getDictTerm: function(termName, context) {

@@ -46,3 +101,3 @@ let dictTerm = dict.terms[termName]

// register usage of vocabulary
usedVocabs[termName.slice(0, termName.indexOf('.'))] = true;
vocabularyDefinitions[termName.slice(0, termName.indexOf('.'))].used = true;
// issue warning for usage of experimental Terms, but only once per Term

@@ -62,3 +117,3 @@ if (dictTerm["$experimental"] && !experimental[termName] && !options.betaMode) {

// register usage of vocabulary
usedVocabs[typeName.slice(0, typeName.indexOf('.'))] = true;
vocabularyDefinitions[typeName.slice(0, typeName.indexOf('.'))].used = true;
}

@@ -70,4 +125,2 @@ return dictType;

let g_annosArray = [];
let v = options.v;

@@ -77,2 +130,7 @@

// global variable where we store all the generated annotations
let g_annosArray = [];
// Crawl over the csn and trigger the annotation translation for all kinds
// of annotated things.
// Note: only works for single service

@@ -106,7 +164,22 @@ // Note: we assume that all objects ly flat in the service, i.e. objName always

// generate the edmx "frame" around the annotations
let schema = new Edm.Schema(v, serviceName, serviceName, g_annosArray, false);
let service = new Edm.DataServices(v, schema);
let edm = new Edm(v, service);
// add references for the used vocabularies
knownVocabularies.forEach(n => {
if(vocabularyDefinitions[n].used) {
let r = new Edm.Reference(v, vocabularyDefinitions[n].ref);
r.append(new Edm.Include(v, vocabularyDefinitions[n].inc))
edm._defaultRefs.push(r);
}
})
return edm;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// helper to determine the OData version

@@ -119,12 +192,5 @@ // tnt is always v2

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//
// this function is called in the translation code to issue a warning message
// messages are reported via the alerts attribute of csn
//
// context contains "semantic location"
function warningMessage(context, message) {

@@ -144,14 +210,44 @@ let fullMessage = "in annotation translation: " + message;

// there are 4 possible kinds of targets for annotations
// csn: annotation at entity
// depending on the term, the target in edm is the corresponding
// - entity type
// - or entity set
// csn: annotation at element of entity
// target in edm is the corresponding element of the entity type
// csn: annotation at parameter of action of entity
// target in edm is the action parameter in the EntityContainer
/*
Mapping annotated thing in cds/csn => annotated thing in edmx:
carrier: the annotated thing in cds, can be: service, entity, structured type, element of entity or structured type,
action/function, parameter of action/function
target: the annotated thing in OData
In the edmx, all annotations for a OData thing are put into an element
<Annotations Target="..."> where Target is the full name of the target
There is one exception (Schema), see below
carrier = service
the target is the EntityContainer, unless the annotation has an "AppliesTo" where only Schema is given, but not EntityContainer
then the <Annotation ...> is directly put into <Schema ...> without an enclosing <Annotations ...>
carrier = entity (incl. view/projection)
the target is the corresponding EntityType, unless the annotation has an "AppliesTo" where only EntitySet is given, but not EntityType
then the target is the corresponding EntitySet
carrier = structured type
the target is the corresponding ComplexType
carrier = element of entity or structured type
the target is the corresponding Property of the EntityType/ComplexType: Target = <entity/type>/<element>
carrier = action/function
v2, unbound: Target = <service>.EntityContainer/<action/function>
v2, bound: Target = <service>.EntityContainer/<entity>_<action/function>
v4, unbound action: Target = <service>.<action>()
v4, bound action: Target = <service>.<action>(<service>.<entity>)
v4, unbound function: Target = <service>.<function>(<1st param type>, <2nd param type>, ...)
v4, bound function: Target = <service>.<function>(<service>.<entity>, <1st param type>, <2nd param type>, ...)
carrier = parameter of action/function
like above, but append "/<parameter" to the Target
*/
// handle the annotations of the elements of an object

@@ -194,3 +290,3 @@ // in: objname : name of the object

// annotations for actions and functions (and their parameters)
// Annotations for actions and functions (and their parameters)
// v2, unbound: Target = <service>.EntityContainer/<action/function>

@@ -207,3 +303,3 @@ // v2, bound: Target = <service>.EntityContainer/<entity>_<action/function>

function handleBoundActions(cObjectname, cObject) {
// service name -> remove last part of the object name
// get service name: remove last part of the object name
// only works if all objects ly flat in the service

@@ -257,17 +353,9 @@ let nameParts = cObjectname.split(".")

// note: in csn, all annotations are flattened out
// => values can be
// - primitive values (string, number)
// - pseudo-records with "#" or "="
// - arrays
// handle the annotations for a specific object or element or action parameter,
// here called carrier
// edmCarrierName : string, name of the annotated object in edm,
// element path is separated from object name by "/"
// TODO: handling of nested elements?
// carrier: object, the annotated object, contains all the annotations
// handle all the annotations for a given cds thing, here called carrier
// edmTargetName : string, name of the target in edm
// carrier: object, the annotated cds thing, contains all the annotations
// as properties with names starting with @
function handleAnnotations(edmCarrierName, carrier) {
function handleAnnotations(edmTargetName, carrier) {
// collect the names of the carrier's annotation properties

@@ -280,3 +368,9 @@ // keep only those annotations that - start with a known vocabulary name

// build prefix tree for the annotations attached to the carrier
// in csn, all annotations are flattened
// => values can be - primitive values (string, number)
// - pseudo-records with "#" or "="
// - arrays
// in OData, there are "structured" annotations -> we first need to regroup the cds annotations
// by building a "prefix tree" for the annotations attached to the carrier
// see example at definition of function mergePathStepsIntoPrefixTree
let prefixTree = {};

@@ -294,8 +388,19 @@ for (let a of knownAnnos) {

// addAnnotation() is used to actually add an <Annotation ...> to the
// construct a function that is used to add an <Annotation ...> to the
// respective <Annotations ...> element
// this function is specific to the actual carrier, following the mapping rules given above
let addAnnotation = function() {
let appliesTest = null; // used in closure
let stdName = edmCarrierName;
let altName = null; // used in closure
// usually, for a given carrier there is one target
// stdName: name of this target
// newAnnosStd: the corresponding <Annotations ...> tag
// for some carriers (service, entity), there can be an alternative target
// altName: name of alternative target
// newAnnosAlt: the corresponding <Annotations ...> tag
// which one to choose depends on the "AppliesTo" info of the single annotations, so we have
// to defer this decision; this is why we here construct a function that can make the decision
// later when looking at single annotations
let appliesTest = null; // the condition to decide between std and alt
let stdName = edmTargetName;
let altName = null;
if (carrier.kind === 'entity' || carrier.kind === 'view') {

@@ -306,3 +411,3 @@ // if annotated object is an entity, annotation goes to the EntityType,

// find last . in name and insert "EntityContainer/"
altName = edmCarrierName.replace(/\.(?=[^.]*$)/, '.EntityContainer/');
altName = edmTargetName.replace(/\.(?=[^.]*$)/, '.EntityContainer/');
}

@@ -313,4 +418,4 @@ else if (carrier.kind === 'service') {

appliesTest = (x => x.match(/Schema/) && !x.match(/EntityContainer/));
stdName = edmCarrierName + '.EntityContainer';
altName = edmCarrierName;
stdName = edmTargetName + '.EntityContainer';
altName = edmTargetName;
}

@@ -321,3 +426,3 @@

g_annosArray.push(newAnnosStd);
let newAnnosAlt = null; // used in closure
let newAnnosAlt = null; // create only on demand

@@ -349,3 +454,3 @@ return function(annotation, appliesTo) {

// and put them into the elements property of the result object
handleAnno2(addAnnotation, edmCarrierName /*used for messages*/, prefixTree);
handleAnno2(addAnnotation, edmTargetName /*used for messages*/, prefixTree);
}

@@ -391,7 +496,11 @@

function handleAnno2(addAnnotationFunc, edmCarrierName, prefixTree) {
// handle all the annotations for a given carrier
// addAnnotationFunc: a function that adds the <Annotation ...> tags created here into the
// correct parent tag (see handleAnnotations())
// edmTargetName: name of the edmx target, only used for messages
// prefixTree: the annotations
function handleAnno2(addAnnotationFunc, edmTargetName, prefixTree) {
// first level names of prefix tree are the vocabulary names
// second level names are the term names
// create an annotation object for each term
// create an annotation tag <Annotation ...> for each term
for (let voc of Object.keys(prefixTree)) {

@@ -401,9 +510,10 @@ for (let term of Object.keys(prefixTree[voc])) {

// context is "semantic" location info used for messages
let context = { target: edmTargetName, term: fullTermName, stack: [] };
// anno is the full <Annotation Term=...>
let context = { target: edmCarrierName, term: fullTermName, stack: [] };
let anno = handleTerm(fullTermName, prefixTree[voc][term], context);
// addAnnotationFunc needs AppliesTo info from dictionary to decide where to put the anno
fullTermName = fullTermName.replace(/#(\w+)$/g, "");
let dictTerm = getDictTerm(fullTermName, context); // message for unknown term issued in handleTerm
fullTermName = fullTermName.replace(/#(\w+)$/g, ""); // remove qualifier
let dictTerm = getDictTerm(fullTermName, context); // message for unknown term was already issued in handleTerm
addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo);

@@ -421,2 +531,3 @@ }

function handleTerm(termName, annoValue, context) {
// create the <Annotation ...> tag
let newAnno = new Edm.Annotation(v, termName);

@@ -446,2 +557,3 @@

// handle the annotation value and put the result into the <Annotation ...> tag just created above
handleValue(annoValue, newAnno, termNameWithoutQualifiers, termTypeName, context);

@@ -451,17 +563,100 @@ return newAnno;

// handle an annotation value
// cAnnoValue: the annotation value (c : csn)
// oTarget: the result object (o: odata)
// oTermName: current term
// dTypeName: expected type of cAnnoValue according to dictionary, may be null (d: dictionary)
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName, context) {
// this function basically only figures out what kind of annotation value we have
// (can be: array, expression, enum, pseudo-record, record, simple value),
// then calls a more specific function to deal with it and puts
// the result into the oTarget object
if (Array.isArray(cAnnoValue))
{
if (isEnumType(dTypeName))
{
// if we find an array although we expect an enum, this may be a "flag enum"
checkMultiEnumValue(cAnnoValue, dTypeName, context);
oTarget.setJSON({ "EnumMember": generateMultiEnumValue(cAnnoValue, dTypeName, false), "EnumMember@odata.type" : '#'+dTypeName });
oTarget.setXml( { "EnumMember": generateMultiEnumValue(cAnnoValue, dTypeName, true) });
}
else
{
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, context));
}
}
else if (cAnnoValue && typeof cAnnoValue === 'object') {
if (Object.keys(cAnnoValue).length == 0) {
warningMessage(context, "empty record");
}
else if ("=" in cAnnoValue) {
// expression
let res = handleExpression(cAnnoValue["="], dTypeName, context);
oTarget.setXml( { [res.name] : res.value });
oTarget.setJSON( { [res.name] : res.value });
}
else if (cAnnoValue["#"] !== undefined) {
// enum
if (dTypeName) {
checkEnumValue(cAnnoValue["#"], dTypeName, context);
oTarget.setJSON({ "EnumMember": cAnnoValue["#"], "EnumMember@odata.type" : '#'+dTypeName, });
oTarget.setXml( { "EnumMember": dTypeName + "/" + cAnnoValue["#"] });
}
else {
// do something seemingly reasonable even if there is no dictionary info
oTarget.setJSON({ "EnumMember": cAnnoValue["#"], "EnumMember@odata.type" : '#'+oTermName + "Type/" });
oTarget.setXml( { "EnumMember": oTermName + "Type/" + "/" + cAnnoValue["#"] });
}
}
else if (cAnnoValue["$value"] !== undefined) {
// "pseudo-structure" used for annotating scalar annotations
handleValue(cAnnoValue["$value"], oTarget, oTermName, dTypeName, context);
let k = Object.keys(cAnnoValue).filter( x => x.charAt(0) == "@");
if (!k || k.length == 0) {
warningMessage(context, "pseudo-struct without nested annotation");
}
for (let nestedAnnoName of k) {
let nestedAnno = handleTerm(nestedAnnoName.slice(1), cAnnoValue[nestedAnnoName], context);
oTarget.append(nestedAnno);
}
}
else if (cAnnoValue["$edmJson"]) {
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
oTarget.append(handleEdmJson(cAnnoValue["$edmJson"], context));
}
else if ( Object.keys(cAnnoValue).filter( x => x.substr(0,1) !== "@" ).length === 0) {
// object consists only of properties starting with "@"
warningMessage(context, "nested annotations without corresponding base annotation");
}
else {
// regular record
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, context));
}
}
else {
let res = handleSimpleValue(cAnnoValue, dTypeName, context);
oTarget.setXml( { [res.name] : res.value });
oTarget.setJSON( { [res.jsonName] : res.value });
}
}
// found an enum value ("#"), check whether this fits
// the expected type "expectedTypeName"
function checkEnumValue(enumValue, expectedTypeName, context) {
let expectedType = getDictType(expectedTypeName);
if (!expectedType && !isPrimitiveType(expectedTypeName)) {
warningMessage(context, "internal error: dictionary inconsistency: type '" + expectedTypeName + "' not found");
// the expected type "dTypeName"
function checkEnumValue(enumValue, dTypeName, context) {
let expectedType = getDictType(dTypeName);
if (!expectedType && !isPrimitiveType(dTypeName)) {
warningMessage(context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
}
else if (isComplexType(expectedTypeName)) {
warningMessage(context, "found enum value, but expected complex type " + expectedTypeName);
else if (isComplexType(dTypeName)) {
warningMessage(context, "found enum value, but expected complex type " + dTypeName);
}
else if (isPrimitiveType(expectedTypeName) || expectedType["$kind"] != "EnumType") {
warningMessage(context, "found enum value, but expected non-enum type " + expectedTypeName);
else if (isPrimitiveType(dTypeName) || expectedType["$kind"] != "EnumType") {
warningMessage(context, "found enum value, but expected non-enum type " + dTypeName);
}
else if (!expectedType["Members"].includes(enumValue)) {
warningMessage(context, "enumeration type " + expectedTypeName + " has no value " + enumValue);
warningMessage(context, "enumeration type " + dTypeName + " has no value " + enumValue);
}

@@ -471,2 +666,35 @@ return;

// cAnnoValue: array
// dTypeName: expected type, already identified as enum type
// array is expected to contain enum values
function checkMultiEnumValue(cAnnoValue, dTypeName, context) {
// we know that dTypeName is not null
let type = getDictType(dTypeName);
if (!type || type["IsFlags"] != "true") {
warningMessage(context, "enum type '" + dTypeName + "' doesn't allow multiple values");
}
let index = 0;
for (let e of cAnnoValue) {
context.stack.push("[" + index++ + "]");
if (e["#"]) {
checkEnumValue(e["#"], dTypeName, context);
}
else {
// TODO improve message: but found ...
warningMessage(context, "expected an enum value");
}
context.stack.pop();
}
}
function generateMultiEnumValue(cAnnoValue, dTypeName, forXml)
{
// remove all invalid entries (warnining message has already been issued)
// replace short enum name by the full name
// concatenate all the enums to a string, separated by spaces
return cAnnoValue.filter( x => x["#"] != undefined ).map( x => (forXml ? dTypeName + "/" : "") + x["#"] ).join(forXml ? ' ' : ',');
}
// found an expression value ("=") "expr"

@@ -687,132 +915,19 @@ // expected type is dTypeName

// handle the actual value cAnnoValue
// oTarget: the result object
// oTermName: current term
// dTypeName: expected type of cAnnoValue according to dictionary, may be null
function handleValue(cAnnoValue, oTarget, oTermName, dTypeName, context) {
// value can be: array, expression, enum, pseudo-record, record, simple value
if (Array.isArray(cAnnoValue))
{
if (isEnumType(dTypeName))
{
// if we find an array although we expect an enum, this may be a "flag enum"
checkMultiEnumValue(cAnnoValue, dTypeName, context);
oTarget.setJSON({ "EnumMember": generateMultiEnumValue(cAnnoValue, dTypeName, false), "EnumMember@odata.type" : '#'+dTypeName });
oTarget.setXml( { "EnumMember": generateMultiEnumValue(cAnnoValue, dTypeName, true) });
}
else
{
oTarget.append(generateCollection(cAnnoValue, oTermName, dTypeName, context));
}
}
else if (cAnnoValue && typeof cAnnoValue === 'object') {
if (Object.keys(cAnnoValue).length == 0) {
warningMessage(context, "empty record");
}
else if ("=" in cAnnoValue) {
let res = handleExpression(cAnnoValue["="], dTypeName, context);
oTarget.setXml( { [res.name] : res.value });
oTarget.setJSON( { [res.name] : res.value });
}
else if (cAnnoValue["#"] !== undefined)
{
if (dTypeName)
{
checkEnumValue(cAnnoValue["#"], dTypeName, context);
oTarget.setJSON({ "EnumMember": cAnnoValue["#"], "EnumMember@odata.type" : '#'+dTypeName, });
oTarget.setXml( { "EnumMember": dTypeName + "/" + cAnnoValue["#"] });
}
else
{
oTarget.setJSON({ "EnumMember": cAnnoValue["#"], "EnumMember@odata.type" : '#'+oTermName + "Type/" });
oTarget.setXml( { "EnumMember": oTermName + "Type/" + "/" + cAnnoValue["#"] });
}
}
else if (cAnnoValue["$value"] !== undefined) {
// "pseudo-structure" used for annotating scalar annotations
handleValue(cAnnoValue["$value"], oTarget, oTermName, dTypeName, context);
let k = Object.keys(cAnnoValue).filter( x => x.charAt(0) == "@");
if (!k || k.length == 0) {
warningMessage(context, "pseudo-struct without nested annotation");
}
for (let nestedAnnoName of k) {
let nestedAnno = handleTerm(nestedAnnoName.slice(1), cAnnoValue[nestedAnnoName], context);
oTarget.append(nestedAnno);
}
}
else if (cAnnoValue["$edmJson"]) {
// "pseudo-structure" used for embedding a piece of JSON that represents "OData CSDL, JSON Representation"
oTarget.append(handleEdmJson(cAnnoValue["$edmJson"], context));
}
else if ( Object.keys(cAnnoValue).filter( x => x.substr(0,1) !== "@" ).length === 0) {
warningMessage(context, "nested annotations without corresponding base annotation");
}
else {
// regular record
oTarget.append(generateRecord(cAnnoValue, oTermName, dTypeName, context));
}
}
else {
let res = handleSimpleValue(cAnnoValue, dTypeName, context);
oTarget.setXml( { [res.name] : res.value });
oTarget.setJSON( { [res.jsonName] : res.value });
}
}
// cAnnoValue: array
// dTypeName: expected type, already identified as enum type
// array is expected to contain enum values
function checkMultiEnumValue(cAnnoValue, dTypeName, context) {
// we know that dTypeName is not null
let type = getDictType(dTypeName);
if (!type || type["IsFlags"] != "true") {
warningMessage(context, "enum type '" + dTypeName + "' doesn't allow multiple values");
}
let index = 0;
for (let e of cAnnoValue) {
context.stack.push("[" + index++ + "]");
if (e["#"]) {
checkEnumValue(e["#"], dTypeName, context);
}
else {
// TODO improve message: but found ...
warningMessage(context, "expected an enum value");
}
context.stack.pop();
}
}
function generateMultiEnumValue(cAnnoValue, dTypeName, forXml)
{
// remove all invalid entries (warnining message has already been issued)
// replace short enum name by the full name
// concatenate all the enums to a string, separated by spaces
return cAnnoValue.filter( x => x["#"] != undefined ).map( x => (forXml ? dTypeName + "/" : "") + x["#"] ).join(forXml ? ' ' : ',');
}
// obj: object representing the record
// dictRecordTypeName : name of the expected record type according to vocabulary, may be null
// obj: object representing the record
// dTypeName : name of the expected record type according to vocabulary, may be null
//
// can be called for a record directly below a term, or at a deeper level
// if the corresponding complex type is unique, it needs not to be written into the
// record; if it is abstract or part of a type hierarchy, it must be written
// into the record as attribute "Type"
function generateRecord(obj, termName, dictRecordTypeName, context) {
function generateRecord(obj, termName, dTypeName, context) {
let newRecord = new Edm.Record(v);
let actualTypeName = null;
if (dictRecordTypeName && !isComplexType(dictRecordTypeName)) {
if (!getDictType(dictRecordTypeName) && !isPrimitiveType(dictRecordTypeName) && !isCollection(dictRecordTypeName))
warningMessage(context, "internal error: dictionary inconsistency: type '" + dictRecordTypeName + "' not found");
// first determine what is the actual type to be used for the record
if (dTypeName && !isComplexType(dTypeName)) {
if (!getDictType(dTypeName) && !isPrimitiveType(dTypeName) && !isCollection(dTypeName))
warningMessage(context, "internal error: dictionary inconsistency: type '" + dTypeName + "' not found");
else
warningMessage(context, "found complex type, but expected type '" + dictRecordTypeName + "'");
warningMessage(context, "found complex type, but expected type '" + dTypeName + "'");
return newRecord;
}
let actualTypeName = null;
if (obj["$Type"]) { // type is explicitly specified

@@ -824,7 +939,7 @@ actualTypeName = obj["$Type"];

}
else if (dictRecordTypeName && !isDerivedFrom(actualTypeName, dictRecordTypeName)) {
else if (dTypeName && !isDerivedFrom(actualTypeName, dTypeName)) {
// this type doesn't fit the expected one
warningMessage(context, "explicitly specified type '" + actualTypeName
+ "' is not derived from expected type '" + dictRecordTypeName + "'");
actualTypeName = dictRecordTypeName;
+ "' is not derived from expected type '" + dTypeName + "'");
actualTypeName = dTypeName;
}

@@ -834,3 +949,3 @@ else if (isAbstractType(actualTypeName)) {

warningMessage(context, "explicitly specified type '" + actualTypeName + "' is abstract, specify a concrete type");
actualTypeName = dictRecordTypeName;
actualTypeName = dTypeName;
}

@@ -842,14 +957,14 @@ else {

}
else if (dictRecordTypeName) { // there is an expected type name according to dictionary
else if (dTypeName) { // there is an expected type name according to dictionary
// convenience for common situation:
// if DataFieldAbstract is expected and no explicit type is provided, automatically choose DataField
if (dictRecordTypeName == "UI.DataFieldAbstract") {
if (dTypeName == "UI.DataFieldAbstract") {
actualTypeName = "UI.DataField";
}
else {
actualTypeName = dictRecordTypeName;
actualTypeName = dTypeName;
}
if (isAbstractType(actualTypeName)) {
warningMessage(context, "type '" + dictRecordTypeName + "' is abstract, use '$Type' to specify a concrete type");
warningMessage(context, "type '" + dTypeName + "' is abstract, use '$Type' to specify a concrete type");
}

@@ -863,2 +978,3 @@

// now the type is clear, so look ath the value
let dictProperties = getAllProperties(actualTypeName);

@@ -871,5 +987,6 @@

if (i == "$Type") {
// nop
// ignore, this is an "artificial" property used to indicate the type
}
else if (i.charAt(0) == "@") {
// not a regular property, but a nested annotation
let newAnno = handleTerm(i.substring(1, i.length), obj[i], context);

@@ -879,2 +996,3 @@ newRecord.append(newAnno);

else {
// regular property
let dictPropertyTypeName = null;

@@ -889,2 +1007,3 @@ if (dictProperties) {

let newPropertyValue = new Edm.PropertyValue(v, i);
// property value can be anything, so delegate handling to handleValue
handleValue(obj[i], newPropertyValue, termName, dictPropertyTypeName, context);

@@ -903,3 +1022,2 @@ newRecord.append(newPropertyValue);

// dTypeName : Collection(...) according to dictionary
//
function generateCollection(annoValue, termName, dTypeName, context) {

@@ -923,2 +1041,5 @@ let newCollection = new Edm.Collection(v);

// for dealing with the single array entries we unfortunately cannot call handleValue(),
// as the values inside an array are represented differently from the values
// in a record or term
if (Array.isArray(value)) {

@@ -955,2 +1076,8 @@ warningMessage(context, "nested collections are not supported");

// Not everything that can occur in OData annotations can be expressed with
// corresponding constructs in cds annotations. For these special cases
// we have a kind of "inline assembler" mode, i.e. you can in cds provide
// as annotation value a json snippet that looks like the final edm-json.
// See example in test/odataAnnotations/smallTests/edmJson_noReverse_ok
function handleEdmJson(obj, context)

@@ -1051,2 +1178,6 @@ {

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// filter function, assumed to be used for array of string

@@ -1060,8 +1191,2 @@ // accepts those strings that start with a known vocabulary name

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// resolve "derived types"

@@ -1110,3 +1235,2 @@ // -> if dTypeName is a TypeDefinition, replace by

// return dictionary of all properties of typeName, including those of base types

@@ -1113,0 +1237,0 @@ function getAllProperties(typeName) {

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

const COMPLEXTYPE_TRENNER='_'
let NAVPROP_TRENNER = '_' // possibly overruled for TNT below

@@ -14,2 +13,4 @@ let VALUELIST_NAVPROP_PREFIX = '' // possibly overruled for TNT below

const translate = require('./annotations/genericTranslation.js');
const alerts = require('../base/alerts');
const { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const { setProp } = require('../base/model');

@@ -29,2 +30,4 @@

const { error, signal } = alerts(csn);
const options = glue.validateOptions(_options);

@@ -50,3 +53,3 @@

if(serviceCsn == undefined)
throw Error('No Service found in model');
signal(error`"No Service found in model"`);

@@ -112,4 +115,9 @@ let navigationProperties = [];

if(Schema._ec._children.length == 0)
throw Error('EntityContainer must contain at least one EntitySet');
signal(error`"EntityContainer must contain at least one EntitySet"`);
// Throw up if we have errors
if (hasErrors(model.messages)) {
throw new CompilationError(sortMessages(model.messages), model);
}
return edm

@@ -299,3 +307,3 @@

// boolean hasSstream : true if at least one element has @Core.MediaType assignment
function createProperties(parentCsn, prefix='')
function createProperties(parentCsn)
{

@@ -309,3 +317,3 @@ let props = [];

if(glue.isAssociationOrComposition(elementCsn))
if(glue.isAssociationOrComposition(elementCsn) && !elementCsn._ignore)
{

@@ -331,13 +339,4 @@ // Foreign keys are part of the generic elementCsn.elements property creation

}
else if(glue.isStructuredArtifact(elementCsn))
else if (!elementCsn._ignore)
{
// TODO: Makeover anonymous structured types
let anonymousComplexType = createAnonymousComplexType(elementCsn, prefix);
props.push(new Edm.Property(v, {
Name: elementCsn.name,
Type: fullQualified(anonymousComplexType.Name)
}, elementCsn));
}
else
{
// CDXCORE-CDXCORE-173

@@ -366,23 +365,2 @@ // V2: filter @Core.MediaType

function createAnonymousComplexType(element, prefix = '') {
// Add named ComplexType as auxiliary for anonyous inner struct
let name = element.name.replace(namespace, '')
// array of complex type not yet allowed
let typecsn = /*element.items ||*/ element;
let type = new Edm.ComplexType({
Name: prefix.replace(/\//g, COMPLEXTYPE_TRENNER) + name
}).append(
...(createProperties(typecsn, prefix + name + '/')[0])
);
// collect anonymous complex type in Auxiliaries array, they will
// be added to the Schema later in createComplexType...
(Schema.Auxillaries || Schema.set({
Auxillaries: []
}).Auxillaries).push(type)
return type
}
function createComplexType(structuredTypeCsn)

@@ -397,8 +375,2 @@ {

Schema.append(complexType);
// append new anonymous complex types to the schema (and overwrite existing ones...)
if (Schema.Auxillaries)
{
Schema.append(...Schema.Auxillaries)
}
}

@@ -526,5 +498,13 @@

// Add ReferentialConstraints if any
if(Object.keys(constraints.constraints).length > 0)
navigationProperty._edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
fromRole, toRole, constraints.constraints));
if(Object.keys(constraints.constraints).length > 0) {
// A managed composition is treated as association
if(navigationProperty._csn.type == 'cds.Composition' && (navigationProperty._csn.on || navigationProperty._csn.onCond)) {
navigationProperty._edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
toRole, fromRole, constraints.constraints));
}
else {
navigationProperty._edmAssociation.append(Edm.ReferentialConstraint.createV2(v,
fromRole, toRole, constraints.constraints));
}
}

@@ -556,3 +536,4 @@ Schema.append(navigationProperty._edmAssociation);

}
edm._defaultRefs = annoEdm._defaultRefs;
}
}

@@ -341,51 +341,2 @@ 'use strict'

setDefaultReferences()
{
// default refs are necessary only if edm has annotations
// existing EDMs render default refs always in V4
this._defaultRefs = [];
if(this.v4 || (this.v2 && this.hasAnnotations()))
{
let r = new Reference(this._v, { Uri : "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.xml" });
r.append(new Include(this._v, {Alias : "Core", Namespace : "Org.OData.Core.V1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Measures.V1.xml" });
r.append(new Include(this._v, {Alias : "Measures", Namespace : "Org.OData.Measures.V1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.xml" });
r.append(new Include(this._v, {Alias : "Capabilities", Namespace : "Org.OData.Capabilities.V1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Aggregation.V1.xml" });
r.append(new Include(this._v, {Alias : "Aggregation", Namespace : "Org.OData.Aggregation.V1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Validation.V1.xml" });
r.append(new Include(this._v, {Alias : "Validation", Namespace : "Org.OData.Validation.V1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://wiki.scn.sap.com/wiki/download/attachments/462030211/Analytics.xml?api=v2" });
r.append(new Include(this._v, {Alias : "Analytics", Namespace : "com.sap.vocabularies.Analytics.v1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://wiki.scn.sap.com/wiki/download/attachments/448470974/Common.xml?api=v2" });
r.append(new Include(this._v, {Alias : "Common", Namespace : "com.sap.vocabularies.Common.v1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://wiki.scn.sap.com/wiki/download/attachments/448470971/Communication.xml?api=v2" });
r.append(new Include(this._v, {Alias : "Communication", Namespace : "com.sap.vocabularies.Communication.v1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://wiki.scn.sap.com/wiki/download/attachments/448470968/UI.xml?api=v2" });
r.append(new Include(this._v, {Alias : "UI", Namespace : "com.sap.vocabularies.UI.v1"} ))
this._defaultRefs.push(r);
r = new Reference(this._v, { Uri : "https://wiki.scn.sap.com/wiki/download/attachments/496435637/PersonalData.xml?api=v2" });
r.append(new Include(this._v, {Alias : "PersonalData", Namespace : "com.sap.vocabularies.PersonalData.v1"} ))
this._defaultRefs.push(r);
}
}
get kind() { return 'edmx:Edmx' }

@@ -422,4 +373,2 @@

{
this.setDefaultReferences();
let schema = this._service._children[0];

@@ -446,3 +395,2 @@

{
this.setDefaultReferences();
return '<?xml version="1.0" encoding="utf-8"?>\n' + super.toXML('', what);

@@ -1010,8 +958,7 @@ }

// V4 referential constraints!
addReferentialConstraintNodes()
{
glue.forAll(this._referentialConstraints.constraints,
c => this.append(new ReferentialConstraint(this._v,
{ Property: c[0], ReferencedProperty: c[1] }
) ) );
c => this.append(new ReferentialConstraint(this._v, { Property: c[0], ReferencedProperty: c[1] } ) ) );
}

@@ -1102,8 +1049,16 @@

if(!assocCsn.target.isParamEntity) {
// remove all arget elements that are not key
glue.foreach(result.constraints, c => !(assocCsn.target.elements[c[1]] && assocCsn.target.elements[c[1]].key),
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys
let principalEntity = assocCsn.target;
if(assocCsn.type == 'cds.Composition') {
principalEntity = assocCsn._parent;
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal]
Object.keys(result.constraints).forEach(cn => {
result.constraints[cn] = [ result.constraints[cn][1], result.constraints[cn][0] ] } );
}
// Remove all arget elements that are not key in the principal entity
glue.foreach(result.constraints, c => !(principalEntity.key[c[1]]),
(c, cn) => { delete result.constraints[cn]; } );
}
}
// this is a managed association
// this is a managed association, a managed composition is treated as association
else

@@ -1115,3 +1070,2 @@ {

// FIXME: If path is something structured, perform a path resolution (or use augmented CSN)
if(!assocCsn.target.isParamEntity && assocCsn.keys) {

@@ -1157,2 +1111,3 @@ for(let fk of assocCsn.keys) {

// fill constraints object with [dependent, principal] pairs and collect all forward assocs for $self terms
function fillConstraints(arg, pos)

@@ -1204,4 +1159,4 @@ {

------------
undef => '*' // CDS default mapping
1 => 0..1
undef => '*' // CDS default mapping for associations
1 => 0..1 // CDS default mapping for compositions
n => '*'

@@ -1214,3 +1169,3 @@ n/a => 1 // not expressable

------------
undef => 0..1 // CDS default mapping
undef => 0..1 // CDS default mapping for associations
0..1 => 0..1

@@ -1220,3 +1175,3 @@ 1 => 0..1

1..1 => 1
0..m => '*'
0..m => '*' // CDS default mapping for compositions
m => '*'

@@ -1238,8 +1193,21 @@ 1..n => '*'

// set missing defaults
if(!csn.cardinality.src)
csn.cardinality.src = '*';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = 1;
// A managed composition is treated as an ordinary association
/*
if(csn.type == 'cds.Composition' && csn.on || csn.onCond) {
if(!csn.cardinality.src)
csn.cardinality.src = '1';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = '*';
}
else */
{
if(!csn.cardinality.src)
csn.cardinality.src = '*';
if(!csn.cardinality.min)
csn.cardinality.min = 0;
if(!csn.cardinality.max)
csn.cardinality.max = 1;
}

@@ -1504,3 +1472,2 @@ let srcCardinality = (csn.cardinality.src == 1) ? '0..1' : '*';

this.set( { _end: [] });
this._end.push(

@@ -1507,0 +1474,0 @@ new End(v, { Role: fromRole[0], Type: fromRole[1], Multiplicity: multiplicity[0] } ),

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

foreach(struct.elements, isAssociationOrComposition, element => {
if (element._ignore) return;
if(typeof element.target === "string") {

@@ -290,0 +291,0 @@ element.target = model.definitions[element.target];

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

//console.log(JSON.stringify(model,null,2))
} // function augment

@@ -87,0 +86,0 @@

@@ -323,3 +323,3 @@ // contains query relevant augmentor functions

let T = parent.transformers[prop]
T(cast,prop,castpath.concat(prop))
T(cast,prop,castpath)
E[prop]=cast[prop]

@@ -387,7 +387,21 @@ }

op:{val: "call", location},
func: {path:[{id:val.func}]}, // to-csn.js has TODO XSN: remove op: 'call', func is no path
func: {path:[{id:val.func, location}]}, // to-csn.js has TODO XSN: remove op: 'call', func is no path
location
};
if (val.args)
r.args = augmentExpression(val.args,path.concat("args"));
if (val.args) {
let aPath = path.concat("args")
if(typeof val.args == "object" && Array.isArray(val.args)==false) {
r.namedArgs=W.dmap(val.args, (N,O) => {
return newValue(O.val,aPath.concat(N), N, U.WILO_FULL, undefined, U.WILO_FIRST);
})
} else if(val.args[0] && val.args[0]==="*") { // count(*)
r.args = [{
val:"*",
literal: "token",
location: U.newLocation(aPath)
}]
} else {
r.args = augmentExpression(val.args,aPath);
}
}
return r;

@@ -440,5 +454,7 @@ } else if(val.hasOwnProperty("#")) {

function newValue(val, path, name=undefined, which=undefined, literal=undefined) {
function newValue(val, path, name=undefined, which=undefined, literal=undefined, nameWhich=undefined) {
if(which===undefined)
which=U.WILO_FULL;
if(nameWhich===undefined)
nameWhich=U.WILO_FULL;
if(val===undefined)

@@ -478,3 +494,3 @@ return undefined;

ret.name = {id:name};
U.setLocation(ret.name, path);
U.setLocation(ret.name, path, nameWhich);
}

@@ -481,0 +497,0 @@ U.setLocation(ret, path, which);

@@ -24,6 +24,3 @@ let W = require("./walker");

let location = U.newLocation(path.concat(name, iX), U.WILO_FULL)
return {
path: [ { id:X, location } ],
location
}
return asPlainPath(X,location);
})

@@ -33,2 +30,9 @@ node[name] = r;

function asPlainPath(id, location) {
return {
path: [ { id, location } ],
location
}
}
function modifyValue(node, name, path) {

@@ -138,12 +142,6 @@ let value = newValue(node[name], path.concat(name))

function augmentTypeRef(val,path) {
let location = U.newLocation(path, U.WILO_LAST)
let ids = [val];
let location = U.newLocation(path, U.WILO_FULL)
let newPath;
if(typeof val == 'string') {
ids = val.split(".");
if(ids[0]==="cds") // cds.XYZ
ids=[val]
newPath = ids.map(id => {
return {id, location};
});
newPath = [{id:val, location}]
} else if(val.ref) {

@@ -150,0 +148,0 @@ newPath = AQ.refAsPath(val.ref, path.concat("ref"))

// csn version functions
// checks if new-csn is requested vie the options of alreaday specified in the CSN
// default: old-style
function isNewCSN(csn,options) {

@@ -4,0 +6,0 @@ if(options && options.newCsn)

let W = require("./walker");
// Adapt augmented or compacted CSN 'model' in place, by turning those objects that may
// contain properties with user-defined names (e.g. 'elements') into dictionaries, i.e.
// by setting their prototype to 'null' and return the modified model.
/**
* Sets the prototype of all dictionary-object to null
* @param model CSN (old-style) to process
* @param options processing configuration:
* setAllMissingProtos - prepare the input for processing when setting all null-protos to Object.prototype
* @returns the modified CSN model
*/
function nullProtos(model, options={}) {

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

let W = require("./walker");
// Adapt augmented or compacted CSN 'model' in place, by turning those objects that may
// contain properties with user-defined names (e.g. 'elements') into dictionaries, i.e.
// by setting their prototype to 'null' and return the modified model.
/**
* Sets the prototype of all dictionary-object to null
* @param model CSN to process
* @param options processing configuration:
* setAllMissingProtos - prepare the input for processing when setting all null-protos to Object.prototype
* @returns the modified CSN model
*/
function nullProtos(model, options={}) {

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

function cbExtension(O) { // TODO improve as it is cloned
function cbExtension(O) {
return [

@@ -69,0 +72,0 @@ nullProto(O.elements, cbElement),

@@ -449,2 +449,4 @@ // Transform augmented CSN into compact "official" CSN

return extra( { "#" : node.symbol.id }, en );
else if (node.literal === 'token')
return node.val; // * in COUNT(*)
else // TODO XSN: literal 'hex'->'x'

@@ -572,6 +574,6 @@ return extra( { val: node.val, literal: (node.literal==='hex') ? 'x' : node.literal },

}
else if (!node._artifact || node._artifact.main) {
else if (!node._artifact || node._artifact._main) { // CQL or follow assoc
return extra( addExplicitAs( artifactRef( node, null ), node.name ), node );
}
else
else // if FROM ref is from USING, we might need an AS
return extra( addExplicitAs( artifactRef( node, null ), node.name, function(id) {

@@ -607,3 +609,4 @@ let name = node._artifact.name.absolute;

// FIXME: Currently toHana requires that an '_ignore' property on the elem is also visible on the column
if (elem._ignore) {
// Don't ignore virtual columns, let the renderer decide how to render that column
if (!elem.virtual && elem._ignore) {
col._ignore = true;

@@ -610,0 +613,0 @@ }

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

const backends = require('./backends');
const { signModel } = require('./model/modelUtils');

@@ -485,10 +486,10 @@ // The compiler version (taken from package.json)

if (!options.modelExtender || '$draft.cds' in sources)
return model;
return signModel(model);
let draft = options.modelExtender( model );
if (!draft)
return model;
return signModel(model);
if (typeof draft === 'string')
return compileSources( Object.assign( { '$draft.cds': draft }, fileContentDict ),
options );
return signModel(compileSources( Object.assign( { '$draft.cds': draft }, fileContentDict ),
options ));
handleMessages( draft );

@@ -495,0 +496,0 @@ throw new Error( 'Option `modelExtender` returns illegal value' );

'use strict'
const revealInternalProperties = require('./revealInternalProperties');
const util = require('util');
const crypto = require('crypto');
// Low-level utility functions to work with augmented CSN.

@@ -14,4 +18,4 @@

// Return true if 'type' is an association type
function isAssociation(type) {
// Return true if 'type' is an association or composition
function isAssocOrComposition(type) {
if (!type)

@@ -24,2 +28,11 @@ return type;

// Return true if 'type' is a composition type
function isComposition(type) {
if (!type)
return type;
if (type._artifact)
type = type._artifact.name;
return type.absolute == 'cds.Composition';
}
// Return true if 'elem' is an element (plus sanity check: must have _finalType)

@@ -403,5 +416,39 @@ function isElement(elem) {

// If model verification is enabled, add a SHA-256 hash digest of the raw augmented CSN of 'model.definitions'
// to 'model' and return 'model'.
// Otherwise, just return 'model' unchanged
function signModel(model) {
if (!model.options || !model.options.verifyModel) {
return model;
}
model.hash = hashModel(model);
return model;
}
// If model verification is enabled, verify the SHA-256 hash digest of the raw augmented CSN of
// 'model.definitions' against 'model.hash', throwing an error mentioning 'procesor' if it does
// not match
// Return 'model' unchanged.
function verifyModelSignature(model, processor) {
if (!model.options || !model.options.verifyModel) {
return model;
}
let hash = hashModel(model);
if (hash != model.hash) {
throw new Error(`This augmented CSN was not produced by the CDS compiler and cannot be processed by ${processor || 'the backend'}`);
}
return model;
}
// Return a SHA-256 hash digest of the raw augmented CSN of 'model.definitions'
function hashModel(model) {
let hash = crypto.createHash('sha256');
hash.update(util.inspect(revealInternalProperties({ definitions: model.definitions }), false, null));
return hash.digest('hex');
}
module.exports = {
isManagedAssociationElement,
isAssociation,
isAssocOrComposition,
isComposition,
isStructuredElement,

@@ -429,2 +476,4 @@ isArrayElement,

getElementDatabaseNameOf,
signModel,
verifyModelSignature,
}

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

const version = require('../../package.json').version;
const alerts = require('../base/alerts');

@@ -21,2 +22,4 @@ // Render the CSN model 'model' to CDS source text. One source is created per

function toCdsSource(model, options) {
const { signal, warning } = alerts(model);
// Merge options (arguments first, then model options)

@@ -51,3 +54,3 @@ options = mergeOptions(model.options, options);

if (sourceStr != '') {
result[plainNames ? uppercaseAndUnderscore(artifactName) : artifactName]
result[plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName]
= `${options.testMode ? '' : `// generated by cds-compiler version ${version} \n`}`

@@ -465,3 +468,3 @@ + renderNamespaceDeclaration(artifactName, env) + renderUsings(artifactName, env) + sourceStr;

if (source.as) {
result += ` as ${quoteId(source.as)}`;
result += ` as ${quoteOrUppercaseId(source.as)}`;
}

@@ -553,22 +556,29 @@ return result;

let result = renderAnnotationAssignments(col, env);
result += env.indent + (col.key ? 'key ' : '') + renderExpr(col, env, true);
let alias = col.as;
// HANA requires an alias for 'key' columns just for syntactical reasons
// FIXME: This will not complain for non-refs (but that should be checked in forHana)
if (options.forHana && col.key && !alias) {
alias = col.ref && col.ref[col.ref.length - 1];
}
// Explicit or implicit alias?
if (alias) {
result += ' as ' + quoteOrUppercaseId(alias);
}
// Explicit type provided for the view element?
if (col.cast) {
// Special case: Explicit association type is actually a redirect
if (col.cast.target) {
// Redirections are never flattened (don't exist in HANA)
result += ' : redirected to ' + renderAbsoluteNameWithQuotes(col.cast.target, env);
} else {
result += ' : ' + renderTypeReference(col.cast, env);
let leaf = col.as || col.ref && col.ref[col.ref.length-1];
// Render 'null as <alias>' only for database and if element is virtual
if(options.forHana && leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
result += env.indent + 'null as ' + quoteOrUppercaseId(leaf);
} else {
result += env.indent + (col.key ? 'key ' : '') + renderExpr(col, env, true);
let alias = col.as;
// HANA requires an alias for 'key' columns just for syntactical reasons
// FIXME: This will not complain for non-refs (but that should be checked in forHana)
// Explicit or implicit alias?
if (options.forHana && col.key && !alias) {
alias = col.ref && col.ref[col.ref.length-1];
}
if (alias) {
result += ' as ' + quoteOrUppercaseId(alias);
}
// Explicit type provided for the view element?
if (col.cast) {
// Special case: Explicit association type is actually a redirect
if (col.cast.target) {
// Redirections are never flattened (don't exist in HANA)
result += ' : redirected to ' + renderAbsoluteNameWithQuotes(col.cast.target, env);
} else {
result += ' : ' + renderTypeReference(col.cast, env);
}
}
}

@@ -600,2 +610,3 @@ return result;

}
env._artifact = art;
result += renderQuery(art.query, true, syntax, env);

@@ -647,7 +658,12 @@ result += ';\n';

if (isLeadingQuery && select.mixin) {
result += ' mixin {\n'
let elems = '';
for (let name in select.mixin) {
result += renderElement(name, select.mixin[name], childEnv);
if (!select.mixin[name]._ignore)
elems += renderElement(name, select.mixin[name], childEnv);
}
result += env.indent + '} into'
if (elems) {
result += ' mixin {\n';
result += elems;
result += env.indent + '} into';
}
}

@@ -759,3 +775,3 @@ result += select.distinct ? ' distinct' : '';

function renderParameter(parName, par, env) {
let result = renderAnnotationAssignments(par, env) + env.indent + quoteId(parName) + ' : ' + renderTypeReference(par, env);
let result = renderAnnotationAssignments(par, env) + env.indent + quoteOrUppercaseId(parName) + ' : ' + renderTypeReference(par, env);
result += renderNullability(par);

@@ -1344,4 +1360,2 @@ return result;

// Return an id 'id' with appropriate "-quotes
// FIXME: Should only quote where necessary (examining the id for magic characters and reserved
// keywords) - for now, simply quote everything
function quoteId(id) {

@@ -1358,20 +1372,29 @@ // Should only ever be called for real IDs (i.e. no dots inside)

}
// Returns quoted name if:
// 1. starts with a digit
// 2. it contains chars different than:
// - uppercase letters
// - lowercase letters
// - digits
// - underscore
if (id.match(/^\d/) || id.match(/\W/g))
return '"' + id.replace(/"/g, '""') + '"';
if (keywords.cdl.includes(id.toUpperCase()))
return '"' + id.replace(/"/g, '""') + '"';
if (options.toHana && keywords.sql92.includes(id.toUpperCase()))
return '"' + id.replace(/"/g, '""') + '"';
if (keywords.functions.includes(id.toUpperCase()))
return '"' + id.replace(/"/g, '""') + '"';
// Quote if required for CDL or (if rendering for toHana) always
if (requiresQuotingForCdl(id) || options.toHana) {
// Sanity check
if (plainNames) {
throw new Error('Not expecting quotes in plain mode');
}
return `"${id.replace(/"/g, '""')}"`;
}
return id;
}
// Returns true if 'id' requires quotes for CDL, i.e. if 'id'
// 1. starts with a digit
// 2. contains chars different than:
// - uppercase letters
// - lowercase letters
// - digits
// - underscore
// 3. is a CDL keyword or a CDL function without parentheses (CURRENT_*, SYSUUID, ...)
function requiresQuotingForCdl(id) {
return id.match(/^\d/)
|| id.match(/\W/g)
|| keywords.cdl.includes(id.toUpperCase())
|| keywords.cdl_functions.includes(id.toUpperCase());
}
// Return an absolute name 'absname', with '::' inserted if required by naming strategy 'hdbcds', quoted

@@ -1393,3 +1416,12 @@ // as if it was a single identifier (required only for native USINGs)

if (plainNames) {
return id.replace(/\./g, '_').toUpperCase();
// Sanity check
if (!options.toHana) {
throw new Error('Not expecting uppercase names in non-HANA mode');
}
let result = uppercaseAndUnderscore(id);
// Warn if colliding with HANA keyword
if (keywords.hana.includes(result)) {
signal(warning`The identifier "${id}" is a HANA keyword.`);
}
return result;
} else {

@@ -1404,9 +1436,13 @@ return quoteId(id);

function renderArtifactName(artifactName, env) {
return (plainNames) ? quoteOrUppercaseId(artifactName)
return (plainNames) ? uppercaseAndUnderscore(artifactName)
: env.namePrefix + quoteId(getLastPartOf(artifactName));
}
// replace . by _
// convert to uppercase
// For 'name', replace '.' by '_', convert to uppercase, and add double-quotes if
// required because of non-leading '$' (but do not consider leading '$', other special
// characters, or SQL keywords/functions - somewhat weird but this retains maximum
// compatibility with a future hdbtable-based solution and with sqlite, where non-leading
// '$' is legal again but nothing else)
function uppercaseAndUnderscore(name) {
// Always replace '.' by '_' and uppercase
return name.replace(/\./g, '_').toUpperCase();

@@ -1413,0 +1449,0 @@ }

@@ -54,3 +54,4 @@

// The final result in hdb-kind-specific form, without leading CREATE, without trailing newlines
// (note that the order here is relevant for transmission into 'resultObj.sql' below)
// (note that the order here is relevant for transmission into 'resultObj.sql' below and that
// the attribute names must be the HDI plugin names for --src hdi)
let resultObj = {

@@ -80,3 +81,3 @@ hdbtabletype: Object.create(null),

// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if toSql.src == 'sql'
// (relying on the order of dictionaries above)

@@ -88,16 +89,23 @@ // FIXME: Should consider inter-view dependencies, too

for (let name in resultObj[hdbKind]) {
let sourceString = resultObj[hdbKind][name];
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
if (options.toSql.dialect == 'hana' && hdbKind == 'hdbtable' && sourceString.startsWith('COLUMN ')) {
sourceString = sourceString.slice('COLUMN '.length);
if (options.toSql.src == 'sql') {
let sourceString = resultObj[hdbKind][name];
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
if (options.toSql.dialect == 'hana' && hdbKind == 'hdbtable' && sourceString.startsWith('COLUMN ')) {
sourceString = sourceString.slice('COLUMN '.length);
}
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
}
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
if (!options.testMode) {
resultObj[hdbKind][name] = sqlVersionLine + resultObj[hdbKind][name];
else {
if (!options.testMode) {
resultObj[hdbKind][name] = sqlVersionLine + resultObj[hdbKind][name];
}
}
}
if (options.toSql.src == 'sql') {
delete resultObj[hdbKind];
}
}
resultObj.sql = sql;
if (options.toSql.src == 'sql') {
resultObj.sql = sql;
}
return resultObj;

@@ -240,3 +248,3 @@

let result = '';
if (elm.target) {
if (elm.target && !elm._ignore) {
result += env.indent + 'MANY TO ';

@@ -461,7 +469,14 @@ if (elm.cardinality && elm.cardinality.max && (elm.cardinality.max == '*' || Number(elm.cardinality.max) > 1)) {

}
// FIXME: We may want to wrap a cast around 'col' if it has an explicit type?
let result = env.indent + renderExpr(col, env, true);
// Explicit or implicit alias?
if (col.as) {
result += ' AS ' + quoteSqlId(col.as);
let result = '';
let leaf = col.as || col.ref && col.ref[col.ref.length-1];
if(leaf && env._artifact.elements[leaf] && env._artifact.elements[leaf].virtual) {
// render a virtual column 'null as <alias>'
result += env.indent + 'NULL AS ' + quoteSqlId(col.as || leaf);
} else {
// FIXME: We may want to wrap a cast around 'col' if it has an explicit type?
result = env.indent + renderExpr(col, env, true);
// Explicit or implicit alias?
if (col.as) {
result += ' AS ' + quoteSqlId(col.as);
}
}

@@ -473,2 +488,3 @@ return result;

function renderView(artifactName, art, env) {
env._artifact = art;
let result = 'VIEW ' + quoteSqlId(absoluteCdsName(artifactName));

@@ -538,3 +554,4 @@ result += renderParameterDefinitions(artifactName, art.params);

result += '\n' +
(select.columns||['*']).filter(col => !(select.mixin || {})[firstPathStepId(col.ref)]) // No mixin columns
(select.columns||['*']).filter(s => !s._ignore)
.filter(col => !(select.mixin || {})[firstPathStepId(col.ref)]) // No mixin columns
.map(col => renderViewColumn(col, childEnv))

@@ -672,3 +689,4 @@ .filter(s => s != '')

'cds.Time': 'TIME',
'cds.DateTime': 'SECONDDATE',
// https://github.wdf.sap.corp/cdx/cds-compiler/issues/2758
'cds.DateTime': options.toSql.dialect === 'sqlite' ? 'TIMESTAMP' : 'SECONDDATE',
'cds.Timestamp': 'TIMESTAMP',

@@ -693,3 +711,4 @@ 'cds.Boolean': 'BOOLEAN',

'cds.LocalTime': 'TIME',
'cds.UTCDateTime': 'SECONDDATE',
//https://github.wdf.sap.corp/cdx/cds-compiler/issues/2758
'cds.UTCDateTime': options.toSql.dialect === 'sqlite' ? 'TIMESTAMP' : 'SECONDDATE',
'cds.UTCTimestamp': 'TIMESTAMP',

@@ -950,22 +969,26 @@ };

// If 'options.toSql.names' is 'plain'
// - replace '.' or '::' by '_' and convert to uppercase
// - replace '.' or '::' by '_'
// else if 'options.toSql.names' is 'quoted'
// - replace '::' by '.'
// Complain about names that collide with known SQL keywords or functions
function quoteSqlId(name) {
if (options.toSql.dialect === 'sqlite' && keywords.sqlite.includes(name.toUpperCase())){
if (options.toSql.dialect === 'sqlite' && keywords.sqlite.includes(name.toUpperCase())) {
// Sanity check
if (options.toSql.names != 'plain') {
throw new Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
}
signal(warning`The identifier "${name}" is a SQLite keyword.`);
}
}
if (options.toSql.names == 'plain') {
if (options.toSql.dialect == 'hana') {
if (keywords.hana.includes(name.toUpperCase())) {
signal(warning`The identifier "${name}" is a HANA keyword.`);
}
}
name = name.replace(/(\.|::)/g, '_');
if (name.match(/\W/g)
|| name.match(/^\d/) || name.match(/^_/)
|| keywords.sql92.includes(name.toUpperCase())
|| keywords.functions.includes(name.toUpperCase()))
return `"${name.replace(/"/g, '""').toUpperCase()}"`
else
return name;
return name;
}
else if (options.toSql.names == 'quoted') {
name = name.replace(/::/g, '.');
}
}
return `"${name.replace(/"/g, '""')}"`;

@@ -972,0 +995,0 @@ }

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

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

@@ -139,3 +139,3 @@ foreachPath, hasBoolAnnotation, getElementDatabaseNameOf, getArtifactDatabaseNameOf } = require('../model/modelUtils');

// Generate foreign key elements for managed associations
if (isManagedAssociationElement(member)) {
if (isManagedAssociationElement(member) && !member._ignore) {
// Flatten foreign keys (replacing foreign keys that are managed associations by their respective foreign keys)

@@ -190,3 +190,3 @@ member.foreignKeys = flattenForeignKeys(member.foreignKeys);

// Check for valid foreign keys
if (isAssociation(elem.type)) {
if (isAssocOrComposition(elem.type)) {
checkForeignKeys(elem);

@@ -231,3 +231,3 @@ }

let containedElem = containedArtifact.elements[containedElemName];
if (isAssociation(containedElem.type)) {
if (isAssocOrComposition(containedElem.type)) {
// Sanity check

@@ -276,3 +276,3 @@ if (!containedElem.target || !containedElem.target._artifact) {

if (artifact._service) {
if (isAssociation(member.type)) {
if (isAssocOrComposition(member.type)) {
// Check that exposed associations do not point to non-exposed targets

@@ -675,3 +675,3 @@ checkExposedAssoc(artifact, member);

else {
renameAnnotation(node, name, '@Core.Immutable');
renameAnnotation(node, name, '@Core.Computed');
}

@@ -678,0 +678,0 @@ }

const { setProp, forEachDefinition, forEachGeneric, forEachMemberRecursively } = require('../base/model');
const { CompilationError, hasErrors, sortMessages } = require('../base/messages');
const transformUtils = require('./transformUtils');
const { isAssociation } = require('../model/modelUtils');
const { isAssocOrComposition } = require('../model/modelUtils');
const alerts = require('../base/alerts');
function isValidAssocOrComposition(elem) {
return isAssocOrComposition(elem.type) && !elem._ignore
}
function preprocessModel(model, tntFlavor) {

@@ -57,3 +61,3 @@ // e.g. signal(error`...`, <artifact>.location);

// 2. check if the target of the association is part of the current service
if (!art.projection && isAssociation(member.type)) {
if (!art.projection && isValidAssocOrComposition(member)) {
if (tntFlavor) { /* ugly tnt magic -> to be removed */

@@ -80,3 +84,3 @@ if (member.target && member.target._artifact && member.target._artifact._service != art._service)

// association redirecting
if (elem.type && isAssociation(elem.type)) {
if (isValidAssocOrComposition(elem)) {
// try to find representation of the association target in the current service

@@ -83,0 +87,0 @@ let targetFromCurrectService = Object.keys(parent.artifacts).filter(artName => parent.artifacts[artName].projection)

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

forEachMemberRecursively } = require('../base/model');
const { addStringAnnotationTo, printableName,
const { addStringAnnotationTo, printableName, isComposition, addBoolAnnotationTo,
copyAnnotations, isStructuredElement, hasBoolAnnotation } = require('../model/modelUtils');

@@ -16,3 +16,3 @@ const alerts = require('../base/alerts');

function getTransformers(model, pathDelimiter) {
const { error, signal } = alerts(model);
const { error, warning, signal } = alerts(model);
let options = model.options;

@@ -404,3 +404,3 @@

function checkExposedAssoc(artifact, association) {
if (association.target && association.target._artifact && association.target._artifact._service != artifact._service)
if (!association._ignore && association.target && association.target._artifact && association.target._artifact._service != artifact._service)
signal(error`Association "${artifact.name.absolute}.${association.name.id}" must be redirected: Target "${association.target._artifact.name.absolute}" is not exposed by service "${artifact._service.name.absolute}"`, association.location);

@@ -476,6 +476,9 @@ }

let implicitlyRedirected = (exposedByProjection[member.target._artifact.name.absolute] || []).filter(p => p._service == artifact._service);
if (implicitlyRedirected.length == 0 && hasBoolAnnotation(member.target._artifact, '@cds.autoexpose')) {
if (implicitlyRedirected.length == 0 &&
(hasBoolAnnotation(member.target._artifact, '@cds.autoexpose') || isComposition(member.type))) {
let projectionId = member.target._artifact.name.absolute.replace(/\./g, '_').replace(/::/g, '__');
let projection = createExposingProjection(member.target._artifact, projectionId, artifact._service, member.location, 'auto-exposure');
if (projection) {
// Mark projection as @autoexposed
addBoolAnnotationTo('@cds.autoexposed', true, projection);
// Take the just-created projection as the (only!) projection exposing the target in this service

@@ -503,5 +506,5 @@ exposedByProjection[member.target._artifact.name.absolute] = (exposedByProjection[member.target._artifact.name.absolute] || []).filter(p => p._service != artifact._service);

let implicitlyRedirected = (exposedByProjection[assoc.target._artifact.name.absolute] || []).filter(p => p._service == artifact._service);
// Complain if no implicit redirection or if not unique
// autoexclude if no implicit redirection or complain if not unique
if (implicitlyRedirected.length == 0) {
signal(error`Association "${artifactName}.${assocName}" cannot be implicitly redirected: Target "${assoc.target._artifact.name.absolute}" is not exposed in service "${artifact._service.name.absolute}" by any projection`, assoc.location);
autoExcludeAssociation(assoc, assocName, artifact, artifactName);
return;

@@ -522,2 +525,38 @@ } else if (implicitlyRedirected.length > 1) {

}
// Autoexclude element 'assoc' with name 'assocName' from 'artifact' with name 'artifactName' implicitly
// by setting the '_ignore' property and put it in the exclude object of the projection.
// If already foreign key fields have been generated for 'assoc', they are set to be ignored too.
// Complain if assoc is requested explicitly by name (not just by '*')
function autoExcludeAssociation(assoc, assocName, artifact, artifactName) {
// Check if assoc is explicitly requested
if (artifact.query && artifact.query.columns) {
for (const col of artifact.query.columns)
if (col.name && col.name.id == assocName) {
signal(error`Association "${artifactName}.${assocName}" is explicitly requested but target "${assoc.target._artifact.name.absolute}" is not exposed in service "${artifact._service.name.absolute}" by any projection`, col.location);
return;
}
} else if (!artifact.query) {
// This is an explicitly defined entity in the service, so auto-exclude must not remove the assoc. Handle it
// as an error like above
signal(error`Association "${artifactName}.${assocName}" is explicitly requested but target "${assoc.target._artifact.name.absolute}" is not exposed in service "${artifact._service.name.absolute}" by any projection`, assoc.location);
return;
}
assoc._ignore = true;
// Ignore also all prevoisly generated foreign keys inserted in createForeignKeyElement()
for (let k of Object.keys(assoc.foreignKeys)) {
let name = assoc.foreignKeys[k].generatedFieldName;
let fKey = assoc._parent.elements[name];
if (fKey) fKey._ignore = true;
}
// Set up or extend the excluding property of the corresponding query
if (artifact.query) {
if (!artifact.query.exclude)
artifact.query.exclude = Object.create(null);
artifact.query.exclude[assocName] = assoc;
}
signal(warning`Association "${assocName}" of "${artifactName}" is autoexcluded since target "${assoc.target._artifact.name.absolute}" is not exposed in service "${artifact._service.name.absolute}" by any projection`, assoc.location);
//signal(warning`Association "${artifactName}.${assocName}" cannot be implicitly redirected: Target "${assoc.target._artifact.name.absolute}" is not exposed in service "${artifact._service.name.absolute}" by any projection`, assoc.location);
return;
}
}

@@ -998,3 +1037,3 @@

for (let prop in elem) {
if (['type', 'key', 'notNull', 'length', 'precision', 'scale', 'localized', 'onCond', 'foreignKeys', 'location', 'cardinality'].includes(prop)
if (['type', 'key', 'notNull', 'length', 'precision', 'scale', 'localized', 'onCond', 'foreignKeys', 'location', 'cardinality', 'items'].includes(prop)
|| prop.startsWith('@')) {

@@ -1001,0 +1040,0 @@ result[prop] = elem[prop];

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

//if(!(mixinJoins && env.location !== 'from') && !childQat.QA)
if((env.fullJoins || env.location === 'from') && !childQat.$QA)

@@ -530,4 +529,7 @@ childQat.$QA = createQA(env, art.target._artifact, childQat._namedArgs);

// this will substitute multiple backlink conditions ($self = ... AND $self = ...AND ...)
if(expr.op)
return { op: { val: expr.op.val }, args: expr.args.map(cloneOnCondition) };
if(expr.op) {
let x = clone(expr);
x.args = expr.args.map(cloneOnCondition);
return x;
}

@@ -551,3 +553,3 @@ // If this is a regular path, rewrite it

if(assocSourceQA.mixin)
if(rhs.mixin)
{

@@ -642,4 +644,5 @@ if(head.id === '$projection')

{
if(alias === undefined)
alias = artifact.name.id;
if(alias === undefined) {
alias = artifact.name.absolute.split('.').pop();
}

@@ -654,3 +657,3 @@ let node = constructPathNode([ { id: artifact.name.absolute, _artifact: artifact, namedArgs } ], alias);

{
QA.name.id += '_$' + env.aliasCount++;
QA.name.id += '_' + env.aliasCount++;
QA.numberedAlias = true;

@@ -1056,2 +1059,4 @@ }

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

@@ -1058,0 +1063,0 @@ if(qat._namedArgs)

{
"name": "@sap/cds-compiler",
"version": "1.8.1",
"version": "1.10.0",
"dependencies": {

@@ -5,0 +5,0 @@ "antlr4": {

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

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

@@ -35,3 +35,3 @@ # Getting started

The exit code is similar to [`grep` and other commands](http://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux):
The exit code of the process is:

@@ -38,0 +38,0 @@ * `0`: successful compilation

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

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

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

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