ydb-sdk
Version:
Node.js bindings for working with YDB API over gRPC
248 lines (247 loc) • 11.8 kB
JavaScript
;
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;
});
}