UNPKG

@naturalcycles/mysql-lib

Version:

MySQL client implementing CommonDB interface

120 lines (119 loc) 4.14 kB
import { Transform } from 'node:stream'; import { commonKeyValueDBFullSupport } from '@naturalcycles/db-lib/kv'; import { AppError } from '@naturalcycles/js-lib/error/error.util.js'; import { pMap } from '@naturalcycles/js-lib/promise/pMap.js'; import { MysqlDB } from './mysql.db.js'; export class MySQLKeyValueDB { cfg; constructor(cfg) { this.cfg = cfg; this.db = new MysqlDB(this.cfg); } db; support = { ...commonKeyValueDBFullSupport, increment: false, }; async ping() { await this.db.ping(); } async close() { await this.db.close(); } async createTable(table, opt = {}) { if (opt.dropIfExists) await this.dropTable(table); // On blob sizes: https://tableplus.com/blog/2019/10/tinyblob-blob-mediumblob-longblob.html // LONGBLOB supports up to 4gb // MEDIUMBLOB up to 16Mb const sql = `create table ${table} (id VARCHAR(64) PRIMARY KEY, v LONGBLOB NOT NULL)`; this.db.cfg.logger.log(sql); await this.db.runSQL({ sql }); } async getByIds(table, ids) { if (!ids.length) return []; const sql = `SELECT id,v FROM ${table} where id in (${ids.map(id => `"${id}"`).join(',')})`; const rows = await this.db.runSQL({ sql }); return rows.map(({ id, v }) => [id, v]); } /** * Use with caution! */ async dropTable(table) { await this.db.runSQL({ sql: `DROP TABLE IF EXISTS ${table}` }); } async deleteByIds(table, ids) { const sql = `DELETE FROM ${table} WHERE id in (${ids.map(id => `"${id}"`).join(',')})`; if (this.cfg.logSQL) this.db.cfg.logger.log(sql); await this.db.runSQL({ sql }); } async saveBatch(table, entries) { const statements = entries.map(([id, buf]) => { return { sql: `INSERT INTO ${table} (id, v) VALUES (?, ?)`, values: [id, buf], }; }); await pMap(statements, async (statement) => { if (this.cfg.debug) this.db.cfg.logger.log(statement.sql); await this.db.runSQL(statement); }); } streamIds(table, limit) { let sql = `SELECT id FROM ${table}`; if (limit) sql += ` LIMIT ${limit}`; if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`); // todo: this is nice, but `mysql` package uses `readable-stream@2` which is not compatible with `node:stream` iterable helpers // return (this.db.pool().query(sql).stream() as ReadableTyped<ObjectWithId>).map(row => row.id) return this.db .pool() .query(sql) .stream() .pipe(new Transform({ objectMode: true, transform(row, _encoding, cb) { cb(null, row.id); }, })); } streamValues(table, limit) { let sql = `SELECT v FROM ${table}`; if (limit) sql += ` LIMIT ${limit}`; if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`); return this.db.pool().query(sql).stream().map(row => row.v); } streamEntries(table, limit) { let sql = `SELECT id,v FROM ${table}`; if (limit) sql += ` LIMIT ${limit}`; if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`); return this.db.pool().query(sql).stream().map(row => [ row.id, row.v, ]); } async beginTransaction() { await this.db.runSQL({ sql: `BEGIN TRANSACTION` }); } async endTransaction() { await this.db.runSQL({ sql: `END TRANSACTION` }); } async count(table) { const sql = `SELECT count(*) as cnt FROM ${table}`; if (this.cfg.logSQL) this.db.cfg.logger.log(sql); const rows = await this.db.runSQL({ sql }); return rows[0].cnt; } async incrementBatch(_table, _entries) { throw new AppError('MySQLKeyValueDB.incrementBatch() is not implemented'); } }