- All Superinterfaces:
Adapter.Contract<T>
,Adapter.Contract.Scalar<T>
- Functional Interface:
- This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.
MONEY
type's PostgreSQL semantics: an integer value, whose
scaling, display format, and currency are all determined by a
user-settable configuration setting.
This type is a strange duck in PostgreSQL. It is stored as a (64 bit) integer, and must have a scaling applied on input and output to the appropriate number of decimal places.
The appropriate scaling, the symbols for decimal point and grouping
separators, how the sign is shown, and even what currency it
represents and the currency symbol to use, are all determined
from the locale specified by the lc_monetary
configuration
setting, which can be changed within any session with no special
privilege at any time. That may make MONEY
the only data type
in PostgreSQL where a person can use a single SET
command to
instantly change what an entire table of data means.
For example, this little catalog of products:
=> SELECT * FROM products; product | price ---------+-------------------- widget | $19.00 tokamak | $19,000,000,000.00
can be instantly marked down by about 12 percent (at the exchange rates looked up at this writing):
=> SET lc_monetary TO 'ja_JP'; SET => SELECT * FROM products; product | price ---------+--------------------- widget | ¥1,900 tokamak | ¥1,900,000,000,000
or marked up by roughly the same amount:
=> SET lc_monetary TO 'de_DE@euro'; SET => SELECT * FROM products; product | price ---------+--------------------- widget | 19,00 € tokamak | 19.000.000.000,00 €
or marked up even further (as of this writing, 26%):
=> SET lc_monetary TO 'en_GB'; SET => SELECT * FROM products; product | price ---------+-------------------- widget | £19.00 tokamak | £19,000,000,000.00
Obtaining the locale information in Java
Before the integer value provided here can be correctly scaled or interpreted, the locale-dependent information must be obtained. In Java, that can be done in six steps:
- Obtain the string value of PostgreSQL's
lc_monetary
configuration setting. - Let's not talk about step 2 just yet.
- Obtain a
Locale
object by passing the BCP 47 tag toLocale.forLanguageTag
. - Pass the
Locale
object toNumberFormat.getCurrencyInstance
. - From that, obtain an actual instance of
Currency
withNumberFormat.getCurrency
. - Obtain the correct power of ten for scaling from
Currency.getDefaultFractionDigits
.
The NumberFormat
obtained in step 4 knows all the appropriate
formatting details, but will not automatically scale the integer
value here by the proper power of ten. That must be done explicitly,
and to avoid compromising the precision objectives of the
MONEY
type, should be done with something like a
BigDecimal
. If fmt was obtained
in step 4 above and scale is the value from step 6:
BigDecimal bd = BigDecimal.valueOf(scaledToInteger).movePointLeft(scale); String s = fmt.format(bd);
would produce the correctly-formatted value, where scaledToInteger is the parameter supplied to this interface method.
If the format is not needed, the scale can be obtained in fewer steps
by passing the Locale
from step 3 directly to
Currency.getInstance
.
That would be enough to build a simple reference implementation for
this data type that would return a BigDecimal
with its point
moved left by the scale.
Now let's talk about step 2.
Java's locale support is based on BCP 47, a format for identifiers standardized by IETF to ensure that they are reliable and specific.
The string obtained from the lc_monetary
setting in step 1
above is, most often, a string that makes sense to the underlying
operating system's C library, using some syntax that predated BCP 47,
and likely demonstrates all of the problems BCP 47 was created to
overcome.
From a first glance at a few simple examples, it can appear that replacing some underscores with hyphens could turn some simple OS-library strings into BCP 47 tags, but that is far from the general case, which is full of nonobvious rules, special cases, and grandfather clauses.
A C library, liblangtag
, is available to perform exactly that
mapping, and weighs in at about two and a half megabytes. The library
might be present on the system where PostgreSQL is running, in which
case it could be used in step 2, at the cost of a native call.
If PostgreSQL was built with ICU, a native method could accomplish
the same (as nearly as practical) thing by calling
uloc_canonicalize
followed by uloc_toLanguageTag
; or,
if the ICU4J Java library is available,
ULocale.createCanonical
could be used to the same effect.
It might be simplest to just use a native call to obtain the scaling and other needed details from the underlying operating system library.
Because of step 2's complexity, PL/Java does not here supply the
simple reference implementation to BigDecimal
proposed above.
-
Nested Class Summary
Nested classes/interfaces inherited from interface org.postgresql.pljava.Adapter.Contract
Adapter.Contract.Array<T,
E, A extends Adapter<E, ?>>, Adapter.Contract.Scalar<T> -
Method Summary
-
Method Details
-
construct
Constructs a representation T from the components of the PostgreSQL data type.It might be necessary to extend this interface with extra parameters (or to use the
Modifier
mechanism) to receive the needed scaling and currency details, and require the correspondingAdapter
(which could no longer be pure Java) to make the needed native calls to obtain those.- Parameters:
scaledToInteger
- integer value that must be scaled according to the setting of the lc_monetary configuration setting, and represents a value in the currency also determined by that setting.
-