From e54b4a6dee01b96d02c243afa529275803843744 Mon Sep 17 00:00:00 2001 From: fox0430 Date: Tue, 21 Apr 2026 16:42:43 +0900 Subject: [PATCH] Add read helpers --- async_postgres/pg_client.nim | 11 +++++----- async_postgres/pg_replication.nim | 16 ++++---------- async_postgres/pg_types/accessors.nim | 30 ++++++-------------------- async_postgres/pg_types/decoding.nim | 20 +++++++++++------ async_postgres/pg_types/user_types.nim | 19 ++++------------ 5 files changed, 34 insertions(+), 62 deletions(-) diff --git a/async_postgres/pg_client.nim b/async_postgres/pg_client.nim index c5bd426..fb922fa 100644 --- a/async_postgres/pg_client.nim +++ b/async_postgres/pg_client.nim @@ -407,9 +407,9 @@ template appendInlineParam( let oldLen = data.len data.setLen(oldLen + int(p.len)) if p.len <= PgInlineBufSize: - copyMem(addr data[oldLen], addr p.inlineBuf[0], int(p.len)) + data.writeBytesAt(oldLen, p.inlineBuf.toOpenArray(0, int(p.len) - 1)) else: - copyMem(addr data[oldLen], addr p.overflow[0], int(p.len)) + data.writeBytesAt(oldLen, p.overflow.toOpenArray(0, int(p.len) - 1)) ranges.add((dataOff, p.len)) proc flattenInline( @@ -1612,7 +1612,7 @@ proc copyIn*( ## Converts to bytes internally; avoids manual toOpenArrayByte. var bytes = newSeq[byte](data.len) if data.len > 0: - copyMem(addr bytes[0], addr data[0], data.len) + bytes.writeBytesAt(0, data.toOpenArrayByte(0, data.high)) copyIn(conn, sql, bytes, timeout) proc copyIn*( @@ -1630,9 +1630,8 @@ proc copyIn*( var combined = newSeq[byte](totalLen) var offset = 0 for chunk in data: - if chunk.len > 0: - copyMem(addr combined[offset], addr chunk[0], chunk.len) - offset += chunk.len + combined.writeBytesAt(offset, chunk) + offset += chunk.len copyIn(conn, sql, combined, timeout) proc copyInStreamImpl( diff --git a/async_postgres/pg_replication.nim b/async_postgres/pg_replication.nim index 4a6d749..bd48812 100644 --- a/async_postgres/pg_replication.nim +++ b/async_postgres/pg_replication.nim @@ -185,9 +185,7 @@ proc `<=`*(a, b: Lsn): bool {.borrow.} proc toString*(field: TupleField): string = ## Convert a TupleField's data to a string by copying the bytes. - result = newString(field.data.len) - if field.data.len > 0: - copyMem(addr result[0], addr field.data[0], field.data.len) + result = readString(field.data, 0, field.data.len) template toUInt64*(lsn: Lsn): uint64 = ## Get the raw uint64 value of an LSN. @@ -238,9 +236,7 @@ proc decodeCStringAt(buf: openArray[byte], offset: int): (string, int) = if i >= buf.len: raise newException(ProtocolError, "decodeCStringAt: missing null terminator") let slen = i - offset - var s = newString(slen) - if slen > 0: - copyMem(addr s[0], addr buf[offset], slen) + let s = readString(buf, offset, slen) inc i # skip null (s, i) @@ -261,9 +257,7 @@ proc decodeTuple(buf: openArray[byte], offset: int): (seq[TupleField], int) = of 't', 'b': let dataLen = decodeInt32(buf, pos) pos += 4 - var data = newSeq[byte](dataLen) - if dataLen > 0: - copyMem(addr data[0], addr buf[pos], dataLen) + let data = readBytes(buf, pos, int(dataLen)) pos += int(dataLen) fields[i] = TupleField(kind: if kind == 't': tdkText else: tdkBinary, data: data) else: @@ -387,9 +381,7 @@ proc parsePgOutputMessage*(data: openArray[byte]): PgOutputMessage = pos = nextPos let contentLen = decodeInt32(data, pos) pos += 4 - if contentLen > 0: - msg.content = newSeq[byte](contentLen) - copyMem(addr msg.content[0], addr data[pos], contentLen) + msg.content = readBytes(data, pos, int(contentLen)) PgOutputMessage(kind: pomkMessage, message: msg) else: raise newException(ProtocolError, "Unknown pgoutput message type: " & msgType) diff --git a/async_postgres/pg_types/accessors.nim b/async_postgres/pg_types/accessors.nim index 1f934ee..37a49d9 100644 --- a/async_postgres/pg_types/accessors.nim +++ b/async_postgres/pg_types/accessors.nim @@ -163,9 +163,7 @@ proc getStr*(row: Row, col: int): string = return $decodeNumericBinary(b.toOpenArray(off, off + clen - 1)) else: discard # text, varchar, bytea: fall through to raw copy - result = newString(clen) - if clen > 0: - copyMem(addr result[0], addr row.data.buf[off], clen) + result = readString(row.data.buf, off, clen) proc getInt*(row: Row, col: int): int32 = ## Get a column value as int32. Handles binary int2/int4 directly. Raises `PgTypeError` on NULL. @@ -352,9 +350,7 @@ proc getBytes*(row: Row, col: int): seq[byte] = raise newException(PgTypeError, "Column " & $col & " is NULL") if row.isBinaryCol(col): # Binary format: raw bytes, no hex encoding - result = newSeq[byte](clen) - if clen > 0: - copyMem(addr result[0], addr row.data.buf[off], clen) + result = readBytes(row.data.buf, off, clen) return # Text format: bytea uses hex encoding \xDEADBEEF if clen >= 2 and row.data.buf[off] == byte('\\') and row.data.buf[off + 1] == byte( @@ -368,9 +364,7 @@ proc getBytes*(row: Row, col: int): seq[byte] = for i in 0 ..< result.len: result[i] = byte(parseHexInt(hex[i * 2 .. i * 2 + 1])) else: - result = newSeq[byte](clen) - if clen > 0: - copyMem(addr result[0], addr row.data.buf[off], clen) + result = readBytes(row.data.buf, off, clen) proc getTimestamp*(row: Row, col: int): DateTime = ## Get a column value as DateTime. Handles binary timestamp format. @@ -946,9 +940,7 @@ proc getStrArray*(row: Row, col: int): seq[string] = for i, e in decoded.elements: if e.len == -1: raise newException(PgTypeError, "NULL element in string array") - result[i] = newString(e.len) - if e.len > 0: - copyMem(addr result[i][0], addr row.data.buf[off + e.off], e.len) + result[i] = readString(row.data.buf, off + e.off, e.len) return let s = row.getStr(col) let elems = parseTextArray(s) @@ -1226,9 +1218,7 @@ proc getBytesArray*(row: Row, col: int): seq[seq[byte]] = for i, e in decoded.elements: if e.len == -1: raise newException(PgTypeError, "NULL element in bytea array") - result[i] = newSeq[byte](e.len) - if e.len > 0: - copyMem(addr result[i][0], addr row.data.buf[off + e.off], e.len) + result[i] = readBytes(row.data.buf, off + e.off, e.len) return let s = row.getStr(col) let elems = parseTextArray(s) @@ -1509,10 +1499,7 @@ template genStringArrayDecoder(getProc: untyped, T: typedesc, typeName: static s for i, e in decoded.elements: if e.len == -1: raise newException(PgTypeError, "NULL element in " & typeName & " array") - var s = newString(e.len) - if e.len > 0: - copyMem(addr s[0], addr row.data.buf[off + e.off], e.len) - result[i] = T(s) + result[i] = T(readString(row.data.buf, off + e.off, e.len)) return let s = row.getStr(col) let elems = parseTextArray(s) @@ -1705,10 +1692,7 @@ proc getStrArrayElemOpt*(row: Row, col: int): seq[Option[string]] = if e.len == -1: result[i] = none(string) else: - var s = newString(e.len) - if e.len > 0: - copyMem(addr s[0], addr row.data.buf[off + e.off], e.len) - result[i] = some(s) + result[i] = some(readString(row.data.buf, off + e.off, e.len)) return let s = row.getStr(col) result = parseTextArray(s) diff --git a/async_postgres/pg_types/decoding.nim b/async_postgres/pg_types/decoding.nim index a567cf0..93f8d30 100644 --- a/async_postgres/pg_types/decoding.nim +++ b/async_postgres/pg_types/decoding.nim @@ -2,6 +2,18 @@ import std/[options, strutils, tables, times, net] import ./core +proc readString*(src: openArray[byte], off, len: int): string = + ## Copy `len` bytes from src starting at off into a new string. + result = newString(len) + if len > 0: + copyMem(addr result[0], addr src[off], len) + +proc readBytes*(src: openArray[byte], off, len: int): seq[byte] = + ## Copy `len` bytes from src starting at off into a new seq[byte]. + result = newSeq[byte](len) + if len > 0: + copyMem(addr result[0], addr src[off], len) + proc decodeHstoreBinary*(data: openArray[byte]): PgHstore = ## Decode PostgreSQL binary hstore format. result = initTable[string, Option[string]]() @@ -16,9 +28,7 @@ proc decodeHstoreBinary*(data: openArray[byte]): PgHstore = pos += 4 if keyLen < 0 or pos + keyLen > data.len: raise newException(PgTypeError, "hstore binary: truncated key data") - var key = newString(keyLen) - if keyLen > 0: - copyMem(addr key[0], addr data[pos], keyLen) + let key = readString(data, pos, keyLen) pos += keyLen if pos + 4 > data.len: raise newException(PgTypeError, "hstore binary: truncated value length") @@ -29,9 +39,7 @@ proc decodeHstoreBinary*(data: openArray[byte]): PgHstore = else: if valLen < 0 or pos + valLen > data.len: raise newException(PgTypeError, "hstore binary: truncated value data") - var val = newString(valLen) - if valLen > 0: - copyMem(addr val[0], addr data[pos], valLen) + let val = readString(data, pos, valLen) pos += valLen result[key] = some(val) diff --git a/async_postgres/pg_types/user_types.nim b/async_postgres/pg_types/user_types.nim index 2feeb28..1f4d28e 100644 --- a/async_postgres/pg_types/user_types.nim +++ b/async_postgres/pg_types/user_types.nim @@ -153,10 +153,7 @@ proc getEnumArray*[T: enum](row: Row, col: int): seq[T] = for i, e in decoded.elements: if e.len == -1: raise newException(PgTypeError, "NULL element in enum array") - var s = newString(e.len) - if e.len > 0: - copyMem(addr s[0], addr row.data.buf[off + e.off], e.len) - result[i] = parseEnum[T](s) + result[i] = parseEnum[T](readString(row.data.buf, off + e.off, e.len)) return let s = row.getStr(col) for e in parseTextArray(s): @@ -183,10 +180,7 @@ proc getEnumArrayElemOpt*[T: enum](row: Row, col: int): seq[Option[T]] = if e.len == -1: result[i] = none(T) else: - var s = newString(e.len) - if e.len > 0: - copyMem(addr s[0], addr row.data.buf[off + e.off], e.len) - result[i] = some(parseEnum[T](s)) + result[i] = some(parseEnum[T](readString(row.data.buf, off + e.off, e.len))) return let s = row.getStr(col) for e in parseTextArray(s): @@ -357,9 +351,7 @@ proc compositeFieldFromText[T](s: string): T = template decodeBinaryField(val, buf: untyped, fOff, fEnd, fLen: int) = when typeof(val) is string: - val = newString(fLen) - if fLen > 0: - copyMem(addr val[0], addr buf[fOff], fLen) + val = readString(buf, fOff, fLen) elif typeof(val) is int16: val = fromBE16(buf.toOpenArray(fOff, fEnd)) elif typeof(val) is int32: @@ -373,10 +365,7 @@ template decodeBinaryField(val, buf: untyped, fOff, fEnd, fLen: int) = elif typeof(val) is bool: val = buf[fOff] != 0 else: - var s = newString(fLen) - if fLen > 0: - copyMem(addr s[0], addr buf[fOff], fLen) - val = compositeFieldFromText[typeof(val)](s) + val = compositeFieldFromText[typeof(val)](readString(buf, fOff, fLen)) proc getComposite*[T: object](row: Row, col: int): T = ## Read a PostgreSQL composite column as a Nim object. Handles binary format.