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

Filter on level 2 properties #517

Closed
daankets opened this issue Aug 29, 2014 · 244 comments
Closed

Filter on level 2 properties #517

daankets opened this issue Aug 29, 2014 · 244 comments

Comments

@daankets
Copy link

daankets commented Aug 29, 2014

I am trying to use the ?filter rest method, in order to query on nested properties. However, this doesn't seem to work:

/someResource?filter[where][field.subField][like]=abc

neither does this (which I think is wrong anyway):

/someResource?filter[where][field][subField][like]=abc

Any hints?

@fabien
Copy link
Contributor

fabien commented Aug 29, 2014

Which connector are you using?

@daankets
Copy link
Author

Memory in dev, mongo in test and beyond. It's the memory connector that has the issues. We use this one for our dev setup as it resets upon restart...

It's the 'dot' notation that is working with mongo.

@fabien
Copy link
Contributor

fabien commented Aug 29, 2014

This is currently a limitation of the memory connector.

@raymondfeng perhaps we can use something like https://github.com/logicalparadox/filtr or https://github.com/crcn/sift.js to enable this

@daankets
Copy link
Author

Well, the nice thing about the memory connector is that it resets, so you test every run with a clean sheet. And of course it's rather speedy ;-)

@fabien
Copy link
Contributor

fabien commented Aug 29, 2014

Of course - however, you can easily reset the test database before you start, like this:

app.datasources['db'].automigrate(next);

@daankets
Copy link
Author

Will this drop the entire schema?

@fabien
Copy link
Contributor

fabien commented Aug 29, 2014

Yes, but in the case of MongoDB collections will be empties and indexes re-created. There's no real schema, like with SQL databases.

@daankets
Copy link
Author

Sure. Just calling it a 'schema' as a 'habit'

@fabien
Copy link
Contributor

fabien commented Aug 29, 2014

Of course! While collections are being recreated, they are not really removed from the database first. So any obsolete collections will stay around, until you delete them, or the database itself. AFAIK loopback will create the db for you, in case it doesn't exist.

@raymondfeng
Copy link
Member

@fabien These libraries are interesting:

I also looked at:

Keep in mind, RDBs won't support nested queries.

@fabien
Copy link
Contributor

fabien commented Aug 30, 2014

@raymondfeng sift.js looks good to me, but it doesn't support skip/limit. nedb does, but it seems quite a bit more involved. It can probably be implemented as a new connector altogether.

Users should be made aware of the limits of RDBM's, which is perhaps easiest when having this as a separate connector - so users have to be explicit about it, instead of relying on the memory connector (for tests and so forth) to support any type (both SQL as well as NoSQL style) of query. It's probably easy to copy the mongodb connector's query normalization code and implement this.

Here are some more libraries of interest:

@bajtos bajtos added #review and removed #review labels Sep 6, 2014
@bajtos
Copy link
Member

bajtos commented Oct 23, 2014

@yagobski
Copy link
Member

How this can work for mysql connector? I have same issue

@stringbeans
Copy link

Looks like Loopback currently does not support nested include filtering. This seems like a huge limitation. I don't believe the db connector should make any difference because ideally the Loopback framework would work the same regardless of what db connector you're using

@weslley39
Copy link

i'm having the same problem trying to do a nested include. Any solution ?

@GameScripting
Copy link

So are there any plans to support nested properties? Is there any way to get the "native" mongodb-connection so we can query the database without the loopback abstraction?

@dominikriedel
Copy link

Hey! I've got the Same Problem. I want to query a List of objects based on the property of an related model as well as the property of the wanted model e.g:

Event.find( { where: { and: [ { endDate : { lt : givenDate}}, { location.name : "Abc" }]} }, cb );

So, am I right, that this is not supported atm?

@bajtos
Copy link
Member

bajtos commented Jan 28, 2015

I want to query a List of objects based on the property of an related model as well as the property of the wanted model.
So, am I right, that this is not supported atm?

You are right, this it not supported yet.

@dominikriedel
Copy link

so the easiest way would be to just query via SQL? Without any further knowlage: is there any caching mechanism I could use?

@pungoyal
Copy link

Can we upvote on this feature?

@bajtos
Copy link
Member

