@naturalcycles/mysql-lib
Version:
MySQL client implementing CommonDB interface
120 lines (119 loc) • 4.14 kB
JavaScript
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');
}
}