Electron SQLite简单数据库升级方案

1,533 阅读1分钟

方案说明

公司有个项目使用 Electron 开发的,并且需要使用 sqlite 管理数据库,记录一下这个简单的方案

首先上代码文件结构:

migration.js
sql/
    v1.sql
    v2.sql
    v3.sql
    ...

启动应用后,调用 migration.js 中的升级方法,这个方法步骤是:

  1. 获取数据库版本,假设是 N
  2. 然后从 vN+1.sql 开始运行 SQL 代码
  3. 直到升级完毕
  4. 中间有错误直接rollback

然后后续迭代,通过增加版本 sql 文件即可

migration.js

import v1SQL from './sql/v1.sql'
import v2SQL from './sql/v2.sql'
import v3SQL from './sql/v3.sql'
...更多版本文件

const SQLs = [v1SQL, v2SQL, v3SQL, ...];

export default function(db, callback) {
  // 查询 db 版本
  db.get('PRAGMA user_version;', (err, row) => {
    if (err) {
      return callback(err)
    }
    let currentVer = row.user_version
    let targetVer = SQLs.length

    if (currentVer > targetVer) {
      return callback(new Error('数据库版本过高'))
    }

    // 版本相同,跳过
    if (currentVer == targetVer) {
      return callback()
    }

    runInTransaction(
      db,
      complete => {
        // 收集所有SQL语句
        let sqlLines = collectSQLs(currentVer, targetVer)
        // 队列运行每一句SQL
        queueRun(db, targetVer, sqlLines, complete)
      },
      err => {
        callback(err)
      }
    )
  })
}

// 将要升级的SQL按语句解析为数据
function collectSQLs(currentVer, targetVer) {
  let sqlLines = []
  let version = currentVer
  while (version < targetVer) {
    let sql = SQLs[version]
    if (sql) {
      // 由于这个sqlite3库只支持单语句,所以使用这个划分一下
      let stats = sql.split(';')
      stats.forEach(stat => {
        if (stat.trim()) {
          sqlLines.push(stat + ';')
        }
      })
    }
    version++
  }
  return sqlLines
}

// 提供一个事务工具函数
function runInTransaction(db, func, callback) {
  db.serialize(() => {
    db.run('BEGIN;')
    func(err => {
      if (err) {
        return db.run('ROLLBACK;') && callback && callback(err)
      }
      db.run('COMMIT;') && callback && callback()
    })
  })
}

// 队列运行SQL
function queueRun(db, targetVer, sqlLines, callback) {
  const runNext = () => {
    if (sqlLines.length == 0) {
      db.exec(`PRAGMA user_version = ${targetVer}`, err => {
        if (err) {
          return callback(err)
        }
        return callback()
      })
      return
    }
    let sql = sqlLines.shift()
    db.exec(sql, err => {
      if (err) {
        return callback(err)
      }
      runNext()
    })
  }

  runNext()
}

这里引入 .sql 文件内容时使用 import:

import v1SQL from './sql/v1.sql'

是因为使用了 webpackraw-loader:

{
  test: /\.sql$/i,
  use: 'raw-loader',
}

实际使用也可以改为读写文件