js-data-rethinkdb icon indicating copy to clipboard operation
js-data-rethinkdb copied to clipboard

Support secondary indices in findAll

Open jmdobry opened this issue 9 years ago • 9 comments

Example:

adapter.findAll(User, {
  where: {
    age: { '>': 30 },
    status: 'unknown'
  }
});

Let's say there is a secondary index setup on the "status" field. In order to tell the query to take advantage of this, I propose the following:

adapter.findAll(User, {
  where: {
    age: { '>': 30 },
    status: { '==': 'unknown' }
  }
}, {
  keys: ['status']
});

Which will optimize the query by first doing a getAll('unknown', { index: 'status' }) and then adding the filter calls.

jmdobry avatar Mar 26 '15 15:03 jmdobry

I also might be worth it to add configuration to resources, such as a keys option, that specifies which fields of the resource are a secondary index, so you don't have to specify keys during the method call.

jmdobry avatar Mar 26 '15 15:03 jmdobry

I like this idea. Would this impact the REST interface? For example, lets say I want to get all projects by user. Right now it appears that /projects gets all projects, and /projects/id looks up by the primary key. Would the interface change to something like: /projects/by/:key It looks like this change would impact all the other adapters. Is there something I could do in the short term?

gtarcea avatar Mar 26 '15 15:03 gtarcea

Another suggestion - the filterQuery which creates the rql starts by setting up the table to query on. If filterQuery was changed to take rql and spit it back out, then it could be used in the application to build up a query to execute. For example, if I did the following: let rql = r.table('files').getAll(owner, {index: 'user'}); rql = User.filterQuery(rql); let results = yield rql.run();

Then I could use the query syntax in my application but work extend the interface in ways that may be specific to my application needs. This would also let me work around the need for a secondary index.

gtarcea avatar Mar 26 '15 16:03 gtarcea

Let me expand on my example (still a very simplified example).

Let's assume I'm using js-data + js-data-angular on the frontend and js-data + js-data-rethinkdb on the backend.

Frontend:

angular.module('myApp', ['ngRoute', 'js-data'])
  .config(function ($routeProvider) {
    $routeProvider.when('/projects', {
      // ...
      resolve: {
        projects: function (User, Project) {
          return User.getLoggedInUser.then(function (user) {
            // assuming user.id is 5 then:
            // GET /projects?userId=5
            return Project.findAll({
              userId: user.id
            });
          });
        }
      }
    });
  });

Backend:

let Project = store.defineResource({
  // ...
  keys: ['userId'],
  // ...
});

export default Project;
app.get('/projects', function (req, res) {

  // right now this will not use "getAll" or any secondary indices
  // but if we change js-data-rethinkdb to look at that "keys"
  // option that I put on the Project resource definition above
  // then the "filterQuery" method can use that to optimize 
  // itself to use "getAll" and the secondary index
  Project.findAll(req.query).then(function (projects) {
    res.status(200).send(projects);
  });
});

This approach will make things work more like "magic", with the adapter automatically using secondary indexes if it has been told about them.

Regarding your suggestion of filterQuery taking reql and spitting it back out, I see the usefulness there, and I will think about how I would want that to work.

jmdobry avatar Mar 26 '15 16:03 jmdobry

@gtarcea In 1.2.0 the adapter now has a filterSequence(sequence, params) method. This will take a given sequence, such as r.table('files').getAll(owner, {index: 'user'}) and then apply the proper RQL filter, orderBy, skip, limit clauses according to what is described in params. This allows you to do what you suggested in your last comment:

let rql = r.table('files').getAll(owner, {index: 'user'});
let params = { type: 'pdf' };
rql = rethinkdbAdapter.filterSequence(rql, params);
let results = yield rql.run();

so you might do something like:

let store = new JSData.DS();
let rethinkdbAdapter = new DSRethinkDBAdapter(...);
store.registerAdapter('rethink', rethinkdbAdapter, { default: true });
let File = store.defineResource({
  name: 'file',
  table: 'files',
  relations: {
    belongsTo: {
      user: {
        localField: 'owner',
        localKey: 'user'
      }
    }
  }
});

File.findAllByIndex = (key, index, params) => {
  let rql = rethinkdbAdapter.r.table('files').getAll(key, { index: index });
  rql = rethinkdbAdapter.filterSequence(rql, params);
  return yield rql.run();
};

jmdobry avatar Mar 27 '15 03:03 jmdobry

This is great. Thank you so much. I will let you know how it works out.

gtarcea avatar Mar 27 '15 13:03 gtarcea

wow, didn't know this was here.

internalfx avatar Jul 21 '15 18:07 internalfx

Is it possible to filter by null? smth like:

where: {
    type: { 'in': 30 },
    subtype: null
  }

Yevgeniuz avatar Mar 31 '16 11:03 Yevgeniuz

Should be able to yeah

jmdobry avatar Mar 31 '16 16:03 jmdobry