Skip to content

Commit

Permalink
- Rewrote Data and List modules
Browse files Browse the repository at this point in the history
- Incorporated new modules into read-only query methods
- Rewrote pragma method
- Added limits to sqlite3 connections to prevent segfaults and data corruption
  • Loading branch information
Joshua Wise committed Sep 16, 2016
1 parent f5e3520 commit 3c542c1
Show file tree
Hide file tree
Showing 12 changed files with 144 additions and 273 deletions.
1 change: 1 addition & 0 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
60 changes: 26 additions & 34 deletions src/objects/database/pragma.cc
Original file line number Diff line number Diff line change
@@ -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; i<len; ++i) {
row->values[i] = new Data::Text(Nan::New(strings[i]).ToLocalChecked());
}
return row;
}
typedef struct PragmaInfo {
v8::Local<v8::Value> rows;
bool simple;
bool after_first;
} PragmaInfo;

int PragmaCallback(void* x, int column_count, char** results, char** column_names) {
List<Data::Row>* table = static_cast<List<Data::Row>*>(x);
table[0].Add(PragmaMakeRowOfStrings(column_names, column_count));
table[1].Add(PragmaMakeRowOfStrings(results, column_count));
PragmaInfo* pragma_info = static_cast<PragmaInfo*>(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<v8::Object> row = Nan::New<v8::Object>();
for (int i=0; i<column_count; ++i) {
Nan::Set(row, Nan::New(column_names[i]).ToLocalChecked(), Nan::New(results[i]).ToLocalChecked());
}
v8::Local<v8::Array> rows = v8::Local<v8::Array>::Cast(pragma_info->rows);
Nan::Set(rows, rows->Length(), row);
}

return 0;
}

Expand All @@ -34,35 +43,18 @@ NAN_METHOD(Database::Pragma) {
char* err;

// Executes the SQL on the database handle.
List<Data::Row> table[2] {List<Data::Row>{}, List<Data::Row>{}};
sqlite3_exec(db->db_handle, *utf8, PragmaCallback, table, &err);
PragmaInfo pragma_info = {Nan::New<v8::Array>(), simple_result, false};
sqlite3_exec(db->db_handle, *utf8, PragmaCallback, &pragma_info, &err);
if (err != NULL) {
CONCAT2(message, "SQLite: ", err);
sqlite3_free(err);
return Nan::ThrowError(message);
}
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<v8::Array> arr = Nan::New<v8::Array>();
table[0].Flush([&arr, &i, &table] (Data::Row* keys) {
Data::Row* values = table[1].Shift();
v8::Local<v8::Object> object = Nan::New<v8::Object>();
for (int j=0; j<keys->column_count; ++j) {
Nan::Set(object, v8::Local<v8::String>::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);
}
}
41 changes: 19 additions & 22 deletions src/objects/statement/all.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,32 @@ NAN_METHOD(Statement::All) {
QUERY_START(stmt, statement, STATEMENT_BIND, info, info.Length());

unsigned int row_count = 0;
List<Data::Row> rows;
List<v8::Local<v8::Value>> 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<v8::Array> returned_array = Nan::New<v8::Array>(row_count);
v8::Local<v8::Array> returnedArray = Nan::New<v8::Array>(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<v8::Object> object = Nan::New<v8::Object>();
for (int j=0; j<row->column_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<v8::Value> 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));
Expand Down
20 changes: 6 additions & 14 deletions src/objects/statement/each.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Value> callback_return_value;

// Flush row to callback.
if (stmt->state & PLUCK_COLUMN) {
v8::Local<v8::Value> args[1] = {row.values[0]->ToJS()};
callback_return_value = callback->Call(Nan::Null(), 1, args);
} else {
v8::Local<v8::Object> object = Nan::New<v8::Object>();
for (int i=0; i<row.column_count; ++i) {
Nan::Set(object, NEW_INTERNAL_STRING16(sqlite3_column_name16(stmt->st_handle, i)), row.values[i]->ToJS());
}
v8::Local<v8::Value> 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<v8::Value> callbackValue = stmt->state & PLUCK_COLUMN
? Data::GetValueJS(stmt->st_handle, 0)
: Data::GetRowJS(stmt->st_handle, stmt->column_count);
v8::Local<v8::Value> 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()) {
Expand Down
19 changes: 4 additions & 15 deletions src/objects/statement/get.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Value> 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::Object>();
v8::Local<v8::Object> returned_object = v8::Local<v8::Object>::Cast(returned_value);
for (int i=0; i<row.column_count; ++i) {
Nan::Set(returned_object, NEW_INTERNAL_STRING16(sqlite3_column_name16(stmt->st_handle, i)), row.values[i]->ToJS());
}
}

v8::Local<v8::Value> 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());
Expand Down
10 changes: 5 additions & 5 deletions src/objects/statement/pluck.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
8 changes: 4 additions & 4 deletions src/objects/statement/run.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<v8::Object> returned_object = Nan::New<v8::Object>();
Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New<v8::Number>(static_cast<double>(changes)));
Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id));
QUERY_RETURN(stmt, STATEMENT_CLEAR_BINDINGS, returned_object);
v8::Local<v8::Object> returnedObject = Nan::New<v8::Object>();
Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New<v8::Number>(static_cast<double>(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));
Expand Down
8 changes: 4 additions & 4 deletions src/objects/transaction/run.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ NAN_METHOD(Transaction::Run) {

// Return info object.
sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle);
v8::Local<v8::Object> returned_object = Nan::New<v8::Object>();
Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("changes"), Nan::New<v8::Number>(static_cast<double>(changes)));
Nan::Set(returned_object, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id));
QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returned_object);
v8::Local<v8::Object> returnedObject = Nan::New<v8::Object>();
Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("changes"), Nan::New<v8::Number>(static_cast<double>(changes)));
Nan::Set(returnedObject, NEW_INTERNAL_STRING_FAST("lastInsertROWID"), Int64::NewProperInteger(id));
QUERY_RETURN(trans, TRANSACTION_CLEAR_BINDINGS, returnedObject);
}
58 changes: 58 additions & 0 deletions src/util/data.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <stdint.h>
#include <sqlite3.h>
#include <nan.h>
#include "../objects/int64/int64.h"
#include "../util/macros.h"

namespace Data {

v8::Local<v8::Value> GetIntegerJS(sqlite3_stmt* handle, int column) {
return Int64::NewProperInteger(sqlite3_column_int64(handle, column));
}
v8::Local<v8::Value> GetFloatJS(sqlite3_stmt* handle, int column) {
return Nan::New<v8::Number>(sqlite3_column_double(handle, column));
}
v8::Local<v8::Value> 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<const uint16_t*>(value),
v8::NewStringType::kNormal,
byte_count / sizeof (uint16_t)
).ToLocalChecked();
}
v8::Local<v8::Value> 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<const char*>(value),
byte_count
).ToLocalChecked();
}

v8::Local<v8::Value> 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<v8::Value> GetRowJS(sqlite3_stmt* handle, int column_count) {
v8::Local<v8::Object> row = Nan::New<v8::Object>();
for (int i=0; i<column_count; ++i) {
Nan::Set(row, NEW_INTERNAL_STRING16(sqlite3_column_name16(handle, i)), Data::GetValueJS(handle, i));
}
return row;
}

}
Loading

0 comments on commit 3c542c1

Please sign in to comment.