Parse.com include key query exceeds stack size - javascript

I'm constructing a Parse.com query inside a Parse query on the browser in a Parse/backbone app.
I have a two stage query (and want the intermediate results) so I construct the second query in a when clause from the first query's fetch:
// changeJob is a backbone event handler triggered from global app state
changeJob: function() {
var taskQuery = new Parse.Query(Task);
taskQuery.equalTo("publicJob", theAppState.get("publicJob"));
taskQuery.equalTo("status", "accepted");
taskQuery.limit(20);
this.tasks.query = taskQuery;
var self = this;
this.tasks.fetch().then(function(foundTasks){
console.log("got " + foundTasks.length + " tasks");
var responsesQuery = new Parse.Query(StepResponse);
responsesQuery.containedIn("task", foundTasks); /// Stack blows here
responsesQuery.include("task");
responsesQuery.include("task.taskLocation");
responsesQuery.limit(1000);
self.responses.query = responsesQuery;
console.log("fetch the step responses");
self.responses.fetch();
});
My collections are model objects are declared with global scope:
var StepResponse = Parse.Collection.extend("StepResponse", {
});
// StepResponse Collection
var StepResponseCollection = Parse.Collection.extend({
model: StepResponse,
});
var Task = Parse.Object.extend("Task", {
});
// Task Collection
var TaskCollection = Parse.Collection.extend({
model: Task,
});
When I try to restrict the inner query based on using the array from the first query, I get a Maximum Stack size exceeded error.
I've seen a few people discuss this on SO but I don't think that they are duplicates - I don't have any bi directional links between these two classes and the stack blows when I'm setting up the query, before I execute it of try to save any objects.

Related

Create index on already existing objectStore

As an example on basic setup one index is created.
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('name', { keyPath: 'id' });
store.createIndex('by name', 'name', { unique: false });
};
Question:
Is it possible to create/append more indexes to the same objectStore on the future versionupdate? Since if I try:
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.createObjectStore('name', { keyPath: 'id' });
store.createIndex('by newName', 'newName', { unique: false });
};
It throws an error that current objectStore does already exist. An if I try to create store reference using transaction:
db.onupgradeneeded = function(event) {
var db = event.target.result;
var store = db.transaction('name', 'readwrite').objectStore('name');
store.createIndex('by newName', 'newName', { unique: false });
};
It throws that version change transaction is currently running
Yes it is possible. It can be a bit confusing at first. You want to get the existing object store via the implicit transaction created for you within onupgradeneeded. This is a transaction of type versionchange which is basically like a readwrite transaction but specific to the onupgradeneeded handler function.
Something like this:
var request = indexedDB.open(name, oldVersionPlusOne);
request.onupgradeneeded = myOnUpgradeNeeded;
function myOnUpgradeNeeded(event) {
// Get a reference to the request related to this event
// #type IDBOpenRequest (a specialized type of IDBRequest)
var request = event.target;
// Get a reference to the IDBDatabase object for this request
// #type IDBDatabase
var db = request.result;
// Get a reference to the implicit transaction for this request
// #type IDBTransaction
var txn = request.transaction;
// Now, get a reference to the existing object store
// #type IDBObjectStore
var store = txn.objectStore('myStore');
// Now, optionally inspect index names, or create a new index
console.log('existing index names in store', store.indexNames);
// Add a new index to the existing object store
store.createIndex(...);
}
You also will want to take care to increment the version so as to guarantee the onupgradeneeded handler function is called, and to represent that your schema (basically the set of tables and indices and properties of things) has changed in the new version.
You will also need to rewrite the function so that you only create or make changes based on the version. You can use event.oldVersion to help with this, or things like db.objectStoreNames.contains.
Something like this:
function myOnUpgradeNeeded(event) {
var is_new_db = isNaN(event.oldVersion) || event.oldVersion === 0;
if(is_new_db) {
var db = event.target.result;
var store = db.createObjectStore(...);
store.createIndex('my-initial-index');
// Now that you decided you want a second index, you also need
// to do this for brand new databases
store.createIndex('my-second-new-index');
}
// But if the database already exists, we are not creating things,
// instead we are modifying the existing things to get into the
// new state of things we want
var is_old_db_not_yet_current_version = !isNaN(event.oldVersion) && event.oldVersion < 2;
if(is_old_db_not_yet_current_version) {
var txn = event.target.transaction;
var store = txn.objectStore('store');
store.createIndex('my-second-new-index');
}
}
Pay close attention to the fact that I used event.target.transaction instead of db.transaction(...). These are not at all the same thing. One references an existing transaction, and one creates a new one.
Finally, and in addition, a personal rule of mine and not a formal coding requirement, you should never be using db.transaction() from within onupgradeneeded. Stick to modifying the schema when doing upgrades, and do all data changes outside of it.

Resuing IDBTransaction in callbacks throws TransactionInactiveError

