├── .gitignore ├── README.md ├── binding.gyp ├── doc ├── examples.html ├── examples.js ├── index.php └── publish.sh ├── index.html ├── speedtest.js ├── sqlite.js ├── sqlite3_bindings.cc ├── sqlite3_bindings.node ├── test.js └── testloop.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | README.html 3 | test.db 4 | .lock-wscript 5 | speedtest.db 6 | node_modules/ 7 | package-lock.json 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SQLite3 bindings for Node.js 2 | ============================= 3 | 4 | Documentation lives at http://grumdrig.com/node-sqlite/ 5 | The code lives at http://github.com/grumdrig/node-sqlite/ 6 | 7 | The two files required to use these bindings are sqlite.js and 8 | build/default/sqlite3_bindings.node. Put this directory in your 9 | NODE_PATH or copy those two files where you need them. 10 | 11 | Tested with Node version v8.4.0 (may not work with older versions) 12 | 13 | ### Synchronous Only 14 | 15 | Only synchronous access is supported! For an asynchronous and 16 | much more full-featured library, see 17 | https://github.com/mapbox/node-sqlite3 18 | 19 | ### TODO 20 | 21 | The C++ code which creates the `Statement` object uses `NewInstance`, 22 | which is marked deprecated. I don't know what it should be replaced 23 | with but that whole bit code is pretty ugly and could probably be 24 | improved a lot. 25 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "sqlite3_bindings", 5 | "include_dirs" : [" 3 | pre, code { color: #060; font-size: 11pt; } 4 | pre { margin-left: 2ex; padding: 1ex; background-color: #eee; } 5 | p { font-size: 12pt; } 6 | body { 7 | margin-left: 10%; 8 | margin-right: 10%; 9 | background-color: #fff; 10 | color: black; 11 | max-width: 800px; 12 | } 13 | h1,h2,h3,h4 { font-family: helvetica } 14 | h1 { font-size: 36pt; } 15 | h1 { 16 | background-color: #28b; 17 | color: white; 18 | padding-left:6px; 19 | } 20 | h2 { 21 | color: #28b; 22 | } 23 | 24 | 25 | 26 |

27 |

Documentation by Example

28 |

29 | Import the library and open a database. (Only syncronous database 30 | access is implemented at this time.) 31 |

32 |

 33 | var sqlite = require("../sqlite");
 34 | var db = sqlite.openDatabaseSync("example.db");
 35 | var assert = require("assert").ok;
 36 | 
37 |

38 | Perform an SQL query on the database: 39 |

40 |

 41 | db.query("CREATE TABLE foo (a,b,c)");
 42 | 
43 |

44 | This is a more convenient form than the HTML5 syntax for the same 45 | thing, but which is also supported: 46 |

47 |

 48 | db.transaction(function(tx) {
 49 |   tx.executeSql("CREATE TABLE bar (x,y,z)");
 50 | });
 51 | 
52 |

53 | This allows the same or similar code to work on the client and 54 | server end (modulo browser support of HTML5 Web SQL). 55 |

56 | Transactions generate either a "commit" or "rollback" event. 57 |

58 |

 59 | var rollbacks = 0;
 60 | db.addListener("rollback", function () {
 61 |   ++rollbacks;
 62 | });
 63 | 
64 |

65 | Both forms take an optional second parameter which is values to 66 | bind to fields in the query, as an array: 67 |

68 |

 69 | db.query("INSERT INTO foo (a,b,c) VALUES (?,?,?)", ['apple','banana',22]);
 70 | 
71 |

72 | or as a map: 73 |

74 |

 75 | db.query("INSERT INTO bar (x,y,z) VALUES ($x,$y,$zebra)", 
 76 |          {$x: 10, $y:20, $zebra:"stripes"});
 77 | 
78 |

79 | Also optional is a callback function which is called with an object 80 | representing the results of the query: 81 |

82 |

 83 | db.query("SELECT x FROM bar", function (records) {
 84 |   assert(records.length == 1);
 85 |   assert(records[0].x == 10);
 86 | 
87 |

88 | The HTML5 semantics for the record set also work: 89 |

90 |

 91 |   assert(records.rows.length == 1);
 92 |   assert(records.rows.item(0).x == 10);
 93 | });
 94 | 
95 |

96 | INSERT, UPDATE & DELETE queries set rowsAffected on their result 97 | set object: 98 |

99 |

100 | db.query("UPDATE foo SET a = ? WHERE a = ?", ['orange', 'apple'], function(r) {
101 |   assert(r.rowsAffected == 1);
102 | });
103 | 
104 |

105 | They also emit an "update" event. 106 |

107 | INSERT queries set insertId: 108 |

109 |

110 | var insert = db.query("INSERT INTO foo VALUES (1,2,3)");
111 | assert(insert.insertId == 2);
112 | 
113 |

114 | Note here that the result set passed to the callback is also 115 | returned by query. 116 |

117 | Multiple-statement queries are supported; each statement's result set is retuned to the callback as a separate parameter: 118 |

119 |

120 | var q = db.query("UPDATE bar SET z=20; SELECT SUM(z) FROM bar;",
121 |                  function (update, select) {
122 |                    assert(update.rowsAffected == 1);
123 |                    assert(select[0]['SUM(z)'] == 20);
124 |                  });
125 | 
126 |

127 | An array of all result sets is available as the .all property on 128 | each result set: 129 |

130 |

131 | assert(q.all[1].length == 1);
132 | 
133 |

134 | HTML5 semantics are supported. 135 |

136 |

137 | db.transaction(function(tx) {
138 |   tx.executeSql("SELECT * FROM foo WHERE c = ?", [3], function(tx,res) {
139 |     assert(res.rows.item(0).c == 3);
140 |   });
141 | });
142 | 
143 |

144 | The query and transaction APIs wrap lower level APIs that more 145 | thinly wrap the underlying C api: 146 |

147 |

