Skip to content

Commit

Permalink
Add Binary Support for Oid.NUMERIC and Oid.NUMERIC_ARRAY
Browse files Browse the repository at this point in the history
  • Loading branch information
mahmoudbahaa committed Dec 4, 2019
1 parent 97d32ca commit fa5c93e
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
Expand Up @@ -332,6 +332,7 @@ private static Set<Integer> getBinaryOids(Properties info) throws PSQLException
binaryOids.add(Oid.INT8);
binaryOids.add(Oid.FLOAT4);
binaryOids.add(Oid.FLOAT8);
binaryOids.add(Oid.NUMERIC);
binaryOids.add(Oid.TIME);
binaryOids.add(Oid.DATE);
binaryOids.add(Oid.TIMETZ);
Expand All @@ -342,6 +343,7 @@ private static Set<Integer> getBinaryOids(Properties info) throws PSQLException
binaryOids.add(Oid.INT8_ARRAY);
binaryOids.add(Oid.FLOAT4_ARRAY);
binaryOids.add(Oid.FLOAT8_ARRAY);
binaryOids.add(Oid.NUMERIC_ARRAY);
binaryOids.add(Oid.VARCHAR_ARRAY);
binaryOids.add(Oid.TEXT_ARRAY);
binaryOids.add(Oid.POINT);
Expand Down
18 changes: 18 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
Expand Up @@ -35,6 +35,7 @@
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Array;
Expand Down Expand Up @@ -2343,6 +2344,13 @@ private Number getNumeric(int columnIndex, int scale, boolean allowNaN) throws S
return res;
}
return toBigDecimal(trimMoney(String.valueOf(obj)), scale);
} else {
Number num = ByteConverter.numeric(thisRow[columnIndex - 1]);
if (allowNaN && Double.isNaN(num.doubleValue())) {
return Double.NaN;
}

return num;
}
}

