Skip to content

Commit 7b7b844

Browse files
committed
Add 'structured' option support for res.fetchall*() methods, with tests, closes #52
1 parent 4e1354b commit 7b7b844

File tree

5 files changed

+232
-24
lines changed

5 files changed

+232
-24
lines changed

src/mysql_bindings_result.cc

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ int MysqlResult::EIO_After_FetchAll(eio_req *req) {
341341
if (req->result) {
342342
argv[0] = V8EXC("Error on fetching fields");
343343
} else {
344+
MYSQL_FIELD *fields = fetchAll_req->fields;
345+
uint32_t num_fields = fetchAll_req->num_fields;
344346
MYSQL_ROW result_row;
345347
uint32_t i = 0, j = 0;
346348

@@ -356,12 +358,21 @@ int MysqlResult::EIO_After_FetchAll(eio_req *req) {
356358
js_result_row = Object::New();
357359
}
358360

359-
for ( j = 0; j < fetchAll_req->num_fields; j++ ) {
360-
js_field = GetFieldValue(fetchAll_req->fields[j], result_row[j]);
361-
if(fetchAll_req->results_array) {
362-
js_result_row->Set(Integer::New(j), js_field);
361+
for (j = 0; j < num_fields; j++) {
362+
js_field = GetFieldValue(fields[j], result_row[j]);
363+
if (fetchAll_req->results_array) {
364+
js_result_row->Set(Integer::New(j), js_field);
363365
} else {
364-
js_result_row->Set(V8STR(fetchAll_req->fields[j].name), js_field);
366+
if (fetchAll_req->results_structured) {
367+
if (!js_result_row->Has(V8STR(fields[j].table))) {
368+
js_result_row->Set(V8STR(fields[j].table),
369+
Object::New());
370+
}
371+
js_result_row->Get(V8STR(fields[j].table))->ToObject()
372+
->Set(V8STR(fields[j].name), js_field);
373+
} else {
374+
js_result_row->Set(V8STR(fields[j].name), js_field);
375+
}
365376
}
366377
}
367378

@@ -413,7 +424,7 @@ int MysqlResult::EIO_FetchAll(eio_req *req) {
413424
/**
414425
* Fetches all result rows as an array
415426
*
416-
* @param {Boolean|Object} 'results_array' (optional)
427+
* @param {Boolean|Object} options (optional)
417428
* @param {Function(error, rows)} callback
418429
*/
419430
Handle<Value> MysqlResult::FetchAll(const Arguments& args) {
@@ -423,6 +434,7 @@ Handle<Value> MysqlResult::FetchAll(const Arguments& args) {
423434
#else
424435
int arg_pos = 0;
425436
bool results_array = false;
437+
bool results_structured = false;
426438

427439
if (args.Length() > 0) {
428440
if (args[0]->IsBoolean()) {
@@ -433,12 +445,20 @@ Handle<Value> MysqlResult::FetchAll(const Arguments& args) {
433445
results_array = args[0]->ToObject()
434446
->Get(V8STR("array"))->BooleanValue();
435447
}
448+
if (args[0]->ToObject()->Has(V8STR("structured"))) {
449+
results_structured = args[0]->ToObject()
450+
->Get(V8STR("structured"))->BooleanValue();
451+
}
436452
arg_pos++;
437453
}
438-
// NOT here, because any function is object
454+
// NOT here: any function is object
439455
// arg_pos++;
440456
}
441457

458+
if (results_array && results_structured) {
459+
return THREXC("You can't mix 'array' and 'structured' parameters");
460+
}
461+
442462
REQ_FUN_ARG(arg_pos, callback)
443463

444464
MysqlResult *res = OBJUNWRAP<MysqlResult>(args.This()); // NOLINT
@@ -456,6 +476,7 @@ Handle<Value> MysqlResult::FetchAll(const Arguments& args) {
456476
fetchAll_req->callback = Persistent<Function>::New(callback);
457477
fetchAll_req->res = res;
458478
fetchAll_req->results_array = results_array;
479+
fetchAll_req->results_structured = results_structured;
459480

460481
eio_custom(EIO_FetchAll, EIO_PRI_DEFAULT, EIO_After_FetchAll, fetchAll_req);
461482

@@ -469,7 +490,7 @@ Handle<Value> MysqlResult::FetchAll(const Arguments& args) {
469490
/**
470491
* Fetches all result rows as an array
471492
*
472-
* @param {Boolean|Object} 'results_array' (optional)
493+
* @param {Boolean|Object} options (optional)
473494
* @return {Array}
474495
*/
475496
Handle<Value> MysqlResult::FetchAllSync(const Arguments& args) {
@@ -480,6 +501,7 @@ Handle<Value> MysqlResult::FetchAllSync(const Arguments& args) {
480501
MYSQLRES_MUSTBE_VALID;
481502

482503
bool results_array = false;
504+
bool results_structured = false;
483505

484506
if (args.Length() > 0) {
485507
if (args[0]->IsBoolean()) {
@@ -489,9 +511,17 @@ Handle<Value> MysqlResult::FetchAllSync(const Arguments& args) {
489511
results_array = args[0]->ToObject()
490512
->Get(V8STR("array"))->BooleanValue();
491513
}
514+
if (args[0]->ToObject()->Has(V8STR("structured"))) {
515+
results_structured = args[0]->ToObject()
516+
->Get(V8STR("structured"))->BooleanValue();
517+
}
492518
}
493519
}
494520

521+
if (results_array && results_structured) {
522+
return THREXC("You can't mix 'array' and 'structured' parameters");
523+
}
524+
495525
MYSQL_FIELD *fields = mysql_fetch_fields(res->_res);
496526
uint32_t num_fields = mysql_num_fields(res->_res);
497527
MYSQL_ROW result_row;
@@ -503,19 +533,27 @@ Handle<Value> MysqlResult::FetchAllSync(const Arguments& args) {
503533

504534
i = 0;
505535
while ( (result_row = mysql_fetch_row(res->_res)) ) {
506-
if(results_array) {
507-
js_result_row = Array::New();
536+
if (results_array) {
537+
js_result_row = Array::New();
508538
} else {
509-
js_result_row = Object::New();
539+
js_result_row = Object::New();
510540
}
511541

512-
for ( j = 0; j < num_fields; j++ ) {
513-
js_field = GetFieldValue(fields[j], result_row[j]);
514-
if(results_array) {
515-
js_result_row->Set(Integer::New(j), js_field);
516-
} else {
517-
js_result_row->Set(V8STR(fields[j].name), js_field);
518-
}
542+
for (j = 0; j < num_fields; j++) {
543+
js_field = GetFieldValue(fields[j], result_row[j]);
544+
if (results_array) {
545+
js_result_row->Set(Integer::New(j), js_field);
546+
} else {
547+
if (results_structured) {
548+
if (!js_result_row->Has(V8STR(fields[j].table))) {
549+
js_result_row->Set(V8STR(fields[j].table), Object::New());
550+
}
551+
js_result_row->Get(V8STR(fields[j].table))->ToObject()
552+
->Set(V8STR(fields[j].name), js_field);
553+
} else {
554+
js_result_row->Set(V8STR(fields[j].name), js_field);
555+
}
556+
}
519557
}
520558

521559
js_result->Set(Integer::New(i), js_result_row);

src/mysql_bindings_result.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class MysqlResult : public node::EventEmitter {
8585
MYSQL_FIELD *fields;
8686
uint32_t num_fields;
8787
bool results_array;
88+
bool results_structured;
8889
};
8990
static int EIO_After_FetchAll(eio_req *req);
9091
static int EIO_FetchAll(eio_req *req);

tests/complex/setup-complex.js

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,24 @@ var
1212
mysql_libmysqlclient = require("../../mysql-libmysqlclient");
1313

1414
exports.createTestTableComplex = function (test) {
15-
test.expect(3);
15+
test.expect(6);
1616

1717
var
1818
conn = mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
1919
res,
2020
tables;
21-
21+
2222
conn.querySync("DROP TABLE IF EXISTS " + cfg.test_table + ";");
2323
conn.querySync("CREATE TABLE " + cfg.test_table +
2424
" (size ENUM('small', 'medium', 'large')," +
25-
" colors SET('red', 'green', 'blue')) TYPE=MEMORY;");
25+
" colors SET('red', 'green', 'blue')) TYPE=MEMORY;");
2626
res = conn.querySync("SHOW TABLES");
2727
tables = res.fetchAllSync();
2828

2929
test.ok(res.fieldCount === 1, "SHOW TABLES result field count === 1");
3030
test.ok(tables.some(function (r) {
3131
return r['Tables_in_' + cfg.database] === cfg.test_table;
32-
}), "Find the test table in result");
32+
}), "Find the test_table in results");
3333

3434
res = conn.querySync("INSERT INTO " + cfg.test_table +
3535
" (size, colors) VALUES ('small', 'red');");
@@ -41,8 +41,29 @@ exports.createTestTableComplex = function (test) {
4141
" (size, colors) VALUES ('large', 'red,blue');") && res;
4242
test.ok(res, "conn.querySync('INSERT INTO test_table ...')");
4343

44-
conn.closeSync();
44+
conn.querySync("DROP TABLE IF EXISTS " + cfg.test_table2 + ";");
45+
conn.querySync("CREATE TABLE " + cfg.test_table2 +
46+
" (size ENUM('small', 'medium', 'large')," +
47+
" colors VARCHAR(32)) TYPE=MEMORY;");
48+
res = conn.querySync("SHOW TABLES");
49+
tables = res.fetchAllSync();
4550

51+
test.ok(res.fieldCount === 1, "SHOW TABLES result field count === 1");
52+
test.ok(tables.some(function (r) {
53+
return r['Tables_in_' + cfg.database] === cfg.test_table2;
54+
}), "Find the test_table2 in results");
55+
56+
res = conn.querySync("INSERT INTO " + cfg.test_table2 +
57+
" (size, colors) VALUES ('small', 'red');");
58+
res = conn.querySync("INSERT INTO " + cfg.test_table2 +
59+
" (size, colors) VALUES ('small', 'orange');");
60+
res = conn.querySync("INSERT INTO " + cfg.test_table2 +
61+
" (size, colors) VALUES ('medium', 'black');") && res;
62+
res = conn.querySync("INSERT INTO " + cfg.test_table2 +
63+
" (size, colors) VALUES ('large', 'deep purple');") && res;
64+
test.ok(res, "conn.querySync('INSERT INTO test_table ...')");
65+
66+
conn.closeSync();
4667
test.done();
4768
};
4869

tests/complex/test-fetchAll.js

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ exports.FetchAllWithObjectArrayOption = function (test) {
9595
test.ok(err === null, "res.fetchAll() err===null");
9696
test.ok(Array.isArray(rows), "Result returns an array");
9797
test.ok(Array.isArray(rows[0]), "Result returns an array of arrays");
98-
test.same(rows, [['small', ['red']]], "conn.querySync('SELECT ...').fetchAll({'array': true})");
98+
test.same(rows, [['small', ['red']]], "conn.querySync('SELECT ...').fetchAll({'array': true})");
9999
res.freeSync();
100100

101101
conn.closeSync();
@@ -133,3 +133,150 @@ exports.FetchAllSyncWithObjectArrayOption = function (test) {
133133
test.done();
134134
};
135135

136+
exports.FetchAllWithObjectArrayOption = function (test) {
137+
test.expect(9);
138+
139+
var
140+
conn = mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
141+
res;
142+
test.ok(conn, "mysql_libmysqlclient.createConnectionSync(host, user, password, database)");
143+
144+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
145+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
146+
"WHERE t1.size = t2.size AND t1.size != 'medium';");
147+
test.ok(res, "SELECT");
148+
149+
res.fetchAll({'structured': false}, function (err, rows) {
150+
test.ok(err === null, "res.fetchAll() err===null");
151+
test.same(rows,
152+
[ { size: 'small', colors: 'red' }
153+
, { size: 'small', colors: 'orange' }
154+
, { size: 'large', colors: 'deep purple' }
155+
, { size: 'large', colors: 'deep purple' }
156+
] , "conn.querySync('SELECT ...').fetchAllSync({'structured': false})");
157+
res.freeSync();
158+
159+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
160+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
161+
"WHERE t1.size = t2.size AND t1.size != 'medium' AND t1.colors != 'green';");
162+
test.ok(res, "SELECT");
163+
164+
res.fetchAll({'structured': true}, function (err, rows) {
165+
test.ok(err === null, "res.fetchAll() err===null");
166+
test.ok(Array.isArray(rows), "Result returns an array");
167+
test.ok(rows[0] instanceof Object, "Result returns an array of objects");
168+
test.same(rows,
169+
[ { t1: { size: 'small', colors: ['red'] }
170+
, t2: { size: 'small', colors: 'red' }
171+
}
172+
, { t1: { size: 'small', colors: ['red'] }
173+
, t2: { size: 'small', colors: 'orange' }
174+
}
175+
, { t1: { size: 'large', colors: ['red', 'blue'] }
176+
, t2: { size: 'large', colors: 'deep purple' }
177+
}
178+
] , "conn.querySync('SELECT ...').fetchAll({'structured': true})");
179+
res.freeSync();
180+
181+
conn.closeSync();
182+
test.done();
183+
});
184+
});
185+
};
186+
187+
exports.FetchAllSyncWithObjectStructuredOption = function (test) {
188+
test.expect(7);
189+
190+
var
191+
conn = mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
192+
res,
193+
rows;
194+
test.ok(conn, "mysql_libmysqlclient.createConnectionSync(host, user, password, database)");
195+
196+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
197+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
198+
"WHERE t1.size = t2.size AND t1.size != 'medium';");
199+
test.ok(res, "SELECT");
200+
201+
rows = res.fetchAllSync({'structured': false});
202+
test.same(rows,
203+
[ { size: 'small', colors: 'red' }
204+
, { size: 'small', colors: 'orange' }
205+
, { size: 'large', colors: 'deep purple' }
206+
, { size: 'large', colors: 'deep purple' }
207+
], "conn.querySync('SELECT ...').fetchAllSync({'structured': false})");
208+
res.freeSync();
209+
210+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
211+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
212+
"WHERE t1.size = t2.size AND t1.size != 'medium' AND t1.colors != 'green';");
213+
test.ok(res, "SELECT");
214+
215+
rows = res.fetchAllSync({'structured': true});
216+
test.ok(Array.isArray(rows), "Result returns an array");
217+
test.ok(rows[0] instanceof Object, "Result returns an array of objects");
218+
test.same(rows,
219+
[ { t1: { size: 'small', colors: ['red'] }
220+
, t2: { size: 'small', colors: 'red' }
221+
}
222+
, { t1: { size: 'small', colors: ['red'] }
223+
, t2: { size: 'small', colors: 'orange' }
224+
}
225+
, { t1: { size: 'large', colors: ['red', 'blue'] }
226+
, t2: { size: 'large', colors: 'deep purple' }
227+
}
228+
] , "conn.querySync('SELECT ...').fetchAllSync({'structured': true})");
229+
res.freeSync();
230+
231+
conn.closeSync();
232+
test.done();
233+
};
234+
235+
exports.FetchAllWithObjectOptionsConflicted = function (test) {
236+
test.expect(3);
237+
238+
var
239+
conn = mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
240+
res,
241+
rows;
242+
test.ok(conn, "mysql_libmysqlclient.createConnectionSync(host, user, password, database)");
243+
244+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
245+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
246+
"WHERE t1.size = t2.size AND t1.size != 'medium';");
247+
test.ok(res, "SELECT");
248+
249+
test.throws(function () {
250+
res.fetchAll({'array': true, 'structured': true}, function (err, rows) {});
251+
}, Error, "You can't mix 'array' and 'structured' parameters");
252+
253+
res.freeSync();
254+
255+
conn.closeSync();
256+
test.done();
257+
};
258+
259+
exports.FetchAllSyncWithObjectOptionsConflicted = function (test) {
260+
test.expect(3);
261+
262+
var
263+
conn = mysql_libmysqlclient.createConnectionSync(cfg.host, cfg.user, cfg.password, cfg.database),
264+
res,
265+
rows;
266+
test.ok(conn, "mysql_libmysqlclient.createConnectionSync(host, user, password, database)");
267+
268+
res = conn.querySync("SELECT t1.size, t1.colors, t2.size, t2.colors " +
269+
"FROM " + cfg.test_table + " t1, " + cfg.test_table2 + " t2 " +
270+
"WHERE t1.size = t2.size AND t1.size != 'medium';");
271+
test.ok(res, "SELECT");
272+
273+
test.throws(function () {
274+
rows = res.fetchAllSync({'array': true, 'structured': true});
275+
}, Error, "You can't mix 'array' and 'structured' parameters");
276+
277+
res.freeSync();
278+
279+
conn.closeSync();
280+
test.done();
281+
};
282+

tests/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports.cfg = {
1313
database: "test",
1414
database_denied: "mysql",
1515
test_table: "test_table",
16+
test_table2: "test_table2",
1617
test_table_notexists: "test_table_notexists",
1718
charset: "utf8",
1819

0 commit comments

Comments
 (0)