Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@sap/cds-sql

Package Overview
Dependencies
Maintainers
3
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sap/cds-sql - npm Package Compare versions

Comparing version 1.5.0 to 1.7.0

lib/composition/backlinks.js

37

CHANGELOG.md

@@ -9,2 +9,39 @@ # Changelog

## Version 1.7.0 - 2019-03-19
### Added
- Support for 'list' in function arguments
- Support for ```from: { ref: [] }``` in DeleteBuilder
- Support for Compositions with custom on condition (no and/or)
### Fixed
- Expanding of on active draft documents lists without $filter
- Expand of entities with compound key might return duplicate results
## Version 1.6.0 - 2019-02-25
### Added
- Support for 'func' as defined in cqn spec
- Support for 'list' in expressions
- Support for deep insert with recursive entities
### Changed
### Fixed
- Recursion in composition tree
- Added brackets in oncond
- Fixed is null / is not null in oncond
- Falsy values at expanded elements
- Fixed expand with selected column 'IsActiveEntity'
## Version 1.5.1 - 2019-02-12
### Added
- Support for sql functions lower, upper, trim, length in $filter and $orderby
## Version 1.5.0 - 2019-02-06

@@ -11,0 +48,0 @@

564

lib/composition/compositionTree.js
const DRAFT_SUFFIX = '_drafts'
const { getBackLinks } = require('./backlinks')
const isRootEntity = (model, entityName) => {
const entity = model[entityName]
const isRootEntity = (definitions, entityName) => {
const entity = definitions[entityName]
if (!entity) return false

@@ -12,6 +13,10 @@

for (const { target } of associationElements) {
const parentEntity = model[target]
const parentEntity = definitions[target]
for (const parentElementName of Object.keys(parentEntity.elements)) {
const parentElement = parentEntity.elements[parentElementName]
if (parentElement.type === 'cds.Composition' && parentElement.target === entityName) {
if (
parentElement.type === 'cds.Composition' &&
parentElement.target === entityName &&
!(parentElement.parent && parentElement.parent.name === entityName)
) {
return false

@@ -21,7 +26,6 @@ }

}
return true
}
const getCompositionRoot = (model, entity) => {
const getCompositionRoot = (definitions, entity) => {
const associationElements = Object.keys(entity.elements)

@@ -32,7 +36,11 @@ .map(key => entity.elements[key])

for (const { target } of associationElements) {
const parentEntity = model[target]
const parentEntity = definitions[target]
for (const parentElementName of Object.keys(parentEntity.elements)) {
const parentElement = parentEntity.elements[parentElementName]
if (parentElement.type === 'cds.Composition' && parentElement.target === entity.name) {
return getCompositionRoot(model, parentEntity)
if (
parentElement.type === 'cds.Composition' &&
parentElement.target === entity.name &&
parentElement.target !== parentElement.parent.name
) {
return getCompositionRoot(definitions, parentEntity)
}

@@ -44,30 +52,35 @@ }

const _addAssociation = (element, compositionTree) => {
if (element.foreignKeys) {
// OLD CSN
for (const foreignKey of Object.keys(element.foreignKeys)) {
compositionTree.backLinks.push({
name: `${element.name}_${foreignKey}`,
target_element: element.foreignKeys[foreignKey].path || element.foreignKeys[foreignKey]
})
}
} else if (element.keys) {
for (const foreignKey of element.keys) {
compositionTree.backLinks.push({
name: `${element.name}_${foreignKey.ref[0]}`,
target_element: foreignKey.ref[0]
})
}
}
const _addNavigationToCompositionElements = (element, definitions, compositionTree, compositionMap) => {
const links = element.is2one ? getBackLinks(element, Object.keys(definitions[element.target].keys)) : []
const backLinks = element.is2many ? getBackLinks(element, Object.keys(definitions[element.parent.name].keys)) : []
compositionTree.compositionElements.push(
Object.assign({}, compositionMap.get(element.target), { name: element.name, backLinks, links })
)
}
const _navigationExistsInCompositionMap = (element, compositionMap, includeAssociations) => {
return (
compositionMap.has(element.target) &&
(element.type === 'cds.Composition' || (includeAssociations && element.type === 'cds.Association'))
)
}
const _isNonRecursiveNavigation = (element, rootEntityName, includeAssociations) => {
return (
rootEntityName !== element.target &&
(element.type === 'cds.Composition' || (includeAssociations && element.type === 'cds.Association'))
)
}
const _getCompositionTree = ({
rootEntityName,
model,
compositionSet,
definitions,
compositionMap,
compositionTree,
entityName,
parentEntityName
parentEntityName,
includeAssociations
}) => {
compositionSet.add(parentEntityName)
compositionMap.set(parentEntityName, compositionTree)
compositionTree.source = parentEntityName

@@ -78,14 +91,19 @@ if (parentEntityName !== rootEntityName) {

compositionTree.compositionElements = []
compositionTree.backLinks = []
compositionTree.backLinks = compositionTree.backLinks || []
const parentEntity = model[parentEntityName]
const parentEntity = definitions[parentEntityName]
const elements = Object.keys(parentEntity.elements).map(key => parentEntity.elements[key])
for (const element of elements) {
if (element.type === 'cds.Composition' && rootEntityName !== element.target) {
const subObject = { name: element.name }
if (_navigationExistsInCompositionMap(element, compositionMap, includeAssociations)) {
_addNavigationToCompositionElements(element, definitions, compositionTree, compositionMap)
} else if (_isNonRecursiveNavigation(element, rootEntityName, includeAssociations)) {
const links = element.is2one ? getBackLinks(element, Object.keys(definitions[element.target].keys)) : []
const backLinks = []
const subObject = { name: element.name, backLinks, links }
compositionTree.compositionElements.push(subObject)
_getCompositionTree({
rootEntityName,
model,
compositionSet,
definitions,
compositionMap,
compositionTree: subObject,

@@ -95,9 +113,8 @@ entityName: parentEntityName,

})
}
if (
} else if (
element.type === 'cds.Association' &&
element.target === compositionTree.target &&
compositionSet.has(element.target)
compositionMap.has(element.target)
) {
_addAssociation(element, compositionTree)
compositionTree.backLinks.push(...getBackLinks(element, Object.keys(definitions[element.target].keys)))
}

@@ -109,28 +126,27 @@ }

* Provides tree of all compositions.
* @param {Object} model Definitions of the reflected model
* @param {Object} definitions Definitions of the reflected model
* @param {String} rootEntityName Name of the root entity
* @param {boolean} checkRoot Check is provided entity is a root
*/
const getCompositionTree = (model, rootEntityName, checkRoot = true) => {
if (checkRoot && !isRootEntity(model, rootEntityName)) {
const getCompositionTree = (definitions, rootEntityName, checkRoot = true, includeAssociations = false) => {
if (checkRoot && !isRootEntity(definitions, rootEntityName)) {
throw new Error('Entity is not root entity')
}
const compositionTree = {}
const compositionSet = new Set()
_getCompositionTree({
rootEntityName,
model,
compositionSet,
definitions,
compositionMap: new Map(),
compositionTree,
entityName: rootEntityName,
parentEntityName: rootEntityName
parentEntityName: rootEntityName,
includeAssociations
})
return compositionTree
}
const _stripDraftSuffix = (model, name) => {
const _stripDraftSuffix = (definitions, name) => {
if (name.endsWith(DRAFT_SUFFIX)) {
const strippedName = name.substr(0, name.length - DRAFT_SUFFIX.length)
if (model && model[strippedName]) {
if (definitions && definitions[strippedName]) {
return strippedName

@@ -152,2 +168,10 @@ }

const _isCompOrAssoc = (entity, k) => {
return (
entity.elements &&
entity.elements[k] &&
(entity.elements[k].type === 'cds.Composition' || entity.elements[k].type === 'cds.Association')
)
}
const _cleanDeepData = (entity, data) => {

@@ -159,3 +183,3 @@ if (!Array.isArray(data)) {

return Object.keys(entry || {}).reduce((result, k) => {
if (!(entity && entity.elements && entity.elements[k] && entity.elements[k].type === 'cds.Composition')) {
if (!_isCompOrAssoc(entity, k)) {
result[k] = entry[k]

@@ -239,7 +263,3 @@ }

const _findByKey = (entity, data, key) => {
return (
data.find(entry => {
return _isKeyEqual(entity, _key(entity, entry), key)
}) || null
)
return data.find(entry => _isKeyEqual(entity, _key(entity, entry), key)) || null
}

@@ -266,20 +286,68 @@

const _propagateKeys = (element, data, subData) => {
return subData.map(subEntry => {
return Object.assign(
{},
subEntry,
element.backLinks.reduce((result, backlink) => {
result[backlink.name] = data[backlink.target_element]
return result
}, {})
)
})
const _toOneElements = subEntity => {
return Object.keys(subEntity.elements)
.map(key => subEntity.elements[key])
.filter(element => element.is2one)
}
const hasCompositionDelete = (model, cqn) => {
const _toManyElements = subEntity => {
return Object.keys(subEntity.elements)
.map(key => subEntity.elements[key])
.filter(element => element.is2many)
}
const _toOneKeys = (subDataEntry, data, toOneElements, element) => {
const toOneKeys = {}
for (const toOneElement of toOneElements) {
if (toOneElement.name in subDataEntry) {
// self referencing backlinks
const links = element.compositionElements.find(
compositionElement => compositionElement.name === toOneElement.name
).links
const toOneData = subDataEntry[toOneElement.name]
for (const link of links) {
toOneKeys[link.name] = toOneData[link.target_element]
}
} else {
const backLinks = element.backLinks
for (const backLink of backLinks) {
toOneKeys[backLink.name] = data[backLink.target_element]
}
}
}
return toOneKeys
}
const _toManyKeys = (data, toManyElements, element) => {
const toManyKeys = {}
for (const toManyElement of toManyElements) {
if (element.name === toManyElement.name) {
for (const backLink of element.backLinks) {
toManyKeys[backLink.name] = data[backLink.target_element] || null
}
}
}
return toManyKeys
}
const _propagateKeys = (subEntity, element, data, subData) => {
const toOneElements = _toOneElements(subEntity)
const toManyElements = _toManyElements(subEntity)
const result = []
for (const subDataEntry of subData) {
const toOneKeys = _toOneKeys(subDataEntry, data, toOneElements, element)
const toManyKeys = _toManyKeys(data, toManyElements, element)
result.push(Object.assign({}, subDataEntry, toManyKeys, toOneKeys))
}
return result
}
const hasCompositionDelete = (definitions, cqn) => {
if (cqn && cqn.DELETE && cqn.DELETE.from) {
const entityName = _stripDraftSuffix(model, cqn.DELETE.from.name || cqn.DELETE.from)
const entity = model && model[entityName]
return entity && !!Object.keys(entity.elements || {}).find(k => entity.elements[k].type === 'cds.Composition')
const entityName = _stripDraftSuffix(definitions, cqn.DELETE.from.name || cqn.DELETE.from)
const entity = definitions && definitions[entityName]
if (entity) {
return !!Object.keys(entity.elements || {}).find(k => entity.elements[k].type === 'cds.Composition')
}
}

@@ -289,38 +357,50 @@ return false

const _addSubCascadeDeleteCQN = (compositionTree, level, cqns, draft) => {
const _addSubCascadeDeleteCQN = (compositionTree, level, cqns, draft, set = new Set()) => {
compositionTree.compositionElements.forEach(element => {
const subWhere = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('and')
}
result.push({ ref: [_addDraftSuffix(draft, element.source), backLink.name] }, '=', {
ref: [_addDraftSuffix(draft, element.target), backLink.target_element]
})
return result
}, [])
const whereKey = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('or')
}
result.push({ ref: [_addDraftSuffix(draft, element.source), backLink.name] }, 'is not null')
return result
}, [])
const where = [
'(',
...whereKey,
')',
'and',
'not exists',
{
SELECT: {
columns: [{ val: 1, as: '_exists' }],
from: { ref: [_addDraftSuffix(draft, element.target)] },
where: subWhere
if (!set.has(element.name)) {
let entity1
let entity2
const subWhere = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('and')
}
}
]
const subCQN = { DELETE: { from: _addDraftSuffix(draft, element.source), where: where } }
cqns[level] = cqns[level] || []
cqns[level].push(subCQN)
_addSubCascadeDeleteCQN(element, level + 1, cqns, draft)
entity1 = { alias: 'ALIAS1', entityName: _addDraftSuffix(draft, element.source), propertyName: backLink.name }
entity2 = {
alias: 'ALIAS2',
entityName: _addDraftSuffix(draft, element.target || element.source),
propertyName: backLink.target_element
}
result.push({ ref: [entity1.alias, entity1.propertyName] }, '=', { ref: [entity2.alias, entity2.propertyName] })
return result
}, [])
const whereKey = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('or')
}
result.push({ ref: [entity1.alias, backLink.name] }, 'is not null')
return result
}, [])
const where = [
'(',
...whereKey,
')',
'and',
'not exists',
{
SELECT: {
columns: [{ val: 1, as: '_exists' }],
from: { ref: [entity2.entityName], as: entity2.alias },
where: subWhere
}
}
]
const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
cqns[level] = cqns[level] || []
cqns[level].push(subCQN)
set.add(element.name)
_addSubCascadeDeleteCQN(element, level + 1, cqns, draft, set)
}
})

@@ -330,51 +410,64 @@ return cqns

const createCascadeDeleteCQNs = (model, cqn) => {
const createCascadeDeleteCQNs = (definitions, cqn) => {
const from = cqn.DELETE.from.name || cqn.DELETE.from
const entityName = _stripDraftSuffix(model, from)
const entityName = _stripDraftSuffix(definitions, from)
const draft = entityName !== from
const compositionTree = getCompositionTree(model, entityName, false)
const compositionTree = getCompositionTree(definitions, entityName, false)
return [[cqn], ..._addSubCascadeDeleteCQN(compositionTree, 0, [], draft)]
}
const _addSubReverseCascadeDeleteCQN = (compositionTree, level, cqn, cqns, draft) => {
const _addSubReverseCascadeDeleteCQN = (compositionTree, level, cqn, cqns, draft, set = new Set()) => {
compositionTree.compositionElements.forEach(element => {
const subWhere = [
...element.backLinks.reduce((result, backLink) => {
if (!set.has(element.name)) {
let entity1
let entity2
const subWhere = [
...element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('and')
}
entity1 = { alias: 'ALIAS1', entityName: _addDraftSuffix(draft, element.source), propertyName: backLink.name }
entity2 = {
alias: 'ALIAS2',
entityName: _addDraftSuffix(draft, element.target || element.source),
propertyName: backLink.target_element
}
result.push({ ref: [entity1.alias, entity1.propertyName] }, '=', {
ref: [entity2.alias, entity2.propertyName]
})
return result
}, [])
]
if (cqn.DELETE && cqn.DELETE.where && cqn.DELETE.where.length > 0) {
subWhere.push('and', '(', ...(cqn.DELETE.where || []), ')')
}
const whereKey = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('and')
result.push('or')
}
result.push({ ref: [_addDraftSuffix(draft, element.source), backLink.name] }, '=', {
ref: [_addDraftSuffix(draft, element.target), backLink.target_element]
})
result.push({ ref: [entity1.alias, entity1.propertyName] }, 'is not null')
return result
}, [])
]
if (cqn.DELETE && cqn.DELETE.where && cqn.DELETE.where.length > 0) {
subWhere.push('and', '(', ...(cqn.DELETE.where || []), ')')
const where = [
'(',
...whereKey,
')',
'and',
'exists',
{
SELECT: {
columns: [{ val: 1, as: '_exists' }],
from: { ref: [entity2.entityName], as: entity2.alias },
where: subWhere
}
}
]
const subCQN = { DELETE: { from: { ref: [entity1.entityName], as: entity1.alias }, where: where } }
cqns[level] = cqns[level] || []
cqns[level].push(subCQN)
set.add(element.name)
_addSubReverseCascadeDeleteCQN(element, level + 1, subCQN, cqns, draft, set)
}
const whereKey = element.backLinks.reduce((result, backLink) => {
if (result.length > 0) {
result.push('or')
}
result.push({ ref: [_addDraftSuffix(draft, element.source), backLink.name] }, 'is not null')
return result
}, [])
const where = [
'(',
...whereKey,
')',
'and',
'exists',
{
SELECT: {
columns: [{ val: 1, as: '_exists' }],
from: { ref: [_addDraftSuffix(draft, element.target)] },
where: subWhere
}
}
]
const subCQN = { DELETE: { from: _addDraftSuffix(draft, element.source), where: where } }
cqns[level] = cqns[level] || []
cqns[level].push(subCQN)
_addSubReverseCascadeDeleteCQN(element, level + 1, subCQN, cqns, draft)
})

@@ -384,18 +477,18 @@ return cqns

const createReverseCascadeDeleteCQNs = (model, cqn) => {
const createReverseCascadeDeleteCQNs = (definitions, cqn) => {
const from = cqn.DELETE.from.name || cqn.DELETE.from
const entityName = _stripDraftSuffix(model, from)
const entityName = _stripDraftSuffix(definitions, from)
const draft = entityName !== from
const compositionTree = getCompositionTree(model, entityName, false)
const compositionTree = getCompositionTree(definitions, entityName, false)
return [[cqn], ..._addSubReverseCascadeDeleteCQN(compositionTree, 0, cqn, [], draft)].reverse()
}
const hasDeepInsert = (model, cqn) => {
const hasDeepInsert = (definitions, cqn) => {
if (cqn && cqn.INSERT && cqn.INSERT.into && cqn.INSERT.entries) {
const entityName = _stripDraftSuffix(model, cqn.INSERT.into.name || cqn.INSERT.into)
const entity = model && model[entityName]
const entityName = _stripDraftSuffix(definitions, cqn.INSERT.into.name || cqn.INSERT.into)
const entity = definitions && definitions[entityName]
if (entity) {
return !!cqn.INSERT.entries.find(entry => {
return !!Object.keys(entry || {}).find(k => {
return entity.elements && entity.elements[k] && entity.elements[k].type === 'cds.Composition'
return _isCompOrAssoc(entity, k)
})

@@ -408,12 +501,7 @@ })

const _addSubDeepInsertCQN = (model, compositionTree, data, cqns, draft) => {
const _addSubDeepInsertCQN = (definitions, compositionTree, data, cqns, draft) => {
compositionTree.compositionElements.forEach(element => {
const subEntity = model[element.source]
const subEntity = definitions[element.source]
const into = _addDraftSuffix(draft, element.source)
const insertCQN = {
INSERT: {
into: into,
entries: []
}
}
const insertCQN = { INSERT: { into: into, entries: [] } }
const subData = data.reduce((result, entry) => {

@@ -424,3 +512,5 @@ if (element.name in entry) {

if (subData.length > 0) {
insertCQN.INSERT.entries.push(..._cleanDeepData(subEntity, _propagateKeys(element, entry, subData)))
insertCQN.INSERT.entries.push(
..._cleanDeepData(subEntity, _propagateKeys(subEntity, element, entry, subData))
)
result.push(...subData)

@@ -435,3 +525,3 @@ }

if (subData.length > 0) {
_addSubDeepInsertCQN(model, element, subData, cqns, draft)
_addSubDeepInsertCQN(definitions, element, subData, cqns, draft)
}

@@ -442,20 +532,26 @@ })

const createDeepInsertCQNs = (model, cqn) => {
const createDeepInsertCQNs = (definitions, cqn) => {
const into = cqn.INSERT.into.name || cqn.INSERT.into
const entityName = _stripDraftSuffix(model, into)
const entityName = _stripDraftSuffix(definitions, into)
const draft = entityName !== into
const data = cqn.INSERT.entries || []
const entity = model && model[entityName]
cqn.INSERT.entries = _cleanDeepData(entity, data)
const compositionTree = getCompositionTree(model, entityName, false)
return [cqn, ..._addSubDeepInsertCQN(model, compositionTree, data, [], draft)]
const dataEntries = cqn.INSERT.entries || []
const entity = definitions && definitions[entityName]
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
const toOneElements = _toOneElements(entity)
cqn.INSERT.entries = []
for (const dataEntry of dataEntries) {
const toOneKeys = _toOneKeys(dataEntry, dataEntries, toOneElements, compositionTree)
cqn.INSERT.entries.push(_cleanDeepData(entity, Object.assign({}, dataEntry, toOneKeys)))
}
return [cqn, ..._addSubDeepInsertCQN(definitions, compositionTree, dataEntries, [], draft)]
}
const hasDeepUpdate = (model, cqn) => {
const hasDeepUpdate = (definitions, cqn) => {
if (cqn && cqn.UPDATE && cqn.UPDATE.entity && cqn.UPDATE.data) {
const entityName = _stripDraftSuffix(model, cqn.UPDATE.entity.name || cqn.UPDATE.entity)
const entity = model && model[entityName]
const entityName = _stripDraftSuffix(definitions, cqn.UPDATE.entity.name || cqn.UPDATE.entity)
const entity = definitions && definitions[entityName]
if (entity) {
return !!Object.keys(cqn.UPDATE.data).find(k => {
return entity.elements && entity.elements[k] && entity.elements[k].type === 'cds.Composition'
return _isCompOrAssoc(entity, k)
})

@@ -467,4 +563,4 @@ }

function _selectDeepUpdateDataRecursion ({ model, compositionTree, entityName, data, result, draft, execute }) {
const entity = model && model[entityName]
function _selectDeepUpdateDataRecursion ({ definitions, compositionTree, entityName, data, result, draft, execute }) {
const entity = definitions && definitions[entityName]
const keys = _keys(entity, result)

@@ -491,3 +587,3 @@ return Promise.all(

return _selectDeepUpdateData({
model,
definitions,
compositionTree: element,

@@ -506,3 +602,3 @@ entityName: element.source,

const _selectDeepUpdateDataResult = ({
model,
definitions,
compositionTree,

@@ -530,7 +626,7 @@ entityName,

}
return _selectDeepUpdateDataRecursion({ model, compositionTree, entityName, data, result, draft, execute })
return _selectDeepUpdateDataRecursion({ definitions, compositionTree, entityName, data, result, draft, execute })
}
const _selectDeepUpdateData = ({
model,
definitions,
compositionTree,

@@ -546,9 +642,5 @@ entityName,

const root = !selectData
const entity = model && model[entityName]
const entity = definitions && definitions[entityName]
const from = _addDraftSuffix(draft, entity.name)
const selectCQN = {
SELECT: {
from: { ref: [from] }
}
}
const selectCQN = { SELECT: { from: { ref: [from] } } }
if (data !== undefined) {

@@ -578,3 +670,3 @@ selectCQN.SELECT.columns = []

return _selectDeepUpdateDataResult({
model,
definitions,
compositionTree,

@@ -597,14 +689,14 @@ entityName,

const selectDeepData = (model, entity, data, execute) => {
const selectDeepData = (definitions, entity, data, execute) => {
if (!Array.isArray(data)) {
return selectDeepData(model, entity, [data], execute)
return selectDeepData(definitions, entity, [data], execute)
}
const from = entity.name || entity
const entityName = _stripDraftSuffix(model, from)
const modelEntity = model && model[entityName]
const entityName = _stripDraftSuffix(definitions, from)
const modelEntity = definitions && definitions[entityName]
const draft = entityName !== from
const keys = _keys(modelEntity, data)
const compositionTree = getCompositionTree(model, entityName, false)
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
return _selectDeepUpdateData({
model,
definitions,
compositionTree,

@@ -619,11 +711,11 @@ entityName,

const selectDeepUpdateData = (model, cqn, execute) => {
const selectDeepUpdateData = (definitions, cqn, execute) => {
const from = cqn.UPDATE.entity.name || cqn.UPDATE.entity
const entityName = _stripDraftSuffix(model, from)
const entityName = _stripDraftSuffix(definitions, from)
const draft = entityName !== from
const data = cqn.UPDATE.data || {}
const where = cqn.UPDATE.where || []
const compositionTree = getCompositionTree(model, entityName, false)
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
return _selectDeepUpdateData({
model,
definitions,
compositionTree,

@@ -638,4 +730,4 @@ entityName,

const _addDeepSelectColumns = (model, compositionTree, data, columns) => {
const entity = model && model[compositionTree.source]
const _addDeepSelectColumns = (definitions, compositionTree, data, columns) => {
const entity = definitions && definitions[compositionTree.source]
_dataElements(entity).forEach(element => {

@@ -671,22 +763,18 @@ if (element.key) {

column.expand = subColumns
_addDeepSelectColumns(model, element, subData, subColumns)
_addDeepSelectColumns(definitions, element, subData, subColumns)
})
}
const createDeepUpdateSelectCQN = (model, cqn) => {
const createDeepUpdateSelectCQN = (definitions, cqn) => {
const from = cqn.UPDATE.entity.name || cqn.UPDATE.entity
const entityName = _stripDraftSuffix(model, from)
const entityName = _stripDraftSuffix(definitions, from)
const draft = entityName !== from
const data = cqn.UPDATE.data || {}
const columns = []
const selectCQN = {
SELECT: {
columns,
from: { ref: [from] }
}
}
const selectCQN = { SELECT: { columns, from: { ref: [from] } } }
if (cqn.UPDATE.where) {
selectCQN.SELECT.where = cqn.UPDATE.where
}
const compositionTree = getCompositionTree(model, entityName, false)
_addDeepSelectColumns(model, compositionTree, [data], columns)
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
_addDeepSelectColumns(definitions, compositionTree, [data], columns)
return selectCQN

@@ -724,7 +812,3 @@ }

updateCQNs.push({
UPDATE: {
entity: entityName,
data: diff,
where: _whereKey(key)
}
UPDATE: { entity: entityName, data: diff, where: _whereKey(key) }
})

@@ -764,3 +848,3 @@ }

function _addSubDeepUpdateCQNCollect (model, cqns, updateCQNs, insertCQN, deleteCQN) {
function _addSubDeepUpdateCQNCollect (definitions, cqns, updateCQNs, insertCQN, deleteCQN) {
if (updateCQNs.length > 0) {

@@ -772,3 +856,3 @@ cqns[0] = cqns[0] || []

cqns[0] = cqns[0] || []
createDeepInsertCQNs(model, insertCQN).forEach(insertCQN => {
createDeepInsertCQNs(definitions, insertCQN).forEach(insertCQN => {
const intoCQN = cqns[0].find(cqn => {

@@ -787,3 +871,3 @@ return cqn.INSERT && cqn.INSERT.into === insertCQN.INSERT.into

cqns[0].push(deleteCQN)
createCascadeDeleteCQNs(model, deleteCQN).forEach((deleteCQNs, index) => {
createCascadeDeleteCQNs(definitions, deleteCQN).forEach((deleteCQNs, index) => {
if (index > 0) {

@@ -796,3 +880,3 @@ _addSubDeepUpdateCQNCollectDelete(deleteCQNs, cqns, index)

function _addSubDeepUpdateCQNRecursion ({ model, compositionTree, entity, data, selectData, cqns, draft }) {
function _addSubDeepUpdateCQNRecursion ({ definitions, compositionTree, entity, data, selectData, cqns, draft }) {
compositionTree.compositionElements.forEach(element => {

@@ -804,3 +888,5 @@ const subData = []

const elementValue = entry[element.name].val || entry[element.name]
subData.push(..._propagateKeys(element, entry, Array.isArray(elementValue) ? elementValue : [elementValue]))
subData.push(
..._propagateKeys(entity, element, entry, Array.isArray(elementValue) ? elementValue : [elementValue])
)
const selectEntry = _findByKey(entity, selectData, _key(entity, entry))

@@ -811,2 +897,3 @@ if (selectEntry && element.name in selectEntry) {

..._propagateKeys(
entity,
element,

@@ -821,3 +908,3 @@ selectEntry,

_addSubDeepUpdateCQN({
model,
definitions,
compositionTree: element,

@@ -833,18 +920,8 @@ data: subData,

const _addSubDeepUpdateCQN = ({ model, compositionTree, data, selectData, cqns, draft }) => {
const entity = model && model[compositionTree.source]
const _addSubDeepUpdateCQN = ({ definitions, compositionTree, data, selectData, cqns, draft }) => {
const entity = definitions && definitions[compositionTree.source]
const entityName = _addDraftSuffix(draft, entity.name)
const updateCQNs = []
const insertCQN = {
INSERT: {
into: entityName,
entries: []
}
}
const deleteCQN = {
DELETE: {
from: entityName,
where: []
}
}
const insertCQN = { INSERT: { into: entityName, entries: [] } }
const deleteCQN = { DELETE: { from: entityName, where: [] } }
_addSubDeepUpdateCQNForDelete({ compositionTree, entity, entityName, data, selectData, deleteCQN })

@@ -860,3 +937,3 @@ _addSubDeepUpdateCQNForUpdateInsert({

})
_addSubDeepUpdateCQNCollect(model, cqns, updateCQNs, insertCQN, deleteCQN)
_addSubDeepUpdateCQNCollect(definitions, cqns, updateCQNs, insertCQN, deleteCQN)
if (data.length === 0) {

@@ -866,3 +943,3 @@ return Promise.resolve()

return _addSubDeepUpdateCQNRecursion({
model,
definitions,
compositionTree,

@@ -877,5 +954,5 @@ entity,

const createDeepUpdateCQNs = (model, cqn, selectData) => {
const createDeepUpdateCQNs = (definitions, cqn, selectData) => {
if (!Array.isArray(selectData)) {
return createDeepUpdateCQNs(model, cqn, [selectData])
return createDeepUpdateCQNs(definitions, cqn, [selectData])
}

@@ -890,16 +967,9 @@ if (selectData.length === 0) {

const from = cqn.UPDATE.entity.name || cqn.UPDATE.entity
const entityName = _stripDraftSuffix(model, from)
const entityName = _stripDraftSuffix(definitions, from)
const draft = entityName !== from
const data = cqn.UPDATE.data || {}
const entity = model && model[entityName]
const entity = definitions && definitions[entityName]
const entry = Object.assign({}, data, _key(entity, selectData[0]))
const compositionTree = getCompositionTree(model, entityName, false)
const subCQNs = _addSubDeepUpdateCQN({
model,
compositionTree,
data: [entry],
selectData,
cqns: [],
draft
})
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
const subCQNs = _addSubDeepUpdateCQN({ definitions, compositionTree, data: [entry], selectData, cqns: [], draft })
subCQNs.forEach((subCQNs, index) => {

@@ -906,0 +976,0 @@ cqns[index] = cqns[index] || []

@@ -1,5 +0,27 @@

const _isContains = ref => {
return ref && /^(:?not )?contains$/.test(ref[0].toLowerCase())
const _fnName = element => {
if (element.ref) {
return element.ref[0].toLowerCase()
}
if (element.func) {
return element.func.toLowerCase()
}
return ''
}
const _isContains = element => {
return /^(:?not )?contains$/.test(_fnName(element))
}
const _args = element => {
if (element.ref) {
return element.ref[1].args
}
if (element.func) {
return element.args
}
}
const _getCQN = cqn => {

@@ -23,3 +45,3 @@ if (cqn.SELECT) {

return _partialCqn && _partialCqn.where ? _partialCqn.where.some(({ ref }) => _isContains(ref)) : false
return _partialCqn && _partialCqn.where ? _partialCqn.where.some(element => _isContains(element)) : false
}

@@ -31,3 +53,3 @@

if (columns[i].ref) {
res.push({ ref: ['lower', { args: [columns[i]] }] }, notContains ? 'not like' : 'like', {
res.push({ func: 'lower', args: [columns[i]] }, notContains ? 'not like' : 'like', {
val: `%${searchText.replace(/(_|%)/g, '\\$1')}%`

@@ -44,4 +66,11 @@ })

const _transformCQN = (notContains, args) => {
const columns = args[0].xpr ? args[0].xpr : args[0].list
const _columns = args => {
return args[0].xpr || args[0].list || [args[0]]
}
const _transformCQN = curr => {
const notContains = _fnName(curr).includes('not')
const args = _args(curr)
const columns = _columns(args)
const res = []

@@ -83,6 +112,3 @@

resCQN.where = _partialCqn.where.reduce(
(res, curr) =>
_isContains(curr.ref)
? [...res, '(', ..._transformCQN(curr.ref[0].toLowerCase().includes('not'), curr.ref[1].args), ')']
: [...res, curr],
(res, curr) => (_isContains(curr) ? [...res, '(', ..._transformCQN(curr), ')'] : [...res, curr]),
[]

@@ -89,0 +115,0 @@ )

@@ -219,105 +219,115 @@ const { resolveAssociation, isAssociation, isComplex } = require('../utils/associations')

/**
* Transform values for a result row.
* @param {Object} row - To be converted row.
* @param {Map} mapper - Instructions, how to transform.
* @private
*/
const _transformRow = (row, mapper) => {
/*
* If mapper is iterated, no iteration at all will happen in case of no mappers.
* Less iteration as if all row keys would iterated.
*/
for (const [column, converter] of mapper.entries()) {
row[column] = converter(row[column])
const _getCombineStructureConvert = (structure, columnName, propName, fn) => {
const length = structure.length
return row => {
if (row[columnName] === undefined) {
return
}
if (!row[structure[0]]) {
row[structure[0]] = {}
}
let subObj = row[structure[0]]
for (let i = 1; i < length; i++) {
subObj = subObj[structure[i]] = {}
}
subObj[propName] = fn ? fn(row[columnName]) : row[columnName]
delete row[columnName]
}
}
/**
* Post process the result as given by the db driver.
* @param {*} result - The result as returned by the db driver.
* @param {Map} dataMapper - Instructions, how to transform.
* @returns {*}
* @private
*/
const _postProcessData = (result, dataMapper) => {
if (Array.isArray(result)) {
for (let i = 0, length = result.length; i < length; i++) {
_transformRow(result[i], dataMapper)
const _getCombineRenameConvert = (columnName, propName, fn) => {
return row => {
if (row[columnName] === undefined) {
return
}
} else if (typeof result === 'object') {
_transformRow(result, dataMapper)
row[propName] = fn ? fn(row[columnName]) : row[columnName]
delete row[columnName]
}
}
return result
const _getConvert = (columnName, fn) => {
return row => {
row[columnName] = fn(row[columnName])
}
}
const _transformProperties = (row, propertyMapper) => {
for (const key of Object.keys(row)) {
if (propertyMapper.has(key)) {
row[propertyMapper.get(key)] = row[key]
const _getRemoveMapper = (mapper, propName) => {
if (mapper) {
const fn = mapper.get(propName)
mapper.delete(propName)
delete row[key]
}
return fn
}
}
/**
* Rename the properties.
* @param {*} result - The result as returned by the db driver.
* @param {Map} propertyMapper - Instructions, how to transform.
* @returns {*}
* @private
*/
const _postProcessProperties = (result, propertyMapper) => {
if (Array.isArray(result)) {
for (let i = 0, length = result.length; i < length; i++) {
_transformProperties(result[i], propertyMapper)
}
} else if (typeof result === 'object') {
_transformProperties(result, propertyMapper)
const _propertyMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
if (!propertyMapper) {
return
}
return result
for (const [columnName, propName] of propertyMapper.entries()) {
const fn = _getRemoveMapper(dataMapper, propName)
const structure = _getRemoveMapper(objStructMapper, propName)
mapper.push(
structure
? _getCombineStructureConvert(structure, columnName, propName, fn)
: _getCombineRenameConvert(columnName, propName, fn)
)
}
}
const _transformBoth = (result, dataMapper, propertyMapper) => {
if (Array.isArray(result)) {
for (let i = 0, length = result.length; i < length; i++) {
_transformProperties(result[i], propertyMapper)
_transformRow(result[i], dataMapper)
}
} else if (typeof result === 'object') {
_transformProperties(result, propertyMapper)
_transformRow(result, dataMapper)
const _objStructMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
if (!objStructMapper) {
return
}
return result
for (const [propName, structure] of objStructMapper.entries()) {
mapper.push(_getCombineStructureConvert(structure, propName, propName, _getRemoveMapper(dataMapper, propName)))
}
}
const _mapHasEntries = map => {
return map && map instanceof Map && map.size !== 0
}
const _postProcessObjStruct = (result, objStructMap) => {
if (!result || !Array.isArray(result)) {
const _dataMapper = (dataMapper, propertyMapper, objStructMapper, mapper) => {
if (!dataMapper) {
return
}
for (let i = 0, length = result.length; i < length; i++) {
let row = result[i]
for (const key of Object.keys(row)) {
if (objStructMap.has(key)) {
const obj = {}
let current = obj
for (const element of objStructMap.get(key)) {
current[element] = {}
current = current[element]
}
current[key] = row[key]
row = Object.assign(row, obj)
for (const [columnName, converter] of dataMapper.entries()) {
mapper.push(_getConvert(columnName, converter))
}
}
delete row[key]
}
}
/**
* Generate the mapper per row up front, so that we do not have to iterate over possibly three mappers
* @param dataMapper
* @param propertyMapper
* @param objStructMapper
* @returns {Array}
* @private
*/
const _combineMappers = (dataMapper, propertyMapper, objStructMapper) => {
const mapper = []
// Technical names + optionally structure and/or type conversions
_propertyMapper(dataMapper, propertyMapper, objStructMapper, mapper)
// Deep structures + optionally type conversions
_objStructMapper(dataMapper, propertyMapper, objStructMapper, mapper)
// type conversion
_dataMapper(dataMapper, propertyMapper, objStructMapper, mapper)
return mapper
}
const _processRow = (mapper, mapperCount, row) => {
for (let i = 0; i < mapperCount; i++) {
mapper[i](row)
}

@@ -331,2 +341,3 @@ }

* @param {Map} propertyMapper - Instructions, how to rename properties.
* @param {Map} objStructMapper - Instructions, how to rename properties.
* @returns {*}

@@ -336,21 +347,17 @@ * @private

const postProcess = (result, dataMapper, propertyMapper, objStructMapper) => {
const hasDataMapper = _mapHasEntries(dataMapper)
const hasPropertyMapper = _mapHasEntries(propertyMapper)
const mapper = _combineMappers(dataMapper, propertyMapper, objStructMapper)
const mapperCount = mapper.length
if (hasDataMapper && hasPropertyMapper) {
_transformBoth(result, dataMapper, propertyMapper)
} else {
if (hasDataMapper) {
_postProcessData(result, dataMapper)
}
if (mapperCount === 0) {
return result
}
if (hasPropertyMapper) {
_postProcessProperties(result, propertyMapper)
if (Array.isArray(result)) {
for (let i = 0, length = result.length; i < length; i++) {
_processRow(mapper, mapperCount, result[i])
}
} else {
_processRow(mapper, mapperCount, result)
}
if (objStructMapper) {
_postProcessObjStruct(result, objStructMapper)
}
return result

@@ -361,2 +368,3 @@ }

const entities = []
if (from.args || from.SET) {

@@ -399,5 +407,7 @@ const args = from.args || from.SET.args

let property = element.ref[element.ref.length - 1]
if (propertyMapper && propertyMapper.has(property)) {
property = propertyMapper.get(property)
}
map.set(property, element.ref.slice(0, element.ref.length - 1))

@@ -404,0 +414,0 @@ }

@@ -9,3 +9,3 @@ const crypto = require('crypto')

const TO_MANY = Symbol.for('toMany')
const TO_MANY_KEYS = Symbol.for('toManyKeys')
const SKIP_MAPPING = Symbol.for('skipMapping')

@@ -29,3 +29,2 @@ const IDENTIFIER = Symbol.for('identifier')

* Each expand with a to many target will result in an extra query and config.
* @returns {Array}
*/

@@ -248,4 +247,7 @@ buildJoinQueries () {

_checkOrderByWhereElementRecursive (cqn, element, tableAlias) {
if (element.ref) {
if (element.func) {
element = Object.assign({}, element)
this._functionNeedsReplacement(cqn, tableAlias, element)
} else if (element.ref) {
element = Object.assign({}, element)
element.ref = element.ref.slice(0)

@@ -302,26 +304,47 @@

_functionNeedsReplacement (cqn, tableAlias, element) {
_isValidFunc (element) {
if (typeof element.func === 'string' && Array.isArray(element.args)) {
return true
}
if (
typeof element.ref[0] !== 'string' ||
typeof element.ref[1] !== 'object' ||
!Array.isArray(element.ref[1].args)
typeof element.ref[0] === 'string' &&
typeof element.ref[1] === 'object' &&
Array.isArray(element.ref[1].args)
) {
return
return true
}
}
element.ref[1] = Object.assign({}, element.ref[1])
element.ref[1].args = element.ref[1].args.map(arg => {
if (Array.isArray(arg.list)) {
arg = Object.assign({}, arg)
arg.list = arg.list.map(item => {
return this._checkOrderByWhereElementRecursive(cqn, item, tableAlias)
})
_mapArg (arg, cqn, tableAlias) {
if (Array.isArray(arg.list)) {
arg = Object.assign({}, arg)
arg.list = arg.list.map(item => {
return this._checkOrderByWhereElementRecursive(cqn, item, tableAlias)
})
return arg
}
return arg
}
return this._checkOrderByWhereElementRecursive(cqn, arg, tableAlias)
})
return this._checkOrderByWhereElementRecursive(cqn, arg, tableAlias)
}
_functionNeedsReplacement (cqn, tableAlias, element) {
if (!this._isValidFunc(element)) {
return
}
if (element.ref) {
element.ref[1] = Object.assign({}, element.ref[1])
element.ref[1].args = element.ref[1].args.map(arg => {
return this._mapArg(arg, cqn, tableAlias)
})
} else {
element.args = element.args.slice(0)
element.args = element.args.map(arg => {
return this._mapArg(arg, cqn, tableAlias)
})
}
}
/**

@@ -334,3 +357,2 @@ * Build CQN(s) with JOINs for expanding. In case of expanding with to many an additional CQN will be pushed to toManyCQN.

* @param {string} arg.tableAlias - Table alias
* @param {Array} arg.toManyCQN - List of build CQNs which are used to read expands with to many target.
* @param {Array} arg.toManyTree - Information, where the expand array is located in the result array.

@@ -340,3 +362,3 @@ * @returns {Object}

*/
_expandedToFlat ({ entity, givenColumns, readToOneCQN, parentAlias, tableAlias, toManyTree }) {
_expandedToFlat ({ entity, givenColumns, readToOneCQN, tableAlias, toManyTree }) {
const toManyColumns = []

@@ -398,7 +420,2 @@ const mappings = this._getMappingObject(toManyTree)

* Adds JOIN instructions to CQN for expands with 1:1 target and returns config how to map it back.
* @param column
* @param entity
* @param readToOneCQN
* @param toManyCQN
* @param toManyTree
* @returns {Object}

@@ -431,5 +448,6 @@ * @private

entity: this._getEntityForTable(target),
givenColumns: column.expand,
givenColumns: column.expand.filter(
columnObj => !(columnObj.ref && columnObj.ref.length && columnObj.ref[0] === 'IsActiveEntity')
),
readToOneCQN: readToOneCQN,
parentAlias: parentAlias,
tableAlias: tableAlias,

@@ -720,4 +738,2 @@ toManyTree: extendedToManyTree

* @param entity
* @param readToOneCQN
* @param tableAlias
* @returns {Array}

@@ -738,2 +754,9 @@ * @private

_hasCompoundKeys (ref) {
if (ref.endsWith('_drafts')) {
return Object.keys(this._csn.definitions[ref.replace('_drafts', '')].keys).length > 1
}
return Object.keys(this._csn.definitions[ref].keys).length > 1
}
/**

@@ -748,5 +771,9 @@ * Construct the base CQN for a to many expands.

const on = getOnCond(entity.elements[column.ref[0]], column.ref[0], this._csn, tableAlias, 'filterExpand')
const filterExpand = this._getFilterExpandCQN(readToOneCQN, on, parentAlias)
const joinColumns = this._getJoinColumnsFromOnAddToMapping(mappings[column.ref[0]], parentAlias, on)
const filterExpand = this._getFilterExpandCQN(readToOneCQN, on, parentAlias, entity.keys)
if (this._hasCompoundKeys(ref)) {
filterExpand.SELECT.distinct = true
}
const expandedEntity = this._csn.definitions[entity.elements[column.ref[0]].target]
const joinColumns = this._getJoinColumnsFromOnAddToMapping(mappings[column.ref[0]], parentAlias, on, entity)

@@ -770,3 +797,4 @@ const cqn = {

joinColumns,
isActive: cqn[IS_ACTIVE]
isActive: cqn[IS_ACTIVE],
parentEntity: entity
})

@@ -896,3 +924,4 @@

xpr: ['case', 'when', hasDraftQuery, 'IS NOT NULL', 'then', 'true', 'else', 'false', 'end'],
as: 'HasDraftEntity'
as: 'HasDraftEntity',
cast: { type: 'cds.Boolean' }
}

@@ -947,7 +976,12 @@ }

if (entry.ref && entry.as) {
return Object.assign({}, entry, { [IDENTIFIER]: entry.ref[1], ref: [entry.ref[0], entry.as] })
const newRef = entry.ref[0] === 'filterExpand' ? [entry.as] : [entry.ref[0], entry.as]
return Object.assign({}, entry, { [IDENTIFIER]: entry.ref[1], ref: newRef })
}
if (entry.val) {
return { [IDENTIFIER]: entry.as, ref: [tableAlias, entry.as], as: entry.as }
const returnValue = { [IDENTIFIER]: entry.as, ref: [tableAlias, entry.as], as: entry.as }
if (SKIP_MAPPING in entry) {
returnValue[SKIP_MAPPING] = entry[SKIP_MAPPING]
}
return returnValue
}

@@ -1040,3 +1074,3 @@

*/
_getFilterExpandCQN (readToOneCQN, on, parentAlias) {
_getFilterExpandCQN (readToOneCQN, on, parentAlias, keyObject) {
const columns = []

@@ -1046,14 +1080,12 @@

if (typeof entry === 'object' && entry.ref[0] === 'filterExpand') {
columns.push({
ref: [
parentAlias,
readToOneCQN.columns.some(ref => ref[IDENTIFIER] === entry.ref[1])
? `${parentAlias}_${entry.ref[1]}`
: entry.ref[1]
],
as: entry.ref[1]
})
columns.push(this._getColumnObjectForFilterExpand(readToOneCQN, parentAlias, entry.ref[1]))
}
}
for (const key of Object.keys(keyObject).filter(key => key !== 'IsActiveEntity')) {
if (!columns.map(entry => entry.as).includes(key)) {
columns.push(this._getColumnObjectForFilterExpand(readToOneCQN, parentAlias, key))
}
}
return {

@@ -1065,2 +1097,17 @@ SELECT: Object.assign({}, readToOneCQN, { columns: columns }),

_getColumnObjectForFilterExpand (readToOneCQN, parentAlias, key) {
return {
ref: [parentAlias, readToOneCQN.columns.some(ref => ref[IDENTIFIER] === key) ? `${parentAlias}_${key}` : key],
as: key
}
}
_getValueFromEntry (entry, parentAlias, key) {
let value = entry[key] || entry[key.toUpperCase()]
if (value === undefined) {
value = entry[`${parentAlias}_${key}`] || entry[`${parentAlias}_${key}`.toUpperCase()]
}
return value
}
/**

@@ -1071,15 +1118,16 @@ * In case a column is used at a JOIN, it needs to be added to the list of selected columns.

*/
_getJoinColumnsFromOnAddToMapping (mapping, parentAlias, on) {
_getJoinColumnsFromOnAddToMapping (mapping, parentAlias, on, entity) {
const columns = []
const columnNames = []
mapping[TO_MANY_KEYS] = []
for (const keyName of Object.keys(entity.keys)) {
const columnNameAlt = keyName === 'IsActiveEntity' ? 'IsActiveEntity' : `${parentAlias}_${keyName}`
if (!columnNames.includes(columnNameAlt)) {
columnNames.push(columnNameAlt)
}
}
for (const entry of on) {
if (typeof entry === 'object' && entry.ref[0] === 'filterExpand') {
columnNames.push(`${parentAlias}_${entry.ref[1]}`)
} else if (typeof entry === 'object') {
if (typeof entry === 'object' && entry.ref[0] !== 'filterExpand') {
const as = entry.ref.join('_')
mapping[TO_MANY_KEYS].push(as)
columns.push({

@@ -1096,6 +1144,8 @@ ref: entry.ref,

const keyValue = []
const keyList = atExpanded ? mapping[TO_MANY_KEYS] : columnNames
const keyList = atExpanded
? Object.keys(entry).filter(keyName => keyName.toLowerCase().startsWith('filterexpand_'))
: columnNames
for (const key of keyList) {
keyValue.push(entry[key] || entry[key.toUpperCase()])
keyValue.push(this._getValueFromEntry(entry, parentAlias, key))
}

@@ -1114,5 +1164,6 @@

*/
_getColumnsForExpand ({ tableAlias, columnList, entity, joinColumns, isActive }) {
_getColumnsForExpand ({ tableAlias, columnList, entity, joinColumns, isActive, parentEntity = {} }) {
const columns = []
const keys = this._getKeyNames(entity)
const parentKeys = this._getKeyNames(parentEntity)

@@ -1123,3 +1174,3 @@ for (const column of columnList.expand) {

} else {
this._addToColumnList(columns, tableAlias, column, isActive)
this._addToColumnList(columns, entity, tableAlias, column, isActive)
}

@@ -1130,2 +1181,3 @@ }

this._addMissingKeyColumns(columns, tableAlias, keys, isActive)
this._addMissingParentKeyColumns(columns, 'filterExpand', parentKeys, isActive)

@@ -1135,6 +1187,6 @@ return columns

_createIsActiveEntity (isActive) {
_createCalculatedBooleanColumn (alias, isActive) {
return {
val: isActive,
as: 'IsActiveEntity',
as: alias,
cast: { type: 'cds.Boolean' }

@@ -1144,10 +1196,34 @@ }

_addToColumnList (columns, tableAlias, column, isActive) {
if (column.ref && column.ref[column.ref.length - 1] === 'IsActiveEntity' && typeof isActive === 'boolean') {
columns.push(this._createIsActiveEntity(isActive))
return
_createIsActiveEntityOfParent (isActive, tableAlias) {
return {
val: isActive,
as: `${tableAlias}_IsActiveEntity`,
cast: { type: 'cds.Boolean' },
[SKIP_MAPPING]: true
}
}
_addToColumnList (columns, entity, tableAlias, column, isActive) {
const columnName = column.ref[column.ref.length - 1]
if (typeof isActive === 'boolean') {
if (columnName === 'IsActiveEntity') {
columns.push(this._createCalculatedBooleanColumn('IsActiveEntity', isActive))
return
}
if (columnName === 'HasActiveEntity') {
columns.push(this._createCalculatedBooleanColumn('HasActiveEntity', false))
return
}
if (isActive && columnName === 'HasDraftEntity') {
columns.push(this._getHasDraftEntityXpr(entity, tableAlias))
return
}
}
columns.push({

@@ -1185,3 +1261,3 @@ ref: [tableAlias, columnName],

if (key === 'IsActiveEntity' && typeof isActive === 'boolean') {
columns.push(this._createIsActiveEntity(isActive))
columns.push(this._createCalculatedBooleanColumn(key, isActive))
} else {

@@ -1195,2 +1271,16 @@ columns.push({

}
_addMissingParentKeyColumns (columns, tableAlias, keys, parentIsActive) {
for (const key of keys) {
if (key === 'IsActiveEntity' && typeof parentIsActive === 'boolean') {
columns.push(this._createIsActiveEntityOfParent(parentIsActive, tableAlias))
} else {
columns.push({
ref: [tableAlias, key],
as: `${tableAlias}_${key}`,
[SKIP_MAPPING]: true
})
}
}
}
}

@@ -1197,0 +1287,0 @@

@@ -34,6 +34,4 @@ const EXPAND = Symbol.for('expand')

_parseMainResult (result, mappings, conversionMapper, toManyTree) {
const resultCache = this._getResultCache(toManyTree)
for (const entry of result) {
const parsed = this._parseRaw({ mappings, toManyTree, conversionMapper, resultCache, entry })
const parsed = this._parseRaw({ mappings, toManyTree, conversionMapper, entry })

@@ -48,9 +46,11 @@ if (parsed) {

* Takes one result row (entry) and converts it into expanded row according to the config at the structureMap.
* @param {Map} mappings - Config how to structure the entry. Can be nested which will lead to recursion.
* @param {Map} conversionMapper - Post processing of values like 1/0 to true/false.
* @param {Object} entry - One row (entry) of the result set.
* @param {Object} config
* @param {Map} config.mappings - Config how to structure the entry. Can be nested which will lead to recursion.
* @param {Map} config.conversionMapper - Post processing of values like 1/0 to true/false.
* @param {Object} config.entry - One row (entry) of the result set.
* @param {Object} config.toManyTree - Tree of 'to many' associations.
* @returns {Object}
* @private
*/
_parseRaw ({ mappings, toManyTree, conversionMapper, resultCache, entry }) {
_parseRaw ({ mappings, toManyTree, conversionMapper, entry }) {
let isEntityNull

@@ -72,3 +72,2 @@

conversionMapper: conversionMapper,
resultCache: this._getResultCache(toManyTree.concat(key)),
entry: entry

@@ -84,4 +83,5 @@ })

// The Hana hdb client converts to upper case in plain mode
const rawValue = entry[mappings[key]] || entry[mappings[key].toUpperCase()]
const rawValue = mappings[key] in entry ? entry[mappings[key]] : entry[mappings[key].toUpperCase()]
// Assume a DB will not return undefined, but always null
row[key] = this._convertValue(rawValue, conversionMapper.get(mappings[key]))

@@ -136,3 +136,3 @@

for (const entry of result) {
const parsed = this._parseRaw({ mappings: expandMapping, toManyTree, conversionMapper, resultCache, entry })
const parsed = this._parseRaw({ mappings: expandMapping, toManyTree, conversionMapper, entry })

@@ -139,0 +139,0 @@ if (parsed) {

@@ -73,3 +73,3 @@ const _toRef = (associationName, alias, columnName) => {

return _args(csnElement, associationName, csn, selectAlias, joinAlias)
return ['(', ..._args(csnElement, associationName, csn, selectAlias, joinAlias), ')']
}

@@ -76,0 +76,0 @@

@@ -40,3 +40,26 @@ // OLD CSN

const _toOn = (one, two, op, csn, csnElement, associationName, selectAlias, joinAlias) => {
const _aliasStartsWithAssociation = (alias, associationName) => {
if (alias) {
return alias.startsWith(`${associationName}.`)
}
return false
}
const _refOrNullVal = (alias, secondAlias, associationName, joinAlias, selectAlias) => {
if (alias) {
return _toRef(
associationName,
_aliasStartsWithAssociation(secondAlias, associationName) ? joinAlias : selectAlias,
alias
)
}
return { val: null }
}
const _toOn = (arg1, arg2, op, csn, csnElement, associationName, selectAlias, joinAlias) => {
const one = arg1 ? arg1['='] : null
const two = arg2 ? arg2['='] : null
if (one === '$self' || two === '$self') {

@@ -46,6 +69,10 @@ return _selfToOn(csn, csnElement, op, one === '$self' ? two : one, joinAlias, selectAlias)

if (op === 'isNull' || op === 'isNotNull') {
return [_toRef(associationName, selectAlias, one), op === 'isNull' ? '=' : '!=', { val: null }]
}
return [
_toRef(associationName, two.startsWith(`${associationName}.`) ? joinAlias : selectAlias, one),
_refOrNullVal(one, two, associationName, joinAlias, selectAlias),
op,
_toRef(associationName, two.startsWith(`${associationName}.`) ? selectAlias : joinAlias, two)
_refOrNullVal(two, two, associationName, joinAlias, selectAlias)
]

@@ -65,7 +92,7 @@ }

if (args[i].op) {
if (args[i] && args[i].op) {
on.push(..._args(csn, csnElement, associationName, args[i], selectAlias, joinAlias))
i++
} else {
on.push(..._toOn(args[i]['='], args[i + 1]['='], op, csn, csnElement, associationName, selectAlias, joinAlias))
on.push(..._toOn(args[i], args[i + 1], op, csn, csnElement, associationName, selectAlias, joinAlias))
i = i + 2

@@ -111,3 +138,3 @@ }

return _args(csn, csnElement, associationName, csnElement.onCond, selectAlias, joinAlias)
return ['(', ..._args(csn, csnElement, associationName, csnElement.onCond, selectAlias, joinAlias), ')']
}

@@ -114,0 +141,0 @@

@@ -23,2 +23,8 @@ const BaseBuilder = require('./BaseBuilder')

get ReferenceBuilder () {
const ReferenceBuilder = require('./ReferenceBuilder')
Object.defineProperty(this, 'ReferenceBuilder', { value: ReferenceBuilder })
return ReferenceBuilder
}
/**

@@ -41,3 +47,3 @@ * Builds an Object based on the properties of the CQN object.

this._from()
if (this._obj.DELETE.hasOwnProperty('where')) {
if (Array.isArray(this._obj.DELETE.where) && this._obj.DELETE.where.length > 0) {
this._where()

@@ -52,3 +58,11 @@ }

this._outputObj.sql.push('FROM')
if (typeof this._obj.DELETE.from === 'string') {
if (this._obj.DELETE.from.ref) {
const res = new this.ReferenceBuilder(this._obj.DELETE.from, this._options, this._csn).build()
this._outputObj.sql.push(res.sql)
if (this._obj.DELETE.from.as) {
// identifier
this._outputObj.sql.push('as', this._quoteElement(this._obj.DELETE.from.as))
}
} else if (typeof this._obj.DELETE.from === 'string') {
this._outputObj.sql.push(this._quoteElement(this._obj.DELETE.from))

@@ -55,0 +69,0 @@ } else {

@@ -175,3 +175,3 @@ const BaseBuilder = require('./BaseBuilder')

for (let i = 0, len = list.length; i < len; i++) {
this._addToOutputObj(new this.ReferenceBuilder(list[i], this._options, this._csn).build(), false)
this._expressionElementToSQL(list[i])

@@ -209,2 +209,4 @@ if (len > 1 && i + 1 < len) {

return this._funcOutputFromElement(element)
case 'list':
return this._addListToOutputObj(element.list)
}

@@ -250,9 +252,18 @@ }

let containsSubFunc = false
for (const arg of element.args) {
const result = new ExpressionBuilder([arg], this._options, this._csn).build()
const { sql, values } = result
args.push(sql)
this._outputObj.values.push(...values)
if (arg.func) {
const result = new ExpressionBuilder([arg], this._options, this._csn).build()
const { sql, values } = result
args.push(sql)
this._outputObj.values.push(...values)
containsSubFunc = true
}
}
if (!containsSubFunc) {
this._refOutputFromElement(element)
return
}
this._outputObj.sql.push(`${element.func}(${args.join(',')})`)

@@ -259,0 +270,0 @@ }

const BaseBuilder = require('./BaseBuilder')
const sqlFunctions = ['contains']
/**
* ReferenceBuilder is used to take a part of a CQN object as an input and to build an object representing a reference
* with SQL string and empty values.
* with SQL string and values.
*
* Currently only support the simple references like below:
* Currently it supports the references like below:
*
* @example <caption>Simple ref part of CQN </caption>
* {ref: ['x']}
* {ref: ['func_name', { args: [func_args] }]}
*/
class ReferenceBuilder extends BaseBuilder {
get SelectBuilder () {
const SelectBuilder = require('./SelectBuilder')
Object.defineProperty(this, 'SelectBuilder', { value: SelectBuilder })
return SelectBuilder
}
get ExpressionBuilder () {
const ExpressionBuilder = require('./ExpressionBuilder')
Object.defineProperty(this, 'ExpressionBuilder', { value: ExpressionBuilder })
return ExpressionBuilder
}
/**

@@ -22,2 +32,6 @@ * Builds an Object based on the properties of the input object in the constructor.

* }
* {
* sql: '"func_name(?,?)"',
* values: [1, 'a']
* }
*

@@ -34,10 +48,4 @@ * @throws InvalidCqnObjectError

if (this._obj.ref && this._obj.ref.length > 1 && this._obj.ref[1].args) {
if (sqlFunctions.some(sqlFunction => this._obj.ref[0].toLowerCase().includes(sqlFunction))) {
// sql functions (exists,...)
this._handleContains()
} else {
// unsupported function
this._aggregatedFunction()
}
if (this._isFunction()) {
this._handleFunction()
} else if (this._obj.ref) {

@@ -50,5 +58,2 @@ // reference

}
if (this._obj.sort) {
this._outputObj.sql.push(this._obj.sort === 'desc' ? 'DESC' : 'ASC')
}
} else {

@@ -58,2 +63,6 @@ this._outputObj.sql.push(this._obj)

if (this._obj.hasOwnProperty('sort')) {
this._outputObj.sql.push(this._obj.sort === 'desc' ? 'DESC' : 'ASC')
}
this._outputObj.sql = this._outputObj.sql.join(' ')

@@ -63,2 +72,40 @@ return this._outputObj

_isFunction () {
return (this._obj.ref && this._obj.ref.length > 1 && this._obj.ref[1].args) || (this._obj.func && this._obj.args)
}
_handleFunction () {
const functionName = this._functionName()
let args = this._functionArgs()
if (functionName.toLowerCase().includes('contains')) {
args = [args[0], this._handleSearchTerm(args.slice(1, args.length - 1)), args[args.length - 1]]
}
this._outputObj.sql.push(functionName, '(')
if (typeof args === 'string') {
this._outputObj.sql.push(args, ')')
} else {
this._addFunctionArgs(args)
this._outputObj.sql.push(')')
}
}
_handleSearchTerm (params) {
let searchTerm = ''
for (const param of params) {
if (param.val) {
searchTerm = `${searchTerm}${param.val.replace(/(%|\?|\*|_)/g, '\\$1')}`
} else if (param === 'and') {
searchTerm = `${searchTerm} `
} else if (param === 'or') {
searchTerm = `${searchTerm} OR `
} else {
// param === 'not'
searchTerm = `${searchTerm}-`
}
}
return { val: `${searchTerm}` }
}
_parseReference (refArray) {

@@ -77,48 +124,33 @@ this._outputObj.sql.push(refArray.map(el => this._quoteElement(el)).join('.'))

_aggregatedFunction () {
this._outputObj.sql.push(this._obj.ref[0], '(')
if (typeof this._obj.ref[1].args === 'string') {
this._outputObj.sql.push(this._obj.ref[1].args, ')')
} else {
this._outputObj.sql.push(
this._obj.ref[1].args.map(e => new ReferenceBuilder(e, this._options, this._csn).build().sql).join(', '),
')'
)
}
}
_createLikeComparison (contains, columns, searchText) {
const length = columns.length
for (let i = 0; i < length; i++) {
this._outputObj.sql.push(`LOWER(${columns[i]})`, contains ? 'LIKE' : 'NOT LIKE', this._options.placeholder)
this._outputObj.values.push(`%${searchText.replace(/(_|%)/g, '\\$1')}%`)
if (i !== length - 1) {
this._outputObj.sql.push(contains ? 'OR' : 'AND')
_addFunctionArgs (args) {
const res = []
for (const arg of args) {
if (arg.ref) {
const { sql, values } = new ReferenceBuilder(arg, this._options, this._csn).build()
res.push(sql)
this._outputObj.values.push(...values)
} else if (arg.SELECT) {
const { sql, values } = new this.SelectBuilder(arg, this._options, this._csn).build()
res.push(sql)
this._outputObj.values.push(...values)
} else if (arg.val) {
this._outputObj.values.push(arg.val)
res.push(this._options.placeholder)
} else if (arg.list) {
const { sql, values } = new this.ExpressionBuilder([arg], this._options, this._csn).build()
res.push(sql)
this._outputObj.values.push(...values)
} else if (typeof arg === 'string') {
res.push(arg)
}
}
this._outputObj.sql.push(res.join(', '))
}
_handleContains () {
const contains = !this._obj.ref[0].toLowerCase().includes('not')
const columns = this._columnsFromContains(this._obj.ref[1].args)
const params = this._obj.ref[1].args.slice(1)
for (const param of params) {
if (param === 'or' || param === 'and' || param === 'not') {
this._outputObj.sql.push(param)
} else {
const searchText = param.val.toLowerCase()
this._outputObj.sql.push('(')
this._createLikeComparison(contains, columns, searchText)
this._outputObj.sql.push(')')
}
}
_functionName () {
return (this._obj.ref && this._obj.ref[0]) || this._obj.func
}
_columnsFromContains (args) {
if (args[0].xpr) {
return [new ReferenceBuilder(args[0].xpr[1], this._options, this._csn).build().sql]
} else {
return args[0].list.map(element => new ReferenceBuilder(element, this._options, this._csn).build().sql)
}
_functionArgs () {
return (this._obj.ref && this._obj.ref[1].args) || this._obj.args
}

@@ -125,0 +157,0 @@ }

@@ -66,3 +66,3 @@ const BaseBuilder = require('./BaseBuilder')

if (this._obj.SELECT.hasOwnProperty('where')) {
if (Array.isArray(this._obj.SELECT.where) && this._obj.SELECT.where.length > 0) {
this._where()

@@ -176,3 +176,3 @@ }

if (col.ref) {
if (col.ref || col.func) {
// ref

@@ -179,0 +179,0 @@ res = new this.ReferenceBuilder(col, this._options, this._csn).build()

@@ -57,3 +57,3 @@ const BaseBuilder = require('./BaseBuilder')

this._data(getAnnotatedColumns(entityName, this._csn))
if (this._obj.UPDATE.hasOwnProperty('where')) {
if (Array.isArray(this._obj.UPDATE.where) && this._obj.UPDATE.where.length > 0) {
this._where()

@@ -60,0 +60,0 @@ }

{
"name": "@sap/cds-sql",
"version": "1.5.0",
"version": "1.7.0",
"lockfileVersion": 1
}

@@ -1,1 +0,1 @@

{"bundleDependencies":false,"dependencies":{},"deprecated":false,"description":"This package offers a factory method to build a SQL string from a CQN object and a BaseClient which performs default post processing to be used by the inheriting clients.","engines":{"node":">= 8.9.0"},"husky":{"hooks":{"pre-commit":"lint-staged"}},"lint-staged":{"{lib,test}/**/*.js":["prettier-standard","standard --fix","git add"]},"main":"lib/index.js","name":"@sap/cds-sql","version":"1.5.0","license":"SEE LICENSE IN developer-license-3.1.txt"}
{"bundleDependencies":false,"dependencies":{},"deprecated":false,"description":"This package offers a factory method to build a SQL string from a CQN object and a BaseClient which performs default post processing to be used by the inheriting clients.","engines":{"node":">= 8.9.0"},"husky":{"hooks":{"pre-commit":"lint-staged"}},"lint-staged":{"{lib,test}/**/*.js":["prettier-standard","standard --fix","git add"]},"main":"lib/index.js","name":"@sap/cds-sql","version":"1.7.0","license":"SEE LICENSE IN developer-license-3.1.txt"}

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

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

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc