Socket
Socket
Sign inDemoInstall

@sap/cds-compiler

Package Overview
Dependencies
Maintainers
1
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-compiler - npm Package Compare versions

Comparing version 1.46.6 to 1.49.0

lib/checks/csn/selectItems.js

58

CHANGELOG.md

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

## Version 1.49.0 - 2021-01-29
### Added
- to.hdi/sql:
+ Updated the list of reserved keywords for HANA and SQLite
+ Use "smart quoting" for naming mode "plain" - automatically quote reserved keywords
- to.hdi.migration:
+ Supports various kinds of entity changes: entity addition/deletion/change (the latter including element additions/deletions/type changes).
+ Provides option to render any element type change as `ALTER TABLE DROP` to prevent deployment issues due to incompatible data
(default for length reductions or association/composition changes).
- to.cdl: Smart artifact references are now rendered explicitly via `:` notation
## Changed
- OData/EDMX:
Change the `EntityType` precedence of the OData term definition `AppliesTo=` attribute. If `AppliesTo` contains
both `EntityType` and `EntitySet`, the annotation was assigned to the entity type. Extending an
`AppliesTo=[EntitySet]` with `EntityType` would be OData compliant but incompatible for clients
which still expect the annotation at the set and do not perform the full lookup.
With this change, `EntitySet` and `EntityType` are treated individually, effectively annotating the type and
(if available) the set. This fixes both extendability and client behavior.
## Fixed
- Structured foreign key and forward association reference paths used in ON condition definitions
are now translatable into the correct short form ON condition paths in Association to Join translation.
- to.hdbcds: Aliased mixin-associations are now handled correctly
## Version 1.48.0 - 2021-01-15
### Changed
- to.hdbcds/hdi/sql: Reject using associations or compositions in query elements starting with `$self` or `$projection`.
- OData: Update vocabularies 'Common', 'PersonalData', 'UI'.
### Fixed
- Using a hex literal like `x'D028'` (in a CSN input) could lead to an error.
- for.odata:
+ Fix a bug in constraint calculation if principal has no primary keys.
+ Don't overwrite user defined `@Core.Computed` annotation.
- to.hdi/sql/hdbcds: Fixed a bug during processing of skipped/otherwise not db-relevant artifacts.
## Version 1.47.0 - 2020-12-11
### Changed
- Update vocabularies 'Aggregation', 'Common'
### Fixed
- to.hdbcds/hdi/sql:
+ Types are not rendered anymore for SAP HANA in quoted mode.
+ Aliases are now respected when resolving $self
+ Association clones are now pre-pended with three underscores (`_`) instead of two
to prevent shadowing of context names or usings
## Version 1.46.6 - 2020-12-01

@@ -11,0 +69,0 @@

2

doc/ApiMigration.md

@@ -203,3 +203,3 @@ # API Migration

// or 'joins' (replace associations by joins)
// toSql.src : if true, generate SQL DDL source files (default)
// toSql.src : if 'sql', generate SQL DDL source files (default)
// toSql.csn : if true, generate the transformed CSN model

@@ -206,0 +206,0 @@ // }

@@ -199,36 +199,2 @@ /** @module API */

/**
* BETA: Return all the bits and pieces needed for schema evolution with SQL
*
* @param {CSN.Model} csn A clean input CSN representing the desired "after"
* @param {CSN.Model} sourceState A HANA transformed CSN representing the "before"
* @param {hdiOptions} [options={}] Options
* @returns {object} - createStatements: A dictionary of SQL CREATE statements - but only the subset of added things to the sourceState!
* - stateChanges: A dictionary of Arrays of SQL ALTER statements, where the key is the filename of the corresponding
* `.hdbtable` artifact. Ideally, the SQL statements describe the steps needed to get from sourceState to targetState.
* - targetState: A CSN representing what would exist on the db, if the returned `createStatements` are executed on top of the sourceState (and deletions are handled somehow).
*/
function mtx(csn, sourceState, options = {}) {
if (!isBetaEnabled(options, 'to.sql.mtx'))
throw new Error('"to.sql.mtx" is only available with beta!');
const internalOptions = prepareOptions.to.sql(options);
cloneCsnMessages(csn, options, internalOptions);
// get CSN output
internalOptions.toSql.csn = true;
const targetState = backends.toSqlWithCsn(csn, internalOptions).csn;
const diff = compareModels(sourceState, targetState, true);
// Make it pass the SQL rendering
internalOptions.forHana = true;
const result = toSqlDdl(diff, internalOptions);
return {
createStatements: result.sql,
stateChanges: result.alterTable,
targetState,
};
}
sql.mtx = mtx;
/**
* Process the given CSN into HDI artifacts.

@@ -249,31 +215,81 @@ *

/**
* BETA: Return all the bits and pieces needed for .hdbmigrationtable
* Return all changes in artifacts between two given models.
* Note: Only supports changes in entities (not views etc.) compiled/rendered as HANA-CSN/SQL.
*
* @param {CSN.Model} csn A clean input CSN representing the desired "after"
* @param {CSN.Model} sourceState A HANA transformed CSN representing the "before"
* @param {CSN.Model} csn A clean input CSN representing the desired "after-image"
* @param {CSN.Model} beforeImage A HANA-transformed CSN representing the "before-image", or null in case no such image
* is known, i.e. for the very first migration step
* @param {hdiOptions} [options={}] Options
* @returns {object} - hdiArtifacts: The same result as it would be returned by `to.hdi`.
* - stateChanges: A dictionary of Arrays of SQL ALTER statements as needed by `.hdbtablemigration`, where the key is the filename of the corresponding
* `.hdbtable` artifact. Ideally, the SQL statements describe the steps needed to get from sourceState to targetState.
* - targetState: A CSN representing what would exist on the db, if the returned `hdiArtifacts` are deployed.
* @returns {object} - afterImage: The desired after-image in HANA-CSN format
* - definitions: An array of objects with all artifacts in the after-image. Each object specifies
* the artifact filename, the suffix, and the corresponding SQL statement to create
* the artifact.
* - deletions: An array of objects with the deleted artifacts. Each object specifies the artifact
* filename and the suffix.
* - migrations: An array of objects with the changed (migrated) artifacts. Each object specifies the
* artifact filename, the suffix, and the changeset (an array of changes, each specifying
* whether it incurs potential data loss, and its respective SQL statement(s), with
* multiple statements concatenated as a multi-line string in case the change e.g.
* consists of a column drop and add).
*/
function hdiMigration(csn, sourceState, options = {}) {
if (!isBetaEnabled(options, 'to.hdi.migration'))
throw new Error('"to.hdi.migration" is only available with beta!');
function hdiMigration(csn, beforeImage, options = {}) {
const internalOptions = prepareOptions.to.hdi(options);
internalOptions.toSql.csn = true;
const internalOptions = prepareOptions.to.hdi(options);
// Prepare after-image.
cloneCsnMessages(csn, options, internalOptions);
// get CSN output
internalOptions.toSql.csn = true;
const targetState = backends.toSqlWithCsn(csn, internalOptions).csn;
const diff = compareModels(sourceState, targetState);
// Make it pass the SQL rendering
internalOptions.forHana = true;
const { alterTable, ...hdiArtifacts } = toSqlDdl(diff, internalOptions);
delete targetState.extensions;
const afterImage = backends.toSqlWithCsn(csn, internalOptions).csn;
// Compare both images.
const diff = compareModels(beforeImage || afterImage, afterImage);
// Convert the diff to SQL.
internalOptions.forHana = true; // Make it pass the SQL rendering
const { deletions, migrations, ...hdbkinds } = toSqlDdl(diff, internalOptions);
return {
hdiArtifacts: flattenResultStructure(hdiArtifacts),
stateChanges: alterTable,
targetState,
afterImage,
definitions: createDefinitions(),
deletions: createDeletions(),
migrations: createMigrations(),
};
/**
* From the given HDI artifacts, create the the correct result structure.
*
* @returns {object[]} Array of objects, each having: name, suffix and sql
*/
function createDefinitions() {
const result = [];
for (const [ kind, artifacts ] of Object.entries(hdbkinds)) {
const suffix = `.${ kind }`;
for (const [ name, sqlStatement ] of Object.entries(artifacts))
result.push({ name, suffix, sql: sqlStatement });
}
return result;
}
/**
* From the given deletions, create the correct result structure.
*
* @returns {object[]} Array of objects, each having: name and suffix - only .hdbtable as suffix for now
*/
function createDeletions() {
const result = [];
for (const [ name ] of Object.entries(deletions))
result.push({ name, suffix: '.hdbtable' });
return result;
}
/**
* From the given migrations, create the correct result structure.
*
* @returns {object[]} Array of objects, each having: name, suffix and changeset.
*/
function createMigrations() {
const result = [];
for (const [ name, changeset ] of Object.entries(migrations))
result.push({ name, suffix: '.hdbmigrationtable', changeset });
return result;
}
}

@@ -484,6 +500,5 @@

const result = {};
for (const fileType of Object.keys(toProcess)) {
for (const [ fileType, artifacts ] of Object.entries(toProcess)) {
if (fileType === 'messages')
continue;
const artifacts = toProcess[fileType];
for (const filename of Object.keys(artifacts))

@@ -490,0 +505,0 @@ result[`${ filename }.${ fileType }`] = artifacts[filename];

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

'sqlMapping',
'sqlChangeMode',
'joinfk',

@@ -79,2 +80,3 @@ 'magicVars',

localizedLanguageFallback: generateStringValidator([ 'none', 'coalesce' ]),
sqlChangeMode: generateStringValidator([ 'alter', 'drop' ]),
}, customValidators ),

@@ -81,0 +83,0 @@ combinationValidators);

