├── .nojekyll ├── dist ├── .npmignore └── .gitignore ├── test ├── issue55.db ├── test.sqlite ├── load_sql_lib.js ├── run.sh ├── sql-requireJS.html ├── all.js ├── test_issue76.js ├── disabled_test_memory_leak_on_error.js ├── test_node_file.js ├── test_issue325.js ├── test_issue55.js ├── test_issue128.js ├── test_blob.js ├── test_transactions.js ├── test_issue73.js ├── test_functions_recreate.js ├── test_errors.js ├── test_modularization.js ├── test_database.js ├── test_extension_functions.js ├── test_statement_iterator.js ├── test_statement.js ├── test_worker.js ├── test_json1.js └── test_functions.js ├── AUTHORS ├── .gitignore ├── examples ├── README.md ├── start_local_server.py ├── repl.html ├── simple.html ├── GUI │ ├── demo.css │ ├── index.html │ └── gui.js ├── requireJS.html └── persistent.html ├── .npmignore ├── src ├── exported_runtime_methods.json ├── shell-post.js ├── exported_functions.json ├── worker.js ├── shell-pre.js └── api.js ├── GUI └── index.html ├── documentation_index.md ├── logo.svg ├── index.html ├── .github └── workflows │ ├── CI.yml │ └── release.yml ├── .jsdoc.config.json ├── package.json ├── LICENSE ├── .eslintrc.js ├── Makefile └── README.md /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore -------------------------------------------------------------------------------- /test/issue55.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phiresky/sql.js/HEAD/test/issue55.db -------------------------------------------------------------------------------- /test/test.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phiresky/sql.js/HEAD/test/test.sqlite -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | !.npmignore 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ophir LOJKINE (https://github.com/lovasoa) 2 | @kripken 3 | @hankinsoft 4 | @firien 5 | @dinedal 6 | @taytay 7 | @kaizhu256 8 | @brodybits 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *~ 3 | 4 | # Intermediary files: 5 | cache/ 6 | out/ 7 | .emsdk-cache/ 8 | sqlite-src/ 9 | tmp/ 10 | c/ 11 | emsdk/ 12 | sqljs.zip 13 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | To show these examples locally, first run: 3 | ./start_local_server.py 4 | 5 | Then, open http://localhost:8081/index.html in a local browser. 6 | 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | c/ 3 | gh-pages/ 4 | node_modules/ 5 | node-debug.log 6 | src/ 7 | cache/ 8 | out/ 9 | examples/ 10 | sqlite-src/ 11 | .git/ 12 | index.html 13 | .github 14 | Makefile 15 | emsdk_set_env.sh 16 | sqljs.zip 17 | -------------------------------------------------------------------------------- /src/exported_runtime_methods.json: -------------------------------------------------------------------------------- 1 | [ 2 | "cwrap", 3 | "stackAlloc", 4 | "stackSave", 5 | "stackRestore", 6 | "UTF8ToString", 7 | "stringToUTF8", 8 | "lengthBytesUTF8", 9 | "ccall", 10 | "setValue", 11 | "getValue", 12 | "addFunction" 13 | ] 14 | -------------------------------------------------------------------------------- /GUI/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This page has moved to: ../examples/GUI/index.html 5 | 6 | -------------------------------------------------------------------------------- /test/load_sql_lib.js: -------------------------------------------------------------------------------- 1 | module.exports = function(sqlLibraryType){ 2 | // Use sql-wasm.js by default 3 | var sqlJsLib = sqlLibraryType ? "../dist/sql-"+sqlLibraryType+".js" : "../dist/sql-wasm.js"; 4 | begin = new Date(); 5 | var initSqlJs = require(sqlJsLib); 6 | return initSqlJs().then((sql)=>{ 7 | end = new Date(); 8 | console.log(`Loaded and inited ${sqlJsLib} in ${end -begin}ms`); 9 | return sql; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | passed=0 4 | total=0 5 | for f in $(dirname $0)/test_*.js 6 | do 7 | total=$((total+1)) 8 | echo -ne "Testing $f...\t" 9 | node "$f" > /tmp/sqljstest 10 | if [ $? = 0 ] 11 | then 12 | echo "Passed." 13 | passed=$((passed+1)) 14 | else 15 | echo -e "\033[31mFail!\e[0m" 16 | cat /tmp/sqljstest 17 | fi 18 | done 19 | 20 | if [ $passed = $total ] 21 | then 22 | echo -e "\033[32mAll $total tests passed\e[0m" 23 | exit 0 24 | else 25 | echo -e "\033[31mWarning\e[0m : $passed tests passed out of $total" 26 | exit 1 27 | fi 28 | -------------------------------------------------------------------------------- /examples/start_local_server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import http.server 4 | import os 5 | 6 | # We need to host from the root because we are going to be requesting files inside of dist 7 | os.chdir("../") 8 | port = 8081 9 | print("Running on port %d" % port) 10 | 11 | http.server.SimpleHTTPRequestHandler.extensions_map[".wasm"] = "application/wasm" 12 | 13 | httpd = http.server.HTTPServer( 14 | ("localhost", port), http.server.SimpleHTTPRequestHandler 15 | ) 16 | 17 | print( 18 | 'Mapping ".wasm" to "%s"' 19 | % http.server.SimpleHTTPRequestHandler.extensions_map[".wasm"] 20 | ) 21 | httpd.serve_forever() 22 | -------------------------------------------------------------------------------- /test/sql-requireJS.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | Error.stackTraceLimit = 200; 3 | var sqlLibType = process.argv[2]; 4 | const sqlLibLoader = require('./load_sql_lib'); 5 | 6 | sqlLibLoader(sqlLibType).then((sql)=>{ 7 | var files = fs.readdirSync(__dirname); 8 | for (var i=0; i{ 24 | console.error(e); 25 | }); 26 | -------------------------------------------------------------------------------- /test/test_issue76.js: -------------------------------------------------------------------------------- 1 | exports.test = function(sql, assert) { 2 | // Create a database 3 | var db = new sql.Database(); 4 | // Ultra-simple query 5 | var stmt = db.prepare("VALUES (?)"); 6 | // Bind null to the parameter and get the result 7 | assert.deepEqual(stmt.get([null]), [null], 8 | "binding a null value to a statement parameter"); 9 | db.close(); 10 | }; 11 | 12 | if (module == require.main) { 13 | const target_file = process.argv[2]; 14 | const sql_loader = require('./load_sql_lib'); 15 | sql_loader(target_file).then((sql)=>{ 16 | require('test').run({ 17 | 'test issue 76': function(assert){ 18 | exports.test(sql, assert); 19 | } 20 | }); 21 | }) 22 | .catch((e)=>{ 23 | console.error(e); 24 | assert.fail(e); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /documentation_index.md: -------------------------------------------------------------------------------- 1 | # sql.js API documentation 2 | 3 | ## Introduction 4 | 5 | If you need a quick intoduction with code samples that you can copy-and-paste, 6 | head over to [sql.js.org](https://sql.js.org/) 7 | 8 | ## API 9 | 10 | ### The initSqlJs function 11 | 12 | The root object in the API is the [`initSqlJs`](./global.html#initSqlJs) function, 13 | that takes an [`SqlJsConfig`](./global.html#SqlJsConfig) parameter, 14 | and returns an [SqlJs](./global.html#SqlJs) object 15 | 16 | ### The SqlJs object 17 | 18 | `initSqlJs` returns the main sql.js object, the [**`SqlJs`**](./module-SqlJs.html) module, which contains : 19 | 20 | #### Database 21 | 22 | [**Database**](./Database.html) is the main class, that represents an SQLite database. 23 | 24 | #### Statement 25 | 26 | The [**Statement**](./Statement.html) class is used for prepared statements. 27 | -------------------------------------------------------------------------------- /src/shell-post.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // The shell-pre.js and emcc-generated code goes above 4 | return Module; 5 | }); // The end of the promise being returned 6 | 7 | return initSqlJsPromise; 8 | } // The end of our initSqlJs function 9 | 10 | // This bit below is copied almost exactly from what you get when you use the MODULARIZE=1 flag with emcc 11 | // However, we don't want to use the emcc modularization. See shell-pre.js 12 | if (typeof exports === 'object' && typeof module === 'object'){ 13 | module.exports = initSqlJs; 14 | // This will allow the module to be used in ES6 or CommonJS 15 | module.exports.default = initSqlJs; 16 | } 17 | else if (typeof define === 'function' && define['amd']) { 18 | define([], function() { return initSqlJs; }); 19 | } 20 | else if (typeof exports === 'object'){ 21 | exports["Module"] = initSqlJs; 22 | } 23 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | SQL 9 | 10 | 11 | .JS 12 | 13 | 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sql.js 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/disabled_test_memory_leak_on_error.js: -------------------------------------------------------------------------------- 1 | // See: https://github.com/sql-js/sql.js/issues/306 2 | exports.test = function(sql, assert) { 3 | var errors = 0, runs=10000; 4 | for (var i=0; i{ 21 | require('test').run({ 22 | 'test memory leak on error': function(assert){ 23 | exports.test(sql, assert); 24 | } 25 | }); 26 | }) 27 | .catch((e)=>{ 28 | console.error(e); 29 | assert.fail(e); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /examples/repl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | SQL REPL 8 | 9 | 10 | 11 | 12 | 14 | 15 |

16 | 	

17 | 	
34 | 
35 | 


--------------------------------------------------------------------------------
/test/test_node_file.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(SQL, assert) {
 2 | 	//Node filesystem module - You know that.
 3 | 	var fs = require('fs');
 4 | 
 5 | 	//Ditto, path module
 6 | 	var path = require('path');
 7 | 
 8 | 	var filebuffer = fs.readFileSync(path.join(__dirname, 'test.sqlite'));
 9 | 
10 | 	//Works
11 | 	var db = new SQL.Database(filebuffer);
12 | 
13 | 	//[{"columns":["id","content"],"values":[["0","hello"],["1","world"]]}]
14 | 	var res = db.exec("SELECT * FROM test WHERE id = 0");
15 | 	assert.deepEqual(res,
16 | 									[{"columns":["id","content"],"values":[[0,"hello"]]}],
17 | 									"One should be able to read the contents of an SQLite database file read from disk");
18 | 	db.close();
19 | }
20 | 
21 | if (module == require.main) {
22 | 	const target_file = process.argv[2];
23 |   const sql_loader = require('./load_sql_lib');
24 |   sql_loader(target_file).then((sql)=>{
25 |     require('test').run({
26 |       'test node file': function(assert){
27 |         exports.test(sql, assert);
28 |       }
29 |     });
30 |   })
31 |   .catch((e)=>{
32 |     console.error(e);
33 |     assert.fail(e);
34 |   });
35 | }
36 | 


--------------------------------------------------------------------------------
/test/test_issue325.js:
--------------------------------------------------------------------------------
 1 | 
 2 | exports.test = function(sql, assert){
 3 |     "use strict";
 4 |     // Create a database
 5 |     var db = new sql.Database();
 6 | 
 7 |     // binding a large number 
 8 |     assert.strictEqual(
 9 |         db.exec("SELECT ?", [1.7976931348623157e+308])[0].values[0][0],
10 |         1.7976931348623157e+308,
11 |         "binding 1.7976931348623159e+308 as a parameter"
12 |     );
13 | 
14 |     // inline result value test
15 |     assert.strictEqual(
16 |         db.exec("SELECT 1.7976931348623157e+308")[0].values[0][0],
17 |         1.7976931348623157e+308,
18 |         "SELECT 1.7976931348623157e+308 is 1.7976931348623157e+308"
19 |     );
20 | 
21 |     // Close the database and all associated statements
22 |     db.close();
23 | };
24 | 
25 | if (module == require.main) {
26 |   const target_file = process.argv[2];
27 |   const sql_loader = require('./load_sql_lib');
28 |   sql_loader(target_file).then((sql)=>{
29 |     require('test').run({
30 |       'test issue325': function(assert){
31 |         exports.test(sql, assert);
32 |       }
33 |     });
34 |   })
35 |   .catch((e)=>{
36 |     console.error(e);
37 |     assert.fail(e);
38 |   });
39 | }
40 | 


--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
 1 | name: CI
 2 | 
 3 | on: [push, pull_request]
 4 | 
 5 | jobs:
 6 |   build:
 7 |     runs-on: ubuntu-latest
 8 |     steps:
 9 |     - uses: actions/checkout@v2
10 |     - uses: actions/cache@v1
11 |       id: cache
12 |       with:
13 |         path: '.emsdk-cache'
14 |         key: emscripten-2.0.6
15 |     - uses: mymindstorm/setup-emsdk@ca33dc66a6b178f65393989c12e9465baf053352
16 |       with:
17 |         version: '2.0.6'
18 |         actions-cache-folder: '.emsdk-cache'
19 |     - name: make
20 |       run: make
21 |     - uses: actions/upload-artifact@v2
22 |       with: {name: dist, path: dist}
23 |     - name: test
24 |       run: npm ci && npm test
25 |     - name: generate documentation
26 |       run: npm run doc
27 |     - name: Update github pages
28 |       if: github.event_name == 'push' && github.ref == 'refs/heads/master'
29 |       uses: JamesIves/github-pages-deploy-action@3.6.2
30 |       with:
31 |         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |         BRANCH: gh-pages # The branch the action should deploy to.
33 |         FOLDER: "." # The folder the action should deploy.
34 |         CLEAN: false # Automatically remove deleted files from the deploy branch
35 | 


--------------------------------------------------------------------------------
/src/exported_functions.json:
--------------------------------------------------------------------------------
 1 | [
 2 | "_malloc",
 3 | "_free",
 4 | "_sqlite3_open",
 5 | "_sqlite3_exec",
 6 | "_sqlite3_free",
 7 | "_sqlite3_errmsg",
 8 | "_sqlite3_changes",
 9 | "_sqlite3_prepare_v2",
10 | "_sqlite3_sql",
11 | "_sqlite3_normalized_sql",
12 | "_sqlite3_bind_text",
13 | "_sqlite3_bind_blob",
14 | "_sqlite3_bind_double",
15 | "_sqlite3_bind_int",
16 | "_sqlite3_bind_parameter_index",
17 | "_sqlite3_step",
18 | "_sqlite3_column_count",
19 | "_sqlite3_data_count",
20 | "_sqlite3_column_double",
21 | "_sqlite3_column_text",
22 | "_sqlite3_column_blob",
23 | "_sqlite3_column_bytes",
24 | "_sqlite3_column_type",
25 | "_sqlite3_column_name",
26 | "_sqlite3_reset",
27 | "_sqlite3_clear_bindings",
28 | "_sqlite3_finalize",
29 | "_sqlite3_close_v2",
30 | "_sqlite3_create_function_v2",
31 | "_sqlite3_create_module_v2",
32 | "_sqlite3_value_bytes",
33 | "_sqlite3_value_type",
34 | "_sqlite3_value_text",
35 | "_sqlite3_value_int",
36 | "_sqlite3_value_blob",
37 | "_sqlite3_value_double",
38 | "_sqlite3_result_double",
39 | "_sqlite3_result_null",
40 | "_sqlite3_result_text",
41 | "_sqlite3_result_blob",
42 | "_sqlite3_result_int",
43 | "_sqlite3_result_int64",
44 | "_sqlite3_result_error",
45 | "_sqlite3_declare_vtab",
46 | "_sqlite3_malloc",
47 | "_RegisterExtensionFunctions"
48 | ]
49 | 


--------------------------------------------------------------------------------
/.jsdoc.config.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "plugins": [
 3 |         "plugins/markdown"
 4 |     ],
 5 |     "source": {
 6 |         "include": [
 7 |             "src/api.js"
 8 |         ]
 9 |     },
10 |     "opts": {
11 |         "encoding": "utf8",
12 |         "destination": "./documentation/",
13 |         "readme": "documentation_index.md",
14 |         "template": "./node_modules/clean-jsdoc-theme",
15 |         "theme_opts": {
16 |             "title": "sql.js",
17 |             "meta": [
18 |                 "sql.js API documentation",
19 |                 "",
20 |                 ""
21 |             ],
22 |             "menu": [
23 |                 {
24 |                     "title": "Website",
25 |                     "link": "https://sql.js.org/"
26 |                 },
27 |                 {
28 |                     "title": "Github",
29 |                     "link": "https://github.com/sql-js/sql.js"
30 |                 },
31 |                 {
32 |                     "title": "Demo",
33 |                     "link": "https://sql.js.org/examples/GUI/"
34 |                 }
35 |             ]
36 |         }
37 |     }
38 | }
39 | 


--------------------------------------------------------------------------------
/test/test_issue55.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(SQL, assert) {
 2 |   var fs = require('fs');
 3 |   var path = require('path');
 4 | 
 5 |   var filebuffer = fs.readFileSync(path.join(__dirname, 'issue55.db'));
 6 | 
 7 |   //Works
 8 |   var db = new SQL.Database(filebuffer);
 9 | 
10 |   var origCount = db.prepare("SELECT COUNT(*) AS count FROM networklocation").getAsObject({}).count;
11 | 
12 |   db.run("INSERT INTO networklocation (x, y, network_id, floor_id) VALUES (?, ?, ?, ?)", [123, 123, 1, 1]);
13 | 
14 |   var count = db.prepare("SELECT COUNT(*) AS count FROM networklocation").getAsObject({}).count;
15 | 
16 |   assert.equal(count, origCount + 1, "The row has been inserted");
17 |   var dbCopy = new SQL.Database(db.export());
18 |   var newCount = dbCopy.prepare("SELECT COUNT(*) AS count FROM networklocation").getAsObject({}).count;
19 |   assert.equal(newCount, count, "export and reimport copies all the data");
20 | };
21 | 
22 | if (module == require.main) {
23 | 	const target_file = process.argv[2];
24 |   const sql_loader = require('./load_sql_lib');
25 |   sql_loader(target_file).then((sql)=>{
26 |     require('test').run({
27 |       'test issue 55': function(assert){
28 |         exports.test(sql, assert);
29 |       }
30 |     });
31 |   })
32 |   .catch((e)=>{
33 |     console.error(e);
34 |     assert.fail(e);
35 |   });
36 | }
37 | 
38 | 


--------------------------------------------------------------------------------
/examples/simple.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
33 | 
34 | 
35 |   Output is in Javscript console
36 | 
37 | 
38 | 


--------------------------------------------------------------------------------
/test/test_issue128.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(sql, assert) {
 2 |   // Create a database
 3 |   var db = new sql.Database();
 4 | 
 5 |   db.run("CREATE TABLE test (data TEXT);");
 6 | 
 7 |   db.exec("SELECT * FROM test;");
 8 |   assert.deepEqual(db.getRowsModified(), 0, "getRowsModified returns 0 at first");
 9 |   
10 |   db.exec("INSERT INTO test VALUES ('Hello1');");
11 |   db.exec("INSERT INTO test VALUES ('Hello');");
12 |   db.exec("INSERT INTO test VALUES ('Hello');");
13 |   db.exec("INSERT INTO test VALUES ('World4');");
14 |   assert.deepEqual(db.getRowsModified(), 1, "getRowsModified works for inserts");
15 | 
16 |   db.exec("UPDATE test SET data = 'World4' where data = 'Hello';");
17 |   assert.deepEqual(db.getRowsModified(), 2, "getRowsModified works for updates");
18 | 
19 |   db.exec("DELETE FROM test;");
20 |   assert.deepEqual(db.getRowsModified(), 4, "getRowsModified works for deletes");
21 | 
22 |   db.exec("SELECT * FROM test;");
23 |   assert.deepEqual(db.getRowsModified(), 4, "getRowsModified unmodified by queries");
24 | 
25 | };
26 | 
27 | if (module == require.main) {
28 | 	const target_file = process.argv[2];
29 |   const sql_loader = require('./load_sql_lib');
30 |   sql_loader(target_file).then((sql)=>{
31 |     require('test').run({
32 |       'test issue 128': function(assert){
33 |         exports.test(sql, assert);
34 |       }
35 |     });
36 |   })
37 |   .catch((e)=>{
38 |     console.error(e);
39 |     assert.fail(e);
40 |   });
41 | }
42 | 


