diff --git a/deps/common-sqlite.gypi b/deps/common-sqlite.gypi index 1148aa6fe..c897b24ea 100644 --- a/deps/common-sqlite.gypi +++ b/deps/common-sqlite.gypi @@ -1,6 +1,6 @@ { 'variables': { - 'sqlite_version%':'3090100', + 'sqlite_version%':'3140100', "toolset%":'', }, 'target_defaults': { diff --git a/deps/sqlite-autoconf-3140100.tar.gz b/deps/sqlite-autoconf-3140100.tar.gz new file mode 100644 index 000000000..3cd6b9b45 Binary files /dev/null and b/deps/sqlite-autoconf-3140100.tar.gz differ diff --git a/deps/sqlite3.gyp b/deps/sqlite3.gyp index 45a642308..f7da66d3f 100755 --- a/deps/sqlite3.gyp +++ b/deps/sqlite3.gyp @@ -81,7 +81,8 @@ 'SQLITE_THREADSAFE=1', 'SQLITE_ENABLE_FTS3', 'SQLITE_ENABLE_JSON1', - 'SQLITE_ENABLE_RTREE' + 'SQLITE_ENABLE_RTREE', + 'SQLITE_ENABLE_PREUPDATE_HOOK' ], }, 'cflags_cc': [ @@ -93,7 +94,8 @@ 'SQLITE_THREADSAFE=1', 'SQLITE_ENABLE_FTS3', 'SQLITE_ENABLE_JSON1', - 'SQLITE_ENABLE_RTREE' + 'SQLITE_ENABLE_RTREE', + 'SQLITE_ENABLE_PREUPDATE_HOOK' ], 'export_dependent_settings': [ 'action_before_build', diff --git a/lib/sqlite3.js b/lib/sqlite3.js index 466b90230..f908d834b 100644 --- a/lib/sqlite3.js +++ b/lib/sqlite3.js @@ -126,7 +126,7 @@ Statement.prototype.map = function() { var isVerbose = false; -var supportedEvents = [ 'trace', 'profile', 'insert', 'update', 'delete' ]; +var supportedEvents = [ 'trace', 'profile', 'insert', 'update', 'delete', 'preupdate' ]; Database.prototype.addListener = Database.prototype.on = function(type) { var val = EventEmitter.prototype.addListener.apply(this, arguments); @@ -138,7 +138,7 @@ Database.prototype.addListener = Database.prototype.on = function(type) { Database.prototype.removeListener = function(type) { var val = EventEmitter.prototype.removeListener.apply(this, arguments); - if (supportedEvents.indexOf(type) >= 0 && !this._events[type]) { + if (supportedEvents.indexOf(type) >= 0) { this.configure(type, false); } return val; diff --git a/package.json b/package.json index 77d890f16..e12aef497 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sqlite3", "description": "Asynchronous, non-blocking SQLite3 bindings", - "version": "3.1.4", + "version": "3.1.4-custom-fork", "homepage": "http://github.com/mapbox/node-sqlite3", "author": { "name": "MapBox", diff --git a/src/database.cc b/src/database.cc index e7891cbd2..e5295b46d 100644 --- a/src/database.cc +++ b/src/database.cc @@ -335,6 +335,11 @@ NAN_METHOD(Database::Configure) { Baton* baton = new Baton(db, handle); db->Schedule(RegisterProfileCallback, baton); } + else if(Nan::Equals(info[0], Nan::New("preupdate").ToLocalChecked()).FromJust()) { + Local handle; + Baton* baton = new Baton(db, handle); + db->Schedule(RegisterPreUpdateCallback, baton); + } else if (Nan::Equals(info[0], Nan::New("busyTimeout").ToLocalChecked()).FromJust()) { if (!info[1]->IsInt32()) { return Nan::ThrowTypeError("Value must be an integer"); @@ -460,6 +465,151 @@ void Database::ProfileCallback(Database *db, ProfileInfo* info) { delete info; } +static void addRowValue(Row *row, sqlite3_value *val) { + int type = sqlite3_value_type(val); + const char *name = "placeholder"; + + switch (type) { + case SQLITE_INTEGER: { + row->push_back(new Values::Integer(name, sqlite3_value_int(val))); + } break; + case SQLITE_FLOAT: { + row->push_back(new Values::Float(name, sqlite3_value_double(val))); + } break; + case SQLITE_TEXT: { + const char* text = (const char*)sqlite3_value_text(val); + int length = sqlite3_value_bytes(val); + row->push_back(new Values::Text(name, length, text)); + } break; + case SQLITE_BLOB: { + const void* blob = sqlite3_value_blob(val); + int length = sqlite3_value_bytes(val); + row->push_back(new Values::Blob(name, length, blob)); + } break; + case SQLITE_NULL: { + row->push_back(new Values::Null(name)); + } break; + default: + assert(false); + } + +} + +Local rowToArray(Row *row) { + Nan::EscapableHandleScope scope; + + if(row == NULL) { + return Nan::Null(); + } + + Local arr(Nan::New(row->size())); + Row::const_iterator it = row->begin(); + Row::const_iterator end = row->end(); + for (int i = 0; it < end; ++it, i++) { + Values::Field* field = *it; + Local value; + + switch (field->type) { + case SQLITE_INTEGER: { + value = Nan::New(((Values::Integer*)field)->value); + } break; + case SQLITE_FLOAT: { + value = Nan::New(((Values::Float*)field)->value); + } break; + case SQLITE_TEXT: { + value = Nan::New(((Values::Text*)field)->value.c_str(), ((Values::Text*)field)->value.size()).ToLocalChecked(); + } break; + case SQLITE_BLOB: { + value = Nan::CopyBuffer(((Values::Blob*)field)->value, ((Values::Blob*)field)->length).ToLocalChecked(); + } break; + case SQLITE_NULL: { + value = Nan::Null(); + } break; + } + + Nan::Set(arr, i, value); + + DELETE_FIELD(field); + } + + return scope.Escape(arr); +} + +void Database::RegisterPreUpdateCallback(Baton* baton) { + assert(baton->db->open); + assert(baton->db->_handle); + Database* db = baton->db; + + if (db->preupdate_event == NULL) { + // Add it. + db->preupdate_event = new AsyncPreUpdate(db, PreUpdateCallback); + sqlite3_preupdate_hook(db->_handle, PreUpdateCallback, db); + } + else { + // Remove it. + sqlite3_preupdate_hook(db->_handle, NULL, NULL); + db->preupdate_event->finish(); + db->preupdate_event = NULL; + } + + delete baton; +} + +void Database::PreUpdateCallback(void* db_, sqlite3 *handle, int type, const char* database, + const char* table, sqlite3_int64 key1, sqlite3_int64 key2) { + Database* db = static_cast(db_); + + // Note: This function is called in the thread pool. + PreUpdateInfo* info = new PreUpdateInfo(); + info->type = type; + info->database = std::string(database); + info->table = std::string(table); + + int count = sqlite3_preupdate_count(db->_handle); + + Row *oldValues = (type != SQLITE_INSERT) ? new Row() : NULL; + Row *newValues = (type != SQLITE_DELETE) ? new Row() : NULL; + for(int i=0; i_handle, i, &old); + addRowValue(oldValues, old); + } + + if(type != SQLITE_DELETE) { + sqlite3_value *new_; + sqlite3_preupdate_new(db->_handle, i, &new_); + addRowValue(newValues, new_); + } + } + + info->oldValues = oldValues; + info->newValues = newValues; + info->oldRowId = key1; + info->newRowId = key2; + + static_cast(db)->preupdate_event->send(info); +} + +void Database::PreUpdateCallback(Database *db, PreUpdateInfo* info) { + Nan::HandleScope scope; + + Local argv[] = { + Nan::New("preupdate").ToLocalChecked(), + Nan::New(sqlite_authorizer_string(info->type)).ToLocalChecked(), + Nan::New(info->database.c_str()).ToLocalChecked(), + Nan::New(info->table.c_str()).ToLocalChecked(), + rowToArray(info->oldValues), + rowToArray(info->newValues), + Nan::New(info->oldRowId), + Nan::New(info->newRowId) + }; + EMIT_EVENT(db->handle(), 8, argv); + delete info->oldValues; + delete info->newValues; + delete info; +} + void Database::RegisterUpdateCallback(Baton* baton) { assert(baton->db->open); assert(baton->db->_handle); @@ -686,4 +836,8 @@ void Database::RemoveCallbacks() { debug_profile->finish(); debug_profile = NULL; } + if (preupdate_event) { + preupdate_event->finish(); + preupdate_event = NULL; + } } diff --git a/src/database.h b/src/database.h index c5455abe3..d7feaac29 100644 --- a/src/database.h +++ b/src/database.h @@ -15,6 +15,57 @@ using namespace v8; namespace node_sqlite3 { +namespace Values { + struct Field { + inline Field(unsigned short _index, unsigned short _type = SQLITE_NULL) : + type(_type), index(_index) {} + inline Field(const char* _name, unsigned short _type = SQLITE_NULL) : + type(_type), index(0), name(_name) {} + + unsigned short type; + unsigned short index; + std::string name; + }; + + struct Integer : Field { + template inline Integer(T _name, int64_t val) : + Field(_name, SQLITE_INTEGER), value(val) {} + int64_t value; + }; + + struct Float : Field { + template inline Float(T _name, double val) : + Field(_name, SQLITE_FLOAT), value(val) {} + double value; + }; + + struct Text : Field { + template inline Text(T _name, size_t len, const char* val) : + Field(_name, SQLITE_TEXT), value(val, len) {} + std::string value; + }; + + struct Blob : Field { + template inline Blob(T _name, size_t len, const void* val) : + Field(_name, SQLITE_BLOB), length(len) { + value = (char*)malloc(len); + memcpy(value, val, len); + } + inline ~Blob() { + free(value); + } + int length; + char* value; + }; + + typedef Field Null; +} + +typedef std::vector Row; +typedef std::vector Rows; +typedef Row Parameters; + + class Database; @@ -90,12 +141,23 @@ class Database : public Nan::ObjectWrap { sqlite3_int64 rowid; }; + struct PreUpdateInfo { + int type; + std::string database; + std::string table; + sqlite3_int64 oldRowId; + sqlite3_int64 newRowId; + Row* oldValues; + Row* newValues; + }; + bool IsOpen() { return open; } bool IsLocked() { return locked; } typedef Async AsyncTrace; typedef Async AsyncProfile; typedef Async AsyncUpdate; + typedef Async AsyncPreUpdate; friend class Statement; @@ -109,7 +171,8 @@ class Database : public Nan::ObjectWrap { serialize(false), debug_trace(NULL), debug_profile(NULL), - update_event(NULL) { + update_event(NULL), + preupdate_event(NULL) { } ~Database() { @@ -168,6 +231,11 @@ class Database : public Nan::ObjectWrap { static void UpdateCallback(void* db, int type, const char* database, const char* table, sqlite3_int64 rowid); static void UpdateCallback(Database* db, UpdateInfo* info); + static void RegisterPreUpdateCallback(Baton* baton); + static void PreUpdateCallback(void* db, sqlite3 *handle, int type, const char* database, + const char* table, sqlite3_int64 key1, sqlite3_int64 key2); + static void PreUpdateCallback(Database* db, PreUpdateInfo* info); + void RemoveCallbacks(); protected: @@ -185,6 +253,7 @@ class Database : public Nan::ObjectWrap { AsyncTrace* debug_trace; AsyncProfile* debug_profile; AsyncUpdate* update_event; + AsyncPreUpdate* preupdate_event; }; } diff --git a/src/statement.h b/src/statement.h index 90d295b70..a298c4398 100644 --- a/src/statement.h +++ b/src/statement.h @@ -19,58 +19,6 @@ using namespace node; namespace node_sqlite3 { -namespace Values { - struct Field { - inline Field(unsigned short _index, unsigned short _type = SQLITE_NULL) : - type(_type), index(_index) {} - inline Field(const char* _name, unsigned short _type = SQLITE_NULL) : - type(_type), index(0), name(_name) {} - - unsigned short type; - unsigned short index; - std::string name; - }; - - struct Integer : Field { - template inline Integer(T _name, int64_t val) : - Field(_name, SQLITE_INTEGER), value(val) {} - int64_t value; - }; - - struct Float : Field { - template inline Float(T _name, double val) : - Field(_name, SQLITE_FLOAT), value(val) {} - double value; - }; - - struct Text : Field { - template inline Text(T _name, size_t len, const char* val) : - Field(_name, SQLITE_TEXT), value(val, len) {} - std::string value; - }; - - struct Blob : Field { - template inline Blob(T _name, size_t len, const void* val) : - Field(_name, SQLITE_BLOB), length(len) { - value = (char*)malloc(len); - memcpy(value, val, len); - } - inline ~Blob() { - free(value); - } - int length; - char* value; - }; - - typedef Field Null; -} - -typedef std::vector Row; -typedef std::vector Rows; -typedef Row Parameters; - - - class Statement : public Nan::ObjectWrap { public: static Nan::Persistent constructor_template;