I'm developing a app which uses IndexedDB extensively. What I'm trying to do is save data of employees table and company table. Each employee belongs to a company and in employee object I've Company's ID and the objects of both of the entity will look like this.
Company's object:
{"id":1,"name":"ABC"}
Employee's object:
{"id":100,"name":"E1","company_id":1}
I'm saving company's details using auto-incremented key (called it appid), so my final object of company's look like this:
{"id":1,"name":"ABC","appid":1}
Where the appid will get auto-incremented as I insert records of company one by one. Now while inserting employee's object I want to find the localid(appid) of the company and save it in employee's object to make employee's object look like:
{"id":100,"name":"E1","company_id":1,"company_app_id":1}
I'm able to get the localid of the company by calling a method while saving employee's details, like:
var transaction = db.transaction(['employees'], 'readwrite');
var objStore = transaction.objectStore('employees');
var company_id=employeeobject.company_id;
companyDB.getCompanyById(company_id,function(companyObject){
transaction = db.transaction(['employees'], 'readwrite');
objStore = transaction.objectStore('employees');
// If I comment above two lines it throws me exception.
var request=objStore.put(employeeobject);
request.onsuccess = function (e) {
// using it for next insertion.
};
});
Problem with the above code is every time when I want to insert employee's data in table I need to reopen the trascation in callback function because if I don't open the transaction again it throws TransactionInactiveError.
I've searched for specific error on SO and found that Transaction get inactive as soon as it's no more used in current scope.
Above code work perfectly fine when I've couple of employee's objects.
But When I'm trying to execute the same code with ~1K of data it takes
(normal execution time x ~10).
By normal execution time I mean without fetching company's localid and saving employee's details directly.
So my question is, what is the best way I can insert the employee's data including company's localid with least execution time? Or Am I doing something wrong ?
It depends on how you're implementing companyDB.getCompanyById().
As presented, it's an asynchronous black box (maybe it's doing a network request?) And as you've discovered, Indexed DB transactions are only active (1) directly after creation and (2) in callbacks from requests made in that transaction, until "control returns to the event loop". Without further details, your best bet would be to batch the work - do the getCompanyById() lookups for N employees, then write those N records.
But from the description at the top "...data of employees table and company table..." maybe this is all within a single Indexed DB database, in which case just use a single transaction for everything:
var tx = db.transaction(['companies', 'employees'], 'readwrite');
employee_records.forEach(function(record) {
var company_id = record.company_id;
var req = tx.objectStore('companies').get(company_id);
req.onsuccess = function() {
var company = req.result;
record.company_app_id = company.app_id;
tx.objectStore('employees').put(record);
};
});
(I'm mangling your actual data/logic here, this is just to illustrate)
Hmm, maybe something like this helps?
function addCompany(db, company, callback) {
var tx = db.transaction('companies', 'readwrite');
var store = tx.objectStore('companies');
var request = store.add(company);
request.onsuccess = callback;
}
function addEmployee(db, employee, callback) {
var tx = db.transaction('employees', 'readwrite');
var store = tx.objectStore('employees');
var request = store.add(employee);
request.onsuccess = callback;
}
function addCompanyThenEmployee(db, company, employee, callback) {
addCompany(db, company, onAddCompany);
function onAddCompany(event) {
var newAppId = event.target.result;
employee.company_app_id = newAppId;
addEmployee(db, employee, callback);
}
}
var company = {'id': 1, 'name': 'xyz'};
var employee = {'id': 1, 'name': 'xyz'};
var request = indexedDB.open(...);
request.onsuccess = function(event) {
var db = event.target.result;
addCompanyThenEmployee(db, company, employee, onAddCompanyThenEmployee.bind(null, company, employee));
};
function onAddCompanyThenEmployee(company, employee, event) {
console.log('Added company', company, 'and employee', employee);
};

Does Parse.com permit the inclusion of pointers inside a relation query?

I have the following many-to-many relationship in my Parse.com application:
Bit ↔ Message
The following fetches related messages from a 'Bit' object:
const query = new Parse.Query('Bit');
query.include('participants');
query.include('user');
query.equalTo('objectId', req.query.bitId);
const promise = query.first().then(function(bit) {
return new Promise(function(resolve) {
const relation = bit.relation('messages');
relation.query().find().then(function(messages) {
resolve(messages);
});
});
});
*Note: messages is a relation column type not a pointer type.
The result inside messages is an array that contains all of the related messages for that bit. Each message contains a pointer to a User called sender. Is it possible to include this User object in the query result? It would be nice if I could use the include() method on a relational query like so:
relation.include('sender');
You can qualify a relation's query as you would any other query. (Also, you've got some superfluous promise code in the OP. I cleaned that up a little, but the point of the answer is on the commented line)...
var query = new Parse.Query('Bit');
query.include('participants');
query.include('user');
query.equalTo('objectId', req.query.bitId);
var promise = query.first().then(function(bit) {
var relationalQuery = bit.relation('messages').query();
relationalQuery.include("sender"); // <-- the point
return relationalQuery().find();
}).then(function(messages) {
return resolve(messages);
});

How to query relational data in Parse JavaScript

