Socket
Socket
Sign inDemoInstall

@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.16.0 to 1.20.0

lib/utils/copy.js

51

CHANGELOG.md

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

## Version 1.20.0 - 2019-11-19
### Fixed
- Managed fields were not generated when values are null
- Read of active entity with navigation and orderBy with draft specific column (e.g `HasActiveEntity`)
## Version 1.19.1 - 2019-10-30
### Fixed
- Expand adding foreign keys twice
## Version 1.19.0 - 2019-10-29
### Changed
- Improved deep update
### Removed
- `npm-shrinkwrap.json`
## Version 1.18.1 - 2019-10-16
### Fixed
- Problems with deep update of a composition of one
- Unhandled promise rejections by expand
## Version 1.18.0 - 2019-10-02
### Fixed
- Problems with backlinks with custom on condition
## Version 1.17.1 - 2019-09-18
### Changed
- Improved error messages
- Improves SQL Builder for `.where` clauses
## Version 1.17.0 - 2019-09-09
### Fixed
- Expand-to-one in draft context
- Expand with multiple orderby elements using window function
- `UUID` generation for `INSERT` statements
## Version 1.16.0 - 2019-08-21

@@ -11,0 +62,0 @@

15

lib/client/BaseClient.js
const cds = require('../cds')
const { typeConversionMap } = require('../utils/dataTypes')
const { InconsistentClientError } = require('../errors')
const { ensureNoDraftsSuffix } = require('../utils/draftUtils')

@@ -139,3 +138,3 @@

* @param {Array} [values] - values for prepared stmt in case of SQL query string.
* @returns {Promise} - resolves with result or rejects with error.
* @returns {Promise} - resolves with result or rejects with error if client is in inconsistent state.
*/

