Node JS version
This commit is contained in:
412
node_modules/cassandra-driver/lib/mapping/mapping-handler.js
generated
vendored
Normal file
412
node_modules/cassandra-driver/lib/mapping/mapping-handler.js
generated
vendored
Normal file
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright DataStax, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const utils = require('../utils');
|
||||
const QueryGenerator = require('./query-generator');
|
||||
const ResultMapper = require('./result-mapper');
|
||||
const Result = require('./result');
|
||||
const Cache = require('./cache');
|
||||
const Tree = require('./tree');
|
||||
const ObjectSelector = require('./object-selector');
|
||||
const DocInfoAdapter = require('./doc-info-adapter');
|
||||
|
||||
const cacheHighWaterMark = 100;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class MappingHandler {
|
||||
/**
|
||||
* @param {Client} client
|
||||
* @param {ModelMappingInfo} mappingInfo
|
||||
*/
|
||||
constructor(client, mappingInfo) {
|
||||
this._client = client;
|
||||
this._cache = {
|
||||
select: new Tree().on('add', length => this._validateCacheLength(length)),
|
||||
selectAll: new Tree().on('add', length => this._validateCacheLength(length)),
|
||||
insert: new Tree().on('add', length => this._validateCacheLength(length)),
|
||||
update: new Tree().on('add', length => this._validateCacheLength(length)),
|
||||
remove: new Tree().on('add', length => this._validateCacheLength(length)),
|
||||
customQueries: new Map()
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the mapping information of the document.
|
||||
* @type {ModelMappingInfo}
|
||||
*/
|
||||
this.info = mappingInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function to be used to execute SELECT the query using the document.
|
||||
* @param {Object} doc
|
||||
* @param {{fields, orderBy, limit}} docInfo
|
||||
* @param {Boolean} allPKsDefined Determines whether all primary keys must be defined in the doc for the query to
|
||||
* be valid.
|
||||
* @return {Promise<Function>}
|
||||
*/
|
||||
getSelectExecutor(doc, docInfo, allPKsDefined) {
|
||||
const docKeys = Object.keys(doc);
|
||||
if (docKeys.length === 0) {
|
||||
return Promise.reject(new Error('Expected object with keys'));
|
||||
}
|
||||
|
||||
const cacheKey = Cache.getSelectKey(docKeys, doc, docInfo);
|
||||
// Cache the executor and the result mapper under the same key
|
||||
// That way, those can get evicted together
|
||||
const cacheItem = this._cache.select.getOrCreate(cacheKey, () => ({ executor: null, resultAdapter: null }));
|
||||
|
||||
if (cacheItem.executor !== null) {
|
||||
return Promise.resolve(cacheItem.executor);
|
||||
}
|
||||
|
||||
const propertiesInfo = DocInfoAdapter.getPropertiesInfo(docKeys, null, doc, this.info);
|
||||
const fieldsInfo = DocInfoAdapter.getPropertiesInfo(utils.emptyArray, docInfo, doc, this.info);
|
||||
const orderByColumns = DocInfoAdapter.adaptOrderBy(docInfo, this.info);
|
||||
const limit = docInfo && docInfo.limit;
|
||||
|
||||
return this._client.connect()
|
||||
.then(() =>
|
||||
ObjectSelector.getForSelect(this._client, this.info, allPKsDefined, propertiesInfo, fieldsInfo, orderByColumns))
|
||||
.then(tableName => {
|
||||
// Part of the closure
|
||||
const query = QueryGenerator.getSelect(tableName, this.info.keyspace, propertiesInfo, fieldsInfo,
|
||||
orderByColumns, limit);
|
||||
const paramsGetter = QueryGenerator.selectParamsGetter(propertiesInfo, limit);
|
||||
const self = this;
|
||||
|
||||
cacheItem.executor = function selectExecutor(doc, docInfo, executionOptions) {
|
||||
return self._executeSelect(query, paramsGetter, doc, docInfo, executionOptions, cacheItem);
|
||||
};
|
||||
|
||||
return cacheItem.executor;
|
||||
});
|
||||
}
|
||||
|
||||
getSelectAllExecutor(docInfo) {
|
||||
const cacheKey = Cache.getSelectAllKey(docInfo);
|
||||
const cacheItem = this._cache.selectAll.getOrCreate(cacheKey, () => ({ executor: null, resultAdapter: null }));
|
||||
|
||||
if (cacheItem.executor !== null) {
|
||||
return cacheItem.executor;
|
||||
}
|
||||
|
||||
const fieldsInfo = DocInfoAdapter.getPropertiesInfo(utils.emptyArray, docInfo, utils.emptyObject, this.info);
|
||||
const orderByColumns = DocInfoAdapter.adaptOrderBy(docInfo, this.info);
|
||||
const limit = docInfo && docInfo.limit;
|
||||
|
||||
const tableName = ObjectSelector.getForSelectAll(this.info);
|
||||
|
||||
// Part of the closure
|
||||
const query = QueryGenerator.getSelect(
|
||||
tableName, this.info.keyspace, utils.emptyArray, fieldsInfo, orderByColumns, limit);
|
||||
const paramsGetter = QueryGenerator.selectParamsGetter(utils.emptyArray, limit);
|
||||
const self = this;
|
||||
|
||||
cacheItem.executor = function selectAllExecutor(docInfo, executionOptions) {
|
||||
return self._executeSelect(query, paramsGetter, null, docInfo, executionOptions, cacheItem);
|
||||
};
|
||||
|
||||
return cacheItem.executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SELECT query and returns the adapted results.
|
||||
* When a result adapter is not yet created, it gets a new one and caches it.
|
||||
* @private
|
||||
*/
|
||||
_executeSelect(query, paramsGetter, doc, docInfo, executionOptions, cacheItem) {
|
||||
const options = DocInfoAdapter.adaptAllOptions(executionOptions, true);
|
||||
|
||||
return this._client.execute(query, paramsGetter(doc, docInfo, this.info), options)
|
||||
.then(rs => {
|
||||
if (cacheItem.resultAdapter === null) {
|
||||
cacheItem.resultAdapter = ResultMapper.getSelectAdapter(this.info, rs);
|
||||
}
|
||||
return new Result(rs, this.info, cacheItem.resultAdapter);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function to be used to execute INSERT the query using the document.
|
||||
* @param {Object} doc
|
||||
* @param {{ifNotExists, ttl, fields}} docInfo
|
||||
* @return {Promise<Function>}
|
||||
*/
|
||||
getInsertExecutor(doc, docInfo) {
|
||||
const docKeys = Object.keys(doc);
|
||||
if (docKeys.length === 0) {
|
||||
return Promise.reject(new Error('Expected object with keys'));
|
||||
}
|
||||
|
||||
const cacheKey = Cache.getInsertKey(docKeys, docInfo);
|
||||
const cacheItem = this._cache.insert.getOrCreate(cacheKey, () => ({ executor: null }));
|
||||
|
||||
if (cacheItem.executor !== null) {
|
||||
return Promise.resolve(cacheItem.executor);
|
||||
}
|
||||
|
||||
return this.createInsertQueries(docKeys, doc, docInfo)
|
||||
.then(queries => {
|
||||
if (queries.length === 1) {
|
||||
return this._setSingleExecutor(cacheItem, queries[0]);
|
||||
}
|
||||
|
||||
return this._setBatchExecutor(cacheItem, queries);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Array containing the query and the params getter function for each table affected by the INSERT.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {{ifNotExists, ttl, fields}} docInfo
|
||||
* @returns {Promise<Array<{query, paramsGetter}>>}
|
||||
*/
|
||||
createInsertQueries(docKeys, doc, docInfo) {
|
||||
const propertiesInfo = DocInfoAdapter.getPropertiesInfo(docKeys, docInfo, doc, this.info);
|
||||
const ifNotExists = docInfo && docInfo.ifNotExists;
|
||||
|
||||
// Get all the tables affected
|
||||
return this._client.connect()
|
||||
.then(() => ObjectSelector.getForInsert(this._client, this.info, propertiesInfo))
|
||||
.then(tables => {
|
||||
|
||||
if (tables.length > 1 && ifNotExists) {
|
||||
throw new Error('Batch with ifNotExists conditions cannot span multiple tables');
|
||||
}
|
||||
|
||||
// For each tables affected, Generate query and parameter getters
|
||||
return tables.map(table =>
|
||||
QueryGenerator.getInsert(table, this.info.keyspace, propertiesInfo, docInfo,ifNotExists));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function to be used to execute the UPDATE queries with the provided document.
|
||||
* @param {Object} doc
|
||||
* @param {{ifExists, when, ttl, fields}} docInfo
|
||||
* @return {Promise<Function>}
|
||||
*/
|
||||
getUpdateExecutor(doc, docInfo) {
|
||||
const docKeys = Object.keys(doc);
|
||||
if (docKeys.length === 0) {
|
||||
return Promise.reject(new Error('Expected object with keys'));
|
||||
}
|
||||
|
||||
const cacheKey = Cache.getUpdateKey(docKeys, doc, docInfo);
|
||||
const cacheItem = this._cache.update.getOrCreate(cacheKey, () => ({ executor: null }));
|
||||
|
||||
if (cacheItem.executor !== null) {
|
||||
return Promise.resolve(cacheItem.executor);
|
||||
}
|
||||
|
||||
return this.createUpdateQueries(docKeys, doc, docInfo)
|
||||
.then(queries => {
|
||||
if (queries.length === 1) {
|
||||
return this._setSingleExecutor(cacheItem, queries[0]);
|
||||
}
|
||||
|
||||
return this._setBatchExecutor(cacheItem, queries);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Array containing the query and the params getter function for each table affected by the UPDATE.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {Object} docInfo
|
||||
* @returns {Promise<Array<{query, paramsGetter, isIdempotent}>>}
|
||||
*/
|
||||
createUpdateQueries(docKeys, doc, docInfo) {
|
||||
const propertiesInfo = DocInfoAdapter.getPropertiesInfo(docKeys, docInfo, doc, this.info);
|
||||
const ifExists = docInfo && docInfo.ifExists;
|
||||
const when = docInfo && docInfo.when
|
||||
? DocInfoAdapter.getPropertiesInfo(Object.keys(docInfo.when), null, docInfo.when, this.info)
|
||||
: utils.emptyArray;
|
||||
|
||||
if (when.length > 0 && ifExists) {
|
||||
throw new Error('Both when and ifExists conditions can not be applied to the same statement');
|
||||
}
|
||||
|
||||
// Get all the tables affected
|
||||
return this._client.connect()
|
||||
.then(() => ObjectSelector.getForUpdate(this._client, this.info, propertiesInfo, when))
|
||||
.then(tables => {
|
||||
|
||||
if (tables.length > 1 && (when.length > 0 || ifExists)) {
|
||||
throw new Error('Batch with when or ifExists conditions cannot span multiple tables');
|
||||
}
|
||||
|
||||
// For each table affected, Generate query and parameter getters
|
||||
return tables.map(table =>
|
||||
QueryGenerator.getUpdate(table, this.info.keyspace, propertiesInfo, docInfo, when, ifExists));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function to be used to execute the DELETE queries with the provided document.
|
||||
* @param {Object} doc
|
||||
* @param {{when, ifExists, fields, deleteOnlyColumns}} docInfo
|
||||
* @return {Promise<Function>}
|
||||
*/
|
||||
getDeleteExecutor(doc, docInfo) {
|
||||
const docKeys = Object.keys(doc);
|
||||
if (docKeys.length === 0) {
|
||||
return Promise.reject(new Error('Expected object with keys'));
|
||||
}
|
||||
|
||||
const cacheKey = Cache.getRemoveKey(docKeys, doc, docInfo);
|
||||
const cacheItem = this._cache.remove.getOrCreate(cacheKey, () => ({ executor: null }));
|
||||
|
||||
if (cacheItem.executor !== null) {
|
||||
return Promise.resolve(cacheItem.executor);
|
||||
}
|
||||
|
||||
return this.createDeleteQueries(docKeys, doc, docInfo)
|
||||
.then(queries => {
|
||||
if (queries.length === 1) {
|
||||
return this._setSingleExecutor(cacheItem, queries[0]);
|
||||
}
|
||||
|
||||
return this._setBatchExecutor(cacheItem, queries);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an Array containing the query and the params getter function for each table affected by the DELETE.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {{when, ifExists, fields, deleteOnlyColumns}} docInfo
|
||||
* @returns {Promise<Array<{query, paramsGetter}>>}
|
||||
*/
|
||||
createDeleteQueries(docKeys, doc, docInfo) {
|
||||
const propertiesInfo = DocInfoAdapter.getPropertiesInfo(docKeys, docInfo, doc, this.info);
|
||||
const ifExists = docInfo && docInfo.ifExists;
|
||||
const when = docInfo && docInfo.when
|
||||
? DocInfoAdapter.getPropertiesInfo(Object.keys(docInfo.when), null, docInfo.when, this.info)
|
||||
: utils.emptyArray;
|
||||
|
||||
if (when.length > 0 && ifExists) {
|
||||
throw new Error('Both when and ifExists conditions can not be applied to the same statement');
|
||||
}
|
||||
|
||||
// Get all the tables affected
|
||||
return this._client.connect()
|
||||
.then(() => ObjectSelector.getForDelete(this._client, this.info, propertiesInfo, when))
|
||||
.then(tables => {
|
||||
|
||||
if (tables.length > 1 && (when.length > 0 || ifExists)) {
|
||||
throw new Error('Batch with when or ifExists conditions cannot span multiple tables');
|
||||
}
|
||||
|
||||
// For each tables affected, Generate query and parameter getters
|
||||
return tables.map(table =>
|
||||
QueryGenerator.getDelete(table, this.info.keyspace, propertiesInfo, docInfo, when, ifExists));
|
||||
});
|
||||
}
|
||||
|
||||
getExecutorFromQuery(query, paramsHandler, commonExecutionOptions) {
|
||||
// Use the current instance in the closure
|
||||
// as there is no guarantee of how the returned function will be invoked
|
||||
const self = this;
|
||||
const commonOptions = commonExecutionOptions ? DocInfoAdapter.adaptAllOptions(commonExecutionOptions) : null;
|
||||
|
||||
return (function queryMappedExecutor(doc, executionOptions) {
|
||||
// When the executionOptions were already specified,
|
||||
// use it and skip the ones provided in each invocation
|
||||
const options = commonOptions
|
||||
? commonOptions
|
||||
: DocInfoAdapter.adaptAllOptions(executionOptions);
|
||||
|
||||
return self._client.execute(query, paramsHandler(doc), options).then(rs => {
|
||||
// Cache the resultAdapter based on the query
|
||||
let resultAdapter = self._cache.customQueries.get(query);
|
||||
|
||||
if (resultAdapter === undefined) {
|
||||
const resultAdapterInfo = ResultMapper.getCustomQueryAdapter(self.info, rs);
|
||||
resultAdapter = resultAdapterInfo.fn;
|
||||
if (resultAdapterInfo.canCache) {
|
||||
// Avoid caching conditional updates results as the amount of columns change
|
||||
// depending on the parameter values.
|
||||
self._cache.customQueries.set(query, resultAdapter);
|
||||
|
||||
if (self._cache.customQueries.size === cacheHighWaterMark) {
|
||||
self._client.log('warning',
|
||||
`Custom queries cache reached ${cacheHighWaterMark} items, this could be caused by ` +
|
||||
`hard-coding parameter values inside the query, which should be avoided`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Result(rs, self.info, resultAdapter);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_setSingleExecutor(cacheItem, queryInfo) {
|
||||
// Parameters and this instance are part of the closure
|
||||
const self = this;
|
||||
|
||||
// Set the function to execute the request in the cache
|
||||
cacheItem.executor = function singleExecutor(doc, docInfo, executionOptions) {
|
||||
const options = DocInfoAdapter.adaptOptions(executionOptions, queryInfo.isIdempotent);
|
||||
|
||||
return self._client.execute(queryInfo.query, queryInfo.paramsGetter(doc, docInfo, self.info), options)
|
||||
.then(rs => new Result(rs, self.info, ResultMapper.getMutationAdapter(rs)));
|
||||
};
|
||||
|
||||
return cacheItem.executor;
|
||||
}
|
||||
|
||||
_setBatchExecutor(cacheItem, queries) {
|
||||
// Parameters and the following fields are part of the closure
|
||||
const self = this;
|
||||
const isIdempotent = queries.reduce((acc, q) => acc && q.isIdempotent, true);
|
||||
|
||||
// Set the function to execute the batch request in the cache
|
||||
cacheItem.executor = function batchExecutor(doc, docInfo, executionOptions) {
|
||||
// Use the params getter function to obtain the parameters each time
|
||||
const queryAndParams = queries.map(q => ({
|
||||
query: q.query,
|
||||
params: q.paramsGetter(doc, docInfo, self.info)
|
||||
}));
|
||||
|
||||
const options = DocInfoAdapter.adaptOptions(executionOptions, isIdempotent);
|
||||
|
||||
// Execute using a Batch
|
||||
return self._client.batch(queryAndParams, options)
|
||||
.then(rs => new Result(rs, self.info, ResultMapper.getMutationAdapter(rs)));
|
||||
};
|
||||
|
||||
return cacheItem.executor;
|
||||
}
|
||||
|
||||
_validateCacheLength(length) {
|
||||
if (length !== cacheHighWaterMark) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._client.log('warning', `ModelMapper cache reached ${cacheHighWaterMark} items, this could be caused by ` +
|
||||
`building the object to map in different ways (with different shapes) each time. Use the same or few object ` +
|
||||
`structures for a model and represent unset values with undefined or types.unset`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MappingHandler;
|
Reference in New Issue
Block a user