@sap/cds-sql
Advanced tools
Comparing version 1.16.0 to 1.17.1
@@ -9,2 +9,17 @@ # Changelog | ||
## 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 +26,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')) | ||
} | ||
@@ -148,0 +147,0 @@ |
@@ -175,6 +175,8 @@ const generateUUID = require('uuid/v4') | ||
* @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`) | ||
} | ||
@@ -181,0 +183,0 @@ const compositionTree = {} |
@@ -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'}` | ||
} | ||
@@ -1048,6 +1076,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)) | ||
} | ||
@@ -1054,0 +1093,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') | ||
@@ -5,0 +5,0 @@ class 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('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]) { | ||
for (const key of Object.values(this._csn.definitions[this._obj.INSERT.into].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('.')) |
@@ -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 |
{ | ||
"name": "@sap/cds-sql", | ||
"version": "1.16.0", | ||
"version": "1.17.1", | ||
"lockfileVersion": 1, | ||
@@ -5,0 +5,0 @@ "requires": true, |
@@ -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":{"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.17.1","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
216095
36
5347