UNPKG

7.89 kBJavaScriptView Raw
1/*
2 * Copyright DataStax, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16'use strict';
17
18const types = require('./types');
19const util = require('util');
20
21const _Murmur3TokenType = types.dataTypes.getByName('bigint');
22const _RandomTokenType = types.dataTypes.getByName('varint');
23const _OrderedTokenType = types.dataTypes.getByName('blob');
24
25/**
26 * Represents a token on the Cassandra ring.
27 */
28class Token {
29 constructor(value) {
30 this._value = value;
31 }
32
33 /**
34 * @returns {{code: number, info: *|Object}} The type info for the
35 * type of the value of the token.
36 */
37 getType() {
38 throw new Error('You must implement a getType function for this Token instance');
39 }
40
41 /**
42 * @returns {*} The raw value of the token.
43 */
44 getValue() {
45 return this._value;
46 }
47
48 toString() {
49 return this._value.toString();
50 }
51
52 /**
53 * Returns 0 if the values are equal, 1 if greater than other, -1
54 * otherwise.
55 *
56 * @param {Token} other
57 * @returns {Number}
58 */
59 compare(other) {
60 return this._value.compare(other._value);
61 }
62
63 equals(other) {
64 return this.compare(other) === 0;
65 }
66
67 inspect() {
68 return this.constructor.name + ' { ' + this.toString() + ' }';
69 }
70}
71
72/**
73 * Represents a token from a Cassandra ring where the partitioner
74 * is Murmur3Partitioner.
75 *
76 * The raw token type is a varint (represented by MutableLong).
77 */
78class Murmur3Token extends Token {
79 constructor(value) {
80 super(value);
81 }
82
83 getType() {
84 return _Murmur3TokenType;
85 }
86}
87
88/**
89 * Represents a token from a Cassandra ring where the partitioner
90 * is RandomPartitioner.
91 *
92 * The raw token type is a bigint (represented by Number).
93 */
94class RandomToken extends Token {
95 constructor(value) {
96 super(value);
97 }
98
99 getType() {
100 return _RandomTokenType;
101 }
102}
103
104/**
105 * Represents a token from a Cassandra ring where the partitioner
106 * is ByteOrderedPartitioner.
107 *
108 * The raw token type is a blob (represented by Buffer or Array).
109 */
110class ByteOrderedToken extends Token {
111 constructor(value) {
112 super(value);
113 }
114
115 getType() {
116 return _OrderedTokenType;
117 }
118
119 toString() {
120 return this._value.toString('hex').toUpperCase();
121 }
122}
123
124/**
125 * Represents a range of tokens on a Cassandra ring.
126 *
127 * A range is start-exclusive and end-inclusive. It is empty when
128 * start and end are the same token, except if that is the minimum
129 * token, in which case the range covers the whole ring (this is
130 * consistent with the behavior of CQL range queries).
131 *
132 * Note that CQL does not handle wrapping. To query all partitions
133 * in a range, see {@link unwrap}.
134 */
135class TokenRange {
136 constructor(start, end, tokenizer) {
137 this.start = start;
138 this.end = end;
139 Object.defineProperty(this, '_tokenizer', { value: tokenizer, enumerable: false});
140 }
141
142 /**
143 * Splits this range into a number of smaller ranges of equal "size"
144 * (referring to the number of tokens, not the actual amount of data).
145 *
146 * Splitting an empty range is not permitted. But not that, in edge
147 * cases, splitting a range might produce one or more empty ranges.
148 *
149 * @param {Number} numberOfSplits Number of splits to make.
150 * @returns {TokenRange[]} Split ranges.
151 * @throws {Error} If splitting an empty range.
152 */
153 splitEvenly(numberOfSplits) {
154 if (numberOfSplits < 1) {
155 throw new Error(util.format("numberOfSplits (%d) must be greater than 0.", numberOfSplits));
156 }
157 if (this.isEmpty()) {
158 throw new Error("Can't split empty range " + this.toString());
159 }
160
161 const tokenRanges = [];
162 const splitPoints = this._tokenizer.split(this.start, this.end, numberOfSplits);
163 let splitStart = this.start;
164 let splitEnd;
165 for (let splitIndex = 0; splitIndex < splitPoints.length; splitIndex++) {
166 splitEnd = splitPoints[splitIndex];
167 tokenRanges.push(new TokenRange(splitStart, splitEnd, this._tokenizer));
168 splitStart = splitEnd;
169 }
170 tokenRanges.push(new TokenRange(splitStart, this.end, this._tokenizer));
171 return tokenRanges;
172 }
173
174 /**
175 * A range is empty when start and end are the same token, except if
176 * that is the minimum token, in which case the range covers the
177 * whole ring. This is consistent with the behavior of CQL range
178 * queries.
179 *
180 * @returns {boolean} Whether this range is empty.
181 */
182 isEmpty() {
183 return this.start.equals(this.end) && !this.start.equals(this._tokenizer.minToken());
184 }
185
186 /**
187 * A range wraps around the end of the ring when the start token
188 * is greater than the end token and the end token is not the
189 * minimum token.
190 *
191 * @returns {boolean} Whether this range wraps around.
192 */
193 isWrappedAround() {
194 return this.start.compare(this.end) > 0 && !this.end.equals(this._tokenizer.minToken());
195 }
196
197 /**
198 * Splits this range into a list of two non-wrapping ranges.
199 *
200 * This will return the range itself if it is non-wrapped, or two
201 * ranges otherwise.
202 *
203 * This is useful for CQL range queries, which do not handle
204 * wrapping.
205 *
206 * @returns {TokenRange[]} The list of non-wrapping ranges.
207 */
208 unwrap() {
209 if (this.isWrappedAround()) {
210 return [
211 new TokenRange(this.start, this._tokenizer.minToken(), this._tokenizer),
212 new TokenRange(this._tokenizer.minToken(), this.end, this._tokenizer)
213 ];
214 }
215 return [this];
216 }
217
218 /**
219 * Whether this range contains a given Token.
220 *
221 * @param {*} token Token to check for.
222 * @returns {boolean} Whether or not the Token is in this range.
223 */
224 contains(token) {
225 if (this.isEmpty()) {
226 return false;
227 }
228 const minToken = this._tokenizer.minToken();
229 if (this.end.equals(minToken)) {
230 if (this.start.equals(minToken)) {
231 return true; // ]minToken, minToken] === full ring
232 } else if (token.equals(minToken)) {
233 return true;
234 }
235 return token.compare(this.start) > 0;
236 }
237
238 const isAfterStart = token.compare(this.start) > 0;
239 const isBeforeEnd = token.compare(this.end) <= 0;
240 // if wrapped around ring, token is in ring if its after start or before end.
241 // otherwise, token is in ring if its after start and before end.
242 return this.isWrappedAround()
243 ? isAfterStart || isBeforeEnd
244 : isAfterStart && isBeforeEnd;
245 }
246
247 /**
248 * Determines if the input range is equivalent to this one.
249 *
250 * @param {TokenRange} other Range to compare with.
251 * @returns {boolean} Whether or not the ranges are equal.
252 */
253 equals(other) {
254 if (other === this) {
255 return true;
256 } else if (other instanceof TokenRange) {
257 return this.compare(other) === 0;
258 }
259 return false;
260 }
261
262 /**
263 * Returns 0 if the values are equal, otherwise compares against
264 * start, if start is equal, compares against end.
265 *
266 * @param {TokenRange} other Range to compare with.
267 * @returns {Number}
268 */
269 compare(other) {
270 const compareStart = this.start.compare(other.start);
271 return compareStart !== 0 ? compareStart : this.end.compare(other.end);
272 }
273
274 toString() {
275 return util.format(']%s, %s]',
276 this.start.toString(),
277 this.end.toString()
278 );
279 }
280}
281
282exports.Token = Token;
283exports.TokenRange = TokenRange;
284exports.ByteOrderedToken = ByteOrderedToken;
285exports.Murmur3Token = Murmur3Token;
286exports.RandomToken = RandomToken;
\No newline at end of file