diff --git a/public/digikam4.db b/public/digikam4.db new file mode 100644 index 0000000..c0f753f Binary files /dev/null and b/public/digikam4.db differ diff --git a/public/test.sqlite b/public/test.sqlite index a530ad4..e7132f8 100644 Binary files a/public/test.sqlite and b/public/test.sqlite differ diff --git a/src/database.js b/src/database.js index 3b70c80..da02f41 100644 --- a/src/database.js +++ b/src/database.js @@ -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 (
- +
); } @@ -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 ( <> -
- - -

Result:

- { this.state.result &&

{this.state.result}

} -
+
+ + +

Result:

+ {this.state.result &&

{this.state.result}

} +
); } diff --git a/src/index.js b/src/index.js index 9660473..72b4b63 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { Fetch } from './fetch.js'; -import { FetchSQLiteDB, DBQueryConsole } from './database.js'; +import { ProvideDB, DBQueryConsole, DBTypeEnum, DBSourceEnum } from './database.js'; import { Photo, PhotoView } from './media.js'; import './index.css'; @@ -33,7 +33,7 @@ export class PhotoFromDB extends React.Component { } componentDidMount() { - this.props.database.queries_async(["SELECT * FROM images;"]) + this.props.database.queries_async(["SELECT * FROM Images;"]) .then(res => { var photo = new Photo(res[0]); this.setState({ done: true, photo: photo }); @@ -50,7 +50,8 @@ export class PhotoFromDB extends React.Component { } const TestDBFetch = ({sqlite_file}) => ( - + {({ loading, error, done, db }) => ( <> { loading && } @@ -60,15 +61,29 @@ const TestDBFetch = ({sqlite_file}) => ( { done && } )} - + +) + +const TestDBPlayground = ({db_name}) => ( + + {({ loading, error, done, db }) => ( + <> + { error && } + { done && } + + )} + ) ReactDOM.render( <>

Test file fetching:

+

IndexedDB playground:

+

Test DB fetching:

- + , document.getElementById('root') );