mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
add new sql.js module and tests #27
This commit is contained in:
@@ -1,98 +1,34 @@
|
|||||||
import registerPromiseWorker from 'promise-worker/register'
|
import registerPromiseWorker from 'promise-worker/register'
|
||||||
import initSqlJs from 'sql.js/dist/sql-wasm.js'
|
import Sql from '@/sql'
|
||||||
import dbUtils from '@/dbUtils'
|
|
||||||
|
|
||||||
const sqlModuleReady = initSqlJs()
|
const sqlReady = Sql.build()
|
||||||
let db = null
|
|
||||||
|
|
||||||
function onModuleReady (SQL) {
|
|
||||||
function createDb (data) {
|
|
||||||
if (db != null) db.close()
|
|
||||||
db = new SQL.Database(data)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function processMsg (sql) {
|
||||||
const data = this
|
const data = this
|
||||||
|
|
||||||
switch (data && data.action) {
|
switch (data && data.action) {
|
||||||
case 'open':
|
case 'open':
|
||||||
const buff = data.buffer
|
return sql.open(data.buffer)
|
||||||
createDb(buff && new Uint8Array(buff))
|
|
||||||
return {
|
|
||||||
ready: true
|
|
||||||
}
|
|
||||||
case 'exec':
|
case 'exec':
|
||||||
if (db === null) {
|
return sql.exec(data.sql, data.params)
|
||||||
createDb()
|
|
||||||
}
|
|
||||||
if (!data.sql) {
|
|
||||||
throw new Error('exec: Missing query string')
|
|
||||||
}
|
|
||||||
return db.exec(data.sql, data.params)
|
|
||||||
case 'each':
|
|
||||||
if (db === null) {
|
|
||||||
createDb()
|
|
||||||
}
|
|
||||||
const callback = function callback (row) {
|
|
||||||
return {
|
|
||||||
row: row,
|
|
||||||
finished: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const done = function done () {
|
|
||||||
return {
|
|
||||||
finished: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return db.each(data.sql, data.params, callback, done)
|
|
||||||
case 'import':
|
case 'import':
|
||||||
createDb()
|
return sql.import(data.columns, data.values, data.progressCounterId, postMessage)
|
||||||
const values = data.values
|
|
||||||
const columns = data.columns
|
|
||||||
const chunkSize = 1500
|
|
||||||
db.exec(dbUtils.getCreateStatement(columns, values))
|
|
||||||
const chunks = dbUtils.generateChunks(values, chunkSize)
|
|
||||||
const chunksAmount = Math.ceil(values.length / chunkSize)
|
|
||||||
let count = 0
|
|
||||||
const insertStr = dbUtils.getInsertStmt(columns)
|
|
||||||
const insertStmt = db.prepare(insertStr)
|
|
||||||
|
|
||||||
postMessage({ progress: 0, id: data.progressCounterId })
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
db.exec('BEGIN')
|
|
||||||
for (const row of chunk) {
|
|
||||||
insertStmt.run(row)
|
|
||||||
}
|
|
||||||
db.exec('COMMIT')
|
|
||||||
count++
|
|
||||||
postMessage({ progress: 100 * (count / chunksAmount), id: data.progressCounterId })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
finish: true
|
|
||||||
}
|
|
||||||
case 'export':
|
case 'export':
|
||||||
return db.export()
|
return sql.export()
|
||||||
case 'close':
|
case 'close':
|
||||||
if (db) {
|
return sql.close()
|
||||||
db.close()
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
finished: true
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid action : ' + (data && data.action))
|
throw new Error('Invalid action : ' + (data && data.action))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onError (err) {
|
function onError (error) {
|
||||||
return {
|
return {
|
||||||
error: new Error(err.message)
|
error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPromiseWorker(data => {
|
registerPromiseWorker(data => {
|
||||||
return sqlModuleReady
|
return sqlReady
|
||||||
.then(onModuleReady.bind(data))
|
.then(processMsg.bind(data))
|
||||||
.catch(onError)
|
.catch(onError)
|
||||||
})
|
})
|
||||||
|
|||||||
79
src/sql.js
Normal file
79
src/sql.js
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import initSqlJs from 'sql.js/dist/sql-wasm.js'
|
||||||
|
import dbUtils from '@/dbUtils'
|
||||||
|
|
||||||
|
let SQL = null
|
||||||
|
const sqlModuleReady = initSqlJs().then(sqlModule => { SQL = sqlModule })
|
||||||
|
|
||||||
|
export default class Sql {
|
||||||
|
constructor () {
|
||||||
|
this.db = null
|
||||||
|
}
|
||||||
|
|
||||||
|
static build () {
|
||||||
|
return sqlModuleReady
|
||||||
|
.then(() => {
|
||||||
|
return new Sql()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createDb (buffer) {
|
||||||
|
if (this.db != null) this.db.close()
|
||||||
|
this.db = new SQL.Database(buffer)
|
||||||
|
return this.db
|
||||||
|
}
|
||||||
|
|
||||||
|
open (buffer) {
|
||||||
|
this.createDb(buffer && new Uint8Array(buffer))
|
||||||
|
return {
|
||||||
|
ready: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exec (sql, params) {
|
||||||
|
if (this.db === null) {
|
||||||
|
this.createDb()
|
||||||
|
}
|
||||||
|
if (!sql) {
|
||||||
|
throw new Error('exec: Missing query string')
|
||||||
|
}
|
||||||
|
return this.db.exec(sql, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
import (columns, values, progressCounterId, progressCallback, chunkSize = 1500) {
|
||||||
|
this.createDb()
|
||||||
|
this.db.exec(dbUtils.getCreateStatement(columns, values))
|
||||||
|
const chunks = dbUtils.generateChunks(values, chunkSize)
|
||||||
|
const chunksAmount = Math.ceil(values.length / chunkSize)
|
||||||
|
let count = 0
|
||||||
|
const insertStr = dbUtils.getInsertStmt(columns)
|
||||||
|
const insertStmt = this.db.prepare(insertStr)
|
||||||
|
|
||||||
|
progressCallback({ progress: 0, id: progressCounterId })
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
this.db.exec('BEGIN')
|
||||||
|
for (const row of chunk) {
|
||||||
|
insertStmt.run(row)
|
||||||
|
}
|
||||||
|
this.db.exec('COMMIT')
|
||||||
|
count++
|
||||||
|
progressCallback({ progress: 100 * (count / chunksAmount), id: progressCounterId })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
finish: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export () {
|
||||||
|
return this.db.export()
|
||||||
|
}
|
||||||
|
|
||||||
|
close () {
|
||||||
|
if (this.db) {
|
||||||
|
this.db.close()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
finished: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
tests/unit/sql.spec.js
Normal file
171
tests/unit/sql.spec.js
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import chai from 'chai'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import chaiAsPromised from 'chai-as-promised'
|
||||||
|
import initSqlJs from 'sql.js'
|
||||||
|
import Sql from '@/sql'
|
||||||
|
chai.use(chaiAsPromised)
|
||||||
|
const expect = chai.expect
|
||||||
|
chai.should()
|
||||||
|
|
||||||
|
const getSQL = initSqlJs()
|
||||||
|
|
||||||
|
describe('sql.js', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
sinon.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns a query result', async () => {
|
||||||
|
const SQL = await getSQL
|
||||||
|
const tempDb = new SQL.Database()
|
||||||
|
tempDb.run(`
|
||||||
|
CREATE TABLE test (
|
||||||
|
id integer,
|
||||||
|
name varchar(100),
|
||||||
|
faculty varchar(100)
|
||||||
|
);
|
||||||
|
INSERT INTO test (id, name, faculty)
|
||||||
|
VALUES
|
||||||
|
( 1, 'Harry Potter', 'Griffindor'),
|
||||||
|
( 2, 'Draco Malfoy', 'Slytherin');
|
||||||
|
`)
|
||||||
|
|
||||||
|
const data = tempDb.export()
|
||||||
|
const sql = await Sql.build()
|
||||||
|
sql.open(data)
|
||||||
|
const result = sql.exec('SELECT * from test')
|
||||||
|
expect(result).to.have.lengthOf(1)
|
||||||
|
expect(result[0].columns).to.eql(['id', 'name', 'faculty'])
|
||||||
|
expect(result[0].values).to.have.lengthOf(2)
|
||||||
|
expect(result[0].values[0]).to.eql([1, 'Harry Potter', 'Griffindor'])
|
||||||
|
expect(result[0].values[1]).to.eql([2, 'Draco Malfoy', 'Slytherin'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('throws an error if query is empty', async () => {
|
||||||
|
const SQL = await getSQL
|
||||||
|
const tempDb = new SQL.Database()
|
||||||
|
tempDb.run(`
|
||||||
|
CREATE TABLE test (
|
||||||
|
id integer,
|
||||||
|
name varchar(100),
|
||||||
|
faculty varchar(100)
|
||||||
|
);
|
||||||
|
INSERT INTO test (id, name, faculty)
|
||||||
|
VALUES
|
||||||
|
( 1, 'Harry Potter', 'Griffindor'),
|
||||||
|
( 2, 'Draco Malfoy', 'Slytherin');
|
||||||
|
`)
|
||||||
|
|
||||||
|
const data = tempDb.export()
|
||||||
|
const sql = await Sql.build()
|
||||||
|
sql.open(data)
|
||||||
|
expect(() => { sql.exec() }).to.throw('exec: Missing query string')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('imports', async () => {
|
||||||
|
const data = {
|
||||||
|
columns: ['id', 'name'],
|
||||||
|
values: [
|
||||||
|
[1, 'Harry Potter'],
|
||||||
|
[2, 'Draco Malfoy'],
|
||||||
|
[3, 'Hermione Granger'],
|
||||||
|
[4, 'Ron Weasley']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
const progressCallback = sinon.stub()
|
||||||
|
const progressCounterId = 1
|
||||||
|
const sql = await Sql.build()
|
||||||
|
sql.import(data.columns, data.values, progressCounterId, progressCallback, 2)
|
||||||
|
const result = sql.exec('SELECT * from csv_import')
|
||||||
|
expect(result).to.have.lengthOf(1)
|
||||||
|
expect(result[0].columns).to.eql(['id', 'name'])
|
||||||
|
expect(result[0].values).to.have.lengthOf(4)
|
||||||
|
expect(result[0].values[0]).to.eql([1, 'Harry Potter'])
|
||||||
|
expect(result[0].values[1]).to.eql([2, 'Draco Malfoy'])
|
||||||
|
expect(result[0].values[2]).to.eql([3, 'Hermione Granger'])
|
||||||
|
expect(result[0].values[3]).to.eql([4, 'Ron Weasley'])
|
||||||
|
|
||||||
|
expect(progressCallback.calledThrice).to.equal(true)
|
||||||
|
expect(progressCallback.getCall(0).args[0]).to.eql({ progress: 0, id: 1 })
|
||||||
|
expect(progressCallback.getCall(1).args[0]).to.eql({ progress: 50, id: 1 })
|
||||||
|
expect(progressCallback.getCall(2).args[0]).to.eql({ progress: 100, id: 1 })
|
||||||
|
})
|
||||||
|
|
||||||
|
it('exports', async () => {
|
||||||
|
const sql = await Sql.build()
|
||||||
|
sql.exec(`
|
||||||
|
CREATE TABLE test (
|
||||||
|
id integer,
|
||||||
|
name varchar(100),
|
||||||
|
faculty varchar(100)
|
||||||
|
);
|
||||||
|
INSERT INTO test (id, name, faculty)
|
||||||
|
VALUES
|
||||||
|
( 1, 'Harry Potter', 'Griffindor'),
|
||||||
|
( 2, 'Draco Malfoy', 'Slytherin');
|
||||||
|
`)
|
||||||
|
const data = sql.export()
|
||||||
|
const anotherSql = await Sql.build()
|
||||||
|
anotherSql.open(data)
|
||||||
|
const result = anotherSql.exec('SELECT * from test')
|
||||||
|
expect(result).to.have.lengthOf(1)
|
||||||
|
expect(result[0].columns).to.eql(['id', 'name', 'faculty'])
|
||||||
|
expect(result[0].values).to.have.lengthOf(2)
|
||||||
|
expect(result[0].values[0]).to.eql([1, 'Harry Potter', 'Griffindor'])
|
||||||
|
expect(result[0].values[1]).to.eql([2, 'Draco Malfoy', 'Slytherin'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('closes', async () => {
|
||||||
|
const sql = await Sql.build()
|
||||||
|
|
||||||
|
// nothing breaks if close empty db
|
||||||
|
sql.close()
|
||||||
|
|
||||||
|
sql.exec(`
|
||||||
|
CREATE TABLE test (
|
||||||
|
id integer,
|
||||||
|
name varchar(100)
|
||||||
|
);
|
||||||
|
INSERT INTO test (id, name)
|
||||||
|
VALUES
|
||||||
|
( 1, 'Harry Potter'),
|
||||||
|
( 2, 'Draco Malfoy');
|
||||||
|
`)
|
||||||
|
expect(sql.db.db).to.not.equal(null)
|
||||||
|
sql.close()
|
||||||
|
expect(sql.db.db).to.equal(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('overwrites', async () => {
|
||||||
|
const sql = await Sql.build()
|
||||||
|
sql.exec(`
|
||||||
|
CREATE TABLE test (
|
||||||
|
id integer,
|
||||||
|
name varchar(100)
|
||||||
|
);
|
||||||
|
INSERT INTO test (id, name)
|
||||||
|
VALUES
|
||||||
|
( 1, 'foo'),
|
||||||
|
( 2, 'bar');
|
||||||
|
`)
|
||||||
|
|
||||||
|
let result = sql.exec('SELECT * from test')
|
||||||
|
expect(result[0].values).to.have.lengthOf(2)
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
columns: ['id', 'name'],
|
||||||
|
values: [
|
||||||
|
[1, 'Harry Potter'],
|
||||||
|
[2, 'Draco Malfoy'],
|
||||||
|
[3, 'Hermione Granger'],
|
||||||
|
[4, 'Ron Weasley']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
// rewrite the database by import
|
||||||
|
sql.import(data.columns, data.values, 1, sinon.stub(), 2)
|
||||||
|
result = sql.exec('SELECT * from csv_import')
|
||||||
|
expect(result[0].values).to.have.lengthOf(4)
|
||||||
|
|
||||||
|
// test table oesn't exists anymore: the db was overwritten
|
||||||
|
expect(() => { sql.exec('SELECT * from test') }).to.throw('no such table: test')
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user