import { open, ABORT } 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 isbol from 'wsemi/src/isbol.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)
*
* 回傳物件為EventEmitter,除各操作函數外另發出change與error兩事件,供呼叫端於單一處集中觀察資料異動與失敗。
* 事件僅為附加通知,其所送出之資訊皆另有正規管道(操作結果經resolve、整批性錯誤經reject、逐筆失敗經該筆之err欄位),
* 故不監聽亦能取得完整資訊,且監聽與否不改變任何操作之回傳值。
*
* change事件,參數為(mode, data, res),於資料實際異動成功後發出:
* mode為操作別字串,可為'insert'、'insertBulk'、'save'、'del'、'delAll';save內若逐筆走自動插入則該筆另發出mode為'insert'之事件。
* data為本次操作之輸入數據,delAll固定為null。
* res為本次操作之回傳結果。
* 逐筆函數以整批為單位發出一次而不逐筆發出,select與selectByPk不發出本事件。
*
* error事件,參數為(mode, data, err),於操作發生錯誤時發出:
* mode為操作別字串,可為'select'、'selectByPk'、'insert'、'insertBulk'、'save'、'del'、'delAll'。
* data為本次操作之輸入數據,無輸入數據者為null。
* err為錯誤訊息字串,內容與正規管道所送出者一致。
* 整批性錯誤於reject之前發出;逐筆失敗於該筆結果定案後發出,每筆一次。
* 註: 逐筆失敗時整批仍resolve,故收到本事件不表示該次呼叫失敗;正常結果(如查無數據、主鍵未命中、全數已存在)不發出本事件。
*
* @class
* @param {Object} [opt={}] 輸入設定物件,預設{}
* @param {String} [opt.url='_db'] 輸入資料庫用資料夾字串,預設'_db'
* @param {String} [opt.db='worm'] 輸入使用資料庫名稱字串,預設'worm'
* @param {String} [opt.cl='test'] 輸入使用資料表名稱字串,預設'test'
* @param {Boolean} [opt.useCache=false] 輸入是否使用select快取,適用於單程序操作,預設false
* @param {Boolean} [opt.autoGenPk=true] 輸入insert與save於數據未帶有效主鍵(本套件為id欄位)時是否自動產生主鍵值,預設true;給false代表主鍵改由呼叫端自備,套件不產生亦不檢查其唯一性與格式,未帶有效主鍵者將reject
* @returns {Object} 回傳操作資料庫物件,各事件功能詳見說明
*/
function WOrmLmdb(opt = {}) {
//_cache
let _cache = null
//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'
}
//useCache
let useCache = get(opt, 'useCache')
if (!isbol(useCache)) {
useCache = false
}
//autoGenPk, 主鍵由誰產生為整個資料表之政策, 故為建構層設定, 不可於insert與save之option逐次覆寫,
//否則同一資料表將混入兩種來源之主鍵而難以追溯
let autoGenPk = get(opt, 'autoGenPk')
if (!isbol(autoGenPk)) {
autoGenPk = true
}
//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() => {
//check
if (useCache && isarr(_cache)) {
return cloneDeep(_cache) //與外部使用數據脫勾
}
//waitOpen
await waitOpen()
// console.log('client.status',client.status)
//ltdt
let ltdt = []
for await (let { value: dt } of client.getRange()) {
// console.log('dt',dt)
ltdt.push(dt)
}
if (useCache) {
_cache = ltdt
return cloneDeep(ltdt) //與外部使用數據脫勾
}
return ltdt
}
//waitOpen, 等待client開啟, 因getRange與get於client關閉時會拋錯
//closed為終態: LMDB env一旦關閉不會自行回到open, 續等必然逾時, 故直接拋出而非空轉至逾時
//各函數入口已有procClosed守門, 此處之終態判斷為縱深防禦, 攔[操作進行中被close]之競態
//等待參數收斂: 開啟中為毫秒級過渡, 50次x200ms=10秒已極寬裕, 不使非終態之等待拖至waitFun預設之200秒
let waitOpen = async () => {
if (client.status === 'closed') {
throw new Error('client is closed, instance can not be used after close()')
}
await waitFun(() => {
return client.status === 'open'
}, { attemptNum: 50, timeInterval: 200 })
}
//procClosed, 各函數入口之終態守門: close()後再操作屬整批性錯誤, 立即reject令誤用當場可見
//不可靜默回正常空值(如null或未命中), 否則[已關閉]與[查無資料]同形,
//去重類呼叫端將把每筆判為未存在而fail-open, 整批重送下游昂貴動作
let procClosed = (mode, data) => {
let msg = `can not ${mode} by closed client, instance can not be used after close()`
emitError(mode, data, msg)
return Promise.reject(new Error(msg))
}
//emitChange, 資料實際異動成功後發出, 事件僅為附加通知不承擔回傳義務
//一律包try/catch, 令訂閱函數自身拋錯不影響本次操作之結果
let emitChange = (mode, data, res) => {
try {
ee.emit('change', mode, data, res)
}
catch (err) {
console.log(err)
}
}
//emitError, 操作發生錯誤時發出, 錯誤訊息一律轉為字串
//此try/catch除攔訂閱函數之例外外另有必要: Node之EventEmitter於'error'無監聽者時會將錯誤直接拋出,
//若不攔則同一次操作會因呼叫端有無註冊監聽而走向不同結果
let emitError = (mode, data, err) => {
try {
ee.emit('error', mode, data, getErrMsg(err))
}
catch (errEmit) {
console.log(errEmit)
}
}
//getErrMsg, 取錯誤訊息字串, 供逐筆結果之err欄位與error事件使用
let getErrMsg = (err) => {
let m = get(err, 'message')
if (isestr(m)) {
return m
}
return String(err)
}
//procPk, 依autoGenPk處理各數據之主鍵, 供insert與save共用
//autoGenPk為true時補值; 為false時不補值, 未帶有效主鍵者屬呼叫端未履行契約, 拋出為整批性錯誤,
//且檢查於任何寫入之前完成, 故不會有部份筆數已寫入而整批失敗之情形
let procPk = (data, fnName) => {
if (autoGenPk) {
return map(data, function(v) {
if (!isestr(v.id)) {
v.id = genIDSeq()
}
return v
})
}
each(data, function(v, k) {
if (!isestr(get(v, 'id'))) {
throw new Error(`can not ${fnName} by data[${k}] without valid id when autoGenPk is false`)
}
})
return data
}
//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 = {}) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('select', null)
}
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) {
//emit, 整批性錯誤須於reject之前發出
emitError('select', null, res)
return Promise.reject(res)
}
return res
}
/**
* 由主鍵查詢單筆數據,因直接由LMDB取值,不需如select提取全表數據再過濾,故數據量大時效能較佳
* 註: 本套件之主鍵欄位固定為id,尚未支援指定其他欄位為主鍵
*
* @memberOf WOrmLmdb
* @param {String} pk 輸入主鍵值字串,本套件之主鍵欄位為id
* @returns {Promise} 回傳Promise,resolve回傳數據物件,若無此主鍵則回傳null,reject回傳錯誤訊息
*/
async function selectByPk(pk) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('selectByPk', null)
}
let isErr = false
let res = null
try {
//check
if (!isestr(pk)) {
//未給有效主鍵值視為查無數據
return null
}
//waitOpen
await waitOpen()
//查找資料表內pk
let v = await getValue(pk) //不會有catch
//check, 判定基準與insert、save、del內對既有數據之認定一致
if (iseobj(v)) {
res = v
}
else {
//不存在此主鍵, 回傳null
res = null
}
}
catch (err) {
isErr = true
res = err
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('selectByPk', null, res)
return Promise.reject(res)
}
return res
}
/**
* 插入數據
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @param {Object} [option={}] 輸入設定物件,預設為{}
* @param {Boolean} [option.returnList=false] 輸入是否改回傳逐筆結果布林值,預設false。給true時回傳與輸入等長保序之陣列,各筆為{n,nInserted,ok},nInserted為1即該筆為新增;聚合計數只回答有幾筆是新的,逐筆結果方能回答是哪幾筆,供下游僅對新資料執行昂貴動作。回傳形式由呼叫點靜態決定,共用之結果處理程式碼不可跨不同取值之呼叫點混用
* @returns {Promise} 回傳Promise,resolve依returnList回傳插入結果:預設回傳聚合物件{n,nInserted,ok},n為輸入筆數、nInserted為實際插入筆數;returnList為true時回傳逐筆陣列,各筆n恆為1(主鍵命中或經插入)、ok恆為1(insert之錯誤皆屬整批性而reject,不進逐筆);reject回傳錯誤訊息
*/
async function insert(data, option = {}) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('insert', data)
}
let isErr = false
//returnList
let returnList = get(option, 'returnList')
if (!isbol(returnList)) {
returnList = false
}
//check
if (!iseobj(data) && !isearr(data)) {
if (returnList) {
//輸入無效, 對齊save與del之T5規定回傳空陣列
return []
}
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 = procPk(data, 'insert')
//each
//ifNoExists, 由LMDB於同一寫交易內原子完成[檢查v.id未存在]與[寫入], 併發時同一v.id僅有一次成功,
//同批含重複v.id時亦僅首筆成功, 故不逐筆await而一次送出, 由LMDB寫執行緒依序處理並合併為批次寫交易
//注意: 回調函數須為同步函數, 且client.put須於回調函數內直接呼叫,
//若將client.put置於await之後, 該寫入會逸出條件批次而變成無條件寫入, 並使回傳值與實際狀態不一致
let nAll = size(data)
let ltdone = await Promise.all(map(data, function(v) {
// console.log(v)
return client.ifNoExists(v.id, () => {
client.put(v.id, v)
})
}))
//nPush
let nPush = 0
each(ltdone, function(done) {
if (done) {
//未存在v.id, 已寫入
nPush++
}
else {
//已存在v.id則不push
}
})
//res
if (returnList) {
//逐筆結果, 與輸入等長保序, 元素沿用逐筆結果之家族形狀(同save與del之元素)
//n恆為1(主鍵命中既有或經插入而產生), ok恆為1(insert之錯誤皆屬整批性而reject, 不進逐筆),
//資訊由nInserted承載, filter(v=>v.nInserted===1)之長度必等於聚合模式之nInserted
res = map(ltdone, function(done) {
return {
n: 1,
nInserted: done ? 1 : 0,
ok: 1,
}
})
}
else {
res = {
n: nAll,
nInserted: nPush,
ok: 1,
}
}
}
catch (err) {
isErr = true
res = err
}
//update, 不能保證插入多少, 一律重設快取
_cache = null
//emit, 於change可能須使用select, 故須放在重設快取之後
if (!isErr) {
emitChange('insert', data, res)
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('insert', data, res)
return Promise.reject(res)
}
return res
}
/**
* 批次插入數據,全批視為一個單位,全部插入成功或一筆都不寫入
* 註: 本函數非insert之加速版,兩者衝突政策不同。insert於主鍵已存在時跳過該筆而整批ok為1,
* 本函數則整批reject且不寫入任何一筆;同批含重複主鍵者亦視為衝突。確無衝突時兩者結果相同
* 註: 於本套件不會較insert快,因insert本即以Promise.all一次送出全部條件寫入而非逐筆await,
* 提供本函數係為與其他w-orm系列套件維持同一組函數,令呼叫端得於各套件間替換而不須改寫呼叫
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳插入結果物件{n,nInserted,ok},n為輸入筆數、nInserted成功時恆等於n,任一筆主鍵已存在則reject回傳錯誤訊息
*/
async function insertBulk(data) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('insertBulk', 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 = procPk(data, 'insertBulk')
//nAll
let nAll = size(data)
//childTransaction, 全有全無由交易回滾保證
//註: 非同步之client.transaction於中止時並不回滾, 實測其內之put仍會落盤, 故此處須用childTransaction
//註: 交易內之client.get讀得到同一交易稍早之put, 故同批含重複主鍵者亦會於此被偵測為衝突
//註: 存在與否採鍵層判定, 與insert之ifNoExists一致
let pkConflict = null
await client.childTransaction(() => {
for (let v of data) {
if (client.get(v.id) !== undefined) {
pkConflict = v.id
return ABORT
}
client.put(v.id, v)
}
})
//check, 衝突則整批視為失敗, 交易已回滾故無任何寫入
if (isestr(pkConflict)) {
throw new Error(`can not insertBulk by existed id[${pkConflict}]`)
}
//res, 未衝突則全數插入, 故nInserted恆等於n
res = {
n: nAll,
nInserted: nAll,
ok: 1,
}
}
catch (err) {
isErr = true
res = err
}
//update, 不能保證插入多少, 一律重設快取
_cache = null
//emit, 於change可能須使用select, 故須放在重設快取之後
if (!isErr) {
emitChange('insertBulk', data, res)
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('insertBulk', data, res)
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回傳與輸入等長之儲存結果陣列,各筆為{n,nInserted,nModified,ok},n為主鍵命中筆數,單筆失敗者ok為0並附err,reject回傳錯誤訊息
*/
async function save(data, option = {}) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('save', data)
}
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 = procPk(data, 'save')
//pmSeries
res = await pmSeries(data, async(v) => {
//rest
let rest = null
//inserted, 供emit使用
let inserted = false
try {
//快速路徑, 先不開寫交易直接預讀, 內容相同者本就不須寫入, 可省去開啟寫交易之成本
//預讀值縱使已被其他寫入者更動而過期亦不影響正確性, 因內容相同時本次save等價於無操作,
//無操作可視為於預讀當下即已完成, 其後他人之寫入結果與[本次save先執行再輪到他人]相同
//註: 預讀僅用於判斷是否略過寫入, 不用於決定寫入內容, 寫入內容一律由交易內讀到之現值決定
let vp = await getValue(v.id) //不會有catch
if (iseobj(vp) && isEqual(merge({}, vp, v), vp)) {
//合併後與現值相同不須寫入, 惟已命中v.id故n為1
return {
n: 1,
nInserted: 0,
nModified: 0,
ok: 1,
}
}
//existed, updated
let existed = false
let updated = false
//transaction, 由LMDB於同一寫交易內原子完成[查找v.id]與[合併寫入],
//併發時不會有兩方各自讀到同一舊值再各自寫入而導致遺失更新
//注意: 回調函數須為同步函數, client.get於寫交易內為同步取值, 不可使用await,
//若將client.get或client.put置於await之後, 該操作會逸出本交易而失去原子性
await client.transaction(() => {
//查找資料表內v.id
let vv = client.get(v.id)
//existed
existed = iseobj(vv)
//check
if (existed) {
//已存在v.id
//vm, 合併後之結果
let vm = merge({}, vv, v)
//判定基準為[合併後結果與現值相同], 而非[待寫入物件與現值全等],
//令nModified忠實反映資料庫端是否真的寫入, 僅給部份欄位且值皆相同者不應回報已修改
if (isEqual(vm, vv)) {
//合併後與現值相同不須寫入
}
else {
//合併後與現值不同須更新
//put
client.put(v.id, vm)
updated = true
}
}
else {
//內容不存在, 若autoInsert則須插入
if (autoInsert) {
//put
client.put(v.id, v)
inserted = true
}
}
})
//rest, nInserted與nModified恆存在, 無對應行為者給0
if (inserted) {
rest = {
n: 1,
nInserted: 1,
nModified: 0,
ok: 1,
}
}
else if (updated) {
rest = {
n: 1,
nInserted: 0,
nModified: 1,
ok: 1,
}
}
else {
//未寫入, 有二:
// 已存在v.id而內容相同不更新, 已命中故n為1
// 不存在v.id且未開啟autoInsert, 未命中故n為0
//n為v.id之命中筆數, 與nModified[實際更新筆數]分屬不同語義, 不可混用
rest = {
n: existed ? 1 : 0,
nInserted: 0,
nModified: 0,
ok: 1,
}
}
}
catch (err) {
//本筆失敗不中斷整批, 以ok為0並附err回報, 由呼叫端逐筆檢查
rest = {
n: 1,
nInserted: 0,
nModified: 0,
ok: 0,
err: getErrMsg(err),
}
}
//emit, 原autoInsert係轉呼叫insert故會發出insert事件, 此處維持既有行為,
//須於交易外發出, 避免外部訂閱函數於寫交易內執行
if (inserted) {
//update, 不能保證插入多少, 一律重設快取
_cache = null
//emit
emitChange('insert', [v], rest)
}
//emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
//此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
if (rest.ok === 0) {
emitError('save', [v], rest.err)
}
return rest
})
}
catch (err) {
isErr = true
res = err
}
//update, 不能保證變更多少, 一律重設快取
_cache = null
//emit, 於change可能須使用select, 故須放在重設快取之後
//逐筆失敗之error事件已於各筆定案時發出, 故必早於本整批change
if (!isErr) {
emitChange('save', data, res)
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('save', data, res)
return Promise.reject(res)
}
return res
}
/**
* 刪除數據
*
* @memberOf WOrmLmdb
* @param {Object|Array} data 輸入數據物件或陣列
* @returns {Promise} 回傳Promise,resolve回傳與輸入等長之刪除結果陣列,各筆為{n,nDeleted,ok},n為主鍵命中筆數,單筆失敗或未給有效id者ok為0並附err,reject回傳錯誤訊息
*/
async function del(data) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('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
try {
//id
let id = get(v, 'id', '')
//check
if (!isestr(id)) {
//未給有效v.id視為該筆數據有問題而無法處理, 以ok為0並附err回報,
//與[已給v.id但查無數據]之ok為1有別, 二者須可由ok分辨
//註: 此處不可直接return, 否則會跳過本回調末尾之error事件發出
rest = {
n: 0,
nDeleted: 0,
ok: 0,
err: `can not delete by invalid id[${id}]`,
}
}
else {
//查找資料表內v.id
let vv = await getValue(id) //不會有catch
//check
if (iseobj(vv)) {
//已存在v.id則須刪除
//del
await client.del(id)
//rest
rest = {
n: 1,
nDeleted: 1,
ok: 1,
}
}
else {
//不存在v.id則不刪除, 未命中故n為0
rest = {
n: 0,
nDeleted: 0,
ok: 1,
}
}
}
}
catch (err) {
//本筆失敗不中斷整批, 以ok為0並附err回報, 由呼叫端逐筆檢查
rest = {
n: 1,
nDeleted: 0,
ok: 0,
err: getErrMsg(err),
}
}
//emit, 逐筆失敗於該筆結果定案後發出, 每筆一次
//此事件之發出不表示整批失敗, 整批仍resolve, 該筆以ok為0回報
if (rest.ok === 0) {
emitError('del', [v], rest.err)
}
return rest
})
}
catch (err) {
isErr = true
res = err
}
//update, 不能保證刪除多少, 一律重設快取
_cache = null
//emit, 於change可能須使用select, 故須放在重設快取之後
//逐筆失敗之error事件已於各筆定案時發出, 故必早於本整批change
if (!isErr) {
emitChange('del', data, res)
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('del', data, res)
return Promise.reject(res)
}
return res
}
/**
* 刪除全部數據,需與del分開,避免未傳數據導致直接刪除全表
*
* @memberOf WOrmLmdb
* @param {Object} [find={}] 輸入刪除條件物件
* @returns {Promise} 回傳Promise,resolve回傳刪除結果物件{n,nDeleted,ok},n與nDeleted同為實際刪除筆數,reject回傳錯誤訊息
*/
async function delAll(find = {}) {
//check client, closed為終態, 於任何檢查與寫入之前先行攔下(T4: 實例已關閉屬整批性錯誤)
if (client.status === 'closed') {
return procClosed('delAll', null)
}
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, n為實際刪除筆數而非全表筆數, 故與nDeleted同值
res = {
n: nDel,
nDeleted: nDel,
ok: 1,
}
}
catch (err) {
isErr = true
res = err
}
//update, 不能保證刪除多少, 一律重設快取
_cache = null
//emit, 於change可能須使用select, 故須放在重設快取之後
if (!isErr) {
emitChange('delAll', null, res)
}
//check
if (isErr) {
//emit, 整批性錯誤須於reject之前發出
emitError('delAll', null, res)
return Promise.reject(res)
}
return res
}
//close, 關閉內部LMDB env並釋放檔案鎖, close後該實例即報廢, 須重新new WOrmLmdb,
//再呼叫任何操作函數將立即reject(訊息明示closed), 不與[查無資料]同形
let close = async () => {
if (client && client.status !== 'closed') {
await client.close() //lmdb root database之close(), 回傳Promise
}
}
//save
ee.select = select
ee.selectByPk = selectByPk
ee.insert = insert
ee.insertBulk = insertBulk
ee.save = save
ee.del = del
ee.delAll = delAll
ee.close = close
return ee
}
export default WOrmLmdb