pgo icon indicating copy to clipboard operation
pgo copied to clipboard

rows_as_map context lost in transaction connection

Open distrill opened this issue 2 months ago • 3 comments

Hello!

Since upgrading pog to version 4, I am seeing some unexpected behavior with the feature to parse rows as a map when inside a transaction. My application, which was working with pog version 3, has started to fail because my decoders that are in transactions no longer have access to map data.

I have included a minimal reproduction. Note that the connection is instantiated with rows_as_map(True) which behaves as expected in a normal query on the connection itself, but that it reverts to tuple access when inside a transaction.

Maybe I'm holding it wrong? But since this seems to have been working before, maybe it's worth creating an issue.

import gleam/dynamic/decode
import gleam/erlang/process
import gleam/io
import gleam/result
import gleam/string
import pog

pub fn main() -> Nil {
  let db = init()

  let assert Ok(rows) =
    "
    SELECT * FROM test
  "
    |> pog.query()
    |> pog.returning({
      use id <- decode.field("id", decode.int)
      use name <- decode.field("name", decode.string)
      decode.success(#(id, name))
    })
    |> pog.execute(db)

  io.println("Rows: " <> rows |> string.inspect())

  let _ =
    pog.transaction(db, fn(tx) {
      let assert Ok(rows) =
        "
        SELECT * FROM test
      "
        |> pog.query()
        |> pog.returning({
          // this works, but it should not because i set rows_as_map on the connection
          let id = decode.at([0], decode.int)
          let name = decode.at([1], decode.string)
          decode.success(#(id, name))
        })
        |> pog.execute(tx)

      io.println(
        "THIS WILL PRINT but it shouldn't rows: " <> rows |> string.inspect(),
      )
      Ok(rows)
    })

  let _ =
    pog.transaction(db, fn(tx) {
      let assert Ok(rows) =
        "
        SELECT * FROM test
      "
        |> pog.query()
        |> pog.returning({
          // this fails, because the rows_as_map is not being applied in the transaction
          use id <- decode.field("id", decode.int)
          use name <- decode.field("name", decode.string)
          decode.success(#(id, name))
        })
        |> pog.execute(tx)

      io.println("THIS WILL NOT PRINT TX rows: " <> rows |> string.inspect())
      Ok(rows)
    })

  Nil
}

fn init() {
  let name = process.new_name("db_pog_test")
  let assert Ok(started) =
    name
    |> pog.url_config("postgresql://pog_test:pog_test@localhost:5434/pog_test")
    |> result.unwrap(pog.default_config(name))
    |> pog.rows_as_map(True)
    |> pog.start()

  let db = started.data

  let assert Ok(_) =
    "
      CREATE TABLE IF NOT EXISTS test (
        id SERIAL PRIMARY KEY,
        name TEXT NOT NULL
      );
    "
    |> pog.query()
    |> pog.execute(db)

  let assert Ok(_) =
    "
      INSERT INTO test(name) VALUES ('pog_test');
    "
    |> pog.query()
    |> pog.execute(db)

  db
}

distrill avatar Sep 30 '25 15:09 distrill

Oh dear! That's unfortunate. Thank you for the report

lpil avatar Oct 02 '25 09:10 lpil

I don't have enough time to make a real PR for this (because it would need more than 20 minutes reading the code), but I investigated a bit the issue. The root cause seems to come from pgo_handler:extended_query, used in pog_ffi:105. I'm sharing what I found, in hope it could help.

Actually, as we can see in pgo, extended_query/6 requires the default formatting options provided as argument. We can provide default formatting options through extended_query/5. It works with regular pgo:query/*, because we can see at pgo:14, that pgo injects the formatting options in extended_query.

I suspect pog_ffi:88 to fail too, but probably that everyone is actually using the pooled version of pog.

ghivert avatar Oct 24 '25 15:10 ghivert

This bit me last night too, though I was crazy for a second. Will spends some time trying to fix it.

gmartsenkov avatar Nov 10 '25 09:11 gmartsenkov