-
Notifications
You must be signed in to change notification settings - Fork 206
Expand file tree
/
Copy pathmapper.ts
More file actions
195 lines (176 loc) · 8.17 KB
/
Copy pathmapper.ts
File metadata and controls
195 lines (176 loc) · 8.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/*
* 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;