diff --git a/binding.gyp b/binding.gyp index fb8757882..0e40d731a 100644 --- a/binding.gyp +++ b/binding.gyp @@ -18,6 +18,7 @@ }, "sources": [ "src/objects/int64/int64.cc", + "src/util/data.cc", "src/objects/database/database.cc", "src/objects/statement/statement.cc", "src/objects/transaction/transaction.cc", diff --git a/src/objects/database/pragma.cc b/src/objects/database/pragma.cc index cabbba2db..192dc4be1 100644 --- a/src/objects/database/pragma.cc +++ b/src/objects/database/pragma.cc @@ -1,19 +1,28 @@ // .pragma(string sql, [boolean simpleResult]) -> array -Data::Row* PragmaMakeRowOfStrings(char** strings, int len) { - Data::Row* row = new Data::Row(); - row->column_count = len; - row->values = new Data::Value* [len]; - for (int i=0; ivalues[i] = new Data::Text(Nan::New(strings[i]).ToLocalChecked()); - } - return row; -} +typedef struct PragmaInfo { + v8::Local rows; + bool simple; + bool after_first; +} PragmaInfo; int PragmaCallback(void* x, int column_count, char** results, char** column_names) { - List* table = static_cast*>(x); - table[0].Add(PragmaMakeRowOfStrings(column_names, column_count)); - table[1].Add(PragmaMakeRowOfStrings(results, column_count)); + PragmaInfo* pragma_info = static_cast(x); + + if (pragma_info->simple) { + if (!pragma_info->after_first) { + pragma_info->after_first = true; + pragma_info->rows = Nan::New(results[0]).ToLocalChecked(); + } + } else { + v8::Local row = Nan::New(); + for (int i=0; i rows = v8::Local::Cast(pragma_info->rows); + Nan::Set(rows, rows->Length(), row); + } + return 0; } @@ -34,8 +43,8 @@ NAN_METHOD(Database::Pragma) { char* err; // Executes the SQL on the database handle. - List table[2] {List{}, List{}}; - sqlite3_exec(db->db_handle, *utf8, PragmaCallback, table, &err); + PragmaInfo pragma_info = {Nan::New(), simple_result, false}; + sqlite3_exec(db->db_handle, *utf8, PragmaCallback, &pragma_info, &err); if (err != NULL) { CONCAT2(message, "SQLite: ", err); sqlite3_free(err); @@ -43,26 +52,9 @@ NAN_METHOD(Database::Pragma) { } sqlite3_free(err); - if (simple_result) { - Data::Row* values = table[1].Shift(); - if (values == NULL) { - info.GetReturnValue().Set(Nan::Undefined()); - } else { - info.GetReturnValue().Set(values->values[0]->ToJS()); - } - delete values; + if (simple_result && !pragma_info.after_first) { + info.GetReturnValue().Set(Nan::Undefined()); } else { - unsigned int i = 0; - v8::Local arr = Nan::New(); - table[0].Flush([&arr, &i, &table] (Data::Row* keys) { - Data::Row* values = table[1].Shift(); - v8::Local object = Nan::New(); - for (int j=0; jcolumn_count; ++j) { - Nan::Set(object, v8::Local::Cast(keys->values[j]->ToJS()), values->values[j]->ToJS()); - } - Nan::Set(arr, i++, object); - delete values; - }); - info.GetReturnValue().Set(arr); + info.GetReturnValue().Set(pragma_info.rows); } } diff --git a/src/objects/statement/all.cc b/src/objects/statement/all.cc index 202e34c36..6011f0906 100644 --- a/src/objects/statement/all.cc +++ b/src/objects/statement/all.cc @@ -8,35 +8,32 @@ NAN_METHOD(Statement::All) { QUERY_START(stmt, statement, STATEMENT_BIND, info, info.Length()); unsigned int row_count = 0; - List rows; + List> rows; - while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { - ++row_count; - rows.Add(new Data::Row(stmt->st_handle, stmt->column_count)); + // Get result rows or plucked columns. + if (stmt->state & PLUCK_COLUMN) { + while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { + ++row_count; + rows.Add(Data::GetValueJS(stmt->st_handle, 0)); + } + } else { + while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { + ++row_count; + rows.Add(Data::GetRowJS(stmt->st_handle, stmt->column_count)); + } } + + // Transfer the result list into a JavaScript array. if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { - v8::Local returned_array = Nan::New(row_count); + v8::Local returnedArray = Nan::New(row_count); - // Get array of result rows or plucked columns. if (row_count > 0) { unsigned int i = 0; - if (stmt->state & PLUCK_COLUMN) { - // Fill array with plucked columns. - rows.Flush([&returned_array, &i] (Data::Row* row) { - Nan::Set(returned_array, i++, row->values[0]->ToJS()); - }); - } else { - // Fill array with row objects. - rows.Flush([&stmt, &returned_array, &i] (Data::Row* row) { - v8::Local object = Nan::New(); - for (int j=0; jcolumn_count; ++j) { - Nan::Set(object, NEW_INTERNAL_STRING16(sqlite3_column_name16(stmt->st_handle, j)), row->values[j]->ToJS()); - } - Nan::Set(returned_array, i++, object); - }); - } + rows.Flush([&returnedArray, &i] (v8::Local value) { + Nan::Set(returnedArray, i++, value); + }); } - QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_array); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedArray); } QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); diff --git a/src/objects/statement/each.cc b/src/objects/statement/each.cc index 861581e61..02a0244c2 100644 --- a/src/objects/statement/each.cc +++ b/src/objects/statement/each.cc @@ -9,24 +9,16 @@ NAN_METHOD(Statement::Each) { QUERY_START(stmt, statement, STATEMENT_BIND_T_BUFFERS, info, func_index); stmt->db->in_each = true; - // Retrieve and feed rows. while (sqlite3_step(stmt->st_handle) == SQLITE_ROW) { - Data::Row row(stmt->st_handle, stmt->column_count); v8::MaybeLocal callback_return_value; - // Flush row to callback. - if (stmt->state & PLUCK_COLUMN) { - v8::Local args[1] = {row.values[0]->ToJS()}; - callback_return_value = callback->Call(Nan::Null(), 1, args); - } else { - v8::Local object = Nan::New(); - for (int i=0; ist_handle, i)), row.values[i]->ToJS()); - } - v8::Local args[1] = {object}; - callback_return_value = callback->Call(Nan::Null(), 1, args); - } + // The pluck setting must be within the loop, because it could change in a callback. + v8::Local callbackValue = stmt->state & PLUCK_COLUMN + ? Data::GetValueJS(stmt->st_handle, 0) + : Data::GetRowJS(stmt->st_handle, stmt->column_count); + v8::Local args[1] = {callbackValue}; + callback_return_value = callback->Call(Nan::Null(), 1, args); // If an exception was thrown in the callback, clean up and stop. if (callback_return_value.IsEmpty()) { diff --git a/src/objects/statement/get.cc b/src/objects/statement/get.cc index c851da2ee..8f341d474 100644 --- a/src/objects/statement/get.cc +++ b/src/objects/statement/get.cc @@ -9,22 +9,11 @@ NAN_METHOD(Statement::Get) { int status = sqlite3_step(stmt->st_handle); if (status == SQLITE_ROW) { - Data::Row row(stmt->st_handle, stmt->column_count); - v8::Local returned_value; - - // Get result row, or plucked column. - if (stmt->state & PLUCK_COLUMN) { - returned_value = row.values[0]->ToJS(); - } else { - returned_value = Nan::New(); - v8::Local returned_object = v8::Local::Cast(returned_value); - for (int i=0; ist_handle, i)), row.values[i]->ToJS()); - } - } - + v8::Local returnedValue = stmt->state & PLUCK_COLUMN + ? Data::GetValueJS(stmt->st_handle, 0) + : Data::GetRowJS(stmt->st_handle, stmt->column_count); sqlite3_reset(stmt->st_handle); - QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_value); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedValue); } else if (status == SQLITE_DONE) { sqlite3_reset(stmt->st_handle); QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, Nan::Undefined()); diff --git a/src/objects/statement/pluck.cc b/src/objects/statement/pluck.cc index 5435e145f..d7a13669e 100644 --- a/src/objects/statement/pluck.cc +++ b/src/objects/statement/pluck.cc @@ -5,12 +5,12 @@ NAN_METHOD(Statement::Pluck) { if (stmt->column_count == 0) { return Nan::ThrowTypeError("The pluck() method can only be used by read-only statements."); } - if (stmt->state & CONFIG_LOCKED) { - return Nan::ThrowTypeError("A statement's pluck setting cannot be altered after execution."); - } - stmt->column_count = 1; - stmt->state |= PLUCK_COLUMN; + if (info.Length() == 0 || info[0]->BooleanValue() == true) { + stmt->state |= PLUCK_COLUMN; + } else { + stmt->state &= ~PLUCK_COLUMN; + } info.GetReturnValue().Set(info.This()); } diff --git a/src/objects/statement/run.cc b/src/objects/statement/run.cc index ced9339a5..a6657f156 100644 --- a/src/objects/statement/run.cc +++ b/src/objects/statement/run.cc @@ -14,10 +14,10 @@ NAN_METHOD(Statement::Run) { if (sqlite3_reset(stmt->st_handle) == SQLITE_OK) { int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle); sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); - v8::Local returned_object = Nan::New(); - Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); - Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); - QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_object); + v8::Local returnedObject = Nan::New(); + Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); + Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); + QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returnedObject); } QUERY_THROW(stmt, STATEMENT_CLEAR_BINDINGS, sqlite3_errmsg(stmt->db->db_handle)); diff --git a/src/objects/transaction/run.cc b/src/objects/transaction/run.cc index ee90e3b67..c515bd4b9 100644 --- a/src/objects/transaction/run.cc +++ b/src/objects/transaction/run.cc @@ -43,8 +43,8 @@ NAN_METHOD(Transaction::Run) { // Return info object. sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle); - v8::Local returned_object = Nan::New(); - Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); - Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); - QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returned_object); + v8::Local returnedObject = Nan::New(); + Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New(static_cast(changes))); + Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id)); + QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returnedObject); } diff --git a/src/util/data.cc b/src/util/data.cc new file mode 100644 index 000000000..2988e2ecc --- /dev/null +++ b/src/util/data.cc @@ -0,0 +1,58 @@ +#include +#include +#include +#include "../objects/int64/int64.h" +#include "../util/macros.h" + +namespace Data { + +v8::Local GetIntegerJS(sqlite3_stmt* handle, int column) { + return Int64::NewProperInteger(sqlite3_column_int64(handle, column)); +} +v8::Local GetFloatJS(sqlite3_stmt* handle, int column) { + return Nan::New(sqlite3_column_double(handle, column)); +} +v8::Local GetTextJS(sqlite3_stmt* handle, int column) { + const void* value = sqlite3_column_text16(handle, column); + int byte_count = sqlite3_column_bytes16(handle, column); + return v8::String::NewFromTwoByte( + v8::Isolate::GetCurrent(), + static_cast(value), + v8::NewStringType::kNormal, + byte_count / sizeof (uint16_t) + ).ToLocalChecked(); +} +v8::Local GetBlobJS(sqlite3_stmt* handle, int column) { + const void* value = sqlite3_column_blob(handle, column); + int byte_count = sqlite3_column_bytes(handle, column); + return Nan::CopyBuffer( + static_cast(value), + byte_count + ).ToLocalChecked(); +} + +v8::Local GetValueJS(sqlite3_stmt* handle, int column) { + int type = sqlite3_column_type(handle, column); + switch (type) { + case SQLITE_INTEGER: + return Data::GetIntegerJS(handle, column); + case SQLITE_FLOAT: + return Data::GetFloatJS(handle, column); + case SQLITE_TEXT: + return Data::GetTextJS(handle, column); + case SQLITE_BLOB: + return Data::GetBlobJS(handle, column); + default: // SQLITE_NULL + return Nan::Null(); + } +} + +v8::Local GetRowJS(sqlite3_stmt* handle, int column_count) { + v8::Local row = Nan::New(); + for (int i=0; i -#include -#include #include -#include -#include "../objects/int64/int64.h" namespace Data { -class SimpleExternalString : public v8::String::ExternalStringResource { - public: - explicit SimpleExternalString(const uint16_t* _data, size_t _length) - : v8::String::ExternalStringResource() - , _data(_data) - , _length(_length) {} - ~SimpleExternalString() { - delete[] _data; - } - const uint16_t* data() const {return _data;} - size_t length() const {return _length;} - private: - const uint16_t* _data; - size_t _length; // Number of uint16_t (not number of bytes) -}; - -// A generic class representing an SQLite3 value. -// When a Value is created, all memory is copied, and managed internally. -// Therefore, you must still manage the memory of the originally passed value. -class Value { public: - explicit Value() {} - virtual ~Value() {} - virtual v8::Local ToJS() {return Nan::Undefined();} -}; - -// An SQLite3 integer value. -class Integer : public Data::Value { public: - explicit Integer(sqlite3_int64 n) : value(n) {} - explicit Integer(v8::Local n) : value(Nan::ObjectWrap::Unwrap(n)->GetValue()) {} - v8::Local ToJS() {return Int64::NewProperInteger(value);} - sqlite3_int64 value; -}; - -// An SQLite3 real/float value. -class Float : public Data::Value { public: - explicit Float(double n) : value(n) {} - explicit Float(v8::Local n) : value(n->Value()) {} - v8::Local ToJS() {return Nan::New(value);} - double value; -}; - -// An SQLite3 text value. -// The given constructor argument (const unsigned char*) should be a -// NUL-terminated string, and len should be the number of bytes in the string, -// not including the NUL terminator. -class Text : public Data::Value { public: - explicit Text(const void* str, int byte_count) : length(static_cast(byte_count / sizeof (uint16_t))), transferred(false) { - value = new uint16_t[length]; - memcpy(value, str, byte_count); - } - explicit Text(v8::Local str) { - v8::String::Value utf16(str); - length = static_cast(utf16.length()); - value = new uint16_t[length]; - memcpy(value, *utf16, length * sizeof (uint16_t)); - } - ~Text() {if (!transferred) {delete[] value;}} - v8::Local ToJS() { - transferred = true; - return v8::String::NewExternalTwoByte(v8::Isolate::GetCurrent(), new SimpleExternalString(value, length)).ToLocalChecked(); - } - uint16_t* value; - size_t length; // Number of uint16_t (not number of bytes) - -private: - bool transferred; -}; - -// An SQLite3 blob value. -// The given constructor argument (const void*) is a pointer to the raw bytes to -// include. The len argument is the number of bytes. Invoking ToJS() multiple -// times returns Buffers that all point to the same underlying memory. -class Blob : public Data::Value { public: - explicit Blob(const void* data, int byte_count) : length(byte_count), transferred(false) { - value = new char[length]; - memcpy(value, data, length); - } - explicit Blob(v8::Local buffer) : transferred(false) { - length = node::Buffer::Length(buffer); - value = new char[length]; - memcpy(value, node::Buffer::Data(buffer), length); - } - ~Blob() {if (!transferred) {delete[] value;}} - v8::Local ToJS() { - transferred = true; - return Nan::NewBuffer(value, length).ToLocalChecked(); - } - char* value; - int length; - -private: - bool transferred; -}; - -// An SQLite3 null value. -class Null : public Data::Value { public: - explicit Null() {} - v8::Local ToJS() {return Nan::Null();} -}; - -// A list of SQLite3 values. -// Values that are added to a row are automatically destroyed when the row is -// destroyed. You should NOT manually destroy values that you add to a row. -// Values should only be added by the constructor or by Fill(). It is an error -// to invoke Fill() on a row more than once. The integer passed to Fill() or the -// constructor must never be less than 1. -class Row { - public: - explicit Row() : column_count(0), values(NULL) {} - explicit Row(sqlite3_stmt* handle, int len) { - this->Fill(handle, len); - } - ~Row() { - if (values) { - for (int i=0; i GetIntegerJS(sqlite3_stmt*, int); +v8::Local GetFloatJS(sqlite3_stmt*, int); +v8::Local GetTextJS(sqlite3_stmt*, int); +v8::Local GetBlobJS(sqlite3_stmt*, int); +v8::Local GetValueJS(sqlite3_stmt*, int); +v8::Local GetRowJS(sqlite3_stmt*, int); } diff --git a/src/util/list.h b/src/util/list.h index c21cd626f..f2cf0114c 100644 --- a/src/util/list.h +++ b/src/util/list.h @@ -5,7 +5,7 @@ template class List { private: typedef struct Node { - T* item; + T item; Node* next; } Node; Node* first; @@ -14,20 +14,16 @@ class List { public: explicit List() : first(NULL), last(NULL) {} - // Items that were added to the list are automatically `delete`d. - // This is not appropriate if the item's memory must be freed with - // free() or delete[]. In such cases, List should not be used. ~List() { while (first != NULL) { Node* next = first->next; - delete first->item; delete first; first = next; } } // Pushes an item onto the list. - void Add(T* item) { + void Add(T item) { Node* new_node = new Node; new_node->item = item; new_node->next = NULL; @@ -39,26 +35,13 @@ class List { } } - // Shifts the oldest item off the list. - // You are responsible for deleting it yourself. - T* Shift() { - if (first == NULL) {return NULL;} - T* item = first->item; - Node* next = first->next; - delete first; - first = next; - return item; - } - // Executes a function for each item in the list, and removes them all // from the list. The passed function must not modify the list. // The execution order goes from first-added to last-added. - // Each item is `delete`d after their callback function returns. template void Flush(F fn) { while (first != NULL) { fn(first->item); Node* next = first->next; - delete first->item; delete first; first = next; } diff --git a/src/workers/open.cc b/src/workers/open.cc index 3057dce9d..ff5563c56 100644 --- a/src/workers/open.cc +++ b/src/workers/open.cc @@ -1,10 +1,13 @@ #include +#include #include #include #include "open.h" #include "../objects/database/database.h" #include "../util/macros.h" #include "../util/transaction-handles.h" +const int max_buffer_size = node::Buffer::kMaxLength > 0x7fffffffU ? 0x7fffffff : static_cast(node::Buffer::kMaxLength); +const int max_string_size = v8::String::kMaxLength; OpenWorker::OpenWorker(Database* db, char* filename) : Nan::AsyncWorker(NULL), db(db), @@ -24,6 +27,12 @@ void OpenWorker::Execute() { assert(sqlite3_db_mutex(db->db_handle) == NULL); sqlite3_busy_timeout(db->db_handle, 5000); + sqlite3_limit(db->db_handle, SQLITE_LIMIT_LENGTH, std::min(max_buffer_size, max_string_size)); + sqlite3_limit(db->db_handle, SQLITE_LIMIT_SQL_LENGTH, max_string_size); + sqlite3_limit(db->db_handle, SQLITE_LIMIT_COLUMN, 0x7fffffff); + sqlite3_limit(db->db_handle, SQLITE_LIMIT_COMPOUND_SELECT, 0x7fffffff); + sqlite3_limit(db->db_handle, SQLITE_LIMIT_VARIABLE_NUMBER, 0x7fffffff); + db->t_handles = new TransactionHandles(db->db_handle, &status); if (status != SQLITE_OK) {