How to design a chained Model.find() function? - javascript

I'm trying to write a ORM in Node.js. I want to declare a class named Model which will be used to declare a data object, like:
Users = new Model(someModelRules);
newUser = new Users(userInfomation);
the data model User have a function named find(). Now, I want to make find() chained, like:
Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)
or maybe just a simply find:
Users.find(" name = 'John' ")
to code this find function, I believe I must build the SQL first,and do the SQL query at the end of this find chain.
I don't know how to do this, all I can think of is to add a function like: doQuery(), so I will know it's time to do the SQL query when the doQuery() function was called, like:
Users.find(" name = 'John' ")
.orderedBy("age").desc()
.limit(0,10)
.doQuery();
I know this is a simply solution, but I don't want the extra doQuery() function. :(
So, how should I design this? It would so nice of you if you can show me some example code with comments.
Thx! (sorry for my poor English)
ps. I know the ORM2 has a find function I just want, but I wanna know how to code it and I can barely understand the code in ORM2 as there are no comments. (I'm not gonna use orm2.)
================================= SOLUTION ==============================
Inspired by #bfavaretto :
function User() {
this.find = function(id, condition) {
return new findChain(id, condition);
}
}
function findChain(id, condition) {
this._id = id
this._condition = condition
this.queryTimerSet = false;
this.scheduleQuery = function () {
var self = this;
if(!self.queryTimerSet) {
console.log('[TEST CASE: ' + self._id + '] Insert query into eventLoop');
setTimeout(function(){
console.log('[TEST CASE: ' + self._id + '] Start query: '+self._condition);
}, 0);
self.queryTimerSet = true;
} else {
console.log('[TEST CASE: ' + self._id + '] No need to insert another query');
}
}
this.orderedBy = function(column) {
console.log('[TEST CASE: ' + this._id + '] orderedBy was called');
this._condition = this._condition + ' ORDER BY ' + column
this.scheduleQuery();
return this;
}
this.desc = function() {
// simply add DESC to the end of sql
this._condition = this._condition + ' DESC'
}
this.scheduleQuery();
}
var user = new User();
user.find(1,'SELECT * FROM test').orderedBy('NAME1').desc();
user.find(2,'SELECT * FROM test').orderedBy('NAME2');
user.find(3,'SELECT * FROM test');
runnning this code, you will get the result:
[TEST CASE: 1] Insert query into eventLoop
[TEST CASE: 1] orderedBy was called
[TEST CASE: 1] No need to insert another query
[TEST CASE: 2] Insert query into eventLoop
[TEST CASE: 2] orderedBy was called
[TEST CASE: 2] No need to insert another query
[TEST CASE: 3] Insert query into eventLoop
[TEST CASE: 1] Start query: SELECT * FROM test ORDER BY NAME1 DESC
[TEST CASE: 2] Start query: SELECT * FROM test ORDER BY NAME2
[TEST CASE: 3] Start query: SELECT * FROM test
I believe there must be a better way to achieve this, but this is the best I can get for now.
Any comments?

It is possible to achieve that if you schedule the doQuery logic to run asynchronously (but as soon as possible). I am thinking on something like this:
function User() {
// Flag to control whether a timer was already setup
var queryTimerSet = false;
// This will schedule the query execution to the next tick of the
// event loop, if it wasn't already scheduled.
// This function is available to your model methods via closure.
function scheduleQuery() {
if(!queryTimerSet) {
setTimeout(function(){
// execute sql
// from the query callback, set queryTimerSet back to false
}, 0);
queryTimerSet = true;
}
}
this.find = function() {
// ... logic that builds the sql
scheduleQuery();
return this;
}
this.orderedBy = function() {
// ... logic that appends to the sql
scheduleQuery();
return this;
}
// etc.
}
One totally different approach is to have a single method for building the SQL, and passing the ORDER BY and LIMIT parameters in an options object. Then your call would look like this:
user.find({
what : " name = 'John' ",
orderedBy : "age DESC",
limit : "0,10"
});
This is more suited for SQL queries than what you're trying to do. What you have looks like noSQL stuff like MongoDB, where fetching the records and sorting are separate operations (I think).

You will always have to have a execute/doQuery function at the end of the chain.
This is because all the other functions before the doQuery help build the query that needs to be executed at the end.

Related

Snowflake only executes first SQL command in stored procedure

I was trying to create a procedure that copies the content of my table into S3 partitioned by 2 different combinations. For that I did the following:
$$
var cmd_partition1 = `...`
var cmd_partition2 = `...`
var store_data_partitioned_by_1_command = snowflake.createStatement({ sqlText: cmd_partition1 })
var store_data_partitioned_by_2_command = snowflake.createStatement({ sqlText: cmd_partition2 })
try {
store_data_partitioned_by_1_command.execute()
store_data_partitioned_by_2_command.execute()
return 'Succeeded.'
}
catch (err) {
return "Failed: " + err
}
$$;
However, each time I execute the procedure the partitioning is only performed for the 1st combination, while the 2nd one is ignored.
Do you know why this is happening and how can I solve it?
I tested each one of the cmd_partition (1 and 2) in the Snowflake GUI and both of them work as expected.
create table test_sp(id int);
-- test sql is good
insert into test_sp values (1);
insert into test_sp values (2);
-- clean up
truncate table test_sp;
create or replace procedure double_exec()
returns varchar
language javascript as
$$
var cmd_partition1 = `insert into test_sp values (1)`
var cmd_partition2 = `insert into test_sp values (2)`
var store_data_partitioned_by_1_command = snowflake.createStatement({ sqlText: cmd_partition1 })
var store_data_partitioned_by_2_command = snowflake.createStatement({ sqlText: cmd_partition2 })
try {
store_data_partitioned_by_1_command.execute()
store_data_partitioned_by_2_command.execute()
return 'Succeeded.'
}
catch (err) {
return "Failed: " + err
}
$$;
and now to run
call double_exec();
DOUBLE_EXEC
Succeeded.
so lets check the steps ran
select * from test_sp;
ID
1
2
so the concept of having to executions in a row is valid.
which makes it something about the SQL itself, and not the stored procedure.

How to update array to firebase?

I am new in this field. I see there are many ways on the internet and most of them is about the old version of Firebase, like using push(), update(), save(). So, I really don't know how to update it. I tried that like this:
function writeNewEvent(eObj) {
// A post entry.
var userObj = authObj.$getAuth();
// Get a key for a new Post.
var newEventKey = ref.child('events').child(userObj.uid).push().key;
// Write the new post's data simultaneously in the posts list and the user's post list.
var updates = {};
updates['/events/' + userObj.uid+ '/' + newEventKey] = userObj;
return ref.update(updates);
}
But the error is:
angular.js:13920 Error: Firebase.update failed: First argument contains an invalid key ($d) in property 'events.LRnkjDgEu1QtuvUTazTwyms4U063.-KW56c87MThrK0PZp-XH.f'. Keys must be non-empty strings and can't contain ".", "#", "$", "/", "[", or "]"
Could you tell me if my method is correct? And how to implement this function.
It looks like you have an ID so you don't need to use push(), just set() at the id like:
// assuming your input looks somthing like:
let eObj = {
"uuid": "98765-8765-1234567890-7890",
"some-data": "stuff",
"other-data": "other stuff",
"numeric-data": 12345
}
function writeNewEventUsingObjUuid(eObj) {
// A post entry.
var userObj = authObj.$getAuth();
return ref.child('events').child(userObj.uid).set(eObj).then(function () {
console.log('Event added with ID: ' + userObj.uid);
}).catch(function (e) {
console.log('Error adding event: ' + e.message);
});
}
If you really want to use push(), firebase will assign you an ID. Note that an empty push() like you had returns a thenable reference. The work is still done asynchronously event though you get an ID upfront.

How to use an external variable in MongoDB 'where' query via Javascript?

I have a MongoDB query that searches all properties for a value defined in the search variable. It works the following way:
db.collection.findOne({
$where: function() {
var search = 'searchstring';
for (var key in this) {
if (this[key] === search) {
return true;
}
return false;
}
}
});
However, I would like to define the search variable outside the query.
But when I do so, I get an error that it is not referenced (i.e. scoping issue):
"ReferenceError: search is not defined near '[key] === search
How can I use or pass the variable to the query filter?
You can try something like this:
var searchstring = 'whatever';
var params = {};
params.$where = 'function() {' +
'var search = "' + searchstring + '";' +
'for (var key in this) {' +
'if (this[key] === search) {' +
'return true;' +
'}' +
'return false;' +
'}' +
'}';
db.collection.findOne(params);
(Stringify your function and concat with external variable)
Worked for me via mongoose
search is a variable that you define in your client, may be the shell, or any client API.
The function that you define for the $where clause, will not be executed on the client side, but on the mongodb server.
so, when the function is being interpreted in the server side and it looks for the search variable, it was never defined on the server and hence you get the error.
In your case, you want the variable search, to be replaced with its content, by the client, before being executed on the server. And this is not possible, unless you build the function content itself in the client side.
The client never really interprets anything you write inside the anonymous function. The server does. The error you see is from the server. You can validate it by firing this query and looking onto the server logs:
2015-12-22T19:03:44.011-0800 I QUERY [conn1] assertion 16722 ReferenceError:
searc is not defined
at _funcs1 (_funcs1:1:39) near 's.key === searc){retu' ns:test.t query:{ $w
here: function (){if(this.key === searc){return true}} }
There is a better way to write what you wish to achieve using the $exists operator.
var search = "title";
var find = {};
find[search] = {$exists:true};
db.collection.findOne(find);
This works, because, you build the query parameter fully on the client side, before passing it on to the findOne() method.
You can solve this problem using mongodb map reduce and scope functionality. Scope allows you to pass variables into map reduce job.
function map() {
for (var key in this) {
if (this[key] === search) {
return emit(this._id, this);
}
}
}
function reduce(key, values) {
return values[0];
}
db.collection.mapReduce(map, reduce, {
out: {inline: 1},
scope: {
search: 'searchstring'
}
}
);

How to do dynamic queries on MySQL from Meteor?

I have been trying to make dynamic queries against MySQL from Meteor using the numtel:mysql package. So far it's not successful. Perhaps I either need to know how to pass a dynamic argument to the subscribe, or need to know how to get the result of liveDb.select as an array or object rather than a cursor (liveDb is instantiated by called new LiveMysql(...)). I have tried doing the query in a method on the server side (as declared in Meteor.methods(...), and the method does not return the result. If anyone has code examples for this, that would be very much appreciated!
Here is helpful links for use Meteor with MySQL for select query, execute stored procedure,user mongo like publish subscribe method as well as join query in publish.
MySql:numtel package
Leaderboard MySql example
// The below code reference from Leaderboard MySql example
Meteor.publish('allPlayers', function() {
return liveDb.select(
'SELECT * FROM players ORDER BY score DESC',
[ { table: 'players' } ]
);
});
Meteor.publish('playerScore', function(name) {
return liveDb.select(
'SELECT id, score FROM players WHERE name = ' + liveDb.db.escape(name),
[
{
table: 'players',
condition: function(row, newRow, rowDeleted) {
// newRow provided on UPDATE query events
return row.name === name || (newRow && newRow.name === name);
}
}
]
);
Meteor.methods({
'incScore': function(id, amount) {
check(id, Number);
check(amount, Number);
liveDb.db.query(
'UPDATE players SET score = score + ? WHERE id = ?', [ amount, id ]);
}
});
You need to call change on your MysqlSubscription with the same params as defined in the Meteor.publish.
In my case:
export const mySqlSubscription = new MysqlSubscription('tableName');
Meteor.publish('tableName', function(filters){
var filterString = buildFilterString(filters);
var qString = 'SELECT ..... FROM tableName '+filterString;
//console.log(qString);
return liveDb.select( qString, [ { table: 'tableName' } ] );
}
);
Then whenever I want to update my subscription for a different filter I call:
mySqlSubscription.change(this.getReactively('filterRecord'));

Node.js multiple Sequelize raw sql query sub queries

The title sounds complicated. I have a users table, and each user can have multiple interests. These interests are linked to the user via a lookup table. In PHP I queried the users table, then for each one did a query to find interests. How can I do this in Node.js/Sequelize? How can I set up some sort of promises too? For example:
sequelize.query("SELECT * FROM users").success(function(users) {
for (var u in users) {
sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).success(function(interests) {
if (interests.length > 0) {
users[u].interests = interests;
}
});
}
return users;
});
From the return statement in the bottom of your code, it seems you have not totally grasped the asynchronous nature of node.js. The return statement in your code will be executed directly after the first call to sequelize.query, that is, before the query returns. This means that users will be undefined.
If you wanted to actually "return" the users and their interest, I would suggest something like this:
sequelize.query("SELECT * FROM users").success(function(users) {
done = _.after(users.length, function () {
callback(users)
})
for (var u in users) {
sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).success(function(interests) {
if (interests.length > 0) {
users[u].interests = interests;
}
done();
});
}
});
In the code above _ refers to a utility lib. that executes the callback function after the function has been called users.length times. Callback is a function that is passed to your piece of code, and should process the return result, for example returning the users to the client in the context of a webserver.
Another comment - if you are only doing raw SQL queries, Sequelize might not be the best choice for you. Any reason why you are not using the SQL driver directly? If you want to use sequelize, you should take advantage of its features. Try to the define a model for users and interests, set up an association and load up users and interests in one go using JOINs / eager loading
update: An example using promises
sequelize.query("SELECT * FROM users").then(function(users) {
return sequelize.Promise.map(users, function (u) {
return sequelize.query("SELECT interests.id, interests.title FROM interests, user_interests WHERE interests.id = user_interests.interest_id AND user_interests.user_id = " + users[u].id).then(function(interests) {
if (interests.length > 0) {
user.interests = interests;
}
});
});
});

Categories

Resources