@sap/cds-sql
Advanced tools
Comparing version 1.16.0 to 1.20.0
@@ -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 @@ |
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 |
@@ -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
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
NPM Shrinkwrap
Supply chain riskPackage contains a shrinkwrap file. This may allow the package to bypass normal install procedures.
Found 1 instance in 1 package
277009
5448
0
36
+ Added@sap/cds-foss@1.1.0
+ Added@babel/runtime@7.25.6(transitive)
+ Added@sap/cds-foss@1.1.0(transitive)
+ Addedfs-extra@7.0.1(transitive)
+ Addedgeneric-pool@3.7.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedjsonfile@4.0.0(transitive)
+ Addedregenerator-runtime@0.14.1(transitive)
+ Addeduniversalify@0.1.2(transitive)
+ Addedyaml@1.5.1(transitive)
- Removeduuid@3.3.2