方案说明
公司有个项目使用 Electron 开发的,并且需要使用 sqlite 管理数据库,记录一下这个简单的方案
首先上代码文件结构:
migration.js
sql/
v1.sql
v2.sql
v3.sql
...
启动应用后,调用 migration.js 中的升级方法,这个方法步骤是:
- 获取数据库版本,假设是
N
- 然后从
vN+1.sql
开始运行 SQL 代码 - 直到升级完毕
- 中间有错误直接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'
是因为使用了 webpack
的 raw-loader
:
{
test: /\.sql$/i,
use: 'raw-loader',
}
实际使用也可以改为读写文件