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