--------------------------------------------------------------------------------
/examples/GUI/demo.css:
--------------------------------------------------------------------------------
 1 | html {
 2 | 	background:#222;
 3 | 	margin:auto;
 4 | 	width:80%;
 5 | }
 6 | 
 7 | body{
 8 | 	background: linear-gradient(#aaa 0, #ddd 10px, #fff 55px);
 9 | 	border: 1px solid black;
10 | 	padding: 10px 20px;
11 | 	box-shadow: 5px 0px 30px #000;
12 | 	border-radius: 8px;
13 | }
14 | 
15 | h1 {
16 | 	text-align: center;
17 | 	color: #222;
18 | 	margin: 0 0 30px;
19 | }
20 | 
21 | .button {
22 | 	color: #333;
23 | 	background: linear-gradient(#eee, #ddd);
24 | 	border: 1px solid #222;
25 | 	border-radius: 3px;
26 | 	padding: 7px;
27 | 	margin-right: 5px;
28 | 	transition: .3s;
29 | 	font-family: ubuntu, sans-serif;
30 | 	font-size: 1em;
31 | }
32 | 
33 | .button:active {
34 | 	background: linear-gradient(#ddd, #eee);
35 | }
36 | 
37 | .button:hover, button:focus {
38 | 	box-shadow: 0 0 2px #222;
39 | }
40 | 
41 | #execute {
42 | 	margin-top: 5px;;
43 | 	width: 10%;
44 | 	min-width:100px;
45 | }
46 | 
47 | .CodeMirror {
48 |   border: 1px solid #222;
49 |   height: auto;
50 | }
51 | .CodeMirror-scroll {
52 |   overflow-y: hidden;
53 |   overflow-x: auto;
54 | }
55 | 
56 | .error {
57 | 	color:red;
58 | 	transition:.5s;
59 | 	overflow:hidden;
60 | 	margin: 15px;
61 | }
62 | 
63 | #output {
64 | 	overflow: auto;
65 | }
66 | 
67 | table {
68 | 	width:auto;
69 | 	margin:auto;
70 | 	border:1px solid black;
71 |  	border-collapse:collapse;
72 |  	margin-bottom:10px;
73 | }
74 | 
75 | th, td {
76 | 	border:1px solid #777;
77 | }
78 | 
79 | footer {
80 | 	font-size:.8em;
81 | 	color: #222;
82 | }
83 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 | 	"name": "sql.js",
 3 | 	"version": "1.5.0",
 4 | 	"description": "SQLite library with support for opening and writing databases, prepared statements, and more. This SQLite library is in pure javascript (compiled with emscripten).",
 5 | 	"keywords": [
 6 | 		"sql",
 7 | 		"sqlite",
 8 | 		"stand-alone",
 9 | 		"relational",
10 | 		"database",
11 | 		"RDBMS",
12 | 		"data",
13 | 		"query",
14 | 		"statement",
15 | 		"emscripten",
16 | 		"asm",
17 | 		"asm.js"
18 | 	],
19 | 	"license": "MIT",
20 | 	"main": "./dist/sql-wasm.js",
21 | 	"scripts": {
22 | 		"build": "make",
23 | 		"rebuild": "make clean && make",
24 | 		"test": "npm run lint && npm run test-asm && npm run test-asm-debug && npm run test-wasm && npm run test-wasm-debug && npm run test-asm-memory-growth",
25 | 		"lint": "eslint .",
26 | 		"prettify": "eslint . --fix",
27 | 		"test-asm": "node test/all.js asm",
28 | 		"test-asm-debug": "node test/all.js asm-debug",
29 | 		"test-asm-memory-growth": "node test/all.js asm-memory-growth",
30 | 		"test-wasm": "node test/all.js wasm",
31 | 		"test-wasm-debug": "node test/all.js wasm-debug",
32 | 		"doc": "jsdoc -c .jsdoc.config.json"
33 | 	},
34 | 	"homepage": "http://github.com/sql-js/sql.js",
35 | 	"repository": {
36 | 		"type": "git",
37 | 		"url": "http://github.com/sql-js/sql.js.git"
38 | 	},
39 | 	"bugs": {
40 | 		"url": "https://github.com/sql-js/sql.js/issues"
41 | 	},
42 | 	"devDependencies": {
43 | 		"clean-jsdoc-theme": "^2.2.15",
44 | 		"eslint": "^6.8.0",
45 | 		"eslint-config-airbnb-base": "^14.2.1",
46 | 		"eslint-plugin-import": "^2.22.1",
47 | 		"jsdoc": "^3.6.6",
48 | 		"puppeteer": "^2.1.1",
49 | 		"test": ">=0.6"
50 | 	},
51 | 	"dependencies": {}
52 | }
53 | 


--------------------------------------------------------------------------------
/test/test_blob.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(SQL, assert){
 2 | 	var db = new SQL.Database();
 3 | 	db.exec("CREATE TABLE test (data); INSERT INTO test VALUES (x'6162ff'),(x'00')"); // Insert binary data. This is invalid UTF8 on purpose
 4 | 
 5 | 	var stmt = db.prepare("INSERT INTO test VALUES (?)");
 6 | 	var bigArray = new Uint8Array(1e6);
 7 | 	bigArray[500] = 0x42
 8 | 	stmt.run([ bigArray ]);
 9 | 
10 | 	var stmt = db.prepare("SELECT * FROM test ORDER BY length(data) DESC");
11 | 
12 | 	stmt.step();
13 | 	var array = stmt.get()[0];
14 | 	assert.equal(array.length, bigArray.length, "BLOB read from the database should be the same size as the one that was inserted");
15 | 	for (var i=0; i{
37 | 		require('test').run({
38 | 			'test blob': function(assert){
39 | 				exports.test(sql, assert);
40 | 			}
41 | 		});
42 | 	})
43 | 	.catch((e)=>{
44 | 		console.error(e);
45 | 	});
46 | }
47 | 


--------------------------------------------------------------------------------
/test/test_transactions.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(SQL, assert){
 2 |   var db = new SQL.Database();
 3 |   db.exec("CREATE TABLE test (data); INSERT INTO test VALUES (1);");
 4 | 
 5 |   // Open a transaction
 6 |   db.exec("BEGIN TRANSACTION;");
 7 | 
 8 |   // Insert a row
 9 |   db.exec("INSERT INTO test VALUES (4);")
10 | 
11 |   // Rollback
12 |   db.exec("ROLLBACK;");
13 | 
14 |   var res = db.exec("SELECT data FROM test WHERE data = 4;");
15 |   var expectedResult =  [];
16 |   assert.deepEqual(res, expectedResult, "transaction rollbacks work");
17 | 
18 |   // Open a transaction
19 |   db.exec("BEGIN TRANSACTION;");
20 | 
21 |   // Insert a row
22 |   db.exec("INSERT INTO test VALUES (4);")
23 | 
24 |   // Commit
25 |   db.exec("COMMIT;");
26 | 
27 |   var res = db.exec("SELECT data FROM test WHERE data = 4;");
28 |   var expectedResult =  [{
29 |     columns : ['data'],
30 |     values : [
31 |       [4]
32 |     ]
33 |   }];
34 |   assert.deepEqual(res, expectedResult, "transaction commits work");
35 | 
36 |   // Open a transaction
37 |   db.exec("BEGIN TRANSACTION;");
38 | 
39 |   // Insert a row
40 |   db.exec("INSERT INTO test VALUES (5);")
41 | 
42 |   // Rollback
43 |   db.exec("ROLLBACK;");
44 | 
45 |   var res = db.exec("SELECT data FROM test WHERE data IN (4,5);");
46 |   var expectedResult =  [{
47 |     columns : ['data'],
48 |     values : [
49 |       [4]
50 |     ]
51 |   }];
52 |   assert.deepEqual(res, expectedResult, "transaction rollbacks after commits work");
53 | 
54 |   db.close();
55 | };
56 | 
57 | if (module == require.main) {
58 | 	const target_file = process.argv[2];
59 |   const sql_loader = require('./load_sql_lib');
60 |   sql_loader(target_file).then((sql)=>{
61 |     require('test').run({
62 |       'test transactions': function(assert){
63 |         exports.test(sql, assert);
64 |       }
65 |     });
66 |   })
67 |   .catch((e)=>{
68 |     console.error(e);
69 |     assert.fail(e);
70 |   });
71 | }
72 | 


--------------------------------------------------------------------------------
/test/test_issue73.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(sql, assert) {
 2 |     // Create a database
 3 |     var db = new sql.Database();
 4 | 
 5 |     // Execute some sql
 6 |     sqlstr = "CREATE TABLE COMPANY("+
 7 | "                     ID INT PRIMARY KEY     NOT NULL,"+
 8 | "                     NAME           TEXT    NOT NULL,"+
 9 | "                     AGE            INT     NOT NULL,"+
10 | "                     ADDRESS        CHAR(50),"+
11 | "                     SALARY         REAL"+
12 | "                    );"+
13 | "                  CREATE TABLE AUDIT("+
14 | "                      EMP_ID INT NOT NULL,"+
15 | "                      ENTRY_DATE TEXT NOT NULL"+
16 | "                  );"+
17 | "                  CREATE TRIGGER audit_log AFTER INSERT"+
18 | "                  ON COMPANY"+
19 | "                  BEGIN"+
20 | "                     INSERT INTO AUDIT"+
21 | "                        (EMP_ID, ENTRY_DATE)"+
22 | "                      VALUES"+
23 | "                        (new.ID, '2014-11-10');"+
24 | "                  END;"+
25 | "                  INSERT INTO COMPANY VALUES (73,'A',8,'',1200);"+
26 | "                  SELECT * FROM AUDIT;"+
27 | "                  INSERT INTO COMPANY VALUES (42,'B',8,'',1600);"+
28 | "                  SELECT EMP_ID FROM AUDIT ORDER BY EMP_ID";
29 |     var res = db.exec(sqlstr);
30 |     var expectedResult =  [
31 |     {
32 |         columns : ['EMP_ID','ENTRY_DATE'],
33 |         values : [
34 |             [73, '2014-11-10']
35 |          ]
36 |     },
37 |     {
38 |         columns : ['EMP_ID'],
39 |         values : [
40 |             [42],[73]
41 |          ]
42 |     }
43 |     ];
44 |     assert.deepEqual(res, expectedResult,
45 |             "db.exec with a statement that contains a ';'");
46 | };
47 | 
48 | if (module == require.main) {
49 | 	const target_file = process.argv[2];
50 |   const sql_loader = require('./load_sql_lib');
51 |   sql_loader(target_file).then((sql)=>{
52 |     require('test').run({
53 |       'test issue 73': function(assert){
54 |         exports.test(sql, assert);
55 |       }
56 |     });
57 |   })
58 |   .catch((e)=>{
59 |     console.error(e);
60 |     assert.fail(e);
61 |   });
62 | }
63 | 


--------------------------------------------------------------------------------
/test/test_functions_recreate.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(sql, assert) {
 2 |   // Test 1: Create a database, Register single function, close database, repeat 1000 times
 3 |   
 4 |   for (var i = 1; i <= 1000; i++) 
 5 |   {
 6 |     let lastStep=(i==1000);
 7 |     let db = new sql.Database();
 8 |     function add() {return i;}
 9 |     try
10 |     {
11 |       db.create_function("TestFunction"+i, add)
12 |     }catch(e)
13 |     {
14 |       assert.ok(false,"Test 1: Recreate database "+i+"th times and register function failed with exception:"+e);
15 |       db.close();
16 |       break;
17 |     }
18 |     var result = db.exec("SELECT TestFunction"+i+"()");
19 |     var result_str = result[0]["values"][0][0];
20 |     if((result_str!=i)||lastStep)
21 |     {
22 |       assert.equal(result_str, i, "Test 1: Recreate database "+i+"th times and register function");
23 |       db.close();
24 |       break;
25 |     }
26 |     db.close();
27 |   }
28 |   
29 |   // Test 2: Create a database, Register same function  1000 times, close database
30 |   {
31 |     let db = new sql.Database();
32 |     for (var i = 1; i <= 1000; i++) 
33 |     {
34 |       let lastStep=(i==1000);
35 |       function add() {return i;}
36 |       try
37 |       {
38 |         db.create_function("TestFunction", add);
39 |       }catch(e)
40 |       {
41 |         assert.ok(false,"Test 2: Reregister function "+i+"th times failed with exception:"+e);
42 |         break;
43 |       }
44 |       var result = db.exec("SELECT TestFunction()");
45 |       var result_str = result[0]["values"][0][0];
46 |       if((result_str!=i)||lastStep)
47 |       {
48 |         assert.equal(result_str, i, "Test 2: Reregister function "+i+"th times");
49 |         break;
50 |       }
51 |     }
52 |     db.close();
53 |   }
54 | };
55 | 
56 | 
57 | if (module == require.main) {
58 | 	const target_file = process.argv[2];
59 |   const sql_loader = require('./load_sql_lib');
60 |   sql_loader(target_file).then((sql)=>{
61 |     require('test').run({
62 |       'test creating multiple functions': function(assert){
63 |         exports.test(sql, assert);
64 |       }
65 |     });
66 |   })
67 |   .catch((e)=>{
68 |     console.error(e);
69 |     assert.fail(e);
70 |   });
71 | }
72 | 


--------------------------------------------------------------------------------
/examples/requireJS.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 
55 | 
56 | 
57 |   Output is in Javscript console
58 | 
59 | 
60 | 
61 | 


--------------------------------------------------------------------------------
/test/test_errors.js:
--------------------------------------------------------------------------------
 1 | exports.test = function(sql, assert) {
 2 | 
 3 |   assert.throws(function(){
 4 |     var db = new sql.Database([1,2,3]);
 5 |     db.exec("SELECT * FROM sqlite_master");
 6 |   },
 7 |                 /not a database/,
 8 |                 "Querying an invalid database should throw an error");
 9 | 
10 |   // Create a database
11 |   var db = new sql.Database();
12 | 
13 |   // Execute some sql
14 |   var res = db.exec("CREATE TABLE test (a INTEGER PRIMARY KEY, b, c, d, e);");
15 | 
16 |   assert.throws(function(){
17 |     db.exec("I ain't be no valid sql ...");
18 |   },
19 |                 /syntax error/,
20 |                 "Executing invalid SQL should throw an error");
21 | 
22 |   assert.throws(function(){
23 |     db.run("INSERT INTO test (a) VALUES (1)");
24 |     db.run("INSERT INTO test (a) VALUES (1)");
25 |   },
26 |                 /UNIQUE constraint failed/,
27 |                 "Inserting two rows with the same primary key should fail");
28 | 
29 |   var stmt = db.prepare("INSERT INTO test (a) VALUES (?)");
30 | 
31 | 
32 |   assert.throws(function(){
33 |     stmt.bind([1,2,3]);
34 |   },
35 |                 /out of range/,
36 |                 "Binding too many parameters should throw an exception");
37 | 
38 |   assert.throws(function(){
39 |     db.run("CREATE TABLE test (this,wont,work)");
40 |   },
41 |                 /table .+ already exists/,
42 |                 "Trying to create a table with a name that is already used should throw an error");
43 | 
44 |   stmt.run([2]);
45 |   assert.deepEqual(db.exec("SELECT a,b FROM test WHERE a=2"),
46 |                    [{columns:['a', 'b'],values:[[2, null]]}],
47 |                    "Previous errors should not have spoiled the statement");
48 | 
49 |   db.close();
50 | 
51 |   assert.throws(function(){
52 |     stmt.run([3]);
53 |   }, "Statements shouldn't be able to execute after the database is closed");
54 | };
55 | 
56 | if (module == require.main) {
57 | 	const target_file = process.argv[2];
58 |   const sql_loader = require('./load_sql_lib');
59 |   sql_loader(target_file).then((sql)=>{
60 |     require('test').run({
61 |       'test errors': function(assert){
62 |         exports.test(sql, assert);
63 |       }
64 |     });
65 |   })
66 |   .catch((e)=>{
67 |     console.error(e);
68 |   });
69 | }
70 | 


--------------------------------------------------------------------------------
/examples/persistent.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 	
 6 | 	Persistent sqlite
 7 | 	
 8 | 
 9 | 
10 | 
11 | 	

You have seen this page 0 times.