@@ -30,3 +30,4 @@ module.exports = {

],
// CDL functions, used for automatic quoting in 'toCdl' renderer
// CDL functions, used for automatic quoting in 'toCdl' renderer,
// only relevant for element references of path length 1.
cdl_functions: [

@@ -48,2 +49,3 @@ 'CURRENT_CONNECTION',

// Taken from http://www.sqlite.org/draft/lang_keywords.html
// Better use keywords in tool/mkkeywordhash.c of a sqlite distribution.
sqlite: [

@@ -186,5 +188,17 @@ 'ABORT',

'WITHOUT',
'ALWAYS',
'EXCLUDE',
'FIRST',
'GENERATED',
'GROUPS',
'LAST',
'NULLS',
'OTHERS',
'TIES',
],
// 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
// Better use keywords in ptime/query/parser/syntax/qp_keyword.cc minus those
// in rule unreserved_keyword_column (=…_common - "CONSTRAINT") in
// ptime/query/parser/syntax/qp_gram.y of the HANA sources.
hana: [

@@ -275,3 +289,392 @@ 'ALL',

'WITH',
]
'ABAP_CHAR',
'ABAP_DATE',
'ABAP_DECFLOAT16',
'ABAP_DECFLOAT34',
'ABAP_FLOAT',
'ABAP_HEX',
'ABAP_INT',
'ABAP_INT1',
'ABAP_INT2',
'ABAP_NUM',
'ABAP_PACKED',
'ABAP_STRING',
'ABAP_TIME',
'ABAP_XSTRING',
'ABAPITAB',
'ABAPSTRUCT',
'ADD_DAYS',
'ADD_MONTHS',
'ADD_SECONDS',
'ADD_YEARS',
'ADOPT',
'ANALYTIC',
'ANY',
'APPLY_FILTER',
'ARRAY',
'ARRAY_AGG',
'AT',
'AUTHORIZATION',
'AUTO',
'AVG',
'BASIC',
'BETWEEN',
'BIGINT',
'BINARY',
'BIND_AS_PARAMETER',
'BIND_AS_VALUE',
'BIND_BIGINT',
'BIND_CHAR',
'BIND_DECIMAL',
'BIND_DOUBLE',
'BIND_NCHAR',
'BIND_REAL',
'BLOB',
'BOOLEAN',
'BOUNDARY',
'BREAKUP',
'BULK',
'BY',
'CAST',
'CE_CALC',
'CE_JOIN',
'CE_PROJECTION',
'CHARACTER',
'CLOB',
'COALESCE',
'COLLATE',
'CONCAT',
'CONSTANT',
'CONSTRAINT',
'COUNT',
'CS_DATE',
'CS_DAYDATE',
'CS_DECIMAL_FLOAT',
'CS_DOUBLE',
'CS_FIXED',
'CS_FIXEDSTRING',
'CS_FLOAT',
'CS_GEOMETRY',
'CS_INT',
'CS_LONGDATE',
'CS_POINT',
'CS_POINTZ',
'CS_RAW',
'CS_SDFLOAT',
'CS_SECONDDATE',
'CS_SECONDTIME',
'CS_SHORTTEXT',
'CS_STRING',
'CS_TEXT',
'CS_TEXT_AE',
'CS_TIME',
'CUME_DIST',
'CURDATE',
'CURRENT_DATABASE',
'CURTIME',
'CURVE',
'DATABASE',
'DATASET',
'DATE',
'DATETIME',
'DATS_EXTRACT',
'DAYDATE',
'DAYOFMONTH',
'DAYOFWEEK',
'DAYS_BETWEEN',
'DDIC_ACCP',
'DDIC_CDAY',
'DDIC_CHAR',
'DDIC_CLNT',
'DDIC_CUKY',
'DDIC_CURR',
'DDIC_D16D',
'DDIC_D16R',
'DDIC_D16S',
'DDIC_D34D',
'DDIC_D34R',
'DDIC_D34S',
'DDIC_DATS',
'DDIC_DAY',
'DDIC_DEC',
'DDIC_FLTP',
'DDIC_GUID',
'DDIC_INT1',
'DDIC_INT2',
'DDIC_INT4',
'DDIC_INT8',
'DDIC_LANG',
'DDIC_LCHR',
'DDIC_LRAW',
'DDIC_MIN',
'DDIC_MON',
'DDIC_NUMC',
'DDIC_PREC',
'DDIC_QUAN',
'DDIC_RAW',
'DDIC_RSTR',
'DDIC_SEC',
'DDIC_SRST',
'DDIC_SSTR',
'DDIC_STRG',
'DDIC_STXT',
'DDIC_TEXT',
'DDIC_TIMS',
'DDIC_UNIT',
'DDIC_UTCL',
'DDIC_UTCM',
'DDIC_UTCS',
'DDIC_VARC',
'DDIC_WEEK',
'DEC',
'DECIMAL',
'DENSE_RANK',
'DEPTH',
'DISTANCE',
'DOUBLE',
'DW_OPTIMIZED',
'EMPTY',
'EXISTS',
'EXTRACT',
'FILTER',
'FIRST_VALUE',
'FLATTEN',
'FLOAT',
'FORCE_FIRST_PASSWORD_CHANGE',
'FREESTYLESEARCHATTRIBUTE',
'GET_NUM_SERVERS',
'GREATEST',
'GROUPING',
'GROUPING_FILTER',
'GROUPING_ID',
'HASANYPRIVILEGES',
'HASSYSTEMPRIVILEGE',
'HEXTOBIN',
'HIERARCHY',
'HIERARCHY_ANCESTORS',
'HIERARCHY_ANCESTORS_AGGREGATE',
'HIERARCHY_DESCENDANTS',
'HIERARCHY_DESCENDANTS_AGGREGATE',
'HIERARCHY_LEVELED',
'HIERARCHY_SIBLINGS',
'HIERARCHY_SPANTREE',
'HIERARCHY_TEMPORAL',
'HILBERT',
'HOST',
'HOUR',
'IFNULL',
'INSTR',
'INT',
'INTEGER',
'INTERNAL',
'IS_EMPTY',
'ISAUTHORIZED',
'ISTOTAL',
'JSON_QUERY',
'JSON_TABLE',
'JSON_VALUE',
'LAG',
'LAST_DAY',
'LAST_VALUE',
'LATERAL',
'LAYOUT',
'LEAD',
'LEAST',
'LEAVES',
'LENGTH',
'LENGTHB',
'LEVELS',
'LOCATE',
'LOCATE_REGEXPR',
'LONGDATE',
'LPAD',
'LTRIM',
'MAP',
'MAP_MERGE',
'MAP_REDUCE',
'MAX',
'MEASURES',
'MIN',
'MINUTE',
'MONTH',
'MULTIPARENT',
'NCLOB',
'NEXT_DAY',
'NO',
'NOT',
'NOW',
'NTEXT',
'NTH_VALUE',
'NTILE',
'NULLIF',
'NUMBER',
'NUMERIC',
'NVARCHAR',
'OBJECT',
'OCCURRENCES_REGEXPR',
'OF',
'OLYMP',
'OPENCYPHER_TABLE',
'ORDINALITY',
'ORPHAN',
'OVER',
'PERCENT_RANK',
'PERCENTILE_CONT',
'PERCENTILE_DISC',
'PLAIN',
'PRODUCT',
'RANGE',
'RANK',
'RAW',
'RDICT',
'REAL',
'RECORD_COMMIT_TIMESTAMP',
'RECORD_COUNT',
'RECORD_ID',
'RECURSIVE',
'REMOTE_EXECUTE_QUERY',
'REPLACE',
'REPLACE_REGEXPR',
'ROUND',
'ROUNDROBIN',
'ROW',
'ROW_NUMBER',
'RPAD',
'RTREE',
'RTRIM',
'SCORE',
'SECOND',
'SECONDDATE',
'SECONDS_BETWEEN',
'SECONDTIME',
'SERIES_ELEMENT_TO_PERIOD',
'SERIES_PERIOD_TO_ELEMENT',
'SERIES_ROUND',
'SESSION_CONTEXT',
'SIBLING',
'SMALLDECIMAL',
'SMALLINT',
'SOME',
'ST_ALPHASHAPEAGGR',
'ST_ALPHASHAPEAREAAGGR',
'ST_ALPHASHAPEEDGEAGGR',
'ST_ASSVGAGGR',
'ST_CIRCULARSTRING',
'ST_CLUSTERCELL',
'ST_CLUSTERCENTROID',
'ST_CLUSTERCONVEXHULL',
'ST_CLUSTERCOREPOINT',
'ST_CLUSTERENVELOPE',
'ST_COLLECTAGGR',
'ST_CONCAVEHULLAGGR',
'ST_CONVEXHULLAGGR',
'ST_DISK_LOB',
'ST_ENVELOPEAGGR',
'ST_FROMTEXT',
'ST_GEOMETRY',
'ST_GEOMETRYCOLLECTION',
'ST_GEOMFROMEWKB',
'ST_GEOMFROMEWKT',
'ST_GEOMFROMGEOHASH',
'ST_GEOMFROMGEOJSON',
'ST_GEOMFROMTEXT',
'ST_GEOMFROMWKB',
'ST_GEOMFROMWKT',
'ST_INTERSECTIONAGGR',
'ST_LINESTRING',
'ST_MAKELINE',
'ST_MULTILINESTRING',
'ST_MULTIPOINT',
'ST_MULTIPOLYGON',
'ST_POINT',
'ST_POINTFROMGEOHASH',
'ST_POINTFROMWKB',
'ST_POINTM',
'ST_POINTZ',
'ST_POINTZM',
'ST_POLYGON',
'ST_RECTANGLE',
'ST_UNIONAGGR',
'STDDEV',
'STRING',
'STRING_AGG',
'SUBSTR',
'SUBSTR_AFTER',
'SUBSTR_BEFORE',
'SUBSTR_REGEXPR',
'SUBSTRING',
'SUBSTRING_REGEXPR',
'SUM',
'SYSTEM_TIME',
'TABLE',
'TABLES',
'TARGET',
'TEMPORARY',
'TEXT',
'THEN',
'THREAD',
'TIME',
'TIMELINE',
'TIMESTAMP',
'TIMEZONE',
'TIMS_EXTRACT',
'TINYINT',
'TM_CATEGORIZE_KNN',
'TM_GET_RELATED_DOCUMENTS',
'TM_GET_RELATED_TERMS',
'TM_GET_RELEVANT_DOCUMENTS',
'TM_GET_RELEVANT_TERMS',
'TM_GET_SUGGESTED_TERMS',
'TO_BIGINT',
'TO_BINARY',
'TO_BLOB',
'TO_CHAR',
'TO_CLOB',
'TO_DATE',
'TO_DECIMAL',
'TO_DOUBLE',
'TO_INT',
'TO_INTEGER',
'TO_JSON_BOOLEAN',
'TO_JSON_NUMBER',
'TO_NCHAR',
'TO_NCLOB',
'TO_NUMBER',
'TO_NVARCHAR',
'TO_REAL',
'TO_SECONDDATE',
'TO_SMALLDECIMAL',
'TO_SMALLINT',
'TO_TIME',
'TO_TIMESTAMP',
'TO_TINYINT',
'TO_VARBINARY',
'TO_VARCHAR',
'TOKEN',
'TOOLOPTION',
'TOPK',
'TOTAL',
'TRACE',
'TRACEPROFILE',
'TRACES',
'TRANSACTION',
'TREE',
'TRIGGER_UPDATE_COLUMN',
'TRIM',
'UNNEST',
'USER',
'VAR',
'VARBINARY',
'VARCHAR',
'VARCHAR1',
'VARCHAR2',
'VARCHAR3',
'VERSIONING',
'WEEKDAY',
'WHY_FOUND',
'WINDOW',
'WITHIN',
'XMLTABLE',
'YEAR'
]
}

