171 lines
4.6 KiB
Markdown
171 lines
4.6 KiB
Markdown
|
---
|
||
|
title: Knex SQL Builder
|
||
|
pagination_prev: demos/desktop/index
|
||
|
pagination_next: demos/local/index
|
||
|
sidebar_custom_props:
|
||
|
sql: true
|
||
|
---
|
||
|
|
||
|
:::note
|
||
|
|
||
|
This demo was last tested on 2023 April 19 with Knex 2.4.2 and `better-sqlite`.
|
||
|
|
||
|
:::
|
||
|
|
||
|
## Integration Details
|
||
|
|
||
|
#### Importing Data
|
||
|
|
||
|
`sheet_to_json` generates an array of objects. An `INSERT` statement can be
|
||
|
generated from each row object using `knex.insert`:
|
||
|
|
||
|
```js
|
||
|
const aoo = XLSX.utils.sheet_to_json(ws);
|
||
|
for(let i = 0; i < aoo.length; ++i) await knex.insert(aoo[i]).into(table_name);
|
||
|
```
|
||
|
|
||
|
#### Exporting Data
|
||
|
|
||
|
The result of a `SELECT` statement is an array of objects:
|
||
|
|
||
|
```js
|
||
|
const aoo = await knex.select("*").from(table_name);
|
||
|
const worksheet = XLSX.utils.json_to_sheet(aoo);
|
||
|
```
|
||
|
|
||
|
## Complete Example
|
||
|
|
||
|
1) Install dependencies:
|
||
|
|
||
|
```bash
|
||
|
npm i --save https://cdn.sheetjs.com/xlsx-latest/xlsx-latest.tgz knex better-sqlite3
|
||
|
```
|
||
|
|
||
|
2) Download the [test file](https://sheetjs.com/pres.numbers)
|
||
|
|
||
|
```bash
|
||
|
curl -LO https://sheetjs.com/pres.numbers
|
||
|
```
|
||
|
|
||
|
3) Save the following utility script to `sheetjsknex.js`:
|
||
|
|
||
|
```js title="sheetjsknex.js"
|
||
|
const XLSX = require("xlsx");
|
||
|
|
||
|
// define mapping between determined types and Knex types
|
||
|
const PG = { "n": "float", "s": "text", "b": "boolean" };
|
||
|
|
||
|
async function ws_to_knex(knex, ws, table_name) {
|
||
|
|
||
|
// generate an array of objects from the data
|
||
|
const aoo = XLSX.utils.sheet_to_json(ws);
|
||
|
|
||
|
// types will map column headers to types, while hdr holds headers in order
|
||
|
const types = {}, hdr = [];
|
||
|
|
||
|
// loop across each row object
|
||
|
aoo.forEach(row =>
|
||
|
// Object.entries returns a row of [key, value] pairs. Loop across those
|
||
|
Object.entries(row).forEach(([k,v]) => {
|
||
|
|
||
|
// If this is first time seeing key, mark unknown and append header array
|
||
|
if(!types[k]) { types[k] = "?"; hdr.push(k); }
|
||
|
|
||
|
// skip null and undefined
|
||
|
if(v == null) return;
|
||
|
|
||
|
// check and resolve type
|
||
|
switch(typeof v) {
|
||
|
case "string": // strings are the broadest type
|
||
|
types[k] = "s"; break;
|
||
|
case "number": // if column is not string, number is the broadest type
|
||
|
if(types[k] != "s") types[k] = "n"; break;
|
||
|
case "boolean": // only mark boolean if column is unknown or boolean
|
||
|
if("?b".includes(types[k])) types[k] = "b"; break;
|
||
|
default: types[k] = "s"; break; // default to string type
|
||
|
}
|
||
|
})
|
||
|
);
|
||
|
|
||
|
await knex.schema.dropTableIfExists(table_name);
|
||
|
|
||
|
// use column type info to create table
|
||
|
await knex.schema.createTable(table_name, (table) => { hdr.forEach(h => { table[PG[types[h]] || "text"](h); }); });
|
||
|
|
||
|
// insert each non-empty row object
|
||
|
for(let i = 0; i < aoo.length; ++i) {
|
||
|
if(!aoo[i] || !Object.keys(aoo[i]).length) continue;
|
||
|
try { await knex.insert(aoo[i]).into(table_name); } catch(e) {}
|
||
|
}
|
||
|
return knex;
|
||
|
}
|
||
|
|
||
|
async function knex_to_ws(knex, table_name) {
|
||
|
const aoo = await knex.select("*").from(table_name);
|
||
|
return XLSX.utils.json_to_sheet(aoo);
|
||
|
}
|
||
|
|
||
|
module.exports = { ws_to_knex, knex_to_ws };
|
||
|
```
|
||
|
|
||
|
4) Save the following to `SheetJSKnexTest.js`:
|
||
|
|
||
|
```js title="SheetJSKnexTest.js"
|
||
|
const { ws_to_knex, knex_to_ws } = require("./sheetjsknex");
|
||
|
const Knex = require('knex');
|
||
|
|
||
|
/* read file and get first worksheet */
|
||
|
const XLSX = require("xlsx");
|
||
|
const oldwb = XLSX.readFile("pres.numbers");
|
||
|
const wsname = oldwb.SheetNames[0];
|
||
|
const oldws = oldwb.Sheets[wsname];
|
||
|
|
||
|
(async() => {
|
||
|
/* open connection to SheetJSKnex.db */
|
||
|
let knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } });
|
||
|
try {
|
||
|
/* load data into database and close connection */
|
||
|
await ws_to_knex(knex, oldws, "Test_Table");
|
||
|
} finally { knex.destroy(); }
|
||
|
|
||
|
/* reconnect to SheetJSKnex.db */
|
||
|
knex = Knex({ client: 'better-sqlite3', connection: { filename: "SheetJSKnex.db" } });
|
||
|
try {
|
||
|
/* get data from db */
|
||
|
const aoo = await knex.select("*").from("Test_Table");
|
||
|
|
||
|
/* export to file */
|
||
|
const newws = await knex_to_ws(knex, "Test_Table");
|
||
|
const newwb = XLSX.utils.book_new();
|
||
|
XLSX.utils.book_append_sheet(newwb, newws, "Export");
|
||
|
XLSX.writeFile(newwb, "SheetJSKnex.xlsx");
|
||
|
} finally { knex.destroy(); }
|
||
|
})();
|
||
|
```
|
||
|
|
||
|
This script will read `pres.numbers` and load data into a table `Test_Table`.
|
||
|
The SQLite database will be saved to `SheetJSKnex.db`.
|
||
|
|
||
|
After closing the connection, the script will make a new connection and export
|
||
|
data to `SheetJSKnex.xlsx`.
|
||
|
|
||
|
5) Run the script:
|
||
|
|
||
|
```bash
|
||
|
node SheetJSKnexTest.js
|
||
|
```
|
||
|
|
||
|
The script will generate two artifacts:
|
||
|
|
||
|
`SheetJSKnex.xlsx` can be opened in a spreadsheet app or tested in the terminal:
|
||
|
|
||
|
```bash
|
||
|
npx xlsx-cli SheetJSKnex.xlsx
|
||
|
```
|
||
|
|
||
|
`SheetJSKnex.db` can be verified with the `sqlite3` command line tool:
|
||
|
|
||
|
```bash
|
||
|
sqlite3 SheetJSKnex.db 'select * from Test_Table'
|
||
|
```
|