148 | var stmt = db.prepare("INSERT INTO foo VALUES (?,?,?)");
149 | stmt.bind(1, "curly");
150 | stmt.bind(2, "moe");
151 | stmt.bind(3, "larry");
152 | stmt.step();  // Insert Curly, Moe & Larry
153 | stmt.reset();
154 | stmt.step();  // Insert another row with same stooges
155 | stmt.reset();
156 | stmt.clearBindings();
157 | stmt.bind(2, "lonely");
158 | stmt.step();  // Insert (null, "lonely", null)
159 | stmt.finalize();
160 | 
161 |

162 | Close it: 163 |

164 |

165 | db.close();
166 | 
167 |

168 |

169 | 170 |

171 | 172 | -------------------------------------------------------------------------------- /doc/examples.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | /* 3 | //!! To be processed by docbyex.py, or run by Node 4 | 5 | Documentation by Example :: 6 | */ 7 | 8 | // Import the library and open a database. (Only syncronous database 9 | // access is implemented at this time.) 10 | 11 | var sqlite = require("../sqlite"); 12 | var db = sqlite.openDatabaseSync("example.db"); 13 | var assert = require("assert").ok; 14 | 15 | // Perform an SQL query on the database: 16 | 17 | db.query("CREATE TABLE foo (a,b,c)"); 18 | 19 | // This is a more convenient form than the HTML5 syntax for the same 20 | // thing, but which is also supported: 21 | 22 | db.transaction(function(tx) { 23 | tx.executeSql("CREATE TABLE bar (x,y,z)"); 24 | }); 25 | 26 | // This allows the same or similar code to work on the client and 27 | // server end (modulo browser support of HTML5 Web SQL). 28 | 29 | // Transactions generate either a "commit" or "rollback" event. 30 | 31 | var rollbacks = 0; 32 | db.addListener("rollback", function () { 33 | ++rollbacks; 34 | }); 35 | 36 | // Both forms take an optional second parameter which is values to 37 | // bind to fields in the query, as an array: 38 | 39 | db.query("INSERT INTO foo (a,b,c) VALUES (?,?,?)", ['apple','banana',22]); 40 | 41 | // or as a map: 42 | 43 | db.query("INSERT INTO bar (x,y,z) VALUES ($x,$y,$zebra)", 44 | {$x: 10, $y:20, $zebra:"stripes"}); 45 | 46 | // Also optional is a callback function which is called with an object 47 | // representing the results of the query: 48 | 49 | db.query("SELECT x FROM bar", function (records) { 50 | assert(records.length == 1); 51 | assert(records[0].x == 10); 52 | 53 | // The HTML5 semantics for the record set also work: 54 | 55 | assert(records.rows.length == 1); 56 | assert(records.rows.item(0).x == 10); 57 | }); 58 | 59 | // INSERT, UPDATE & DELETE queries set `rowsAffected` on their result 60 | // set object: 61 | 62 | db.query("UPDATE foo SET a = ? WHERE a = ?", ['orange', 'apple'], function(r) { 63 | assert(r.rowsAffected == 1); 64 | }); 65 | 66 | // They also emit an `"update"` event. 67 | 68 | // INSERT queries set `insertId`: 69 | 70 | var insert = db.query("INSERT INTO foo VALUES (1,2,3)"); 71 | assert(insert.insertId == 2); 72 | 73 | // Note here that the result set passed to the callback is also 74 | // returned by `query`. 75 | 76 | // Multiple-statement queries are supported; each statement's result set is retuned to the callback as a separate parameter: 77 | 78 | var q = db.query("UPDATE bar SET z=20; SELECT SUM(z) FROM bar;", 79 | function (update, select) { 80 | assert(update.rowsAffected == 1); 81 | assert(select[0]['SUM(z)'] == 20); 82 | }); 83 | 84 | // An array of all result sets is available as the `.all` property on 85 | // each result set: 86 | 87 | assert(q.all[1].length == 1); 88 | 89 | // HTML5 semantics are supported. 90 | 91 | db.transaction(function(tx) { 92 | tx.executeSql("SELECT * FROM foo WHERE c = ?", [3], function(tx,res) { 93 | assert(res.rows.item(0).c == 3); 94 | }); 95 | }); 96 | 97 | // The `query` and `transaction` APIs wrap lower level APIs that more 98 | // thinly wrap the underlying C api: 99 | 100 | var stmt = db.prepare("INSERT INTO foo VALUES (?,?,?)"); 101 | stmt.bind(1, "curly"); 102 | stmt.bind(2, "moe"); 103 | stmt.bind(3, "larry"); 104 | stmt.step(); // Insert Curly, Moe & Larry 105 | stmt.reset(); 106 | stmt.step(); // Insert another row with same stooges 107 | stmt.reset(); 108 | stmt.clearBindings(); 109 | stmt.bind(2, "lonely"); 110 | stmt.step(); // Insert (null, "lonely", null) 111 | stmt.finalize(); 112 | 113 | // Close it: 114 | 115 | db.close(); 116 | 117 | // !!** 118 | // Might as well clean up the mess we made. 119 | 120 | require('fs').unlink('example.db'); 121 | 122 | var sys = require("sys"); 123 | sys.puts("OK"); 124 | -------------------------------------------------------------------------------- /doc/index.php: -------------------------------------------------------------------------------- 1 | 2 | node-sqlite 3 | 25 | 26 | 27 | 28 |

node-sqlite

29 | 30 | SQLite bindings for 31 | Node. 32 | 33 |

34 | The semantics conform somewhat to those of the HTML5 Web SQL API, 36 | plus some extensions. Also, only the synchronous API is implemented; 37 | the asynchronous API is a big TODO item. 38 | 39 |

Download

40 |

41 | The spiritual home of node-sqlite is at 42 | 43 | http://grumdrig.github.com/node-sqlite/. 44 | 45 |

46 | The code lives at 47 | http://github.com/grumdrig/node-sqlite 48 | 49 | 50 | 51 | 52 |