12 |
13 | You have been here on the following dates:
    14 |
    15 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/test_modularization.js: -------------------------------------------------------------------------------- 1 | 2 | exports.test = function (SQL, assert, done, sqlLibFilename) { 3 | if (!sqlLibFilename){ 4 | // Whew - this is ugly and fragile and makes way too many assumptions about how these tests are run from all.js 5 | // However, this is the quickest way to make sure that we are testing the lib that is requested 6 | const targetFile = process.argv[2]; 7 | sqlLibFilename = targetFile ? "../dist/sql-"+targetFile+".js" : "../dist/sql-wasm.js"; 8 | } 9 | 10 | var initSqlJsLib1 = require(sqlLibFilename); 11 | initSqlJsLib1().then((sqlModule1) => { 12 | var initSqlJsLib2 = require(sqlLibFilename); 13 | initSqlJsLib2().then((sqlModule2) => { 14 | assert.equal(SQL, sqlModule1, "Initializing the module multiple times only creates it once"); 15 | assert.equal(sqlModule1, sqlModule2, "Initializing the module multiple times only creates it once"); 16 | var db1 = new sqlModule1.Database(); 17 | assert.equal(Object.getPrototypeOf(db1), SQL.Database.prototype, "sqlModule1 has a Database object that has the same prototype as the originally loaded SQL module"); 18 | assert.equal(Object.getPrototypeOf(db1), sqlModule2.Database.prototype, "sqlModule1 has a Database object that has the same prototype as the sqlModule2"); 19 | 20 | 21 | var db2 = new sqlModule2.Database(); 22 | assert.equal(Object.getPrototypeOf(db2), sqlModule1.Database.prototype, "sqlModule2 has a Database object that has the same prototype as the sqlModule1"); 23 | 24 | done(); 25 | }); 26 | }); 27 | }; 28 | 29 | if (module == require.main) { 30 | const targetFile = process.argv[2]; 31 | const loadSqlLib = require('./load_sql_lib'); 32 | loadSqlLib(targetFile).then((sql) => { 33 | require('test').run({ 34 | 'test modularization': function (assert, done) { 35 | // TODO: Dry this up so that this code isn't duped between here and load_sql_lib.js 36 | var sqlJsLibFilename = targetFile ? "../dist/sql-"+targetFile+".js" : "../dist/sql-wasm.js"; 37 | exports.test(sql, assert, done, sqlJsLibFilename); 38 | } 39 | }) 40 | }) 41 | .catch((e) => { 42 | console.error(e); 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | =========== 3 | 4 | Copyright (c) 2017 sql.js authors (see AUTHORS) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | 25 | 26 | # Some portions of the Makefile taken from: 27 | Copyright 2017 Ryusei Yamaguchi 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy of 30 | this software and associated documentation files (the "Software"), to deal in 31 | the Software without restriction, including without limitation the rights to 32 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 33 | the Software, and to permit persons to whom the Software is furnished to do so, 34 | subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 41 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 42 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 43 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 44 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | env: { 5 | browser: true, 6 | es6: true, 7 | node: true 8 | }, 9 | extends: [ 10 | "airbnb-base" 11 | ], 12 | globals: { 13 | Atomics: "readonly", 14 | SharedArrayBuffer: "readonly" 15 | }, 16 | ignorePatterns: [ 17 | "/dist/", 18 | "/examples/", 19 | "/node_modules/", 20 | "/out/", 21 | "/src/shell-post.js", 22 | "/src/shell-pre.js", 23 | "/test/", 24 | "!/.eslintrc.js" 25 | ], 26 | parserOptions: { 27 | ecmaVersion: 5, 28 | sourceType: "script" 29 | }, 30 | rules: { 31 | // reason - sqlite exposes functions with underscore-naming-convention 32 | camelcase: "off", 33 | // reason - They make it easier to add new elements to arrays 34 | // and parameters to functions, and make commit diffs clearer 35 | "comma-dangle": "off", 36 | // reason - string-notation needed to prevent closure-minifier 37 | // from mangling property-name 38 | "dot-notation": "off", 39 | // reason - enforce 4-space indent 40 | indent: ["error", 4, { SwitchCase: 1 }], 41 | // reason - enforce 80-column-width limit 42 | "max-len": ["error", { code: 80 }], 43 | // reason - src/api.js uses bitwise-operators 44 | "no-bitwise": "off", 45 | "no-cond-assign": ["error", "except-parens"], 46 | "no-param-reassign": "off", 47 | "no-throw-literal": "off", 48 | // reason - parserOptions is set to es5 language-syntax 49 | "no-var": "off", 50 | // reason - parserOptions is set to es5 language-syntax 51 | "object-shorthand": "off", 52 | // reason - parserOptions is set to es5 language-syntax 53 | "prefer-arrow-callback": "off", 54 | // reason - parserOptions is set to es5 language-syntax 55 | "prefer-destructuring": "off", 56 | // reason - parserOptions is set to es5 language-syntax 57 | "prefer-spread": "off", 58 | // reason - parserOptions is set to es5 language-syntax 59 | "prefer-template": "off", 60 | // reason - sql.js frequently use sql-query-strings containing 61 | // single-quotes 62 | quotes: ["error", "double"], 63 | // reason - allow top-level "use-strict" in commonjs-modules 64 | strict: ["error", "safe"], 65 | "vars-on-top": "off" 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/worker.js: -------------------------------------------------------------------------------- 1 | /* global initSqlJs */ 2 | /* eslint-env worker */ 3 | /* eslint no-restricted-globals: ["error"] */ 4 | 5 | "use strict"; 6 | 7 | var db; 8 | 9 | function onModuleReady(SQL) { 10 | function createDb(data) { 11 | if (db != null) db.close(); 12 | db = new SQL.Database(data); 13 | return db; 14 | } 15 | 16 | var buff; var data; var result; 17 | data = this["data"]; 18 | switch (data && data["action"]) { 19 | case "open": 20 | buff = data["buffer"]; 21 | createDb(buff && new Uint8Array(buff)); 22 | return postMessage({ 23 | id: data["id"], 24 | ready: true 25 | }); 26 | case "exec": 27 | if (db === null) { 28 | createDb(); 29 | } 30 | if (!data["sql"]) { 31 | throw "exec: Missing query string"; 32 | } 33 | return postMessage({ 34 | id: data["id"], 35 | results: db.exec(data["sql"], data["params"]) 36 | }); 37 | case "each": 38 | if (db === null) { 39 | createDb(); 40 | } 41 | var callback = function callback(row) { 42 | return postMessage({ 43 | id: data["id"], 44 | row: row, 45 | finished: false 46 | }); 47 | }; 48 | var done = function done() { 49 | return postMessage({ 50 | id: data["id"], 51 | finished: true 52 | }); 53 | }; 54 | return db.each(data["sql"], data["params"], callback, done); 55 | case "export": 56 | buff = db["export"](); 57 | result = { 58 | id: data["id"], 59 | buffer: buff 60 | }; 61 | try { 62 | return postMessage(result, [result]); 63 | } catch (error) { 64 | return postMessage(result); 65 | } 66 | case "close": 67 | if (db) { 68 | db.close(); 69 | } 70 | return postMessage({ 71 | id: data["id"] 72 | }); 73 | default: 74 | throw new Error("Invalid action : " + (data && data["action"])); 75 | } 76 | } 77 | 78 | function onError(err) { 79 | return postMessage({ 80 | id: this["data"]["id"], 81 | error: err["message"] 82 | }); 83 | } 84 | 85 | if (typeof importScripts === "function") { 86 | db = null; 87 | var sqlModuleReady = initSqlJs(); 88 | self.onmessage = function onmessage(event) { 89 | return sqlModuleReady 90 | .then(onModuleReady.bind(event)) 91 | .catch(onError.bind(event)); 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /test/test_database.js: -------------------------------------------------------------------------------- 1 | exports.test = function(SQL, assert, done) { 2 | assert.notEqual(SQL.Database, undefined, "Should export a Database object"); 3 | 4 | // Create a database 5 | var db = new SQL.Database(); 6 | assert.equal(Object.getPrototypeOf(db), SQL.Database.prototype, "Creating a database object"); 7 | 8 | // Execute some sql 9 | sqlstr = "CREATE TABLE test (a, b, c, d, e);"; 10 | var res = db.exec(sqlstr); 11 | assert.deepEqual(res, [], "Creating a table should not return anything"); 12 | 13 | db.run("INSERT INTO test VALUES (NULL, 42, 4.2, 'fourty two', x'42');"); 14 | 15 | //Retrieving values 16 | sqlstr = "SELECT * FROM test;"; 17 | var res = db.exec(sqlstr); 18 | var expectedResult = [{ 19 | columns : ['a','b','c','d','e'], 20 | values : [ 21 | [null,42,4.2,'fourty two', new Uint8Array([0x42])] 22 | ] 23 | }]; 24 | assert.deepEqual(res, expectedResult, "db.exec() return value"); 25 | 26 | 27 | // Export the database to an Uint8Array containing the SQLite database file 28 | var binaryArray = db.export(); 29 | assert.strictEqual(String.fromCharCode.apply(null,binaryArray.subarray(0,6)), 'SQLite', 30 | "The first 6 bytes of an SQLite database should form the word 'SQLite'"); 31 | db.close(); 32 | 33 | var db2 = new SQL.Database(binaryArray); 34 | result = db2.exec("SELECT * FROM test"); 35 | assert.deepEqual(result, expectedResult, 36 | "Exporting and re-importing the database should lead to the same database"); 37 | db2.close(); 38 | 39 | db = new SQL.Database(); 40 | assert.deepEqual(db.exec("SELECT * FROM sqlite_master"), 41 | [], 42 | "Newly created databases should be empty"); 43 | // Testing db.each 44 | db.run("CREATE TABLE test (a,b); INSERT INTO test VALUES (1,'a'),(2,'b')"); 45 | var count = 0, finished = false; 46 | db.each("SELECT * FROM test ORDER BY a", function callback (row){ 47 | count++; 48 | if (count === 1) assert.deepEqual(row, {a:1,b:'a'}, 'db.each returns the correct 1st row'); 49 | if (count === 2) assert.deepEqual(row, {a:2,b:'b'}, 'db.each returns the correct 2nd row'); 50 | }, function last () { 51 | finished = true; 52 | assert.strictEqual(count, 2, "db.each returns the right number of rows"); 53 | // No need to wait for this timeout anymore 54 | // In fact, if we do keep waiting for this, we'll get an error when it fires because we've already called done 55 | clearTimeout(testTimeoutId); 56 | done(); 57 | }); 58 | var testTimeoutId = setTimeout(function timeout(){ 59 | if (!finished) { 60 | assert.fail("db.each should call its last callback after having returned the rows"); 61 | done(); 62 | } 63 | }, 3000); 64 | }; 65 | 66 | if (module == require.main) { 67 | const target_file = process.argv[2]; 68 | const sql_loader = require('./load_sql_lib'); 69 | sql_loader(target_file).then((sql)=>{ 70 | require('test').run({ 71 | 'test database': function(assert, done){ 72 | exports.test(sql, assert, done); 73 | } 74 | }); 75 | }) 76 | .catch((e)=>{ 77 | console.error(e); 78 | assert.fail(e); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /test/test_extension_functions.js: -------------------------------------------------------------------------------- 1 | exports.test = function(sql, assert) { 2 | var db = new sql.Database(); 3 | var res = db.exec("CREATE TABLE test (str_data, data);"); 4 | 5 | db.run("INSERT INTO test VALUES ('Hello World!', 1);"); 6 | db.run("INSERT INTO test VALUES ('', 2);"); 7 | db.run("INSERT INTO test VALUES ('', 2);"); 8 | db.run("INSERT INTO test VALUES ('', 4);"); 9 | db.run("INSERT INTO test VALUES ('', 5);"); 10 | db.run("INSERT INTO test VALUES ('', 6);"); 11 | db.run("INSERT INTO test VALUES ('', 7);"); 12 | db.run("INSERT INTO test VALUES ('', 8);"); 13 | db.run("INSERT INTO test VALUES ('', 9);"); 14 | 15 | var res = db.exec("SELECT mode(data) FROM test;"); 16 | var expectedResult = [{ 17 | columns : ['mode(data)'], 18 | values : [ 19 | [2] 20 | ] 21 | }]; 22 | assert.deepEqual(res, expectedResult, "mode() function works"); 23 | 24 | var res = db.exec("SELECT lower_quartile(data) FROM test;"); 25 | var expectedResult = [{ 26 | columns : ['lower_quartile(data)'], 27 | values : [ 28 | [2] 29 | ] 30 | }]; 31 | assert.deepEqual(res, expectedResult, "upper_quartile() function works"); 32 | 33 | var res = db.exec("SELECT upper_quartile(data) FROM test;"); 34 | var expectedResult = [{ 35 | columns : ['upper_quartile(data)'], 36 | values : [ 37 | [7] 38 | ] 39 | }]; 40 | assert.deepEqual(res, expectedResult, "upper_quartile() function works"); 41 | 42 | var res = db.exec("SELECT variance(data) FROM test;"); 43 | assert.equal(res[0]['values'][0][0].toFixed(2), 8.11, "variance() function works"); 44 | 45 | var res = db.exec("SELECT stdev(data) FROM test;"); 46 | assert.equal(res[0]['values'][0][0].toFixed(2), 2.85, "stdev() function works"); 47 | 48 | var res = db.exec("SELECT acos(data) FROM test;"); 49 | assert.equal(res[0]['values'][0][0].toFixed(2), 0, "acos() function works"); 50 | 51 | var res = db.exec("SELECT asin(data) FROM test;"); 52 | assert.equal(res[0]['values'][0][0].toFixed(2), 1.57, "asin() function works"); 53 | 54 | var res = db.exec("SELECT atan2(data, 1) FROM test;"); 55 | assert.equal(res[0]['values'][0][0].toFixed(2), 0.79, "atan2() function works"); 56 | 57 | var res = db.exec("SELECT difference(str_data, 'ello World!') FROM test;"); 58 | assert.equal(res[0]['values'][0][0], 3, "difference() function works"); 59 | 60 | var res = db.exec("SELECT ceil(4.1)"); 61 | assert.equal(res[0]['values'][0][0], 5, "ceil() function works"); 62 | 63 | var res = db.exec("SELECT floor(4.1)"); 64 | assert.equal(res[0]['values'][0][0], 4, "floor() function works"); 65 | 66 | var res = db.exec("SELECT pi()"); 67 | assert.equal(res[0]['values'][0][0].toFixed(5), 3.14159, "pi() function works"); 68 | 69 | var res = db.exec("SELECT reverse(str_data) FROM test;"); 70 | assert.equal(res[0]['values'][0][0], "!dlroW olleH", "reverse() function works"); 71 | 72 | }; 73 | 74 | if (module == require.main) { 75 | const target_file = process.argv[2]; 76 | const sql_loader = require('./load_sql_lib'); 77 | sql_loader(target_file).then((sql)=>{ 78 | require('test').run({ 79 | 'test extension functions': function(assert){ 80 | exports.test(sql, assert); 81 | } 82 | }); 83 | }) 84 | .catch((e)=>{ 85 | console.error(e); 86 | assert.fail(e); 87 | }); 88 | } 89 | -------------------------------------------------------------------------------- /examples/GUI/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | sql.js demo: Online SQL interpreter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Fork me on GitHub 19 | 20 |

    Online SQL interpreter

    21 | 22 |
    23 | 24 |
    25 | 26 | 49 | 50 | 51 | 52 | 53 | 54 |
    55 | 56 |
    Results will be displayed here
    57 |
    58 | 59 | 60 | 61 |
    62 | Original work by kripken (sql.js). 63 | C to Javascript compiler by kripken (emscripten). 64 | Project now maintained by lovasoa 65 |
    66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /test/test_statement_iterator.js: -------------------------------------------------------------------------------- 1 | exports.test = function (SQL, assert) { 2 | // Create a database 3 | var db = new SQL.Database(); 4 | 5 | // Multiline SQL 6 | var sqlstr = "CREATE TABLE test (x text, y integer);\n" 7 | + "INSERT INTO test\n" 8 | + "VALUES ('hello', 42), ('goodbye', 17);\n" 9 | + "SELECT * FROM test;\n" 10 | + " -- nothing here"; 11 | var sqlstart = "CREATE TABLE test (x text, y integer);" 12 | 13 | // Manual iteration 14 | // Get an iterator 15 | var it = db.iterateStatements(sqlstr); 16 | 17 | // Get first item 18 | var x = it.next(); 19 | assert.equal(x.done, false, "Valid iterator object produced"); 20 | assert.equal(x.value.getSQL(), sqlstart, "Statement is for first query only"); 21 | assert.equal(it.getRemainingSQL(), sqlstr.slice(sqlstart.length), "Remaining sql retrievable"); 22 | 23 | // execute the first query 24 | x.value.step(); 25 | 26 | // get and execute the second query 27 | x = it.next(); 28 | assert.equal(x.done, false, "Second query found"); 29 | x.value.step(); 30 | 31 | // get and execute the third query 32 | x = it.next(); 33 | assert.equal(x.done, false, "Third query found"); 34 | x.value.step(); 35 | assert.deepEqual(x.value.getColumnNames(), ['x', 'y'], "Third query is SELECT"); 36 | 37 | // check for additional queries 38 | x = it.next(); 39 | assert.deepEqual(x, { done: true }, "Done reported after last query"); 40 | 41 | // additional iteration does nothing 42 | x = it.next(); 43 | assert.deepEqual(x, { done: true }, "Done reported when iterating past completion"); 44 | 45 | db.run("DROP TABLE test;"); 46 | 47 | // for...of 48 | var count = 0; 49 | for (let statement of db.iterateStatements(sqlstr)) { 50 | statement.step(); 51 | count = count + 1; 52 | } 53 | assert.equal(count, 3, "For loop iterates correctly"); 54 | 55 | var badsql = "SELECT 1 as x;garbage in, garbage out"; 56 | 57 | // bad sql will stop iteration 58 | it = db.iterateStatements(badsql); 59 | x = it.next(); 60 | x.value.step(); 61 | assert.deepEqual(x.value.getAsObject(), { x: 1 }, "SQL before bad statement executes successfully"); 62 | assert.throws(function () { it.next() }, /syntax error/, "Bad SQL stops iteration with exception"); 63 | assert.deepEqual(it.next(), { done: true }, "Done reported when iterating after exception"); 64 | 65 | // valid SQL executes, remaining SQL accessible after exception 66 | it = db.iterateStatements(badsql); 67 | var remains = ''; 68 | try { 69 | for (let statement of it) { 70 | statement.step(); 71 | } 72 | } catch { 73 | remains = it.getRemainingSQL(); 74 | } 75 | assert.equal(remains, "garbage in, garbage out", "Remaining SQL accessible after exception"); 76 | 77 | // From the doc example on the iterateStatements method 78 | const results = []; 79 | const sql_queries = "SELECT 1 AS x; SELECT '2' as y"; 80 | for (const statement of db.iterateStatements(sql_queries)) { 81 | const sql = statement.getSQL(); 82 | const result = statement.getAsObject({}); 83 | results.push({ sql, result }); 84 | } 85 | assert.deepEqual(results, [ 86 | { sql: 'SELECT 1 AS x;', result: { x: 1 } }, 87 | { sql: " SELECT '2' as y", result: { y: '2' } } 88 | ], "The code example from the documentation works"); 89 | }; 90 | 91 | if (module == require.main) { 92 | const target_file = process.argv[2]; 93 | const sql_loader = require('./load_sql_lib'); 94 | sql_loader(target_file).then((sql) => { 95 | require('test').run({ 96 | 'test statement iterator': function (assert) { 97 | exports.test(sql, assert); 98 | } 99 | }); 100 | }) 101 | .catch((e) => { 102 | console.error(e); 103 | assert.fail(e); 104 | }); 105 | } 106 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Create a release 8 | 9 | jobs: 10 | build: 11 | name: Create a release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: mymindstorm/setup-emsdk@ca33dc66a6b178f65393989c12e9465baf053352 16 | with: {version: '2.0.6'} 17 | - name: make 18 | run: make 19 | - name: Create Release 20 | id: create_release 21 | uses: actions/create-release@v1.0.0 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | tag_name: ${{ github.ref }} 26 | release_name: Release ${{ github.ref }} 27 | draft: false 28 | prerelease: false 29 | - run: cd dist && zip sqljs-wasm.zip sql-wasm.{js,wasm} 30 | - name: Upload Release Asset (wasm) 31 | uses: lovasoa/upload-release-asset@851d9cc59fe8113912edffbd8fddaa09470a5ac0 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | with: 35 | upload_url: ${{ steps.create_release.outputs.upload_url }} 36 | asset_path: dist/sqljs-wasm.zip 37 | asset_name: sqljs-wasm.zip 38 | asset_label: wasm version, best runtime performance, smaller assets, requires configuration 39 | asset_content_type: application/zip 40 | - name: Upload Release Asset (asm) 41 | uses: lovasoa/upload-release-asset@851d9cc59fe8113912edffbd8fddaa09470a5ac0 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | upload_url: ${{ steps.create_release.outputs.upload_url }} 46 | asset_path: dist/sql-asm.js 47 | asset_name: sql.js 48 | asset_label: asm.js version, slower, easy to integrate and compatible with old browsers 49 | asset_content_type: text/javascript 50 | - run: cd dist && zip sqljs-worker-wasm.zip worker.sql-wasm.js sql-wasm.wasm 51 | - name: Upload Release Asset (worker wasm) 52 | uses: lovasoa/upload-release-asset@851d9cc59fe8113912edffbd8fddaa09470a5ac0 53 | env: 54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | with: 56 | upload_url: ${{ steps.create_release.outputs.upload_url }} 57 | asset_path: dist/sqljs-worker-wasm.zip 58 | asset_name: sqljs-worker-wasm.zip 59 | asset_label: webworker wasm version, to be loaded as a web worker 60 | asset_content_type: application/zip 61 | - name: Upload Release Asset (worker asm) 62 | uses: lovasoa/upload-release-asset@851d9cc59fe8113912edffbd8fddaa09470a5ac0 63 | env: 64 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | with: 66 | upload_url: ${{ steps.create_release.outputs.upload_url }} 67 | asset_path: dist/worker.sql-asm.js 68 | asset_name: worker.sql-asm.js 69 | asset_label: webworker asm version, to be loaded as a web worker 70 | asset_content_type: text/javascript 71 | - run: cd dist && zip sqljs-all.zip *.{js,wasm} 72 | - name: Upload Release Asset (all) 73 | uses: lovasoa/upload-release-asset@851d9cc59fe8113912edffbd8fddaa09470a5ac0 74 | env: 75 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 76 | with: 77 | upload_url: ${{ steps.create_release.outputs.upload_url }} 78 | asset_path: dist/sqljs-all.zip 79 | asset_name: sqljs-all.zip 80 | asset_label: all versions, including non-minified javascript 81 | asset_content_type: application/zip 82 | - name: publish the package to NPM 83 | run: | 84 | npm config set '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}" 85 | npm publish 86 | env: 87 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 88 | -------------------------------------------------------------------------------- /test/test_statement.js: -------------------------------------------------------------------------------- 1 | 2 | exports.test = function(sql, assert){ 3 | // Create a database 4 | var db = new sql.Database(); 5 | 6 | // Execute some sql 7 | sqlstr = "CREATE TABLE alphabet (letter, code);"; 8 | db.exec(sqlstr); 9 | 10 | var result = db.exec("SELECT name FROM sqlite_master WHERE type='table'"); 11 | assert.deepEqual(result, [{columns:['name'], values:[['alphabet']]}], 12 | "Table properly created"); 13 | 14 | // Prepare a statement to insert values in tha database 15 | var stmt = db.prepare("INSERT INTO alphabet (letter,code) VALUES (?,?)"); 16 | // Execute the statement several times 17 | stmt.run(['a',1]); 18 | stmt.run(['b',2.2]); 19 | stmt.run(['c']); // The second parameter will be bound to NULL 20 | 21 | // Free the statement 22 | stmt.free(); 23 | 24 | result = db.exec("SELECT * FROM alphabet"); 25 | assert.deepEqual(result, 26 | [{columns:['letter', 'code'], values:[['a',1],['b',2.2],['c',null]]}], 27 | "Statement.run() should have added data to the database"); 28 | 29 | db.run("CREATE TABLE data (nbr, str, no_value); INSERT INTO data VALUES (5, '粵語😄', NULL);"); 30 | stmt = db.prepare("SELECT * FROM data"); 31 | stmt.step(); // Run the statement 32 | assert.deepEqual(stmt.getColumnNames(), ['nbr','str','no_value'], 'Statement.GetColumnNames()'); 33 | var res = stmt.getAsObject(); 34 | assert.strictEqual(res.nbr, 5, 'Read number'); 35 | assert.strictEqual(res.str, '粵語😄', "Read string"); 36 | assert.strictEqual(res.no_value, null, "Read null"); 37 | assert.deepEqual(res, {nbr:5, str:'粵語😄', no_value:null}, "Statement.getAsObject()"); 38 | stmt.free(); 39 | 40 | // getColumnNames() should work even if query returns no data 41 | stmt = db.prepare("SELECT * FROM data WHERE nbr = -1"); 42 | assert.deepEqual(stmt.getColumnNames(), ['nbr','str','no_value'], 'Statement.GetColumnNames()'); 43 | stmt.free(); 44 | 45 | stmt = db.prepare("SELECT str FROM data WHERE str=?"); 46 | assert.deepEqual(stmt.getAsObject(['粵語😄']), {'str':'粵語😄'}, "UTF8 support in prepared statements"); 47 | 48 | // Prepare an sql statement 49 | stmt = db.prepare("SELECT * FROM alphabet WHERE code BETWEEN :start AND :end ORDER BY code"); 50 | // Bind values to the parameters 51 | stmt.bind([0, 256]); 52 | // Execute the statement 53 | stmt.step(); 54 | // Get one row of result 55 | result = stmt.get(); 56 | assert.deepEqual(result, ['a',1], "Binding named parameters by their position"); 57 | 58 | // Fetch the next row of result 59 | result = stmt.step(); 60 | assert.equal(result, true); 61 | result = stmt.get(); 62 | assert.deepEqual(result, ['b',2.2], "Fetching the next row of result"); 63 | 64 | // Reset and reuse at once 65 | result = stmt.get([0, 1]); 66 | assert.deepEqual(result, ['a',1], "Reset and reuse at once"); 67 | 68 | // Pass objects to get() and bind() to use named parameters 69 | result = stmt.get({':start':1, ':end':1}); 70 | assert.deepEqual(result, ['a',1], "Binding named parameters"); 71 | 72 | // Prepare statement, pass null to bind() and check that it works 73 | stmt = db.prepare("SELECT 'bind-with-null'"); 74 | result = stmt.bind(null); 75 | assert.equal(result, true); 76 | stmt.step(); 77 | result = stmt.get(); 78 | assert.equal(result,"bind-with-null") 79 | 80 | // Close the database and all associated statements 81 | db.close(); 82 | }; 83 | 84 | if (module == require.main) { 85 | const target_file = process.argv[2]; 86 | const sql_loader = require('./load_sql_lib'); 87 | sql_loader(target_file).then((sql)=>{ 88 | require('test').run({ 89 | 'test statement': function(assert){ 90 | exports.test(sql, assert); 91 | } 92 | }); 93 | }) 94 | .catch((e)=>{ 95 | console.error(e); 96 | assert.fail(e); 97 | }); 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/shell-pre.js: -------------------------------------------------------------------------------- 1 | 2 | // We are modularizing this manually because the current modularize setting in Emscripten has some issues: 3 | // https://github.com/kripken/emscripten/issues/5820 4 | // In addition, When you use emcc's modularization, it still expects to export a global object called `Module`, 5 | // which is able to be used/called before the WASM is loaded. 6 | // The modularization below exports a promise that loads and resolves to the actual sql.js module. 7 | // That way, this module can't be used before the WASM is finished loading. 8 | 9 | // We are going to define a function that a user will call to start loading initializing our Sql.js library 10 | // However, that function might be called multiple times, and on subsequent calls, we don't actually want it to instantiate a new instance of the Module 11 | // Instead, we want to return the previously loaded module 12 | 13 | // TODO: Make this not declare a global if used in the browser 14 | var initSqlJsPromise = undefined; 15 | 16 | var initSqlJs = function (moduleConfig) { 17 | 18 | if (initSqlJsPromise){ 19 | return initSqlJsPromise; 20 | } 21 | // If we're here, we've never called this function before 22 | initSqlJsPromise = new Promise(function (resolveModule, reject) { 23 | 24 | // We are modularizing this manually because the current modularize setting in Emscripten has some issues: 25 | // https://github.com/kripken/emscripten/issues/5820 26 | 27 | // The way to affect the loading of emcc compiled modules is to create a variable called `Module` and add 28 | // properties to it, like `preRun`, `postRun`, etc 29 | // We are using that to get notified when the WASM has finished loading. 30 | // Only then will we return our promise 31 | 32 | // If they passed in a moduleConfig object, use that 33 | // Otherwise, initialize Module to the empty object 34 | var Module = typeof moduleConfig !== 'undefined' ? moduleConfig : {}; 35 | 36 | // EMCC only allows for a single onAbort function (not an array of functions) 37 | // So if the user defined their own onAbort function, we remember it and call it 38 | var originalOnAbortFunction = Module['onAbort']; 39 | Module['onAbort'] = function (errorThatCausedAbort) { 40 | reject(new Error(errorThatCausedAbort)); 41 | if (originalOnAbortFunction){ 42 | originalOnAbortFunction(errorThatCausedAbort); 43 | } 44 | }; 45 | 46 | Module['postRun'] = Module['postRun'] || []; 47 | Module['postRun'].push(function () { 48 | // When Emscripted calls postRun, this promise resolves with the built Module 49 | resolveModule(Module); 50 | }); 51 | 52 | // There is a section of code in the emcc-generated code below that looks like this: 53 | // (Note that this is lowercase `module`) 54 | // if (typeof module !== 'undefined') { 55 | // module['exports'] = Module; 56 | // } 57 | // When that runs, it's going to overwrite our own modularization export efforts in shell-post.js! 58 | // The only way to tell emcc not to emit it is to pass the MODULARIZE=1 or MODULARIZE_INSTANCE=1 flags, 59 | // but that carries with it additional unnecessary baggage/bugs we don't want either. 60 | // So, we have three options: 61 | // 1) We undefine `module` 62 | // 2) We remember what `module['exports']` was at the beginning of this function and we restore it later 63 | // 3) We write a script to remove those lines of code as part of the Make process. 64 | // 65 | // Since those are the only lines of code that care about module, we will undefine it. It's the most straightforward 66 | // of the options, and has the side effect of reducing emcc's efforts to modify the module if its output were to change in the future. 67 | // That's a nice side effect since we're handling the modularization efforts ourselves 68 | module = undefined; 69 | 70 | // The emcc-generated code and shell-post.js code goes below, 71 | // meaning that all of it runs inside of this promise. If anything throws an exception, our promise will abort 72 | -------------------------------------------------------------------------------- /examples/GUI/gui.js: -------------------------------------------------------------------------------- 1 | var execBtn = document.getElementById("execute"); 2 | var outputElm = document.getElementById('output'); 3 | var errorElm = document.getElementById('error'); 4 | var commandsElm = document.getElementById('commands'); 5 | var dbFileElm = document.getElementById('dbfile'); 6 | var savedbElm = document.getElementById('savedb'); 7 | 8 | // Start the worker in which sql.js will run 9 | var worker = new Worker("../../dist/worker.sql-wasm.js"); 10 | worker.onerror = error; 11 | 12 | // Open a database 13 | worker.postMessage({ action: 'open' }); 14 | 15 | // Connect to the HTML element we 'print' to 16 | function print(text) { 17 | outputElm.innerHTML = text.replace(/\n/g, '
    '); 18 | } 19 | function error(e) { 20 | console.log(e); 21 | errorElm.style.height = '2em'; 22 | errorElm.textContent = e.message; 23 | } 24 | 25 | function noerror() { 26 | errorElm.style.height = '0'; 27 | } 28 | 29 | // Run a command in the database 30 | function execute(commands) { 31 | tic(); 32 | worker.onmessage = function (event) { 33 | var results = event.data.results; 34 | toc("Executing SQL"); 35 | if (!results) { 36 | error({message: event.data.error}); 37 | return; 38 | } 39 | 40 | tic(); 41 | outputElm.innerHTML = ""; 42 | for (var i = 0; i < results.length; i++) { 43 | outputElm.appendChild(tableCreate(results[i].columns, results[i].values)); 44 | } 45 | toc("Displaying results"); 46 | } 47 | worker.postMessage({ action: 'exec', sql: commands }); 48 | outputElm.textContent = "Fetching results..."; 49 | } 50 | 51 | // Create an HTML table 52 | var tableCreate = function () { 53 | function valconcat(vals, tagName) { 54 | if (vals.length === 0) return ''; 55 | var open = '<' + tagName + '>', close = ''; 56 | return open + vals.join(close + open) + close; 57 | } 58 | return function (columns, values) { 59 | var tbl = document.createElement('table'); 60 | var html = '' + valconcat(columns, 'th') + ''; 61 | var rows = values.map(function (v) { return valconcat(v, 'td'); }); 62 | html += '' + valconcat(rows, 'tr') + ''; 63 | tbl.innerHTML = html; 64 | return tbl; 65 | } 66 | }(); 67 | 68 | // Execute the commands when the button is clicked 69 | function execEditorContents() { 70 | noerror() 71 | execute(editor.getValue() + ';'); 72 | } 73 | execBtn.addEventListener("click", execEditorContents, true); 74 | 75 | // Performance measurement functions 76 | var tictime; 77 | if (!window.performance || !performance.now) { window.performance = { now: Date.now } } 78 | function tic() { tictime = performance.now() } 79 | function toc(msg) { 80 | var dt = performance.now() - tictime; 81 | console.log((msg || 'toc') + ": " + dt + "ms"); 82 | } 83 | 84 | // Add syntax highlihjting to the textarea 85 | var editor = CodeMirror.fromTextArea(commandsElm, { 86 | mode: 'text/x-mysql', 87 | viewportMargin: Infinity, 88 | indentWithTabs: true, 89 | smartIndent: true, 90 | lineNumbers: true, 91 | matchBrackets: true, 92 | autofocus: true, 93 | extraKeys: { 94 | "Ctrl-Enter": execEditorContents, 95 | "Ctrl-S": savedb, 96 | } 97 | }); 98 | 99 | // Load a db from a file 100 | dbFileElm.onchange = function () { 101 | var f = dbFileElm.files[0]; 102 | var r = new FileReader(); 103 | r.onload = function () { 104 | worker.onmessage = function () { 105 | toc("Loading database from file"); 106 | // Show the schema of the loaded database 107 | editor.setValue("SELECT `name`, `sql`\n FROM `sqlite_master`\n WHERE type='table';"); 108 | execEditorContents(); 109 | }; 110 | tic(); 111 | try { 112 | worker.postMessage({ action: 'open', buffer: r.result }, [r.result]); 113 | } 114 | catch (exception) { 115 | worker.postMessage({ action: 'open', buffer: r.result }); 116 | } 117 | } 118 | r.readAsArrayBuffer(f); 119 | } 120 | 121 | // Save the db to a file 122 | function savedb() { 123 | worker.onmessage = function (event) { 124 | toc("Exporting the database"); 125 | var arraybuff = event.data.buffer; 126 | var blob = new Blob([arraybuff]); 127 | var a = document.createElement("a"); 128 | document.body.appendChild(a); 129 | a.href = window.URL.createObjectURL(blob); 130 | a.download = "sql.db"; 131 | a.onclick = function () { 132 | setTimeout(function () { 133 | window.URL.revokeObjectURL(a.href); 134 | }, 1500); 135 | }; 136 | a.click(); 137 | }; 138 | tic(); 139 | worker.postMessage({ action: 'export' }); 140 | } 141 | savedbElm.addEventListener("click", savedb, true); 142 | -------------------------------------------------------------------------------- /test/test_worker.js: -------------------------------------------------------------------------------- 1 | // TODO: Instead of using puppeteer, we could use the new Node 11 workers via 2 | // node --experimental-worker test/all.js 3 | // Then we could do this: 4 | //const { Worker } = require('worker_threads'); 5 | // But it turns out that the worker_threads interface is just different enough not to work. 6 | var puppeteer = require("puppeteer"); 7 | var path = require("path"); 8 | var fs = require("fs"); 9 | 10 | class Worker { 11 | constructor(handle) { 12 | this.handle = handle; 13 | } 14 | static async fromFile(file) { 15 | const browser = await puppeteer.launch(); 16 | const page = await browser.newPage(); 17 | const source = fs.readFileSync(file, 'utf8'); 18 | const worker = await page.evaluateHandle(x => { 19 | const url = URL.createObjectURL(new Blob([x]), { type: 'application/javascript; charset=utf-8' }); 20 | return new Worker(url); 21 | }, source); 22 | return new Worker(worker); 23 | } 24 | async postMessage(msg) { 25 | return await this.handle.evaluate((worker, msg) => { 26 | return new Promise((accept, reject) => { 27 | setTimeout(reject, 20000, new Error("time out")); 28 | worker.onmessage = evt => accept(evt.data); 29 | worker.onerror = reject; 30 | worker.postMessage(msg); 31 | }) 32 | }, msg); 33 | } 34 | } 35 | 36 | exports.test = async function test(SQL, assert) { 37 | var target = process.argv[2]; 38 | var file = target ? "sql-" + target : "sql-wasm"; 39 | if (file.indexOf('wasm') > -1 || file.indexOf('memory-growth') > -1) { 40 | console.error("Skipping worker test for " + file + ". Not implemented yet"); 41 | return; 42 | }; 43 | // If we use puppeteer, we need to pass in this new cwd as the root of the file being loaded: 44 | const filename = "../dist/worker." + file + ".js"; 45 | var worker = await Worker.fromFile(path.join(__dirname, filename)); 46 | var data = await worker.postMessage({ id: 1, action: 'open' }); 47 | assert.strictEqual(data.id, 1, "Return the given id in the correct format"); 48 | assert.deepEqual(data, { id: 1, ready: true }, 'Correct data answered to the "open" query'); 49 | 50 | data = await worker.postMessage({ 51 | id: 2, 52 | action: 'exec', 53 | params: { 54 | ":num2": 2, 55 | "@str2": 'b', 56 | // test_worker.js has issue message-passing Uint8Array 57 | // but it works fine in real-world browser-usage 58 | // "$hex2": new Uint8Array([0x00, 0x42]), 59 | ":num3": 3, 60 | "@str3": 'c' 61 | // "$hex3": new Uint8Array([0x00, 0x44]) 62 | }, 63 | sql: "CREATE TABLE test (num, str, hex);" + 64 | "INSERT INTO test VALUES (1, 'a', x'0042');" + 65 | "INSERT INTO test VALUES (:num2, @str2, x'0043');" + 66 | // test passing params split across multi-statement "exec" 67 | "INSERT INTO test VALUES (:num3, @str3, x'0044');" + 68 | "SELECT * FROM test;" 69 | }); 70 | assert.strictEqual(data.id, 2, "Correct id"); 71 | // debug error 72 | assert.strictEqual(data.error, undefined, data.error); 73 | var results = data.results; 74 | assert.ok(Array.isArray(results), 'Correct result type'); 75 | assert.strictEqual(results.length, 1, 'Expected exactly 1 table'); 76 | var table = results[0]; 77 | assert.strictEqual(typeof table, 'object', 'Type of the returned table'); 78 | assert.deepEqual(table.columns, ['num', 'str', 'hex'], 'Reading column names'); 79 | assert.strictEqual(table.values[0][0], 1, 'Reading number'); 80 | assert.strictEqual(table.values[0][1], 'a', 'Reading string'); 81 | assert.deepEqual(obj2array(table.values[0][2]), [0x00, 0x42], 'Reading BLOB byte'); 82 | assert.strictEqual(table.values[1][0], 2, 'Reading number'); 83 | assert.strictEqual(table.values[1][1], 'b', 'Reading string'); 84 | assert.deepEqual(obj2array(table.values[1][2]), [0x00, 0x43], 'Reading BLOB byte'); 85 | assert.strictEqual(table.values[2][0], 3, 'Reading number'); 86 | assert.strictEqual(table.values[2][1], 'c', 'Reading string'); 87 | assert.deepEqual(obj2array(table.values[2][2]), [0x00, 0x44], 'Reading BLOB byte'); 88 | 89 | data = await worker.postMessage({ action: 'export' }); 90 | var header = "SQLite format 3\0"; 91 | var actual = ""; 92 | for (let i = 0; i < header.length; i++) actual += String.fromCharCode(data.buffer[i]); 93 | assert.equal(actual, header, 'Data returned is an SQLite database file'); 94 | 95 | // test worker properly opens db after closing 96 | await worker.postMessage({ action: "close" }); 97 | await worker.postMessage({ action: "open" }); 98 | data = await worker.postMessage({ action: "exec", sql: "SELECT 1" }); 99 | assert.deepEqual(data.results, [{"columns":["1"],"values":[[1]]}]); 100 | } 101 | 102 | function obj2array(obj) { 103 | var buffer = [] 104 | for (var p in obj) { buffer[p] = obj[p] } 105 | return buffer; 106 | } 107 | 108 | if (module == require.main) { 109 | process.on('unhandledRejection', r => console.log(r)); 110 | 111 | require('test').run({ 112 | 'test worker': function (assert, done) { 113 | exports.test(null, assert).then(done); 114 | } 115 | }); 116 | 117 | } 118 | -------------------------------------------------------------------------------- /test/test_json1.js: -------------------------------------------------------------------------------- 1 | exports.test = function(sql, assert) { 2 | var db = new sql.Database(); 3 | // tests taken from https://www.sqlite.org/json1.html#jmini 4 | [ 5 | // The json() function 6 | `json(' { "this" : "is", "a": [ "test" ] } ') = '{"this":"is","a":["test"]}'`, 7 | 8 | // The json_array_length() function 9 | `json_array(1,2,'3',4) = '[1,2,"3",4]'`, 10 | `json_array('[1,2]') = '["[1,2]"]'`, 11 | `json_array(json_array(1,2)) = '[[1,2]]'`, 12 | `json_array(1,null,'3','[4,5]','{"six":7.7}') = '[1,null,"3","[4,5]","{\\"six\\":7.7}"]'`, 13 | `json_array(1,null,'3',json('[4,5]'),json('{"six":7.7}')) = '[1,null,"3",[4,5],{"six":7.7}]'`, 14 | `json_array_length('[1,2,3,4]') = 4`, 15 | `json_array_length('[1,2,3,4]', '$') = 4`, 16 | `json_array_length('[1,2,3,4]', '$[2]') = 0`, 17 | `json_array_length('{"one":[1,2,3]}') = 0`, 18 | `json_array_length('{"one":[1,2,3]}', '$.one') = 3`, 19 | `json_array_length('{"one":[1,2,3]}', '$.two') = null`, 20 | 21 | // The json_extract() function 22 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$') = '{"a":2,"c":[4,5,{"f":7}]}'`, 23 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c') = '[4,5,{"f":7}]'`, 24 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2]') = '{"f":7}'`, 25 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.c[2].f') = 7`, 26 | `json_extract('{"a":2,"c":[4,5],"f":7}','$.c','$.a') = '[[4,5],2]'`, 27 | `json_extract('{"a":2,"c":[4,5],"f":7}','$.c[#-1]') = 5`, 28 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x') = null`, 29 | `json_extract('{"a":2,"c":[4,5,{"f":7}]}', '$.x', '$.a') = '[null,2]'`, 30 | 31 | // The json_insert(), json_replace, and json_set() functions 32 | `json_insert('[1,2,3,4]','$[#]',99) = '[1,2,3,4,99]'`, 33 | `json_insert('[1,[2,3],4]','$[1][#]',99) = '[1,[2,3,99],4]'`, 34 | `json_insert('{"a":2,"c":4}', '$.a', 99) = '{"a":2,"c":4}'`, 35 | `json_insert('{"a":2,"c":4}', '$.e', 99) = '{"a":2,"c":4,"e":99}'`, 36 | `json_replace('{"a":2,"c":4}', '$.a', 99) = '{"a":99,"c":4}'`, 37 | `json_replace('{"a":2,"c":4}', '$.e', 99) = '{"a":2,"c":4}'`, 38 | `json_set('{"a":2,"c":4}', '$.a', 99) = '{"a":99,"c":4}'`, 39 | `json_set('{"a":2,"c":4}', '$.e', 99) = '{"a":2,"c":4,"e":99}'`, 40 | `json_set('{"a":2,"c":4}', '$.c', '[97,96]') = '{"a":2,"c":"[97,96]"}'`, 41 | `json_set('{"a":2,"c":4}', '$.c', json('[97,96]')) = '{"a":2,"c":[97,96]}'`, 42 | `json_set('{"a":2,"c":4}', '$.c', json_array(97,96)) = '{"a":2,"c":[97,96]}'`, 43 | 44 | // The json_object() function 45 | `json_object('a',2,'c',4) = '{"a":2,"c":4}'`, 46 | `json_object('a',2,'c','{e:5}') = '{"a":2,"c":"{e:5}"}'`, 47 | `json_object('a',2,'c',json_object('e',5)) = '{"a":2,"c":{"e":5}}'`, 48 | 49 | // The json_patch() function 50 | `json_patch('{"a":1,"b":2}','{"c":3,"d":4}') = '{"a":1,"b":2,"c":3,"d":4}'`, 51 | `json_patch('{"a":[1,2],"b":2}','{"a":9}') = '{"a":9,"b":2}'`, 52 | `json_patch('{"a":[1,2],"b":2}','{"a":null}') = '{"b":2}'`, 53 | `json_patch('{"a":1,"b":2}','{"a":9,"b":null,"c":8}') = '{"a":9,"c":8}'`, 54 | `json_patch('{"a":{"x":1,"y":2},"b":3}','{"a":{"y":9},"c":8}') = '{"a":{"x":1,"y":9},"b":3,"c":8}'`, 55 | 56 | // The json_remove() function 57 | `json_remove('[0,1,2,3,4]','$[2]') = '[0,1,3,4]'`, 58 | `json_remove('[0,1,2,3,4]','$[2]','$[0]') = '[1,3,4]'`, 59 | `json_remove('[0,1,2,3,4]','$[0]','$[2]') = '[1,2,4]'`, 60 | `json_remove('[0,1,2,3,4]','$[#-1]','$[0]') = '[1,2,3]'`, 61 | `json_remove('{"x":25,"y":42}') = '{"x":25,"y":42}'`, 62 | `json_remove('{"x":25,"y":42}','$.z') = '{"x":25,"y":42}'`, 63 | `json_remove('{"x":25,"y":42}','$.y') = '{"x":25}'`, 64 | `json_remove('{"x":25,"y":42}','$') = null`, 65 | 66 | // The json_type() function 67 | `json_type('{"a":[2,3.5,true,false,null,"x"]}') = 'object'`, 68 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$') = 'object'`, 69 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a') = 'array'`, 70 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[0]') = 'integer'`, 71 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[1]') = 'real'`, 72 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[2]') = 'true'`, 73 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[3]') = 'false'`, 74 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[4]') = 'null'`, 75 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[5]') = 'text'`, 76 | `json_type('{"a":[2,3.5,true,false,null,"x"]}','$.a[6]') = null`, 77 | 78 | // The json_valid() function 79 | `json_valid('{"x":35}') = 1`, 80 | `json_valid('{"x":35') = 0`, 81 | 82 | // The json_quote() function 83 | `json_quote(3.14159) = 3.14159`, 84 | `json_quote('verdant') = "verdant"` 85 | ].forEach(function (sql) { 86 | assert.equal( 87 | String(db.exec( 88 | "SELECT " + sql.split(" = ")[0] + " AS val;" 89 | )[0].values[0][0]), 90 | String(sql.split(" = ")[1].replace(/'/g, "")) 91 | ); 92 | }); 93 | }; 94 | 95 | if (module == require.main) { 96 | const target_file = process.argv[2]; 97 | const sql_loader = require('./load_sql_lib'); 98 | sql_loader(target_file).then((sql)=>{ 99 | require('test').run({ 100 | 'test extension functions': function(assert){ 101 | exports.test(sql, assert); 102 | } 103 | }); 104 | }) 105 | .catch((e)=>{ 106 | console.error(e); 107 | assert.fail(e); 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /test/test_functions.js: -------------------------------------------------------------------------------- 1 | exports.test = function(SQL, assert){ 2 | var db = new SQL.Database(); 3 | db.exec("CREATE TABLE test (data); INSERT INTO test VALUES ('Hello World');"); 4 | 5 | // Simple function, appends extra text on a string. 6 | function test_function(string_arg) { 7 | return "Function called with: " + string_arg; 8 | }; 9 | 10 | // Register with SQLite. 11 | db.create_function("TestFunction", test_function); 12 | 13 | // Use in a query, check expected result. 14 | var result = db.exec("SELECT TestFunction(data) FROM test;"); 15 | var result_str = result[0]["values"][0][0]; 16 | assert.equal(result_str, "Function called with: Hello World", "Named functions can be registered"); 17 | 18 | // 2 arg function, adds two ints together. 19 | db.exec("CREATE TABLE test2 (int1, int2); INSERT INTO test2 VALUES (456, 789);"); 20 | 21 | function test_add(int1, int2) { 22 | return int1 + int2; 23 | }; 24 | 25 | db.create_function("TestAdd", test_add); 26 | result = db.exec("SELECT TestAdd(int1, int2) FROM test2;"); 27 | result_int = result[0]["values"][0][0]; 28 | assert.equal(result_int, 1245, "Multiple argument functions can be registered"); 29 | 30 | // Binary data function, tests which byte in a column is set to 0 31 | db.exec("CREATE TABLE test3 (data); INSERT INTO test3 VALUES (x'6100ff'), (x'ffffff00ffff');"); 32 | 33 | function test_zero_byte_index(data) { 34 | // Data is a Uint8Array 35 | for (var i=0; i val === b[i]); 107 | } 108 | 109 | [ 110 | {info:"null",value:null}, // sqlite special value null 111 | {info:"false",value:false,sql_value:"0",equal:numberEqual}, // sqlite special value (==false) 112 | {info:"true", value:true,sql_value:"1",equal:numberEqual}, // sqlite special value (==true) 113 | {info:"integer 0",value:0}, // sqlite special value (==false) 114 | {info:"integer 1",value:1}, // sqlite special value (==true) 115 | {info:"integer -1",value:-1}, 116 | {info:"long integer 5e+9",value:5000000000}, // int64 117 | {info:"long integer -5e+9",value:-5000000000}, // negative int64 118 | {info:"double",value:0.5}, 119 | {info:"string",value:"Test",sql_value:"'Test'"}, 120 | {info:"empty string",value:"",sql_value:"''"}, 121 | {info:"unicode string",value:"\uC7B8",sql_value:"CAST(x'EC9EB8' AS TEXT)"}, // unicode-hex: C7B8 utf8-hex: EC9EB8 122 | {info:"blob",value:new Uint8Array([0xC7,0xB8]),sql_value:"x'C7B8'",equal:blobEqual}, 123 | {info:"empty blob",value:new Uint8Array([]),sql_value:"x''",equal:blobEqual} 124 | ].forEach(function(testData) 125 | { 126 | assert.ok(canHandle(testData),"Can handle "+testData.info); 127 | }); 128 | 129 | db.create_function("throwFunction", function () { throw "internal exception"; return 5;} ); 130 | assert.throws(function(){db.exec("SELECT throwFunction()");},/internal exception/, "Can handle internal exceptions"); 131 | 132 | db.create_function("customeObjectFunction", function () { return {test:123};} ); 133 | assert.throws(function(){db.exec("SELECT customeObjectFunction()");},/Wrong API use/, "Reports wrong API use"); 134 | 135 | db.close(); 136 | }; 137 | 138 | if (module == require.main) { 139 | const target_file = process.argv[2]; 140 | const sql_loader = require('./load_sql_lib'); 141 | sql_loader(target_file).then((sql)=>{ 142 | require('test').run({ 143 | 'test functions': function(assert, done){ 144 | exports.test(sql, assert, done); 145 | } 146 | }); 147 | }) 148 | .catch((e)=>{ 149 | console.error(e); 150 | assert.fail(e); 151 | }); 152 | } 153 | 154 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Note: Last built with version 1.38.30 of Emscripten 2 | 3 | # TODO: Emit a file showing which version of emcc and SQLite was used to compile the emitted output. 4 | # TODO: Create a release on Github with these compiled assets rather than checking them in 5 | # TODO: Consider creating different files based on browser vs module usage: https://github.com/vuejs/vue/tree/dev/dist 6 | 7 | # I got this handy makefile syntax from : https://github.com/mandel59/sqlite-wasm (MIT License) Credited in LICENSE 8 | # To use another version of Sqlite, visit https://www.sqlite.org/download.html and copy the appropriate values here: 9 | SQLITE_AMALGAMATION = sqlite-amalgamation-3350000 10 | SQLITE_AMALGAMATION_ZIP_URL = https://www.sqlite.org/2021/sqlite-amalgamation-3350000.zip 11 | SQLITE_AMALGAMATION_ZIP_SHA1 = ba64bad885c9f51df765a9624700747e7bf21b79 12 | 13 | # Note that extension-functions.c hasn't been updated since 2010-02-06, so likely doesn't need to be updated 14 | EXTENSION_FUNCTIONS = extension-functions.c 15 | EXTENSION_FUNCTIONS_URL = https://www.sqlite.org/contrib/download/extension-functions.c?get=25 16 | EXTENSION_FUNCTIONS_SHA1 = c68fa706d6d9ff98608044c00212473f9c14892f 17 | 18 | EMCC=emcc 19 | 20 | CFLAGS = \ 21 | -O2 \ 22 | -DSQLITE_OMIT_LOAD_EXTENSION \ 23 | -DSQLITE_DISABLE_LFS \ 24 | -DSQLITE_ENABLE_FTS3 \ 25 | -DSQLITE_ENABLE_FTS3_PARENTHESIS \ 26 | -DSQLITE_ENABLE_FTS5 \ 27 | -DSQLITE_ENABLE_JSON1 \ 28 | -DSQLITE_THREADSAFE=0 \ 29 | -DSQLITE_ENABLE_NORMALIZE 30 | 31 | # When compiling to WASM, enabling memory-growth is not expected to make much of an impact, so we enable it for all builds 32 | # Since tihs is a library and not a standalone executable, we don't want to catch unhandled Node process exceptions 33 | # So, we do : `NODEJS_CATCH_EXIT=0`, which fixes issue: https://github.com/sql-js/sql.js/issues/173 and https://github.com/sql-js/sql.js/issues/262 34 | EMFLAGS = \ 35 | --memory-init-file 0 \ 36 | -s RESERVED_FUNCTION_POINTERS=64 \ 37 | -s ALLOW_TABLE_GROWTH=1 \ 38 | -s EXPORTED_FUNCTIONS=@src/exported_functions.json \ 39 | -s EXTRA_EXPORTED_RUNTIME_METHODS=@src/exported_runtime_methods.json \ 40 | -s SINGLE_FILE=0 \ 41 | -s NODEJS_CATCH_EXIT=0 \ 42 | -s NODEJS_CATCH_REJECTION=0 43 | 44 | # -s ASYNCIFY=1 \ 45 | # -s ASYNCIFY_IMPORTS='["sqlite3VdbeExec"]' 46 | EMFLAGS_ASM = \ 47 | -s WASM=0 48 | 49 | EMFLAGS_ASM_MEMORY_GROWTH = \ 50 | -s WASM=0 \ 51 | -s ALLOW_MEMORY_GROWTH=1 52 | 53 | EMFLAGS_WASM = \ 54 | -s WASM=1 \ 55 | -s ALLOW_MEMORY_GROWTH=1 56 | 57 | EMFLAGS_OPTIMIZED= \ 58 | -s INLINING_LIMIT=50 \ 59 | -O3 \ 60 | -flto 61 | 62 | EMFLAGS_DEBUG = \ 63 | -s INLINING_LIMIT=10 \ 64 | -s ASSERTIONS=1 \ 65 | -s SAFE_HEAP=1 \ 66 | -O0 67 | 68 | BITCODE_FILES = out/sqlite3.bc out/extension-functions.bc 69 | 70 | OUTPUT_WRAPPER_FILES = src/shell-pre.js src/shell-post.js 71 | 72 | SOURCE_API_FILES = src/api.js 73 | 74 | EMFLAGS_PRE_JS_FILES = \ 75 | --pre-js src/api.js 76 | 77 | EXPORTED_METHODS_JSON_FILES = src/exported_functions.json src/exported_runtime_methods.json 78 | 79 | all: optimized debug worker 80 | 81 | .PHONY: debug 82 | debug: dist/sql-asm-debug.js dist/sql-wasm-debug.js 83 | 84 | dist/sql-asm-debug.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) $(SOURCE_API_FILES) $(EXPORTED_METHODS_JSON_FILES) 85 | $(EMCC) $(EMFLAGS) $(EMFLAGS_DEBUG) $(EMFLAGS_ASM) $(BITCODE_FILES) $(EMFLAGS_PRE_JS_FILES) -o $@ 86 | mv $@ out/tmp-raw.js 87 | cat src/shell-pre.js out/tmp-raw.js src/shell-post.js > $@ 88 | rm out/tmp-raw.js 89 | 90 | dist/sql-wasm-debug.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) $(SOURCE_API_FILES) $(EXPORTED_METHODS_JSON_FILES) 91 | $(EMCC) $(EMFLAGS) $(EMFLAGS_DEBUG) $(EMFLAGS_WASM) $(BITCODE_FILES) $(EMFLAGS_PRE_JS_FILES) -o $@ 92 | mv $@ out/tmp-raw.js 93 | cat src/shell-pre.js out/tmp-raw.js src/shell-post.js > $@ 94 | rm out/tmp-raw.js 95 | 96 | .PHONY: optimized 97 | optimized: dist/sql-asm.js dist/sql-wasm.js dist/sql-asm-memory-growth.js 98 | 99 | dist/sql-asm.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) $(SOURCE_API_FILES) $(EXPORTED_METHODS_JSON_FILES) 100 | $(EMCC) $(EMFLAGS) $(EMFLAGS_OPTIMIZED) $(EMFLAGS_ASM) $(BITCODE_FILES) $(EMFLAGS_PRE_JS_FILES) -o $@ 101 | mv $@ out/tmp-raw.js 102 | cat src/shell-pre.js out/tmp-raw.js src/shell-post.js > $@ 103 | rm out/tmp-raw.js 104 | 105 | dist/sql-wasm.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) $(SOURCE_API_FILES) $(EXPORTED_METHODS_JSON_FILES) 106 | $(EMCC) $(EMFLAGS) $(EMFLAGS_OPTIMIZED) $(EMFLAGS_WASM) $(BITCODE_FILES) $(EMFLAGS_PRE_JS_FILES) -o $@ 107 | mv $@ out/tmp-raw.js 108 | cat src/shell-pre.js out/tmp-raw.js src/shell-post.js > $@ 109 | rm out/tmp-raw.js 110 | 111 | dist/sql-asm-memory-growth.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) $(SOURCE_API_FILES) $(EXPORTED_METHODS_JSON_FILES) 112 | $(EMCC) $(EMFLAGS) $(EMFLAGS_OPTIMIZED) $(EMFLAGS_ASM_MEMORY_GROWTH) $(BITCODE_FILES) $(EMFLAGS_PRE_JS_FILES) -o $@ 113 | mv $@ out/tmp-raw.js 114 | cat src/shell-pre.js out/tmp-raw.js src/shell-post.js > $@ 115 | rm out/tmp-raw.js 116 | 117 | # Web worker API 118 | .PHONY: worker 119 | worker: dist/worker.sql-asm.js dist/worker.sql-asm-debug.js dist/worker.sql-wasm.js dist/worker.sql-wasm-debug.js 120 | 121 | dist/worker.sql-asm.js: dist/sql-asm.js src/worker.js 122 | cat $^ > $@ 123 | 124 | dist/worker.sql-asm-debug.js: dist/sql-asm-debug.js src/worker.js 125 | cat $^ > $@ 126 | 127 | dist/worker.sql-wasm.js: dist/sql-wasm.js src/worker.js 128 | cat $^ > $@ 129 | 130 | dist/worker.sql-wasm-debug.js: dist/sql-wasm-debug.js src/worker.js 131 | cat $^ > $@ 132 | 133 | # Building it this way gets us a wrapper that _knows_ it's in worker mode, which is nice. 134 | # However, since we can't tell emcc that we don't need the wasm generated, and just want the wrapper, we have to pay to have the .wasm generated 135 | # even though we would have already generated it with our sql-wasm.js target above. 136 | # This would be made easier if this is implemented: https://github.com/emscripten-core/emscripten/issues/8506 137 | # dist/worker.sql-wasm.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) src/api.js src/worker.js $(EXPORTED_METHODS_JSON_FILES) dist/sql-wasm-debug.wasm 138 | # $(EMCC) $(EMFLAGS) $(EMFLAGS_OPTIMIZED) -s ENVIRONMENT=worker -s $(EMFLAGS_WASM) $(BITCODE_FILES) --pre-js src/api.js -o out/sql-wasm.js 139 | # mv out/sql-wasm.js out/tmp-raw.js 140 | # cat src/shell-pre.js out/tmp-raw.js src/shell-post.js src/worker.js > $@ 141 | # #mv out/sql-wasm.wasm dist/sql-wasm.wasm 142 | # rm out/tmp-raw.js 143 | 144 | # dist/worker.sql-wasm-debug.js: $(BITCODE_FILES) $(OUTPUT_WRAPPER_FILES) src/api.js src/worker.js $(EXPORTED_METHODS_JSON_FILES) dist/sql-wasm-debug.wasm 145 | # $(EMCC) -s ENVIRONMENT=worker $(EMFLAGS) $(EMFLAGS_DEBUG) -s ENVIRONMENT=worker -s WASM_BINARY_FILE=sql-wasm-foo.debug $(EMFLAGS_WASM) $(BITCODE_FILES) --pre-js src/api.js -o out/sql-wasm-debug.js 146 | # mv out/sql-wasm-debug.js out/tmp-raw.js 147 | # cat src/shell-pre.js out/tmp-raw.js src/shell-post.js src/worker.js > $@ 148 | # #mv out/sql-wasm-debug.wasm dist/sql-wasm-debug.wasm 149 | # rm out/tmp-raw.js 150 | 151 | out/sqlite3.bc: sqlite-src/$(SQLITE_AMALGAMATION) 152 | mkdir -p out 153 | # Generate llvm bitcode 154 | $(EMCC) $(CFLAGS) -c sqlite-src/$(SQLITE_AMALGAMATION)/sqlite3.c -o $@ 155 | 156 | # Since the extension-functions.c includes other headers in the sqlite_amalgamation, we declare that this depends on more than just extension-functions.c 157 | out/extension-functions.bc: sqlite-src/$(SQLITE_AMALGAMATION) 158 | mkdir -p out 159 | # Generate llvm bitcode 160 | $(EMCC) $(CFLAGS) -s LINKABLE=1 -c sqlite-src/$(SQLITE_AMALGAMATION)/extension-functions.c -o $@ 161 | 162 | # TODO: This target appears to be unused. If we re-instatate it, we'll need to add more files inside of the JS folder 163 | # module.tar.gz: test package.json AUTHORS README.md dist/sql-asm.js 164 | # tar --create --gzip $^ > $@ 165 | 166 | ## cache 167 | cache/$(SQLITE_AMALGAMATION).zip: 168 | mkdir -p cache 169 | curl -LsSf '$(SQLITE_AMALGAMATION_ZIP_URL)' -o $@ 170 | 171 | cache/$(EXTENSION_FUNCTIONS): 172 | mkdir -p cache 173 | curl -LsSf '$(EXTENSION_FUNCTIONS_URL)' -o $@ 174 | 175 | ## sqlite-src 176 | .PHONY: sqlite-src 177 | sqlite-src: sqlite-src/$(SQLITE_AMALGAMATION) sqlite-src/$(SQLITE_AMALGAMATION)/$(EXTENSION_FUNCTIONS) 178 | 179 | sqlite-src/$(SQLITE_AMALGAMATION): cache/$(SQLITE_AMALGAMATION).zip sqlite-src/$(SQLITE_AMALGAMATION)/$(EXTENSION_FUNCTIONS) 180 | mkdir -p sqlite-src/$(SQLITE_AMALGAMATION) 181 | echo '$(SQLITE_AMALGAMATION_ZIP_SHA1) ./cache/$(SQLITE_AMALGAMATION).zip' > cache/check.txt 182 | sha1sum -c cache/check.txt 183 | # We don't delete the sqlite_amalgamation folder. That's a job for clean 184 | # Also, the extension functions get copied here, and if we get the order of these steps wrong, 185 | # this step could remove the extension functions, and that's not what we want 186 | unzip -u 'cache/$(SQLITE_AMALGAMATION).zip' -d sqlite-src/ 187 | touch $@ 188 | 189 | sqlite-src/$(SQLITE_AMALGAMATION)/$(EXTENSION_FUNCTIONS): cache/$(EXTENSION_FUNCTIONS) 190 | mkdir -p sqlite-src/$(SQLITE_AMALGAMATION) 191 | echo '$(EXTENSION_FUNCTIONS_SHA1) ./cache/$(EXTENSION_FUNCTIONS)' > cache/check.txt 192 | sha1sum -c cache/check.txt 193 | cp 'cache/$(EXTENSION_FUNCTIONS)' $@ 194 | 195 | 196 | .PHONY: clean 197 | clean: 198 | rm -f out/* dist/* cache/* 199 | rm -rf sqlite-src/ 200 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # SQLite compiled to JavaScript 4 | 5 | [![CI status](https://github.com/sql-js/sql.js/workflows/CI/badge.svg)](https://github.com/sql-js/sql.js/actions) 6 | [![npm](https://img.shields.io/npm/v/sql.js)](https://www.npmjs.com/package/sql.js) 7 | [![CDNJS version](https://img.shields.io/cdnjs/v/sql.js.svg)](https://cdnjs.com/libraries/sql.js) 8 | 9 | *sql.js* is a javascript SQL database. It allows you to create a relational database and query it entirely in the browser. You can try it in [this online demo](https://sql.js.org/examples/GUI/). It uses a [virtual database file stored in memory](https://emscripten.org/docs/porting/files/file_systems_overview.html), and thus **doesn't persist the changes** made to the database. However, it allows you to **import** any existing sqlite file, and to **export** the created database as a [JavaScript typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays). 10 | 11 | *sql.js* uses [emscripten](https://emscripten.org/docs/introducing_emscripten/about_emscripten.html) to compile [SQLite](http://sqlite.org/about.html) to webassembly (or to javascript code for compatibility with older browsers). It includes [contributed math and string extension functions](https://www.sqlite.org/contrib?orderby=date). 12 | 13 | sql.js can be used like any traditional JavaScript library. If you are building a native application in JavaScript (using Electron for instance), or are working in node.js, you will likely prefer to use [a native binding of SQLite to JavaScript](https://www.npmjs.com/package/sqlite3). A native binding will not only be faster because it will run native code, but it will also be able to work on database files directly instead of having to load the entire database in memory, avoiding out of memory errors and further improving performances. 14 | 15 | SQLite is public domain, sql.js is MIT licensed. 16 | 17 | ## API documentation 18 | A [full API documentation](https://sql.js.org/documentation/) for all the available classes and methods is available. 19 | Is is generated from comments inside the source code, and is thus always up to date. 20 | 21 | ## Usage 22 | 23 | By default, *sql.js* uses [wasm](https://developer.mozilla.org/en-US/docs/WebAssembly), and thus needs to load a `.wasm` file in addition to the javascript library. You can find this file in `./node_modules/sql.js/dist/sql-wasm.wasm` after installing sql.js from npm, and instruct your bundler to add it to your static assets or load it from [a CDN](https://cdnjs.com/libraries/sql.js). Then use the [`locateFile`](https://emscripten.org/docs/api_reference/module.html#Module.locateFile) property of the configuration object passed to `initSqlJs` to indicate where the file is. If you use an asset builder such as webpack, you can automate this. See [this demo of how to integrate sql.js with webpack (and react)](https://github.com/sql-js/react-sqljs-demo). 24 | 25 | ```javascript 26 | const initSqlJs = require('sql.js'); 27 | // or if you are in a browser: 28 | // var initSqlJs = window.initSqlJs; 29 | 30 | const SQL = await initSqlJs({ 31 | // Required to load the wasm binary asynchronously. Of course, you can host it wherever you want 32 | // You can omit locateFile completely when running in node 33 | locateFile: file => `https://sql.js.org/dist/${file}` 34 | }); 35 | 36 | // Create a database 37 | var db = new SQL.Database(); 38 | // NOTE: You can also use new SQL.Database(data) where 39 | // data is an Uint8Array representing an SQLite database file 40 | 41 | // Prepare an sql statement 42 | var stmt = db.prepare("SELECT * FROM hello WHERE a=:aval AND b=:bval"); 43 | 44 | // Bind values to the parameters and fetch the results of the query 45 | var result = stmt.getAsObject({':aval' : 1, ':bval' : 'world'}); 46 | console.log(result); // Will print {a:1, b:'world'} 47 | 48 | // Bind other values 49 | stmt.bind([0, 'hello']); 50 | while (stmt.step()) console.log(stmt.get()); // Will print [0, 'hello'] 51 | // free the memory used by the statement 52 | stmt.free(); 53 | // You can not use your statement anymore once it has been freed. 54 | // But not freeing your statements causes memory leaks. You don't want that. 55 | 56 | // Execute a single SQL string that contains multiple statements 57 | sqlstr = "CREATE TABLE hello (a int, b char);"; 58 | sqlstr += "INSERT INTO hello VALUES (0, 'hello');" 59 | sqlstr += "INSERT INTO hello VALUES (1, 'world');" 60 | db.run(sqlstr); // Run the query without returning anything 61 | 62 | var res = db.exec("SELECT * FROM hello"); 63 | /* 64 | [ 65 | {columns:['a','b'], values:[[0,'hello'],[1,'world']]} 66 | ] 67 | */ 68 | 69 | // You can also use JavaScript functions inside your SQL code 70 | // Create the js function you need 71 | function add(a, b) {return a+b;} 72 | // Specifies the SQL function's name, the number of it's arguments, and the js function to use 73 | db.create_function("add_js", add); 74 | // Run a query in which the function is used 75 | db.run("INSERT INTO hello VALUES (add_js(7, 3), add_js('Hello ', 'world'));"); // Inserts 10 and 'Hello world' 76 | 77 | // Export the database to an Uint8Array containing the SQLite database file 78 | var binaryArray = db.export(); 79 | ``` 80 | 81 | ## Demo 82 | There are a few examples [available here](https://sql-js.github.io/sql.js/index.html). The most full-featured is the [Sqlite Interpreter](https://sql-js.github.io/sql.js/examples/GUI/index.html). 83 | 84 | ## Examples 85 | The test files provide up to date example of the use of the api. 86 | ### Inside the browser 87 | #### Example **HTML** file: 88 | ```html 89 | 90 | 91 | 92 | 118 | 119 | Output is in Javascript console 120 | 121 | 122 | ``` 123 | 124 | #### Creating a database from a file chosen by the user 125 | `SQL.Database` constructor takes an array of integer representing a database file as an optional parameter. 126 | The following code uses an HTML input as the source for loading a database: 127 | ```javascript 128 | dbFileElm.onchange = () => { 129 | var f = dbFileElm.files[0]; 130 | var r = new FileReader(); 131 | r.onload = function() { 132 | var Uints = new Uint8Array(r.result); 133 | db = new SQL.Database(Uints); 134 | } 135 | r.readAsArrayBuffer(f); 136 | } 137 | ``` 138 | See : https://sql-js.github.io/sql.js/examples/GUI/gui.js 139 | 140 | #### Loading a database from a server 141 | 142 | ##### using fetch 143 | 144 | ```javascript 145 | const sqlPromise = initSqlJs({ 146 | locateFile: file => `https://path/to/your/dist/folder/dist/${file}` 147 | }); 148 | const dataPromise = fetch("/path/to/databse.sqlite").then(res => res.arrayBuffer()); 149 | const [SQL, buf] = await Promise.all([sqlPromise, dataPromise]) 150 | const db = new SQL.Database(new Uint8Array(buf)); 151 | ``` 152 | 153 | ##### using XMLHttpRequest 154 | 155 | ```javascript 156 | var xhr = new XMLHttpRequest(); 157 | // For example: https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite 158 | xhr.open('GET', '/path/to/database.sqlite', true); 159 | xhr.responseType = 'arraybuffer'; 160 | 161 | xhr.onload = e => { 162 | var uInt8Array = new Uint8Array(xhr.response); 163 | var db = new SQL.Database(uInt8Array); 164 | var contents = db.exec("SELECT * FROM my_table"); 165 | // contents is now [{columns:['col1','col2',...], values:[[first row], [second row], ...]}] 166 | }; 167 | xhr.send(); 168 | ``` 169 | See: https://github.com/sql-js/sql.js/wiki/Load-a-database-from-the-server 170 | 171 | 172 | ### Use from node.js 173 | 174 | `sql.js` is [hosted on npm](https://www.npmjs.org/package/sql.js). To install it, you can simply run `npm install sql.js`. 175 | Alternatively, you can simply download `sql-wasm.js` and `sql-wasm.wasm`, from the download link below. 176 | 177 | #### read a database from the disk: 178 | ```javascript 179 | var fs = require('fs'); 180 | var initSqlJs = require('sql-wasm.js'); 181 | var filebuffer = fs.readFileSync('test.sqlite'); 182 | 183 | initSqlJs().then(function(SQL){ 184 | // Load the db 185 | var db = new SQL.Database(filebuffer); 186 | }); 187 | 188 | ``` 189 | 190 | #### write a database to the disk 191 | You need to convert the result of `db.export` to a buffer 192 | ```javascript 193 | var fs = require("fs"); 194 | // [...] (create the database) 195 | var data = db.export(); 196 | var buffer = new Buffer(data); 197 | fs.writeFileSync("filename.sqlite", buffer); 198 | ``` 199 | 200 | See : https://github.com/sql-js/sql.js/blob/master/test/test_node_file.js 201 | 202 | ### Use as web worker 203 | If you don't want to run CPU-intensive SQL queries in your main application thread, 204 | you can use the *more limited* WebWorker API. 205 | 206 | You will need to download [dist/worker.sql-wasm.js](dist/worker.sql-wasm.js) [dist/worker.sql-wasm.wasm](dist/worker.sql-wasm.wasm). 207 | 208 | Example: 209 | ```html 210 | 233 | ``` 234 | 235 | See [examples/GUI/gui.js](examples/GUI/gui.js) for a full working example. 236 | 237 | ## Flavors/versions Targets/Downloads 238 | 239 | This library includes both WebAssembly and asm.js versions of Sqlite. (WebAssembly is the newer, preferred way to compile to JavaScript, and has superceded asm.js. It produces smaller, faster code.) Asm.js versions are included for compatibility. 240 | 241 | ## Upgrading from 0.x to 1.x 242 | 243 | Version 1.0 of sql.js must be loaded asynchronously, whereas asm.js was able to be loaded synchronously. 244 | 245 | So in the past, you would: 246 | ```html 247 | 248 | 252 | ``` 253 | or: 254 | ```javascript 255 | var SQL = require('sql.js'); 256 | var db = new SQL.Database(); 257 | //... 258 | ``` 259 | 260 | Version 1.x: 261 | ```html 262 | 263 | 269 | ``` 270 | or: 271 | ```javascript 272 | var initSqlJs = require('sql-wasm.js'); 273 | initSqlJs().then(function(SQL){ 274 | var db = new SQL.Database(); 275 | //... 276 | }); 277 | ``` 278 | 279 | `NOTHING` is now a reserved word in SQLite, whereas previously it was not. This could cause errors like `Error: near "nothing": syntax error` 280 | 281 | ### Downloading/Using: ### 282 | Although asm.js files were distributed as a single Javascript file, WebAssembly libraries are most efficiently distributed as a pair of files, the `.js` loader and the `.wasm` file, like `sql-wasm.js` and `sql-wasm.wasm`. The `.js` file is responsible for loading the `.wasm` file. You can find these files on our [release page](https://github.com/sql-js/sql.js/releases) 283 | 284 | 285 | 286 | 287 | ## Versions of sql.js included in the distributed artifacts 288 | You can always find the latest published artifacts on https://github.com/sql-js/sql.js/releases/latest. 289 | 290 | For each [release](https://github.com/sql-js/sql.js/releases/), you will find a file called `sqljs.zip` in the *release assets*. It will contain: 291 | - `sql-wasm.js` : The Web Assembly version of Sql.js. Minified and suitable for production. Use this. If you use this, you will need to include/ship `sql-wasm.wasm` as well. 292 | - `sql-wasm-debug.js` : The Web Assembly, Debug version of Sql.js. Larger, with assertions turned on. Useful for local development. You will need to include/ship `sql-wasm-debug.wasm` if you use this. 293 | - `sql-asm.js` : The older asm.js version of Sql.js. Slower and larger. Provided for compatibility reasons. 294 | - `sql-asm-memory-growth.js` : Asm.js doesn't allow for memory to grow by default, because it is slower and de-optimizes. If you are using sql-asm.js and you see this error (`Cannot enlarge memory arrays`), use this file. 295 | - `sql-asm-debug.js` : The _Debug_ asm.js version of Sql.js. Use this for local development. 296 | - `worker.*` - Web Worker versions of the above libraries. More limited API. See [examples/GUI/gui.js](examples/GUI/gui.js) for a good example of this. 297 | 298 | ## Compiling 299 | 300 | - Install the EMSDK, [as described here](https://emscripten.org/docs/getting_started/downloads.html) 301 | - Run `npm run rebuild` 302 | 303 | In order to enable extensions like FTS5, change the CFLAGS in the [Makefile](Makefile) and rebuild: 304 | 305 | ``` diff 306 | CFLAGS = \ 307 | -O2 \ 308 | -DSQLITE_OMIT_LOAD_EXTENSION \ 309 | -DSQLITE_DISABLE_LFS \ 310 | -DSQLITE_ENABLE_FTS3 \ 311 | -DSQLITE_ENABLE_FTS3_PARENTHESIS \ 312 | + -DSQLITE_ENABLE_FTS5 \ 313 | -DSQLITE_ENABLE_JSON1 \ 314 | -DSQLITE_THREADSAFE=0 315 | ``` 316 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | /* global 2 | ALLOC_NORMAL 3 | FS 4 | HEAP8 5 | Module 6 | _malloc 7 | _free 8 | addFunction 9 | allocate 10 | allocateUTF8OnStack 11 | getValue 12 | intArrayFromString 13 | removeFunction 14 | setValue 15 | stackAlloc 16 | stackRestore 17 | stackSave 18 | UTF8ToString 19 | stringToUTF8 20 | lengthBytesUTF8 21 | */ 22 | 23 | "use strict"; 24 | 25 | /** 26 | * @typedef {{Database:Database, Statement:Statement}} SqlJs 27 | * @property {Database} Database A class that represents an SQLite database 28 | * @property {Statement} Statement The prepared statement class 29 | */ 30 | 31 | /** 32 | * @typedef {{locateFile:function(string):string}} SqlJsConfig 33 | * @property {function(string):string} locateFile 34 | * a function that returns the full path to a resource given its file name 35 | * @see https://emscripten.org/docs/api_reference/module.html 36 | */ 37 | 38 | /** 39 | * Asynchronously initializes sql.js 40 | * @function initSqlJs 41 | * @param {SqlJsConfig} config module inititialization parameters 42 | * @returns {SqlJs} 43 | * @example 44 | * initSqlJs({ 45 | * locateFile: name => '/path/to/assets/' + name 46 | * }).then(SQL => { 47 | * const db = new SQL.Database(); 48 | * const result = db.exec("select 'hello world'"); 49 | * console.log(result); 50 | * }) 51 | */ 52 | 53 | /** 54 | * @module SqlJs 55 | */ 56 | // Wait for preRun to run, and then finish our initialization 57 | Module["onRuntimeInitialized"] = function onRuntimeInitialized() { 58 | // Declare toplevel variables 59 | // register, used for temporary stack values 60 | var apiTemp = stackAlloc(4); 61 | var cwrap = Module["cwrap"]; 62 | // Null pointer 63 | var NULL = 0; 64 | // SQLite enum 65 | var SQLITE_OK = 0; 66 | var SQLITE_ROW = 100; 67 | var SQLITE_DONE = 101; 68 | var SQLITE_INTEGER = 1; 69 | var SQLITE_FLOAT = 2; 70 | var SQLITE_TEXT = 3; 71 | var SQLITE_BLOB = 4; 72 | // var - Encodings, used for registering functions. 73 | var SQLITE_UTF8 = 1; 74 | // var - cwrap function 75 | var sqlite3_open = cwrap("sqlite3_open", "number", ["string", "number"]); 76 | var sqlite3_open_v2 = cwrap("sqlite3_open_v2", "number", ["string", "number", "number", "string"]); 77 | var sqlite3_close_v2 = cwrap("sqlite3_close_v2", "number", ["number"]); 78 | var sqlite3_exec = cwrap( 79 | "sqlite3_exec", 80 | "number", 81 | ["number", "string", "number", "number", "number"] 82 | ); 83 | var sqlite3_changes = cwrap("sqlite3_changes", "number", ["number"]); 84 | var sqlite3_prepare_v2 = cwrap( 85 | "sqlite3_prepare_v2", 86 | "number", 87 | ["number", "string", "number", "number", "number"] 88 | ); 89 | var sqlite3_sql = cwrap("sqlite3_sql", "string", ["number"]); 90 | var sqlite3_normalized_sql = cwrap( 91 | "sqlite3_normalized_sql", 92 | "string", 93 | ["number"] 94 | ); 95 | var sqlite3_prepare_v2_sqlptr = cwrap( 96 | "sqlite3_prepare_v2", 97 | "number", 98 | ["number", "number", "number", "number", "number"] 99 | ); 100 | var sqlite3_bind_text = cwrap( 101 | "sqlite3_bind_text", 102 | "number", 103 | ["number", "number", "number", "number", "number"] 104 | ); 105 | var sqlite3_bind_blob = cwrap( 106 | "sqlite3_bind_blob", 107 | "number", 108 | ["number", "number", "number", "number", "number"] 109 | ); 110 | var sqlite3_bind_double = cwrap( 111 | "sqlite3_bind_double", 112 | "number", 113 | ["number", "number", "number"] 114 | ); 115 | var sqlite3_bind_int = cwrap( 116 | "sqlite3_bind_int", 117 | "number", 118 | ["number", "number", "number"] 119 | ); 120 | var sqlite3_bind_parameter_index = cwrap( 121 | "sqlite3_bind_parameter_index", 122 | "number", 123 | ["number", "string"] 124 | ); 125 | var sqlite3_step = cwrap("sqlite3_step", "number", ["number"]); 126 | var sqlite3_errmsg = cwrap("sqlite3_errmsg", "string", ["number"]); 127 | var sqlite3_column_count = cwrap( 128 | "sqlite3_column_count", 129 | "number", 130 | ["number"] 131 | ); 132 | var sqlite3_data_count = cwrap("sqlite3_data_count", "number", ["number"]); 133 | var sqlite3_column_double = cwrap( 134 | "sqlite3_column_double", 135 | "number", 136 | ["number", "number"] 137 | ); 138 | var sqlite3_column_text = cwrap( 139 | "sqlite3_column_text", 140 | "string", 141 | ["number", "number"] 142 | ); 143 | var sqlite3_column_blob = cwrap( 144 | "sqlite3_column_blob", 145 | "number", 146 | ["number", "number"] 147 | ); 148 | var sqlite3_column_bytes = cwrap( 149 | "sqlite3_column_bytes", 150 | "number", 151 | ["number", "number"] 152 | ); 153 | var sqlite3_column_type = cwrap( 154 | "sqlite3_column_type", 155 | "number", 156 | ["number", "number"] 157 | ); 158 | var sqlite3_column_name = cwrap( 159 | "sqlite3_column_name", 160 | "string", 161 | ["number", "number"] 162 | ); 163 | var sqlite3_reset = cwrap("sqlite3_reset", "number", ["number"]); 164 | var sqlite3_clear_bindings = cwrap( 165 | "sqlite3_clear_bindings", 166 | "number", 167 | ["number"] 168 | ); 169 | var sqlite3_finalize = cwrap("sqlite3_finalize", "number", ["number"]); 170 | var sqlite3_create_module_v2 = cwrap( 171 | "sqlite3_create_module_v2", 172 | "number", 173 | [ 174 | "number", "string", "number", "number", "number" 175 | ] 176 | ) 177 | var sqlite3_create_function_v2 = cwrap( 178 | "sqlite3_create_function_v2", 179 | "number", 180 | [ 181 | "number", 182 | "string", 183 | "number", 184 | "number", 185 | "number", 186 | "number", 187 | "number", 188 | "number", 189 | "number" 190 | ] 191 | ); 192 | var sqlite3_value_type = cwrap("sqlite3_value_type", "number", ["number"]); 193 | var sqlite3_value_bytes = cwrap( 194 | "sqlite3_value_bytes", 195 | "number", 196 | ["number"] 197 | ); 198 | var sqlite3_value_text = cwrap("sqlite3_value_text", "string", ["number"]); 199 | var sqlite3_value_blob = cwrap("sqlite3_value_blob", "number", ["number"]); 200 | var sqlite3_value_double = cwrap( 201 | "sqlite3_value_double", 202 | "number", 203 | ["number"] 204 | ); 205 | var sqlite3_result_double = cwrap( 206 | "sqlite3_result_double", 207 | "", 208 | ["number", "number"] 209 | ); 210 | var sqlite3_result_null = cwrap( 211 | "sqlite3_result_null", 212 | "", 213 | ["number"] 214 | ); 215 | var sqlite3_result_text = cwrap( 216 | "sqlite3_result_text", 217 | "", 218 | ["number", "string", "number", "number"] 219 | ); 220 | var sqlite3_result_blob = cwrap( 221 | "sqlite3_result_blob", 222 | "", 223 | ["number", "number", "number", "number"] 224 | ); 225 | var sqlite3_result_int = cwrap( 226 | "sqlite3_result_int", 227 | "", 228 | ["number", "number"] 229 | ); 230 | var sqlite3_result_error = cwrap( 231 | "sqlite3_result_error", 232 | "", 233 | ["number", "string", "number"] 234 | ); 235 | var sqlite3_malloc = cwrap( 236 | "sqlite3_malloc", 237 | "number", 238 | ["number"] 239 | ); 240 | Module["sqlite3_malloc"] = sqlite3_malloc; 241 | var registerExtensionFunctions = cwrap( 242 | "RegisterExtensionFunctions", 243 | "number", 244 | ["number"] 245 | ); 246 | 247 | /** 248 | * @classdesc 249 | * Represents a prepared statement. 250 | * Prepared statements allow you to have a template sql string, 251 | * that you can execute multiple times with different parameters. 252 | * 253 | * You can't instantiate this class directly, you have to use a 254 | * {@link Database} object in order to create a statement. 255 | * 256 | * **Warnings** 257 | * 1. When you close a database (using db.close()), all 258 | * its statements are closed too and become unusable. 259 | * 1. After calling db.prepare() you must manually free the assigned memory 260 | * by calling Statement.free(). Failure to do this will cause subsequent 261 | * 'DROP TABLE ...' statements to fail with 'Uncaught Error: database table 262 | * is locked'. 263 | * 264 | * Statements can't be created by the API user directly, only by 265 | * Database::prepare 266 | * 267 | * @see Database.html#prepare-dynamic 268 | * @see https://en.wikipedia.org/wiki/Prepared_statement 269 | * 270 | * @constructs Statement 271 | * @memberof module:SqlJs 272 | * @param {number} stmt1 The SQLite statement reference 273 | * @param {Database} db The database from which this statement was created 274 | */ 275 | function Statement(stmt1, db) { 276 | this.stmt = stmt1; 277 | this.db = db; 278 | // Index of the leftmost parameter is 1 279 | this.pos = 1; 280 | // Pointers to allocated memory, that need to be freed 281 | // when the statemend is destroyed 282 | this.allocatedmem = []; 283 | } 284 | 285 | /** @typedef {string|number|null|Uint8Array} Database.SqlValue */ 286 | /** @typedef { 287 | Database.SqlValue[]|Object|null 288 | } Statement.BindParams 289 | */ 290 | 291 | /** Bind values to the parameters, after having reseted the statement. 292 | * If values is null, do nothing and return true. 293 | * 294 | * SQL statements can have parameters, 295 | * named *'?', '?NNN', ':VVV', '@VVV', '$VVV'*, 296 | * where NNN is a number and VVV a string. 297 | * This function binds these parameters to the given values. 298 | * 299 | * *Warning*: ':', '@', and '$' are included in the parameters names 300 | * 301 | * ## Value types 302 | * Javascript type | SQLite type 303 | * -----------------| ----------- 304 | * number | REAL, INTEGER 305 | * boolean | INTEGER 306 | * string | TEXT 307 | * Array, Uint8Array| BLOB 308 | * null | NULL 309 | * 310 | * @example Bind values to named parameters 311 | * var stmt = db.prepare( 312 | * "UPDATE test SET a=@newval WHERE id BETWEEN $mini AND $maxi" 313 | * ); 314 | * stmt.bind({$mini:10, $maxi:20, '@newval':5}); 315 | * 316 | * @example Bind values to anonymous parameters 317 | * // Create a statement that contains parameters like '?', '?NNN' 318 | * var stmt = db.prepare("UPDATE test SET a=? WHERE id BETWEEN ? AND ?"); 319 | * // Call Statement.bind with an array as parameter 320 | * stmt.bind([5, 10, 20]); 321 | * 322 | * @see http://www.sqlite.org/datatype3.html 323 | * @see http://www.sqlite.org/lang_expr.html#varparam 324 | 325 | * @param {Statement.BindParams} values The values to bind 326 | * @return {boolean} true if it worked 327 | * @throws {String} SQLite Error 328 | */ 329 | Statement.prototype["bind"] = function bind(values) { 330 | if (!this.stmt) { 331 | throw "Statement closed"; 332 | } 333 | this["reset"](); 334 | if (Array.isArray(values)) return this.bindFromArray(values); 335 | if (values != null && typeof values === "object") { 336 | return this.bindFromObject(values); 337 | } 338 | return true; 339 | }; 340 | Statement.prototype["bind_"] = Statement.prototype.bind; // work around https://github.com/GoogleChromeLabs/comlink/blob/4ba8162f6c28fb1bf53b491565ef9a3ae42b72d3/src/comlink.ts#L432 341 | 342 | /** Execute the statement, fetching the the next line of result, 343 | that can be retrieved with {@link Statement.get}. 344 | 345 | @return {boolean} true if a row of result available 346 | @throws {String} SQLite Error 347 | */ 348 | Statement.prototype["step"] = function step() { 349 | if (!this.stmt) { 350 | throw "Statement closed"; 351 | } 352 | this.pos = 1; 353 | var ret = sqlite3_step(this.stmt); 354 | switch (ret) { 355 | case SQLITE_ROW: 356 | return true; 357 | case SQLITE_DONE: 358 | return false; 359 | default: 360 | throw this.db.handleError(ret); 361 | } 362 | }; 363 | 364 | /* 365 | Internal methods to retrieve data from the results of a statement 366 | that has been executed 367 | */ 368 | Statement.prototype.getNumber = function getNumber(pos) { 369 | if (pos == null) { 370 | pos = this.pos; 371 | this.pos += 1; 372 | } 373 | return sqlite3_column_double(this.stmt, pos); 374 | }; 375 | 376 | Statement.prototype.getString = function getString(pos) { 377 | if (pos == null) { 378 | pos = this.pos; 379 | this.pos += 1; 380 | } 381 | return sqlite3_column_text(this.stmt, pos); 382 | }; 383 | 384 | Statement.prototype.getBlob = function getBlob(pos) { 385 | if (pos == null) { 386 | pos = this.pos; 387 | this.pos += 1; 388 | } 389 | var size = sqlite3_column_bytes(this.stmt, pos); 390 | var ptr = sqlite3_column_blob(this.stmt, pos); 391 | var result = new Uint8Array(size); 392 | for (var i = 0; i < size; i += 1) { 393 | result[i] = HEAP8[ptr + i]; 394 | } 395 | return result; 396 | }; 397 | 398 | /** Get one row of results of a statement. 399 | If the first parameter is not provided, step must have been called before. 400 | @param {Statement.BindParams} [params] If set, the values will be bound 401 | to the statement before it is executed 402 | @return {Database.SqlValue[]} One row of result 403 | 404 | @example 405 | Print all the rows of the table test to the console 406 | var stmt = db.prepare("SELECT * FROM test"); 407 | while (stmt.step()) console.log(stmt.get()); 408 | */ 409 | Statement.prototype["get"] = function get(params) { 410 | if (params != null && this["bind"](params)) { 411 | this["step"](); 412 | } 413 | var results1 = []; 414 | var ref = sqlite3_data_count(this.stmt); 415 | for (var field = 0; field < ref; field += 1) { 416 | switch (sqlite3_column_type(this.stmt, field)) { 417 | case SQLITE_INTEGER: 418 | case SQLITE_FLOAT: 419 | results1.push(this.getNumber(field)); 420 | break; 421 | case SQLITE_TEXT: 422 | results1.push(this.getString(field)); 423 | break; 424 | case SQLITE_BLOB: 425 | results1.push(this.getBlob(field)); 426 | break; 427 | default: 428 | results1.push(null); 429 | } 430 | } 431 | return results1; 432 | }; 433 | 434 | /** Get the list of column names of a row of result of a statement. 435 | @return {string[]} The names of the columns 436 | @example 437 | var stmt = db.prepare( 438 | "SELECT 5 AS nbr, x'616200' AS data, NULL AS null_value;" 439 | ); 440 | stmt.step(); // Execute the statement 441 | console.log(stmt.getColumnNames()); 442 | // Will print ['nbr','data','null_value'] 443 | */ 444 | Statement.prototype["getColumnNames"] = function getColumnNames() { 445 | var results1 = []; 446 | var ref = sqlite3_column_count(this.stmt); 447 | for (var i = 0; i < ref; i += 1) { 448 | results1.push(sqlite3_column_name(this.stmt, i)); 449 | } 450 | return results1; 451 | }; 452 | 453 | /** Get one row of result as a javascript object, associating column names 454 | with their value in the current row. 455 | @param {Statement.BindParams} [params] If set, the values will be bound 456 | to the statement, and it will be executed 457 | @return {Object} The row of result 458 | @see {@link Statement.get} 459 | 460 | @example 461 | 462 | var stmt = db.prepare( 463 | "SELECT 5 AS nbr, x'010203' AS data, NULL AS null_value;" 464 | ); 465 | stmt.step(); // Execute the statement 466 | console.log(stmt.getAsObject()); 467 | // Will print {nbr:5, data: Uint8Array([1,2,3]), null_value:null} 468 | */ 469 | Statement.prototype["getAsObject"] = function getAsObject(params) { 470 | var values = this["get"](params); 471 | var names = this["getColumnNames"](); 472 | var rowObject = {}; 473 | for (var i = 0; i < names.length; i += 1) { 474 | var name = names[i]; 475 | rowObject[name] = values[i]; 476 | } 477 | return rowObject; 478 | }; 479 | 480 | /** Get the SQL string used in preparing this statement. 481 | @return {string} The SQL string 482 | */ 483 | Statement.prototype["getSQL"] = function getSQL() { 484 | return sqlite3_sql(this.stmt); 485 | }; 486 | 487 | /** Get the SQLite's normalized version of the SQL string used in 488 | preparing this statement. The meaning of "normalized" is not 489 | well-defined: see {@link https://sqlite.org/c3ref/expanded_sql.html 490 | the SQLite documentation}. 491 | 492 | @example 493 | db.run("create table test (x integer);"); 494 | stmt = db.prepare("select * from test where x = 42"); 495 | // returns "SELECT*FROM test WHERE x=?;" 496 | 497 | @return {string} The normalized SQL string 498 | */ 499 | Statement.prototype["getNormalizedSQL"] = function getNormalizedSQL() { 500 | return sqlite3_normalized_sql(this.stmt); 501 | }; 502 | 503 | /** Shorthand for bind + step + reset 504 | Bind the values, execute the statement, ignoring the rows it returns, 505 | and resets it 506 | @param {Statement.BindParams} [values] Value to bind to the statement 507 | */ 508 | Statement.prototype["run"] = function run(values) { 509 | if (values != null) { 510 | this["bind"](values); 511 | } 512 | this["step"](); 513 | return this["reset"](); 514 | }; 515 | 516 | Statement.prototype.bindString = function bindString(string, pos) { 517 | if (pos == null) { 518 | pos = this.pos; 519 | this.pos += 1; 520 | } 521 | var bytes = intArrayFromString(string); 522 | var strptr = allocate(bytes, ALLOC_NORMAL); 523 | this.allocatedmem.push(strptr); 524 | this.db.handleError(sqlite3_bind_text( 525 | this.stmt, 526 | pos, 527 | strptr, 528 | bytes.length - 1, 529 | 0 530 | )); 531 | return true; 532 | }; 533 | 534 | Statement.prototype.bindBlob = function bindBlob(array, pos) { 535 | if (pos == null) { 536 | pos = this.pos; 537 | this.pos += 1; 538 | } 539 | var blobptr = allocate(array, ALLOC_NORMAL); 540 | this.allocatedmem.push(blobptr); 541 | this.db.handleError(sqlite3_bind_blob( 542 | this.stmt, 543 | pos, 544 | blobptr, 545 | array.length, 546 | 0 547 | )); 548 | return true; 549 | }; 550 | 551 | Statement.prototype.bindNumber = function bindNumber(num, pos) { 552 | if (pos == null) { 553 | pos = this.pos; 554 | this.pos += 1; 555 | } 556 | var bindfunc = ( 557 | num === (num | 0) 558 | ? sqlite3_bind_int 559 | : sqlite3_bind_double 560 | ); 561 | this.db.handleError(bindfunc(this.stmt, pos, num)); 562 | return true; 563 | }; 564 | 565 | Statement.prototype.bindNull = function bindNull(pos) { 566 | if (pos == null) { 567 | pos = this.pos; 568 | this.pos += 1; 569 | } 570 | return sqlite3_bind_blob(this.stmt, pos, 0, 0, 0) === SQLITE_OK; 571 | }; 572 | 573 | Statement.prototype.bindValue = function bindValue(val, pos) { 574 | if (pos == null) { 575 | pos = this.pos; 576 | this.pos += 1; 577 | } 578 | switch (typeof val) { 579 | case "string": 580 | return this.bindString(val, pos); 581 | case "number": 582 | case "boolean": 583 | return this.bindNumber(val + 0, pos); 584 | case "object": 585 | if (val === null) { 586 | return this.bindNull(pos); 587 | } 588 | if (val.length != null) { 589 | return this.bindBlob(val, pos); 590 | } 591 | break; 592 | default: 593 | break; 594 | } 595 | throw ( 596 | "Wrong API use : tried to bind a value of an unknown type (" 597 | + val + ")." 598 | ); 599 | }; 600 | 601 | /** Bind names and values of an object to the named parameters of the 602 | statement 603 | @param {Object} valuesObj 604 | @private 605 | @nodoc 606 | */ 607 | Statement.prototype.bindFromObject = function bindFromObject(valuesObj) { 608 | var that = this; 609 | Object.keys(valuesObj).forEach(function each(name) { 610 | var num = sqlite3_bind_parameter_index(that.stmt, name); 611 | if (num !== 0) { 612 | that.bindValue(valuesObj[name], num); 613 | } 614 | }); 615 | return true; 616 | }; 617 | 618 | /** Bind values to numbered parameters 619 | @param {Database.SqlValue[]} values 620 | @private 621 | @nodoc 622 | */ 623 | Statement.prototype.bindFromArray = function bindFromArray(values) { 624 | for (var num = 0; num < values.length; num += 1) { 625 | this.bindValue(values[num], num + 1); 626 | } 627 | return true; 628 | }; 629 | 630 | /** Reset a statement, so that it's parameters can be bound to new values 631 | It also clears all previous bindings, freeing the memory used 632 | by bound parameters. 633 | */ 634 | Statement.prototype["reset"] = function reset() { 635 | this.freemem(); 636 | return ( 637 | sqlite3_clear_bindings(this.stmt) === SQLITE_OK 638 | && sqlite3_reset(this.stmt) === SQLITE_OK 639 | ); 640 | }; 641 | 642 | /** Free the memory allocated during parameter binding */ 643 | Statement.prototype["freemem"] = function freemem() { 644 | var mem; 645 | while ((mem = this.allocatedmem.pop()) !== undefined) { 646 | _free(mem); 647 | } 648 | }; 649 | 650 | /** Free the memory used by the statement 651 | @return {boolean} true in case of success 652 | */ 653 | Statement.prototype["free"] = function free() { 654 | var res; 655 | this.freemem(); 656 | res = sqlite3_finalize(this.stmt) === SQLITE_OK; 657 | delete this.db.statements[this.stmt]; 658 | this.stmt = NULL; 659 | return res; 660 | }; 661 | 662 | /** 663 | * @classdesc 664 | * An iterator over multiple SQL statements in a string, 665 | * preparing and returning a Statement object for the next SQL 666 | * statement on each iteration. 667 | * 668 | * You can't instantiate this class directly, you have to use a 669 | * {@link Database} object in order to create a statement iterator 670 | * 671 | * {@see Database#iterateStatements} 672 | * 673 | * @example 674 | * // loop over and execute statements in string sql 675 | * for (let statement of db.iterateStatements(sql) { 676 | * statement.step(); 677 | * // get results, etc. 678 | * // do not call statement.free() manually, each statement is freed 679 | * // before the next one is parsed 680 | * } 681 | * 682 | * // capture any bad query exceptions with feedback 683 | * // on the bad sql 684 | * let it = db.iterateStatements(sql); 685 | * try { 686 | * for (let statement of it) { 687 | * statement.step(); 688 | * } 689 | * } catch(e) { 690 | * console.log( 691 | * `The SQL string "${it.getRemainingSQL()}" ` + 692 | * `contains the following error: ${e}` 693 | * ); 694 | * } 695 | * 696 | * @implements {Iterator} 697 | * @implements {Iterable} 698 | * @constructs StatementIterator 699 | * @memberof module:SqlJs 700 | * @param {string} sql A string containing multiple SQL statements 701 | * @param {Database} db The database from which this iterator was created 702 | */ 703 | function StatementIterator(sql, db) { 704 | this.db = db; 705 | var sz = lengthBytesUTF8(sql) + 1; 706 | this.sqlPtr = _malloc(sz); 707 | if (this.sqlPtr === null) { 708 | throw new Error("Unable to allocate memory for the SQL string"); 709 | } 710 | stringToUTF8(sql, this.sqlPtr, sz); 711 | this.nextSqlPtr = this.sqlPtr; 712 | this.nextSqlString = null; 713 | this.activeStatement = null; 714 | } 715 | 716 | /** 717 | * @typedef {{ done:true, value:undefined } | 718 | * { done:false, value:Statement}} 719 | * StatementIterator.StatementIteratorResult 720 | * @property {Statement} value the next available Statement 721 | * (as returned by {@link Database.prepare}) 722 | * @property {boolean} done true if there are no more available statements 723 | */ 724 | 725 | /** Prepare the next available SQL statement 726 | @return {StatementIterator.StatementIteratorResult} 727 | @throws {String} SQLite error or invalid iterator error 728 | */ 729 | StatementIterator.prototype["next"] = function next() { 730 | if (this.sqlPtr === null) { 731 | return { done: true }; 732 | } 733 | if (this.activeStatement !== null) { 734 | this.activeStatement["free"](); 735 | this.activeStatement = null; 736 | } 737 | if (!this.db.db) { 738 | this.finalize(); 739 | throw new Error("Database closed"); 740 | } 741 | var stack = stackSave(); 742 | var pzTail = stackAlloc(4); 743 | setValue(apiTemp, 0, "i32"); 744 | setValue(pzTail, 0, "i32"); 745 | try { 746 | this.db.handleError(sqlite3_prepare_v2_sqlptr( 747 | this.db.db, 748 | this.nextSqlPtr, 749 | -1, 750 | apiTemp, 751 | pzTail 752 | )); 753 | this.nextSqlPtr = getValue(pzTail, "i32"); 754 | var pStmt = getValue(apiTemp, "i32"); 755 | if (pStmt === NULL) { 756 | this.finalize(); 757 | return { done: true }; 758 | } 759 | this.activeStatement = new Statement(pStmt, this.db); 760 | this.db.statements[pStmt] = this.activeStatement; 761 | return { value: this.activeStatement, done: false }; 762 | } catch (e) { 763 | this.nextSqlString = UTF8ToString(this.nextSqlPtr); 764 | this.finalize(); 765 | throw e; 766 | } finally { 767 | stackRestore(stack); 768 | } 769 | }; 770 | 771 | StatementIterator.prototype.finalize = function finalize() { 772 | _free(this.sqlPtr); 773 | this.sqlPtr = null; 774 | }; 775 | 776 | /** Get any un-executed portions remaining of the original SQL string 777 | @return {String} 778 | */ 779 | StatementIterator.prototype["getRemainingSQL"] = function getRemainder() { 780 | // iff an exception occurred, we set the nextSqlString 781 | if (this.nextSqlString !== null) return this.nextSqlString; 782 | // otherwise, convert from nextSqlPtr 783 | return UTF8ToString(this.nextSqlPtr); 784 | }; 785 | 786 | /* implement Iterable interface */ 787 | 788 | if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 789 | StatementIterator.prototype[Symbol.iterator] = function iterator() { 790 | return this; 791 | }; 792 | } 793 | 794 | /** @classdesc 795 | * Represents an SQLite database 796 | * @constructs Database 797 | * @memberof module:SqlJs 798 | * Open a new database either by creating a new one or opening an existing 799 | * one stored in the byte array passed in first argument 800 | * @param {number[]} data An array of bytes representing 801 | * an SQLite database file 802 | */ 803 | function Database(data) { 804 | this.filename = "dbfile_" + (0xffffffff * Math.random() >>> 0); 805 | if (data != null) { 806 | FS.createDataFile("/", this.filename, data, true, true); 807 | } 808 | const ret = sqlite3_open(this.filename, apiTemp); 809 | this.db = getValue(apiTemp, "i32"); 810 | this.handleError(ret); 811 | registerExtensionFunctions(this.db); 812 | // A list of all prepared statements of the database 813 | this.statements = {}; 814 | // A list of all user function of the database 815 | // (created by create_function call) 816 | this.functions = {}; 817 | } 818 | 819 | function CustomDatabase(filename) { 820 | this.filename = filename; 821 | const ret = sqlite3_open(this.filename, apiTemp); 822 | this.db = getValue(apiTemp, "i32"); 823 | this.handleError(ret); 824 | registerExtensionFunctions(this.db); 825 | // A list of all prepared statements of the database 826 | this.statements = {}; 827 | // A list of all user function of the database 828 | // (created by create_function call) 829 | this.functions = {}; 830 | } 831 | 832 | /** Execute an SQL query, ignoring the rows it returns. 833 | @param {string} sql a string containing some SQL text to execute 834 | @param {Statement.BindParams} [params] When the SQL statement contains 835 | placeholders, you can pass them in here. They will be bound to the statement 836 | before it is executed. If you use the params argument, you **cannot** 837 | provide an sql string that contains several statements (separated by `;`) 838 | 839 | @example 840 | // Insert values in a table 841 | db.run( 842 | "INSERT INTO test VALUES (:age, :name)", 843 | { ':age' : 18, ':name' : 'John' } 844 | ); 845 | 846 | @return {Database} The database object (useful for method chaining) 847 | */ 848 | Database.prototype["run"] = function run(sql, params) { 849 | if (!this.db) { 850 | throw "Database closed"; 851 | } 852 | if (params) { 853 | var stmt = this["prepare"](sql, params); 854 | try { 855 | stmt["step"](); 856 | } finally { 857 | stmt["free"](); 858 | } 859 | } else { 860 | this.handleError(sqlite3_exec(this.db, sql, 0, 0, apiTemp)); 861 | } 862 | return this; 863 | }; 864 | 865 | /** 866 | * @typedef {{ 867 | columns:string[], 868 | values:Database.SqlValue[][] 869 | }} Database.QueryExecResult 870 | * @property {string[]} columns the name of the columns of the result 871 | * (as returned by {@link Statement.getColumnNames}) 872 | * @property {Database.SqlValue[][]} values one array per row, containing 873 | * the column values 874 | */ 875 | 876 | /** Execute an SQL query, and returns the result. 877 | * 878 | * This is a wrapper against 879 | * {@link Database.prepare}, 880 | * {@link Statement.bind}, 881 | * {@link Statement.step}, 882 | * {@link Statement.get}, 883 | * and {@link Statement.free}. 884 | * 885 | * The result is an array of result elements. There are as many result 886 | * elements as the number of statements in your sql string (statements are 887 | * separated by a semicolon) 888 | * 889 | * ## Example use 890 | * We will create the following table, named *test* and query it with a 891 | * multi-line statement using params: 892 | * 893 | * | id | age | name | 894 | * |:--:|:---:|:------:| 895 | * | 1 | 1 | Ling | 896 | * | 2 | 18 | Paul | 897 | * 898 | * We query it like that: 899 | * ```javascript 900 | * var db = new SQL.Database(); 901 | * var res = db.exec( 902 | * "DROP TABLE IF EXISTS test;\n" 903 | * + "CREATE TABLE test (id INTEGER, age INTEGER, name TEXT);" 904 | * + "INSERT INTO test VALUES ($id1, :age1, @name1);" 905 | * + "INSERT INTO test VALUES ($id2, :age2, @name2);" 906 | * + "SELECT id FROM test;" 907 | * + "SELECT age,name FROM test WHERE id=$id1", 908 | * { 909 | * "$id1": 1, ":age1": 1, "@name1": "Ling", 910 | * "$id2": 2, ":age2": 18, "@name2": "Paul" 911 | * } 912 | * ); 913 | * ``` 914 | * 915 | * `res` is now : 916 | * ```javascript 917 | * [ 918 | * {"columns":["id"],"values":[[1],[2]]}, 919 | * {"columns":["age","name"],"values":[[1,"Ling"]]} 920 | * ] 921 | * ``` 922 | * 923 | @param {string} sql a string containing some SQL text to execute 924 | @param {Statement.BindParams} [params] When the SQL statement contains 925 | placeholders, you can pass them in here. They will be bound to the statement 926 | before it is executed. If you use the params argument as an array, 927 | you **cannot** provide an sql string that contains several statements 928 | (separated by `;`). This limitation does not apply to params as an object. 929 | * @return {Database.QueryExecResult[]} The results of each statement 930 | */ 931 | Database.prototype["exec"] = function exec(sql, params) { 932 | if (!this.db) { 933 | throw "Database closed"; 934 | } 935 | var stack = stackSave(); 936 | var stmt = null; 937 | try { 938 | var nextSqlPtr = allocateUTF8OnStack(sql); 939 | var pzTail = stackAlloc(4); 940 | var results = []; 941 | while (getValue(nextSqlPtr, "i8") !== NULL) { 942 | setValue(apiTemp, 0, "i32"); 943 | setValue(pzTail, 0, "i32"); 944 | this.handleError(sqlite3_prepare_v2_sqlptr( 945 | this.db, 946 | nextSqlPtr, 947 | -1, 948 | apiTemp, 949 | pzTail 950 | )); 951 | // pointer to a statement, or null 952 | var pStmt = getValue(apiTemp, "i32"); 953 | nextSqlPtr = getValue(pzTail, "i32"); 954 | // Empty statement 955 | if (pStmt !== NULL) { 956 | var curresult = null; 957 | stmt = new Statement(pStmt, this); 958 | if (params != null) { 959 | stmt.bind(params); 960 | } 961 | while (stmt["step"]()) { 962 | if (curresult === null) { 963 | curresult = { 964 | columns: stmt["getColumnNames"](), 965 | values: [], 966 | }; 967 | results.push(curresult); 968 | } 969 | curresult["values"].push(stmt["get"]()); 970 | } 971 | stmt["free"](); 972 | } 973 | } 974 | return results; 975 | } catch (errCaught) { 976 | if (stmt) stmt["free"](); 977 | throw errCaught; 978 | } finally { 979 | stackRestore(stack); 980 | } 981 | }; 982 | 983 | /** Execute an sql statement, and call a callback for each row of result. 984 | 985 | Currently this method is synchronous, it will not return until the callback 986 | has been called on every row of the result. But this might change. 987 | 988 | @param {string} sql A string of SQL text. Can contain placeholders 989 | that will be bound to the parameters given as the second argument 990 | @param {Statement.BindParams} [params=[]] Parameters to bind to the query 991 | @param {function(Object):void} callback 992 | Function to call on each row of result 993 | @param {function():void} done A function that will be called when 994 | all rows have been retrieved 995 | 996 | @return {Database} The database object. Useful for method chaining 997 | 998 | @example Read values from a table 999 | db.each("SELECT name,age FROM users WHERE age >= $majority", {$majority:18}, 1000 | function (row){console.log(row.name + " is a grown-up.")} 1001 | ); 1002 | */ 1003 | Database.prototype["each"] = function each(sql, params, callback, done) { 1004 | var stmt; 1005 | if (typeof params === "function") { 1006 | done = callback; 1007 | callback = params; 1008 | params = undefined; 1009 | } 1010 | stmt = this["prepare"](sql, params); 1011 | try { 1012 | while (stmt["step"]()) { 1013 | callback(stmt["getAsObject"]()); 1014 | } 1015 | } finally { 1016 | stmt["free"](); 1017 | } 1018 | if (typeof done === "function") { 1019 | return done(); 1020 | } 1021 | return undefined; 1022 | }; 1023 | 1024 | /** Prepare an SQL statement 1025 | @param {string} sql a string of SQL, that can contain placeholders 1026 | (`?`, `:VVV`, `:AAA`, `@AAA`) 1027 | @param {Statement.BindParams} [params] values to bind to placeholders 1028 | @return {Statement} the resulting statement 1029 | @throws {String} SQLite error 1030 | */ 1031 | Database.prototype["prepare"] = function prepare(sql, params) { 1032 | setValue(apiTemp, 0, "i32"); 1033 | this.handleError(sqlite3_prepare_v2(this.db, sql, -1, apiTemp, NULL)); 1034 | // pointer to a statement, or null 1035 | var pStmt = getValue(apiTemp, "i32"); 1036 | if (pStmt === NULL) { 1037 | throw "Nothing to prepare"; 1038 | } 1039 | var stmt = new Statement(pStmt, this); 1040 | if (params != null) { 1041 | stmt.bind(params); 1042 | } 1043 | this.statements[pStmt] = stmt; 1044 | return stmt; 1045 | }; 1046 | 1047 | /** Iterate over multiple SQL statements in a SQL string. 1048 | * This function returns an iterator over {@link Statement} objects. 1049 | * You can use a for..of loop to execute the returned statements one by one. 1050 | * @param {string} sql a string of SQL that can contain multiple statements 1051 | * @return {StatementIterator} the resulting statement iterator 1052 | * @example Get the results of multiple SQL queries 1053 | * const sql_queries = "SELECT 1 AS x; SELECT '2' as y"; 1054 | * for (const statement of db.iterateStatements(sql_queries)) { 1055 | * const sql = statement.getSQL(); // Get the SQL source 1056 | * const result = statement.getAsObject({}); // Get the row of data 1057 | * console.log(sql, result); 1058 | * } 1059 | * // This will print: 1060 | * // 'SELECT 1 AS x;' { x: 1 } 1061 | * // " SELECT '2' as y" { y: '2' } 1062 | */ 1063 | Database.prototype["iterateStatements"] = function iterateStatements(sql) { 1064 | return new StatementIterator(sql, this); 1065 | }; 1066 | 1067 | /** Exports the contents of the database to a binary array 1068 | @return {Uint8Array} An array of bytes of the SQLite3 database file 1069 | */ 1070 | Database.prototype["export"] = function exportDatabase() { 1071 | Object.values(this.statements).forEach(function each(stmt) { 1072 | stmt["free"](); 1073 | }); 1074 | Object.values(this.functions).forEach(removeFunction); 1075 | this.functions = {}; 1076 | this.handleError(sqlite3_close_v2(this.db)); 1077 | var binaryDb = FS.readFile(this.filename, { encoding: "binary" }); 1078 | this.handleError(sqlite3_open(this.filename, apiTemp)); 1079 | this.db = getValue(apiTemp, "i32"); 1080 | return binaryDb; 1081 | }; 1082 | 1083 | /** Close the database, and all associated prepared statements. 1084 | * The memory associated to the database and all associated statements 1085 | * will be freed. 1086 | * 1087 | * **Warning**: A statement belonging to a database that has been closed 1088 | * cannot be used anymore. 1089 | * 1090 | * Databases **must** be closed when you're finished with them, or the 1091 | * memory consumption will grow forever 1092 | */ 1093 | Database.prototype["close"] = function close() { 1094 | // do nothing if db is null or already closed 1095 | if (this.db === null) { 1096 | return; 1097 | } 1098 | Object.values(this.statements).forEach(function each(stmt) { 1099 | stmt["free"](); 1100 | }); 1101 | Object.values(this.functions).forEach(removeFunction); 1102 | this.functions = {}; 1103 | this.handleError(sqlite3_close_v2(this.db)); 1104 | FS.unlink("/" + this.filename); 1105 | this.db = null; 1106 | }; 1107 | 1108 | /** Analyze a result code, return null if no error occured, and throw 1109 | an error with a descriptive message otherwise 1110 | @param {int} returnCode 1111 | @nodoc 1112 | */ 1113 | Database.prototype["handleError"] = function handleError(returnCode) { 1114 | var errmsg; 1115 | if (returnCode === SQLITE_OK) { 1116 | return null; 1117 | } 1118 | errmsg = sqlite3_errmsg(this.db); 1119 | throw new Error("SQLite: " + (errmsg || "Code " + returnCode)); 1120 | }; 1121 | 1122 | /** Returns the number of changed rows (modified, inserted or deleted) 1123 | by the latest completed INSERT, UPDATE or DELETE statement on the 1124 | database. Executing any other type of SQL statement does not modify 1125 | the value returned by this function. 1126 | 1127 | @return {number} the number of rows modified 1128 | */ 1129 | Database.prototype["getRowsModified"] = function getRowsModified() { 1130 | return sqlite3_changes(this.db); 1131 | }; 1132 | 1133 | /** Register a custom function with SQLite 1134 | @example Register a simple function 1135 | db.create_function("addOne", function (x) {return x+1;}) 1136 | db.exec("SELECT addOne(1)") // = 2 1137 | 1138 | @param {string} name the name of the function as referenced in 1139 | SQL statements. 1140 | @param {function} func the actual function to be executed. 1141 | @return {Database} The database object. Useful for method chaining 1142 | */ 1143 | Database.prototype["create_function"] = function create_function( 1144 | name, 1145 | func 1146 | ) { 1147 | function wrapped_func(cx, argc, argv) { 1148 | var result; 1149 | var args = []; 1150 | for (var i = 0; i < argc; i += 1) { 1151 | args.push(Module.extract_value(argv + 4 * i)); 1152 | } 1153 | try { 1154 | result = func.apply(null, args); 1155 | } catch (error) { 1156 | sqlite3_result_error(cx, "JS threw: " + error, -1); 1157 | return; 1158 | } 1159 | Module.set_return_value(cx, result); 1160 | 1161 | } 1162 | if (Object.prototype.hasOwnProperty.call(this.functions, name)) { 1163 | removeFunction(this.functions[name]); 1164 | delete this.functions[name]; 1165 | } 1166 | // The signature of the wrapped function is : 1167 | // void wrapped(sqlite3_context *db, int argc, sqlite3_value **argv) 1168 | var func_ptr = addFunction(wrapped_func, "viii"); 1169 | this.functions[name] = func_ptr; 1170 | this.handleError(sqlite3_create_function_v2( 1171 | this.db, 1172 | name, 1173 | func.length, 1174 | SQLITE_UTF8, 1175 | 0, 1176 | func_ptr, 1177 | 0, 1178 | 0, 1179 | 0 1180 | )); 1181 | return this; 1182 | }; 1183 | 1184 | function extract_blob(ptr) { 1185 | var size = sqlite3_value_bytes(ptr); 1186 | var blob_ptr = sqlite3_value_blob(ptr); 1187 | var blob_arg = new Uint8Array(size); 1188 | for (var j = 0; j < size; j += 1) { 1189 | blob_arg[j] = HEAP8[blob_ptr + j]; 1190 | } 1191 | return blob_arg; 1192 | } 1193 | Module["extract_value"] = function extract_value(ptr) { 1194 | var value_ptr = getValue(ptr, "i32"); 1195 | var value_type = sqlite3_value_type(value_ptr); 1196 | var arg; 1197 | if ( 1198 | value_type === SQLITE_INTEGER 1199 | || value_type === SQLITE_FLOAT 1200 | ) { 1201 | arg = sqlite3_value_double(value_ptr); 1202 | } else if (value_type === SQLITE_TEXT) { 1203 | arg = sqlite3_value_text(value_ptr); 1204 | } else if (value_type === SQLITE_BLOB) { 1205 | arg = extract_blob(value_ptr); 1206 | } else arg = null; 1207 | return arg; 1208 | } 1209 | Module["set_return_value"] = function set_return_value(cx, result) { 1210 | switch (typeof result) { 1211 | case "boolean": 1212 | sqlite3_result_int(cx, result ? 1 : 0); 1213 | break; 1214 | case "number": 1215 | sqlite3_result_double(cx, result); 1216 | break; 1217 | case "string": 1218 | sqlite3_result_text(cx, result, -1, -1); 1219 | break; 1220 | case "object": 1221 | if (result === null) { 1222 | sqlite3_result_null(cx); 1223 | } else if (result.length != null) { 1224 | var blobptr = allocate(result, ALLOC_NORMAL); 1225 | sqlite3_result_blob(cx, blobptr, result.length, -1); 1226 | _free(blobptr); 1227 | } else { 1228 | sqlite3_result_error(cx, ( 1229 | "Wrong API use : tried to return a value " 1230 | + "of an unknown type (" + result + ")." 1231 | ), -1); 1232 | } 1233 | break; 1234 | default: 1235 | console.warn("unknown sqlite result type: ", typeof result, result); 1236 | sqlite3_result_null(cx); 1237 | } 1238 | } 1239 | 1240 | Database.prototype.create_vtab = function create_vtab(cons) { 1241 | const ele = new cons(Module, this); 1242 | const module_things = { 1243 | iVersion: null, 1244 | xCreate: "ptr", 1245 | xConnect: "ptr", 1246 | xBestIndex: "ptr", 1247 | xDisconnect: "ptr", 1248 | xDestroy: "ptr", 1249 | xOpen: "ptr", 1250 | xClose: "ptr", 1251 | xFilter: "ptr", 1252 | xNext: "ptr", 1253 | xEof: "ptr", 1254 | xColumn: "ptr", 1255 | xRowid: "ptr", 1256 | xUpdate: "ptr", 1257 | xBegin: "ptr", 1258 | xSync: "ptr", 1259 | xCommit: "ptr", 1260 | xRollback: "ptr", 1261 | xFindFunction: "ptr", 1262 | xRename: "ptr", 1263 | xSavepoint: "ptr", 1264 | xRelease: "ptr", 1265 | xRollbackTo: "ptr", 1266 | xShadowName: "ptr", 1267 | } 1268 | 1269 | const sqlite3_module = _malloc(Object.keys(module_things).length * 4); // 24 ints / pointers 1270 | let i = 0; 1271 | for (const k in module_things) { 1272 | let tgt = ele[k] || 0; 1273 | let type = "i32"; 1274 | if(module_things[k] && ele[k]) { 1275 | const fn = ele[k].bind(ele); 1276 | var sig = Array(1 + fn.length).fill("i").join(""); // every arg passed as an int 1277 | tgt = addFunction(fn, sig); 1278 | type = "*"; 1279 | } 1280 | setValue(sqlite3_module + i * 4, tgt, type); 1281 | i++; 1282 | } 1283 | this.handleError(sqlite3_create_module_v2(this.db, ele.name, sqlite3_module, 0, 0)); 1284 | } 1285 | 1286 | // export Database to Module 1287 | Module.Database = Database; 1288 | Module["CustomDatabase"] = CustomDatabase; 1289 | Module["FS"] = FS; 1290 | CustomDatabase.prototype = Object.create(Database.prototype); 1291 | }; 1292 | --------------------------------------------------------------------------------