| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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; |