diff --git a/async_postgres/pg_types.nim b/async_postgres/pg_types.nim index 5eec51c..0422371 100644 --- a/async_postgres/pg_types.nim +++ b/async_postgres/pg_types.nim @@ -1746,6 +1746,24 @@ proc getInt*(row: Row, col: int): int32 = raise newException(PgTypeError, "Column " & $col & ": invalid integer value") result = int32(v) +proc getInt16*(row: Row, col: int): int16 = + ## Get a column value as int16. Handles binary int2 directly. Raises `PgTypeError` on NULL. + let (off, clen) = cellInfo(row, col) + if clen == -1: + raise newException(PgTypeError, "Column " & $col & " is NULL") + if row.isBinaryCol(col): + if clen == 2: + let b = row.data.buf + return int16((uint16(b[off]) shl 8) or uint16(b[off + 1])) + raise newException( + PgTypeError, + "Column " & $col & ": unexpected binary length " & $clen & " for int16", + ) + var v: int + if parseInt(row.bufView(off, clen), v) == 0: + raise newException(PgTypeError, "Column " & $col & ": invalid int16 value") + result = int16(v) + proc getInt64*(row: Row, col: int): int64 = ## Get a column value as int64. Handles binary int2/4/8 directly. Raises `PgTypeError` on NULL. let (off, clen) = cellInfo(row, col) @@ -1803,6 +1821,25 @@ proc getFloat*(row: Row, col: int): float64 = return float64(f32) discard parseFloat(row.bufView(off, clen), result) +proc getFloat32*(row: Row, col: int): float32 = + ## Get a column value as float32. Handles binary float4 directly. Raises `PgTypeError` on NULL. + let (off, clen) = cellInfo(row, col) + if clen == -1: + raise newException(PgTypeError, "Column " & $col & " is NULL") + if row.isBinaryCol(col): + if clen == 4: + var bits: uint32 + let b = row.data.buf + bits = + (uint32(b[off]) shl 24) or (uint32(b[off + 1]) shl 16) or + (uint32(b[off + 2]) shl 8) or uint32(b[off + 3]) + copyMem(addr result, addr bits, 4) + return + var f: float64 + if parseFloat(row.bufView(off, clen), f) == 0: + raise newException(PgTypeError, "Column " & $col & ": invalid float32 value") + result = float32(f) + proc getNumeric*(row: Row, col: int): PgNumeric = ## Get a column value as PgNumeric. Handles binary numeric format. if row.isBinaryCol(col): @@ -2582,12 +2619,17 @@ template nameAccessor(getProc: untyped, T: typedesc) = optAccessor(getStr, getStrOpt, string) optAccessor(getInt, getIntOpt, int32) +optAccessor(getInt16, getInt16Opt, int16) optAccessor(getInt64, getInt64Opt, int64) optAccessor(getFloat, getFloatOpt, float64) +optAccessor(getFloat32, getFloat32Opt, float32) optAccessor(getNumeric, getNumericOpt, PgNumeric) optAccessor(getUuid, getUuidOpt, PgUuid) optAccessor(getBool, getBoolOpt, bool) +optAccessor(getBytes, getBytesOpt, seq[byte]) optAccessor(getJson, getJsonOpt, JsonNode) +optAccessor(getTimestamp, getTimestampOpt, DateTime) +optAccessor(getDate, getDateOpt, DateTime) optAccessor(getInterval, getIntervalOpt, PgInterval) optAccessor(getInet, getInetOpt, PgInet) optAccessor(getCidr, getCidrOpt, PgCidr) @@ -4521,6 +4563,10 @@ optAccessor(getDateRangeArray, getDateRangeArrayOpt, seq[PgRange[DateTime]]) # timestamptz, date) making a single `get` overload ambiguous. Use the # explicit getters (getTimestamp, getTimestampTz, getDate, etc.) instead. +proc get*(row: Row, col: int, T: typedesc[int16]): int16 = + ## Generic typed accessor. Usage: ``row.get(0, int16)`` + row.getInt16(col) + proc get*(row: Row, col: int, T: typedesc[int32]): int32 = ## Generic typed accessor. Usage: ``row.get(0, int32)`` row.getInt(col) @@ -4528,6 +4574,9 @@ proc get*(row: Row, col: int, T: typedesc[int32]): int32 = proc get*(row: Row, col: int, T: typedesc[int64]): int64 = row.getInt64(col) +proc get*(row: Row, col: int, T: typedesc[float32]): float32 = + row.getFloat32(col) + proc get*(row: Row, col: int, T: typedesc[float64]): float64 = row.getFloat(col) @@ -4570,6 +4619,15 @@ proc get*(row: Row, col: int, T: typedesc[PgTsQuery]): PgTsQuery = proc get*(row: Row, col: int, T: typedesc[PgXml]): PgXml = row.getXml(col) +proc get*(row: Row, col: int, T: typedesc[PgBit]): PgBit = + row.getBit(col) + +proc get*(row: Row, col: int, T: typedesc[PgHstore]): PgHstore = + row.getHstore(col) + +proc get*(row: Row, col: int, T: typedesc[PgUuid]): PgUuid = + row.getUuid(col) + proc get*(row: Row, col: int, T: typedesc[PgPoint]): PgPoint = row.getPoint(col) @@ -4614,6 +4672,9 @@ proc get*(row: Row, col: int, T: typedesc[seq[bool]]): seq[bool] = proc get*(row: Row, col: int, T: typedesc[seq[string]]): seq[string] = row.getStrArray(col) +proc get*(row: Row, col: int, T: typedesc[seq[PgBit]]): seq[PgBit] = + row.getBitArray(col) + # Range types (DateTime-based ranges excluded — see note above) proc get*(row: Row, col: int, T: typedesc[PgRange[int32]]): PgRange[int32] = diff --git a/tests/test_types.nim b/tests/test_types.nim index 6c0ba2e..892c5ec 100644 --- a/tests/test_types.nim +++ b/tests/test_types.nim @@ -291,6 +291,19 @@ suite "Row accessors": expect IndexDefect: discard row.getStr(-1) + test "getInt16": + let row = @[some(toBytes("123"))] + check row.getInt16(0) == 123'i16 + + test "getInt16 negative": + let row = @[some(toBytes("-456"))] + check row.getInt16(0) == -456'i16 + + test "getInt16 invalid value raises": + let row = @[some(toBytes("abc"))] + expect PgTypeError: + discard row.getInt16(0) + test "getInt": let row = @[some(toBytes("42"))] check row.getInt(0) == 42'i32 @@ -313,6 +326,15 @@ suite "Row accessors": expect PgTypeError: discard row.getInt64(0) + test "getFloat32": + let row = @[some(toBytes("2.5"))] + check abs(row.getFloat32(0) - 2.5'f32) < 1e-6 + + test "getFloat32 invalid value raises": + let row = @[some(toBytes("notanumber"))] + expect PgTypeError: + discard row.getFloat32(0) + test "getFloat": let row = @[some(toBytes("3.14"))] check abs(row.getFloat(0) - 3.14) < 1e-10 @@ -709,6 +731,14 @@ suite "Option accessors": let row: Row = @[none(seq[byte])] check row.getStrOpt(0) == none(string) + test "getInt16Opt some": + let row: Row = @[some(toBytes("123"))] + check row.getInt16Opt(0) == some(123'i16) + + test "getInt16Opt none": + let row: Row = @[none(seq[byte])] + check row.getInt16Opt(0) == none(int16) + test "getIntOpt some": let row: Row = @[some(toBytes("42"))] check row.getIntOpt(0) == some(42'i32) @@ -725,6 +755,16 @@ suite "Option accessors": let row: Row = @[none(seq[byte])] check row.getInt64Opt(0) == none(int64) + test "getFloat32Opt some": + let row: Row = @[some(toBytes("2.5"))] + let v = row.getFloat32Opt(0) + check v.isSome + check abs(v.get - 2.5'f32) < 1e-6 + + test "getFloat32Opt none": + let row: Row = @[none(seq[byte])] + check row.getFloat32Opt(0) == none(float32) + test "getFloatOpt some": let row: Row = @[some(toBytes("3.14"))] let v = row.getFloatOpt(0)