@@ -285,2 +285,20 @@ // Functions and classes for syntax messages

/**
* Compare two severities. Returns 0 if they are the same, and <0 if
* `a` has a lower `level` than `b` according to {@link severitySpecs}.
*
* compareSeverities('Error', 'Info') => Error < Info => -1
*
* @param {CSN.MessageSeverity} a
* @param {CSN.MessageSeverity} b
* @see severitySpecs
*/
function compareSeverities( a, b ) {
// default: low priority
const aSpec = severitySpecs[a.toLowerCase()] || { level: 10 };
const bSpec = severitySpecs[b.toLowerCase()] || { level: 10 };
// Levels are "inverted" in severitySpecs, so revert order here:
return aSpec.level - bSpec.level;
}
// Return message function to issue errors, warnings, info and debug messages.

@@ -615,2 +633,15 @@ // Messages are put into the `messages` property of argument `options` or `model`.

/**
* Compare two messages `a` and `b`. Return 0 if they are equal in both their
* location and severity, >0 if `a` is larger than `b`, and <0 if `a` is smaller
* than `b`. See `compareSeverities()` for how severities are compared.
*
* @param {CSN.Message} a
* @param {CSN.Message} b
*/
function compareMessageSeverityAware( a, b ) {
const c = compareSeverities(a.severity, b.severity);
return c || compareMessage( a, b );
}
// Return sort-relevant part of semantic location (after the ':').

