Permalink
Browse files

feat: find with cache in mysql

  • Loading branch information...
XadillaX committed Sep 22, 2016
1 parent d13566a commit 8257a6716a72cfe1b6564efdb9a3409458cd8f21
Showing with 98 additions and 15 deletions.
  1. +6 −6 lib/adapters/mysql.js
  2. +3 −3 lib/model.js
  3. +12 −5 lib/query.js
  4. +75 −0 test/adapters/mysql.js
  5. +2 −1 test/query.js
@@ -435,7 +435,7 @@ class MySQLAdapter extends Adapter {

findWithCache(cache, model, callback, options) {
const self = this;
const primaryKeys = model.primaryKeys;
const primaryKeys = model.primaryKeys.map(field => field.name);
const origFields = _.uniq((options.fields || []).concat(primaryKeys));

// search for primary keys
@@ -444,7 +444,7 @@ class MySQLAdapter extends Adapter {
this.execute(pkSQL, function(err, rows) {
if(err) {
options.fields = origFields;
return callback(err, undefined, this.makeSql("find", model, options));
return callback(err, undefined, self.makeSql("find", model, options));
}

// get data from cache first
@@ -458,12 +458,10 @@ class MySQLAdapter extends Adapter {
function fetchFromMySQL(taskObject) {
const idx = taskObject.task;
const sql = self.makeSql("find", model, {
where: model.columnToName(rows[idx]),
where: model.convertColumnToName(rows[idx]),
limit: [ 0, 1 ]
});

debug(sql);

self.execute(sql, function(err, row) {
if(err) {
errors.push(err);
@@ -479,6 +477,8 @@ class MySQLAdapter extends Adapter {
// insert this uncached data to cache
// and also insert it to result
cache.setData(self.database, model.name, rows[idx], row, function() {
// no matter insert succesfully or not

result[idx] = row;
taskObject.done();
});
@@ -559,7 +559,7 @@ class MySQLAdapter extends Adapter {

find(query, callback, options) {
const _options = this.queryToOptions(query, options);
if(!query.cache) {
if(!query.cache || options.noCache) {
return this.findWithNoCache(query.model, callback, _options);
} else {
return this.findWithCache(query.cache, query.model, callback, _options);
@@ -144,20 +144,20 @@ class ToshihikoModel extends EventEmitter {
return (new Query(this)).findOne(callback, toJSON);
}

columnToName(object) {
convertColumnToName(object) {
if(typeof object === "string") {
return this.columnToName[object];
}

if(Array.isArray(object)) {
return object.map(o => this.columnToName(o));
return object.map(o => this.columnToName[o]);
}

if(typeof object === "object") {
const result = {};
for(let key in object) {
if(!object.hasOwnProperty(key)) continue;
const temp = this.columnToName(key);
const temp = this.convertColumnToName(key);
if(undefined === temp) continue;
result[temp] = object[key];
}
@@ -142,18 +142,24 @@ class ToshihikoQuery {
return this;
}

find(callback, toJSON, single) {
find(callback, toJSON, options) {
if(typeof toJSON === "object") {
options = toJSON;
toJSON = false;
}
options = options || {};

const self = this;
this.adapter.find(this, function(err, row, extra) {
if(single && row) {
if(options.single && row) {
if(row instanceof Yukari && toJSON) {
row = row.toJSON();
} else {
const yukari = new Yukari(self.model, "query");
yukari.fillRowFromSource(row, true);
row = yukari;
}
} else if(!single) {
} else if(!options.single) {
if(row && row.length) {
row = row.map(row => {
if(row instanceof Yukari) return toJSON ? row.toJSON() : row;
@@ -167,12 +173,13 @@ class ToshihikoQuery {

return callback(err, row, extra);
}, {
single: !!single
single: !!options.single,
noCache: !!options.noCache
});
}

findOne(callback, toJSON) {
this.find(callback, toJSON, true);
this.find(callback, toJSON, { single: true });
}
}

@@ -903,6 +903,81 @@ describe("🐣 adapters/mysql", function() {
});
});
});

describe(`${name} findWithCache`, function() {
const toshihiko = new Toshihiko("mysql", correctOptions);
const adapter = toshihiko.adapter;
const model = toshihiko.define("test1", common.COMMON_SCHEMA, {
cache: {
name: "memcached",
servers: [ "localhost:11211" ],
options: { prefix: "findWithCache_" }
}
});

after(function() {
model.cache.memcached.flush(function() {
adapter.mysql.end();
});
});

it("normal 1", function(done) {
const query = new Query(model).fields("key1,key2,key3,key5").order("key1 asc").limit(1);
adapter.findWithCache(model.cache, model, function(err, rows, extra) {
should.ifError(err);
extra.should.equal("SELECT `id`, `key2`, `key3`, `key5` FROM `test1` " +
"ORDER BY `id` ASC LIMIT 1");
rows.should.match([
{ id: 1, key2: 0.5, key3: "{\"foo\":\"bar\"}" }
]);
model.cache.memcached.get("findWithCache___toshihiko__:test1:1", function(err, res) {
should.ifError(err);
res.should.match({ id: 1,
key2: 0.5,
key3: "{\"foo\":\"bar\"}",
key4: null,
key5: rows[0].key5.toISOString(),
key6: "10101000"
});

model.cache.memcached.get("findWithCache___toshihiko__:test1:2", function(err, res) {
should.ifError(err);
should(res).equal(undefined);
done();
});
});
}, adapter.queryToOptions(query, {}));
});

it("normal 2", function(done) {
const query = new Query(model).fields("key1,key2,key3,key5").order("key2 asc").limit(100);

adapter.findWithCache(model.cache, model, function(err, rows, extra) {
should.ifError(err);
extra.should.equal("SELECT `id`, `key2`, `key3`, `key5` FROM `test1` " +
"ORDER BY `key2` ASC LIMIT 100");
rows.should.match([
{ id: 1, key2: 0.5, key3: "{\"foo\":\"bar\"}" },
{ id: 2, key2: 0.5, key3: "{\"foo\":\"bar\"}" },
{ id: 3, key2: 0.5, key3: "{\"foo\":\"bar\"}" },
{ id: 4, key2: 0.5, key3: "{\"foo\":\"bar\"}" }
]);

model.cache.memcached.get("findWithCache___toshihiko__:test1:2", function(err, res) {
should.ifError(err);
res.should.match({
key2: 0.5,
key3: "{\"foo\":\"bar\"}",
key4: "dummy primary",
key5: rows[1].key5.toISOString(),
key6: "100100010111010000000011011001",
id: 2
});
done();
});
}, adapter.queryToOptions(query, {}));
});
});
});
});
});
@@ -243,7 +243,8 @@ describe("🐣 query", function() {
toshihiko.adapter.find = function(_query, callback, options) {
query.should.equal(_query);
options.should.deepEqual({
single: false
single: false,
noCache: false
});
toshihiko.adapter.find = find;
process.nextTick(callback);

0 comments on commit 8257a67

Please sign in to comment.