Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to connect on Sails JS #20

Open
rishab-yumist opened this issue Oct 16, 2015 · 5 comments
Open

Unable to connect on Sails JS #20

rishab-yumist opened this issue Oct 16, 2015 · 5 comments

Comments

@rishab-yumist
Copy link

Controller:
test : function( req, res ){

    var settings = module.exports = {
        host: 'localhost',
        user: 'root',
        password: 'root',
        database: 'leaderboard',
        port : 3307
    };

    var liveConnection = new sails.LiveSelect(settings);

    return res.send(liveConnection);

}

bootstrap.js

sails.LiveSelect = require('mysql-live-select');

@numtel
Copy link
Owner

numtel commented Oct 16, 2015

See my response further down for a better way to integrate with Sails.js

I've never used sails before you sent me that email and this is not yet the optimal way to do it but it seems to work.

First, the sails-mysql adapter must be modified to allow returning the query responsible for finding the data. I have accomplished this by hacking the find method on the model to respond to a returnQueryOnly option.

node_modules/sails-mysql/lib/adapter.js at about line 800:

    find: function(connectionName, collectionName, options, cb, connection) {

      if(_.isUndefined(connection)) {
        return spawnConnection(connectionName, __FIND__, cb);
      } else {
        __FIND__(connection, cb);
      }

      function __FIND__(connection, cb) {
        // Begin modification #1
        var returnQueryOnly = false;
        if(options.where && options.where.returnQueryOnly) {
          returnQueryOnly = true;
          delete options.where.returnQueryOnly;
          if(Object.keys(options.where).length === 0) {
            delete options.where;
          }
        }
        // End modification #1

        // Check if this is an aggregate query and that there is something to return
        if(options.groupBy || options.sum || options.average || options.min || options.max) {
          if(!options.sum && !options.average && !options.min && !options.max) {
            return cb(Errors.InvalidGroupBy);
          }
        }

        var connectionObject = connections[connectionName];
        var collection = connectionObject.collections[collectionName];

        // Build find query
        var schema = connectionObject.schema;
        var _query;

        var sequel = new Sequel(schema, sqlOptions);

        // Build a query for the specific query strategy
        try {
          _query = sequel.find(collectionName, options);
        } catch(e) {
          return cb(e);
        }

        // Run query
        log('MYSQL.find: ', _query.query[0]);

        // Begin modification #2
        if(returnQueryOnly) {
          cb(null, {query: _query.query[0], collectionName: collectionName});
        } else {
          connection.query(_query.query[0], function(err, result) {
            if(err) return cb(err);
            cb(null, result);
          });
        }
        // End modification #2

      }
    },

Then I have a controller for a model called chat that I modified from this tutorial on using sockets with sails

var LiveMysql = require('mysql-live-select');
var mysqlConnSettings = {
  host        : 'localhost',
  user        : 'todouser',
  password    : 'todopass',
  database    : 'sails_todo',
  serverId    : 1337,
  minInterval : 200
};
var liveDb = new LiveMysql(mysqlConnSettings);


module.exports = {

  addConv:function (req,res) {

    var data_from_client = req.params.all();

    if(req.isSocket && req.method === 'POST'){

      // This is the message from connected client
      // So add new conversation
      Chat.create(data_from_client)
        .exec(function(error,data_from_client){
          // New chat message completed
        }); 
    }
  },
  testStream: function(req, res){

    if (req.isSocket){
      console.log( 'User subscribed on ' + req.socket.id );


        // Start the stream.  Pipe it to sockets.
        Chat.find({returnQueryOnly:true}).exec(function(error, result) {
          liveDb.select(result[0].query, [ { table: 'chat' } ])
            .on('update', function(diff, data) {
              sails.sockets.emit(req.socket.id, 'chatDiff', diff);
            });
        });

    }


  }
};

The angular controller on the frontend now listens for the chatDiff event:

    var socketApp = angular.module('socketApp',[]);

    socketApp.controller('ChatController',['$http','$log','$scope',function($http,$log,$scope){


      $scope.predicate = '-id';
      $scope.reverse = false;
      $scope.chatList =[];

      io.socket.get('/chat/testStream');

      $scope.chatUser = "nikkyBot"
      $scope.chatMessage="";

      io.socket.on('chatDiff', function(diff) {
        console.log("got diff", diff);
        $scope.chatList = window.applyDiff($scope.chatList, diff);
        $scope.$digest();
      });

      $scope.sendMsg = function(){
        $log.info($scope.chatMessage);
        io.socket.post('/chat/addconv/',{user:$scope.chatUser,message: $scope.chatMessage});
        $scope.chatMessage = "";
      };
    }]);

