diff --git a/async_postgres/pg_client.nim b/async_postgres/pg_client.nim index 644d768..37244d1 100644 --- a/async_postgres/pg_client.nim +++ b/async_postgres/pg_client.nim @@ -861,6 +861,21 @@ proc queryOne*( else: return none(Row) +proc queryRow*( + conn: PgConnection, + sql: string, + params: seq[PgParam] = @[], + resultFormat: ResultFormat = rfAuto, + timeout: Duration = ZeroDuration, +): Future[Row] {.async.} = + ## Execute a query and return the first row. + ## Raises `PgError` if no rows are returned. + let row = + await conn.queryOne(sql, params, resultFormat = resultFormat, timeout = timeout) + if row.isNone: + raise newException(PgError, "Query returned no rows") + return row.get + proc queryValue*( conn: PgConnection, sql: string, diff --git a/async_postgres/pg_pool.nim b/async_postgres/pg_pool.nim index c33bc8f..1f312c2 100644 --- a/async_postgres/pg_pool.nim +++ b/async_postgres/pg_pool.nim @@ -685,6 +685,21 @@ proc queryOne*( return some(Row(data: qr.data, rowIdx: 0)) return none(Row) +proc queryRow*( + pool: PgPool, + sql: string, + params: seq[PgParam] = @[], + resultFormat: ResultFormat = rfAuto, + timeout: Duration = ZeroDuration, +): Future[Row] {.async.} = + ## Execute a query and return the first row. + ## Raises `PgError` if no rows are returned. + let row = + await pool.queryOne(sql, params, resultFormat = resultFormat, timeout = timeout) + if row.isNone: + raise newException(PgError, "Query returned no rows") + return row.get + proc queryValue*( pool: PgPool, sql: string, diff --git a/async_postgres/pg_pool_cluster.nim b/async_postgres/pg_pool_cluster.nim index 13b1425..8af5eca 100644 --- a/async_postgres/pg_pool_cluster.nim +++ b/async_postgres/pg_pool_cluster.nim @@ -237,6 +237,14 @@ clusterForwards("read"): timeout: Duration = ZeroDuration, ): Future[Option[Row]] + proc readQueryRow*( + cluster: PgPoolCluster, + sql: string, + params: seq[PgParam] = @[], + resultFormat: ResultFormat = rfAuto, + timeout: Duration = ZeroDuration, + ): Future[Row] + proc readQueryValue*( cluster: PgPoolCluster, sql: string, @@ -335,6 +343,14 @@ clusterForwards("write"): timeout: Duration = ZeroDuration, ): Future[Option[Row]] + proc writeQueryRow*( + cluster: PgPoolCluster, + sql: string, + params: seq[PgParam] = @[], + resultFormat: ResultFormat = rfAuto, + timeout: Duration = ZeroDuration, + ): Future[Row] + proc writeQueryValue*( cluster: PgPoolCluster, sql: string, diff --git a/examples/pool.nim b/examples/pool.nim index 1285999..10be154 100644 --- a/examples/pool.nim +++ b/examples/pool.nim @@ -6,7 +6,6 @@ ## Usage: ## nim c -r examples/pool.nim -import std/options import pkg/async_postgres proc main() {.async.} = @@ -49,8 +48,8 @@ proc main() {.async.} = proc cheapest(): Future[string] {.async.} = pool.withConnection(conn): - let row = await conn.queryOne("SELECT name FROM products ORDER BY price LIMIT 1") - return options.get(row).getStr("name") + let row = await conn.queryRow("SELECT name FROM products ORDER BY price LIMIT 1") + return row.getStr("name") # Launch concurrently, then await each result let expCountFut = countExpensive() diff --git a/tests/test_e2e.nim b/tests/test_e2e.nim index 80f705b..4a2fe6e 100644 --- a/tests/test_e2e.nim +++ b/tests/test_e2e.nim @@ -4465,6 +4465,29 @@ suite "E2E: Convenience Query Methods": waitFor t() + test "queryRow returns first row": + proc t() {.async.} = + let conn = await connect(plainConfig()) + let row = await conn.queryRow("SELECT 1 AS a, 'hello' AS b") + doAssert row.getStr(0) == "1" + doAssert row.getStr(1) == "hello" + await conn.close() + + waitFor t() + + test "queryRow raises on no rows": + proc t() {.async.} = + let conn = await connect(plainConfig()) + var raised = false + try: + discard await conn.queryRow("SELECT 1 WHERE false") + except PgError: + raised = true + doAssert raised + await conn.close() + + waitFor t() + test "queryValue returns scalar": proc t() {.async.} = let conn = await connect(plainConfig()) @@ -4736,6 +4759,28 @@ suite "E2E: Convenience Query Methods": waitFor t() + test "pool queryRow": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + let row = await pool.queryRow("SELECT 'pooled' AS v") + doAssert row.getStr("v") == "pooled" + await pool.close() + + waitFor t() + + test "pool queryRow raises on no rows": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + var raised = false + try: + discard await pool.queryRow("SELECT 1 WHERE false") + except PgError: + raised = true + doAssert raised + await pool.close() + + waitFor t() + test "pool queryValue": proc t() {.async.} = let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) diff --git a/tests/test_pool_cluster.nim b/tests/test_pool_cluster.nim index 0850c62..c2a63a8 100644 --- a/tests/test_pool_cluster.nim +++ b/tests/test_pool_cluster.nim @@ -360,6 +360,13 @@ suite "Write routing": expect(PgError): discard waitFor cluster.writeQueryOne("SELECT 1") + test "writeQueryRow routes to primary": + let cluster = makeCluster() + cluster.primary.closed = true + + expect(PgError): + discard waitFor cluster.writeQueryRow("SELECT 1") + test "writeQueryValue routes to primary": let cluster = makeCluster() cluster.primary.closed = true @@ -456,6 +463,14 @@ suite "Read routing targets replica": expect(PgError): discard waitFor cluster.readQueryOne("SELECT 1") + test "readQueryRow routes to replica": + let cluster = makeCluster() + cluster.replica.closed = true + cluster.fallback = fallbackNone + + expect(PgError): + discard waitFor cluster.readQueryRow("SELECT 1") + test "readQueryValue routes to replica": let cluster = makeCluster() cluster.replica.closed = true