@@ -145,3 +144,3 @@ run (query, values) {

if (this._toBeDestroyed) {
return Promise.reject(new InconsistentClientError())
return Promise.reject(new Error('Client is in an inconsistent state'))
}

@@ -200,2 +199,12 @@

/**
* Sets a session context on the database
*
* Does nothing on sqlite yet.
*
* @param contextName name of the session context
* @param value value of the session context
*/
setSessionContext (contextName, value) {}
/**
* Executes the statement and processes the result set one by one.

@@ -202,0 +211,0 @@ * Should be used if huge result sets are expected to process it in a streaming-like fashion instead of

106

lib/composition/backlinks.js

@@ -1,19 +0,14 @@

const _backLinkCustom = (name, target, element, entityKeys) => {
const _backLinkCustom = (entityKey, targetKey, element, entityKeys) => {
const backlink = {
name: name.startsWith(`${element.name}.`) ? name.replace(`${element.name}.`, '') : name,
target_element: target.startsWith(`${element.name}.`) ? target.replace(`${element.name}.`, '') : target
entityKey: entityKey.startsWith(`${element.name}.`) ? entityKey.replace(`${element.name}.`, '') : entityKey,
targetKey: targetKey.startsWith(`${element.name}.`) ? targetKey.replace(`${element.name}.`, '') : targetKey,
skip: true
}
if (entityKeys && entityKeys.some(key => name === `${element.target}.${key}`)) {
return { name: backlink.target_element, target_element: backlink.name }
}
if (entityKeys && entityKeys.some(key => name === `${element.name}_${key}`)) {
if (element.parent.elements[backlink.targetKey] && element._target.elements[backlink.entityKey]) {
return backlink
} else if (element.parent.elements[backlink.entityKey] && element._target.elements[backlink.targetKey]) {
return { entityKey: backlink.targetKey, targetKey: backlink.entityKey }
}
if (target.startsWith(`${element.name}.`)) {
return { name: backlink.target_element, target_element: backlink.name }
}
return backlink

@@ -23,9 +18,15 @@ }

const _backlinkForCustomOn = (element, entityKeys) => {
const { name, target } = _onElements(element)
return _backLinkCustom(name, target, element, entityKeys)
if (!element.on) {
return
}
const { entityKey, targetKey } = _onElements(element)
return _backLinkCustom(entityKey, targetKey, element, entityKeys)
}
const _backlinkForCustomOnCond = (element, entityKeys) => {
const { name, target } = _onCondElements(element)
return _backLinkCustom(name, target, element, entityKeys)
if (!element.onCond) {
return
}
const { entityKey, targetKey } = _onCondElements(element)
return _backLinkCustom(entityKey, targetKey, element, entityKeys)
}

@@ -49,13 +50,13 @@

const _onElements = element => {
const name = element.on[2].ref.join('.')
const target = element.on[0].ref.join('.')
const entityKey = element.on[2].ref.join('.')
const targetKey = element.on[0].ref.join('.')
return { name, target }
return { entityKey, targetKey }
}
const _onCondElements = element => {
const name = element.onCond.args[1]['=']
const target = element.onCond.args[0]['=']
const entityKey = element.onCond.args[1]['=']
const targetKey = element.onCond.args[0]['=']
return { name, target }
return { entityKey, targetKey }
}

@@ -103,31 +104,54 @@

const getBackLinks = (element, entityKeys) => {
const _buildBacklinks = (prefix, entityKeys) => {
const backLinks = []
let prefix
for (const entityKey of entityKeys) {
if (entityKey === 'IsActiveEntity') {
continue
}
backLinks.push({ entityKey: `${prefix}_${entityKey}`, targetKey: entityKey })
}
if (element.on) {
if (_isSelfManagedOn(element.on)) {
prefix = _backLinkNameFromOn(element)
} else {
return [_backlinkForCustomOn(element, entityKeys)]
return backLinks
}
const _onBacklinks = (element, entityKeys) => {
if (_isSelfManagedOn(element.on)) {
const prefix = _backLinkNameFromOn(element)
const customBacklink = _backlinkForCustomOn(element._target.elements[prefix], entityKeys)
if (customBacklink) {
return [{ entityKey: customBacklink.targetKey, targetKey: customBacklink.entityKey, skip: customBacklink.skip }]
}
} else if (element.onCond && element.onCond.op === '=') {
if (_isSelfManagedOnCond(element.onCond)) {
prefix = _backLinkNameFromOnCond(element)
} else {
return [_backlinkForCustomOnCond(element, entityKeys)]
}
return _buildBacklinks(prefix, entityKeys)
} else {
prefix = element.name
return [_backlinkForCustomOn(element, entityKeys)]
}
}
for (const entityKey of entityKeys) {
if (entityKey === 'IsActiveEntity') {
continue
const _onCondBacklinks = (element, entityKeys) => {
if (_isSelfManagedOnCond(element.onCond)) {
const prefix = _backLinkNameFromOnCond(element)
const customBacklink = _backlinkForCustomOnCond(element._target.elements[prefix], entityKeys)
if (customBacklink) {
return [{ entityKey: customBacklink.targetKey, targetKey: customBacklink.entityKey, skip: customBacklink.skip }]
}
backLinks.push({ name: `${prefix}_${entityKey}`, target_element: entityKey })
return _buildBacklinks(prefix, entityKeys)
} else {
return [_backlinkForCustomOnCond(element, entityKeys)]
}
return backLinks
}
const getBackLinks = (element, entityKeys) => {
if (element.on) {
return _onBacklinks(element, entityKeys)
} else if (element.onCond && element.onCond.op === '=') {
return _onCondBacklinks(element, entityKeys)
}
return _buildBacklinks(element.name, entityKeys)
}
module.exports = {

@@ -134,0 +158,0 @@ getBackLinks,

@@ -1,4 +0,5 @@

const generateUUID = require('uuid/v4')
const generateUUID = require('@sap/cds-foss')('uuid/v4')
const { getBackLinks, isSelfManaged, getOnCondElements } = require('./backlinks')
const { ensureNoDraftsSuffix, ensureDraftsSuffix } = require('../utils/draftUtils')
const { deepCopy, deepCopyArray } = require('../utils/copy')

@@ -98,5 +99,8 @@ const isRootEntity = (definitions, entityName) => {

) {
const { name, target } = getOnCondElements(targetElement)
if (name === `${elementName}.${element.name}` || target === `${elementName}.${element.name}`) {
return true
const onCondElements = getOnCondElements(targetElement)
if (onCondElements) {
const { entityKey, targetKey } = onCondElements
if (entityKey === `${elementName}.${element.name}` || targetKey === `${elementName}.${element.name}`) {
return true
}
}

@@ -110,3 +114,8 @@ }

if (_checkIfBackLink(element, definitions)) {
compositionTree.customBackLinks.push(...getBackLinks(element, Object.keys(definitions[element.target].keys)))
const backLinks = getBackLinks(element, Object.keys(definitions[element.target].keys)).map(backLink => ({
entityKey: backLink.targetKey,
targetKey: backLink.entityKey,
skip: backLink.skip
}))
compositionTree.customBackLinks.push(...backLinks)
}

@@ -177,6 +186,8 @@ } else {

* @param {boolean} checkRoot Check is provided entity is a root
* @returns {Object} tree of all compositions
* @throws Error if no valid root entity provided
*/
const getCompositionTree = (definitions, rootEntityName, checkRoot = true, includeAssociations = false) => {
if (checkRoot && !isRootEntity(definitions, rootEntityName)) {
throw new Error('Entity is not root entity')
throw new Error(`Entity "${rootEntityName}" is not root entity`)
}

@@ -203,20 +214,21 @@ const compositionTree = {}

.map(key => entity.elements[key])
.filter(({ type }) => type !== 'cds.Association' && type !== 'cds.Composition')
.filter(({ type, virtual }) => type !== 'cds.Association' && type !== 'cds.Composition' && !virtual)
}
const _isCompOrAssoc = (entity, k) => {
const _isCompOrAssoc = (entity, k, onlyToOne) => {
return (
entity.elements &&
entity.elements[k] &&
(entity.elements[k].type === 'cds.Composition' || entity.elements[k].type === 'cds.Association')
(entity.elements[k].type === 'cds.Composition' || entity.elements[k].type === 'cds.Association') &&
((onlyToOne && entity.elements[k].is2one) || !onlyToOne)
)
}
const _cleanDeepData = (entity, data) => {
const _cleanDeepData = (entity, data, onlyToOne = false) => {
if (!Array.isArray(data)) {
return _cleanDeepData(entity, [data])[0]
return _cleanDeepData(entity, [data], onlyToOne)[0]
}
return data.map(entry => {
return Object.keys(entry || {}).reduce((result, k) => {
if (!_isCompOrAssoc(entity, k)) {
if (!_isCompOrAssoc(entity, k, onlyToOne)) {
result[k] = entry[k]

@@ -248,3 +260,3 @@ }

element.customBackLinks.reduce((parentKey, customBackLink) => {
parentKey[customBackLink.name] = key[customBackLink.target_element]
parentKey[customBackLink.entityKey] = key[customBackLink.targetKey]
return parentKey

@@ -254,3 +266,3 @@ }, parentKey)

return element.backLinks.reduce((parentKey, backlink) => {
parentKey[backlink.name] = key[backlink.target_element]
parentKey[backlink.entityKey] = key[backlink.targetKey]
return parentKey

@@ -320,3 +332,3 @@ }, parentKey)

const _diffData = (entity, data, otherData) => {
const _diffData = (data, otherData) => {
return Object.keys(data).reduce((result, key) => {

@@ -354,3 +366,3 @@ const dataVal = (data[key] && data[key].val) || data[key]

for (const link of links) {
toOneKeys[link.name] = toOneData[link.target_element]
toOneKeys[link.entityKey] = toOneData[link.targetKey]
}

@@ -360,3 +372,6 @@ } else {

for (const backLink of backLinks) {
toOneKeys[backLink.name] = data[backLink.target_element]
if (backLink.skip) {
continue
}
toOneKeys[backLink.entityKey] = data[backLink.targetKey]
}

@@ -373,3 +388,3 @@ }

for (const backLink of element.backLinks) {
toManyKeys[backLink.name] = data[backLink.target_element] || null
toManyKeys[backLink.entityKey] = data[backLink.targetKey] || null
}

@@ -381,3 +396,6 @@ }

for (const customBackLink of element.customBackLinks) {
toManyKeys[customBackLink.name] = data[customBackLink.target_element] || null
if (!data[customBackLink.targetKey] && customBackLink.skip) {
continue
}
toManyKeys[customBackLink.entityKey] = data[customBackLink.targetKey] || null
}

@@ -389,3 +407,3 @@ }

const _propagateKeys = (subEntity, element, data, subData) => {
const propagateKeys = (subEntity, element, data, subData) => {
const toOneElements = _toOneElements(subEntity)

@@ -417,2 +435,3 @@ const toManyElements = _toManyElements(subEntity)

const _hasWhereInDelete = cqn => cqn.DELETE && cqn.DELETE.where && cqn.DELETE.where.length > 0
const _addSubCascadeDeleteCQN = (compositionTree, level, cqns, draft, set = new Set()) => {

@@ -430,7 +449,11 @@ compositionTree.compositionElements.forEach(element => {

entity1 = { alias: 'ALIAS1', entityName: _addDraftSuffix(draft, element.source), propertyName: backLink.name }
entity1 = {
alias: 'ALIAS1',
entityName: _addDraftSuffix(draft, element.source),
propertyName: backLink.entityKey
}
entity2 = {
alias: 'ALIAS2',
entityName: _addDraftSuffix(draft, element.target || element.source),
propertyName: backLink.target_element
propertyName: backLink.targetKey
}

@@ -445,25 +468,28 @@

}
result.push({ ref: [entity1.alias, backLink.name] }, 'is not null')
result.push({ ref: [entity1.alias, backLink.entityKey] }, '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
if (allBackLinks.length > 0) {
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)
]
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)
set.add(element.name)
_addSubCascadeDeleteCQN(element, level + 1, cqns, draft, set)
}
}

@@ -498,7 +524,11 @@ })

entity1 = { alias: 'ALIAS1', entityName: _addDraftSuffix(draft, element.source), propertyName: backLink.name }
entity1 = {
alias: 'ALIAS1',
entityName: _addDraftSuffix(draft, element.source),
propertyName: backLink.entityKey
}
entity2 = {
alias: 'ALIAS2',
entityName: _addDraftSuffix(draft, element.target || element.source),
propertyName: backLink.target_element
propertyName: backLink.targetKey
}

@@ -512,6 +542,6 @@

]
if (cqn.DELETE && cqn.DELETE.where && cqn.DELETE.where.length > 0) {
if (_hasWhereInDelete(cqn)) {
subWhere.push('and', '(', ...(cqn.DELETE.where || []), ')')
}
const whereKey = allBackLinks.reduce((result, backLink) => {
const whereKey = allBackLinks.reduce(result => {
if (result.length > 0) {

@@ -523,21 +553,23 @@ result.push('or')

}, [])
const where = [
'(',
...whereKey,
')',
'and',
'exists',
{
SELECT: {
columns: [{ val: 1, as: '_exists' }],
from: { ref: [entity2.entityName], as: entity2.alias },
where: subWhere
if (allBackLinks.length > 0) {
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 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)
}
}

@@ -594,5 +626,3 @@ })

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

@@ -627,3 +657,3 @@ }

const draft = entityName !== into
const dataEntries = cqn.INSERT.entries || []
const dataEntries = cqn.INSERT.entries ? deepCopyArray(cqn.INSERT.entries) : []
const entity = definitions && definitions[entityName]

@@ -682,2 +712,12 @@ const compositionTree = getCompositionTree(definitions, entityName, false, !draft)

}, [])
let where
if (element.links && element.links.length > 0) {
const whereObj = element.links.reduce((res, currentLink) => {
res[currentLink.targetKey] = result[0][currentLink.entityKey]
return res
}, {})
where = _whereKey(whereObj)
}
return _selectDeepUpdateData({

@@ -688,2 +728,3 @@ definitions,

data: subData,
where,
selectData: result,

@@ -704,4 +745,2 @@ parentKeys: _parentKeys(element, keys),

selectData,
where,
parentKeys,
root,

@@ -726,2 +765,12 @@ draft,

const _getLinksOfCompTree = compositionTree => {
const links = []
for (const compElement of compositionTree.compositionElements || []) {
for (const link of compElement.links || []) {
links.push(link.entityKey)
}
}
return links
}
const _selectDeepUpdateData = ({

@@ -736,3 +785,4 @@ definitions,

draft,
execute
execute,
includeAllRootColumns
}) => {

@@ -743,9 +793,11 @@ const root = !selectData

const selectCQN = { SELECT: { from: { ref: [from] } } }
const links = _getLinksOfCompTree(compositionTree)
if (data !== undefined) {
selectCQN.SELECT.columns = []
const backLinkKeys = compositionTree.backLinks.map(backLink => backLink.name)
const backLinkKeys = compositionTree.backLinks.map(backLink => backLink.entityKey)
_dataElements(entity).forEach(element => {
if (element.key || backLinkKeys.includes(element.name)) {
if (element.key || links.includes(element.name) || backLinkKeys.includes(element.name)) {
selectCQN.SELECT.columns.push({ ref: [element.name] })
} else if (
(includeAllRootColumns && root) ||
data.find(entry => {

@@ -806,9 +858,38 @@ return element.name in entry

}
const _isSameEntity = (cqn, context) => {
const where = cqn.UPDATE.where || []
const persistentObj = Array.isArray(context._.partialPersistentState)
? context._.partialPersistentState[0]
: context._.partialPersistentState
if (!persistentObj) {
// If no data was found we don't know if it is the same entity
return false
}
if (context && context.target && context.target.source !== cqn.UPDATE.entity) {
return false
}
for (let i = 0; i < where.length; i++) {
if (!where[i] || !where[i].ref || !context.target.elements[where[i].ref]) {
continue
}
const key = where[i].ref
const val = where[i + 2].val
const sign = where[i + 1]
// eslint-disable-next-line
if (context.target.elements[key].key && key in persistentObj && sign === '=' && val != persistentObj[key]) {
return false
}
}
return true
}
const selectDeepUpdateData = (definitions, cqn, execute) => {
const selectDeepUpdateData = (definitions, cqn, execute, context, includeAllRootColumns = false) => {
if (context && _isSameEntity(cqn, context)) {
return Promise.resolve(context._.partialPersistentState)
}
const from = cqn.UPDATE.entity.name || cqn.UPDATE.entity
const entityName = ensureNoDraftsSuffix(from)
const draft = entityName !== from
const where = cqn.UPDATE.where || []
const data = cqn.UPDATE.data || {}
const where = cqn.UPDATE.where || []
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)

@@ -822,58 +903,8 @@ return _selectDeepUpdateData({

draft,
execute
execute,
includeAllRootColumns
})
}
const _addDeepSelectColumns = (definitions, compositionTree, data, columns) => {
const entity = definitions && definitions[compositionTree.source]
_dataElements(entity).forEach(element => {
if (element.key) {
columns.push({ ref: [element.name] })
}
})
data.forEach(entry => {
Object.keys(entry).forEach(key => {
if (!columns.find(column => column.ref[0] === key)) {
columns.push({ ref: [key] })
}
})
})
compositionTree.compositionElements.forEach(element => {
if (
!data.find(entry => {
return element.name in entry
})
) {
return
}
const subColumns = []
const subData = data.reduce((result, entry) => {
if (element.name in entry) {
const elementValue = entry[element.name].val || entry[element.name]
result.push(...(Array.isArray(elementValue) ? elementValue : [elementValue]))
}
return result
}, [])
const column = columns.find(column => column.ref[0] === element.name)
column.expand = subColumns
_addDeepSelectColumns(definitions, element, subData, subColumns)
})
}
const createDeepUpdateSelectCQN = (definitions, cqn) => {
const from = cqn.UPDATE.entity.name || cqn.UPDATE.entity
const entityName = ensureNoDraftsSuffix(from)
const draft = entityName !== from
const data = cqn.UPDATE.data || {}
const columns = []
const selectCQN = { SELECT: { columns, from: { ref: [from] } } }
if (cqn.UPDATE.where) {
selectCQN.SELECT.where = cqn.UPDATE.where
}
const compositionTree = getCompositionTree(definitions, entityName, false, !draft)
_addDeepSelectColumns(definitions, compositionTree, [data], columns)
return selectCQN
}
function _addSubDeepUpdateCQNForDelete ({ compositionTree, entity, entityName, data, selectData, deleteCQN }) {
function _addSubDeepUpdateCQNForDelete ({ entity, data, selectData, deleteCQN }) {
selectData.forEach(selectEntry => {

@@ -891,16 +922,20 @@ const selectKey = _key(entity, selectEntry)

function _addSubDeepUpdateCQNForUpdateInsert ({
compositionTree,
entity,
entityName,
data,
selectData,
updateCQNs,
insertCQN
}) {
function _fillLinkFromStructuredData (entity, entry) {
for (const elementName in entity.elements) {
const foreignKey4 = entity.elements[elementName]['@odata.foreignKey4']
if (foreignKey4 && entry[foreignKey4]) {
const foreignKey = entity.elements[elementName].name
const childKey = foreignKey.split('_')[1]
entry[foreignKey] = _unwrapVal(entry[foreignKey4])[childKey]
}
}
}
function _addSubDeepUpdateCQNForUpdateInsert ({ entity, entityName, data, selectData, updateCQNs, insertCQN }) {
;[...data].forEach(entry => {
const key = _key(entity, entry)
const selectEntry = _findByKey(entity, selectData, key)
_fillLinkFromStructuredData(entity, entry)
if (selectEntry) {
const diff = _diffData(entity, _cleanDeepData(entity, entry), _cleanDeepData(entity, selectEntry))
const diff = _diffData(_cleanDeepData(entity, entry), _cleanDeepData(entity, selectEntry))
if (Object.keys(diff).length > 0) {

@@ -912,3 +947,3 @@ updateCQNs.push({

} else {
insertCQN.INSERT.entries.push(entry)
insertCQN.INSERT.entries.push(_cleanDeepData(entity, entry, true))
data.splice(data.indexOf(entry), 1)

@@ -973,2 +1008,8 @@ }

const _unwrapVal = elementValue =>
Object.keys(elementValue).reduce((res, curr) => {
res[curr] = (elementValue[curr] && elementValue[curr].val) || elementValue[curr]
return res
}, {})
function _addSubDeepUpdateCQNRecursion ({ definitions, compositionTree, entity, data, selectData, cqns, draft }) {

@@ -980,6 +1021,11 @@ compositionTree.compositionElements.forEach(element => {

if (element.name in entry) {
const elementValue = entry[element.name].val || entry[element.name]
subData.push(
..._propagateKeys(entity, element, entry, Array.isArray(elementValue) ? elementValue : [elementValue])
let elementValue = entry[element.name].val || entry[element.name]
const subDataEntries = propagateKeys(
entity,
element,
entry,
Array.isArray(elementValue) ? elementValue : [elementValue]
)
const unwrappedSubData = subDataEntries.map(entry => (Array.isArray(entry) ? entry : _unwrapVal(entry)))
subData.push(...unwrappedSubData)
const selectEntry = _findByKey(entity, selectData, _key(entity, entry))

@@ -989,3 +1035,3 @@ if (selectEntry && element.name in selectEntry) {

selectSubData.push(
..._propagateKeys(
...propagateKeys(
entity,

@@ -1018,5 +1064,4 @@ element,

const deleteCQN = { DELETE: { from: entityName, where: [] } }
_addSubDeepUpdateCQNForDelete({ compositionTree, entity, entityName, data, selectData, deleteCQN })
_addSubDeepUpdateCQNForDelete({ entity, data, selectData, deleteCQN })
_addSubDeepUpdateCQNForUpdateInsert({
compositionTree,
entity,

@@ -1058,3 +1103,3 @@ entityName,

const draft = entityName !== from
const data = cqn.UPDATE.data || {}
const data = cqn.UPDATE.data ? deepCopy(cqn.UPDATE.data) : {}
const entity = definitions && definitions[entityName]

@@ -1096,2 +1141,3 @@ const entry = Object.assign({}, data, _key(entity, selectData[0]))

module.exports = {
propagateKeys,
isRootEntity,

@@ -1107,3 +1153,2 @@ getCompositionTree,

createDeepInsertCQNs,
createDeepUpdateSelectCQN,
createDeepUpdateCQNs,

@@ -1110,0 +1155,0 @@ selectDeepData,

@@ -9,3 +9,3 @@ const { isComplex, isAssociation, resolveAssociation } = require('../utils/associations')

* @returns {function}
* @throws Error
* @throws Error if no valid parameter fn provided
* @private

@@ -18,3 +18,3 @@ */

throw Error(`Method '${fn}' does not exist`)
throw new Error(`Method "${fn}" does not exist.`)
}

@@ -21,0 +21,0 @@

@@ -7,9 +7,9 @@ const getColumns = require('../utils/columns')

// Symbols are used to add extra information in response structure
const GET_KEY_VALUE = Symbol.for('getKeyValue')
const TO_MANY = Symbol.for('toMany')
const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
const TO_MANY = Symbol.for('sap.cds.toMany')
const SKIP_MAPPING = Symbol.for('skipMapping')
const IDENTIFIER = Symbol.for('identifier')
const IS_ACTIVE = Symbol.for('isActive')
const IS_UNION_DRAFT = Symbol.for('isUnionDraft')
const SKIP_MAPPING = Symbol.for('sap.cds.skipMapping')
const IDENTIFIER = Symbol.for('sap.cds.identifier')
const IS_ACTIVE = Symbol.for('sap.cds.isActive')
const IS_UNION_DRAFT = Symbol.for('sap.cds.isUnionDraft')
const DRAFT_COLUMNS = ['IsActiveEntity', 'HasActiveEntity', 'HasDraftEntity', 'DraftAdministrativeData_DraftUUID']

@@ -21,3 +21,3 @@

this._SELECT = Object.assign({}, cqn.SELECT)
this._csn = cqn[Symbol.for('cds.ql.model')] || csn
this._csn = cqn[Symbol.for('sap.cds.model')] || csn
this.queries = []

@@ -36,3 +36,3 @@ this.mappings = {}

// Add table aliases to all refs in where part obtained from annotations
this._adaptWhereAnnotations(this._SELECT.where)
this._adaptAliasForWhere(this._SELECT.where)

@@ -250,2 +250,7 @@ // Update elements at WHERE, so there are no issues with ambiguity

cqn.where = cqn.where.map(element => {
if (element.list) {
return Object.assign(element, {
list: element.list.map(element => this._checkOrderByWhereElementRecursive(cqn, element, tableAlias))
})
}
return this._checkOrderByWhereElementRecursive(cqn, element, tableAlias)

@@ -264,8 +269,16 @@ })

_adaptWhereAnnotations (where) {
_addAliasAndDeleteSymbol (whereElement) {
this._addAlias(whereElement)
delete whereElement.ref[Symbol.for('sap.cds.FROM_ANNOTATION')]
}
_addAlias (whereElement) {
whereElement.ref && whereElement.ref.unshift(Object.values(this._aliases)[0])
}
_adaptAliasForWhere (where) {
if (where) {
for (const w of where) {
if (w.ref && w.ref[Symbol.for('FROM_ANNOTATION')] === true) {
w.ref.unshift(Object.values(this._aliases)[0])
delete w.ref[Symbol.for('FROM_ANNOTATION')]
for (const whereElement of where) {
if (whereElement.ref && whereElement.ref[Symbol.for('sap.cds.FROM_ANNOTATION')] === true) {
this._addAliasAndDeleteSymbol(whereElement)
}

@@ -454,2 +467,7 @@ }

// if union always only expand with active, otherwise evaluate flag
// if flag shows false, we check entity for associations to non draft
const activeTableRequired =
readToOneCQN[IS_UNION_DRAFT] || readToOneCQN[IS_ACTIVE] || !this._isDraftEnabled(this._csn.definitions[target])
// TODO: If draft union and composition target add union as to be joined

@@ -459,3 +477,3 @@ readToOneCQN.from = {

readToOneCQN.from.SET ? this._unionToSubQuery(readToOneCQN) : readToOneCQN.from,
{ ref: [this._refFromRefByExpand(column.ref[0], entity.elements)], as: tableAlias }
{ ref: [this._refFromRefByExpand(column.ref[0], entity.elements, activeTableRequired)], as: tableAlias }
],

@@ -475,5 +493,12 @@ join:

entity: this._getEntityForTable(target),
givenColumns: column.expand.filter(
columnObj => !(columnObj.ref && columnObj.ref.length && columnObj.ref[0] === 'IsActiveEntity')
),
givenColumns: column.expand.map(col => {
if (activeTableRequired && col.ref && col.ref.length && col.ref[0] === 'IsActiveEntity') {
return {
val: true,
as: 'IsActiveEntity',
cast: { type: 'cds.Boolean' }
}
}
return col
}),
readToOneCQN: readToOneCQN,

@@ -485,4 +510,7 @@ tableAlias: tableAlias,

_refFromRefByExpand (column, elements) {
return column === 'DraftAdministrativeData' ? 'DRAFT.DraftAdministrativeData' : elements[column].target
_refFromRefByExpand (column, elements, isActiveRequired = true) {
if (column === 'DraftAdministrativeData') {
return 'DRAFT.DraftAdministrativeData'
}
return `${elements[column].target}${isActiveRequired ? '' : '_drafts'}`
}

@@ -612,3 +640,3 @@

entry.ref[0] === parentAlias &&
!columns.some(column => column.ref && column.ref[0] === entry.ref[1])
!columns.some(column => column.ref && column.ref[column.ref.length - 1] === entry.ref[1])
)

@@ -1049,6 +1077,17 @@ })

_getOrderByForWindowFn (orderBy) {
return orderBy.reduce((arr, value, i) => {
arr.push(value)
if (i < orderBy.length - 1) {
arr.push(',')
}
return arr
}, [])
}
_getWindowXpr (columns, orderBy) {
const xpr = [{ func: 'ROW_NUMBER', args: [] }, 'OVER', '(', 'PARTITION BY', ...columns]
if (orderBy.length !== 0) {
xpr.push('ORDER BY', ...orderBy)
xpr.push('ORDER BY', ...this._getOrderByForWindowFn(orderBy))
}

@@ -1055,0 +1094,0 @@ xpr.push(')')

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

const EXPAND = Symbol.for('expand')
const GET_KEY_VALUE = Symbol.for('getKeyValue')
const TO_MANY = Symbol.for('toMany')
const EXPAND = Symbol.for('sap.cds.expand')
const GET_KEY_VALUE = Symbol.for('sap.cds.getKeyValue')
const TO_MANY = Symbol.for('sap.cds.toMany')

@@ -180,5 +180,8 @@ class RawToExpanded {

const rawToExpanded = (configs, queries, one) => {
return new RawToExpanded(configs, queries, one).toExpanded()
return new RawToExpanded(configs, queries, one).toExpanded().catch(err => {
Promise.all(queries).catch(() => {})
throw err
})
}
module.exports = rawToExpanded

@@ -14,7 +14,2 @@ const cds = require('./cds')

},
get errors () {
const errors = require('./errors')
Object.defineProperty(dependencies, 'errors', { value: errors })
return errors
},
get builder () {

@@ -21,0 +16,0 @@ const sqlBuilder = require('./sql-builder/')

const cds = require('../cds')
const { InvalidQuotingStyleError } = require('../errors')

@@ -49,4 +48,6 @@ const _slugify = name => name.replace(/::/g, '__').replace(/\./g, '_')

_validateQuotingStyle () {
if (typeof this._quotingStyle !== 'string' || !quotingStyles.hasOwnProperty(this._quotingStyle)) {
throw new InvalidQuotingStyleError(this._quotingStyle)
let type = typeof this._quotingStyle
if (type !== 'string' || !quotingStyles.hasOwnProperty(this._quotingStyle)) {
type = type !== 'string' ? `Type ${type}` : `"${this._quotingStyle}"`
throw new Error(`Quoting style: ${type} is not supported. Allowed strings: "quoted", "plain".`)
}

@@ -53,0 +54,0 @@ }

const BaseBuilder = require('./BaseBuilder')
const { InvalidCqnObjectError } = require('../errors')

@@ -59,3 +58,3 @@ /**

*
* @throws InvalidCqnObjectError
* @throws Error if the input object is invalid
* @returns {{sql: string, values: Array}} Object with two properties.

@@ -84,4 +83,5 @@ * SQL string for prepared statement and array of values to replace the placeholders.

// Some keywords need to be process as a block, while others can be treated one at a time
if (this._reseverdKeyWords(objects, i)) {
i = i + 3
const reserved = this._reseverdKeyWords(objects, i)
if (reserved) {
i = i + reserved
} else {

@@ -106,10 +106,28 @@ this._expressionElementToSQL(objects[i])

if (objects[i + 2] && objects[i + 2].val === null) {
return this._addNullOrNotNull(objects[i], objects[i + 1])
this._addNullOrNotNull(objects[i], objects[i + 1])
return 3
}
if (/^(not )?in+/i.test(objects[i + 1])) {
return this._addInOrNotIn(objects[i], objects[i + 1].toUpperCase(), objects[i + 2])
if (objects[i + 2] !== '(') {
this._addInOrNotIn(objects[i], objects[i + 1].toUpperCase(), objects[i + 2])
return 3
}
// map other notation to current notation
const arr = []
let skip = 3
for (let j = i + 3; j < objects.length; j++) {
skip++
if (objects[j] === ')') {
break
} else if (objects[j].val) {
arr.push(objects[j].val)
}
}
this._addInOrNotIn(objects[i], objects[i + 1].toUpperCase(), { val: arr })
return skip
}
return false
return 0
}

@@ -141,3 +159,3 @@

_addInOrNotIn (reference, operator, values) {
if (values.val instanceof Array) {
if (Array.isArray(values.val)) {
this._addArrayForInQuery(reference, operator, values.val)

@@ -223,3 +241,3 @@ return true

throw new InvalidCqnObjectError()
throw new Error(`Cannot build SQL. Invalid CQN object provided: ${JSON.stringify(element)}`)
}

@@ -226,0 +244,0 @@

const BaseBuilder = require('./BaseBuilder')
const SelectBuilder = require('./SelectBuilder')
const getAnnotatedColumns = require('../utils/annotations')
const generateUUID = require('@sap/cds-foss')('uuid/v4')

@@ -54,2 +55,5 @@ /**

// side effect: sets this.uuidKeys if found any
this._findUuidKeys(entityName)
this._columnIndexesToDelete = []

@@ -129,2 +133,17 @@ const annotatedColumns = getAnnotatedColumns(entityName, this._csn)

_findUuidKeys (entityName) {
const uuidKeys = []
if (this._csn && this._csn.definitions[entityName] && this._csn.definitions[entityName].keys) {
for (const key of Object.values(this._csn.definitions[entityName].keys)) {
if (key.type === 'cds.UUID') {
uuidKeys.push(key.name)
}
}
}
if (uuidKeys.length > 0) {
this.uuidKeys = uuidKeys
}
}
_columns (annotatedColumns) {

@@ -138,4 +157,14 @@ this._outputObj.sql.push('(')

this._outputObj.sql.push(this._obj.INSERT.columns.map(col => this._quoteElement(col)).join(', '))
const insertColumns = [...this._obj.INSERT.columns.map(col => this._quoteElement(col))]
if (this.uuidKeys) {
for (const key of this.uuidKeys) {
if (!this._obj.INSERT.columns.includes(key)) {
insertColumns.unshift(this._quoteElement(key))
}
}
}
this._outputObj.sql.push(insertColumns.join(', '))
if (annotatedColumns) {

@@ -177,2 +206,13 @@ // add insert annotated columns

if (this.uuidKeys && this._obj.INSERT.columns) {
for (const key of this.uuidKeys) {
if (!this._obj.INSERT.columns.includes(key)) {
placeholderNum += 1
this._obj.INSERT.values
? this._outputObj.values.unshift(generateUUID())
: this._outputObj.values.forEach(arr => arr.unshift(generateUUID()))
}
}
}
this._outputObj.sql.push(

@@ -183,2 +223,12 @@ ...this._createPlaceholderString(placeholderNum, annotatedInsertColumnValues.valuesAndSQLs)

_addUuidToColumns (columns, flattenColumnMap) {
if (this.uuidKeys) {
for (const key of this.uuidKeys) {
if (!flattenColumnMap.get(key)) {
columns.push(...this.uuidKeys.map(key => this._quoteElement(key)))
}
}
}
}
/**

@@ -207,2 +257,3 @@ * This method creates insert statement in case of multiple entries.

this._addUuidToColumns(columns, flattenColumnMap)
columns.push(...flattenColumnMap.keys())

@@ -216,4 +267,8 @@

for (const key of flattenColumnMap.get(column)) {
val = val[key]
if (!flattenColumnMap.get(column) && this.uuidKeys.includes(column)) {
val = generateUUID()
} else {
for (const key of flattenColumnMap.get(column)) {
val = val[key]
}
}

@@ -248,3 +303,3 @@

entry[key] !== null &&
!(entry[key] instanceof Buffer) &&
!Buffer.isBuffer(entry[key]) &&
typeof entry[key].pipe !== 'function'

@@ -251,0 +306,0 @@ ) {

const BaseBuilder = require('./BaseBuilder')
const { FeatureNotSupportedError } = require('../errors')

@@ -33,3 +32,2 @@ /**

*
* @throws InvalidCqnObjectError
* @returns {{sql: string, values: Array}} Object with two properties.

@@ -73,3 +71,3 @@ * SQL string for prepared statement and an empty array of values.

if (refArray[0].id) {
throw new FeatureNotSupportedError()
throw new Error(`${refArray[0].id}: Views with parameters supported only on HANA`)
}

@@ -76,0 +74,0 @@ this._outputObj.sql.push(refArray.map(el => this._quoteElement(el)).join('.'))

const BaseBuilder = require('./BaseBuilder')
const DRAFT_COLUMNS = ['IsActiveEntity', 'HasActiveEntity', 'HasDraftEntity']
/**

@@ -247,2 +248,18 @@ * SelectBuilder is used to take a CQN object as an input and to build a SQL Select string from it.

_quote (element) {
return `${this._options.delimiter}${element}${this._options.delimiter}`
}
_quoteDraftSpecificColumns (element) {
if (this._quotingStyle === 'plain' && element.ref) {
element.ref = element.ref.map(el => {
if (DRAFT_COLUMNS.includes(el)) {
return this._quote(el)
}
return el
})
}
}
_groupBy () {

@@ -252,2 +269,3 @@ const sqls = []

for (const element of this._obj.SELECT.groupBy) {
this._quoteDraftSpecificColumns(element)
const res = new this.ReferenceBuilder(element, this._options, this._csn).build()

@@ -270,2 +288,3 @@ sqls.push(res.sql)

for (const element of this._obj.SELECT.orderBy) {
this._quoteDraftSpecificColumns(element)
const { sql, values } = new this.ReferenceBuilder(element, this._options, this._csn).build()

@@ -272,0 +291,0 @@ sqls.push(sql)

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

const { IllegalFunctionArgumentError } = require('../errors')
const DeleteBuilder = require('./DeleteBuilder')

@@ -49,8 +48,8 @@ const InsertBuilder = require('./InsertBuilder')

* @param {Object} [csn] CSN
* @throws IllegalFunctionArgumentError
* @returns {string} The SQL string
* * @throws Error if no valid CQN object provided
*/
const build = (cqn, options, csn) => {
if (!cqn) {
throw new IllegalFunctionArgumentError('cqn')
throw new Error('Cannot build SQL. No CQN object provided.')
}

@@ -91,5 +90,5 @@

throw new IllegalFunctionArgumentError('cqn')
throw new Error(`Cannot build SQL. Invalid CQN object provided: ${JSON.stringify(cqn)}`)
}
module.exports = build

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

for (const columnName of annotatedColumns.updateAnnotatedColumns.keys()) {
if (resMap.has(columnName)) {
if (resMap.get(columnName)) {
// only if value is !null && !undefined it should be kept
annotatedColumns.updateAnnotatedColumns.delete(columnName)

@@ -105,0 +106,0 @@ }

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

{"bundleDependencies":false,"dependencies":{"uuid":"3.3.2"},"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","scripts":{"format":"prettier-standard 'lib/**/*.js' 'test/**/*.js' && standard --fix"},"version":"1.16.0","license":"SEE LICENSE IN developer-license-3.1.txt"}
{"bundleDependencies":false,"dependencies":{"@sap/cds-foss":"1.1.0"},"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","scripts":{"format":"prettier-standard 'lib/**/*.js' 'test/**/*.js' && standard --fix"},"version":"1.20.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