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