@sap/cds-compiler
Advanced tools
Comparing version 1.45.0 to 1.46.4
@@ -9,2 +9,37 @@ # ChangeLog for cdx compiler and backends | ||
## Version 1.46.4 - 2020-11-26 | ||
### Fixed | ||
- Association to Join translation: Fix using forward association target as table alias in ON condition. | ||
## Version 1.46.2 - 2020-11-20 | ||
### Fixed | ||
- to.edm(x) Fix a bug in the alias calculation for key references in structured OData. | ||
## Version 1.46.0 - 2020-11-20 | ||
### Changed | ||
- to.edm(x): | ||
+ V4 structured key ref path aliases are now the basenames, colliding aliases are numbered. | ||
+ Lower level to `info` for "‹Term› is not applied" message if an annotation cannot be applied. | ||
- OData: | ||
+ Update vocabulary 'UI' | ||
+ Correctly handle `not null` during flattening. Only if the parent and all subelements in the chain | ||
are `not null`, make the corresponding flat leaf element `not null`. | ||
### Fixed | ||
- Do not consider events to be potential targets for implicit redirections: | ||
strange warnings for multiple projections or other strange errors disappear. | ||
- to.hdbcds/hdi/sql: | ||
+ Reject structured view parameters for HANA. | ||
+ Correctly handle `not null` during flattening. | ||
Only if the parent and all subelements in the chain are `not null`, make the corresponding flat leaf element `not null`. | ||
- to.edm(x): Render @assert.range enum annotations correctly (enum symbol as value and don't omit zero value). | ||
- Fixed CDS module resolution with option `newResolve` on Windows where a superfluous `\` was prepended to absolute paths. | ||
## Version 1.45.0 - 2020-10-30 | ||
@@ -11,0 +46,0 @@ |
@@ -17,3 +17,3 @@ # ChangeLog of Beta Features for cdx compiler and backends | ||
all generated texts entities additionally contain an element `language` | ||
which is an association to `sap.common.Languages` using element `local`. | ||
which is an association to `sap.common.Languages` using element `locale`. | ||
@@ -20,0 +20,0 @@ ## Version 1.43.0 |
@@ -6,2 +6,3 @@ 'use strict'; | ||
const alerts = require('../base/alerts'); | ||
const { getMessageFunction } = require('../base/messages'); | ||
@@ -12,2 +13,3 @@ // Check the annotation assignments (if any) of 'annotatable', possibly using annotation | ||
const { warning, signal } = alerts(model); | ||
const message = getMessageFunction(model); | ||
@@ -52,5 +54,59 @@ // Iterate all annotations of 'annotatable' | ||
// The annotation resolution will change in v2: Only absolute paths are allowed. | ||
// To make the upgrade easier, warn for cases where a non-absolute path is used. | ||
// Note that we cannot simply compare the first path segment or we would miss cases like: | ||
// | ||
// namespace test; | ||
// annotation test { test: { str: String; } } | ||
// @test.test.str: 'test' | ||
// entity E { ... } | ||
// | ||
// We also cannot _always_ compare `artifactInV1` with `artifactInV2` as the path may | ||
// be absolute and `path[0]` may refer to an USING, e.g. | ||
// | ||
// namespace ns; | ||
// annotation test { str: String; } | ||
// @(ns.test.str: 'some string') // path[0]._artifact !== xsn.definitions['ns'] | ||
// entity SomeEntity { key id : String; } | ||
// | ||
// So only if the first path segment resolves to an annotation, we check if the | ||
// representation changes. Or if the first path segment refers to a (temporary) | ||
// namespace, e.g. for: | ||
// | ||
// namespace test.test; | ||
// annotation test.SecondLevelAnnotation { int: Integer; } | ||
// @(test.SecondLevelAnnotation.int: 42) | ||
// entity OtherEntity { key id : String; } | ||
// | ||
// The example above could be replaced by an absolute path | ||
const oldName = anno.name.path[0].id; | ||
const artifactInV1 = anno.name.path[0]._artifact; | ||
const artifactInV2 = model.definitions[oldName]; | ||
if (artifactInV1.kind !== 'using' && artifactInV1 !== artifactInV2) { | ||
const loc = anno.name.path[0].location || anno.name.location; | ||
const canBeReplaced = !(annoDecl.name.absolute.startsWith(oldName)); | ||
// The used annotation path in the CDS source. | ||
let oldFullName = anno.name.path.map(step => step && step.id).join('.'); | ||
// Remove the element path because we only check for definition resolution. | ||
// Note that elementDecl may be an entity or other artifact at this stage. | ||
if (elementDecl && elementDecl.name.element) { | ||
const elementSuffix = elementDecl.name.element.length; | ||
oldFullName = oldFullName.slice(0, oldFullName.length - elementSuffix - 1); | ||
} | ||
message('anno-name-resolution', loc, annotatable, | ||
{ | ||
'#': canBeReplaced ? 'replace' : 'std', | ||
anno: oldFullName, | ||
alias: '@' + annoDecl.name.absolute | ||
}, 'Warning', | ||
{ | ||
std: 'The annotation $(ALIAS), not $(ANNO) is used with cds-compiler v1.x', | ||
replace: 'The annotation $(ALIAS), not $(ANNO) is used with cds-compiler v1.x; replace the annotation accordingly if this is really intended' | ||
} | ||
); | ||
} | ||
// Must be an annotation if found | ||
if (annoDecl.kind !== 'annotation') { | ||
signal(warning`"${annoDecl.name.absolute}" is a ${annoDecl.kind}, not an annotation`, anno.location || anno.name.location); | ||
// no warning anymore => behavior changes in v2 | ||
return; | ||
@@ -57,0 +113,0 @@ } |
@@ -26,3 +26,3 @@ 'use strict'; | ||
// TODO: Check if the on-condition only references things inside of the .items | ||
this.signal(this.error`Unmanaged associations in "array of" or "many" are not allowed.`, member.$path) | ||
this.error(null, member.$path, `Unmanaged associations in "array of" or "many" are not allowed.`) | ||
} | ||
@@ -29,0 +29,0 @@ } |
@@ -10,3 +10,3 @@ 'use strict'; | ||
if(TableUdfCv.length > 1) | ||
this.signal(this.error`Annotations ${TableUdfCv.join(', ')} cannot be used in combination`, path); | ||
this.error(null, path, `Annotations ${TableUdfCv.join(', ')} cannot be used in combination`); | ||
} | ||
@@ -13,0 +13,0 @@ } |
@@ -19,3 +19,3 @@ 'use strict'; | ||
if(i>1) | ||
this.signal(this.error`Illegal number of unary '+/-' operators`, path); | ||
this.error(null, path, `Illegal number of unary '+/-' operators`); | ||
} | ||
@@ -27,3 +27,3 @@ } | ||
if(member.default && prop === 'params' && this.csn.options.toHana) { | ||
this.signal(this.error`Parameter default values are not supported in HANA CDS`, path); | ||
this.error(null, path, `Parameter default values are not supported in HANA CDS`); | ||
} | ||
@@ -30,0 +30,0 @@ } |
@@ -42,3 +42,3 @@ 'use strict'; | ||
if(elementCount === 0){ | ||
this.signal(this.error`Empty structured types/elements must not be used as foreign keys.`, member.$path); | ||
this.error(null, member.$path, `Empty structured types/elements must not be used as foreign keys.`); | ||
} | ||
@@ -50,5 +50,5 @@ } | ||
if(mem.items){ | ||
this.signal(this.error`Array-like properties must not be foreign keys`, member.$path); | ||
this.error(null, member.$path, `Array-like properties must not be foreign keys`); | ||
} else if(isUnmanagedAssoc(mem)){ | ||
this.signal(this.error`Unmanaged association must not be a foreign key`, member.$path); | ||
this.error(null, member.$path, `Unmanaged association must not be a foreign key`); | ||
} else if(mem.keys){ | ||
@@ -55,0 +55,0 @@ handleAssociation(mem); |
@@ -53,3 +53,3 @@ 'use strict'; | ||
if(!mem.target && mem.targetAspect && typeof mem.targetAspect !== 'string') | ||
this.signal(this.error`Types with anonymous aspect compositions cannot be used.`, member.$path); | ||
this.error(null, member.$path, `Types with anonymous aspect compositions cannot be used.`); | ||
} else if(mem.elements) { | ||
@@ -56,0 +56,0 @@ handleStructured(mem, assertNoAnonymousAspectComposition); |
@@ -114,6 +114,8 @@ 'use strict'; | ||
for(let j = 0; j < _links.length-1; j++){ | ||
const csnPath = path.concat(['on', i, 'ref', j]); | ||
if(_links[j].art.target && !((_links[j].art === member) || ref[j] === '$self' || ref[j] === '$projection' || (validDollarSelf && j === _links.length - 1))){ | ||
if(_links[j].art.on){ | ||
// It's an unmanaged association - traversal is always forbidden | ||
this.signal(this.error`ON-Conditions can not follow unmanaged associations, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`, path.concat(['on', i, 'ref', j])); | ||
this.error(null, csnPath, `ON-Conditions can not follow unmanaged associations, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`); | ||
} else { | ||
@@ -123,3 +125,3 @@ // It's a managed association - access of the foreign keys is allowed | ||
if(!_links[j].art.keys.some(ref => ref.ref[0] === nextRef)){ | ||
this.signal(this.error`ON-Conditions can only follow managed associations to the foreign keys of the managed association, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i, 'ref', j])); | ||
this.error(null, csnPath, `ON-Conditions can only follow managed associations to the foreign keys of the managed association, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`); | ||
} | ||
@@ -130,7 +132,7 @@ } | ||
if(ref[j].where){ | ||
this.signal(this.error`ON-Conditions must not contain filters, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`, path.concat(['on', i, 'ref', j])); | ||
this.error(null, csnPath, `ON-Conditions must not contain filters, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`); | ||
} | ||
if(ref[j].args){ | ||
this.signal(this.error`ON-Conditions must not contain parameters, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`, path.concat(['on', i, 'ref', j])); | ||
this.error(null, csnPath, `ON-Conditions must not contain parameters, step "${logReady(ref[j])}" of path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}.`); | ||
} | ||
@@ -149,6 +151,8 @@ } | ||
/* 2) */ (_art.target && validDollarSelf))) { | ||
this.signal(this.error`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i,'ref',ref.length-1])) | ||
this.error(null, path.concat(['on', i,'ref',ref.length-1]), | ||
`The last path of an on-condition must be a scalar value, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`) | ||
} | ||
else if(_art.items){ | ||
this.signal(this.error`ON-Conditions can not use array-like elements, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`, path.concat(['on', i, 'ref', ref.length-1])); | ||
this.error(null, path.concat(['on', i, 'ref', ref.length-1]), | ||
`ON-Conditions can not use array-like elements, path ${ref.map(ps => `"${logReady(ps)}"`).join('.')}`); | ||
} | ||
@@ -155,0 +159,0 @@ } |
@@ -132,3 +132,3 @@ 'use strict'; | ||
*/ | ||
function csn2annotationEdm(csn, serviceName, options=undefined) { | ||
function csn2annotationEdm(csn, edm, serviceName, options=undefined) { | ||
@@ -226,18 +226,34 @@ if(!options) | ||
// generate the edmx "frame" around the annotations | ||
let schema = new Edm.Schema(v, serviceName, serviceName, undefined, g_annosArray, false); | ||
let service = new Edm.DataServices(v, schema); | ||
/** @type {object} */ | ||
let edm = new Edm.Edm(v, service); | ||
// edm is undefined in testODataAnnotations.js | ||
if(edm !== undefined) { | ||
// distribute edm:Annotations into the schemas | ||
// Create list of full qualified schema names (ServiceName[.subschema]) | ||
// Sort in reverse order for longest match | ||
const fqSchemaNames = Object.keys(edm._service._schemas).sort((a,b) => b.length-a.length); | ||
// Distribute each anno into Schema | ||
g_annosArray.forEach(annos => { | ||
const carrierName = annos.Target; | ||
let targetSchema = fqSchemaNames.reduce((rc, sn) => !rc && carrierName && carrierName.startsWith(sn + '.') ? rc = sn : rc, undefined); | ||
// if no target schema has been found, it's a service annotation that applies to the service schema | ||
if(targetSchema === undefined) | ||
targetSchema = serviceName; | ||
if(targetSchema) { | ||
if(targetSchema !== serviceName) { | ||
annos.Target = annos.Target.replace(serviceName + '.', ''); | ||
} | ||
edm._service._schemas[targetSchema]._annotations.push(annos); | ||
} | ||
}); | ||
// add references for the used vocabularies | ||
knownVocabularies.forEach(n => { | ||
if(vocabularyDefinitions[n].used) { | ||
let r = new Edm.Reference(v, vocabularyDefinitions[n].ref); | ||
r.append(new Edm.Include(v, vocabularyDefinitions[n].inc)) | ||
edm._defaultRefs.push(r); | ||
} | ||
}) | ||
// add references for the used vocabularies | ||
knownVocabularies.forEach(n => { | ||
if(vocabularyDefinitions[n].used) { | ||
let r = new Edm.Reference(v, vocabularyDefinitions[n].ref); | ||
r.append(new Edm.Include(v, vocabularyDefinitions[n].inc)) | ||
edm._defaultRefs.push(r); | ||
} | ||
}) | ||
} | ||
return edm; | ||
return g_annosArray; | ||
@@ -486,3 +502,3 @@ //------------------------------------------------------------------------------------------------- | ||
// find last . in name and insert "EntityContainer/" | ||
alternativeEdmTargetName = (carrier.entitySetName || edmTargetName).replace(/\.(?=[^.]*$)/, '.EntityContainer/'); | ||
alternativeEdmTargetName = (carrier.$entitySetName || edmTargetName).replace(/\.(?=[^.]*$)/, '.EntityContainer/'); | ||
hasAlternativeCarrier = carrier.$hasEntitySet; | ||
@@ -635,4 +651,5 @@ } | ||
if(!addAnnotationFunc(anno, dictTerm && dictTerm.AppliesTo)) { | ||
if(dictTerm && dictTerm.AppliesTo) | ||
message(warning, context, 'Term "' + fullTermName + '" is not applied (AppliesTo="' + dictTerm.AppliesTo.join(' ') + '")'); | ||
if(dictTerm && dictTerm.AppliesTo) { | ||
message(info, context, 'Term "' + fullTermName + '" is not applied (AppliesTo="' + dictTerm.AppliesTo.join(' ') + '")'); | ||
} | ||
} | ||
@@ -639,0 +656,0 @@ } |
@@ -123,13 +123,32 @@ 'use strict'; | ||
options.serviceName = serviceCsn.name; | ||
options.fqSchemaXRef = [serviceCsn.name]; | ||
const fqSchemaXRef = [serviceCsn.name]; | ||
options.whatsMySchemaName = function(n) { | ||
return fqSchemaXRef.reduce((rc, sn) => !rc && n && n.startsWith(sn + '.') ? rc = sn : rc, undefined); | ||
} | ||
const schemas = { [serviceCsn.name]: { name: serviceCsn.name, fqName: serviceCsn.name, _csn: serviceCsn, container: true, definitions: Object.create(null) } }; | ||
Object.keys(csn.definitions).reduce((schemas, fqName) => { | ||
const art = csn.definitions[fqName]; | ||
const schemas = { | ||
[serviceCsn.name]: { | ||
name: serviceCsn.name, | ||
fqName: serviceCsn.name, | ||
_csn: serviceCsn, | ||
container: true, | ||
definitions: Object.create(null) | ||
} | ||
}; | ||
const fqNameToSchema = { [serviceCsn.name]: schemas[serviceCsn.name] }; | ||
Object.entries(csn.definitions).reduce((schemas, [fqName, art]) => { | ||
// add sub schemas | ||
if(fqName.startsWith(serviceCsn.name + '.') && art.kind === 'context') { | ||
options.fqSchemaXRef.push(fqName); | ||
fqSchemaXRef.push(fqName); | ||
// strip the toplevel service schema name | ||
const name = fqName.replace(serviceCsn.name + '.', ''); | ||
schemas[name] = { name, fqName, _csn: art, container: false, definitions: Object.create(null) }; | ||
schemas[name] = { | ||
name, | ||
fqName, | ||
_csn: art, | ||
container: false, | ||
definitions: Object.create(null) | ||
}; | ||
fqNameToSchema[fqName] = schemas[name]; | ||
} | ||
@@ -139,12 +158,11 @@ return schemas; | ||
// sort schemas in reverse order to allow longest match | ||
fqSchemaXRef.sort((a,b) => b.length-a.length); | ||
// fill the schemas, unfortunately this can't be done in one step | ||
// as all possible prefix combinations must be known in fqSchemaXRef | ||
Object.keys(csn.definitions).reduce((schemas, name) => { | ||
const art = csn.definitions[name]; | ||
Object.entries(csn.definitions).reduce((schemas, [name, art]) => { | ||
// Identify service members by their definition name only, this allows | ||
// to let the internal object.name have the sub-schema name. | ||
let schemaName = options.fqSchemaXRef.reduce((a, n) => { | ||
if(name.startsWith( n + '.')) a = n; | ||
return a; }, undefined); | ||
let schemaName = options.whatsMySchemaName(name); | ||
@@ -186,7 +204,6 @@ if(schemaName && art.kind !== 'context') { | ||
*/ | ||
const references = Object.keys(csn.definitions).reduce((references, fqName) => { | ||
const art = csn.definitions[fqName]; | ||
const references = Object.entries(csn.definitions).reduce((references, [fqName, art]) => { | ||
// add references | ||
if(fqName.startsWith(serviceCsn.name + '.') && art.kind === 'reference') { | ||
options.fqSchemaXRef.push(fqName); | ||
fqSchemaXRef.push(fqName); | ||
references.push(art); | ||
@@ -197,12 +214,13 @@ } | ||
fqSchemaXRef.sort((a,b) => b.length-a.length); | ||
// bring the schemas in alphabetical order, service first, root last | ||
const schemaNames = Object.keys(schemas).filter(n => n !== 'root' && n !== serviceCsn.name).sort(); | ||
schemaNames.splice(0,0, serviceCsn.name); | ||
const sortedSchemaNames = Object.keys(schemas).filter(n => n !== 'root' && n !== serviceCsn.name).sort(); | ||
sortedSchemaNames.splice(0,0, serviceCsn.name); | ||
if(schemas.root) | ||
schemaNames.push('root'); | ||
sortedSchemaNames.push('root'); | ||
// finally create the schemas and register them in the service. | ||
schemaNames.forEach(name => { | ||
sortedSchemaNames.forEach(name => { | ||
const schema = schemas[name]; | ||
service.append(createSchema(schema)); | ||
service.registerSchema(schema.fqName, createSchema(schema)); | ||
}); | ||
@@ -218,8 +236,16 @@ | ||
else { | ||
const schema = { name: serviceCsn.name, _csn: serviceCsn, container: true, definitions: csn.definitions }; | ||
const schema = { | ||
name: serviceCsn.name, | ||
fqName: serviceCsn.name, | ||
_csn: serviceCsn, | ||
container: true, | ||
definitions: csn.definitions | ||
}; | ||
const LeadSchema = createSchema(schema); | ||
service.append(LeadSchema); | ||
service.registerSchema(schema.fqName, LeadSchema); | ||
} | ||
createAnnotations(edm); | ||
// Create annotations and distribute into Schemas | ||
translate.csn2annotationEdm(csn, edm, serviceCsn.name, options); | ||
return edm | ||
@@ -303,3 +329,3 @@ | ||
let EntityTypeName = entityCsn.name.replace(schemaNamePrefix, ''); | ||
let EntitySetName = (entityCsn.entitySetName || entityCsn.name).replace(schemaNamePrefix, ''); | ||
let EntitySetName = (entityCsn.$entitySetName || entityCsn.name).replace(schemaNamePrefix, ''); | ||
@@ -638,4 +664,4 @@ let [ properties, hasStream ] = createProperties(entityCsn); | ||
// differing set names (<T>Parameters => <T>, <T>Type => <T>Set) | ||
let fromEntitySet = ( navigationProperty._csn._parent.entitySetName || fromEntityType).replace(schemaNamePrefix, ''); | ||
let toEntitySet = (navigationProperty._targetCsn.entitySetName || toEntityType).replace(schemaNamePrefix, ''); | ||
let fromEntitySet = ( navigationProperty._csn._parent.$entitySetName || fromEntityType).replace(schemaNamePrefix, ''); | ||
let toEntitySet = (navigationProperty._targetCsn.$entitySetName || toEntityType).replace(schemaNamePrefix, ''); | ||
@@ -759,15 +785,4 @@ // from and to roles must be distinguishable (in case of self association entity E { toE: association to E; ... }) | ||
} | ||
function createAnnotations(edm) | ||
{ | ||
/** @type {object} */ | ||
let annoEdm = translate.csn2annotationEdm(csn, serviceCsn.name, options); | ||
for(let i = 0; i < annoEdm.getSchemaCount(); i++) | ||
{ | ||
edm.setAnnotations(annoEdm.getAnnotations(i), i); | ||
} | ||
edm._defaultRefs.push(...annoEdm._defaultRefs); | ||
} | ||
} | ||
} | ||
module.exports = { csn2edm, csn2edmAll }; |
@@ -300,6 +300,7 @@ // @ts-nocheck | ||
{ | ||
constructor(v, schema) | ||
constructor(v) | ||
{ | ||
super(v); | ||
this.append(schema); | ||
this.set( { _schemas: Object.create(null) } ); | ||
if(this.v2) | ||
@@ -311,2 +312,10 @@ this.setXml( { 'm:DataServiceVersion': '2.0' } ) | ||
registerSchema(fqName, schema) | ||
{ | ||
if(!this._schemas[fqName]) { | ||
this._schemas[fqName] = schema; | ||
super.append(schema); | ||
} | ||
} | ||
toJSONchildren(json) | ||
@@ -602,8 +611,4 @@ { | ||
if(options.fqSchemaXRef && this[typeName]) { | ||
let schemaName = options.fqSchemaXRef.reduce((a, n) => | ||
{ | ||
if(this[typeName].startsWith(n+'.')) a=n; | ||
return a; | ||
}, undefined); | ||
if(options.whatsMySchemaName && this[typeName]) { | ||
let schemaName = options.whatsMySchemaName(this[typeName]); | ||
if(schemaName && schemaName !== options.serviceName) { | ||
@@ -653,15 +658,34 @@ this[typeName] = this[typeName].replace(options.serviceName + '.', ''); | ||
this.append(...properties); | ||
const aliasXref = Object.create(null); | ||
csn.$edmKeyPaths.forEach((p, i) => { | ||
// If key is a path, prepare an alias for it | ||
if(p[0].indexOf('/') > -1) { | ||
// Limit Key length to 14+14+4 => 32 characters | ||
let alias = (this.Name + p[0]).replace(/\.|\//g, ''); | ||
if(alias.length > 28) { | ||
alias = alias.substr(0, 13)+ '__' +alias.substr(alias.length-13, alias.length); | ||
csn.$edmKeyPaths.forEach(p => { | ||
const [alias, ...tail] = p[0].split('/').reverse(); | ||
if(aliasXref[alias] === undefined) | ||
aliasXref[alias] = 0; | ||
else | ||
aliasXref[alias]++; | ||
// if it's a path, push the alias | ||
if(tail.length > 0) | ||
p.push(alias); | ||
}); | ||
csn.$edmKeyPaths.slice().reverse().forEach(p => { | ||
let alias = p[1]; | ||
if(alias) | ||
{ | ||
const c = aliasXref[alias]--; | ||
// Limit Key length to 32 characters | ||
if(c > 0) { | ||
if(alias.length > 28) { | ||
alias = alias.substr(0, 13)+ '__' +alias.substr(alias.length-13, alias.length); | ||
} | ||
alias = alias+'_'+c.toString().padStart(3,0); | ||
} | ||
alias = alias+'_'+i.toString().padStart(3,0); | ||
p.push(alias); | ||
else if(alias.length > 32) { | ||
alias = alias.substr(0, 15)+ '__' +alias.substr(alias.length-15, alias.length); | ||
} | ||
p[1] = alias; | ||
} | ||
}); | ||
if(csn.$edmKeyPaths && csn.$edmKeyPaths.length) | ||
@@ -1107,5 +1131,5 @@ this.set( { _keys: new Key(v, csn.$edmKeyPaths) } ); | ||
{ | ||
/* short notation for Edm.Boolean, Edm.String and Edm.Float, see: | ||
https://github.wdf.sap.corp/edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md | ||
*/ | ||
/* short notation for Edm.Boolean, Edm.String and Edm.Float, see internal project: | ||
edmx2csn-npm/edm-converters/blob/835d92a1aa6b0be25c56cef85e260c9188187429/lib/edmxV40ToJsonV40/README.md | ||
*/ | ||
case 'Edm.Boolean': | ||
@@ -1112,0 +1136,0 @@ v = (v=='true'?true:(v=='false'?false:v)); |
@@ -51,2 +51,3 @@ 'use strict'; | ||
getNamespaceOfArtifact, | ||
getContextOfArtifact, | ||
addStringAnnotationTo, | ||
@@ -211,2 +212,3 @@ getServiceName, | ||
let lastDotIdx = name.lastIndexOf('.'); | ||
if (lastDotIdx === -1) return undefined; | ||
while (model.definitions[name]) { | ||
@@ -221,3 +223,20 @@ if (model.definitions[name].kind === 'namespace') | ||
} | ||
/** | ||
* Return the context part of the artifact name if any. | ||
* @param {string} name Absolute name of artifact | ||
*/ | ||
function getContextOfArtifact(name) { | ||
let lastDotIdx = name.lastIndexOf('.'); | ||
while (model.definitions[name]) { | ||
if (model.definitions[name].kind === 'context') | ||
return name; | ||
lastDotIdx = name.lastIndexOf('.'); | ||
if (lastDotIdx === -1) return undefined; | ||
name = name.substring(0, lastDotIdx); | ||
} | ||
return undefined; | ||
} | ||
/** | ||
* Add an annotation with absolute name 'absoluteName' (including the at-sign) and string value 'theValue' to 'node' | ||
@@ -224,0 +243,0 @@ * |
@@ -257,4 +257,4 @@ | ||
} | ||
let tableName = quoteSqlId(absoluteCdsName(artifactName)); | ||
definitionsDuplicateChecker.addArtifact(tableName, art && art.$location, artifactName) | ||
let tableName = quoteSqlId(absoluteCdsName(artifactName), art.$location); | ||
definitionsDuplicateChecker.addArtifact(tableName, art.$location, artifactName) | ||
result += 'TABLE ' + tableName; | ||
@@ -273,3 +273,3 @@ result += ' (\n'; | ||
.filter(name => !art.elements[name]._ignore) | ||
.map(name => quoteSqlId(name)) | ||
.map(name => quoteSqlId(name, art.elements[name].$location)) | ||
.join(', '); | ||
@@ -323,3 +323,3 @@ let uniqueFields = Object.keys(art.elements).filter(name => art.elements[name].unique && !art.elements[name]._ignore) | ||
let tableName = quoteSqlId(absoluteCdsName(artifactName)); | ||
let tableName = quoteSqlId(absoluteCdsName(artifactName), art.$location); | ||
extensionsDuplicateChecker.addArtifact(tableName, art && art.$location, artifactName) | ||
@@ -390,4 +390,4 @@ | ||
} | ||
const quotedElementName = quoteSqlId(elementName); | ||
duplicateChecker.addElement(quotedElementName, elm && elm.$location, elementName); | ||
const quotedElementName = quoteSqlId(elementName, elm.$location); | ||
duplicateChecker.addElement(quotedElementName, elm.$location, elementName); | ||
@@ -431,3 +431,3 @@ let result = env.indent + quotedElementName + ' ' | ||
result += ' JOIN '; | ||
result += quoteSqlId(absoluteCdsName(elm.target)) + ' AS ' + quoteSqlId(elementName) + ` ON (`; | ||
result += quoteSqlId(absoluteCdsName(elm.target)) + ' AS ' + quoteSqlId(elementName, elm.$location) + ` ON (`; | ||
result += renderExpr(elm.on, env) + ')'; | ||
@@ -602,5 +602,8 @@ } | ||
} | ||
else if (getLastPartOf(result) != quoteSqlId(implicitAlias)) { | ||
// Render an artificial alias if the result would produce a different one | ||
result += ' AS ' + quoteSqlId(implicitAlias); | ||
else { | ||
const quotedAlias = quoteSqlId(implicitAlias); | ||
if (getLastPartOf(result) != quotedAlias) { | ||
// Render an artificial alias if the result would produce a different one | ||
result += ' AS ' + quotedAlias; | ||
} | ||
} | ||
@@ -706,3 +709,3 @@ return result; | ||
env._artifact = art; | ||
let viewName = quoteSqlId(absoluteCdsName(artifactName)); | ||
let viewName = quoteSqlId(absoluteCdsName(artifactName), art.$location); | ||
definitionsDuplicateChecker.addArtifact(viewName, art && art.$location, artifactName) | ||
@@ -848,5 +851,5 @@ let result = 'VIEW ' + viewName; | ||
} | ||
let typeName = quoteSqlId(absoluteCdsName(artifactName)); | ||
const typeName = quoteSqlId(absoluteCdsName(artifactName), art.$location); | ||
definitionsDuplicateChecker.addArtifact(typeName, art && art.$location, artifactName) | ||
let result = 'TYPE ' + quoteSqlId(absoluteCdsName(artifactName)) + ' AS TABLE (\n'; | ||
let result = 'TYPE ' + typeName + ' AS TABLE (\n'; | ||
let childEnv = increaseIndent(env); | ||
@@ -1208,3 +1211,3 @@ if (art.elements) { | ||
// Complain about names that collide with known SQL keywords or functions | ||
function quoteSqlId(name) { | ||
function quoteSqlId(name, location=null) { | ||
if (options.toSql.dialect === 'sqlite' && keywords.sqlite.includes(name.toUpperCase())) { | ||
@@ -1215,3 +1218,3 @@ // Sanity check | ||
} | ||
signal(warning`The identifier "${name}" is a SQLite keyword`); | ||
signal(warning`The identifier "${name}" is a SQLite keyword`, location); | ||
} | ||
@@ -1221,3 +1224,3 @@ if (options.toSql.names === 'plain') { | ||
if (keywords.hana.includes(name.toUpperCase())) { | ||
signal(warning`The identifier "${name}" is a HANA keyword`); | ||
signal(warning`The identifier "${name}" is a HANA keyword`, location); | ||
} | ||
@@ -1224,0 +1227,0 @@ } |
@@ -5,5 +5,5 @@ 'use strict'; | ||
const { handleMessages } = require('../base/messages'); | ||
const { setProp } = require('../base/model'); | ||
const { setProp, isBetaEnabled } = require('../base/model'); | ||
const transformUtils = require('./transformUtilsNew'); | ||
const { mergeOptions, copyAnnotations } = require('../model/modelUtils'); | ||
const { mergeOptions } = require('../model/modelUtils'); | ||
const { getUtils, | ||
@@ -26,7 +26,8 @@ cloneCsn, | ||
const typesExposure = require('./odata/typesExposure'); | ||
const { isArtifactInSomeService, getServiceOfArtifact, isLocalizedArtifactInService } = require('./odata/utils'); | ||
const typesExposure = require('./odata/typesExposure'); | ||
const ReferenceFlattener = require('./odata/referenceFlattener'); | ||
const structureFlattener = require('./odata/structureFlattener'); | ||
const processForeignKeys = require('./odata/foreignKeys'); | ||
const { flattenCSN } = require('./odata/structureFlattener'); | ||
const processForeignKeys = require('./odata/generateForeignKeyElements'); | ||
const expandStructKeysInAssociations = require('./odata/expandStructKeysInAssociations'); | ||
@@ -93,4 +94,3 @@ // Transformation for ODATA. Expects a CSN 'inputModel', processes it for ODATA. | ||
const { error, warning, info, signal } = alerts(csn, options); | ||
const { error: _error, warning: _warning, info: _info } = alerts.makeMessageFunction(csn, options); | ||
const { error, warning, info } = alerts.makeMessageFunction(csn, options); | ||
@@ -163,3 +163,3 @@ // the new transformer works only with new CSN | ||
if (validKey.length && !(validFrom.length && validTo.length)) { | ||
_error(null, path, '@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing'); | ||
error(null, path, '@cds.valid.key was used but @cds.valid.from and @cds.valid.to are missing'); | ||
} | ||
@@ -184,3 +184,3 @@ }); | ||
validator(csn, { | ||
error, warning, info, signal, inspectRef, effectiveType, artifactRef, csn, | ||
error, warning, info, inspectRef, effectiveType, artifactRef, csn, | ||
}, | ||
@@ -209,3 +209,3 @@ /* Member Validators */ [ validateOnCondition, validateForeignKeys, validateAssociationsInArrayOf, validateDefaultValues ], | ||
if (!finalTypeDef.type) { | ||
_error(null, ['definitions', defName], { name: defName }, `${ defName } has no final type`); | ||
error(null, ['definitions', defName], { name: defName }, `${ defName } has no final type`); | ||
return; | ||
@@ -218,3 +218,3 @@ } | ||
} catch (ex) { | ||
_error(null, ['definitions', defName], { name: defName }, `Final base type of ${ defName } not found`); | ||
error(null, ['definitions', defName], { name: defName }, `Final base type of ${ defName } not found`); | ||
return | ||
@@ -275,3 +275,3 @@ } | ||
if (!structuredOData) { | ||
structureFlattener(csn, { csnUtils, cloneCsn, error, signal, forEachDefinition, setProp, referenceFlattener, copyAnnotations }) | ||
flattenCSN(csn, csnUtils, referenceFlattener, error); | ||
} | ||
@@ -283,4 +283,5 @@ | ||
// Expose user-defined types and anonymous types | ||
typesExposure(csn, services, options, csnUtils, signal, referenceFlattener); | ||
if (!(structuredOData && (isBetaEnabled(options, 'odataProxies') && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)))) | ||
// Expose user-defined types and anonymous types | ||
typesExposure(csn, services, options, csnUtils, { error }, referenceFlattener); | ||
@@ -291,5 +292,8 @@ // flatten references, attach new paths | ||
// Process associations - expand, generate foreign keys | ||
let flatKeys = !structuredOData || (structuredOData && options.toOdata.odataForeignKeys); | ||
processForeignKeys(csn, flatKeys, { referenceFlattener, csnUtils, transformers }) | ||
// Process associations | ||
// 1. expand structured foreign keys, rewrite the 'ref' for such keys | ||
expandStructKeysInAssociations(csn, referenceFlattener, csnUtils); | ||
// 2. generate foreign keys for managed associations | ||
processForeignKeys(csn, flatKeys, referenceFlattener, csnUtils, transformers, error); | ||
@@ -318,3 +322,3 @@ // Flatten on-conditions in unmanaged associations | ||
if (!isArtifactInSomeService(defName, services)) { | ||
_warning(null, ['definitions', defName], { art: defName }, | ||
warning(null, ['definitions', defName], { art: defName }, | ||
`Ignoring annotation "@odata.draft.enabled" because artifact "${ defName }" is not part of a service`); | ||
@@ -345,3 +349,3 @@ } | ||
if (def.kind === 'type' && options.toOdata.version === 'v2') { | ||
_warning(null, path, | ||
warning(null, path, | ||
`"${defName}.${memberName}": Structured types must not contain associations for OData V2`); | ||
@@ -353,3 +357,3 @@ } | ||
if (options.toOdata.version === 'v2') { | ||
_error(null, path, | ||
error(null, path, | ||
`"${defName}.${memberName}": Element must not be an "array of" for OData V2`); | ||
@@ -361,3 +365,3 @@ } | ||
// if (member.items.elements && !member.items.type) { | ||
// signal(error`"${defName}.${memberName}": Element must not be an "array of anonymous type"`, path); | ||
// error(null, path, `"${defName}.${memberName}": Element must not be an "array of anonymous type"`); | ||
// } | ||
@@ -373,7 +377,7 @@ } | ||
// (4.5) https://github.wdf.sap.corp/cdx/cds-compiler/issues/837 | ||
// (4.5) cdx/cds-compiler#837 | ||
// add check here for @Analytics.Measure and @Aggregation.default | ||
// @Analytics has scope element | ||
if (member['@Analytics.Measure'] && !member['@Aggregation.default']) { | ||
_info(null, path, // ['definitions', defName, 'elements', memberName] | ||
info(null, path, // ['definitions', defName, 'elements', memberName] | ||
`'@Analytics.Measure' expects '@Aggregation.default' to be assigned as well in element '${defName}.${memberName}'`, | ||
@@ -424,3 +428,3 @@ ); | ||
if (illV2Prefix.test(elemName)) { | ||
_error(null, ['definitions', defName, 'elements', elemName], | ||
error(null, ['definitions', defName, 'elements', elemName], | ||
`"${defName}.${elemName}: Element name must not begin with '${elemName[0]}' for OData V2`); | ||
@@ -440,3 +444,3 @@ } | ||
if (mediaTypes.length > 1) { | ||
_error(null, ['definitions', defName], `"${defName}: Multiple elements [${mediaTypes.map(e => e[0]).join(', ')}] annotated with '@Core.MediaType', OData V2 allows only one`); | ||
error(null, ['definitions', defName], `"${defName}: Multiple elements [${mediaTypes.map(e => e[0]).join(', ')}] annotated with '@Core.MediaType', OData V2 allows only one`); | ||
} | ||
@@ -449,3 +453,3 @@ } | ||
if (!allowedTypes.includes(e[1].type)) { | ||
_error(null, ['definitions', defName, 'elements', e[0]], `"${defName}.${e[0]}": Element annotated with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`); | ||
error(null, ['definitions', defName, 'elements', e[0]], `"${defName}.${e[0]}": Element annotated with '@Core.MediaType' must be of either type "${allowedTypes.join(', ')}"`); | ||
} | ||
@@ -531,3 +535,3 @@ }); | ||
rewriteCapabilities = false; | ||
_warning(null, path, '"@readonly" and "@insertonly" cannot be assigned in combination'); | ||
warning(null, path, '"@readonly" and "@insertonly" cannot be assigned in combination'); | ||
} | ||
@@ -581,3 +585,7 @@ for (let name in node) { | ||
let result = { '@Core.SymbolicName': enumName }; | ||
if (node.enum[enumName].val) result.Value = node.enum[enumName].val; | ||
if (node.enum[enumName].val !== undefined) | ||
result.Value = node.enum[enumName].val; | ||
else if(node.type && node.type === 'cds.String') | ||
// the symbol is used as value only for type 'cds.String' | ||
result.Value = enumName; | ||
return result; | ||
@@ -599,3 +607,3 @@ }); | ||
if (typeof node[name] !== 'boolean' && typeof node[name] !== 'string') { | ||
_warning(null, defPath, { name }, `Annotation "${ name }" must have a string or boolean value`); | ||
warning(null, defPath, { name }, `Annotation "${ name }" must have a string or boolean value`); | ||
} | ||
@@ -674,3 +682,3 @@ } | ||
if (keys.length !== 1) { | ||
_warning(null, ['definitions', artifactName], `"${artifactName}": "@odata.draft.enabled" - Entity should expose exactly one key element`); | ||
warning(null, ['definitions', artifactName], `"${artifactName}": "@odata.draft.enabled" - Entity should expose exactly one key element`); | ||
} | ||
@@ -682,3 +690,3 @@ | ||
if (uuidCount === 0) { | ||
_warning(null, ['definitions', artifactName], `"${artifactName}": "@odata.draft.enabled" - Entity key element should be of type "cds.UUID"`); | ||
warning(null, ['definitions', artifactName], `"${artifactName}": "@odata.draft.enabled" - Entity key element should be of type "cds.UUID"`); | ||
} | ||
@@ -694,3 +702,3 @@ | ||
if (draftAdminDataProjection.kind !== 'entity' || !draftAdminDataProjection.elements['DraftUUID']) { | ||
_error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName }, | ||
error(null, ['definitions', draftAdminDataProjectionName], { name: draftAdminDataProjectionName }, | ||
`Generated entity "${ draftAdminDataProjectionName }" conflicts with existing artifact`); | ||
@@ -776,7 +784,7 @@ } | ||
if (hasBoolAnnotation(draftNode, '@odata.draft.enabled', true)) { | ||
_error(null, ['definitions', artifactName, 'elements', elemName], `"${artifactName}.${elemName}": Composition in draft-enabled entity cannot lead to another entity with "@odata.draft.enabled"`); | ||
error(null, ['definitions', artifactName, 'elements', elemName], `"${artifactName}.${elemName}": Composition in draft-enabled entity cannot lead to another entity with "@odata.draft.enabled"`); | ||
} | ||
// Ignore composition if not part of a service | ||
else if (!getServiceName(elem.target)) { | ||
_warning(null, ['definitions', artifactName, 'elements', elemName], `Target "${elem.target}" of composition "${artifactName}.${elemName}" cannot be a draft node because it is not part of a service`); | ||
warning(null, ['definitions', artifactName, 'elements', elemName], `Target "${elem.target}" of composition "${artifactName}.${elemName}" cannot be a draft node because it is not part of a service`); | ||
continue; | ||
@@ -783,0 +791,0 @@ } |
@@ -0,3 +1,45 @@ | ||
'use strict'; | ||
const { copyAnnotations } = require('../../model/modelUtils'); | ||
const { setProp } = require('../../base/model'); | ||
const { cloneCsn, forEachDefinition } = require('../../model/csnUtils'); | ||
// these functions are used for propagation of the annotations, collected along the path during flattening | ||
const { addAnnotationsForPropagationFromElement, propagateAnnotationsToElement, resetAnnotationsForRropagation } = function () { | ||
let toBePropagatedAnnotations = Object.create(null); | ||
return { | ||
addAnnotationsForPropagationFromElement: function (element) { | ||
copyAnnotations(element, toBePropagatedAnnotations); | ||
}, | ||
propagateAnnotationsToElement: function (element) { | ||
copyAnnotations(toBePropagatedAnnotations, element); | ||
}, | ||
resetAnnotationsForRropagation: function () { | ||
toBePropagatedAnnotations = Object.create(null); | ||
} | ||
} | ||
}(); | ||
// keep here the state of the 'notNull' attribute | ||
// this is needed because during flattening all the elements | ||
// along the chain need to be assigned with not null so | ||
// the resulting element to be not null as well | ||
const { isNotNull, setNotNull, setUpNotNull } = function () { | ||
let notNull = undefined; | ||
return { | ||
isNotNull: function () { | ||
return notNull; | ||
}, | ||
setNotNull: function (value) { | ||
notNull = value; | ||
}, | ||
setUpNotNull: function (element, isParentNotNull) { | ||
if (isParentNotNull && element.notNull) setNotNull(element.notNull); | ||
else if (isNotNull() && !element.notNull || (isNotNull() === false && element.notNull !== false)) setNotNull(undefined); | ||
} | ||
} | ||
}(); | ||
/** | ||
* During the OData transfromations in flat-mode, all structured elements will be flattened. | ||
* During the OData transformations in flat-mode, all structured elements will be flattened. | ||
* This module performs the complete flattening. | ||
@@ -9,69 +51,106 @@ * It also provides information to the reference flattener: elements produced for specific path in the CSN structure. | ||
* @param {*} csn CSN-object to flatten | ||
* @param {*} functions instances of utility functions | ||
* @param {*} csnUtils instances of utility functions | ||
*/ | ||
function flattenCSN(csn, functions) { | ||
const { forEachDefinition } = functions; | ||
forEachDefinition(csn, (def, _defName, _propertyName, path) => { | ||
flattenDefinition(def, path, functions); | ||
}) | ||
function flattenCSN(csn, csnUtils, referenceFlattener, signal) { | ||
forEachDefinition(csn, (def, defName, propertyName, path) => | ||
flattenDefinition(def, path, csnUtils, referenceFlattener, signal)); | ||
} | ||
/** | ||
* Flattens one single definition and all structures in it | ||
* Flattens one single definition and all structures in it. Modifies the definition in place. | ||
* @param {*} definition definition object to flatten | ||
* @param {*} definitionPath path in CSN object | ||
* @param {*} functions utility functions | ||
* @param {*} csnUtils utility functions | ||
*/ | ||
function flattenDefinition(definition, definitionPath, functions) { | ||
const { csnUtils, cloneCsn, error, signal, setProp, copyAnnotations } = functions; | ||
if (definition.kind !== 'entity' && definition.kind !== 'view') | ||
return; | ||
function flattenDefinition(definition, definitionPath, csnUtils, referenceFlattener, signal) { | ||
if (definition.kind !== 'entity' && definition.kind !== 'view') return; | ||
let referenceFlattener = functions.referenceFlattener; | ||
let { newFlatElements } = flattenStructure(definition, definitionPath, csnUtils, signal, referenceFlattener); | ||
// these functions are used for propagation of the annotations, collected along the path during flattening | ||
const { addAnnotationsForPropagationFromElement, propagateAnnotationsToElement, resetAnnotationsForRropagation } = function () { | ||
let toBePropagatedAnnotations = Object.create(null); | ||
return { | ||
addAnnotationsForPropagationFromElement: function (element) { | ||
copyAnnotations(element, toBePropagatedAnnotations); | ||
}, | ||
propagateAnnotationsToElement: function (element) { | ||
copyAnnotations(toBePropagatedAnnotations, element); | ||
}, | ||
resetAnnotationsForRropagation: function () { | ||
toBePropagatedAnnotations = Object.create(null); | ||
} | ||
referenceFlattener.attachPaths(newFlatElements, definitionPath.concat('elements')); | ||
definition.elements = newFlatElements; | ||
} // flattenDefinition | ||
/** | ||
* Flattenes structured element by calling element flattener for each structured child. | ||
* Returns a dictionary containing all the new elements for the given structure. | ||
* @param {*} struct the structure to flatten | ||
* @param {*} path the path of the structure in the CSN tree | ||
* @param {*} isTopLevelElement states if this is a top level element | ||
* @param {*} elementPathInStructure list of parent element names | ||
* @param {*} isKey true if this or the parent element is a key - will be propagated to all child elements | ||
*/ | ||
function flattenStructure(struct, path, csnUtils, error, referenceFlattener = undefined, elementPathInStructure = [], | ||
newFlatElements = Object.create(null), isTopLevelElement = true, isKey = false, propagateAnnotations = false, isParentNotNull = false) { | ||
isTopLevelElement ? resetAnnotationsForRropagation() : addAnnotationsForPropagationFromElement(struct); | ||
let generatedNewFlatElementsNames = []; // holds the names of all new child elements of the structure | ||
for (let elementName in struct.elements) { | ||
let element = struct.elements[elementName]; | ||
let currPath = path.concat('elements', elementName); | ||
if (isTopLevelElement) { | ||
isKey = element.key; | ||
setNotNull(element.notNull) | ||
} else { | ||
setUpNotNull(element, isParentNotNull); | ||
} | ||
}(); | ||
let newElements = {}; | ||
flattenStructure(definition, definitionPath); | ||
referenceFlattener.attachPaths(newElements,definitionPath.concat('elements')) | ||
definition.elements = newElements; | ||
// flat elements when structured and NOT empty (allow incomplete structures - cds-compiler#4337) | ||
if (csnUtils.isStructured(element) && !(element.elements && Object.keys(element.elements).length === 0)) { | ||
if (referenceFlattener) referenceFlattener.registerFlattenedElement(currPath, element.$path); | ||
addAnnotationsForPropagationFromElement(element); | ||
// if the child element is structured itself -> needs to be flattened | ||
const subStruct = element.elements ? element : csnUtils.getFinalBaseType(element.type); | ||
let result = flattenStructure(subStruct, currPath, csnUtils, error, referenceFlattener, elementPathInStructure.concat(elementName), newFlatElements, false, isKey || element.key, true, isNotNull()); | ||
generatedNewFlatElementsNames.push(...result.generatedNewFlatElementsNames); // accomulate names of produced elements | ||
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener | ||
let newElementName = elementPathInStructure.concat(elementName).join('_'); | ||
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.'); | ||
addNewElementToResult(element, newElementName, elementNameWithDots, currPath); | ||
} | ||
} | ||
if (referenceFlattener) { | ||
referenceFlattener.registerGeneratedElementsForPath(path, generatedNewFlatElementsNames); | ||
} | ||
return { newFlatElements, generatedNewFlatElementsNames }; | ||
// adds newly created element into the final dictionary of elements | ||
function addNewElement(element, elementName, elementNameWithDots, path, key, topLevel, propagateAnnotations) { | ||
if (newElements[elementName]) { | ||
signal(error`Generated element ${elementName} conflicts with other generated element`, path); | ||
return undefined; | ||
function addNewElementToResult(element, elementName, elementNameWithDots, path) { | ||
if (newFlatElements[elementName]) { | ||
error(null, path, `Generated element ${elementName} conflicts with other generated element`); | ||
} else { | ||
let newElement = createNewElement(element, key, elementNameWithDots, topLevel, propagateAnnotations); | ||
let newPath = definitionPath.concat('elements',elementName) | ||
newElements[elementName] = newElement; | ||
referenceFlattener.registerElementTransition(path,newPath); | ||
return newElement; | ||
let newElement = createNewElement(element, elementNameWithDots); | ||
newFlatElements[elementName] = newElement; | ||
generatedNewFlatElementsNames.push(elementName); | ||
if (referenceFlattener) { | ||
let newPath = path.slice(0, 2).concat('elements', elementName); | ||
referenceFlattener.registerElementTransition(path, newPath); | ||
let movedTo = referenceFlattener.getElementTransition(path) | ||
if (movedTo) { | ||
setProp(newElement, '$paths', [movedTo]); // moved always on top-level -> new $paths has only one path element | ||
} | ||
} | ||
} | ||
} // addNewElement | ||
} // addNewElementToResult | ||
// creates new element by copying the properties of the originating element | ||
function createNewElement(element, isKey, elementNameWithDots, topLevel, propagateAnnotations) { | ||
function createNewElement(element, elementNameWithDots) { | ||
let newElement = cloneCsn(element); | ||
if (propagateAnnotations) propagateAnnotationsToElement(newElement); | ||
if (isKey) | ||
newElement.key = true; | ||
if (!topLevel) { | ||
if (isNotNull() === undefined) delete newElement.notNull; | ||
if (isKey) newElement.key = true; | ||
if (!isTopLevelElement) { | ||
setProp(newElement, '$viaTransform', true); | ||
@@ -83,61 +162,4 @@ setProp(newElement, '_flatElementNameWithDots', elementNameWithDots); | ||
/** | ||
* Flattenes structured element by calling element flattener for each structured child. | ||
* @param {*} struct the structure to flatten | ||
* @param {*} path the path of the structure in the CSN tree | ||
* @param {*} isDefinition states if this is a top level element | ||
* @param {*} elementPathInStructure list of parent element names | ||
* @param {*} isKey true if this or the parent element is a key - will be propagated to all child elements | ||
*/ | ||
function flattenStructure(struct, path, isDefinition = true, elementPathInStructure = [], isKey = false, propagateAnnotations = false) { | ||
isDefinition ? resetAnnotationsForRropagation() : addAnnotationsForPropagationFromElement(struct); | ||
} // flattenStructure | ||
let resultingElementNames = []; // holds the names of all child elements of the structure | ||
for (let elementName in struct.elements) { | ||
let ikey = isKey; // parent element is key | ||
let element = struct.elements[elementName]; | ||
if (element.key) ikey = true; // current element is key | ||
let ipath = path.concat('elements', elementName); | ||
// flat elements when structured and NOT empty (allow incomplete structures - cds-compiler#4337) | ||
if (csnUtils.isStructured(element) && !(element.elements && Object.keys(element.elements).length === 0)) { | ||
let namesOfCreatedElements = flattenStructuredElement(element, elementPathInStructure.concat(elementName), ipath, ikey); | ||
resultingElementNames = resultingElementNames.concat(namesOfCreatedElements); // accomulate names of produced elements | ||
} else { // when we do not need to flat, this is scalar or empty (cds-compiler#4337) -> needs to be registered in referenceFlattener | ||
let newElementName = elementPathInStructure.concat(elementName).join('_'); | ||
let elementNameWithDots = elementPathInStructure.concat(elementName).join('.'); | ||
let newElement = addNewElement(element, newElementName, elementNameWithDots, ipath, ikey, isDefinition, propagateAnnotations); | ||
if(newElement) { | ||
resultingElementNames.push(newElementName); | ||
let movedTo = referenceFlattener.getElementTransition(ipath) | ||
if(movedTo) { | ||
setProp(newElement,'$paths',[movedTo]); // moved always on top-level -> new $paths has only one path element | ||
} | ||
} | ||
} | ||
} | ||
if (referenceFlattener) { | ||
referenceFlattener.registerGeneratedElementsForPath(path, resultingElementNames); | ||
} | ||
return resultingElementNames; | ||
} // flattenStructure | ||
// flattenes on single structured element by calling the structure flattener for it | ||
function flattenStructuredElement(element, elementPathInStructure, path, key) { | ||
if (referenceFlattener) | ||
referenceFlattener.registerFlattenedElement(path, element.$path); | ||
let elemType; | ||
if (!element.elements) { // structures do not have final base type | ||
elemType = csnUtils.getFinalBaseType(element.type); | ||
addAnnotationsForPropagationFromElement(element); | ||
} | ||
const struct = elemType ? elemType : element; | ||
//TODO what happens with 'path' if element has base type? | ||
return flattenStructure(struct, path, false, elementPathInStructure, key || element.key, true); | ||
} // flattenStructuredElement | ||
} // flattenDefinition | ||
module.exports = flattenCSN; | ||
module.exports = { flattenCSN, flattenStructure }; |
@@ -13,6 +13,17 @@ 'use strict'; | ||
module.exports = function (csn, services, options, csnUtils, signal, referenceFlattener) { | ||
/** | ||
* @param {CSN.Model} csn | ||
* @param {string[]} services | ||
* @param {CSN.Options} options | ||
* @param {*} csnUtils | ||
* @param {object} message message object with { error } function | ||
* @param {*} referenceFlattener | ||
*/ | ||
function typesExposure(csn, services, options, csnUtils, message, referenceFlattener = undefined) { | ||
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 && (isBetaEnabled(options, 'odataProxies') && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs)); | ||
// collect in this variable all the newly exposed types | ||
@@ -30,3 +41,3 @@ let exposedStructTypes = []; | ||
if (propertyName === 'elements') { | ||
exposeStructTypeOf(element, `${defName}.${elementName}`, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`, structuredOData, path); | ||
exposeStructTypeOf(element, `${defName}.${elementName}`, getServiceOfArtifact(defName, services), `${isMultiSchema ? defName : defName.replace(/\./g, '_')}_${elementName}`, structuredOData, path); | ||
// TODO: use the next line once the array of logic is reworked | ||
@@ -53,3 +64,3 @@ // exposeTypeOf(element, elementName, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`); | ||
if (csnUtils.isStructured(element)) { | ||
exposeStructTypeOf(element, elementName, serviceName, `${defNameWithoutServiceName(defName, serviceName).replace(/\./g, '_')}_${elementName}`, structuredOData, path); | ||
exposeStructTypeOf(element, elementName, 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 | ||
@@ -60,3 +71,3 @@ // exposeTypeOf(element, elementName, getServiceOfArtifact(defName, services), `${defName.replace(/\./g, '_')}_${elementName}`); | ||
if (csnUtils.getServiceName(defName) && !element.type && !element.items && !element.elements) { | ||
signal(signal.error`Element "${defName}.${elementName}" does not have a type: Elements of ODATA entities must have a type`, path); | ||
error(null, path, `Element "${defName}.${elementName}" does not have a type: Elements of ODATA entities must have a type`); | ||
} | ||
@@ -88,3 +99,4 @@ } | ||
exposedStructTypes.forEach(typeName => referenceFlattener.attachPaths(csn.definitions[typeName], ['definitions', typeName])) | ||
if (referenceFlattener) | ||
exposedStructTypes.forEach(typeName => referenceFlattener.attachPaths(csn.definitions[typeName], ['definitions', typeName])) | ||
@@ -109,4 +121,4 @@ // still WIP function | ||
* @param {Object} action | ||
* @param {String} actionName | ||
* @param {String} service | ||
* @param {String} actionName | ||
* @param {String} service | ||
*/ | ||
@@ -126,6 +138,6 @@ function exposeTypesOfAction(action, actionName, service, path) { | ||
* for a value of the 'node.type' property. | ||
* @param {Object} node | ||
* @param {String} memberName | ||
* @param {String} service | ||
* @param {String} artificialName | ||
* @param {Object} node | ||
* @param {String} memberName | ||
* @param {String} service | ||
* @param {String} artificialName | ||
*/ | ||
@@ -144,6 +156,6 @@ function exposeStructTypeOf(node, memberName, service, artificialName, deleteElems = structuredOData, path) { | ||
let typeDef = node.type ? csnUtils.getCsnDef(node.type) : /* structure|anonymous type */ node; | ||
let newTypeId = node.type ? `${node.type.replace(/\./g, '_')}` : artificialName; | ||
let newTypeId = node.type ? `${isMultiSchema ? node.type : node.type.replace(/\./g, '_')}` : artificialName; | ||
let newTypeFullName = | ||
(structuredOData && (isBetaEnabled(options, 'odataProxies') && (options.toOdata.odataProxies || options.toOdata.odataXServiceRefs))) | ||
? getNewTypeName(node.type || artificialNameWitoutService(artificialName, service), !node.type) | ||
isMultiSchema | ||
? getNewTypeNameInMultiSchema(node.type || artificialNameWitoutService(artificialName, service), !node.type) | ||
: `${service}.${newTypeId}`; | ||
@@ -163,2 +175,3 @@ | ||
if (node.$location) setProp(newType, '$location', node.$location); | ||
setProp(newType, '$exposedBy', 'typeExposure'); | ||
@@ -168,3 +181,3 @@ // Recurse into elements of 'type' (if any) and expose them as well (is needed) | ||
if (node.elements && node.elements[elemName].$location) setProp(newType.elements[elemName], '$location', node.elements[elemName].$location); | ||
exposeStructTypeOf(newType.elements[elemName], memberName, service, `${newTypeId}_${elemName}`, deleteElems, path); | ||
exposeStructTypeOf(newType.elements[elemName], memberName, service, isMultiSchema ? `${newTypeFullName}_${elemName}` : `${newTypeId}_${elemName}`, deleteElems, path); | ||
} | ||
@@ -181,3 +194,3 @@ typeDef.kind === 'type' ? copyAnnotations(typeDef, newType) : copyAnnotations(node, newType); | ||
* 2. When we have structured element (the object has property 'elements') | ||
* @param {Object} node | ||
* @param {Object} node | ||
*/ | ||
@@ -195,13 +208,14 @@ function isExposableStructure(node) { | ||
function getNewTypeName(typeName, isAnonym = false) { | ||
if (isArtifactInSomeService(typeName, services) && !isAnonym) { | ||
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 | ||
let crossServiceName = getServiceOfArtifact(typeName, services); | ||
let typeWithoutServiceName = defNameWithoutServiceName(typeName, crossServiceName); | ||
if (typeWithoutServiceName.startsWith('external.')) { | ||
if (!csn.definitions[`${service}.external`]) | ||
csn.definitions[`${service}.external`] = { kind: 'context' }; | ||
return `${service}.${typeWithoutServiceName}`; | ||
} | ||
let crossServTypeDefName = `${service}.${crossServiceName}`; | ||
const crossServiceName = getServiceOfArtifact(typeName, services); | ||
const typeWithoutServiceName = defNameWithoutServiceName(typeName, crossServiceName); | ||
const crossServTypeDefName = `${service}.${crossServiceName}`; | ||
// is there such subContext already, if not -> create one | ||
@@ -213,8 +227,12 @@ if (!csn.definitions[crossServTypeDefName]) | ||
} else { | ||
let typeNamespace = csnUtils.getNamespaceOfArtifact(typeName); | ||
let contextName = typeNamespace ?`${service}.${typeNamespace}` : `${service}.root`; | ||
if (!csn.definitions[`${contextName}`]) | ||
csn.definitions[`${contextName}`] = { kind: 'context' }; | ||
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' }; | ||
// return the new type name | ||
return `${contextName}.${nameWithoutNamespace(typeName).replace(/\./g, '_')}`; | ||
return `${newContextName}.${contextOfArt ? | ||
defNameWithoutServiceName(typeName, contextOfArt).replace(/\./g, '_') | ||
: nameWithoutNamespace(typeName).replace(/\./g, '_')}`; | ||
} | ||
@@ -228,5 +246,5 @@ } | ||
* 'parentName' is used for error reporting.x | ||
* @param {String} typeName | ||
* @param {Object} elements | ||
* @param {String} parentName | ||
* @param {String} typeName | ||
* @param {Object} elements | ||
* @param {String} parentName | ||
*/ | ||
@@ -238,3 +256,3 @@ function exposeStructType(typeName, elements, parentName, path) { | ||
if (!exposedStructTypes.includes(typeName)) { | ||
signal(signal.error`Cannot create artificial type "${typeName}" for "${parentName}" because the name is already used`, path); | ||
error(null, path, `Cannot create artificial type "${typeName}" for "${parentName}" because the name is already used`); | ||
return null; | ||
@@ -255,3 +273,3 @@ } | ||
const path = ['definitions', typeName, 'elements', elemName]; | ||
signal(signal.error`"${elemName}": Element name conflicts with existing element`, path); | ||
error(null, path, `"${elemName}": Element name conflicts with existing element`); | ||
} | ||
@@ -307,3 +325,3 @@ let cloned = cloneCsn(elements[elemName]); | ||
if (!exposedStructTypes.includes(typeId)) { | ||
signal(signal.error`Cannot create artificial type "${typeId}" because the name is already used`, newType.$path); | ||
error(null, newType.$path, `Cannot create artificial type "${typeId}" because the name is already used`); | ||
} | ||
@@ -340,6 +358,8 @@ return newType; | ||
function nameWithoutNamespace(name) { | ||
let namespace = csnUtils.getNamespaceOfArtifact(name); | ||
return name.replace(`${namespace}.`, ''); | ||
function nameWithoutNamespace(name) { | ||
let namespace = csnUtils.getNamespaceOfArtifact(name); | ||
return name.replace(`${namespace}.`, ''); | ||
} | ||
} | ||
module.exports = typesExposure; |
@@ -279,2 +279,4 @@ 'use strict'; | ||
let flatElem = cloneCsn(childElem); | ||
// Don't take over notNull from leaf elements | ||
delete flatElem.notNull; | ||
setProp(flatElem, '$viaTransform', true); // FIXME: This name is not ideal but used elsewhere, too) | ||
@@ -285,2 +287,3 @@ setProp(flatElem, '_flatElementNameWithDots', elementPath.concat(childName).join('.')); | ||
} | ||
// Fix all collected flat elements (names, annotations, properties, origin ..) | ||
@@ -292,3 +295,3 @@ for (let name in result) { | ||
// Copy selected type properties | ||
for (let p of ['key', 'notNull', 'virtual', 'masked', 'viaAll']) { | ||
for (let p of ['key', 'virtual', 'masked', 'viaAll']) { | ||
if (elem[p]) { | ||
@@ -295,0 +298,0 @@ flatElem[p] = elem[p]; |
@@ -16,4 +16,17 @@ // Util functions for operations usually used with files. | ||
/** | ||
* Change Windows style line endings to Unix style | ||
* | ||
* @param {string} src | ||
* @returns {string} | ||
*/ | ||
function normalizeLineEndings(src) { | ||
return (src && process.platform === 'win32') ? src.replace(/\r\n/g, '\n') : src; | ||
} | ||
module.exports = { | ||
splitLines, | ||
normalizeLineEndings, | ||
}; |
@@ -24,2 +24,3 @@ // Custom resolve functionality for the CDS compiler | ||
function resolveCDS(moduleName, options, callback) { | ||
const isWindows = (process.platform === 'win32'); | ||
let resolvedBaseDir = path.resolve(options.basedir); | ||
@@ -214,9 +215,17 @@ | ||
function nodeModulesPaths(absoluteStart) { | ||
const parts = absoluteStart.split(path.sep); // TODO: Check if path is normalized on Windows | ||
const dirs = []; // TODO: GLOBAL_FOLDERS | ||
// Use platform-dependent separator. All NodeJS `path` methods use the system's path separator. | ||
const parts = absoluteStart.split(path.sep); | ||
// Do NOT use global node_modules directories. | ||
const dirs = []; | ||
// If we're on *nix systems, the first part is just an empty string '' | ||
// because the path is absolute. Re-add it here because `path.join()` | ||
// ignores empty segments which would result in a relative path. | ||
if (!isWindows && parts.length > 0 && parts[0] === '') | ||
parts[0] = '/'; | ||
for (let i = parts.length - 1; i >= 0; i--) { | ||
if (parts[i] === 'node_modules') | ||
continue; | ||
const dir = path.join('/', ...parts.slice(0, i + 1), 'node_modules'); | ||
const dir = path.join(...parts.slice(0, i + 1), 'node_modules'); | ||
dirs.push(dir); | ||
@@ -223,0 +232,0 @@ } |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "1.45.0", | ||
"version": "1.46.4", | ||
"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 not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is 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
3814033
128
73088