UNPKG

ydb-sdk

Version:

Node.js bindings for working with YDB API over gRPC

248 lines (247 loc) 11.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CANNOT_MANAGE_TRASACTIONS_ERROR = void 0; exports.execute = execute; const ydb_sdk_proto_1 = require("ydb-sdk-proto"); const symbols_1 = require("./symbols"); const build_async_queue_iterator_1 = require("../utils/build-async-queue-iterator"); const result_set_1 = require("./result-set"); const process_ydb_operation_result_1 = require("../utils/process-ydb-operation-result"); const long_1 = __importDefault(require("long")); const errors_1 = require("../errors"); const query_session_1 = require("./query-session"); const types_1 = require("../types"); exports.CANNOT_MANAGE_TRASACTIONS_ERROR = 'Cannot manage transactions at the session level if do() has the txSettings parameter or doTx() is used'; /** * Finishes when the first data block is received or when the end of the stream is received. So if you are sure * that the operation does not return any data, you may not process resultSets. */ function execute(args) { var _a, _b, _c, _d, _e, _f, _g, _h; // Validate args if (!args.text.trim()) throw new Error('"text" parameter is empty'); if (args.parameters) Object.keys(args.parameters).forEach(n => { if (!n.startsWith('$')) throw new Error(`Parameter name must start with "$": ${n}`); }); if (args.txControl && this[symbols_1.sessionTxSettingsSymbol]) throw new Error(exports.CANNOT_MANAGE_TRASACTIONS_ERROR); if ((_a = args.txControl) === null || _a === void 0 ? void 0 : _a.txId) throw new Error('Cannot contain txControl.txId because the current session transaction is used (see session.txId)'); if (this[symbols_1.sessionTxIdSymbol]) { if ((_b = args.txControl) === null || _b === void 0 ? void 0 : _b.beginTx) throw new Error('txControl.beginTx when there\'s already an open transaction'); } else { if (((_c = args.txControl) === null || _c === void 0 ? void 0 : _c.commitTx) && !((_d = args.txControl) === null || _d === void 0 ? void 0 : _d.beginTx)) throw new Error('txControl.commitTx === true when no open transaction and there\'s no txControl.beginTx'); } // Build params const executeQueryRequest = { sessionId: this.sessionId, queryContent: { text: args.text, syntax: (_e = args.syntax) !== null && _e !== void 0 ? _e : ydb_sdk_proto_1.Ydb.Query.Syntax.SYNTAX_YQL_V1, }, execMode: (_f = args.execMode) !== null && _f !== void 0 ? _f : ydb_sdk_proto_1.Ydb.Query.ExecMode.EXEC_MODE_EXECUTE, poolId: (_g = args.poolId) !== null && _g !== void 0 ? _g : args.resourcePool, }; if (args.statsMode) executeQueryRequest.statsMode = args.statsMode; if (args.parameters) executeQueryRequest.parameters = args.parameters; if (this[symbols_1.sessionTxSettingsSymbol] && !this[symbols_1.sessionTxIdSymbol]) executeQueryRequest.txControl = { beginTx: this[symbols_1.sessionTxSettingsSymbol], commitTx: false }; else if (args.txControl) executeQueryRequest.txControl = args.txControl; if (this[symbols_1.sessionTxIdSymbol]) (executeQueryRequest.txControl || (executeQueryRequest.txControl = {})).txId = this[symbols_1.sessionTxIdSymbol]; executeQueryRequest.concurrentResultSets = (_h = args.concurrentResultSets) !== null && _h !== void 0 ? _h : false; if (args.hasOwnProperty('idempotent')) { if (this[symbols_1.isIdempotentDoLevelSymbol]) throw new Error('The attribute of idempotency is already set at the level of do()'); if (args.idempotent) this[symbols_1.isIdempotentSymbol] = true; } // Run the operation let finished = false; const resultSetByIndex = []; const resultSetIterator = (0, build_async_queue_iterator_1.buildAsyncQueueIterator)(); const concurrentResultSets = executeQueryRequest.concurrentResultSets; let lastRowsIterator; let resultResolve; let resultReject; let finishedResolve; let finishedReject; let responseStream; let execStats; let unsub; if (this.ctx.onCancel) { unsub = this.ctx.onCancel((cause) => { cancel(cause); }); } // One operation per session in a time. And it might be cancelled if (this[symbols_1.sessionCurrentOperationSymbol]) throw new Error('There\'s another active operation in the session'); const cancel = (reason, onStreamError) => { if (finished) return; finished = true; if (onStreamError !== true) responseStream.cancel(); if (unsub) unsub(); if (resultReject) { resultReject(reason); resultResolve = resultReject = undefined; } else { // resultSet has already been returned to a client code resultSetIterator.error(reason); Object.values(resultSetByIndex).forEach(([iterator]) => { iterator.error(reason); }); } if (finishedReject) finishedReject(reason); delete this[symbols_1.sessionCurrentOperationSymbol]; }; this[symbols_1.sessionCurrentOperationSymbol] = { cancel }; // Operation responseStream = this[query_session_1.implSymbol].grpcServiceClient.makeServerStreamRequest('/Ydb.Query.V1.QueryService/ExecuteQuery', (v) => ydb_sdk_proto_1.Ydb.Query.ExecuteQueryRequest.encode(v).finish(), ydb_sdk_proto_1.Ydb.Query.ExecuteQueryResponsePart.decode, ydb_sdk_proto_1.Ydb.Query.ExecuteQueryRequest.create(executeQueryRequest), this[query_session_1.implSymbol].metadata); responseStream.on('data', (partialResp) => { var _a, _b, _c, _d; this.logger.trace('execute(): data: %o', partialResp); try { (0, process_ydb_operation_result_1.ensureCallSucceeded)(partialResp); } catch (ydbErr) { delete this[symbols_1.sessionTxIdSymbol]; return cancel(ydbErr); } if ((_a = partialResp.txMeta) === null || _a === void 0 ? void 0 : _a.id) this[symbols_1.sessionTxIdSymbol] = partialResp.txMeta.id; if (partialResp.resultSet) { const _index = partialResp.resultSetIndex; const index = long_1.default.isLong(_index) ? _index.toInt() : resultSetByIndex; let iterator; let resultSet; let resultSetTuple = resultSetByIndex[index]; if (!resultSetTuple) { iterator = (0, build_async_queue_iterator_1.buildAsyncQueueIterator)(); switch (args.rowMode) { case 1 /* RowType.Ydb */: resultSet = new result_set_1.ResultSet(index, partialResp.resultSet.columns, (_b = args.rowMode) !== null && _b !== void 0 ? _b : 0 /* RowType.Native */, iterator); break; default: // Native const nativeColumnsNames = partialResp.resultSet.columns.map(v => types_1.snakeToCamelCaseConversion.ydbToJs(v.name)); resultSet = new result_set_1.ResultSet(index, nativeColumnsNames, (_c = args.rowMode) !== null && _c !== void 0 ? _c : 0 /* RowType.Native */, iterator); resultSet[symbols_1.resultsetYdbColumnsSymbol] = partialResp.resultSet.columns; } resultSetIterator.push(resultSet); resultSetByIndex[index] = [iterator, resultSet]; if (!concurrentResultSets) { lastRowsIterator === null || lastRowsIterator === void 0 ? void 0 : lastRowsIterator.end(); lastRowsIterator = iterator; } } else { [iterator, resultSet] = resultSetTuple; } switch (args.rowMode) { case 1 /* RowType.Ydb */: for (const row of partialResp.resultSet.rows) iterator.push(row); break; default: // Native for (const row of partialResp.resultSet.rows) { // TODO: Rewrite to reduce const nativeRow = {}; // reduced was not used due some strange typing behaviour try { (_d = row.items) === null || _d === void 0 ? void 0 : _d.forEach((v, i) => { const nativeColumnName = resultSet.columns[i]; nativeRow[nativeColumnName] = (0, types_1.convertYdbValueToNative)(resultSet[symbols_1.resultsetYdbColumnsSymbol][i].type, v); }); } catch (err) { throw err; } iterator.push(nativeRow); } } if (resultResolve) { resultResolve({ resultSets: resultSetIterator[Symbol.asyncIterator](), // a list with first block already in it get execStats() { return execStats; }, opFinished: new Promise((resolve, reject) => { finishedResolve = resolve; finishedReject = reject; }) }); resultResolve = resultReject = undefined; } } if (partialResp.execStats) { execStats = partialResp.execStats; } partialResp.txMeta; }); responseStream.on('error', (err) => { this.logger.trace('execute(): error: %o', err); if (err.code === 1) return; // skip "cancelled" error cancel(errors_1.TransportError.convertToYdbError(err), true); }); responseStream.on('metadata', (metadata) => { if (this[symbols_1.sessionTrailerCallbackSymbol]) { this[symbols_1.sessionTrailerCallbackSymbol](metadata); } // TODO: Process partial meta // TODO: Expect to see on graceful shutdown }); responseStream.on('end', () => { var _a; if (finished) return; // finished by cancel() - error or timeout. note: got to be before any logging, so Jest would not complain on logging after test end this.logger.trace('execute(): end'); resultSetIterator.end(); if (concurrentResultSets) { Object.values(resultSetByIndex).forEach(([iterator]) => { iterator.end(); }); } else { lastRowsIterator === null || lastRowsIterator === void 0 ? void 0 : lastRowsIterator.end(); } if (resultResolve) { resultResolve({ resultSets: resultSetIterator[Symbol.asyncIterator](), // an empty list get execStats() { return execStats; }, opFinished: Promise.resolve() }); resultResolve = resultReject = undefined; } if ((_a = args.txControl) === null || _a === void 0 ? void 0 : _a.commitTx) { delete this[symbols_1.sessionTxIdSymbol]; } if (finishedResolve) finishedResolve(); delete this[symbols_1.sessionCurrentOperationSymbol]; if (unsub) unsub(); finished = true; }); return new Promise((resolve, reject) => { resultResolve = resolve; resultReject = reject; }); }