UNPKG

cassandra-driver

Version:

DataStax Node.js Driver for Apache Cassandra

519 lines (482 loc) 15.3 kB
/* * 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 Long = require('long'); const util = require('util'); const utils = require('../utils'); const VIntCoding = utils.VIntCoding; /** @module types */ // Reuse the same buffers that should perform slightly better than built-in buffer pool const reusableBuffers = { months: utils.allocBuffer(9), days: utils.allocBuffer(9), nanoseconds: utils.allocBuffer(9) }; const maxInt32 = 0x7FFFFFFF; const longOneThousand = Long.fromInt(1000); const nanosPerMicro = longOneThousand; const nanosPerMilli = longOneThousand.multiply(nanosPerMicro); const nanosPerSecond = longOneThousand.multiply(nanosPerMilli); const nanosPerMinute = Long.fromInt(60).multiply(nanosPerSecond); const nanosPerHour = Long.fromInt(60).multiply(nanosPerMinute); const daysPerWeek = 7; const monthsPerYear = 12; const standardRegex = /(\d+)(y|mo|w|d|h|s|ms|us|µs|ns|m)/gi; const iso8601Regex = /P((\d+)Y)?((\d+)M)?((\d+)D)?(T((\d+)H)?((\d+)M)?((\d+)S)?)?/; const iso8601WeekRegex = /P(\d+)W/; const iso8601AlternateRegex = /P(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/; /** * Creates a new instance of {@link Duration}. * @classdesc * Represents a duration. A duration stores separately months, days, and seconds due to the fact that the number of * days in a month varies, and a day can have 23 or 25 hours if a daylight saving is involved. * @param {Number} months The number of months. * @param {Number} days The number of days. * @param {Number|Long} nanoseconds The number of nanoseconds. * @constructor */ function Duration(months, days, nanoseconds) { /** * Gets the number of months. * @type {Number} */ this.months = months; /** * Gets the number of days. * @type {Number} */ this.days = days; /** * Gets the number of nanoseconds represented as a <code>int64</code>. * @type {Long} */ this.nanoseconds = typeof nanoseconds === 'number' ? Long.fromNumber(nanoseconds) : nanoseconds; } Duration.prototype.equals = function (other) { if (!(other instanceof Duration)) { return false; } return this.months === other.months && this.days === other.days && this.nanoseconds.equals(other.nanoseconds); }; /** * Serializes the duration and returns the representation of the value in bytes. * @returns {Buffer} */ Duration.prototype.toBuffer = function () { const lengthMonths = VIntCoding.writeVInt(Long.fromNumber(this.months), reusableBuffers.months); const lengthDays = VIntCoding.writeVInt(Long.fromNumber(this.days), reusableBuffers.days); const lengthNanoseconds = VIntCoding.writeVInt(this.nanoseconds, reusableBuffers.nanoseconds); const buffer = utils.allocBufferUnsafe(lengthMonths + lengthDays + lengthNanoseconds); reusableBuffers.months.copy(buffer, 0, 0, lengthMonths); let offset = lengthMonths; reusableBuffers.days.copy(buffer, offset, 0, lengthDays); offset += lengthDays; reusableBuffers.nanoseconds.copy(buffer, offset, 0, lengthNanoseconds); return buffer; }; /** * Returns the string representation of the value. * @return {string} */ Duration.prototype.toString = function () { let value = ''; function append(dividend, divisor, unit) { if (dividend === 0 || dividend < divisor) { return dividend; } // string concatenation is supposed to be fasted than join() value += (dividend / divisor).toFixed(0) + unit; return dividend % divisor; } function append64(dividend, divisor, unit) { if (dividend.equals(Long.ZERO) || dividend.lessThan(divisor)) { return dividend; } // string concatenation is supposed to be fasted than join() value += dividend.divide(divisor).toString() + unit; return dividend.modulo(divisor); } if (this.months < 0 || this.days < 0 || this.nanoseconds.isNegative()) { value = '-'; } let remainder = append(Math.abs(this.months), monthsPerYear, "y"); append(remainder, 1, "mo"); append(Math.abs(this.days), 1, "d"); if (!this.nanoseconds.equals(Long.ZERO)) { const nanos = this.nanoseconds.isNegative() ? this.nanoseconds.negate() : this.nanoseconds; remainder = append64(nanos, nanosPerHour, "h"); remainder = append64(remainder, nanosPerMinute, "m"); remainder = append64(remainder, nanosPerSecond, "s"); remainder = append64(remainder, nanosPerMilli, "ms"); remainder = append64(remainder, nanosPerMicro, "us"); append64(remainder, Long.ONE, "ns"); } return value; }; /** * Creates a new {@link Duration} instance from the binary representation of the value. * @param {Buffer} buffer * @returns {Duration} */ Duration.fromBuffer = function (buffer) { const offset = { value: 0 }; const months = VIntCoding.readVInt(buffer, offset).toNumber(); const days = VIntCoding.readVInt(buffer, offset).toNumber(); const nanoseconds = VIntCoding.readVInt(buffer, offset); return new Duration(months, days, nanoseconds); }; /** * Creates a new {@link Duration} instance from the string representation of the value. * <p> * Accepted formats: * </p> * <ul> * <li>multiple digits followed by a time unit like: 12h30m where the time unit can be: * <ul> * <li>{@code y}: years</li> * <li>{@code m}: months</li> * <li>{@code w}: weeks</li> * <li>{@code d}: days</li> * <li>{@code h}: hours</li> * <li>{@code m}: minutes</li> * <li>{@code s}: seconds</li> * <li>{@code ms}: milliseconds</li> * <li>{@code us} or {@code µs}: microseconds</li> * <li>{@code ns}: nanoseconds</li> * </ul> * </li> * <li>ISO 8601 format: <code>P[n]Y[n]M[n]DT[n]H[n]M[n]S or P[n]W</code></li> * <li>ISO 8601 alternative format: <code>P[YYYY]-[MM]-[DD]T[hh]:[mm]:[ss]</code></li> * </ul> * @param {String} input * @returns {Duration} */ Duration.fromString = function (input) { const isNegative = input.charAt(0) === '-'; const source = isNegative ? input.substr(1) : input; if (source.charAt(0) === 'P') { if (source.charAt(source.length - 1) === 'W') { return parseIso8601WeekFormat(isNegative, source); } if (source.indexOf('-') > 0) { return parseIso8601AlternativeFormat(isNegative, source); } return parseIso8601Format(isNegative, source); } return parseStandardFormat(isNegative, source); }; /** * @param {Boolean} isNegative * @param {String} source * @returns {Duration} * @private */ function parseStandardFormat(isNegative, source) { const builder = new Builder(isNegative); standardRegex.lastIndex = 0; let matches; while ((matches = standardRegex.exec(source)) && matches.length <= 3) { builder.add(matches[1], matches[2]); } return builder.build(); } /** * @param {Boolean} isNegative * @param {String} source * @returns {Duration} * @private */ function parseIso8601Format(isNegative, source) { const matches = iso8601Regex.exec(source); if (!matches || matches[0] !== source) { throw new TypeError(util.format("Unable to convert '%s' to a duration", source)); } const builder = new Builder(isNegative); if (matches[1]) { builder.addYears(matches[2]); } if (matches[3]) { builder.addMonths(matches[4]); } if (matches[5]) { builder.addDays(matches[6]); } if (matches[7]) { if (matches[8]) { builder.addHours(matches[9]); } if (matches[10]) { builder.addMinutes(matches[11]); } if (matches[12]) { builder.addSeconds(matches[13]); } } return builder.build(); } /** * @param {Boolean} isNegative * @param {String} source * @returns {Duration} * @private */ function parseIso8601WeekFormat(isNegative, source) { const matches = iso8601WeekRegex.exec(source); if (!matches || matches[0] !== source) { throw new TypeError(util.format("Unable to convert '%s' to a duration", source)); } return new Builder(isNegative) .addWeeks(matches[1]) .build(); } /** * @param {Boolean} isNegative * @param {String} source * @returns {Duration} * @private */ function parseIso8601AlternativeFormat(isNegative, source) { const matches = iso8601AlternateRegex.exec(source); if (!matches || matches[0] !== source) { throw new TypeError(util.format("Unable to convert '%s' to a duration", source)); } return new Builder(isNegative).addYears(matches[1]) .addMonths(matches[2]) .addDays(matches[3]) .addHours(matches[4]) .addMinutes(matches[5]) .addSeconds(matches[6]) .build(); } /** * @param {Boolean} isNegative * @private * @constructor */ function Builder(isNegative) { this._isNegative = isNegative; this._unitIndex = 0; this._months = 0; this._days = 0; this._nanoseconds = Long.ZERO; this._addMethods = { 'y': this.addYears, 'mo': this.addMonths, 'w': this.addWeeks, 'd': this.addDays, 'h': this.addHours, 'm': this.addMinutes, 's': this.addSeconds, 'ms': this.addMillis, // µs '\u00B5s': this.addMicros, 'us': this.addMicros, 'ns': this.addNanos }; this._unitByIndex = [ null, 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'milliseconds', 'microseconds', 'nanoseconds' ]; } Builder.prototype._validateOrder = function (unitIndex) { if (unitIndex === this._unitIndex) { throw new TypeError(util.format("Invalid duration. The %s are specified multiple times", this._getUnitName(unitIndex))); } if (unitIndex <= this._unitIndex) { throw new TypeError(util.format("Invalid duration. The %s should be after %s", this._getUnitName(this._unitIndex), this._getUnitName(unitIndex))); } this._unitIndex = unitIndex; }; /** * @param {Number} units * @param {Number} monthsPerUnit */ Builder.prototype._validateMonths = function(units, monthsPerUnit) { this._validate32(units, (maxInt32 - this._months) / monthsPerUnit, "months"); }; /** * @param {Number} units * @param {Number} daysPerUnit */ Builder.prototype._validateDays = function(units, daysPerUnit) { this._validate32(units, (maxInt32 - this._days) / daysPerUnit, "days"); }; /** * @param {Long} units * @param {Long} nanosPerUnit */ Builder.prototype._validateNanos = function(units, nanosPerUnit) { this._validate64(units, Long.MAX_VALUE.subtract(this._nanoseconds).divide(nanosPerUnit), "nanoseconds"); }; /** * @param {Number} units * @param {Number} limit * @param {String} unitName */ Builder.prototype._validate32 = function(units, limit, unitName) { if (units > limit) { throw new TypeError(util.format('Invalid duration. The total number of %s must be less or equal to %s', unitName, maxInt32)); } }; /** * @param {Long} units * @param {Long} limit * @param {String} unitName */ Builder.prototype._validate64 = function(units, limit, unitName) { if (units.greaterThan(limit)) { throw new TypeError(util.format('Invalid duration. The total number of %s must be less or equal to %s', unitName, Long.MAX_VALUE.toString())); } }; Builder.prototype._getUnitName = function(unitIndex) { const name = this._unitByIndex[+unitIndex]; if (!name) { throw new Error('unknown unit index: ' + unitIndex); } return name; }; Builder.prototype.add = function (textValue, symbol) { const addMethod = this._addMethods[symbol.toLowerCase()]; if (!addMethod) { throw new TypeError(util.format("Unknown duration symbol '%s'", symbol)); } return addMethod.call(this, textValue); }; /** * @param {String|Number} years * @return {Builder} */ Builder.prototype.addYears = function (years) { const value = +years; this._validateOrder(1); this._validateMonths(value, monthsPerYear); this._months += value * monthsPerYear; return this; }; /** * @param {String|Number} months * @return {Builder} */ Builder.prototype.addMonths = function(months) { const value = +months; this._validateOrder(2); this._validateMonths(value, 1); this._months += value; return this; }; /** * @param {String|Number} weeks * @return {Builder} */ Builder.prototype.addWeeks = function(weeks) { const value = +weeks; this._validateOrder(3); this._validateDays(value, daysPerWeek); this._days += value * daysPerWeek; return this; }; /** * @param {String|Number} days * @return {Builder} */ Builder.prototype.addDays = function(days) { const value = +days; this._validateOrder(4); this._validateDays(value, 1); this._days += value; return this; }; /** * @param {String|Long} hours * @return {Builder} */ Builder.prototype.addHours = function(hours) { const value = typeof hours === 'string' ? Long.fromString(hours) : hours; this._validateOrder(5); this._validateNanos(value, nanosPerHour); this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerHour)); return this; }; /** * @param {String|Long} minutes * @return {Builder} */ Builder.prototype.addMinutes = function(minutes) { const value = typeof minutes === 'string' ? Long.fromString(minutes) : minutes; this._validateOrder(6); this._validateNanos(value, nanosPerMinute); this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerMinute)); return this; }; /** * @param {String|Long} seconds * @return {Builder} */ Builder.prototype.addSeconds = function(seconds) { const value = typeof seconds === 'string' ? Long.fromString(seconds) : seconds; this._validateOrder(7); this._validateNanos(value, nanosPerSecond); this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerSecond)); return this; }; /** * @param {String|Long} millis * @return {Builder} */ Builder.prototype.addMillis = function(millis) { const value = typeof millis === 'string' ? Long.fromString(millis) : millis; this._validateOrder(8); this._validateNanos(value, nanosPerMilli); this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerMilli)); return this; }; /** * @param {String|Long} micros * @return {Builder} */ Builder.prototype.addMicros = function(micros) { const value = typeof micros === 'string' ? Long.fromString(micros) : micros; this._validateOrder(9); this._validateNanos(value, nanosPerMicro); this._nanoseconds = this._nanoseconds.add(value.multiply(nanosPerMicro)); return this; }; /** * @param {String|Long} nanos * @return {Builder} */ Builder.prototype.addNanos = function(nanos) { const value = typeof nanos === 'string' ? Long.fromString(nanos) : nanos; this._validateOrder(10); this._validateNanos(value, Long.ONE); this._nanoseconds = this._nanoseconds.add(value); return this; }; /** @return {Duration} */ Builder.prototype.build = function () { return (this._isNegative ? new Duration(-this._months, -this._days, this._nanoseconds.negate()) : new Duration(this._months, this._days, this._nanoseconds)); }; module.exports = Duration;