@sap/cds-compiler
Advanced tools
Comparing version 1.17.1 to 1.18.2
@@ -459,3 +459,3 @@ 'use strict'; | ||
function toSwagger(model, options) { | ||
const { warning, signal } = alerts(model); | ||
const { warning, error, signal } = alerts(model); | ||
@@ -471,2 +471,10 @@ // In case of API usage the options are in the 'options' argument | ||
options = mergeOptions(model.options, options); | ||
// hide to swagger behind betaMode | ||
if (options.betaMode) { | ||
signal(warning`The to swagger backend is experimental`); | ||
} else { | ||
signal(error`The to swagger backend is only available in beta-mode and is experimental`); | ||
} | ||
// If neither 'json' nor 'csn' is specified as output option, produce 'json' | ||
@@ -473,0 +481,0 @@ if (!options.toSwagger || (!options.toSwagger.json && !options.toSwagger.csn)) { |
@@ -46,2 +46,5 @@ // Implementation of alerts | ||
signal.error = error; | ||
signal.warning = warning; | ||
signal.ino = info; | ||
return { | ||
@@ -48,0 +51,0 @@ info, warning, error, // tag functions for the different alerts |
@@ -225,2 +225,4 @@ // Functions and classes for syntax messages | ||
arg = arg._artifact; | ||
if (arg._outer) | ||
arg = arg._outer; | ||
let name = arg.name; | ||
@@ -227,0 +229,0 @@ if (!name) |
@@ -25,6 +25,6 @@ // | ||
// for details. | ||
function forEachMember( construct, callback ) { | ||
function forEachMember( construct, callback, target ) { | ||
let obj = construct.returns || construct; // why the extra `returns` for actions? | ||
obj = obj.items || obj; | ||
forEachGeneric( obj, 'elements', callback ); | ||
forEachGeneric( target || obj, 'elements', callback ); | ||
forEachGeneric( obj, 'enum', callback ); | ||
@@ -31,0 +31,0 @@ forEachGeneric( obj, 'foreignKeys', callback ); |
@@ -376,2 +376,5 @@ 'use strict' | ||
if(options.betaMode && !options.testMode) { | ||
result.push('Option --beta-mode was used. This option should not be used in productive scenarios!') | ||
} | ||
if (options && options.newCsn === false) { | ||
@@ -381,2 +384,12 @@ result.push(`Option --old-csn was used. This option is deprecated and should not be used in productive scenarios!`) | ||
if(options) { | ||
['length', 'precision', 'scale'].forEach(facet => { | ||
if(options[facet] && isNaN(options[facet])) { | ||
result.push(`Invalid value "${options[facet]}" for option "--${facet}" - not an Integer`); | ||
} else { | ||
options[facet] = parseInt(options[facet]); | ||
} | ||
}); | ||
} | ||
if (command) { | ||
@@ -383,0 +396,0 @@ let cmd = optionProcessor.commands[command]; |
@@ -40,5 +40,7 @@ 'use strict'; | ||
// FIXME: convenience function (reuse in forHana)? | ||
const context = { EntityhasPersistenceSkipOrTrueOrAbstract: elem._parent.abstract || ((elem._parent['@cds.persistence.skip'] && elem._parent['@cds.persistence.skip'].val !== null && elem._parent['@cds.persistence.skip'].val !== false)|| (elem._parent['@cds.persistence.exists'] && elem._parent['@cds.persistence.exists'].val !== null && elem._parent['@cds.persistence.exists'].val !== false))}; | ||
let assocType = isComposition(elem.type) ? "composition" : "association"; | ||
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, elem.location, undefined, 'to-many-no-on', context); | ||
if (!process.env.skip_warning_about_assoc_to_many) { | ||
const context = { EntityhasPersistenceSkipOrTrueOrAbstract: elem._parent.abstract || ((elem._parent['@cds.persistence.skip'] && elem._parent['@cds.persistence.skip'].val !== null && elem._parent['@cds.persistence.skip'].val !== false)|| (elem._parent['@cds.persistence.exists'] && elem._parent['@cds.persistence.exists'].val !== null && elem._parent['@cds.persistence.exists'].val !== false))}; | ||
let assocType = isComposition(elem.type) ? "composition" : "association"; | ||
signal(warning`The ${assocType} "${elem.name.id}" has cardinality "to many" but no ON-condition`, elem.location, undefined, 'to-many-no-on', context); | ||
} | ||
} | ||
@@ -45,0 +47,0 @@ } |
@@ -112,2 +112,4 @@ 'use strict'; | ||
if (options.toHana) { | ||
// TODO: Clarify when this is needed - only HANA, all transformers, after compile? | ||
// TODO: Fix "options-check" - does not work if no HANA option passed... | ||
checkNotEmptyOrOnlyVirtualElems(art, model); | ||
@@ -114,0 +116,0 @@ } |
@@ -121,2 +121,3 @@ // Consistency checker on model (XSN = augmented CSN) | ||
dependencies: { test: TODO }, // TODO: describe | ||
fileDep: { test: TODO }, | ||
$frontend: { parser: true, test: isString, enum: [ 'cdl', 'json', 'xml' ] }, | ||
@@ -156,3 +157,3 @@ messages: { | ||
requires: [ 'kind', 'location' ], | ||
optional: [ 'name', 'extern', 'usings', 'annotationAssignments' ], // TODO: get rid of annos: [] | ||
optional: [ 'name', 'extern', 'usings', 'annotationAssignments', 'fileDep' ], // TODO: get rid of annos: [] | ||
}, | ||
@@ -260,3 +261,11 @@ extern: { | ||
}, | ||
target: { kind: true, inherits: 'type' }, | ||
target: { | ||
kind: true, | ||
requires: [ 'location' ], | ||
optional: [ | ||
'path', 'elements', '_elementsIndexNo', '_outer', | ||
'scope', '_artifact', '$inferred', // TODO: remove the rest | ||
'calculated', // TODO: remove calculated | ||
], | ||
}, | ||
path: { | ||
@@ -263,0 +272,0 @@ test: isArray( pathItem ), |
@@ -59,3 +59,5 @@ // The builtin artifacts of CDS | ||
SESSION_USER: {}, | ||
// SQL-92: also SYSTEM_USER, just USER (is with parens in HANA SQL), VALUE | ||
SYSTEM_USER: {}, // not in HANA | ||
// SQL92: USER - intentionally omitted (useful element name), most DB have USER() | ||
// SQL-92: USER - intentionally omitted (useful element name), which DB support this? | ||
$user: { | ||
@@ -71,11 +73,3 @@ elements: { id: {}, locale: {} }, | ||
// const magicVariablesHana = { | ||
// CURRENT_CONNECTION: {}, | ||
// CURRENT_SCHEMA: {}, | ||
// CURRENT_TRANSACTION_ISOLATION_LEVEL: {}, | ||
// CURRENT_UTCDATE: {}, | ||
// CURRENT_UTCTIME: {}, | ||
// CURRENT_UTCTIMESTAMP: {}, | ||
// SYSUUID: {}, | ||
// }; | ||
// see lib/render/renderUtil.js for DB-specific magic vars, specified in CAP Cds via function | ||
@@ -82,0 +76,0 @@ function initBuiltins( model ) { |
@@ -80,5 +80,6 @@ // Compiler functions and utilities shared across all phases | ||
typeOf: { next: '_$next', assoc: false }, // warn | ||
include: { reject: rejectNonStruct }, | ||
context: { reject: rejectNonContext }, | ||
target: { reject: rejectNonEntity, noDep: true }, | ||
include: { reject: rejectNonStruct, envFn: artifactsEnv }, | ||
context: { reject: rejectNonContext, envFn: artifactsEnv }, | ||
target: { reject: rejectNonEntity, noDep: true, envFn: artifactsEnv }, | ||
compositionTarget: { reject: rejectNonTarget, noDep: true, envFn: artifactsEnv }, | ||
// TODO: dep for (explicit+implicit!) foreign keys | ||
@@ -143,2 +144,6 @@ element: { next: '__none_' }, // foreign key element | ||
function rejectNonTarget( art ) { | ||
return (options.betaMode && art.kind === 'type') ? rejectNonStruct( art ) : rejectNonEntity( art ); | ||
} | ||
function rejectNonSource( art, path ) { | ||
@@ -675,3 +680,3 @@ if ([ 'view', 'entity' ].includes( art.kind )) | ||
if (prop) { // extension or structure include | ||
// TODO: consider ARRAY OF and RETURNS | ||
// TODO: consider ARRAY OF and RETURNS, COMPOSITION OF type | ||
if (!(prop in parent)) | ||
@@ -678,0 +683,0 @@ parent[prop] = Object.create(null); |
@@ -158,2 +158,6 @@ 'use strict'; | ||
} | ||
if(properties.filter(p => p.isKey).length === 0) { | ||
signal(error`EntityType "${serviceName}/${EntityTypeName}" has no primary key`, ['definitions',entityCsn.name]); | ||
} | ||
// construct EntityType attributes | ||
@@ -356,7 +360,7 @@ let attributes = { Name : EntityTypeName }; | ||
// (undefined !== false) still evaluates to true | ||
if (!elementCsn.target.abstract && elementCsn['@odata.navigable'] !== false) | ||
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false) | ||
{ | ||
let navProp = new Edm.NavigationProperty(v, { | ||
Name: elementName, | ||
Type: fullQualified(elementCsn.target.name) | ||
Type: fullQualified(elementCsn._target.name) | ||
}, elementCsn); | ||
@@ -363,0 +367,0 @@ |
@@ -880,3 +880,3 @@ 'use strict' | ||
_isCollection: this.isToMany(), | ||
_targetCsn: csn.target | ||
_targetCsn: csn._target | ||
} ); | ||
@@ -987,3 +987,3 @@ | ||
return new NavigationPropertyBinding(this._v, | ||
{ Path: this.Name, Target: this._csn.target.name.replace(namespace, '') } | ||
{ Path: this.Name, Target: this._csn._target.name.replace(namespace, '') } | ||
); | ||
@@ -990,0 +990,0 @@ } |
@@ -110,3 +110,3 @@ 'use strict'; | ||
if(entityCsn._containerEntity) { | ||
parameterCsn._containerEntity = []; | ||
setProp(parameterCsn, '_containerEntity', []); | ||
for(let c of entityCsn._containerEntity) { | ||
@@ -130,10 +130,10 @@ parameterCsn._containerEntity.push((c==entityCsn.name)?parameterCsn.name:c); | ||
name: parameterToOriginalAssocName, | ||
target: entityCsn, | ||
target: entityCsn.name, | ||
type: 'cds.Association', | ||
_partnerCsn: [], | ||
cardinality: { src: 1, min: 0, max: '*' } | ||
}; | ||
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_target', entityCsn); | ||
setProp(parameterCsn.elements[parameterToOriginalAssocName], '_parameterCsn', []); | ||
model.definitions[parameterCsn.name] = parameterCsn; | ||
// modify the original parameter entity with backlink and new name | ||
@@ -147,3 +147,3 @@ entityCsn.name = originalEntityName; | ||
name: backlinkAssocName, | ||
target: parameterCsn, | ||
target: parameterCsn.name, | ||
type: 'cds.Association', | ||
@@ -153,2 +153,4 @@ _partnerCsn: [], | ||
}; | ||
setProp(entityCsn.elements[backlinkAssocName], '_target', parameterCsn); | ||
setProp(entityCsn.elements[backlinkAssocName], '_parameterCsn', []); | ||
} | ||
@@ -239,6 +241,6 @@ } | ||
return; | ||
if(typeof element.target === "string") { | ||
if(!element._target) { | ||
let target = model.definitions[element.target]; | ||
if(target) { | ||
element.target = target; | ||
setProp(element, '_target', target); | ||
} | ||
@@ -266,8 +268,8 @@ else { | ||
return; | ||
if(typeof element.target === "string") { | ||
if(!element._target) { | ||
throw Error('Expect target to be resolved, parent: ' + struct.name + ', assoc: ' + element.name + ', target: ' + element.target); | ||
} | ||
// in case this is a forward assoc, store the backlink partneres here, _partnerCsn.length > 1 => error | ||
element._partnerCsn = []; | ||
element.target.$proxies = []; | ||
setProp(element, '_partnerCsn', []); | ||
setProp(element._target, '$proxies', []); | ||
@@ -302,16 +304,16 @@ //forward annotations from managed association element to its foreign keys | ||
// (array because the contanee may contained more then once) | ||
if (!element.target._containerEntity) { | ||
element.target._containerEntity = []; | ||
if (!element._target._containerEntity) { | ||
setProp(element._target, '_containerEntity', []); | ||
} | ||
// add container only once per containee | ||
if (!element.target._containerEntity.includes(container.name)) { | ||
element.target._containerEntity.push(container.name); | ||
if (!element._target._containerEntity.includes(container.name)) { | ||
element._target._containerEntity.push(container.name); | ||
// Mark associations in the containee pointing to the container (i.e. to this entity) | ||
for (let containeeElementName in element.target.elements) { | ||
let containeeElement = element.target.elements[containeeElementName]; | ||
if (containeeElement.target && containeeElement.target.name) { | ||
for (let containeeElementName in element._target.elements) { | ||
let containeeElement = element._target.elements[containeeElementName]; | ||
if (containeeElement._target && containeeElement._target.name) { | ||
// If this is an association that points to a container (but is not by itself contained, | ||
// which would indicate the top role in a hierarchy) mark it with '_isToContainer' | ||
if (containeeElement.target.name == container.name && !containeeElement['@odata.contained']) { | ||
containeeElement._isToContainer = true; | ||
if (containeeElement._target.name == container.name && !containeeElement['@odata.contained']) { | ||
setProp(containeeElement, '_isToContainer', true); | ||
} | ||
@@ -328,3 +330,3 @@ } | ||
if (element._ignore) return; | ||
element._constraints = getReferentialConstraints(element, signal, warning); | ||
setProp(element, '_constraints', getReferentialConstraints(element, signal, warning)); | ||
@@ -375,3 +377,3 @@ // only in V2 we must set the target cardinality of the backlink to the forward: | ||
/* | ||
if(element.target['@cds.autoexpose'] === false) { | ||
if(element._target['@cds.autoexpose'] === false) { | ||
// :TODO: Also _ignore foreign keys to association? | ||
@@ -383,3 +385,3 @@ foreach(struct.elements, | ||
element._ignore = true; | ||
signal(info`${element.type.replace('cds.', '')} "${element.name}" excluded, target "${element.target.name}" is annotated '@cds.autoexpose: ${element.target['@cds.autoexpose']}'`, | ||
signal(info`${element.type.replace('cds.', '')} "${element.name}" excluded, target "${element._target.name}" is annotated '@cds.autoexpose: ${element._target['@cds.autoexpose']}'`, | ||
['definitions', struct.name, 'elements', element.name]); | ||
@@ -390,42 +392,52 @@ return; | ||
// if target is outside defining service, create/reuse proxy | ||
if(myServiceName !== whatsMyServiceName(element.target.name)) { | ||
// search for eventually existing proxy | ||
let proxy = element.target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0]; | ||
if(!proxy) { | ||
let name = myServiceName + '.' + element.target.name.split('.').join('_') + '_Proxy_0'; | ||
proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) }; | ||
let hasKeys = false; | ||
foreach(element.target.elements, e => e.key, e => { | ||
// :TODO: getFinalBaseType, resolve structs | ||
// Omit associations (no navigation properties) | ||
if (isAssocOrComposition(e.type)) { | ||
e._ignore = true; | ||
} | ||
if(isStructured(e)) { | ||
e._ignore = true; | ||
// parameters/elements | ||
signal(info`Structured types not yet supported as primary keys of proxy entity type "${name}" for unexposed association target "${element.target.name}"`, | ||
if(myServiceName !== whatsMyServiceName(element._target.name)) { | ||
if(options.betaModeProxy) { | ||
// search for eventually existing proxy | ||
let proxy = element._target.$proxies.filter(p => p.name.startsWith(myServiceName + '.'))[0]; | ||
if(!proxy) { | ||
let name = myServiceName + '.' + element._target.name.split('.').join('_') + '_Proxy_0'; | ||
proxy = { name, kind: 'entity', $proxy: true, elements: Object.create(null) }; | ||
let hasKeys = false; | ||
foreach(element._target.elements, e => e.key, e => { | ||
// :TODO: getFinalBaseType, resolve structs | ||
// Omit associations (no navigation properties) | ||
let ignore = false; | ||
if (isAssocOrComposition(e.type)) { | ||
ignore = true; | ||
} | ||
if(isStructured(e)) { | ||
ignore = true; | ||
// parameters/elements | ||
signal(info`Structured types not yet supported as primary keys of proxy entity type "${name}" for unexposed association target "${element._target.name}"`, | ||
['definitions', struct.name, 'elements', element.name]); | ||
} | ||
if(!ignore) { | ||
// clone elements and strip of all annotations | ||
proxy.elements[e.name] = cloneCsn(e); | ||
Object.keys(proxy.elements[e.name]).forEach(k => { if(k[0] === '@') delete proxy.elements[e.name][k] } ); | ||
hasKeys = true; | ||
} | ||
}); | ||
if(!hasKeys) { | ||
element._ignore = true; | ||
signal(info`Could not create proxy entity type "${name}" for unexposed association target "${element._target.name}", because target has no primary keys`, | ||
['definitions', struct.name, 'elements', element.name]); | ||
return; | ||
} | ||
// clone elements and strip of all annotations | ||
proxy.elements[e.name] = cloneCsn(e); | ||
hasKeys = true; | ||
}); | ||
if(!hasKeys) { | ||
element._ignore = true; | ||
delete element.target.$proxies; | ||
signal(info`Could not create proxy entity type "${name}" for unexposed association target "${element.target.name}", because target has no primary keys"`, | ||
signal(info`Created proxy entity type "${name}" for unexposed association target "${element._target.name}"`, | ||
['definitions', struct.name, 'elements', element.name]); | ||
return; | ||
// wire up proxy | ||
model.definitions[name] = proxy; | ||
element._target.$proxies.push(proxy); | ||
} | ||
signal(info`Created proxy entity type "${name}" for unexposed association target "${element.target.name}"`, | ||
element._target = proxy; | ||
// remove referential constraints | ||
element._constraints.constraints = Object.create(null); | ||
} | ||
else { | ||
element._ignore = true; | ||
signal(info`Association is auto-excluded, as target "${element._target.name}" is outside any service`, | ||
['definitions', struct.name, 'elements', element.name]); | ||
// wire up proxy | ||
model.definitions[name] = proxy; | ||
element.target.$proxies.push(proxy); | ||
return; | ||
} | ||
element.target = proxy; | ||
// remove referential constraints | ||
element._constraints.constraints = Object.create(null); | ||
} | ||
@@ -432,0 +444,0 @@ }); |
@@ -149,3 +149,3 @@ 'use strict'; | ||
if(partner) { | ||
let originAssocCsn = assocCsn.target.elements[partner]; | ||
let originAssocCsn = assocCsn._target.elements[partner]; | ||
if(originAssocCsn == undefined && assocCsn.originalTarget) | ||
@@ -155,4 +155,5 @@ originAssocCsn = assocCsn.originalTarget.elements[partner]; | ||
if(originAssocCsn) { | ||
if(originAssocCsn.target != assocCsn._parent) { | ||
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn.target.name}"`, ['definitions', parentArtifactName]); | ||
if(originAssocCsn._target != assocCsn._parent) { | ||
isBacklink = false; | ||
signal(warning`"${assocCsn._parent.name}/${assocCsn.name}" references "${originAssocCsn._parent.name}/${partner}" in $self ON condition with target "${originAssocCsn._target.name}"`, ['definitions', parentArtifactName]); | ||
} | ||
@@ -162,3 +163,3 @@ if(isAssociationOrComposition(originAssocCsn)) { | ||
// as they are primary keys of the other entity as well | ||
if(!assocCsn.target.isParamEntity && originAssocCsn.key) { | ||
if(!assocCsn._target.isParamEntity && originAssocCsn.key) { | ||
if(originAssocCsn.keys) { | ||
@@ -196,3 +197,3 @@ for(let fk of originAssocCsn.keys) { | ||
{ | ||
signal(warning`Cannot resolve backlink to ${assocCsn.target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]); | ||
signal(warning`Cannot resolve backlink to ${assocCsn._target.name}/${partner}" from "${parentArtifactName}/${assocCsn.name}"`, ['definitions', parentArtifactName]); | ||
} | ||
@@ -206,9 +207,9 @@ } | ||
if(!assocCsn.target.isParamEntity) { | ||
if(!assocCsn._target.isParamEntity) { | ||
// Header is composed of Items => Cds.Composition: Header is principal => use header's primary keys | ||
let dependentEntity = assocCsn._parent; | ||
let principalEntity = assocCsn.target; | ||
let principalEntity = assocCsn._target; | ||
if(assocCsn.type == 'cds.Composition') { | ||
principalEntity = assocCsn._parent; | ||
dependentEntity = assocCsn.target; | ||
dependentEntity = assocCsn._target; | ||
// Swap the constraint elements to be correct on Composition [principal, dependent] => [dependent, principal] | ||
@@ -238,6 +239,6 @@ Object.keys(result.constraints).forEach(cn => { | ||
// FIXME: If path is something structured, perform a path resolution (or use augmented CSN) | ||
if(!assocCsn.target.isParamEntity && assocCsn.keys) { | ||
if(!assocCsn._target.isParamEntity && assocCsn.keys) { | ||
for(let fk of assocCsn.keys) { | ||
let realFk = assocCsn._parent.elements[fk.$generatedFieldName]; | ||
let pk = assocCsn.target.elements[fk.ref[0]]; | ||
let pk = assocCsn._target.elements[fk.ref[0]]; | ||
if(pk && pk.key && !(pk['@cds.api.ignore'] || realFk['@cds.api.ignore'])) | ||
@@ -254,3 +255,3 @@ { | ||
// continue with multiplicity | ||
if(assocCsn.target.isParamEntity) | ||
if(assocCsn._target.isParamEntity) | ||
{ | ||
@@ -451,2 +452,4 @@ result.constraints = Object.create(null); | ||
} | ||
if(cdsType === 'cds.DecimalFloat' || cdsType === 'cds.hana.SMALLDECIMAL') | ||
signal(signal.warning`"OData V2 does not support ${cdsType}"`, csn.$location); | ||
} | ||
@@ -462,11 +465,12 @@ else // isV4 | ||
function addTypeFacets(node, csn, isV2=false) | ||
function addTypeFacets(node, csn) | ||
{ | ||
if (csn.length) | ||
const isV2 = node.v2; | ||
if (csn.length !== undefined) | ||
node.MaxLength = csn.length; | ||
if (csn.scale) | ||
if (csn.scale !== undefined) | ||
node.Scale = csn.scale; | ||
else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2) | ||
node.Scale = 'floating'; | ||
if (csn.precision) | ||
if (csn.precision !== undefined) | ||
node.Precision = csn.precision; | ||
@@ -477,2 +481,6 @@ else if (csn.type === 'cds.hana.SMALLDECIMAL' && !isV2) | ||
node.Precision = 7; | ||
else if(csn.type === 'cds.DecimalFloat' && !isV2) { | ||
node.Scale = 'floating'; | ||
node.Precision = 34; | ||
} | ||
// Unicode unused today | ||
@@ -479,0 +487,0 @@ if(csn.unicode) |
@@ -44,3 +44,3 @@ // Transform augmented CSN into compact "official" CSN | ||
cardinality: standard, // also for pathItem: after 'id', before 'where' | ||
target: artifactRef, | ||
target, | ||
foreignKeys: renameTo( 'keys', dictAsArray ), // XSN: rename? | ||
@@ -63,3 +63,3 @@ enum: insertOrderDict, | ||
on: (cond) => (typeof cond === 'string' ? undefined : condition( cond )), // also for join | ||
onCond : renameTo( 'on', condition ), // XSN TODO: onCond -> on | ||
onCond : renameTo( 'on', c => (csn_gensrc && c.$inferred) ? undefined : condition(c) ), // XSN TODO: onCond -> on | ||
// definitions, extensions, members ---------------------------------------- | ||
@@ -78,3 +78,3 @@ returns: standard, // storing the return type of actions | ||
definitions: sortedDict, | ||
extensions: standard, // is array - TODO: sort | ||
extensions, // is array | ||
messages, // consider compactQuery / compactExpr | ||
@@ -202,6 +202,5 @@ options: ignore, | ||
set( 'definitions', csn, model ); | ||
if (!csn_gensrc) | ||
set( 'extensions', csn, model ); | ||
else | ||
extensions( csn, model ); | ||
const exts = extensions( model.extensions || [], csn, model ); | ||
if (exts.length) | ||
csn.extensions = exts; | ||
set( 'messages', csn, model ); | ||
@@ -234,5 +233,7 @@ if (model.version) | ||
function extensions( csn, model ) { | ||
let extensions = (model.extensions) ? standard( model.extensions ) : []; | ||
for (let name in model.definitions) { | ||
function extensions( node, csn, model ) { | ||
const exts = node.map( standard ).sort( (a, b) => a.annotate.localeCompare( b.annotate ) ); | ||
if (!csn_gensrc) | ||
return exts; | ||
for (let name of Object.keys( model.definitions ).sort()) { | ||
let art = model.definitions[name]; | ||
@@ -248,7 +249,6 @@ // in definitions (without redef) with potential inferred elements: | ||
if (Object.keys( annotate ).length > 1) | ||
extensions.push( annotate ); | ||
exts.push( annotate ); | ||
} | ||
} | ||
if (extensions.length) | ||
csn.extensions = extensions; | ||
return exts; | ||
} | ||
@@ -302,2 +302,11 @@ | ||
function target( val ) { | ||
if (!val.elements) | ||
return artifactRef( val, true ); | ||
else if (csn_gensrc) | ||
return standard( val ) | ||
else | ||
return val._artifact && val._artifact.name.absolute; | ||
} | ||
function elements( dict, csn, node ) { | ||
@@ -510,3 +519,3 @@ if (csn.from) // with SELECT | ||
const magicFunctions = // TODO: calculate from compiler/builtins.js (more with HANA?): | ||
['CURRENT_DATE','CURRENT_TIME','CURRENT_TIMESTAMP','CURRENT_USER','SESSION_USER']; | ||
['CURRENT_DATE','CURRENT_TIME','CURRENT_TIMESTAMP','CURRENT_USER','SESSION_USER','SYSTEM_USER']; | ||
// TODO: quoted magic names like $now should be complained about in the compiler | ||
@@ -614,5 +623,5 @@ | ||
const select = { SELECT: standard( node ) }; | ||
const elements = node.elements; | ||
if (elements && node._main && node._main.$queries && node !== node._main.$queries[0]) | ||
setHidden( select, 'elements', elements ); | ||
const elems = node.elements; | ||
if (elems && node._main && node._main.$queries && node !== node._main.$queries[0]) | ||
setHidden( select, 'elements', elements( elems, select, node ) ); | ||
return addLocation( node.location, select ); | ||
@@ -619,0 +628,0 @@ } |
@@ -318,3 +318,3 @@ // Error strategy with special handling for (non-reserved) keywords | ||
function intervalSetToArray( recognizer, expected ) { | ||
function intervalSetToArray( recognizer, expected, excludesForNextToken ) { | ||
// similar to `IntervalSet#toTokenString` | ||
@@ -327,6 +327,11 @@ var names = []; | ||
} | ||
if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) | ||
names = names.filter( n => !recognizer.$adaptExpectedExcludes.includes( n ) ); | ||
else if (names.includes("';'")) | ||
if (recognizer.$adaptExpectedToken && recognizer.$nextTokensToken === recognizer.$adaptExpectedToken) { | ||
let excludes = (excludesForNextToken && recognizer.$adaptExpectedExcludes[0] instanceof Array) | ||
? recognizer.$adaptExpectedExcludes[0] | ||
: recognizer.$adaptExpectedExcludes; | ||
names = names.filter( n => !excludes.includes( n ) ); | ||
} | ||
else if (names.includes("';'")) { | ||
names = names.filter( n => n !== "'}'" ); | ||
} | ||
names.sort( (a, b) => tokenPrecedence(a) < tokenPrecedence(b) ? -1 : 1 ); | ||
@@ -408,2 +413,3 @@ return names; | ||
expected, lookBusy, calledRules, true, true ); | ||
return intervalSetToArray( recognizer, expected, true ); | ||
} | ||
@@ -410,0 +416,0 @@ else if (offendingToken && recognizer.$nextTokensContext && |
@@ -48,2 +48,3 @@ // Generic ANTLR parser class with AST-building functions | ||
setMaxCardinality, | ||
handleComposition, | ||
hanaFlavorOnly, | ||
@@ -153,2 +154,5 @@ betaModeOnly, | ||
// Using this function "during ATN decision making" has no effect | ||
// In front of an ATN decision, you might specify dedicated excludes | ||
// for non-LA1 tokens via a sub-array in excludes[0]. | ||
function excludeExpected( excludes ) { | ||
@@ -160,2 +164,3 @@ if (excludes) { | ||
this.$nextTokensToken = t; | ||
this.$nextTokensContext = null; | ||
} | ||
@@ -469,11 +474,25 @@ } | ||
function setMaxCardinality( art, token, max ) { | ||
function setMaxCardinality( art, token, max, inferred ) { | ||
let location = this.tokenLocation( token ); | ||
if (art.cardinality) { | ||
if (!art.cardinality) { | ||
art.cardinality = { targetMax: Object.assign( {location}, max ), location }; | ||
if (inferred) | ||
art.cardinality.$inferred = inferred; | ||
} | ||
else if (!inferred) { | ||
this.message( 'syntax-repeated-cardinality', location, { token: token.text }, | ||
'Warning', 'The target cardinality has already been specified - ignored $(TOKEN)' ); | ||
} | ||
else { | ||
art.cardinality = { targetMax: Object.assign( {location}, max ), location }; | ||
} | ||
function handleComposition( cardinality, isComposition) { | ||
if (isComposition && !cardinality) { | ||
const lt1 = this._input.LT(1).type; | ||
const la2 = this._input.LT(2); | ||
if (la2.text === '{' && (lt1 === this.constructor.MANY || lt1 === this.constructor.ONE)) | ||
la2.type = this.constructor.COMPOSITIONofBRACE; | ||
} | ||
const brace1 = (isComposition) ? 'COMPOSITIONofBRACE' : "'{'"; | ||
const manyOne = (cardinality) ? ['MANY', 'ONE'] : []; | ||
this.excludeExpected( [["'}'", 'COMPOSITIONofBRACE'], brace1, ...manyOne] ); | ||
} | ||
@@ -480,0 +499,0 @@ |
@@ -65,3 +65,3 @@ // Main entry point for the Research Vanilla CDS Compiler | ||
const extensions = ['.cds', '.json']; | ||
const extensions = ['.cds', '.csn', '.csn.json', '.json']; | ||
@@ -68,0 +68,0 @@ function packageFilter( pkg ) { |
@@ -274,8 +274,8 @@ // CSN functionality for resolving references | ||
for (const prop of csnPath) { | ||
if (isName) { // name/index of artifact/member | ||
isName = false; | ||
if (isName !== 'args') { | ||
if (isName || typeof prop !== 'string') { // array item, name/index of artifact/member, (named) argument | ||
if (typeof isName === 'string') { | ||
parent = art; | ||
art = obj[ prop ]; | ||
} | ||
isName = false; | ||
} | ||
@@ -287,3 +287,3 @@ else if (artifactProperties.includes( prop )) { | ||
else if (prop === 'args') { | ||
isName = prop; | ||
isName = true; // for named arguments | ||
if (scope === 'orderBy') | ||
@@ -302,10 +302,11 @@ scope = 'orderBy-xpr'; // no need to extra 'orderBy-args' | ||
} | ||
else if (typeof prop === 'string') { | ||
if (prop !== 'xpr') | ||
scope = prop; | ||
else if (scope === 'orderBy') | ||
scope = 'orderBy-xpr'; | ||
else if (prop !== 'xpr') { | ||
scope = prop; | ||
} | ||
else if (scope === 'orderBy') { | ||
scope = 'orderBy-xpr'; | ||
} | ||
obj = obj[ prop ]; | ||
} | ||
// console.log( 'CPATH:', csnPath, scope, obj ); | ||
return { obj, parent, query, scope }; | ||
@@ -312,0 +313,0 @@ } |
@@ -499,3 +499,3 @@ 'use strict' | ||
let dictObj = dict[name]; | ||
if (dictObj instanceof Array) // redefinitions | ||
if (dictObj instanceof Array) // redefinitions - not in CSN! | ||
dictObj.forEach( (o) => callback( o, name, prop, path.concat([prop, name])) ) | ||
@@ -502,0 +502,0 @@ else |
@@ -31,2 +31,5 @@ const { createOptionProcessor } = require('./base/optionProcessorHelper'); | ||
.option(' --old-localized-conv') | ||
.option('--precision <prec>') | ||
.option('--scale <scale>') | ||
.option('--length <length>') | ||
.help(` | ||
@@ -55,2 +58,7 @@ Usage: cdsc <command> [options] <file...> | ||
Type options | ||
--precision <prec> Default precision for 'cds.Decimal' | ||
--scale <scale> Default scale for 'cds.Decimal' | ||
--length <length> Default 'length' for 'cds.String' | ||
Diagnostic options | ||
@@ -57,0 +65,0 @@ --trace-parser Trace parser |
@@ -0,3 +1,35 @@ | ||
// Common render functions for toCdl.js and toSql.js | ||
const functionsWithoutParams = { | ||
hana: { | ||
CURRENT_CONNECTION: {}, | ||
CURRENT_SCHEMA: {}, | ||
CURRENT_TRANSACTION_ISOLATION_LEVEL: {}, | ||
CURRENT_UTCDATE: {}, | ||
CURRENT_UTCTIME: {}, | ||
CURRENT_UTCTIMESTAMP: {}, | ||
SYSUUID: {}, | ||
} | ||
} | ||
// Dialect = 'hana' (only relevance at the moment) | 'cap' | 'sqlite' | ||
function renderFunc( node, dialect, renderArgs, parenNameToUpper = false ) { | ||
if (funcWithoutParen( node, dialect )) | ||
return node.func; | ||
else | ||
// unclear why we transform the function name to uppercase in SQL, but only with parentheses | ||
return `${parenNameToUpper ? node.func.toUpperCase() : node.func}(${renderArgs( node.args )})`; | ||
} | ||
function funcWithoutParen( node, dialect ) { | ||
if (!node.args) | ||
return true; | ||
if (!Array.isArray( node.args ) || node.args.length) | ||
return false; | ||
const specials = functionsWithoutParams[dialect]; | ||
return specials && specials[node.func.toUpperCase()]; | ||
} | ||
/** | ||
* Get the $location from an object and make it look like the XSN location | ||
* Get the $location from an object and make it look like the XSN location - delete - not necessary | ||
* | ||
@@ -21,3 +53,4 @@ * @param {any} location | ||
module.exports = { | ||
renderFunc, | ||
transformLocation | ||
} | ||
} |
@@ -10,3 +10,3 @@ "use strict"; | ||
const alerts = require('../base/alerts'); | ||
const { transformLocation } = require('./renderUtil'); | ||
const { transformLocation, renderFunc } = require('./renderUtil'); // TODO: transformLocation should not be necessary | ||
const DuplicateChecker = require('./DuplicateChecker'); | ||
@@ -484,8 +484,10 @@ | ||
// One join operation, possibly with ON-condition | ||
// FIXME: Clarify if join operators can be n-ary (assuming binary here) | ||
let result = `(${renderViewSource(source.args[0], env)} ${source.join} join ${renderViewSource(source.args[1], env)}`; | ||
if (source.on) { | ||
result += ` on ${renderExpr(source.on, env)}`; | ||
let result = `${renderViewSource(source.args[0], env)}`; | ||
for (let i = 1; i < source.args.length; i++) { | ||
result = `(${result} ${source.join} join ${renderViewSource(source.args[i], env)}`; | ||
if (source.on) { | ||
result += ` on ${renderExpr(source.on, env)}`; | ||
} | ||
result += `)`; | ||
} | ||
result += `)`; | ||
return result; | ||
@@ -1008,6 +1010,3 @@ } | ||
else if (x.func) { | ||
if (x.args) | ||
return `${x.func}(${renderArgs(x.args, '=>', env)})`; | ||
else | ||
return x.func; | ||
return renderFunc( x, (options.forHana ? 'hana' : 'cap'), a => renderArgs(a, '=>', env) ); | ||
} | ||
@@ -1014,0 +1013,0 @@ // Nested expression |
@@ -10,2 +10,3 @@ | ||
const version = require('../../package.json').version; | ||
const { renderFunc } = require('./renderUtil'); // TODO: transformLocation should not be necessary | ||
const DuplicateChecker = require("./DuplicateChecker"); | ||
@@ -434,8 +435,10 @@ | ||
// One join operation, possibly with ON-condition | ||
// FIXME: Clarify if join operators can be n-ary (assuming binary here) | ||
let result = `(${renderViewSource(artifactName, source.args[0], env)} ${source.join.toUpperCase()} JOIN ${renderViewSource(artifactName, source.args[1], env)}`; | ||
if (source.on) { | ||
result += ` ON ${renderExpr(source.on, env)}`; | ||
let result = `${renderViewSource(artifactName, source.args[0], env)}`; | ||
for (let i = 1; i < source.args.length; i++) { | ||
result = `(${result} ${source.join.toUpperCase()} JOIN ${renderViewSource(artifactName, source.args[i], env)}` | ||
if (source.on) { | ||
result += ` ON ${renderExpr(source.on, env)}`; | ||
} | ||
result += `)`; | ||
} | ||
result += `)`; | ||
return result; | ||
@@ -920,6 +923,3 @@ } | ||
else if (x.func) { | ||
if (x.args) | ||
return `${x.func.toUpperCase()}(${renderArgs(x.args, '=>', env)})`; | ||
else | ||
return x.func; | ||
return renderFunc( x, options.toSql.dialect, a => renderArgs(a, '=>', env), true ); | ||
} | ||
@@ -926,0 +926,0 @@ // Nested expression |
@@ -5,3 +5,3 @@ const schemaObjects = require('./swaggerSchemaObjects'); | ||
const { compactSorted } = require('../json/compactor'); | ||
const { compactModel } = require('../json/to-csn') | ||
const { compactModel } = require('../json/to-csn'); | ||
let swaggerJson = null; | ||
@@ -8,0 +8,0 @@ |
@@ -6,3 +6,3 @@ 'use strict'; | ||
const { CompilationError, hasErrors, sortMessages } = require('../base/messages'); | ||
const { isManagedAssociationElement, isStructuredElement, isAssociation, isComposition, isAssocOrComposition, isElementWithType, | ||
const { isManagedAssociationElement, isStructuredElement, isAssociation, isAssocOrComposition, isElementWithType, | ||
renameAnnotation, addBoolAnnotationTo, addStringAnnotationTo, addRefAnnotationTo, copyAnnotations, | ||
@@ -221,6 +221,2 @@ foreachPath, hasBoolAnnotation, getElementDatabaseNameOf, getArtifactDatabaseNameOf } = require('../model/modelUtils'); | ||
checkForeignKeys(elem); | ||
if(options.betaMode && isComposition(elem.type) && | ||
options.toOdata.version == 'v4' && | ||
!hasBoolAnnotation(elem, '@odata.contained')) | ||
elem['@odata.contained'] = { val: true }; | ||
} | ||
@@ -227,0 +223,0 @@ |
@@ -95,3 +95,2 @@ 'use strict'; | ||
isAssociation, | ||
isComposition, | ||
isStructured, | ||
@@ -278,7 +277,2 @@ hasBoolAnnotation, | ||
checkForeignKeys(elem, elemName, defName); | ||
if(options.betaMode && | ||
isComposition(elem.type) && | ||
options.toOdata.version == 'v4' && | ||
elem['@odata.contained'] === undefined) | ||
elem['@odata.contained'] = true; | ||
} | ||
@@ -285,0 +279,0 @@ |
@@ -18,2 +18,3 @@ 'use strict'; | ||
return { | ||
addDefaultTypeFacets, | ||
flattenForeignKeys, | ||
@@ -44,2 +45,19 @@ createForeignKeyElement, | ||
// Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'. | ||
// If 'obj' has primitive type 'cds.String' and no length (and it was not previously a UUID), add default length 5000 if no option is available. | ||
// if 'obj' has primitive type 'cds.Decimal' and no precision or scale try to apply precision, scale from options if available. | ||
function addDefaultTypeFacets(element) { | ||
if (element && element.type && element.type._artifact && !element.type._artifact.name.$renamed) { | ||
if(element.type._artifact.name.absolute == 'cds.String' && element.length === undefined) { | ||
element.length = { literal: 'number', val: (model.options && model.options.length ? model.options.length : 5000) } | ||
} | ||
if(element.type._artifact.name.absolute == 'cds.Decimal' && element.precision === undefined && model.options.precision) { | ||
element.precision = { literal: 'number', val: model.options.precision } | ||
} | ||
if(element.type._artifact.name.absolute == 'cds.Decimal' && element.scale === undefined && model.options.scale) { | ||
element.scale = { literal: 'number', val: model.options.scale } | ||
} | ||
} | ||
} | ||
// For a dictionary `foreignKeys` of foreign key infos, return a dictionary in flattened form, i.e. | ||
@@ -46,0 +64,0 @@ // replace all foreign keys that are managed associations themselves by their respective foreign keys, |
@@ -32,2 +32,3 @@ 'use strict'; | ||
return { | ||
addDefaultTypeFacets, | ||
flattenForeignKeys, | ||
@@ -61,2 +62,21 @@ createForeignKeyElement, | ||
// Try to apply length, precision, scale from options if no type facet is set on the primitive types 'cds.String' or 'cds.Decimal'. | ||
// If 'obj' has primitive type 'cds.String' and no length try to apply length from options if available or set to default 5000. | ||
// if 'obj' has primitive type 'cds.Decimal' try to apply precision, scale from options if available. | ||
function addDefaultTypeFacets(element) { | ||
if(!element || !element.type) | ||
return; | ||
if (element.type === 'cds.String' && element.length === undefined) { | ||
element.length = model.options && model.options.length ? model.options.length : 5000; | ||
} | ||
if(element.type === 'cds.Decimal' && element.precision === undefined && model.options.precision) { | ||
element.precision = model.options.precision; | ||
} | ||
if(element.type === 'cds.Decimal' && element.scale === undefined && model.options.scale) { | ||
element.scale = model.options.scale; | ||
} | ||
} | ||
// For an array `keys` of foreign key infos, return an array in flattened form | ||
@@ -165,3 +185,4 @@ // in one of the two cases: | ||
copyAnnotations(assoc, foreignKeyElement, true); | ||
if (model.options && !model.options.forHana) | ||
copyAnnotations(assoc, foreignKeyElement, true); | ||
@@ -168,0 +189,0 @@ // If the association is non-fkArtifact resp. key, so should be the foreign key field |
{ | ||
"name": "@sap/cds-compiler", | ||
"version": "1.17.1", | ||
"version": "1.18.2", | ||
"lockfileVersion": 1, | ||
@@ -5,0 +5,0 @@ "requires": true, |
@@ -1,1 +0,1 @@ | ||
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.17.1","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bin":{"cdsc":"bin/cdsc.js","cdshi":"bin/cdshi.js","cdsse":"bin/cdsse.js"},"bundleDependencies":false,"dependencies":{"antlr4":"4.7.1","resolve":"1.8.1","sax":"1.2.4"},"deprecated":false,"description":"CDS (Core Data Services) compiler and backends","keywords":["CDS"],"main":"lib/main.js","name":"@sap/cds-compiler","version":"1.18.2","license":"SEE LICENSE IN developer-license-3.1.txt"} |
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 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
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
3373436
66886
10