blakeembrey / sql-template-tag

ES2015 tagged template string for preparing SQL statements, works with `pg`, `mysql`, and `sqlite`

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Ways to simplify dynamic query building

ivan-kleshnin opened this issue · comments

How would you shorten the following kind of code (which appears as soon as you start to build an API and accept filtering conditions via JSON)?

  let filter = {
    id: {eq: "123"},
    username: {neq: "ivan-kleshnin"},
    // etc
  }

  function makeWhere(filter) {
    return sql`
      WHERE 1 = 1
      ${
        filter.id ? (
          sql`AND "account"."id" ${
            filter.id.eq  ? sql`= ${filter.id.eq}` :
            filter.id.neq ? sql`<> ${filter.id.neq}` : sql.empty  
          }`         
        ) : sql.empty
      }  
      ${
        filter.username ? (
          sql`AND "account"."username" ${
            filter.username.eq ? sql`= ${filter.username.eq}` :
            filter.username.neq ? sql`<> ${filter.username.neq}` : sql.empty  
          }`   
        ) : sql.empty
      }  
    `
  }

  let account = await pg.query(sql`
    SELECT * FROM "account"
    ${makeWhere(filter)}
    LIMIT 1
  `).firstRow()

For now, I ended up with this:

  // TODO escape
  function whereTable(tableName, filter) {
    // simplified: only string/number values, only two operators
    function whereField(fieldName, value) {
      return sql`"${sql.raw(tableName)}"."${sql.raw(fieldName)}" ${
        value.eq  ? sql`= ${value.eq}` :
        value.neq ? sql`<> ${value.neq}` : sql.empty  
      }`
    }

    return sql`
      WHERE 1 = 1
      ${sql.join(
        Object.keys(filter).map(k => sql` AND ${whereField(k, filter[k])}`)
      , "")}  
    `
  }

...

let account = await pg.query(sql`
    SELECT * FROM "account"
    ${whereTable("account", filter)}
    LIMIT 1
  `).firstRow()

It would be possible to get rid of 1 = 1 placeholder if sql.join accepted sql values as separators:

    return sql`
      WHERE 1 = 1
      ${sql.join(
        Object.keys(filter).map(k => whereField(k, filter[k]))
      , sql` AND `)}  
    `

if sql.join accepted sql values as separators

It should accept SQL values today, it supports the same thing as any values of the sql tag. Using your last example, it'd look like this:

join(Object.keys(filter).map(k => whereField(k, filter[k])), " AND ")

You can't put SQL into the join value, only strings.

How would you shorten the following kind of code

You can also do Object.keys(filter).length === 0 return return empty so you don't have any invalid WHERE. Additionally, I think join solves your last issue.

Yeah, right. It works like that. Thank you!