I am quite new to Parse.
I have a database set up using this code:
var Class = Parse.Object.extend("Class");
var Team = Parse.Object.extend("Team");
var Student = Parse.Object.extend("Student");
var newClass = new Class();
newClass.set("name", className);
newClass.set("code", classCode);
newClass.set("creator", currentUser);
var classACL = new Parse.ACL(currentUser);
classACL.setPublicReadAccess(true);
newClass.setACL(classACL);
newClass.save();
for (var i = 0; i < teamNames.length; i++) {
var team = new Team();
team.set("name", teamNames[i]);
var teamACL = new Parse.ACL(currentUser);
teamACL.setPublicReadAccess(true);
team.setACL(teamACL);
team.save();
for (var j = 0; j < studentNames[i].length; j++) {
if (studentNames[i][j]) {
var student = new Student();
student.set("name", studentNames[i][j]);
student.set("parent", team);
student.save();
}
}
team.set("parent", newClass);
team.save();
}
newClass.save(null, {
success: function(newClass) {
//success
},
error: function(newClass, error) {
//fail
}
});
Here Class, Team, and Student are modeled as one-to-many relationships.
Now when a student signs up for the class using his or her own user account, the corresponding Student's user column is set to the current user.
Then I want to list all the classes whose creator OR one of its student's user column (if exists) equals to currentUser.
How do I create such a query referencing multiple classes in Parse (or how can I optimize the database so that such a query can be made as efficient as possible -- without having to create two separate queries?)
Any help is appreciated.
Clarification:
I knew that I could do an or query as described in Parse docs (I should have stated this in the question), however, my question is about doing so on relational data (defined by a pointer type property to parent). Here I need user be a property of a Student instance, which belongs to Team, and then to Class, and I'd like to filter only Class objects that has either its creator property or one of its grandchildren's (an instance of Student) user property equal to the currentUser, effectively listing only the classes that you created or are registered as a student.
Since the current database schema is having nested Pointers, there is no easy way to achieve this without adjusting it.
Database Schema
In Class class, add a Relation or Array field to contain references to Student/User objects. If you use User as object pointer, we wouldn't need to look up for Student at first.
Query
I assume that you have students as new Array field in Class class. students contains User objects.
var user = Parse.User.current();
var studentQuery = new Parse.Query(Class);
var creatorQuery = new Parse.Query(Class);
studentQuery.equalTo("students", user);
creatorQuery.equalTo("creator", user);
var query = Parse.Query.or(studentQuery, creatorQuery);
query.find().then(function(objects){
// Proceed with the results
},function(error){
// Handle error
});
Ok, what you want to do in an OR query with an internal subquery. One call to parse and you can filter the student properties using the subquery.
var studentQuery = new Parse.Query(Student);
studentQuery.equalTo("user", Parse.User.current());
var firstQuery = new Parse.Query(Class);
firstQuery.matchesQuery("student", studentQuery);
var secondQuery = new Parse.Query(Class);
secondQuery.equalTo("creator", Parse.User.current());
var mainQuery = Parse.Query.or(firstQuery, secondQuery);
mainQuery.find({
success: function(results) {
// results contains a list of Classes where the user is either the creator or the user property of the student (if available)
},
error: function(error) {
// There was an error.
}
});

Parse.com multiple relational queries

Here is a question for parse.com gurus.
I am using Parse Javascript API and trying to execute a query over 2 Pointers and cannot get it to work.
So i have following classes: Posts, Users, Groups. Posts has Pointer to Users. Users has a Pointer to Groups.
I need to get all POSTS, where a USER belongs to GROUP, which name starts with "Admin". Here is my code that doesn't work:
var Posts = Parse.Object.extend("Posts");
var Users = Parse.Object.extend("Users");
var Groups = Parse.Object.extend("Groups");
var query = new Parse.Query(Posts);
var innerQueryUsers = new Parse.Query(Users);
var innerQueryGroups = new Parse.Query(Groups);
innerQueryGroups.startsWith("name", "Admin");
innerQueryUsers.matchesQuery("group", innerQueryGroups);
query.matchesQuery("user", innerQueryUsers);
query.find({
success: function(data) {
},
error: function(error){
// here i get error: {code: 102, message: "bad type for $inQuery"}
}
});
Anybody have an idea how to do it right?
Edit - This can be done in one (untested) query by combining a group query and a post query with the same user query...
function postsInGroup(name) {
var groupQuery = new Parse.Query("Group");
groupQuery.equalTo("name", name);
var userQuery = new Parse.Query(Parse.User);
userQuery.matchesQuery("group", groupQuery);
var postQuery = new Parse.Query("Post");
postQuery.matchesQuery("user", userQuery);
return postQuery.find();
}
Call it like this...
postsInGroup("Admin").then(function(posts) {
console.log(JSON.stringify(posts));
}, function(error) {
console.log(JSON.stringify(error));
});
Its not clear what savings there is between this approach and first querying the group. It's likely that parse.com runs the inner queries much as you would. The difference in readability is a matter of taste.

Categories

Resources