@google-cloud/bigtable
Version:
Cloud Bigtable Client Library for Node.js
353 lines • 15.6 kB
JavaScript
;
// Copyright 2024 Google LLC
//
// 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
//
// https://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.
Object.defineProperty(exports, "__esModule", { value: true });
exports.PartialFailureError = exports.TabularApiSurface = exports.DEFAULT_BACKOFF_SETTINGS = exports.IGNORED_STATUS_CODES = exports.RETRYABLE_STATUS_CODES = void 0;
exports.getNextDelay = getNextDelay;
exports.populateAttemptHeader = populateAttemptHeader;
const promisify_1 = require("@google-cloud/promisify");
const arrify = require("arrify");
const mutation_1 = require("./mutation");
const row_1 = require("./row");
const google_gax_1 = require("google-gax");
const stream_1 = require("stream");
const createReadStreamInternal_1 = require("./utils/createReadStreamInternal");
const getRowsInternal_1 = require("./utils/getRowsInternal");
const client_side_metrics_attributes_1 = require("./client-side-metrics/client-side-metrics-attributes");
const mutateInternal_1 = require("./utils/mutateInternal");
// See protos/google/rpc/code.proto
// (4=DEADLINE_EXCEEDED, 8=RESOURCE_EXHAUSTED, 10=ABORTED, 14=UNAVAILABLE)
exports.RETRYABLE_STATUS_CODES = new Set([4, 8, 10, 14]);
// (1=CANCELLED)
exports.IGNORED_STATUS_CODES = new Set([1]);
exports.DEFAULT_BACKOFF_SETTINGS = {
initialRetryDelayMillis: 10,
retryDelayMultiplier: 2,
maxRetryDelayMillis: 60000,
};
// eslint-disable-next-line @typescript-eslint/no-var-requires
const concat = require('concat-stream');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pumpify = require('pumpify');
/**
* The TabularApiSurface class is a class that contains methods we want to
* expose on both tables and authorized views. It also contains data that will
* be used by both Authorized views and Tables for these shared methods.
*
* @class
* @param {Instance} instance Instance Object.
* @param {string} id Unique identifier of the table.
*
*/
class TabularApiSurface {
bigtable;
instance;
name;
id;
metadata;
maxRetries;
// We need viewName to be public because now we need it in Row class
// We need it in Row class because now we use getRowsInternal instead of getRows
viewName;
constructor(instance, id, viewName) {
this.bigtable = instance.bigtable;
this.instance = instance;
this.viewName = viewName;
let name;
if (id.includes('/')) {
if (id.startsWith(`${instance.name}/tables/`)) {
name = id;
}
else {
throw new Error(`Table id '${id}' is not formatted correctly.
Please use the format 'prezzy' or '${instance.name}/tables/prezzy'.`);
}
}
else {
name = `${instance.name}/tables/${id}`;
}
this.name = name;
this.id = name.split('/').pop();
}
/**
* Get {@link Row} objects for the rows currently in your table as a
* readable object stream.
*
* @param {boolean} [options.decode=true] If set to `false` it will not decode
* Buffer values returned from Bigtable.
* @param {boolean} [options.encoding] The encoding to use when converting
* Buffer values to a string.
* @param {string} [options.end] End value for key range.
* @param {Filter} [options.filter] Row filters allow you to
* both make advanced queries and format how the data is returned.
* @param {object} [options.gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {string[]} [options.keys] A list of row keys.
* @param {number} [options.limit] Maximum number of rows to be returned.
* @param {string} [options.prefix] Prefix that the row key must match.
* @param {string[]} [options.prefixes] List of prefixes that a row key must
* match.
* @param {object[]} [options.ranges] A list of key ranges.
* @param {string} [options.start] Start value for key range.
* @returns {stream}
*
* @example <caption>include:samples/api-reference-doc-snippets/table.js</caption>
* region_tag:bigtable_api_table_readstream
* @param opts
*/
createReadStream(opts) {
const metricsCollector = this.bigtable._metricsConfigManager.createOperation(client_side_metrics_attributes_1.MethodName.READ_ROWS, client_side_metrics_attributes_1.StreamingState.STREAMING, this);
return (0, createReadStreamInternal_1.createReadStreamInternal)(this, metricsCollector, opts);
}
/**
* Get {@link Row} objects for the rows currently in your table.
*
* This method is not recommended for large datasets as it will buffer all rows
* before returning the results. Instead we recommend using the streaming API
* via {@link Table#createReadStream}.
*
* {@link Table#createReadStream} for a complete list of options.
* @param {object} [options.gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {?error} callback.err An error returned while making this request.
* @param {Row[]} callback.rows List of Row objects.
*
* @example <caption>include:samples/api-reference-doc-snippets/table.js</caption>
* region_tag:bigtable_api_get_rows
* @param optionsOrCallback
* @param cb
*/
getRows(optionsOrCallback, cb) {
const metricsCollector = this.bigtable._metricsConfigManager.createOperation(client_side_metrics_attributes_1.MethodName.READ_ROWS, client_side_metrics_attributes_1.StreamingState.STREAMING, this);
return (0, getRowsInternal_1.getRowsInternal)(this, metricsCollector, optionsOrCallback, cb);
}
/**
* Insert or update rows in your table. It should be noted that gRPC only allows
* you to send payloads that are less than or equal to 4MB. If you're inserting
* more than that you may need to send smaller individual requests.
*
* @param {object|object[]} entries List of entries to be inserted.
* See {@link Table#mutate}.
* @param {object} [gaxOptions] Request configuration options, outlined here:
* https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {function} callback The callback function.
* @param {?error} callback.err An error returned while making this request.
* @param {object[]} callback.err.errors If present, these represent partial
* failures. It's possible for part of your request to be completed
* successfully, while the other part was not.
*
* @example <caption>include:samples/api-reference-doc-snippets/table.js</caption>
* region_tag:bigtable_api_insert_rows
*/
insert(entries, optionsOrCallback, cb) {
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const gaxOptions = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
entries = arrify(entries).map((entry) => {
entry.method = mutation_1.Mutation.methods.INSERT;
return entry;
});
return this.mutate(entries, { gaxOptions }, callback);
}
/**
* Apply a set of changes to be atomically applied to the specified row(s).
* Mutations are applied in order, meaning that earlier mutations can be masked
* by later ones.
*
* @param {object|object[]} entries List of entities to be inserted or
* deleted.
* @param {object} [options] Configuration object.
* @param {object} [options.gaxOptions] Request configuration options, outlined
* here: https://googleapis.github.io/gax-nodejs/global.html#CallOptions.
* @param {boolean} [options.rawMutation] If set to `true` will treat entries
* as a raw Mutation object. See {@link Mutation#parse}.
* @param {function} callback The callback function.
* @param {?error} callback.err An error returned while making this request.
* @param {object[]} callback.err.errors If present, these represent partial
* failures. It's possible for part of your request to be completed
* successfully, while the other part was not.
*
* @example <caption>include:samples/api-reference-doc-snippets/table.js</caption>
* region_tag:bigtable_api_mutate_rows
*/
mutate(entriesRaw, optionsOrCallback, cb) {
const metricsCollector = this.bigtable._metricsConfigManager.createOperation(client_side_metrics_attributes_1.MethodName.MUTATE_ROWS, client_side_metrics_attributes_1.StreamingState.STREAMING, this);
(0, mutateInternal_1.mutateInternal)(this, metricsCollector, entriesRaw, optionsOrCallback, cb);
}
/**
* Get a reference to a table row.
*
* @throws {error} If a key is not provided.
*
* @param {string} key The row key.
* @returns {Row}
*
* @example
* ```
* const row = table.row('lincoln');
* ```
*/
row(key) {
if (!key) {
throw new Error('A row key must be provided.');
}
return new row_1.Row(this, key);
}
/**
* Returns a sample of row keys in the table. The returned row keys will delimit
* contiguous sections of the table of approximately equal size, which can be
* used to break up the data for distributed tasks like mapreduces.
*
* @param {object} [gaxOptions] Request configuration options, outlined here:
* https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @param {function} [callback] The callback function.
* @param {?error} callback.err An error returned while making this request.
* @param {object[]} callback.keys The list of keys.
*
* @example <caption>include:samples/api-reference-doc-snippets/table.js</caption>
* region_tag:bigtable_api_sample_row_keys
*/
sampleRowKeys(optionsOrCallback, cb) {
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const gaxOptions = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
/*
The following line of code sets the timeout if it was provided while
creating the client. This will be used to determine if the client should
retry on DEADLINE_EXCEEDED errors. Eventually, this will be handled
downstream in google-gax.
*/
const timeout = gaxOptions?.timeout;
const callTimeMillis = new Date().getTime();
this.sampleRowKeysStream(gaxOptions)
.on('error', (error) => {
if (timeout && timeout > new Date().getTime() - callTimeMillis) {
error.code = google_gax_1.grpc.status.DEADLINE_EXCEEDED;
}
callback(error);
})
.pipe(concat((keys) => {
callback(null, keys);
}));
}
/**
* Returns a sample of row keys in the table as a readable object stream.
*
* See {@link Table#sampleRowKeys} for more details.
*
* @param {object} [gaxOptions] Request configuration options, outlined here:
* https://googleapis.github.io/gax-nodejs/CallSettings.html.
* @returns {stream}
*
* @example
* ```
* table.sampleRowKeysStream()
* .on('error', console.error)
* .on('data', function(key) {
* // Do something with the `key` object.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing.
* //-
* table.sampleRowKeysStream()
* .on('data', function(key) {
* this.end();
* });
* ```
*/
sampleRowKeysStream(gaxOptions) {
// If the viewName is provided then request will be made for an
// authorized view. Otherwise, the request is made for a table.
const reqOpts = (this.viewName
? {
authorizedViewName: `${this.name}/authorizedViews/${this.viewName}`,
appProfileId: this.bigtable.appProfileId,
}
: {
tableName: this.name,
appProfileId: this.bigtable.appProfileId,
});
const rowKeysStream = new stream_1.Transform({
transform(key, enc, next) {
next(null, {
key: key.rowKey,
offset: key.offsetBytes,
});
},
objectMode: true,
});
const metricsCollector = this.bigtable._metricsConfigManager.createOperation(client_side_metrics_attributes_1.MethodName.SAMPLE_ROW_KEYS, client_side_metrics_attributes_1.StreamingState.STREAMING, this);
metricsCollector.onOperationStart();
metricsCollector.onAttemptStart();
const requestStream = this.bigtable.request({
client: 'BigtableClient',
method: 'sampleRowKeys',
reqOpts,
gaxOpts: Object.assign({}, gaxOptions),
});
metricsCollector.wrapRequest(requestStream);
const stream = pumpify.obj([requestStream, rowKeysStream]);
stream.on('end', () => {
metricsCollector.onOperationComplete(0);
});
stream.on('error', (err) => {
metricsCollector.onOperationComplete(err.code);
});
return stream;
}
}
exports.TabularApiSurface = TabularApiSurface;
function getNextDelay(numConsecutiveErrors, config) {
// 0 - 100 ms jitter
const jitter = Math.floor(Math.random() * 100);
const calculatedNextRetryDelay = config.initialRetryDelayMillis *
Math.pow(config.retryDelayMultiplier, numConsecutiveErrors) +
jitter;
return Math.min(calculatedNextRetryDelay, config.maxRetryDelayMillis);
}
function populateAttemptHeader(attempt, gaxOpts) {
gaxOpts = gaxOpts || {};
gaxOpts.otherArgs = gaxOpts.otherArgs || {};
gaxOpts.otherArgs.headers = gaxOpts.otherArgs.headers || {};
gaxOpts.otherArgs.headers['bigtable-attempt'] = attempt;
return gaxOpts;
}
/*! Developer Documentation
*
* All async methods (except for streams) will return a Promise in the event
* that a callback is omitted.
*/
(0, promisify_1.promisifyAll)(TabularApiSurface, {
exclude: ['family', 'row'],
});
class PartialFailureError extends Error {
errors;
constructor(errors, rpcError) {
super();
this.errors = errors;
this.name = 'PartialFailureError';
let messages = errors.map(e => e.message);
if (messages.length > 1) {
messages = messages.map((message, i) => ` ${i + 1}. ${message}`);
messages.unshift('Multiple errors occurred during the request. Please see the `errors` array for complete details.\n');
messages.push('\n');
}
this.message = messages.join('\n');
if (rpcError) {
this.message += 'Request failed with: ' + rpcError.message;
}
}
}
exports.PartialFailureError = PartialFailureError;
//# sourceMappingURL=tabular-api-surface.js.map