@sap/cds-sql
Advanced tools
Comparing version 1.5.0 to 1.7.0
@@ -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 @@ |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
200441
40
5066