Skip to content

Add support for (de)serialization of Integer to/from numeric#90

Merged
arybczak merged 2 commits intomasterfrom
integer
Apr 14, 2026
Merged

Add support for (de)serialization of Integer to/from numeric#90
arybczak merged 2 commits intomasterfrom
integer

Conversation

@arybczak
Copy link
Copy Markdown
Contributor

This turned out to be way more annoying than anticipated, but the C code for (de)serialization of numeric was incomprehensible (and buggy, e.g. it didn't handle NaN properly), so I adjusted code from postgresql-binary package to serialize on Haskell side instead.

This turned out to be way more annoying than anticipated, but the C code for
(de)serialization of numeric was incomprehensible (and buggy, e.g. it didn't
handle NaN properly), so I adjusted code from postgresql-binary package to
serialize on Haskell side instead.
@arybczak arybczak requested a review from bgohla April 13, 2026 19:43
, numVarWeight :: !CShort
, numVarSign :: !CShort
, numVarDscale :: !CShort
, numVarDigits :: !(Ptr CShort) -- elements in network byte order
Copy link
Copy Markdown

@bgohla bgohla Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about the network byte order? Isn't that what we invoke htons and ntohs for in pqt_{get,put}_numeric for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are invoked for all fields except the array:

It's a bit messy, but avoids having to allocate a new buffer for digits on the C side (which is always annoying) in pqt_get_numeric.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, how stupid of me 🤦

mkInteger acc ptr = \case
0 -> pure acc
n -> do
v <- ntohs <$> peek ptr
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related to my above comment, aren't we swapping the byte order a second time here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I wrote above, no, because the order is not swapped for the array in C.

0 -> pure acc
n -> do
v <- ntohs <$> peek ptr
mkInteger (acc * 10000 + fromIntegral v) (ptr `plusPtr` 2) (n - 1)
Copy link
Copy Markdown

@bgohla bgohla Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check that each short is < 10^4 ? (And >= 0)

Every NumericDigit represents values 0 <= NBASE -1 where NBASE = 10^4.

c.f., https://github.com/postgres/postgres/blob/66ad764c8d517f59577d41ac3dad786729c9e10e/src/backend/utils/adt/numeric.c#L56

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, on a tangential note, why do we use (platform dependent) short when the postgres backend uses int16 for NumericDigit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check that each short is < 10^4 ? (And >= 0)

Sounds good, I added a check.

Also, on a tangential note, why do we use (platform dependent) short when the postgres backend uses int16 for NumericDigit?

Because libpqtypes is not of the highest quality and uses short/int all over the place :P short is 2 bytes on all platforms that matter though.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. That's also how we get away with using a constant 2 for the pointer increment I guess.

Copy link
Copy Markdown

@bgohla bgohla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I'm confused about the byte order conversion though.

Comment thread CHANGELOG.md
@@ -1,5 +1,6 @@
# hpqtypes-1.14.1.0
# hpqtypes-1.14.1.0 (????-??-??)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the date be set in a separate commit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

Copy link
Copy Markdown

@bgohla bgohla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @arybczak for the quick turnaround, I would not have been able to.

@arybczak arybczak merged commit 5f24157 into master Apr 14, 2026
9 checks passed
@arybczak arybczak deleted the integer branch April 14, 2026 12:52
type PQDest Word64 = CULLong
toSQL n _ = putAsPtr (fromIntegral n)

instance ToSQL Integer where
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there is a major caveat, that I should have pointed out, that should be documented here: There is a limit to how many decimal digits Postgres allows in a Numeric value, namely

up to 131072 digits before the decimal point; up to 16383 digits after the decimal point

according to https://www.postgresql.org/docs/18/datatype-numeric.html#DATATYPE-NUMERIC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants