loopback-next icon indicating copy to clipboard operation
loopback-next copied to clipboard

Include filter generating multiple queries instead of one

Open nivruth-naka opened this issue 1 year ago • 21 comments

Describe the bug

https://github.com/loopbackio/loopback-next/discussions/9344

Logs

https://github.com/nivruth-naka/lb-include/blob/main/lb-include.log

Additional information

No response

Reproduction

https://github.com/nivruth-naka/lb-include

nivruth-naka avatar Mar 15 '23 07:03 nivruth-naka

The Cause

Loopback never run SQL JOIN Queries internally. It uses inclusion resolvers which is responsible to include related models and is called after your source table's data is returned. Resulting in two different calls when you use include in your filter object.

This behaviour is driven by juggler (the ORM/ODM loopback uses), and the reasoning might be that it provides a common set of interfaces for interacting with different data sources.

You might say that "We're just running SQL database, it should support JOINs in this case". Well yes, but the bad news is this is currently not the case.

The Solution

To address this problem, we at ARC have developed an extension for LoopBack that utilizes Sequelize ORM, which supports join queries for SQL databases. This extension has recently been added as the official Loopback framework extension and can be found on the npm package page:

https://www.npmjs.com/package/@loopback/sequelize

Although the package is labeled as experimental, it has been tested and is continually being improved based on feedback. Additionally, two of the limitations mentioned in the README are expected to be resolved real soon as their PR is opened for review.

Please feel free to use the extension and provide feedback or report any issues on Github as the I'm actively addressing them.

shubhamp-sf avatar Mar 15 '23 11:03 shubhamp-sf

@shubhamp-sf ,

getting this error in spite of adding primaryKey: true. any idea?

Error: A column called 'id' was added to the attributes of 'main' but not marked with 'primaryKey: true'

nivruth-naka avatar Mar 15 '23 16:03 nivruth-naka

Is this the correct way to add?

nivruth-naka avatar Mar 15 '23 16:03 nivruth-naka

Yes, it is. Please use id: true along with it.

shubhamp-sf avatar Mar 15 '23 16:03 shubhamp-sf

thanks @shubhamp-sf . now seeing

Error: SequelizeConnectionRefusedError: connect ECONNREFUSED 127.0.0.1:5432

how does datasource setup connection? is it different from default juggler? I don't have 127.0.0.1:5432 in my code, so unsure where it's picking this from

nivruth-naka avatar Mar 15 '23 16:03 nivruth-naka

It's not different from juggler, but this port is default for postgresql so it might be the case that you're missing out those config in the datasource.

Similar to Below is what works for both juggler and sequelize.

const config = {
  name: 'db',
  connector: 'postgresql',
  host: 'localhost',
  port: 5003,
  user: 'postgres',
  password: 'super-secret',
  database: 'postgres',
};

see if you are missing anything?

shubhamp-sf avatar Mar 15 '23 16:03 shubhamp-sf

We are actually reading this from a .env file, this process works fine if I revert Sequelize changes back to Juggler but somehow Sequelize datasource is looking for something else.

nivruth-naka avatar Mar 15 '23 17:03 nivruth-naka

Currently url strings are not respected in the sequelize extension. I've noted down the issue and it'll be fixed in the upcoming releases. Meanwhile you can use the specific keys to specify settings like host, port, etc. (they can come from .env)

shubhamp-sf avatar Mar 15 '23 17:03 shubhamp-sf

@shubhamp-sf, in that case it looks like it's defaulting to postgres local credentials, would be good to throw an error right away. if possible, could you please point me to a working example for the connection using host, port, etc?

nivruth-naka avatar Mar 15 '23 17:03 nivruth-naka

Sure here you go: https://github.com/shubhamp-sf/loopback4-sequelize-transaction-example

shubhamp-sf avatar Mar 15 '23 17:03 shubhamp-sf

@shubhamp-sf , seeing status code 500. Error: SequelizeDatabaseError: operator does not exist: jsonb ~~* unknown

does this work for postgres jsonb columns as well? if yes, please suggest how to fix the filter

{
  "where": {
    "and": [
      {
        "effectiveDate": {
          "lte": "2023-03-01"
        }
      },
      {
        "expiryDate": {
          "gte": "2023-03-01"
        }
      }
    ]
  },
  "order": "code asc",
  "include": [
    {
      "relation": "trans",
      "scope": {
        "where": {
          "and": [
            {
              "segment": {
                "ilike": "%Ware%"
              }
            }
          ]
        }
      },
      "required": true
    }
  ]
}

nivruth-naka avatar Mar 15 '23 18:03 nivruth-naka

My use case is basically to include some strings and do a case insensitive search within a jsonb column. The above filter (without required = true) works in Loopback / Juggler but is too slow as there are multiple queries.

nivruth-naka avatar Mar 15 '23 19:03 nivruth-naka

does this work for postgres jsonb columns as well?

This seems to a postgres specific type, and current release of @loopback/sequelize doesn't address this type. Will raise a PR for this in a day or two and let you know.

Thanks for sharing your use-case.

shubhamp-sf avatar Mar 16 '23 03:03 shubhamp-sf

Thanks @shubhamp-sf ! yes, it is postgres specific but a widely used column type and the current default implementation of Loopback (with Juggler) supports this type of querying.

nivruth-naka avatar Mar 16 '23 06:03 nivruth-naka

I did a quick test and got jsonb column to work with loopback-sequelize

For the model, you have to define it using "object" like so:

  @property({
    type: 'object',
    postgresql: {
      dataType: 'jsonb',
    },
  })
  test?: object;

For the filter, the dot notation could be used where "test" is the jsonb column:

{
  "where": {
  "and":[
    {"test.name": { "ilike": "%an%" }},
    {"test.address.city": { "ilike": "%no%" }}
   ]
  }
}

yhrchan avatar Mar 17 '23 07:03 yhrchan

Thanks @yhrchan , great catch! I'll try this out shortly from my end to see if this helps. However, one thing to keep in mind is this nested format you showed above is different from the include syntax used in Juggler/Sequelize for relational object querying and may impact the underlying querying process. But if we are able to use jsonb with this nested syntax and internally have sequelize generate just one join query then it will help us. cc : @shubhamp-sf

nivruth-naka avatar Mar 17 '23 07:03 nivruth-naka

@nivruth-naka The reason it works with default juggler without specifying property as object is that they to do type-casting hack (see the ::TEXT in your query logs). Which stringifies the jsonb data and perform regular ilike match as It was with text. Which in my opinion isn't utilising jsonb capabilities and might lead to bad performance when the jsonb column contains heavily nested data.

PS: Thanks so much @yhrchan for testing this out.

shubhamp-sf avatar Mar 17 '23 07:03 shubhamp-sf

@shubhamp-sf do you know how to enable SQL logging outputs from loopback-sequelize?

yhrchan avatar Mar 17 '23 09:03 yhrchan

@yhrchan loopback:sequelize:queries is used for that purpose.

It's mentioned in the debug string ref here.

shubhamp-sf avatar Mar 17 '23 09:03 shubhamp-sf

@shubhamp-sf thanks got it to work

@nivruth-naka

{
  "where": {
    "code": {
            "ilike": "%33%"
          }
  },
  "include": [
    {
      "relation": "trans",
      "scope": {
        "where": {
          "test.name": {
            "ilike": "%o%"
          }
        }
      },
      "required": true
    }
  ]
}

yhrchan avatar Mar 17 '23 10:03 yhrchan

Same problem here, and it is not only the way of querying but also that the relationships of the includes do not take them into account in the main table. Exmaple: I want to obtain the products including the category where the category name is 'CLOTHING'

From what I see, it launches 2 queries when it should be one, and it shows me both products with category and without category.

pookdeveloper avatar Nov 02 '23 13:11 pookdeveloper