How to bind to parametrized query

I got stuck with writing the binding to parametrized query() call from

const text = 'INSERT INTO users(name, email) VALUES($1, $2) RETURNING *'
const values = ['brianc', '']
// callback
client.query(text, values, (err, res) => {
  if (err) {
  } else {
    // { name: 'brianc', email: '' }

The input parameters array values is not homogeneous. The array may contain strings or numbers. Perhaps I could type it as array of strings and use strings only for query parameter values.

But returned result res is array of not homogeneous hash map. The hash map key is always string but value may be string or number. How do I type such a structure and bind to it?

1 Like

I think I found the way. I just have to use Js.Json.t

For the output rows, Js.Json.t is certainly a valid option, but having to decode every row when they are all guaranteed by the database to be the same shape, is a bit of a bummer. I would probably do something like this:

The key is the usage in the following lines:

module UserQuery = Query({
  type row = {"name": string, "email": string}
  let sql = "INSERT INTO users(name, email) VALUES($1, $2) RETURNING *"

let test = client =>
    [Client.string("brianc"), Client.string("")],
    (err, rows) =>
      switch err {
      | Some(e) => Js.log(Js.Exn.stack(e))
      | None => Js.log(rows["rows"][0]["name"])

The Query functor ties together the SQL query and its result row type in one place, meaning you just need to pay attention to that one place and take care to get the type correct there. The functor takes care of the details of the external binding.

After that you get the UserQuery module which automatically has the correct result row type for the query that it runs. You just need to pass it the input parameters and process the result.

The input parameters are being modelled as an abstract type using the ‘identity’ trick–the same thing that ReasonReact e.g. uses for rendering different types of content values in JSX.

1 Like

Thanks for your working example, I think I got it. It was definitely enlightening to see how elegant and simple this could be solved.

1 Like

Here’s my implementation without the functor:

It is a bit simpler, but note that the type annotation at the usage site doesn’t actually enforce correct usage. E.g. you could potentially use the same query in different places with different result types. Not saying that you would, but it’s possible. The functor version ties them together so you would enforce that whenever you ran, it would always give you results of the same type. And it just needs to be checked at one place–the functor definition site.

Also I noticed that the API provides a promise version in addition to the callback version. I would recommend using the promise API as it’s a bit easier to get right than callbacks.