@sap/cds-ql
Advanced tools
Comparing version 1.14.0 to 1.17.0
@@ -9,2 +9,38 @@ # Changelog | ||
## Version 1.17.0 - 2019-08-21 | ||
### Added | ||
- `options.kind` can also be an object | ||
- Service related functions `create`, `insert`, `read`, `update` and `delete` to transaction | ||
- Http related functions `get`, `post`, `put`, `patch` and `delete` to transaction | ||
### Changed | ||
- `.transaction(...).run` can be used with an array of queries | ||
### Fixed | ||
- Update with binary property | ||
## Version 1.16.0 - 2019-07-23 | ||
### Changed | ||
- Updated version of @sap/cds-sql to 1.15.0 | ||
- Updated version of @sap/cds-sqlite to 1.15.0 | ||
- Updated version of @sap/cds-hana to 1.15.0 | ||
## Version 1.15.0 - 2019-07-09 | ||
### Added | ||
- Enhanced statements to be compatible to specification | ||
- Support for views with parameters | ||
### Removed | ||
- `generateUUID` function and `uuid`dependency | ||
- usage of xsenv for authorization (is provided by cds.env) | ||
## Version 1.14.0 - 2019-06-24 | ||
@@ -11,0 +47,0 @@ |
const { IllegalFunctionArgumentError } = require('../errors') | ||
const xsenv = require('./xsenv') | ||
const runBlock = require('../statements/runBlock') | ||
const cds = require('../cds') | ||
const MAINKINDS = new Map([ | ||
['messaging', ['enterprise-messaging', 'file-based-messaging', 'in-process-messaging']], | ||
// TODO: In the future there should be an intermediate layer ['db', ['sqlite', 'hana']], | ||
['rest', ['rest', 'odata']] | ||
]) | ||
/** | ||
@@ -36,5 +40,13 @@ * The credentials might be configured at the package.json | ||
return { url: url } | ||
return { url } | ||
} | ||
const _getMainKind = kind => { | ||
for (const [mainKind, kinds] of MAINKINDS) { | ||
if (kinds.includes(kind)) { | ||
return mainKind | ||
} | ||
} | ||
return kind | ||
} | ||
/** | ||
@@ -90,2 +102,18 @@ * Parse a string into options. | ||
const _getDriverPackage = mainKind => { | ||
switch (mainKind) { | ||
case 'hana': | ||
case 'sqlite': | ||
case 'rest': | ||
case 'messaging': | ||
return require(`@sap/cds-${mainKind}`) | ||
case 'odata': | ||
return require(`@sap/cds-rest`) | ||
case undefined: | ||
case null: | ||
default: | ||
return require(mainKind) | ||
} | ||
} | ||
/** | ||
@@ -98,19 +126,34 @@ * Provided clients will have the @sap group and cds- prefix. | ||
*/ | ||
const _getDriverPackage = kind => { | ||
switch (kind) { | ||
case 'hana': | ||
case 'sqlite': | ||
case 'rest': | ||
return require(`@sap/cds-${kind}`) | ||
case 'enterpriseMessaging': | ||
return require(`@sap/cds-em`) | ||
case undefined: | ||
case null: | ||
case '': | ||
throw new IllegalFunctionArgumentError('options.kind') | ||
default: | ||
return require(kind) | ||
const _getDriverPackages = kind => { | ||
if (typeof kind === 'string') { | ||
return _getDriverPackages({ [kind]: {} }) | ||
} | ||
const drivers = new Map() | ||
for (const singleKind of Object.keys(kind)) { | ||
const mainKind = _getMainKind(singleKind) | ||
drivers.set(mainKind, Object.assign(_getDriverPackage(mainKind), { kind: singleKind })) | ||
} | ||
return drivers | ||
} | ||
const _addConnectionOptions = options => { | ||
const baseOptions = Object.assign({}, options) | ||
options.connectionOptions = new Map() | ||
if (typeof options.kind === 'string') { | ||
options.connectionOptions.set(_getMainKind(options.kind), baseOptions) | ||
return | ||
} | ||
delete baseOptions.kind | ||
for (const _kind of Object.keys(options.kind)) { | ||
options.connectionOptions.set( | ||
_getMainKind(_kind), | ||
Object.assign({}, baseOptions, options.kind[_kind], { kind: _kind }) | ||
) | ||
} | ||
} | ||
/** | ||
@@ -122,3 +165,3 @@ * In case the kind is provided as string, require and use the package | ||
const _addClientToOptions = options => { | ||
if (options.package) { | ||
if (options.packages) { | ||
return | ||
@@ -128,19 +171,24 @@ } | ||
if (typeof options.kind === 'function') { | ||
options.package = { Client: options.kind } | ||
options.packages = new Map([[options.kind.name, { Client: options.kind }]]) | ||
return | ||
} | ||
options.package = _getDriverPackage(options.kind) | ||
options.packages = _getDriverPackages(options.kind) | ||
if (options.multiTenant !== true) { | ||
options.package.options(options) | ||
} | ||
for (const [mainKind, _package] of options.packages) { | ||
if (options.multiTenant !== true) { | ||
// TODO: Is it really options? | ||
if (_package.options) { | ||
_package.options(options.connectionOptions.get(mainKind)) | ||
} | ||
} | ||
options.package.inject(cds.cds) | ||
_package.inject(cds.cds) | ||
if (!options.package.Client.prototype._runBlock) { | ||
options.package.Client.prototype._runBlock = runBlock | ||
if (!_package.Client.prototype._runBlock) { | ||
_package.Client.prototype._runBlock = runBlock | ||
if (process.env.DEBUG) { | ||
_addDebugToClient(options.package.Client) | ||
if (process.env.DEBUG) { | ||
_addDebugToClient(_package.Client) | ||
} | ||
} | ||
@@ -163,3 +211,5 @@ } | ||
options = | ||
datasource || !options ? Object.assign(_parseOptionsString(datasource), options) : Object.assign({}, options) | ||
(datasource && datasource.includes(':')) || !options | ||
? Object.assign(_parseOptionsString(datasource), options) | ||
: Object.assign({}, options) | ||
@@ -175,2 +225,6 @@ if (options.credentials) { | ||
if (!options.kind) { | ||
throw new IllegalFunctionArgumentError('options.kind') | ||
} | ||
options.pool = Object.assign( | ||
@@ -186,19 +240,7 @@ { | ||
if ( | ||
!options.credentials || | ||
(options.credentials && | ||
!options.credentials.user && | ||
!options.credentials.database && | ||
!options.credentials.mock && | ||
!options.credentials.sources && | ||
!options.credentials.targets) | ||
) { | ||
xsenv(options) | ||
} | ||
_addConnectionOptions(options) | ||
_addClientToOptions(options) | ||
return options | ||
} | ||
module.exports = checkOptions | ||
module.exports = { checkOptions } |
@@ -13,26 +13,28 @@ const genericPool = require('generic-pool') | ||
* @param {string} poolKey - Identifier the clients are tagged with. | ||
* @param {Object} options - Pool and Client settings | ||
* @param {Object} [options.pool] - The min and max pool settings. | ||
* @param {number} [options.pool.min] - The minimum number of db connection clients. | ||
* @param {number} [options.pool.max] - The maximum number of db connection clients. | ||
* @param {number} [options.pool.evictionRunIntervalMillis] - The time interval in ms for an eviction check of idle db | ||
* @param {string} Client - To be used client. | ||
* @param {Object} connectionOptions - Pool and Client settings | ||
* @param {Object} [connectionOptions.pool] - The min and max pool settings. | ||
* @param {number} [connectionOptions.pool.min] - The minimum number of db connection clients. | ||
* @param {number} [connectionOptions.pool.max] - The maximum number of db connection clients. | ||
* @param {number} [connectionOptions.pool.evictionRunIntervalMillis] - The time interval in ms for an eviction check of idle db | ||
* clients in a pool. | ||
* @param {number} [options.pool.numTestsPerEvictionRun] - The number of db clients to be checked in each eviction run. | ||
* @param {number} [options.pool.softIdleTimeoutMillis] - The time interval in ms until a db client in idle state is | ||
* @param {number} [connectionOptions.pool.numTestsPerEvictionRun] - The number of db clients to be checked in each eviction run. | ||
* @param {number} [connectionOptions.pool.softIdleTimeoutMillis] - The time interval in ms until a db client in idle state is | ||
* considered as 'to be evicted'. Does not evict idle db clients if number of available clients would fall below | ||
* options.pool.min. | ||
* @param {number} [options.pool.idleTimeoutMillis] - The time interval in ms until a db client in idle state is | ||
* @param {number} [connectionOptions.pool.idleTimeoutMillis] - The time interval in ms until a db client in idle state is | ||
* considered as 'to be evicted'. If number of available clients would fall below options.pool.min it evicts this | ||
* client and creates a new one to meet options.pool.min. | ||
* @param {Object} options.kind - The to be used DB client. | ||
* @param {Object} [options.credentials] - Credentials how to connect with a client | ||
* @param {string} [options.credentials.host] - Address to the db | ||
* @param {string} [options.credentials.port] - Port for db | ||
* @param {string} [options.credentials.user] - Username for authentication | ||
* @param {string} [options.credentials.password] - Password for authentication | ||
* @param {string} [options.reflectedModel] - Reflected model, that belongs to the data source | ||
* @param {Object} [connectionOptions.credentials] - Credentials how to connect with a client | ||
* @param {string} [connectionOptions.credentials.host] - Address to the db | ||
* @param {string} [connectionOptions.credentials.port] - Port for db | ||
* @param {string} [connectionOptions.credentials.user] - Username for authentication | ||
* @param {string} [connectionOptions.credentials.password] - Password for authentication | ||
* @param {string} [connectionOptions.reflectedModel] - Reflected model, that belongs to the data source | ||
*/ | ||
constructor (poolKey, options) { | ||
constructor (poolKey, Client, connectionOptions) { | ||
this._poolKey = poolKey | ||
this._pool = genericPool.createPool(this._getPoolFactory(options), options.pool) | ||
this._Client = Client | ||
this._connectOptions = connectionOptions | ||
this._pool = genericPool.createPool(this._getPoolFactory(connectionOptions), connectionOptions.pool) | ||
} | ||
@@ -49,5 +51,4 @@ | ||
create: () => { | ||
const Client = options.package.Client | ||
const client = new Client(options.credentials || options) | ||
return client.connect() | ||
const Client = this._Client | ||
return new Client(options).connect() | ||
}, | ||
@@ -54,0 +55,0 @@ |
const Pool = require('./Pool') | ||
const Transaction = require('./Transaction') | ||
const TenantManager = require('./TenantManager') | ||
const _options = require('./options') | ||
const { checkOptions } = require('./options') | ||
const resolved = require('./resolved') | ||
const { resolve } = require('../utils/thenable') | ||
const cds = require('../cds') | ||
@@ -40,4 +40,4 @@ const { IllegalFunctionArgumentError } = require('../errors') | ||
* Default 480000 ms (8 min). | ||
* @param {Object} options.kind - The to be used DB client. | ||
* @param {Object} [options.model] - The to be used model. | ||
* @param {Object|String} [options.kind] - The to be used kind(s). | ||
* @param {Object} [options.service] - if options.model, the to be used service name. | ||
@@ -52,61 +52,72 @@ * @param {Object} [options.credentials] - Credentials how to connect with a client | ||
constructor (datasource, options) { | ||
this.options = _options(datasource, options) | ||
this.options = checkOptions(datasource, options) | ||
this._addTenantManagers() | ||
this._includeStaticPackageMethods() | ||
this._init(datasource) | ||
this._pools = new Map() | ||
// Create cache for accessing entities | ||
Object.defineProperties(this, { _entities: { value: {} } }) | ||
} | ||
_addTenantManagers () { | ||
if (this.options.multiTenant === true) { | ||
if (this.options.package) { | ||
this._enrichOptions = this.options.package.options | ||
if (!this._tenantManagers) { | ||
this._tenantManagers = new Map() | ||
for (const [mainKind, _package] of this.options.packages) { | ||
const TenantManager = _package.TenantManager | ||
if (TenantManager) { | ||
this._tenantManagers.set(mainKind, new TenantManager(this.options.connectionOptions.get(mainKind))) | ||
} | ||
} | ||
} | ||
if (!this._tenantManager) { | ||
this._tenantManager = new TenantManager(this.options) | ||
} | ||
} | ||
} | ||
if (this.options.package) { | ||
if (this.options.package.serviceFunctions) { | ||
_includeStaticPackageMethods () { | ||
for (const [, _package] of this.options.packages) { | ||
if (_package.serviceFunctions) { | ||
Object.defineProperties(this, { | ||
create: { value: this.options.package.serviceFunctions.create, writable: true }, | ||
read: { value: this.options.package.serviceFunctions.read, writable: true }, | ||
update: { value: this.options.package.serviceFunctions.update, writable: true }, | ||
delete: { value: this.options.package.serviceFunctions.delete, writable: true }, | ||
insert: { value: this.options.package.serviceFunctions.insert, writable: true } | ||
create: { value: _package.serviceFunctions.create, writable: true }, | ||
read: { value: _package.serviceFunctions.read, writable: true }, | ||
update: { value: _package.serviceFunctions.update, writable: true }, | ||
delete: { value: _package.serviceFunctions.delete, writable: true }, | ||
insert: { value: _package.serviceFunctions.insert, writable: true } | ||
}) | ||
} | ||
if (this.options.package.httpFunctions) { | ||
if (_package.httpFunctions) { | ||
Object.defineProperties(this, { | ||
get: { value: this.options.package.httpFunctions.get, writable: true }, | ||
post: { value: this.options.package.httpFunctions.post, writable: true }, | ||
put: { value: this.options.package.httpFunctions.put, writable: true }, | ||
patch: { value: this.options.package.httpFunctions.patch, writable: true }, | ||
delete: { value: this.options.package.httpFunctions.delete, writable: true } | ||
get: { value: _package.httpFunctions.get, writable: true }, | ||
post: { value: _package.httpFunctions.post, writable: true }, | ||
put: { value: _package.httpFunctions.put, writable: true }, | ||
patch: { value: _package.httpFunctions.patch, writable: true }, | ||
delete: { value: _package.httpFunctions.delete, writable: true } | ||
}) | ||
} | ||
if (this.options.package.messagingFunctions) { | ||
if (_package.messagingFunctions) { | ||
Object.defineProperties(this, { | ||
on: { value: this.options.package.messagingFunctions.on, writable: true }, | ||
emit: { value: this.options.package.messagingFunctions.emit, writable: true }, | ||
removeAllListeners: { value: this.options.package.messagingFunctions.removeAllListeners, writable: true }, | ||
putQueue: { value: this.options.package.messagingFunctions.putQueue, writable: true }, | ||
deleteQueue: { value: this.options.package.messagingFunctions.deleteQueue, writable: true }, | ||
addSubscription: { value: this.options.package.messagingFunctions.addSubscription, writable: true } | ||
removeAllListeners: { value: _package.messagingFunctions.removeAllListeners, writable: true }, | ||
putQueue: { value: _package.messagingFunctions.putQueue, writable: true }, | ||
deleteQueue: { value: _package.messagingFunctions.deleteQueue, writable: true }, | ||
addSubscription: { value: _package.messagingFunctions.addSubscription, writable: true } | ||
}) | ||
this.on.error = this.options.package.messagingFunctions.onError.bind(this) | ||
this.on.topic = this.options.package.messagingFunctions.onTopic.bind(this) | ||
this.emit.to = Object.defineProperties(this, { | ||
topic: { value: this.options.package.messagingFunctions.emitToTopic, writable: true }, | ||
queue: { value: this.options.package.messagingFunctions.emitToQueue, writable: true } | ||
}) | ||
} | ||
} | ||
} | ||
this._init() | ||
this._pools = new Map() | ||
// Create cache for accessing entities | ||
Object.defineProperties(this, { _entities: { value: {} } }) | ||
_init (datasource) { | ||
if (this.options.model && typeof this.options.model === 'object' && this.options.model.childrenOf) { | ||
// if csn is already there, just use it | ||
this._useReflectedModel(this.options.model) | ||
this._addBoundMessagingFunctions(datasource) | ||
} else { | ||
this._loadModelAndHandleMethods(datasource) | ||
} | ||
} | ||
_init () { | ||
_loadModelAndHandleMethods (datasource) { | ||
// 1. Save current functions | ||
@@ -129,2 +140,7 @@ const _acquire = this.acquire | ||
const _removeAllListeners = this.removeAllListeners | ||
const _putQueue = this.putQueue | ||
const _deleteQueue = this.deleteQueue | ||
const _addSubscription = this.addSubscription | ||
// 2. Load the model asynchronously | ||
@@ -158,5 +174,19 @@ const done = this._loadServiceModel(this.options.model) | ||
if (_removeAllListeners) { | ||
this._addBoundMessagingFunctions(datasource) | ||
this.removeAllListeners = _removeAllListeners | ||
this.putQueue = _putQueue | ||
this.deleteQueue = _deleteQueue | ||
this.addSubscription = _addSubscription | ||
} | ||
return this | ||
}) | ||
this._deferMainMethods(done, _create, _get, _removeAllListeners) | ||
return done | ||
} | ||
_deferMainMethods (done, _create, _get, _removeAllListeners) { | ||
// 3. Add placeholder methods to defer operations until the model is loaded | ||
@@ -185,4 +215,49 @@ this.then = (resolve, reject) => done.then(resolve).catch(reject) | ||
} | ||
if (_removeAllListeners) { | ||
this.emit = (...args) => { | ||
done.then(() => this.emit(...args)) | ||
return this | ||
} | ||
this.emit.to = { | ||
topic: (...args) => { | ||
done.then(() => this.emit.to.topic(...args)) | ||
return this | ||
} | ||
} | ||
this.on = (...args) => { | ||
done.then(() => this.on(...args)) | ||
return this | ||
} | ||
this.on.topic = (...args) => { | ||
done.then(() => this.on.topic(...args)) | ||
return this | ||
} | ||
this.removeAllListeners = (...args) => done.then(() => this.removeAllListeners(...args)) | ||
this.putQueue = (...args) => done.then(() => this.putQueue(...args)) | ||
this.deleteQueue = (...args) => done.then(() => this.deleteQueue(...args)) | ||
this.addSubscription = (...args) => done.then(() => this.addSubscription(...args)) | ||
} | ||
} | ||
_addBoundMessagingFunctions (datasource) { | ||
let model | ||
// This is needed since model is implemented as a getter. | ||
// Once we get already loaded models in the future, it's not needed anymore. | ||
try { | ||
model = this.model | ||
} catch (err) {} | ||
for (const [, _package] of this.options.packages) { | ||
if (_package.messagingFunctions) { | ||
const boundMessagingFunctions = _package.messagingFunctions.bind.call(this, datasource, model) | ||
Object.defineProperties(this, { | ||
messagingOptions: { value: boundMessagingFunctions.messagingOptions, writable: true }, | ||
emit: { value: boundMessagingFunctions.emit, writable: true }, | ||
on: { value: boundMessagingFunctions.on, writable: true } | ||
}) | ||
return | ||
} | ||
} | ||
} | ||
_loadServiceModel (model) { | ||
@@ -192,22 +267,3 @@ if (model) { | ||
const model = cds.linked(cds.unfold.for.odata(csn)) | ||
this.model = model | ||
const services = this.model.all('service') | ||
if (services.length === 1) { | ||
this.entities = this.model.childrenOf(services[0], child => child.kind === 'entity') | ||
} else if (this.options.service) { | ||
this.entities = this.model.childrenOf( | ||
services.find(srv => srv.name === this.options.service), | ||
child => child.kind === 'entity' | ||
) | ||
} else { | ||
this.entities = this.model.entities | ||
} | ||
if (this.options.credentials) { | ||
this.options.credentials.reflectedModel = model | ||
} else { | ||
this.options.reflectedModel = model | ||
} | ||
this._useReflectedModel(model) | ||
}) | ||
@@ -219,12 +275,26 @@ } | ||
_useReflectedModel (model) { | ||
this.model = model | ||
const services = this.model.all('service') | ||
if (services.length === 1) { | ||
this.entities = this.model.childrenOf(services[0], child => child.kind === 'entity') | ||
} else if (this.options.service) { | ||
this.entities = this.model.childrenOf( | ||
services.find(srv => srv.name === this.options.service), | ||
child => child.kind === 'entity' | ||
) | ||
} else { | ||
this.entities = this.model.entities | ||
} | ||
for (const [, connectionOption] of this.options.connectionOptions) { | ||
if (!connectionOption.credentials) { | ||
connectionOption.credentials = {} | ||
} | ||
connectionOption.credentials.reflectedModel = model | ||
} | ||
} | ||
// TODO: Get model for mainKind | ||
get model () { | ||
// throw an error unless model is loaded | ||
const target = | ||
(this.options.credentials ? this.options.credentials.database : '') || this.options.url || this.options.host || '' | ||
throw new Error( | ||
`${this.options.model ? 'Model not yet loaded' : 'No model configured'} for service ${ | ||
this.options.kind | ||
}: ${target}` | ||
) | ||
throw new Error(`${this.options.model ? 'Model not yet loaded' : 'No model configured'}`) | ||
} | ||
@@ -248,13 +318,23 @@ | ||
* @param {Object} [context] - Context object | ||
* @param {string} [context.user.id] - The user name for the db connection. | ||
* @param {string} [context.user.locale] - The language identifier. | ||
* @param {string} [context.attr.token] - Full JWT from (HTTP) request. | ||
* @param {string} [context.attr.identityZone] - GUID of a tenant. | ||
* @returns {Promise.<Object>} A promise for the acquired db client. | ||
* @param {string} [context.user.id] - The user name for the db connection | ||
* @param {string} [context.user.locale] - The language identifier | ||
* @param {string} [context.attr.token] - Full JWT from (HTTP) request | ||
* @param {string} [context.attr.identityZone] - GUID of a tenant | ||
* @param {string} [mainKind] - Kind to be acquired (for compound services) | ||
* @returns {Promise.<Object>} A promise for the acquired db client | ||
*/ | ||
async acquire (context = {}) { | ||
const poolKey = this._getPoolIdentifier(context.attr) | ||
async acquire (context = {}, mainKind = null) { | ||
if (!mainKind) { | ||
if (this.options.packages.size === 1) { | ||
mainKind = this.options.packages.keys().next().value | ||
} else { | ||
throw new Error('You need to specify a kind to acquire a connection.') | ||
} | ||
} | ||
const tenantId = this._getTenantId(context.attr) | ||
const poolKey = `${tenantId}-${mainKind}` | ||
// Fallback in case of first call, or pool has been invalidated | ||
let pool = this._pools.get(poolKey) || this._createPool(poolKey) | ||
let pool = this._pools.get(poolKey) || this._createPool(poolKey, tenantId, mainKind) | ||
@@ -269,9 +349,3 @@ // Test connect or instance manager call is not finished yet | ||
/** | ||
* Get the identifier for the pool. | ||
* @param {string} tenantId | ||
* @returns {string} | ||
* @private | ||
*/ | ||
_getPoolIdentifier ({ identityZone } = {}) { | ||
_getTenantId ({ identityZone } = {}) { | ||
if (this.options.multiTenant) { | ||
@@ -290,3 +364,8 @@ if (identityZone) { | ||
// Hack if deploy is called, then no model must be loaded. | ||
return this.options.model && this.hasOwnProperty('model') ? this.model : context[MODEL] | ||
if (this.options.model && this.hasOwnProperty('model')) { | ||
// Another hack, check if model is already compiled (otherwise e.g. DraftAdminData is missing) | ||
this.model = this.model['@sql_mapping'] ? this.model : cds.linked(cds.compile.for.odata(this.model)) | ||
return this.model | ||
} | ||
return context[MODEL] | ||
} | ||
@@ -320,7 +399,8 @@ | ||
* @param {Object} [context] | ||
* @param {string} [mainKind] - Kind to be acquired (for compound services). | ||
* @returns {Promise} Promise, that resolves with stream if successful or rejects with error if not. | ||
* Result object can be undefined if no rows obtained. | ||
*/ | ||
stream (query, values, context = {}) { | ||
return this.acquire(context).then(client => { | ||
stream (query, values, context = {}, mainKind = null) { | ||
return this.acquire(context, mainKind).then(client => { | ||
return client.stream(query, values).then(result => { | ||
@@ -334,2 +414,3 @@ return this.release(client).then(() => { | ||
// TODO: Instead of a getter, use a function getTenantManager(mainKind) to support multiple tenantManagers | ||
/** | ||
@@ -340,3 +421,7 @@ * Getter for TenantManager | ||
get tenantManager () { | ||
return this._tenantManager | ||
if (this._tenantManagers.size === 1) { | ||
for (const [, tenantManager] of this._tenantManagers) { | ||
return tenantManager | ||
} | ||
} | ||
} | ||
@@ -379,6 +464,6 @@ | ||
*/ | ||
_createPool (poolKey) { | ||
const ready = this._getAndCheckCredentials(poolKey) | ||
_createPool (poolKey, tenantId, mainKind) { | ||
const ready = this._getAndCheckCredentials(tenantId, mainKind) | ||
.then(options => { | ||
return this._createPoolInstance(options, poolKey) | ||
return this._createPoolInstance(poolKey, this.options.packages.get(mainKind).Client, options) | ||
}) | ||
@@ -404,4 +489,4 @@ .catch(err => { | ||
_createPoolInstance (options, poolKey) { | ||
const pool = new Pool(poolKey, options) | ||
_createPoolInstance (poolKey, Client, connectionOptions) { | ||
const pool = new Pool(poolKey, Client, connectionOptions) | ||
pool._name = poolKey | ||
@@ -420,20 +505,27 @@ this._pools.set(poolKey, pool) | ||
*/ | ||
_getAndCheckCredentials (tenantId) { | ||
_getAndCheckCredentials (tenantId, mainKind) { | ||
if (this.options.multiTenant) { | ||
return this._tenantManager.get(tenantId).then(credentials => { | ||
// Get defaults from general options; mostly pool options are relevant | ||
const options = Object.assign({}, this.options, { kind: this.options.kind }) | ||
options.pool = Object.assign({}, options.pool) | ||
options.credentials = Object.assign({}, options.credentials, credentials) | ||
return this._tenantManagers | ||
.get(mainKind) | ||
.get(tenantId) | ||
.then(credentials => { | ||
// Get defaults from general options; mostly pool options are relevant | ||
const options = Object.assign({}, this.options.connectionOptions.get(mainKind)) | ||
options.credentials = Object.assign({}, options.credentials || {}, credentials) | ||
options.pool = Object.assign({}, options.pool) | ||
// A client might not provide the "options" function | ||
if (this._enrichOptions) { | ||
this._enrichOptions(options) | ||
} | ||
const _package = this.options.packages.get(mainKind) | ||
// A client might not provide the "options" function | ||
if (_package && _package.options) { | ||
_package.options(options) | ||
} | ||
return this._testConnection(options) | ||
}) | ||
return this._testConnection(options, _package.Client) | ||
}) | ||
} | ||
return this._testConnection(this.options) | ||
return this._testConnection( | ||
this.options.connectionOptions.get(mainKind), | ||
this.options.packages.get(mainKind).Client | ||
) | ||
} | ||
@@ -443,18 +535,15 @@ | ||
* Test the credentials by doing a test connect. | ||
* @param {Object} options | ||
* @param {Object} connectionOptions | ||
* @returns {Promise<Object>} | ||
* @private | ||
*/ | ||
_testConnection (options) { | ||
const Client = options.package.Client | ||
const client = new Client(options.credentials || options) | ||
async _testConnection (connectionOptions, Client) { | ||
if (!Client) { | ||
return resolve(connectionOptions) | ||
} | ||
return client | ||
.connect() | ||
.then(() => { | ||
return client.end() | ||
}) | ||
.then(() => { | ||
return options | ||
}) | ||
const client = new Client(connectionOptions) | ||
await client.connect() | ||
await client.end() | ||
return connectionOptions | ||
} | ||
@@ -550,8 +639,10 @@ | ||
* @param {Object} [context] Event context. | ||
* @param {string} [mainKind] - Kind to be acquired (for compound services). | ||
* @returns {Object} thenable, that resolves with result object (array) if successful or rejects with error if not. | ||
* | ||
*/ | ||
run (query, values, context = {}) { | ||
run (query, values, context = {}, mainKind = null) { | ||
const runs = [] | ||
const promise = this.acquire(context).then(client => { | ||
const promise = this.acquire(context, mainKind).then(client => { | ||
if (this.options.model && !client._csn) client.setCSN(this.model) | ||
let chain = client.run(query, values) | ||
@@ -605,6 +696,8 @@ | ||
* @param {function} cb - synchronous function to process each row of the result set. | ||
* @param {Object} [context] Event context. | ||
* @param {string} [mainKind] - Kind to be acquired (for compound services). | ||
* @returns {Promise} Promise, that resolves with undefined if successful or rejects with error if not. | ||
*/ | ||
foreach (query, values, cb) { | ||
return this.acquire().then(client => { | ||
foreach (query, values, cb, context = {}, mainKind = null) { | ||
return this.acquire(context, mainKind).then(client => { | ||
return client | ||
@@ -624,6 +717,8 @@ .foreach(query, values, cb) | ||
* @param {object} csn the unreflected CSN. | ||
* @param {Object} [context] Event context. | ||
* @param {string} [mainKind] - Kind to be acquired (for compound services). | ||
* @returns {Promise} Promise, that resolves with undefined if successful or rejects with error if not. | ||
*/ | ||
deploy (csn) { | ||
return this.acquire().then(client => { | ||
deploy (csn, context = {}, mainKind = null) { | ||
return this.acquire(context, mainKind).then(client => { | ||
return this._load(csn).then(csn => { | ||
@@ -630,0 +725,0 @@ return client |
@@ -18,2 +18,24 @@ // Support for legacy push down of model | ||
this._context = context | ||
for (const [, _package] of this._service.options.packages) { | ||
if (_package.serviceFunctions) { | ||
Object.defineProperties(this, { | ||
create: { value: _package.serviceFunctions.create, writable: true }, | ||
read: { value: _package.serviceFunctions.read, writable: true }, | ||
update: { value: _package.serviceFunctions.update, writable: true }, | ||
delete: { value: _package.serviceFunctions.delete, writable: true }, | ||
insert: { value: _package.serviceFunctions.insert, writable: true } | ||
}) | ||
} | ||
if (_package.httpFunctions) { | ||
Object.defineProperties(this, { | ||
get: { value: _package.httpFunctions.get, writable: true }, | ||
post: { value: _package.httpFunctions.post, writable: true }, | ||
put: { value: _package.httpFunctions.put, writable: true }, | ||
patch: { value: _package.httpFunctions.patch, writable: true }, | ||
delete: { value: _package.httpFunctions.delete, writable: true } | ||
}) | ||
} | ||
} | ||
} | ||
@@ -45,3 +67,3 @@ | ||
this.client = this._service | ||
.acquire(this._context) | ||
.acquire(this._context, args[3]) | ||
.then(client => { | ||
@@ -60,2 +82,5 @@ this.client = client | ||
.then(() => { | ||
if (args.length === 1 && Array.isArray(args[0])) { | ||
return this._executedChainOfStatements(clientFn, args) | ||
} | ||
return this.client[clientFn](...args) | ||
@@ -67,2 +92,10 @@ }) | ||
_executedChainOfStatements (clientFn, args) { | ||
let chain = Promise.resolve() | ||
for (const stmt of args[0]) { | ||
chain = chain.then(() => this.client[clientFn](stmt)) | ||
} | ||
return chain | ||
} | ||
_removeNoLongerNeededConnection () { | ||
@@ -69,0 +102,0 @@ const dbc = this.client |
@@ -7,7 +7,2 @@ const dependencies = { | ||
}, | ||
get generateUUID () { | ||
const uuid = require('./id-generation/uuid') | ||
Object.defineProperty(dependencies, 'generateUUID', { value: uuid }) | ||
return uuid | ||
}, | ||
get statements () { | ||
@@ -14,0 +9,0 @@ const statements = require('./statements/index') |
@@ -67,4 +67,8 @@ const { addToQueue } = require('./queue') | ||
} | ||
get _isQuery () { | ||
return true | ||
} | ||
} | ||
module.exports = BaseStatement |
@@ -172,2 +172,11 @@ const BaseStatement = require('./BaseStatement') | ||
module.exports = Insert | ||
const insert = (...entries) => { | ||
return { | ||
into: entity => { | ||
return Insert.into(entity).entries(...entries) | ||
} | ||
} | ||
} | ||
insert.into = Insert.into | ||
module.exports = insert |
@@ -42,2 +42,3 @@ const cds = require('../cds') | ||
fn.one = Select.one | ||
fn.distinct = Select.distinct | ||
@@ -47,2 +48,16 @@ return fn | ||
static distinct (entity, model) { | ||
if (!entity) { | ||
throw new IllegalFunctionArgumentError('entity') | ||
} | ||
const cqn = new Select() | ||
if (model) { | ||
Object.defineProperty(cqn, MODEL, { value: model }) | ||
} | ||
cqn._parseEntity(entity) | ||
return cqn.distinct() | ||
} | ||
/** | ||
@@ -61,7 +76,5 @@ * Select entries of an entity. | ||
const cqn = new Select() | ||
if (model) { | ||
Object.defineProperty(cqn, MODEL, { value: model }) | ||
} | ||
if (model) Object.defineProperty(cqn, MODEL, { value: model }) | ||
cqn._parseEntity(entity) | ||
cqn._parseColumns(columns, entity) | ||
cqn._parseEntity(entity) | ||
@@ -108,2 +121,8 @@ return cqn | ||
this._parseString(entity) | ||
} else if ( | ||
typeof entity === 'object' && | ||
entity.hasOwnProperty('params') && | ||
Object.keys(entity.params).length !== 0 | ||
) { | ||
this._fromViewWithParams(entity) | ||
} else if (typeof entity === 'object' && entity.hasOwnProperty('name')) { | ||
@@ -442,2 +461,10 @@ this._from(entity.name) | ||
_fromViewWithParams (entity, asName) { | ||
this.SELECT.from = { ref: [{ id: entity.name }] } | ||
if (asName) { | ||
this.SELECT.from.as = asName | ||
} | ||
} | ||
_addColumnToOrderByAsObject (columnObject) { | ||
@@ -625,3 +652,3 @@ this._extractKeysAndValuesFromObject(columnObject).forEach(item => { | ||
valueOf () { | ||
return `SELECT * FROM ${Select._quoteElement(this.SELECT.from.ref[0])} ` | ||
return `SELECT * FROM ${Select._quoteElement(this.SELECT.from.ref.join('.'))} ` | ||
} | ||
@@ -628,0 +655,0 @@ } |
@@ -37,2 +37,14 @@ const Where = require('./Where') | ||
/** | ||
* Sets the values that should be updated | ||
* | ||
* @example | ||
* UPDATE('Authors').set({NAME: 'Jon Doe', STOCK: 123}) | ||
* | ||
* @param {object} valueObject contains the properties that should be updated | ||
*/ | ||
with (valueObject) { | ||
return this.set(valueObject) | ||
} | ||
_convertToCqnObject (valueObject) { | ||
@@ -51,2 +63,6 @@ const cqnObj = {} | ||
if (value instanceof Buffer) { | ||
return { val: value } | ||
} | ||
if (['-=', '+=', '*=', '/=', '%='].includes(Object.keys(value)[0])) { | ||
@@ -83,2 +99,3 @@ const op = Object.keys(value)[0] | ||
typeof valueObject[key] === 'object' && | ||
!(valueObject[key] instanceof Buffer) && | ||
!Array.isArray(valueObject[key]) && | ||
@@ -85,0 +102,0 @@ Object.keys(valueObject[key]).length === 0) |
@@ -20,3 +20,3 @@ const _asyncReturn = returned => { | ||
try { | ||
return _asyncReturn(reject(err)) | ||
return reject ? _asyncReturn(reject(err)) : this | ||
} catch (e) { | ||
@@ -23,0 +23,0 @@ return rejectedThenable(e) |
{ | ||
"name": "@sap/cds-ql", | ||
"version": "1.14.0", | ||
"version": "1.17.0", | ||
"lockfileVersion": 1, | ||
@@ -8,14 +8,17 @@ "requires": true, | ||
"@sap/cds-hana": { | ||
"version": "1.13.0", | ||
"version": "1.16.0", | ||
"requires": { | ||
"@sap/cds-sql": "1.13.0" | ||
"@sap/cds-sql": "1.16.0" | ||
} | ||
}, | ||
"@sap/cds-sql": { | ||
"version": "1.13.0" | ||
"version": "1.16.0", | ||
"requires": { | ||
"uuid": "3.3.2" | ||
} | ||
}, | ||
"@sap/cds-sqlite": { | ||
"version": "1.13.0", | ||
"version": "1.16.0", | ||
"requires": { | ||
"@sap/cds-sql": "1.13.0" | ||
"@sap/cds-sql": "1.16.0" | ||
} | ||
@@ -22,0 +25,0 @@ }, |
@@ -1,1 +0,1 @@ | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-hana":"1.13.0","@sap/cds-sql":"1.13.0","@sap/cds-sqlite":"1.13.0","generic-pool":"3.7.1","uuid":"3.3.2"},"deprecated":false,"description":"This package deals with creating a pool of connection clients, connecting to a driver (read: db) and using these connection clients from the pool to insert, delete, select and update values or rows from a specific table. Performing these insert, delete, select and update operations also includes executing embedded queries and plain statements.","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-ql","version":"1.14.0","license":"SEE LICENSE IN developer-license-3.1.txt"} | ||
{"bundleDependencies":false,"dependencies":{"@sap/cds-hana":"1.16.0","@sap/cds-sql":"1.16.0","@sap/cds-sqlite":"1.16.0","generic-pool":"3.7.1"},"deprecated":false,"description":"This package deals with creating a pool of connection clients, connecting to a driver (read: db) and using these connection clients from the pool to insert, delete, select and update values or rows from a specific table. Performing these insert, delete, select and update operations also includes executing embedded queries and plain statements.","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-ql","version":"1.17.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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
156138
4
3335
35
+ Added@sap/cds-hana@1.16.0(transitive)
+ Added@sap/cds-sql@1.16.0(transitive)
+ Added@sap/cds-sqlite@1.16.0(transitive)
- Removeduuid@3.3.2
- Removed@sap/cds-hana@1.13.0(transitive)
- Removed@sap/cds-sql@1.13.0(transitive)
- Removed@sap/cds-sqlite@1.13.0(transitive)
Updated@sap/cds-hana@1.16.0
Updated@sap/cds-sql@1.16.0
Updated@sap/cds-sqlite@1.16.0