Closed fgm closed 2 years ago
Thanks! Mentioning go-cmp somewhere would indeed be good.
What are the benefits of trying to use BinaryMarshaler for db storage?
My assumption was that anyone storing amounts in the database would use separate decimal and char fields for the number and currency_code. The binary marshal was implemented for exchange on the wire (e.g. the gob package).
I believe that if the format was reversed ("9.99USD" instead of "USD9.99") then MySQL could cast the value into a number for ordering, but I haven't explored the performance of that, nor how it would work in Postgres.
The idea is that not all databases support decimal in a portable way, AFAIK (maybe they now do ? it wasn't implemented in a portable way some years ago in SQL engines, even less in NoSQL): providing a string representation allows the type to be stored to just about anything without having to implement application-level mapping.
Using BinaryMarshaler for this is just a really simple way to do it, no special interest in it.
The decimal type is a part of the SQL standard, and it's currently supported by all databases that I checked, so it's safe to recommend.
For cases where a decimal type is not available, there are two solid options:
This means that for proper sorting in any system, the primary thing to do is store the number and the currency code separately. Which means that we can probably keep the binary format as-is, but document storage recommendations. How does that sound?
Hadn't realized that Decimal was now usable across engines, so it is indeed enough to document it. Better have better doc than more code, I think.
Just to explain how this issue came: when drafting a homework subject for my students using monetary amounts, I wondered, thinking about how they'd have to store these data: "ok so we have currency.NewAmount" to create an Amount from a string. Now, what is the inverse function generating the same string I can pass to NewAmount again to obtain an identity when they get the result from the DB ?"
Yeah, that's a valid question, it's not obvious at all. I'll see what I can come up with for the README
BTW, I double checked, and AIUI SQLite does not actually support DECIMAL: it only pretends to, by mapping it to its NUMERIC type, which uses a float if the value is not an integer. That's really something currencies want to avoid.
Haven't forgotten about this. In the meantime, opened #9 to improve DX for Postgres users.
Documented go-cmp and pgx integration in 7535b5417d25ed1f87167770a20c7adc53ad2dfb.
The next step will be to create a separate section for database integration, with integration notes for mysql (decimal column) and sqlite (int column). The int approach has been made possible by e18a43233c0ec25e3ad0d130e580d56d6d031158, since there are now methods for both getting the amount as an integer, and creating an amount from an integer.
Database integration notes are now complete at https://github.com/bojanz/currency/wiki/Database-integration-notes
The SQLite section can be expanded with actual examples if someone's in the mood to give us an example schema.
I think the README should document at least two more features in the code:
github.com/google/go-cmp
, making comparisons reliable and reasonably fast without having to resort toAmount.Cmp
when just testing for equalityencoding.BinaryMarshaler
interface actually produces valid strings in its[]byte
results, allowing them to be stored in database as strings in SQL and compared for equality, but not for ordering.Alternatively, documenting a serialization that would allow SQL ordering would be nice too. I didn't find one in the current implementation, though.