diff --git a/async_postgres/pg_client.nim b/async_postgres/pg_client.nim index 3faa704..14862d3 100644 --- a/async_postgres/pg_client.nim +++ b/async_postgres/pg_client.nim @@ -1188,8 +1188,6 @@ proc queryRowOpt*( ## Execute a query and return the first row, or `none` if no rows. let qr = await conn.query(sql, params, resultFormat = resultFormat, timeout = timeout) if qr.rowCount > 0: - if qr.fields.len > 0 and qr.data.fields.len == 0: - qr.data.fields = qr.fields return some(initRow(qr.data, 0)) else: return none(Row) @@ -1202,11 +1200,11 @@ proc queryRow*( timeout: Duration = ZeroDuration, ): Future[Row] {.async.} = ## Execute a query and return the first row. - ## Raises `PgError` if no rows are returned. + ## Raises `PgNoRowsError` if no rows are returned. let row = await conn.queryRowOpt(sql, params, resultFormat = resultFormat, timeout = timeout) if row.isNone: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") return row.get proc queryValue*( @@ -1216,13 +1214,13 @@ proc queryValue*( timeout: Duration = ZeroDuration, ): Future[string] {.async.} = ## Execute a query and return the first column of the first row as a string. - ## Raises PgError if no rows are returned or the value is NULL. + ## Raises `PgNoRowsError` if no rows are returned, or `PgNullError` if the value is NULL. let qr = await conn.query(sql, params, timeout = timeout) if qr.rowCount == 0: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") let row = initRow(qr.data, 0) if row.isNull(0): - raise newException(PgError, "Query returned NULL") + raise newException(PgNullError, "Query returned NULL") return row.getStr(0) proc queryValue*[T]( @@ -1233,14 +1231,14 @@ proc queryValue*[T]( timeout: Duration = ZeroDuration, ): Future[T] {.async.} = ## Execute a query and return the first column of the first row as `T`. - ## Raises PgError if no rows are returned or the value is NULL. + ## Raises `PgNoRowsError` if no rows are returned, or `PgNullError` if the value is NULL. ## Supported types: int32, int64, float64, bool, string. let qr = await conn.query(sql, params, timeout = timeout) if qr.rowCount == 0: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") let row = initRow(qr.data, 0) if row.isNull(0): - raise newException(PgError, "Query returned NULL") + raise newException(PgNullError, "Query returned NULL") return row.get(0, T) proc queryValueOpt*( @@ -1349,12 +1347,12 @@ proc queryColumn*( timeout: Duration = ZeroDuration, ): Future[seq[string]] {.async.} = ## Execute a query and return the first column of all rows as strings. - ## Raises PgTypeError if any value is NULL. + ## Raises `PgNullError` if any value is NULL. let qr = await conn.query(sql, params, timeout = timeout) for i in 0 ..< qr.rowCount: let row = initRow(qr.data, i) if row.isNull(0): - raise newException(PgTypeError, "NULL value in column") + raise newException(PgNullError, "NULL value in column") result.add(row.getStr(0)) proc prepareImpl( diff --git a/async_postgres/pg_errors.nim b/async_postgres/pg_errors.nim index 0cd55a8..f127c4a 100644 --- a/async_postgres/pg_errors.nim +++ b/async_postgres/pg_errors.nim @@ -13,6 +13,13 @@ type PgTypeError* = object of PgError ## Raised when a PostgreSQL value cannot be converted to the requested Nim type. + PgNoRowsError* = object of PgError + ## Raised by single-row/single-value queries when the result set is empty. + + PgNullError* = object of PgError + ## Raised by single-value queries when the value is SQL NULL and the + ## caller requested a non-nullable result. + PgConnectionError* = object of PgError ## Connection failures, disconnections, SSL/auth errors. diff --git a/async_postgres/pg_pool.nim b/async_postgres/pg_pool.nim index bbd6a7d..7efe184 100644 --- a/async_postgres/pg_pool.nim +++ b/async_postgres/pg_pool.nim @@ -745,8 +745,6 @@ proc queryRowOpt*( ## Execute a query and return the first row, or `none` if no rows. let qr = await pool.query(sql, params, resultFormat, timeout) if qr.rowCount > 0: - if qr.fields.len > 0 and qr.data.fields.len == 0: - qr.data.fields = qr.fields return some(initRow(qr.data, 0)) return none(Row) @@ -758,11 +756,11 @@ proc queryRow*( timeout: Duration = ZeroDuration, ): Future[Row] {.async.} = ## Execute a query and return the first row. - ## Raises `PgError` if no rows are returned. + ## Raises `PgNoRowsError` if no rows are returned. let row = await pool.queryRowOpt(sql, params, resultFormat = resultFormat, timeout = timeout) if row.isNone: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") return row.get proc queryValue*( @@ -772,13 +770,13 @@ proc queryValue*( timeout: Duration = ZeroDuration, ): Future[string] {.async.} = ## Execute a query and return the first column of the first row as a string. - ## Raises `PgError` if no rows or the value is NULL. + ## Raises `PgNoRowsError` if no rows are returned, or `PgNullError` if the value is NULL. let qr = await pool.query(sql, params, timeout = timeout) if qr.rowCount == 0: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") let row = initRow(qr.data, 0) if row.isNull(0): - raise newException(PgError, "Query returned NULL") + raise newException(PgNullError, "Query returned NULL") return row.getStr(0) proc queryValue*[T]( @@ -789,13 +787,13 @@ proc queryValue*[T]( timeout: Duration = ZeroDuration, ): Future[T] {.async.} = ## Execute a query and return the first column of the first row as `T`. - ## Raises `PgError` if no rows or the value is NULL. + ## Raises `PgNoRowsError` if no rows are returned, or `PgNullError` if the value is NULL. let qr = await pool.query(sql, params, timeout = timeout) if qr.rowCount == 0: - raise newException(PgError, "Query returned no rows") + raise newException(PgNoRowsError, "Query returned no rows") let row = initRow(qr.data, 0) if row.isNull(0): - raise newException(PgError, "Query returned NULL") + raise newException(PgNullError, "Query returned NULL") return row.get(0, T) proc queryValueOpt*( @@ -901,12 +899,12 @@ proc queryColumn*( timeout: Duration = ZeroDuration, ): Future[seq[string]] {.async.} = ## Execute a query and return the first column of all rows as strings. - ## Raises PgTypeError if any value is NULL. + ## Raises `PgNullError` if any value is NULL. let qr = await pool.query(sql, params, timeout = timeout) for i in 0 ..< qr.rowCount: let row = initRow(qr.data, i) if row.isNull(0): - raise newException(PgTypeError, "NULL value in column") + raise newException(PgNullError, "NULL value in column") result.add(row.getStr(0)) proc simpleQuery*(pool: PgPool, sql: string): Future[seq[QueryResult]] {.async.} = diff --git a/tests/test_e2e.nim b/tests/test_e2e.nim index 13a1c65..027d00d 100644 --- a/tests/test_e2e.nim +++ b/tests/test_e2e.nim @@ -4952,7 +4952,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryRow("SELECT 1 WHERE false") - except PgError: + except PgNoRowsError: raised = true doAssert raised await conn.close() @@ -4974,7 +4974,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryValue("SELECT 1 WHERE false") - except PgError: + except PgNoRowsError: raised = true doAssert raised await conn.close() @@ -4987,7 +4987,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryValue("SELECT NULL::text") - except PgError: + except PgNullError: raised = true doAssert raised await conn.close() @@ -5043,7 +5043,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryValue(int32, "SELECT 1 WHERE false") - except PgError: + except PgNoRowsError: raised = true doAssert raised await conn.close() @@ -5056,7 +5056,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryValue(int64, "SELECT NULL::int8") - except PgError: + except PgNullError: raised = true doAssert raised await conn.close() @@ -5184,7 +5184,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await conn.queryColumn("SELECT NULL::text") - except PgTypeError: + except PgNullError: raised = true doAssert raised await conn.close() @@ -5271,7 +5271,7 @@ suite "E2E: Convenience Query Methods": var raised = false try: discard await pool.queryRow("SELECT 1 WHERE false") - except PgError: + except PgNoRowsError: raised = true doAssert raised await pool.close() @@ -5287,6 +5287,32 @@ suite "E2E: Convenience Query Methods": waitFor t() + test "pool queryValue raises on no rows": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + var raised = false + try: + discard await pool.queryValue("SELECT 1 WHERE false") + except PgNoRowsError: + raised = true + doAssert raised + await pool.close() + + waitFor t() + + test "pool queryValue raises on NULL": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + var raised = false + try: + discard await pool.queryValue("SELECT NULL::text") + except PgNullError: + raised = true + doAssert raised + await pool.close() + + waitFor t() + test "pool queryExists": proc t() {.async.} = let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) @@ -5336,6 +5362,32 @@ suite "E2E: Convenience Query Methods": waitFor t() + test "pool queryValue with typedesc raises on no rows": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + var raised = false + try: + discard await pool.queryValue(int32, "SELECT 1 WHERE false") + except PgNoRowsError: + raised = true + doAssert raised + await pool.close() + + waitFor t() + + test "pool queryValue with typedesc raises on NULL": + proc t() {.async.} = + let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2)) + var raised = false + try: + discard await pool.queryValue(int64, "SELECT NULL::int8") + except PgNullError: + raised = true + doAssert raised + await pool.close() + + waitFor t() + test "pool queryValueOrDefault with typedesc": proc t() {.async.} = let pool = await newPool(initPoolConfig(plainConfig(), minSize = 1, maxSize = 2))