Installation

53 | 54 |

55 |

    56 |
  1. Install Node and 57 | SQLite. 58 | 59 |

    60 |

  2. Get the code 61 | 62 |
    63 | $ git clone git://github.com/grumdrig/node-sqlite.git
    64 | 
    65 | 66 |
  3. Configure and build 67 | 68 |
    69 | $ cd node-sqlite
    70 | $ node-waf configure
    71 | $ node-waf build
    72 | 
    73 | 74 |
  4. Test: 75 | 76 |
    77 | $ node test.js
    78 | $ node doc/examples.js
    79 | 
    80 | 81 |
82 | 83 |

84 | The two files needed to use this library are sqlite.js and 85 | build/default/sqlite3_bindings.node; copy them where you need 86 | them. -------------------------------------------------------------------------------- /doc/publish.sh: -------------------------------------------------------------------------------- 1 | docbyex -s examples.js > examples.html 2 | php < index.php > ../index.html 3 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | node-sqlite 3 | 25 | 26 | 27 | 28 |

node-sqlite

29 | 30 | SQLite bindings for 31 | Node. 32 | 33 |

34 | The semantics conform somewhat to those of the HTML5 Web SQL API, 36 | plus some extensions. Also, only the synchronous API is implemented; 37 | the asynchronous API is a big TODO item. 38 | 39 |

Download

40 |

41 | The spiritual home of node-sqlite is at 42 | 43 | http://grumdrig.github.com/node-sqlite/. 44 | 45 |

46 | The code lives at 47 | http://github.com/grumdrig/node-sqlite 48 | 49 | 50 | 72 | 73 | 74 |

75 |

Documentation by Example

76 |

77 | Import the library and open a database. (Only syncronous database 78 | access is implemented at this time.) 79 |

80 |

 81 | var sqlite = require("../sqlite");
 82 | var db = sqlite.openDatabaseSync("example.db");
 83 | var assert = require("assert").ok;
 84 | 
85 |

86 | Perform an SQL query on the database: 87 |

88 |

 89 | db.query("CREATE TABLE foo (a,b,c)");
 90 | 
91 |

92 | This is a more convenient form than the HTML5 syntax for the same 93 | thing, but which is also supported: 94 |

95 |

 96 | db.transaction(function(tx) {
 97 |   tx.executeSql("CREATE TABLE bar (x,y,z)");
 98 | });
 99 | 
100 |

101 | This allows the same or similar code to work on the client and 102 | server end (modulo browser support of HTML5 Web SQL). 103 |

104 | Transactions generate either a "commit" or "rollback" event. 105 |

106 |

107 | var rollbacks = 0;
108 | db.addListener("rollback", function () {
109 |   ++rollbacks;
110 | });
111 | 
112 |

113 | Both forms take an optional second parameter which is values to 114 | bind to fields in the query, as an array: 115 |

116 |

117 | db.query("INSERT INTO foo (a,b,c) VALUES (?,?,?)", ['apple','banana',22]);
118 | 
119 |

120 | or as a map: 121 |

122 |

123 | db.query("INSERT INTO bar (x,y,z) VALUES ($x,$y,$zebra)", 
124 |          {$x: 10, $y:20, $zebra:"stripes"});
125 | 
126 |

127 | Also optional is a callback function which is called with an object 128 | representing the results of the query: 129 |

130 |

131 | db.query("SELECT x FROM bar", function (records) {
132 |   assert(records.length == 1);
133 |   assert(records[0].x == 10);
134 | 
135 |

136 | The HTML5 semantics for the record set also work: 137 |

138 |

139 |   assert(records.rows.length == 1);
140 |   assert(records.rows.item(0).x == 10);
141 | });
142 | 
143 |

144 | INSERT, UPDATE & DELETE queries set rowsAffected on their result 145 | set object: 146 |

147 |

148 | db.query("UPDATE foo SET a = ? WHERE a = ?", ['orange', 'apple'], function(r) {
149 |   assert(r.rowsAffected == 1);
150 | });
151 | 
152 |

153 | They also emit an "update" event. 154 |

155 | INSERT queries set insertId: 156 |

157 |

158 | var insert = db.query("INSERT INTO foo VALUES (1,2,3)");
159 | assert(insert.insertId == 2);
160 | 
161 |

162 | Note here that the result set passed to the callback is also 163 | returned by query. 164 |

165 | Multiple-statement queries are supported; each statement's result set is retuned to the callback as a separate parameter: 166 |

167 |

168 | var q = db.query("UPDATE bar SET z=20; SELECT SUM(z) FROM bar;",
169 |                  function (update, select) {
170 |                    assert(update.rowsAffected == 1);
171 |                    assert(select[0]['SUM(z)'] == 20);
172 |                  });
173 | 
174 |

175 | An array of all result sets is available as the .all property on 176 | each result set: 177 |

178 |

179 | assert(q.all[1].length == 1);
180 | 
181 |

182 | HTML5 semantics are supported. 183 |

184 |

185 | db.transaction(function(tx) {
186 |   tx.executeSql("SELECT * FROM foo WHERE c = ?", [3], function(tx,res) {
187 |     assert(res.rows.item(0).c == 3);
188 |   });
189 | });
190 | 
191 |

192 | The query and transaction APIs wrap lower level APIs that more 193 | thinly wrap the underlying C api: 194 |

195 |

196 | var stmt = db.prepare("INSERT INTO foo VALUES (?,?,?)");
197 | stmt.bind(1, "curly");
198 | stmt.bind(2, "moe");
199 | stmt.bind(3, "larry");
200 | stmt.step();  // Insert Curly, Moe & Larry
201 | stmt.reset();
202 | stmt.step();  // Insert another row with same stooges
203 | stmt.reset();
204 | stmt.clearBindings();
205 | stmt.bind(2, "lonely");
206 | stmt.step();  // Insert (null, "lonely", null)
207 | stmt.finalize();
208 | 
209 |

