import { setUnEnumerable } from "../../../objects" import { Kvlite } from "./Kvlite" import { inputDoc, outputDoc } from "./util" import { defineIndexes, IKvDefineIndexesOptions, IKvIndex } from "./defineIndexes" export interface IKvliteArrayOptions {} /** * 基于 Kvlite 的数组 * 数据的增删改查都在 collection 中进行 * 实现了 Array 的方法 */ export class KvliteArray { collectionName!: string _kvlite!: Kvlite _options!: IKvliteArrayOptions /** 预编译的 SQL */ _stmts!: ReturnType constructor(kvlite: Kvlite, collectionName: string, options?: IKvliteArrayOptions) { this.collectionName = collectionName const sqlite = kvlite._sqlite const exists = sqlite .prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='${collectionName}';`) .get() if (!exists) { sqlite.exec(`CREATE TABLE ${collectionName} (id INTEGER PRIMARY KEY AUTOINCREMENT, value JSON);`) } setUnEnumerable(this, "_kvlite", kvlite) setUnEnumerable(this, "_options", options) setUnEnumerable(this, "_stmts", this._prepareStmts()) } /** 初始化预编译语句 */ _prepareStmts() { const sqlite = this._kvlite._sqlite const collection = this.collectionName // 准备常用的 SQL 语句 const stmtPush = sqlite.prepare(`INSERT INTO ${collection} (value) VALUES (?)`) const stmtPop = sqlite.prepare( `DELETE FROM ${collection} WHERE id = (SELECT MAX(id) FROM ${collection}) RETURNING value` ) const stmtShift = sqlite.prepare( `DELETE FROM ${collection} WHERE id = (SELECT MIN(id) FROM ${collection}) RETURNING value` ) const stmtUnshift = sqlite.prepare( `INSERT INTO ${collection} (id, value) VALUES ((SELECT MIN(id) FROM ${collection}) - 1, ?)` ) const stmtLength = sqlite.prepare(`SELECT COUNT(*) as count FROM ${collection}`) const stmtClear = sqlite.prepare(`DELETE FROM ${collection}`) const stmtGetByIndex = sqlite.prepare(`SELECT value FROM ${collection} ORDER BY id LIMIT 1 OFFSET ?`) const stmtSetByIndex = sqlite.prepare( `UPDATE ${collection} SET value = ? WHERE id = (SELECT id FROM ${collection} ORDER BY id LIMIT 1 OFFSET ?)` ) const stmtGetAll = sqlite.prepare(`SELECT value FROM ${collection} ORDER BY id`) const stmtSlice = sqlite.prepare(`SELECT value FROM ${collection} ORDER BY id LIMIT ? OFFSET ?`) return { stmtPush, stmtPop, stmtShift, stmtUnshift, stmtLength, stmtClear, stmtGetByIndex, stmtSetByIndex, stmtGetAll, stmtSlice, } } // ========================================================================== /** * 获取数组长度 */ get length(): number { const { stmtLength } = this._stmts const result = stmtLength.get() as any return result.count } /** * 在数组末尾添加一个或多个元素 */ push(...items: TData[]): number { if (items.length === 0) return this.length const { stmtPush } = this._stmts const sqlite = this._kvlite._sqlite const transaction = sqlite.transaction(() => { for (const item of items) { stmtPush.run(inputDoc(item)) } }) transaction() return this.length } /** * 删除并返回数组的最后一个元素 */ pop(): TData | undefined { const { stmtPop } = this._stmts const result = stmtPop.get() as any return result ? outputDoc(result.value) : undefined } /** * 删除并返回数组的第一个元素 */ shift(): TData | undefined { const { stmtShift } = this._stmts const result = stmtShift.get() as any return result ? outputDoc(result.value) : undefined } /** * 在数组开头添加一个或多个元素 */ unshift(...items: TData[]): number { if (items.length === 0) return this.length const { stmtUnshift } = this._stmts const sqlite = this._kvlite._sqlite const transaction = sqlite.transaction(() => { for (let i = items.length - 1; i >= 0; i--) { stmtUnshift.run(inputDoc(items[i])) } }) transaction() return this.length } /** * 通过索引获取元素 */ at(index: number): TData | undefined { if (index < 0) { index = this.length + index } if (index < 0 || index >= this.length) { return undefined } const { stmtGetByIndex } = this._stmts const result = stmtGetByIndex.get(index) as any return result ? outputDoc(result.value) : undefined } /** * 通过索引获取元素(不支持负数索引) */ get(index: number): TData | undefined { if (index < 0 || index >= this.length) { return undefined } const { stmtGetByIndex } = this._stmts const result = stmtGetByIndex.get(index) as any return result ? outputDoc(result.value) : undefined } /** * 通过索引设置元素 */ set(index: number, value: TData): void { const { stmtSetByIndex } = this._stmts if (index < 0) { index = this.length + index } if (index < 0 || index >= this.length) { return } stmtSetByIndex.run(inputDoc(value), index) } /** * 清空数组 */ clear(): void { const { stmtClear } = this._stmts stmtClear.run() } /** * 将数组转换为 JavaScript 数组 */ toArray(): TData[] { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] return results.map((row) => outputDoc(row.value)) } /** * 查找元素的索引 */ indexOf(searchElement: TData): number { const { stmtGetAll } = this._stmts const allRows = stmtGetAll.all() as any[] let docSearchElement = inputDoc(searchElement) for (let i = 0; i < allRows.length; i++) { const curData = String(allRows[i].value) if (docSearchElement === curData) { return i } } return -1 } /** * 检查数组是否包含某个元素 */ includes(searchElement: TData): boolean { return this.indexOf(searchElement) !== -1 } /** * 返回数组的一部分 */ slice(start: number = 0, end?: number): TData[] { const length = this.length if (start < 0) start = Math.max(0, length + start) if (end === undefined) end = length if (end < 0) end = Math.max(0, length + end) const sliceLength = Math.max(0, end - start) if (sliceLength === 0) return [] const { stmtSlice } = this._stmts const results = stmtSlice.all(sliceLength, start) as any[] return results.map((row) => outputDoc(row.value)) } /** * 删除现有元素并可选择性地添加新元素 */ splice(start: number, deleteCount?: number, ...items: TData[]): TData[] { const length = this.length const { stmtClear, stmtPush } = this._stmts const sqlite = this._kvlite._sqlite // 处理负数索引 if (start < 0) { start = Math.max(0, length + start) } else { start = Math.min(start, length) } // 处理 deleteCount if (deleteCount === undefined) { deleteCount = length - start } else { deleteCount = Math.max(0, Math.min(deleteCount, length - start)) } // 获取要删除的元素 const deletedElements = this.slice(start, start + deleteCount) // 使用事务执行操作 const transaction = sqlite.transaction(() => { // 先获取当前所有元素 const allItems = this.toArray() // 构建新数组 const newArray = [...allItems.slice(0, start), ...items, ...allItems.slice(start + deleteCount!)] // 清空表并重新插入 stmtClear.run() for (const item of newArray) { stmtPush.run(inputDoc(item)) } }) transaction() return deletedElements } /** * 遍历数组 */ forEach(callback: (value: TData, index: number, array: KvliteArray) => void): void { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] results.forEach((row, index) => { callback(outputDoc(row.value), index, this) }) } /** * 映射数组 */ map(callback: (value: TData, index: number, array: KvliteArray) => U): U[] { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] return results.map((row, index) => { return callback(outputDoc(row.value), index, this) }) } /** * 过滤数组 */ filter(callback: (value: TData, index: number, array: KvliteArray) => boolean): TData[] { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] return results .map((row, index) => ({ value: outputDoc(row.value), index })) .filter(({ value, index }) => callback(value, index, this)) .map(({ value }) => value) } /** * 查找第一个符合条件的元素 */ find(callback: (value: TData, index: number, array: KvliteArray) => boolean): TData | undefined { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] for (let i = 0; i < results.length; i++) { const value = outputDoc(results[i].value) if (callback(value, i, this)) { return value } } return undefined } /** * 查找第一个符合条件的元素的索引 */ findIndex(callback: (value: TData, index: number, array: KvliteArray) => boolean): number { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] for (let i = 0; i < results.length; i++) { const value = outputDoc(results[i].value) if (callback(value, i, this)) { return i } } return -1 } /** * 检查是否所有元素都符合条件 */ every(callback: (value: TData, index: number, array: KvliteArray) => boolean): boolean { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] for (let i = 0; i < results.length; i++) { const value = outputDoc(results[i].value) if (!callback(value, i, this)) { return false } } return true } /** * 检查是否有元素符合条件 */ some(callback: (value: TData, index: number, array: KvliteArray) => boolean): boolean { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] for (let i = 0; i < results.length; i++) { const value = outputDoc(results[i].value) if (callback(value, i, this)) { return true } } return false } /** * 聚合数组元素 */ reduce( callback: (accumulator: U, currentValue: TData, currentIndex: number, array: KvliteArray) => U, initialValue: U ): U { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] let accumulator = initialValue for (let i = 0; i < results.length; i++) { const value = outputDoc(results[i].value) accumulator = callback(accumulator, value, i, this) } return accumulator } /** * 实现迭代器 */ [Symbol.iterator](): Iterator { const { stmtGetAll } = this._stmts const results = stmtGetAll.all() as any[] let index = 0 return { next(): IteratorResult { if (index < results.length) { return { value: outputDoc(results[index++].value), done: false } } else { return { done: true, value: undefined } } }, } } /** * 转换为字符串 */ toString(): string { return this.toArray().toString() } /** * 转换为 JSON 字符串 */ toJSON(): TData[] { return this.toArray() } // ========================================================================== /** * 执行事务 * 执行一个函数,保证事务的完整性。 * 如果有大量的数据操作,如批量插入、删除、更新,使用事务能大幅度提高性能 */ runTransaction(func: () => any) { let sqlite = this._kvlite._sqlite const adopt = sqlite.transaction(func) adopt() } /** * 定义索引 * 指定 data 中哪些字段需要创建索引,可以加快查询速度 * * field 为 false 会删除索引 * * @example * defineIndexes({ id:{unique:true}, name:true, age:{type:'number'} }) */ defineIndexes(indexes: { [field: string]: IKvIndex }, options?: IKvDefineIndexesOptions) { let sqlite = this._kvlite._sqlite let collectionName = this.collectionName return defineIndexes(sqlite, collectionName, indexes, { ...options, columnName: "value" }) } /** * 将集合所属的 Kvlite 导出为 Buffer 数据,可以保存为文件 */ toBuffer() { return this._kvlite.toBuffer() } }