Expand Down Expand Up @@ -3014,6 +3022,8 @@ private double readDoubleValue(byte[] bytes, int oid, String targetType) throws
return ByteConverter.float4(bytes, 0);
case Oid.FLOAT8:
return ByteConverter.float8(bytes, 0);
case Oid.NUMERIC:
return ByteConverter.numeric(bytes).doubleValue();
}
throw new PSQLException(GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Oid.toString(oid), targetType), PSQLState.DATA_TYPE_MISMATCH);
Expand Down Expand Up @@ -3058,6 +3068,14 @@ private long readLongValue(byte[] bytes, int oid, long minVal, long maxVal, Stri
case Oid.FLOAT8:
val = (long) ByteConverter.float8(bytes, 0);
break;
case Oid.NUMERIC:
Number num = ByteConverter.numeric(bytes);
if (num instanceof BigDecimal) {
val = ((BigDecimal) num).setScale(0 , RoundingMode.DOWN).longValueExact();
} else {
val = num.longValue();
}
break;
default:
throw new PSQLException(
GT.tr("Cannot convert the column of type {0} to requested type {1}.",
Expand Down
147 changes: 147 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java
Expand Up @@ -5,13 +5,24 @@

package org.postgresql.util;

import java.math.BigDecimal;
import java.nio.CharBuffer;

/**
* Helper methods to parse java base types from byte arrays.
*
* @author Mikko Tiihonen
*/
public class ByteConverter {

private static final int NBASE = 10000;
private static final int NUMERIC_DSCALE_MASK = 0x00003FFF;
private static final short NUMERIC_POS = 0x0000;
private static final short NUMERIC_NEG = 0x4000;
private static final short NUMERIC_NAN = (short) 0xC000;
private static final int DEC_DIGITS = 4;
private static final int[] round_powers = {0, 1000, 100, 10};

private ByteConverter() {
// prevent instantiation of static helper class
}
Expand All @@ -35,6 +46,142 @@ public static int bytesToInt(byte []bytes) {
}
}

/**
* Convert a number from binary representation to text representation.
* @param idx index of the digit to be converted in the digits array
* @param digits array of shorts that can be decoded as the number String
* @param buffer the character buffer to put the text representation in
* @param alwaysPutIt a flag that indicate whether or not to put the digit char even if it is zero
* @return String the number as String
*/
private static void digitToString(int idx, short[] digits, CharBuffer buffer, boolean alwaysPutIt) {
short dig = (idx >= 0 && idx < digits.length) ? digits[idx] : 0;
for (int p = 1; p < round_powers.length; p++) {
int pow = round_powers[p];
short d1 = (short)(dig / pow);
dig -= d1 * pow;
boolean putit = (d1 > 0);
if (putit || alwaysPutIt) {
buffer.put((char)(d1 + '0'));
}
}

buffer.put((char)(dig + '0'));
}

/**
* Convert a number from binary representation to text representation.
* @param digits array of shorts that can be decoded as the number String
* @param scale the scale of the number binary representation
* @param weight the weight of the number binary representation
* @param sign the sign of the number
* @return String the number as String
*/
private static String numberBytesToString(short[] digits, int scale, int weight, int sign) {
CharBuffer buffer;
int i;
int d;

/*
* Allocate space for the result.
*
* i is set to the # of decimal digits before decimal point. dscale is the
* # of decimal digits we will print after decimal point. We may generate
* as many as DEC_DIGITS-1 excess digits at the end, and in addition we
* need room for sign, decimal point, null terminator.
*/
i = (weight + 1) * DEC_DIGITS;
if (i <= 0) {
i = 1;
}

buffer = CharBuffer.allocate((i + scale + DEC_DIGITS + 2));

/*
* Output a dash for negative values
*/
if (sign == NUMERIC_NEG) {
buffer.put('-');
}

/*
* Output all digits before the decimal point
*/
if (weight < 0) {
d = weight + 1;
buffer.put('0');
} else {
for (d = 0; d <= weight; d++) {
/* In the first digit, suppress extra leading decimal zeroes */
digitToString(d, digits, buffer, d != 0);
}
}

/*
* If requested, output a decimal point and all the digits that follow it.
* We initially put out a multiple of DEC_DIGITS digits, then truncate if
* needed.
*/
if (scale > 0) {
buffer.put('.');
for (i = 0; i < scale; d++, i += DEC_DIGITS) {
digitToString(d, digits, buffer, true);
}
}

/*
* terminate the string and return it
*/
int extra = (i - scale) % DEC_DIGITS;
return new String(buffer.array(), 0, buffer.position() - extra);
}

/**
* Convert a variable length array of bytes to an integer
* @param bytes array of bytes that can be decoded as an integer
* @return integer
*/
public static Number numeric(byte []bytes) {
if (bytes.length < 8) {
throw new IllegalArgumentException("number of bytes should be at-least 8");
}

short len = ByteConverter.int2(bytes, 0);
short weight = ByteConverter.int2(bytes, 2);
short sign = ByteConverter.int2(bytes, 4);
short scale = ByteConverter.int2(bytes, 6);

if (!(sign == NUMERIC_POS
|| sign == NUMERIC_NEG
|| sign == NUMERIC_NAN)) {
throw new IllegalArgumentException("invalid sign in \"numeric\" value");
}

if (sign == NUMERIC_NAN) {
return Double.NaN;
}

if ((scale & NUMERIC_DSCALE_MASK) != scale) {
throw new IllegalArgumentException("invalid scale in \"numeric\" value");
}

short[] digits = new short[len];
int idx = 8;
for (int i = 0; i < len; i++) {
short d = ByteConverter.int2(bytes, idx);
idx += 2;

if (d < 0 || d >= NBASE) {
throw new IllegalArgumentException("invalid digit in \"numeric\" value");
}

digits[i] = d;
}

String numString = numberBytesToString(digits, scale, weight, sign);
return new BigDecimal(numString);
}

/**
* Parses a long value from the byte array.
*
Expand Down

0 comments on commit fa5c93e

Please sign in to comment.