1 /*
2  *  Copyright (c) 2010, 2021, Oracle and/or its affiliates.
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License, version 2.0,
6  *  as published by the Free Software Foundation.
7  *
8  *  This program is also distributed with certain software (including
9  *  but not limited to OpenSSL) that is licensed under separate terms,
10  *  as designated in a particular file or component or in included license
11  *  documentation.  The authors of MySQL hereby grant you an additional
12  *  permission to link the program and your derivative works with the
13  *  separately licensed software that they have included with MySQL.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License, version 2.0, for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23  */
24 
25 package com.mysql.clusterj.tie;
26 
27 import java.lang.reflect.Method;
28 import java.math.BigDecimal;
29 import java.math.BigInteger;
30 import java.math.RoundingMode;
31 import java.nio.ByteBuffer;
32 import java.nio.ByteOrder;
33 import java.nio.CharBuffer;
34 import java.nio.charset.CharacterCodingException;
35 import java.nio.charset.Charset;
36 import java.nio.charset.CharsetDecoder;
37 import java.nio.charset.CharsetEncoder;
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.Calendar;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.TreeMap;
46 
47 import com.mysql.ndbjtie.mysql.CharsetMap;
48 import com.mysql.ndbjtie.mysql.CharsetMapConst;
49 import com.mysql.ndbjtie.mysql.Utils;
50 import com.mysql.ndbjtie.ndbapi.NdbErrorConst;
51 import com.mysql.ndbjtie.ndbapi.NdbRecAttr;
52 import com.mysql.clusterj.ClusterJDatastoreException;
53 import com.mysql.clusterj.ClusterJFatalInternalException;
54 import com.mysql.clusterj.ClusterJUserException;
55 import com.mysql.clusterj.core.store.Column;
56 import com.mysql.clusterj.core.util.I18NHelper;
57 import com.mysql.clusterj.core.util.Logger;
58 import com.mysql.clusterj.core.util.LoggerFactoryService;
59 import com.mysql.clusterj.tie.DbImpl.BufferManager;
60 
61 /** This class provides utility methods.
62  *
63  */
64 public class Utility {
65 
66     /** My message translator */
67     static final I18NHelper local = I18NHelper
68             .getInstance(Utility.class);
69 
70     /** My logger */
71     static final Logger logger = LoggerFactoryService.getFactory()
72             .getInstance(Utility.class);
73 
74     /** Standard Java charset */
75     static Charset charset = Charset.forName("windows-1252");
76 
77     static final long ooooooooooooooff = 0x00000000000000ffL;
78     static final long ooooooooooooffoo = 0x000000000000ff00L;
79     static final long ooooooooooffoooo = 0x0000000000ff0000L;
80     static final long ooooooooffoooooo = 0x00000000ff000000L;
81     static final long ooooooffoooooooo = 0x000000ff00000000L;
82     static final long ooooffoooooooooo = 0x0000ff0000000000L;
83     static final long ooffoooooooooooo = 0x00ff000000000000L;
84     static final long ffoooooooooooooo = 0xff00000000000000L;
85     static final long ooooooooffffffff = 0x00000000ffffffffL;
86     static final int ooooooff = 0x000000ff;
87     static final int ooooffff = 0x0000ffff;
88     static final int ooooffoo = 0x0000ff00;
89     static final int ooffoooo = 0x00ff0000;
90     static final int ooffffff = 0x00ffffff;
91 
92     static final char[] SPACE_PAD = new char[255];
93     static {
94         for (int i = 0; i < 255; ++i) {
95             SPACE_PAD[i] = ' ';
96         }
97     }
98 
99     static final byte[] ZERO_PAD = new byte[255];
100     static {
101         for (int i = 0; i < 255; ++i) {
102             ZERO_PAD[i] = (byte)0;
103         }
104     }
105 
106     static final byte[] BLANK_PAD = new byte[255];
107     static {
108         for (int i = 0; i < 255; ++i) {
109             BLANK_PAD[i] = (byte)' ';
110         }
111     }
112 
113     static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
114 
115     /** Scratch buffer pool used for decimal conversions; 65 digits of precision, sign, decimal, null terminator */
116     static final FixedByteBufferPoolImpl decimalByteBufferPool = new FixedByteBufferPoolImpl(68, "Decimal Pool");
117 
118     /* Error codes that are not severe, and simply reflect expected conditions */
119     private static Set<Integer> NonSevereErrorCodes = new HashSet<Integer>();
120 
121     public static final int SET_NOT_NULL_TO_NULL = 4203;
122     public static final int INDEX_NOT_FOUND = 4243;
123     public static final int ROW_NOT_FOUND = 626;
124     public static final int DUPLICATE_PRIMARY_KEY = 630;
125     public static final int DUPLICATE_UNIQUE_KEY = 893;
126     public static final int FOREIGN_KEY_NO_PARENT = 255;
127     public static final int FOREIGN_KEY_REFERENCED_ROW_EXISTS = 256;
128 
129     static {
130         NonSevereErrorCodes.add(SET_NOT_NULL_TO_NULL); // Attempt to set a NOT NULL attribute to NULL
131         NonSevereErrorCodes.add(INDEX_NOT_FOUND); // Index not found
132         NonSevereErrorCodes.add(ROW_NOT_FOUND); // Tuple did not exist
133         NonSevereErrorCodes.add(DUPLICATE_PRIMARY_KEY); // Duplicate primary key on insert
134         NonSevereErrorCodes.add(DUPLICATE_UNIQUE_KEY); // Duplicate unique key on insert
135         NonSevereErrorCodes.add(FOREIGN_KEY_NO_PARENT); // Foreign key violation; no parent exists
136         NonSevereErrorCodes.add(FOREIGN_KEY_REFERENCED_ROW_EXISTS); // Foreign key violation; referenced row exists
137     }
138 
139     // TODO: this is intended to investigate a class loader issue with Sparc java
140     // The idea is to force loading the CharsetMap native class prior to calling the static create method
141     // First, make sure that the native library is loaded because CharsetMap depends on it
142     static {
143         ClusterConnectionServiceImpl.loadSystemLibrary("ndbclient");
144     }
145     static Class<?> charsetMapClass = loadClass("com.mysql.ndbjtie.mysql.CharsetMap");
loadClass(String className)146     static Class<?> loadClass(String className) {
147         try {
148             return Class.forName(className);
149         } catch (ClassNotFoundException e) {
150             throw new ClusterJUserException(local.message("ERR_Loading_Native_Class", className), e);
151         }
152     }
153 
154     // TODO: change this to a weak reference so we can call delete on it when not needed
155     /** Note that mysql refers to charset number and charset name, but the number is
156     * actually a collation number. The CharsetMap interface thus has methods like
157     * getCharsetNumber(String charsetName) but what is returned is actually a collation number.
158     */
159     static CharsetMap charsetMap = createCharsetMap();
160 
161     // TODO: this is intended to investigate a class loader issue with Sparc java
162     // The idea is to create the CharsetMap create method in a try/catch block to report the exact error
createCharsetMap()163     static CharsetMap createCharsetMap() {
164         StringBuilder builder = new StringBuilder();
165         CharsetMap result = null;
166         try {
167             return CharsetMap.create();
168         } catch (Throwable t1) {
169             builder.append("CharsetMap.create() threw " + t1.getClass().getName() + ":" + t1.getMessage());
170             try {
171                 Method charsetMapCreateMethod = charsetMapClass.getMethod("create", (Class[])null);
172                 result = (CharsetMap)charsetMapCreateMethod.invoke(null, (Object[])null);
173                 builder.append("charsetMapCreateMethod.invoke() succeeded:" + result);
174             } catch (Throwable t2) {
175                 builder.append("charsetMapCreateMethod.invoke() threw " + t2.getClass().getName() + ":" + t2.getMessage());
176             }
177             throw new ClusterJUserException(builder.toString());
178         }
179     }
180 
181     /** The maximum mysql collation (charset) number. This is hard coded in <mysql>/include/my_sys.h */
182     static int MAXIMUM_MYSQL_COLLATION_NUMBER = 256;
183 
184     /** The mysql collation number for the standard charset */
185     static int collationLatin1 = charsetMap.getCharsetNumber("latin1");
186 
187     /** The mysql collation number for UTF16 */
188     static protected final int collationUTF16 = charsetMap.getUTF16CharsetNumber();
189 
190     /** The mysql charset map */
getCharsetMap()191     public static CharsetMap getCharsetMap() {
192         return charsetMap;
193     }
194 
195     /** The map of charset name to collations that share the charset name */
196     private static Map<String, int[]> collationPeersMap = new TreeMap<String, int[]>();
197 
198     /** The ClusterJ charset converter for all multibyte charsets */
199     private static CharsetConverter charsetConverterMultibyte = new MultiByteCharsetConverter();
200 
201     /** Charset converters */
202     private static CharsetConverter[] charsetConverters =
203         new CharsetConverter[MAXIMUM_MYSQL_COLLATION_NUMBER + 1];
204 
205     /** Initialize the the array of charset converters and the map of known collations that share the same charset */
206     static {
207         Map<String, List<Integer>> workingCollationPeersMap = new TreeMap<String, List<Integer>>();
208         for (int collation = 1; collation <= MAXIMUM_MYSQL_COLLATION_NUMBER; ++collation) {
209             String mysqlName = charsetMap.getMysqlName(collation);
210             if (mysqlName != null) {
211                 if ((isMultibyteCollation(collation))) {
212                     // multibyte collations all use the multibyte charset converter
213                     charsetConverters[collation] = charsetConverterMultibyte;
214                 } else {
215                     // find out if this charset name is already used by another (peer) collation
216                     List<Integer> collations = workingCollationPeersMap.get(mysqlName);
217                     if (collations == null) {
218                         // this is the first collation to use this charset name
219                         collations = new ArrayList<Integer>(8);
220                         collations.add(collation);
workingCollationPeersMap.put(mysqlName, collations)221                         workingCollationPeersMap.put(mysqlName, collations);
222                     } else {
223                         // add this collation to the list of (peer) collations
224                         collations.add(collation);
225                     }
226                 }
227             }
228         }
229 
230         for (Map.Entry<String, List<Integer>> workingCollationPeers: workingCollationPeersMap.entrySet()) {
231             String mysqlName = workingCollationPeers.getKey();
232             List<Integer> collations = workingCollationPeers.getValue();
233             int[] collationArray = new int[collations.size()];
234             int i = 0;
235             for (Integer collation: collations) {
236                 collationArray[i++] = collation;
237             }
collationPeersMap.put(mysqlName, collationArray)238             collationPeersMap.put(mysqlName, collationArray);
239         }
240         if (logger.isDetailEnabled()) {
241             for (Map.Entry<String, int[]> collationEntry: collationPeersMap.entrySet()) {
242                 logger.detail("Utility collationMap " + collationEntry.getKey()
243                         + " collations : " + Arrays.toString(collationEntry.getValue()));
244             }
245         }
246         // initialize the charset converter for latin1 and its peer collations (after peers are known)
247         addCollation(collationLatin1);
248     }
249 
250     /** Determine if the exception is retriable
251      * @param ex the exception
252      * @return if the status is retriable
253      */
isRetriable(ClusterJDatastoreException ex)254     public static boolean isRetriable(ClusterJDatastoreException ex) {
255         return NdbErrorConst.Status.TemporaryError == ex.getStatus();
256     }
257 
258     private final static EndianManager endianManager = ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder())?
259         /*
260          * Big Endian algorithms to convert NdbRecAttr buffer into primitive types
261          */
262         new EndianManager() {
263 
264         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
265             switch (storeColumn.getType()) {
266                 case Bit:
267                     return ndbRecAttr.int32_value() == 1;
268                 case Tinyint:
269                     return ndbRecAttr.int8_value() == 1;
270                 default:
271                     throw new ClusterJUserException(
272                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
273             }
274         }
275 
276         public boolean getBoolean(Column storeColumn, int value) {
277             switch (storeColumn.getType()) {
278                 case Bit:
279                     return value == 1;
280                 case Tinyint:
281                     // the value is stored in the top 8 bits
282                     return (value >>> 24) == 1;
283                 default:
284                     throw new ClusterJUserException(
285                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
286             }
287         }
288 
289         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
290             switch (storeColumn.getType()) {
291                 case Bit:
292                     return (byte)ndbRecAttr.int32_value();
293                 case Tinyint:
294                 case Year:
295                     return ndbRecAttr.int8_value();
296                 default:
297                     throw new ClusterJUserException(
298                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
299             }
300         }
301 
302         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
303             switch (storeColumn.getType()) {
304                 case Bit:
305                     return (short)ndbRecAttr.int32_value();
306                 case Smallint:
307                     return ndbRecAttr.short_value();
308                 default:
309                     throw new ClusterJUserException(
310                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
311             }
312         }
313 
314         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
315             switch (storeColumn.getType()) {
316                 case Bit:
317                 case Int:
318                 case Timestamp:
319                     return ndbRecAttr.int32_value();
320                 case Date:
321                     return ndbRecAttr.u_medium_value();
322                 case Time:
323                     return ndbRecAttr.medium_value();
324                 default:
325                     throw new ClusterJUserException(
326                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
327             }
328         }
329 
330         public int getInt(Column storeColumn, int value) {
331             switch (storeColumn.getType()) {
332                 case Bit:
333                 case Int:
334                 case Timestamp:
335                     return value;
336                 case Date:
337                     // the unsigned value is stored in the top 3 bytes
338                     return value >>> 8;
339                 case Time:
340                     // the signed value is stored in the top 3 bytes
341                     return value >> 8;
342                 default:
343                     throw new ClusterJUserException(
344                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
345             }
346         }
347 
348         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
349             switch (storeColumn.getType()) {
350                 case Bit:
351                     long rawValue = ndbRecAttr.int64_value();
352                     return (rawValue >>> 32) | (rawValue << 32);
353                 case Bigint:
354                 case Bigunsigned:
355                     return ndbRecAttr.int64_value();
356                 case Datetime:
357                     return unpackDatetime(ndbRecAttr.int64_value());
358                 case Timestamp:
359                     return (((long)ndbRecAttr.int32_value()) & ooooooooffffffff) * 1000L;
360                 case Date:
361                     return unpackDate(ndbRecAttr.u_medium_value());
362                 case Time:
363                     return unpackTime(ndbRecAttr.medium_value());
364                 default:
365                     throw new ClusterJUserException(
366                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
367             }
368         }
369 
370         public long getLong(Column storeColumn, long value) {
371             switch (storeColumn.getType()) {
372                 case Bit:
373                     // the data is stored as two int values
374                     return (value >>> 32) | (value << 32);
375                 case Bigint:
376                 case Bigunsigned:
377                     return value;
378                 case Datetime:
379                     return unpackDatetime(value);
380                 case Timestamp:
381                     return (value >> 32) * 1000L;
382                 case Date:
383                     // the three high order bytes are the little endian representation
384                     // the original is zzyyxx0000000000 and the result is 0000000000xxyyzz
385                     long packedDate = 0L;
386                     packedDate |= (value & ffoooooooooooooo) >>> 56;
387                     packedDate |= (value & ooffoooooooooooo) >>> 40;
388                     // the xx byte is signed, so shift left 16 and arithmetic shift right 40
389                     packedDate |= ((value & ooooffoooooooooo) << 16) >> 40;
390                     return unpackDate((int)packedDate);
391                 case Time:
392                     // the three high order bytes are the little endian representation
393                     // the original is zzyyxx0000000000 and the result is 0000000000xxyyzz
394                     long packedTime = 0L;
395                     packedTime |= (value & ffoooooooooooooo) >>> 56;
396                     packedTime |= (value & ooffoooooooooooo) >>> 40;
397                     // the xx byte is signed, so shift left 16 and arithmetic shift right 40
398                     packedTime |= ((value & ooooffoooooooooo) << 16) >> 40;
399                     return unpackTime((int)packedTime);
400                 case Datetime2:
401                     return unpackDatetime2(storeColumn.getPrecision(), value);
402                 case Timestamp2:
403                     return unpackTimestamp2(storeColumn.getPrecision(), value);
404                 case Time2:
405                     return unpackTime2(storeColumn.getPrecision(), value);
406                 default:
407                     throw new ClusterJUserException(
408                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
409             }
410         }
411 
412         /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
413          * The format for medium value is always little-endian even on big-endian architectures.
414          * Do not flip the buffer, as the caller will do that if needed.
415          * @param byteBuffer the byte buffer
416          * @param value the input value
417          */
418         public void put3byteInt(ByteBuffer byteBuffer, int value) {
419             byteBuffer.put((byte)(value));
420             byteBuffer.put((byte)(value >> 8));
421             byteBuffer.put((byte)(value >> 16));
422         }
423 
424         public ByteBuffer convertValue(Column storeColumn, byte value) {
425             ByteBuffer result;
426             switch (storeColumn.getType()) {
427                 case Bit:
428                     result = ByteBuffer.allocateDirect(4);
429                     // bit fields are always stored in an int32
430                     result.order(ByteOrder.BIG_ENDIAN);
431                     result.putInt(value & 0xff);
432                     result.flip();
433                     return result;
434                 case Tinyint:
435                 case Year:
436                     result = ByteBuffer.allocateDirect(1);
437                     result.put(value);
438                     result.flip();
439                     return result;
440                 default:
441                     throw new ClusterJUserException(local.message(
442                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
443             }
444         }
445 
446         public ByteBuffer convertValue(Column storeColumn, short value) {
447             ByteBuffer result;
448             switch (storeColumn.getType()) {
449                 case Bit:
450                     result = ByteBuffer.allocateDirect(4);
451                     // bit fields are always stored in an int32
452                     result.order(ByteOrder.BIG_ENDIAN);
453                     result.putInt(value & 0xffff);
454                     result.flip();
455                     return result;
456                 case Smallint:
457                     result = ByteBuffer.allocateDirect(2);
458                     result.order(ByteOrder.BIG_ENDIAN);
459                     result.putShort(value);
460                     result.flip();
461                     return result;
462                 default:
463                     throw new ClusterJUserException(local.message(
464                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
465             }
466         }
467 
468         public ByteBuffer convertValue(Column storeColumn, int value) {
469             ByteBuffer result = ByteBuffer.allocateDirect(4);
470             switch (storeColumn.getType()) {
471                 case Bit:
472                 case Int:
473                     result.order(ByteOrder.BIG_ENDIAN);
474                     break;
475                 default:
476                     throw new ClusterJUserException(local.message(
477                             "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
478             }
479             result.putInt(value);
480             result.flip();
481             return result;
482         }
483 
484         public int convertIntValueForStorage(Column storeColumn, int value) {
485             switch (storeColumn.getType()) {
486                 case Bit:
487                 case Int:
488                     return value;
489                 default:
490                     throw new ClusterJUserException(local.message(
491                             "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
492             }
493         }
494 
495         public ByteBuffer convertValue(Column storeColumn, long value) {
496             ByteBuffer result = ByteBuffer.allocateDirect(8);
497             return convertValue(storeColumn, value, result);
498         }
499 
500         public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
501             switch (storeColumn.getType()) {
502                 case Bit:
503                     // bit fields are stored in two int32 fields
504                     result.order(ByteOrder.BIG_ENDIAN);
505                     result.putInt((int)((value)));
506                     result.putInt((int)((value >>> 32)));
507                     result.flip();
508                     return result;
509                 case Bigint:
510                 case Bigunsigned:
511                     result.order(ByteOrder.BIG_ENDIAN);
512                     result.putLong(value);
513                     result.flip();
514                     return result;
515                 case Date:
516                     result.order(ByteOrder.LITTLE_ENDIAN);
517                     put3byteInt(result, packDate(value));
518                     result.flip();
519                     return result;
520                 case Datetime:
521                     result.order(ByteOrder.BIG_ENDIAN);
522                     result.putLong(packDatetime(value));
523                     result.flip();
524                     return result;
525                 case Time:
526                     result.order(ByteOrder.LITTLE_ENDIAN);
527                     put3byteInt(result, packTime(value));
528                     result.flip();
529                     return result;
530                 case Timestamp:
531                     result.order(ByteOrder.BIG_ENDIAN);
532                     result.putInt((int)(value/1000L));
533                     result.flip();
534                     return result;
535                 case Datetime2:
536                     result.order(ByteOrder.BIG_ENDIAN);
537                     result.putLong(packDatetime2(storeColumn.getPrecision(), value));
538                     result.flip();
539                     return result;
540                 case Time2:
541                     result.order(ByteOrder.BIG_ENDIAN);
542                     result.putLong(packTime2(storeColumn.getPrecision(), value));
543                     result.flip();
544                     return result;
545                 case Timestamp2:
546                     result.order(ByteOrder.BIG_ENDIAN);
547                     result.putLong(packTimestamp2(storeColumn.getPrecision(), value));
548                     result.flip();
549                     return result;
550                 default:
551                     throw new ClusterJUserException(local.message(
552                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
553             }
554         }
555 
556         public long convertLongValueForStorage(Column storeColumn, long value) {
557             long result = 0L;
558             switch (storeColumn.getType()) {
559                 case Bit:
560                     // bit fields are stored in two int32 fields
561                     result |= (value >>> 32);
562                     result |= (value << 32);
563                     return result;
564                 case Bigint:
565                 case Bigunsigned:
566                     return value;
567                 case Date:
568                     // the high order bytes are the little endian representation
569                     // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
570                     long packDate = packDate(value);
571                     result |= (packDate & ooooooff) << 56;
572                     result |= (packDate & ooooffoo) << 40;
573                     result |= (packDate & ooffoooo) << 24;
574                     return result;
575                 case Datetime:
576                     return packDatetime(value);
577                 case Time:
578                     // the high order bytes are the little endian representation
579                     // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
580                     long packTime = packTime(value);
581                     result |= (packTime & ooooooff) << 56;
582                     result |= (packTime & ooooffoo) << 40;
583                     result |= (packTime & ooffoooo) << 24;
584                     return result;
585                 case Timestamp:
586                     // timestamp is an int so put the value into the high bytes
587                     // the original is 00000000tttttttt and the result is tttttttt00000000
588                     return (value/1000L) << 32;
589                 case Datetime2:
590                     // value is in milliseconds since the epoch
591                     return packDatetime2(storeColumn.getPrecision(), value);
592                 case Time2:
593                     // value is in milliseconds since the epoch
594                     return packTime2(storeColumn.getPrecision(), value);
595                 case Timestamp2:
596                     // value is in milliseconds since the epoch
597                     return packTimestamp2(storeColumn.getPrecision(), value);
598                 default:
599                     throw new ClusterJUserException(local.message(
600                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
601             }
602         }
603 
604         public int convertByteValueForStorage(Column storeColumn, byte value) {
605             switch (storeColumn.getType()) {
606                 case Bit:
607                     // bit fields are always stored in an int32
608                     return value & ooooooff;
609                 case Tinyint:
610                 case Year:
611                     // other byte values are stored in the high byte of an int
612                     return value << 24;
613                 default:
614                     throw new ClusterJUserException(local.message(
615                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
616             }
617         }
618 
619         public int convertShortValueForStorage(Column storeColumn, short value) {
620             switch (storeColumn.getType()) {
621                 case Bit:
622                     // bit fields are always stored in an int32
623                     return value & ooooffff;
624                 case Smallint:
625                     // short values are in the top 16 bits of an int
626                     return value << 16;
627                 default:
628                     throw new ClusterJUserException(local.message(
629                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
630             }
631         }
632 
633         public long convertLongValueFromStorage(Column storeColumn,
634                 long fromStorage) {
635             // TODO Auto-generated method stub
636             return 0;
637         }
638 
639     }:
640         /*
641          * Little Endian algorithms to convert NdbRecAttr buffer into primitive types
642          */
643         new EndianManager() {
644 
645         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
646             switch (storeColumn.getType()) {
647                 case Bit:
648                     return ndbRecAttr.int32_value() == 1;
649                 case Tinyint:
650                     return ndbRecAttr.int8_value() == 1;
651                 default:
652                     throw new ClusterJUserException(
653                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
654             }
655         }
656 
657         public boolean getBoolean(Column storeColumn, int value) {
658             switch (storeColumn.getType()) {
659                 case Bit:
660                 case Tinyint:
661                     return value == 1;
662                 default:
663                     throw new ClusterJUserException(
664                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
665             }
666         }
667 
668         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
669             switch (storeColumn.getType()) {
670                 case Bit:
671                 case Tinyint:
672                 case Year:
673                     return ndbRecAttr.int8_value();
674                 default:
675                     throw new ClusterJUserException(
676                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
677             }
678         }
679 
680         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
681             switch (storeColumn.getType()) {
682                 case Bit:
683                 case Smallint:
684                     return ndbRecAttr.short_value();
685                 default:
686                     throw new ClusterJUserException(
687                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
688             }
689         }
690 
691         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
692             switch (storeColumn.getType()) {
693                 case Bit:
694                 case Int:
695                 case Timestamp:
696                     return ndbRecAttr.int32_value();
697                 case Date:
698                     return ndbRecAttr.u_medium_value();
699                 case Time:
700                     return ndbRecAttr.medium_value();
701                 default:
702                     throw new ClusterJUserException(
703                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
704             }
705         }
706 
707         public int getInt(Column storeColumn, int value) {
708             switch (storeColumn.getType()) {
709                 case Bit:
710                 case Int:
711                 case Timestamp:
712                     return value;
713                 case Date:
714                     return value & ooffffff;
715                 case Time:
716                     // propagate the sign bit from 3 byte medium_int
717                     return (value << 8) >> 8;
718                 default:
719                     throw new ClusterJUserException(
720                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
721             }
722         }
723 
724         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
725             switch (storeColumn.getType()) {
726                 case Bigint:
727                 case Bigunsigned:
728                 case Bit:
729                     return ndbRecAttr.int64_value();
730                 case Datetime:
731                     return unpackDatetime(ndbRecAttr.int64_value());
732                 case Timestamp:
733                     return ndbRecAttr.int32_value() * 1000L;
734                 case Date:
735                     return unpackDate(ndbRecAttr.int32_value());
736                 case Time:
737                     return unpackTime(ndbRecAttr.int32_value());
738                 default:
739                     throw new ClusterJUserException(
740                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
741             }
742         }
743 
744         public long getLong(Column storeColumn, long value) {
745             switch (storeColumn.getType()) {
746                 case Bigint:
747                 case Bigunsigned:
748                 case Bit:
749                     return value;
750                 case Datetime:
751                     return unpackDatetime(value);
752                 case Timestamp:
753                     return value * 1000L;
754                 case Date:
755                     return unpackDate((int)(value));
756                 case Time:
757                     return unpackTime((int)(value));
758                 case Datetime2:
759                     // datetime2 is stored in big endian format so need to swap the input
760                     return unpackDatetime2(storeColumn.getPrecision(), swap(value));
761                 case Timestamp2:
762                     // timestamp2 is stored in big endian format so need to swap the input
763                     return unpackTimestamp2(storeColumn.getPrecision(), swap(value));
764                 case Time2:
765                     // time2 is stored in big endian format so need to swap the input
766                     return unpackTime2(storeColumn.getPrecision(), swap(value));
767                 default:
768                     throw new ClusterJUserException(
769                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
770             }
771         }
772 
773         /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
774          * The format for medium value is always little-endian even on big-endian architectures.
775          * Do not flip the buffer, as the caller will do that if needed.
776          * @param byteBuffer the byte buffer
777          * @param value the input value
778          */
779         public void put3byteInt(ByteBuffer byteBuffer, int value) {
780             byteBuffer.putInt(value);
781             byteBuffer.limit(3);
782         }
783 
784         public ByteBuffer convertValue(Column storeColumn, byte value) {
785             ByteBuffer result;
786             switch (storeColumn.getType()) {
787                 case Bit:
788                     // bit fields are always stored as int32
789                     result = ByteBuffer.allocateDirect(4);
790                     result.order(ByteOrder.nativeOrder());
791                     result.putInt(value & 0xff);
792                     result.flip();
793                     return result;
794                 case Tinyint:
795                 case Year:
796                     result = ByteBuffer.allocateDirect(1);
797                     result.order(ByteOrder.nativeOrder());
798                     result.put(value);
799                     result.flip();
800                     return result;
801                 default:
802                     throw new ClusterJUserException(local.message(
803                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
804             }
805         }
806 
807         public ByteBuffer convertValue(Column storeColumn, short value) {
808             switch (storeColumn.getType()) {
809                 case Bit:
810                 case Smallint:
811                     ByteBuffer result = ByteBuffer.allocateDirect(2);
812                     result.order(ByteOrder.nativeOrder());
813                     result.putShort(value);
814                     result.flip();
815                     return result;
816                 default:
817                     throw new ClusterJUserException(local.message(
818                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
819             }
820         }
821 
822         public ByteBuffer convertValue(Column storeColumn, int value) {
823             switch (storeColumn.getType()) {
824                 case Bit:
825                 case Int:
826                     ByteBuffer result = ByteBuffer.allocateDirect(4);
827                     result.order(ByteOrder.nativeOrder());
828                     result.putInt(value);
829                     result.flip();
830                     return result;
831                 default:
832                     throw new ClusterJUserException(local.message(
833                             "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
834             }
835         }
836 
837         public int convertIntValueForStorage(Column storeColumn, int value) {
838             switch (storeColumn.getType()) {
839                 case Bit:
840                 case Int:
841                     return value;
842                 default:
843                     throw new ClusterJUserException(local.message(
844                             "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
845             }
846         }
847 
848         public ByteBuffer convertValue(Column storeColumn, long value) {
849             ByteBuffer result = ByteBuffer.allocateDirect(8);
850             return convertValue(storeColumn, value, result);
851         }
852 
853         public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
854             switch (storeColumn.getType()) {
855                 case Bit:
856                 case Bigint:
857                 case Bigunsigned:
858                     result.order(ByteOrder.LITTLE_ENDIAN);
859                     result.putLong(value);
860                     result.flip();
861                     return result;
862                 case Datetime:
863                     result.order(ByteOrder.LITTLE_ENDIAN);
864                     result.putLong(packDatetime(value));
865                     result.flip();
866                     return result;
867                 case Timestamp:
868                     result.order(ByteOrder.LITTLE_ENDIAN);
869                     result.putInt((int)(value/1000L));
870                     result.flip();
871                     return result;
872                 case Date:
873                     result.order(ByteOrder.LITTLE_ENDIAN);
874                     put3byteInt(result, packDate(value));
875                     result.flip();
876                     return result;
877                 case Time:
878                     result.order(ByteOrder.LITTLE_ENDIAN);
879                     put3byteInt(result, packTime(value));
880                     result.flip();
881                     return result;
882                 case Datetime2:
883                     result.order(ByteOrder.BIG_ENDIAN);
884                     result.putLong(packDatetime2(storeColumn.getPrecision(), value));
885                     result.flip();
886                     return result;
887                 case Time2:
888                     result.order(ByteOrder.BIG_ENDIAN);
889                     result.putLong(packTime2(storeColumn.getPrecision(), value));
890                     result.flip();
891                     return result;
892                 case Timestamp2:
893                     result.order(ByteOrder.BIG_ENDIAN);
894                     result.putLong(packTimestamp2(storeColumn.getPrecision(), value));
895                     result.flip();
896                     return result;
897                 default:
898                     throw new ClusterJUserException(local.message(
899                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
900             }
901         }
902 
903         public long convertLongValueForStorage(Column storeColumn, long value) {
904             switch (storeColumn.getType()) {
905                 case Bit:
906                 case Bigint:
907                 case Bigunsigned:
908                     return value;
909                 case Datetime:
910                     return packDatetime(value);
911                case Timestamp:
912                     return value/1000L;
913                 case Date:
914                     return packDate(value);
915                 case Time:
916                     return packTime(value);
917                 case Datetime2:
918                     // value is in milliseconds since the epoch
919                     // datetime2 is in big endian format so need to swap the result
920                     return swap(packDatetime2(storeColumn.getPrecision(), value));
921                 case Time2:
922                     // value is in milliseconds since the epoch
923                     // time2 is in big endian format so need to swap the result
924                     return swap(packTime2(storeColumn.getPrecision(), value));
925                 case Timestamp2:
926                     // value is in milliseconds since the epoch
927                     // timestamp2 is in big endian format so need to swap the result
928                     return swap(packTimestamp2(storeColumn.getPrecision(), value));
929                 default:
930                     throw new ClusterJUserException(local.message(
931                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
932             }
933         }
934 
935         public int convertByteValueForStorage(Column storeColumn, byte value) {
936             switch (storeColumn.getType()) {
937                 case Bit:
938                     // bit fields are always stored as int32
939                 case Tinyint:
940                 case Year:
941                     return  value & ooooooff;
942                 default:
943                     throw new ClusterJUserException(local.message(
944                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
945             }
946         }
947 
948         public int convertShortValueForStorage(Column storeColumn, short value) {
949             switch (storeColumn.getType()) {
950                 case Bit:
951                     // bit fields are always stored as int32
952                 case Smallint:
953                     return value & ooooffff;
954                 default:
955                     throw new ClusterJUserException(local.message(
956                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
957             }
958         }
959 
960         public long convertLongValueFromStorage(Column storeColumn, long fromStorage) {
961             switch (storeColumn.getType()) {
962                 case Bigint:
963                 case Bigunsigned:
964                 case Bit:
965                     return fromStorage;
966                 case Datetime:
967                     return unpackDatetime(fromStorage);
968                 case Timestamp:
969                     return fromStorage * 1000L;
970                 case Date:
971                     return unpackDate((int)fromStorage);
972                 case Time:
973                     return unpackTime((int)fromStorage);
974                 default:
975                     throw new ClusterJUserException(
976                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
977             }
978         }
979 
980     };
981 
982     protected static interface EndianManager {
put3byteInt(ByteBuffer byteBuffer, int value)983         public void put3byteInt(ByteBuffer byteBuffer, int value);
getInt(Column storeColumn, int value)984         public int getInt(Column storeColumn, int value);
getInt(Column storeColumn, NdbRecAttr ndbRecAttr)985         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr);
getShort(Column storeColumn, NdbRecAttr ndbRecAttr)986         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr);
getLong(Column storeColumn, NdbRecAttr ndbRecAttr)987         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr);
getLong(Column storeColumn, long value)988         public long getLong(Column storeColumn, long value);
getByte(Column storeColumn, NdbRecAttr ndbRecAttr)989         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr);
convertValue(Column storeColumn, byte value)990         public ByteBuffer convertValue(Column storeColumn, byte value);
convertValue(Column storeColumn, short value)991         public ByteBuffer convertValue(Column storeColumn, short value);
convertValue(Column storeColumn, int value)992         public ByteBuffer convertValue(Column storeColumn, int value);
convertValue(Column storeColumn, long value)993         public ByteBuffer convertValue(Column storeColumn, long value);
getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr)994         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr);
getBoolean(Column storeColumn, int value)995         public boolean getBoolean(Column storeColumn, int value);
convertIntValueForStorage(Column storeColumn, int value)996         public int convertIntValueForStorage(Column storeColumn, int value);
convertLongValueForStorage(Column storeColumn, long value)997         public long convertLongValueForStorage(Column storeColumn, long value);
convertLongValueFromStorage(Column storeColumn, long fromStorage)998         public long convertLongValueFromStorage(Column storeColumn, long fromStorage);
convertByteValueForStorage(Column storeColumn, byte value)999         public int convertByteValueForStorage(Column storeColumn, byte value);
convertShortValueForStorage(Column storeColumn, short value)1000         public int convertShortValueForStorage(Column storeColumn, short value);
1001     }
1002 
1003     /** Swap the bytes in the value, thereby converting a big-endian value
1004      * into a little-endian value (or vice versa).
1005      * @param value the value to be swapped
1006      * @return the swapped value
1007      */
swap(short value)1008     protected static short swap(short value) {
1009         return (short)((0x00ff & (value >>> 8)) |
1010                        (0xff00 & (value  << 8)));
1011     }
1012 
1013     /** Swap the bytes in the value, thereby converting a big-endian value
1014      * into a little-endian value (or vice versa).
1015      * @param value the value to be swapped
1016      * @return the swapped value
1017      */
swap(int value)1018     protected static int swap(int value) {
1019         return   0x000000ff & (value >>> 24) |
1020                 (0x0000ff00 & (value >>> 8)) |
1021                 (0x00ff0000 & (value  << 8)) |
1022                 (0xff000000 & (value  << 24));
1023     }
1024 
1025     /** Swap the bytes in the value, thereby converting a big-endian value
1026      * into a little-endian value (or vice versa).
1027      * @param value the value to be swapped
1028      * @return the swapped value
1029      */
swap(long value)1030     protected static long swap(long value) {
1031         return   ooooooooooooooff & (value >>> 56) |
1032                 (ooooooooooooffoo & (value >>> 40)) |
1033                 (ooooooooooffoooo & (value >>> 24)) |
1034                 (ooooooooffoooooo & (value >>> 8)) |
1035                 (ooooooffoooooooo & (value  << 8)) |
1036                 (ooooffoooooooooo & (value  << 24)) |
1037                 (ooffoooooooooooo & (value  << 40)) |
1038                 (ffoooooooooooooo & (value  << 56));
1039     }
1040 
throwError(Object returnCode, NdbErrorConst ndbError)1041     protected static void throwError(Object returnCode, NdbErrorConst ndbError) {
1042         throwError(returnCode, ndbError, "");
1043     }
1044 
throwError(Object returnCode, NdbErrorConst ndbError, String extra)1045     protected static void throwError(Object returnCode, NdbErrorConst ndbError, String extra) {
1046         String message = ndbError.message();
1047         int code = ndbError.code();
1048         int mysqlCode = ndbError.mysql_code();
1049         int status = ndbError.status();
1050         int classification = ndbError.classification();
1051         String msg = local.message("ERR_NdbJTie", returnCode, code, mysqlCode,
1052                 status, classification, message, extra);
1053         if (!NonSevereErrorCodes .contains(code)) {
1054             logger.error(msg);
1055         }
1056         throw new ClusterJDatastoreException(msg, code, mysqlCode, status, classification);
1057     }
1058 
1059     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1060      *
1061      * @param storeColumn the column definition
1062      * @param value the value to be converted
1063      * @return the ByteBuffer
1064      */
convertValue(Column storeColumn, byte[] value)1065     public static ByteBuffer convertValue(Column storeColumn, byte[] value) {
1066         int requiredLength = storeColumn.getColumnSpace();
1067         ByteBuffer result = ByteBuffer.allocateDirect(requiredLength);
1068         convertValue(result, storeColumn, value);
1069         result.flip();
1070         return result;
1071     }
1072 
1073     /** Convert the parameter value and store it in a given ByteBuffer that can be passed to ndbjtie.
1074      *
1075      * @param buffer the buffer, positioned at the location to store the value
1076      * @param storeColumn the column definition
1077      * @param value the value to be converted
1078      */
convertValue(ByteBuffer buffer, Column storeColumn, byte[] value)1079     public static void convertValue(ByteBuffer buffer, Column storeColumn, byte[] value) {
1080         int dataLength = value.length;
1081         int maximumLength = storeColumn.getLength();
1082         if (dataLength > maximumLength) {
1083             throw new ClusterJUserException(
1084                     local.message("ERR_Data_Too_Long",
1085                     storeColumn.getName(), maximumLength, dataLength));
1086         }
1087         int prefixLength = storeColumn.getPrefixLength();
1088         switch (prefixLength) {
1089             case 0:
1090                 buffer.put(value);
1091                 if (dataLength < maximumLength) {
1092                     // pad with 0x00 on right
1093                     buffer.put(ZERO_PAD, 0, maximumLength - dataLength);
1094                 }
1095                 break;
1096             case 1:
1097                 buffer.put((byte)dataLength);
1098                 buffer.put(value);
1099                 break;
1100             case 2:
1101                 buffer.put((byte)(dataLength%256));
1102                 buffer.put((byte)(dataLength/256));
1103                 buffer.put(value);
1104                 break;
1105             default:
1106                     throw new ClusterJFatalInternalException(
1107                             local.message("ERR_Unknown_Prefix_Length",
1108                             prefixLength, storeColumn.getName()));
1109         }
1110     }
1111 
1112     /** Convert a BigDecimal value to the binary decimal form used by MySQL.
1113      * Store the result in the given ByteBuffer that is already positioned.
1114      * Use the precision and scale of the column to convert. Values that don't fit
1115      * into the column throw a ClusterJUserException.
1116      * @param result the buffer, positioned at the location to store the value
1117      * @param storeColumn the column metadata
1118      * @param value the value to be converted
1119      * @return the ByteBuffer
1120      */
convertValue(ByteBuffer result, Column storeColumn, BigDecimal value)1121     public static void convertValue(ByteBuffer result, Column storeColumn, BigDecimal value) {
1122         int precision = storeColumn.getPrecision();
1123         int scale = storeColumn.getScale();
1124         int bytesNeeded = getDecimalColumnSpace(precision, scale);
1125         // TODO this should be a policy option, perhaps an annotation to fail on truncation
1126         BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
1127         // the new value has the same scale as the column
1128         String stringRepresentation = scaledValue.toPlainString();
1129         int length = stringRepresentation.length();
1130         ByteBuffer byteBuffer = decimalByteBufferPool.borrowBuffer();
1131         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
1132         // basic encoding
1133         charset.newEncoder().encode(charBuffer, byteBuffer, true);
1134         byteBuffer.flip();
1135         int returnCode = Utils.decimal_str2bin(
1136                 byteBuffer, length, precision, scale, result, bytesNeeded);
1137         decimalByteBufferPool.returnBuffer(byteBuffer);
1138         if (returnCode != 0) {
1139             throw new ClusterJUserException(
1140                     local.message("ERR_String_To_Binary_Decimal",
1141                     returnCode, scaledValue, storeColumn.getName(), precision, scale));
1142         }
1143     }
1144 
1145     /** Convert a BigDecimal value to the binary decimal form used by MySQL.
1146      * Use the precision and scale of the column to convert. Values that don't fit
1147      * into the column throw a ClusterJUserException.
1148      * @param storeColumn the column metadata
1149      * @param value the value to be converted
1150      * @return the ByteBuffer
1151      */
convertValue(Column storeColumn, BigDecimal value)1152     public static ByteBuffer convertValue(Column storeColumn, BigDecimal value) {
1153         int precision = storeColumn.getPrecision();
1154         int scale = storeColumn.getScale();
1155         int bytesNeeded = getDecimalColumnSpace(precision, scale);
1156         ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
1157         // TODO this should be a policy option, perhaps an annotation to fail on truncation
1158         BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
1159         // the new value has the same scale as the column
1160         String stringRepresentation = scaledValue.toPlainString();
1161         int length = stringRepresentation.length();
1162         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
1163         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
1164         // basic encoding
1165         charset.newEncoder().encode(charBuffer, byteBuffer, true);
1166         byteBuffer.flip();
1167         int returnCode = Utils.decimal_str2bin(
1168                 byteBuffer, length, precision, scale, result, bytesNeeded);
1169         byteBuffer.flip();
1170         if (returnCode != 0) {
1171             throw new ClusterJUserException(
1172                     local.message("ERR_String_To_Binary_Decimal",
1173                     returnCode, scaledValue, storeColumn.getName(), precision, scale));
1174         }
1175         return result;
1176     }
1177 
1178     /** Convert a BigInteger value to the binary decimal form used by MySQL.
1179      * Store the result in the given ByteBuffer that is already positioned.
1180      * Use the precision and scale of the column to convert. Values that don't fit
1181      * into the column throw a ClusterJUserException.
1182      * @param result the buffer, positioned at the location to store the value
1183      * @param storeColumn the column metadata
1184      * @param value the value to be converted
1185      * @return the ByteBuffer
1186      */
convertValue(ByteBuffer result, Column storeColumn, BigInteger value)1187     public static ByteBuffer convertValue(ByteBuffer result, Column storeColumn, BigInteger value) {
1188         int precision = storeColumn.getPrecision();
1189         int scale = storeColumn.getScale();
1190         int bytesNeeded = getDecimalColumnSpace(precision, scale);
1191         String stringRepresentation = value.toString();
1192         int length = stringRepresentation.length();
1193         ByteBuffer byteBuffer = decimalByteBufferPool.borrowBuffer();
1194         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
1195         // basic encoding
1196         charset.newEncoder().encode(charBuffer, byteBuffer, true);
1197         byteBuffer.flip();
1198         int returnCode = Utils.decimal_str2bin(
1199                 byteBuffer, length, precision, scale, result, bytesNeeded);
1200         decimalByteBufferPool.returnBuffer(byteBuffer);
1201         byteBuffer.flip();
1202         if (returnCode != 0) {
1203             throw new ClusterJUserException(
1204                     local.message("ERR_String_To_Binary_Decimal",
1205                     returnCode, stringRepresentation, storeColumn.getName(), precision, scale));
1206         }
1207         return result;
1208     }
1209 
1210     /** Convert a BigInteger value to the binary decimal form used by MySQL.
1211      * Use the precision and scale of the column to convert. Values that don't fit
1212      * into the column throw a ClusterJUserException.
1213      * @param storeColumn the column metadata
1214      * @param value the value to be converted
1215      * @return the ByteBuffer
1216      */
convertValue(Column storeColumn, BigInteger value)1217     public static ByteBuffer convertValue(Column storeColumn, BigInteger value) {
1218         int precision = storeColumn.getPrecision();
1219         int scale = storeColumn.getScale();
1220         int bytesNeeded = getDecimalColumnSpace(precision, scale);
1221         ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
1222         String stringRepresentation = value.toString();
1223         int length = stringRepresentation.length();
1224         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
1225         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
1226         // basic encoding
1227         charset.newEncoder().encode(charBuffer, byteBuffer, true);
1228         byteBuffer.flip();
1229         int returnCode = Utils.decimal_str2bin(
1230                 byteBuffer, length, precision, scale, result, bytesNeeded);
1231         byteBuffer.flip();
1232         if (returnCode != 0) {
1233             throw new ClusterJUserException(
1234                     local.message("ERR_String_To_Binary_Decimal",
1235                     returnCode, stringRepresentation, storeColumn.getName(), precision, scale));
1236         }
1237         return result;
1238     }
1239 
1240     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1241      *
1242      * @param storeColumn the column definition
1243      * @param value the value to be converted
1244      * @return the ByteBuffer
1245      */
convertValue(Column storeColumn, double value)1246     public static ByteBuffer convertValue(Column storeColumn, double value) {
1247         ByteBuffer result = ByteBuffer.allocateDirect(8);
1248         result.order(ByteOrder.nativeOrder());
1249         result.putDouble(value);
1250         result.flip();
1251         return result;
1252     }
1253 
1254     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1255      *
1256      * @param storeColumn the column definition
1257      * @param value the value to be converted
1258      * @return the ByteBuffer
1259      */
convertValue(Column storeColumn, float value)1260     public static ByteBuffer convertValue(Column storeColumn, float value) {
1261         ByteBuffer result = ByteBuffer.allocateDirect(4);
1262         result.order(ByteOrder.nativeOrder());
1263         result.putFloat(value);
1264         result.flip();
1265         return result;
1266     }
1267 
1268     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1269      *
1270      * @param storeColumn the column definition
1271      * @param value the value to be converted
1272      * @return the ByteBuffer
1273      */
convertValue(Column storeColumn, byte value)1274     public static ByteBuffer convertValue(Column storeColumn, byte value) {
1275         return endianManager.convertValue(storeColumn, value);
1276     }
1277 
1278     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1279      *
1280      * @param storeColumn the column definition
1281      * @param value the value to be converted
1282      * @return the ByteBuffer
1283      */
convertValue(Column storeColumn, short value)1284     public static ByteBuffer convertValue(Column storeColumn, short value) {
1285         return endianManager.convertValue(storeColumn, value);
1286     }
1287 
1288     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1289      *
1290      * @param storeColumn the column definition
1291      * @param value the value to be converted
1292      * @return the ByteBuffer
1293      */
convertValue(Column storeColumn, int value)1294     public static ByteBuffer convertValue(Column storeColumn, int value) {
1295         return endianManager.convertValue(storeColumn, value);
1296     }
1297 
1298     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
1299      *
1300      * @param storeColumn the column definition
1301      * @param value the value to be converted
1302      * @return the ByteBuffer
1303      */
convertValue(Column storeColumn, long value)1304     public static ByteBuffer convertValue(Column storeColumn, long value) {
1305         return endianManager.convertValue(storeColumn, value);
1306     }
1307 
1308     /** Encode a String as a ByteBuffer that can be passed to ndbjtie.
1309      * Put the length information in the beginning of the buffer.
1310      * Pad fixed length strings with blanks.
1311      * @param storeColumn the column definition
1312      * @param value the value to be converted
1313      * @return the ByteBuffer
1314      */
convertValue(Column storeColumn, String value)1315     protected static ByteBuffer convertValue(Column storeColumn, String value) {
1316         if (value == null) {
1317             value = "";
1318         }
1319         CharSequence chars = value;
1320         int offset = storeColumn.getPrefixLength();
1321         if (offset == 0) {
1322             chars = padString(value, storeColumn);
1323         }
1324         ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), offset);
1325         fixBufferPrefixLength(storeColumn.getName(), byteBuffer, offset);
1326         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1327         return byteBuffer;
1328     }
1329 
1330     /** Encode a String as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
1331      * There is no length information in the beginning of the buffer.
1332      * @param storeColumn the column definition
1333      * @param value the value to be converted
1334      * @return the ByteBuffer
1335      */
convertValueForLikeFilter(Column storeColumn, String value)1336     protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, String value) {
1337         if (value == null) {
1338             value = "";
1339         }
1340         CharSequence chars = value;
1341         ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), 0);
1342         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1343         return byteBuffer;
1344     }
1345 
1346     /** Encode a byte[] as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
1347      * There is no length information in the beginning of the buffer.
1348      * @param storeColumn the column definition
1349      * @param value the value to be converted
1350      * @return the ByteBuffer
1351      */
convertValueForLikeFilter(Column storeColumn, byte[] value)1352     protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, byte[] value) {
1353         if (value == null) {
1354             value = EMPTY_BYTE_ARRAY;
1355         }
1356         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(value.length);
1357         byteBuffer.put(value);
1358         byteBuffer.flip();
1359         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1360         return byteBuffer;
1361     }
1362 
1363     /** Pad the value with blanks on the right.
1364      * @param value the input value
1365      * @param storeColumn the store column
1366      * @return the value padded with blanks on the right
1367      */
padString(CharSequence value, Column storeColumn)1368     private static CharSequence padString(CharSequence value, Column storeColumn) {
1369         CharSequence chars = value;
1370         int suppliedLength = value.length();
1371         int requiredLength = storeColumn.getColumnSpace();
1372         if (suppliedLength > requiredLength) {
1373             throw new ClusterJUserException(local.message("ERR_Data_Too_Long",
1374                     storeColumn.getName(), requiredLength, suppliedLength));
1375         } else if (suppliedLength < requiredLength) {
1376             // pad to fixed length
1377             StringBuilder buffer = new StringBuilder(requiredLength);
1378             buffer.append(value);
1379             buffer.append(SPACE_PAD, 0, requiredLength - suppliedLength);
1380             chars = buffer;
1381         }
1382         return chars;
1383     }
1384 
1385     /** Pad the value with blanks on the right.
1386      * @param buffer the input value
1387      * @param storeColumn the store column
1388      * @return the buffer padded with blanks on the right
1389      */
padString(ByteBuffer buffer, Column storeColumn)1390     private static ByteBuffer padString(ByteBuffer buffer, Column storeColumn) {
1391         int suppliedLength = buffer.limit();
1392         int requiredLength = storeColumn.getColumnSpace();
1393         if (suppliedLength > requiredLength) {
1394             throw new ClusterJUserException(local.message("ERR_Data_Too_Long",
1395                     storeColumn.getName(), requiredLength, suppliedLength));
1396         } else if (suppliedLength < requiredLength) {
1397         	//reset buffer's limit
1398         	buffer.limit(requiredLength);
1399         	//pad to fixed length
1400         	buffer.position(suppliedLength);
1401         	buffer.put(BLANK_PAD, 0, requiredLength - suppliedLength);
1402         	buffer.position(0);
1403         }
1404         return buffer;
1405     }
1406 
1407     /** Fix the length information in a buffer based on the length prefix,
1408      * either 0, 1, or 2 bytes that hold the length information.
1409      * @param byteBuffer the byte buffer to fix
1410      * @param offset the size of the length prefix
1411      */
fixBufferPrefixLength(String columnName, ByteBuffer byteBuffer, int offset)1412     public static void fixBufferPrefixLength(String columnName, ByteBuffer byteBuffer, int offset) {
1413         int limit = byteBuffer.limit();
1414         // go back and fill in the length field(s)
1415         // size of the output char* is current limit minus offset
1416         int length = limit - offset;
1417         byteBuffer.position(0);
1418         switch (offset) {
1419             case 0:
1420                 break;
1421             case 1:
1422                 byteBuffer.put((byte)(length % 256));
1423                 break;
1424             case 2:
1425                 byteBuffer.put((byte)(length % 256));
1426                 byteBuffer.put((byte)(length / 256));
1427                 break;
1428         }
1429         // reset the position and limit for return
1430         byteBuffer.position(0);
1431         byteBuffer.limit(limit);
1432     }
1433 
1434     /** Pack milliseconds since the Epoch into an int in database Date format.
1435      * The date is converted into a three-byte value encoded as
1436      * YYYYx16x32 + MMx32 + DD.
1437      * Add one to the month since Calendar month is 0-origin.
1438      * @param millis milliseconds since the Epoch
1439      * @return the int in packed Date format
1440      */
packDate(long millis)1441     private static int packDate(long millis) {
1442         Calendar calendar = Calendar.getInstance();
1443         calendar.clear();
1444         calendar.setTimeInMillis(millis);
1445         int year = calendar.get(Calendar.YEAR);
1446         int month = calendar.get(Calendar.MONTH);
1447         int day = calendar.get(Calendar.DATE);
1448         int date = (year * 512) + ((month + 1) * 32) + day;
1449         return date;
1450     }
1451 
1452     /** Pack milliseconds since the Epoch into an int in database Time format.
1453      * Subtract one from date to get number of days (date is 1 origin).
1454      * The time is converted into a three-byte value encoded as
1455      * DDx240000 + HHx10000 + MMx100 + SS.
1456      * @param millis milliseconds since the Epoch
1457      * @return the int in packed Time format
1458      */
packTime(long millis)1459     private static int packTime(long millis) {
1460         Calendar calendar = Calendar.getInstance();
1461         calendar.clear();
1462         calendar.setTimeInMillis(millis);
1463         int year = calendar.get(Calendar.YEAR);
1464         int month = calendar.get(Calendar.MONTH);
1465         int day = calendar.get(Calendar.DATE);
1466         int hour = calendar.get(Calendar.HOUR);
1467         int minute = calendar.get(Calendar.MINUTE);
1468         int second = calendar.get(Calendar.SECOND);
1469         if (month != 0) {
1470             throw new ClusterJUserException(
1471                     local.message("ERR_Write_Time_Domain", new java.sql.Time(millis), millis, year, month, day, hour, minute, second));
1472         }
1473         int time = ((day - 1) * 240000) + (hour * 10000) + (minute * 100) + second;
1474         return time;
1475     }
1476 
1477     /** Pack milliseconds since the Epoch into a long in database Datetime format.
1478      * The Datetime contains a eight-byte date and time packed as
1479      * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
1480      * Calendar month is 0 origin so add 1 to get packed month
1481      * @param value milliseconds since the Epoch
1482      * @return the long in packed Datetime format
1483      */
packDatetime(long value)1484     protected static long packDatetime(long value) {
1485         Calendar calendar = Calendar.getInstance();
1486         calendar.clear();
1487         calendar.setTimeInMillis(value);
1488         long year = calendar.get(Calendar.YEAR);
1489         long month = calendar.get(Calendar.MONTH) + 1;
1490         long day = calendar.get(Calendar.DATE);
1491         long hour = calendar.get(Calendar.HOUR);
1492         long minute = calendar.get(Calendar.MINUTE);
1493         long second = calendar.get(Calendar.SECOND);
1494         long packedDatetime = (year * 10000000000L) + (month * 100000000L) + (day * 1000000L)
1495                 + (hour * 10000L) + (minute * 100) + second;
1496         return packedDatetime;
1497     }
1498 
1499     /** Pack milliseconds since the Epoch into a long in database Datetime2 format.
1500      * Reference: http://dev.mysql.com/doc/internals/en/date-and-time-data-type-representation.html
1501      * The packed datetime2 integer part is:
1502      *  1 bit  sign (1= non-negative, 0= negative) [ALWAYS POSITIVE IN MYSQL 5.6]
1503      * 17 bits year*13+month (year 0-9999, month 1-12)
1504      *  5 bits day           (0-31)
1505      *  5 bits hour          (0-23)
1506      *  6 bits minute        (0-59)
1507      *  6 bits second        (0-59)
1508      *  ---------------------------
1509      * 40 bits = 5 bytes
1510      * Calendar month is 0 origin so add 1 to get packed month
1511      * @param millis milliseconds since the Epoch
1512      * @return the long in big endian packed Datetime2 format
1513      */
packDatetime2(int precision, long millis)1514     protected static long packDatetime2(int precision, long millis) {
1515         Calendar calendar = Calendar.getInstance();
1516         calendar.clear();
1517         calendar.setTimeInMillis(millis);
1518         long year = calendar.get(Calendar.YEAR);
1519         long month = calendar.get(Calendar.MONTH);
1520         long day = calendar.get(Calendar.DATE);
1521         long hour = calendar.get(Calendar.HOUR);
1522         long minute = calendar.get(Calendar.MINUTE);
1523         long second = calendar.get(Calendar.SECOND);
1524         long milliseconds = calendar.get(Calendar.MILLISECOND);
1525         long packedMillis = packFractionalSeconds(precision, milliseconds);
1526         long result =         0x8000000000000000L  // 1 bit sign
1527                 + (year     * 0x0003400000000000L) // 17 bits year * 13
1528                 + ((month+1)* 0x0000400000000000L) //         + month
1529                 + (day      * 0x0000020000000000L) // 5 bits day
1530                 + (hour     * 0x0000001000000000L) // 5 bits hour
1531                 + (minute   * 0x0000000040000000L) // 6 bits minute
1532                 + (second   * 0x0000000001000000L) // 6 bits second
1533                 + packedMillis;        // fractional microseconds
1534         return result;
1535     }
1536 
unpackDatetime2(int precision, long packedDatetime2)1537     protected static long unpackDatetime2(int precision, long packedDatetime2) {
1538         int yearMonth = (int)((packedDatetime2 & 0x7FFFC00000000000L) >>> 46); // 17 bits year * 13 + month
1539         int year = yearMonth / 13;
1540         int month= (yearMonth % 13) - 1; // calendar month is 0-11
1541         int day =       (int)((packedDatetime2 & 0x00003E0000000000L) >>> 41); // 5 bits day
1542         int hour =      (int)((packedDatetime2 & 0x000001F000000000L) >>> 36); // 5 bits hour
1543         int minute =    (int)((packedDatetime2 & 0x0000000FC0000000L) >>> 30); // 6 bits minute
1544         int second =    (int)((packedDatetime2 & 0x000000003F000000L) >>> 24); // 6 bits second
1545         int milliseconds = unpackFractionalSeconds(precision, (int)(packedDatetime2 & 0x0000000000FFFFFFL));
1546         Calendar calendar = Calendar.getInstance();
1547         calendar.clear();
1548         calendar.set(Calendar.YEAR, year);
1549         calendar.set(Calendar.MONTH, month);
1550         calendar.set(Calendar.DATE, day);
1551         calendar.set(Calendar.HOUR, hour);
1552         calendar.set(Calendar.MINUTE, minute);
1553         calendar.set(Calendar.SECOND, second);
1554         calendar.set(Calendar.MILLISECOND, milliseconds);
1555         return calendar.getTimeInMillis();
1556     }
1557 
1558     /** Convert milliseconds to packed time2 format
1559      * Fractional format
1560 
1561      * FSP      nBytes MaxValue MaxValueHex
1562      * ---      -----  -------- -----------
1563      * FSP=1    1byte  9                 9
1564      * FSP=2    1byte  99               63
1565 
1566      * FSP=3    2bytes 999             3E7
1567      * FSP=4    2bytes 9999           270F
1568 
1569      * FSP=5    3bytes 99999         1869F
1570      * FSP=6    3bytes 999999        F423F
1571 
1572      * @param precision number of digits of precision, 0 to 6
1573      * @param milliseconds
1574      * @return
1575      */
packFractionalSeconds(int precision, long milliseconds)1576     protected static long packFractionalSeconds(int precision, long milliseconds) {
1577         switch (precision) {
1578             case 0:
1579                 if (milliseconds > 0) throwOnTruncation();
1580                 return 0L;
1581             case 1: // possible truncation
1582                 if (milliseconds % 100 != 0) throwOnTruncation();
1583                 return (milliseconds / 100L) * 0x0000000000010000L;
1584             case 2: // possible truncation
1585                 if (milliseconds % 10 != 0) throwOnTruncation();
1586                 return (milliseconds / 10L)  * 0x0000000000010000L;
1587             case 3:
1588                 return milliseconds * 0x0000000000000100L;
1589             case 4:
1590                 return milliseconds * 0x0000000000000A00L; // milliseconds * 10
1591             case 5:
1592                 return milliseconds * 0x0000000000000064L; // milliseconds * 100
1593             case 6:
1594                 return milliseconds * 0x00000000000003E8L; // milliseconds * 1000
1595             default:
1596                 return 0L;
1597         }
1598     }
1599 
unpackFractionalSeconds(int precision, int fraction)1600     protected static int unpackFractionalSeconds(int precision, int fraction) {
1601         switch (precision) {
1602             case 0:
1603                 return 0;
1604             case 1:
1605                 return (fraction & 0x00FF0000) * 100;
1606             case 2:
1607                 return (fraction & 0x00FF0000) * 10;
1608             case 3:
1609                 return  fraction & 0x00FFFF00;
1610             case 4:
1611                 return (fraction & 0x00FFFF00) / 10;
1612             case 5:
1613                 return  fraction / 100;
1614             case 6:
1615                 return  fraction / 1000;
1616             default:
1617                 return 0;
1618         }
1619     }
1620 
1621     /** Throw an exception if truncation is not allowed
1622      * Not currently implemented.
1623      */
throwOnTruncation()1624     protected static void throwOnTruncation() {
1625     }
1626 
1627     /** Pack milliseconds since the Epoch into a long in database Time2 format.
1628      *       1 bit sign (1= non-negative, 0= negative)
1629      *       1 bit unused (reserved for INTERVAL type)
1630      *      10 bits hour   (0-838)
1631      *       6 bits minute (0-59)
1632      *       6 bits second (0-59)
1633      *       --------------------
1634      *       24 bits = 3 bytes
1635 
1636      * @param millis milliseconds since the Epoch
1637      * @return the long in big endian packed Time format
1638      */
packTime2(int precision, long millis)1639     protected static long packTime2(int precision, long millis) {
1640         Calendar calendar = Calendar.getInstance();
1641         calendar.clear();
1642         calendar.setTimeInMillis(millis);
1643         long hour = calendar.get(Calendar.HOUR);
1644         long minute = calendar.get(Calendar.MINUTE);
1645         long second = calendar.get(Calendar.SECOND);
1646         long milliseconds = calendar.get(Calendar.MILLISECOND);
1647         long packedMillis = packFractionalSeconds(precision, milliseconds);
1648         long result =     0x8000000000000000L |
1649                 (hour   * 0x0010000000000000L) |
1650                 (minute * 0x0000400000000000L) |
1651                 (second * 0x0000010000000000L) |
1652                 packedMillis << 16;
1653         return result;
1654     }
1655 
unpackTime2(int precision, long value)1656     protected static long unpackTime2(int precision, long value) {
1657         int hour =     (int)((value & 0x3FF0000000000000L) >>> 52);
1658         int minute =   (int)((value & 0x000FC00000000000L) >>> 46);
1659         int second =   (int)((value & 0x00003F0000000000L) >>> 40);
1660         int fraction = (int)((value & 0x000000FFFFFF0000L) >>> 16);
1661 
1662         int milliseconds = unpackFractionalSeconds(precision, fraction);
1663         Calendar calendar = Calendar.getInstance();
1664         calendar.clear();
1665         calendar.set(Calendar.HOUR, hour);
1666         calendar.set(Calendar.MINUTE, minute);
1667         calendar.set(Calendar.SECOND, second);
1668         calendar.set(Calendar.MILLISECOND, milliseconds);
1669         return calendar.getTimeInMillis();
1670     }
1671 
1672     /** Pack milliseconds since the Epoch into a long in database Timestamp2 format.
1673      * First four bytes are Unix time format: seconds since the epoch;
1674      * Fractional part is 0-3 bytes
1675      * @param millis milliseconds since the Epoch
1676      * @return the long in big endian packed Timestamp2 format
1677      */
packTimestamp2(int precision, long millis)1678     protected static long packTimestamp2(int precision, long millis) {
1679         long milliseconds = millis % 1000; // extract milliseconds
1680         long seconds = millis/1000; // truncate to seconds
1681         long packedMillis = packFractionalSeconds(precision, milliseconds);
1682         long result = (seconds << 32) +
1683                 (packedMillis << 8);
1684         return result;
1685     }
1686 
unpackTimestamp2(int precision, long value)1687     protected static long unpackTimestamp2(int precision, long value) {
1688         long result = ((value >>> 32) * 1000) +
1689                 (unpackFractionalSeconds(precision, (int)value) >>> 8) ;
1690         return result;
1691     }
1692 
1693     /** Convert the byte[] into a String to be used for logging and debugging.
1694      *
1695      * @param bytes the byte[] to be dumped
1696      * @return the String representation
1697      */
dumpBytes(byte[] bytes)1698     public static String dumpBytes (byte[] bytes) {
1699         StringBuffer buffer = new StringBuffer("byte[");
1700         buffer.append(bytes.length);
1701         buffer.append("]: [");
1702         for (int i = 0; i < bytes.length; ++i) {
1703             buffer.append((int)bytes[i]);
1704             buffer.append(" ");
1705         }
1706         buffer.append("]");
1707         return buffer.toString();
1708     }
1709 
1710     /** Convert the byteBuffer into a String to be used for logging and debugging.
1711      *
1712      * @param byteBuffer the byteBuffer to be dumped
1713      * @return the String representation
1714      */
dumpBytes(ByteBuffer byteBuffer)1715     public static String dumpBytes(ByteBuffer byteBuffer) {
1716         byteBuffer.mark();
1717         int length = byteBuffer.limit() - byteBuffer.position();
1718         byte[] dst = new byte[length];
1719         byteBuffer.get(dst);
1720         byteBuffer.reset();
1721         return dumpBytes(dst);
1722     }
1723 
dumpBytesToLog(ByteBuffer byteBuffer, int limit)1724     private static void dumpBytesToLog(ByteBuffer byteBuffer, int limit) {
1725         StringBuffer message = new StringBuffer("String position is: ");
1726         message.append(byteBuffer.position());
1727         message.append(" limit: ");
1728         message.append(byteBuffer.limit());
1729         message.append(" data [");
1730         while (byteBuffer.hasRemaining()) {
1731             message.append((int)byteBuffer.get());
1732             message.append(" ");
1733         }
1734         message.append("]");
1735         logger.detail(message.toString());
1736         byteBuffer.position(0);
1737         byteBuffer.limit(limit);
1738     }
1739 
getDecimal(ByteBuffer byteBuffer, int length, int precision, int scale)1740     public static BigDecimal getDecimal(ByteBuffer byteBuffer, int length, int precision, int scale) {
1741         String decimal = null;
1742         try {
1743             decimal = getDecimalString(byteBuffer, length, precision, scale);
1744             return new BigDecimal(decimal);
1745         } catch (NumberFormatException nfe) {
1746             throw new ClusterJUserException(
1747                     local.message("ERR_Number_Format", decimal, dump(decimal)));
1748         }
1749     }
1750 
getBigInteger(ByteBuffer byteBuffer, int length, int precision, int scale)1751     public static BigInteger getBigInteger(ByteBuffer byteBuffer, int length, int precision, int scale) {
1752         String decimal = null;
1753         try {
1754             decimal = getDecimalString(byteBuffer, length, precision, scale);
1755             return new BigInteger(decimal);
1756         } catch (NumberFormatException nfe) {
1757             throw new ClusterJUserException(
1758                     local.message("ERR_Number_Format", decimal, dump(decimal)));
1759         }
1760     }
1761 
1762     /** Get a Decimal String from the byte buffer.
1763      *
1764      * @param byteBuffer the byte buffer with the raw data, starting at position()
1765      * @param length the length of the data
1766      * @param precision the precision of the data
1767      * @param scale the scale of the data
1768      * @return the Decimal String representation of the value
1769      */
getDecimalString(ByteBuffer byteBuffer, int length, int precision, int scale)1770     public static String getDecimalString(ByteBuffer byteBuffer, int length, int precision, int scale) {
1771         // allow for decimal point and sign and one more for trailing null
1772         int capacity = precision + 3;
1773         ByteBuffer digits = decimalByteBufferPool.borrowBuffer();
1774         int returnCode = Utils.decimal_bin2str(byteBuffer, length, precision, scale, digits, capacity);
1775         if (returnCode != 0) {
1776             decimalByteBufferPool.returnBuffer(digits);
1777             throw new ClusterJUserException(
1778                     local.message("ERR_Binary_Decimal_To_String",
1779                     returnCode, precision, scale, dumpBytes(byteBuffer)));
1780         }
1781         String string = null;
1782         // look for the end (null) of the result string
1783         for (int i = 0; i < digits.limit(); ++i) {
1784             if (digits.get(i) == 0) {
1785                 // found the end; mark it so we only decode the answer characters
1786                 digits.limit(i);
1787                 break;
1788             }
1789         }
1790         try {
1791             // use basic decoding
1792             CharBuffer charBuffer;
1793             charBuffer = charset.decode(digits);
1794             string = charBuffer.toString();
1795             return string;
1796         } finally {
1797             decimalByteBufferPool.returnBuffer(digits);
1798         }
1799 
1800     }
1801 
1802     /** Unpack a Date from its packed int representation.
1803      * Date is a three-byte integer packed as YYYYx16x32 + MMx32 + DD
1804      * @param packedDate the packed representation
1805      * @return the long value as milliseconds since the Epoch
1806      */
unpackDate(int packedDate)1807     public static long unpackDate(int packedDate) {
1808         int date = packedDate & 0x1f;
1809         packedDate = packedDate >>> 5;
1810         int month = (packedDate & 0x0f) - 1; // Month value is 0-based. e.g., 0 for January.
1811         int year = packedDate >>> 4;
1812         Calendar calendar = Calendar.getInstance();
1813         calendar.clear();
1814         calendar.set(year, month, date);
1815         return calendar.getTimeInMillis();
1816     }
1817 
1818     /** Unpack a Time from its packed int representation.
1819      * Time is a three-byte integer packed as DDx240000 + HHx10000 + MMx100 + SS
1820      * @param packedTime the packed representation
1821      * @return the long value as milliseconds since the Epoch
1822      */
unpackTime(int packedTime)1823     public static long unpackTime(int packedTime) {
1824         int second = packedTime % 100;
1825         packedTime /= 100;
1826         int minute = packedTime % 100;
1827         packedTime /= 100;
1828         int hour = packedTime % 24;
1829         int date = (packedTime / 24) + 1;
1830         if (date > 31) {
1831             throw new ClusterJUserException(
1832                     local.message("ERR_Read_Time_Domain", packedTime, date, hour, minute, second));
1833         }
1834         Calendar calendar = Calendar.getInstance();
1835         calendar.clear();
1836         calendar.set(Calendar.DATE, date);
1837         calendar.set(Calendar.HOUR, hour);
1838         calendar.set(Calendar.MINUTE, minute);
1839         calendar.set(Calendar.SECOND, second);
1840         calendar.set(Calendar.MILLISECOND, 0);
1841         return calendar.getTimeInMillis();
1842     }
1843 
1844     /** Unpack a Datetime from its packed long representation.
1845      * The Datetime contains a long packed as
1846      * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
1847      * Calendar month is 0 origin so subtract 1 from packed month
1848      * @param packedDatetime the packed representation
1849      * @return the value as milliseconds since the Epoch
1850      */
unpackDatetime(long packedDatetime)1851     protected static long unpackDatetime(long packedDatetime) {
1852         int second = (int)(packedDatetime % 100);
1853         packedDatetime /= 100;
1854         int minute = (int)(packedDatetime % 100);
1855         packedDatetime /= 100;
1856         int hour = (int)(packedDatetime % 100);
1857         packedDatetime /= 100;
1858         int day = (int)(packedDatetime % 100);
1859         packedDatetime /= 100;
1860         int month = (int)(packedDatetime % 100) - 1;
1861         int year = (int)(packedDatetime / 100);
1862         Calendar calendar = Calendar.getInstance();
1863         calendar.clear();
1864         calendar.set(Calendar.YEAR, year);
1865         calendar.set(Calendar.MONTH, month);
1866         calendar.set(Calendar.DATE, day);
1867         calendar.set(Calendar.HOUR, hour);
1868         calendar.set(Calendar.MINUTE, minute);
1869         calendar.set(Calendar.SECOND, second);
1870         calendar.set(Calendar.MILLISECOND, 0);
1871         return calendar.getTimeInMillis();
1872 
1873     }
1874 
1875     /** Decode a byte[] into a String using the charset. The return value
1876      * is in UTF16 format.
1877      *
1878      * @param array the byte[] to be decoded
1879      * @param collation the collation
1880      * @return the decoded String
1881      */
decode(byte[] array, int collation)1882     public static String decode(byte[] array, int collation) {
1883         if (array == null) return null;
1884         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
1885         byteBuffer.put(array);
1886         byteBuffer.flip();
1887         int inputLength = array.length;
1888         // TODO make this more reasonable
1889         int outputLength = inputLength * 4;
1890         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1891         int[] lengths = new int[] {inputLength, outputLength};
1892         int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
1893                 byteBuffer, outputByteBuffer);
1894         switch (returnCode) {
1895             case CharsetMapConst.RecodeStatus.RECODE_OK:
1896                 outputByteBuffer.limit(lengths[1]);
1897                 CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
1898                 return charBuffer.toString();
1899             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1900                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
1901                         collation));
1902             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1903                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
1904                         collation, lengths[0]));
1905             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1906                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
1907                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1908             default:
1909                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
1910                         returnCode));
1911         }
1912     }
1913 
1914     /** Decode a ByteBuffer into a String using the charset. The return value
1915      * is in UTF16 format.
1916      *
1917      * @param inputByteBuffer the byte buffer to be decoded
1918      * @param collation the collation
1919      * @return the decoded String
1920      */
decode(ByteBuffer inputByteBuffer, int collation)1921     protected static String decode(ByteBuffer inputByteBuffer, int collation) {
1922         int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
1923         int outputLength = inputLength * 2;
1924         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1925         int[] lengths = new int[] {inputLength, outputLength};
1926         int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
1927                 inputByteBuffer, outputByteBuffer);
1928         switch (returnCode) {
1929             case CharsetMapConst.RecodeStatus.RECODE_OK:
1930                 outputByteBuffer.limit(lengths[1]);
1931                 CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
1932                 return charBuffer.toString();
1933             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1934                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
1935                         collation));
1936             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1937                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
1938                         collation, lengths[0]));
1939             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1940                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
1941                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1942             default:
1943                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
1944                         returnCode));
1945         }
1946     }
1947 
1948 
1949     /** Encode a String into a byte[] for storage.
1950      * This is used by character large objects when mapping text columns.
1951      *
1952      * @param string the String to encode
1953      * @param collation the collation
1954      * @return the encoded byte[]
1955      */
encode(String string, int collation)1956     public static byte[] encode(String string, int collation) {
1957         ByteBuffer encoded = encodeToByteBuffer(string, collation, 0);
1958         int length = encoded.limit();
1959         byte[] result = new byte[length];
1960         encoded.get(result);
1961         return result;
1962     }
1963 
1964     /** Encode a String into a ByteBuffer
1965      * using the mysql native encoding method.
1966      * @param string the String to encode
1967      * @param collation the collation
1968      * @param prefixLength the length of the length prefix
1969      * @return the encoded ByteBuffer with position set to prefixLength
1970      * and limit one past the last converted byte
1971      */
encodeToByteBuffer(CharSequence string, int collation, int prefixLength)1972     public static ByteBuffer encodeToByteBuffer(CharSequence string, int collation, int prefixLength) {
1973         if (string == null) return null;
1974         int inputLength = (string.length() * 2);
1975         ByteBuffer inputByteBuffer = ByteBuffer.allocateDirect(inputLength);
1976         CharBuffer charBuffer = inputByteBuffer.asCharBuffer();
1977         charBuffer.append(string);
1978         int outputLength = (2 * inputLength) + prefixLength;
1979         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1980         outputByteBuffer.position(prefixLength);
1981         int[] lengths = new int[] {inputLength, outputLength - prefixLength};
1982         int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
1983                 inputByteBuffer, outputByteBuffer);
1984 
1985         switch (returnCode) {
1986             case CharsetMapConst.RecodeStatus.RECODE_OK:
1987                 outputByteBuffer.limit(prefixLength + lengths[1]);
1988                 return outputByteBuffer;
1989             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1990                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
1991                         collation));
1992             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1993                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
1994                         collation, lengths[0]));
1995             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1996                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Buffer_Too_Small",
1997                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1998             default:
1999                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
2000                         returnCode));
2001         }
2002     }
2003 
2004     /** Encode a String into a ByteBuffer for storage.
2005      *
2006      * @param input the input String
2007      * @param storeColumn the store column
2008      * @param bufferManager the buffer manager with shared buffers
2009      * @return a byte buffer with prefix length
2010      */
encode(String input, Column storeColumn, BufferManager bufferManager)2011     public static ByteBuffer encode(String input, Column storeColumn, BufferManager bufferManager) {
2012         int collation = storeColumn.getCharsetNumber();
2013 //        System.out.println("Utility.encode storeColumn: " + storeColumn.getName() +
2014 //                " charsetName " + storeColumn.getCharsetName() +
2015 //                " charsetNumber " + collation +
2016 //                " input '" + input + "'");
2017         CharsetConverter charsetConverter = getCharsetConverter(collation);
2018         CharSequence chars = input;
2019         int prefixLength = storeColumn.getPrefixLength();
2020         ByteBuffer byteBuffer = charsetConverter.encode(storeColumn.getName(), chars, collation, prefixLength, bufferManager);
2021         if (prefixLength == 0) {
2022             padString(byteBuffer, storeColumn);
2023         }
2024         return byteBuffer;
2025     }
2026 
2027     /** Decode a ByteBuffer into a String using the charset. The return value
2028      * is in UTF16 format.
2029      *
2030      * @param inputByteBuffer the byte buffer to be decoded positioned past the length prefix
2031      * @param collation the collation
2032      * @param bufferManager the buffer manager with shared buffers
2033      * @return the decoded String
2034      */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)2035     public static String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
2036         CharsetConverter charsetConverter = getCharsetConverter(collation);
2037         return charsetConverter.decode(inputByteBuffer, collation, bufferManager);
2038     }
2039 
2040     /** Get the charset converter for the given collation.
2041      * This is in the inner loop and must be highly optimized for performance.
2042      * @param collation the collation
2043      * @return the charset converter for the collation
2044      */
getCharsetConverter(int collation)2045     private static CharsetConverter getCharsetConverter(int collation) {
2046         // must be synchronized because the charsetConverters is not synchronized
2047         // we avoid a race condition where a charset converter is in the process
2048         // of being created and it's partially visible by another thread in charsetConverters
2049         synchronized (charsetConverters) {
2050             if (collation + 1 > charsetConverters.length) {
2051                 // unlikely; only if collations are added beyond existing collation number
2052                 String charsetName = charsetMap.getName(collation);
2053                 logger.warn(local.message("ERR_Charset_Number_Too_Big", collation, charsetName,
2054                         MAXIMUM_MYSQL_COLLATION_NUMBER));
2055                 return charsetConverterMultibyte;
2056             }
2057             CharsetConverter result = charsetConverters[collation];
2058             if (result == null) {
2059                 result = addCollation(collation);
2060             }
2061             return result;
2062         }
2063     }
2064 
2065     /** Create a new charset converter and add it to the collection of charset converters
2066      * for all collations that share the same charset.
2067      *
2068      * @param collation the collation to add
2069      * @return the charset converter for the collation
2070      */
addCollation(int collation)2071     private static CharsetConverter addCollation(int collation) {
2072         if (isMultibyteCollation(collation)) {
2073             return charsetConverters[collation] = charsetConverterMultibyte;
2074         }
2075         String charsetName = charsetMap.getMysqlName(collation);
2076         CharsetConverter charsetConverter = new SingleByteCharsetConverter(collation);
2077         int[] collations = collationPeersMap.get(charsetName);
2078         if (collations == null) {
2079             // unlikely; only if a new collation is added
2080             collations = new int[] {collation};
2081             collationPeersMap.put(charsetName, collations);
2082             logger.warn(local.message("WARN_Unknown_Collation", collation, charsetName));
2083             return charsetConverter;
2084         }
2085         for (int peer: collations) {
2086             // for each collation that shares the same charset name, set the charset converter
2087             logger.info("Adding charset converter " + charsetName + " for collation " + peer);
2088             charsetConverters[peer] = charsetConverter;
2089         }
2090         return charsetConverter;
2091     }
2092 
2093     /** Is the collation multibyte?
2094      *
2095      * @param collation the collation number
2096      * @return true if the collation uses a multibyte charset; false if the collation uses a single byte charset;
2097      * and null if the collation is not a valid collation
2098      */
isMultibyteCollation(int collation)2099     private static Boolean isMultibyteCollation(int collation) {
2100         boolean[] multibyte = charsetMap.isMultibyte(collation);
2101         return (multibyte == null)?null:multibyte[0];
2102     }
2103 
2104     /** Utility methods for encoding and decoding Strings.
2105      */
2106     protected interface CharsetConverter {
2107 
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)2108         ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager);
2109 
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)2110         String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager);
2111     }
2112 
2113     /** Class for encoding and decoding multibyte charsets. A single instance of this class
2114      * can be shared among all multibyte charsets.
2115      */
2116     protected static class MultiByteCharsetConverter implements CharsetConverter {
2117 
2118         /** Encode a String into a ByteBuffer. The input String is copied into a shared byte buffer.
2119          * The buffer is encoded via the mysql recode method to a shared String storage buffer.
2120          * If the output buffer is too small, a new buffer is allocated and the encoding is repeated.
2121          * @param input the input String
2122          * @param collation the charset number
2123          * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
2124          * @param bufferManager the buffer manager with shared buffers
2125          * @return a byte buffer positioned at zero with the data ready to send to the database
2126          */
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)2127         public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
2128             // input length in bytes is twice String length
2129             int inputLength = input.length() * 2;
2130             ByteBuffer inputByteBuffer = bufferManager.copyStringToByteBuffer(input);
2131             boolean done = false;
2132             // first try with output length equal input length
2133             int sizeNeeded = inputLength;
2134             while (!done) {
2135                 ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(sizeNeeded);
2136                 int outputLength = outputByteBuffer.limit();
2137                 outputByteBuffer.position(prefixLength);
2138                 int[] lengths = new int[] {inputLength, outputLength - prefixLength};
2139                 int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
2140                         inputByteBuffer, outputByteBuffer);
2141                 switch (returnCode) {
2142                     case CharsetMapConst.RecodeStatus.RECODE_OK:
2143                         outputByteBuffer.limit(prefixLength + lengths[1]);
2144                         outputByteBuffer.position(0);
2145                         fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
2146                         return outputByteBuffer;
2147                     case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
2148                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
2149                                 collation));
2150                     case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
2151                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
2152                                 collation, lengths[0]));
2153                     case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
2154                         // loop increasing output buffer size until success or run out of memory...
2155                         sizeNeeded = sizeNeeded * 3 / 2;
2156                         break;
2157                     default:
2158                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
2159                                 returnCode));
2160                 }
2161             }
2162             return null; // to make compiler happy; we never get here
2163         }
2164 
2165         /** Decode a byte buffer into a String. The input is decoded by the mysql charset recode method
2166          * into a shared buffer. Then the shared buffer is used to create the result String.
2167          * The input byte buffer is positioned just past the length, and its limit is set to one past the
2168          * characters to decode.
2169          * @param inputByteBuffer the input byte buffer
2170          * @param collation the charset number
2171          * @param bufferManager the buffer manager with shared buffers
2172          * @return the decoded String
2173          */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)2174         public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
2175             int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
2176             int sizeNeeded = inputLength * 4;
2177             boolean done = false;
2178             while (!done) {
2179                 ByteBuffer outputByteBuffer = bufferManager.getStringByteBuffer(sizeNeeded);
2180                 CharBuffer outputCharBuffer = bufferManager.getStringCharBuffer();
2181                 int outputLength = outputByteBuffer.capacity();
2182                 outputByteBuffer.position(0);
2183                 outputByteBuffer.limit(outputLength);
2184                 int[] lengths = new int[] {inputLength, outputLength};
2185                 int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
2186                         inputByteBuffer, outputByteBuffer);
2187                 switch (returnCode) {
2188                     case CharsetMapConst.RecodeStatus.RECODE_OK:
2189                         outputCharBuffer.position(0);
2190                         // each output character is two bytes for UTF16
2191                         outputCharBuffer.limit(lengths[1] / 2);
2192                         return outputCharBuffer.toString();
2193                     case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
2194                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
2195                                 collation));
2196                     case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
2197                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
2198                                 collation, lengths[0]));
2199                     case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
2200                         // try a bigger buffer
2201                         sizeNeeded = sizeNeeded * 3 / 2;
2202                         break;
2203                     default:
2204                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
2205                                 returnCode));
2206                 }
2207             }
2208             return null; // never reached; make the compiler happy
2209         }
2210     }
2211 
2212     /** Class for encoding and decoding single byte collations.
2213      */
2214     protected static class SingleByteCharsetConverter implements CharsetConverter {
2215 
2216         private static final int BYTE_RANGE = (1 + Byte.MAX_VALUE) - Byte.MIN_VALUE;
2217         private static byte[] allBytes = new byte[BYTE_RANGE];
2218         // The initial charToByteMap, with all char mappings mapped
2219         // to (byte) '?', so that unknown characters are mapped to '?'
2220         // instead of '\0' (which means end-of-string to MySQL).
2221         private static byte[] unknownCharsMap = new byte[65536];
2222 
2223         static {
2224             // initialize allBytes with all possible byte values
2225             for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
2226                 allBytes[i - Byte.MIN_VALUE] = (byte) i;
2227             }
2228             // initialize unknownCharsMap to '?' in each position
2229             for (int i = 0; i < unknownCharsMap.length; i++) {
2230                 unknownCharsMap[i] = (byte) '?'; // use something 'sane' for unknown chars
2231             }
2232         }
2233 
2234         /** The byte to char array */
2235         private char[] byteToChars = new char[BYTE_RANGE];
2236 
2237         /** The char to byte array */
2238         private byte[] charToBytes = new byte[65536];
2239 
2240         /** Construct a new single byte charset converter. This converter is only used for
2241          * charsets that encode to a single byte for any input character.
2242          * @param collation
2243          */
SingleByteCharsetConverter(int collation)2244         public SingleByteCharsetConverter(int collation) {
2245             ByteBuffer allBytesByteBuffer = ByteBuffer.allocateDirect(256);
2246             allBytesByteBuffer.put(allBytes);
2247             allBytesByteBuffer.flip();
2248             String allBytesString = Utility.decode(allBytesByteBuffer, collation);
2249             if (allBytesString.length() != 256) {
2250                 String charsetName = charsetMap.getName(collation);
2251                 throw new ClusterJFatalInternalException(local.message("ERR_Bad_Charset_Decode_All_Chars",
2252                         collation, charsetName, allBytesString.length()));
2253             }
2254             int allBytesLen = allBytesString.length();
2255 
2256             System.arraycopy(unknownCharsMap, 0, this.charToBytes, 0,
2257                     this.charToBytes.length);
2258 
2259             for (int i = 0; i < BYTE_RANGE && i < allBytesLen; i++) {
2260                 char c = allBytesString.charAt(i);
2261                 this.byteToChars[i] = c;
2262                 this.charToBytes[c] = allBytes[i];
2263             }
2264         }
2265 
2266         /** Encode a String into a ByteBuffer. The input String is encoded, character by character,
2267          * into an output byte[]. Then the output is copied into a shared byte buffer.
2268          * @param input the input String
2269          * @param collation the charset number
2270          * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
2271          * @param bufferManager the buffer manager with shared buffers
2272          * @return a byte buffer positioned at zero with the data ready to send to the database
2273          */
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)2274         public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
2275             int length = input.length();
2276             byte[] outputBytes = new byte[length];
2277             for (int position = 0; position < length; ++position) {
2278                 outputBytes[position] = charToBytes[input.charAt(position)];
2279             }
2280             // input is now encoded; copy to shared output buffer
2281             ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(length + prefixLength);
2282             // skip over prefix
2283             outputByteBuffer.position(prefixLength);
2284             outputByteBuffer.put(outputBytes);
2285             outputByteBuffer.flip();
2286             // adjust the length prefix
2287             fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
2288             return outputByteBuffer;
2289         }
2290 
2291         /** Decode a byte buffer into a String. The input byte buffer is copied into a byte[],
2292          * then encoded byte by byte into an output char[]. Then the result String is created from the char[].
2293          * The input byte buffer is positioned just past the length, and its limit is set to one past the
2294          * characters to decode.
2295          * @param inputByteBuffer the input byte buffer
2296          * @param collation the charset number
2297          * @param bufferManager the buffer manager with shared buffers
2298          * @return the decoded String
2299          */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)2300         public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
2301             int inputLimit = inputByteBuffer.limit();
2302             int inputPosition = inputByteBuffer.position();
2303             int inputSize = inputLimit- inputPosition;
2304             byte[] inputBytes = new byte[inputSize];
2305             inputByteBuffer.get(inputBytes);
2306             char[] outputChars = new char[inputSize];
2307             for (int position = 0; position < inputSize; ++position) {
2308                 outputChars[position] = byteToChars[inputBytes[position] - Byte.MIN_VALUE];
2309             }
2310             // input is now decoded; create a new String from the output
2311             String result = new String(outputChars);
2312             return result;
2313         }
2314     }
2315 
dump(String string)2316     private static String dump(String string) {
2317         StringBuffer buffer = new StringBuffer("[");
2318         for (int i = 0; i < string.length(); ++i) {
2319             int theCharacter = string.charAt(i);
2320             buffer.append(theCharacter);
2321             buffer.append(" ");
2322         }
2323         buffer.append("]");
2324         return buffer.toString();
2325     }
2326 
2327     /** For each group of 9 decimal digits, the number of bytes needed
2328      * to represent that group of digits:
2329      * 10, 100 -> 1; 256
2330      * 1,000, 10,000 -> 2; 65536
2331      * 100,000, 1,000,000 -> 3 16,777,216
2332      * 10,000,000, 100,000,000, 1,000,000,000 -> 4
2333      */
2334     static int[] howManyBytesNeeded = new int[] {0,  1,  1,  2,  2,  3,  3,  4,  4,  4,
2335                                                      5,  5,  6,  6,  7,  7,  8,  8,  8,
2336                                                      9,  9, 10, 10, 11, 11, 12, 12, 12,
2337                                                     13, 13, 14, 14, 15, 15, 16, 16, 16,
2338                                                     17, 17, 18, 18, 19, 19, 20, 20, 20,
2339                                                     21, 21, 22, 22, 23, 23, 24, 24, 24,
2340                                                     25, 25, 26, 26, 27, 27, 28, 28, 28,
2341                                                     29, 29};
2342     /** Get the number of bytes needed in memory to represent the decimal number.
2343      *
2344      * @param precision the precision of the number
2345      * @param scale the scale
2346      * @return the number of bytes needed for the binary representation of the number
2347      */
getDecimalColumnSpace(int precision, int scale)2348     public static int getDecimalColumnSpace(int precision, int scale) {
2349         int howManyBytesNeededForIntegral = howManyBytesNeeded[precision - scale];
2350         int howManyBytesNeededForFraction = howManyBytesNeeded[scale];
2351         int result = howManyBytesNeededForIntegral + howManyBytesNeededForFraction;
2352         return result;
2353     }
2354 
2355     /** Get a boolean from this ndbRecAttr.
2356      *
2357      * @param storeColumn the Column
2358      * @param ndbRecAttr the NdbRecAttr
2359      * @return the boolean
2360      */
getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr)2361     public static boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
2362         return endianManager.getBoolean(storeColumn, ndbRecAttr);
2363     }
2364 
getBoolean(Column storeColumn, int value)2365     public static boolean getBoolean(Column storeColumn, int value) {
2366         return endianManager.getBoolean(storeColumn, value);
2367     }
2368 
2369     /** Get a byte from this ndbRecAttr.
2370      *
2371      * @param storeColumn the Column
2372      * @param ndbRecAttr the NdbRecAttr
2373      * @return the byte
2374      */
getByte(Column storeColumn, NdbRecAttr ndbRecAttr)2375     public static byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
2376         return endianManager.getByte(storeColumn, ndbRecAttr);
2377     }
2378 
2379     /** Get a short from this ndbRecAttr.
2380      *
2381      * @param storeColumn the Column
2382      * @param ndbRecAttr the NdbRecAttr
2383      * @return the short
2384      */
getShort(Column storeColumn, NdbRecAttr ndbRecAttr)2385     public static short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
2386         return endianManager.getShort(storeColumn, ndbRecAttr);
2387     }
2388 
2389     /** Get an int from this ndbRecAttr.
2390      *
2391      * @param storeColumn the Column
2392      * @param ndbRecAttr the NdbRecAttr
2393      * @return the int
2394      */
getInt(Column storeColumn, NdbRecAttr ndbRecAttr)2395     public static int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
2396         return endianManager.getInt(storeColumn, ndbRecAttr);
2397     }
2398 
getInt(Column storeColumn, int value)2399     public static int getInt(Column storeColumn, int value) {
2400         return endianManager.getInt(storeColumn, value);
2401     }
2402 
2403     /** Get a long from this ndbRecAttr.
2404      *
2405      * @param storeColumn the Column
2406      * @param ndbRecAttr the NdbRecAttr
2407      * @return the long
2408      */
2409 
2410     /** Convert a long value from storage.
2411      * The value stored in the database might be a time, timestamp, date, bit array,
2412      * or simply a long value. The converted value can be converted into a
2413      * time, timestamp, date, bit array, or long value.
2414      */
getLong(Column storeColumn, long value)2415     public static long getLong(Column storeColumn, long value) {
2416         return endianManager.getLong(storeColumn, value);
2417     }
2418 
2419     /** Convert a long value into a long for storage. The value parameter
2420      * may be a date (milliseconds since the epoch), a bit array, or simply a long value.
2421      * The storage format depends on the type of the column and the endian-ness of
2422      * the host.
2423      * @param storeColumn the column
2424      * @param value the java value
2425      * @return the storage value
2426      */
convertLongValueForStorage(Column storeColumn, long value)2427     public static long convertLongValueForStorage(Column storeColumn, long value) {
2428         return endianManager.convertLongValueForStorage(storeColumn, value);
2429     }
2430 
2431     /** Convert a byte value into an int for storage. The value parameter
2432      * may be a bit, a bit array (BIT(1..8) needs to be stored as an int) or a byte value.
2433      * The storage format depends on the type of the column and the endian-ness of
2434      * the host.
2435      * @param storeColumn the column
2436      * @param value the java value
2437      * @return the storage value
2438      */
convertByteValueForStorage(Column storeColumn, byte value)2439     public static int convertByteValueForStorage(Column storeColumn, byte value) {
2440         return endianManager.convertByteValueForStorage(storeColumn, value);
2441     }
2442 
2443     /** Convert a short value into an int for storage. The value parameter
2444      * may be a bit array (BIT(1..16) needs to be stored as an int) or a short value.
2445      * The storage format depends on the type of the column and the endian-ness of
2446      * the host.
2447      * @param storeColumn the column
2448      * @param value the java value
2449      * @return the storage value
2450      */
convertShortValueForStorage(Column storeColumn, short value)2451     public static int convertShortValueForStorage(Column storeColumn,
2452             short value) {
2453         return endianManager.convertShortValueForStorage(storeColumn, value);
2454     }
2455 
convertIntValueForStorage(Column storeColumn, int value)2456     public static int convertIntValueForStorage(Column storeColumn, int value) {
2457         return endianManager.convertIntValueForStorage(storeColumn, value);
2458     }
2459 
2460 }
2461