int-cli
Version:
INT is the new generation of bottom-up created system of IoT and blockchain
663 lines (662 loc) • 26.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs-extra");
const path = require("path");
const assert = require("assert");
const sqlite = require("sqlite");
const sqlite3 = require("sqlite3");
const { TransactionDatabase } = require('sqlite3-transactions');
const error_code_1 = require("../error_code");
const serializable_1 = require("../serializable");
const storage_1 = require("../storage");
const util_1 = require("util");
const digest = require('../lib/digest');
const { LogShim } = require('../lib/log_shim');
class SqliteStorageKeyValue {
constructor(db, fullName, logger) {
this.db = db;
this.fullName = fullName;
this.logger = new LogShim(logger).bind(`[transaction: ${this.fullName}]`, true).log;
}
async set(key, value) {
try {
assert(key);
const json = JSON.stringify(serializable_1.toStringifiable(value, true));
const sql = `REPLACE INTO '${this.fullName}' (name, field, value) VALUES ('${key}', "____default____", '${json}')`;
await this.db.exec(sql);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`set ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async get(key) {
try {
assert(key);
const result = await this.db.get(`SELECT value FROM '${this.fullName}' \
WHERE name=? AND field="____default____"`, key);
if (result == null) {
return { err: error_code_1.ErrorCode.RESULT_NOT_FOUND };
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: serializable_1.fromStringifiable(JSON.parse(result.value)) };
}
catch (e) {
this.logger.error(`get ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hset(key, field, value) {
try {
assert(key);
assert(field);
const json = JSON.stringify(serializable_1.toStringifiable(value, true));
const sql = `REPLACE INTO '${this.fullName}' (name, field, value) VALUES ('${key}', '${field}', '${json}')`;
await this.db.exec(sql);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`hset ${key} ${field} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hget(key, field) {
try {
assert(key);
assert(field);
const result = await this.db.get(`SELECT value FROM '${this.fullName}' WHERE name=? AND field=?`, key, field);
if (result == null) {
return { err: error_code_1.ErrorCode.RESULT_NOT_FOUND };
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: serializable_1.fromStringifiable(JSON.parse(result.value)) };
}
catch (e) {
this.logger.error(`hget ${key} ${field} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hdel(key, field) {
try {
await this.db.exec(`DELETE FROM '${this.fullName}' WHERE name='${key}' and field='${field}'`);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`hdel ${key} ${field} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hlen(key) {
try {
assert(key);
const result = await this.db.get(`SELECT count(*) as value FROM '${this.fullName}' WHERE name=?`, key);
return { err: error_code_1.ErrorCode.RESULT_OK, value: result.value };
}
catch (e) {
this.logger.error(`hlen ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hexists(key, field) {
let { err } = await this.hget(key, field);
if (!err) {
return { err: error_code_1.ErrorCode.RESULT_OK, value: true };
}
else if (err === error_code_1.ErrorCode.RESULT_NOT_FOUND) {
return { err: error_code_1.ErrorCode.RESULT_OK, value: false };
}
else {
this.logger.error(`hexists ${key} ${field} `, err);
return { err };
}
}
async hmset(key, fields, values) {
try {
assert(key);
assert(fields.length === values.length);
const statement = await this.db.prepare(`REPLACE INTO '${this.fullName}' (name, field, value) VALUES (?, ?, ?)`);
for (let i = 0; i < fields.length; i++) {
await statement.run([key, fields[i], JSON.stringify(serializable_1.toStringifiable(values[i], true))]);
}
await statement.finalize();
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`hmset ${key} ${fields} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hmget(key, fields) {
try {
assert(key);
const sql = `SELECT * FROM '${this.fullName}' WHERE name=? AND field in (${fields.map((x) => '?').join(',')})`;
// console.log({ sql });
const result = await this.db.all(sql, key, ...fields);
const resultMap = {};
result.forEach((x) => resultMap[x.field] = serializable_1.fromStringifiable(JSON.parse(x.value)));
const values = fields.map((x) => resultMap[x]);
return { err: error_code_1.ErrorCode.RESULT_OK, value: values };
}
catch (e) {
this.logger.error(`hmget ${key} ${fields} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hkeys(key) {
try {
assert(key);
const result = await this.db.all(`SELECT * FROM '${this.fullName}' WHERE name=?`, key);
return { err: error_code_1.ErrorCode.RESULT_OK, value: result.map((x) => x.field) };
}
catch (e) {
this.logger.error(`hkeys ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hvalues(key) {
try {
assert(key);
const result = await this.db.all(`SELECT * FROM '${this.fullName}' WHERE name=?`, key);
return { err: error_code_1.ErrorCode.RESULT_OK, value: result.map((x) => serializable_1.fromStringifiable(JSON.parse(x.value))) };
}
catch (e) {
this.logger.error(`hvalues ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hgetall(key) {
try {
const result = await this.db.all(`SELECT * FROM '${this.fullName}' WHERE name=?`, key);
return {
err: error_code_1.ErrorCode.RESULT_OK, value: result.map((x) => {
return { key: x.field, value: serializable_1.fromStringifiable(JSON.parse(x.value)) };
})
};
}
catch (e) {
this.logger.error(`hgetall ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async hclean(key) {
try {
const result = await this.db.exec(`DELETE FROM ${this.fullName} WHERE name='${key}'`);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`hclean ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async lindex(key, index) {
return this.hget(key, index.toString());
}
async lset(key, index, value) {
try {
assert(key);
assert(!util_1.isNullOrUndefined(index));
const json = JSON.stringify(serializable_1.toStringifiable(value, true));
const sql = `REPLACE INTO '${this.fullName}' (name, field, value) VALUES ('${key}', '${index.toString()}', '${json}')`;
await this.db.exec(sql);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`lset ${key} ${index} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async llen(key) {
return await this.hlen(key);
}
async lrange(key, start, stop) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
if (!len) {
return { err: error_code_1.ErrorCode.RESULT_OK, value: [] };
}
if (start < 0) {
start = len + start;
}
if (stop < 0) {
stop = len + stop;
}
if (stop >= len) {
stop = len - 1;
}
let fields = [];
for (let i = start; i <= stop; ++i) {
fields.push(i);
}
const result = await this.db.all(`SELECT * FROM '${this.fullName}' WHERE name='${key}' AND field in (${fields.map((x) => `'${x}'`).join(',')})`);
let ret = new Array(result.length);
for (let x of result) {
ret[parseInt(x.field) - start] = serializable_1.fromStringifiable(JSON.parse(x.value));
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: ret };
}
catch (e) {
this.logger.error(`lrange ${key} ${start} ${stop}`, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async lpush(key, value) {
try {
assert(key);
// update index += 1
// set index[0] = value
const json = JSON.stringify(serializable_1.toStringifiable(value, true));
await this.db.exec(`UPDATE '${this.fullName}' SET field=field+1 WHERE name='${key}'`);
const sql = `INSERT INTO '${this.fullName}' (name, field, value) VALUES ('${key}', '0', '${json}')`;
// console.log('lpush', { sql });
await this.db.exec(sql);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`lpush ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async lpushx(key, value) {
try {
assert(key);
const len = value.length;
await this.db.exec(`UPDATE '${this.fullName}' SET field=field+${len} WHERE name='${key}'`);
for (let i = 0; i < len; i++) {
const json = JSON.stringify(serializable_1.toStringifiable(value[i], true));
await this.db.exec(`INSERT INTO '${this.fullName}' (name, field, value) VALUES ('${key}', '${i}', '${json}')`);
}
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`lpushx ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async lpop(key) {
try {
const index = 0;
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
if (len === 0) {
return { err: error_code_1.ErrorCode.RESULT_NOT_FOUND };
}
else {
const { err: err2, value } = await this.lindex(key, index);
let sql = `DELETE FROM '${this.fullName}' WHERE name='${key}' AND field='${index}'`;
await this.db.exec(sql);
for (let i = index + 1; i < len; i++) {
sql = `UPDATE '${this.fullName}' SET field=field-1 WHERE name='${key}' AND field = ${i}`;
await this.db.exec(sql);
}
return { err: error_code_1.ErrorCode.RESULT_OK, value };
}
}
catch (e) {
this.logger.error(`lpop ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async rpush(key, value) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
const json = JSON.stringify(serializable_1.toStringifiable(value, true));
await this.db.exec(`INSERT INTO '${this.fullName}' (name, field, value) VALUES ('${key}', '${len}', '${json}')`);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`rpush ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async rpushx(key, value) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
for (let i = 0; i < value.length; i++) {
const json = JSON.stringify(serializable_1.toStringifiable(value[i], true));
await this.db.exec(`INSERT INTO '${this.fullName}' (name, field, value) \
VALUES ('${key}', '${len + i}', '${json}')`);
}
return { err: error_code_1.ErrorCode.RESULT_OK };
}
catch (e) {
this.logger.error(`rpushx ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async rpop(key) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
if (len === 0) {
return { err: error_code_1.ErrorCode.RESULT_NOT_FOUND };
}
else {
const { err: err2, value } = await this.lindex(key, len - 1);
await this.db.exec(`DELETE FROM '${this.fullName}' WHERE name='${key}' AND field=${len - 1}`);
return { err: error_code_1.ErrorCode.RESULT_OK, value };
}
}
catch (e) {
this.logger.error(`rpop ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async linsert(key, index, value) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
if (len === 0 || index >= len) {
return await this.lset(key, len, value);
}
else {
for (let i = len - 1; i >= index; i--) {
await this.db.exec(`UPDATE '${this.fullName}' SET field=field+1 WHERE name='${key}' AND field = ${i}`);
}
return await this.lset(key, index, value);
}
}
catch (e) {
this.logger.error(`linsert ${key} ${index} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
async lremove(key, index) {
try {
assert(key);
const { err, value: len } = await this.llen(key);
if (err) {
return { err };
}
if (len === 0) {
return { err: error_code_1.ErrorCode.RESULT_NOT_FOUND };
}
else {
const { err: err2, value } = await this.lindex(key, index);
let sql = `DELETE FROM '${this.fullName}' WHERE name='${key}' AND field='${index}'`;
// console.log('lremove', { sql });
await this.db.exec(sql);
for (let i = index + 1; i < len; i++) {
sql = `UPDATE '${this.fullName}' SET field=field-1 WHERE name='${key}' AND field = ${i}`;
// console.log({ sql });
await this.db.exec(sql);
}
return { err: error_code_1.ErrorCode.RESULT_OK, value };
}
}
catch (e) {
this.logger.error(`lremove ${key} `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
}
class SqliteStorageTransaction {
constructor(db) {
this.m_transcationDB = new TransactionDatabase(db.driver);
}
beginTransaction() {
return new Promise((resolve, reject) => {
this.m_transcationDB.beginTransaction((err, transcation) => {
if (err) {
reject(err);
}
else {
this.m_transcation = transcation;
resolve(error_code_1.ErrorCode.RESULT_OK);
}
});
});
}
commit() {
return new Promise((resolve, reject) => {
this.m_transcation.commit((err) => {
if (err) {
reject(err);
}
else {
resolve(error_code_1.ErrorCode.RESULT_OK);
}
});
});
}
rollback() {
return new Promise((resolve, reject) => {
this.m_transcation.rollback((err) => {
if (err) {
reject(err);
}
else {
resolve(error_code_1.ErrorCode.RESULT_OK);
}
});
});
}
}
class SqliteReadableDatabase {
constructor(name, db, logger) {
this.name = name;
this.logger = logger;
this.m_db = db;
}
async getReadableKeyValue(name) {
const fullName = storage_1.Storage.getKeyValueFullName(this.name, name);
let tbl = new SqliteStorageKeyValue(this.m_db, fullName, this.logger);
return { err: error_code_1.ErrorCode.RESULT_OK, kv: tbl };
}
}
class SqliteReadWritableDatabase extends SqliteReadableDatabase {
async createKeyValue(name) {
let err = storage_1.Storage.checkTableName(name);
if (err) {
return { err };
}
const fullName = storage_1.Storage.getKeyValueFullName(this.name, name);
// 先判断表是否存在
let count;
try {
let ret = await this.m_db.get(`SELECT COUNT(*) FROM sqlite_master where type='table' and name='${fullName}'`);
count = ret['COUNT(*)'];
}
catch (e) {
this.logger.error(`select table name failed `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
if (count > 0) {
err = error_code_1.ErrorCode.RESULT_ALREADY_EXIST;
}
else {
err = error_code_1.ErrorCode.RESULT_OK;
await this.m_db.exec(`CREATE TABLE IF NOT EXISTS '${fullName}'\
(name TEXT, field TEXT, value TEXT, unique(name, field))`);
}
let tbl = new SqliteStorageKeyValue(this.m_db, fullName, this.logger);
return { err: error_code_1.ErrorCode.RESULT_OK, kv: tbl };
}
async getReadWritableKeyValue(name) {
let tbl = new SqliteStorageKeyValue(this.m_db, storage_1.Storage.getKeyValueFullName(this.name, name), this.logger);
return { err: error_code_1.ErrorCode.RESULT_OK, kv: tbl };
}
}
class SqliteStorage extends storage_1.Storage {
constructor() {
super(...arguments);
this.m_isInit = false;
}
_createLogger() {
return new storage_1.JStorageLogger();
}
get isInit() {
return this.m_isInit;
}
async init(readonly) {
if (this.m_db) {
return error_code_1.ErrorCode.RESULT_SKIPPED;
}
assert(!this.m_db);
fs.ensureDirSync(path.dirname(this.m_filePath));
let options = {};
if (!readonly) {
options.mode = sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE;
}
else {
options.mode = sqlite3.OPEN_READONLY;
}
let err = error_code_1.ErrorCode.RESULT_OK;
try {
this.m_db = await sqlite.open(this.m_filePath, options);
}
catch (e) {
this.m_logger.error(`open sqlite database file ${this.m_filePath} failed `, e);
err = error_code_1.ErrorCode.RESULT_EXCEPTION;
}
if (!err) {
this.m_isInit = true;
}
try {
this.m_db.run('PRAGMA journal_mode = MEMORY');
this.m_db.run('PRAGMA synchronous = OFF');
this.m_db.run('PRAGMA locking_mode = EXCLUSIVE');
}
catch (e) {
this.m_logger.error(`pragma some options on sqlite database file ${this.m_filePath} failed `, e);
err = error_code_1.ErrorCode.RESULT_EXCEPTION;
}
if (!err) {
this.m_isInit = true;
}
setImmediate(() => {
this.m_eventEmitter.emit('init', err);
});
return err;
}
async uninit() {
if (this.m_db) {
await this.m_db.close();
delete this.m_db;
}
return error_code_1.ErrorCode.RESULT_OK;
}
async messageDigest() {
let buf = await fs.readFile(this.m_filePath);
const sqliteHeaderSize = 100;
if (buf.length < sqliteHeaderSize) {
return { err: error_code_1.ErrorCode.RESULT_INVALID_FORMAT };
}
const content = Buffer.from(buf.buffer, sqliteHeaderSize, buf.length - sqliteHeaderSize);
let hash = digest.hash256(content).toString('hex');
return { err: error_code_1.ErrorCode.RESULT_OK, value: hash };
}
async getReadableDataBase(name) {
let err = storage_1.Storage.checkDataBaseName(name);
if (err) {
return { err };
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: new SqliteReadableDatabase(name, this.m_db, this.m_logger) };
}
async createDatabase(name) {
let err = storage_1.Storage.checkDataBaseName(name);
if (err) {
return { err };
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: new SqliteReadWritableDatabase(name, this.m_db, this.m_logger) };
}
async getReadWritableDatabase(name) {
let err = storage_1.Storage.checkDataBaseName(name);
if (err) {
return { err };
}
return { err: error_code_1.ErrorCode.RESULT_OK, value: new SqliteReadWritableDatabase(name, this.m_db, this.m_logger) };
}
async beginTransaction() {
assert(this.m_db);
let transcation = new SqliteStorageTransaction(this.m_db);
await transcation.beginTransaction();
return { err: error_code_1.ErrorCode.RESULT_OK, value: transcation };
}
async toJsonStorage(storage) {
let tableNames = new Map();
try {
const results = await this.m_db.all(`select name fromsqlite_master where type='table' order by name;`);
for (const { name } of results) {
const { dbName, kvName } = SqliteStorage.splitFullName(name);
if (!tableNames.has(dbName)) {
tableNames.set(dbName, []);
}
tableNames.get(dbName).push(kvName);
}
}
catch (e) {
this.m_logger.error(`get all tables failed `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
let root = Object.create(null);
for (let [dbName, kvNames] of tableNames.entries()) {
let dbRoot = Object.create(null);
root[dbName] = dbRoot;
for (let kvName of kvNames) {
let kvRoot = Object.create(null);
dbRoot[kvName] = kvRoot;
const tableName = SqliteStorage.getKeyValueFullName(dbName, kvName);
try {
const elems = await this.m_db.all(`select * from ${tableName}`);
for (const elem of elems) {
if (util_1.isUndefined(elem.field)) {
kvRoot[elem.name] = serializable_1.fromStringifiable(JSON.parse(elem.value));
}
else {
const index = parseInt(elem.field);
if (isNaN(index)) {
if (util_1.isUndefined(kvRoot[elem.name])) {
kvRoot[elem.name] = Object.create(null);
}
kvRoot[elem.name][elem.filed] = serializable_1.fromStringifiable(JSON.parse(elem.value));
}
else {
if (!util_1.isArray(kvRoot[elem.name])) {
kvRoot[elem.name] = [];
}
let arr = kvRoot[elem.name];
if (arr.length > index) {
arr[index] = serializable_1.fromStringifiable(JSON.parse(elem.value));
}
else {
const offset = index - arr.length - 1;
for (let ix = 0; ix < offset; ++ix) {
arr.push(undefined);
}
arr.push(serializable_1.fromStringifiable(JSON.parse(elem.value)));
}
}
}
}
}
catch (e) {
this.m_logger.error(`database: ${dbName} kv: ${kvName} transfer error `, e);
return { err: error_code_1.ErrorCode.RESULT_EXCEPTION };
}
}
}
await storage.flush(root);
return { err: error_code_1.ErrorCode.RESULT_OK };
}
}
exports.SqliteStorage = SqliteStorage;