Node JS version
This commit is contained in:
207
node_modules/cassandra-driver/lib/mapping/cache.js
generated
vendored
Normal file
207
node_modules/cassandra-driver/lib/mapping/cache.js
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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 qModule = require('./q');
|
||||
const QueryOperator = qModule.QueryOperator;
|
||||
const QueryAssignment = qModule.QueryAssignment;
|
||||
|
||||
/**
|
||||
* Provides utility methods for obtaining a caching keys based on the specifics of the Mapper methods.
|
||||
* @ignore
|
||||
*/
|
||||
class Cache {
|
||||
/**
|
||||
* Gets an iterator of keys to uniquely identify a document shape for a select query.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {{fields, limit, orderBy}} docInfo
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
static *getSelectKey(docKeys, doc, docInfo) {
|
||||
yield* Cache._yieldKeyAndOperators(docKeys, doc);
|
||||
|
||||
yield* Cache._getSelectDocInfo(docInfo);
|
||||
}
|
||||
/**
|
||||
* Gets an iterator of keys to uniquely identify a shape for a select all query.
|
||||
* @param {{fields, limit, orderBy}} docInfo
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
static *getSelectAllKey(docInfo) {
|
||||
yield 'root';
|
||||
|
||||
yield* Cache._getSelectDocInfo(docInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parts of the key for a select query related to the docInfo.
|
||||
* @param {{fields, limit, orderBy}} docInfo
|
||||
* @private
|
||||
*/
|
||||
static *_getSelectDocInfo(docInfo) {
|
||||
if (docInfo) {
|
||||
if (docInfo.fields && docInfo.fields.length > 0) {
|
||||
// Use a separator from properties
|
||||
yield '|f|';
|
||||
yield* docInfo.fields;
|
||||
}
|
||||
|
||||
if (typeof docInfo.limit === 'number') {
|
||||
yield '|l|';
|
||||
}
|
||||
|
||||
if (docInfo.orderBy) {
|
||||
yield '|o|';
|
||||
|
||||
// orderBy is uses property names as keys and 'asc'/'desc' as values
|
||||
const keys = Object.keys(docInfo.orderBy);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
yield key;
|
||||
yield docInfo.orderBy[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an iterator of keys to uniquely identify a document shape for an insert query.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {{ifNotExists, ttl, fields}} docInfo
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
static *getInsertKey(docKeys, docInfo) {
|
||||
// No operator supported on INSERT values
|
||||
yield* docKeys;
|
||||
|
||||
if (docInfo) {
|
||||
if (docInfo.fields && docInfo.fields.length > 0) {
|
||||
// Use a separator from properties
|
||||
yield '|f|';
|
||||
yield* docInfo.fields;
|
||||
}
|
||||
|
||||
if (typeof docInfo.ttl === 'number') {
|
||||
yield '|t|';
|
||||
}
|
||||
|
||||
if (docInfo.ifNotExists) {
|
||||
yield '|e|';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an iterator of keys to uniquely identify a document shape for an UPDATE query.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {{ifExists, when, ttl, fields}} docInfo
|
||||
*/
|
||||
static *getUpdateKey(docKeys, doc, docInfo) {
|
||||
yield* Cache._yieldKeyAndAllQs(docKeys, doc);
|
||||
|
||||
if (docInfo) {
|
||||
if (docInfo.fields && docInfo.fields.length > 0) {
|
||||
// Use a separator from properties
|
||||
yield '|f|';
|
||||
yield* docInfo.fields;
|
||||
}
|
||||
|
||||
if (typeof docInfo.ttl === 'number') {
|
||||
yield '|t|';
|
||||
}
|
||||
|
||||
if (docInfo.ifExists) {
|
||||
yield '|e|';
|
||||
}
|
||||
|
||||
if (docInfo.when) {
|
||||
yield* Cache._yieldKeyAndOperators(Object.keys(docInfo.when), docInfo.when);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an iterator of keys to uniquely identify a document shape for a DELETE query.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {Object} doc
|
||||
* @param {{ifExists, when, fields, deleteOnlyColumns}} docInfo
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
static *getRemoveKey(docKeys, doc, docInfo) {
|
||||
yield* Cache._yieldKeyAndOperators(docKeys, doc);
|
||||
|
||||
if (docInfo) {
|
||||
if (docInfo.fields && docInfo.fields.length > 0) {
|
||||
// Use a separator from properties
|
||||
yield '|f|';
|
||||
yield* docInfo.fields;
|
||||
}
|
||||
|
||||
if (docInfo.ifExists) {
|
||||
yield '|e|';
|
||||
}
|
||||
|
||||
if (docInfo.deleteOnlyColumns) {
|
||||
yield '|dc|';
|
||||
}
|
||||
|
||||
if (docInfo.when) {
|
||||
yield* Cache._yieldKeyAndOperators(Object.keys(docInfo.when), docInfo.when);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static *_yieldKeyAndOperators(keys, obj) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
yield key;
|
||||
yield* Cache._yieldOperators(obj[key]);
|
||||
}
|
||||
}
|
||||
|
||||
static *_yieldOperators(value) {
|
||||
if (value !== null && value !== undefined && value instanceof QueryOperator) {
|
||||
yield value.key;
|
||||
if (value.hasChildValues) {
|
||||
yield* Cache._yieldOperators(value.value[0]);
|
||||
yield '|/|';
|
||||
yield* Cache._yieldOperators(value.value[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static *_yieldKeyAndAllQs(keys, obj) {
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
yield key;
|
||||
const value = obj[key];
|
||||
if (value !== null && value !== undefined) {
|
||||
if (value instanceof QueryOperator) {
|
||||
yield* Cache._yieldOperators(value);
|
||||
}
|
||||
else if (value instanceof QueryAssignment) {
|
||||
yield value.sign;
|
||||
yield value.inverted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Cache;
|
162
node_modules/cassandra-driver/lib/mapping/doc-info-adapter.js
generated
vendored
Normal file
162
node_modules/cassandra-driver/lib/mapping/doc-info-adapter.js
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 errors = require('../errors');
|
||||
const utils = require('../utils');
|
||||
|
||||
/**
|
||||
* Provides utility methods to adapt and map user provided docInfo and executionOptions to a predictable object format.
|
||||
* @ignore
|
||||
*/
|
||||
class DocInfoAdapter {
|
||||
/**
|
||||
* Returns an Array where each item contains the property name, the column name and the property value (to obtain
|
||||
* the operator).
|
||||
* When docInfo.fields is specified, it uses that array to obtain the information.
|
||||
* @param {Array<String>} docKeys
|
||||
* @param {null|{fields}} docInfo
|
||||
* @param {Object} doc
|
||||
* @param {ModelMappingInfo} mappingInfo
|
||||
* @returns {Array}
|
||||
*/
|
||||
static getPropertiesInfo(docKeys, docInfo, doc, mappingInfo) {
|
||||
let propertyKeys = docKeys;
|
||||
if (docInfo && docInfo.fields && docInfo.fields.length > 0) {
|
||||
propertyKeys = docInfo.fields;
|
||||
}
|
||||
|
||||
return propertyKeys.map(propertyName => ({
|
||||
propertyName,
|
||||
columnName: mappingInfo.getColumnName(propertyName),
|
||||
value: doc[propertyName],
|
||||
fromModel: mappingInfo.getFromModelFn(propertyName)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{orderBy}} docInfo
|
||||
* @param {ModelMappingInfo} mappingInfo
|
||||
* @returns {Array<String>}
|
||||
*/
|
||||
static adaptOrderBy(docInfo, mappingInfo){
|
||||
if (!docInfo || !docInfo.orderBy) {
|
||||
return utils.emptyArray;
|
||||
}
|
||||
return Object.keys(docInfo.orderBy).map(key => {
|
||||
const value = docInfo.orderBy[key];
|
||||
const ordering = typeof value === 'string' ? value.toUpperCase() : value;
|
||||
if (ordering !== 'ASC' && ordering !== 'DESC') {
|
||||
throw new errors.ArgumentError('Order must be either "ASC" or "DESC", obtained: ' + value);
|
||||
}
|
||||
return [ mappingInfo.getColumnName(key), ordering ];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the QueryOptions for an INSERT/UPDATE/DELETE statement.
|
||||
* @param {Object|String|undefined} executionOptions
|
||||
* @param {Boolean} isIdempotent
|
||||
*/
|
||||
static adaptOptions(executionOptions, isIdempotent) {
|
||||
const options = {
|
||||
prepare: true,
|
||||
executionProfile: undefined,
|
||||
timestamp: undefined,
|
||||
isIdempotent: isIdempotent
|
||||
};
|
||||
|
||||
if (typeof executionOptions === 'string') {
|
||||
options.executionProfile = executionOptions;
|
||||
}
|
||||
else if (executionOptions !== null && executionOptions !== undefined) {
|
||||
options.executionProfile = executionOptions.executionProfile;
|
||||
options.timestamp = executionOptions.timestamp;
|
||||
|
||||
if (executionOptions.isIdempotent !== undefined) {
|
||||
options.isIdempotent = executionOptions.isIdempotent;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the QueryOptions for a SELECT statement.
|
||||
* @param {Object|String|undefined} executionOptions
|
||||
* @param {Boolean} [overrideIdempotency]
|
||||
*/
|
||||
static adaptAllOptions(executionOptions, overrideIdempotency) {
|
||||
const options = {
|
||||
prepare: true,
|
||||
executionProfile: undefined,
|
||||
fetchSize: undefined,
|
||||
pageState: undefined,
|
||||
timestamp: undefined,
|
||||
isIdempotent: undefined
|
||||
};
|
||||
|
||||
if (typeof executionOptions === 'string') {
|
||||
options.executionProfile = executionOptions;
|
||||
}
|
||||
else if (executionOptions !== null && executionOptions !== undefined) {
|
||||
options.executionProfile = executionOptions.executionProfile;
|
||||
options.fetchSize = executionOptions.fetchSize;
|
||||
options.pageState = executionOptions.pageState;
|
||||
options.timestamp = executionOptions.timestamp;
|
||||
options.isIdempotent = executionOptions.isIdempotent;
|
||||
}
|
||||
|
||||
if (overrideIdempotency) {
|
||||
options.isIdempotent = true;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the QueryOptions for a batch statement.
|
||||
* @param {Object|String|undefined} executionOptions
|
||||
* @param {Boolean} isIdempotent
|
||||
* @param {Boolean} isCounter
|
||||
*/
|
||||
static adaptBatchOptions(executionOptions, isIdempotent, isCounter) {
|
||||
const options = {
|
||||
prepare: true,
|
||||
executionProfile: undefined,
|
||||
timestamp: undefined,
|
||||
logged: undefined,
|
||||
isIdempotent: isIdempotent,
|
||||
counter: isCounter
|
||||
};
|
||||
|
||||
if (typeof executionOptions === 'string') {
|
||||
options.executionProfile = executionOptions;
|
||||
}
|
||||
else if (executionOptions !== null && executionOptions !== undefined) {
|
||||
options.executionProfile = executionOptions.executionProfile;
|
||||
options.timestamp = executionOptions.timestamp;
|
||||
options.logged = executionOptions.logged !== false;
|
||||
|
||||
if (executionOptions.isIdempotent !== undefined) {
|
||||
options.isIdempotent = executionOptions.isIdempotent;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DocInfoAdapter;
|
189
node_modules/cassandra-driver/lib/mapping/index.d.ts
generated
vendored
Normal file
189
node_modules/cassandra-driver/lib/mapping/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { types } from '../types';
|
||||
import { Client } from '../../';
|
||||
import Long = types.Long;
|
||||
|
||||
export namespace mapping {
|
||||
interface TableMappings {
|
||||
getColumnName(propName: string): string;
|
||||
|
||||
getPropertyName(columnName: string): string;
|
||||
|
||||
newObjectInstance(): any;
|
||||
}
|
||||
|
||||
class DefaultTableMappings implements TableMappings {
|
||||
getColumnName(propName: string): string;
|
||||
|
||||
getPropertyName(columnName: string): string;
|
||||
|
||||
newObjectInstance(): any;
|
||||
}
|
||||
|
||||
class UnderscoreCqlToCamelCaseMappings implements TableMappings {
|
||||
getColumnName(propName: string): string;
|
||||
|
||||
getPropertyName(columnName: string): string;
|
||||
|
||||
newObjectInstance(): any;
|
||||
}
|
||||
|
||||
interface Result<T = any> extends Iterator<T> {
|
||||
wasApplied(): boolean;
|
||||
|
||||
first(): T | null;
|
||||
|
||||
forEach(callback: (currentValue: T, index: number) => void, thisArg?: any): void;
|
||||
|
||||
toArray(): T[];
|
||||
}
|
||||
|
||||
type MappingExecutionOptions = {
|
||||
executionProfile?: string;
|
||||
isIdempotent?: boolean;
|
||||
logged?: boolean;
|
||||
timestamp?: number | Long;
|
||||
fetchSize?: number;
|
||||
pageState?: number;
|
||||
}
|
||||
|
||||
interface ModelTables {
|
||||
name: string;
|
||||
isView: boolean;
|
||||
}
|
||||
|
||||
class Mapper {
|
||||
constructor(client: Client, options?: MappingOptions);
|
||||
|
||||
batch(items: ModelBatchItem[], executionOptions?: string | MappingExecutionOptions): Promise<Result>;
|
||||
|
||||
forModel<T = any>(name: string): ModelMapper<T>;
|
||||
}
|
||||
|
||||
type MappingOptions = {
|
||||
models: { [key: string]: ModelOptions };
|
||||
}
|
||||
|
||||
type FindDocInfo = {
|
||||
fields?: string[];
|
||||
orderBy?: { [key: string]: string };
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
type InsertDocInfo = {
|
||||
fields?: string[];
|
||||
ttl?: number;
|
||||
ifNotExists?: boolean;
|
||||
}
|
||||
|
||||
type UpdateDocInfo = {
|
||||
fields?: string[];
|
||||
ttl?: number;
|
||||
ifExists?: boolean;
|
||||
when?: { [key: string]: any };
|
||||
orderBy?: { [key: string]: string };
|
||||
limit?: number;
|
||||
deleteOnlyColumns?: boolean;
|
||||
}
|
||||
|
||||
type RemoveDocInfo = {
|
||||
fields?: string[];
|
||||
ttl?: number;
|
||||
ifExists?: boolean;
|
||||
when?: { [key: string]: any };
|
||||
deleteOnlyColumns?: boolean;
|
||||
}
|
||||
|
||||
type ModelOptions = {
|
||||
tables?: string[] | ModelTables[];
|
||||
mappings?: TableMappings;
|
||||
columns?: { [key: string]: string|ModelColumnOptions };
|
||||
keyspace?: string;
|
||||
}
|
||||
|
||||
type ModelColumnOptions = {
|
||||
name: string;
|
||||
toModel?: (columnValue: any) => any;
|
||||
fromModel?: (modelValue: any) => any;
|
||||
};
|
||||
|
||||
interface ModelBatchItem {
|
||||
|
||||
}
|
||||
|
||||
interface ModelBatchMapper {
|
||||
insert(doc: any, docInfo?: InsertDocInfo): ModelBatchItem;
|
||||
|
||||
remove(doc: any, docInfo?: RemoveDocInfo): ModelBatchItem;
|
||||
|
||||
update(doc: any, docInfo?: UpdateDocInfo): ModelBatchItem;
|
||||
}
|
||||
|
||||
interface ModelMapper<T = any> {
|
||||
name: string;
|
||||
batching: ModelBatchMapper;
|
||||
|
||||
get(doc: { [key: string]: any }, docInfo?: { fields?: string[] }, executionOptions?: string | MappingExecutionOptions): Promise<null | T>;
|
||||
|
||||
find(doc: { [key: string]: any }, docInfo?: FindDocInfo, executionOptions?: string | MappingExecutionOptions): Promise<Result<T>>;
|
||||
|
||||
findAll(docInfo?: FindDocInfo, executionOptions?: string | MappingExecutionOptions): Promise<Result<T>>;
|
||||
|
||||
insert(doc: { [key: string]: any }, docInfo?: InsertDocInfo, executionOptions?: string | MappingExecutionOptions): Promise<Result<T>>;
|
||||
|
||||
update(doc: { [key: string]: any }, docInfo?: UpdateDocInfo, executionOptions?: string | MappingExecutionOptions): Promise<Result<T>>;
|
||||
|
||||
remove(doc: { [key: string]: any }, docInfo?: RemoveDocInfo, executionOptions?: string | MappingExecutionOptions): Promise<Result<T>>;
|
||||
|
||||
mapWithQuery(
|
||||
query: string,
|
||||
paramsHandler: (doc: any) => any[],
|
||||
executionOptions?: string | MappingExecutionOptions
|
||||
): (doc: any, executionOptions?: string | MappingExecutionOptions) => Promise<Result<T>>;
|
||||
}
|
||||
|
||||
namespace q {
|
||||
interface QueryOperator {
|
||||
|
||||
}
|
||||
|
||||
function in_(arr: any): QueryOperator;
|
||||
|
||||
function gt(value: any): QueryOperator;
|
||||
|
||||
function gte(value: any): QueryOperator;
|
||||
|
||||
function lt(value: any): QueryOperator;
|
||||
|
||||
function lte(value: any): QueryOperator;
|
||||
|
||||
function notEq(value: any): QueryOperator;
|
||||
|
||||
function and(condition1: any, condition2: any): QueryOperator;
|
||||
|
||||
function incr(value: any): QueryOperator;
|
||||
|
||||
function decr(value: any): QueryOperator;
|
||||
|
||||
function append(value: any): QueryOperator;
|
||||
|
||||
function prepend(value: any): QueryOperator;
|
||||
|
||||
function remove(value: any): QueryOperator;
|
||||
}
|
||||
}
|
33
node_modules/cassandra-driver/lib/mapping/index.js
generated
vendored
Normal file
33
node_modules/cassandra-driver/lib/mapping/index.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Module containing classes and fields related to the Mapper.
|
||||
* @module mapping
|
||||
*/
|
||||
|
||||
exports.Mapper = require('./mapper');
|
||||
exports.ModelMapper = require('./model-mapper');
|
||||
exports.ModelBatchMapper = require('./model-batch-mapper');
|
||||
exports.ModelBatchItem = require('./model-batch-item').ModelBatchItem;
|
||||
exports.Result = require('./result');
|
||||
const tableMappingsModule = require('./table-mappings');
|
||||
exports.TableMappings = tableMappingsModule.TableMappings;
|
||||
exports.DefaultTableMappings = tableMappingsModule.DefaultTableMappings;
|
||||
exports.UnderscoreCqlToCamelCaseMappings = tableMappingsModule.UnderscoreCqlToCamelCaseMappings;
|
||||
exports.q = require('./q').q;
|
193
node_modules/cassandra-driver/lib/mapping/mapper.js
generated
vendored
Normal file
193
node_modules/cassandra-driver/lib/mapping/mapper.js
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 ModelMapper = require('./model-mapper');
|
||||
const MappingHandler = require('./mapping-handler');
|
||||
const DocInfoAdapter = require('./doc-info-adapter');
|
||||
const errors = require('../errors');
|
||||
const Result = require('./result');
|
||||
const ResultMapper = require('./result-mapper');
|
||||
const ModelMappingInfo = require('./model-mapping-info');
|
||||
const { ModelBatchItem } = require('./model-batch-item');
|
||||
|
||||
/**
|
||||
* Represents an object mapper for Apache Cassandra and DataStax Enterprise.
|
||||
* @alias module:mapping~Mapper
|
||||
* @example <caption>Creating a Mapper instance with some options for the model 'User'</caption>
|
||||
* const mappingOptions = {
|
||||
* models: {
|
||||
* 'User': {
|
||||
* tables: ['users'],
|
||||
* mappings: new UnderscoreCqlToCamelCaseMappings(),
|
||||
* columnNames: {
|
||||
* 'userid': 'id'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
* const mapper = new Mapper(client, mappingOptions);
|
||||
* @example <caption>Creating a Mapper instance with other possible options for a model</caption>
|
||||
* const mappingOptions = {
|
||||
* models: {
|
||||
* 'Video': {
|
||||
* tables: ['videos', 'user_videos', 'latest_videos', { name: 'my_videos_view', isView: true }],
|
||||
* mappings: new UnderscoreCqlToCamelCaseMappings(),
|
||||
* columnNames: {
|
||||
* 'videoid': 'id'
|
||||
* },
|
||||
* keyspace: 'ks1'
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
* const mapper = new Mapper(client, mappingOptions);
|
||||
*/
|
||||
class Mapper {
|
||||
/**
|
||||
* Creates a new instance of Mapper.
|
||||
* @param {Client} client The Client instance to use to execute the queries and fetch the metadata.
|
||||
* @param {MappingOptions} [options] The [MappingOptions]{@link module:mapping~MappingOptions} containing the
|
||||
* information of the models and table mappings.
|
||||
*/
|
||||
constructor(client, options) {
|
||||
if (!client) {
|
||||
throw new Error('client must be defined');
|
||||
}
|
||||
|
||||
/**
|
||||
* The Client instance used to create this Mapper instance.
|
||||
* @type {Client}
|
||||
*/
|
||||
this.client = client;
|
||||
|
||||
this._modelMappingInfos = ModelMappingInfo.parse(options, client.keyspace);
|
||||
this._modelMappers = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a [ModelMapper]{@link module:mapping~ModelMapper} that is able to map documents of a certain model into
|
||||
* CQL rows.
|
||||
* @param {String} name The name to identify the model. Note that the name is case-sensitive.
|
||||
* @returns {ModelMapper} A [ModelMapper]{@link module:mapping~ModelMapper} instance.
|
||||
*/
|
||||
forModel(name) {
|
||||
let modelMapper = this._modelMappers.get(name);
|
||||
|
||||
if (modelMapper === undefined) {
|
||||
let mappingInfo = this._modelMappingInfos.get(name);
|
||||
|
||||
if (mappingInfo === undefined) {
|
||||
if (!this.client.keyspace) {
|
||||
throw new Error(`No mapping information found for model '${name}'. ` +
|
||||
`Mapper is unable to create default mappings without setting the keyspace`);
|
||||
}
|
||||
|
||||
mappingInfo = ModelMappingInfo.createDefault(name, this.client.keyspace);
|
||||
this.client.log('info', `Mapping information for model '${name}' not found, creating default mapping. ` +
|
||||
`Keyspace: ${mappingInfo.keyspace}; Table: ${mappingInfo.tables[0].name}.`);
|
||||
} else {
|
||||
this.client.log('info', `Creating model mapper for '${name}' using mapping information. Keyspace: ${
|
||||
mappingInfo.keyspace}; Table${mappingInfo.tables.length > 1? 's' : ''}: ${
|
||||
mappingInfo.tables.map(t => t.name)}.`);
|
||||
}
|
||||
|
||||
modelMapper = new ModelMapper(name, new MappingHandler(this.client, mappingInfo));
|
||||
this._modelMappers.set(name, modelMapper);
|
||||
}
|
||||
|
||||
return modelMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a batch of queries represented in the items.
|
||||
* @param {Array<ModelBatchItem>} items
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Boolean} [executionOptions.isIdempotent] Defines whether the query can be applied multiple times without
|
||||
* changing the result beyond the initial application.
|
||||
* <p>
|
||||
* The mapper uses the generated queries to determine the default value. When an UPDATE is generated with a
|
||||
* counter column or appending/prepending to a list column, the execution is marked as not idempotent.
|
||||
* </p>
|
||||
* <p>
|
||||
* Additionally, the mapper uses the safest approach for queries with lightweight transactions (Compare and
|
||||
* Set) by considering them as non-idempotent. Lightweight transactions at client level with transparent retries can
|
||||
* break linearizability. If that is not an issue for your application, you can manually set this field to true.
|
||||
* </p>
|
||||
* @param {Boolean} [executionOptions.logged=true] Determines whether the batch should be written to the batchlog.
|
||||
* @param {Number|Long} [executionOptions.timestamp] The default timestamp for the query in microseconds from the
|
||||
* unix epoch (00:00:00, January 1st, 1970).
|
||||
* @returns {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result}.
|
||||
*/
|
||||
batch(items, executionOptions) {
|
||||
if (!Array.isArray(items) || !(items.length > 0)) {
|
||||
return Promise.reject(
|
||||
new errors.ArgumentError('First parameter items should be an Array with 1 or more ModelBatchItem instances'));
|
||||
}
|
||||
|
||||
const queries = [];
|
||||
let isIdempotent = true;
|
||||
let isCounter;
|
||||
|
||||
return Promise
|
||||
.all(items
|
||||
.map(item => {
|
||||
if (!(item instanceof ModelBatchItem)) {
|
||||
return Promise.reject(new Error(
|
||||
'Batch items must be instances of ModelBatchItem, use modelMapper.batching object to create each item'));
|
||||
}
|
||||
|
||||
return item.pushQueries(queries)
|
||||
.then(options => {
|
||||
// The batch is idempotent when all the queries contained are idempotent
|
||||
isIdempotent = isIdempotent && options.isIdempotent;
|
||||
|
||||
// Let it fail at server level when there is a mix of counter and normal mutations
|
||||
isCounter = options.isCounter;
|
||||
});
|
||||
}))
|
||||
.then(() =>
|
||||
this.client.batch(queries, DocInfoAdapter.adaptBatchOptions(executionOptions, isIdempotent, isCounter)))
|
||||
.then(rs => {
|
||||
// Results should only be adapted when the batch contains LWT (single table)
|
||||
const info = items[0].getMappingInfo();
|
||||
return new Result(rs, info, ResultMapper.getMutationAdapter(rs));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the mapping options.
|
||||
* @typedef {Object} module:mapping~MappingOptions
|
||||
* @property {Object<String, ModelOptions>} models An associative array containing the
|
||||
* name of the model as key and the table and column information as value.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a set of options that applies to a certain model.
|
||||
* @typedef {Object} module:mapping~ModelOptions
|
||||
* @property {Array<String>|Array<{name, isView}>} tables An Array containing the name of the tables or An Array
|
||||
* containing the name and isView property to describe the table.
|
||||
* @property {TableMappings} mappings The TableMappings implementation instance that is used to convert from column
|
||||
* names to property names and the other way around.
|
||||
* @property {Object.<String, String>} [columnNames] An associative array containing the name of the columns and
|
||||
* properties that doesn't follow the convention defined in the <code>TableMappings</code>.
|
||||
* @property {String} [keyspace] The name of the keyspace. Only mandatory when the Client is not using a keyspace.
|
||||
*/
|
||||
|
||||
module.exports = Mapper;
|
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;
|
191
node_modules/cassandra-driver/lib/mapping/model-batch-item.js
generated
vendored
Normal file
191
node_modules/cassandra-driver/lib/mapping/model-batch-item.js
generated
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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 Cache = require('./cache');
|
||||
|
||||
/**
|
||||
* Represents a query or a set of queries used to perform a mutation in a batch.
|
||||
* @alias module:mapping~ModelBatchItem
|
||||
*/
|
||||
class ModelBatchItem {
|
||||
/**
|
||||
* @param {Object} doc
|
||||
* @param {Object} docInfo
|
||||
* @param {MappingHandler} handler
|
||||
* @param {Tree} cache
|
||||
*/
|
||||
constructor(doc, docInfo, handler, cache) {
|
||||
this.doc = doc;
|
||||
this.docInfo = docInfo;
|
||||
this.handler = handler;
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @returns <Promise<Array>>
|
||||
*/
|
||||
getQueries() {
|
||||
const docKeys = Object.keys(this.doc);
|
||||
const cacheItem = this.cache.getOrCreate(this.getCacheKey(docKeys), () => ({ queries: null }));
|
||||
|
||||
if (cacheItem.queries === null) {
|
||||
cacheItem.queries = this.createQueries(docKeys);
|
||||
}
|
||||
|
||||
return cacheItem.queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cache key for this item.
|
||||
* @abstract
|
||||
* @param {Array} docKeys
|
||||
* @returns {Iterator}
|
||||
*/
|
||||
getCacheKey(docKeys) {
|
||||
throw new Error('getCacheKey must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Promise to create the queries.
|
||||
* @abstract
|
||||
* @param {Array} docKeys
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
createQueries(docKeys) {
|
||||
throw new Error('getCacheKey must be implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the queries and parameters represented by this instance to the provided array.
|
||||
* @internal
|
||||
* @ignore
|
||||
* @param {Array} arr
|
||||
* @return {Promise<{isIdempotent, isCounter}>}
|
||||
*/
|
||||
pushQueries(arr) {
|
||||
let isIdempotent = true;
|
||||
let isCounter;
|
||||
|
||||
return this.getQueries().then(queries => {
|
||||
queries.forEach(q => {
|
||||
// It's idempotent if all the queries contained are idempotent
|
||||
isIdempotent = isIdempotent && q.isIdempotent;
|
||||
|
||||
// Either all queries are counter mutation or we let it fail at server level
|
||||
isCounter = q.isCounter;
|
||||
|
||||
arr.push({ query: q.query, params: q.paramsGetter(this.doc, this.docInfo, this.getMappingInfo()) });
|
||||
});
|
||||
|
||||
return { isIdempotent, isCounter };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the mapping information for this batch item.
|
||||
* @internal
|
||||
* @ignore
|
||||
*/
|
||||
getMappingInfo() {
|
||||
return this.handler.info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single or a set of INSERT queries in a batch.
|
||||
* @ignore
|
||||
* @internal
|
||||
*/
|
||||
class InsertModelBatchItem extends ModelBatchItem {
|
||||
/**
|
||||
* @param {Object} doc
|
||||
* @param {Object} docInfo
|
||||
* @param {MappingHandler} handler
|
||||
* @param {Tree} cache
|
||||
*/
|
||||
constructor(doc, docInfo, handler, cache) {
|
||||
super(doc, docInfo, handler, cache);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getCacheKey(docKeys) {
|
||||
return Cache.getInsertKey(docKeys, this.docInfo);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
createQueries(docKeys) {
|
||||
return this.handler.createInsertQueries(docKeys, this.doc, this.docInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single or a set of UPDATE queries in a batch.
|
||||
* @ignore
|
||||
* @internal
|
||||
*/
|
||||
class UpdateModelBatchItem extends ModelBatchItem {
|
||||
/**
|
||||
* @param {Object} doc
|
||||
* @param {Object} docInfo
|
||||
* @param {MappingHandler} handler
|
||||
* @param {Tree} cache
|
||||
*/
|
||||
constructor(doc, docInfo, handler, cache) {
|
||||
super(doc, docInfo, handler, cache);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getCacheKey(docKeys) {
|
||||
return Cache.getUpdateKey(docKeys, this.doc, this.docInfo);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
createQueries(docKeys) {
|
||||
return this.handler.createUpdateQueries(docKeys, this.doc, this.docInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single or a set of DELETE queries in a batch.
|
||||
* @ignore
|
||||
* @internal
|
||||
*/
|
||||
class RemoveModelBatchItem extends ModelBatchItem {
|
||||
/**
|
||||
* @param {Object} doc
|
||||
* @param {Object} docInfo
|
||||
* @param {MappingHandler} handler
|
||||
* @param {Tree} cache
|
||||
*/
|
||||
constructor(doc, docInfo, handler, cache) {
|
||||
super(doc, docInfo, handler, cache);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getCacheKey(docKeys) {
|
||||
return Cache.getRemoveKey(docKeys, this.doc, this.docInfo);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
createQueries(docKeys) {
|
||||
return this.handler.createDeleteQueries(docKeys, this.doc, this.docInfo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ModelBatchItem, InsertModelBatchItem, UpdateModelBatchItem, RemoveModelBatchItem };
|
125
node_modules/cassandra-driver/lib/mapping/model-batch-mapper.js
generated
vendored
Normal file
125
node_modules/cassandra-driver/lib/mapping/model-batch-mapper.js
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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 Tree = require('./tree');
|
||||
const moduleBatchItemModule = require('./model-batch-item');
|
||||
const InsertModelBatchItem = moduleBatchItemModule.InsertModelBatchItem;
|
||||
const UpdateModelBatchItem = moduleBatchItemModule.UpdateModelBatchItem;
|
||||
const RemoveModelBatchItem = moduleBatchItemModule.RemoveModelBatchItem;
|
||||
|
||||
/**
|
||||
* Provides utility methods to group multiple mutations on a single batch.
|
||||
* @alias module:mapping~ModelBatchMapper
|
||||
*/
|
||||
class ModelBatchMapper {
|
||||
/**
|
||||
* Creates a new instance of model batch mapper.
|
||||
* <p>
|
||||
* An instance of this class is exposed as a singleton in the <code>batching</code> field of the
|
||||
* [ModelMapper]{@link module:mapping~ModelMapper}. Note that new instances should not be create with this
|
||||
* constructor.
|
||||
* </p>
|
||||
* @param {MappingHandler} handler
|
||||
* @ignore
|
||||
*/
|
||||
constructor(handler) {
|
||||
this._handler = handler;
|
||||
this._cache = {
|
||||
insert: new Tree(),
|
||||
update: new Tree(),
|
||||
remove: new Tree()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a [ModelBatchItem]{@link module:mapping~ModelBatchItem} containing the queries for the INSERT mutation to be
|
||||
* used in a batch execution.
|
||||
* @param {Object} doc An object containing the properties to insert.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* INSERT cql statements generated. If specified, it must include the columns to insert and the primary keys.
|
||||
* @param {Number} [docInfo.ttl] Specifies an optional Time To Live (in seconds) for the inserted values.
|
||||
* @param {Boolean} [docInfo.ifNotExists] When set, it only inserts if the row does not exist prior to the insertion.
|
||||
* <p>Please note that using IF NOT EXISTS will incur a non negligible performance cost so this should be used
|
||||
* sparingly.</p>
|
||||
* @returns {ModelBatchItem} A [ModelBatchItem]{@link module:mapping~ModelBatchItem} instance representing a query
|
||||
* or a set of queries to be included in a batch.
|
||||
*/
|
||||
insert(doc, docInfo) {
|
||||
return new InsertModelBatchItem(doc, docInfo, this._handler, this._cache.insert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a [ModelBatchItem]{@link module:mapping~ModelBatchItem} containing the queries for the UPDATE mutation to be
|
||||
* used in a batch execution.
|
||||
* @param {Object} doc An object containing the properties to update.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* UPDATE cql statements generated. If specified, it must include the columns to update and the primary keys.
|
||||
* @param {Number} [docInfo.ttl] Specifies an optional Time To Live (in seconds) for the inserted values.
|
||||
* @param {Boolean} [docInfo.ifExists] When set, it only updates if the row already exists on the server.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Object} [docInfo.when] A document that act as the condition that has to be met for the UPDATE to occur.
|
||||
* Use this property only in the case you want to specify a conditional clause for lightweight transactions (CAS).
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @returns {ModelBatchItem} A [ModelBatchItem]{@link module:mapping~ModelBatchItem} instance representing a query
|
||||
* or a set of queries to be included in a batch.
|
||||
*/
|
||||
update(doc, docInfo) {
|
||||
return new UpdateModelBatchItem(doc, docInfo, this._handler, this._cache.update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a [ModelBatchItem]{@link module:mapping~ModelBatchItem} containing the queries for the DELETE mutation to be
|
||||
* used in a batch execution.
|
||||
* @param {Object} doc A document containing the primary keys values of the document to delete.
|
||||
* @param {Object} [docInfo] An object containing the additional doc information.
|
||||
* @param {Object} [docInfo.when] A document that act as the condition that has to be met for the DELETE to occur.
|
||||
* Use this property only in the case you want to specify a conditional clause for lightweight transactions (CAS).
|
||||
* When the CQL query is generated, this would be used to generate the `IF` clause.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Boolean} [docInfo.ifExists] When set, it only issues the DELETE command if the row already exists on the
|
||||
* server.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* DELETE cql statement generated. If specified, it must include the columns to delete and the primary keys.
|
||||
* @param {Boolean} [docInfo.deleteOnlyColumns] Determines that, when more document properties are specified
|
||||
* besides the primary keys, the generated DELETE statement should be used to delete some column values but leave
|
||||
* the row. When this is enabled and more properties are specified, a DELETE statement will have the following form:
|
||||
* "DELETE col1, col2 FROM table1 WHERE pk1 = ? AND pk2 = ?"
|
||||
* @returns {ModelBatchItem} A [ModelBatchItem]{@link module:mapping~ModelBatchItem} instance representing a query
|
||||
* or a set of queries to be included in a batch.
|
||||
*/
|
||||
remove(doc, docInfo) {
|
||||
return new RemoveModelBatchItem(doc, docInfo, this._handler, this._cache.update);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModelBatchMapper;
|
306
node_modules/cassandra-driver/lib/mapping/model-mapper.js
generated
vendored
Normal file
306
node_modules/cassandra-driver/lib/mapping/model-mapper.js
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
* 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 ModelBatchMapper = require('./model-batch-mapper');
|
||||
|
||||
/**
|
||||
* Represents an object mapper for a specific model.
|
||||
* @alias module:mapping~ModelMapper
|
||||
*/
|
||||
class ModelMapper {
|
||||
constructor(name, handler) {
|
||||
/**
|
||||
* Gets the name identifier of the model.
|
||||
* @type {String}
|
||||
*/
|
||||
this.name = name;
|
||||
this._handler = handler;
|
||||
/**
|
||||
* Gets a [ModelBatchMapper]{@link module:mapping~ModelBatchMapper} instance containing utility methods to group
|
||||
* multiple doc mutations in a single batch.
|
||||
* @type {ModelBatchMapper}
|
||||
*/
|
||||
this.batching = new ModelBatchMapper(this._handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first document matching the provided filter or null when not found.
|
||||
* <p>
|
||||
* Note that all partition and clustering keys must be defined in order to use this method.
|
||||
* </p>
|
||||
* @param {Object} doc The object containing the properties that map to the primary keys.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* SELECT cql statement generated, in order to restrict the amount of columns retrieved.
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @return {Promise<Object>}
|
||||
* @example <caption>Get a video by id</caption>
|
||||
* videoMapper.get({ id })
|
||||
* @example <caption>Get a video by id, selecting specific columns</caption>
|
||||
* videoMapper.get({ id }, fields: ['name', 'description'])
|
||||
*/
|
||||
get(doc, docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
return this._handler.getSelectExecutor(doc, docInfo, true)
|
||||
.then(executor => executor(doc, docInfo, executionOptions))
|
||||
.then(result => result.first());
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SELECT query based on the filter and returns the result as an iterable of documents.
|
||||
* @param {Object} doc An object containing the properties that map to the primary keys to filter.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* SELECT cql statement generated, in order to restrict the amount of columns retrieved.
|
||||
* @param {Object<String, String>} [docInfo.orderBy] An associative array containing the column names as key and
|
||||
* the order string (asc or desc) as value used to set the order of the results server-side.
|
||||
* @param {Number} [docInfo.limit] Restricts the result of the query to a maximum number of rows on the
|
||||
* server.
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Number} [executionOptions.fetchSize] The amount of rows to retrieve per page.
|
||||
* @param {Number} [executionOptions.pageState] A Buffer instance or a string token representing the paging state.
|
||||
* <p>When provided, the query will be executed starting from a given paging state.</p>
|
||||
* @return {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
* @example <caption>Get user's videos</caption>
|
||||
* const result = await videoMapper.find({ userId });
|
||||
* for (let video of result) {
|
||||
* console.log(video.name);
|
||||
* }
|
||||
* @example <caption>Get user's videos from a certain date</caption>
|
||||
* videoMapper.find({ userId, addedDate: q.gte(date)});
|
||||
* @example <caption>Get user's videos in reverse order</caption>
|
||||
* videoMapper.find({ userId }, { orderBy: { addedDate: 'desc' }});
|
||||
*/
|
||||
find(doc, docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
return this._handler.getSelectExecutor(doc, docInfo, false)
|
||||
.then(executor => executor(doc, docInfo, executionOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a SELECT query without a filter and returns the result as an iterable of documents.
|
||||
* <p>
|
||||
* This is only recommended to be used for tables with a limited amount of results. Otherwise, breaking up the
|
||||
* token ranges on the client side should be used.
|
||||
* </p>
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* SELECT cql statement generated, in order to restrict the amount of columns retrieved.
|
||||
* @param {Object<String, String>} [docInfo.orderBy] An associative array containing the column names as key and
|
||||
* the order string (asc or desc) as value used to set the order of the results server-side.
|
||||
* @param {Number} [docInfo.limit] Restricts the result of the query to a maximum number of rows on the
|
||||
* server.
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Number} [executionOptions.fetchSize] The mount of rows to retrieve per page.
|
||||
* @param {Number} [executionOptions.pageState] A Buffer instance or a string token representing the paging state.
|
||||
* <p>When provided, the query will be executed starting from a given paging state.</p>
|
||||
* @return {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
*/
|
||||
findAll(docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
const executor = this._handler.getSelectAllExecutor(docInfo);
|
||||
return executor(docInfo, executionOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a document.
|
||||
* <p>
|
||||
* When the model is mapped to multiple tables, it will insert a row in each table when all the primary keys
|
||||
* are specified.
|
||||
* </p>
|
||||
* @param {Object} doc An object containing the properties to insert.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* INSERT cql statements generated. If specified, it must include the columns to insert and the primary keys.
|
||||
* @param {Number} [docInfo.ttl] Specifies an optional Time To Live (in seconds) for the inserted values.
|
||||
* @param {Boolean} [docInfo.ifNotExists] When set, it only inserts if the row does not exist prior to the insertion.
|
||||
* <p>Please note that using IF NOT EXISTS will incur a non negligible performance cost so this should be used
|
||||
* sparingly.</p>
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Boolean} [executionOptions.isIdempotent] Defines whether the query can be applied multiple times without
|
||||
* changing the result beyond the initial application.
|
||||
* <p>
|
||||
* By default all generated INSERT statements are considered idempotent, except in the case of lightweight
|
||||
* transactions. Lightweight transactions at client level with transparent retries can
|
||||
* break linearizability. If that is not an issue for your application, you can manually set this field to true.
|
||||
* </p>
|
||||
* @param {Number|Long} [executionOptions.timestamp] The default timestamp for the query in microseconds from the
|
||||
* unix epoch (00:00:00, January 1st, 1970).
|
||||
* <p>When provided, this will replace the client generated and the server side assigned timestamp.</p>
|
||||
* @return {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
* @example <caption>Insert a video</caption>
|
||||
* videoMapper.insert({ id, name });
|
||||
*/
|
||||
insert(doc, docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
return this._handler.getInsertExecutor(doc, docInfo)
|
||||
.then(executor => executor(doc, docInfo, executionOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a document.
|
||||
* <p>
|
||||
* When the model is mapped to multiple tables, it will update a row in each table when all the primary keys
|
||||
* are specified.
|
||||
* </p>
|
||||
* @param {Object} doc An object containing the properties to update.
|
||||
* @param {Object} [docInfo] An object containing the additional document information.
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* UPDATE cql statements generated. If specified, it must include the columns to update and the primary keys.
|
||||
* @param {Number} [docInfo.ttl] Specifies an optional Time To Live (in seconds) for the inserted values.
|
||||
* @param {Boolean} [docInfo.ifExists] When set, it only updates if the row already exists on the server.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Object} [docInfo.when] A document that act as the condition that has to be met for the UPDATE to occur.
|
||||
* Use this property only in the case you want to specify a conditional clause for lightweight transactions (CAS).
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Boolean} [executionOptions.isIdempotent] Defines whether the query can be applied multiple times without
|
||||
* changing the result beyond the initial application.
|
||||
* <p>
|
||||
* The mapper uses the generated queries to determine the default value. When an UPDATE is generated with a
|
||||
* counter column or appending/prepending to a list column, the execution is marked as not idempotent.
|
||||
* </p>
|
||||
* <p>
|
||||
* Additionally, the mapper uses the safest approach for queries with lightweight transactions (Compare and
|
||||
* Set) by considering them as non-idempotent. Lightweight transactions at client level with transparent retries can
|
||||
* break linearizability. If that is not an issue for your application, you can manually set this field to true.
|
||||
* </p>
|
||||
* @param {Number|Long} [executionOptions.timestamp] The default timestamp for the query in microseconds from the
|
||||
* unix epoch (00:00:00, January 1st, 1970).
|
||||
* <p>When provided, this will replace the client generated and the server side assigned timestamp.</p>
|
||||
* @return {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
* @example <caption>Update the name of a video</caption>
|
||||
* videoMapper.update({ id, name });
|
||||
*/
|
||||
update(doc, docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
return this._handler.getUpdateExecutor(doc, docInfo)
|
||||
.then(executor => executor(doc, docInfo, executionOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a document.
|
||||
* @param {Object} doc A document containing the primary keys values of the document to delete.
|
||||
* @param {Object} [docInfo] An object containing the additional doc information.
|
||||
* @param {Object} [docInfo.when] A document that act as the condition that has to be met for the DELETE to occur.
|
||||
* Use this property only in the case you want to specify a conditional clause for lightweight transactions (CAS).
|
||||
* When the CQL query is generated, this would be used to generate the `IF` clause.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Boolean} [docInfo.ifExists] When set, it only issues the DELETE command if the row already exists on the
|
||||
* server.
|
||||
* <p>
|
||||
* Please note that using IF conditions will incur a non negligible performance cost on the server-side so this
|
||||
* should be used sparingly.
|
||||
* </p>
|
||||
* @param {Array<String>} [docInfo.fields] An Array containing the name of the properties that will be used in the
|
||||
* DELETE cql statement generated. If specified, it must include the columns to delete and the primary keys.
|
||||
* @param {Boolean} [docInfo.deleteOnlyColumns] Determines that, when more document properties are specified
|
||||
* besides the primary keys, the generated DELETE statement should be used to delete some column values but leave
|
||||
* the row. When this is enabled and more properties are specified, a DELETE statement will have the following form:
|
||||
* "DELETE col1, col2 FROM table1 WHERE pk1 = ? AND pk2 = ?"
|
||||
* @param {Object|String} [executionOptions] An object containing the options to be used for the requests
|
||||
* execution or a string representing the name of the execution profile.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Boolean} [executionOptions.isIdempotent] Defines whether the query can be applied multiple times without
|
||||
* changing the result beyond the initial application.
|
||||
* <p>
|
||||
* By default all generated DELETE statements are considered idempotent, except in the case of lightweight
|
||||
* transactions. Lightweight transactions at client level with transparent retries can
|
||||
* break linearizability. If that is not an issue for your application, you can manually set this field to true.
|
||||
* </p>
|
||||
* @param {Number|Long} [executionOptions.timestamp] The default timestamp for the query in microseconds from the
|
||||
* unix epoch (00:00:00, January 1st, 1970).
|
||||
* <p>When provided, this will replace the client generated and the server side assigned timestamp.</p>
|
||||
* @return {Promise<Result>} A Promise that resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
* @example <caption>Delete a video</caption>
|
||||
* videoMapper.remove({ id });
|
||||
*/
|
||||
remove(doc, docInfo, executionOptions) {
|
||||
if (executionOptions === undefined && typeof docInfo === 'string') {
|
||||
executionOptions = docInfo;
|
||||
docInfo = null;
|
||||
}
|
||||
|
||||
return this._handler.getDeleteExecutor(doc, docInfo)
|
||||
.then(executor => executor(doc, docInfo, executionOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided query and param getter function to execute a query and map the results.
|
||||
* Gets a function that takes the document, executes the query and returns the mapped results.
|
||||
* @param {String} query The query to execute.
|
||||
* @param {Function} paramsHandler The function to execute to extract the parameters of a document.
|
||||
* @param {Object|String} [executionOptions] When provided, the options for all executions generated with this
|
||||
* method will use the provided options and it will not consider the executionOptions per call.
|
||||
* @param {String} [executionOptions.executionProfile] The name of the execution profile.
|
||||
* @param {Number} [executionOptions.fetchSize] Amount of rows to retrieve per page.
|
||||
* @param {Boolean} [executionOptions.isIdempotent] Defines whether the query can be applied multiple times
|
||||
* without changing the result beyond the initial application.
|
||||
* @param {Number} [executionOptions.pageState] Buffer or string token representing the paging state.
|
||||
* <p>When provided, the query will be executed starting from a given paging state.</p>
|
||||
* @param {Number|Long} [executionOptions.timestamp] The default timestamp for the query in microseconds from the
|
||||
* unix epoch (00:00:00, January 1st, 1970).
|
||||
* <p>When provided, this will replace the client generated and the server side assigned timestamp.</p>
|
||||
* @return {Function} Returns a function that takes the document and execution options as parameters and returns a
|
||||
* Promise the resolves to a [Result]{@link module:mapping~Result} instance.
|
||||
*/
|
||||
mapWithQuery(query, paramsHandler, executionOptions) {
|
||||
return this._handler.getExecutorFromQuery(query, paramsHandler, executionOptions);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModelMapper;
|
194
node_modules/cassandra-driver/lib/mapping/model-mapping-info.js
generated
vendored
Normal file
194
node_modules/cassandra-driver/lib/mapping/model-mapping-info.js
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* 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 tableMappingsModule = require('./table-mappings');
|
||||
const TableMappings = tableMappingsModule.TableMappings;
|
||||
const DefaultTableMappings = tableMappingsModule.DefaultTableMappings;
|
||||
|
||||
/**
|
||||
* Represents the parsed user information of the table mappings of a model.
|
||||
* @ignore
|
||||
*/
|
||||
class ModelMappingInfo {
|
||||
/**
|
||||
* @param {String} keyspace
|
||||
* @param {Array<{name, isView}>} tables
|
||||
* @param {TableMappings} mappings
|
||||
* @param {Map<String,ModelColumnInfo>} columns
|
||||
*/
|
||||
constructor(keyspace, tables, mappings, columns) {
|
||||
this.keyspace = keyspace;
|
||||
this.tables = tables;
|
||||
this._mappings = mappings;
|
||||
this._columns = columns;
|
||||
|
||||
// Define a map of column information per property name
|
||||
/** @type {Map<String, ModelColumnInfo>} */
|
||||
this._documentProperties = new Map();
|
||||
for (const modelColumnInfo of columns.values()) {
|
||||
this._documentProperties.set(modelColumnInfo.propertyName, modelColumnInfo);
|
||||
}
|
||||
}
|
||||
|
||||
getColumnName(propName) {
|
||||
const modelColumnInfo = this._documentProperties.get(propName);
|
||||
if (modelColumnInfo !== undefined) {
|
||||
// There is an specific name transformation between the column name and the property name
|
||||
return modelColumnInfo.columnName;
|
||||
}
|
||||
// Rely on the TableMappings (i.e. maybe there is a convention defined for this property)
|
||||
return this._mappings.getColumnName(propName);
|
||||
}
|
||||
|
||||
getPropertyName(columnName) {
|
||||
const modelColumnInfo = this._columns.get(columnName);
|
||||
if (modelColumnInfo !== undefined) {
|
||||
// There is an specific name transformation between the column name and the property name
|
||||
return modelColumnInfo.propertyName;
|
||||
}
|
||||
// Rely on the TableMappings (i.e. maybe there is a convention defined for this column)
|
||||
return this._mappings.getPropertyName(columnName);
|
||||
}
|
||||
|
||||
getFromModelFn(propName) {
|
||||
const modelColumnInfo = this._documentProperties.get(propName);
|
||||
return modelColumnInfo !== undefined ? modelColumnInfo.fromModel : null;
|
||||
}
|
||||
|
||||
getToModelFn(columnName) {
|
||||
const modelColumnInfo = this._columns.get(columnName);
|
||||
return modelColumnInfo !== undefined ? modelColumnInfo.toModel : null;
|
||||
}
|
||||
|
||||
newInstance() {
|
||||
return this._mappings.newObjectInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the user options into a map of model names and ModelMappingInfo.
|
||||
* @param {MappingOptions} options
|
||||
* @param {String} currentKeyspace
|
||||
* @returns {Map<String, ModelMappingInfo>}
|
||||
*/
|
||||
static parse(options, currentKeyspace) {
|
||||
const result = new Map();
|
||||
if (!options || !options.models) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Object.keys(options.models).forEach(modelName => {
|
||||
const modelOptions = options.models[modelName];
|
||||
result.set(modelName, ModelMappingInfo._create(modelName, currentKeyspace, modelOptions));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static _create(modelName, currentKeyspace, modelOptions) {
|
||||
if (!currentKeyspace && (!modelOptions || !modelOptions.keyspace)) {
|
||||
throw new Error(
|
||||
'You should specify the keyspace of the model in the MappingOptions when the Client is not using a keyspace');
|
||||
}
|
||||
|
||||
if (!modelOptions) {
|
||||
return ModelMappingInfo.createDefault(modelName, currentKeyspace);
|
||||
}
|
||||
|
||||
let tables;
|
||||
|
||||
if (modelOptions.tables && modelOptions.tables.length > 0) {
|
||||
tables = modelOptions.tables.map(item => {
|
||||
const table = { name: null, isView: false };
|
||||
if (typeof item === 'string') {
|
||||
table.name = item;
|
||||
} else if (item) {
|
||||
table.name = item.name;
|
||||
table.isView = !!item.isView;
|
||||
}
|
||||
|
||||
if (!table.name) {
|
||||
throw new Error(`Table name not specified for model '${modelName}'`);
|
||||
}
|
||||
|
||||
return table;
|
||||
});
|
||||
} else {
|
||||
tables = [ { name: modelName, isView: false }];
|
||||
}
|
||||
|
||||
if (modelOptions.mappings && !(modelOptions.mappings instanceof TableMappings)) {
|
||||
throw new Error('mappings should be an instance of TableMappings');
|
||||
}
|
||||
|
||||
const columns = new Map();
|
||||
if (modelOptions.columns !== null && typeof modelOptions.columns === 'object') {
|
||||
Object.keys(modelOptions.columns).forEach(columnName => {
|
||||
columns.set(columnName, ModelColumnInfo.parse(columnName, modelOptions.columns[columnName]));
|
||||
});
|
||||
}
|
||||
|
||||
return new ModelMappingInfo(
|
||||
modelOptions.keyspace || currentKeyspace,
|
||||
tables,
|
||||
modelOptions.mappings || new DefaultTableMappings(),
|
||||
columns
|
||||
);
|
||||
}
|
||||
|
||||
static createDefault(modelName, currentKeyspace) {
|
||||
return new ModelMappingInfo(
|
||||
currentKeyspace,
|
||||
[ { name: modelName, isView: false }],
|
||||
new DefaultTableMappings(),
|
||||
new Map());
|
||||
}
|
||||
}
|
||||
|
||||
class ModelColumnInfo {
|
||||
constructor(columnName, propertyName, toModel, fromModel) {
|
||||
this.columnName = columnName;
|
||||
this.propertyName = propertyName;
|
||||
|
||||
if (toModel && typeof toModel !== 'function') {
|
||||
throw new TypeError(`toModel type for property '${propertyName}' should be a function (obtained ${
|
||||
typeof toModel})`);
|
||||
}
|
||||
|
||||
if (fromModel && typeof fromModel !== 'function') {
|
||||
throw new TypeError(`fromModel type for property '${propertyName}' should be a function (obtained ${
|
||||
typeof fromModel})`);
|
||||
}
|
||||
|
||||
this.toModel = toModel;
|
||||
this.fromModel = fromModel;
|
||||
}
|
||||
|
||||
static parse(columnName, value) {
|
||||
if (!value) {
|
||||
return new ModelColumnInfo(columnName, columnName);
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return new ModelColumnInfo(columnName, value);
|
||||
}
|
||||
|
||||
return new ModelColumnInfo(columnName, value.name || columnName, value.toModel, value.fromModel);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModelMappingInfo;
|
321
node_modules/cassandra-driver/lib/mapping/object-selector.js
generated
vendored
Normal file
321
node_modules/cassandra-driver/lib/mapping/object-selector.js
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* 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 keyMatches = {
|
||||
all: 1,
|
||||
none: 0,
|
||||
some: -1
|
||||
};
|
||||
|
||||
/**
|
||||
* Provides utility methods to choose the correct tables and views that should be included in a statement.
|
||||
* @ignore
|
||||
*/
|
||||
class ObjectSelector {
|
||||
/**
|
||||
* Gets the table/view that should be used to execute the SELECT query.
|
||||
* @param {Client} client
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {Boolean} allPKsDefined
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} fieldsInfo
|
||||
* @param {Array<Array<String>>} orderByColumns
|
||||
* @return {Promise<String>} A promise that resolves to a table names.
|
||||
*/
|
||||
static getForSelect(client, info, allPKsDefined, propertiesInfo, fieldsInfo, orderByColumns) {
|
||||
return Promise.all(
|
||||
info.tables.map(t => {
|
||||
if (t.isView) {
|
||||
return client.metadata.getMaterializedView(info.keyspace, t.name);
|
||||
}
|
||||
return client.metadata.getTable(info.keyspace, t.name);
|
||||
}))
|
||||
.then(tables => {
|
||||
for (let i = 0; i < tables.length; i++) {
|
||||
const table = tables[i];
|
||||
if (table === null) {
|
||||
throw new Error(`Table "${info.tables[i].name}" could not be retrieved`);
|
||||
}
|
||||
|
||||
if (keysAreIncluded(table.partitionKeys, propertiesInfo) !== keyMatches.all) {
|
||||
// Not all the partition keys are covered
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (allPKsDefined) {
|
||||
if (keysAreIncluded(table.clusteringKeys, propertiesInfo) !== keyMatches.all) {
|
||||
// All clustering keys should be included as allPKsDefined flag is set
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (propertiesInfo.length > table.partitionKeys.length) {
|
||||
// Check that the Where clause is composed by partition and clustering keys
|
||||
const allPropertiesArePrimaryKeys = propertiesInfo
|
||||
.reduce(
|
||||
(acc, p) => acc && (
|
||||
contains(table.partitionKeys, c => c.name === p.columnName) ||
|
||||
contains(table.clusteringKeys, c => c.name === p.columnName)
|
||||
),
|
||||
true);
|
||||
|
||||
if (!allPropertiesArePrimaryKeys) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// All fields must be contained
|
||||
const containsAllFields = fieldsInfo
|
||||
.reduce((acc, p) => acc && table.columnsByName[p.columnName] !== undefined, true);
|
||||
|
||||
if (!containsAllFields) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// CQL:
|
||||
// - "ORDER BY" is currently only supported on the clustered columns of the PRIMARY KEY
|
||||
// - "ORDER BY" currently only support the ordering of columns following their declared order in
|
||||
// the PRIMARY KEY
|
||||
//
|
||||
// In the mapper, we validate that the ORDER BY columns appear in the same order as in the clustering keys
|
||||
const containsAllOrderByColumns = orderByColumns
|
||||
.reduce((acc, order, index) => {
|
||||
if (!acc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ck = table.clusteringKeys[index];
|
||||
|
||||
return ck && ck.name === order[0];
|
||||
}, true);
|
||||
|
||||
if (!containsAllOrderByColumns) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return table.name;
|
||||
}
|
||||
|
||||
let message = `No table matches the filter (${allPKsDefined ? 'all PKs have to be specified' : 'PKs'}): [${
|
||||
propertiesInfo.map(p => p.columnName)}]`;
|
||||
|
||||
if (fieldsInfo.length > 0) {
|
||||
message += `; fields: [${fieldsInfo.map(p => p.columnName)}]`;
|
||||
}
|
||||
if (orderByColumns.length > 0) {
|
||||
message += `; orderBy: [${orderByColumns.map(item => item[0])}]`;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
});
|
||||
}
|
||||
|
||||
/** Returns the name of the first table */
|
||||
static getForSelectAll(info) {
|
||||
return info.tables[0].name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tables that should be used to execute the INSERT query.
|
||||
* @param {Client} client
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {Array} propertiesInfo
|
||||
* @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
|
||||
*/
|
||||
static getForInsert(client, info, propertiesInfo) {
|
||||
return Promise.all(info.tables.filter(t => !t.isView).map(t => client.metadata.getTable(info.keyspace, t.name)))
|
||||
.then(tables => {
|
||||
const filteredTables = tables
|
||||
.filter((table, i) => {
|
||||
if (table === null) {
|
||||
throw new Error(`Table "${info.tables[i].name}" could not be retrieved`);
|
||||
}
|
||||
|
||||
if (keysAreIncluded(table.partitionKeys, propertiesInfo) !== keyMatches.all) {
|
||||
// Not all the partition keys are covered
|
||||
return false;
|
||||
}
|
||||
|
||||
const clusteringKeyMatches = keysAreIncluded(table.clusteringKeys, propertiesInfo);
|
||||
|
||||
// All clustering keys should be included or it can be inserting a static column value
|
||||
if (clusteringKeyMatches === keyMatches.all) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (clusteringKeyMatches === keyMatches.some) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const staticColumns = staticColumnCount(table);
|
||||
return propertiesInfo.length === table.partitionKeys.length + staticColumns && staticColumns > 0;
|
||||
});
|
||||
|
||||
if (filteredTables.length === 0) {
|
||||
throw new Error(`No table matches (all PKs have to be specified) fields: [${
|
||||
propertiesInfo.map(p => p.columnName)}]`);
|
||||
}
|
||||
|
||||
return filteredTables;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tables that should be used to execute the UPDATE query.
|
||||
* @param {Client} client
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} when
|
||||
* @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
|
||||
*/
|
||||
static getForUpdate(client, info, propertiesInfo, when) {
|
||||
return Promise.all(info.tables.filter(t => !t.isView).map(t => client.metadata.getTable(info.keyspace, t.name)))
|
||||
.then(tables => {
|
||||
const filteredTables = tables
|
||||
.filter((table, i) => {
|
||||
if (table === null) {
|
||||
throw new Error(`Table "${info.tables[i].name}" could not be retrieved`);
|
||||
}
|
||||
|
||||
if (keysAreIncluded(table.partitionKeys, propertiesInfo) !== keyMatches.all) {
|
||||
// Not all the partition keys are covered
|
||||
return false;
|
||||
}
|
||||
|
||||
const clusteringKeyMatches = keysAreIncluded(table.clusteringKeys, propertiesInfo);
|
||||
|
||||
// All clustering keys should be included or it can be updating a static column value
|
||||
if (clusteringKeyMatches === keyMatches.some) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clusteringKeyMatches === keyMatches.none && !hasStaticColumn(table)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const applicableColumns = propertiesInfo
|
||||
.reduce((acc, p) => acc + (table.columnsByName[p.columnName] !== undefined ? 1 : 0), 0);
|
||||
|
||||
if (applicableColumns <= table.partitionKeys.length + table.clusteringKeys.length) {
|
||||
if (!hasStaticColumn(table) || applicableColumns <= table.partitionKeys.length) {
|
||||
// UPDATE statement does not contain columns to SET
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// "when" conditions should be contained in the table
|
||||
return when.reduce((acc, p) => acc && table.columnsByName[p.columnName] !== undefined, true);
|
||||
});
|
||||
|
||||
if (filteredTables.length === 0) {
|
||||
let message = `No table matches (all PKs and columns to set have to be specified) fields: [${
|
||||
propertiesInfo.map(p => p.columnName)}]`;
|
||||
|
||||
if (when.length > 0) {
|
||||
message += `; condition: [${when.map(p => p.columnName)}]`;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return filteredTables;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tables that should be used to execute the DELETE query.
|
||||
* @param {Client} client
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} when
|
||||
* @return {Promise<Array<TableMetadata>>} A promise that resolves to an Array of tables.
|
||||
*/
|
||||
static getForDelete(client, info, propertiesInfo, when) {
|
||||
return Promise.all(info.tables.filter(t => !t.isView).map(t => client.metadata.getTable(info.keyspace, t.name)))
|
||||
.then(tables => {
|
||||
const filteredTables = tables
|
||||
.filter((table, i) => {
|
||||
if (table === null) {
|
||||
throw new Error(`Table "${info.tables[i].name}" could not be retrieved`);
|
||||
}
|
||||
|
||||
// All partition and clustering keys from the table should be included in the document
|
||||
const keyNames = table.partitionKeys.concat(table.clusteringKeys).map(k => k.name);
|
||||
const columns = propertiesInfo.map(p => p.columnName);
|
||||
|
||||
for (let i = 0; i < keyNames.length; i++) {
|
||||
if (columns.indexOf(keyNames[i]) === -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// "when" conditions should be contained in the table
|
||||
return when.reduce((acc, p) => acc && table.columnsByName[p.columnName] !== undefined, true);
|
||||
});
|
||||
|
||||
if (filteredTables.length === 0) {
|
||||
let message = `No table matches (all PKs have to be specified) fields: [${
|
||||
propertiesInfo.map(p => p.columnName)}]`;
|
||||
|
||||
if (when.length > 0) {
|
||||
message += `; condition: [${when.map(p => p.columnName)}]`;
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return filteredTables;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function contains(arr, fn) {
|
||||
return arr.filter(fn).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount of matches for a given key
|
||||
* @private
|
||||
* @param {Array} keys
|
||||
* @param {Array} propertiesInfo
|
||||
*/
|
||||
function keysAreIncluded(keys, propertiesInfo) {
|
||||
if (keys.length === 0) {
|
||||
return keyMatches.all;
|
||||
}
|
||||
|
||||
// Filtering by name might look slow / ineffective to using hash maps
|
||||
// but we expect `keys` and `propertiesInfo` to contain only few items
|
||||
const matches = propertiesInfo.reduce((acc, p) => acc + (contains(keys, k => p.columnName === k.name) ? 1 : 0), 0);
|
||||
if (matches === 0) {
|
||||
return keyMatches.none;
|
||||
}
|
||||
|
||||
return matches === keys.length ? keyMatches.all : keyMatches.some;
|
||||
}
|
||||
|
||||
function hasStaticColumn(table) {
|
||||
return staticColumnCount(table) > 0;
|
||||
}
|
||||
|
||||
function staticColumnCount(table) {
|
||||
return table.columns.reduce((acc, column) => acc + (column.isStatic ? 1 : 0), 0);
|
||||
}
|
||||
|
||||
module.exports = ObjectSelector;
|
154
node_modules/cassandra-driver/lib/mapping/q.js
generated
vendored
Normal file
154
node_modules/cassandra-driver/lib/mapping/q.js
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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 errors = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents a CQL query operator, like >=, IN, <, ...
|
||||
* @ignore
|
||||
*/
|
||||
class QueryOperator {
|
||||
/**
|
||||
* Creates a new instance of <code>QueryOperator</code>.
|
||||
* @param {String} key
|
||||
* @param value
|
||||
* @param [hasChildValues]
|
||||
* @param [isInOperator]
|
||||
*/
|
||||
constructor(key, value, hasChildValues, isInOperator) {
|
||||
/**
|
||||
* The CQL key representing the operator
|
||||
* @type {string}
|
||||
*/
|
||||
this.key = key;
|
||||
|
||||
/**
|
||||
* The value to be used as parameter.
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* Determines whether a query operator can have child values or operators (AND, OR)
|
||||
*/
|
||||
this.hasChildValues = hasChildValues;
|
||||
|
||||
/**
|
||||
* Determines whether this instance represents CQL "IN" operator.
|
||||
*/
|
||||
this.isInOperator = isInOperator;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a CQL assignment operation, like col = col + x.
|
||||
* @ignore
|
||||
*/
|
||||
class QueryAssignment {
|
||||
constructor(sign, value, inverted) {
|
||||
/**
|
||||
* Gets the sign of the assignment operation.
|
||||
*/
|
||||
this.sign = sign;
|
||||
|
||||
/**
|
||||
* Gets the value to be assigned.
|
||||
*/
|
||||
this.value = value;
|
||||
|
||||
/**
|
||||
* Determines whether the assignment should be inverted (prepends), e.g: col = x + col
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.inverted = !!inverted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains functions that represents operators in a query.
|
||||
* @alias module:mapping~q
|
||||
* @type {Object}
|
||||
* @property {function} in_ Represents the CQL operator "IN".
|
||||
* @property {function} gt Represents the CQL operator greater than ">".
|
||||
* @property {function} gte Represents the CQL operator greater than or equals to ">=" .
|
||||
* @property {function} lt Represents the CQL operator less than "<" .
|
||||
* @property {function} lte Represents the CQL operator less than or equals to "<=" .
|
||||
* @property {function} notEq Represents the CQL operator not equals to "!=" .
|
||||
* @property {function} and When applied to a property, it represents two CQL conditions on the same column separated
|
||||
* by the logical AND operator, e.g: "col1 >= x col < y"
|
||||
* @property {function} incr Represents the CQL increment assignment used for counters, e.g: "col = col + x"
|
||||
* @property {function} decr Represents the CQL decrement assignment used for counters, e.g: "col = col - x"
|
||||
* @property {function} append Represents the CQL append assignment used for collections, e.g: "col = col + x"
|
||||
* @property {function} prepend Represents the CQL prepend assignment used for lists, e.g: "col = x + col"
|
||||
* @property {function} remove Represents the CQL remove assignment used for collections, e.g: "col = col - x"
|
||||
*/
|
||||
const q = {
|
||||
in_: function in_(arr) {
|
||||
if (!Array.isArray(arr)) {
|
||||
throw new errors.ArgumentError('IN operator supports only Array values');
|
||||
}
|
||||
return new QueryOperator('IN', arr, false, true);
|
||||
},
|
||||
|
||||
gt: function gt(value) {
|
||||
return new QueryOperator('>', value);
|
||||
},
|
||||
|
||||
gte: function gte(value) {
|
||||
return new QueryOperator('>=', value);
|
||||
},
|
||||
|
||||
lt: function lt(value) {
|
||||
return new QueryOperator('<', value);
|
||||
},
|
||||
|
||||
lte: function lte(value) {
|
||||
return new QueryOperator('<=', value);
|
||||
},
|
||||
|
||||
notEq: function notEq(value) {
|
||||
return new QueryOperator('!=', value);
|
||||
},
|
||||
|
||||
and: function (condition1, condition2) {
|
||||
return new QueryOperator('AND', [ condition1, condition2 ], true);
|
||||
},
|
||||
|
||||
incr: function incr(value) {
|
||||
return new QueryAssignment('+', value);
|
||||
},
|
||||
|
||||
decr: function decr(value) {
|
||||
return new QueryAssignment('-', value);
|
||||
},
|
||||
|
||||
append: function append(value) {
|
||||
return new QueryAssignment('+', value);
|
||||
},
|
||||
|
||||
prepend: function prepend(value) {
|
||||
return new QueryAssignment('+', value, true);
|
||||
},
|
||||
|
||||
remove: function remove(value) {
|
||||
return new QueryAssignment('-', value);
|
||||
}
|
||||
};
|
||||
|
||||
exports.q = q;
|
||||
exports.QueryAssignment = QueryAssignment;
|
||||
exports.QueryOperator = QueryOperator;
|
446
node_modules/cassandra-driver/lib/mapping/query-generator.js
generated
vendored
Normal file
446
node_modules/cassandra-driver/lib/mapping/query-generator.js
generated
vendored
Normal file
@@ -0,0 +1,446 @@
|
||||
/*
|
||||
* 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 vm = require('vm');
|
||||
const qModule = require('./q');
|
||||
const QueryOperator = qModule.QueryOperator;
|
||||
const QueryAssignment = qModule.QueryAssignment;
|
||||
const types = require('../types');
|
||||
const dataTypes = types.dataTypes;
|
||||
|
||||
const vmFileName = 'gen-param-getter.js';
|
||||
|
||||
/**
|
||||
* Provides methods to generate a query and parameter handlers.
|
||||
* @ignore
|
||||
*/
|
||||
class QueryGenerator {
|
||||
/**
|
||||
* Gets the SELECT query given the doc.
|
||||
* @param {String} tableName
|
||||
* @param {String} keyspace
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} fieldsInfo
|
||||
* @param {Array} orderByColumns
|
||||
* @param {Number|undefined} limit
|
||||
* @return {string}
|
||||
*/
|
||||
static getSelect(tableName, keyspace, propertiesInfo, fieldsInfo, orderByColumns, limit) {
|
||||
let query = 'SELECT ';
|
||||
query += fieldsInfo.length > 0 ? fieldsInfo.map(p => p.columnName).join(', ') : '*';
|
||||
query += ` FROM ${keyspace}.${tableName}`;
|
||||
|
||||
if (propertiesInfo.length > 0) {
|
||||
query += ' WHERE ';
|
||||
query += QueryGenerator._getConditionWithOperators(propertiesInfo);
|
||||
}
|
||||
|
||||
if (orderByColumns.length > 0) {
|
||||
query += ' ORDER BY ';
|
||||
query += orderByColumns.map(order => order[0] + ' ' + order[1]).join(', ');
|
||||
}
|
||||
|
||||
if (typeof limit === 'number') {
|
||||
query += ' LIMIT ?';
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
static selectParamsGetter(propertiesInfo, limit) {
|
||||
let scriptText = '(function getParametersSelect(doc, docInfo, mappingInfo) {\n';
|
||||
scriptText += ' return [';
|
||||
|
||||
scriptText += QueryGenerator._valueGetterExpression(propertiesInfo);
|
||||
|
||||
if (typeof limit === 'number') {
|
||||
if (propertiesInfo.length > 0) {
|
||||
scriptText += ', ';
|
||||
}
|
||||
scriptText += `docInfo['limit']`;
|
||||
}
|
||||
|
||||
// Finish return statement
|
||||
scriptText += '];\n})';
|
||||
|
||||
const script = new vm.Script(scriptText, { filename: vmFileName });
|
||||
return script.runInThisContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the INSERT query and function to obtain the parameters, given the doc.
|
||||
* @param {TableMetadata} table
|
||||
* @param {String} keyspace
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Object} docInfo
|
||||
* @param {Boolean|undefined} ifNotExists
|
||||
* @return {{query: String, paramsGetter: Function, isIdempotent: Boolean}}
|
||||
*/
|
||||
static getInsert(table, keyspace, propertiesInfo, docInfo, ifNotExists) {
|
||||
const ttl = docInfo && docInfo.ttl;
|
||||
|
||||
// Not all columns are contained in the table
|
||||
const filteredPropertiesInfo = propertiesInfo
|
||||
.filter(pInfo => table.columnsByName[pInfo.columnName] !== undefined);
|
||||
|
||||
return ({
|
||||
query: QueryGenerator._getInsertQuery(table.name, keyspace, filteredPropertiesInfo, ifNotExists, ttl),
|
||||
paramsGetter: QueryGenerator._insertParamsGetter(filteredPropertiesInfo, docInfo),
|
||||
isIdempotent: !ifNotExists
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the query for an insert statement.
|
||||
* @param {String} tableName
|
||||
* @param {String} keyspace
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Boolean} ifNotExists
|
||||
* @param {Number|undefined} ttl
|
||||
* @return {String}
|
||||
*/
|
||||
static _getInsertQuery(tableName, keyspace, propertiesInfo, ifNotExists, ttl) {
|
||||
let query = `INSERT INTO ${keyspace}.${tableName} (`;
|
||||
query += propertiesInfo.map(pInfo => pInfo.columnName).join(', ');
|
||||
query += ') VALUES (';
|
||||
query += propertiesInfo.map(() => '?').join(', ');
|
||||
query += ')';
|
||||
|
||||
if (ifNotExists === true) {
|
||||
query += ' IF NOT EXISTS';
|
||||
}
|
||||
|
||||
if (typeof ttl === 'number') {
|
||||
query += ' USING TTL ?';
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
static _insertParamsGetter(propertiesInfo, docInfo) {
|
||||
let scriptText = '(function getParametersInsert(doc, docInfo, mappingInfo) {\n';
|
||||
scriptText += ' return [';
|
||||
|
||||
scriptText += QueryGenerator._valueGetterExpression(propertiesInfo);
|
||||
|
||||
if (docInfo && typeof docInfo.ttl === 'number') {
|
||||
scriptText += `, docInfo['ttl']`;
|
||||
}
|
||||
|
||||
// Finish return statement
|
||||
scriptText += '];\n})';
|
||||
|
||||
const script = new vm.Script(scriptText, { filename: vmFileName });
|
||||
return script.runInThisContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the UPDATE query and function to obtain the parameters, given the doc.
|
||||
* @param {TableMetadata} table
|
||||
* @param {String} keyspace
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Object} docInfo
|
||||
* @param {Array} when
|
||||
* @param {Boolean|undefined} ifExists
|
||||
* @return {{query: String, paramsGetter: Function, isIdempotent: Boolean, isCounter}}
|
||||
*/
|
||||
static getUpdate(table, keyspace, propertiesInfo, docInfo, when, ifExists) {
|
||||
const ttl = docInfo && docInfo.ttl;
|
||||
const primaryKeys = new Set(table.partitionKeys.concat(table.clusteringKeys).map(c => c.name));
|
||||
let isIdempotent = true;
|
||||
let isCounter = false;
|
||||
|
||||
// Not all columns are contained in the table
|
||||
const filteredPropertiesInfo = propertiesInfo.filter(pInfo => {
|
||||
const column = table.columnsByName[pInfo.columnName];
|
||||
if (column === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (column.type.code === dataTypes.list && pInfo.value instanceof QueryAssignment) {
|
||||
// Its not idempotent when list append/prepend
|
||||
isIdempotent = false;
|
||||
} else if (column.type.code === dataTypes.counter) {
|
||||
// Any update on a counter table is not idempotent
|
||||
isIdempotent = false;
|
||||
isCounter = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
query: QueryGenerator._getUpdateQuery(
|
||||
table.name, keyspace, primaryKeys, filteredPropertiesInfo, when, ifExists, ttl),
|
||||
isIdempotent: isIdempotent && when.length === 0 && !ifExists,
|
||||
paramsGetter: QueryGenerator._updateParamsGetter(primaryKeys, filteredPropertiesInfo, when, ttl),
|
||||
isCounter
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the query for an UPDATE statement.
|
||||
* @param {String} tableName
|
||||
* @param {String} keyspace
|
||||
* @param {Set} primaryKeys
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Object} when
|
||||
* @param {Boolean} ifExists
|
||||
* @param {Number|undefined} ttl
|
||||
*/
|
||||
static _getUpdateQuery(tableName, keyspace, primaryKeys, propertiesInfo, when, ifExists, ttl) {
|
||||
let query = `UPDATE ${keyspace}.${tableName} `;
|
||||
|
||||
if (typeof ttl === 'number') {
|
||||
query += 'USING TTL ? ';
|
||||
}
|
||||
|
||||
query += 'SET ';
|
||||
|
||||
query += propertiesInfo
|
||||
.filter(p => !primaryKeys.has(p.columnName))
|
||||
.map(p => {
|
||||
if (p.value instanceof QueryAssignment) {
|
||||
if (p.value.inverted) {
|
||||
// e.g: prepend "col1 = ? + col1"
|
||||
return `${p.columnName} = ? ${p.value.sign} ${p.columnName}`;
|
||||
}
|
||||
// e.g: increment "col1 = col1 + ?"
|
||||
return `${p.columnName} = ${p.columnName} ${p.value.sign} ?`;
|
||||
}
|
||||
|
||||
return p.columnName + ' = ?';
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
query += ' WHERE ';
|
||||
query += propertiesInfo.filter(p => primaryKeys.has(p.columnName)).map(p => p.columnName + ' = ?').join(' AND ');
|
||||
|
||||
if (ifExists === true) {
|
||||
query += ' IF EXISTS';
|
||||
}
|
||||
else if (when.length > 0) {
|
||||
query += ' IF ' + QueryGenerator._getConditionWithOperators(when);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function to obtain the parameter values from a doc for an UPDATE statement.
|
||||
* @param {Set} primaryKeys
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} when
|
||||
* @param {Number|undefined} ttl
|
||||
* @returns {Function}
|
||||
*/
|
||||
static _updateParamsGetter(primaryKeys, propertiesInfo, when, ttl) {
|
||||
let scriptText = '(function getParametersUpdate(doc, docInfo, mappingInfo) {\n';
|
||||
scriptText += ' return [';
|
||||
|
||||
if (typeof ttl === 'number') {
|
||||
scriptText += `docInfo['ttl'], `;
|
||||
}
|
||||
|
||||
// Assignment clause
|
||||
scriptText += QueryGenerator._assignmentGetterExpression(propertiesInfo.filter(p => !primaryKeys.has(p.columnName)));
|
||||
scriptText += ', ';
|
||||
|
||||
// Where clause
|
||||
scriptText += QueryGenerator._valueGetterExpression(propertiesInfo.filter(p => primaryKeys.has(p.columnName)));
|
||||
|
||||
// Condition clause
|
||||
if (when.length > 0) {
|
||||
scriptText += ', ' + QueryGenerator._valueGetterExpression(when, 'docInfo.when');
|
||||
}
|
||||
|
||||
// Finish return statement
|
||||
scriptText += '];\n})';
|
||||
|
||||
const script = new vm.Script(scriptText, { filename: vmFileName });
|
||||
return script.runInThisContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the DELETE query and function to obtain the parameters, given the doc.
|
||||
* @param {TableMetadata} table
|
||||
* @param {String} keyspace
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Object} docInfo
|
||||
* @param {Array} when
|
||||
* @param {Boolean|undefined} ifExists
|
||||
* @return {{query: String, paramsGetter: Function, isIdempotent}}
|
||||
*/
|
||||
static getDelete(table, keyspace, propertiesInfo, docInfo, when, ifExists) {
|
||||
const deleteOnlyColumns = docInfo && docInfo.deleteOnlyColumns;
|
||||
const primaryKeys = new Set(table.partitionKeys.concat(table.clusteringKeys).map(c => c.name));
|
||||
|
||||
const filteredPropertiesInfo = propertiesInfo
|
||||
.filter(pInfo => table.columnsByName[pInfo.columnName] !== undefined);
|
||||
|
||||
|
||||
return ({
|
||||
query: QueryGenerator._getDeleteQuery(
|
||||
table.name, keyspace, primaryKeys, filteredPropertiesInfo, when, ifExists, deleteOnlyColumns),
|
||||
paramsGetter: QueryGenerator._deleteParamsGetter(primaryKeys, filteredPropertiesInfo, when),
|
||||
isIdempotent: when.length === 0 && !ifExists
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the query for an UPDATE statement.
|
||||
* @param {String} tableName
|
||||
* @param {String} keyspace
|
||||
* @param {Set} primaryKeys
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} when
|
||||
* @param {Boolean} ifExists
|
||||
* @param {Boolean} deleteOnlyColumns
|
||||
* @private
|
||||
* @return {String}
|
||||
*/
|
||||
static _getDeleteQuery(tableName, keyspace, primaryKeys, propertiesInfo, when, ifExists, deleteOnlyColumns) {
|
||||
let query = 'DELETE';
|
||||
|
||||
if (deleteOnlyColumns) {
|
||||
const columnsToDelete = propertiesInfo.filter(p => !primaryKeys.has(p.columnName))
|
||||
.map(p => p.columnName)
|
||||
.join(', ');
|
||||
|
||||
if (columnsToDelete !== '') {
|
||||
query += ' ' + columnsToDelete;
|
||||
}
|
||||
}
|
||||
|
||||
query += ` FROM ${keyspace}.${tableName} WHERE `;
|
||||
query += propertiesInfo.filter(p => primaryKeys.has(p.columnName)).map(p => p.columnName + ' = ?').join(' AND ');
|
||||
|
||||
if (ifExists === true) {
|
||||
query += ' IF EXISTS';
|
||||
}
|
||||
else if (when.length > 0) {
|
||||
query += ' IF ' + QueryGenerator._getConditionWithOperators(when);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
/**
|
||||
* Returns a function to obtain the parameter values from a doc for an UPDATE statement.
|
||||
* @param {Set} primaryKeys
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {Array} when
|
||||
* @returns {Function}
|
||||
*/
|
||||
static _deleteParamsGetter(primaryKeys, propertiesInfo, when) {
|
||||
let scriptText = '(function getParametersDelete(doc, docInfo, mappingInfo) {\n';
|
||||
scriptText += ' return [';
|
||||
|
||||
// Where clause
|
||||
scriptText += QueryGenerator._valueGetterExpression(propertiesInfo.filter(p => primaryKeys.has(p.columnName)));
|
||||
|
||||
// Condition clause
|
||||
if (when.length > 0) {
|
||||
scriptText += ', ' + QueryGenerator._valueGetterExpression(when, 'docInfo.when');
|
||||
}
|
||||
|
||||
// Finish return statement
|
||||
scriptText += '];\n})';
|
||||
|
||||
const script = new vm.Script(scriptText, { filename: vmFileName });
|
||||
return script.runInThisContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string containing the doc properties to get.
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {String} [objectName='doc']
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
static _valueGetterExpression(propertiesInfo, objectName) {
|
||||
objectName = objectName || 'doc';
|
||||
|
||||
return propertiesInfo
|
||||
.map(p =>
|
||||
QueryGenerator._valueGetterSingle(`${objectName}['${p.propertyName}']`, p.propertyName, p.value, p.fromModel))
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
static _valueGetterSingle(prefix, propName, value, fromModelFn) {
|
||||
let valueGetter = prefix;
|
||||
|
||||
if (value instanceof QueryOperator) {
|
||||
if (value.hasChildValues) {
|
||||
return `${QueryGenerator._valueGetterSingle(`${prefix}.value[0]`, propName, value.value[0], fromModelFn)}` +
|
||||
`, ${QueryGenerator._valueGetterSingle(`${prefix}.value[1]`, propName, value.value[1], fromModelFn)}`;
|
||||
}
|
||||
|
||||
valueGetter = `${prefix}.value`;
|
||||
|
||||
if (value.isInOperator && fromModelFn) {
|
||||
// Transform each individual value
|
||||
return `${valueGetter}.map(v => ${QueryGenerator._getMappingFunctionCall(propName, 'v')})`;
|
||||
}
|
||||
}
|
||||
|
||||
return !fromModelFn ? valueGetter : QueryGenerator._getMappingFunctionCall(propName, valueGetter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a string containing the doc properties to SET, considering QueryAssignment instances.
|
||||
* @param {Array} propertiesInfo
|
||||
* @param {String} [prefix='doc']
|
||||
* @return {string}
|
||||
* @private
|
||||
*/
|
||||
static _assignmentGetterExpression(propertiesInfo, prefix) {
|
||||
prefix = prefix || 'doc';
|
||||
|
||||
return propertiesInfo
|
||||
.map(p => {
|
||||
const valueGetter = `${prefix}['${p.propertyName}']${p.value instanceof QueryAssignment ? '.value' : ''}`;
|
||||
if (p.fromModel) {
|
||||
return QueryGenerator._getMappingFunctionCall(p.propertyName, valueGetter);
|
||||
}
|
||||
return valueGetter;
|
||||
})
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
static _getConditionWithOperators(propertiesInfo) {
|
||||
return propertiesInfo
|
||||
.map(p => QueryGenerator._getSingleCondition(p.columnName, p.value))
|
||||
.join(' AND ');
|
||||
}
|
||||
|
||||
static _getMappingFunctionCall(propName, valueGetter) {
|
||||
return `mappingInfo.getFromModelFn('${propName}')(${valueGetter})`;
|
||||
}
|
||||
|
||||
static _getSingleCondition(columnName, value) {
|
||||
if (value instanceof QueryOperator) {
|
||||
if (value.hasChildValues) {
|
||||
return `${QueryGenerator._getSingleCondition(columnName, value.value[0])}` +
|
||||
` ${value.key} ${QueryGenerator._getSingleCondition(columnName, value.value[1])}`;
|
||||
}
|
||||
return `${columnName} ${value.key} ?`;
|
||||
}
|
||||
return `${columnName} = ?`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = QueryGenerator;
|
112
node_modules/cassandra-driver/lib/mapping/result-mapper.js
generated
vendored
Normal file
112
node_modules/cassandra-driver/lib/mapping/result-mapper.js
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 vm = require('vm');
|
||||
const utils = require('../utils');
|
||||
const types = require('../types');
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
class ResultMapper {
|
||||
/**
|
||||
* Gets a generated function to adapt the row to a document.
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {ResultSet} rs
|
||||
* @returns {Function}
|
||||
*/
|
||||
static getSelectAdapter(info, rs) {
|
||||
const columns = rs.columns;
|
||||
if (!columns) {
|
||||
throw new Error('Expected ROWS result obtained VOID');
|
||||
}
|
||||
|
||||
let scriptText = '(function rowAdapter(row, info) {\n' +
|
||||
' const item = info.newInstance();\n';
|
||||
|
||||
for (const c of columns) {
|
||||
scriptText += ` item['${info.getPropertyName(c.name)}'] = `;
|
||||
|
||||
if (!info.getToModelFn(c.name)) {
|
||||
scriptText += `row['${c.name}'];\n`;
|
||||
} else {
|
||||
scriptText += `info.getToModelFn('${c.name}')(row['${c.name}']);\n`;
|
||||
}
|
||||
}
|
||||
|
||||
scriptText += ' return item;\n})';
|
||||
|
||||
const script = new vm.Script(scriptText, { filename: 'gen-result-mapper.js'});
|
||||
return script.runInThisContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function used to adapt VOID results or conditional updates.
|
||||
* @param {ResultSet} rs
|
||||
* @returns {Function}
|
||||
*/
|
||||
static getMutationAdapter(rs) {
|
||||
if (rs.columns === null) {
|
||||
// VOID result
|
||||
return utils.noop;
|
||||
}
|
||||
|
||||
if (
|
||||
rs.columns.length === 1 && rs.columns[0].name === '[applied]' &&
|
||||
rs.columns[0].type.code === types.dataTypes.boolean) {
|
||||
return utils.noop;
|
||||
}
|
||||
|
||||
return ResultMapper._getConditionalRowAdapter(rs);
|
||||
}
|
||||
|
||||
static _getConditionalRowAdapter(rs) {
|
||||
return (function conditionalRowAdapter(row, info) {
|
||||
const item = info.newInstance();
|
||||
|
||||
// Skip the first column ("[applied]")
|
||||
for (let i = 1; i < rs.columns.length; i++) {
|
||||
const c = rs.columns[i];
|
||||
item[info.getPropertyName(c.name)] = row[c.name];
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {ResultSet} rs
|
||||
* @returns {{canCache: Boolean, fn: Function}}
|
||||
*/
|
||||
static getCustomQueryAdapter(info, rs) {
|
||||
if (rs.columns === null || rs.columns.length === 0) {
|
||||
// VOID result
|
||||
return { canCache: true, fn: utils.noop };
|
||||
}
|
||||
|
||||
if (rs.columns[0].name === '[applied]' && rs.columns[0].type.code === types.dataTypes.boolean) {
|
||||
// Conditional update results adapter functions should not be cached
|
||||
return { canCache: false, fn: ResultMapper._getConditionalRowAdapter(rs) };
|
||||
}
|
||||
|
||||
return { canCache: true, fn: ResultMapper.getSelectAdapter(info, rs) };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ResultMapper;
|
136
node_modules/cassandra-driver/lib/mapping/result.js
generated
vendored
Normal file
136
node_modules/cassandra-driver/lib/mapping/result.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 util = require('util');
|
||||
const utils = require('../utils');
|
||||
const inspectMethod = util.inspect.custom || 'inspect';
|
||||
|
||||
/**
|
||||
* Represents the result of an execution as an iterable of objects in the Mapper.
|
||||
* @alias module:mapping~Result
|
||||
*/
|
||||
class Result {
|
||||
/**
|
||||
* Creates a new instance of Result.
|
||||
* @param {ResultSet} rs
|
||||
* @param {ModelMappingInfo} info
|
||||
* @param {Function} rowAdapter
|
||||
*/
|
||||
constructor(rs, info, rowAdapter) {
|
||||
this._rs = rs;
|
||||
this._info = info;
|
||||
this._rowAdapter = rowAdapter;
|
||||
|
||||
/**
|
||||
* When there is a single cell containing the result of the a LWT operation, hide the result from the user.
|
||||
* @private
|
||||
*/
|
||||
this._isEmptyLwt = (rs.columns !== null
|
||||
&& rs.columns.length === 1 && this._rs.rowLength === 1 && rs.columns[0].name === '[applied]');
|
||||
|
||||
/**
|
||||
* Gets the amount of the documents contained in this Result instance.
|
||||
* <p>
|
||||
* When the results are paged, it returns the length of the current paged results not the total amount of
|
||||
* rows in the table matching the query.
|
||||
* </p>
|
||||
* @type {Number}
|
||||
*/
|
||||
this.length = this._isEmptyLwt ? 0 : (rs.rowLength || 0);
|
||||
|
||||
/**
|
||||
* A string token representing the current page state of query.
|
||||
* <p>
|
||||
* When provided, it can be used in the following executions to continue paging and retrieve the remained of the
|
||||
* result for the query.
|
||||
* </p>
|
||||
* @type {String}
|
||||
* @default null
|
||||
*/
|
||||
this.pageState = rs.pageState;
|
||||
}
|
||||
|
||||
/**
|
||||
* When this instance is the result of a conditional update query, it returns whether it was successful.
|
||||
* Otherwise, it returns <code>true</code>.
|
||||
* <p>
|
||||
* For consistency, this method always returns <code>true</code> for non-conditional queries (although there is
|
||||
* no reason to call the method in that case). This is also the case for conditional DDL statements
|
||||
* (CREATE KEYSPACE... IF NOT EXISTS, CREATE TABLE... IF NOT EXISTS), for which the server doesn't return
|
||||
* information whether it was applied or not.
|
||||
* </p>
|
||||
*/
|
||||
wasApplied() {
|
||||
return this._rs.wasApplied();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first document in this result or null when the result is empty.
|
||||
*/
|
||||
first() {
|
||||
if (!this._rs.rowLength || this._isEmptyLwt) {
|
||||
return null;
|
||||
}
|
||||
return this._rowAdapter(this._rs.rows[0], this._info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Iterator object that contains the document values.
|
||||
*/
|
||||
*[Symbol.iterator]() {
|
||||
if (this._isEmptyLwt) {
|
||||
// Empty iterator
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._rs.rows.length; i++) {
|
||||
yield this._rowAdapter(this._rs.rows[i], this._info);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the current instance to an Array of documents.
|
||||
* @return {Array<Object>}
|
||||
*/
|
||||
toArray() {
|
||||
if (this._isEmptyLwt || !this._rs.rows) {
|
||||
return utils.emptyArray;
|
||||
}
|
||||
|
||||
return this._rs.rows.map(row => this._rowAdapter(row, this._info));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a provided function once per result element.
|
||||
* @param {Function} callback Function to execute for each element, taking two arguments: currentValue and index.
|
||||
* @param {Object} [thisArg] Value to use as <code>this</code> when executing callback.
|
||||
*/
|
||||
forEach(callback, thisArg) {
|
||||
let index = 0;
|
||||
thisArg = thisArg || this;
|
||||
for (const doc of this) {
|
||||
callback.call(thisArg, doc, index++);
|
||||
}
|
||||
}
|
||||
|
||||
[inspectMethod]() {
|
||||
return this.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Result;
|
122
node_modules/cassandra-driver/lib/mapping/table-mappings.js
generated
vendored
Normal file
122
node_modules/cassandra-driver/lib/mapping/table-mappings.js
generated
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* Contains a set of methods to represent a row into a document and a document into a row.
|
||||
* @alias module:mapping~TableMappings
|
||||
* @interface
|
||||
*/
|
||||
class TableMappings {
|
||||
/**
|
||||
* Method that is called by the mapper to create the instance of the document.
|
||||
* @return {Object}
|
||||
*/
|
||||
newObjectInstance() {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the column based on the document property name.
|
||||
* @param {String} propName The name of the property.
|
||||
* @returns {String}
|
||||
*/
|
||||
getColumnName(propName) {
|
||||
return propName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the document property based on the column name.
|
||||
* @param {String} columnName The name of the column.
|
||||
* @returns {String}
|
||||
*/
|
||||
getPropertyName(columnName) {
|
||||
return columnName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A [TableMappings]{@link module:mapping~TableMappings} implementation that converts CQL column names in all-lowercase
|
||||
* identifiers with underscores (snake case) to camel case (initial lowercase letter) property names.
|
||||
* <p>
|
||||
* The conversion is performed without any checks for the source format, you should make sure that the source
|
||||
* format is snake case for CQL identifiers and camel case for properties.
|
||||
* </p>
|
||||
* @alias module:mapping~UnderscoreCqlToCamelCaseMappings
|
||||
* @implements {module:mapping~TableMappings}
|
||||
*/
|
||||
class UnderscoreCqlToCamelCaseMappings extends TableMappings {
|
||||
/**
|
||||
* Creates a new instance of {@link UnderscoreCqlToCamelCaseMappings}
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a property name in camel case to snake case.
|
||||
* @param {String} propName Name of the property to convert to snake case.
|
||||
* @return {String}
|
||||
*/
|
||||
getColumnName(propName) {
|
||||
return propName.replace(/[a-z][A-Z]/g, (match, offset) => match.charAt(0) + '_' + match.charAt(1)).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a column name in snake case to camel case.
|
||||
* @param {String} columnName The column name to convert to camel case.
|
||||
* @return {String}
|
||||
*/
|
||||
getPropertyName(columnName) {
|
||||
return columnName.replace(/_[a-z]/g, (match, offset) => ((offset === 0) ? match : match.substr(1).toUpperCase()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of [TableMappings]{@link module:mapping~TableMappings} that doesn't perform any conversion.
|
||||
* @alias module:mapping~DefaultTableMappings
|
||||
* @implements {module:mapping~TableMappings}
|
||||
*/
|
||||
class DefaultTableMappings extends TableMappings {
|
||||
/**
|
||||
* Creates a new instance of {@link DefaultTableMappings}.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getColumnName(propName) {
|
||||
return super.getColumnName(propName);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
getPropertyName(columnName) {
|
||||
return super.getPropertyName(columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new object instance, using object initializer.
|
||||
*/
|
||||
newObjectInstance() {
|
||||
return super.newObjectInstance();
|
||||
}
|
||||
}
|
||||
|
||||
exports.TableMappings = TableMappings;
|
||||
exports.UnderscoreCqlToCamelCaseMappings = UnderscoreCqlToCamelCaseMappings;
|
||||
exports.DefaultTableMappings = DefaultTableMappings;
|
151
node_modules/cassandra-driver/lib/mapping/tree.js
generated
vendored
Normal file
151
node_modules/cassandra-driver/lib/mapping/tree.js
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 EventEmitter = require('events');
|
||||
|
||||
/**
|
||||
* Represents a tree node where the key is composed by 1 or more strings.
|
||||
* @ignore
|
||||
*/
|
||||
class Node extends EventEmitter {
|
||||
/**
|
||||
* Creates a new instance of {@link Node}.
|
||||
* @param {Array<String>} key
|
||||
* @param {Object} value
|
||||
* @param {Array} [edges]
|
||||
*/
|
||||
constructor(key, value, edges) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.edges = edges || [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A radix tree where each node contains a key, a value and edges.
|
||||
* @ignore
|
||||
*/
|
||||
class Tree extends Node {
|
||||
constructor() {
|
||||
super([], null);
|
||||
this.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the existing item in the tree or creates a new one with the value provided by valueHandler
|
||||
* @param {Iterator} keyIterator
|
||||
* @param {Function} valueHandler
|
||||
* @return {Object}
|
||||
*/
|
||||
getOrCreate(keyIterator, valueHandler) {
|
||||
if (typeof keyIterator.next !== 'function') {
|
||||
keyIterator = keyIterator[Symbol.iterator]();
|
||||
}
|
||||
let node = this;
|
||||
let isMatch = false;
|
||||
let item = keyIterator.next();
|
||||
while (true) {
|
||||
let newBranch;
|
||||
// Check node keys at position 1 and above
|
||||
for (let i = 1; i < node.key.length; i++) {
|
||||
if (item.done || node.key[i] !== item.value) {
|
||||
// We should branch out
|
||||
newBranch = this._createBranch(node, i, item.done, valueHandler);
|
||||
break;
|
||||
}
|
||||
item = keyIterator.next();
|
||||
}
|
||||
|
||||
if (item.done) {
|
||||
isMatch = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (newBranch !== undefined) {
|
||||
break;
|
||||
}
|
||||
|
||||
const edges = node.edges;
|
||||
let nextNode;
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
const e = edges[i];
|
||||
if (e.key[0] === item.value) {
|
||||
// its a match
|
||||
nextNode = e;
|
||||
item = keyIterator.next();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextNode === undefined) {
|
||||
// Current node is the root for a new leaf
|
||||
break;
|
||||
}
|
||||
else {
|
||||
node = nextNode;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMatch) {
|
||||
// Create using "node" as the root
|
||||
const value = valueHandler();
|
||||
node.edges.push(new Node(iteratorToArray(item.value, keyIterator), value));
|
||||
this._onItemAdded();
|
||||
return value;
|
||||
}
|
||||
if (node.value === null && node.edges.length > 0) {
|
||||
node.value = valueHandler();
|
||||
}
|
||||
return node.value;
|
||||
}
|
||||
|
||||
_createBranch(node, index, useNewValue, valueHandler) {
|
||||
const newBranch = new Node(node.key.slice(index), node.value, node.edges);
|
||||
node.key = node.key.slice(0, index);
|
||||
node.edges = [ newBranch ];
|
||||
if (useNewValue) {
|
||||
// The previous node value has moved to a leaf
|
||||
// The node containing the new leaf should use the new value
|
||||
node.value = valueHandler();
|
||||
this._onItemAdded();
|
||||
}
|
||||
else {
|
||||
// Clear the value as it was copied in the branch
|
||||
node.value = null;
|
||||
}
|
||||
return newBranch;
|
||||
}
|
||||
|
||||
_onItemAdded() {
|
||||
this.length++;
|
||||
this.emit('add', this.length);
|
||||
}
|
||||
}
|
||||
|
||||
function iteratorToArray(value, iterator) {
|
||||
const values = [ value ];
|
||||
let item = iterator.next();
|
||||
while (!item.done) {
|
||||
values.push(item.value);
|
||||
item = iterator.next();
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
module.exports = Tree;
|
Reference in New Issue
Block a user