You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@shardingsphere.apache.org by wu...@apache.org on 2022/02/15 05:27:36 UTC

[shardingsphere] branch master updated: Numeric type supported for PostgreSQL (#15366)

This is an automated email from the ASF dual-hosted git repository.

wuweijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git


The following commit(s) were added to refs/heads/master by this push:
     new a9d4987  Numeric type supported for PostgreSQL (#15366)
a9d4987 is described below

commit a9d49873dded90fd0e264ea25e37beb2266506f6
Author: gin <ja...@163.com>
AuthorDate: Tue Feb 15 13:26:19 2022 +0800

    Numeric type supported for PostgreSQL (#15366)
    
    * Numeric type supported for PostgreSQL
    
    * Optimize PostgreSQL byte converter
    
    * Fix PostgreSQL byte converter test
    
    * Fix PostgreSQL byte converter test to byte array
---
 .../bind/protocol/PostgreSQLByteConverter.java     | 418 +++++++++++++++++++++
 .../PostgreSQLNumericBinaryProtocolValue.java      |   3 +-
 .../bind/protocol/PostgreSQLByteConverterTest.java |  55 +++
 .../PostgreSQLNumericBinaryProtocolValueTest.java  |  10 +-
 .../shadow/postgresql/pro_insert_order_value.xml   |   2 +-
 .../postgresql/pro_update_order_by_user_id.xml     |   4 +-
 .../postgresql/shadow_insert_order_value.xml       |   2 +-
 .../postgresql/shadow_update_order_by_user_id.xml  |   4 +-
 .../cases/dml/dml-integration-test-cases.xml       |  16 +-
 .../env/shadow/init-sql/postgresql/init.sql        |   4 +-
 10 files changed, 494 insertions(+), 24 deletions(-)

diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverter.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverter.java
new file mode 100644
index 0000000..a43d86b
--- /dev/null
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverter.java
@@ -0,0 +1,418 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.db.protocol.postgresql.packet.command.query.extended.bind.protocol;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+
+/**
+ * Refer to
+ * <a href="https://github.com/pgjdbc/pgjdbc/blob/REL42.3.2/pgjdbc/src/main/java/org/postgresql/util/ByteConverter.java" >
+ *      org.postgresql.util.ByteConverter
+ * </a>.
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class PostgreSQLByteConverter {
+    
+    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[] INT_TEN_POWERS = new int[6];
+    
+    private static final BigInteger[] BI_TEN_POWERS = new BigInteger[32];
+    
+    private static final BigInteger BI_TEN_THOUSAND = BigInteger.valueOf(10000);
+    
+    static {
+        for (int i = 0; i < INT_TEN_POWERS.length; ++i) {
+            INT_TEN_POWERS[i] = (int) Math.pow(10, i);
+        }
+        for (int i = 0; i < BI_TEN_POWERS.length; ++i) {
+            BI_TEN_POWERS[i] = BigInteger.TEN.pow(i);
+        }
+    }
+    
+    /**
+     * Convert a variable length array of bytes to an number.
+     *
+     * @param bytes array of bytes that can be decoded as an integer
+     * @return number
+     */
+    public static Number numeric(final byte[] bytes) {
+        return numeric(bytes, 0);
+    }
+    
+    /**
+     * Convert a variable length array of bytes to an number.
+     *
+     * @param bytes array of bytes that can be decoded as an integer
+     * @param pos index of the start position of the bytes array for number
+     * @return number
+     */
+    public static Number numeric(final byte[] bytes, final int pos) {
+        short sign = readShort2(bytes, pos + 4);
+        short scale = readShort2(bytes, pos + 6);
+        validator(sign, scale);
+        if (sign == NUMERIC_NAN) {
+            return Double.NaN;
+        }
+        short len = readShort2(bytes, pos);
+        if (len == 0) {
+            return new BigDecimal(BigInteger.ZERO, scale);
+        }
+        short weight = readShort2(bytes, pos + 2);
+        if (weight < 0) {
+            ++weight;
+            return initBigDecimalNoneWeight(bytes, pos, len, weight, sign, scale);
+        } else if (scale == 0) {
+            return initBigDecimalNoneScale(bytes, pos, len, weight, sign);
+        } else {
+            return initBigDecimal(bytes, pos, len, weight, sign, scale);
+        }
+    }
+    
+    /**
+     * Converts a non-null {@link BigDecimal} to binary.
+     *
+     * @param number number to represent in binary.
+     * @return The binary representation of <i>input</i>.
+     */
+    public static byte[] numeric(final BigDecimal number) {
+        BigInteger unscaled = number.unscaledValue().abs();
+        int scale = number.scale();
+        if (BigInteger.ZERO.equals(unscaled)) {
+            return initBytesZeroCase(scale);
+        }
+        final PositiveShortStack shortStacks = new PositiveShortStack();
+        int weight = -1;
+        if (scale < 0) {
+            scale = Math.abs(scale);
+            weight += scale / 4;
+            int mod = scale % 4;
+            unscaled = unscaled.multiply(tenPower(mod));
+            scale = 0;
+        }
+        if (scale == 0) {
+            weight = initShortValuesNoneScaled(shortStacks, unscaled, weight);
+        } else {
+            weight = initShortValuesScaled(shortStacks, unscaled, scale);
+        }
+        return initBytes(number, shortStacks, scale, weight);
+    }
+    
+    private static byte[] initBytesZeroCase(final int scale) {
+        final byte[] bytes = new byte[] {0, 0, -1, -1, 0, 0, 0, 0};
+        int2(bytes, 6, Math.max(0, scale));
+        return bytes;
+    }
+    
+    private static int initShortValuesNoneScaled(final PositiveShortStack shortStacks, final BigInteger unscaled, final int weight) {
+        int result = weight;
+        BigInteger tempUnscaled = unscaled;
+        BigInteger maxInteger = BigInteger.valueOf(Long.MAX_VALUE);
+        while (unscaled.compareTo(maxInteger) > 0) {
+            final BigInteger[] pair = unscaled.divideAndRemainder(BI_TEN_THOUSAND);
+            tempUnscaled = pair[0];
+            shortStacks.push(pair[1].shortValue());
+            ++result;
+        }
+        long unscaledLong = tempUnscaled.longValueExact();
+        do {
+            shortStacks.push((short) (unscaledLong % 10000));
+            unscaledLong = unscaledLong / 10000L;
+            ++result;
+        } while (unscaledLong != 0);
+        return result;
+    }
+    
+    private static int initShortValuesScaled(final PositiveShortStack shortStacks, final BigInteger unscaled, final int scale) {
+        int weight = -1;
+        final BigInteger[] split = unscaled.divideAndRemainder(tenPower(scale));
+        BigInteger decimal = split[1];
+        BigInteger wholes = split[0];
+        if (!BigInteger.ZERO.equals(decimal)) {
+            int mod = scale % 4;
+            int segments = scale / 4;
+            if (mod != 0) {
+                decimal = decimal.multiply(tenPower(4 - mod));
+                ++segments;
+            }
+            do {
+                final BigInteger[] pair = decimal.divideAndRemainder(BI_TEN_THOUSAND);
+                decimal = pair[0];
+                shortStacks.push(pair[1].shortValue());
+                --segments;
+            } while (!BigInteger.ZERO.equals(decimal));
+            if (BigInteger.ZERO.equals(wholes)) {
+                weight -= segments;
+            } else {
+                for (int i = 0; i < segments; ++i) {
+                    shortStacks.push((short) 0);
+                }
+            }
+        }
+        while (!BigInteger.ZERO.equals(wholes)) {
+            ++weight;
+            final BigInteger[] pair = wholes.divideAndRemainder(BI_TEN_THOUSAND);
+            wholes = pair[0];
+            shortStacks.push(pair[1].shortValue());
+        }
+        return weight;
+    }
+    
+    private static byte[] initBytes(final BigDecimal number, final PositiveShortStack shortStacks, final int scale, final int weight) {
+        final byte[] result = new byte[8 + (2 * shortStacks.size())];
+        int idx = 0;
+        int2(result, idx, shortStacks.size());
+        idx += 2;
+        int2(result, idx, weight);
+        idx += 2;
+        int2(result, idx, number.signum() == -1 ? NUMERIC_NEG : NUMERIC_POS);
+        idx += 2;
+        int2(result, idx, Math.max(0, scale));
+        idx += 2;
+        short s;
+        while ((s = shortStacks.pop()) != -1) {
+            int2(result, idx, s);
+            idx += 2;
+        }
+        return result;
+    }
+    
+    private static void int2(final byte[] target, final int idx, final int value) {
+        target[idx] = (byte) (value >>> 8);
+        target[idx + 1] = (byte) value;
+    }
+    
+    private static Number initBigDecimalNoneWeight(final byte[] bytes, final int pos, final short len, final short weight, final short sign, final short scale) {
+        int idx = pos + 8;
+        short d = readShort2(bytes, idx);
+        assert scale > 0;
+        int effectiveScale = scale;
+        if (weight < 0) {
+            effectiveScale += 4 * weight;
+        }
+        for (int i = 1; i < len && d == 0; ++i) {
+            effectiveScale -= 4;
+            idx += 2;
+            d = readShort2(bytes, idx);
+        }
+        assert effectiveScale > 0;
+        if (effectiveScale >= 4) {
+            effectiveScale -= 4;
+        } else {
+            d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
+            effectiveScale = 0;
+        }
+        BigInteger unscaledBI = null;
+        long unscaledInt = d;
+        for (int i = 1; i < len; ++i) {
+            if (i == 4 && effectiveScale > 2) {
+                unscaledBI = BigInteger.valueOf(unscaledInt);
+            }
+            idx += 2;
+            d = readShort2(bytes, idx);
+            if (effectiveScale >= 4) {
+                if (unscaledBI == null) {
+                    unscaledInt *= 10000;
+                } else {
+                    unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
+                }
+                effectiveScale -= 4;
+            } else {
+                if (unscaledBI == null) {
+                    unscaledInt *= INT_TEN_POWERS[effectiveScale];
+                } else {
+                    unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
+                }
+                d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
+                effectiveScale = 0;
+            }
+            if (unscaledBI == null) {
+                unscaledInt += d;
+            } else {
+                if (d != 0) {
+                    unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
+                }
+            }
+        }
+        if (unscaledBI == null) {
+            unscaledBI = BigInteger.valueOf(unscaledInt);
+        }
+        if (effectiveScale > 0) {
+            unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
+        }
+        if (sign == NUMERIC_NEG) {
+            unscaledBI = unscaledBI.negate();
+        }
+        return new BigDecimal(unscaledBI, scale);
+    }
+    
+    private static Number initBigDecimalNoneScale(final byte[] bytes, final int pos, final short len, final short weight, final short sign) {
+        int idx = pos + 8;
+        short d = readShort2(bytes, idx);
+        BigInteger unscaledBI = null;
+        long unscaledInt = d;
+        for (int i = 1; i < len; ++i) {
+            if (i == 4) {
+                unscaledBI = BigInteger.valueOf(unscaledInt);
+            }
+            idx += 2;
+            d = readShort2(bytes, idx);
+            if (unscaledBI == null) {
+                unscaledInt *= 10000;
+                unscaledInt += d;
+            } else {
+                unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
+                if (d != 0) {
+                    unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
+                }
+            }
+        }
+        if (unscaledBI == null) {
+            unscaledBI = BigInteger.valueOf(unscaledInt);
+        }
+        if (sign == NUMERIC_NEG) {
+            unscaledBI = unscaledBI.negate();
+        }
+        final int bigDecScale = (len - (weight + 1)) * 4;
+        return bigDecScale == 0 ? new BigDecimal(unscaledBI) : new BigDecimal(unscaledBI, bigDecScale);
+    }
+    
+    private static Number initBigDecimal(final byte[] bytes, final int pos, final short len, final short weight, final short sign, final short scale) {
+        int idx = pos + 8;
+        short d = readShort2(bytes, idx);
+        BigInteger unscaledBI = null;
+        long unscaledInt = d;
+        int effectiveWeight = weight;
+        int effectiveScale = scale;
+        for (int i = 1; i < len; ++i) {
+            if (i == 4) {
+                unscaledBI = BigInteger.valueOf(unscaledInt);
+            }
+            idx += 2;
+            d = readShort2(bytes, idx);
+            if (effectiveWeight > 0) {
+                --effectiveWeight;
+                if (unscaledBI == null) {
+                    unscaledInt *= 10000;
+                } else {
+                    unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
+                }
+            } else if (effectiveScale >= 4) {
+                effectiveScale -= 4;
+                if (unscaledBI == null) {
+                    unscaledInt *= 10000;
+                } else {
+                    unscaledBI = unscaledBI.multiply(BI_TEN_THOUSAND);
+                }
+            } else {
+                if (unscaledBI == null) {
+                    unscaledInt *= INT_TEN_POWERS[effectiveScale];
+                } else {
+                    unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
+                }
+                d = (short) (d / INT_TEN_POWERS[4 - effectiveScale]);
+                effectiveScale = 0;
+            }
+            if (unscaledBI == null) {
+                unscaledInt += d;
+            } else {
+                if (d != 0) {
+                    unscaledBI = unscaledBI.add(BigInteger.valueOf(d));
+                }
+            }
+        }
+        if (unscaledBI == null) {
+            unscaledBI = BigInteger.valueOf(unscaledInt);
+        }
+        if (effectiveWeight > 0) {
+            unscaledBI = unscaledBI.multiply(tenPower(effectiveWeight * 4));
+        }
+        if (effectiveScale > 0) {
+            unscaledBI = unscaledBI.multiply(tenPower(effectiveScale));
+        }
+        if (sign == NUMERIC_NEG) {
+            unscaledBI = unscaledBI.negate();
+        }
+        return new BigDecimal(unscaledBI, scale);
+    }
+    
+    private static void validator(final short sign, final short scale) {
+        if (!(sign == 0x0000 || NUMERIC_NEG == sign || NUMERIC_NAN == sign)) {
+            throw new IllegalArgumentException("invalid sign in \"numeric\" value");
+        }
+        if ((scale & 0x00003FFF) != scale) {
+            throw new IllegalArgumentException("invalid scale in \"numeric\" value");
+        }
+    }
+    
+    private static BigInteger tenPower(final int exponent) {
+        return BI_TEN_POWERS.length > exponent ? BI_TEN_POWERS[exponent] : BigInteger.TEN.pow(exponent);
+    }
+    
+    private static short readShort2(final byte[] bytes, final int index) {
+        return (short) (((bytes[index] & 255) << 8) + ((bytes[index + 1] & 255)));
+    }
+    
+    /**
+     * Simple stack structure for non-negative {@code short} values.
+     */
+    private static final class PositiveShortStack {
+        
+        private short[] shorts = new short[8];
+        
+        private int index;
+        
+        public void push(final short value) {
+            if (value != 0 || index != 0) {
+                if (value < 0) {
+                    throw new IllegalArgumentException("only non-negative values accepted: " + value);
+                }
+                if (index == shorts.length) {
+                    grow();
+                }
+                shorts[index++] = value;
+            }
+        }
+        
+        public int size() {
+            return index;
+        }
+        
+        public boolean isEmpty() {
+            return index == 0;
+        }
+        
+        public short pop() {
+            return index > 0 ? shorts[--index] : -1;
+        }
+        
+        private void grow() {
+            final int newSize = shorts.length <= 1024 ? shorts.length << 1 : (int) (shorts.length * 1.5);
+            shorts = Arrays.copyOf(shorts, newSize);
+        }
+    }
+}
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValue.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValue.java
index a8ef6b9..001bd8c 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValue.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/main/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValue.java
@@ -19,7 +19,6 @@ package org.apache.shardingsphere.db.protocol.postgresql.packet.command.query.ex
 
 import org.apache.shardingsphere.db.protocol.postgresql.payload.PostgreSQLPacketPayload;
 
-import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 
 /**
@@ -36,7 +35,7 @@ public final class PostgreSQLNumericBinaryProtocolValue implements PostgreSQLBin
     public Object read(final PostgreSQLPacketPayload payload, final int parameterValueLength) {
         byte[] bytes = new byte[parameterValueLength];
         payload.getByteBuf().readBytes(bytes);
-        return new BigDecimal(new String(bytes));
+        return PostgreSQLByteConverter.numeric(bytes);
     }
     
     @Override
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverterTest.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverterTest.java
new file mode 100644
index 0000000..a91d1e0
--- /dev/null
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLByteConverterTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shardingsphere.db.protocol.postgresql.packet.command.query.extended.bind.protocol;
+
+import lombok.RequiredArgsConstructor;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+@RunWith(Parameterized.class)
+@RequiredArgsConstructor
+public final class PostgreSQLByteConverterTest {
+    
+    private final BigDecimal input;
+    
+    private final byte[] expected;
+    
+    @Parameterized.Parameters(name = "{0}")
+    public static Iterable<Object[]> textValues() {
+        return Arrays.asList(
+                new Object[]{new BigDecimal("0"), new byte[] {0, 0, -1, -1, 0, 0, 0, 0}},
+                new Object[]{new BigDecimal("0.00"), new byte[] {0, 0, -1, -1, 0, 0, 0, 2}},
+                new Object[]{new BigDecimal("0.0001"), new byte[] {0, 1, -1, -1, 0, 0, 0, 4, 0, 1}},
+                new Object[]{new BigDecimal("9999"), new byte[] {0, 1, 0, 0, 0, 0, 0, 0, 39, 15}},
+                new Object[]{new BigDecimal("9999.0"), new byte[] {0, 1, 0, 0, 0, 0, 0, 1, 39, 15}},
+                new Object[]{new BigDecimal("9999.9999"), new byte[] {0, 2, 0, 0, 0, 0, 0, 4, 39, 15, 39, 15}}
+        );
+    }
+    
+    @Test
+    public void assertNumeric() {
+        assertThat(PostgreSQLByteConverter.numeric(input), is(expected));
+    }
+}
diff --git a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValueTest.java b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValueTest.java
index edf0e8f..f01d3d9 100644
--- a/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValueTest.java
+++ b/shardingsphere-db-protocol/shardingsphere-db-protocol-postgresql/src/test/java/org/apache/shardingsphere/db/protocol/postgresql/packet/command/query/extended/bind/protocol/PostgreSQLNumericBinaryProtocolValueTest.java
@@ -44,14 +44,12 @@ public final class PostgreSQLNumericBinaryProtocolValueTest {
         PostgreSQLNumericBinaryProtocolValue binaryProtocolValue = new PostgreSQLNumericBinaryProtocolValue();
         String decimalText = "1234567890.12";
         BigDecimal expectedDecimal = new BigDecimal(decimalText);
-        int columnLength = binaryProtocolValue.getColumnLength(expectedDecimal);
-        int expectedLength = 4 + columnLength;
+        byte[] numericBytes = PostgreSQLByteConverter.numeric(expectedDecimal);
+        int expectedLength = numericBytes.length;
         ByteBuf byteBuf = ByteBufTestUtils.createByteBuf(expectedLength);
-        byteBuf.writeInt(columnLength);
-        byteBuf.writeBytes(decimalText.getBytes(StandardCharsets.UTF_8));
-        byteBuf.readInt();
+        byteBuf.writeBytes(numericBytes);
         PostgreSQLPacketPayload payload = new PostgreSQLPacketPayload(byteBuf, StandardCharsets.UTF_8);
-        Object result = binaryProtocolValue.read(payload, columnLength);
+        Object result = binaryProtocolValue.read(payload, expectedLength);
         assertNotNull(result);
         assertTrue(result instanceof BigDecimal);
         assertThat(result, is(expectedDecimal));
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_insert_order_value.xml b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_insert_order_value.xml
index 668e724..4a315c5 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_insert_order_value.xml
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_insert_order_value.xml
@@ -34,5 +34,5 @@
     <row data-node="db.t_order" values="3, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="db.t_order" values="4, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="db.t_order" values="5, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
-    <row data-node="db.t_order" values="6, 1, pro_order, S, true, 50, summer, 100.00, 2021-01-01, 12:30:30, 2021-01-01 12:30:30.0" />
+    <row data-node="db.t_order" values="6, 1, pro_order, S, true, 50, summer, 120.00, 2021-01-01, 12:30:30, 2021-01-01 12:30:30.0" />
 </dataset>
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_update_order_by_user_id.xml b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_update_order_by_user_id.xml
index 477b8ac..9be10b1 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_update_order_by_user_id.xml
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/pro_update_order_by_user_id.xml
@@ -29,8 +29,8 @@
         <column name="type_time" type="time" />
         <column name="type_timestamp" type="timestamp"/>
     </metadata>
-    <row data-node="db.t_order" values="1, 1, pro_order_update, F, false, 60, spring, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
-    <row data-node="db.t_order" values="2, 1, pro_order_update, F, false, 60, spring, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
+    <row data-node="db.t_order" values="1, 1, pro_order_update, F, false, 60, spring, 120.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
+    <row data-node="db.t_order" values="2, 1, pro_order_update, F, false, 60, spring, 120.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="db.t_order" values="3, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="db.t_order" values="4, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="db.t_order" values="5, 1, pro_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_insert_order_value.xml b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_insert_order_value.xml
index 248c76c..80985e1 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_insert_order_value.xml
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_insert_order_value.xml
@@ -34,5 +34,5 @@
     <row data-node="shadow_db.t_order" values="3, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="shadow_db.t_order" values="4, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="shadow_db.t_order" values="5, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
-    <row data-node="shadow_db.t_order" values="6, 0, shadow_order, S, true, 50, summer, 100.00, 2021-01-01, 12:30:30, 2021-01-01 12:30:30.0" />
+    <row data-node="shadow_db.t_order" values="6, 0, shadow_order, S, true, 50, summer, 120.00, 2021-01-01, 12:30:30, 2021-01-01 12:30:30.0" />
 </dataset>
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_update_order_by_user_id.xml b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_update_order_by_user_id.xml
index e5f8ff5..b5d4e320 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_update_order_by_user_id.xml
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dataset/shadow/postgresql/shadow_update_order_by_user_id.xml
@@ -29,8 +29,8 @@
         <column name="type_time" type="time" />
         <column name="type_timestamp" type="timestamp"/>
     </metadata>
-    <row data-node="shadow_db.t_order" values="1, 0, shadow_order_update, F, false, 60, spring, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
-    <row data-node="shadow_db.t_order" values="2, 0, shadow_order_update, F, false, 60, spring, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
+    <row data-node="shadow_db.t_order" values="1, 0, shadow_order_update, F, false, 60, spring, 120.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
+    <row data-node="shadow_db.t_order" values="2, 0, shadow_order_update, F, false, 60, spring, 120.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="shadow_db.t_order" values="3, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="shadow_db.t_order" values="4, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
     <row data-node="shadow_db.t_order" values="5, 0, shadow_order, S, true, 5, summer, 10.00, 2017-08-08, 18:30:30, 2017-08-08 18:30:30.0" />
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dml-integration-test-cases.xml b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dml-integration-test-cases.xml
index 9180d08..bea5abb 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dml-integration-test-cases.xml
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/cases/dml/dml-integration-test-cases.xml
@@ -253,20 +253,20 @@
         <assertion parameters="0:int, 4:long, 5:long" expected-data-file="shadow_delete_order_by_user_id.xml" />
     </test-case>
 
-    <!-- FIXME Support decimal, enum types for PostgreSQL -->
-    <test-case sql="INSERT INTO t_order (order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_date, type_time, type_timestamp)
-                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);" db-types="PostgreSQL" scenario-types="shadow">
-        <assertion parameters="6:long, 1:int, pro_order:String, S:char, true:boolean, 50:smallint, 2021-01-01:Date, 12:30:30:time, 2021-01-01 12:30:30:timestamp"
+    <!-- FIXME Support enum types for PostgreSQL -->
+    <test-case sql="INSERT INTO t_order (order_id, user_id, order_name, type_char, type_boolean, type_smallint, type_decimal, type_date, type_time, type_timestamp)
+                    VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" db-types="PostgreSQL" scenario-types="shadow">
+        <assertion parameters="6:long, 1:int, pro_order:String, S:char, true:boolean, 50:smallint, 120.00:decimal, 2021-01-01:Date, 12:30:30:time, 2021-01-01 12:30:30:timestamp"
                    expected-data-file="pro_insert_order_value.xml" />
-        <assertion parameters="6:long, 0:int, shadow_order:String, S:char, true:boolean, 50:smallint, 2021-01-01:Date, 12:30:30:time, 2021-01-01 12:30:30:timestamp"
+        <assertion parameters="6:long, 0:int, shadow_order:String, S:char, true:boolean, 50:smallint, 120.00:decimal, 2021-01-01:Date, 12:30:30:time, 2021-01-01 12:30:30:timestamp"
                    expected-data-file="shadow_insert_order_value.xml" />
     </test-case>
 
-    <test-case sql="UPDATE t_order SET order_name = ?, type_char = ?, type_boolean = ?, type_smallint = ?, type_enum = ?::season
+    <test-case sql="UPDATE t_order SET order_name = ?, type_char = ?, type_boolean = ?, type_smallint = ?, type_decimal = ?, type_enum = ?::season
                     WHERE user_id = ? and order_id in (?, ?)" db-types="PostgreSQL" scenario-types="shadow">
-        <assertion parameters="pro_order_update:String, F:char, false:boolean, 60:smallint, spring:enum#season, 1:int, 1:long, 2:long"
+        <assertion parameters="pro_order_update:String, F:char, false:boolean, 60:smallint, 120.00:decimal, spring:enum#season, 1:int, 1:long, 2:long"
                    expected-data-file="pro_update_order_by_user_id.xml" />
-        <assertion parameters="shadow_order_update:String, F:char, false:boolean, 60:smallint, spring:enum#season, 0:int, 1:long, 2:long"
+        <assertion parameters="shadow_order_update:String, F:char, false:boolean, 60:smallint, 120.00:decimal, spring:enum#season, 0:int, 1:long, 2:long"
                    expected-data-file="shadow_update_order_by_user_id.xml" />
     </test-case>
 
diff --git a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/env/shadow/init-sql/postgresql/init.sql b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/env/shadow/init-sql/postgresql/init.sql
index 0fc7eec..ec42c1a 100644
--- a/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/env/shadow/init-sql/postgresql/init.sql
+++ b/shardingsphere-test/shardingsphere-integration-test/shardingsphere-integration-test-suite/src/test/resources/env/shadow/init-sql/postgresql/init.sql
@@ -28,7 +28,7 @@ DROP TABLE IF EXISTS t_order;
 CREATE TYPE season AS ENUM ('spring', 'summer', 'autumn', 'winter');
 
 CREATE TABLE t_order (order_id BIGINT NOT NULL, user_id INT NOT NULL, order_name VARCHAR(32) NOT NULL, type_char CHAR(1) NOT NULL, type_boolean BOOLEAN NOT NULL, type_smallint SMALLINT NOT NULL,
-                      type_enum season DEFAULT 'summer', type_decimal NUMERIC(18,2) DEFAULT '100.00', type_date DATE DEFAULT NULL, type_time TIME DEFAULT NULL, type_timestamp TIMESTAMP DEFAULT NULL,
+                      type_enum season DEFAULT 'summer', type_decimal NUMERIC(18,2) DEFAULT NULL, type_date DATE DEFAULT NULL, type_time TIME DEFAULT NULL, type_timestamp TIMESTAMP DEFAULT NULL,
                       PRIMARY KEY (order_id));
 
 \c shadow_db
@@ -38,5 +38,5 @@ DROP TABLE IF EXISTS t_order;
 CREATE TYPE season AS ENUM ('spring', 'summer', 'autumn', 'winter');
 
 CREATE TABLE t_order (order_id BIGINT NOT NULL, user_id INT NOT NULL, order_name VARCHAR(32) NOT NULL, type_char CHAR(1) NOT NULL, type_boolean BOOLEAN NOT NULL, type_smallint SMALLINT NOT NULL,
-                      type_enum season DEFAULT 'summer', type_decimal NUMERIC(18,2) DEFAULT '100.00', type_date DATE DEFAULT NULL, type_time TIME DEFAULT NULL, type_timestamp TIMESTAMP DEFAULT NULL,
+                      type_enum season DEFAULT 'summer', type_decimal NUMERIC(18,2) DEFAULT NULL, type_date DATE DEFAULT NULL, type_time TIME DEFAULT NULL, type_timestamp TIMESTAMP DEFAULT NULL,
                       PRIMARY KEY (order_id));