diff --git a/async_postgres/pg_types.nim b/async_postgres/pg_types.nim
index af231d7..d8ae668 100644
--- a/async_postgres/pg_types.nim
+++ b/async_postgres/pg_types.nim
@@ -51,6 +51,8 @@ type
PgTsVector* = distinct string ## PostgreSQL tsvector (full-text search document).
PgTsQuery* = distinct string ## PostgreSQL tsquery (full-text search query).
+ PgXml* = distinct string ## PostgreSQL xml type.
+
PgPoint* = object ## PostgreSQL point type: (x, y).
x*: float64
y*: float64
@@ -199,6 +201,8 @@ const
OidTsVector* = 3614'i32
OidTsQuery* = 3615'i32
+ OidXml* = 142'i32
+
rangeEmpty* = 0x01'u8 ## Range flag: range is empty.
rangeHasLower* = 0x02'u8 ## Range flag: lower bound present.
rangeHasUpper* = 0x04'u8 ## Range flag: upper bound present.
@@ -221,6 +225,9 @@ proc `==`*(a, b: PgTsVector): bool {.borrow.}
proc `$`*(v: PgTsQuery): string {.borrow.}
proc `==`*(a, b: PgTsQuery): bool {.borrow.}
+proc `$`*(v: PgXml): string {.borrow.}
+proc `==`*(a, b: PgXml): bool {.borrow.}
+
proc parsePgNumeric*(s: string): PgNumeric =
## Parse a decimal string (e.g. "123.45", "-0.001", "NaN") into PgNumeric.
if s.len == 0:
@@ -650,6 +657,9 @@ proc toPgParam*(v: PgTsVector): PgParam =
proc toPgParam*(v: PgTsQuery): PgParam =
PgParam(oid: OidTsQuery, format: 0, value: some(toBytes(string(v))))
+proc toPgParam*(v: PgXml): PgParam =
+ PgParam(oid: OidXml, format: 0, value: some(toBytes(string(v))))
+
proc toPgParam*(v: PgPoint): PgParam =
PgParam(oid: OidPoint, format: 0, value: some(toBytes($v)))
@@ -979,6 +989,10 @@ proc toPgBinaryParam*(v: PgTsQuery): PgParam =
## Send as text format — PostgreSQL handles the parsing.
PgParam(oid: OidTsQuery, format: 0, value: some(toBytes(string(v))))
+proc toPgBinaryParam*(v: PgXml): PgParam =
+ ## Binary wire format for xml is the text representation itself.
+ PgParam(oid: OidXml, format: 1, value: some(toBytes(string(v))))
+
proc encodePointBinary(p: PgPoint): seq[byte] =
## Encode a point as 16 bytes (two float64 big-endian).
result = newSeq[byte](16)
@@ -2021,6 +2035,18 @@ proc getTsQuery*(row: Row, col: int): PgTsQuery =
return PgTsQuery(decodeBinaryTsQuery(row.data.buf.toOpenArray(off, off + clen - 1)))
PgTsQuery(row.getStr(col))
+proc getXml*(row: Row, col: int): PgXml =
+ ## Get a column value as PgXml. Handles both text and binary format.
+ if row.isBinaryCol(col):
+ let (off, clen) = cellInfo(row, col)
+ if clen == -1:
+ raise newException(PgTypeError, "Column " & $col & " is NULL")
+ var s = newString(clen)
+ for i in 0 ..< clen:
+ s[i] = char(row.data.buf[off + i])
+ return PgXml(s)
+ PgXml(row.getStr(col))
+
# Geometry text format parsers
proc parsePointText(s: string): PgPoint =
@@ -2256,6 +2282,7 @@ optAccessor(getMacAddr, getMacAddrOpt, PgMacAddr)
optAccessor(getMacAddr8, getMacAddr8Opt, PgMacAddr8)
optAccessor(getTsVector, getTsVectorOpt, PgTsVector)
optAccessor(getTsQuery, getTsQueryOpt, PgTsQuery)
+optAccessor(getXml, getXmlOpt, PgXml)
optAccessor(getPoint, getPointOpt, PgPoint)
optAccessor(getLine, getLineOpt, PgLine)
optAccessor(getLseg, getLsegOpt, PgLseg)
@@ -3842,6 +3869,9 @@ proc get*(row: Row, col: int, T: typedesc[PgTsVector]): PgTsVector =
proc get*(row: Row, col: int, T: typedesc[PgTsQuery]): PgTsQuery =
row.getTsQuery(col)
+proc get*(row: Row, col: int, T: typedesc[PgXml]): PgXml =
+ row.getXml(col)
+
proc get*(row: Row, col: int, T: typedesc[PgPoint]): PgPoint =
row.getPoint(col)
@@ -3964,6 +3994,7 @@ nameAccessor(getMacAddr, PgMacAddr)
nameAccessor(getMacAddr8, PgMacAddr8)
nameAccessor(getTsVector, PgTsVector)
nameAccessor(getTsQuery, PgTsQuery)
+nameAccessor(getXml, PgXml)
nameAccessor(getPoint, PgPoint)
nameAccessor(getLine, PgLine)
nameAccessor(getLseg, PgLseg)
@@ -3986,6 +4017,7 @@ nameAccessor(getMacAddrOpt, Option[PgMacAddr])
nameAccessor(getMacAddr8Opt, Option[PgMacAddr8])
nameAccessor(getTsVectorOpt, Option[PgTsVector])
nameAccessor(getTsQueryOpt, Option[PgTsQuery])
+nameAccessor(getXmlOpt, Option[PgXml])
nameAccessor(getPointOpt, Option[PgPoint])
nameAccessor(getLineOpt, Option[PgLine])
nameAccessor(getLsegOpt, Option[PgLseg])
diff --git a/tests/test_e2e.nim b/tests/test_e2e.nim
index edb0d4c..f61b57f 100644
--- a/tests/test_e2e.nim
+++ b/tests/test_e2e.nim
@@ -6140,3 +6140,48 @@ suite "E2E: cancelNoWait":
await conn.close()
waitFor t()
+
+ test "xml roundtrip":
+ proc t() {.async.} =
+ let conn = await connect(plainConfig())
+ let v = PgXml("- hello
")
+ let res = await conn.query("SELECT $1::xml", @[toPgParam(v)])
+ doAssert res.rows.len == 1
+ let got = res.rows[0].getXml(0)
+ doAssert $got == "- hello
"
+ await conn.close()
+
+ waitFor t()
+
+ test "xmlparse function":
+ proc t() {.async.} =
+ let conn = await connect(plainConfig())
+ let res = await conn.query("SELECT xmlparse(CONTENT '- test
')")
+ doAssert res.rows.len == 1
+ let v = res.rows[0].getXml(0)
+ doAssert "- test
" == $v
+ await conn.close()
+
+ waitFor t()
+
+ test "NULL xml":
+ proc t() {.async.} =
+ let conn = await connect(plainConfig())
+ let res = await conn.query("SELECT NULL::xml")
+ doAssert res.rows.len == 1
+ doAssert res.rows[0].getXmlOpt(0).isNone
+ await conn.close()
+
+ waitFor t()
+
+ test "xml binary results":
+ proc t() {.async.} =
+ let conn = await connect(plainConfig())
+ let res =
+ await conn.query("SELECT 'data'::xml", resultFormat = rfBinary)
+ doAssert res.rows.len == 1
+ let v = res.rows[0].getXml(0)
+ doAssert "data" == $v
+ await conn.close()
+
+ waitFor t()
diff --git a/tests/test_types.nim b/tests/test_types.nim
index 49ba3b6..2b2e3f2 100644
--- a/tests/test_types.nim
+++ b/tests/test_types.nim
@@ -3924,3 +3924,53 @@ suite "tsvector / tsquery":
let fields = @[mkField(OidTsQuery, 0'i16)]
let row = mkRow(@[none(seq[byte])], fields)
check row.getTsQueryOpt(0).isNone
+
+suite "xml":
+ test "OID constant":
+ check OidXml == 142'i32
+
+ test "toPgParam PgXml":
+ let v = PgXml("- hello
")
+ let p = toPgParam(v)
+ check p.oid == OidXml
+ check p.format == 0
+ check toString(p.value.get) == "- hello
"
+
+ test "toPgBinaryParam PgXml sends binary format":
+ let v = PgXml("")
+ let p = toPgBinaryParam(v)
+ check p.oid == OidXml
+ check p.format == 1
+
+ test "$ PgXml":
+ let v = PgXml("")
+ check $v == ""
+
+ test "== PgXml":
+ check PgXml("") == PgXml("")
+ check PgXml("") != PgXml("")
+
+ test "getXml text format":
+ let data = toBytes("- test
")
+ let fields = @[mkField(OidXml, 0'i16)]
+ let row = mkRow(@[some(data)], fields)
+ check $row.getXml(0) == "- test
"
+
+ test "getXml binary format":
+ let data = toBytes("- test
")
+ let fields = @[mkField(OidXml, 1'i16)]
+ let row = mkRow(@[some(data)], fields)
+ check $row.getXml(0) == "- test
"
+
+ test "getXmlOpt some":
+ let data = toBytes("")
+ let fields = @[mkField(OidXml, 0'i16)]
+ let row = mkRow(@[some(data)], fields)
+ let r = row.getXmlOpt(0)
+ check r.isSome
+ check $r.get == ""
+
+ test "getXmlOpt none":
+ let fields = @[mkField(OidXml, 0'i16)]
+ let row = mkRow(@[none(seq[byte])], fields)
+ check row.getXmlOpt(0).isNone