@sap/cds-compiler
Advanced tools
Comparing version 4.0.0 to 4.0.2
@@ -10,2 +10,17 @@ # ChangeLog for cds compiler and backends | ||
## Version 4.0.2 - 2023-06-22 | ||
### Fixed | ||
- to.sql.migration: When drop-creating views, also drop-create (transitively) dependent views. | ||
- to.edm(x): | ||
+ Forward `@odata.singleton { nullable }` annotation to parameter entity. | ||
+ Annotations assigned to a parameterized entity are propagated to the parameter entity if the annotation is | ||
applicable to either an `edm.EntitySet` or `edm.Singleton`. This especially covers all `@Capabilities` and their | ||
shortcut forms like `@readonly` and `@insertonly`. The original annotation is not removed from the original entity. | ||
Annotations that should be rendered at the parameter `edm.EntityType` can be qualified with `$parameters`. | ||
Explicitly qualified annotations are removed from the original entity allowing individual assignments. | ||
## Version 4.0.0 - 2023-06-06 | ||
@@ -12,0 +27,0 @@ |
@@ -387,2 +387,3 @@ /** @module API */ | ||
const markedSkipByUs = {}; | ||
const cleanup = []; | ||
@@ -408,2 +409,3 @@ // Delete artifacts that are already present in csn | ||
cleanup.push(() => delete diffArtifact['@cds.persistence.skip']); | ||
markedSkipByUs[artifactName] = true; | ||
} | ||
@@ -413,2 +415,29 @@ }); | ||
const sortOrder = sortViews({ sql: {}, csn: afterImage }); | ||
const dependentsDict = {}; | ||
sortOrder.forEach(({ name, dependents }) => { | ||
dependentsDict[name] = dependents; | ||
}); | ||
const stack = Object.keys(drops.creates); | ||
while (stack.length > 0) { | ||
const name = stack.pop(); | ||
const artifact = diff.definitions[name]; | ||
if (drops.creates[name] === undefined) { | ||
if (artifact['@cds.persistence.skip'] && markedSkipByUs[name]) { | ||
// Remove the skip so we render a CREATE VIEW | ||
diff.definitions[name]['@cds.persistence.skip'] = false; | ||
drops.creates[name] = `DROP VIEW ${ identifierUtils.renderArtifactName(name) };`; | ||
} | ||
} | ||
const dependents = dependentsDict[name]; | ||
if (dependents) { // schedule any dependents for processing that don't have a drop-create yet | ||
for (const dependantName in dependents) { | ||
if (!drops.creates[dependantName]) | ||
stack.push(dependantName); | ||
} | ||
} | ||
} | ||
// Convert the diff to SQL. | ||
@@ -425,3 +454,2 @@ if (!internalOptions.beta) | ||
// TODO: Handle `ADD CONSTRAINT` etc! | ||
const sortOrder = sortViews({ sql: {}, csn: afterImage }); | ||
@@ -428,0 +456,0 @@ const dropSqls = []; |
@@ -96,3 +96,2 @@ 'use strict'; | ||
const { layers, leftover } = sortTopologically(csn, _dependents, _dependencies); | ||
cleanup.forEach(fn => fn()); | ||
if (leftover.length > 0) | ||
@@ -103,3 +102,3 @@ throw new ModelError('Unable to build a correct dependency graph! Are there cycles?'); | ||
// keep the "artifact name" - needed for to.hdi sorting | ||
layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName] }))); | ||
layers.forEach(layer => layer.forEach(objName => result.push({ name: objName, sql: sql[objName], dependents: csn.definitions[objName][_dependents] }))); | ||
// attach sql artifacts which are not considered during the view sorting algorithm | ||
@@ -112,3 +111,6 @@ // --> this is the case for "ALTER TABLE ADD CONSTRAINT" statements, | ||
}); | ||
cleanup.forEach(fn => fn()); | ||
return result; | ||
}; |
@@ -315,6 +315,4 @@ 'use strict'; | ||
// Rename shorthand annotations within artifact or element 'node' according to a builtin | ||
// list. | ||
// Rename shorthand annotations within artifact or element 'node' according to a builtin list | ||
function renameShorthandAnnotations(node) { | ||
// FIXME: Verify this list - are they all still required? Do we need any more? | ||
const setMappings = { | ||
@@ -326,8 +324,8 @@ '@label': '@Common.Label', | ||
const renameMappings = { | ||
'@ValueList.entity': '@Common.ValueList.entity', | ||
'@ValueList.type': '@Common.ValueList.type', | ||
'@Capabilities.Deletable': '@Capabilities.DeleteRestrictions.Deletable', | ||
'@Capabilities.Insertable': '@Capabilities.InsertRestrictions.Insertable', | ||
'@Capabilities.Updatable': '@Capabilities.UpdateRestrictions.Updatable', | ||
'@Capabilities.Readable': '@Capabilities.ReadRestrictions.Readable', | ||
'@ValueList.entity': { val: '@Common.ValueList', op: 'entity' }, | ||
'@ValueList.type': { val: '@Common.ValueList', op: 'type' }, | ||
'@Capabilities.Deletable': { val: '@Capabilities.DeleteRestrictions', op: 'Deletable' }, | ||
'@Capabilities.Insertable': { val: '@Capabilities.InsertRestrictions', op: 'Insertable' }, | ||
'@Capabilities.Updatable': { val: '@Capabilities.UpdateRestrictions', op: 'Updatable' }, | ||
'@Capabilities.Readable': { val: '@Capabilities.ReadRestrictions', op: 'Readable' } | ||
}; | ||
@@ -337,2 +335,4 @@ | ||
const renameShortCuts = Object.keys(renameMappings); | ||
// Capabilities shortcuts have precedence over @readonly/@insertonly | ||
Object.keys(node).forEach( name => { | ||
@@ -342,9 +342,15 @@ if (!name.startsWith('@')) | ||
// Rename according to map above | ||
const renamePrefix = (name in renameMappings) ? name : renameShortCuts.find(p => name.startsWith(p + '.')); | ||
const renamePrefix = (name in renameMappings) | ||
? name | ||
: renameShortCuts.find(p => name.startsWith(p + '.')); | ||
if(renamePrefix) { | ||
renameAnnotation(node, name, name.replace(renamePrefix, renameMappings[renamePrefix])); | ||
} else { | ||
const mapping = renameMappings[renamePrefix]; | ||
renameAnnotation(node, name, name.replace(renamePrefix, `${mapping.val}.${mapping.op}`)); | ||
} | ||
else { | ||
// The two mappings have no overlap, so no need to check for second map if first matched. | ||
// Rename according to map above | ||
const setPrefix = (name in setMappings) ? name : setShortCuts.find(p => name.startsWith(p + '.')); | ||
const setPrefix = (name in setMappings) | ||
? name | ||
: setShortCuts.find(p => name.startsWith(p + '.') || name.startsWith(p + '#')); | ||
if(setPrefix) { | ||
@@ -354,34 +360,42 @@ setAnnotation(node, name.replace(setPrefix, setMappings[setPrefix]), node[name]); | ||
} | ||
}); | ||
// Special case: '@readonly' becomes a triplet of capability restrictions for entities, | ||
// but '@Core.Immutable' for everything else. | ||
if (!(node['@readonly'] && node['@insertonly'])) { | ||
if (name === '@readonly' && node[name]) { | ||
// Special case: '@readonly' becomes a triplet of capability restrictions for entities, | ||
// but '@Core.Computed' for everything else. | ||
// only if not both readonly/insertonly are true do the mapping | ||
if(!(node['@readonly'] && node['@insertonly'])) { | ||
if(node['@readonly']) { | ||
const setRO = (qualifier) => { | ||
if (node.kind === 'entity' || node.kind === 'aspect') { | ||
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false); | ||
setAnnotation(node, '@Capabilities.InsertRestrictions.Insertable', false); | ||
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false); | ||
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false); | ||
setAnnotation(node, `@Capabilities.InsertRestrictions${ qualifier ? '#' + qualifier : ''}.Insertable`, false); | ||
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false); | ||
} else { | ||
setAnnotation(node, '@Core.Computed', true); | ||
} | ||
}; | ||
setRO(undefined); | ||
} | ||
// @insertonly is effective on entities/queries only | ||
if (node['@insertonly'] && (node.kind === 'entity' || node.kind === 'aspect')) { | ||
const setIO = (qualifier) => { | ||
setAnnotation(node, `@Capabilities.DeleteRestrictions${ qualifier ? '#' + qualifier : ''}.Deletable`, false); | ||
setAnnotation(node, `@Capabilities.ReadRestrictions${ qualifier ? '#' + qualifier : ''}.Readable`, false); | ||
setAnnotation(node, `@Capabilities.UpdateRestrictions${ qualifier ? '#' + qualifier : ''}.Updatable`, false); | ||
} | ||
// @insertonly is effective on entities/queries only | ||
else if (name === '@insertonly' && node[name]) { | ||
if (node.kind === 'entity' || node.kind === 'aspect') { | ||
setAnnotation(node, '@Capabilities.DeleteRestrictions.Deletable', false); | ||
setAnnotation(node, '@Capabilities.ReadRestrictions.Readable', false); | ||
setAnnotation(node, '@Capabilities.UpdateRestrictions.Updatable', false); | ||
} | ||
} | ||
setIO(undefined); | ||
} | ||
// Only on element level: translate @mandatory | ||
if (name === '@mandatory' && node[name] && | ||
node.kind === undefined && node['@Common.FieldControl'] === undefined) { | ||
} | ||
// @Validation.Pattern is applicable to "Term" => node.kind === annotation | ||
if (node['@assert.format'] != null) | ||
setAnnotation(node, '@Validation.Pattern', node['@assert.format']); | ||
// Only on element level | ||
if(node.kind == null) { | ||
if (node['@mandatory']&& node['@Common.FieldControl'] === undefined) { | ||
setAnnotation(node, '@Common.FieldControl', { '#': 'Mandatory' }); | ||
} | ||
if (name === '@assert.format' && node[name] !== null) | ||
setAnnotation(node, '@Validation.Pattern', node['@assert.format']); | ||
if (name === '@assert.range' && node[name] !== null) { | ||
if (node['@assert.range'] != null) { | ||
if (Array.isArray(node['@assert.range']) && node['@assert.range'].length === 2) { | ||
@@ -392,3 +406,3 @@ setAnnotation(node, '@Validation.Minimum', node['@assert.range'][0]); | ||
} | ||
}); | ||
} | ||
} | ||
@@ -395,0 +409,0 @@ |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "4.0.0", | ||
"version": "4.0.2", | ||
"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
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
4510506
90227