Skip to content

Commit 50accf3

Browse files
committed
Initial commit
0 parents  commit 50accf3

5 files changed

Lines changed: 269 additions & 0 deletions

File tree

‎.gitignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.*~
2+
*.swp
3+
*.swo
4+
.npm
5+
node_modules

‎README.md‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# mysql-live-select
2+
3+
NPM Package to provide events when a MySQL select statement result set changes.
4+
5+
Built using the [`zongji` Binlog Tailer](https://github.com/nevill/zongji) and [`node-mysql`](https://github.com/felixge/node-mysql) projects.
6+
7+
## Installation
8+
9+
* Enable MySQL binlog in `my.cnf`, restart MySQL server after making the changes.
10+
11+
```
12+
# binlog config
13+
server-id = 1
14+
log_bin = /usr/local/var/log/mysql/mysql-bin.log
15+
binlog_do_db = employees # optional
16+
expire_logs_days = 10 # optional
17+
max_binlog_size = 100M # optional
18+
19+
# Very important if you want to receive write, update and delete row events
20+
binlog_format = row
21+
```
22+
* Create an account with replication privileges:
23+
24+
```sql
25+
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'user'@'localhost'
26+
```
27+
28+
## LiveMysql Constructor
29+
30+
The `LiveMysql` constructor makes 3 connections to your MySQL database:
31+
32+
* Connection for executing `SELECT` queries
33+
* Replication slave connection
34+
* `information_schema` connection for column information
35+
36+
One argument, an object defining the settings. In addition the [`node-mysql` connection settings](#...), the following settings are available:
37+
38+
Setting | Type | Description
39+
--------|------|------------------------------
40+
`serverId` | `integer` | [Unique number (1 - 2<sup>32</sup>)](http://dev.mysql.com/doc/refman/5.0/en/replication-options.html#option_mysqld_server-id) to identify this replication slave instance. Must be specified if running more than one instance.<br>**Default:** `1`
41+
`minInterval` | `integer` | Pass a number of milliseconds to use as the minimum between result set updates. Omit to refresh results on every update.
42+
43+
### LiveMysql.prototype.select(query, triggers)
44+
45+
Argument | Type | Description
46+
---------|------|----------------------------------
47+
`query` | `string` or `function` | `SELECT` SQL statement. See note below about passing function.
48+
`triggers` | `[object]` | Array of objects defining which row changes to update result set
49+
50+
Returns `LiveMysqlSelect` object
51+
52+
#### Function as `query`
53+
54+
A function may be passed as the `query` argument that accepts two arguments.
55+
56+
* The first argument, `esc` is a function that escapes values in the query.
57+
* The second argument, `escId` is a function that escapes identifiers in the query.
58+
59+
#### Trigger options
60+
61+
Name | Type | Description
62+
-----|------|------------------------------
63+
`table` | `string` | Name of table (required)
64+
`database` | `string` | Name of database (optional, default to `database` setting specified on connection)
65+
`condition` | `function` | Evaluate row values (optional)
66+
67+
#### Condition Function
68+
69+
A condition function accepts one or two arguments:
70+
71+
Argument Name | Description
72+
--------------|-----------------------------
73+
`row` | Table row data
74+
`newRow` | New row data (only available on `UPDATE` queries)
75+
76+
Return `true` when the row data meets the condition to update the result set.
77+
78+
#### Example
79+
80+
```javascript
81+
// Example:
82+
var table = 'my_table';
83+
var id = 11;
84+
85+
liveMysqlInstance.select(function(esc, escId){
86+
return (
87+
'select * from ' + escId(table) +
88+
'where `id`=' + esc(id)
89+
);
90+
}, [
91+
table: table,
92+
condition: function(row, newRow){ return row.id === id; }
93+
]).on('update', function(data){
94+
console.log(data);
95+
});
96+
```
97+

‎example.js‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
var LiveMysql = require('./');
2+
3+
var settings = {
4+
host : 'localhost',
5+
user : 'root',
6+
password : 'numtel',
7+
database : 'leaderboard',
8+
serverId : 34,
9+
minInterval : 200
10+
// debug: true
11+
};
12+
13+
var liveConnection = new LiveMysql(settings);
14+
// var result = liveConnection.select(
15+
// 'select * from players',
16+
// [
17+
// { table: 'players' }
18+
// ]
19+
// );
20+
// result.on('update', function(event){
21+
// // do something to update the client
22+
// console.log(event);
23+
// });
24+
25+
liveConnection.select('select score from players where id=11', [
26+
{
27+
table: 'players',
28+
condition: function(row, newRow){ return row.id === 11; }
29+
}
30+
]).on('update', function(event){
31+
console.log(event);
32+
});

‎index.js‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
var ZongJi = require('zongji');
2+
var mysql = require('mysql');
3+
var EventEmitter = require('events').EventEmitter;
4+
var util = require('util');
5+
6+
function LiveMysql(settings){
7+
var self = this;
8+
var zongji = new ZongJi(settings);
9+
var db = mysql.createConnection(settings);
10+
11+
self.settings = settings;
12+
self.zongji = zongji;
13+
self.db = db;
14+
self._select = [];
15+
16+
db.connect();
17+
18+
zongji.on('binlog', function(event) {
19+
if(event.getEventName() === 'tablemap') return;
20+
21+
self._select.forEach(function(select){
22+
if(select.matchRowEvent(event))
23+
select.update();
24+
});
25+
});
26+
27+
zongji.start({
28+
serverId: settings.serverId,
29+
startAtEnd: true,
30+
includeEvents: [ 'tablemap', 'writerows', 'updaterows', 'deleterows' ]
31+
});
32+
}
33+
34+
LiveMysql.prototype.select = function(query, triggers){
35+
var self = this;
36+
var newSelect = new LiveMysqlSelect(query, triggers, this);
37+
self._select.push(newSelect);
38+
return newSelect;
39+
};
40+
41+
function LiveMysqlSelect(query, triggers, base){
42+
if(!query)
43+
throw new Error('query required');
44+
if(!(triggers instanceof Array))
45+
throw new Error('triggers array required');
46+
if(!(base instanceof LiveMysql))
47+
throw new Error('base LiveMysql instance required');
48+
49+
var self = this;
50+
EventEmitter.call(self);
51+
self.query = self._escapeQueryFun(query);
52+
self.triggers = triggers;
53+
self.base = base;
54+
self.lastUpdate = 0;
55+
56+
self.update();
57+
}
58+
59+
util.inherits(LiveMysqlSelect, EventEmitter);
60+
// util.inherits(LiveMysqlSelect, Array);
61+
62+
63+
LiveMysqlSelect.prototype._escapeQueryFun: function(query){
64+
var self = this;
65+
if(typeof query === 'function'){
66+
var escId = self.base.db.escapeId;
67+
var esc = self.base.db.escape.bind(self.base.db);
68+
return query(esc, escId);
69+
}
70+
return query;
71+
};
72+
73+
LiveMysqlSelect.prototype.matchRowEvent = function(event){
74+
var self = this;
75+
var tableMap = event.tableMap[event.tableId];
76+
var eventName = event.getEventName();
77+
var trigger, row;
78+
for(var i = 0; i < self.triggers.length; i++){
79+
trigger = self.triggers[i];
80+
triggerDatabase = trigger.database ||
81+
self.base.settings.database;
82+
if(triggerDatabase === tableMap.parentSchema &&
83+
trigger.table === tableMap.tableName){
84+
if(trigger.condition === undefined){
85+
return true;
86+
}else{
87+
for(var r = 0; r < event.rows.length; r++){
88+
row = event.rows[r];
89+
if(eventName === 'updaterows'){
90+
return trigger.condition.call(self, row.before, row.after);
91+
}else{
92+
// writerows or deleterows
93+
return trigger.condition.call(self, row);
94+
}
95+
}
96+
}
97+
}
98+
}
99+
return false;
100+
};
101+
102+
LiveMysqlSelect.prototype.update = function(){
103+
var self = this;
104+
if(self.base.settings.minInterval === undefined ||
105+
self.lastUpdate + self.base.settings.minInterval < Date.now()){
106+
self.base.db.query(self.query, function(error, rows){
107+
if(error){
108+
self.emit('error', error);
109+
}else{
110+
self.lastUpdate = Date.now();
111+
self.emit('update', rows);
112+
}
113+
});
114+
}
115+
};
116+
117+
module.exports = LiveMysql;

‎package.json‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "mysql-live-select",
3+
"version": "0.0.1",
4+
"description": "Live updating MySQL SELECT statements",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "nodeunit --reporter=minimal test"
8+
},
9+
"keywords": [
10+
"mysql",
11+
"binlog",
12+
"select",
13+
"live",
14+
"update"
15+
],
16+
"author": "Ben Green <ben@latenightsketches.com> (http://github.com/numtel)",
17+
"license": "MIT"
18+
}

0 commit comments

Comments
 (0)