import React from 'react'; export const DBTypeEnum = { ALASQL_SQLITE: 1, ALASQL_INDEXEDDB: 2, ALASQL_NATIVE: 3, SQLJS_SQLITE: 4 }; export const DBSourceEnum = { ATTACHFILE: 1, CREATE: 2 } export function alasql_async_queries(alasql_object, queries) { var p = Promise.resolve(null); for (let i = 0; i < queries.length; i++) { p = p.then(() => { return alasql_object.promise(queries[i]); }); } return p; } export class DB { state = { db_type: false, db_object: false, db_name: false, } // TODO random database names constructor(obj, name, type, library) { this.state.db_object = obj; this.state.db_name = name; this.state.db_type = type; } // 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 || this.state.db_type === DBTypeEnum.ALASQL_NATIVE) { var self = this; return alasql_async_queries(self.state.db_object, s); } 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 || 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]); } if (this.state.db_type === DBTypeEnum.SQLJS_SQLITE) { for (var i = 0; i < (s.length - 1); i++) { this.state.db_object.exec(s[i]); } return this.state.db_object.exec(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) { var tables = self.queries_sync(["SHOW TABLES;"]); var queries = []; 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 + ";"); }); 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 + ";"); 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.SQLJS_SQLITE && db_type === DBTypeEnum.ALASQL_NATIVE) { return new Promise(function (resolve, reject) { var tables = self.queries_sync(["SELECT * FROM sqlite_master WHERE type='table';"])[0].values; // Get all table definition lines. var table_definitions = []; var table_names = []; tables.forEach(elem => { table_definitions.push(elem[4]); table_names.push(elem[1]); }); // We want to create all these tables in the AlaSQL database as well. However, that will require // us to escape the table names because AlaSQL may break on colums with names equal to keywords // (see https://github.com/agershun/alasql/issues/1155). // Here we take a dirty shortcut: explicitly escape all occurrences of "value", "query" and "matrix". var new_table_definitions = []; table_definitions.forEach(elem => { var newelem = elem .replace(/value/g, "`value`") .replace(/query/g, "`query`") .replace(/matrix/g, "`matrix`"); new_table_definitions.push(newelem); }); var asql = require('alasql'); var queries = []; queries.push("CREATE DATABASE IF NOT EXISTS " + db_name + ";"); queries.push("USE " + db_name + ";"); new_table_definitions.forEach(elem => { queries.push(elem); // Creates the table }); alasql_async_queries(asql, queries) .then(() => { // Fill the tables by passing through a JS dictionary object. table_names.forEach(elem => { var res = self.queries_sync(["SELECT * FROM " + elem + ";"])[0]; if (res) { var structured = []; res.values.forEach(vals => { var row = {}; var i = 0; res.columns.forEach(column => { row[column] = vals[i]; i++; }) structured.push(row); }); asql("SELECT * INTO " + elem + " FROM ?", [structured]); } }); }) .then(() => { self.state.db_type = db_type; self.state.db_name = db_name; self.state.db_object = asql; resolve(); }); }); } throw new Error("Unsupported copy creation from db_type " + this.state.db_type + " to " + db_type); } } // TODO random database names 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'); 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 fetch_sqljs_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 => { fetch(filename) .then(res => { if (!res.ok) { throw new Error(res.status); } return res.arrayBuffer(); }) .then(data => { var array = new Uint8Array(data); var sqljs_db = new SQL.Database(array); var imported_db = new DB(sqljs_db, db_name, DBTypeEnum.SQLJS_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 created_db = new DB(asql, name, DBTypeEnum.ALASQL_INDEXEDDB); resolve(created_db); }); }); } 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 class ProvideDB extends React.Component { state = { loading: true, error: false, done: false, db: false }; componentDidMount() { 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 if (this.props.db_source_type === DBTypeEnum.SQLJS_SQLITE && this.props.db_source === DBSourceEnum.ATTACHFILE) { fetch_sqljs_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 { 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() { return this.props.children(this.state); } } export class DBQueryBar extends React.Component { onChangeHandler = (event) => { if(this.props.onChange) { this.props.onChange(event.target.value) }; } render() { return (
); } } export class DBQueryConsole extends React.Component { state = { processing: false, result: false, query: "", } onQueryChangeHandler = query => { this.setState({ query: query }); } onQuerySubmitHandler = () => { this.setState({ processing: true, result: false }); this.props.database.queries_async([this.state.query]) .then(result => { this.setState({ processing: false, result: JSON.stringify(result) }); }); } render() { return ( <>

Result:

{this.state.result &&

{this.state.result}

}
); } }