1 /*
2  *  Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
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 encoder */
75     static CharsetEncoder charsetEncoder = Charset.forName("windows-1252").newEncoder();
76 
77     /** Standard Java charset decoder */
78     static CharsetDecoder charsetDecoder = Charset.forName("windows-1252").newDecoder();
79 
80     static final long ooooooooooooooff = 0x00000000000000ffL;
81     static final long ooooooooooooffoo = 0x000000000000ff00L;
82     static final long ooooooooooffoooo = 0x0000000000ff0000L;
83     static final long ooooooooffoooooo = 0x00000000ff000000L;
84     static final long ooooooffoooooooo = 0x000000ff00000000L;
85     static final long ooooffoooooooooo = 0x0000ff0000000000L;
86     static final long ooffoooooooooooo = 0x00ff000000000000L;
87     static final long ffoooooooooooooo = 0xff00000000000000L;
88     static final long ooooooooffffffff = 0x00000000ffffffffL;
89     static final int ooooooff = 0x000000ff;
90     static final int ooooffff = 0x0000ffff;
91     static final int ooooffoo = 0x0000ff00;
92     static final int ooffoooo = 0x00ff0000;
93 
94     static final char[] SPACE_PAD = new char[255];
95     static {
96         for (int i = 0; i < 255; ++i) {
97             SPACE_PAD[i] = ' ';
98         }
99     }
100 
101     static final byte[] ZERO_PAD = new byte[255];
102     static {
103         for (int i = 0; i < 255; ++i) {
104             ZERO_PAD[i] = (byte)0;
105         }
106     }
107 
108     static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
109 
110     // TODO: this is intended to investigate a class loader issue with Sparc java
111     // The idea is to force loading the CharsetMap native class prior to calling the static create method
112     static Class<?> charsetMapClass = loadClass("com.mysql.ndbjtie.mysql.CharsetMap");
loadClass(String className)113     static Class<?> loadClass(String className) {
114         try {
115             return Class.forName(className);
116         } catch (ClassNotFoundException e) {
117             // TODO Auto-generated catch block
118             throw new ClusterJUserException(local.message("ERR_Loading_Native_Class", className), e);
119         }
120     }
121 
122     // TODO: change this to a weak reference so we can call delete on it when not needed
123     /** Note that mysql refers to charset number and charset name, but the number is
124     * actually a collation number. The CharsetMap interface thus has methods like
125     * getCharsetNumber(String charsetName) but what is returned is actually a collation number.
126     */
127     static CharsetMap charsetMap = createCharsetMap();
128 
129     // TODO: this is intended to investigate a class loader issue with Sparc java
130     // The idea is to create the CharsetMap create method in a try/catch block to report the exact error
createCharsetMap()131     static CharsetMap createCharsetMap() {
132         StringBuilder builder = new StringBuilder();
133         CharsetMap result = null;
134         try {
135             return CharsetMap.create();
136         } catch (Throwable t1) {
137             builder.append("CharsetMap.create() threw " + t1.getClass().getName() + ":" + t1.getMessage());
138             try {
139                 Method charsetMapCreateMethod = charsetMapClass.getMethod("create", (Class[])null);
140                 result = (CharsetMap)charsetMapCreateMethod.invoke(null, (Object[])null);
141                 builder.append("charsetMapCreateMethod.invoke() succeeded:" + result);
142             } catch (Throwable t2) {
143                 builder.append("charsetMapCreateMethod.invoke() threw " + t2.getClass().getName() + ":" + t2.getMessage());
144             }
145             throw new ClusterJUserException(builder.toString());
146         }
147     }
148 
149     /** The maximum mysql collation (charset) number. This is hard coded in <mysql>/include/my_sys.h */
150     static int MAXIMUM_MYSQL_COLLATION_NUMBER = 256;
151 
152     /** The mysql collation number for the standard charset */
153     static int collationLatin1 = charsetMap.getCharsetNumber("latin1");
154 
155     /** The mysql collation number for UTF16 */
156     static protected final int collationUTF16 = charsetMap.getUTF16CharsetNumber();
157 
158     /** The mysql charset map */
getCharsetMap()159     public static CharsetMap getCharsetMap() {
160         return charsetMap;
161     }
162 
163     /** The map of charset name to collations that share the charset name */
164     private static Map<String, int[]> collationPeersMap = new TreeMap<String, int[]>();
165 
166     /** The ClusterJ charset converter for all multibyte charsets */
167     private static CharsetConverter charsetConverterMultibyte = new MultiByteCharsetConverter();
168 
169     /** Charset converters */
170     private static CharsetConverter[] charsetConverters =
171         new CharsetConverter[MAXIMUM_MYSQL_COLLATION_NUMBER + 1];
172 
173     /** Initialize the the array of charset converters and the map of known collations that share the same charset */
174     static {
175         Map<String, List<Integer>> workingCollationPeersMap = new TreeMap<String, List<Integer>>();
176         for (int collation = 1; collation <= MAXIMUM_MYSQL_COLLATION_NUMBER; ++collation) {
177             String mysqlName = charsetMap.getMysqlName(collation);
178             if (mysqlName != null) {
179                 if ((isMultibyteCollation(collation))) {
180                     // multibyte collations all use the multibyte charset converter
181                     charsetConverters[collation] = charsetConverterMultibyte;
182                 } else {
183                     // find out if this charset name is already used by another (peer) collation
184                     List<Integer> collations = workingCollationPeersMap.get(mysqlName);
185                     if (collations == null) {
186                         // this is the first collation to use this charset name
187                         collations = new ArrayList<Integer>(8);
188                         collations.add(collation);
workingCollationPeersMap.put(mysqlName, collations)189                         workingCollationPeersMap.put(mysqlName, collations);
190                     } else {
191                         // add this collation to the list of (peer) collations
192                         collations.add(collation);
193                     }
194                 }
195             }
196         }
197 
198         for (Map.Entry<String, List<Integer>> workingCollationPeers: workingCollationPeersMap.entrySet()) {
199             String mysqlName = workingCollationPeers.getKey();
200             List<Integer> collations = workingCollationPeers.getValue();
201             int[] collationArray = new int[collations.size()];
202             int i = 0;
203             for (Integer collation: collations) {
204                 collationArray[i++] = collation;
205             }
collationPeersMap.put(mysqlName, collationArray)206             collationPeersMap.put(mysqlName, collationArray);
207         }
208         if (logger.isDetailEnabled()) {
209             for (Map.Entry<String, int[]> collationEntry: collationPeersMap.entrySet()) {
210                 logger.detail("Utility collationMap " + collationEntry.getKey()
211                         + " collations : " + Arrays.toString(collationEntry.getValue()));
212             }
213         }
214         // initialize the charset converter for latin1 and its peer collations (after peers are known)
215         addCollation(collationLatin1);
216     }
217 
218     /** Determine if the exception is retriable
219      * @param ex the exception
220      * @return if the status is retriable
221      */
isRetriable(ClusterJDatastoreException ex)222     public static boolean isRetriable(ClusterJDatastoreException ex) {
223         return NdbErrorConst.Status.TemporaryError == ex.getStatus();
224     }
225 
226     private final static EndianManager endianManager = ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder())?
227         /*
228          * Big Endian algorithms to convert NdbRecAttr buffer into primitive types
229          */
230         new EndianManager() {
231 
232         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
233             switch (storeColumn.getType()) {
234                 case Bit:
235                     return ndbRecAttr.int32_value() == 1;
236                 case Tinyint:
237                     return ndbRecAttr.int8_value() == 1;
238                 default:
239                     throw new ClusterJUserException(
240                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
241             }
242         }
243 
244         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
245             switch (storeColumn.getType()) {
246                 case Bit:
247                     return (byte)ndbRecAttr.int32_value();
248                 case Tinyint:
249                 case Year:
250                     return ndbRecAttr.int8_value();
251                 default:
252                     throw new ClusterJUserException(
253                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
254             }
255         }
256         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
257             switch (storeColumn.getType()) {
258                 case Bit:
259                     return (short)ndbRecAttr.int32_value();
260                 case Smallint:
261                     return ndbRecAttr.short_value();
262                 default:
263                     throw new ClusterJUserException(
264                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
265             }
266         }
267         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
268             switch (storeColumn.getType()) {
269                 case Bit:
270                 case Int:
271                 case Timestamp:
272                     return ndbRecAttr.int32_value();
273                 case Date:
274                     return ndbRecAttr.u_medium_value();
275                 case Time:
276                     return ndbRecAttr.medium_value();
277                 default:
278                     throw new ClusterJUserException(
279                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
280             }
281         }
282         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
283             switch (storeColumn.getType()) {
284                 case Bit:
285                     long rawValue = ndbRecAttr.int64_value();
286                     return (rawValue >>> 32) | (rawValue << 32);
287                 case Bigint:
288                 case Bigunsigned:
289                     return ndbRecAttr.int64_value();
290                 case Datetime:
291                     return unpackDatetime(ndbRecAttr.int64_value());
292                 case Timestamp:
293                     return (((long)ndbRecAttr.int32_value()) & ooooooooffffffff) * 1000L;
294                 case Date:
295                     return unpackDate(ndbRecAttr.u_medium_value());
296                 case Time:
297                     return unpackTime(ndbRecAttr.medium_value());
298                 default:
299                     throw new ClusterJUserException(
300                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
301             }
302         }
303         /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
304          * The format for medium value is always little-endian even on big-endian architectures.
305          * Do not flip the buffer, as the caller will do that if needed.
306          * @param byteBuffer the byte buffer
307          * @param value the input value
308          */
309         public void put3byteInt(ByteBuffer byteBuffer, int value) {
310             byteBuffer.put((byte)(value));
311             byteBuffer.put((byte)(value >> 8));
312             byteBuffer.put((byte)(value >> 16));
313         }
314 
315         public ByteBuffer convertValue(Column storeColumn, byte value) {
316             ByteBuffer result;
317             switch (storeColumn.getType()) {
318                 case Bit:
319                     result = ByteBuffer.allocateDirect(4);
320                     // bit fields are always stored in an int32
321                     result.order(ByteOrder.BIG_ENDIAN);
322                     result.putInt(value & 0xff);
323                     result.flip();
324                     return result;
325                 case Tinyint:
326                 case Year:
327                     result = ByteBuffer.allocateDirect(1);
328                     result.put(value);
329                     result.flip();
330                     return result;
331                 default:
332                     throw new ClusterJUserException(local.message(
333                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
334             }
335         }
336 
337         public ByteBuffer convertValue(Column storeColumn, short value) {
338             ByteBuffer result;
339             switch (storeColumn.getType()) {
340                 case Bit:
341                     result = ByteBuffer.allocateDirect(4);
342                     // bit fields are always stored in an int32
343                     result.order(ByteOrder.BIG_ENDIAN);
344                     result.putInt(value & 0xffff);
345                     result.flip();
346                     return result;
347                 case Smallint:
348                     result = ByteBuffer.allocateDirect(2);
349                     result.order(ByteOrder.BIG_ENDIAN);
350                     result.putShort(value);
351                     result.flip();
352                     return result;
353                 default:
354                     throw new ClusterJUserException(local.message(
355                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
356             }
357         }
358 
359         public ByteBuffer convertValue(Column storeColumn, int value) {
360             ByteBuffer result = ByteBuffer.allocateDirect(4);
361             switch (storeColumn.getType()) {
362                 case Bit:
363                 case Int:
364                     result.order(ByteOrder.BIG_ENDIAN);
365                     break;
366                 default:
367                     throw new ClusterJUserException(local.message(
368                             "ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
369             }
370             result.putInt(value);
371             result.flip();
372             return result;
373         }
374 
375         public ByteBuffer convertValue(Column storeColumn, long value) {
376             ByteBuffer result = ByteBuffer.allocateDirect(8);
377             return convertValue(storeColumn, value, result);
378         }
379 
380         public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
381             switch (storeColumn.getType()) {
382                 case Bit:
383                     // bit fields are stored in two int32 fields
384                     result.order(ByteOrder.BIG_ENDIAN);
385                     result.putInt((int)((value)));
386                     result.putInt((int)((value >>> 32)));
387                     result.flip();
388                     return result;
389                 case Bigint:
390                 case Bigunsigned:
391                     result.order(ByteOrder.BIG_ENDIAN);
392                     result.putLong(value);
393                     result.flip();
394                     return result;
395                 case Date:
396                     result.order(ByteOrder.LITTLE_ENDIAN);
397                     put3byteInt(result, packDate(value));
398                     result.flip();
399                     return result;
400                 case Datetime:
401                     result.order(ByteOrder.BIG_ENDIAN);
402                     result.putLong(packDatetime(value));
403                     result.flip();
404                     return result;
405                 case Time:
406                     result.order(ByteOrder.LITTLE_ENDIAN);
407                     put3byteInt(result, packTime(value));
408                     result.flip();
409                     return result;
410                 case Timestamp:
411                     result.order(ByteOrder.BIG_ENDIAN);
412                     result.putInt((int)(value/1000L));
413                     result.flip();
414                     return result;
415                 default:
416                     throw new ClusterJUserException(local.message(
417                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
418             }
419         }
420 
421         public long convertLongValueForStorage(Column storeColumn, long value) {
422             long result = 0L;
423             switch (storeColumn.getType()) {
424                 case Bit:
425                     // bit fields are stored in two int32 fields
426                     result |= (value >>> 32);
427                     result |= (value << 32);
428                     return result;
429                 case Bigint:
430                 case Bigunsigned:
431                     return value;
432                 case Date:
433                     // the high order bytes are the little endian representation
434                     // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
435                     long packDate = packDate(value);
436                     result |= (packDate & ooooooff) << 56;
437                     result |= (packDate & ooooffoo) << 40;
438                     result |= (packDate & ooffoooo) << 24;
439                     return result;
440                 case Datetime:
441                     return packDatetime(value);
442                 case Time:
443                     // the high order bytes are the little endian representation
444                     // the original is 0000000000xxyyzz and the result is zzyyxx0000000000
445                     long packTime = packTime(value);
446                     result |= (packTime & ooooooff) << 56;
447                     result |= (packTime & ooooffoo) << 40;
448                     result |= (packTime & ooffoooo) << 24;
449                     return result;
450                 case Timestamp:
451                     // timestamp is an int so put the value into the high bytes
452                     // the original is 00000000tttttttt and the result is tttttttt00000000
453                     return (value/1000L) << 32;
454                 default:
455                     throw new ClusterJUserException(local.message(
456                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
457             }
458         }
459 
460         public int convertByteValueForStorage(Column storeColumn, byte value) {
461             switch (storeColumn.getType()) {
462                 case Bit:
463                     // bit fields are always stored in an int32
464                     return value & ooooooff;
465                 case Tinyint:
466                 case Year:
467                     // other byte values are stored in the high byte of an int
468                     return value << 24;
469                 default:
470                     throw new ClusterJUserException(local.message(
471                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
472             }
473         }
474 
475         public int convertShortValueForStorage(Column storeColumn, short value) {
476             switch (storeColumn.getType()) {
477                 case Bit:
478                     // bit fields are always stored in an int32
479                     return value & ooooffff;
480                 case Smallint:
481                     // short values are in the top 16 bits of an int
482                     return value << 16;
483                 default:
484                     throw new ClusterJUserException(local.message(
485                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
486             }
487         }
488 
489     }:
490         /*
491          * Little Endian algorithms to convert NdbRecAttr buffer into primitive types
492          */
493         new EndianManager() {
494 
495         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
496             switch (storeColumn.getType()) {
497                 case Bit:
498                     return ndbRecAttr.int32_value() == 1;
499                 case Tinyint:
500                     return ndbRecAttr.int8_value() == 1;
501                 default:
502                     throw new ClusterJUserException(
503                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "boolean"));
504             }
505         }
506 
507         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
508             switch (storeColumn.getType()) {
509                 case Bit:
510                 case Tinyint:
511                 case Year:
512                     return ndbRecAttr.int8_value();
513                 default:
514                     throw new ClusterJUserException(
515                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
516             }
517         }
518         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
519             switch (storeColumn.getType()) {
520                 case Bit:
521                 case Smallint:
522                     return ndbRecAttr.short_value();
523                 default:
524                     throw new ClusterJUserException(
525                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
526             }
527         }
528         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
529             switch (storeColumn.getType()) {
530                 case Bit:
531                 case Int:
532                 case Timestamp:
533                     return ndbRecAttr.int32_value();
534                 case Date:
535                     return ndbRecAttr.u_medium_value();
536                 case Time:
537                     return ndbRecAttr.medium_value();
538                 default:
539                     throw new ClusterJUserException(
540                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "int"));
541             }
542         }
543         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
544             switch (storeColumn.getType()) {
545                 case Bigint:
546                 case Bigunsigned:
547                 case Bit:
548                     return ndbRecAttr.int64_value();
549                 case Datetime:
550                     return unpackDatetime(ndbRecAttr.int64_value());
551                 case Timestamp:
552                     return ndbRecAttr.int32_value() * 1000L;
553                 case Date:
554                     return unpackDate(ndbRecAttr.int32_value());
555                 case Time:
556                     return unpackTime(ndbRecAttr.int32_value());
557                 default:
558                     throw new ClusterJUserException(
559                             local.message("ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
560             }
561         }
562 
563         /** Put the low order three bytes of the input value into the ByteBuffer as a medium_value.
564          * The format for medium value is always little-endian even on big-endian architectures.
565          * Do not flip the buffer, as the caller will do that if needed.
566          * @param byteBuffer the byte buffer
567          * @param value the input value
568          */
569         public void put3byteInt(ByteBuffer byteBuffer, int value) {
570             byteBuffer.putInt(value);
571             byteBuffer.limit(3);
572         }
573 
574         public ByteBuffer convertValue(Column storeColumn, byte value) {
575             ByteBuffer result;
576             switch (storeColumn.getType()) {
577                 case Bit:
578                     // bit fields are always stored as int32
579                     result = ByteBuffer.allocateDirect(4);
580                     result.order(ByteOrder.nativeOrder());
581                     result.putInt(value & 0xff);
582                     result.flip();
583                     return result;
584                 case Tinyint:
585                 case Year:
586                     result = ByteBuffer.allocateDirect(1);
587                     result.order(ByteOrder.nativeOrder());
588                     result.put(value);
589                     result.flip();
590                     return result;
591                 default:
592                     throw new ClusterJUserException(local.message(
593                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
594             }
595         }
596 
597         public ByteBuffer convertValue(Column storeColumn, short value) {
598             switch (storeColumn.getType()) {
599                 case Bit:
600                 case Smallint:
601                     ByteBuffer result = ByteBuffer.allocateDirect(2);
602                     result.order(ByteOrder.nativeOrder());
603                     result.putShort(value);
604                     result.flip();
605                     return result;
606                 default:
607                     throw new ClusterJUserException(local.message(
608                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
609             }
610         }
611 
612         public ByteBuffer convertValue(Column storeColumn, int value) {
613             switch (storeColumn.getType()) {
614                 case Bit:
615                 case Int:
616                     ByteBuffer result = ByteBuffer.allocateDirect(4);
617                     result.order(ByteOrder.nativeOrder());
618                     result.putInt(value);
619                     result.flip();
620                     return result;
621                 default:
622                     throw new ClusterJUserException(local.message(
623                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
624             }
625         }
626 
627         public ByteBuffer convertValue(Column storeColumn, long value) {
628             ByteBuffer result = ByteBuffer.allocateDirect(8);
629             return convertValue(storeColumn, value, result);
630         }
631 
632         public ByteBuffer convertValue(Column storeColumn, long value, ByteBuffer result) {
633             switch (storeColumn.getType()) {
634                 case Bit:
635                 case Bigint:
636                 case Bigunsigned:
637                     result.order(ByteOrder.LITTLE_ENDIAN);
638                     result.putLong(value);
639                     result.flip();
640                     return result;
641                 case Datetime:
642                     result.order(ByteOrder.LITTLE_ENDIAN);
643                     result.putLong(packDatetime(value));
644                     result.flip();
645                     return result;
646                 case Timestamp:
647                     result.order(ByteOrder.LITTLE_ENDIAN);
648                     result.putInt((int)(value/1000L));
649                     result.flip();
650                     return result;
651                 case Date:
652                     result.order(ByteOrder.LITTLE_ENDIAN);
653                     put3byteInt(result, packDate(value));
654                     result.flip();
655                     return result;
656                 case Time:
657                     result.order(ByteOrder.LITTLE_ENDIAN);
658                     put3byteInt(result, packTime(value));
659                     result.flip();
660                     return result;
661                 default:
662                     throw new ClusterJUserException(local.message(
663                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
664             }
665         }
666 
667         public long convertLongValueForStorage(Column storeColumn, long value) {
668             switch (storeColumn.getType()) {
669                 case Bit:
670                 case Bigint:
671                 case Bigunsigned:
672                     return value;
673                 case Datetime:
674                     return packDatetime(value);
675                case Timestamp:
676                     return value/1000L;
677                 case Date:
678                     return packDate(value);
679                 case Time:
680                     return packTime(value);
681                 default:
682                     throw new ClusterJUserException(local.message(
683                             "ERR_Unsupported_Mapping", storeColumn.getType(), "long"));
684             }
685         }
686 
687         public int convertByteValueForStorage(Column storeColumn, byte value) {
688             switch (storeColumn.getType()) {
689                 case Bit:
690                     // bit fields are always stored as int32
691                 case Tinyint:
692                 case Year:
693                     return  value & ooooooff;
694                 default:
695                     throw new ClusterJUserException(local.message(
696                             "ERR_Unsupported_Mapping", storeColumn.getType(), "byte"));
697             }
698         }
699 
700         public int convertShortValueForStorage(Column storeColumn, short value) {
701             switch (storeColumn.getType()) {
702                 case Bit:
703                     // bit fields are always stored as int32
704                 case Smallint:
705                     return value & ooooffff;
706                 default:
707                     throw new ClusterJUserException(local.message(
708                             "ERR_Unsupported_Mapping", storeColumn.getType(), "short"));
709             }
710         }
711 
712     };
713 
714     /* Error codes that are not severe, and simply reflect expected conditions */
715     private static Set<Integer> NonSevereErrorCodes = new HashSet<Integer>();
716 
717     static {
718         NonSevereErrorCodes.add(4203); // Trying to set a NOT NULL attribute to NULL
719         NonSevereErrorCodes.add(4243); // Index not found
720         NonSevereErrorCodes.add(626); // Tuple did not exist
721     }
722 
723     protected static interface EndianManager {
put3byteInt(ByteBuffer byteBuffer, int value)724         public void put3byteInt(ByteBuffer byteBuffer, int value);
getInt(Column storeColumn, NdbRecAttr ndbRecAttr)725         public int getInt(Column storeColumn, NdbRecAttr ndbRecAttr);
getShort(Column storeColumn, NdbRecAttr ndbRecAttr)726         public short getShort(Column storeColumn, NdbRecAttr ndbRecAttr);
getLong(Column storeColumn, NdbRecAttr ndbRecAttr)727         public long getLong(Column storeColumn, NdbRecAttr ndbRecAttr);
getByte(Column storeColumn, NdbRecAttr ndbRecAttr)728         public byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr);
convertValue(Column storeColumn, byte value)729         public ByteBuffer convertValue(Column storeColumn, byte value);
convertValue(Column storeColumn, short value)730         public ByteBuffer convertValue(Column storeColumn, short value);
convertValue(Column storeColumn, int value)731         public ByteBuffer convertValue(Column storeColumn, int value);
convertValue(Column storeColumn, long value)732         public ByteBuffer convertValue(Column storeColumn, long value);
getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr)733         public boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr);
convertLongValueForStorage(Column storeColumn, long value)734         public long convertLongValueForStorage(Column storeColumn, long value);
convertByteValueForStorage(Column storeColumn, byte value)735         public int convertByteValueForStorage(Column storeColumn, byte value);
convertShortValueForStorage(Column storeColumn, short value)736         public int convertShortValueForStorage(Column storeColumn, short value);
737     }
738 
739     /** Swap the bytes in the value, thereby converting a big-endian value
740      * into a little-endian value (or vice versa).
741      * @param value the value to be swapped
742      * @return the swapped value
743      */
swap(short value)744     protected static short swap(short value) {
745         return (short)((0x00ff & (value >>> 8)) |
746                        (0xff00 & (value  << 8)));
747     }
748 
749     /** Swap the bytes in the value, thereby converting a big-endian value
750      * into a little-endian value (or vice versa).
751      * @param value the value to be swapped
752      * @return the swapped value
753      */
swap(int value)754     protected static int swap(int value) {
755         return   0x000000ff & (value >>> 24) |
756                 (0x0000ff00 & (value >>> 8)) |
757                 (0x00ff0000 & (value  << 8)) |
758                 (0xff000000 & (value  << 24));
759     }
760 
761     /** Swap the bytes in the value, thereby converting a big-endian value
762      * into a little-endian value (or vice versa).
763      * @param value the value to be swapped
764      * @return the swapped value
765      */
swap(long value)766     protected static long swap(long value) {
767         return   ooooooooooooooff & (value >>> 56) |
768                 (ooooooooooooffoo & (value >>> 40)) |
769                 (ooooooooooffoooo & (value >>> 24)) |
770                 (ooooooooffoooooo & (value >>> 8)) |
771                 (ooooooffoooooooo & (value  << 8)) |
772                 (ooooffoooooooooo & (value  << 24)) |
773                 (ooffoooooooooooo & (value  << 40)) |
774                 (ffoooooooooooooo & (value  << 56));
775     }
776 
throwError(Object returnCode, NdbErrorConst ndbError)777     protected static void throwError(Object returnCode, NdbErrorConst ndbError) {
778         throwError(returnCode, ndbError, "");
779     }
780 
throwError(Object returnCode, NdbErrorConst ndbError, String extra)781     protected static void throwError(Object returnCode, NdbErrorConst ndbError, String extra) {
782         String message = ndbError.message();
783         int code = ndbError.code();
784         int mysqlCode = ndbError.mysql_code();
785         int status = ndbError.status();
786         int classification = ndbError.classification();
787         String msg = local.message("ERR_NdbJTie", returnCode, code, mysqlCode,
788                 status, classification, message, extra);
789         if (!NonSevereErrorCodes .contains(code)) {
790             logger.error(msg);
791         }
792         throw new ClusterJDatastoreException(msg, code, mysqlCode, status, classification);
793     }
794 
795     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
796      *
797      * @param storeColumn the column definition
798      * @param value the value to be converted
799      * @return the ByteBuffer
800      */
convertValue(Column storeColumn, byte[] value)801     public static ByteBuffer convertValue(Column storeColumn, byte[] value) {
802         int dataLength = value.length;
803         int prefixLength = storeColumn.getPrefixLength();
804         ByteBuffer result;
805         switch (prefixLength) {
806             case 0:
807                 int requiredLength = storeColumn.getColumnSpace();
808                 if (dataLength > requiredLength) {
809                     throw new ClusterJFatalInternalException(
810                             local.message("ERR_Data_Too_Long",
811                             storeColumn.getName(), requiredLength, dataLength));
812                 } else {
813                     result = ByteBuffer.allocateDirect(requiredLength);
814                     result.order(ByteOrder.nativeOrder());
815                     result.put(value);
816                     if (dataLength < requiredLength) {
817                         // pad with 0x00 on right
818                         result.put(ZERO_PAD, 0, requiredLength - dataLength);
819                     }
820                 }
821                 break;
822             case 1:
823                 if (dataLength > 255) {
824                     throw new ClusterJFatalInternalException(
825                             local.message("ERR_Data_Too_Long",
826                             storeColumn.getName(), "255", dataLength));
827                 }
828                 result = ByteBuffer.allocateDirect(prefixLength + dataLength);
829                 result.order(ByteOrder.nativeOrder());
830                 result.put((byte)dataLength);
831                 result.put(value);
832                 break;
833             case 2:
834                 if (dataLength > 8000) {
835                     throw new ClusterJFatalInternalException(
836                             local.message("ERR_Data_Too_Long",
837                             storeColumn.getName(), "8000", dataLength));
838                 }
839                 result = ByteBuffer.allocateDirect(prefixLength + dataLength);
840                 result.order(ByteOrder.nativeOrder());
841                 result.put((byte)(dataLength%256));
842                 result.put((byte)(dataLength/256));
843                 result.put(value);
844                 break;
845             default:
846                     throw new ClusterJFatalInternalException(
847                             local.message("ERR_Unknown_Prefix_Length",
848                             prefixLength, storeColumn.getName()));
849         }
850         result.flip();
851         return result;
852     }
853 
854     /** Convert a BigDecimal value to the binary decimal form used by MySQL.
855      * Use the precision and scale of the column to convert. Values that don't fit
856      * into the column throw a ClusterJUserException.
857      * @param storeColumn the column metadata
858      * @param value the value to be converted
859      * @return the ByteBuffer
860      */
convertValue(Column storeColumn, BigDecimal value)861     public static ByteBuffer convertValue(Column storeColumn, BigDecimal value) {
862         int precision = storeColumn.getPrecision();
863         int scale = storeColumn.getScale();
864         int bytesNeeded = getDecimalColumnSpace(precision, scale);
865         ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
866         // TODO this should be a policy option, perhaps an annotation to fail on truncation
867         BigDecimal scaledValue = value.setScale(scale, RoundingMode.HALF_UP);
868         // the new value has the same scale as the column
869         String stringRepresentation = scaledValue.toPlainString();
870         int length = stringRepresentation.length();
871         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
872         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
873         // basic encoding
874         charsetEncoder.encode(charBuffer, byteBuffer, true);
875         byteBuffer.flip();
876         int returnCode = Utils.decimal_str2bin(
877                 byteBuffer, length, precision, scale, result, bytesNeeded);
878         byteBuffer.flip();
879         if (returnCode != 0) {
880             throw new ClusterJUserException(
881                     local.message("ERR_String_To_Binary_Decimal",
882                     returnCode, scaledValue, storeColumn.getName(), precision, scale));
883         }
884         return result;
885     }
886 
887     /** Convert a BigInteger value to the binary decimal form used by MySQL.
888      * Use the precision and scale of the column to convert. Values that don't fit
889      * into the column throw a ClusterJUserException.
890      * @param storeColumn the column metadata
891      * @param value the value to be converted
892      * @return the ByteBuffer
893      */
convertValue(Column storeColumn, BigInteger value)894     public static ByteBuffer convertValue(Column storeColumn, BigInteger value) {
895         int precision = storeColumn.getPrecision();
896         int scale = storeColumn.getScale();
897         int bytesNeeded = getDecimalColumnSpace(precision, scale);
898         ByteBuffer result = ByteBuffer.allocateDirect(bytesNeeded);
899         String stringRepresentation = value.toString();
900         int length = stringRepresentation.length();
901         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(length);
902         CharBuffer charBuffer = CharBuffer.wrap(stringRepresentation);
903         // basic encoding
904         charsetEncoder.encode(charBuffer, byteBuffer, true);
905         byteBuffer.flip();
906         int returnCode = Utils.decimal_str2bin(
907                 byteBuffer, length, precision, scale, result, bytesNeeded);
908         byteBuffer.flip();
909         if (returnCode != 0) {
910             throw new ClusterJUserException(
911                     local.message("ERR_String_To_Binary_Decimal",
912                     returnCode, stringRepresentation, storeColumn.getName(), precision, scale));
913         }
914         return result;
915     }
916 
917     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
918      *
919      * @param storeColumn the column definition
920      * @param value the value to be converted
921      * @return the ByteBuffer
922      */
convertValue(Column storeColumn, double value)923     public static ByteBuffer convertValue(Column storeColumn, double value) {
924         ByteBuffer result = ByteBuffer.allocateDirect(8);
925         result.order(ByteOrder.nativeOrder());
926         result.putDouble(value);
927         result.flip();
928         return result;
929     }
930 
931     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
932      *
933      * @param storeColumn the column definition
934      * @param value the value to be converted
935      * @return the ByteBuffer
936      */
convertValue(Column storeColumn, float value)937     public static ByteBuffer convertValue(Column storeColumn, float value) {
938         ByteBuffer result = ByteBuffer.allocateDirect(4);
939         result.order(ByteOrder.nativeOrder());
940         result.putFloat(value);
941         result.flip();
942         return result;
943     }
944 
945     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
946      *
947      * @param storeColumn the column definition
948      * @param value the value to be converted
949      * @return the ByteBuffer
950      */
convertValue(Column storeColumn, byte value)951     public static ByteBuffer convertValue(Column storeColumn, byte value) {
952         return endianManager.convertValue(storeColumn, value);
953     }
954 
955     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
956      *
957      * @param storeColumn the column definition
958      * @param value the value to be converted
959      * @return the ByteBuffer
960      */
convertValue(Column storeColumn, short value)961     public static ByteBuffer convertValue(Column storeColumn, short value) {
962         return endianManager.convertValue(storeColumn, value);
963     }
964 
965     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
966      *
967      * @param storeColumn the column definition
968      * @param value the value to be converted
969      * @return the ByteBuffer
970      */
convertValue(Column storeColumn, int value)971     public static ByteBuffer convertValue(Column storeColumn, int value) {
972         return endianManager.convertValue(storeColumn, value);
973     }
974 
975     /** Convert the parameter value to a ByteBuffer that can be passed to ndbjtie.
976      *
977      * @param storeColumn the column definition
978      * @param value the value to be converted
979      * @return the ByteBuffer
980      */
convertValue(Column storeColumn, long value)981     public static ByteBuffer convertValue(Column storeColumn, long value) {
982         return endianManager.convertValue(storeColumn, value);
983     }
984 
985     /** Encode a String as a ByteBuffer that can be passed to ndbjtie.
986      * Put the length information in the beginning of the buffer.
987      * Pad fixed length strings with blanks.
988      * @param storeColumn the column definition
989      * @param value the value to be converted
990      * @return the ByteBuffer
991      */
convertValue(Column storeColumn, String value)992     protected static ByteBuffer convertValue(Column storeColumn, String value) {
993         if (value == null) {
994             value = "";
995         }
996         CharSequence chars = value;
997         int offset = storeColumn.getPrefixLength();
998         if (offset == 0) {
999             chars = padString(value, storeColumn);
1000         }
1001         ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), offset);
1002         fixBufferPrefixLength(storeColumn.getName(), byteBuffer, offset);
1003         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1004         return byteBuffer;
1005     }
1006 
1007     /** Encode a String as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
1008      * There is no length information in the beginning of the buffer.
1009      * @param storeColumn the column definition
1010      * @param value the value to be converted
1011      * @return the ByteBuffer
1012      */
convertValueForLikeFilter(Column storeColumn, String value)1013     protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, String value) {
1014         if (value == null) {
1015             value = "";
1016         }
1017         CharSequence chars = value;
1018         ByteBuffer byteBuffer = encodeToByteBuffer(chars, storeColumn.getCharsetNumber(), 0);
1019         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1020         return byteBuffer;
1021     }
1022 
1023     /** Encode a byte[] as a ByteBuffer that can be passed to ndbjtie in a COND_LIKE filter.
1024      * There is no length information in the beginning of the buffer.
1025      * @param storeColumn the column definition
1026      * @param value the value to be converted
1027      * @return the ByteBuffer
1028      */
convertValueForLikeFilter(Column storeColumn, byte[] value)1029     protected static ByteBuffer convertValueForLikeFilter(Column storeColumn, byte[] value) {
1030         if (value == null) {
1031             value = EMPTY_BYTE_ARRAY;
1032         }
1033         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(value.length);
1034         byteBuffer.put(value);
1035         byteBuffer.flip();
1036         if (logger.isDetailEnabled()) dumpBytesToLog(byteBuffer, byteBuffer.limit());
1037         return byteBuffer;
1038     }
1039 
1040     /** Pad the value with blanks on the right.
1041      * @param value the input value
1042      * @param storeColumn the store column
1043      * @return the value padded with blanks on the right
1044      */
padString(CharSequence value, Column storeColumn)1045     private static CharSequence padString(CharSequence value, Column storeColumn) {
1046         CharSequence chars = value;
1047         int suppliedLength = value.length();
1048         int requiredLength = storeColumn.getColumnSpace();
1049         if (suppliedLength > requiredLength) {
1050             throw new ClusterJUserException(local.message("ERR_Data_Too_Long",
1051                     storeColumn.getName(), requiredLength, suppliedLength));
1052         } else if (suppliedLength < requiredLength) {
1053             // pad to fixed length
1054             StringBuilder buffer = new StringBuilder(requiredLength);
1055             buffer.append(value);
1056             buffer.append(SPACE_PAD, 0, requiredLength - suppliedLength);
1057             chars = buffer;
1058         }
1059         return chars;
1060     }
1061 
1062     /** Fix the length information in a buffer based on the length prefix,
1063      * either 0, 1, or 2 bytes that hold the length information.
1064      * @param byteBuffer the byte buffer to fix
1065      * @param offset the size of the length prefix
1066      */
fixBufferPrefixLength(String columnName, ByteBuffer byteBuffer, int offset)1067     public static void fixBufferPrefixLength(String columnName, ByteBuffer byteBuffer, int offset) {
1068         int limit = byteBuffer.limit();
1069         // go back and fill in the length field(s)
1070         // size of the output char* is current limit minus offset
1071         int length = limit - offset;
1072         byteBuffer.position(0);
1073         switch (offset) {
1074             case 0:
1075                 break;
1076             case 1:
1077                 if (length > 255) {
1078                     throw new ClusterJUserException(local.message("ERR_Varchar_Too_Big",
1079                             length, columnName));
1080                 }
1081                 byteBuffer.put((byte)(length % 256));
1082                 break;
1083             case 2:
1084                 if (length > 65535) {
1085                     throw new ClusterJUserException(local.message("ERR_Varchar_Too_Big",
1086                             length, columnName));
1087                 }
1088                 byteBuffer.put((byte)(length % 256));
1089                 byteBuffer.put((byte)(length / 256));
1090                 break;
1091         }
1092         // reset the position and limit for return
1093         byteBuffer.position(0);
1094         byteBuffer.limit(limit);
1095     }
1096 
1097     /** Pack milliseconds since the Epoch into an int in database Date format.
1098      * The date is converted into a three-byte value encoded as
1099      * YYYYx16x32 + MMx32 + DD.
1100      * Add one to the month since Calendar month is 0-origin.
1101      * @param millis milliseconds since the Epoch
1102      * @return the int in packed Date format
1103      */
packDate(long millis)1104     private static int packDate(long millis) {
1105         Calendar calendar = Calendar.getInstance();
1106         calendar.clear();
1107         calendar.setTimeInMillis(millis);
1108         int year = calendar.get(Calendar.YEAR);
1109         int month = calendar.get(Calendar.MONTH);
1110         int day = calendar.get(Calendar.DATE);
1111         int date = (year * 512) + ((month + 1) * 32) + day;
1112         return date;
1113     }
1114 
1115     /** Pack milliseconds since the Epoch into an int in database Time format.
1116      * Subtract one from date to get number of days (date is 1 origin).
1117      * The time is converted into a three-byte value encoded as
1118      * DDx240000 + HHx10000 + MMx100 + SS.
1119      * @param millis milliseconds since the Epoch
1120      * @return the int in packed Time format
1121      */
packTime(long millis)1122     private static int packTime(long millis) {
1123         Calendar calendar = Calendar.getInstance();
1124         calendar.clear();
1125         calendar.setTimeInMillis(millis);
1126         int year = calendar.get(Calendar.YEAR);
1127         int month = calendar.get(Calendar.MONTH);
1128         int day = calendar.get(Calendar.DATE);
1129         int hour = calendar.get(Calendar.HOUR);
1130         int minute = calendar.get(Calendar.MINUTE);
1131         int second = calendar.get(Calendar.SECOND);
1132         if (month != 0) {
1133             throw new ClusterJUserException(
1134                     local.message("ERR_Write_Time_Domain", new java.sql.Time(millis), millis, year, month, day, hour, minute, second));
1135         }
1136         int time = ((day - 1) * 240000) + (hour * 10000) + (minute * 100) + second;
1137         return time;
1138     }
1139 
1140     /** Pack milliseconds since the Epoch into a long in database Datetime format.
1141      * The Datetime contains a eight-byte date and time packed as
1142      * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
1143      * Calendar month is 0 origin so add 1 to get packed month
1144      * @param value milliseconds since the Epoch
1145      * @return the long in packed Datetime format
1146      */
packDatetime(long value)1147     protected static long packDatetime(long value) {
1148         Calendar calendar = Calendar.getInstance();
1149         calendar.clear();
1150         calendar.setTimeInMillis(value);
1151         long year = calendar.get(Calendar.YEAR);
1152         long month = calendar.get(Calendar.MONTH) + 1;
1153         long day = calendar.get(Calendar.DATE);
1154         long hour = calendar.get(Calendar.HOUR);
1155         long minute = calendar.get(Calendar.MINUTE);
1156         long second = calendar.get(Calendar.SECOND);
1157         long packedDatetime = (year * 10000000000L) + (month * 100000000L) + (day * 1000000L)
1158                 + (hour * 10000L) + (minute * 100) + second;
1159         return packedDatetime;
1160     }
1161 
1162     /** Convert the byte[] into a String to be used for logging and debugging.
1163      *
1164      * @param bytes the byte[] to be dumped
1165      * @return the String representation
1166      */
dumpBytes(byte[] bytes)1167     public static String dumpBytes (byte[] bytes) {
1168         StringBuffer buffer = new StringBuffer("byte[");
1169         buffer.append(bytes.length);
1170         buffer.append("]: [");
1171         for (int i = 0; i < bytes.length; ++i) {
1172             buffer.append((int)bytes[i]);
1173             buffer.append(" ");
1174         }
1175         buffer.append("]");
1176         return buffer.toString();
1177     }
1178 
1179     /** Convert the byteBuffer into a String to be used for logging and debugging.
1180      *
1181      * @param byteBuffer the byteBuffer to be dumped
1182      * @return the String representation
1183      */
dumpBytes(ByteBuffer byteBuffer)1184     public static String dumpBytes(ByteBuffer byteBuffer) {
1185         byteBuffer.mark();
1186         int length = byteBuffer.limit() - byteBuffer.position();
1187         byte[] dst = new byte[length];
1188         byteBuffer.get(dst);
1189         byteBuffer.reset();
1190         return dumpBytes(dst);
1191     }
1192 
dumpBytesToLog(ByteBuffer byteBuffer, int limit)1193     private static void dumpBytesToLog(ByteBuffer byteBuffer, int limit) {
1194         StringBuffer message = new StringBuffer("String position is: ");
1195         message.append(byteBuffer.position());
1196         message.append(" limit: ");
1197         message.append(byteBuffer.limit());
1198         message.append(" data [");
1199         while (byteBuffer.hasRemaining()) {
1200             message.append((int)byteBuffer.get());
1201             message.append(" ");
1202         }
1203         message.append("]");
1204         logger.detail(message.toString());
1205         byteBuffer.position(0);
1206         byteBuffer.limit(limit);
1207     }
1208 
getDecimal(ByteBuffer byteBuffer, int length, int precision, int scale)1209     public static BigDecimal getDecimal(ByteBuffer byteBuffer, int length, int precision, int scale) {
1210         String decimal = null;
1211         try {
1212             decimal = getDecimalString(byteBuffer, length, precision, scale);
1213             return new BigDecimal(decimal);
1214         } catch (NumberFormatException nfe) {
1215             throw new ClusterJUserException(
1216                     local.message("ERR_Number_Format", decimal, dump(decimal)));
1217         }
1218     }
1219 
getBigInteger(ByteBuffer byteBuffer, int length, int precision, int scale)1220     public static BigInteger getBigInteger(ByteBuffer byteBuffer, int length, int precision, int scale) {
1221         String decimal = null;
1222         try {
1223             decimal = getDecimalString(byteBuffer, length, precision, scale);
1224             return new BigInteger(decimal);
1225         } catch (NumberFormatException nfe) {
1226             throw new ClusterJUserException(
1227                     local.message("ERR_Number_Format", decimal, dump(decimal)));
1228         }
1229     }
1230 
1231     /** Get a Decimal String from the byte buffer.
1232      *
1233      * @param byteBuffer the byte buffer with the raw data, starting at position()
1234      * @param length the length of the data
1235      * @param precision the precision of the data
1236      * @param scale the scale of the data
1237      * @return the Decimal String representation of the value
1238      */
getDecimalString(ByteBuffer byteBuffer, int length, int precision, int scale)1239     public static String getDecimalString(ByteBuffer byteBuffer, int length, int precision, int scale) {
1240         // allow for decimal point and sign and one more for trailing null
1241         int capacity = precision + 3;
1242         ByteBuffer digits = ByteBuffer.allocateDirect(capacity);
1243         int returnCode = Utils.decimal_bin2str(byteBuffer, length, precision, scale, digits, capacity);
1244         if (returnCode != 0) {
1245             throw new ClusterJUserException(
1246                     local.message("ERR_Binary_Decimal_To_String",
1247                     returnCode, precision, scale, dumpBytes(byteBuffer)));
1248         }
1249         String string = null;
1250         // look for the end (null) of the result string
1251         for (int i = 0; i < digits.limit(); ++i) {
1252             if (digits.get(i) == 0) {
1253                 // found the end; mark it so we only decode the answer characters
1254                 digits.limit(i);
1255                 break;
1256             }
1257         }
1258         try {
1259             // use basic decoding
1260             CharBuffer charBuffer = charsetDecoder.decode(digits);
1261             string = charBuffer.toString();
1262             return string;
1263         } catch (CharacterCodingException e) {
1264             throw new ClusterJFatalInternalException(
1265                     local.message("ERR_Character_Encoding", string));
1266         }
1267 
1268     }
1269 
1270     /** Unpack a Date from its packed int representation.
1271      * Date is a three-byte integer packed as YYYYx16x32 + MMx32 + DD
1272      * @param packedDate the packed representation
1273      * @return the long value as milliseconds since the Epoch
1274      */
unpackDate(int packedDate)1275     public static long unpackDate(int packedDate) {
1276         int date = packedDate & 0x1f;
1277         packedDate = packedDate >>> 5;
1278         int month = (packedDate & 0x0f) - 1; // Month value is 0-based. e.g., 0 for January.
1279         int year = packedDate >>> 4;
1280         Calendar calendar = Calendar.getInstance();
1281         calendar.clear();
1282         calendar.set(year, month, date);
1283         return calendar.getTimeInMillis();
1284     }
1285 
1286     /** Unpack a Time from its packed int representation.
1287      * Time is a three-byte integer packed as DDx240000 + HHx10000 + MMx100 + SS
1288      * @param packedTime the packed representation
1289      * @return the long value as milliseconds since the Epoch
1290      */
unpackTime(int packedTime)1291     public static long unpackTime(int packedTime) {
1292         int second = packedTime % 100;
1293         packedTime /= 100;
1294         int minute = packedTime % 100;
1295         packedTime /= 100;
1296         int hour = packedTime % 24;
1297         int date = (packedTime / 24) + 1;
1298         if (date > 31) {
1299             throw new ClusterJUserException(
1300                     local.message("ERR_Read_Time_Domain", packedTime, date, hour, minute, second));
1301         }
1302         Calendar calendar = Calendar.getInstance();
1303         calendar.clear();
1304         calendar.set(Calendar.DATE, date);
1305         calendar.set(Calendar.HOUR, hour);
1306         calendar.set(Calendar.MINUTE, minute);
1307         calendar.set(Calendar.SECOND, second);
1308         calendar.set(Calendar.MILLISECOND, 0);
1309         return calendar.getTimeInMillis();
1310     }
1311 
1312     /** Unpack a Datetime from its packed long representation.
1313      * The Datetime contains a long packed as
1314      * YYYYx10000000000 + MMx100000000 + DDx1000000 + HHx10000 + MMx100 + SS
1315      * Calendar month is 0 origin so subtract 1 from packed month
1316      * @param packedDatetime the packed representation
1317      * @return the value as milliseconds since the Epoch
1318      */
unpackDatetime(long packedDatetime)1319     protected static long unpackDatetime(long packedDatetime) {
1320         int second = (int)(packedDatetime % 100);
1321         packedDatetime /= 100;
1322         int minute = (int)(packedDatetime % 100);
1323         packedDatetime /= 100;
1324         int hour = (int)(packedDatetime % 100);
1325         packedDatetime /= 100;
1326         int day = (int)(packedDatetime % 100);
1327         packedDatetime /= 100;
1328         int month = (int)(packedDatetime % 100) - 1;
1329         int year = (int)(packedDatetime / 100);
1330         Calendar calendar = Calendar.getInstance();
1331         calendar.clear();
1332         calendar.set(Calendar.YEAR, year);
1333         calendar.set(Calendar.MONTH, month);
1334         calendar.set(Calendar.DATE, day);
1335         calendar.set(Calendar.HOUR, hour);
1336         calendar.set(Calendar.MINUTE, minute);
1337         calendar.set(Calendar.SECOND, second);
1338         calendar.set(Calendar.MILLISECOND, 0);
1339         return calendar.getTimeInMillis();
1340 
1341     }
1342 
1343     /** Decode a byte[] into a String using the charset. The return value
1344      * is in UTF16 format.
1345      *
1346      * @param array the byte[] to be decoded
1347      * @param collation the collation
1348      * @return the decoded String
1349      */
decode(byte[] array, int collation)1350     public static String decode(byte[] array, int collation) {
1351         if (array == null) return null;
1352         ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
1353         byteBuffer.put(array);
1354         byteBuffer.flip();
1355         int inputLength = array.length;
1356         // TODO make this more reasonable
1357         int outputLength = inputLength * 4;
1358         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1359         int[] lengths = new int[] {inputLength, outputLength};
1360         int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
1361                 byteBuffer, outputByteBuffer);
1362         switch (returnCode) {
1363             case CharsetMapConst.RecodeStatus.RECODE_OK:
1364                 outputByteBuffer.limit(lengths[1]);
1365                 CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
1366                 return charBuffer.toString();
1367             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1368                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
1369                         collation));
1370             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1371                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
1372                         collation, lengths[0]));
1373             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1374                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
1375                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1376             default:
1377                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
1378                         returnCode));
1379         }
1380     }
1381 
1382     /** Decode a ByteBuffer into a String using the charset. The return value
1383      * is in UTF16 format.
1384      *
1385      * @param inputByteBuffer the byte buffer to be decoded
1386      * @param collation the collation
1387      * @return the decoded String
1388      */
decode(ByteBuffer inputByteBuffer, int collation)1389     protected static String decode(ByteBuffer inputByteBuffer, int collation) {
1390         int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
1391         int outputLength = inputLength * 2;
1392         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1393         int[] lengths = new int[] {inputLength, outputLength};
1394         int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
1395                 inputByteBuffer, outputByteBuffer);
1396         switch (returnCode) {
1397             case CharsetMapConst.RecodeStatus.RECODE_OK:
1398                 outputByteBuffer.limit(lengths[1]);
1399                 CharBuffer charBuffer = outputByteBuffer.asCharBuffer();
1400                 return charBuffer.toString();
1401             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1402                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
1403                         collation));
1404             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1405                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
1406                         collation, lengths[0]));
1407             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1408                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Buffer_Too_Small",
1409                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1410             default:
1411                 throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
1412                         returnCode));
1413         }
1414     }
1415 
1416 
1417     /** Encode a String into a byte[] for storage.
1418      * This is used by character large objects when mapping text columns.
1419      *
1420      * @param string the String to encode
1421      * @param collation the collation
1422      * @return the encoded byte[]
1423      */
encode(String string, int collation)1424     public static byte[] encode(String string, int collation) {
1425         ByteBuffer encoded = encodeToByteBuffer(string, collation, 0);
1426         int length = encoded.limit();
1427         byte[] result = new byte[length];
1428         encoded.get(result);
1429         return result;
1430     }
1431 
1432     /** Encode a String into a ByteBuffer
1433      * using the mysql native encoding method.
1434      * @param string the String to encode
1435      * @param collation the collation
1436      * @param prefixLength the length of the length prefix
1437      * @return the encoded ByteBuffer with position set to prefixLength
1438      * and limit one past the last converted byte
1439      */
encodeToByteBuffer(CharSequence string, int collation, int prefixLength)1440     public static ByteBuffer encodeToByteBuffer(CharSequence string, int collation, int prefixLength) {
1441         if (string == null) return null;
1442         int inputLength = (string.length() * 2);
1443         ByteBuffer inputByteBuffer = ByteBuffer.allocateDirect(inputLength);
1444         CharBuffer charBuffer = inputByteBuffer.asCharBuffer();
1445         charBuffer.append(string);
1446         int outputLength = (2 * inputLength) + prefixLength;
1447         ByteBuffer outputByteBuffer = ByteBuffer.allocateDirect(outputLength);
1448         outputByteBuffer.position(prefixLength);
1449         int[] lengths = new int[] {inputLength, outputLength - prefixLength};
1450         int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
1451                 inputByteBuffer, outputByteBuffer);
1452 
1453         switch (returnCode) {
1454             case CharsetMapConst.RecodeStatus.RECODE_OK:
1455                 outputByteBuffer.limit(prefixLength + lengths[1]);
1456                 return outputByteBuffer;
1457             case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1458                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
1459                         collation));
1460             case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1461                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
1462                         collation, lengths[0]));
1463             case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1464                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Buffer_Too_Small",
1465                         collation, inputLength, outputLength, lengths[0], lengths[1]));
1466             default:
1467                 throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
1468                         returnCode));
1469         }
1470     }
1471 
1472     /** Encode a String into a ByteBuffer for storage.
1473      *
1474      * @param input the input String
1475      * @param storeColumn the store column
1476      * @param bufferManager the buffer manager with shared buffers
1477      * @return a byte buffer with prefix length
1478      */
encode(String input, Column storeColumn, BufferManager bufferManager)1479     public static ByteBuffer encode(String input, Column storeColumn, BufferManager bufferManager) {
1480         int collation = storeColumn.getCharsetNumber();
1481         CharsetConverter charsetConverter = getCharsetConverter(collation);
1482         CharSequence chars = input;
1483         int prefixLength = storeColumn.getPrefixLength();
1484         if (prefixLength == 0) {
1485             chars = padString(input, storeColumn);
1486         }
1487         return charsetConverter.encode(storeColumn.getName(), chars, collation, prefixLength, bufferManager);
1488     }
1489 
1490     /** Decode a ByteBuffer into a String using the charset. The return value
1491      * is in UTF16 format.
1492      *
1493      * @param inputByteBuffer the byte buffer to be decoded positioned past the length prefix
1494      * @param collation the collation
1495      * @param bufferManager the buffer manager with shared buffers
1496      * @return the decoded String
1497      */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)1498     public static String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
1499         CharsetConverter charsetConverter = getCharsetConverter(collation);
1500         return charsetConverter.decode(inputByteBuffer, collation, bufferManager);
1501     }
1502 
1503     /** Get the charset converter for the given collation.
1504      * This is in the inner loop and must be highly optimized for performance.
1505      * @param collation the collation
1506      * @return the charset converter for the collation
1507      */
getCharsetConverter(int collation)1508     private static CharsetConverter getCharsetConverter(int collation) {
1509         // must be synchronized because the charsetConverters is not synchronized
1510         // we avoid a race condition where a charset converter is in the process
1511         // of being created and it's partially visible by another thread in charsetConverters
1512         synchronized (charsetConverters) {
1513             if (collation + 1 > charsetConverters.length) {
1514                 // unlikely; only if collations are added beyond existing collation number
1515                 String charsetName = charsetMap.getName(collation);
1516                 logger.warn(local.message("ERR_Charset_Number_Too_Big", collation, charsetName,
1517                         MAXIMUM_MYSQL_COLLATION_NUMBER));
1518                 return charsetConverterMultibyte;
1519             }
1520             CharsetConverter result = charsetConverters[collation];
1521             if (result == null) {
1522                 result = addCollation(collation);
1523             }
1524             return result;
1525         }
1526     }
1527 
1528     /** Create a new charset converter and add it to the collection of charset converters
1529      * for all collations that share the same charset.
1530      *
1531      * @param collation the collation to add
1532      * @return the charset converter for the collation
1533      */
addCollation(int collation)1534     private static CharsetConverter addCollation(int collation) {
1535         if (isMultibyteCollation(collation)) {
1536             return charsetConverters[collation] = charsetConverterMultibyte;
1537         }
1538         String charsetName = charsetMap.getMysqlName(collation);
1539         CharsetConverter charsetConverter = new SingleByteCharsetConverter(collation);
1540         int[] collations = collationPeersMap.get(charsetName);
1541         if (collations == null) {
1542             // unlikely; only if a new collation is added
1543             collations = new int[] {collation};
1544             collationPeersMap.put(charsetName, collations);
1545             logger.warn(local.message("WARN_Unknown_Collation", collation, charsetName));
1546             return charsetConverter;
1547         }
1548         for (int peer: collations) {
1549             // for each collation that shares the same charset name, set the charset converter
1550             logger.info("Adding charset converter " + charsetName + " for collation " + peer);
1551             charsetConverters[peer] = charsetConverter;
1552         }
1553         return charsetConverter;
1554     }
1555 
1556     /** Is the collation multibyte?
1557      *
1558      * @param collation the collation number
1559      * @return true if the collation uses a multibyte charset; false if the collation uses a single byte charset;
1560      * and null if the collation is not a valid collation
1561      */
isMultibyteCollation(int collation)1562     private static Boolean isMultibyteCollation(int collation) {
1563         boolean[] multibyte = charsetMap.isMultibyte(collation);
1564         return (multibyte == null)?null:multibyte[0];
1565     }
1566 
1567     /** Utility methods for encoding and decoding Strings.
1568      */
1569     protected interface CharsetConverter {
1570 
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)1571         ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager);
1572 
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)1573         String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager);
1574     }
1575 
1576     /** Class for encoding and decoding multibyte charsets. A single instance of this class
1577      * can be shared among all multibyte charsets.
1578      */
1579     protected static class MultiByteCharsetConverter implements CharsetConverter {
1580 
1581         /** Encode a String into a ByteBuffer. The input String is copied into a shared byte buffer.
1582          * The buffer is encoded via the mysql recode method to a shared String storage buffer.
1583          * If the output buffer is too small, a new buffer is allocated and the encoding is repeated.
1584          * @param input the input String
1585          * @param collation the charset number
1586          * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
1587          * @param bufferManager the buffer manager with shared buffers
1588          * @return a byte buffer positioned at zero with the data ready to send to the database
1589          */
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)1590         public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
1591             // input length in bytes is twice String length
1592             int inputLength = input.length() * 2;
1593             ByteBuffer inputByteBuffer = bufferManager.copyStringToByteBuffer(input);
1594             boolean done = false;
1595             // first try with output length equal input length
1596             int sizeNeeded = inputLength;
1597             while (!done) {
1598                 ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(sizeNeeded);
1599                 int outputLength = outputByteBuffer.limit();
1600                 outputByteBuffer.position(prefixLength);
1601                 int[] lengths = new int[] {inputLength, outputLength - prefixLength};
1602                 int returnCode = charsetMap.recode(lengths, collationUTF16, collation,
1603                         inputByteBuffer, outputByteBuffer);
1604                 switch (returnCode) {
1605                     case CharsetMapConst.RecodeStatus.RECODE_OK:
1606                         outputByteBuffer.limit(prefixLength + lengths[1]);
1607                         outputByteBuffer.position(0);
1608                         fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
1609                         return outputByteBuffer;
1610                     case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1611                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Charset",
1612                                 collation));
1613                     case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1614                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Source",
1615                                 collation, lengths[0]));
1616                     case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1617                         // loop increasing output buffer size until success or run out of memory...
1618                         sizeNeeded = sizeNeeded * 3 / 2;
1619                         break;
1620                     default:
1621                         throw new ClusterJFatalInternalException(local.message("ERR_Encode_Bad_Return_Code",
1622                                 returnCode));
1623                 }
1624             }
1625             return null; // to make compiler happy; we never get here
1626         }
1627 
1628         /** Decode a byte buffer into a String. The input is decoded by the mysql charset recode method
1629          * into a shared buffer. Then the shared buffer is used to create the result String.
1630          * The input byte buffer is positioned just past the length, and its limit is set to one past the
1631          * characters to decode.
1632          * @param inputByteBuffer the input byte buffer
1633          * @param collation the charset number
1634          * @param bufferManager the buffer manager with shared buffers
1635          * @return the decoded String
1636          */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)1637         public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
1638             int inputLength = inputByteBuffer.limit() - inputByteBuffer.position();
1639             int sizeNeeded = inputLength * 4;
1640             boolean done = false;
1641             while (!done) {
1642                 ByteBuffer outputByteBuffer = bufferManager.getStringByteBuffer(sizeNeeded);
1643                 CharBuffer outputCharBuffer = bufferManager.getStringCharBuffer();
1644                 int outputLength = outputByteBuffer.capacity();
1645                 outputByteBuffer.position(0);
1646                 outputByteBuffer.limit(outputLength);
1647                 int[] lengths = new int[] {inputLength, outputLength};
1648                 int returnCode = charsetMap.recode(lengths, collation, collationUTF16,
1649                         inputByteBuffer, outputByteBuffer);
1650                 switch (returnCode) {
1651                     case CharsetMapConst.RecodeStatus.RECODE_OK:
1652                         outputCharBuffer.position(0);
1653                         // each output character is two bytes for UTF16
1654                         outputCharBuffer.limit(lengths[1] / 2);
1655                         return outputCharBuffer.toString();
1656                     case CharsetMapConst.RecodeStatus.RECODE_BAD_CHARSET:
1657                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Charset",
1658                                 collation));
1659                     case CharsetMapConst.RecodeStatus.RECODE_BAD_SRC:
1660                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Source",
1661                                 collation, lengths[0]));
1662                     case CharsetMapConst.RecodeStatus.RECODE_BUFF_TOO_SMALL:
1663                         // try a bigger buffer
1664                         sizeNeeded = sizeNeeded * 3 / 2;
1665                         break;
1666                     default:
1667                         throw new ClusterJFatalInternalException(local.message("ERR_Decode_Bad_Return_Code",
1668                                 returnCode));
1669                 }
1670             }
1671             return null; // never reached; make the compiler happy
1672         }
1673     }
1674 
1675     /** Class for encoding and decoding single byte collations.
1676      */
1677     protected static class SingleByteCharsetConverter implements CharsetConverter {
1678 
1679         private static final int BYTE_RANGE = (1 + Byte.MAX_VALUE) - Byte.MIN_VALUE;
1680         private static byte[] allBytes = new byte[BYTE_RANGE];
1681         // The initial charToByteMap, with all char mappings mapped
1682         // to (byte) '?', so that unknown characters are mapped to '?'
1683         // instead of '\0' (which means end-of-string to MySQL).
1684         private static byte[] unknownCharsMap = new byte[65536];
1685 
1686         static {
1687             // initialize allBytes with all possible byte values
1688             for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
1689                 allBytes[i - Byte.MIN_VALUE] = (byte) i;
1690             }
1691             // initialize unknownCharsMap to '?' in each position
1692             for (int i = 0; i < unknownCharsMap.length; i++) {
1693                 unknownCharsMap[i] = (byte) '?'; // use something 'sane' for unknown chars
1694             }
1695         }
1696 
1697         /** The byte to char array */
1698         private char[] byteToChars = new char[BYTE_RANGE];
1699 
1700         /** The char to byte array */
1701         private byte[] charToBytes = new byte[65536];
1702 
1703         /** Construct a new single byte charset converter. This converter is only used for
1704          * charsets that encode to a single byte for any input character.
1705          * @param collation
1706          */
SingleByteCharsetConverter(int collation)1707         public SingleByteCharsetConverter(int collation) {
1708             ByteBuffer allBytesByteBuffer = ByteBuffer.allocateDirect(256);
1709             allBytesByteBuffer.put(allBytes);
1710             allBytesByteBuffer.flip();
1711             String allBytesString = Utility.decode(allBytesByteBuffer, collation);
1712             if (allBytesString.length() != 256) {
1713                 String charsetName = charsetMap.getName(collation);
1714                 throw new ClusterJFatalInternalException(local.message("ERR_Bad_Charset_Decode_All_Chars",
1715                         collation, charsetName, allBytesString.length()));
1716             }
1717             int allBytesLen = allBytesString.length();
1718 
1719             System.arraycopy(unknownCharsMap, 0, this.charToBytes, 0,
1720                     this.charToBytes.length);
1721 
1722             for (int i = 0; i < BYTE_RANGE && i < allBytesLen; i++) {
1723                 char c = allBytesString.charAt(i);
1724                 this.byteToChars[i] = c;
1725                 this.charToBytes[c] = allBytes[i];
1726             }
1727         }
1728 
1729         /** Encode a String into a ByteBuffer. The input String is encoded, character by character,
1730          * into an output byte[]. Then the output is copied into a shared byte buffer.
1731          * @param input the input String
1732          * @param collation the charset number
1733          * @param prefixLength the prefix length (0, 1, or 2 depending on the type)
1734          * @param bufferManager the buffer manager with shared buffers
1735          * @return a byte buffer positioned at zero with the data ready to send to the database
1736          */
encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager)1737         public ByteBuffer encode(String columnName, CharSequence input, int collation, int prefixLength, BufferManager bufferManager) {
1738             int length = input.length();
1739             byte[] outputBytes = new byte[length];
1740             for (int position = 0; position < length; ++position) {
1741                 outputBytes[position] = charToBytes[input.charAt(position)];
1742             }
1743             // input is now encoded; copy to shared output buffer
1744             ByteBuffer outputByteBuffer = bufferManager.getStringStorageBuffer(length + prefixLength);
1745             // skip over prefix
1746             outputByteBuffer.position(prefixLength);
1747             outputByteBuffer.put(outputBytes);
1748             outputByteBuffer.flip();
1749             // adjust the length prefix
1750             fixBufferPrefixLength(columnName, outputByteBuffer, prefixLength);
1751             return outputByteBuffer;
1752         }
1753 
1754         /** Decode a byte buffer into a String. The input byte buffer is copied into a byte[],
1755          * then encoded byte by byte into an output char[]. Then the result String is created from the char[].
1756          * The input byte buffer is positioned just past the length, and its limit is set to one past the
1757          * characters to decode.
1758          * @param inputByteBuffer the input byte buffer
1759          * @param collation the charset number
1760          * @param bufferManager the buffer manager with shared buffers
1761          * @return the decoded String
1762          */
decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager)1763         public String decode(ByteBuffer inputByteBuffer, int collation, BufferManager bufferManager) {
1764             int inputLimit = inputByteBuffer.limit();
1765             int inputPosition = inputByteBuffer.position();
1766             int inputSize = inputLimit- inputPosition;
1767             byte[] inputBytes = new byte[inputSize];
1768             inputByteBuffer.get(inputBytes);
1769             char[] outputChars = new char[inputSize];
1770             for (int position = 0; position < inputSize; ++position) {
1771                 outputChars[position] = byteToChars[inputBytes[position] - Byte.MIN_VALUE];
1772             }
1773             // input is now decoded; create a new String from the output
1774             String result = new String(outputChars);
1775             return result;
1776         }
1777     }
1778 
dump(String string)1779     private static String dump(String string) {
1780         StringBuffer buffer = new StringBuffer("[");
1781         for (int i = 0; i < string.length(); ++i) {
1782             int theCharacter = string.charAt(i);
1783             buffer.append(theCharacter);
1784             buffer.append(" ");
1785         }
1786         buffer.append("]");
1787         return buffer.toString();
1788     }
1789 
1790     /** For each group of 9 decimal digits, the number of bytes needed
1791      * to represent that group of digits:
1792      * 10, 100 -> 1; 256
1793      * 1,000, 10,000 -> 2; 65536
1794      * 100,000, 1,000,000 -> 3 16,777,216
1795      * 10,000,000, 100,000,000, 1,000,000,000 -> 4
1796      */
1797     static int[] howManyBytesNeeded = new int[] {0,  1,  1,  2,  2,  3,  3,  4,  4,  4,
1798                                                      5,  5,  6,  6,  7,  7,  8,  8,  8,
1799                                                      9,  9, 10, 10, 11, 11, 12, 12, 12,
1800                                                     13, 13, 14, 14, 15, 15, 16, 16, 16,
1801                                                     17, 17, 18, 18, 19, 19, 20, 20, 20,
1802                                                     21, 21, 22, 22, 23, 23, 24, 24, 24,
1803                                                     25, 25, 26, 26, 27, 27, 28, 28, 28,
1804                                                     29, 29};
1805     /** Get the number of bytes needed in memory to represent the decimal number.
1806      *
1807      * @param precision the precision of the number
1808      * @param scale the scale
1809      * @return the number of bytes needed for the binary representation of the number
1810      */
getDecimalColumnSpace(int precision, int scale)1811     public static int getDecimalColumnSpace(int precision, int scale) {
1812         int howManyBytesNeededForIntegral = howManyBytesNeeded[precision - scale];
1813         int howManyBytesNeededForFraction = howManyBytesNeeded[scale];
1814         int result = howManyBytesNeededForIntegral + howManyBytesNeededForFraction;
1815         return result;
1816     }
1817 
1818     /** Get a boolean from this ndbRecAttr.
1819      *
1820      * @param storeColumn the Column
1821      * @param ndbRecAttr the NdbRecAttr
1822      * @return the boolean
1823      */
getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr)1824     public static boolean getBoolean(Column storeColumn, NdbRecAttr ndbRecAttr) {
1825         return endianManager.getBoolean(storeColumn, ndbRecAttr);
1826     }
1827 
1828     /** Get a byte from this ndbRecAttr.
1829      *
1830      * @param storeColumn the Column
1831      * @param ndbRecAttr the NdbRecAttr
1832      * @return the byte
1833      */
getByte(Column storeColumn, NdbRecAttr ndbRecAttr)1834     public static byte getByte(Column storeColumn, NdbRecAttr ndbRecAttr) {
1835         return endianManager.getByte(storeColumn, ndbRecAttr);
1836     }
1837 
1838     /** Get a short from this ndbRecAttr.
1839      *
1840      * @param storeColumn the Column
1841      * @param ndbRecAttr the NdbRecAttr
1842      * @return the short
1843      */
getShort(Column storeColumn, NdbRecAttr ndbRecAttr)1844     public static short getShort(Column storeColumn, NdbRecAttr ndbRecAttr) {
1845         return endianManager.getShort(storeColumn, ndbRecAttr);
1846     }
1847 
1848     /** Get an int from this ndbRecAttr.
1849      *
1850      * @param storeColumn the Column
1851      * @param ndbRecAttr the NdbRecAttr
1852      * @return the int
1853      */
getInt(Column storeColumn, NdbRecAttr ndbRecAttr)1854     public static int getInt(Column storeColumn, NdbRecAttr ndbRecAttr) {
1855         return endianManager.getInt(storeColumn, ndbRecAttr);
1856     }
1857 
1858     /** Get a long from this ndbRecAttr.
1859      *
1860      * @param storeColumn the Column
1861      * @param ndbRecAttr the NdbRecAttr
1862      * @return the long
1863      */
getLong(Column storeColumn, NdbRecAttr ndbRecAttr)1864     public static long getLong(Column storeColumn, NdbRecAttr ndbRecAttr) {
1865         return endianManager.getLong(storeColumn, ndbRecAttr);
1866     }
1867 
1868     /** Convert a long value into a long for storage. The value parameter
1869      * may be a date (milliseconds since the epoch), a bit array, or simply a long value.
1870      * The storage format depends on the type of the column and the endian-ness of
1871      * the host.
1872      * @param storeColumn the column
1873      * @param value the java value
1874      * @return the storage value
1875      */
convertLongValueForStorage(Column storeColumn, long value)1876     public static long convertLongValueForStorage(Column storeColumn, long value) {
1877         return endianManager.convertLongValueForStorage(storeColumn, value);
1878     }
1879 
1880     /** Convert a byte value into an int for storage. The value parameter
1881      * may be a bit, a bit array (BIT(1..8) needs to be stored as an int) or a byte value.
1882      * The storage format depends on the type of the column and the endian-ness of
1883      * the host.
1884      * @param storeColumn the column
1885      * @param value the java value
1886      * @return the storage value
1887      */
convertByteValueForStorage(Column storeColumn, byte value)1888     public static int convertByteValueForStorage(Column storeColumn, byte value) {
1889         return endianManager.convertByteValueForStorage(storeColumn, value);
1890     }
1891 
1892     /** Convert a short value into an int for storage. The value parameter
1893      * may be a bit array (BIT(1..16) needs to be stored as an int) or a short value.
1894      * The storage format depends on the type of the column and the endian-ness of
1895      * the host.
1896      * @param storeColumn the column
1897      * @param value the java value
1898      * @return the storage value
1899      */
convertShortValueForStorage(Column storeColumn, short value)1900     public static int convertShortValueForStorage(Column storeColumn,
1901             short value) {
1902         return endianManager.convertShortValueForStorage(storeColumn, value);
1903     }
1904 
1905 }
1906