|
|
|
@ -1,8 +1,9 @@ |
|
|
|
|
import React from 'react'; |
|
|
|
|
|
|
|
|
|
var DBTypeEnum = { |
|
|
|
|
export const DBTypeEnum = { |
|
|
|
|
ALASQL_SQLITE: 1, |
|
|
|
|
ALASQL_INDEXEDDB: 2 // TODO: implement with migration
|
|
|
|
|
ALASQL_INDEXEDDB: 2, |
|
|
|
|
ALASQL_NATIVE: 3 |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
export class DB { |
|
|
|
@ -22,44 +23,65 @@ export class DB { |
|
|
|
|
// Perform a chain of asynchronous queries, each one waiting for the last before
|
|
|
|
|
// starting. Not supported on all database types.
|
|
|
|
|
queries_async(s) { |
|
|
|
|
if(this.state.db_type === DBTypeEnum.ALASQL_SQLITE || |
|
|
|
|
this.state.db_type === DBTypeEnum.ALASQL_INDEXEDDB) { |
|
|
|
|
var self = this; |
|
|
|
|
var p = Promise.resolve(null); |
|
|
|
|
for(let i = 0; i < s.length; i++) { |
|
|
|
|
p = p.then(() => { return self.state.db_object.promise(s[i]); }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return p; |
|
|
|
|
if (this.state.db_type === DBTypeEnum.ALASQL_SQLITE || |
|
|
|
|
this.state.db_type === DBTypeEnum.ALASQL_INDEXEDDB || |
|
|
|
|
this.state.db_type === DBTypeEnum.ALASQL_NATIVE) { |
|
|
|
|
var self = this; |
|
|
|
|
var p = Promise.resolve(null); |
|
|
|
|
for (let i = 0; i < s.length; i++) { |
|
|
|
|
p = p.then(() => { return self.state.db_object.promise(s[i]); }); |
|
|
|
|
} |
|
|
|
|
throw new Error("Unsupported database type for async operations: " + this.state.db_type); |
|
|
|
|
|
|
|
|
|
return p; |
|
|
|
|
} |
|
|
|
|
throw new Error("Unsupported database type for async operations: " + this.state.db_type); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Perform a series of queries sequentially, then return the last result.
|
|
|
|
|
// Note that synchronous operation is not supported for all database types.
|
|
|
|
|
queries_sync(s) { |
|
|
|
|
if(this.state.db_type === DBTypeEnum.ALASQL_SQLITE) { |
|
|
|
|
for (var i = 0; i < (s.length-1); i++) { |
|
|
|
|
if (this.state.db_type === DBTypeEnum.ALASQL_SQLITE || |
|
|
|
|
this.state.db_type === DBTypeEnum.ALASQL_NATIVE) { |
|
|
|
|
for (var i = 0; i < (s.length - 1); i++) { |
|
|
|
|
this.state.db_object(s[i]); |
|
|
|
|
} |
|
|
|
|
return this.state.db_object(s[s.length-1]); |
|
|
|
|
return this.state.db_object(s[s.length - 1]); |
|
|
|
|
} |
|
|
|
|
throw new Error("Unsupported database type for sync operations: " + this.state.db_type); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
migrate_async(db_type, db_name) { |
|
|
|
|
var self = this; |
|
|
|
|
if(this.state.db_type === DBTypeEnum.ALASQL_SQLITE && db_type === DBTypeEnum.ALASQL_INDEXEDDB) { |
|
|
|
|
return new Promise(function(resolve, reject) { |
|
|
|
|
if (this.state.db_type === DBTypeEnum.ALASQL_SQLITE && db_type === DBTypeEnum.ALASQL_INDEXEDDB) { |
|
|
|
|
return new Promise(function (resolve, reject) { |
|
|
|
|
var tables = self.queries_sync(["SHOW TABLES;"]); |
|
|
|
|
var queries = []; |
|
|
|
|
queries.push("DROP INDEXEDDB DATABASE " + db_name + ";"); |
|
|
|
|
queries.push("DROP INDEXEDDB DATABASE IF EXISTS " + db_name + ";"); |
|
|
|
|
queries.push("CREATE INDEXEDDB DATABASE IF NOT EXISTS " + db_name + ";"); |
|
|
|
|
queries.push("ATTACH INDEXEDDB DATABASE " + db_name + ";"); |
|
|
|
|
tables.forEach(elem => { |
|
|
|
|
queries.push("CREATE TABLE " + db_name + "." + elem.tableid + ";"); |
|
|
|
|
queries.push("SELECT * INTO " + db_name + "." + elem.tableid + |
|
|
|
|
" FROM " + self.state.db_name + "." + elem.tableid + ";"); |
|
|
|
|
" FROM " + self.state.db_name + "." + elem.tableid + ";"); |
|
|
|
|
}); |
|
|
|
|
queries.push("DETACH DATABASE " + self.state.db_name + ";"); |
|
|
|
|
queries.push("USE " + db_name + ";"); |
|
|
|
|
self.queries_async(queries).then(() => { |
|
|
|
|
self.state.db_type = db_type; |
|
|
|
|
self.state.db_name = db_name; |
|
|
|
|
resolve(); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} else if (this.state.db_type === DBTypeEnum.ALASQL_SQLITE && db_type === DBTypeEnum.ALASQL_NATIVE) { |
|
|
|
|
return new Promise(function (resolve, reject) { |
|
|
|
|
var tables = self.queries_sync(["SHOW TABLES;"]); |
|
|
|
|
var queries = []; |
|
|
|
|
queries.push("DROP DATABASE IF EXISTS " + db_name + ";"); |
|
|
|
|
queries.push("CREATE DATABASE IF NOT EXISTS " + db_name + ";"); |
|
|
|
|
tables.forEach(elem => { |
|
|
|
|
queries.push("CREATE TABLE " + db_name + "." + elem.tableid + ";"); |
|
|
|
|
queries.push("SELECT * INTO " + db_name + "." + elem.tableid + |
|
|
|
|
" FROM " + self.state.db_name + "." + elem.tableid + ";"); |
|
|
|
|
}); |
|
|
|
|
queries.push("DETACH DATABASE " + self.state.db_name + ";"); |
|
|
|
|
queries.push("USE " + db_name + ";"); |
|
|
|
@ -75,28 +97,56 @@ export class DB { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO random database names
|
|
|
|
|
function fetch_db_from_sqlite(filename) { |
|
|
|
|
return new Promise(function(resolve, reject) { |
|
|
|
|
function fetch_db_from_sqlite(filename, db_name) { |
|
|
|
|
return new Promise(function (resolve, reject) { |
|
|
|
|
var initSqlJs = require('sql.js'); |
|
|
|
|
initSqlJs({ locateFile: filename => `/sql.js/dist/${filename}` }) |
|
|
|
|
.then( SQL => { |
|
|
|
|
var asql = require('alasql'); |
|
|
|
|
// Attach to the SQLite database file,
|
|
|
|
|
// Create an IndexedDB to migrate it to, named "db",
|
|
|
|
|
// Get tables
|
|
|
|
|
var query = ""; |
|
|
|
|
query += "ATTACH SQLITE DATABASE db(\"" + filename + "\"); USE db;"; |
|
|
|
|
asql.promise(query) |
|
|
|
|
.then(SQL => { |
|
|
|
|
var asql = require('alasql'); |
|
|
|
|
asql.promise("ATTACH SQLITE DATABASE " + db_name + "(\"" + filename + "\");") |
|
|
|
|
.then(res => { asql.promise("USE " + db_name + ";"); }) |
|
|
|
|
.then(res => { |
|
|
|
|
var imported_db = new DB(asql, db_name, DBTypeEnum.ALASQL_SQLITE); |
|
|
|
|
resolve(imported_db); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function create_indexed_db(name) { |
|
|
|
|
return new Promise(function (resolve, reject) { |
|
|
|
|
var asql = require('alasql'); |
|
|
|
|
asql.promise("DROP INDEXEDDB DATABASE " + name + ";") |
|
|
|
|
.then(res => { asql.promise("CREATE INDEXEDDB DATABASE IF NOT EXISTS " + name + ";"); }) |
|
|
|
|
.then(res => { asql.promise("ATTACH INDEXEDDB DATABASE " + name + ";"); }) |
|
|
|
|
//.then(res => { asql.promise("USE " + name + ";"); }) // TODO why does this break things?
|
|
|
|
|
.then(res => { |
|
|
|
|
var imported_db = new DB(asql, "db", DBTypeEnum.ALASQL_SQLITE); |
|
|
|
|
resolve(imported_db); |
|
|
|
|
var created_db = new DB(asql, name, DBTypeEnum.ALASQL_INDEXEDDB); |
|
|
|
|
resolve(created_db); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: generalize to FetchDB
|
|
|
|
|
export class FetchSQLiteDB extends React.Component { |
|
|
|
|
function create_native_db(name) { |
|
|
|
|
return new Promise(function (resolve, reject) { |
|
|
|
|
var asql = require('alasql'); |
|
|
|
|
asql.promise("DROP DATABASE " + name + ";") |
|
|
|
|
.then(res => { asql.promise("CREATE DATABASE IF NOT EXISTS " + name + ";"); }) |
|
|
|
|
.then(res => { asql.promise("ATTACH DATABASE " + name + ";"); }) |
|
|
|
|
//.then(res => { asql.promise("USE " + name + ";"); }) // TODO why does this break things?
|
|
|
|
|
.then(res => { |
|
|
|
|
var created_db = new DB(asql, name, DBTypeEnum.ALASQL_INDEXEDDB); |
|
|
|
|
resolve(created_db); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export const DBSourceEnum = { |
|
|
|
|
ATTACHFILE: 1, |
|
|
|
|
CREATE: 2 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class ProvideDB extends React.Component { |
|
|
|
|
state = { |
|
|
|
|
loading: true, |
|
|
|
|
error: false, |
|
|
|
@ -105,14 +155,35 @@ export class FetchSQLiteDB extends React.Component { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
componentDidMount() { |
|
|
|
|
fetch_db_from_sqlite(this.props.sqlite_file) |
|
|
|
|
.then(db => { |
|
|
|
|
if(this.props.migrate_to_indexeddb) { |
|
|
|
|
db.migrate_async(DBTypeEnum.ALASQL_INDEXEDDB, "indexed_db") |
|
|
|
|
.then(() => { this.setState({ loading: false, done: true, db: db }) }); |
|
|
|
|
} else { this.setState({ loading: false, done: true, db: db }) }; |
|
|
|
|
}) |
|
|
|
|
.catch(error => this.setState({ loading: false, done: false, error })); |
|
|
|
|
if (this.props.db_source_type === DBTypeEnum.ALASQL_SQLITE && this.props.db_source === DBSourceEnum.ATTACHFILE) { |
|
|
|
|
fetch_db_from_sqlite(this.props.db_file, this.props.db_source_name) |
|
|
|
|
.then(db => { |
|
|
|
|
if (this.props.db_target_type !== this.props.db_source_type) { |
|
|
|
|
db.migrate_async(this.props.db_target_type, this.props.db_target_name) |
|
|
|
|
.then(() => { this.setState({ loading: false, done: true, db: db }) }); |
|
|
|
|
} else { this.setState({ loading: false, done: true, db: db }) }; |
|
|
|
|
}) |
|
|
|
|
.catch(error => this.setState({ loading: false, done: false, error })); |
|
|
|
|
} else if (this.props.db_source_type === DBTypeEnum.ALASQL_INDEXEDDB && this.props.db_source === DBSourceEnum.CREATE) { |
|
|
|
|
create_indexed_db(this.props.db_source_name) |
|
|
|
|
.then(db => { |
|
|
|
|
if (this.props.db_target_type !== this.props.db_source_type) { |
|
|
|
|
db.migrate_async(this.props.db_target_type, this.props.db_target_name) |
|
|
|
|
.then(() => { this.setState({ loading: false, done: true, db: db }) }); |
|
|
|
|
} else { this.setState({ loading: false, done: true, db: db }) }; |
|
|
|
|
}); |
|
|
|
|
} else if (this.props.db_source_type === DBTypeEnum.ALASQL_NATIVE && this.props.db_source === DBSourceEnum.CREATE) { |
|
|
|
|
create_native_db(this.props.db_source_name) |
|
|
|
|
.then(db => { |
|
|
|
|
if (this.props.db_target_type !== this.props.db_source_type) { |
|
|
|
|
db.migrate_async(this.props.db_target_type, this.props.db_target_name) |
|
|
|
|
.then(() => { this.setState({ loading: false, done: true, db: db }) }); |
|
|
|
|
} else { this.setState({ loading: false, done: true, db: db }) }; |
|
|
|
|
}); |
|
|
|
|
} else { |
|
|
|
|
throw new Error("Unsupported ProvideDB configuration: from source " + this.props.db_source + |
|
|
|
|
" with source type " + this.props.db_source_type + " to target type " + this.props.db_target_type + "."); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
@ -128,7 +199,7 @@ export class DBQueryBar extends React.Component { |
|
|
|
|
render() { |
|
|
|
|
return ( |
|
|
|
|
<form> |
|
|
|
|
<input type="text" onChange={ this.onChangeHandler }/> |
|
|
|
|
<input type="text" onChange={this.onChangeHandler} /> |
|
|
|
|
</form> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
@ -141,24 +212,24 @@ export class DBQueryConsole extends React.Component { |
|
|
|
|
query: "", |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
onQueryChangeHandler = query => { this.setState( { query: query } ); } |
|
|
|
|
onQueryChangeHandler = query => { this.setState({ query: query }); } |
|
|
|
|
onQuerySubmitHandler = () => { |
|
|
|
|
this.setState( { processing: true, result: false } ); |
|
|
|
|
this.setState({ processing: true, result: false }); |
|
|
|
|
this.props.database.queries_async([this.state.query]) |
|
|
|
|
.then(result => { |
|
|
|
|
this.setState( { processing: false, result: JSON.stringify(result) } ); |
|
|
|
|
}); |
|
|
|
|
.then(result => { |
|
|
|
|
this.setState({ processing: false, result: JSON.stringify(result) }); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
return ( |
|
|
|
|
<> |
|
|
|
|
<div> |
|
|
|
|
<DBQueryBar onChange={this.onQueryChangeHandler} /> |
|
|
|
|
<button onClick={this.onQuerySubmitHandler}>Submit</button> |
|
|
|
|
<p>Result:</p> |
|
|
|
|
{ this.state.result && <p>{this.state.result}</p> } |
|
|
|
|
</div> |
|
|
|
|
<div> |
|
|
|
|
<DBQueryBar onChange={this.onQueryChangeHandler} /> |
|
|
|
|
<button onClick={this.onQuerySubmitHandler}>Submit</button> |
|
|
|
|
<p>Result:</p> |
|
|
|
|
{this.state.result && <p>{this.state.result}</p>} |
|
|
|
|
</div> |
|
|
|
|
</> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|