In order to apply the diff, you need to copy this function from this npm module to be used on the client. Underscore or lodash is required for this function as well.

window.applyDiff = function(data, diff) {
  data = _.clone(data, true).map(function(row, index) {
    row._index = index + 1;
    return row;
  });

  var newResults = data.slice();

  diff.removed !== null && diff.removed.forEach(
    function(removed) { newResults[removed._index - 1] = undefined; });

  // Deallocate first to ensure no overwrites
  diff.moved !== null && diff.moved.forEach(
    function(moved) { newResults[moved.old_index - 1] = undefined; });

  diff.copied !== null && diff.copied.forEach(function(copied) {
    var copyRow = _.clone(data[copied.orig_index - 1]);
    copyRow._index = copied.new_index;
    newResults[copied.new_index - 1] = copyRow;
  });

  diff.moved !== null && diff.moved.forEach(function(moved) {
    var movingRow = data[moved.old_index - 1];
    movingRow._index = moved.new_index;
    newResults[moved.new_index - 1] = movingRow;
  });

  diff.added !== null && diff.added.forEach(
    function(added) { newResults[added._index - 1] = added; });

  var result = newResults.filter(function(row) { return row !== undefined; });

  return result.map(function(row) {
    row = _.clone(row);
    delete row._index;
    return row;
  });
}

It will definitely leak memory because there's no way for the server to know when to stop listening for changes to a query. Hopefully this helps though.

@rishab-yumist
Copy link
Author

Hey ben.

Thanks for a quick response. What do you suggest in order to overcome the
memory leak issue? Listening to DB needs to be done in order to retrieve
the changes in the DB. Thus, I'm using MySql Live package made by you.

On Fri, Oct 16, 2015 at 9:48 PM, Ben Green [email protected] wrote:

I've never used sails before you sent me that email and this is not yet
the optimal way to do it but it seems to work.

First, the sails-mysql adapter must be modified to allow returning the
query responsible for finding the data. I have accomplished this by hacking
the find method on the model to respond to a returnQueryOnly option.

node_modules/sails-mysql/lib/adapter.js at about line 800:

find: function(connectionName, collectionName, options, cb, connection) {

  if(_.isUndefined(connection)) {
    return spawnConnection(connectionName, __FIND__, cb);
  } else {
    __FIND__(connection, cb);
  }

  function __FIND__(connection, cb) {
    // Begin modification #1
    var returnQueryOnly = false;
    if(options.where && options.where.returnQueryOnly) {
      returnQueryOnly = true;
      delete options.where.returnQueryOnly;
      if(Object.keys(options.where).length === 0) {
        delete options.where;
      }
    }
    // End modification #1

    // Check if this is an aggregate query and that there is something to return
    if(options.groupBy || options.sum || options.average || options.min || options.max) {
      if(!options.sum && !options.average && !options.min && !options.max) {
        return cb(Errors.InvalidGroupBy);
      }
    }

    var connectionObject = connections[connectionName];
    var collection = connectionObject.collections[collectionName];

    // Build find query
    var schema = connectionObject.schema;
    var _query;

    var sequel = new Sequel(schema, sqlOptions);

    // Build a query for the specific query strategy
    try {
      _query = sequel.find(collectionName, options);
    } catch(e) {
      return cb(e);
    }

    // Run query
    log('MYSQL.find: ', _query.query[0]);

    // Begin modification #2
    if(returnQueryOnly) {
      cb(null, {query: _query.query[0], collectionName: collectionName});
    } else {
      connection.query(_query.query[0], function(err, result) {
        if(err) return cb(err);
        cb(null, result);
      });
    }
    // End modification #2

  }
},

Then I have a controller for a model called chat that I modified from this
tutorial on using sockets with sails
http://maangalabs.com/blog/2014/12/04/socket-in-sails/

