@sap/cds-hana
Advanced tools
Comparing version 1.7.1 to 1.11.1
@@ -9,2 +9,36 @@ # Changelog | ||
## Version 1.11.1 - 2019-05-16 | ||
### Changed | ||
- Updated version of @sap/cds-sql to 1.11.1 | ||
## Version 1.11.0 - 2019-05-15 | ||
### Changed | ||
- Improved performance by reducing calls to process.nextTick() | ||
## Version 1.10.0 - 2019-05-03 | ||
### Added | ||
- Service related functions | ||
## Version 1.9.0 - 2019-04-16 | ||
### Added | ||
- `client.stream()` for streaming large binaries | ||
### Changed | ||
- Make hdb default driver | ||
## Version 1.8.0 - 2019-03-29 | ||
### Changed | ||
- Updated version of @sap/cds-sql to 1.8.0 | ||
## Version 1.7.1 - 2019-03-19 | ||
@@ -11,0 +45,0 @@ |
@@ -1,33 +0,36 @@ | ||
const _notInjected = () => { | ||
throw new Error('This method requires `cds` to be injected') | ||
} | ||
const injection = { | ||
inject (cds) { | ||
if (!cds || typeof cds !== 'object') { | ||
throw new Error('Injected value is not of type `cds`') | ||
} | ||
const _validateInjection = cds => { | ||
if (!cds || typeof cds !== 'object') { | ||
throw new Error('Injected value is not of type `cds`') | ||
injection.cds = cds | ||
require('@sap/cds-sql').inject(cds) | ||
} | ||
} | ||
const injection = { | ||
cds: { | ||
parse: { | ||
cql: _notInjected | ||
const handler = { | ||
get: (target, name) => { | ||
switch (name) { | ||
case 'inject': | ||
case 'cds': | ||
return target[name] | ||
} | ||
}, | ||
inject (cds) { | ||
_validateInjection(cds) | ||
injection.cds = cds | ||
require('@sap/cds-sql').inject(cds) | ||
}, | ||
parse: { | ||
cql (arg) { | ||
return injection.cds.parse.cql(arg) | ||
if (!injection.cds) { | ||
if (name === 'config') { | ||
return { data: {} } | ||
} | ||
throw new Error('This method requires `cds` to be injected') | ||
} | ||
}, | ||
get config () { | ||
return injection.cds.config || {} | ||
if (name === 'config' && !target.cds.config) { | ||
return { data: {} } | ||
} | ||
return target.cds[name] | ||
} | ||
} | ||
module.exports = injection | ||
module.exports = new Proxy(injection, handler) |
@@ -10,2 +10,5 @@ const hanaClient = require('./hanaClient') | ||
const { | ||
postProcessing: { getPostProcessMapper, getPropertyMapper, getStructMapper } | ||
} = cdsSql | ||
const { | ||
convertToBoolean, | ||
@@ -49,2 +52,7 @@ convertInt64ToString, | ||
// hana-client | ||
if (this._dbc.constructor.name === 'Connection') { | ||
this._hanaStream = require('@sap/hana-client/extension/Stream.js') | ||
} | ||
registerReconnect(this) | ||
@@ -60,3 +68,3 @@ } | ||
return new Promise((resolve, reject) => { | ||
this._dbc.connect(err => { | ||
this._traced(this._dbc.connect, err => { | ||
if (err) { | ||
@@ -66,6 +74,7 @@ return reject(convertErrorCodeToString(err)) | ||
this._isInUse = true | ||
if (this._credentials.schema) { | ||
this.execute(`SET SCHEMA ${this._credentials.schema}`) | ||
.then(() => { | ||
this._isInUse = true | ||
resolve(this) | ||
@@ -78,3 +87,2 @@ }) | ||
} else { | ||
this._isInUse = true | ||
return resolve(this) | ||
@@ -93,5 +101,15 @@ } | ||
this._isInUse = false | ||
return Promise.resolve(this._dbc.end()) | ||
return cdsSql.thenable.resolve(this._dbc.end()) | ||
} | ||
_getDynatraceDbInfo () { | ||
return { | ||
name: `${this._credentials.host}:${this._credentials.port}`, // TODO: Get real name from VCAP | ||
vendor: 'HanaDB', | ||
host: this._credentials.host, | ||
port: Number(this._credentials.port) | ||
} | ||
} | ||
/** | ||
@@ -131,11 +149,11 @@ * Execute SQL statement. | ||
if (this._toBeDestroyed) { | ||
return Promise.reject(new cdsSql.errors.InconsistentClientError()) | ||
return cdsSql.thenable.reject(new cdsSql.errors.InconsistentClientError()) | ||
} | ||
if (!Array.isArray(values)) { | ||
return Promise.reject(new cdsSql.errors.IllegalFunctionArgumentError('values')) | ||
return cdsSql.thenable.reject(new cdsSql.errors.IllegalFunctionArgumentError('values')) | ||
} | ||
if (typeof query === 'string') { | ||
return this._executeSQL(query, values, new Map()) | ||
return this._executeSQL(query, values, false, new Map()) | ||
} | ||
@@ -167,30 +185,65 @@ | ||
// in case an object is passed and sql builder throws an error | ||
return Promise.reject(convertErrorCodeToString(err)) | ||
return cdsSql.thenable.reject(convertErrorCodeToString(err)) | ||
} | ||
} | ||
_execute (query, inValues = []) { | ||
const query_ = this._addDefaultValues(query, false, true) | ||
/** | ||
* Stream large binary from HANA. | ||
* | ||
* The query can be provided as SELECT SQL string or as SELECT CQN object selecting exactly one large binary columns. | ||
* | ||
* @example <caption>Simple Select as SQL string<caption> | ||
* .execute("SELECT BLOB FROM T") | ||
* @example <caption>Select with filter as CQN object<caption> | ||
* .execute(SELECT.from('T').columns('BLOB').where(['x', '=', 1]) | ||
* | ||
* @param {string|object} query - SELECT SQL string or SELECT CQN object. | ||
* @param {Array} [values] - Values to be set in the SQL statement if query is provided as string or as CQN object with placeholders. | ||
* @returns {Promise} Promise, that resolves with stream if successful or rejects with error if not. | ||
* Result object can be undefined if no rows obtained. | ||
*/ | ||
async stream (query, values = []) { | ||
if (!query.SELECT && (typeof query !== 'string' || !query.trim().startsWith('SELECT'))) { | ||
return cdsSql.thenable.reject(new cdsSql.errors.IllegalFunctionArgumentError('query')) | ||
} | ||
values.streaming = true | ||
const resultSet = await this.execute(query, values) | ||
if (resultSet.length === 0) { | ||
return | ||
} | ||
// resultset entry always has values | ||
const stream = Object.values(resultSet[0])[0] | ||
// non-blob or multiple rows selected | ||
if (typeof stream.pipe !== 'function') { | ||
return | ||
} | ||
return stream | ||
} | ||
_execute (cqn, inValues = []) { | ||
const cqnWithDefaultValues = this._addDefaultValues(cqn, false, true) | ||
const { sql, values = [] } = cdsSql.builder.sqlFactory( | ||
query_, | ||
{ | ||
typeConversion: this._typeConversionMap, | ||
customBuilder: CustomBuilder, | ||
user: this._user | ||
}, | ||
cqnWithDefaultValues, | ||
{ typeConversion: this._typeConversionMap, customBuilder: CustomBuilder, user: this._user }, | ||
this._csn | ||
) | ||
const { postProcessing: { getPostProcessMapper, getPropertyMapper, getStructMapper } } = cdsSql | ||
const propertyMapper = getPropertyMapper(this._csn, cqn, true) | ||
const outValues = inValues.length > 0 ? inValues : values | ||
outValues.streaming = inValues.streaming | ||
const propertyMapper = getPropertyMapper(this._csn, query, true) | ||
const outValues = inValues.length > 0 ? inValues : values | ||
return this._executeSQL( | ||
sql, | ||
outValues, | ||
getPostProcessMapper(this._toService, this._csn, query), | ||
cqn.SELECT && cqn.SELECT.one, | ||
getPostProcessMapper(this._toService, this._csn, cqn), | ||
propertyMapper, | ||
getStructMapper(this._csn, query, propertyMapper) | ||
getStructMapper(this._csn, cqn, propertyMapper), | ||
this._hanaStream && (cqn.INSERT || cqn.UPDATE) | ||
) | ||
@@ -210,3 +263,3 @@ } | ||
return cdsSql.expand.rawToExpanded(expandQueries, queries) | ||
return cdsSql.expand.rawToExpanded(expandQueries, queries, cqn.SELECT.one) | ||
} | ||
@@ -216,9 +269,7 @@ | ||
if (Array.isArray(values) && values.length !== 0) { | ||
return this.prepareStatement(sql).then(statement => { | ||
return statement.execute(values) | ||
}) | ||
return this.preparedExecute(sql, false, values) | ||
} | ||
return new Promise((resolve, reject) => { | ||
this._dbc.exec(sql, (err, result) => { | ||
this._traced(this._dbc.exec, sql, (err, result) => { | ||
if (err) { | ||
@@ -261,18 +312,21 @@ convertErrorCodeToString(err) | ||
_executeSQL (sql, values, postMapper, propertyMapper, objStructMapper) { | ||
_executeSQL (sql, values, isOne, postMapper, propertyMapper, objStructMapper, useHanaClientStatement) { | ||
if (values.length !== 0) { | ||
return this.prepareStatement(sql) | ||
.then(statement => { | ||
return statement.execute(values) | ||
const executed = this.preparedExecute(sql, useHanaClientStatement, values) | ||
if (this._postProcessNeeded(isOne, postMapper, propertyMapper, objStructMapper)) { | ||
return executed.then(result => { | ||
result = this._returnFirstResultIfOne( | ||
isOne, | ||
cdsSql.postProcessing.postProcess(result, postMapper, propertyMapper, objStructMapper) | ||
) | ||
return result | ||
}) | ||
.then(result => { | ||
return Promise.resolve(cdsSql.postProcessing.postProcess(result, postMapper, propertyMapper, objStructMapper)) | ||
}) | ||
.catch(err => { | ||
return Promise.reject(err) | ||
}) | ||
} | ||
return executed | ||
} | ||
return new Promise((resolve, reject) => { | ||
this._dbc.exec(sql, (err, result) => { | ||
this._traced(this._dbc.exec, sql, (err, result) => { | ||
if (err) { | ||
@@ -284,3 +338,8 @@ convertErrorCodeToString(err) | ||
resolve(cdsSql.postProcessing.postProcess(result, postMapper, propertyMapper, objStructMapper)) | ||
resolve( | ||
this._returnFirstResultIfOne( | ||
isOne, | ||
cdsSql.postProcessing.postProcess(result, postMapper, propertyMapper, objStructMapper) | ||
) | ||
) | ||
}) | ||
@@ -290,19 +349,88 @@ }) | ||
_postProcessNeeded (isOne, postMapper, propertyMapper, objStructMapper) { | ||
if (isOne) { | ||
return true | ||
} | ||
if (postMapper && postMapper.size) { | ||
return true | ||
} | ||
if (propertyMapper && propertyMapper.size) { | ||
return true | ||
} | ||
return objStructMapper && objStructMapper.size | ||
} | ||
_returnFirstResultIfOne (isOne, result) { | ||
if (isOne) { | ||
return result.length > 0 ? result[0] : null | ||
} | ||
return result | ||
} | ||
/** | ||
* Prepare and execute SQL statement. | ||
* | ||
* @param {string} sql - SQL string to be prepared. | ||
* @param {boolean} useHanaClientStatement - Use HanaClientStatement | ||
* @param {Array} [values] - Values to be set in the SQL statement if query is provided as string or as CQN object with placeholders. | ||
* @returns {Promise} Promise, that resolves with HdbStatement if successful and rejects if not. | ||
*/ | ||
preparedExecute (sql, useHanaClientStatement, values) { | ||
const that = this | ||
return new Promise((resolve, reject) => { | ||
this._traced(this._preparedExecuteCb, sql, useHanaClientStatement, values, that, (error, results) => { | ||
if (error) { | ||
reject(error) | ||
} else { | ||
resolve(results) | ||
} | ||
}) | ||
}) | ||
} | ||
/** | ||
* Wrapper to use callbacks, which are needed for tracing. | ||
*/ | ||
_preparedExecuteCb (sql, useHanaClientStatement, values, client, cb) { | ||
client | ||
.prepareStatement(sql, useHanaClientStatement) | ||
.then(statement => { | ||
return statement.execute(values) | ||
}) | ||
.then(results => cb(null, results)) | ||
.catch(error => cb(error)) | ||
} | ||
/** | ||
* Prepare SQL statement. | ||
* Beware: For tracing use preparedExecute instead. | ||
* | ||
* @param {string} sql - SQL string to be prepared. | ||
* @param {boolean} useHanaClientStatement - Use HanaClientStatement | ||
* @returns {Promise} Promise, that resolves with HdbStatement if successful and rejects if not. | ||
*/ | ||
prepareStatement (sql) { | ||
prepareStatement (sql, useHanaClientStatement) { | ||
return new Promise((resolve, reject) => { | ||
this._dbc.prepare(sql, (err, statement) => { | ||
const cb = (err, statement) => { | ||
if (err) { | ||
convertErrorCodeToString(err) | ||
err.failedQuery = sql | ||
return reject(err) | ||
} | ||
resolve(new HdbStatement(statement, sql)) | ||
}) | ||
resolve(new HdbStatement(statement, sql, this._hanaStream)) | ||
} | ||
if (this._hanaStream && useHanaClientStatement) { | ||
this._hanaStream.createStatement(this._dbc, sql, cb) | ||
} else { | ||
this._dbc.prepare(sql, cb) | ||
} | ||
}) | ||
@@ -367,2 +495,3 @@ } | ||
this._transCount++ | ||
if (this._transCount === 1) { | ||
@@ -372,3 +501,3 @@ this._dbc.setAutoCommit(false) | ||
return Promise.resolve() | ||
return cdsSql.thenable.resolve() | ||
} | ||
@@ -383,12 +512,16 @@ | ||
if (this._transCount === 0) { | ||
return Promise.resolve() | ||
return cdsSql.thenable.resolve() | ||
} | ||
this._transCount-- | ||
if (this._transCount === 0) { | ||
return new Promise((resolve, reject) => { | ||
this._dbc.commit(err => { | ||
this._traced(this._dbc.commit, err => { | ||
this._dbc.setAutoCommit(true) | ||
if (err) { | ||
return reject(convertErrorCodeToString(err)) | ||
} | ||
resolve() | ||
@@ -399,3 +532,3 @@ }) | ||
return Promise.resolve() | ||
return cdsSql.thenable.resolve() | ||
} | ||
@@ -410,12 +543,16 @@ | ||
if (this._transCount === 0) { | ||
return Promise.resolve() | ||
return cdsSql.thenable.resolve() | ||
} | ||
this._transCount-- | ||
if (this._transCount === 0) { | ||
return new Promise((resolve, reject) => { | ||
this._dbc.rollback(err => { | ||
this._traced(this._dbc.rollback, err => { | ||
this._dbc.setAutoCommit(true) | ||
if (err) { | ||
return reject(convertErrorCodeToString(err)) | ||
} | ||
resolve() | ||
@@ -426,3 +563,3 @@ }) | ||
return Promise.resolve() | ||
return cdsSql.thenable.resolve() | ||
} | ||
@@ -429,0 +566,0 @@ |
@@ -1,13 +0,16 @@ | ||
const _getClient = (name, err) => { | ||
let error | ||
const _getClient = name => { | ||
try { | ||
return require(name) | ||
} catch (e) { | ||
if (name === '@sap/hana-client') { | ||
return _getClient('hdb', e) | ||
} catch (err) { | ||
if (name === 'hdb') { | ||
error = err | ||
return _getClient('@sap/hana-client') | ||
} | ||
throw e | ||
throw error | ||
} | ||
} | ||
module.exports = _getClient('@sap/hana-client') | ||
module.exports = _getClient('hdb') |
const _checkHanaClient = () => { | ||
try { | ||
require.resolve('@sap/hana-client') | ||
require.resolve('hdb') | ||
} catch (e) { | ||
try { | ||
require.resolve('@sap/hana-client') | ||
} catch (e) { | ||
return false | ||
} | ||
return true | ||
} catch (e) { | ||
return false | ||
} | ||
return false | ||
} | ||
@@ -9,0 +16,0 @@ |
@@ -0,1 +1,2 @@ | ||
const cds = require('../cds') | ||
const SelectBuilder = require('@sap/cds-sql').builder.SelectBuilder | ||
@@ -13,2 +14,13 @@ | ||
if (cds.config.data.sql_mapping === 'plain') { | ||
CustomSelectBuilder.prototype._buildRefElement = function (col, res, noQuoting) { | ||
res = new this.ReferenceBuilder(col, this._options, this._csn).build() | ||
if (!noQuoting && !col.as && res.sql && !res.sql.includes(' as ')) { | ||
res.sql += ` AS ${this._options.delimiter}${col.ref[col.ref.length - 1]}${this._options.delimiter}` | ||
} | ||
return res | ||
} | ||
} | ||
module.exports = CustomSelectBuilder |
@@ -12,2 +12,7 @@ const dependencies = { | ||
}, | ||
get serviceFunctions () { | ||
const { serviceFunctions } = require('@sap/cds-sql') | ||
Object.defineProperty(dependencies, 'serviceFunctions', { value: serviceFunctions }) | ||
return serviceFunctions | ||
}, | ||
inject: (...args) => { | ||
@@ -14,0 +19,0 @@ return require('./cds').inject(...args) |
@@ -9,6 +9,8 @@ const { convertErrorCodeToString } = require('../util') | ||
* @param {string} sql - SQL string to be passed to SqlError | ||
* @param {object} hanaStream - hanaStream object if using hana-client | ||
*/ | ||
constructor (statement, sql) { | ||
constructor (statement, sql, hanaStream) { | ||
this._stmt = statement | ||
this._sql = sql | ||
this._hanaStream = hanaStream | ||
} | ||
@@ -38,3 +40,3 @@ | ||
return new Promise((resolve, reject) => { | ||
this._stmt.exec(values, (err, result) => { | ||
this._stmt[this._getFunctionName(values.streaming)](values, (err, result) => { | ||
if (err) { | ||
@@ -50,8 +52,116 @@ convertErrorCodeToString(err) | ||
} | ||
resolve(result) | ||
if (values.streaming) { | ||
if (this._hanaStream) { | ||
return this._getReadObjectHanaClient(result, resolve, reject) | ||
} | ||
const rows = [] | ||
const objStream = result.createObjectStream() | ||
objStream | ||
.on('readable', this._getReadObjectHdb(objStream, rows)) | ||
.once('error', this._getErrorObjectHdb(reject)) | ||
.once('end', this._getEndObjectHdb(rows, resolve)) | ||
} else { | ||
resolve(result) | ||
} | ||
}) | ||
}) | ||
} | ||
_getReadObjectHdb (result, rows) { | ||
return () => { | ||
const row = result.read() | ||
if (row) { | ||
for (const key of Object.keys(row)) { | ||
if (typeof row[key] === 'object' && typeof row[key].createReadStream === 'function') { | ||
row[key] = row[key].createReadStream() | ||
} | ||
} | ||
rows.push(row) | ||
} | ||
} | ||
} | ||
_getEndObjectHdb (rows, resolve) { | ||
return () => { | ||
return resolve(rows) | ||
} | ||
} | ||
_getErrorObjectHdb (reject) { | ||
return err => { | ||
return reject(err) | ||
} | ||
} | ||
_getColumnInfo (result) { | ||
const columnInfo = [] | ||
for (let i = 0, length = result.getColumnCount(); i < length; i++) { | ||
columnInfo.push({ | ||
name: result.getColumnInfo()[i].originalColumnName, | ||
lob: result.getColumnInfo()[i].nativeTypeName === 'BLOB' | ||
}) | ||
} | ||
return columnInfo | ||
} | ||
_getResultSetRow (result, columnInfo) { | ||
const res = {} | ||
for (let i = 0, length = result.getColumnCount(); i < length; i++) { | ||
if (columnInfo[i].lob) { | ||
res[columnInfo[i].name] = this._hanaStream.createLobStream(result, i, { readSize: 1024000 }) | ||
} else { | ||
res[columnInfo[i].name] = result.getValue(i) | ||
} | ||
} | ||
return res | ||
} | ||
// The method createObjectStream does not work in hana-client as expected. | ||
// It provides the complete LOBs and not the streams. | ||
// The resultset should be constructed like bellow. | ||
_getReadObjectHanaClient (result, resolve, reject) { | ||
const resultSet = [] | ||
const columnInfo = this._getColumnInfo(result) | ||
let next = true | ||
while (next) { | ||
next = result.next((err, ret) => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
if (ret) { | ||
resultSet.push(this._getResultSetRow(result, columnInfo)) | ||
} | ||
if (!next) { | ||
resolve(resultSet) | ||
} | ||
}) | ||
} | ||
} | ||
_getFunctionName (streaming) { | ||
if (streaming) { | ||
if (this._hanaStream) { | ||
return 'executeQuery' | ||
} | ||
return 'execute' | ||
} | ||
return 'exec' | ||
} | ||
} | ||
module.exports = HdbStatement |
{ | ||
"name": "@sap/cds-hana", | ||
"version": "1.7.1", | ||
"version": "1.11.1", | ||
"lockfileVersion": 1, | ||
@@ -8,5 +8,5 @@ "requires": true, | ||
"@sap/cds-sql": { | ||
"version": "1.7.0" | ||
"version": "1.11.1" | ||
} | ||
} | ||
} |
@@ -1,1 +0,1 @@ | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-sql":"1.7.0"},"deprecated":false,"description":"Driver package for access to hana database, including setting up the client, configuring all the necessary options to initiate the connection and handling database specifics so that they can be processed on our end.","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-hana","version":"1.7.1","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-sql":"1.11.1"},"deprecated":false,"description":"Driver package for access to hana database, including setting up the client, configuring all the necessary options to initiate the connection and handling database specifics so that they can be processed on our end.","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-hana","version":"1.11.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
57851
885
18
+ Added@sap/cds-sql@1.11.1(transitive)
- Removed@sap/cds-sql@1.7.0(transitive)
Updated@sap/cds-sql@1.11.1