bajtos commented Feb 27, 2015

Sure, leave a comment containing one of :+1: or +1 (as regular text, not like I did).

@pungoyal
Copy link

👍

@kurtommy
Copy link

in the doc i can read: Loopback supports filtering nested properties in three NoSQL connectors: Mongodb, Cloudant, Memory.

so a query like this it's impossible on mysql
User.find({where: {'address.tags.tag': 'business'}}

i can't see any valid workarount to achieve multiple join with where condition... that will require multiple queries leading performance issues (and resource waste)

any suggestion?

@F3L1X79
Copy link

F3L1X79 commented Feb 2, 2018

I am lost at the moment. This ticket is closed, but does filtering on nested properties work with mongodb?

@nVitius
Copy link

nVitius commented Feb 2, 2018

@F3L1X79 It works as long as the nested properties are embedded on the same document.

@acrodrig
Copy link

acrodrig commented Feb 2, 2018

This does not work for MySQL. It should be possible to translate something like:

User.find({where: {'address.tags.tag': 'business'}}

Into JSON_EXTRACT conditions instead of simple SQL equality.

Can someone point me in the direction of the code where the translation from JSON to SQL is done? Thanks.

@nVitius
Copy link

nVitius commented Feb 2, 2018

@acrodrig I think most of the code you're looking for will be here: https://github.com/strongloop/loopback-connector-mysql/blob/master/lib/mysql.js.

@zbarbuto
Copy link
Member

zbarbuto commented Feb 3, 2018

Also for anyone interested - there is support for this to some degree in postrgres using the json data type as per this PR

loopbackio/loopback-connector-postgresql#304

@elviocb
Copy link

elviocb commented Mar 3, 2018

Why the filter with property id does not work? If I change it to any other property name it works (ie: customer.name). I'm using mongodb connector.

// Does not work
        var filter = {
            where: {
                'customer.id': customerId
            },
            order: 'order_number DESC'
        }
// Works
        var filter = {
            where: {
                'customer.name': customerName
            },
            order: 'order_number DESC'
        }

Order.find(filter, function (err, orders) {..})

@princecharmx
Copy link

princecharmx commented Mar 3, 2018

Hey @elviocb , this must be problem with customer.id stored as String and not ObjectId. Can you share how your Order model looks. It should be related to https://loopback.io/doc/ja/lb3/MongoDB-connector.html#strictobjectidcoercion-flag

@elviocb
Copy link

elviocb commented Mar 3, 2018

hey @princecharmx .. thanks for your reply!

That's my order model definition:

{
  "name": "order",
  "base": "PersistedModel",
  "idInjection": true,
  "options": {
    "validateUpsert": true
  },
  "properties": {
    "status": {
      "type": "string",
      "required": true,
      "default": "open"
    },
    "customer": {
      "type": "object"
    },
    "customer_id": {
      "type": "string"
    },
    "discounts": {
      "type": [
        "object"
      ]
    },
    "discount_codes": {
      "type": [
        "string"
      ]
    },
    "comissions": {
      "type": [
        "object"
      ]
    },
    "email": {
      "type": "string",
      "required": true
    },
    "cart": {
      "type": "object"
    },
    "financial_status": {
      "type": "string",
      "required": true,
      "default": "pending"
    },
    "tags": {
      "type": "string"
    },
    "number": {
      "type": "number",
      "required": true,
      "default": 1000
    },
    "order_number": {
      "type": "number",
      "required": true
    },
    "name": {
      "type": "string",
      "required": true
    },
    "subtotal_price": {
      "type": "string",
      "required": true
    },
    "total_discounts": {
      "type": "string",
      "required": true
    },
    "total_price": {
      "type": "string",
      "required": true
    },
    "total_tax": {
      "type": "string",
      "required": true
    },
    "total_comissions": {
      "type": "string",
      "required": true
    },
    "tax_lines": {
      "type": [
        "object"
      ]
    },
    "cancel_reason": {
      "type": "any"
    },
    "canceled_at": {
      "type": "date"
    }
  },
  "validations": [],
  "relations": {
    "shop": {
      "type": "belongsTo",
      "model": "shop",
      "foreignKey": "shop_id"
    },
    "customers": {
      "type": "hasOne",
      "model": "customer",
      "foreignKey": "customer_id"
    }
  },
  "acls": [],
  "methods": {}
}

And this is how an order model is stored:

{
    "_id" : ObjectId("5a9a9b3b4fd628739efb4972"),
    "status" : "open",
    "customer" : {
        "name" : "Laila ",
        "lastname" : "Gonçalves",
        "email" : "laila@gmail.com",
        "gender" : "female",
        "age" : "23",
        "cpf" : "24898932100",
        "id" : "5a9352c29fe55f9a981661a8",
        "partner_id" : "5a80d66992856a1c701ab2c2"
    },
    "customer_id" : "5a9352c29fe55f9a981661a8",
    "discounts" : [],
    "discount_codes" : [],
    "email" : "laila@gmail.com",
    "cart" : {
        "items" : [ 
            {
                "id" : 994925047,
                "product_id" : "5a77690bd3715f5127e10cff",
                "title" : "Feijoada monster",
                "price" : "14.90",
                "quantity" : 1,
                "image" : "https://dummyimage.com/400x400/000000/ffffff.jpg"
            }, 
            {
                "id" : 518365228,
                "product_id" : "5a77694ad3715f5127e10d00",
                "title" : "Escondidinho Power",
                "price" : "14.90",
                "quantity" : 1,
                "image" : "https://dummyimage.com/400x400/cc14cc/ffffff.jpg"
            }
        ],
        "subtotal_price" : "29.80",
        "total_price" : "29.80",
        "formatted_price" : "R$29.80",
        "item_count" : 2,
        "total_discount" : 0,
        "discounts" : [],
        "tax_lines" : [],
        "total_taxes" : 0,
        "allow_payment_method" : true,
        "allow_discount_override" : false,
        "payment_method" : {
            "id" : "003",
            "display_name" : "Dinheiro",
            "name" : "money",
            "short" : "d"
        },
        "customer" : {
            "name" : "Laila ",
            "lastname" : "Gonçalves",
            "email" : "laila@gmail.com",
            "gender" : "female",
            "age" : "23",
            "cpf" : "24898932100",
            "id" : "5a9352c29fe55f9a981661a8",
            "partner_id" : "5a80d66992856a1c701ab2c2"
        }
    },
    "financial_status" : "pending",
    "tags" : "",
    "number" : 1000,
    "order_number" : 1002,
    "name" : "#1002",
    "subtotal_price" : "29.80",
    "total_discounts" : "0.00",
    "total_price" : "29.80",
    "total_tax" : "0.00",
    "total_comissions" : "5.60",
    "tax_lines" : [],
    "cancel_reason" : null,
    "canceled_at" : null,
    "shop_id" : ObjectId("5a8ddeba4f11315cb6076cc3"),
    "comission_ids" : [ 
        "5a9a9b3b4fd628739efb4973"
    ],
    "id" : ObjectId("5a9a9b3b4fd628739efb4972")
}

@princecharmx
Copy link

princecharmx commented Mar 3, 2018

You have incorrect model. You are having property customer and also have a relation customers with hasOne.
You should use either.

Also, as pointed note, your customer.id is stored as string and hence you are having problem.
Add strictObjectIDCoercion to true in above model.

  "options": {
    "validateUpsert": true,
    "strictObjectIDCoercion": true
  }

Fyi, your usage is incorrect at many places. First you confirm if my suggestion works. I will then guide you next steps.

@elviocb
Copy link

elviocb commented Mar 4, 2018

Hey @princecharmx .. It works. Thanks!.. Could you guide me which other parts of the model are incorrect please?

@kurtommy
Copy link

I can't see valid solution for MYSQL inner join yet, has anyone found at least a workaround?

@jolivervidal
Copy link

I've made a fork of loopback-connector-mysql to support joins. It's not an ideal solution but it works, I'm using it in production in my applications. It only supports read operations.

This is a filter working over the User built-in model, to find all users with an ADMIN name role (the relation between User and Role is needed):

{"where": {"roles": {"name": "ADMIN" }}}

You can check it out https://github.com/jolivervidal/loopback-connector-mysql

@kurtommy
Copy link

@jolivervidal just tested and that work perfectly !!! you save me a lot of time, why this is not included in the standard library? i'll check what you have done in your fork!

@bellgetlinks
Copy link

Right now we have a workaround solution for this case ? :( for mongoDB

@jackrvaughan
Copy link

jackrvaughan commented Aug 24, 2018

@bellgetlinks For mongodb I've ended up opening a connection to mongodb in a boot script and running direct queries and filters on related models using .aggregate and $lookup through remote methods. Essentially sidestepping loopback.

I found this stack overflow post helpful for including related documents in queries.

This is the boot script I used to connect to mongodb and expose it as mongoDB for use in the models:


const MongoClient = require('mongodb').MongoClient;
// Connection URL
const url = '< mongodb connection URL>';
// Database Name
const dbName = '< db name >';

module.exports = function connectToMongoDB(server) {
    // Use connect method to connect to the server
    MongoClient.connect(url).then(client => {
        console.log("Connected successfully to server");
        const db = client.db(dbName);
        server.mongoDB = db
    })
};

@ranggarifqi
Copy link

ranggarifqi commented Oct 1, 2018

I've made a fork of loopback-connector-mysql to support joins. It's not an ideal solution but it works, I'm using it in production in my applications. It only supports read operations.

This is a filter working over the User built-in model, to find all users with an ADMIN name role (the relation between User and Role is needed):

{"where": {"roles": {"name": "ADMIN" }}}

You can check it out https://github.com/jolivervidal/loopback-connector-mysql

@jolivervidal
Thanks dude, it works
I hope it won't create new errors on another things though
how about creating a PR on the official module about this feature you just made ? 😃

@michelgokan
Copy link

What's the status of this issue? Seems a very important feature is missing. @jolivervidal your fork seems working very well, why not merging with the master branch?

@zbarbuto
Copy link
Member

zbarbuto commented Mar 3, 2019

@michelgokan as you'll see from the thread:

  • There is some support in postgres connector
  • Mongo also has some basic support (only for dot nested properties)
  • Further support isn't going to be added to other RDB connectors as loopback 3 is in LTS and no longer accepting new feature PRs
  • Loopback 3 ends LTS at the end of 2019 so it's even less likely to be added.

Options are:

  • Write the custom SQL yourself as a custom endpoint
  • Create an alternate connector for something like Knex or Bookshelf that has support (big job)
  • Do the filtering in memory
  • Use json for the nested data to use dot queries in something like mongo or postgres
  • Look at migrating to loopback next which may add support in the future

@michelgokan
Copy link

michelgokan commented Mar 4, 2019 via email

@jeff3yan
Copy link

jeff3yan commented Mar 4, 2019

Does anyone know if the loopback datasource is compatible with the knex connector? It'd be great to use the same connections for both, to migrate to knex if possible. Then wrap around something like objection.js for filtering on nested relations.

@zbarbuto
Copy link
Member

zbarbuto commented Mar 4, 2019

Does anyone know if the loopback datasource is compatible with the knex connector?

@jeff3yan You'd have to write your own loopback-knex-connector. I've tried a few times but it's a big job and never really got anywhere with it. Having said that it would be an amazingly useful took if someone did manage to crack it. Particularly since join style queries (include) are pretty inefficient in loopback.

@michelgokan if that fork is working for you with MySQL then sounds like a good solution. I agree it's an unfortunate situation for users of other RDBs (like Postgres) but it's just the way loopback works - it does not assume your models are in any particular datasource so it does not leverage the full power of the underlying datasource (you may be querying two models in postgres or one might be in postgres and one might be in Mongo - it makes no assumptions)

@vanderdill
Copy link

@tellex solution works very well for PostgreSQL. Thank you so much!
I've tested many scenarios, with where conditions and order/limit situations and it is exactly what I needed.

Code and instructions:
https://github.com/tellex/findComplex

@alexdee2007
Copy link

#683 (comment)

@vanderdill
Copy link

For those who are using PostgreSQL I'm using @tellex solution, and it's working greate on a large project.
I've done some bug fixes and I'm keeping this fork updated with it:
https://github.com/vanderdill/findComplex

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

No branches or pull requests