@sap/cds-compiler
Advanced tools
Comparing version 2.1.4 to 2.1.6
@@ -33,2 +33,3 @@ #!/usr/bin/env node | ||
const { addLocalizationViews } = require('../lib/transform/localized'); | ||
const { availableBetaFlags } = require('../lib/base/model'); | ||
@@ -119,11 +120,3 @@ // Note: Instead of throwing ProcessExitError, we would rather just call process.exit(exitCode), | ||
if(cmdLine.options.betaMode) { | ||
cmdLine.options.beta = { | ||
'foreignKeyConstraints': true, | ||
'toRename': true, | ||
'addTextsLanguageAssoc': true, | ||
'assocsWithParams': true, | ||
'hanaAssocRealCardinality': true, | ||
'mapAssocToJoinCardinality': true, | ||
'ignoreAssocPublishingInUnion': true, | ||
} | ||
cmdLine.options.beta = availableBetaFlags; | ||
} | ||
@@ -130,0 +123,0 @@ if (cmdLine.options.deprecated) { |
# ChangeLog for cds compiler and backends | ||
<!-- markdownlint-disable MD024 --> | ||
<!-- markdownlint-disable MD004 --> | ||
<!-- (no-duplicate-heading)--> | ||
@@ -9,2 +10,21 @@ | ||
## Version 2.1.6 - 2021-04-14 | ||
### Fixed | ||
- Do not unjustified complain about `$self` comparisons. | ||
- Auto-exposed entities are represented as projections in the CSN. | ||
- to.sql/to.hdi: | ||
+ Revert change "Default values are no longer propagated from the principal to the generated foreign key element." from version 2.1.0 | ||
+ Fix regression where localized convenience views for temporal entities used keys in the from clause that did not exist on the texts-entity | ||
+ Mixin associations are properly removed and are not rendered into views anymore | ||
- to.hdi(.migration): Ensure filenames for `.hdbindex` files stay compatible to V1 | ||
- for.odata: An association as a type of action's parameter or return type now signals an error | ||
- to.edm(x): | ||
+ `@Capabilities` annotations remain on the containees entity type | ||
+ In containment mode don't render foreign keys of the containment constituting 'up' association in the containee | ||
as primary key refs. | ||
+ Revert change "Default values are no longer propagated from the principal to the generated foreign key element." from version 2.1.0 | ||
+ Allow `--odata-proxies` and/or `--odata-x-service-refs` in combination with `--odata-format=flat` and `--version=v4` | ||
## Version 2.1.4 - 2021-03-31 | ||
@@ -35,5 +55,5 @@ | ||
+ Allow the user to define draft actions for annotation purposes | ||
* `draftPrepare(SideEffectsQualifier: String) returns <ET>`, | ||
* `draftActivate() returns <ET>`, | ||
* `draftEdit(PreserveChanges: Boolean) returns <ET>` | ||
+ `draftPrepare(SideEffectsQualifier: String) returns <ET>`, | ||
+ `draftActivate() returns <ET>`, | ||
+ `draftEdit(PreserveChanges: Boolean) returns <ET>` | ||
- to.edm(x): | ||
@@ -115,4 +135,4 @@ + Warn about non-applicable annotations. | ||
+ Temporal rendering: | ||
* `@cds.valid.from` is not `Edm.KeyRef` anymore. | ||
* `@cds.valid.key` is rendered as `@Core.AlternateKeys`. | ||
+ `@cds.valid.from` is not `Edm.KeyRef` anymore. | ||
+ `@cds.valid.key` is rendered as `@Core.AlternateKeys`. | ||
+ Downgrade message "`<Term>` is not applied" from warning to info. | ||
@@ -133,8 +153,8 @@ + Update Vocabularies 'Aggregation', 'Capabilities', 'Core', 'Validation'. | ||
+ Changed type mappings for `--dialect=sqlite`: | ||
* `cds.Date` -> `DATE_TEXT` | ||
* `cds.Time` -> `TIME_TEXT` | ||
* `cds.Timestamp` -> `TIMESTAMP_TEXT` | ||
* `cds.DateTime` -> `TIMESTAMP_TEXT` | ||
* `cds.Binary` -> `BINARY_BLOB` | ||
* `cds.hana.Binary` -> `BINARY_BLOB` | ||
+ `cds.Date` -> `DATE_TEXT` | ||
+ `cds.Time` -> `TIME_TEXT` | ||
+ `cds.Timestamp` -> `TIMESTAMP_TEXT` | ||
+ `cds.DateTime` -> `TIMESTAMP_TEXT` | ||
+ `cds.Binary` -> `BINARY_BLOB` | ||
+ `cds.hana.Binary` -> `BINARY_BLOB` | ||
+ Don't check missing type facets. | ||
@@ -205,3 +225,3 @@ - to.hdbcds: | ||
+ Rendering of `@Validation.AllowedValue` for elements of type enum annotated with `@assert.range`: | ||
* Add `@Core.Description`, if the enum symbol has a `@Core.Description`, `@description` or document comments. | ||
+ Add `@Core.Description`, if the enum symbol has a `@Core.Description`, `@description` or document comments. | ||
+ Primary key aliases are now the path basenames, colliding aliases are numbered. | ||
@@ -238,2 +258,8 @@ + Fix a bug in constraint calculation if principal has no primary keys. | ||
## Version 1.50.4 - 2021-04-06 | ||
### Fixed | ||
- to.hdbcds: CDS and HANA CDS types inside cast expressions are mapped to their SQL-counterparts, as the CDS types can't be used in a cast. | ||
## Version 1.50.2 - 2021-03-19 | ||
@@ -240,0 +266,0 @@ |
@@ -228,3 +228,3 @@ /** @module API */ | ||
.reduce((previous, current) => { | ||
const hdiArtifactName = remapName(nameMapping[current.name], sqlCSN); | ||
const hdiArtifactName = remapName(nameMapping[current.name], sqlCSN, k => !k.endsWith('.hdbindex')); | ||
previous[hdiArtifactName] = current.sql; | ||
@@ -236,3 +236,3 @@ return previous; | ||
Object.keys(sqlsNotToSort).forEach((key) => { | ||
sorted[remapName(key, sqlCSN)] = sqlsNotToSort[key]; | ||
sorted[remapName(key, sqlCSN, k => !k.endsWith('.hdbindex'))] = sqlsNotToSort[key]; | ||
}); | ||
@@ -243,3 +243,3 @@ | ||
return remapNames(flattenResultStructure(intermediateResult), sqlCSN); | ||
return remapNames(flattenResultStructure(intermediateResult), sqlCSN, k => !k.endsWith('.hdbindex')); | ||
} | ||
@@ -253,9 +253,10 @@ /** | ||
* @param {CSN.Model} csn SQL transformed CSN | ||
* @param {Function} filter | ||
* @returns {object} New result structure | ||
*/ | ||
function remapNames(dict, csn) { | ||
function remapNames(dict, csn, filter) { | ||
const result = Object.create(null); | ||
for (const [ key, value ] of Object.entries(dict)) { | ||
const name = remapName(key, csn); | ||
const name = remapName(key, csn, filter); | ||
result[name] = value; | ||
@@ -273,11 +274,16 @@ } | ||
* @param {CSN.Model} csn SQL transformed CSN | ||
* @param {Function} filter Filter for keys not to remap | ||
* @returns {string} Remapped filename | ||
*/ | ||
function remapName(key, csn) { | ||
const lastDot = key.lastIndexOf('.'); | ||
const prefix = key.slice(0, lastDot); | ||
const suffix = key.slice(lastDot); | ||
function remapName(key, csn, filter = () => true) { | ||
if (filter(key)) { | ||
const lastDot = key.lastIndexOf('.'); | ||
const prefix = key.slice(0, lastDot); | ||
const suffix = key.slice(lastDot); | ||
const remappedName = getFileName(prefix, csn); | ||
return remappedName + suffix; | ||
const remappedName = getFileName(prefix, csn); | ||
return remappedName + suffix; | ||
} | ||
return key; | ||
} | ||
@@ -362,4 +368,8 @@ | ||
const suffix = `.${ kind }`; | ||
for (const [ name, sqlStatement ] of Object.entries(artifacts)) | ||
result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement }); | ||
for (const [ name, sqlStatement ] of Object.entries(artifacts)) { | ||
if ( kind !== 'hdbindex' ) | ||
result.push({ name: getFileName(name, afterImage), suffix, sql: sqlStatement }); | ||
else | ||
result.push({ name, suffix, sql: sqlStatement }); | ||
} | ||
} | ||
@@ -366,0 +376,0 @@ return result; |
@@ -86,3 +86,3 @@ 'use strict'; | ||
if(options.testMode){ | ||
const sorted = sortCsn(forHanaCsn, true); | ||
const sorted = sortCsn(forHanaCsn, options); | ||
result.hdbcds = toHdbcdsSource(sorted, options); | ||
@@ -95,3 +95,3 @@ } else { | ||
if (options.toHana.csn) { | ||
result.csn = options.testMode ? sortCsn(forHanaCsn) : forHanaCsn; | ||
result.csn = options.testMode ? sortCsn(forHanaCsn, options) : forHanaCsn; | ||
} | ||
@@ -313,3 +313,3 @@ | ||
if (transformedOptions.options.toSql.csn) { | ||
result.csn = options.testMode ? sortCsn(forSqlCsn, true) : forSqlCsn; | ||
result.csn = options.testMode ? sortCsn(forSqlCsn, options) : forSqlCsn; | ||
} | ||
@@ -316,0 +316,0 @@ |
@@ -13,2 +13,25 @@ 'use strict'; | ||
/** | ||
* Object of all available beta flags that will be enabled/disabled by `--beta-mode` | ||
* through cdsc. Only intended for INTERNAL USE. | ||
* NOT to be used by umbrella, etc. | ||
* | ||
* @type {{[flag: string]: boolean}} Indicates whether it is enabled by --beta-mode or not. | ||
* @private | ||
*/ | ||
const availableBetaFlags = { | ||
// enabled by --beta-mode | ||
foreignKeyConstraints: true, | ||
toRename: true, | ||
addTextsLanguageAssoc: true, | ||
assocsWithParams: true, | ||
hanaAssocRealCardinality: true, | ||
mapAssocToJoinCardinality: true, | ||
ignoreAssocPublishingInUnion: true, | ||
// disabled by --beta-mode | ||
pretransformedCSN: false, | ||
renderSQL: false, | ||
nestedServices: false, | ||
}; | ||
/** | ||
* Test for early-adaptor feature, stored in option `beta`(new-style) / `betaMode`(old-style) | ||
@@ -220,2 +243,3 @@ * With that, the value of `beta` is a dictionary of feature=>Boolean. | ||
isBetaEnabled, | ||
availableBetaFlags, | ||
isDeprecatedEnabled, | ||
@@ -222,0 +246,0 @@ queryOps, |
@@ -33,3 +33,3 @@ 'use strict'; | ||
if (act.returns) | ||
checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ])); | ||
checkReturns.bind(this)(act.returns, path.concat([ 'actions', actName, 'returns' ]), act.kind); | ||
} | ||
@@ -43,3 +43,3 @@ } | ||
if (art.returns) | ||
checkReturns.bind(this)(art.returns, path.concat('returns')); | ||
checkReturns.bind(this)(art.returns, path.concat('returns'), art.kind); | ||
} | ||
@@ -65,2 +65,10 @@ | ||
if (paramType.type && this.csnUtils.isAssocOrComposition(param.type)) { | ||
this.error(null, currPath, { '#': actKind }, | ||
{ | ||
action: 'An association is not allowed as action\'s parameter type', | ||
function: 'An association is not allowed as function\'s parameter type', | ||
}); | ||
} | ||
if (paramType.items && paramType.items.type) | ||
@@ -78,6 +86,15 @@ checkActionOrFunctionParameter.bind(this)(paramType.items, currPath.concat('items'), actKind); | ||
* @param {CSN.Path} currPath path to the returns object | ||
* @param {string} actKind 'action' or 'function' | ||
*/ | ||
function checkReturns(returns, currPath) { | ||
function checkReturns(returns, currPath, actKind) { | ||
const finalReturnType = returns.type ? this.csnUtils.getFinalBaseType(returns.type) : returns; | ||
if (this.csnUtils.isAssocOrComposition(finalReturnType)) { | ||
this.error(null, currPath, { '#': actKind }, | ||
{ | ||
action: 'An association is not allowed as action\'s return type', | ||
function: 'An association is not allowed as function\'s return type', | ||
}); | ||
} | ||
if (isBuiltinType(finalReturnType) && this.options.toOdata && this.options.toOdata.version === 'v2') { | ||
@@ -90,3 +107,3 @@ // in ODATA v2 scalar types cannot be returned | ||
if (finalReturnType.items) // check array return type | ||
checkReturns.bind(this)(finalReturnType.items, currPath.concat('items')); | ||
checkReturns.bind(this)(finalReturnType.items, currPath.concat('items'), actKind); | ||
else // check if return type is user definited from the current service | ||
@@ -93,0 +110,0 @@ checkUserDefinedType.bind(this)(finalReturnType, returns.type, currPath); |
@@ -8,2 +8,15 @@ 'use strict'; | ||
/** | ||
* View parameter for hana must be of scalar type | ||
* | ||
* @param {CSN.Element} member the element to be checked | ||
* @param {string} memberName the elements name | ||
* @param {string} prop which kind of member are we looking at -> only prop "elements" | ||
* @param {CSN.Path} path the path to the member | ||
*/ | ||
function checkTypeIsScalar(member, memberName, prop, path) { | ||
if ( prop === 'params' && this.csnUtils.isStructured(member)) | ||
this.error(null, path, 'View parameter type must be scalar'); | ||
} | ||
/** | ||
* Check that the `type of` information in the given element | ||
@@ -39,2 +52,6 @@ * has proper type information or issue an error otherwise. The element's final type is checked. | ||
} | ||
else if (member._type) { | ||
if ( this.isAspect(member._type) || member._type.kind === 'type' && member._type.$syntax === 'aspect') | ||
this.error('ref-sloppy-type', path, 'A type or an element is expected here'); | ||
} | ||
return; | ||
@@ -153,2 +170,2 @@ } | ||
module.exports = { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType }; | ||
module.exports = { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType, checkTypeIsScalar }; |
@@ -25,3 +25,3 @@ 'use strict'; | ||
const validateForeignKeys = require('./foreignKeys'); | ||
const { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType } = require('./types'); | ||
const { checkTypeDefinitionHasType, checkElementTypeDefinitionHasType, checkTypeIsScalar } = require('./types'); | ||
const { checkPrimaryKey, checkVirtualElement, checkManagedAssoc } = require('./elements'); | ||
@@ -35,2 +35,3 @@ const checkForInvalidTarget = require('./invalidTarget'); | ||
rejectParamDefaultsInHanaCds, | ||
checkTypeIsScalar, | ||
]; | ||
@@ -89,5 +90,10 @@ | ||
* @param {Function[]} [queryValidators=[]] Validations on query-level | ||
* @param {object} iterateOptions can be used to skip certain kinds from being iterated e.g. 'action' and 'function' for hana | ||
* @returns {Function} Function taking no parameters, that cleans up the attached helpers | ||
*/ | ||
function _validate(csn, that, memberValidators = [], artifactValidators = [], queryValidators = []) { | ||
function _validate(csn, that, | ||
memberValidators = [], | ||
artifactValidators = [], | ||
queryValidators = [], | ||
iterateOptions = {}) { | ||
const { cleanup } = enrich(csn); | ||
@@ -100,8 +106,13 @@ | ||
that.artifact = artifact; | ||
if (memberValidators.length) | ||
forEachMemberRecursively( artifact, memberValidators.map(v => v.bind(that)), path ); | ||
if (memberValidators.length) { | ||
forEachMemberRecursively( artifact, | ||
memberValidators.map(v => v.bind(that)), | ||
path, | ||
true, | ||
iterateOptions ); | ||
} | ||
if (queryValidators.length && artifact.query) | ||
forAllQueries(artifact.query, queryValidators.map(v => v.bind(that)), path.concat([ 'query' ])); | ||
}); | ||
}, iterateOptions); | ||
@@ -130,3 +141,10 @@ return cleanup; | ||
), | ||
forHanaQueryValidators.concat(commonQueryValidators)); | ||
forHanaQueryValidators.concat(commonQueryValidators), | ||
{ | ||
skip: [ | ||
'action', | ||
'function', | ||
'event', | ||
], | ||
}); | ||
} | ||
@@ -133,0 +151,0 @@ |
@@ -556,3 +556,3 @@ // Checks on XSN performed during compile() | ||
return; | ||
if (path0.id === '$self') { // $self (backlink) checks | ||
if (path0.id === '$self' && arg.path.length === 1) { // $self (backlink) checks | ||
checkAssociationArgumentStartingWithSelf( op, elem ); | ||
@@ -559,0 +559,0 @@ return; |
@@ -87,2 +87,8 @@ // Compiler functions and utilities shared across all phases | ||
const specExpected = { | ||
global: { // for using declaration | ||
envFn: artifactsEnv, | ||
artItemsCount: Number.MAX_SAFE_INTEGER, | ||
useDefinitions: true, | ||
global: 'definitions', | ||
}, | ||
// TODO: re-check -------------------------------------------------------- | ||
@@ -164,3 +170,3 @@ annotation: { useDefinitions: true, noMessage: true, global: 'vocabularies' }, | ||
expr: { // in: from-on, | ||
next: '_$next', escape: 'param', assoc: 'nav', | ||
next: '_$next', dollar: true, escape: 'param', assoc: 'nav', | ||
}, | ||
@@ -170,2 +176,3 @@ on: { // TODO: there will also be a 'from-on' (see 'expr') | ||
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment | ||
dollar: true, | ||
rootEnv: 'elements', // the final environment for the path root | ||
@@ -177,9 +184,10 @@ noDep: true, // do not set dependency for circular-check | ||
next: '_$next', // TODO: lexical: ... how to find the (next) lexical environment | ||
dollar: true, | ||
noDep: true, // do not set dependency for circular-check | ||
}, // TODO: special assoc for only on user | ||
rewrite: { | ||
next: '_$next', escape: 'param', noDep: true, rewrite: true, | ||
next: '_$next', dollar: true, escape: 'param', noDep: true, rewrite: true, | ||
}, // TODO: assertion that there is no next/escape used | ||
'order-by-union': { | ||
next: '_$next', escape: 'param', noDep: true, noExt: true, | ||
next: '_$next', dollar: true, escape: 'param', noDep: true, noExt: true, | ||
}, | ||
@@ -192,8 +200,2 @@ // expr TODO: better - on condition for assoc, other on | ||
}, | ||
global: { // for using declaration | ||
envFn: artifactsEnv, | ||
artItemsCount: Number.MAX_SAFE_INTEGER, | ||
useDefinitions: true, | ||
global: 'definitions', | ||
}, | ||
}; | ||
@@ -514,4 +516,7 @@ | ||
} | ||
const nodollar = !spec.dollar && spec.next; | ||
const nextProp = spec.next || '_block'; | ||
for (let art = env; art; art = art[nextProp]) { | ||
if (nodollar && !art._main) // $self stored in main.$tableAliases | ||
break; // TODO: probably remove _$next link | ||
const e = art.artifacts || art.$tableAliases || Object.create(null); | ||
@@ -586,3 +591,3 @@ const r = e[head.id]; | ||
for (const name in extDict) { | ||
if (!name.includes('.') && (!spec.dollar || name[0] !== '$')) | ||
if (!name.includes('.') && (spec.nodollar || name[0] !== '$')) | ||
e[name] = extDict[name]; | ||
@@ -589,0 +594,0 @@ } |
@@ -249,3 +249,3 @@ // CSN frontend - transform CSN into XSN | ||
onlyWith: 'target', | ||
inKind: [ 'element', 'type' ], | ||
inKind: [ 'element', 'type', 'param' ], | ||
}, | ||
@@ -293,3 +293,3 @@ foreignKeys: { // CSN v0.1.0 property -> use 'keys' | ||
optional: [ 'elements' ], // 'elements' for ad-hoc COMPOSITION OF (gensrc style CSN) | ||
inKind: [ 'element', 'type', 'mixin' ], | ||
inKind: [ 'element', 'type', 'mixin', 'param' ], | ||
}, | ||
@@ -296,0 +296,0 @@ cardinality: { // there is an extra def for 'from' |
@@ -445,5 +445,6 @@ 'use strict'; | ||
* @param {(genericCallback|genericCallback[])} callback | ||
* @param {object} iterateOptions can be used to skip certain kinds from being iterated | ||
*/ | ||
function forEachDefinition( csn, callback ) { | ||
forEachGeneric( csn, 'definitions', callback ); | ||
function forEachDefinition( csn, callback, iterateOptions = {} ) { | ||
forEachGeneric( csn, 'definitions', callback, [], iterateOptions ); | ||
} | ||
@@ -462,4 +463,5 @@ | ||
* @param {boolean} [ignoreIgnore] | ||
* @param {object} iterateOptions can be used to skip certain kinds from being iterated | ||
*/ | ||
function forEachMember( construct, callback, path=[], ignoreIgnore=true) { | ||
function forEachMember( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) { | ||
let obj = construct.returns || construct; // why the extra `returns` for actions? | ||
@@ -482,7 +484,9 @@ const obj_path = Array.from(path); // Clone the path | ||
obj = obj.items || obj; | ||
forEachGeneric( obj, 'elements', callback, obj_path ); | ||
forEachGeneric( obj, 'enum', callback, obj_path ); | ||
forEachGeneric( obj, 'foreignKeys', callback, obj_path ); | ||
forEachGeneric( construct, 'actions', callback, path ); | ||
forEachGeneric( construct, 'params', callback, path ); | ||
const props = ['elements', 'enum', 'foreignKeys', 'actions', 'params']; | ||
props.forEach((prop) => { | ||
if(prop === 'actions' || prop === 'params') | ||
forEachGeneric( construct, prop, callback, path, iterateOptions ); | ||
else | ||
forEachGeneric( obj, prop, callback, obj_path, iterateOptions ); | ||
}); | ||
} | ||
@@ -498,4 +502,5 @@ | ||
* @param {boolean} [ignoreIgnore] | ||
* @param {object} iterateOptions can be used to skip certain kinds from being iterated | ||
*/ | ||
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true) { | ||
function forEachMemberRecursively( construct, callback, path=[], ignoreIgnore=true, iterateOptions = {}) { | ||
forEachMember( construct, ( member, memberName, prop, subpath ) => { | ||
@@ -507,4 +512,4 @@ if(Array.isArray(callback)) | ||
// Descend into nested members, too | ||
forEachMemberRecursively( member, callback, subpath, ignoreIgnore); | ||
}, path, ignoreIgnore); | ||
forEachMemberRecursively( member, callback, subpath, ignoreIgnore, iterateOptions); | ||
}, path, ignoreIgnore, iterateOptions); | ||
} | ||
@@ -522,4 +527,5 @@ | ||
* @param {CSN.Path} path | ||
* @param {object} iterateOptions can be used to skip certain kinds from being iterated | ||
*/ | ||
function forEachGeneric( obj, prop, callback, path = []) { | ||
function forEachGeneric( obj, prop, callback, path = [], iterateOptions = {}) { | ||
const dict = obj[prop]; | ||
@@ -530,2 +536,4 @@ for (const name in dict) { | ||
const dictObj = dict[name]; | ||
if(iterateOptions.skip && iterateOptions.skip.includes(dictObj.kind)) | ||
continue; | ||
cb( dictObj, name ); | ||
@@ -532,0 +540,0 @@ } |
@@ -163,3 +163,3 @@ | ||
// Render each artifact on its own | ||
forEachDefinition((options && options.testMode) ? sortCsn(csn, true) : csn, (artifact, artifactName) => { | ||
forEachDefinition((options && options.testMode) ? sortCsn(csn, options) : csn, (artifact, artifactName) => { | ||
// This environment is passed down the call hierarchy, for dealing with | ||
@@ -166,0 +166,0 @@ // indentation issues |
@@ -43,3 +43,3 @@ // API functions returning the SQL identifier token text for a name | ||
sqlite: { | ||
regularRegex: /^[A-Za-z_$][A-Za-z_$0-9]*$/, | ||
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/, | ||
reservedWords: keywords.sqlite, | ||
@@ -46,0 +46,0 @@ effectiveName: name => name, |
@@ -20,4 +20,8 @@ 'use strict'; | ||
const { inspectRef } = csnRefs(csn); | ||
// prepare the functions with the compositions and associations across all entities first | ||
// and execute it afterwards because compositions must be processed first | ||
const compositions = []; | ||
const associations = []; | ||
forEachDefinition(csn, (artifact, artifactName) => { | ||
if(!artifact.query){ | ||
if(!artifact.query && artifact.kind === 'entity' ){ | ||
forAllElements(artifact, artifactName, (parent, elements, path) => { | ||
@@ -28,3 +32,5 @@ // Step I: iterate compositions, enrich dependent keys (in target entity) | ||
if(element.type === 'cds.Composition' && !treatCompositionLikeAssociation(element)) { | ||
foreignKeyConstraintForComposition(element, parent, path.concat([elementName])); | ||
compositions.push(() => { | ||
foreignKeyConstraintForComposition(element, parent, path.concat([elementName])); | ||
}); | ||
} | ||
@@ -37,3 +43,5 @@ } | ||
element.type == 'cds.Composition' && treatCompositionLikeAssociation(element)) { | ||
foreignKeyConstraintForAssociation(element, elements, path.concat([elementName])); | ||
associations.push(() => { | ||
foreignKeyConstraintForAssociation(element, elements, path.concat([elementName])); | ||
}); | ||
} | ||
@@ -44,2 +52,5 @@ } | ||
}); | ||
// create constraints on foreign keys | ||
compositions.forEach(fn => fn()); | ||
associations.forEach(fn => fn()); | ||
// Step III: Create the final referential constraints from all dependent key <-> parent key pairs stemming from the same $sourceAssociation | ||
@@ -46,0 +57,0 @@ forEachDefinition(csn, collectAndAttachReferentialConstraints); |
@@ -12,3 +12,5 @@ 'use strict'; | ||
getArtifactDatabaseNameOf, | ||
getElementDatabaseNameOf } = require('../model/csnUtils'); | ||
getElementDatabaseNameOf, | ||
isAspect, | ||
} = require('../model/csnUtils'); | ||
const { checkCSNVersion } = require('../json/csnVersion'); | ||
@@ -123,3 +125,3 @@ const validate = require('../checks/validator'); | ||
validate.forOdata(csn, { | ||
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType | ||
error, warning, info, inspectRef, effectiveType, artifactRef, csn, options, csnUtils, services, getFinalBaseType, isAspect | ||
}); | ||
@@ -126,0 +128,0 @@ |
@@ -173,3 +173,5 @@ 'use strict'; | ||
const elem = entity.elements[originalElement]; | ||
if (!elem.key) | ||
// Note: $key is used by forHanaNew.js to indicate that this element was a key in the original, | ||
// user's entity. Keys may have been changed by the backends (e.g. by `@cds.valid.key`) | ||
if (!elem.key && !elem.$key) | ||
columns.push( createColumnLocalizedElement( originalElement, shouldUseJoin ) ); | ||
@@ -202,3 +204,3 @@ else if (shouldUseJoin) | ||
const elem = entity.elements[originalElement]; | ||
if (elem.key) { | ||
if (elem.key || elem.$key) { | ||
from.on.push( createColumnRef( [ 'localized_1', originalElement ] )); | ||
@@ -295,3 +297,3 @@ from.on.push( '=' ); | ||
} | ||
else if (!element.key) { | ||
else if (!element.key && !element.$key) { | ||
// Because in coalesce mode a function is used, localized non-key elements | ||
@@ -316,3 +318,3 @@ // are not directly referenced which results in a `@Core.Computed` annotation. | ||
let keyCount = 0; | ||
const textElements = []; | ||
let textElements = []; | ||
@@ -323,6 +325,6 @@ forEachGeneric(art, 'elements', (elem, elemName /*, prop, path*/) => { | ||
if (elem.key) | ||
if (elem.key || elem.$key) | ||
keyCount += 1; | ||
if (elem.key || elem.localized) | ||
if (elem.key || elem.$key || elem.localized) | ||
textElements.push( elemName ); | ||
@@ -335,3 +337,3 @@ | ||
if (textElements.length <= keyCount || !keyCount) | ||
if (textElements.length <= keyCount || keyCount <= 0) | ||
// Nothing to do: no localized fields or all localized fields are keys | ||
@@ -361,2 +363,17 @@ return null; | ||
// There may be keys in the original artifact that were added by the core compiler, | ||
// for example elements that are marked @cds.valid.from. | ||
// These keys are not present in the texts entity generated by the compiler. | ||
// So if we don't filter them out, we may generate invalid SQL. | ||
textElements = textElements.filter((elemName) => { | ||
const hasElement = !!textsEntity.elements[elemName]; | ||
if (!hasElement && (art.elements[elemName].key || art.elements[elemName].$key)) | ||
keyCount--; | ||
return hasElement; | ||
}); | ||
if (textElements.length <= keyCount || keyCount <= 0) | ||
// Repeat the check already used above as the number of keys may have changed. | ||
return null; | ||
return textElements; | ||
@@ -419,3 +436,3 @@ } | ||
if ((art.query || art.projection) && elem.localized && !elem.key) { | ||
if ((art.query || art.projection) && elem.localized && !elem.key && !elem.$key) { | ||
// e.g. projections ; ignore if key is present (warning already issued) or | ||
@@ -422,0 +439,0 @@ // if the artifact is an entity (already processed in (1)) |
@@ -134,2 +134,4 @@ 'use strict'; | ||
parent = parent.items; | ||
if (parent.returns) | ||
parent = parent.returns.items || parent.returns; | ||
@@ -198,3 +200,3 @@ let currElementsNames = Object.keys(parent.elements); | ||
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property. | ||
for (let prop of ['type', 'length', 'scale', 'precision', 'srid', '@odata.Type']) { | ||
for (let prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) { | ||
if (fkArtifact[prop] != undefined) { | ||
@@ -201,0 +203,0 @@ foreignKeyElement[prop] = fkArtifact[prop]; |
@@ -23,6 +23,4 @@ 'use strict'; | ||
const { error } = message; | ||
// are we working with structured OData or not | ||
const structuredOData = options.toOdata.odataFormat === 'structured' && options.toOdata.version === 'v4'; | ||
// are we working with OData proxies or cross-service refs | ||
const isMultiSchema = structuredOData && ((options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)); | ||
const isMultiSchema = options.toOdata.version === 'v4' && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs); | ||
// collect in this variable all the newly exposed types | ||
@@ -29,0 +27,0 @@ const exposedStructTypes = []; |
@@ -100,3 +100,3 @@ 'use strict'; | ||
// FIXME: There is currently no other way but to treat the annotation '@odata.Type' as a type property. | ||
for (const prop of ['type', 'length', 'scale', 'precision', 'srid', '@odata.Type']) { | ||
for (const prop of ['type', 'length', 'scale', 'precision', 'srid', 'default', '@odata.Type']) { | ||
if (fkArtifact[prop] != undefined) { | ||
@@ -366,5 +366,6 @@ foreignKeyElement[prop] = fkArtifact[prop]; | ||
}); | ||
const magicVars = ['$now'] | ||
// If the path starts with '$self', this is now redundant (because of flattening) and can be omitted, | ||
// making life easier for consumers | ||
if (result[0] === '$self' && result.length > 1) { | ||
if (result[0] === '$self' && result.length > 1 && !magicVars.includes(result[1])) { | ||
result = result.slice(1); | ||
@@ -371,0 +372,0 @@ } |
@@ -448,3 +448,3 @@ // Custom resolve functionality for the CDS compiler | ||
function packageFilter( pkg ) { | ||
return { main: pkg.cds && pkg.cds.main || pkg.main }; | ||
return { main: pkg.cds && pkg.cds.main }; | ||
} | ||
@@ -451,0 +451,0 @@ |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "2.1.4", | ||
"version": "2.1.6", | ||
"description": "CDS (Core Data Services) compiler and backends", | ||
@@ -5,0 +5,0 @@ "homepage": "https://cap.cloud.sap/", |
@@ -42,3 +42,3 @@ # anno-duplicate-unrelated-layer | ||
## Fix | ||
## How to Fix | ||
@@ -45,0 +45,0 @@ To fix the issue, remove one of the duplicate annotations. Chances are, that |
@@ -34,3 +34,3 @@ # check-proper-type-of | ||
## Fix | ||
## How to Fix | ||
@@ -37,0 +37,0 @@ To fix the issue, assign an explicit type to `ViewFoo:calculatedField`. |
@@ -28,3 +28,3 @@ # check-proper-type | ||
## Fix | ||
## How to Fix | ||
@@ -31,0 +31,0 @@ To fix the issue, add explicit type information to `MainType`, for example, add |
@@ -41,3 +41,3 @@ # extend-repeated-intralayer | ||
## Fix | ||
## How to Fix | ||
@@ -44,0 +44,0 @@ To fix the issue, move extensions for the same artifact into the same extension |
@@ -39,3 +39,3 @@ # extend-unrelated-layer | ||
## Fix | ||
## How to Fix | ||
@@ -42,0 +42,0 @@ To fix the issue, move extensions for the same artifact into the same layer, |
@@ -43,3 +43,3 @@ # redirected-to-ambiguous | ||
## Fix | ||
## How to Fix | ||
@@ -46,0 +46,0 @@ To fix the issue, you must have the original target only once in your direct |
@@ -59,3 +59,3 @@ # redirected-to-unrelated | ||
## Fix | ||
## How to Fix | ||
@@ -62,0 +62,0 @@ To fix the issue, you must redirect the association to an entity that originates |
@@ -40,3 +40,3 @@ # rewrite-not-supported | ||
## Fix | ||
## How to Fix | ||
@@ -43,0 +43,0 @@ To fix the issue, you have to provide an explicit ON condition. This can be |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3333494
140
66481