@@ -629,2 +660,9 @@ // Messages without semantic locations are considered smaller (for syntax errors)

*
* Does NOT keep the original order!
*
* Two messages are the same if they have the same message hash. See messageHash().
* If one of the two is more precise then it replaces the other.
* A message is more precise if it is contained in the other or if
* the first does not have an endLine/endCol.
*
* @param {CSN.Message[]} messages

@@ -801,2 +839,3 @@ */

sortMessages: (m => m.sort(compareMessage)),
sortMessagesSeverityAware: (m => m.sort(compareMessageSeverityAware)),
deduplicateMessages,

@@ -803,0 +842,0 @@ CompileMessage,

@@ -102,3 +102,3 @@ const parseXml = require('./xmlParserWithLocations');

// look if there is an extension for this top level artifact already
let toBeAnnotated = csn.extensions.find(ext => ext.name.absolute === topLevelArt);
let toBeAnnotated = csn.extensions.find(ext => ext.name.path.map(p=>p.id).join('.') === topLevelArt);
if (!toBeAnnotated) { // if not -> create one

@@ -125,2 +125,3 @@ toBeAnnotated = augmented.extension(topLevelArt, obj._location);

// takes an 'Annotation' tag object of a parsed xml and creates annotation assignment in the extension from it

@@ -127,0 +128,0 @@ // last two arguments(messages, targetName) are used for logging during valdiation of an edmx annotation definition

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

if (carrier.kind === 'entity' || carrier.kind === 'view') {
// if annotated object is an entity, annotation goes to the EntityType,
// except if AppliesTo contains Singleton/EntitySet but not EntityType, then annotation goes to EntitySet
if(carrier['@odata.singleton.nullable'])
testToAlternativeEdmTarget = (x => x.includes('Singleton') && !x.includes('EntityType'));
else
testToAlternativeEdmTarget = (x => x.includes('EntitySet') && !x.includes('EntityType'));
// If AppliesTo=[EntitySet/Singleton, EntityType], EntitySet/Singleton has precedence
testToAlternativeEdmTarget = (x => x.includes('EntitySet') || x.includes('Singleton'));
testToStandardEdmTarget = (x => x ? x.includes('EntityType') : true);
// if carrier has an alternate 'entitySetName' use this instead of EdmTargetName

@@ -508,4 +505,8 @@ // (see edmPreprocessor.initializeParameterizedEntityOrView(), where parameterized artifacts

testToAlternativeEdmTarget = (x => x.includes('Schema') && !x.includes('EntityContainer'));
// standard is to allow both Schema and Container
testToStandardEdmTarget = ( x => x ? (x.includes('Schema') || x.includes('EntityContainer')) : true );
testToStandardEdmTarget = ( x => x ? (
// either only AppliesTo=[EntityContainer]
(!x.includes('Schema') && x.includes('EntityContainer')) ||
// or AppliesTo=[Schema, EntityContainer]
(x.includes('Schema') && x.includes('EntityContainer')))
: true );
stdName = edmTargetName + '.EntityContainer';

@@ -559,2 +560,3 @@ alternativeEdmTargetName = edmTargetName;

return function(annotation, appliesTo) {
let rc=false;
if (testToAlternativeEdmTarget && appliesTo && testToAlternativeEdmTarget(appliesTo)) {

@@ -576,9 +578,9 @@ if (carrier.kind === 'service') {

}
return true;
rc=true;
}
else if(testToStandardEdmTarget(appliesTo)) {
if(testToStandardEdmTarget(appliesTo)) {
newAnnosStd.append(annotation);
return true;
rc=true;
}
return false;
return rc;
}

@@ -585,0 +587,0 @@ }();

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

let fk = dependentEntity.elements[ ( options.isFlatFormat ? c[0].join('_') : c[0][0] )];
let pk = principalEntity.$keys[ ( options.isFlatFormat ? c[1].join('_') : c[1][0] )];
let pk = principalEntity.$keys && principalEntity.$keys[ ( options.isFlatFormat ? c[1].join('_') : c[1][0] )];
return !(isConstraintCandidate(fk) && isConstraintCandidate(pk));

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

@@ -1098,3 +1098,3 @@ // CSN frontend - transform CSN into XSN

if (typeof val === 'string' && validLiteralsExtra[val] === type)
return (val === 'x' ? 'raw' : val); // XSN TODO: 'raw'->'x'
return (val === 'x' ? 'hex' : val); // XSN TODO: 'hex'->'x'
message( 'syntax-csn-expected-valid', location(true), null, { prop: spec.msgProp },

@@ -1101,0 +1101,0 @@ 'Error', 'Expected valid string for property $(PROP)' );

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

const { getArtifactDatabaseNameOf, getElementDatabaseNameOf } = require('./model/csnUtils');
const { sortMessages, sortMessagesSeverityAware, deduplicateMessages } = require('./base/messages');

@@ -604,2 +605,5 @@ // The compiler version (taken from package.json)

CompilationError,
sortMessages,
sortMessagesSeverityAware,
deduplicateMessages,
messageString,

@@ -606,0 +610,0 @@ messageStringMultiline,

@@ -9,48 +9,95 @@ 'use strict';

// TODO clarify API
function compareModels(beforeModel, afterModel, deltaMode=false) {
const extensions = [];
const addedDefinitions = Object.create(null);
const removedDefinitions = Object.create(null);
forEachDefinition(afterModel, getArtifactComparator(beforeModel, extensions, addedDefinitions, removedDefinitions)); // (, alerts(afterModel))
/**
* Compares two models, in HANA-transformed CSN format, to each other.
*
* @param beforeModel the before-model
* @param afterModel the after-model
* @returns {object} the sets of deletions, extensions, and migrations of entities necessary to transform the before-model
* to the after-model, together with all the definitions of the after-model
*/
function compareModels(beforeModel, afterModel) {
const deletedEntities = Object.create(null);
const elementAdditions = [];
const elementChanges = [];
if(!deltaMode){
afterModel.extensions = extensions; // assuming that afterModel is nowhere needed in its original state
return afterModel;
} else {
const returnObj = Object.create(null);
returnObj.definitions = addedDefinitions;
returnObj.extensions = extensions;
returnObj.deletions = removedDefinitions;
return returnObj;
}
// There is currently no use in knowing the added entities only. If this changes, hand in `addedEntities` to `getArtifactComparator` below.
forEachDefinition(afterModel, getArtifactComparator(beforeModel, null, null, elementAdditions, elementChanges)); // (, alerts(afterModel))
forEachDefinition(beforeModel, getArtifactComparator(afterModel, null, deletedEntities, null, null)); // (, alerts(beforeModel))
const returnObj = Object.create(null);
returnObj.definitions = afterModel.definitions;
returnObj.deletions = deletedEntities;
returnObj.extensions = elementAdditions;
returnObj.migrations = elementChanges;
return returnObj;
}
function getArtifactComparator(beforeModel, extensions, addedDefinitions, removedDefinitions) { // (, alerts)
function getArtifactComparator(otherModel, addedEntities, deletedEntities, elementAdditions, elementChanges) { // (, alerts)
return function compareArtifacts(artifact, name) { // (, topKey, path) topKey == 'definitions'
if (!isPersistedAsTable(artifact)) {
// Will be handled automatically by HDI - might be needed for SQL
removedDefinitions[name] = artifact;
return;
function addElements() {
const elements = {};
forEachMember(artifact, getElementComparator(otherArtifact, elements));
if (Object.keys(elements).length > 0) {
elementAdditions.push({
extend: name,
elements: elements
});
}
}
function removeOrChangeElements() {
const removedElements = {};
const changedElements = {};
const modification = { migrate: name };
const beforeArtifact = beforeModel.definitions[name];
if (!beforeArtifact || !isPersistedAsTable(beforeArtifact)) {
// Not needed for HDI - needed for SQL, for a "tight" delta
addedDefinitions[name] = artifact;
forEachMember(otherArtifact, getElementComparator(artifact, removedElements));
if (Object.keys(removedElements).length > 0) {
modification.remove = removedElements;
}
forEachMember(artifact, getElementComparator(otherArtifact, null, changedElements));
if (Object.keys(changedElements).length > 0) {
modification.change = changedElements;
}
if (modification.remove || modification.change) {
elementChanges.push(modification);
}
}
const otherArtifact = otherModel.definitions[name];
const isPersisted = isPersistedAsTable(artifact);
const isPersistedOther = otherArtifact && isPersistedAsTable(otherArtifact);
if (deletedEntities) {
// Looking for deleted entities only.
// Arguments are interchanged in this case: `artifact` from beforeModel and `otherArtifact` from afterModel.
if (isPersisted && !isPersistedOther) {
deletedEntities[name] = artifact;
}
return;
}
const extensionsElements = {};
forEachMember(artifact, getElementComparator(beforeArtifact, extensionsElements)); // (, alerts)
// Looking for added entities and added/deleted/changed elements.
// Parameters: `artifact` from afterModel and `otherArtifact` from beforeModel.
if (Object.keys(extensionsElements).length === 0) {
// No relevant elements have been added.
if (!isPersisted) {
// Artifact not persisted in afterModel.
return;
}
extensions.push({
extend: name,
elements: extensionsElements
});
if (!isPersistedOther) {
if (addedEntities) {
addedEntities[name] = artifact;
}
return;
}
// Artifact changed?
if (elementAdditions) {
addElements();
}
if (elementChanges) {
removeOrChangeElements();
}
};

@@ -68,3 +115,3 @@ }

function getElementComparator(beforeArtifact, extensionsElements) { // (, alerts)
function getElementComparator(otherArtifact, addedElements = null, changedElements = null) { // (, alerts)
return function compareElements(element, name) { // (, topKey, path) topKey == 'elements'

@@ -75,21 +122,31 @@ if (element._ignore) {

const beforeElement = beforeArtifact.elements[name];
if (beforeElement && !beforeElement._ignore) {
// Old element.
const otherElement = otherArtifact.elements[name];
if (otherElement && !otherElement._ignore) {
// Element type changed?
if (!changedElements) {
return;
}
if (otherElement.type !== element.type || JSON.stringify(otherElement) !== JSON.stringify(element)) {
// Type or parameters, e.g. association target, changed.
changedElements[name] = changedElement(element, otherElement);
}
return;
}
// TODO perhaps check these conditions elsewhere: // if (element.notNull) {
// alerts.signal(alerts.error`Elements with attribute 'not null' cannot be added`, path);
// }
// if (element.key) {
// alerts.signal(alerts.error`Elements with attribute 'key' cannot be added`, path);
// }
extensionsElements[name] = element;
if (addedElements) {
addedElements[name] = element;
}
}
}
function changedElement(element, otherElement) {
return {
old: otherElement,
new: element
};
}
module.exports = {
compareModels
};

@@ -18,8 +18,7 @@ // Common render functions for toCdl.js and toSql.js

// Dialect = 'hana' (only relevance at the moment) | 'cap' | 'sqlite'
function renderFunc( funcName, node, dialect, renderArgs, parenNameToUpper = false ) {
function renderFunc( funcName, node, dialect, renderArgs) {
if (funcWithoutParen( node, dialect ))
return funcName;
else
// unclear why we transform the function name to uppercase in SQL, but only with parentheses
return `${parenNameToUpper ? funcName.toUpperCase() : funcName}(${renderArgs( node.args )})`;
return `${funcName}(${renderArgs( node.args )})`;
}

@@ -26,0 +25,0 @@

@@ -6,3 +6,2 @@

const { hasBoolAnnotation, isBuiltinType } = require('../model/csnUtils');
const keywords = require('../base/keywords');
const alerts = require('../base/alerts');

@@ -16,2 +15,3 @@ const version = require('../../package.json').version;

const { isBetaEnabled } = require('../base/model');
const { smartId, smartFuncId, delimitedId } = require('../sql-identifier');

@@ -66,4 +66,13 @@ // Type mapping from cds type names to DB type names:

* per top-level artifact into dictionaries 'hdbtable', 'hdbview', ..., without
* leading CREATE, without trailing semicolon. All statements (in proper order)
* are copied into dictionary 'sql', with trailing semicolon.
* leading CREATE, without trailing semicolon. All corresponding statements (in
* proper order) are copied into dictionary 'sql', with trailing semicolon.
* Also included in the result are dictionaries 'deletions' and 'migrations',
* keyed by entity name, which reflect statements needed for deleting or changing
* (migrating) entities.
* In the case of 'deletions', each entry contains the corresponding DROP statement.
* In the case of 'migrations', each entry is an array of objects representing
* changes to the entity. Each change object contains one or more SQL statements
* (concatenated to one string using \n) and information whether these incur
* potential data loss.
*
* Return an object like this:

@@ -79,2 +88,21 @@ * { "hdbtable": {

* "bar::wiz" : "CREATE VIEW \"bar::wiz\" AS SELECT \"x\" FROM ...;\n"
* },
* "deletions": {
* "baz": "DROP TABLE baz"
* },
* "migrations": {
* "foo": [
* {
* "drop": false,
* "sql": "ALTER TABLE foo ALTER (elm DECIMAL(12, 9));"
* },
* {
* "drop": true,
* "sql": "ALTER TABLE foo DROP (eln);"
* },
* {
* "drop": false,
* "sql": "ALTER TABLE foo ADD (elt NVARCHAR(99));"
* }
* ]
* }

@@ -90,2 +118,55 @@ * }

// Utils to render SQL statements.
const render = {
/*
Render column additions as HANA SQL. Checks for duplicate elements.
Only HANA SQL is currently supported.
*/
addColumns: {
fromElementStrings: function(tableName, eltStrings) {
return [`ALTER TABLE ${tableName} ADD (${eltStrings.join(', ')});`];
},
fromElementsObj: function(artifactName, tableName, elementsObj, env, duplicateChecker) {
// Only extend with 'ADD' for elements/associations
// TODO: May also include 'RENAME' at a later stage
const elements = Object.entries(elementsObj)
.map(([name, elt]) => renderElement(artifactName, name, elt, duplicateChecker, null, env))
.filter(s => s !== '');
if (elements.length) {
return render.addColumns.fromElementStrings(tableName, elements);
}
return [];
}
},
/*
Render association additions as HANA SQL.
TODO duplicity check
*/
addAssociations: function(artifactName, tableName, elementsObj, env) {
return Object.entries(elementsObj)
.map(([name, elt]) => renderAssociationElement(name, elt, env))
.filter(s => s !== '')
.map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`);
},
/*
Render column removals as HANA SQL.
*/
dropColumns: function(tableName, sqlIds) {
return [`ALTER TABLE ${tableName} DROP (${sqlIds.join(', ')});`];
},
/*
Render association removals as HANA SQL.
*/
dropAssociation: function(tableName, sqlId) {
return [`ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};`];
},
/*
Render column modifications as HANA SQL.
*/
alterColumns: function (tableName, definitionsStr) {
return [`ALTER TABLE ${tableName} ALTER (${definitionsStr});`];
}
};
// FIXME: Currently requires 'options.forHana', because it can only render HANA-ish SQL dialect

@@ -120,3 +201,4 @@ if (!options.forHana) {

hdbview: Object.create(null),
alterTable: Object.create(null)
deletions: Object.create(null),
migrations: Object.create(null)
}

@@ -126,3 +208,6 @@

let definitionsDuplicateChecker = new DuplicateChecker();
let deletionsDuplicateChecker = new DuplicateChecker();
let extensionsDuplicateChecker = new DuplicateChecker();
let removeElementsDuplicateChecker = new DuplicateChecker();
let changeElementsDuplicateChecker = new DuplicateChecker();

@@ -133,26 +218,43 @@ // Render each artifact on its own

// indentation issues
let env = {
const env = {
// Current indentation string
indent: '',
}
};
renderArtifactInto(artifactName, csn.definitions[artifactName], resultObj, env);
}
// Render each deleted artifact
for (let artifactName in csn.deletions) {
renderArtifactDeletionInto(artifactName, csn.deletions[artifactName], resultObj);
}
// Render each artifact extension
for (let artifactIndex in csn.extensions) {
if ('extend' in csn.extensions[artifactIndex]) {
const artifactName = csn.extensions[artifactIndex]['extend']
// This environment is passed down the call hierarchy, for dealing with
// indentation issues
let env = {
// Current indentation string
indent: '',
// Only HANA SQL is currently supported.
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
if (csn.extensions && options.toSql.dialect === 'hana') {
for (const extension of csn.extensions) {
if (extension.extend) {
const artifactName = extension.extend;
const env = { indent: '' };
renderArtifactExtensionInto(artifactName, extension, resultObj, env);
}
renderArtifactExtensionInto(artifactName, csn.extensions[artifactIndex], resultObj, env);
}
}
// Render each artifact change
// Only HANA SQL is currently supported.
if (csn.migrations && options.toSql.dialect === 'hana') {
for (const migration of csn.migrations) {
if (migration.migrate) {
const artifactName = migration.migrate;
const env = { indent: '' };
renderArtifactMigrationInto(artifactName, migration, resultObj, env);
}
}
}
// trigger artifact and element name checks
definitionsDuplicateChecker.check(signal, error);
extensionsDuplicateChecker.check(signal, error);
deletionsDuplicateChecker.check(signal, error);

@@ -169,3 +271,4 @@ // Throw exception in case of errors

// Handle hdbKinds separately from alterTable case
const { alterTable, ...hdbKinds } = resultObj;
// eslint-disable-next-line no-unused-vars
const { deletions, migrations, ...hdbKinds } = resultObj;
for (let hdbKind of Object.keys(hdbKinds)) {

@@ -194,4 +297,4 @@ for (let name in resultObj[hdbKind]) {

}
for (let name in alterTable) {
alterTable[name][0] = `${options.testMode ? '' : sqlVersionLine}` + alterTable[name][0];
for (let name in deletions) {
deletions[name] = `${options.testMode ? '' : sqlVersionLine}` + deletions[name];
}

@@ -202,2 +305,3 @@

// Render an artifact into the appropriate dictionary of 'resultObj'.

@@ -242,8 +346,17 @@ function renderArtifactInto(artifactName, art, resultObj, env) {

// Render an artifact deletion into the appropriate dictionary of 'resultObj'.
function renderArtifactDeletionInto(artifactName, art, resultObj) {
const tableName = quoteSqlId(absoluteCdsName(artifactName), art.$location);
deletionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName);
addDeletion(resultObj, artifactName, `DROP TABLE ${tableName}`)
}
// Render an artifact extension into the appropriate dictionary of 'resultObj'.
function renderArtifactExtensionInto(artifactName, art, resultObj, env) {
// Only HANA SQL is currently supported.
function renderArtifactExtensionInto(artifactName, ext, resultObj, env) {
// Property kind is always omitted for elements and can be omitted for
// top-level type definitions, it does not exist for extensions.
if (artifactName && !art.query) {
renderExtendInto(artifactName, art, resultObj, env)
if (artifactName && !ext.query) {
renderExtendInto(artifactName, ext.elements, resultObj, env, extensionsDuplicateChecker);
}

@@ -253,2 +366,60 @@ if (!artifactName) throw new Error('Undefined artifact name: ' + artifactName);

// Render an artifact migration into the appropriate dictionary of 'resultObj'.
// Only HANA SQL is currently supported.
function renderArtifactMigrationInto(artifactName, migration, resultObj, env) {
function reducesTypeSize(def) {
// HANA does not allow decreasing the value of any of those type parameters.
return def.old.type === def.new.type &&
['length', 'precision', 'scale'].some(param => def.new[param] < def.old[param]);
}
function concat(...statements) {
return [statements.join('\n')];
}
const tableName = quoteSqlId(absoluteCdsName(artifactName));
// Drop column (unsupported in sqlite)
if (migration.remove) {
const entries = Object.entries(migration.remove);
if (entries.length) {
const removeCols = entries.filter(([, value]) => !value.target).map(([key, ]) => quoteSqlId(key));
const removeAssocs = entries.filter(([, value]) => value.target).map(([key, ]) => quoteSqlId(key));
removeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName);
[...removeCols, ...removeAssocs].forEach(element => removeElementsDuplicateChecker.addElement(quoteSqlId(element), undefined, element));
// Remove columns.
if (removeCols.length) {
addMigration(resultObj, artifactName, true, render.dropColumns(tableName, removeCols));
}
// Remove associations.
removeAssocs.forEach(assoc => addMigration(resultObj, artifactName, true, render.dropAssociation(tableName, assoc)));
}
}
// Change column type (unsupported in sqlite)
if (migration.change) {
changeElementsDuplicateChecker.addArtifact(tableName, undefined, artifactName)
for (const [eltName, def] of Object.entries(migration.change)) {
const sqlId = quoteSqlId(eltName);
changeElementsDuplicateChecker.addElement(sqlId, undefined, eltName);
if (options.sqlChangeMode === 'drop' || def.old.target || def.new.target || reducesTypeSize(def)) {
// Lossy change because either an association is removed and/or added, or the type size is reduced.
// Drop old element and re-add it in its new shape.
const drop = def.old.target
? render.dropAssociation(tableName, sqlId)
: render.dropColumns(tableName, [sqlId]);
const add = def.new.target
? render.addAssociations(artifactName, tableName, {[eltName]: def.new}, env)
: render.addColumns.fromElementsObj(artifactName, tableName, {[eltName]: def.new}, env);
addMigration(resultObj, artifactName, true, concat(...drop, ...add));
} else {
// Lossless change: no associations directly affected, no size reduction.
const eltStr = renderElement(artifactName, eltName, def.new, null, null, env);
addMigration(resultObj, artifactName, false, render.alterColumns(tableName, eltStr));
}
}
}
}
// Render a (non-projection, non-view) entity (and possibly its indices) into the appropriate

@@ -331,43 +502,27 @@ // dictionaries of 'resultObj'.

// Render an extended entity into the appropriate dictionaries of 'resultObj'.
function renderExtendInto(artifactName, art, resultObj, env) {
let tableName = quoteSqlId(absoluteCdsName(artifactName), art.$location);
extensionsDuplicateChecker.addArtifact(tableName, art && art.$location, artifactName)
// Only extend with 'ADD' for elements/associations
// TODO: May also include 'RENAME' at a later stage
let elements = Object.keys(art.elements).map((eltName) => {
const eltStr = renderElement(artifactName, eltName, art.elements[eltName], extensionsDuplicateChecker, null, env);
if (options.toSql.dialect === 'sqlite') {
return eltStr.length ? 'ALTER TABLE ' + tableName + ' ADD COLUMN ' + eltStr + ';': eltStr;
}
return eltStr.length ? eltStr : eltStr;
})
.filter(s => s !== '')
if (options.toSql.dialect === 'hana' && elements.length) {
elements = ['ALTER TABLE ' + tableName + ' ADD (' + elements.join(', ') + ');'];
// Only HANA SQL is currently supported.
function renderExtendInto(artifactName, elementsObj, resultObj, env, duplicateChecker) {
const tableName = quoteSqlId(absoluteCdsName(artifactName));
duplicateChecker && duplicateChecker.addArtifact(tableName, undefined, artifactName)
const elements = render.addColumns.fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker);
const associations = render.addAssociations(artifactName, tableName, elementsObj, env);
if (elements.length + associations.length > 0) {
addMigration(resultObj, artifactName, false, [...elements, ...associations]);
}
}
let associations = Object.keys(art.elements).map(name => {
const eltStr = renderAssociationElement(name, art.elements[name], env);
return eltStr.length ? 'ALTER TABLE ' + tableName + ' ADD ASSOCIATION (' + eltStr + ');': eltStr;
})
.filter(s => s !== '');
// In order to render a statement, elements for sql dialect 'hana' must be at least one of type or association
if ((elements.length > 0 && options.toSql.dialect === 'sqlite') ||
((elements.length > 0 || associations.length > 0) && options.toSql.dialect === 'hana')) {
let resultsArray = [];
resultsArray.push(...elements);
if (options.toSql.dialect === 'hana') {
resultsArray.push(...associations);
}
resultObj.alterTable[artifactName] = resultsArray;
function addMigration(resultObj, artifactName, drop, sqlArray) {
if (!(artifactName in resultObj.migrations)) {
resultObj.migrations[artifactName] = [];
}
const migrations = sqlArray.map(sql => ({ drop, sql }));
resultObj.migrations[artifactName].push(...migrations);
}
function addDeletion(resultObj, artifactName, sql) {
resultObj.deletions[artifactName] = sql;
}
// Retrieve the 'fzindex' (fuzzy index) property (if any) for element 'elemName' from hanaTc (if defined)

@@ -402,3 +557,3 @@ function getFzIndex(elemName, hanaTc) {

const quotedElementName = quoteSqlId(elementName, elm.$location);
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
duplicateChecker && duplicateChecker.addElement(quotedElementName, elm.$location, elementName);

@@ -423,2 +578,3 @@ let result = env.indent + quotedElementName + ' '

// Any change to the cardinality rendering must be reflected in A2J mapAssocToJoinCardinality() as well.
// TODO duplicity check
function renderAssociationElement(elementName, elm, env) {

@@ -743,3 +899,3 @@ let result = '';

const p = params[pn];
let pstr = 'IN ' + quoteSqlId(pn) + ' ' + renderTypeReference(artifactName, pn, p);
let pstr = 'IN ' + prepareIdentifier(pn) + ' ' + renderTypeReference(artifactName, pn, p);
if(p.default) {

@@ -1102,12 +1258,4 @@ pstr += ' DEFAULT ' + renderExpr(p.default);

else if (x.func) {
// test for non-regular HANA identifier that needs to be quoted
// identifier {letter}({letter_or_digit}|[#$])*
// letter [A-Za-z_]
// letter_or_digit [A-Za-z_0-9]
const regex = RegExp(/^[a-zA-Z][\w#$]*$/, 'g');
const isRegularId = regex.test(x.func);
const funcName = isRegularId ? x.func : quoteSqlId(x.func);
return renderFunc( funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env),
!isRegularId ? false : true );
const funcName = smartFuncId(prepareIdentifier(x.func), options.toSql.dialect);
return renderFunc(funcName, x, options.toSql.dialect, a => renderArgs(a, '=>', env));
}

@@ -1214,2 +1362,32 @@ // Nested expression

/**
* Prepare an identifier:
* If 'options.toSql.names' is 'plain'
* - replace '.' or '::' by '_'
* else if 'options.toSql.names' is 'quoted'
* - replace '::' by '.'
*
* @param {String} name
*
* @returns {String}
*/
function prepareIdentifier(name) {
// Sanity check
if(options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain') {
throw new Error(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
}
switch(options.toSql.names){
case 'plain':
return name.replace(/(\.|::)/g, '_');
case 'quoted':
return name.replace(/::/g, '.');
case 'hdbcds':
return name;
}
throw new Error(`No matching rendering found for naming mode ${options.toSql.names}`);
}
// Return 'name' with appropriate "-quotes.

@@ -1222,23 +1400,15 @@ // Additionally perform the following conversions on 'name'

// Complain about names that collide with known SQL keywords or functions
function quoteSqlId(name, location=null) {
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`, location);
function quoteSqlId(name) {
name = prepareIdentifier(name);
switch(options.toSql.names){
case 'plain':
return smartId(name, options.toSql.dialect);
case 'quoted':
return delimitedId(name, options.toSql.dialect);
case 'hdbcds':
return delimitedId(name, options.toSql.dialect);
}
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`, location);
}
}
name = name.replace(/(\.|::)/g, '_');
return name;
}
else if (options.toSql.names === 'quoted') {
name = name.replace(/::/g, '.');
}
return `"${name.replace(/"/g, '""')}"`;
return undefined;
}

@@ -1245,0 +1415,0 @@ }

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

// If @Core.Computed is explicitly set, don't overwrite it!
if (node['@Core.Computed']) return;
if (node['@Core.Computed'] !== undefined) return;

@@ -494,0 +494,0 @@ // For @odata.on.insert/update, also add @Core.Computed

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

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

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

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

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

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

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

if (structuredOData)
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path);
exposeArrayOfTypeOf(member, memberName, defName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path);
else if (options.toOdata.version === 'v4' && !isAction) {
exposeArrayOfTypeOf(member, memberName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path);
exposeArrayOfTypeOf(member, memberName, defName, serviceName, `${defNameWithoutServiceName(defName, serviceName)}_${memberName}`, path);
}

@@ -103,7 +103,7 @@ }

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

@@ -124,8 +124,8 @@

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

@@ -143,3 +143,3 @@ }

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

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

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

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

isMultiSchema
? getNewTypeNameInMultiSchema(node.type || artificialNameWitoutService(artificialName, service), !node.type)
? node.type ? getTypeNameInMultiSchema(node.type, service) : getAnonymousTypeNameInMultiSchema(artificialName, parentName || defName)
: `${service}.${newTypeId}`;

@@ -181,3 +181,10 @@

if (node.elements && node.elements[elemName].$location) setProp(newType.elements[elemName], '$location', node.elements[elemName].$location);
exposeStructTypeOf(newType.elements[elemName], memberName, service, isMultiSchema ? `${newTypeFullName}_${elemName}` : `${newTypeId}_${elemName}`, deleteElems, path);
exposeStructTypeOf(newType.elements[elemName],
memberName,
typeDef.kind === 'type' ? node.type : defName,
service,
isMultiSchema ? `${newTypeFullName}_${elemName}` : `${newTypeId}_${elemName}`,
deleteElems,
path,
newTypeFullName);
}

@@ -203,34 +210,28 @@ typeDef.kind === 'type' ? copyAnnotations(typeDef, newType) : copyAnnotations(node, newType);

function artificialNameWitoutService(name, service) {
return name.replace(`${service}_`, '');
}
function getNewTypeNameInMultiSchema(typeName, isAnonym = false) {
if (isAnonym) {
const lastDotIdx = typeName.lastIndexOf('.');
const newContextName = lastDotIdx === -1 ? 'root' : typeName.substring(0, lastDotIdx);
if (!csn.definitions[`${newContextName}`])
csn.definitions[`${newContextName}`] = { kind: 'context' };
return `${lastDotIdx === -1 ? newContextName + '.' : ''}${typeName}`;
} else if (isArtifactInSomeService(typeName, services) && !isAnonym) {
// what is the name of the cross service references by the type
const crossServiceName = getServiceOfArtifact(typeName, services);
const typeWithoutServiceName = defNameWithoutServiceName(typeName, crossServiceName);
const crossServTypeDefName = `${service}.${crossServiceName}`;
// is there such subContext already, if not -> create one
if (!csn.definitions[crossServTypeDefName])
csn.definitions[crossServTypeDefName] = { kind: 'context' };
/**
* Calculate the new type name that will be exposed in multi schema,
* in case that the element has a named type.
*
* @param {string} typeName type of the element
* @param {string} service current service name
*/
function getTypeNameInMultiSchema(typeName, service) {
if (isArtifactInSomeService(typeName, services)) {
const typeService = csnUtils.getServiceName(typeName);
// new type name without any prefixes
const typePlainName = defNameWithoutServiceName(typeName, typeService);
const newContextName = `${service}.${typeService}`;
createContext(newContextName);
// return the new type name
return `${crossServTypeDefName}.${typeWithoutServiceName.replace(/\./g, '_')}`;
return `${newContextName}.${typePlainName.replace(/\./g, '_')}`;
} else {
const typeContext = csnUtils.getContextOfArtifact(typeName);
const typeNamespace = csnUtils.getNamespaceOfArtifact(typeName);
const contextOfArt = csnUtils.getContextOfArtifact(typeName);
const newContextName = `${service}.${contextOfArt || typeNamespace || 'root'}`;
if (!csn.definitions[`${newContextName}`])
csn.definitions[`${newContextName}`] = { kind: 'context' };
const newContextName = `${service}.${typeContext || typeNamespace || 'root'}`;
// new type name without any prefixes
const typePlainName = typeContext ? defNameWithoutServiceName(typeName, typeContext)
: typeName.replace(`${typeNamespace}.`, '');
createContext(newContextName);
// return the new type name
return `${newContextName}.${contextOfArt ?
defNameWithoutServiceName(typeName, contextOfArt).replace(/\./g, '_')
: nameWithoutNamespace(typeName).replace(/\./g, '_')}`;
return `${newContextName}.${typePlainName.replace(/\./g, '_')}`;
}

@@ -240,2 +241,28 @@ }

/**
* Calculate the new type name that will be exposed in multi schema,
* in case that the element has an anonymous type.
*
* @param {string} typeName type of the element
* @param {string} parentName name of the parent def holding the element
*/
function getAnonymousTypeNameInMultiSchema(typeName, parentName) {
let currPrefix = parentName.substring(0, parentName.lastIndexOf('.'));
const newContextName = currPrefix || 'root';
// new type name without any prefixes
const typePlainName = defNameWithoutServiceName(typeName, newContextName);
createContext(newContextName);
return `${newContextName}.${typePlainName.replace(/\./g, '_')}`;
}
/**
* Tf does not exists, create a context with the given name in the CSN
* @param {string} newContextName
*/
function createContext(newContextName) {
if (!csn.definitions[`${newContextName}`])
csn.definitions[`${newContextName}`] = { kind: 'context' };
}
/**
* Expose a new type definition in the 'definitions' of the CSN and return that type(reusing such a type

@@ -287,3 +314,3 @@ * if it already exists).

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

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

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

@@ -354,9 +381,4 @@ // we can have both of the 'type' and 'items' in the cases:

}
function nameWithoutNamespace(name) {
let namespace = csnUtils.getNamespaceOfArtifact(name);
return name.replace(`${namespace}.`, '');
}
}
module.exports = typesExposure;
{
"name": "@sap/cds-compiler",
"version": "1.46.6",
"version": "1.49.0",
"description": "CDS (Core Data Services) compiler and backends",

@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/",

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

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

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

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

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

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc