2018-01-27 00:52:46 +00:00
|
|
|
# Databases
|
|
|
|
|
|
|
|
"Database" is a catch-all term referring to traditional RDBMS as well as K/V
|
|
|
|
stores, document databases, and other "NoSQL" storages. There are many external
|
|
|
|
database systems as well as browser APIs like WebSQL and `localStorage`
|
|
|
|
|
|
|
|
This demo discusses general strategies and provides examples for a variety of
|
|
|
|
database systems. The examples are merely intended to demonstrate very basic
|
|
|
|
functionality.
|
|
|
|
|
|
|
|
|
|
|
|
## Structured Tables
|
|
|
|
|
|
|
|
Database tables are a common import and export target for spreadsheets. One
|
|
|
|
common representation of a database table is an array of JS objects whose keys
|
|
|
|
are column headers and whose values are the underlying data values. For example,
|
|
|
|
|
|
|
|
| Name | Index |
|
|
|
|
| :----------- | ----: |
|
|
|
|
| Barack Obama | 44 |
|
|
|
|
| Donald Trump | 45 |
|
|
|
|
|
|
|
|
is naturally represented as an array of objects
|
|
|
|
|
|
|
|
```js
|
|
|
|
[
|
|
|
|
{ Name: "Barack Obama", Index: 44 },
|
|
|
|
{ Name: "Donald Trump", Index: 45 }
|
|
|
|
]
|
|
|
|
```
|
|
|
|
|
|
|
|
The `sheet_to_json` and `json_to_sheet` helper functions work with objects of
|
|
|
|
similar shape, converting to and from worksheet objects. The corresponding
|
|
|
|
worksheet would include a header row for the labels:
|
|
|
|
|
|
|
|
```
|
|
|
|
XXX| A | B |
|
|
|
|
---+--------------+-------+
|
|
|
|
1 | Name | Index |
|
|
|
|
2 | Barack Obama | 44 |
|
|
|
|
3 | Donald Trump | 45 |
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Building Schemas from Worksheets
|
|
|
|
|
|
|
|
The `sheet_to_json` helper function generates arrays of JS objects that can be
|
|
|
|
scanned to determine the column "types", and there are third-party connectors
|
|
|
|
that can push arrays of JS objects to database tables.
|
|
|
|
|
|
|
|
The [`sexql`](http://sheetjs.com/sexql) browser demo uses WebSQL, which is
|
2018-03-13 02:51:54 +00:00
|
|
|
limited to the SQLite fundamental types.
|
|
|
|
|
|
|
|
<details>
|
|
|
|
<summary><b>Implementation details</b> (click to show)</summary>
|
|
|
|
|
|
|
|
The `sexql` schema builder scans the first row to find headers:
|
2018-01-27 00:52:46 +00:00
|
|
|
|
|
|
|
```js
|
|
|
|
if(!ws || !ws['!ref']) return;
|
|
|
|
var range = XLSX.utils.decode_range(ws['!ref']);
|
|
|
|
if(!range || !range.s || !range.e || range.s > range.e) return;
|
|
|
|
var R = range.s.r, C = range.s.c;
|
|
|
|
|
|
|
|
var names = new Array(range.e.c-range.s.c+1);
|
|
|
|
for(C = range.s.c; C<= range.e.c; ++C){
|
|
|
|
var addr = XLSX.utils.encode_cell({c:C,r:R});
|
|
|
|
names[C-range.s.c] = ws[addr] ? ws[addr].v : XLSX.utils.encode_col(C);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
After finding the headers, a deduplication step ensures that data is not lost.
|
|
|
|
Duplicate headers will be suffixed with `_1`, `_2`, etc.
|
|
|
|
|
|
|
|
```js
|
|
|
|
for(var i = 0; i < names.length; ++i) if(names.indexOf(names[i]) < i)
|
|
|
|
for(var j = 0; j < names.length; ++j) {
|
|
|
|
var _name = names[i] + "_" + (j+1);
|
|
|
|
if(names.indexOf(_name) > -1) continue;
|
|
|
|
names[i] = _name;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A column-major walk helps determine the data type. For SQLite the only relevant
|
|
|
|
data types are `REAL` and `TEXT`. If a string or date or error is seen in any
|
|
|
|
value of a column, the column is marked as `TEXT`:
|
|
|
|
|
|
|
|
```js
|
|
|
|
var types = new Array(range.e.c-range.s.c+1);
|
|
|
|
for(C = range.s.c; C<= range.e.c; ++C) {
|
|
|
|
var seen = {}, _type = "";
|
|
|
|
for(R = range.s.r+1; R<= range.e.r; ++R)
|
|
|
|
seen[(ws[XLSX.utils.encode_cell({c:C,r:R})]||{t:"z"}).t] = true;
|
|
|
|
if(seen.s || seen.str) _type = "TEXT";
|
|
|
|
else if(seen.n + seen.b + seen.d + seen.e > 1) _type = "TEXT";
|
|
|
|
else switch(true) {
|
|
|
|
case seen.b:
|
|
|
|
case seen.n: _type = "REAL"; break;
|
|
|
|
case seen.e: _type = "TEXT"; break;
|
|
|
|
case seen.d: _type = "TEXT"; break;
|
|
|
|
}
|
|
|
|
types[C-range.s.c] = _type || "TEXT";
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-03-13 02:51:54 +00:00
|
|
|
</details>
|
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
The included `SheetJSSQL.js` script demonstrates SQL statement generation.
|
|
|
|
|
2018-03-13 02:51:54 +00:00
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
## Objects, K/V and "Schema-less" Databases
|
|
|
|
|
|
|
|
So-called "Schema-less" databases allow for arbitrary keys and values within the
|
|
|
|
entries in the database. K/V stores and Objects add additional restrictions.
|
|
|
|
|
|
|
|
There is no natural way to translate arbitrarily shaped schemas to worksheets
|
|
|
|
in a workbook. One common trick is to dedicate one worksheet to holding named
|
|
|
|
keys. For example, considering the JS object:
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"title": "SheetDB",
|
|
|
|
"metadata": {
|
|
|
|
"author": "SheetJS",
|
|
|
|
"code": 7262
|
|
|
|
},
|
|
|
|
"data": [
|
|
|
|
{ "Name": "Barack Obama", "Index": 44 },
|
|
|
|
{ "Name": "Donald Trump", "Index": 45 },
|
|
|
|
]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
A dedicated worksheet should store the one-off named values:
|
|
|
|
|
|
|
|
```
|
|
|
|
XXX| A | B |
|
|
|
|
---+-----------------+---------+
|
|
|
|
1 | Path | Value |
|
|
|
|
2 | title | SheetDB |
|
|
|
|
3 | metadata.author | SheetJS |
|
|
|
|
4 | metadata.code | 7262 |
|
|
|
|
```
|
|
|
|
|
|
|
|
The included `ObjUtils.js` script demonstrates object-workbook conversion:
|
|
|
|
|
2018-03-13 02:51:54 +00:00
|
|
|
<details>
|
|
|
|
<summary><b>Implementation details</b> (click to show)</summary>
|
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
```js
|
|
|
|
function deepset(obj, path, value) {
|
|
|
|
if(path.indexOf(".") == -1) return obj[path] = value;
|
|
|
|
var parts = path.split(".");
|
|
|
|
if(!obj[parts[0]]) obj[parts[0]] = {};
|
|
|
|
return deepset(obj[parts[0]], parts.slice(1).join("."), value);
|
|
|
|
}
|
|
|
|
function workbook_to_object(wb) {
|
|
|
|
var out = {};
|
|
|
|
|
|
|
|
/* assign one-off keys */
|
|
|
|
var ws = wb.Sheets["_keys"]; if(ws) {
|
|
|
|
var data = XLSX.utils.sheet_to_json(ws, {raw:true});
|
|
|
|
data.forEach(function(r) { deepset(out, r.path, r.value); });
|
|
|
|
}
|
|
|
|
|
|
|
|
/* assign arrays from worksheet tables */
|
|
|
|
wb.SheetNames.forEach(function(n) {
|
|
|
|
if(n == "_keys") return;
|
|
|
|
out[n] = XLSX.utils.sheet_to_json(wb.Sheets[n], {raw:true});
|
|
|
|
});
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
function walk(obj, key, arr) {
|
|
|
|
if(Array.isArray(obj)) return;
|
|
|
|
if(typeof obj != "object") { arr.push({path:key, value:obj}); return; }
|
|
|
|
Object.keys(obj).forEach(function(k) { walk(obj[k], key?key+"."+k:k, arr); });
|
|
|
|
}
|
|
|
|
function object_to_workbook(obj) {
|
|
|
|
var wb = XLSX.utils.book_new();
|
|
|
|
|
|
|
|
/* keyed entries */
|
|
|
|
var base = []; walk(obj, "", base);
|
|
|
|
var ws = XLSX.utils.json_to_sheet(base, {header:["path", "value"]});
|
|
|
|
XLSX.utils.book_append_sheet(wb, ws, "_keys");
|
|
|
|
|
|
|
|
/* arrays */
|
|
|
|
Object.keys(obj).forEach(function(k) {
|
|
|
|
if(!Array.isArray(obj[k])) return;
|
|
|
|
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(obj[k]), k);
|
|
|
|
});
|
|
|
|
|
|
|
|
return wb;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-03-13 02:51:54 +00:00
|
|
|
</details>
|
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
|
|
|
|
## Browser APIs
|
|
|
|
|
|
|
|
#### WebSQL
|
|
|
|
|
|
|
|
WebSQL is a popular SQL-based in-browser database available on Chrome / Safari.
|
|
|
|
In practice, it is powered by SQLite, and most simple SQLite-compatible queries
|
|
|
|
work as-is in WebSQL.
|
|
|
|
|
|
|
|
The public demo <http://sheetjs.com/sexql> generates a database from workbook.
|
|
|
|
|
|
|
|
#### LocalStorage and SessionStorage
|
|
|
|
|
|
|
|
The Storage API, encompassing `localStorage` and `sessionStorage`, describes
|
|
|
|
simple key-value stores that only support string values and keys. Objects can be
|
|
|
|
stored as JSON using `JSON.stringify` and `JSON.parse` to set and get keys.
|
|
|
|
|
|
|
|
`SheetJSStorage.js` extends the `Storage` prototype with a `load` function to
|
|
|
|
populate the db based on an object and a `dump` function to generate a workbook
|
|
|
|
from the data in the storage. `LocalStorage.html` tests `localStorage`.
|
|
|
|
|
|
|
|
#### IndexedDB
|
|
|
|
|
|
|
|
IndexedDB is a more complex storage solution, but the `localForage` wrapper
|
|
|
|
supplies a Promise-based interface mimicking the `Storage` API.
|
|
|
|
|
|
|
|
`SheetJSForage.js` extends the `localforage` object with a `load` function to
|
|
|
|
populate the db based on an object and a `dump` function to generate a workbook
|
|
|
|
from the data in the storage. `LocalForage.html` forces IndexedDB mode.
|
|
|
|
|
|
|
|
|
|
|
|
## External Database Demos
|
|
|
|
|
|
|
|
### SQL Databases
|
|
|
|
|
|
|
|
There are nodejs connector libraries for all of the popular RDBMS systems. They
|
|
|
|
have facilities for connecting to a database, executing queries, and obtaining
|
|
|
|
results as arrays of JS objects that can be passed to `json_to_sheet`. The main
|
|
|
|
differences surround API shape and supported data types.
|
|
|
|
|
|
|
|
#### SQLite
|
|
|
|
|
|
|
|
[The `better-sqlite3` module](https://www.npmjs.com/package/better-sqlite3)
|
|
|
|
provides a very simple API for working with SQLite databases. `Statement#all`
|
2018-03-13 02:51:54 +00:00
|
|
|
runs a prepared statement and returns an array of JS objects.
|
2018-01-27 00:52:46 +00:00
|
|
|
|
|
|
|
`SQLiteTest.js` generates a simple two-table SQLite database (`SheetJS1.db`),
|
|
|
|
exports to XLSX (`sqlite.xlsx`), imports the new XLSX file to a new database
|
|
|
|
(`SheetJS2.db`) and verifies the tables are preserved.
|
|
|
|
|
|
|
|
#### MySQL / MariaDB
|
|
|
|
|
|
|
|
[The `mysql2` module](https://www.npmjs.com/package/mysql2) supplies a callback
|
|
|
|
API as well as a Promise wrapper. `Connection#query` runs a statement and
|
|
|
|
returns an array whose first element is an array of JS objects.
|
|
|
|
|
|
|
|
`MySQLTest.js` connects to the MySQL instance running on `localhost`, builds two
|
|
|
|
tables in the `sheetjs` database, exports to XLSX, imports the new XLSX file to
|
|
|
|
the `sheetj5` database and verifies the tables are preserved.
|
|
|
|
|
|
|
|
#### PostgreSQL
|
|
|
|
|
|
|
|
[The `pg` module](https://www.npmjs.com/package/pg) supplies a Promise wrapper.
|
|
|
|
Like with `mysql2`, `Client#query` runs a statement and returns a result object.
|
|
|
|
The `rows` key of the object is an array of JS objects.
|
|
|
|
|
|
|
|
`PgSQLTest.js` connects to the PostgreSQL server on `localhost`, builds two
|
|
|
|
tables in the `sheetjs` database, exports to XLSX, imports the new XLSX file to
|
|
|
|
the `sheetj5` database and verifies the tables are preserved.
|
|
|
|
|
2018-03-13 02:51:54 +00:00
|
|
|
#### Knex Query Builder
|
|
|
|
|
|
|
|
[The `knex` module](https://www.npmjs.com/package/knex) builds SQL queries. The
|
|
|
|
same exact code can be used against Oracle Database, MSSQL, and other engines.
|
|
|
|
|
|
|
|
`KnexTest.js` uses the `sqlite3` connector and follows the same procedure as the
|
|
|
|
SQLite test. The included `SheetJSKnex.js` script converts between the query
|
|
|
|
builder and the common spreadsheet format.
|
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
### Key/Value Stores
|
|
|
|
|
|
|
|
#### Redis
|
|
|
|
|
|
|
|
Redis is a powerful data structure server that can store simple strings, sets,
|
|
|
|
sorted sets, hashes and lists. One simple database representation stores the
|
|
|
|
strings in a special worksheet (`_strs`), the manifest in another worksheet
|
|
|
|
(`_manifest`), and each object in its own worksheet (`obj##`).
|
|
|
|
|
|
|
|
`RedisTest.js` connects to a local Redis server, populates data based on the
|
|
|
|
official Redis tutorial, exports to XLSX, flushes the server, imports the new
|
|
|
|
XLSX file and verifies the data round-tripped correctly. `SheetJSRedis.js`
|
2018-03-13 02:51:54 +00:00
|
|
|
includes the implementation details.
|
2018-01-27 00:52:46 +00:00
|
|
|
|
|
|
|
#### LowDB
|
|
|
|
|
|
|
|
LowDB is a small schemaless database powered by `lodash`. `_.get` and `_.set`
|
|
|
|
helper functions make storing metadata a breeze. The included `SheetJSLowDB.js`
|
|
|
|
script demonstrates a simple adapter that can load and dump data.
|
|
|
|
|
2018-03-29 04:31:36 +00:00
|
|
|
### Document Databases
|
|
|
|
|
|
|
|
Since document databases are capable of holding more complex objects, they can
|
|
|
|
actually hold the underlying worksheet objects! In some cases, where arrays are
|
|
|
|
supported, they can even hold the workbook object.
|
|
|
|
|
|
|
|
#### MongoDB
|
|
|
|
|
|
|
|
MongoDB is a popular document-oriented database engine. `MongoDBTest.js` uses
|
|
|
|
MongoDB to hold a simple workbook and export to XLSX.
|
|
|
|
|
|
|
|
`MongoDBCRUD.js` follows the SQL examples using an idiomatic collection
|
|
|
|
structure. Exporting and importing collections are straightforward:
|
|
|
|
|
|
|
|
<details>
|
|
|
|
<summary><b>Example code</b> (click to show)</summary>
|
|
|
|
|
|
|
|
```js
|
|
|
|
/* generate a worksheet from a collection */
|
|
|
|
const aoa = await db.collection('coll').find({}).toArray();
|
|
|
|
aoa.forEach((x) => delete x._id);
|
|
|
|
const ws = XLSX.utils.json_to_sheet(aoa);
|
|
|
|
|
|
|
|
|
|
|
|
/* import data from a worksheet to a collection */
|
|
|
|
const aoa = XLSX.utils.sheet_to_json(ws);
|
|
|
|
await db.collection('coll').insertMany(aoa, {ordered: true});
|
|
|
|
```
|
|
|
|
|
2018-03-29 15:42:49 +00:00
|
|
|
</details>
|
|
|
|
|
2018-03-29 04:31:36 +00:00
|
|
|
#### Firebase
|
|
|
|
|
|
|
|
[`firebase-server`](https://www.npmjs.com/package/firebase-server) is a simple
|
|
|
|
mock Firebase server used in the tests, but the same code works in an external
|
|
|
|
Firebase deployment when plugging in the database connection info.
|
|
|
|
|
|
|
|
`FirebaseDemo.html` and `FirebaseTest.js` demonstrate a whole-workbook process.
|
|
|
|
The entire workbook object is persisted, a few cells are changed, and the stored
|
|
|
|
data is dumped and exported to XLSX.
|
|
|
|
|
2018-01-27 00:52:46 +00:00
|
|
|
[![Analytics](https://ga-beacon.appspot.com/UA-36810333-1/SheetJS/js-xlsx?pixel)](https://github.com/SheetJS/js-xlsx)
|