var LiveMysql = require('mysql-live-select');var mysqlConnSettings = {
host : 'localhost',
user : 'todouser',
password : 'todopass',
database : 'sails_todo',
serverId : 1337,
minInterval : 200
};var liveDb = new LiveMysql(mysqlConnSettings);

module.exports = {

addConv:function (req,res) {

var data_from_client = req.params.all();

if(req.isSocket && req.method === 'POST'){

  // This is the message from connected client
  // So add new conversation
  Chat.create(data_from_client)
    .exec(function(error,data_from_client){
      // New chat message completed
    });
}

},
testStream: function(req, res){

if (req.isSocket){
  console.log( 'User subscribed on ' + req.socket.id );


    // Start the stream.  Pipe it to sockets.
    Chat.find({returnQueryOnly:true}).exec(function(error, result) {
      liveDb.select(result[0].query, [ { table: 'chat' } ])
        .on('update', function(diff, data) {
          sails.sockets.emit(req.socket.id, 'chatDiff', diff);
        });
    });

}

}
};

The angular controller on the frontend now listens for the chatDiff event:

var socketApp = angular.module('socketApp',[]);

socketApp.controller('ChatController',['$http','$log','$scope',function($http,$log,$scope){


  $scope.predicate = '-id';
  $scope.reverse = false;
  $scope.chatList =[];

  io.socket.get('/chat/testStream');

  $scope.chatUser = "nikkyBot"
  $scope.chatMessage="";

  io.socket.on('chatDiff', function(diff) {
    console.log("got diff", diff);
    $scope.chatList = window.applyDiff($scope.chatList, diff);
    $scope.$digest();
  });

  $scope.sendMsg = function(){
    $log.info($scope.chatMessage);
    io.socket.post('/chat/addconv/',{user:$scope.chatUser,message: $scope.chatMessage});
    $scope.chatMessage = "";
  };
}]);

In order to apply the diff, you need to copy this function from this npm
module to be used on the client. Underscore or lodash is required for this
function as well.

window.applyDiff = function(data, diff) {
data = _.clone(data, true).map(function(row, index) {
row._index = index + 1;
return row;
});

var newResults = data.slice();

diff.removed !== null && diff.removed.forEach(
function(removed) { newResults[removed._index - 1] = undefined; });

// Deallocate first to ensure no overwrites
diff.moved !== null && diff.moved.forEach(
function(moved) { newResults[moved.old_index - 1] = undefined; });

diff.copied !== null && diff.copied.forEach(function(copied) {
var copyRow = _.clone(data[copied.orig_index - 1]);
copyRow._index = copied.new_index;
newResults[copied.new_index - 1] = copyRow;
});

diff.moved !== null && diff.moved.forEach(function(moved) {
var movingRow = data[moved.old_index - 1];
movingRow._index = moved.new_index;
newResults[moved.new_index - 1] = movingRow;
});

diff.added !== null && diff.added.forEach(
function(added) { newResults[added._index - 1] = added; });

var result = newResults.filter(function(row) { return row !== undefined; });

return result.map(function(row) {
row = _.clone(row);
delete row._index;
return row;
});
}

It will definitely leak memory because there's no way for the server to
know when to stop listening for changes to a query. Hopefully this helps
though.


Reply to this email directly or view it on GitHub
#20 (comment)
.

@numtel
Copy link
Owner

numtel commented Oct 16, 2015

I'm working on cleaning this up, give me a little bit to make either a pull request to make it a feature of sails-mysql or a separate package then it should be a simple function call with a method available for closing the liveSelect.

@rishab-yumist
Copy link
Author

Ben,

It'll be amazing if you could do this :)

Thanks in advance. Appreciate your support.

I'm keenly waiting for this.

On Sat, Oct 17, 2015 at 12:19 AM, Ben Green [email protected]
wrote:

I'm working on cleaning this up, give me a little bit to make either a
pull request to make it a feature of sails-mysql or a separate package
then it should be a simple function call with a method available for
closing the liveSelect.


Reply to this email directly or view it on GitHub
#20 (comment)
.

@numtel
Copy link
Owner

numtel commented Oct 16, 2015

Ok, check out the new NPM package and the associated example application. Should be pretty simple to integrate now.

https://github.com/numtel/sails-mysql-live-select

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants