import { open } from 'lmdb'
// import mingo from 'mingo' //mingo內未更新import寫法, 會導致ERR_UNSUPPORTED_DIR_IMPORT, 故須改用require引入使用
import mingo from './reqMingo.js'
import size from 'lodash-es/size.js'
import get from 'lodash-es/get.js'
import each from 'lodash-es/each.js'
import map from 'lodash-es/map.js'
import merge from 'lodash-es/merge.js'
import isEqual from 'lodash-es/isEqual.js'
import cloneDeep from 'lodash-es/cloneDeep.js'
import isestr from 'wsemi/src/isestr.mjs'
import isarr from 'wsemi/src/isarr.mjs'
import isearr from 'wsemi/src/isearr.mjs'
import iseobj from 'wsemi/src/iseobj.mjs'
import haskey from 'wsemi/src/haskey.mjs'
import evem from 'wsemi/src/evem.mjs'
import genIDSeq from 'wsemi/src/genIDSeq.mjs'
import pmSeries from 'wsemi/src/pmSeries.mjs'
import waitFun from 'wsemi/src/waitFun.mjs'
/**
* 操作資料庫(LMDB)
*
* @class
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {String} [opt.url='_db'] 輸入資料庫用資料夾字串,預設'_db'
* @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
* @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
* @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
*/
function WOrmLmdb(opt = {}) {
//url
let url = get(opt, 'url')
if (!isestr(url)) {
url = './_db'
}
//db
let db = get(opt, 'db')
if (!isestr(db)) {
db = 'worm'
}
//cl
let cl = get(opt, 'cl')
if (!isestr(cl)) {
cl = 'test'
}
//storage
let storage = `${url}/${db}/${cl}`
// console.log('storage',storage)
// client
let client = open({
path: storage,
compression: true,
useVersions: true,
})
//ee
let ee = evem()
//getData
let getData = async() => {
let errTemp = null
//waitFun
await waitFun(() => {
if (client.status === 'closed') {
console.log(`client.status[${client.status}], level is closed`)
}
return client.status === 'open'
})
// console.log('client.status',client.status)
let ltdt = []
for await (let { value: dt } of client.getRange()) {
// console.log('dt',dt)
ltdt.push(dt)
}
if (errTemp !== null) {
return Promise.reject(errTemp)
}
return ltdt
}
//getValue
let getValue = async (key) => {
let value = null
try {
value = await client.get(key)
}
catch (err) {
// if (err.notFound) {
// console.log('資料不存在')
// }
// else {
// console.log('其他錯誤:', err)
// }
}
return value
}
/**
* 查詢數據
*
* @memberOf WOrmLmdb
* @param {Object} [find={}] 輸入查詢條件物件
* @returns {Promise} 回傳Promise,resolve回傳數據,reject回傳錯誤訊息
*/
async function select(find = {}) {
let isErr = false
let res = null
try {
//ltdt
let ltdt = await getData()
// console.log('select ltdt',ltdt)
//filter
if (iseobj(find)) {
//q
let q = new mingo.Query(find)
// console.log('q', q)
//find
res = q.find(ltdt).all()
// console.log('select(find) ltdt',res)
}
else {
res = ltdt
}
//check
if (!isarr(res)) {
isErr = true
res = `can not select by find[${JSON.stringify(find)}]`
}
}
catch (err) {
isErr = true
res = err
}
// console.log('res', res)
//check
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 插入數據
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳插入結果,reject回傳錯誤訊息
*/
async function insert(data) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return {
n: 0,
nInserted: 0,
ok: 1,
}
}
//cloneDeep, 與外部數據脫勾
data = cloneDeep(data)
//res
let res = null
try {
//check
if (!isarr(data)) {
data = [data]
}
//check id
data = map(data, function(v) {
if (!isestr(v.id)) {
v.id = genIDSeq()
}
return v
})
//each
let nAll = size(data)
let nPush = 0
for (let v of data) {
// console.log(v)
//查找資料表內v.id
let vv = await getValue(v.id) //不會有catch
// console.log('getValue',vv)
//check
if (!iseobj(vv)) {
//未存在v.id
//put
await client.put(v.id, v)
nPush++
}
else {
//已存在v.id則不push
}
}
//res
res = {
n: nAll,
nInserted: nPush,
ok: 1,
}
//emit
ee.emit('change', 'insert', data, res)
}
catch (err) {
isErr = true
res = err
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 儲存數據
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @param {Object} [option={}] 輸入設定物件,預設為{}
* @param {boolean} [option.autoInsert=true] 輸入是否於儲存時發現原本無數據,則自動改以插入處理,預設為true
* @returns {Promise} 回傳Promise,resolve回傳儲存結果,reject回傳錯誤訊息
*/
async function save(data, option = {}) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return []
}
//cloneDeep, 與外部數據脫勾
data = cloneDeep(data)
//autoInsert
let autoInsert = get(option, 'autoInsert', true)
//res
let res = null
try {
//check
if (!isarr(data)) {
data = [data]
}
//check id
data = map(data, function(v) {
if (!isestr(v.id)) {
v.id = genIDSeq()
}
return v
})
//pmSeries
res = await pmSeries(data, async(v) => {
//rest
let rest = null
//查找資料表內v.id
let vv = await getValue(v.id) //不會有catch
//check
if (iseobj(vv)) {
//已存在v.id
if (isEqual(v, vv)) {
//內容相同不更新
}
else {
//內容不同須更新
//merge and put
await client.put(v.id, merge(vv, v))
rest = { update: true }
}
}
//rest
if (iseobj(rest)) {
rest = {
n: 1,
nModified: 1,
ok: 1,
}
}
else {
rest = {
n: 0,
nModified: 0,
ok: 1,
}
}
//autoInsert
if (autoInsert && rest.n === 0) {
rest = await insert(v)
}
return rest
})
//emit
ee.emit('change', 'save', data, res)
}
catch (err) {
isErr = true
res = err
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 刪除數據
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function del(data) {
let isErr = false
//check
if (!iseobj(data) && !isearr(data)) {
return []
}
//cloneDeep, 與外部數據脫勾
data = cloneDeep(data)
//res
let res = null
try {
//check
if (!isarr(data)) {
data = [data]
}
//pmSeries
res = await pmSeries(data, async(v) => {
//rest
let rest = null
//id
let id = get(v, 'id', '')
//check
if (isestr(id)) {
//查找資料表內v.id
let vv = await getValue(v.id) //不會有catch
//check
if (iseobj(vv)) {
//已存在v.id則須刪除
//del
await client.del(v.id)
//rest
rest = {
n: 1,
nDeleted: 1,
ok: 1,
}
}
else {
//不存在v.id則不刪除
//rest
rest = {
n: 1,
nDeleted: 0,
ok: 1,
}
}
}
else {
//未給v.id則不刪除
//rest
rest = {
n: 1,
nDeleted: 0,
ok: 0, //未給v.id視為有問題數據, 故ok給0
}
}
return rest
})
//emit
ee.emit('change', 'del', data, res)
}
catch (err) {
isErr = true
res = err
}
if (isErr) {
return Promise.reject(res)
}
return res
}
/**
* 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
*
* @memberOf WOrmLmdb
* @param {Object} [find={}] 輸入刪除條件物件
* @returns {Promise} 回傳Promise,resolve回傳刪除結果,reject回傳錯誤訊息
*/
async function delAll(find = {}) {
let isErr = false
//res
let res = null
try {
//ltdt
let ltdt = await getData()
//filter
let nAll = size(ltdt)
let nDel = 0
if (iseobj(find)) {
//q
let q = new mingo.Query(find)
// console.log('q', q)
//find
let _res = q.find(ltdt).all()
// console.log('_res', _res)
//nDel
nDel = size(_res)
// console.log('nDel', nDel)
if (nDel === 0) {
//未有find結果等於不刪除
}
else if (nAll === nDel) {
//全在find結果內等於全部刪除
//empty
for (let v of ltdt) {
await client.del(v.id)
}
}
else {
//部份在find結果內
//_kp
let _kp = {}
each(_res, (v, k) => {
_kp[v.id] = { k, v }
})
//del
for (let v of ltdt) {
if (haskey(_kp, v.id)) {
//在find結果內代表須刪除
await client.del(v.id)
}
}
}
}
else {
//nDel
nDel = nAll
//empty
for (let v of ltdt) {
await client.del(v.id)
}
}
//res
res = {
n: nAll,
nDeleted: nDel,
ok: 1,
}
//emit
ee.emit('change', 'delAll', null, res)
}
catch (err) {
isErr = true
res = err
}
if (isErr) {
return Promise.reject(res)
}
return res
}
//save
ee.select = select
ee.insert = insert
ee.save = save
ee.del = del
ee.delAll = delAll
return ee
}
export default WOrmLmdb