210 | Close it: 211 |

212 |

213 | db.close();
214 | 
215 |

216 |

217 | 218 |

219 | 220 | 221 | 222 |

Installation

223 | 224 |

225 |

    226 |
  1. Install Node and 227 | SQLite. 228 | 229 |

    230 |

  2. Get the code 231 | 232 |
    233 | $ git clone git://github.com/grumdrig/node-sqlite.git
    234 | 
    235 | 236 |
  3. Configure and build 237 | 238 |
    239 | $ cd node-sqlite
    240 | $ node-waf configure
    241 | $ node-waf build
    242 | 
    243 | 244 |
  4. Test: 245 | 246 |
    247 | $ node test.js
    248 | $ node doc/examples.js
    249 | 
    250 | 251 |
252 | 253 |

254 | The two files needed to use this library are sqlite.js and 255 | build/default/sqlite3_bindings.node; copy them where you need 256 | them. -------------------------------------------------------------------------------- /speedtest.js: -------------------------------------------------------------------------------- 1 | // Speed test for SQL. Based on http://www.sqlite.org/speed.html 2 | 3 | var util = require("util"); 4 | var posix = require("posix"); 5 | 6 | try { 7 | posix.unlink("speedtest.db").wait(); 8 | } catch (e) { 9 | // Not there? That's okay. 10 | } 11 | 12 | function connect() { 13 | if (true) { 14 | // node-sqlite 15 | var sqlite = require("./sqlite"); 16 | var db = sqlite.openDatabaseSync("speedtest.db"); 17 | } else { 18 | // node-persistence 19 | // TODO: this doesn't work yet 20 | var sqlite = require("../../lib/node-persistence/lib/persistence/sqlite"); 21 | var db = sqlite.new_connection("speedtest.db"); 22 | db.executeSql = db.query; 23 | db.transaction = function(callback) { 24 | this.query("BEGIN TRANSACTION;"); 25 | callback(this); 26 | this.query("COMMIT"); 27 | } 28 | } 29 | return db; 30 | } 31 | 32 | function _rome(dn, ds) { 33 | if (n < dn) return false; 34 | n -= dn; 35 | s += ds; 36 | return true; 37 | } 38 | 39 | function toRoman(n) { 40 | if (isNaN(n)) return n; 41 | if (n <= 0) return "N"; 42 | s = ""; 43 | while (_rome(1000, "M")) {} 44 | _rome(900, "CM"); 45 | _rome(500, "D"); 46 | _rome(400, "CD"); 47 | while (_rome(100, "C")) {} 48 | _rome(90, "XC"); 49 | _rome(50, "L"); 50 | _rome(40, "XL"); 51 | while (_rome(10, "X")) {} 52 | _rome(9, "IX"); 53 | _rome(5, "V"); 54 | _rome(4, "IV"); 55 | while (_rome(1, "I")) {} 56 | return s; 57 | } 58 | 59 | var db; 60 | 61 | var SECTION; 62 | var LASTTIME; 63 | function time(section) { 64 | if (db) db.close(); 65 | db = connect(); 66 | var now = (new Date()).getTime(); 67 | if (SECTION) { 68 | var elapsed = ((now - LASTTIME)/1000.0).toFixed(3); 69 | util.puts(elapsed + ' ' + SECTION); 70 | } 71 | SECTION = section; 72 | LASTTIME = now; 73 | } 74 | 75 | time("Test 1: 1000 INSERTs"); 76 | db.query("CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100));"); 77 | for (var i = 0; i < 1000; ++i) { 78 | var n = Math.floor(Math.random() * 99999); 79 | db.query("INSERT INTO t1 VALUES(?,?,?);", [i, n, toRoman(n)]); 80 | } 81 | 82 | time("Test 2: 25000 INSERTs in a transaction"); 83 | db.transaction(function (tx) { 84 | tx.executeSql("CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100));"); 85 | for (var i = 0; i < 25000; ++i) { 86 | var n = Math.floor(Math.random() * 99999); 87 | db.query("INSERT INTO t2 VALUES(?,?,?);", [i, n, toRoman(n)]); 88 | } 89 | }); 90 | 91 | time("Test 3: 25000 INSERTs into an indexed table"); 92 | db.transaction(function (tx) { 93 | tx.executeSql("CREATE TABLE t3(a INTEGER, b INTEGER, c VARCHAR(100));"); 94 | tx.executeSql("CREATE INDEX i3 ON t3(c);"); 95 | for (var i = 0; i < 25000; ++i) { 96 | var n = Math.floor(Math.random() * 99999); 97 | db.query("INSERT INTO t3 VALUES(?,?,?);", [i, n, toRoman(n)]); 98 | } 99 | }); 100 | 101 | time("Test 4: 100 SELECTs without an index"); 102 | db.transaction(function (tx) { 103 | for (var i = 0; i < 100; ++i) { 104 | db.query("SELECT count(*), avg(b) FROM t2 WHERE b>=? AND b=? AND b=? AND a10 AND a<20000;"); 161 | 162 | time("Test 14: A big INSERT after a big DELETE"); 163 | db.query("INSERT INTO t2 SELECT * FROM t1;"); 164 | 165 | time("Test 15: A big DELETE followed by 12000 small INSERTs"); 166 | db.transaction(function (tx) { 167 | tx.executeSql("DELETE FROM t1;"); 168 | for (var i = 0; i < 12000; ++i) { 169 | var n = Math.floor(Math.random() * 100000); 170 | tx.executeSql("INSERT INTO t1 VALUES(?,?,?);", [i, n, toRoman(n)]); 171 | } 172 | }); 173 | 174 | time("Test 16: DROP TABLE"); 175 | db.query("DROP TABLE t1;"); 176 | db.query("DROP TABLE t2;"); 177 | db.query("DROP TABLE t3;"); 178 | 179 | time(); -------------------------------------------------------------------------------- /sqlite.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Eric Fredricksen 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | // TODO: async 18 | 19 | function mixin(target, source) { 20 | for (var name in source) { 21 | if (source.hasOwnProperty(name)) 22 | target[name] = source[name]; 23 | } 24 | } 25 | 26 | var bindings = require("./build/Release/sqlite3_bindings"); 27 | mixin(global, bindings); 28 | mixin(exports, bindings); 29 | 30 | 31 | // Conform somewhat to http://dev.w3.org/html5/webdatabase/#sql 32 | 33 | exports.SQLITE_DELETE = 9; 34 | exports.SQLITE_INSERT = 18; 35 | exports.SQLITE_UPDATE = 23; 36 | 37 | 38 | exports.openDatabaseSync = function (name, version, displayName, 39 | estimatedSize, creationCallback) { 40 | // 2nd-4th parameters are ignored 41 | var db = new DatabaseSync(name); 42 | if (creationCallback) creationCallback(db); 43 | return db; 44 | } 45 | 46 | 47 | DatabaseSync.prototype.query = function (sql, bindings, callback) { 48 | // TODO: error callback 49 | if (typeof(bindings) == "function") { 50 | var tmp = bindings; 51 | bindings = callback; 52 | callback = tmp; 53 | } 54 | 55 | var all = []; 56 | 57 | var stmt = this.prepare(sql); 58 | while(stmt) { 59 | if (bindings) { 60 | if (Object.prototype.toString.call(bindings) === "[object Array]") { 61 | for (var i = 0; i < stmt.bindParameterCount(); ++i) 62 | stmt.bind(i+1, bindings.shift()); 63 | } else { 64 | for (var key in bindings) 65 | if (bindings.hasOwnProperty(key)) 66 | stmt.bind(key, bindings[key]); 67 | } 68 | } 69 | 70 | var rows = []; 71 | 72 | while (true) { 73 | var row = stmt.step(); 74 | if (!row) break; 75 | rows.push(row); 76 | } 77 | 78 | rows.rowsAffected = this.changes(); 79 | rows.insertId = this.lastInsertRowid(); 80 | 81 | all.push(rows); 82 | 83 | stmt.finalize(); 84 | stmt = this.prepare(stmt.tail); 85 | } 86 | 87 | if (all.length == 0) { 88 | var result = null; 89 | } else { 90 | for (var i = 0; i < all.length; ++i) { 91 | var resultset = all[i]; 92 | resultset.all = all; 93 | resultset.rows = {item: function (index) { return resultset[index]; }, 94 | length: resultset.length}; 95 | } 96 | var result = all[0]; 97 | } 98 | if (typeof(callback) == "function") { 99 | callback.apply(result, all); 100 | } 101 | return result; 102 | } 103 | 104 | 105 | 106 | function SQLTransactionSync(db, txCallback, errCallback, successCallback) { 107 | this.database = db; 108 | 109 | this.rolledBack = false; 110 | 111 | this.executeSql = function(sqlStatement, arguments, callback) { 112 | if (this.rolledBack) return; 113 | var result = db.query(sqlStatement, arguments); 114 | if (callback) { 115 | var tx = this; 116 | callback.apply(result, [tx].concat(result.all)); 117 | } 118 | return result; 119 | } 120 | 121 | var that = this; 122 | var oldrollback = db.onrollback; 123 | function unroll() { 124 | that.rolledBack = true; 125 | if (oldrollback) oldrollback(); 126 | } 127 | db.onrollback = unroll; 128 | 129 | this.executeSql("BEGIN TRANSACTION"); 130 | txCallback(this); 131 | this.executeSql("COMMIT"); 132 | 133 | db.onrollback = oldrollback; 134 | 135 | if (!this.rolledBack && successCallback) { 136 | successCallback(this); 137 | } else if (this.rolledBack && errCallback) { 138 | errCallback(this); 139 | } 140 | } 141 | 142 | 143 | DatabaseSync.prototype.transaction = function (txCallback, errCallback, 144 | successCallback) { 145 | var tx = new SQLTransactionSync(this, txCallback, 146 | errCallback, successCallback); 147 | } 148 | 149 | // TODO: readTransaction() 150 | 151 | -------------------------------------------------------------------------------- /sqlite3_bindings.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009, Eric Fredricksen 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | #include 17 | #include 18 | 19 | 20 | #define CHECK(rc) if ((rc) != SQLITE_OK) { Nan::ThrowError(sqlite3_errmsg(db->db_)); return; } 21 | 22 | #define SCHECK(rc) if ((rc) != SQLITE_OK) { Nan::ThrowError(sqlite3_errmsg(sqlite3_db_handle(stmt->stmt_))); return; } 23 | 24 | #define STRING_ARG(N, NAME) \ 25 | if (info.Length() <= (N) || !info[N]->IsString()) return Nan::ThrowTypeError("Argument " #N " must be a string"); \ 26 | Nan::Utf8String NAME(info[N]); 27 | 28 | #define REQ_ARGS(N) if (info.Length() < (N)) { Nan::ThrowError("Expected " #N "arguments"); return; } 29 | 30 | 31 | 32 | class Sqlite3Db : public Nan::ObjectWrap { 33 | public: 34 | 35 | static void Init(v8::Local exports) { 36 | Nan::HandleScope scope; 37 | 38 | v8::Local tpl = Nan::New(New); 39 | tpl->SetClassName(Nan::New("DatabaseSync").ToLocalChecked()); 40 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 41 | 42 | Nan::SetPrototypeMethod(tpl, "changes", Changes); 43 | Nan::SetPrototypeMethod(tpl, "close", Close); 44 | Nan::SetPrototypeMethod(tpl, "lastInsertRowid", LastInsertRowid); 45 | Nan::SetPrototypeMethod(tpl, "exec", Exec); 46 | Nan::SetPrototypeMethod(tpl, "prepare", Prepare); 47 | 48 | constructor.Reset(tpl->GetFunction()); 49 | exports->Set(Nan::New("DatabaseSync").ToLocalChecked(), tpl->GetFunction()); 50 | 51 | Statement::Init(exports); 52 | } 53 | 54 | private: 55 | 56 | explicit Sqlite3Db(sqlite3* db) : db_(db) { 57 | } 58 | 59 | ~Sqlite3Db() { 60 | sqlite3_close(db_); 61 | } 62 | 63 | sqlite3* db_; 64 | 65 | static Nan::Persistent constructor; 66 | 67 | protected: 68 | static void New(const Nan::FunctionCallbackInfo& info) { 69 | if (!info.IsConstructCall()) return Nan::ThrowError("Call as constructor with `new` expected"); 70 | 71 | STRING_ARG(0, filename); 72 | sqlite3* db; 73 | int rc = sqlite3_open(*filename, &db); 74 | if (rc) { 75 | Nan::ThrowError("Error opening database"); 76 | return; 77 | } 78 | Sqlite3Db* dbo = new Sqlite3Db(db); 79 | dbo->Wrap(info.This()); 80 | 81 | sqlite3_commit_hook(db, CommitHook, dbo); 82 | sqlite3_rollback_hook(db, RollbackHook, dbo); 83 | sqlite3_update_hook(db, UpdateHook, dbo); 84 | 85 | info.GetReturnValue().Set(info.This()); 86 | } 87 | 88 | 89 | // 90 | // JS DatabaseSync bindings 91 | // 92 | 93 | static void Changes(const Nan::FunctionCallbackInfo& info) { 94 | Sqlite3Db* db = ObjectWrap::Unwrap(info.This()); 95 | info.GetReturnValue().Set(Nan::New(sqlite3_changes(db->db_))); 96 | } 97 | 98 | static void Close(const Nan::FunctionCallbackInfo& info) { 99 | Sqlite3Db* db = ObjectWrap::Unwrap(info.This()); 100 | CHECK(sqlite3_close(db->db_)); 101 | db->db_ = NULL; 102 | } 103 | 104 | static void LastInsertRowid(const Nan::FunctionCallbackInfo& info) { 105 | Sqlite3Db* db = ObjectWrap::Unwrap(info.This()); 106 | info.GetReturnValue().Set(Nan::New((int32_t)sqlite3_last_insert_rowid(db->db_))); 107 | } 108 | 109 | static int CommitHook(void* v_this) { 110 | Nan::HandleScope scope; 111 | auto db = static_cast(v_this)->handle(); 112 | v8::Local oncommit = db->Get(Nan::New("oncommit").ToLocalChecked()); 113 | if (oncommit->IsFunction()) { 114 | Nan::MakeCallback(db, oncommit.As(), 0, 0); 115 | // TODO: allow change in return value to convert to rollback...somehow 116 | } 117 | return 0; 118 | } 119 | 120 | static void RollbackHook(void* v_this) { 121 | Nan::HandleScope scope; 122 | auto db = static_cast(v_this)->handle(); 123 | v8::Local onrollback = db->Get(Nan::New("onrollback").ToLocalChecked()); 124 | if (onrollback->IsFunction()) { 125 | Nan::MakeCallback(db, onrollback.As(), 0, 0); 126 | } 127 | } 128 | 129 | static void UpdateHook(void* v_this, int operation, const char* database, 130 | const char* table, sqlite_int64 rowid) { 131 | Nan::HandleScope scope; 132 | auto db = static_cast(v_this)->handle(); 133 | v8::Local onupdate = db->Get(Nan::New("onupdate").ToLocalChecked()); 134 | v8::Local args[] = { 135 | Nan::New(operation), 136 | Nan::New(database).ToLocalChecked(), 137 | Nan::New(table).ToLocalChecked(), 138 | Nan::New((uint32_t)rowid) 139 | }; 140 | if (onupdate->IsFunction()) { 141 | Nan::MakeCallback(db, onupdate.As(), 4, args); 142 | } 143 | } 144 | 145 | static void Exec(const Nan::FunctionCallbackInfo& info) { 146 | Nan::HandleScope scope; 147 | Sqlite3Db* db = ObjectWrap::Unwrap(info.This()); 148 | STRING_ARG(0, sql); 149 | CHECK(sqlite3_exec(db->db_, *sql, NULL/*callback*/, 0/*cbarg*/, NULL/*errmsg*/)); 150 | } 151 | 152 | static void Prepare(const Nan::FunctionCallbackInfo& info) { 153 | Nan::HandleScope scope; 154 | Sqlite3Db* db = ObjectWrap::Unwrap(info.This()); 155 | STRING_ARG(0, sql); 156 | sqlite3_stmt* stmt = NULL; 157 | const char* tail = NULL; 158 | CHECK(sqlite3_prepare_v2(db->db_, *sql, -1, &stmt, &tail)); 159 | if (stmt) { 160 | v8::Local arg = Nan::New(stmt); 161 | v8::Local statement(Statement::NewInstance(arg)); 162 | ObjectWrap::Unwrap(statement)->stmt_ = stmt; 163 | if (tail) { 164 | statement->Set(Nan::New("tail").ToLocalChecked(), Nan::New(tail).ToLocalChecked()); 165 | } 166 | info.GetReturnValue().Set(statement); 167 | } else { 168 | info.GetReturnValue().Set(Nan::Null()); 169 | } 170 | } 171 | 172 | 173 | class Statement : public Nan::ObjectWrap { 174 | public: 175 | 176 | static Nan::Persistent constructor; 177 | 178 | static void Init(v8::Handle target) { 179 | Nan::HandleScope scope; 180 | 181 | v8::Local tpl = Nan::New(New); 182 | tpl->SetClassName(Nan::New("Statement").ToLocalChecked()); 183 | tpl->InstanceTemplate()->SetInternalFieldCount(1); 184 | 185 | Nan::SetPrototypeMethod(tpl, "bind", Bind); 186 | Nan::SetPrototypeMethod(tpl, "clearBindings", ClearBindings); 187 | Nan::SetPrototypeMethod(tpl, "finalize", Finalize); 188 | Nan::SetPrototypeMethod(tpl, "bindParameterCount", BindParameterCount); 189 | Nan::SetPrototypeMethod(tpl, "reset", Reset); 190 | Nan::SetPrototypeMethod(tpl, "step", Step); 191 | 192 | constructor.Reset(tpl->GetFunction()); 193 | //exports->Set(Nan::New("Statement").ToLocalChecked(), tpl->GetFunction()); 194 | } 195 | 196 | static void New(const Nan::FunctionCallbackInfo& info) { 197 | Statement* obj = new Statement((sqlite3_stmt*)info[0]->IntegerValue()); 198 | obj->Wrap(info.This()); 199 | info.GetReturnValue().Set(info.This()); 200 | } 201 | 202 | static v8::Local NewInstance(v8::Local arg) { 203 | Nan::EscapableHandleScope scope; 204 | 205 | const unsigned argc = 1; 206 | v8::Local argv[argc] = { arg }; 207 | v8::Local cons = Nan::New(constructor); 208 | v8::Local instance = cons->NewInstance(argc, argv); 209 | 210 | return scope.Escape(instance); 211 | } 212 | 213 | // protected: 214 | explicit Statement(sqlite3_stmt* stmt) : stmt_(stmt) {} 215 | 216 | ~Statement() { if (stmt_) sqlite3_finalize(stmt_); } 217 | 218 | sqlite3_stmt* stmt_; 219 | 220 | operator sqlite3_stmt* () const { return stmt_; } 221 | 222 | // 223 | // JS prepared statement bindings 224 | // 225 | 226 | static void Bind(const Nan::FunctionCallbackInfo& info) { 227 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 228 | 229 | REQ_ARGS(2); 230 | if (!info[0]->IsString() && !info[0]->IsInt32()) { 231 | Nan::ThrowError("First argument must be a string or integer"); 232 | return; 233 | } 234 | int index = info[0]->IsString() ? 235 | sqlite3_bind_parameter_index(*stmt, *v8::String::Utf8Value(info[0])) : 236 | info[0]->Int32Value(); 237 | 238 | if (info[1]->IsInt32()) { 239 | sqlite3_bind_int(*stmt, index, info[1]->Int32Value()); 240 | } else if (info[1]->IsNumber()) { 241 | sqlite3_bind_double(*stmt, index, info[1]->NumberValue()); 242 | } else if (info[1]->IsString()) { 243 | v8::String::Utf8Value text(info[1]); 244 | sqlite3_bind_text(*stmt, index, *text, text.length(),SQLITE_TRANSIENT); 245 | } else if (info[1]->IsNull() || info[1]->IsUndefined()) { 246 | sqlite3_bind_null(*stmt, index); 247 | } else { 248 | Nan::ThrowError("Unable to bind value of this type"); 249 | } 250 | info.GetReturnValue().Set(info.This()); 251 | } 252 | 253 | static void BindParameterCount(const Nan::FunctionCallbackInfo& info) { 254 | Nan::HandleScope scope; 255 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 256 | info.GetReturnValue().Set(Nan::New(sqlite3_bind_parameter_count(stmt->stmt_))); 257 | } 258 | 259 | static void ClearBindings(const Nan::FunctionCallbackInfo& info) { 260 | Nan::HandleScope scope; 261 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 262 | SCHECK(sqlite3_clear_bindings(stmt->stmt_)); 263 | } 264 | 265 | static void Finalize(const Nan::FunctionCallbackInfo& info) { 266 | Nan::HandleScope scope; 267 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 268 | SCHECK(sqlite3_finalize(stmt->stmt_)); 269 | stmt->stmt_ = NULL; 270 | //info.This().MakeWeak(); 271 | } 272 | 273 | static void Reset(const Nan::FunctionCallbackInfo& info) { 274 | Nan::HandleScope scope; 275 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 276 | SCHECK(sqlite3_reset(stmt->stmt_)); 277 | } 278 | 279 | static void Step(const Nan::FunctionCallbackInfo& info) { 280 | Nan::HandleScope scope; 281 | Statement* stmt = ObjectWrap::Unwrap(info.This()); 282 | 283 | 284 | int rc = sqlite3_step(stmt->stmt_); 285 | if (rc == SQLITE_ROW) { 286 | v8::Local row = Nan::New(); 287 | for (int c = 0; c < sqlite3_column_count(stmt->stmt_); ++c) { 288 | v8::Handle value; 289 | switch (sqlite3_column_type(*stmt, c)) { 290 | case SQLITE_INTEGER: 291 | value = Nan::New(sqlite3_column_int(*stmt, c)); 292 | break; 293 | case SQLITE_FLOAT: 294 | value = Nan::New(sqlite3_column_double(*stmt, c)); 295 | break; 296 | case SQLITE_TEXT: 297 | value = Nan::New((const char*) sqlite3_column_text(*stmt, c)).ToLocalChecked(); 298 | break; 299 | case SQLITE_NULL: 300 | default: // We don't handle any other types just now 301 | value = Nan::Undefined(); 302 | break; 303 | } 304 | row->Set(Nan::New(sqlite3_column_name(*stmt, c)).ToLocalChecked(), value); 305 | } 306 | info.GetReturnValue().Set(row); 307 | } else if (rc == SQLITE_DONE) { 308 | info.GetReturnValue().Set(Nan::Null()); 309 | } else { 310 | Nan::ThrowError(sqlite3_errmsg(sqlite3_db_handle(stmt->stmt_))); 311 | } 312 | } 313 | 314 | }; 315 | 316 | 317 | }; 318 | 319 | 320 | Nan::Persistent Sqlite3Db::constructor; 321 | Nan::Persistent Sqlite3Db::Statement::constructor; 322 | 323 | void InitAll(v8::Local exports) { 324 | Sqlite3Db::Init(exports); 325 | } 326 | 327 | NODE_MODULE(addon, InitAll) 328 | 329 | 330 | 331 | -------------------------------------------------------------------------------- /sqlite3_bindings.node: -------------------------------------------------------------------------------- 1 | build/default/sqlite3_bindings.node -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // Test script for node_sqlite 2 | 3 | var util = require("util"); 4 | var fs = require("fs"); 5 | var sqlite = require("./sqlite"); 6 | 7 | fs.unlink('test.db', () => {}); 8 | 9 | var assert = require("assert").ok; 10 | 11 | function asserteq(v1, v2) { 12 | if (v1 != v2) { 13 | console.log(util.inspect(v1)); 14 | console.log(util.inspect(v2)); 15 | } 16 | assert(v1 == v2); 17 | } 18 | 19 | var db = sqlite.openDatabaseSync('test.db'); 20 | 21 | var commits = 0; 22 | var rollbacks = 0; 23 | var updates = 0; 24 | 25 | db.oncommit = function () { console.log("COMMIT"); commits++; }; 26 | db.onrollback = function () { console.log("ROLLBACK"); rollbacks++; }; 27 | db.onupdate = function () { console.log("UPDATE"); updates++; }; 28 | 29 | db.exec("CREATE TABLE egg (a,y,e)"); 30 | db.query("INSERT INTO egg (a) VALUES (1)", function () { 31 | assert(this.insertId == 1); 32 | }); 33 | 34 | var i2 = db.query("INSERT INTO egg (a) VALUES (?)", [5]); 35 | assert(i2.insertId == 2); 36 | db.query("UPDATE egg SET y='Y'; UPDATE egg SET e='E';"); 37 | db.query("UPDATE egg SET y=?; UPDATE egg SET e=? WHERE ROWID=1", 38 | ["arm","leg"] ); 39 | db.query("INSERT INTO egg (a,y,e) VALUES (?,?,?)", [1.01, 10e20, -0.0]); 40 | db.query("INSERT INTO egg (a,y,e) VALUES (?,?,?)", ["one", "two", "three"]); 41 | 42 | db.query("SELECT * FROM egg", function (rows) { 43 | console.log(JSON.stringify(rows)); 44 | }); 45 | 46 | db.query("SELECT a FROM egg; SELECT y FROM egg", function (as, ys) { 47 | console.log("As " + JSON.stringify(as)); 48 | console.log("Ys " + JSON.stringify(ys)); 49 | assert(as.length == 4); 50 | assert(ys.length == 4); 51 | }); 52 | 53 | db.query("SELECT e FROM egg WHERE a = ?", [5], function (es) { 54 | asserteq(es.length, 1); 55 | asserteq(es[0].e, es.rows.item(0).e); 56 | asserteq(es[0].e, "E"); 57 | }); 58 | 59 | 60 | db.transaction(function(tx) { 61 | tx.executeSql("CREATE TABLE tex (t,e,x)"); 62 | var i = tx.executeSql("INSERT INTO tex (t,e,x) VALUES (?,?,?)", 63 | ["this","is","Sparta"]); 64 | asserteq(i.rowsAffected, 1); 65 | var s = tx.executeSql("SELECT * FROM tex"); 66 | asserteq(s.rows.length, 1); 67 | asserteq(s.rows.item(0).t, "this"); 68 | asserteq(s.rows.item(0).e, "is"); 69 | asserteq(s.rows.item(0).x, "Sparta"); 70 | }); 71 | 72 | 73 | db.query("CREATE TABLE test (x,y,z)", function () { 74 | db.query("INSERT INTO test (x,y) VALUES (?,?)", [5,10]); 75 | db.query("INSERT INTO test (x,y,z) VALUES ($x,$y,$z)", {$x:1, $y:2, $z:3}); 76 | }); 77 | 78 | db.query("SELECT * FROM test WHERE rowid < ?;", [1]); 79 | 80 | db.query("UPDATE test SET y = 10;", [], function () { 81 | assert(this.rowsAffected == 2); 82 | }); 83 | 84 | db.transaction(function(tx) { 85 | tx.executeSql("SELECT * FROM test WHERE x = ?", [1], function (tx,records) { 86 | for (var i = 0; records.rows.item(i); ++i) 87 | asserteq(records.rows.item(i).z, 3); 88 | }); 89 | }); 90 | 91 | var na = db.query(""); 92 | asserteq(na, null); 93 | 94 | try { 95 | na = db.query("CRAPPY QUERY THAT DOESN'T WORK"); 96 | asserteq("Apples", "Oranges"); 97 | } catch (e) { 98 | } 99 | 100 | db.transaction(function(tx){ 101 | for (var i = 0; i < 3; ++i) 102 | tx.executeSql("INSERT INTO test VALUES (6,6,6)"); 103 | tx.executeSql("ROLLBACK"); 104 | }); 105 | 106 | asserteq(commits, 14); 107 | asserteq(rollbacks, 1); 108 | asserteq(updates, 19); 109 | 110 | 111 | db.close(); 112 | console.log("OK\n"); 113 | 114 | // Perhaps do this, one day 115 | //var q = db.prepare("SELECT * FROM t WHERE rowid=?"); 116 | //var rows = q.execute([1]); 117 | -------------------------------------------------------------------------------- /testloop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Wait for changes to relevant files, then compile and run test. And 3 | # do this forever. 4 | 5 | import os, sys, time 6 | 7 | filenames = ["sqlite3_bindings.cc", "binding.gyp", "sqlite.js", "test.js"] 8 | 9 | mtime = [] 10 | while True: 11 | m = [os.stat(filename).st_mtime for filename in filenames] 12 | if mtime != m: 13 | os.system("clear; rm -f test.db") 14 | os.system("node-gyp build && node test.js && sleep 1 && sqlite3 test.db .dump"); 15 | mtime = m 16 | 17 | time.sleep(1) 18 | 19 | --------------------------------------------------------------------------------