UNPKG

@sqb/connect

Version:

Multi-dialect database connection framework written with TypeScript

255 lines (254 loc) 7.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Cursor = void 0; const tslib_1 = require("tslib"); const debug_1 = tslib_1.__importDefault(require("debug")); const doublylinked_1 = tslib_1.__importDefault(require("doublylinked")); const power_tasks_1 = require("power-tasks"); const putil_varhelpers_1 = require("putil-varhelpers"); const strict_typed_events_1 = require("strict-typed-events"); const cursor_stream_js_1 = require("./cursor-stream.js"); const helpers_js_1 = require("./helpers.js"); const debug = (0, debug_1.default)('sqb:cursor'); class Cursor extends (0, strict_typed_events_1.TypedEventEmitterClass)(strict_typed_events_1.AsyncEventEmitter) { constructor(connection, fields, adapterCursor, request) { super(); this._taskQueue = new power_tasks_1.TaskQueue(); this._fetchCache = new doublylinked_1.default(); this._rowNum = 0; this._fetchedAll = false; this._fetchedRows = 0; this._connection = connection; this._intlcur = adapterCursor; this._fields = fields; this._request = request; this._prefetchRows = request?.fetchRows || 100; } /** * Returns the Connection instance */ get connection() { return this._connection; } /** * Returns if cursor is before first record. */ get isBof() { return !this._rowNum; } /** * Returns if cursor is closed. */ get isClosed() { return !this._intlcur; } /** * Returns if cursor is after last record. */ get isEof() { return this._fetchedAll && this.rowNum > this._fetchedRows; } /** * Returns number of fetched record count from database. */ get fetchedRows() { return this._fetchedRows; } /** * Returns object instance which contains information about fields. */ get fields() { return this._fields; } /** * Returns current row */ get row() { return this._row; } /** * Returns current row number. */ get rowNum() { return this._rowNum; } /** * Enables cache */ cached() { if (this.fetchedRows) throw new Error('Cache can be enabled before fetching rows'); if (!this._cache) this._cache = new doublylinked_1.default(); } /** * Closes cursor */ async close() { if (!this._intlcur) return; try { await this._intlcur.close(); this._intlcur = undefined; debug('close'); this.emit('close'); } catch (err) { debug('close-error:', err); this.emit('error', err); throw err; } } /** * If cache is enabled, this call fetches and keeps all records in the internal cache. * Otherwise it throws error. Once all all records fetched, * you can close Cursor safely and can continue to use it in memory. * Returns number of fetched rows */ async fetchAll() { if (!this._cache) throw new Error('fetchAll() method needs cache to be enabled'); const n = this.rowNum; const v = await this._seek(Number.MAX_SAFE_INTEGER, true); await this._seek(n - this.rowNum, true); return v; } /** * Moves cursor to given row number. * cursor can move both forward and backward if cache enabled. * Otherwise it throws error. */ async moveTo(rowNum) { await this._seek(rowNum - this.rowNum); return this.row; } /** * Moves cursor forward by one row and returns that row. * And also it allows iterating over rows easily. */ async next() { debug('next'); await this._seek(1); return this.row; } /** * Moves cursor back by one row and returns that row. * And also it allows iterating over rows easily. */ async prev() { await this._seek(-1); return this.row; } /** * Moves cursor before first row. (Required cache enabled) */ reset() { if (!this._cache) throw new Error('reset() method needs cache to be enabled'); this._cache.reset(); this._rowNum = 0; this.emit('reset'); } /** * Moves cursor by given step. If caching is enabled, * cursor can move both forward and backward. Otherwise it throws error. */ async seek(step) { await this._seek(step); return this.row; } /** * Creates and returns a readable stream. */ toStream(options) { return new cursor_stream_js_1.CursorStream(this, options); } toString() { return '[object ' + Object.getPrototypeOf(this).constructor.name + ']'; } inspect() { return this.toString(); } /** * */ async _seek(step, silent) { step = (0, putil_varhelpers_1.coerceToInt)(step, 0); if (!step || (step > 0 && this.isClosed)) return this.rowNum; if (step < 0 && !this._cache) throw new Error('To move cursor back, it needs cache to be enabled'); const _this = this; await this._taskQueue .enqueue(async () => { /* If moving backward */ if (step < 0) { /* Seek cache */ while (step < 0 && _this._cache?.cursor) { _this._row = _this._cache.prev(); _this._rowNum--; step++; } return; } while (step > 0) { if (_this.isEof) return; /* Seek cache */ while (step > 0 && _this._cache && (_this._row = _this._cache.next())) { _this._rowNum++; step--; } /* Fetch from prefetch cache */ while (step > 0 && (_this._row = _this._fetchCache.shift())) { _this._rowNum++; step--; } if (_this._fetchedAll) { _this._rowNum++; _this.emit('eof'); return; } if (!step || _this._fetchedAll) return; await _this._fetchRows(); } }) .toPromise(); if (!silent) this.emit('move', this.row, this._rowNum); return this._rowNum; } /** * */ async _fetchRows() { if (!this._intlcur) throw new Error('Cursor is closed'); let rows = await this._intlcur.fetch(this._prefetchRows); if (rows && rows.length) { debug('Fetched %d rows from database', rows.length); // Normalize rows rows = this._request.objectRows ? (0, helpers_js_1.normalizeRowsToObjectRows)(this._fields, this._intlcur.rowType, rows, this._request) : (0, helpers_js_1.normalizeRowsToArrayRows)(this._fields, this._intlcur.rowType, rows, this._request); (0, helpers_js_1.callFetchHooks)(rows, this._request); for (const [idx, row] of rows.entries()) { this.emit('fetch', row, this._rowNum + idx + 1); } /* Add rows to cache */ if (this._cache) { this._cache.push(...rows); } else this._fetchCache.push(...rows); this._fetchedRows += rows.length; return; } this._fetchedAll = true; return this.close(); } } exports.Cursor = Cursor;