@sap/cds-sqlite
Advanced tools
Comparing version 0.9.0 to 1.5.0
@@ -9,2 +9,53 @@ # Changelog | ||
## Version 1.5.0 - 2019-02-06 | ||
### Changed | ||
- Minimum node version 8.9.0 | ||
- Improve expand performance | ||
## Version 1.4.0 - 2019-01-22 | ||
### Added | ||
- `.execute` supports placeholders in CQN | ||
## Version 1.3.0 - 2019-01-11 | ||
### Changed | ||
- Use latest version of @sap/cds-sql | ||
## Version 1.2.0 - 2018-12-21 | ||
### Added | ||
- Set default values in case of CREATE, UPSERT and adding a child in deep documents | ||
## Version 1.1.0 - 2018-12-12 | ||
### Added | ||
- Support Deep Document CQNs | ||
## Version 1.0.3 - 2018-11-27 | ||
### Added | ||
- credentials.database can be used instead of parameters host and url | ||
### Changed | ||
- Throw db error instead of wrapping it in Sql Error | ||
- Throw an error if database is not defined instead of fallback to memory | ||
### Fixed | ||
- Bulk Insert with $user / $now | ||
- Post processing of Binary, Boolean, DateTime and Integer64 | ||
## Version 0.10.0 - 2018-10-17 | ||
- Refactoring and changes due to updated dependencies | ||
## Version 0.9.0 - 2018-10-04 | ||
@@ -11,0 +62,0 @@ |
@@ -5,3 +5,3 @@ const _notInjected = () => { | ||
const _validateInjection = (cds) => { | ||
const _validateInjection = cds => { | ||
if (!cds || typeof cds !== 'object') { | ||
@@ -8,0 +8,0 @@ throw new Error('Injected value is not of type `cds`') |
@@ -1,26 +0,23 @@ | ||
const {Database} = require('sqlite3') | ||
const { Database } = require('sqlite3') | ||
const SqliteStatement = require('../statement/SqliteStatement') | ||
const { | ||
BaseClient, | ||
errors: { | ||
IllegalFunctionArgumentError, | ||
InconsistentClientError, | ||
SqlError | ||
errors: { IllegalFunctionArgumentError, InconsistentClientError }, | ||
builder: { sqlFactory }, | ||
expand: { createJoinCQNFromExpanded, hasExpand, rawToExpanded }, | ||
composition: { | ||
hasCompositionDelete, | ||
createCascadeDeleteCQNs, | ||
hasDeepInsert, | ||
createDeepInsertCQNs, | ||
hasDeepUpdate, | ||
selectDeepUpdateData, | ||
createDeepUpdateCQNs | ||
}, | ||
builder: {sqlFactory}, | ||
expand: { | ||
createJoinCQNFromExpanded, | ||
hasExpand, | ||
rawToExpanded | ||
}, | ||
contains: { | ||
replaceContainsWithLike, | ||
hasContains | ||
}, | ||
postProcessing: { | ||
getPostProcessMapper, | ||
postProcess | ||
} | ||
contains: { replaceContainsWithLike, hasContains }, | ||
postProcessing: { getPostProcessMapper, postProcess } | ||
} = require('@sap/cds-sql') | ||
const { convertToBoolean, convertInt64ToString, convertToISOTime, convertToISONoMillis } = require('./util') | ||
class Client extends BaseClient { | ||
@@ -30,36 +27,14 @@ /** | ||
* | ||
* Username/password are currently not supported, | ||
* but at least the host must be provided. | ||
* | ||
* @param {Object} connect - Connection details. | ||
* @param {string} connect.host - Filename to the database or :memory; for in memory | ||
* @param {string} [connect.user] - Username for authentication | ||
* @param {string} [connect.password] - Password for authentication | ||
* @param {Object} credentials - Connection details. | ||
* @param {string} credentials.database - Filename to the database or :memory; for in memory | ||
*/ | ||
constructor (connect) { | ||
const convertToBase64Buffer = (element) => { | ||
return Buffer.from(element, 'base64') | ||
} | ||
const convertToISOTime = (value) => { | ||
if (value === null) { | ||
return value | ||
} | ||
if (!value) { | ||
value = 0 | ||
} | ||
return new Date(value).toISOString() | ||
} | ||
constructor (credentials) { | ||
super([ | ||
['cds.Boolean', Boolean], | ||
['cds.Integer64', String], | ||
['cds.Binary', convertToBase64Buffer], | ||
['cds.LargeBinary', convertToBase64Buffer], | ||
['cds.DateTime', convertToISOTime], | ||
['cds.Boolean', convertToBoolean], | ||
['cds.Integer64', convertInt64ToString], | ||
['cds.DateTime', convertToISONoMillis], | ||
['cds.Timestamp', convertToISOTime] | ||
]) | ||
this._connect = connect | ||
this._credentials = Object.assign({}, credentials) | ||
this._user = 'anonymous' // default user | ||
@@ -74,11 +49,12 @@ this._transCount = 0 | ||
* | ||
* @returns {Promise} Promise, that resolves with SqliteClient if _connect is successful or rejects with error if not. | ||
* @returns {Promise} Promise, that resolves with SqliteClient if _credentials is successful or rejects with error if not. | ||
*/ | ||
connect () { | ||
return new Promise((resolve, reject) => { | ||
if (!this._connect) { | ||
return reject(new IllegalFunctionArgumentError('connect')) | ||
// .options() will ensure that credentials.database is set | ||
if (!this._credentials) { | ||
return reject(new IllegalFunctionArgumentError('options.credentials')) | ||
} | ||
this._dbc = new Database(this._connect.host, (err) => { | ||
this._dbc = new Database(this._credentials.database, err => { | ||
if (err) { | ||
@@ -102,3 +78,3 @@ return reject(err) | ||
return new Promise((resolve, reject) => { | ||
this._dbc.close((err) => { | ||
this._dbc.close(err => { | ||
if (err) { | ||
@@ -141,3 +117,3 @@ return reject(err) | ||
* @param {string|object} query - SQL string or CQN object generated by the DML statements. | ||
* @param {Array} [values] - Values to be set in the SQL statement if query is provided as string. | ||
* @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 result object (array) if successful or rejects with error if not. | ||
@@ -172,10 +148,15 @@ * | ||
const {sql, values = []} = sqlFactory(newQuery, { | ||
typeConversion: this._typeConversionMap, | ||
user: this._user, | ||
now: {sql: 'strftime(\'%Y-%m-%dT%H:%M:%SZ\',\'now\')'} // '2012-12-03T07:16:23Z' | ||
}, this._csn) | ||
if (hasCompositionDelete(this._csn && this._csn.definitions, newQuery)) { | ||
return this._processCascadeDelete(newQuery) | ||
} | ||
return this._executeSQL(sql, values, getPostProcessMapper(this._toService, this._csn, newQuery)) | ||
if (hasDeepInsert(this._csn && this._csn.definitions, newQuery)) { | ||
return this._processDeepInsert(newQuery) | ||
} | ||
if (hasDeepUpdate(this._csn && this._csn.definitions, newQuery)) { | ||
return this._processDeepUpdate(newQuery) | ||
} | ||
return this._execute(newQuery, values) | ||
// The clients of this client are expecting a promise return in any case. | ||
@@ -187,2 +168,16 @@ } catch (err) { | ||
_execute (cqn, inValues = []) { | ||
const { sql, values = [] } = sqlFactory( | ||
cqn, | ||
{ | ||
typeConversion: this._typeConversionMap, | ||
user: this._user, | ||
now: { sql: "strftime('%Y-%m-%dT%H:%M:%SZ','now')" } // '2012-12-03T07:16:23Z' | ||
}, | ||
this._csn | ||
) | ||
const outValues = inValues.length > 0 ? inValues : values | ||
return this._executeSQL(sql, outValues, getPostProcessMapper(this._toService, this._csn, cqn)) | ||
} | ||
/** | ||
@@ -211,3 +206,3 @@ * Checks which of the sqlite3 is most fitting for the sql values combination. | ||
// Regex is faster than toLower + trim + startsWith | ||
return (new RegExp(`^\\s*${type}`, 'i')).test(sql) | ||
return new RegExp(`^\\s*${type}`, 'i').test(sql) | ||
} | ||
@@ -220,3 +215,4 @@ | ||
if (err) { | ||
return reject(new SqlError(err, sql, values)) | ||
err.failedQuery = sql | ||
return reject(err) | ||
} | ||
@@ -233,3 +229,4 @@ | ||
if (err) { | ||
return reject(new SqlError(err, sql, values)) | ||
err.failedQuery = sql | ||
return reject(err) | ||
} | ||
@@ -242,56 +239,10 @@ | ||
/** | ||
* One can not use more than 999 values per insert. | ||
* There is no build in bulk support of sqlite3. | ||
* This requires to split into multiple SQLs in case the total sum of values is greater than 999. | ||
*/ | ||
_bulkInsert (sql, values) { | ||
const valuesPerRow = values[0].length | ||
const placeholders = this._getBulkPlaceholders(valuesPerRow) | ||
const promises = [] | ||
// In case 999 will be reached this vars will be reset | ||
let bulkSql = sql | ||
let flattenedValues = [] | ||
for (const insert of values) { | ||
// Execute as adding this row would add more values than supported | ||
if ((flattenedValues.length + valuesPerRow) > 999) { | ||
promises.push(this._runSingle(bulkSql, flattenedValues)) | ||
bulkSql = sql | ||
flattenedValues = [] | ||
} | ||
// Array is empty on first run and after reset | ||
if (flattenedValues.length !== 0) { | ||
bulkSql += placeholders | ||
} | ||
flattenedValues.push(...insert) | ||
} | ||
// Ensure last bulk is executed | ||
promises.push(this._runSingle(bulkSql, flattenedValues)) | ||
// Wait for all inserts to be done | ||
return Promise.all(promises) | ||
.then(() => { | ||
// return affected rows | ||
return values.length | ||
}) | ||
return this.prepareStatement(sql) | ||
.then(stmt => stmt.execute(values)) | ||
.then(() => values.length) | ||
} | ||
_getBulkPlaceholders (valuesPerRow) { | ||
const placeholders = [] | ||
for (let i = 0; i < valuesPerRow; i++) { | ||
placeholders.push('?') | ||
} | ||
return `,(${placeholders.join(',')})` | ||
} | ||
_processExpand (cqn) { | ||
const sqls = [] | ||
const queries = [] | ||
const expandQueries = createJoinCQNFromExpanded(cqn, this._csn) | ||
@@ -302,12 +253,33 @@ | ||
const {sql, values} = sqlFactory(cqn) | ||
sqls.push(this._executeSelect(sql, values, false)) | ||
const { sql, values } = sqlFactory(cqn) | ||
queries.push(this._executeSelect(sql, values, false)) | ||
} | ||
return Promise.all(sqls) | ||
.then((results) => { | ||
return rawToExpanded(expandQueries, results) | ||
}) | ||
return rawToExpanded(expandQueries, queries) | ||
} | ||
_processCascadeDelete (cqn) { | ||
return this.processNestedCQNs( | ||
createCascadeDeleteCQNs(this._csn && this._csn.definitions, cqn), | ||
this._execute.bind(this) | ||
) | ||
} | ||
_processDeepInsert (cqn) { | ||
return this.processNestedCQNs( | ||
[createDeepInsertCQNs(this._csn && this._csn.definitions, cqn)], | ||
this._execute.bind(this) | ||
) | ||
} | ||
_processDeepUpdate (cqn) { | ||
/* istanbul ignore next */ | ||
return selectDeepUpdateData(this._csn && this._csn.definitions, cqn, this._execute.bind(this)).then(selectData => { | ||
return this.processNestedCQNs( | ||
createDeepUpdateCQNs(this._csn && this._csn.definitions, cqn, selectData), | ||
this._execute.bind(this) | ||
) | ||
}) | ||
} | ||
/** | ||
@@ -322,5 +294,6 @@ * Prepare SQL statement. | ||
this._dbc.serialize(() => { | ||
const stmt = this._dbc.prepare(sql, (err) => { | ||
const stmt = this._dbc.prepare(sql, err => { | ||
if (err) { | ||
return reject(new SqlError(err, sql)) | ||
err.failedQuery = sql | ||
return reject(err) | ||
} | ||
@@ -350,4 +323,3 @@ | ||
*/ | ||
setLocale () { | ||
} | ||
setLocale () {} | ||
@@ -354,0 +326,0 @@ /** |
@@ -1,28 +0,56 @@ | ||
const {IllegalFunctionArgumentError} = require('@sap/cds-sql').errors | ||
const { errors: { IllegalFunctionArgumentError } } = require('@sap/cds-sql') | ||
const _validateDatabase = database => { | ||
if (!database) { | ||
throw new IllegalFunctionArgumentError('options.credentials.database') | ||
} | ||
} | ||
const _validatePool = options => { | ||
if (options.pool.min > options.pool.max) { | ||
throw new IllegalFunctionArgumentError('options.pool.min') | ||
} | ||
if (options.credentials.database === ':memory:') { | ||
if (options.pool.max !== 1) { | ||
throw new IllegalFunctionArgumentError('options.pool.max') | ||
} | ||
if (options.pool.evictionRunIntervalMillis !== 0) { | ||
throw new IllegalFunctionArgumentError('options.pool.evictionRunIntervalMillis') | ||
} | ||
if (options.pool.idleTimeoutMillisForPools !== 0) { | ||
throw new IllegalFunctionArgumentError('options.pool.idleTimeoutMillisForPools') | ||
} | ||
} | ||
} | ||
/** | ||
* Validates the connect and pool options and adds defaults if not given. | ||
* @param {Object} [options] - The db connection options. | ||
* @param {Object} [options.url] - Connect url with driver. | ||
* @param {Object} [options.database] - Alias for url. | ||
* @param {Object} [options.driver] - Package ID of driver facade. | ||
* @param {string} [options.host] - Filename to the database or :memory: for in memory | ||
* @param {string} [options.user] - Username for authentication | ||
* @param {string} [options.password] - Password for authentication | ||
* @param {Object} [options.credentials] - The db connection options. | ||
* @param {Object} [options.credentials.database] - Alias for url. | ||
* @param {Object} [options.pool] - The min and max pool options. | ||
* @param {number} [options.pool.min] - The minimum number of db connection clients. | ||
* @param {number} [options.pool.max] - The maximum number of db connection clients. | ||
* @throws {Error} If pool is set up with more than one client in ':memory' case | ||
* @param {number} [options.pool.evictionRunIntervalMillis] - How often to run eviction checks. | ||
* @param {number} [options.pool.idleTimeoutMillisForPools] - The time interval in ms until an idle pool is evicted. | ||
* @throws {IllegalFunctionArgumentError} | ||
*/ | ||
const options = (options) => { | ||
options.host = options.host || options.database || options.url || ':memory:' | ||
const options = options => { | ||
options.credentials = options.credentials || {} | ||
options.credentials.database = options.credentials.database || options.database || options.host || options.url | ||
options.pool.min = options.pool.min || 1 | ||
options.pool.max = options.pool.max || ((options.host === ':memory:') ? 1 : 10) | ||
options.pool.max = options.pool.max || (options.credentials.database === ':memory:' ? 1 : 10) | ||
options.pool.evictionRunIntervalMillis = | ||
options.pool.evictionRunIntervalMillis || (options.credentials.database === ':memory:' ? 0 : 10000) | ||
options.pool.idleTimeoutMillisForPools = | ||
options.pool.idleTimeoutMillisForPools || (options.credentials.database === ':memory:' ? 0 : 60000) | ||
if (options.host === ':memory:' && options.pool.max !== 1) { | ||
throw new IllegalFunctionArgumentError('pool') | ||
} | ||
_validateDatabase(options.credentials.database) | ||
_validatePool(options) | ||
} | ||
module.exports = options |
const dependencies = { | ||
get Client () { | ||
const Client = require('./client').Client | ||
Object.defineProperty(dependencies, 'Client', {value: Client}) | ||
Object.defineProperty(dependencies, 'Client', { value: Client }) | ||
return Client | ||
@@ -9,3 +9,3 @@ }, | ||
const options = require('./client').options | ||
Object.defineProperty(dependencies, 'options', {value: options}) | ||
Object.defineProperty(dependencies, 'options', { value: options }) | ||
return options | ||
@@ -15,3 +15,3 @@ }, | ||
const SqliteStatement = require('./statement/SqliteStatement') | ||
Object.defineProperty(dependencies, 'SqliteStatement', {value: SqliteStatement}) | ||
Object.defineProperty(dependencies, 'SqliteStatement', { value: SqliteStatement }) | ||
return SqliteStatement | ||
@@ -18,0 +18,0 @@ }, |
@@ -1,2 +0,2 @@ | ||
const {IllegalFunctionArgumentError} = require('@sap/cds-sql').errors | ||
const { IllegalFunctionArgumentError } = require('@sap/cds-sql').errors | ||
@@ -68,12 +68,14 @@ class SqliteStatement { | ||
values.forEach((val) => { | ||
listPromise.push(new Promise((resolve, reject) => { | ||
this._stmt.all(val, (err) => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
values.forEach(val => { | ||
listPromise.push( | ||
new Promise((resolve, reject) => { | ||
this._stmt.all(val, err => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
resolve() | ||
resolve() | ||
}) | ||
}) | ||
})) | ||
) | ||
}) | ||
@@ -88,3 +90,3 @@ | ||
}) | ||
.catch((err) => { | ||
.catch(err => { | ||
this._stmt.finalize() // statement must be finalized also in case of error | ||
@@ -91,0 +93,0 @@ reject(err) |
{ | ||
"name": "@sap/cds-sqlite", | ||
"version": "0.9.0", | ||
"version": "1.5.0", | ||
"lockfileVersion": 1, | ||
"requires": true, | ||
"dependencies": { | ||
"@sap/cds-sql": { | ||
"version": "0.11.0" | ||
"version": "1.5.0" | ||
} | ||
} | ||
} |
@@ -1,1 +0,1 @@ | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-sql":"0.11.0"},"deprecated":false,"description":"Driver package for access to sqlite 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.","devDependencies":{"@sap/cds":"git://github.wdf.sap.corp/cdx/cds","jest":"^23.4.0","jest-junit":"^3.6.0","jsdoc-to-markdown":"^4.0.1","request":"2.83.0","sqlite3":"4.0.1","standard":"^11.0.0","standard-reporter":"^1.0.5"},"engines":{"node":">= 6.11.0"},"jest":{"testEnvironment":"node"},"jest-junit":{"suiteName":"jest tests","output":"reports/sonar/test-reporter.xml","classNameTemplate":"{classname}-{title}","titleTemplate":"{classname}-{title}","ancestorSeparator":" › ","usePathForSuiteName":"true"},"main":"lib/index.js","name":"@sap/cds-sqlite","scripts":{"build":"npm run test && npm run jsdoc2md","clean":"rm -rf reports && rm -f npm-debug.log","jsdoc2md":"jsdoc2md --param-list-format list lib/**/*.js > docs/api.md","lint":"([ -d reports ] || mkdir reports) && standard --env jest && standard | standard-reporter --checkstyle > reports/eslint.jslint.xml","test":"npm run clean && npm run lint && npm run test-unit && npm run test-integration","test-integration":"jest --config=jest-integration.json && [ -e reports/sonar/test-reporter.xml ] && mv reports/sonar/test-reporter.xml reports/sonar/test-integration.xml","test-unit":"jest --config=jest-unit.json && [ -e reports/sonar/test-reporter.xml ] && mv reports/sonar/test-reporter.xml reports/sonar/test-unit.xml"},"standard":{"env":["jest"],"globals":["jest"]},"version":"0.9.0","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-sql":"1.5.0"},"deprecated":false,"description":"Driver package for access to sqlite 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-sqlite","version":"1.5.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
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
44646
0
13
566
1
+ Added@sap/cds-sql@1.5.0(transitive)
- Removed@sap/cds-sql@0.11.0(transitive)
Updated@sap/cds-sql@1.5.0