Permalink
Browse files

Initial commit of contents of svn repo. node_sqlite is sqlite3 bindin…

…gs for node.js
  • Loading branch information...
0 parents commit 4093c7842d07b57a08986dc2c130233f1eed490c @grumdrig committed with Eric Fredricksen Dec 4, 2009
Showing with 480 additions and 0 deletions.
  1. +8 −0 .hgignore
  2. +61 −0 README
  3. +26 −0 compiloop.js
  4. +29 −0 compiloop.py
  5. +19 −0 sqlite3.js
  6. +240 −0 sqlite3_bindings.cc
  7. +1 −0 sqlite3_bindings.node
  8. +80 −0 test.js
  9. +16 −0 wscript
8 .hgignore
@@ -0,0 +1,8 @@
+syntax:glob
+
+build
+README.html
+.svn
+test.db
+.lock-wscript
+
61 README
@@ -0,0 +1,61 @@
+Node.js bindings for sqlite3
+============================
+
+Functions
+-------------------
+
+### `new sqlite3.Db(filename)`
+
+Returns an object representing the sqlite3 database with given filename.
+
+### `sqlite3.Db.query(sql [,bindings] [,callback])`
+
+Executes the query `sql`, with variables bound from `bindings`. The
+variables can take the form `?` or `?NNN` where `NNN` is a number, in which
+case `bindings` should be an array of values, or the form `$VVV` where
+`VVV` is an identifier, in which canse `bindings` should be an object
+with keys matching the variable names.
+
+If provided the `callback` is called with an argument for each
+statement in the query. Each argument is an array of objects mapping
+column names to values.
+
+Each callback argument `rows` also has these properties
+
+- **`rows.count`** is the number of rows affected by the query.
+- **`rows.rowid`** is the `ROWID` of the last `INSERT` command
+
+Within the callback, `this` is an array of all such arrays, with a
+`count` property giving the total number of rows affected. That same
+`this` object is returned by `query`.
+
+### `sqlite3.Db.close()`
+
+Closes the database.
+
+
+Example
+--------
+
+ var sqlite3 = require("./sqlite3");
+ var db = new sqlite3.Db("test.db");
+ db.query("INSERT INTO test (column) VALUES ($value)", {$value: 10});
+ db.query("SELECT column FROM test WHERE rowid<?", [5], function (rows) {
+ process.assert(rows[0].column == 10);
+ });
+ db.query("UPDATE test SET column=20; SELECT column FROM test;",
+ function (update, select) {
+ assert(update.count == 1);
+ assert(select[0].column == 20);
+ });
+ db.close();
+
+Build
+-----
+
+`$` **`node-waf build`**
+
+Test
+----
+
+`$` **`node test.js`**
26 compiloop.js
@@ -0,0 +1,26 @@
+var sys = require("sys");
+
+function handler(curr, prev) {
+ sys.puts("Handling");
+ sys.puts("the current mtime is: " + curr.mtime);
+ sys.puts("the previous mtime was: " + prev.mtime);
+ sys.exec("clear;rm -f test.db; node-waf build && node test.js");
+}
+
+sys.puts(JSON.stringify(process.ARGV));
+for (f in process.ARGV) {
+ f = process.ARGV[f]
+ sys.puts("Watching " + f);
+ process.watchFile(f, handler);
+}
+
+//var tcp = require("tcp");
+//var server = tcp.createServer();
+//server.listen(7000, "localhost");
+
+for (;;) {
+ sys.exec("sleep 1").wait();
+}
+
+//var p = new process.Promise();
+//p.wait();
29 compiloop.py
@@ -0,0 +1,29 @@
+#!/usr/bin/python
+# Wait for changes for files specified on the command line, or here in the file
+# Then compile and run test. And do this forever.
+
+import os, sys, time
+
+#filenames = sys.argv[1:]
+filenames = ["sqlite3_bindings.cc", "wscript", "sqlite3.js", "test.js"]
+mdname = "README"
+
+def handler():
+ os.system("clear; rm -f test.db")
+ os.system("node-waf build && node test.js && sqlite3 test.db .dump");
+
+mtime = []
+mdtime = None
+while True:
+ m = [os.stat(filename).st_mtime for filename in filenames]
+ if mtime != m:
+ handler()
+ mtime = m
+
+ m = os.stat(mdname).st_mtime
+ if mdtime != m:
+ os.system("Markdown.pl < %s > %s.html" % (mdname, mdname))
+ mdtime = m
+
+ time.sleep(1)
+
19 sqlite3.js
@@ -0,0 +1,19 @@
+var sys = require("sys");
+var bindings = require("./sqlite3_bindings");
+
+var Db = bindings.Db;
+
+Db.prototype.query = function (sql, bindings, callback) {
+ if (typeof(bindings) == "function") {
+ var tmp = bindings;
+ bindings = callback;
+ callback = tmp;
+ }
+ var result = this.performQuery(sql, bindings);
+ if (typeof(callback) == "function") {
+ callback.apply(result, result);
+ }
+ return result;
+}
+
+exports.Db = Db;
240 sqlite3_bindings.cc
@@ -0,0 +1,240 @@
+#include <sqlite3.h>
+#include <v8.h>
+#include <node.h>
+#include <node_events.h>
+#include <deque>
+
+using namespace v8;
+using namespace node;
+
+class Sqlite3Db : public EventEmitter
+{
+public:
+ static void Init(v8::Handle<Object> target)
+ {
+ HandleScope scope;
+
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ t->Inherit(EventEmitter::constructor_template);
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+
+ NODE_SET_PROTOTYPE_METHOD(t, "performQuery", PerformQuery);
+ NODE_SET_PROTOTYPE_METHOD(t, "close", Close);
+
+ target->Set(v8::String::NewSymbol("Db"), t->GetFunction());
+ }
+
+protected:
+ Sqlite3Db(sqlite3* db) : db_(db) {
+ }
+
+ ~Sqlite3Db() {
+ sqlite3_close(db_);
+ }
+
+ sqlite3* db_;
+
+ operator sqlite3* () { return db_; }
+
+protected:
+ static Handle<Value> New(const Arguments& args)
+ {
+ HandleScope scope;
+
+ if (args.Length() == 0 || !args[0]->IsString()) {
+ return ThrowException(Exception::TypeError(
+ String::New("First argument must be a string")));
+ }
+
+ String::Utf8Value filename(args[0]->ToString());
+ sqlite3* db;
+ int rc = sqlite3_open(*filename, &db);
+ if (rc) {
+ Local<String> err = v8::String::New(sqlite3_errmsg(db));
+ sqlite3_close(db);
+ return ThrowException(Exception::Error(err));
+ }
+ (new Sqlite3Db(db))->Wrap(args.This());
+ return args.This();
+ }
+
+
+ static Handle<Value> PerformQuery(const Arguments& args)
+ {
+ HandleScope scope;
+ Sqlite3Db* db = ObjectWrap::Unwrap<Sqlite3Db>(args.This());
+
+ if (args.Length() == 0 || !args[0]->IsString()) {
+ return ThrowException(Exception::TypeError(
+ String::New("First argument must be a string")));
+ }
+
+ String::Utf8Value usql(args[0]->ToString());
+ const char* sql(*usql);
+
+ int changes = 0;
+ int param = 0;
+
+ std::deque< Handle<Array> > resulting;
+
+ for(;;) {
+
+ sqlite3_stmt* stmt;
+ int rc = sqlite3_prepare_v2(*db, sql, -1, &stmt, &sql);
+ if (!stmt) break;
+ Statement statement(stmt);
+
+ if (args.Length() > 1) {
+ if (args[1]->IsArray()) {
+ Local<Array> a(Array::Cast(*args[1]));
+ int start = param;
+ int stop = start + sqlite3_bind_parameter_count(statement);
+ for (; param < a->Length() && param < stop; ++param) {
+ Local<Value> v = a->Get(Integer::New(param));
+ statement.Bind(param+1-start, v);
+ }
+ } else if (args[1]->IsObject()) {
+ Local<Array> keys(args[1]->ToObject()->GetPropertyNames());
+ for (int k = 0; k < keys->Length(); ++k) {
+ Local<Value> key(keys->Get(Integer::New(k)));
+ statement.Bind(key, args[1]->ToObject()->Get(key));
+ }
+ } else if (args[1]->IsUndefined() || args[1]->IsNull()) {
+ // That's okay
+ } else {
+ return ThrowException(Exception::TypeError(
+ String::New("Second argument invalid")));
+ }
+ }
+
+ std::deque< Handle<Object> > rows;
+
+ for (int r = 0; ; ++r) {
+ int rc = sqlite3_step(statement);
+ if (rc == SQLITE_ROW) {
+ Local<Object> row = Object::New();
+ for (int c = 0; c < sqlite3_column_count(statement); ++c) {
+ Handle<Value> value;
+ switch (sqlite3_column_type(statement, c)) {
+ case SQLITE_INTEGER:
+ value = Integer::New(sqlite3_column_int(statement, c));
+ break;
+ case SQLITE_FLOAT:
+ value = Number::New(sqlite3_column_double(statement, c));
+ break;
+ case SQLITE_TEXT:
+ value = String::New((const char*) sqlite3_column_text(statement, c));
+ break;
+ case SQLITE_NULL:
+ default: // We don't handle any other types just now
+ value = Undefined();
+ break;
+ }
+ row->Set(String::NewSymbol(sqlite3_column_name(statement, c)),
+ value);
+ }
+ rows.push_back(row);
+ } else if (rc == SQLITE_DONE) {
+ break;
+ } else {
+ return ThrowException(Exception::Error(
+ v8::String::New(sqlite3_errmsg(*db))));
+ }
+ }
+
+ changes += sqlite3_changes(*db);
+
+ Local<Array> rosult(Array::New(rows.size()));
+ std::deque< Handle<Object> >::const_iterator ri(rows.begin());
+ for (int r = 0; r < rows.size(); ++r, ++ri)
+ rosult->Set(Integer::New(r), *ri);
+ rosult->Set(String::New("changes"), Integer::New(sqlite3_changes(*db)));
+ rosult->Set(String::New("rowid"),
+ Integer::New(sqlite3_last_insert_rowid(*db)));
+ resulting.push_back(rosult);
+ }
+
+ Local<Array> result(Array::New(0));
+ result->Set(String::New("changes"), Integer::New(changes));
+ result->Set(String::New("rowid"),
+ Integer::New(sqlite3_last_insert_rowid(*db)));
+ std::deque< Handle<Array> >::iterator ri(resulting.begin());
+ for (int r = 0; r < resulting.size(); ++r, ++ri) {
+ result->Set(Integer::New(r), *ri);
+ }
+ return result;
+ }
+
+
+ static Handle<Value> Close (const Arguments& args)
+ {
+ Sqlite3Db* db = ObjectWrap::Unwrap<Sqlite3Db>(args.This());
+ HandleScope scope;
+ db->Close();
+ return Undefined();
+ }
+
+ void Close() {
+ sqlite3_close(db_);
+ db_ = NULL;
+ Detach();
+ }
+
+ class Statement : public EventEmitter
+ {
+ public:
+ Statement(sqlite3_stmt* stmt) : stmt_(stmt) {}
+
+ ~Statement() { sqlite3_finalize(stmt_); }
+
+ operator sqlite3_stmt* () { return stmt_; }
+
+ bool Bind(int index, Handle<Value> value)
+ {
+ HandleScope scope;
+ if (value->IsInt32()) {
+ sqlite3_bind_int(stmt_, index, value->Int32Value());
+ } else if (value->IsNumber()) {
+ sqlite3_bind_double(stmt_, index, value->NumberValue());
+ } else if (value->IsString()) {
+ String::Utf8Value text(value);
+ sqlite3_bind_text(stmt_, index, *text, text.length(), SQLITE_TRANSIENT);
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ bool Bind(Handle<Value> key, Handle<Value> value) {
+ HandleScope scope;
+ String::Utf8Value skey(key);
+ //string x = ":" + key
+ int index = sqlite3_bind_parameter_index(stmt_, *skey);
+ Bind(index, value);
+ }
+
+ Handle<Object> Cast()
+ {
+ HandleScope scope;
+ Local<ObjectTemplate> t(ObjectTemplate::New());
+ t->SetInternalFieldCount(1);
+ Local<Object> thus = t->NewInstance();
+ thus->SetInternalField(0, External::New(this));
+ //Wrap(thus);
+ return thus;
+ }
+
+ protected:
+ sqlite3_stmt* stmt_;
+ };
+
+
+};
+
+
+extern "C" void init (v8::Handle<Object> target)
+{
+ Sqlite3Db::Init(target);
+}
+
1 sqlite3_bindings.node
80 test.js
@@ -0,0 +1,80 @@
+// Test script for node_sqlite
+
+var sys = require("sys");
+var sqlite3 = require("./sqlite3");
+
+var db = new sqlite3.Db('test.db');
+
+db.query("CREATE TABLE egg (a,y,e)");
+db.query("INSERT INTO egg (a) VALUES (1)", function () {
+ process.assert(this.rowid == 1);
+ });
+var i2 = db.query("INSERT INTO egg (a) VALUES (?)", [5]);
+process.assert(i2.rowid == 2);
+db.query("UPDATE egg SET y='Y'; UPDATE egg SET e='E';");
+db.query("UPDATE egg SET y=?; UPDATE egg SET e=? WHERE ROWID=1",
+ ["arm","leg"] );
+db.query("INSERT INTO egg (a,y,e) VALUES (?,?,?)", [1.01, 10e20, -0.0]);
+db.query("INSERT INTO egg (a,y,e) VALUES (?,?,?)", ["one", "two", "three"]);
+
+db.query("SELECT * FROM egg", function (rows) {
+ sys.puts(JSON.stringify(rows));
+ });
+
+db.query("SELECT a FROM egg; SELECT y FROM egg", function (as, ys) {
+ sys.puts("As " + JSON.stringify(as));
+ sys.puts("Ys " + JSON.stringify(ys));
+ });
+
+db.query("SELECT e FROM egg WHERE a = ?", [5], function (rows) {
+ process.assert(rows[0].e == "E");
+ });
+
+db.query("CREATE TABLE test (x,y,z)", function () {
+ db.query("INSERT INTO test (x,y) VALUES (?,?)", [5,10]);
+ db.query("INSERT INTO test (x,y,z) VALUES ($x, $y, $z)", {$x:1, $y:2, $z:3});
+ });
+
+db.query("SELECT * FROM test WHERE rowid < ?;", [1],
+ function (stmt) {
+ sys.puts("rose:");
+ sys.puts(stmt);
+ });
+
+db.query("UPDATE test SET y = 10;", [], function () {
+ sys.puts(this.changes + " rows affected");
+ process.assert(this.changes == 2);
+ });
+
+
+db.close();
+sys.puts("OK\n");
+process.exit()
+
+// Perhaps do this, one day
+//var q = db.prepare("SELECT * FROM t WHERE rowid=?");
+//var rows = q.execute([1]);
+
+// Perhaps use this syntax if query starts returning a promise:
+
+c.query("select * from test;").addCallback(function (rows) {
+ puts("result1:");
+ p(rows);
+});
+
+c.query("select * from test limit 1;").addCallback(function (rows) {
+ puts("result2:");
+ p(rows);
+});
+
+c.query("select ____ from test limit 1;").addCallback(function (rows) {
+ puts("result3:");
+ p(rows);
+}).addErrback(function (e) {
+ puts("error! "+ e.message);
+ puts("full: "+ e.full);
+ puts("severity: "+ e.severity);
+ c.close();
+});
+
+
16 wscript
@@ -0,0 +1,16 @@
+srcdir = "."
+blddir = "build"
+VERSION = "0.0.1"
+
+def set_options(opt):
+ opt.tool_options("compiler_cxx")
+
+def configure(conf):
+ conf.check_tool("compiler_cxx")
+ conf.check_tool("node_addon")
+
+def build(bld):
+ obj = bld.new_task_gen("cxx", "shlib", "node_addon")
+ obj.target = "sqlite3_bindings"
+ obj.source = "sqlite3_bindings.cc"
+ obj.lib = "sqlite3"

0 comments on commit 4093c78

Please sign in to comment.