View Javadoc
1   /*
2    * Copyright 2021 TiKV Project Authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  
18  package org.tikv.common.codec;
19  
20  import java.math.BigDecimal;
21  import java.nio.charset.StandardCharsets;
22  import java.sql.Date;
23  import java.sql.Timestamp;
24  import java.util.Arrays;
25  import java.util.List;
26  import org.joda.time.DateTimeZone;
27  import org.tikv.common.ExtendedDateTime;
28  import org.tikv.common.codec.Codec.DateTimeCodec;
29  import org.tikv.common.codec.Codec.DecimalCodec;
30  import org.tikv.common.codec.Codec.EnumCodec;
31  import org.tikv.common.codec.Codec.SetCodec;
32  import org.tikv.common.exception.CodecException;
33  import org.tikv.common.types.Converter;
34  import org.tikv.common.types.DataType;
35  import org.tikv.common.util.JsonUtils;
36  
37  public class RowDecoderV2 {
38  
39    private static final long SIGN_MASK = 0x8000000000000000L;
40  
41    public static Object decodeCol(byte[] colData, DataType tp) {
42      switch (tp.getType()) {
43        case TypeLonglong:
44        case TypeLong:
45        case TypeInt24:
46        case TypeShort:
47        case TypeTiny:
48          // TODO: decode consider unsigned
49          return decodeInt(colData);
50        case TypeFloat:
51          return decodeFloat(colData);
52        case TypeDouble:
53          return decodeDouble(colData);
54        case TypeString:
55        case TypeVarString:
56        case TypeVarchar:
57          return new String(colData, StandardCharsets.UTF_8);
58        case TypeBlob:
59        case TypeTinyBlob:
60        case TypeMediumBlob:
61        case TypeLongBlob:
62          return colData;
63        case TypeNewDecimal:
64          return decodeDecimal(colData);
65        case TypeBit:
66          int byteSize = (int) ((tp.getLength() + 7) >>> 3);
67          return decodeBit(decodeInt(colData), byteSize);
68        case TypeDate:
69          return new Date(decodeTimestamp(colData, Converter.getLocalTimezone()).getTime());
70        case TypeDatetime:
71          return decodeTimestamp(colData, Converter.getLocalTimezone());
72        case TypeTimestamp:
73          return decodeTimestamp(colData, DateTimeZone.UTC);
74        case TypeDuration:
75        case TypeYear:
76          return decodeInt(colData);
77        case TypeEnum:
78          return decodeEnum(colData, tp.getElems());
79        case TypeSet:
80          return decodeSet(colData, tp.getElems());
81        case TypeJSON:
82          return decodeJson(colData);
83        case TypeNull:
84          return null;
85        case TypeDecimal:
86        case TypeGeometry:
87        case TypeNewDate:
88          throw new CodecException("type should not appear in colData");
89        default:
90          throw new CodecException("invalid data type " + tp.getType().name());
91      }
92    }
93  
94    private static long decodeInt(byte[] val) {
95      switch (val.length) {
96        case 1:
97          return val[0];
98        case 2:
99          return new CodecDataInputLittleEndian(val).readShort();
100       case 4:
101         return new CodecDataInputLittleEndian(val).readInt();
102       default:
103         return new CodecDataInputLittleEndian(val).readLong();
104     }
105   }
106 
107   private static float decodeFloat(byte[] val) {
108     return (float) decodeDouble(val);
109   }
110 
111   private static double decodeDouble(byte[] val) {
112     CodecDataInput cdi = new CodecDataInput(val);
113     if (val.length < 8) {
114       throw new CodecException("insufficient bytes to decode value");
115     }
116     long u = cdi.readLong();
117     // signMask is less than zero in int64.
118     if ((u & SIGN_MASK) < 0) {
119       u &= ~SIGN_MASK;
120     } else {
121       u = ~u;
122     }
123     return Double.longBitsToDouble(u);
124   }
125 
126   private static BigDecimal decodeDecimal(byte[] val) {
127     return DecimalCodec.readDecimal(new CodecDataInputLittleEndian(val));
128   }
129 
130   private static byte[] trimLeadingZeroBytes(byte[] bytes) {
131     if (bytes.length == 0) {
132       return bytes;
133     }
134     int pos = 0, posMax = bytes.length - 1;
135     for (; pos < posMax; pos++) {
136       if (bytes[pos] != 0) {
137         break;
138       }
139     }
140     return Arrays.copyOfRange(bytes, pos, bytes.length);
141   }
142 
143   private static byte[] decodeBit(long val, int byteSize) {
144     if (byteSize != -1 && (byteSize < 1 || byteSize > 8)) {
145       throw new CodecException("Invalid byteSize " + byteSize);
146     }
147     CodecDataOutput cdo = new CodecDataOutput();
148     cdo.writeLong(val);
149     if (byteSize != -1) {
150       return trimLeadingZeroBytes(cdo.toBytes());
151     } else {
152       return Arrays.copyOfRange(cdo.toBytes(), 8 - byteSize, 8);
153     }
154   }
155 
156   private static Timestamp decodeTimestamp(byte[] val, DateTimeZone tz) {
157     ExtendedDateTime extendedDateTime =
158         DateTimeCodec.fromPackedLong(new CodecDataInputLittleEndian(val).readLong(), tz);
159     // Even though null is filtered out but data like 0000-00-00 exists
160     // according to MySQL JDBC behavior, it can chose the **ROUND** behavior converted to the
161     // nearest
162     // value which is 0001-01-01.
163     if (extendedDateTime == null) {
164       return DateTimeCodec.createExtendedDateTime(tz, 1, 1, 1, 0, 0, 0, 0).toTimeStamp();
165     }
166     return extendedDateTime.toTimeStamp();
167   }
168 
169   private static String decodeEnum(byte[] val, List<String> elems) {
170     int idx = (int) decodeInt(val) - 1;
171     return EnumCodec.readEnumFromIndex(idx, elems);
172   }
173 
174   private static String decodeSet(byte[] val, List<String> elems) {
175     long number = decodeInt(val);
176     return SetCodec.readSetFromLong(number, elems);
177   }
178 
179   private static String decodeJson(byte[] val) {
180     return JsonUtils.parseJson(new CodecDataInput(val)).toString();
181   }
182 }