Skip to content

适配器

FlexTree本质上就是将对树的查询、删除、移动、更新等操作转换为SQL,然后交给数据库引擎去执行。所以FlexTree是一个抽象的树存储库,它并不直接操作数据库,而是通过数据库适配器来操作数据库。

适配器接口

当用户调用FlexTree的API后,会调用数据库适配器的方法来操作数据库。数据库适配器是一个实现了IFlexTreeAdapter接口的对象,它负责执行SQL语句并返回结果。

IFlexTreeAdapter接口定义如下:

ts
interface IFlexTreeAdapter {
    // 当数据库打开准备就绪时
    ready: boolean
    // 绑定树管理器
    bind: (treeManager: manager.FlexTreeManager) => void
    // 执行sql,并返回结果
    exec: (sqls: string | string[]) => Promise<void>
    // 执行查询并返回结果
    getRows: (sql: string) => Promise<any[]>
    // 执行查询并返回标量
    getScalar: <T = number>(sql: string) => Promise<T>
    open: (config?: any) => Promise<any>
    // 返回一个数据库实例对象
    db: any
}

ready

当数据库适配器准备就绪时,ready属性为true,否则为false

bind

bind方法用于绑定树管理器,当FlexTree创建时,会调用bind方法将树管理器绑定到适配器上。

exec

执行SQL语句,exec方法接收一个SQL语句或SQL语句数组,然后执行SQL语句。

getRows

执行查询并返回结果集,getRows方法接收一个SQL语句,然后执行查询并返回结果集。

getScalar

执行查询并返回标量,getScalar方法接收一个SQL语句,然后执行查询并返回标量。

open

FlexTree初始化时,会调用open方法打开数据库连接。

db

返回一个数据库实例对象,仅仅在测试中使用。

适配器实现示例

以下是flextree-sqlite-adapter的实现代码。

ts
import type { FlexTreeManager, IFlexTreeAdapter } from 'flextree'
import Database from 'better-sqlite3' 

export type SqliteDatabase = Database.Database
export default class SqliteAdapter implements IFlexTreeAdapter {
    _db?: SqliteDatabase
    _options: Database.Options
    _ready: boolean = false
    _filename?: string
    _treeManager?: FlexTreeManager
    constructor(filename?: string, options?: Database.Options) {
        this._options = Object.assign({}, options)
        this._filename = filename || ':memory:'
    }

    get ready() { return this._ready }
    get db() { return this._db! as SqliteDatabase }
    get treeManager() { return this._treeManager! }
    get tableName() { return this.treeManager.tableName }
    bind(treeManager: FlexTreeManager) {
        this._treeManager = treeManager
    }
    open(options?: Database.Options) {
        return new Promise((resolve, reject) => {
            try {
                this._db = new Database(this._filename, Object.assign({}, this._options, options))
                this._ready = true
                resolve(this._db)
            } catch (e: any) {
                this._ready = false
                reject(e)
            }
        })
    }

    assertDbIsOpen() {
        if (!this.db) {
            throw new Error('Sqlite database is not opened.')
        }
    }

    async getRows<T>(sql: string): Promise<T[]> {
        this.assertDbIsOpen()
        return await this.db.prepare<unknown[], T>(sql).all()
    }

    async getScalar<T>(sql: string): Promise<T> {
        this.assertDbIsOpen()
        return await this.db.prepare(sql).pluck().get() as T
    }

    async exec(sqls: string | string[]) {
        this.assertDbIsOpen()
        if (typeof sqls === 'string') {
            sqls = [sqls]
        }
        const stmts = sqls.map(sql => this.db.prepare(sql))
        const trans = this.db.transaction(() => {
            for (const stmt of stmts) {
                stmt.run()
            }
        })
        trans()
    }
}