Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

mysql-live-select

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mysql-live-select - npm Package Compare versions

Comparing version 0.0.24 to 1.0.0

lib/differ.js

12

example.js

@@ -25,6 +25,14 @@ /* mysql-live-select, MIT License ben@latenightsketches.com

table: table,
condition: function(row, newRow){ return row.id === id; }
} ]).on('update', function(data){
condition: function(row, newRow){
// Only refresh the results when the row matching the specified id is
// changed.
return row.id === id
// On UPDATE queries, newRow must be checked as well
|| (newRow && newRow.id === id);
}
} ]).on('update', function(diff, data){
// diff contains an object describing the difference since the previous update
// data contains an array of rows of the new result set
console.log(data);
});

23

lib/LiveMysql.js

@@ -38,11 +38,8 @@ /* mysql-live-select, MIT License ben@latenightsketches.com

for(var query in self._queryCache){
if (!self._queryCache.hasOwnProperty(query)){
continue;
Object.keys(self._queryCache).forEach(function(query) {
var curCache = self._queryCache[query];
if(curCache.updateTimeout === null && curCache.matchRowEvent(event)){
curCache.invalidate();
}
var queryCache = self._queryCache[query];
if(!queryCache.canSkipRowEvent() && queryCache.matchRowEvent(event)){
queryCache.invalidate();
}
}
});
})

@@ -153,8 +150,5 @@

// Update all select statements
for(var query in self._queryCache){
if (!self._queryCache.hasOwnProperty(query)){
continue;
}
Object.keys(self._queryCache).forEach(function(query) {
self._queryCache[query].invalidate();
}
});
};

@@ -171,2 +165,5 @@

// Expose diff apply function statically
LiveMysql.applyDiff = require('./differ').apply;
module.exports = LiveMysql;

@@ -5,2 +5,3 @@ /* mysql-live-select, MIT License ben@latenightsketches.com

var util = require('util');
var _ = require('lodash');

@@ -34,17 +35,12 @@ function LiveMysqlSelect(queryCache, triggers, base){

self.emit('update', queryCache.data);
// Initial diff is the currently cached data with _index keys as well
var diffAdded = queryCache.data.map(function(row, index) {
var modifiedRow = _.clone(row);
row._index = index + 1;
return row;
});
if(queryCache.data.length !== 0 && !self.base.settings.skipDiff){
var diff = queryCache.data.map(function(row, index) {
return [ 'added', row, index ];
});
diff.forEach(function(evt){
self.emit.apply(self, evt);
// New row added to end
self.data[evt[2]] = evt[1];
});
// Output all difference events in a single event
self.emit('diff', diff);
}
self.emit('update',
{ removed: null, moved: null, copied: null, added: diffAdded },
queryCache.data);
}, 50);

@@ -51,0 +47,0 @@ }else{

@@ -1,8 +0,15 @@

/* mysql-live-select, MIT License ben@latenightsketches.com, wj32.64@gmail.com
lib/QueryCache.js - Query Results Cache Class */
/* mysql-live-select, MIT License ben@latenightsketches.com, wj32.64@gmail.com
lib/QueryCache.js - Query Results Cache Class
// Many LiveMysqlSelect objects can share the same query cache if they have the
// same query string.
Many LiveMysqlSelect objects can share the same query cache if they have the
same query string.
function QueryCache(query, base){
*/
var _ = require('lodash');
var md5 = require('md5');
var differ = require('./differ');
function QueryCache(query, base) {
if(!query)

@@ -18,9 +25,13 @@ throw new Error('query required');

self.data = [];
self.oldHashes = [];
self.selects = [];
self.initialized = false;
self.updateTimeout = null;
}
QueryCache.prototype._setDataOnSelects = function(){
QueryCache.prototype.setData = function(rows) {
var self = this;
for(var i = 0; i < self.selects.length; i++){
self.data = rows;
for(var i = 0; i < self.selects.length; i++) {
self.selects[i].data = self.data;

@@ -30,126 +41,88 @@ }

QueryCache.prototype._emitOnSelects = function(name, arg){
QueryCache.prototype._emitOnSelects = function(/* arguments */) {
var self = this;
for(var i = 0; i < self.selects.length; i++){
self.selects[i].emit(name, arg);
}
};
QueryCache.prototype._emitApplyOnSelects = function(evt){
var self = this;
for(var i = 0; i < self.selects.length; i++){
var select = self.selects[i];
select.emit.apply(select, evt);
select.emit.apply(select, arguments);
}
};
QueryCache.prototype._setRows = function(rows){
QueryCache.prototype.matchRowEvent = function(event) {
var self = this;
var diff = [];
// Determine what changes before updating cache in order to
// be able to skip all event emissions if no change
// TODO update this algorithm to use less data
rows.forEach(function(row, index){
if(self.data.length - 1 < index){
diff.push([ 'added', row, index ]);
}else if(JSON.stringify(self.data[index]) !== JSON.stringify(row)){
diff.push([ 'changed', self.data[index], row, index ]);
}
});
if(self.data.length > rows.length){
for(var i = self.data.length - 1; i >= rows.length; i--){
diff.push([ 'removed', self.data[i], i ]);
}
for(var i = 0; i < self.selects.length; i++) {
if(self.selects[i].matchRowEvent(event)) return true;
}
if(diff.length !== 0){
self._emitOnSelects('update', rows);
// Make sure the relevant select objects have the right data array.
self._setDataOnSelects();
diff.forEach(function(evt){
if(!self.base.settings.skipDiff){
self._emitApplyOnSelects(evt);
}
switch(evt[0]){
case 'added':
// New row added to end
self.data[evt[2]] = evt[1];
break;
case 'changed':
// Update row data reference
self.data[evt[3]] = evt[2];
break;
case 'removed':
// Remove extra rows off the end
self.data.splice(evt[2], 1);
break;
}
});
if(!self.base.settings.skipDiff){
// Output all difference events in a single event
self._emitOnSelects('diff', diff);
}
}else if(self.initialized === false){
// If the result set initializes to 0 rows, it still needs to output an
// update event.
self._emitOnSelects('update', rows);
}
self.initialized = true;
self.lastUpdate = Date.now();
};
QueryCache.prototype.matchRowEvent = function(event){
var self = this;
for(var i = 0; i < self.selects.length; i++){
var select = self.selects[i];
if (select.matchRowEvent(event)){
return true;
}
}
return false;
};
QueryCache.prototype.canSkipRowEvent = function(){
QueryCache.prototype.invalidate = function() {
var self = this;
return self.base.settings.canSkipRowEvents && self._updateTimeout !== undefined;
};
QueryCache.prototype.invalidate = function(){
var self = this;
function update(){
function update() {
// Refuse to send more than one query out at a time. Note that the code
// below always sets needUpdate to true; when the current query finishes
// below always sets needUpdate to false; when the current query finishes
// running we will check needUpdate again and re-run the query if necessary.
if(self.updating){
if(self.updating) {
self.needUpdate = true;
return;
}
self.updating = true;
self.needUpdate = false;
self.base.db.query(self.query, function(error, rows){
// Perform the update
self.base.db.query(self.query, function(error, rows) {
self.updating = false;
if(error){
self._emitOnSelects('error', error);
}else{
self._setRows(rows);
if(self.needUpdate){
update();
if(error) return self._emitOnSelects('error', error);
if(rows.length === 0 && self.initialized === false) {
// If the result set initializes to 0 rows, it still needs to output an
// update event.
self._emitOnSelects('update',
{ removed: null, moved: null, copied: null, added: [] },
[]
);
} else {
// Perform deep clone of new data to be modified for the differ
var rowsForDiff = _.clone(rows, true);
var newHashes = rows.map(function(row, index) {
var hash = md5(JSON.stringify(row));
// Provide the differ with the necessary details
rowsForDiff[index]._hash = hash;
rowsForDiff[index]._index = index + 1;
return hash;
});
var diff =
filterHashProperties(differ.generate(self.oldHashes, rowsForDiff));
if(diff !== null) {
self._emitOnSelects('update', diff, rows);
// Now that event has been emitted, the new becomes the old
self.oldHashes = newHashes;
}
}
self.setData(rows);
self.initialized = true;
self.lastUpdate = Date.now();
if(self.needUpdate) update();
});
}
if(self.base.settings.minInterval === undefined){
if(typeof self.base.settings.minInterval !== 'number') {
update();
}else if(self.lastUpdate + self.base.settings.minInterval < Date.now()){
} else if(self.lastUpdate + self.base.settings.minInterval < Date.now()) {
update();
}else{ // Before minInterval
if(!self._updateTimeout){
self._updateTimeout = setTimeout(function(){
delete self._updateTimeout;
} else { // Before minInterval
if(self.updateTimeout === null){
self.updateTimeout = setTimeout(function(){
self.updateTimeout = null;
update();

@@ -162,1 +135,18 @@ }, self.lastUpdate + self.base.settings.minInterval - Date.now());

module.exports = QueryCache;
function filterHashProperties(diff, alsoIndex) {
if(diff instanceof Array) {
var omitKeys = [ '_hash' ];
if(alsoIndex) omitKeys.push('_index');
return diff.map(function(event) {
return _.omit(event, omitKeys)
});
}
// Otherwise, diff is object with arrays for keys
_.forOwn(diff, function(rows, key) {
diff[key] = filterHashProperties(rows, alsoIndex)
});
return diff;
}
{
"name": "mysql-live-select",
"version": "0.0.24",
"version": "1.0.0",
"description": "Live updating MySQL SELECT statements",

@@ -20,2 +20,4 @@ "main": "lib/LiveMysql.js",

"dependencies": {
"lodash": "^3.10.1",
"md5": "^2.0.0",
"mysql": "^2.8.0",

@@ -22,0 +24,0 @@ "zongji": "^0.3.2"

@@ -33,3 +33,3 @@ # mysql-live-select [![Build Status](https://travis-ci.org/numtel/mysql-live-select.svg?branch=master)](https://travis-ci.org/numtel/mysql-live-select)

```sql
GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'user'@'localhost'
GRANT REPLICATION SLAVE, REPLICATION CLIENT, SELECT ON *.* TO 'user'@'localhost'
```

@@ -55,3 +55,2 @@

`minInterval` | `integer` | Pass a number of milliseconds to use as the minimum between result set updates. Omit to refresh results on every update. May be changed at runtime.
`skipDiff` | `boolean` | If `true`, the `added`, `changed`, and `removed` events will not be emitted. May be changed at runtime.<br>**Default:** `false`

@@ -71,4 +70,12 @@ ```javascript

table: table,
condition: function(row, newRow){ return row.id === id; }
} ]).on('update', function(data){
condition: function(row, newRow){
// Only refresh the results when the row matching the specified id is
// changed.
return row.id === id
// On UPDATE queries, newRow must be checked as well
|| (newRow && newRow.id === id);
}
} ]).on('update', function(diff, data){
// diff contains an object describing the difference since the previous update
// data contains an array of rows of the new result set
console.log(data);

@@ -128,2 +135,6 @@ });

### LiveMysql.applyDiff(data, diff)
Exposed statically on the LiveMysql object is a function for applying a `diff` given in an `update` event to an array of rows given in the `data` argument.
## LiveMysqlSelect object

@@ -146,7 +157,3 @@

-----------|-----------|---------------------------
`update` | `rows` | Single argument contains complete result set array. Called before `added`, `changed`, and `removed` events.
`added` | `row`, `index` | Row added to result set at index
`changed` | `row`, `newRow`, `index` | Row contents mutated at index
`removed` | `row`, `index` | Row removed at index
`diff` | `diff` | Aggregation of `added`, `changed`, `removed` events for current event into a single array for easier handling of multiple changes
`update` | `diff`, `data` | First argument contains an object describing the difference since the previous `update` event with `added`, `removed`, `moved`, and `copied` rows. Second argument contains complete result set array.
`error` | `error` | Unhandled errors will be thrown

@@ -153,0 +160,0 @@

@@ -67,7 +67,18 @@ /* mysql-live-select, MIT License ben@latenightsketches.com

conn.select(query, triggers).on('update', function(data){
conn.select(query, triggers).on('update', function(diff, data) {
// After initial update
if(data.length > 0 && data[0].col === 10){
// Second select instance to check resultsBuffer
conn.select(query, triggersSimple).on('update', function(data){
var secondInstanceInitialized = false;
conn.select(query, triggersSimple).on('update', function(diff, data) {
if(secondInstanceInitialized === false) {
// Check that the diff is correct when initializing from cache
test.deepEqual(diff,
{ removed: null,
moved: null,
copied: null,
added: [ { col: 10, _index: 1 } ] });
secondInstanceInitialized = true;
}
if(data.length > 0 && data[0].col === 15){

@@ -81,43 +92,43 @@ // [1] Test in LiveMysqlSelect created later,

}
}).on('added', function(row, index){
test.equal(index, 0);
test.equal(row.col, 10);
}).on('changed', function(row, newRow, index){
test.equal(index, 0);
test.equal(row.col, 10);
test.equal(newRow.col, 15);
}).on('removed', function(row, index){
test.equal(index, 0);
test.equal(row.col, 15);
}).on('diff', function(diff){
// Only one row will change at once
test.equal(diff.length, 1);
// Index will always be 0, the first item
test.equal(diff[0][diff[0].length - 1], 0);
switch(diff[0][0]){
case 'added':
test.equal(diff[0][1].col, 10);
break;
case 'changed':
test.equal(diff[0][1].col, 10);
test.equal(diff[0][2].col, 15);
break;
case 'added':
test.equal(diff[0][1].col, 15);
break;
}
});
}
switch(conditionCheckIndex) {
case 0:
// Initialized as empty
test.equal(data.length, 0);
test.deepEqual(diff,
{ removed: null, moved: null, copied: null, added: [] });
break;
case 1:
// Row has been inserted
test.equal(data[0].col, 10);
test.equal(data.length, 1);
test.deepEqual(diff,
{ removed: null,
moved: null,
copied: null,
added: [ { col: 10, _index: 1 } ] });
break;
case 2:
// Row has been updated
test.equal(data[0].col, 15);
test.equal(data.length, 1);
test.deepEqual(diff,
{ removed: [ { _index: 1 } ],
moved: null,
copied: null,
added: [ { col: 15, _index: 1 } ] });
break;
case 3:
// Row has been deleted
test.equal(data.length, 0);
test.deepEqual(diff,
{ removed: [ { _index: 1 } ],
moved: null,
copied: null,
added: null });
test.done();
break;
}
}).on('added', function(row, index){
test.equal(index, 0);
test.equal(row.col, 10);
}).on('changed', function(row, newRow, index){
test.equal(index, 0);
test.equal(row.col, 10);
test.equal(newRow.col, 15);
}).on('removed', function(row, index){
test.equal(index, 0);
test.equal(row.col, 15);
test.done();
});

@@ -142,74 +153,2 @@

},
skipDiff: function(test){
var table = 'skip_diff';
server.on('ready', function(conn, esc, escId, queries){
querySequence(conn.db, [
'DROP TABLE IF EXISTS ' + escId(table),
'CREATE TABLE ' + escId(table) + ' (col INT UNSIGNED)',
'INSERT INTO ' + escId(table) + ' (col) VALUES (10)',
], function(results){
var error = function(){
throw new Error('diff events should not be called');
};
conn.settings.skipDiff = true;
conn.select('SELECT * FROM ' + escId(table), [ {
table: table,
database: server.database
} ]).on('update', function(rows){
if(rows.length > 0 && rows[0].col === 10){
test.ok(true);
}else if(rows.length > 0 && rows[0].col === 15){
conn.db.query('DELETE FROM ' + escId(table));
}else if(rows.length === 0){
// Give time, just in case the `removed` event comes in
setTimeout(function(){
conn.settings.skipDiff = false;
test.done();
}, 100);
}
})
.on('added', error)
.on('changed', error)
.on('removed', error)
.on('diff', error);
querySequence(conn.db, [
'UPDATE ' + escId(table) +
' SET `col` = 15'
], function(results){
// ...
});
});
});
},
emptyResults: function(test){
var waitTime = 500;
var table = 'empty_results';
server.on('ready', function(conn, esc, escId, queries){
querySequence(conn.db, [
'DROP TABLE IF EXISTS ' + escId(table),
'CREATE TABLE ' + escId(table) + ' (col INT UNSIGNED)',
], function(results){
var pauseTime = Date.now();
conn.select('SELECT * FROM ' + escId(table), [ {
table: table,
database: server.database
} ]).on('update', function(rows){
if(rows.length === 0) {
// Initialized with no rows, so add one
querySequence(conn.db, [
'INSERT INTO ' + escId(table) + ' (col) VALUES (10)'
], function(results){
// ...
});
}else if(rows.length > 0 && rows[0].col === 10){
// Row was added, all done
test.done();
}
});
});
});
},
pauseAndResume: function(test){

@@ -228,3 +167,3 @@ var waitTime = 500;

database: server.database
} ]).on('update', function(rows){
} ]).on('update', function(diff, rows){
if(rows.length > 0 && rows[0].col === 10){

@@ -252,6 +191,6 @@ test.ok(true);

},
stopAndActive: function(test){
// Must be last test that uses binlog updates since it calls stop()
stopAndActive: function(test) {
// NOTE: Must be last test that uses binlog updates since it calls stop()
var table = 'stop_active';
server.on('ready', function(conn, esc, escId, queries){
server.on('ready', function(conn, esc, escId, queries) {
querySequence(conn.db, [

@@ -266,10 +205,9 @@ 'DROP TABLE IF EXISTS ' + escId(table),

database: server.database
} ]).on('update', function(rows){
if(rows.length > 0 && rows[0].col === 10){
} ]).on('update', function(diff, rows) {
if(rows.length > 0 && rows[0].col === 10) {
test.ok(true);
}else if(rows.length > 0 && rows[0].col === 15){
}else if(rows.length > 0 && rows[0].col === 15) {
test.ok(this.active());
this.stop();
// When all instances of query removed, resultsBuffer removed too
// TODO: Update for queryCache
test.equal(typeof conn._queryCache[query], 'undefined');

@@ -279,8 +217,8 @@

conn.db.query('DELETE FROM ' + escId(table));
setTimeout(function(){
setTimeout(function() {
test.done();
}, 100);
}else if(rows.length === 0) {
throw new Error('Select should have been stopped!');
}
}).on('removed', function(row, index){
throw new Error('should not be called');
});

@@ -287,0 +225,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc