porsager / postgres

Postgres.js - The Fastest full featured PostgreSQL client for Node.js, Deno, Bun and CloudFlare
The Unlicense
7.04k stars 257 forks source link

Issue with dynamically building condition clause #813

Open lz000 opened 4 months ago

lz000 commented 4 months ago

I try to build a dynamic query with dynamic filters within a transaction but does not work. It returns 0 results.

Here is my code

let dynamicFilters = [
        transactionSql`id=${id}`,
        transactionSql`name=${name}`,
    ];

await transactionSql`
SELECT * FROM table WHERE ${ dynamicFilters.join(' AND ') }
`

I also tried below but does not work either

await transactionSql`
SELECT * FROM table WHERE ${ transactionSql`${ dynamicFilters.join(' AND ') }` }
`

If I replace the expression with the actual sql string it will work

await transactionSql`
SELECT * FROM table WHERE id=${id} AND name=${name} }
`

Any idea why?

raprocks commented 4 months ago

try debugging the query that the client creates, maybe might be helpful to understand if your conditions are correct.

  debug: (
    conn: number,
    query: string,
    params: unknown[],
    paramTypes: unknown[],
  ) => {
    logger.debug(`
    SQL::
      Executing query: "${query.trim()}"
      Params: ${JSON.stringify(params)}
      Param Types: ${JSON.stringify(paramTypes)}
      Connection: ${conn}
    `);
  },

put this in the postgres initialization options

lz000 commented 4 months ago

Here is my test results

let dynamicFilters = [
        transactionSql`id=${id}`,
        transactionSql`name=${name}`,
    ];

await transactionSql`
SELECT * FROM table WHERE ${ dynamicFilters.join(' AND ') }
`

The generated sql is SELECT * FROM table WHERE $1 and the param is [object Promise]

let dynamicFilters = [
        `id=${id}`,
        `name=${name}`,
    ];

await transactionSql`
SELECT * FROM table WHERE ${ transactionSql`${ dynamicFilters.join(' AND ') }` }
`

The generated sql is SELECT * FROM table WHERE $1 and the param is id=x AND name=x

let dynamicFilters = [
        transactionSql`id=${id}`,
        transactionSql`name=${name}`,
    ];

await transactionSql`
SELECT * FROM table WHERE ${ transactionSql`${ dynamicFilters.join(' AND ') }` }
`

The generated sql is SELECT * FROM table WHERE $1 and the param is ["[object Promise]"

try debugging the query that the client creates, maybe might be helpful to understand if your conditions are correct.

  debug: (
    conn: number,
    query: string,
    params: unknown[],
    paramTypes: unknown[],
  ) => {
    logger.debug(`
    SQL::
      Executing query: "${query.trim()}"
      Params: ${JSON.stringify(params)}
      Param Types: ${JSON.stringify(paramTypes)}
      Connection: ${conn}
    `);
  },

put this in the postgres initialization options

raprocks commented 4 months ago

the filter fragments are being generated as promises instead of SQL query fragments. maybe await before the fragments can help?

lz000 commented 4 months ago

the filter fragments are being generated as promises instead of SQL query fragments. maybe await before the fragments can help?

let dynamicFilters = [
        await transactionSql`id=${id}`,
        await transactionSql`name=${name}`,
    ];

await transactionSql`
SELECT * FROM table WHERE ${ dynamicFilters.join(' AND ') }
`

I tried that too, but got error invalid sql id=$1. Seems like it try to run each snippet

henryzhang03 commented 2 months ago

bumping this, getting exactly the [object Promise] issue.

henrywalters commented 2 months ago

Bumping as well - pretty big deal breaker sadly :(

Marviel commented 2 months ago

Currently this requires you to use sql.unsafe to get around it, which is ugly and ... unsafe

const filters = [
    'foo IN ('nice')',
    'foo IN ('cool')
]

await sql`
SELECT foo FROM bar
${sql.unsafe(WHERE ${filters.join (' AND ')})}
`

@porsager I'm not an expert in either lib, but it seems the library-level fix for this would be a sql-safe .join, similar to what slonik has ?

What do you think?

granthusbands commented 1 month ago

So the problem you've got is that Array.prototype.join returns a string, and sql is not just strings. You need to use a join that correctly handles sql and parameters. You need each part to be an sql fragment, rather than a string. Look at the code in issue #807 if you're unsure.

(Note: Do not use unsafe or await for this; they both will have unexpected outcomes.)